Merge with master
This commit is contained in:
commit
04427cabf5
24 changed files with 858 additions and 104 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,6 +9,8 @@
|
||||||
**/*.exe
|
**/*.exe
|
||||||
**/*~
|
**/*~
|
||||||
**/*.orig
|
**/*.orig
|
||||||
|
**/*.out
|
||||||
|
**/*.test
|
||||||
core
|
core
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
@ -17,3 +19,4 @@ _test*
|
||||||
|
|
||||||
**/*.dll
|
**/*.dll
|
||||||
**/core*[0-9]
|
**/core*[0-9]
|
||||||
|
.private
|
||||||
|
|
88
README.md
88
README.md
|
@ -1,29 +1,93 @@
|
||||||
draw2d
|
draw2d
|
||||||
======
|
======
|
||||||
|
|
||||||
This package (written in [go](http://golang.org)) provides an API to draw 2d vector forms on [images](http://golang.org/pkg/image).
|
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dopengl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative.
|
||||||
This library is inspired by [postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/).
|
|
||||||
|
|
||||||
The package depends on [freetype-go](http://code.google.com/p/freetype-go) package for its rasterization algorithm.
|
See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
Using
|
Operations in draw2d include stroking and filling polygons, arcs, Bézier curves, drawing images and text rendering with truetype fonts. All drawing operations can be transformed by affine transformations (scale, rotation, translation).
|
||||||
-----
|
|
||||||
|
|
||||||
Install [golang](http://golang.org/doc/install) and get `draw2d`
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
go get github.com/llgcode/draw2d
|
go get -u github.com/llgcode/draw2d
|
||||||
```
|
```
|
||||||
|
|
||||||
and start coding using one of the [Samples](https://github.com/llgcode/draw2d.samples).
|
Quick Start
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Initialize the graphic context on an RGBA image
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
|
gc := draw2d.NewGraphicContext(dest)
|
||||||
|
|
||||||
|
// Set some properties
|
||||||
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
|
gc.SetLineWidth(5)
|
||||||
|
|
||||||
|
// Draw a closed shape
|
||||||
|
gc.MoveTo(10, 10) // should always be called first for a new path
|
||||||
|
gc.LineTo(100, 50)
|
||||||
|
gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
|
gc.Close()
|
||||||
|
gc.FillStroke()
|
||||||
|
|
||||||
|
// Save to file
|
||||||
|
draw2d.SaveToPngFile(fn, dest)
|
||||||
|
```
|
||||||
|
|
||||||
|
The same Go code can also generate a pdf document with package draw2dpdf:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Initialize the graphic context on an RGBA image
|
||||||
|
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
|
gc := draw2d.NewGraphicContext(dest)
|
||||||
|
|
||||||
|
// Set some properties
|
||||||
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
|
gc.SetLineWidth(5)
|
||||||
|
|
||||||
|
// Draw a closed shape
|
||||||
|
gc.MoveTo(10, 10) // should always be called first for a new path
|
||||||
|
gc.LineTo(100, 50)
|
||||||
|
gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
|
gc.Close()
|
||||||
|
gc.FillStroke()
|
||||||
|
|
||||||
|
// Save to file
|
||||||
|
draw2dpdf.SaveToPdfFile(fn, dest)
|
||||||
|
```
|
||||||
|
|
||||||
|
There are more examples here: https://github.com/llgcode/draw2d.samples
|
||||||
|
|
||||||
|
Drawing on opengl is provided by the draw2dgl package.
|
||||||
|
|
||||||
|
Acknowledgments
|
||||||
|
---------------
|
||||||
|
|
||||||
|
[Laurent Le Goff](https://github.com/llgcode) wrote this library, inspired by [Postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/). He implemented the image and opengl backend with the [freetype-go](https://code.google.com/p/freetype-go/) package. Also he created a pure go [Postscript interpreter](https://github.com/llgcode/ps), which can read postscript images and draw to a draw2d graphic context. [Stani Michiels](https://github.com/stanim) implemented the pdf backend with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
|
||||||
|
|
||||||
|
|
||||||
Softwares and Packages using draw2d
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
- [golang postscript interpreter](https://github.com/llgcode/ps)
|
Packages using draw2d
|
||||||
- [gonum plot](https://github.com/gonum/plot)
|
---------------------
|
||||||
|
|
||||||
|
- [ps](https://github.com/llgcode/ps): Postscript interpreter written in Go
|
||||||
|
- [gonum/plot](https://github.com/gonum/plot): drawing plots in Go
|
||||||
|
- [go.uik](https://github.com/skelterjohn/go.uik): a concurrent UI kit written in pure go.
|
||||||
|
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
|
||||||
|
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
|
||||||
|
- [chart](https://github.com/vdobler/chart): basic charts in Go
|
||||||
|
|
||||||
References
|
References
|
||||||
---------
|
---------
|
||||||
|
|
78
draw2d.go
78
draw2d.go
|
@ -1,7 +1,77 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 13/12/2010 by Laurent Le Goff
|
// created: 13/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
// Package draw2d provides a Graphic Context that can draw vector form on canvas.
|
// Package draw2d is a pure go 2D vector graphics library with support
|
||||||
|
// for multiple output devices such as images (draw2d), pdf documents
|
||||||
|
// (draw2dpdf) and opengl (draw2dopengl), which can also be used on the
|
||||||
|
// google app engine. It can be used as a pure go Cairo alternative.
|
||||||
|
//
|
||||||
|
// Features
|
||||||
|
//
|
||||||
|
// Operations in draw2d include stroking and filling polygons, arcs,
|
||||||
|
// Bézier curves, drawing images and text rendering with truetype fonts.
|
||||||
|
// All drawing operations can be transformed by affine transformations
|
||||||
|
// (scale, rotation, translation).
|
||||||
|
//
|
||||||
|
// Installation
|
||||||
|
//
|
||||||
|
// To install or update the package draw2d on your system, run:
|
||||||
|
// go get -u github.com/llgcode/draw2d
|
||||||
|
//
|
||||||
|
// Quick Start
|
||||||
|
//
|
||||||
|
// Package draw2d itself provides a graphic context that can draw vector
|
||||||
|
// graphics and text on an image canvas. The following Go code
|
||||||
|
// generates a simple drawing and saves it to an image file:
|
||||||
|
// // Initialize the graphic context on an RGBA image
|
||||||
|
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
|
// gc := draw2d.NewGraphicContext(dest)
|
||||||
|
//
|
||||||
|
// // Set some properties
|
||||||
|
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
|
// gc.SetLineWidth(5)
|
||||||
|
//
|
||||||
|
// // Draw a closed shape
|
||||||
|
// gc.MoveTo(10, 10) // should always be called first for a new path
|
||||||
|
// gc.LineTo(100, 50)
|
||||||
|
// gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
|
// gc.Close()
|
||||||
|
// gc.FillStroke()
|
||||||
|
//
|
||||||
|
// // Save to file
|
||||||
|
// draw2d.SaveToPngFile(fn, dest)
|
||||||
|
//
|
||||||
|
// There are more examples here:
|
||||||
|
// https://github.com/llgcode/draw2d.samples
|
||||||
|
//
|
||||||
|
// Drawing on pdf documents is provided by the draw2dpdf package.
|
||||||
|
// Drawing on opengl is provided by the draw2dgl package.
|
||||||
|
// See subdirectories at the bottom of this page.
|
||||||
|
//
|
||||||
|
// Acknowledgments
|
||||||
|
//
|
||||||
|
// Laurent Le Goff wrote this library, inspired by Postscript and
|
||||||
|
// HTML5 canvas. He implemented the image and opengl backend with the
|
||||||
|
// freetype-go package. Also he created a pure go Postscript
|
||||||
|
// interpreter, which can read postscript images and draw to a draw2d
|
||||||
|
// graphic context (https://github.com/llgcode/ps). Stani Michiels
|
||||||
|
// implemented the pdf backend with the gofpdf package.
|
||||||
|
//
|
||||||
|
// The package depends on freetype-go package for its rasterization
|
||||||
|
// algorithm.
|
||||||
|
//
|
||||||
|
// Packages using draw2d
|
||||||
|
//
|
||||||
|
// - https://github.com/llgcode/ps: Postscript interpreter written in Go
|
||||||
|
//
|
||||||
|
// - https://github.com/gonum/plot: drawing plots in Go
|
||||||
|
//
|
||||||
|
// - https://github.com/muesli/smartcrop: content aware image cropping
|
||||||
|
//
|
||||||
|
// - https://github.com/peterhellberg/karta: drawing Voronoi diagrams
|
||||||
|
//
|
||||||
|
// - https://github.com/vdobler/chart: basic charts in Go
|
||||||
package draw2d
|
package draw2d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,7 +79,7 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FillRule defines the fill rule used when fill
|
// FillRule defines the type for fill rules
|
||||||
type FillRule int
|
type FillRule int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -81,7 +151,7 @@ type SolidFillStyle struct {
|
||||||
FillRule FillRule
|
FillRule FillRule
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertical Alignment of the text
|
// Valign Vertical Alignment of the text
|
||||||
type Valign int
|
type Valign int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -91,7 +161,7 @@ const (
|
||||||
ValignTopBaseline
|
ValignTopBaseline
|
||||||
)
|
)
|
||||||
|
|
||||||
// Horizontal Alignment of the text
|
// Halign Horizontal Alignment of the text
|
||||||
type Halign int
|
type Halign int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -8,13 +8,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
|
||||||
CurveRecursionLimit = 32
|
CurveRecursionLimit = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cubic
|
// Cubic
|
||||||
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
|
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
|
||||||
|
|
||||||
// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
// SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
||||||
// c1 and c2 parameters are the resulting curves
|
// c1 and c2 parameters are the resulting curves
|
||||||
func SubdivideCubic(c, c1, c2 []float64) {
|
func SubdivideCubic(c, c1, c2 []float64) {
|
||||||
// First point of c is the first point of c1
|
// First point of c is the first point of c1
|
||||||
|
@ -47,7 +48,7 @@ func SubdivideCubic(c, c1, c2 []float64) {
|
||||||
|
|
||||||
// TraceCubic generate lines subdividing the cubic curve using a Flattener
|
// TraceCubic generate lines subdividing the cubic curve using a Flattener
|
||||||
// flattening_threshold helps determines the flattening expectation of the curve
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
|
func TraceCubic(t Flattener, cubic []float64, flatteningThreshold float64) {
|
||||||
// Allocation curves
|
// Allocation curves
|
||||||
var curves [CurveRecursionLimit * 8]float64
|
var curves [CurveRecursionLimit * 8]float64
|
||||||
copy(curves[0:8], cubic[0:8])
|
copy(curves[0:8], cubic[0:8])
|
||||||
|
@ -67,7 +68,7 @@ func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
|
||||||
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
||||||
|
|
||||||
// if it's flat then trace a line
|
// if it's flat then trace a line
|
||||||
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
if (d2+d3)*(d2+d3) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
t.LineTo(c[6], c[7])
|
t.LineTo(c[6], c[7])
|
||||||
i--
|
i--
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,7 +82,7 @@ func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
|
||||||
// Quad
|
// Quad
|
||||||
// x1, y1, cpx1, cpy2, x2, y2 float64
|
// x1, y1, cpx1, cpy2, x2, y2 float64
|
||||||
|
|
||||||
// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves.
|
// SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
|
||||||
// c1 and c2 parameters are the resulting curves
|
// c1 and c2 parameters are the resulting curves
|
||||||
func SubdivideQuad(c, c1, c2 []float64) {
|
func SubdivideQuad(c, c1, c2 []float64) {
|
||||||
// First point of c is the first point of c1
|
// First point of c is the first point of c1
|
||||||
|
@ -100,9 +101,9 @@ func SubdivideQuad(c, c1, c2 []float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace generate lines subdividing the curve using a Flattener
|
// TraceQuad generate lines subdividing the curve using a Flattener
|
||||||
// flattening_threshold helps determines the flattening expectation of the curve
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
|
func TraceQuad(t Flattener, quad []float64, flatteningThreshold float64) {
|
||||||
// Allocates curves stack
|
// Allocates curves stack
|
||||||
var curves [CurveRecursionLimit * 6]float64
|
var curves [CurveRecursionLimit * 6]float64
|
||||||
copy(curves[0:6], quad[0:6])
|
copy(curves[0:6], quad[0:6])
|
||||||
|
@ -119,7 +120,7 @@ func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
|
||||||
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
||||||
|
|
||||||
// if it's flat then trace a line
|
// if it's flat then trace a line
|
||||||
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
if (d*d) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
t.LineTo(c[4], c[5])
|
t.LineTo(c[4], c[5])
|
||||||
i--
|
i--
|
||||||
} else {
|
} else {
|
||||||
|
@ -157,5 +158,4 @@ func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, la
|
||||||
angle += da
|
angle += da
|
||||||
t.LineTo(curX, curY)
|
t.LineTo(curX, curY)
|
||||||
}
|
}
|
||||||
return curX, curY
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"code.google.com/p/freetype-go/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ type ContextStack struct {
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||||
// 26.6 fixed point units in 1 em.
|
// 26.6 fixed point units in 1 em.
|
||||||
Scale int32
|
Scale float64
|
||||||
|
|
||||||
Previous *ContextStack
|
Previous *ContextStack
|
||||||
}
|
}
|
||||||
|
|
3
draw2dgl/doc.go
Normal file
3
draw2dgl/doc.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Package draw2dgl provides a graphic context that can draw vector
|
||||||
|
// graphics and text on OpenGL.
|
||||||
|
package draw2dgl
|
|
@ -1,12 +1,13 @@
|
||||||
package draw2dimg
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/raster"
|
"code.google.com/p/freetype-go/freetype/raster"
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"code.google.com/p/freetype-go/freetype/truetype"
|
||||||
"github.com/llgcode/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"github.com/llgcode/draw2d/draw2dbase"
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Drawer struct {
|
type Drawer struct {
|
||||||
|
@ -37,7 +38,7 @@ func (d *Drawer) Matrix() *draw2d.Matrix {
|
||||||
func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) {
|
func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) {
|
||||||
switch fillStyle := style.(type) {
|
switch fillStyle := style.(type) {
|
||||||
case draw2d.SolidFillStyle:
|
case draw2d.SolidFillStyle:
|
||||||
d.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(fillStyle.FillRule)
|
d.fillRasterizer.UseNonZeroWinding = fillStyle.FillRule == draw2d.FillRuleWinding
|
||||||
d.painter.SetColor(fillStyle.Color)
|
d.painter.SetColor(fillStyle.Color)
|
||||||
default:
|
default:
|
||||||
panic("FillStyle not supported")
|
panic("FillStyle not supported")
|
||||||
|
|
|
@ -5,23 +5,26 @@ package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
"github.com/llgcode/draw2d/draw2dbase"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
|
|
||||||
"code.google.com/p/freetype-go/freetype/raster"
|
"code.google.com/p/freetype-go/freetype/raster"
|
||||||
"code.google.com/p/freetype-go/freetype/truetype"
|
"code.google.com/p/freetype-go/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
|
||||||
type Painter interface {
|
type Painter interface {
|
||||||
raster.Painter
|
raster.Painter
|
||||||
SetColor(color color.Color)
|
SetColor(color color.Color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphicContext is the implementation of draw2d.GraphicContext for a raster image
|
||||||
type GraphicContext struct {
|
type GraphicContext struct {
|
||||||
*draw2dbase.StackGraphicContext
|
*draw2dbase.StackGraphicContext
|
||||||
img draw.Image
|
img draw.Image
|
||||||
|
@ -32,10 +35,9 @@ type GraphicContext struct {
|
||||||
DPI int
|
DPI int
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// NewGraphicContext creates a new Graphic context from an image.
|
||||||
* Create a new Graphic context from an image
|
|
||||||
*/
|
|
||||||
func NewGraphicContext(img draw.Image) *GraphicContext {
|
func NewGraphicContext(img draw.Image) *GraphicContext {
|
||||||
|
|
||||||
var painter Painter
|
var painter Painter
|
||||||
switch selectImage := img.(type) {
|
switch selectImage := img.(type) {
|
||||||
case *image.RGBA:
|
case *image.RGBA:
|
||||||
|
@ -46,7 +48,7 @@ func NewGraphicContext(img draw.Image) *GraphicContext {
|
||||||
return NewGraphicContextWithPainter(img, painter)
|
return NewGraphicContextWithPainter(img, painter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Graphic context from an image and a Painter (see Freetype-go)
|
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
|
||||||
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
|
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
|
||||||
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
width, height := img.Bounds().Dx(), img.Bounds().Dy()
|
||||||
dpi := 92
|
dpi := 92
|
||||||
|
@ -62,38 +64,46 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicConte
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDPI returns the resolution of the Image GraphicContext
|
||||||
func (gc *GraphicContext) GetDPI() int {
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
return gc.DPI
|
return gc.DPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear fills the current canvas with a default transparent color
|
||||||
func (gc *GraphicContext) Clear() {
|
func (gc *GraphicContext) Clear() {
|
||||||
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
|
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
|
||||||
gc.ClearRect(0, 0, width, height)
|
gc.ClearRect(0, 0, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearRect fills the current canvas with a default transparent color at the specified rectangle
|
||||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
imageColor := image.NewUniform(gc.Current.FillColor)
|
imageColor := image.NewUniform(gc.Current.FillColor)
|
||||||
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
|
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawImage draws the raster image in the current canvas
|
||||||
func (gc *GraphicContext) DrawImage(img image.Image) {
|
func (gc *GraphicContext) DrawImage(img image.Image) {
|
||||||
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
|
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FillString draws the text at point (0, 0)
|
||||||
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||||
return gc.FillStringAt(text, 0, 0)
|
return gc.FillStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||||
width := gc.CreateStringPath(text, x, y)
|
width := gc.CreateStringPath(text, x, y)
|
||||||
gc.Fill()
|
gc.Fill()
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrokeString draws the contour of the text at point (0, 0)
|
||||||
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||||
width := gc.CreateStringPath(text, x, y)
|
width := gc.CreateStringPath(text, x, y)
|
||||||
gc.Stroke()
|
gc.Stroke()
|
||||||
|
@ -118,7 +128,7 @@ func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
// going downwards.
|
// going downwards.
|
||||||
|
|
||||||
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, glyph, truetype.NoHinting); err != nil {
|
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e0 := 0
|
e0 := 0
|
||||||
|
@ -146,14 +156,14 @@ func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
||||||
for _, rune := range s {
|
for _, rune := range s {
|
||||||
index := font.Index(rune)
|
index := font.Index(rune)
|
||||||
if hasPrev {
|
if hasPrev {
|
||||||
x += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
|
x += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
|
||||||
}
|
}
|
||||||
err := gc.drawGlyph(index, x, y)
|
err := gc.drawGlyph(index, x, y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return startx - x
|
return startx - x
|
||||||
}
|
}
|
||||||
x += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
|
x += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
||||||
prev, hasPrev = index, true
|
prev, hasPrev = index, true
|
||||||
}
|
}
|
||||||
return x - startx
|
return x - startx
|
||||||
|
@ -175,9 +185,10 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
|
||||||
for _, rune := range s {
|
for _, rune := range s {
|
||||||
index := font.Index(rune)
|
index := font.Index(rune)
|
||||||
if hasPrev {
|
if hasPrev {
|
||||||
cursor += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
|
|
||||||
|
cursor += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
|
||||||
}
|
}
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, index, truetype.NoHinting); err != nil {
|
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
|
@ -192,7 +203,7 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
|
||||||
right = math.Max(right, x+cursor)
|
right = math.Max(right, x+cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
|
cursor += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
||||||
prev, hasPrev = index, true
|
prev, hasPrev = index, true
|
||||||
}
|
}
|
||||||
return left, top, right, bottom
|
return left, top, right, bottom
|
||||||
|
@ -201,7 +212,7 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
func (gc *GraphicContext) recalc() {
|
func (gc *GraphicContext) recalc() {
|
||||||
gc.Current.Scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0))
|
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDPI sets the screen resolution in dots per inch.
|
// SetDPI sets the screen resolution in dots per inch.
|
||||||
|
@ -228,11 +239,12 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
|
||||||
gc.Current.Path.Clear()
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2dbase.Flattener
|
var liner draw2dbase.Flattener
|
||||||
|
@ -248,12 +260,13 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||||
|
|
||||||
/**** first method ****/
|
/**** first method ****/
|
||||||
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
@ -261,14 +274,15 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FillStroke first fills the paths and than strokes them
|
||||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}}
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2dbase.Flattener
|
var liner draw2dbase.Flattener
|
||||||
|
@ -278,7 +292,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
liner = stroker
|
liner = stroker
|
||||||
}
|
}
|
||||||
|
|
||||||
demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}}
|
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
@ -288,13 +302,3 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
// Stroke
|
// Stroke
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func useNonZeroWinding(f draw2d.FillRule) bool {
|
|
||||||
switch f {
|
|
||||||
case draw2d.FillRuleEvenOdd:
|
|
||||||
return false
|
|
||||||
case draw2d.FillRuleWinding:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,19 +5,25 @@
|
||||||
package draw2dimg
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ImageFilter defines the type of filter to use
|
||||||
type ImageFilter int
|
type ImageFilter int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// LinearFilter defines a linear filter
|
||||||
LinearFilter ImageFilter = iota
|
LinearFilter ImageFilter = iota
|
||||||
|
// BilinearFilter defines a bilinear filter
|
||||||
BilinearFilter
|
BilinearFilter
|
||||||
|
// BicubicFilter defines a bicubic filter
|
||||||
BicubicFilter
|
BicubicFilter
|
||||||
|
// M is the maximum value for a rgb component
|
||||||
M = 1<<16 - 1
|
M = 1<<16 - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,14 +54,7 @@ func getColorBilinear(img image.Image, x, y float64) color.Color {
|
||||||
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
|
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// lerp is a linear interpolation bertween 2 points
|
||||||
-- LERP
|
|
||||||
-- /lerp/, vi.,n.
|
|
||||||
--
|
|
||||||
-- Quasi-acronym for Linear Interpolation, used as a verb or noun for
|
|
||||||
-- the operation. "Bresenham's algorithm lerps incrementally between the
|
|
||||||
-- two endpoints of the line." (From Jargon File (4.4.4, 14 Aug 2003)
|
|
||||||
*/
|
|
||||||
func lerp(v1, v2, ratio float64) float64 {
|
func lerp(v1, v2, ratio float64) float64 {
|
||||||
return v1*(1-ratio) + v2*ratio
|
return v1*(1-ratio) + v2*ratio
|
||||||
}
|
}
|
||||||
|
@ -105,6 +104,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 {
|
||||||
(-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0)
|
(-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawImage draws an image into dest using an affine transformation matrix, an op and a filter
|
||||||
func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) {
|
func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) {
|
||||||
bounds := src.Bounds()
|
bounds := src.Bounds()
|
||||||
x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y))
|
x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y))
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"github.com/llgcode/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// drawContour draws the given closed contour at the given sub-pixel offset.
|
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
||||||
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
|
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
|
||||||
if len(ps) == 0 {
|
if len(ps) == 0 {
|
||||||
return
|
return
|
||||||
|
@ -67,8 +67,8 @@ type FontExtents struct {
|
||||||
Height float64
|
Height float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
|
|
||||||
// Extents returns the FontExtents for a font.
|
// Extents returns the FontExtents for a font.
|
||||||
|
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
|
||||||
func Extents(font *truetype.Font, size float64) FontExtents {
|
func Extents(font *truetype.Font, size float64) FontExtents {
|
||||||
bounds := font.Bounds(font.FUnitsPerEm())
|
bounds := font.Bounds(font.FUnitsPerEm())
|
||||||
scale := size / float64(font.FUnitsPerEm())
|
scale := size / float64(font.FUnitsPerEm())
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 13/12/2010 by Laurent Le Goff
|
// created: 13/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
// Package draw2dkit provides helpers to draw common figures using a Path or a GraphicContext
|
// Package draw2dkit provides helpers to draw common figures using a Path
|
||||||
package draw2dkit
|
package draw2dkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rectangle draws a rectangle using a path
|
// Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||||
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
||||||
path.MoveTo(x1, y1)
|
path.MoveTo(x1, y1)
|
||||||
path.LineTo(x2, y1)
|
path.LineTo(x2, y1)
|
||||||
|
@ -18,7 +19,7 @@ func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundedRectangle draws a rounded rectangle using a path
|
// RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
|
||||||
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||||
arcWidth = arcWidth / 2
|
arcWidth = arcWidth / 2
|
||||||
arcHeight = arcHeight / 2
|
arcHeight = arcHeight / 2
|
||||||
|
@ -33,13 +34,13 @@ func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeig
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse draws an ellipse using a path
|
// Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
|
||||||
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
|
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
|
||||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circle draws a circle using a path
|
// Circle draws a circle using a path with center (cx,cy) and radius
|
||||||
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
|
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
|
||||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package draw2dkit
|
package draw2dkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Droid draws a droid at specified position
|
// Droid draws a droid at specified position
|
||||||
|
|
39
draw2dpdf/doc.go
Normal file
39
draw2dpdf/doc.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
|
||||||
|
// Package draw2dpdf provides a graphic context that can draw vector
|
||||||
|
// graphics and text on pdf file.
|
||||||
|
//
|
||||||
|
// Quick Start
|
||||||
|
//
|
||||||
|
// The following Go code generates a simple drawing and saves it to a
|
||||||
|
// pdf document:
|
||||||
|
// // Initialize the graphic context on an RGBA image
|
||||||
|
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
|
// gc := draw2d.NewGraphicContext(dest)
|
||||||
|
//
|
||||||
|
// // Set some properties
|
||||||
|
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
|
// gc.SetLineWidth(5)
|
||||||
|
//
|
||||||
|
// // Draw a closed shape
|
||||||
|
// gc.MoveTo(10, 10) // should always be called first for a new path
|
||||||
|
// gc.LineTo(100, 50)
|
||||||
|
// gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
|
// gc.Close()
|
||||||
|
// gc.FillStroke()
|
||||||
|
//
|
||||||
|
// // Save to file
|
||||||
|
// draw2dpdf.SaveToPdfFile(fn, dest)
|
||||||
|
//
|
||||||
|
// There are more examples here:
|
||||||
|
// https://github.com/llgcode/draw2d.samples
|
||||||
|
//
|
||||||
|
// Drawing on images is provided by the draw2d package.
|
||||||
|
// Drawing on opengl is provided by the draw2dgl package.
|
||||||
|
//
|
||||||
|
// Acknowledgments
|
||||||
|
//
|
||||||
|
// The pdf backend uses https://github.com/jung-kurt/gofpdf
|
||||||
|
package draw2dpdf
|
8
draw2dpdf/fileutil.go
Normal file
8
draw2dpdf/fileutil.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
import "github.com/jung-kurt/gofpdf"
|
||||||
|
|
||||||
|
// SaveToPdfFile creates and saves a pdf document to a file
|
||||||
|
func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
|
||||||
|
return pdf.OutputFileAndClose(filePath)
|
||||||
|
}
|
329
draw2dpdf/gc.go
Normal file
329
draw2dpdf/gc.go
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
// TODO: fonts, dpi
|
||||||
|
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.google.com/p/freetype-go/freetype/truetype"
|
||||||
|
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DPI of a pdf document is fixed at 72.
|
||||||
|
DPI = 72
|
||||||
|
c255 = 255.0 / 65535.0
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
caps = map[draw2d.LineCap]string{
|
||||||
|
draw2d.RoundCap: "round",
|
||||||
|
draw2d.ButtCap: "butt",
|
||||||
|
draw2d.SquareCap: "square"}
|
||||||
|
joins = map[draw2d.LineJoin]string{
|
||||||
|
draw2d.RoundJoin: "round",
|
||||||
|
draw2d.BevelJoin: "bevel",
|
||||||
|
draw2d.MiterJoin: "miter",
|
||||||
|
}
|
||||||
|
imageCount uint32
|
||||||
|
white color.Color = color.RGBA{255, 255, 255, 255}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPdf creates a new pdf document with the draw2d fontfolder, adds
|
||||||
|
// a page and set fill color to white.
|
||||||
|
func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
|
||||||
|
pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
|
||||||
|
pdf.AddPage()
|
||||||
|
pdf.SetFillColor(255, 255, 255) // to be compatible with draw2d
|
||||||
|
return pdf
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgb converts a color (used by draw2d) into 3 int (used by gofpdf)
|
||||||
|
func rgb(c color.Color) (int, int, int) {
|
||||||
|
r, g, b, _ := c.RGBA()
|
||||||
|
return int(float64(r) * c255), int(float64(g) * c255), int(float64(b) * c255)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearRect draws a white rectangle
|
||||||
|
func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
|
||||||
|
// save state
|
||||||
|
f := gc.Current.FillColor
|
||||||
|
x, y := gc.pdf.GetXY()
|
||||||
|
// cover page with white rectangle
|
||||||
|
gc.SetFillColor(white)
|
||||||
|
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
|
||||||
|
gc.Fill()
|
||||||
|
// restore state
|
||||||
|
gc.SetFillColor(f)
|
||||||
|
gc.pdf.MoveTo(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphicContext implements the draw2d.GraphicContext interface
|
||||||
|
// It provides draw2d with a pdf backend (based on gofpdf)
|
||||||
|
type GraphicContext struct {
|
||||||
|
*draw2dbase.StackGraphicContext
|
||||||
|
pdf *gofpdf.Fpdf
|
||||||
|
DPI int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGraphicContext creates a new pdf GraphicContext
|
||||||
|
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
||||||
|
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
|
||||||
|
gc.SetDPI(DPI)
|
||||||
|
return gc
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawImage draws an image as PNG
|
||||||
|
// TODO: add type (tp) as parameter to argument list?
|
||||||
|
func (gc *GraphicContext) DrawImage(image image.Image) {
|
||||||
|
name := strconv.Itoa(int(imageCount))
|
||||||
|
imageCount += 1
|
||||||
|
tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
png.Encode(b, image)
|
||||||
|
gc.pdf.RegisterImageReader(name, tp, b)
|
||||||
|
bounds := image.Bounds()
|
||||||
|
x0, y0 := float64(bounds.Min.X), float64(bounds.Min.Y)
|
||||||
|
w, h := float64(bounds.Dx()), float64(bounds.Dy())
|
||||||
|
gc.pdf.Image(name, x0, y0, w, h, false, tp, 0, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear draws a white rectangle over the whole page
|
||||||
|
func (gc *GraphicContext) Clear() {
|
||||||
|
width, height := gc.pdf.GetPageSize()
|
||||||
|
clearRect(gc, 0, 0, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRect draws a white rectangle over the specified area
|
||||||
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
|
clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
|
func (gc *GraphicContext) recalc() {
|
||||||
|
// TODO: resolve properly the font size for pdf and bitmap
|
||||||
|
gc.Current.Scale = 3 * float64(gc.DPI) / 72
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDPI sets the DPI which influences the font size.
|
||||||
|
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||||
|
gc.DPI = dpi
|
||||||
|
gc.recalc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDPI returns the DPI which influences the font size.
|
||||||
|
// (Note that gofpdf uses a fixed dpi of 72:
|
||||||
|
// https://godoc.org/code.google.com/p/gofpdf#Fpdf.PointConvert)
|
||||||
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
|
return gc.DPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
|
||||||
|
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||||
|
_, h := gc.pdf.GetFontSize()
|
||||||
|
return 0, 0, gc.pdf.GetStringWidth(s), h
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||||
|
func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
|
||||||
|
_, _, w, h := gc.GetStringBounds(text)
|
||||||
|
margin := gc.pdf.GetCellMargin()
|
||||||
|
gc.pdf.MoveTo(x-margin, y+margin-0.82*h)
|
||||||
|
gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
|
||||||
|
// gc.pdf.Cell(w, h, text)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillString draws a string at 0, 0
|
||||||
|
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||||
|
return gc.FillStringAt(text, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStringAt draws a string at x, y
|
||||||
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||||
|
return gc.CreateStringPath(text, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeString draws a string at 0, 0 (stroking is unsupported,
|
||||||
|
// string will be filled)
|
||||||
|
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
||||||
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeStringAt draws a string at x, y (stroking is unsupported,
|
||||||
|
// string will be filled)
|
||||||
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||||
|
return gc.CreateStringPath(text, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke strokes the paths with the color specified by SetStrokeColor
|
||||||
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
|
gc.draw("D", paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill fills the paths with the color specified by SetFillColor
|
||||||
|
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
|
gc.draw("F", paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStroke first fills the paths and than strokes them
|
||||||
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
|
gc.draw("FD", paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger = log.New(os.Stdout, "", log.Lshortfile)
|
||||||
|
|
||||||
|
// draw fills and/or strokes paths
|
||||||
|
func (gc *GraphicContext) draw(style string, paths ...*draw2d.Path) {
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
for _, p := range paths {
|
||||||
|
ConvertPath(p, gc.pdf)
|
||||||
|
}
|
||||||
|
if gc.Current.FillRule == draw2d.FillRuleWinding {
|
||||||
|
style += "*"
|
||||||
|
}
|
||||||
|
gc.pdf.DrawPath(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite StackGraphicContext methods
|
||||||
|
|
||||||
|
// SetStrokeColor sets the stroke color
|
||||||
|
func (gc *GraphicContext) SetStrokeColor(c color.Color) {
|
||||||
|
gc.StackGraphicContext.SetStrokeColor(c)
|
||||||
|
gc.pdf.SetDrawColor(rgb(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFillColor sets the fill and text color
|
||||||
|
func (gc *GraphicContext) SetFillColor(c color.Color) {
|
||||||
|
gc.StackGraphicContext.SetFillColor(c)
|
||||||
|
gc.pdf.SetFillColor(rgb(c))
|
||||||
|
gc.pdf.SetTextColor(rgb(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFont is unsupported by the pdf graphic context, use SetFontData
|
||||||
|
// instead.
|
||||||
|
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
||||||
|
// TODO: what to do with this api conflict between draw2d and gofpdf?!
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFontData sets the current font used to draw text. Always use
|
||||||
|
// this method, as SetFont is unsupported by the pdf graphic context.
|
||||||
|
// It is mandatory to call this method at least once before printing
|
||||||
|
// text or the resulting document will not be valid.
|
||||||
|
// It is necessary to generate a font definition file first with the
|
||||||
|
// makefont utility. It is not necessary to call this function for the
|
||||||
|
// core PDF fonts (courier, helvetica, times, zapfdingbats).
|
||||||
|
// go get github.com/jung-kurt/gofpdf/makefont
|
||||||
|
// http://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.AddFont
|
||||||
|
func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
|
||||||
|
// TODO: call Makefont embed if json file does not exist yet
|
||||||
|
gc.StackGraphicContext.SetFontData(fontData)
|
||||||
|
var style string
|
||||||
|
if fontData.Style&draw2d.FontStyleBold != 0 {
|
||||||
|
style += "B"
|
||||||
|
}
|
||||||
|
if fontData.Style&draw2d.FontStyleItalic != 0 {
|
||||||
|
style += "I"
|
||||||
|
}
|
||||||
|
fn := draw2d.FontFileName(fontData)
|
||||||
|
fn = fn[:len(fn)-4]
|
||||||
|
jfn := filepath.Join(draw2d.GetFontFolder(), fn+".json")
|
||||||
|
gc.pdf.AddFont(fn, style, jfn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||||
|
// TODO: resolve this with ImgGraphicContext (now done with gc.Current.Scale)
|
||||||
|
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
|
gc.StackGraphicContext.SetFontSize(fontSize)
|
||||||
|
gc.recalc()
|
||||||
|
gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLineWidth sets the line width
|
||||||
|
func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
|
||||||
|
gc.StackGraphicContext.SetLineWidth(LineWidth)
|
||||||
|
gc.pdf.SetLineWidth(LineWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLineCap sets the line cap (round, but or square)
|
||||||
|
func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
|
||||||
|
gc.StackGraphicContext.SetLineCap(Cap)
|
||||||
|
gc.pdf.SetLineCapStyle(caps[Cap])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLineJoin sets the line cap (round, bevel or miter)
|
||||||
|
func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
|
||||||
|
gc.StackGraphicContext.SetLineJoin(Join)
|
||||||
|
gc.pdf.SetLineJoinStyle(joins[Join])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformations
|
||||||
|
|
||||||
|
// Scale generally scales the following text, drawings and images.
|
||||||
|
// sx and sy are the scaling factors for width and height.
|
||||||
|
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||||
|
// the pdf is invalid.
|
||||||
|
func (gc *GraphicContext) Scale(sx, sy float64) {
|
||||||
|
gc.StackGraphicContext.Scale(sx, sy)
|
||||||
|
gc.pdf.TransformScale(sx*100, sy*100, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate rotates the following text, drawings and images.
|
||||||
|
// Angle is specified in radians and measured clockwise from the
|
||||||
|
// 3 o'clock position.
|
||||||
|
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||||
|
// the pdf is invalid.
|
||||||
|
func (gc *GraphicContext) Rotate(angle float64) {
|
||||||
|
gc.StackGraphicContext.Rotate(angle)
|
||||||
|
gc.pdf.TransformRotate(-angle*180/math.Pi, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate moves the following text, drawings and images
|
||||||
|
// horizontally and vertically by the amounts specified by tx and ty.
|
||||||
|
// This must be placed between gc.Save() and gc.Restore(), otherwise
|
||||||
|
// the pdf is invalid.
|
||||||
|
func (gc *GraphicContext) Translate(tx, ty float64) {
|
||||||
|
gc.StackGraphicContext.Translate(tx, ty)
|
||||||
|
gc.pdf.TransformTranslate(tx, ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the current context stack
|
||||||
|
// (transformation, font, color,...).
|
||||||
|
func (gc *GraphicContext) Save() {
|
||||||
|
gc.StackGraphicContext.Save()
|
||||||
|
gc.pdf.TransformBegin()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the current context stack
|
||||||
|
// (transformation, color,...). Restoring the font is not supported.
|
||||||
|
func (gc *GraphicContext) Restore() {
|
||||||
|
gc.pdf.TransformEnd()
|
||||||
|
gc.StackGraphicContext.Restore()
|
||||||
|
c := gc.Current
|
||||||
|
gc.SetFontSize(c.FontSize)
|
||||||
|
// gc.SetFontData(c.FontData) unsupported, causes bug (do not enable)
|
||||||
|
gc.SetLineWidth(c.LineWidth)
|
||||||
|
gc.SetStrokeColor(c.StrokeColor)
|
||||||
|
gc.SetFillColor(c.FillColor)
|
||||||
|
gc.SetFillRule(c.FillRule)
|
||||||
|
// gc.SetLineDash(c.Dash, c.DashOffset) // TODO
|
||||||
|
gc.SetLineCap(c.Cap)
|
||||||
|
gc.SetLineJoin(c.Join)
|
||||||
|
// c.Path unsupported
|
||||||
|
// c.Font unsupported
|
||||||
|
}
|
44
draw2dpdf/path_converter.go
Normal file
44
draw2dpdf/path_converter.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
)
|
||||||
|
|
||||||
|
const deg = 180 / math.Pi
|
||||||
|
|
||||||
|
// ConvertPath converts a paths to the pdf api
|
||||||
|
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
|
||||||
|
var startX, startY float64 = 0, 0
|
||||||
|
i := 0
|
||||||
|
for _, cmp := range path.Components {
|
||||||
|
switch cmp {
|
||||||
|
case draw2d.MoveToCmp:
|
||||||
|
startX, startY = path.Points[i], path.Points[i+1]
|
||||||
|
pdf.MoveTo(startX, startY)
|
||||||
|
i += 2
|
||||||
|
case draw2d.LineToCmp:
|
||||||
|
pdf.LineTo(path.Points[i], path.Points[i+1])
|
||||||
|
i += 2
|
||||||
|
case draw2d.QuadCurveToCmp:
|
||||||
|
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
|
||||||
|
i += 4
|
||||||
|
case draw2d.CubicCurveToCmp:
|
||||||
|
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
|
||||||
|
i += 6
|
||||||
|
case draw2d.ArcToCmp:
|
||||||
|
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
|
||||||
|
0, // degRotate
|
||||||
|
path.Points[i+4]*deg, // degStart = startAngle
|
||||||
|
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
|
||||||
|
i += 6
|
||||||
|
case draw2d.CloseCmp:
|
||||||
|
pdf.LineTo(startX, startY)
|
||||||
|
pdf.ClosePath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
draw2dpdf/samples_test.go
Normal file
49
draw2dpdf/samples_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// See also test_test.go
|
||||||
|
|
||||||
|
package draw2dpdf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d.samples"
|
||||||
|
"github.com/llgcode/draw2d.samples/android"
|
||||||
|
"github.com/llgcode/draw2d.samples/frameimage"
|
||||||
|
"github.com/llgcode/draw2d.samples/gopher"
|
||||||
|
"github.com/llgcode/draw2d.samples/helloworld"
|
||||||
|
"github.com/llgcode/draw2d.samples/line"
|
||||||
|
"github.com/llgcode/draw2d.samples/linecapjoin"
|
||||||
|
"github.com/llgcode/draw2d.samples/postscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
test(t, android.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher(t *testing.T) {
|
||||||
|
test(t, gopher.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleHelloWorld(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
// The pdf backend needs for every ttf file its corresponding json
|
||||||
|
// file which is generated by gofpdf/makefont.
|
||||||
|
draw2d.SetFontFolder(samples.Dir("helloworld", "../"))
|
||||||
|
test(t, helloworld.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleFrameImage(t *testing.T) {
|
||||||
|
test(t, frameimage.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLine(t *testing.T) {
|
||||||
|
test(t, line.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLineCap(t *testing.T) {
|
||||||
|
test(t, linecapjoin.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplePostscript(t *testing.T) {
|
||||||
|
test(t, postscript.Main)
|
||||||
|
}
|
35
draw2dpdf/test_test.go
Normal file
35
draw2dpdf/test_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// Package draw2dpdf_test gives test coverage with the command:
|
||||||
|
// go test -cover ./... | grep -v "no test"
|
||||||
|
// (It should be run from its parent draw2d directory.)
|
||||||
|
package draw2dpdf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
||||||
|
|
||||||
|
func test(t *testing.T, draw sample) {
|
||||||
|
// Initialize the graphic context on an pdf document
|
||||||
|
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
|
gc := draw2dpdf.NewGraphicContext(dest)
|
||||||
|
// Draw sample
|
||||||
|
fn, err := draw(gc, "pdf")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Drawing %q failed: %v", fn, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Save to pdf only if it doesn't exist because of git
|
||||||
|
if _, err = os.Stat(fn); err == nil {
|
||||||
|
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", fn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = draw2dpdf.SaveToPdfFile(fn, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Saving %q failed: %v", fn, err)
|
||||||
|
}
|
||||||
|
}
|
22
draw2dpdf/vectorizer.go
Normal file
22
draw2dpdf/vectorizer.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
// Vectorizer defines the minimal interface for gofpdf.Fpdf
|
||||||
|
// to be passed to a PathConvertor.
|
||||||
|
// It is also implemented by for example VertexMatrixTransform
|
||||||
|
type Vectorizer interface {
|
||||||
|
// MoveTo creates a new subpath that start at the specified point
|
||||||
|
MoveTo(x, y float64)
|
||||||
|
// LineTo adds a line to the current subpath
|
||||||
|
LineTo(x, y float64)
|
||||||
|
// CurveTo adds a quadratic bezier curve to the current subpath
|
||||||
|
CurveTo(cx, cy, x, y float64)
|
||||||
|
// CurveTo adds a cubic bezier curve to the current subpath
|
||||||
|
CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||||
|
// ArcTo adds an arc to the current subpath
|
||||||
|
ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
|
||||||
|
// ClosePath closes the subpath
|
||||||
|
ClosePath()
|
||||||
|
}
|
6
font.go
6
font.go
|
@ -38,7 +38,7 @@ type FontData struct {
|
||||||
Style FontStyle
|
Style FontStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
func fontFileName(fontData FontData) string {
|
func FontFileName(fontData FontData) string {
|
||||||
fontFileName := fontData.Name
|
fontFileName := fontData.Name
|
||||||
switch fontData.Family {
|
switch fontData.Family {
|
||||||
case FontFamilySans:
|
case FontFamilySans:
|
||||||
|
@ -62,11 +62,11 @@ func fontFileName(fontData FontData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFont(fontData FontData, font *truetype.Font) {
|
func RegisterFont(fontData FontData, font *truetype.Font) {
|
||||||
fonts[fontFileName(fontData)] = font
|
fonts[FontFileName(fontData)] = font
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFont(fontData FontData) *truetype.Font {
|
func GetFont(fontData FontData) *truetype.Font {
|
||||||
fontFileName := fontFileName(fontData)
|
fontFileName := FontFileName(fontData)
|
||||||
font := fonts[fontFileName]
|
font := fonts[fontFileName]
|
||||||
if font != nil {
|
if font != nil {
|
||||||
return font
|
return font
|
||||||
|
|
1
gc.go
1
gc.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
|
||||||
type GraphicContext interface {
|
type GraphicContext interface {
|
||||||
PathBuilder
|
PathBuilder
|
||||||
// BeginPath creates a new path
|
// BeginPath creates a new path
|
||||||
|
|
47
path.go
47
path.go
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
// created: 21/11/2010 by Laurent Le Goff
|
// created: 21/11/2010 by Laurent Le Goff
|
||||||
|
|
||||||
// Package path implements function to build path
|
|
||||||
package draw2d
|
package draw2d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,27 +8,23 @@ import (
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PathBuilder defines methods that creates path
|
// PathBuilder describes the interface for path drawing.
|
||||||
type PathBuilder interface {
|
type PathBuilder interface {
|
||||||
// LastPoint returns the current point of the current path
|
// LastPoint returns the current point of the current sub path
|
||||||
LastPoint() (x, y float64)
|
LastPoint() (x, y float64)
|
||||||
|
// MoveTo creates a new subpath that start at the specified point
|
||||||
// MoveTo starts a new path at (x, y) position
|
|
||||||
MoveTo(x, y float64)
|
MoveTo(x, y float64)
|
||||||
|
// LineTo adds a line to the current subpath
|
||||||
// LineTo adds a line to the current path
|
|
||||||
LineTo(x, y float64)
|
LineTo(x, y float64)
|
||||||
|
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
|
||||||
// QuadCurveTo adds a quadratic bezier curve to the current path
|
|
||||||
QuadCurveTo(cx, cy, x, y float64)
|
QuadCurveTo(cx, cy, x, y float64)
|
||||||
|
// CubicCurveTo adds a cubic Bézier curve to the current subpath
|
||||||
// CubicCurveTo adds a cubic bezier curve to the current path
|
|
||||||
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||||
|
// ArcTo adds an arc to the current subpath
|
||||||
// ArcTo adds an arc to the path
|
|
||||||
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
|
||||||
|
// Close creates a line from the current point to the last MoveTo
|
||||||
// Close closes the current path
|
// point (if not the same) and mark the path as closed so the
|
||||||
|
// first and last lines join nicely.
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,18 +32,27 @@ type PathBuilder interface {
|
||||||
type PathCmp int
|
type PathCmp int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// MoveToCmp is a MoveTo component in a Path
|
||||||
MoveToCmp PathCmp = iota
|
MoveToCmp PathCmp = iota
|
||||||
|
// LineToCmp is a LineTo component in a Path
|
||||||
LineToCmp
|
LineToCmp
|
||||||
|
// QuadCurveToCmp is a QuadCurveTo component in a Path
|
||||||
QuadCurveToCmp
|
QuadCurveToCmp
|
||||||
|
// CubicCurveToCmp is a CubicCurveTo component in a Path
|
||||||
CubicCurveToCmp
|
CubicCurveToCmp
|
||||||
|
// ArcToCmp is a ArcTo component in a Path
|
||||||
ArcToCmp
|
ArcToCmp
|
||||||
|
// CloseCmp is a ArcTo component in a Path
|
||||||
CloseCmp
|
CloseCmp
|
||||||
)
|
)
|
||||||
|
|
||||||
// Path stores points
|
// Path stores points
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
// Components is a slice of PathCmp in a Path and mark the role of each points in the Path
|
||||||
Components []PathCmp
|
Components []PathCmp
|
||||||
|
// Points are combined with Components to have a specific role in the path
|
||||||
Points []float64
|
Points []float64
|
||||||
|
// Last Point of the Path
|
||||||
x, y float64
|
x, y float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +69,6 @@ func (p *Path) LastPoint() (x, y float64) {
|
||||||
// MoveTo starts a new path at (x, y) position
|
// MoveTo starts a new path at (x, y) position
|
||||||
func (p *Path) MoveTo(x, y float64) {
|
func (p *Path) MoveTo(x, y float64) {
|
||||||
p.appendToPath(MoveToCmp, x, y)
|
p.appendToPath(MoveToCmp, x, y)
|
||||||
|
|
||||||
p.x = x
|
p.x = x
|
||||||
p.y = y
|
p.y = y
|
||||||
}
|
}
|
||||||
|
@ -135,13 +138,13 @@ func (p *Path) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy make a clone of the current path and return it
|
// Copy make a clone of the current path and return it
|
||||||
func (src *Path) Copy() (dest *Path) {
|
func (p *Path) Copy() (dest *Path) {
|
||||||
dest = new(Path)
|
dest = new(Path)
|
||||||
dest.Components = make([]PathCmp, len(src.Components))
|
dest.Components = make([]PathCmp, len(p.Components))
|
||||||
copy(dest.Components, src.Components)
|
copy(dest.Components, p.Components)
|
||||||
dest.Points = make([]float64, len(src.Points))
|
dest.Points = make([]float64, len(p.Points))
|
||||||
copy(dest.Points, src.Points)
|
copy(dest.Points, p.Points)
|
||||||
dest.x, dest.y = src.x, src.y
|
dest.x, dest.y = p.x, p.y
|
||||||
return dest
|
return dest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
samples_test.go
Normal file
47
samples_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// See also test_test.go
|
||||||
|
|
||||||
|
package draw2d_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d.samples"
|
||||||
|
"github.com/llgcode/draw2d.samples/android"
|
||||||
|
"github.com/llgcode/draw2d.samples/frameimage"
|
||||||
|
"github.com/llgcode/draw2d.samples/gopher"
|
||||||
|
"github.com/llgcode/draw2d.samples/helloworld"
|
||||||
|
"github.com/llgcode/draw2d.samples/line"
|
||||||
|
"github.com/llgcode/draw2d.samples/linecapjoin"
|
||||||
|
"github.com/llgcode/draw2d.samples/postscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
test(t, android.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher(t *testing.T) {
|
||||||
|
test(t, gopher.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleHelloWorld(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
draw2d.SetFontFolder(samples.Dir("helloworld", ""))
|
||||||
|
test(t, helloworld.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleFrameImage(t *testing.T) {
|
||||||
|
test(t, frameimage.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLine(t *testing.T) {
|
||||||
|
test(t, line.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLineCap(t *testing.T) {
|
||||||
|
test(t, linecapjoin.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplePostscript(t *testing.T) {
|
||||||
|
test(t, postscript.Main)
|
||||||
|
}
|
29
test_test.go
Normal file
29
test_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Package draw2d_test gives test coverage with the command:
|
||||||
|
// go test -cover ./... | grep -v "no test"
|
||||||
|
package draw2d_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
||||||
|
|
||||||
|
func test(t *testing.T, draw sample) {
|
||||||
|
// Initialize the graphic context on an RGBA image
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
|
gc := draw2d.NewGraphicContext(dest)
|
||||||
|
// Draw Android logo
|
||||||
|
fn, err := draw(gc, "png")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Drawing %q failed: %v", fn, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Save to png
|
||||||
|
err = draw2d.SaveToPngFile(fn, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Saving %q failed: %v", fn, err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue