├── .github
└── workflows
│ └── go.yml
├── .gitignore
├── AUTHORS
├── LICENSE
├── README.md
├── draw2d.go
├── draw2dbase
├── README.md
├── curve.go
├── curve_test.go
├── dasher.go
├── demux_flattener.go
├── flattener.go
├── line.go
├── stack_gc.go
├── stroker.go
└── text.go
├── draw2dgl
├── doc.go
├── gc.go
├── notes.md
└── text.go
├── draw2dimg
├── README.md
├── curve_limit_test.go
├── fileutil.go
├── ftgc.go
├── ftpath.go
└── text.go
├── draw2dkit
├── README.md
├── draw2dkit.go
└── draw2dkit_test.go
├── draw2dpdf
├── README.md
├── doc.go
├── fileutil.go
├── gc.go
├── path_converter.go
├── samples_test.go
├── test_test.go
└── vectorizer.go
├── draw2dsvg
├── converters.go
├── doc.go
├── fileutil.go
├── gc.go
├── samples_test.go
├── svg.go
├── test_test.go
├── text.go
└── xml_test.go
├── font.go
├── gc.go
├── go.mod
├── go.sum
├── matrix.go
├── output
├── README.md
├── curve
│ └── .gitignore
├── draw2dkit
│ └── .gitignore
├── raster
│ └── .gitignore
└── samples
│ ├── .gitignore
│ ├── geometry.png
│ └── postscript.png
├── path.go
├── resource
├── font
│ ├── COPYING
│ ├── README
│ ├── luximb.ttf
│ ├── luximbi.json
│ ├── luximbi.ttf
│ ├── luximbi.z
│ ├── luximr.ttf
│ ├── luximri.ttf
│ ├── luxirb.ttf
│ ├── luxirbi.ttf
│ ├── luxirr.ttf
│ ├── luxirri.ttf
│ ├── luxisb.ttf
│ ├── luxisbi.ttf
│ ├── luxisr.ttf
│ └── luxisri.ttf
├── image
│ ├── geometry.pdf
│ ├── gopher.png
│ ├── postscript.pdf
│ └── tiger.ps
└── result
│ ├── TestAndroid.png
│ ├── TestBigPicture.png
│ ├── TestBubble.png
│ ├── TestCurveRectangle.png
│ ├── TestDash.png
│ ├── TestDrawArc.png
│ ├── TestDrawArcNegative.png
│ ├── TestDrawCubicCurve.png
│ ├── TestDrawImage.png
│ ├── TestFillString.png
│ ├── TestFillStroke.png
│ ├── TestFillStyle.png
│ ├── TestGopher.png
│ ├── TestLineCap.png
│ ├── TestLineJoin.png
│ ├── TestMultiSegmentCaps.png
│ ├── TestPath.png
│ ├── TestPathTransform.png
│ ├── TestRoundRectangle.png
│ ├── TestStar.png
│ └── TestTransform.png
├── samples
├── README.md
├── android
│ └── android.go
├── appengine
│ ├── app.yaml
│ └── server.go
├── frameimage
│ └── frameimage.go
├── geometry
│ └── geometry.go
├── gopher
│ └── gopher.go
├── gopher2
│ └── gopher2.go
├── helloworld
│ └── helloworld.go
├── helloworldgl
│ └── helloworldgl.go
├── line
│ └── line.go
├── linecapjoin
│ └── linecapjoin.go
├── postscript
│ └── postscript.go
├── postscriptgl
│ ├── postscriptgl.go
│ └── tiger.ps
└── samples.go
├── samples_test.go
├── sync_test.go
├── test
└── test_test.go
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a golang project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
3 |
4 | name: Go
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 |
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Set up Go
20 | uses: actions/setup-go@v4
21 | with:
22 | go-version: '1.20'
23 |
24 | - name: Get Dependencies
25 | run: go mod tidy
26 |
27 | - name: Build
28 | run: go build -v . ./draw2dbase ./draw2dimg ./draw2dkit ./draw2dpdf ./draw2dsvg
29 |
30 | - name: Test
31 | run: go test -v . ./draw2dbase ./draw2dimg ./draw2dkit ./draw2dpdf ./draw2dsvg
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | **/*.[568ao]
3 | **/*.ao
4 | **/*.so
5 | **/*.pyc
6 | **/._*
7 | **/.nfs.*
8 | **/[568a].out
9 | **/*.exe
10 | **/*~
11 | **/*.orig
12 | **/*.out
13 | **/*.test
14 | core
15 | _obj
16 | _test
17 | out.png
18 | _test*
19 |
20 | **/*.dll
21 | **/core*[0-9]
22 | .private
23 |
24 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Laurent Le Goff
2 | Stani Michiels, gmail:stani.be
3 | Drahoslav Bednář
4 | Sebastien Binet
5 | sdkawata
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Laurent Le Goff
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
13 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
14 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
15 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
16 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
18 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | draw2d
2 | ======
3 | [](http://gocover.io/github.com/llgcode/draw2d)
4 | [](https://godoc.org/github.com/llgcode/draw2d)
5 | [](https://www.buymeacoffee.com/llgcoffee)
6 |
7 | Package draw2d is a [go](http://golang.org) 2D vector graphics library with support for multiple outputs such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf), opengl (draw2dgl) and svg (draw2dsvg).
8 | There's also a [Postscript reader](https://github.com/llgcode/ps) that uses draw2d.
9 | draw2d is released under the BSD license.
10 | See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
11 |
12 | [](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/geometry.pdf)[](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/postscript.pdf)
13 |
14 | Click on an image above to get the pdf, generated with exactly the same draw2d code. The first image is the output of `samples/geometry`. The second image is the result of `samples/postcript`, which demonstrates that draw2d can draw postscript files into images or pdf documents with the [ps](https://github.com/llgcode/ps) package.
15 |
16 | Features
17 | --------
18 |
19 | 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).
20 |
21 | Package draw2d follows the conventions of the [HTML Canvas 2D Context](http://www.w3.org/TR/2dcontext/) for coordinate system, angles, etc...
22 |
23 | Installation
24 | ------------
25 |
26 | Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
27 |
28 | Stable release
29 | ```
30 | go get -u gopkg.in/llgcode/draw2d.v1
31 | ```
32 |
33 | or Current release
34 | ```
35 | go get -u github.com/llgcode/draw2d
36 | ```
37 |
38 |
39 | Quick Start
40 | -----------
41 |
42 | The following Go code generates a simple drawing and saves it to an image file with package draw2d:
43 |
44 | ```go
45 | package main
46 |
47 | import (
48 | "github.com/llgcode/draw2d/draw2dimg"
49 | "image"
50 | "image/color"
51 | )
52 |
53 | func main() {
54 | // Initialize the graphic context on an RGBA image
55 | dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
56 | gc := draw2dimg.NewGraphicContext(dest)
57 |
58 | // Set some properties
59 | gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
60 | gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
61 | gc.SetLineWidth(5)
62 |
63 | // Draw a closed shape
64 | gc.BeginPath() // Initialize a new path
65 | gc.MoveTo(10, 10) // Move to a position to start the new path
66 | gc.LineTo(100, 50)
67 | gc.QuadCurveTo(100, 10, 10, 10)
68 | gc.Close()
69 | gc.FillStroke()
70 |
71 | // Save to file
72 | draw2dimg.SaveToPngFile("hello.png", dest)
73 | }
74 | ```
75 |
76 | The same Go code can also generate a pdf document with package draw2dpdf:
77 |
78 | ```go
79 | package main
80 |
81 | import (
82 | "github.com/llgcode/draw2d/draw2dpdf"
83 | "image/color"
84 | )
85 |
86 | func main() {
87 | // Initialize the graphic context on an RGBA image
88 | dest := draw2dpdf.NewPdf("L", "mm", "A4")
89 | gc := draw2dpdf.NewGraphicContext(dest)
90 |
91 | // Set some properties
92 | gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
93 | gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
94 | gc.SetLineWidth(5)
95 |
96 | // Draw a closed shape
97 | gc.MoveTo(10, 10) // should always be called first for a new path
98 | gc.LineTo(100, 50)
99 | gc.QuadCurveTo(100, 10, 10, 10)
100 | gc.Close()
101 | gc.FillStroke()
102 |
103 | // Save to file
104 | draw2dpdf.SaveToPdfFile("hello.pdf", dest)
105 | }
106 | ```
107 |
108 | There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
109 |
110 | Drawing on opengl is provided by the draw2dgl package.
111 |
112 | Testing
113 | -------
114 |
115 | The samples are run as tests from the root package folder `draw2d` by:
116 | ```
117 | go test ./...
118 | ```
119 | Or if you want to run with test coverage:
120 | ```
121 | go test -cover ./... | grep -v "no test"
122 | ```
123 | This will generate output by the different backends in the output folder.
124 |
125 | Acknowledgments
126 | ---------------
127 |
128 | [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.
129 |
130 |
131 |
132 | Packages using draw2d
133 | ---------------------
134 |
135 | - [ps](https://github.com/llgcode/ps): Postscript interpreter written in Go
136 | - [go.uik](https://github.com/skelterjohn/go.uik): a concurrent UI kit written in pure go.
137 | - [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
138 | - [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
139 | - [chart](https://github.com/vdobler/chart): basic charts in Go
140 | - [hilbert](https://github.com/google/hilbert): package for drawing Hilbert curves
141 |
142 | References
143 | ---------
144 |
145 | - [antigrain.com](http://www.antigrain.com)
146 | - [freetype-go](http://code.google.com/p/freetype-go)
147 | -
148 |
--------------------------------------------------------------------------------
/draw2d.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | // Package draw2d is a pure go 2D vector graphics library with support
5 | // for multiple output devices such as images (draw2d), pdf documents
6 | // (draw2dpdf) and opengl (draw2dgl), which can also be used on the
7 | // google app engine. It can be used as a pure go Cairo alternative.
8 | // draw2d is released under the BSD license.
9 | //
10 | // Features
11 | //
12 | // Operations in draw2d include stroking and filling polygons, arcs,
13 | // Bézier curves, drawing images and text rendering with truetype fonts.
14 | // All drawing operations can be transformed by affine transformations
15 | // (scale, rotation, translation).
16 | //
17 | // Package draw2d follows the conventions of http://www.w3.org/TR/2dcontext for coordinate system, angles, etc...
18 | //
19 | // Installation
20 | //
21 | // To install or update the package draw2d on your system, run:
22 | // go get -u github.com/llgcode/draw2d
23 | //
24 | // Quick Start
25 | //
26 | // Package draw2d itself provides a graphic context that can draw vector
27 | // graphics and text on an image canvas. The following Go code
28 | // generates a simple drawing and saves it to an image file:
29 | // package main
30 | //
31 | // import (
32 | // "github.com/llgcode/draw2d/draw2dimg"
33 | // "image"
34 | // "image/color"
35 | // )
36 | //
37 | // func main() {
38 | // // Initialize the graphic context on an RGBA image
39 | // dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
40 | // gc := draw2dimg.NewGraphicContext(dest)
41 | //
42 | // // Set some properties
43 | // gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
44 | // gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
45 | // gc.SetLineWidth(5)
46 | //
47 | // // Draw a closed shape
48 | // gc.MoveTo(10, 10) // should always be called first for a new path
49 | // gc.LineTo(100, 50)
50 | // gc.QuadCurveTo(100, 10, 10, 10)
51 | // gc.Close()
52 | // gc.FillStroke()
53 | //
54 | // // Save to file
55 | // draw2dimg.SaveToPngFile("hello.png", dest)
56 | // }
57 | //
58 | //
59 | // There are more examples here:
60 | // https://github.com/llgcode/draw2d/tree/master/samples
61 | //
62 | // Drawing on pdf documents is provided by the draw2dpdf package.
63 | // Drawing on opengl is provided by the draw2dgl package.
64 | // See subdirectories at the bottom of this page.
65 | //
66 | // Testing
67 | //
68 | // The samples are run as tests from the root package folder `draw2d` by:
69 | // go test ./...
70 | //
71 | // Or if you want to run with test coverage:
72 | // go test -cover ./... | grep -v "no test"
73 | //
74 | // This will generate output by the different backends in the output folder.
75 | //
76 | // Acknowledgments
77 | //
78 | // Laurent Le Goff wrote this library, inspired by Postscript and
79 | // HTML5 canvas. He implemented the image and opengl backend with the
80 | // freetype-go package. Also he created a pure go Postscript
81 | // interpreter, which can read postscript images and draw to a draw2d
82 | // graphic context (https://github.com/llgcode/ps). Stani Michiels
83 | // implemented the pdf backend with the gofpdf package.
84 | //
85 | // Packages using draw2d
86 | //
87 | // - https://github.com/llgcode/ps: Postscript interpreter written in Go
88 | //
89 | // - https://github.com/gonum/plot: drawing plots in Go
90 | //
91 | // - https://github.com/muesli/smartcrop: content aware image cropping
92 | //
93 | // - https://github.com/peterhellberg/karta: drawing Voronoi diagrams
94 | //
95 | // - https://github.com/vdobler/chart: basic charts in Go
96 | package draw2d
97 |
98 | import "image/color"
99 |
100 | // FillRule defines the type for fill rules
101 | type FillRule int
102 |
103 | const (
104 | // FillRuleEvenOdd determines the "insideness" of a point in the shape
105 | // by drawing a ray from that point to infinity in any direction
106 | // and counting the number of path segments from the given shape that the ray crosses.
107 | // If this number is odd, the point is inside; if even, the point is outside.
108 | FillRuleEvenOdd FillRule = iota
109 | // FillRuleWinding determines the "insideness" of a point in the shape
110 | // by drawing a ray from that point to infinity in any direction
111 | // and then examining the places where a segment of the shape crosses the ray.
112 | // Starting with a count of zero, add one each time a path segment crosses
113 | // the ray from left to right and subtract one each time
114 | // a path segment crosses the ray from right to left. After counting the crossings,
115 | // if the result is zero then the point is outside the path. Otherwise, it is inside.
116 | FillRuleWinding
117 | )
118 |
119 | // LineCap is the style of line extremities
120 | type LineCap int
121 |
122 | const (
123 | // RoundCap defines a rounded shape at the end of the line
124 | RoundCap LineCap = iota
125 | // ButtCap defines a squared shape exactly at the end of the line
126 | ButtCap
127 | // SquareCap defines a squared shape at the end of the line
128 | SquareCap
129 | )
130 |
131 | func (cap LineCap) String() string {
132 | return map[LineCap]string{
133 | RoundCap: "round",
134 | ButtCap: "cap",
135 | SquareCap: "square",
136 | }[cap]
137 | }
138 |
139 | // LineJoin is the style of segments joint
140 | type LineJoin int
141 |
142 | const (
143 | // BevelJoin represents cut segments joint
144 | BevelJoin LineJoin = iota
145 | // RoundJoin represents rounded segments joint
146 | RoundJoin
147 | // MiterJoin represents peaker segments joint
148 | MiterJoin
149 | )
150 |
151 | func (join LineJoin) String() string {
152 | return map[LineJoin]string{
153 | RoundJoin: "round",
154 | BevelJoin: "bevel",
155 | MiterJoin: "miter",
156 | }[join]
157 | }
158 |
159 | // StrokeStyle keeps stroke style attributes
160 | // that is used by the Stroke method of a Drawer
161 | type StrokeStyle struct {
162 | // Color defines the color of stroke
163 | Color color.Color
164 | // Line width
165 | Width float64
166 | // Line cap style rounded, butt or square
167 | LineCap LineCap
168 | // Line join style bevel, round or miter
169 | LineJoin LineJoin
170 | // offset of the first dash
171 | DashOffset float64
172 | // array represented dash length pair values are plain dash and impair are space between dash
173 | // if empty display plain line
174 | Dash []float64
175 | }
176 |
177 | // SolidFillStyle define style attributes for a solid fill style
178 | type SolidFillStyle struct {
179 | // Color defines the line color
180 | Color color.Color
181 | // FillRule defines the file rule to used
182 | FillRule FillRule
183 | }
184 |
185 | // Valign Vertical Alignment of the text
186 | type Valign int
187 |
188 | const (
189 | // ValignTop top align text
190 | ValignTop Valign = iota
191 | // ValignCenter centered text
192 | ValignCenter
193 | // ValignBottom bottom aligned text
194 | ValignBottom
195 | // ValignBaseline align text with the baseline of the font
196 | ValignBaseline
197 | )
198 |
199 | // Halign Horizontal Alignment of the text
200 | type Halign int
201 |
202 | const (
203 | // HalignLeft Horizontally align to left
204 | HalignLeft = iota
205 | // HalignCenter Horizontally align to center
206 | HalignCenter
207 | // HalignRight Horizontally align to right
208 | HalignRight
209 | )
210 |
211 | // TextStyle describe text property
212 | type TextStyle struct {
213 | // Color defines the color of text
214 | Color color.Color
215 | // Size font size
216 | Size float64
217 | // The font to use
218 | Font FontData
219 | // Horizontal Alignment of the text
220 | Halign Halign
221 | // Vertical Alignment of the text
222 | Valign Valign
223 | }
224 |
225 | // ScalingPolicy is a constant to define how to scale an image
226 | type ScalingPolicy int
227 |
228 | const (
229 | // ScalingNone no scaling applied
230 | ScalingNone ScalingPolicy = iota
231 | // ScalingStretch the image is stretched so that its width and height are exactly the given width and height
232 | ScalingStretch
233 | // ScalingWidth the image is scaled so that its width is exactly the given width
234 | ScalingWidth
235 | // ScalingHeight the image is scaled so that its height is exactly the given height
236 | ScalingHeight
237 | // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height
238 | ScalingFit
239 | // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height
240 | ScalingSameArea
241 | // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height
242 | ScalingFill
243 | )
244 |
245 | // ImageScaling style attributes used to display the image
246 | type ImageScaling struct {
247 | // Horizontal Alignment of the image
248 | Halign Halign
249 | // Vertical Alignment of the image
250 | Valign Valign
251 | // Width Height used by scaling policy
252 | Width, Height float64
253 | // ScalingPolicy defines the scaling policy to applied to the image
254 | ScalingPolicy ScalingPolicy
255 | }
256 |
--------------------------------------------------------------------------------
/draw2dbase/README.md:
--------------------------------------------------------------------------------
1 | draw2d/draw2dbase
2 | =================
3 |
4 | [](http://gocover.io/github.com/llgcode/draw2d/draw2dbase)
5 | [](https://godoc.org/github.com/llgcode/draw2d/draw2dbase)
6 |
7 | Base package implementation that is used by pdf, svg, img, gl implementations.
8 |
--------------------------------------------------------------------------------
/draw2dbase/curve.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 17/05/2011 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | import (
7 | "errors"
8 | "math"
9 | )
10 |
11 | const (
12 | // CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
13 | CurveRecursionLimit = 32
14 | )
15 |
16 | // Cubic
17 | // x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
18 |
19 | // SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
20 | // c1 and c2 parameters are the resulting curves
21 | // length of c, c1 and c2 must be 8 otherwise it panics.
22 | func SubdivideCubic(c, c1, c2 []float64) {
23 | // First point of c is the first point of c1
24 | c1[0], c1[1] = c[0], c[1]
25 | // Last point of c is the last point of c2
26 | c2[6], c2[7] = c[6], c[7]
27 |
28 | // Subdivide segment using midpoints
29 | c1[2] = (c[0] + c[2]) / 2
30 | c1[3] = (c[1] + c[3]) / 2
31 |
32 | midX := (c[2] + c[4]) / 2
33 | midY := (c[3] + c[5]) / 2
34 |
35 | c2[4] = (c[4] + c[6]) / 2
36 | c2[5] = (c[5] + c[7]) / 2
37 |
38 | c1[4] = (c1[2] + midX) / 2
39 | c1[5] = (c1[3] + midY) / 2
40 |
41 | c2[2] = (midX + c2[4]) / 2
42 | c2[3] = (midY + c2[5]) / 2
43 |
44 | c1[6] = (c1[4] + c2[2]) / 2
45 | c1[7] = (c1[5] + c2[3]) / 2
46 |
47 | // Last Point of c1 is equal to the first point of c2
48 | c2[0], c2[1] = c1[6], c1[7]
49 | }
50 |
51 | // TraceCubic generate lines subdividing the cubic curve using a Liner
52 | // flattening_threshold helps determines the flattening expectation of the curve
53 | func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) error {
54 | if len(cubic) < 8 {
55 | return errors.New("cubic length must be >= 8")
56 | }
57 | // Allocation curves
58 | var curves [CurveRecursionLimit * 8]float64
59 | copy(curves[0:8], cubic[0:8])
60 | i := 0
61 |
62 | // current curve
63 | var c []float64
64 |
65 | var dx, dy, d2, d3 float64
66 |
67 | for i >= 0 {
68 | c = curves[i:]
69 | dx = c[6] - c[0]
70 | dy = c[7] - c[1]
71 |
72 | d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx)
73 | d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
74 |
75 | // if it's flat then trace a line
76 | if (d2+d3)*(d2+d3) <= flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-8 {
77 | t.LineTo(c[6], c[7])
78 | i -= 8
79 | } else {
80 | // second half of bezier go lower onto the stack
81 | SubdivideCubic(c, curves[i+8:], curves[i:])
82 | i += 8
83 | }
84 | }
85 | return nil
86 | }
87 |
88 | // Quad
89 | // x1, y1, cpx1, cpy2, x2, y2 float64
90 |
91 | // SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
92 | // c1 and c2 parameters are the resulting curves
93 | // length of c, c1 and c2 must be 6 otherwise it panics.
94 | func SubdivideQuad(c, c1, c2 []float64) {
95 | // First point of c is the first point of c1
96 | c1[0], c1[1] = c[0], c[1]
97 | // Last point of c is the last point of c2
98 | c2[4], c2[5] = c[4], c[5]
99 |
100 | // Subdivide segment using midpoints
101 | c1[2] = (c[0] + c[2]) / 2
102 | c1[3] = (c[1] + c[3]) / 2
103 | c2[2] = (c[2] + c[4]) / 2
104 | c2[3] = (c[3] + c[5]) / 2
105 | c1[4] = (c1[2] + c2[2]) / 2
106 | c1[5] = (c1[3] + c2[3]) / 2
107 | c2[0], c2[1] = c1[4], c1[5]
108 | return
109 | }
110 |
111 | // TraceQuad generate lines subdividing the curve using a Liner
112 | // flattening_threshold helps determines the flattening expectation of the curve
113 | func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) error {
114 | if len(quad) < 6 {
115 | return errors.New("quad length must be >= 6")
116 | }
117 | // Allocates curves stack
118 | var curves [CurveRecursionLimit * 6]float64
119 | copy(curves[0:6], quad[0:6])
120 | i := 0
121 | // current curve
122 | var c []float64
123 | var dx, dy, d float64
124 |
125 | for i >= 0 {
126 | c = curves[i:]
127 | dx = c[4] - c[0]
128 | dy = c[5] - c[1]
129 |
130 | d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
131 |
132 | // if it's flat then trace a line
133 | if (d*d) <= flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-6 {
134 | t.LineTo(c[4], c[5])
135 | i -= 6
136 | } else {
137 | // second half of bezier go lower onto the stack
138 | SubdivideQuad(c, curves[i+6:], curves[i:])
139 | i += 6
140 | }
141 | }
142 | return nil
143 | }
144 |
145 | // TraceArc trace an arc using a Liner
146 | func TraceArc(t Liner, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
147 | end := start + angle
148 | clockWise := true
149 | if angle < 0 {
150 | clockWise = false
151 | }
152 | ra := (math.Abs(rx) + math.Abs(ry)) / 2
153 | da := math.Acos(ra/(ra+0.125/scale)) * 2
154 | //normalize
155 | if !clockWise {
156 | da = -da
157 | }
158 | angle = start + da
159 | var curX, curY float64
160 | for {
161 | if (angle < end-da/4) != clockWise {
162 | curX = x + math.Cos(end)*rx
163 | curY = y + math.Sin(end)*ry
164 | return curX, curY
165 | }
166 | curX = x + math.Cos(angle)*rx
167 | curY = y + math.Sin(angle)*ry
168 |
169 | angle += da
170 | t.LineTo(curX, curY)
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/draw2dbase/curve_test.go:
--------------------------------------------------------------------------------
1 | package draw2dbase
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "image"
7 | "image/color"
8 | "image/draw"
9 | "image/png"
10 | "log"
11 | "os"
12 | "testing"
13 | )
14 |
15 | var (
16 | flatteningThreshold = 0.5
17 | testsCubicFloat64 = []float64{
18 | 100, 100, 200, 100, 100, 200, 200, 200,
19 | 100, 100, 300, 200, 200, 200, 300, 100,
20 | 100, 100, 0, 300, 200, 0, 300, 300,
21 | 150, 290, 10, 10, 290, 10, 150, 290,
22 | 10, 290, 10, 10, 290, 10, 290, 290,
23 | 100, 290, 290, 10, 10, 10, 200, 290,
24 | }
25 | testsQuadFloat64 = []float64{
26 | 100, 100, 200, 100, 200, 200,
27 | 100, 100, 290, 200, 290, 100,
28 | 100, 100, 0, 290, 200, 290,
29 | 150, 290, 10, 10, 290, 290,
30 | 10, 290, 10, 10, 290, 290,
31 | 100, 290, 290, 10, 120, 290,
32 | }
33 | )
34 |
35 | func init() {
36 | os.Mkdir("test_results", 0666)
37 | f, err := os.Create("../output/curve/_test.html")
38 | if err != nil {
39 | log.Println(err)
40 | os.Exit(1)
41 | }
42 | defer f.Close()
43 | log.Printf("Create html viewer")
44 | f.Write([]byte("
"))
45 | for i := 0; i < len(testsCubicFloat64)/8; i++ {
46 | f.Write([]byte(fmt.Sprintf("
\n", i)))
47 | }
48 | for i := 0; i < len(testsQuadFloat64); i++ {
49 | f.Write([]byte(fmt.Sprintf("
\n
\n", i)))
50 | }
51 | f.Write([]byte(""))
52 |
53 | }
54 |
55 | func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image {
56 | for i := 0; i < len(s); i += 2 {
57 | x, y := int(s[i]+0.5), int(s[i+1]+0.5)
58 | img.Set(x, y, c)
59 | img.Set(x, y+1, c)
60 | img.Set(x, y-1, c)
61 | img.Set(x+1, y, c)
62 | img.Set(x+1, y+1, c)
63 | img.Set(x+1, y-1, c)
64 | img.Set(x-1, y, c)
65 | img.Set(x-1, y+1, c)
66 | img.Set(x-1, y-1, c)
67 |
68 | }
69 | return img
70 | }
71 |
72 | func TestCubicCurve(t *testing.T) {
73 | for i := 0; i < len(testsCubicFloat64); i += 8 {
74 | var p SegmentedPath
75 | p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
76 | TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold)
77 | img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
78 | PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...)
79 | PolylineBresenham(img, image.Black, p.Points...)
80 | //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
81 | drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
82 | SaveToPngFile(fmt.Sprintf("../output/curve/_test%d.png", i/8), img)
83 | log.Printf("Num of points: %d\n", len(p.Points))
84 | }
85 | fmt.Println()
86 | }
87 |
88 | func TestQuadCurve(t *testing.T) {
89 | for i := 0; i < len(testsQuadFloat64); i += 6 {
90 | var p SegmentedPath
91 | p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1])
92 | TraceQuad(&p, testsQuadFloat64[i:], flatteningThreshold)
93 | img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
94 | PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...)
95 | PolylineBresenham(img, image.Black, p.Points...)
96 | //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
97 | drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
98 | SaveToPngFile(fmt.Sprintf("../output/curve/_testQuad%d.png", i), img)
99 | log.Printf("Num of points: %d\n", len(p.Points))
100 | }
101 | fmt.Println()
102 | }
103 |
104 | func TestQuadCurveCombinedPoint(t *testing.T) {
105 | var p1 SegmentedPath
106 | TraceQuad(&p1, []float64{0, 0, 0, 0, 0, 0}, flatteningThreshold)
107 | if len(p1.Points) != 2 {
108 | t.Error("It must have one point for this curve", len(p1.Points))
109 | }
110 | var p2 SegmentedPath
111 | TraceQuad(&p2, []float64{0, 0, 100, 100, 0, 0}, flatteningThreshold)
112 | if len(p2.Points) != 2 {
113 | t.Error("It must have one point for this curve", len(p2.Points))
114 | }
115 | }
116 |
117 | func TestCubicCurveCombinedPoint(t *testing.T) {
118 | var p1 SegmentedPath
119 | TraceCubic(&p1, []float64{0, 0, 0, 0, 0, 0, 0, 0}, flatteningThreshold)
120 | if len(p1.Points) != 2 {
121 | t.Error("It must have one point for this curve", len(p1.Points))
122 | }
123 | var p2 SegmentedPath
124 | TraceCubic(&p2, []float64{0, 0, 100, 100, 200, 200, 0, 0}, flatteningThreshold)
125 | if len(p2.Points) != 2 {
126 | t.Error("It must have one point for this curve", len(p2.Points))
127 | }
128 | }
129 |
130 | func BenchmarkCubicCurve(b *testing.B) {
131 | for i := 0; i < b.N; i++ {
132 | for i := 0; i < len(testsCubicFloat64); i += 8 {
133 | var p SegmentedPath
134 | p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
135 | TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold)
136 | }
137 | }
138 | }
139 |
140 | // SaveToPngFile create and save an image to a file using PNG format
141 | func SaveToPngFile(filePath string, m image.Image) error {
142 | // Create the file
143 | f, err := os.Create(filePath)
144 | if err != nil {
145 | return err
146 | }
147 | defer f.Close()
148 | // Create Writer from file
149 | b := bufio.NewWriter(f)
150 | // Write the image into the buffer
151 | err = png.Encode(b, m)
152 | if err != nil {
153 | return err
154 | }
155 | err = b.Flush()
156 | if err != nil {
157 | return err
158 | }
159 | return nil
160 | }
161 |
162 | func TestOutOfRangeTraceCurve(t *testing.T) {
163 | c := []float64{
164 | 100, 100, 200, 100, 100, 200,
165 | }
166 | var p SegmentedPath
167 | TraceCubic(&p, c, flatteningThreshold)
168 | }
169 |
170 | func TestOutOfRangeTraceQuad(t *testing.T) {
171 | c := []float64{
172 | 100, 100, 200, 100,
173 | }
174 | var p SegmentedPath
175 | TraceQuad(&p, c, flatteningThreshold)
176 | }
177 |
--------------------------------------------------------------------------------
/draw2dbase/dasher.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | type DashVertexConverter struct {
7 | next Flattener
8 | x, y, distance float64
9 | dash []float64
10 | currentDash int
11 | dashOffset float64
12 | }
13 |
14 | func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
15 | var dasher DashVertexConverter
16 | dasher.dash = dash
17 | dasher.currentDash = 0
18 | dasher.dashOffset = dashOffset
19 | dasher.next = flattener
20 | return &dasher
21 | }
22 |
23 | func (dasher *DashVertexConverter) LineTo(x, y float64) {
24 | dasher.lineTo(x, y)
25 | }
26 |
27 | func (dasher *DashVertexConverter) MoveTo(x, y float64) {
28 | dasher.next.MoveTo(x, y)
29 | dasher.x, dasher.y = x, y
30 | dasher.distance = dasher.dashOffset
31 | dasher.currentDash = 0
32 | }
33 |
34 | func (dasher *DashVertexConverter) LineJoin() {
35 | dasher.next.LineJoin()
36 | }
37 |
38 | func (dasher *DashVertexConverter) Close() {
39 | dasher.next.Close()
40 | }
41 |
42 | func (dasher *DashVertexConverter) End() {
43 | dasher.next.End()
44 | }
45 |
46 | func (dasher *DashVertexConverter) lineTo(x, y float64) {
47 | rest := dasher.dash[dasher.currentDash] - dasher.distance
48 | for rest < 0 {
49 | dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
50 | dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
51 | rest = dasher.dash[dasher.currentDash] - dasher.distance
52 | }
53 | d := distance(dasher.x, dasher.y, x, y)
54 | for d >= rest {
55 | k := rest / d
56 | lx := dasher.x + k*(x-dasher.x)
57 | ly := dasher.y + k*(y-dasher.y)
58 | if dasher.currentDash%2 == 0 {
59 | // line
60 | dasher.next.LineTo(lx, ly)
61 | } else {
62 | // gap
63 | dasher.next.End()
64 | dasher.next.MoveTo(lx, ly)
65 | }
66 | d = d - rest
67 | dasher.x, dasher.y = lx, ly
68 | dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
69 | rest = dasher.dash[dasher.currentDash]
70 | }
71 | dasher.distance = d
72 | if dasher.currentDash%2 == 0 {
73 | // line
74 | dasher.next.LineTo(x, y)
75 | } else {
76 | // gap
77 | dasher.next.End()
78 | dasher.next.MoveTo(x, y)
79 | }
80 | if dasher.distance >= dasher.dash[dasher.currentDash] {
81 | dasher.distance = dasher.distance - dasher.dash[dasher.currentDash]
82 | dasher.currentDash = (dasher.currentDash + 1) % len(dasher.dash)
83 | }
84 | dasher.x, dasher.y = x, y
85 | }
86 |
87 | func distance(x1, y1, x2, y2 float64) float64 {
88 | return vectorDistance(x2-x1, y2-y1)
89 | }
90 |
--------------------------------------------------------------------------------
/draw2dbase/demux_flattener.go:
--------------------------------------------------------------------------------
1 | package draw2dbase
2 |
3 | type DemuxFlattener struct {
4 | Flatteners []Flattener
5 | }
6 |
7 | func (dc DemuxFlattener) MoveTo(x, y float64) {
8 | for _, flattener := range dc.Flatteners {
9 | flattener.MoveTo(x, y)
10 | }
11 | }
12 |
13 | func (dc DemuxFlattener) LineTo(x, y float64) {
14 | for _, flattener := range dc.Flatteners {
15 | flattener.LineTo(x, y)
16 | }
17 | }
18 |
19 | func (dc DemuxFlattener) LineJoin() {
20 | for _, flattener := range dc.Flatteners {
21 | flattener.LineJoin()
22 | }
23 | }
24 |
25 | func (dc DemuxFlattener) Close() {
26 | for _, flattener := range dc.Flatteners {
27 | flattener.Close()
28 | }
29 | }
30 |
31 | func (dc DemuxFlattener) End() {
32 | for _, flattener := range dc.Flatteners {
33 | flattener.End()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/draw2dbase/flattener.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 06/12/2010 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | import (
7 | "github.com/llgcode/draw2d"
8 | )
9 |
10 | // Liner receive segment definition
11 | type Liner interface {
12 | // LineTo Draw a line from the current position to the point (x, y)
13 | LineTo(x, y float64)
14 | }
15 |
16 | // Flattener receive segment definition
17 | type Flattener interface {
18 | // MoveTo Start a New line from the point (x, y)
19 | MoveTo(x, y float64)
20 | // LineTo Draw a line from the current position to the point (x, y)
21 | LineTo(x, y float64)
22 | // LineJoin use Round, Bevel or miter to join points
23 | LineJoin()
24 | // Close add the most recent starting point to close the path to create a polygon
25 | Close()
26 | // End mark the current line as finished so we can draw caps
27 | End()
28 | }
29 |
30 | // Flatten convert curves into straight segments keeping join segments info
31 | func Flatten(path *draw2d.Path, flattener Flattener, scale float64) {
32 | // First Point
33 | var startX, startY float64 = 0, 0
34 | // Current Point
35 | var x, y float64 = 0, 0
36 | i := 0
37 | for _, cmp := range path.Components {
38 | switch cmp {
39 | case draw2d.MoveToCmp:
40 | x, y = path.Points[i], path.Points[i+1]
41 | startX, startY = x, y
42 | if i != 0 {
43 | flattener.End()
44 | }
45 | flattener.MoveTo(x, y)
46 | i += 2
47 | case draw2d.LineToCmp:
48 | x, y = path.Points[i], path.Points[i+1]
49 | flattener.LineTo(x, y)
50 | flattener.LineJoin()
51 | i += 2
52 | case draw2d.QuadCurveToCmp:
53 | TraceQuad(flattener, path.Points[i-2:], 0.5)
54 | x, y = path.Points[i+2], path.Points[i+3]
55 | flattener.LineTo(x, y)
56 | i += 4
57 | case draw2d.CubicCurveToCmp:
58 | TraceCubic(flattener, path.Points[i-2:], 0.5)
59 | x, y = path.Points[i+4], path.Points[i+5]
60 | flattener.LineTo(x, y)
61 | i += 6
62 | case draw2d.ArcToCmp:
63 | x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale)
64 | flattener.LineTo(x, y)
65 | i += 6
66 | case draw2d.CloseCmp:
67 | flattener.LineTo(startX, startY)
68 | flattener.Close()
69 | }
70 | }
71 | flattener.End()
72 | }
73 |
74 | // Transformer apply the Matrix transformation tr
75 | type Transformer struct {
76 | Tr draw2d.Matrix
77 | Flattener Flattener
78 | }
79 |
80 | func (t Transformer) MoveTo(x, y float64) {
81 | u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
82 | v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
83 | t.Flattener.MoveTo(u, v)
84 | }
85 |
86 | func (t Transformer) LineTo(x, y float64) {
87 | u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
88 | v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
89 | t.Flattener.LineTo(u, v)
90 | }
91 |
92 | func (t Transformer) LineJoin() {
93 | t.Flattener.LineJoin()
94 | }
95 |
96 | func (t Transformer) Close() {
97 | t.Flattener.Close()
98 | }
99 |
100 | func (t Transformer) End() {
101 | t.Flattener.End()
102 | }
103 |
104 | type SegmentedPath struct {
105 | Points []float64
106 | }
107 |
108 | func (p *SegmentedPath) MoveTo(x, y float64) {
109 | p.Points = append(p.Points, x, y)
110 | // TODO need to mark this point as moveto
111 | }
112 |
113 | func (p *SegmentedPath) LineTo(x, y float64) {
114 | p.Points = append(p.Points, x, y)
115 | }
116 |
117 | func (p *SegmentedPath) LineJoin() {
118 | // TODO need to mark the current point as linejoin
119 | }
120 |
121 | func (p *SegmentedPath) Close() {
122 | // TODO Close
123 | }
124 |
125 | func (p *SegmentedPath) End() {
126 | // Nothing to do
127 | }
128 |
--------------------------------------------------------------------------------
/draw2dbase/line.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 The draw2d Authors. All rights reserved.
2 | // created: 27/05/2011 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | import (
7 | "image/color"
8 | "image/draw"
9 | )
10 |
11 | func abs(i int) int {
12 | if i < 0 {
13 | return -i
14 | }
15 | return i
16 | }
17 |
18 | // PolylineBresenham draws a polyline to an image
19 | func PolylineBresenham(img draw.Image, c color.Color, s ...float64) {
20 | for i := 2; i < len(s); i += 2 {
21 | Bresenham(img, c, int(s[i-2]+0.5), int(s[i-1]+0.5), int(s[i]+0.5), int(s[i+1]+0.5))
22 | }
23 | }
24 |
25 | // Bresenham draws a line between (x0, y0) and (x1, y1)
26 | func Bresenham(img draw.Image, color color.Color, x0, y0, x1, y1 int) {
27 | dx := abs(x1 - x0)
28 | dy := abs(y1 - y0)
29 | var sx, sy int
30 | if x0 < x1 {
31 | sx = 1
32 | } else {
33 | sx = -1
34 | }
35 | if y0 < y1 {
36 | sy = 1
37 | } else {
38 | sy = -1
39 | }
40 | err := dx - dy
41 |
42 | var e2 int
43 | for {
44 | img.Set(x0, y0, color)
45 | if x0 == x1 && y0 == y1 {
46 | return
47 | }
48 | e2 = 2 * err
49 | if e2 > -dy {
50 | err = err - dy
51 | x0 = x0 + sx
52 | }
53 | if e2 < dx {
54 | err = err + dx
55 | y0 = y0 + sy
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/draw2dbase/stack_gc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | import (
7 | "fmt"
8 | "image"
9 | "image/color"
10 |
11 | "github.com/llgcode/draw2d"
12 |
13 | "github.com/golang/freetype/truetype"
14 | )
15 |
16 | var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
17 |
18 | type StackGraphicContext struct {
19 | Current *ContextStack
20 | }
21 |
22 | type ContextStack struct {
23 | Tr draw2d.Matrix
24 | Path *draw2d.Path
25 | LineWidth float64
26 | Dash []float64
27 | DashOffset float64
28 | StrokeColor color.Color
29 | FillColor color.Color
30 | FillRule draw2d.FillRule
31 | Cap draw2d.LineCap
32 | Join draw2d.LineJoin
33 | FontSize float64
34 | FontData draw2d.FontData
35 |
36 | Font *truetype.Font
37 | // fontSize and dpi are used to calculate scale. scale is the number of
38 | // 26.6 fixed point units in 1 em.
39 | Scale float64
40 |
41 | Previous *ContextStack
42 | }
43 |
44 | // GetFontName gets the current FontData with fontSize as a string
45 | func (cs *ContextStack) GetFontName() string {
46 | fontData := cs.FontData
47 | return fmt.Sprintf("%s:%d:%d:%9.2f", fontData.Name, fontData.Family, fontData.Style, cs.FontSize)
48 | }
49 |
50 |
51 | /**
52 | * Create a new Graphic context from an image
53 | */
54 | func NewStackGraphicContext() *StackGraphicContext {
55 | gc := &StackGraphicContext{}
56 | gc.Current = new(ContextStack)
57 | gc.Current.Tr = draw2d.NewIdentityMatrix()
58 | gc.Current.Path = new(draw2d.Path)
59 | gc.Current.LineWidth = 1.0
60 | gc.Current.StrokeColor = image.Black
61 | gc.Current.FillColor = image.White
62 | gc.Current.Cap = draw2d.RoundCap
63 | gc.Current.FillRule = draw2d.FillRuleEvenOdd
64 | gc.Current.Join = draw2d.RoundJoin
65 | gc.Current.FontSize = 10
66 | gc.Current.FontData = DefaultFontData
67 | return gc
68 | }
69 |
70 | func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix {
71 | return gc.Current.Tr
72 | }
73 |
74 | func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) {
75 | gc.Current.Tr = Tr
76 | }
77 |
78 | func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) {
79 | gc.Current.Tr.Compose(Tr)
80 | }
81 |
82 | func (gc *StackGraphicContext) Rotate(angle float64) {
83 | gc.Current.Tr.Rotate(angle)
84 | }
85 |
86 | func (gc *StackGraphicContext) Translate(tx, ty float64) {
87 | gc.Current.Tr.Translate(tx, ty)
88 | }
89 |
90 | func (gc *StackGraphicContext) Scale(sx, sy float64) {
91 | gc.Current.Tr.Scale(sx, sy)
92 | }
93 |
94 | func (gc *StackGraphicContext) SetStrokeColor(c color.Color) {
95 | gc.Current.StrokeColor = c
96 | }
97 |
98 | func (gc *StackGraphicContext) SetFillColor(c color.Color) {
99 | gc.Current.FillColor = c
100 | }
101 |
102 | func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) {
103 | gc.Current.FillRule = f
104 | }
105 |
106 | func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) {
107 | gc.Current.LineWidth = lineWidth
108 | }
109 |
110 | func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) {
111 | gc.Current.Cap = cap
112 | }
113 |
114 | func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) {
115 | gc.Current.Join = join
116 | }
117 |
118 | func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) {
119 | gc.Current.Dash = dash
120 | gc.Current.DashOffset = dashOffset
121 | }
122 |
123 | func (gc *StackGraphicContext) SetFontSize(fontSize float64) {
124 | gc.Current.FontSize = fontSize
125 | }
126 |
127 | func (gc *StackGraphicContext) GetFontSize() float64 {
128 | return gc.Current.FontSize
129 | }
130 |
131 | func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) {
132 | gc.Current.FontData = fontData
133 | }
134 |
135 | func (gc *StackGraphicContext) GetFontData() draw2d.FontData {
136 | return gc.Current.FontData
137 | }
138 |
139 | func (gc *StackGraphicContext) BeginPath() {
140 | gc.Current.Path.Clear()
141 | }
142 |
143 | func (gc *StackGraphicContext) GetPath() draw2d.Path {
144 | return *gc.Current.Path.Copy()
145 | }
146 |
147 | func (gc *StackGraphicContext) IsEmpty() bool {
148 | return gc.Current.Path.IsEmpty()
149 | }
150 |
151 | func (gc *StackGraphicContext) LastPoint() (float64, float64) {
152 | return gc.Current.Path.LastPoint()
153 | }
154 |
155 | func (gc *StackGraphicContext) MoveTo(x, y float64) {
156 | gc.Current.Path.MoveTo(x, y)
157 | }
158 |
159 | func (gc *StackGraphicContext) LineTo(x, y float64) {
160 | gc.Current.Path.LineTo(x, y)
161 | }
162 |
163 | func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) {
164 | gc.Current.Path.QuadCurveTo(cx, cy, x, y)
165 | }
166 |
167 | func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
168 | gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
169 | }
170 |
171 | func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
172 | gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle)
173 | }
174 |
175 | func (gc *StackGraphicContext) Close() {
176 | gc.Current.Path.Close()
177 | }
178 |
179 | func (gc *StackGraphicContext) Save() {
180 | context := new(ContextStack)
181 | context.FontSize = gc.Current.FontSize
182 | context.FontData = gc.Current.FontData
183 | context.LineWidth = gc.Current.LineWidth
184 | context.StrokeColor = gc.Current.StrokeColor
185 | context.FillColor = gc.Current.FillColor
186 | context.FillRule = gc.Current.FillRule
187 | context.Dash = gc.Current.Dash
188 | context.DashOffset = gc.Current.DashOffset
189 | context.Cap = gc.Current.Cap
190 | context.Join = gc.Current.Join
191 | context.Path = gc.Current.Path.Copy()
192 | context.Font = gc.Current.Font
193 | context.Scale = gc.Current.Scale
194 | copy(context.Tr[:], gc.Current.Tr[:])
195 | context.Previous = gc.Current
196 | gc.Current = context
197 | }
198 |
199 | func (gc *StackGraphicContext) Restore() {
200 | if gc.Current.Previous != nil {
201 | oldContext := gc.Current
202 | gc.Current = gc.Current.Previous
203 | oldContext.Previous = nil
204 | }
205 | }
206 |
207 | func (gc *StackGraphicContext) GetFontName() string {
208 | return gc.Current.GetFontName()
209 | }
210 |
--------------------------------------------------------------------------------
/draw2dbase/stroker.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | package draw2dbase
5 |
6 | import (
7 | "math"
8 |
9 | "github.com/llgcode/draw2d"
10 | )
11 |
12 | type LineStroker struct {
13 | Flattener Flattener
14 | HalfLineWidth float64
15 | Cap draw2d.LineCap
16 | Join draw2d.LineJoin
17 | vertices []float64
18 | rewind []float64
19 | x, y, nx, ny float64
20 | }
21 |
22 | func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener Flattener) *LineStroker {
23 | l := new(LineStroker)
24 | l.Flattener = flattener
25 | l.HalfLineWidth = 0.5
26 | l.Cap = c
27 | l.Join = j
28 | return l
29 | }
30 |
31 | func (l *LineStroker) MoveTo(x, y float64) {
32 | l.x, l.y = x, y
33 | }
34 |
35 | func (l *LineStroker) LineTo(x, y float64) {
36 | l.line(l.x, l.y, x, y)
37 | }
38 |
39 | func (l *LineStroker) LineJoin() {
40 |
41 | }
42 |
43 | func (l *LineStroker) line(x1, y1, x2, y2 float64) {
44 | dx := (x2 - x1)
45 | dy := (y2 - y1)
46 | d := vectorDistance(dx, dy)
47 | if d != 0 {
48 | nx := dy * l.HalfLineWidth / d
49 | ny := -(dx * l.HalfLineWidth / d)
50 | l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny)
51 | l.x, l.y, l.nx, l.ny = x2, y2, nx, ny
52 | }
53 | }
54 |
55 | func (l *LineStroker) Close() {
56 | if len(l.vertices) > 1 {
57 | l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1])
58 | }
59 | }
60 |
61 | func (l *LineStroker) End() {
62 | if len(l.vertices) > 1 {
63 | l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
64 | for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
65 | l.Flattener.LineTo(l.vertices[i], l.vertices[j])
66 | }
67 | }
68 | for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
69 | l.Flattener.LineTo(l.rewind[i], l.rewind[j])
70 | }
71 | if len(l.vertices) > 1 {
72 | l.Flattener.LineTo(l.vertices[0], l.vertices[1])
73 | }
74 | l.Flattener.End()
75 | // reinit vertices
76 | l.vertices = l.vertices[0:0]
77 | l.rewind = l.rewind[0:0]
78 | l.x, l.y, l.nx, l.ny = 0, 0, 0, 0
79 |
80 | }
81 |
82 | func (l *LineStroker) appendVertex(vertices ...float64) {
83 | s := len(vertices) / 2
84 | l.vertices = append(l.vertices, vertices[:s]...)
85 | l.rewind = append(l.rewind, vertices[s:]...)
86 | }
87 |
88 | func vectorDistance(dx, dy float64) float64 {
89 | return float64(math.Sqrt(dx*dx + dy*dy))
90 | }
91 |
--------------------------------------------------------------------------------
/draw2dbase/text.go:
--------------------------------------------------------------------------------
1 | package draw2dbase
2 |
3 | import "github.com/llgcode/draw2d"
4 |
5 | // GlyphCache manage a cache of glyphs
6 | type GlyphCache interface {
7 | // Fetch fetches a glyph from the cache, storing with Render first if it doesn't already exist
8 | Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph
9 | }
10 |
11 | // GlyphCacheImp manage a map of glyphs without sync mecanism, not thread safe
12 | type GlyphCacheImp struct {
13 | glyphs map[string]map[rune]*Glyph
14 | }
15 |
16 |
17 | // NewGlyphCache initializes a GlyphCache
18 | func NewGlyphCache() *GlyphCacheImp {
19 | glyphs := make(map[string]map[rune]*Glyph)
20 | return &GlyphCacheImp {
21 | glyphs: glyphs,
22 | }
23 | }
24 |
25 | // Fetch fetches a glyph from the cache, calling renderGlyph first if it doesn't already exist
26 | func (glyphCache *GlyphCacheImp) Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
27 | if glyphCache.glyphs[fontName] == nil {
28 | glyphCache.glyphs[fontName] = make(map[rune]*Glyph, 60)
29 | }
30 | if glyphCache.glyphs[fontName][chr] == nil {
31 | glyphCache.glyphs[fontName][chr] = renderGlyph(gc, fontName, chr)
32 | }
33 | return glyphCache.glyphs[fontName][chr].Copy()
34 | }
35 |
36 | // renderGlyph renders a glyph then caches and returns it
37 | func renderGlyph(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
38 | gc.Save()
39 | defer gc.Restore()
40 | gc.BeginPath()
41 | width := gc.CreateStringPath(string(chr), 0, 0)
42 | path := gc.GetPath()
43 | return &Glyph{
44 | Path: &path,
45 | Width: width,
46 | }
47 | }
48 |
49 | // Glyph represents a rune which has been converted to a Path and width
50 | type Glyph struct {
51 | // path represents a glyph, it is always at (0, 0)
52 | Path *draw2d.Path
53 | // Width of the glyph
54 | Width float64
55 | }
56 |
57 | // Copy Returns a copy of a Glyph
58 | func (g *Glyph) Copy() *Glyph {
59 | return &Glyph{
60 | Path: g.Path.Copy(),
61 | Width: g.Width,
62 | }
63 | }
64 |
65 | // Fill copies a glyph from the cache, and fills it
66 | func (g *Glyph) Fill(gc draw2d.GraphicContext, x, y float64) float64 {
67 | gc.Save()
68 | gc.BeginPath()
69 | gc.Translate(x, y)
70 | gc.Fill(g.Path)
71 | gc.Restore()
72 | return g.Width
73 | }
74 |
75 | // Stroke fetches a glyph from the cache, and strokes it
76 | func (g *Glyph) Stroke(gc draw2d.GraphicContext, x, y float64) float64 {
77 | gc.Save()
78 | gc.BeginPath()
79 | gc.Translate(x, y)
80 | gc.Stroke(g.Path)
81 | gc.Restore()
82 | return g.Width
83 | }
84 |
--------------------------------------------------------------------------------
/draw2dgl/doc.go:
--------------------------------------------------------------------------------
1 | // Package draw2dgl provides a graphic context that can draw vector
2 | // graphics and text on OpenGL.
3 | package draw2dgl
4 |
--------------------------------------------------------------------------------
/draw2dgl/gc.go:
--------------------------------------------------------------------------------
1 | package draw2dgl
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "image/draw"
7 | "log"
8 | "math"
9 | "runtime"
10 |
11 | "github.com/go-gl/gl/v2.1/gl"
12 | "github.com/golang/freetype/raster"
13 | "github.com/golang/freetype/truetype"
14 | "github.com/llgcode/draw2d"
15 | "github.com/llgcode/draw2d/draw2dbase"
16 | "github.com/llgcode/draw2d/draw2dimg"
17 |
18 | "golang.org/x/image/font"
19 | "golang.org/x/image/math/fixed"
20 | )
21 |
22 | func init() {
23 | runtime.LockOSThread()
24 | }
25 |
26 | type Painter struct {
27 | // The Porter-Duff composition operator.
28 | Op draw.Op
29 | // The 16-bit color to paint the spans.
30 | cr, cg, cb uint8
31 | ca uint32
32 | colors []uint8
33 | vertices []int32
34 | }
35 |
36 | const M16 uint32 = 1<<16 - 1
37 |
38 | // Paint satisfies the Painter interface by painting ss onto an image.RGBA.
39 | func (p *Painter) Paint(ss []raster.Span, done bool) {
40 | //gl.Begin(gl.LINES)
41 | sslen := len(ss)
42 | clenrequired := sslen * 8
43 | vlenrequired := sslen * 4
44 | if clenrequired >= (cap(p.colors) - len(p.colors)) {
45 | p.Flush()
46 |
47 | if clenrequired >= cap(p.colors) {
48 | p.vertices = make([]int32, 0, vlenrequired+(vlenrequired/2))
49 | p.colors = make([]uint8, 0, clenrequired+(clenrequired/2))
50 | }
51 | }
52 | vi := len(p.vertices)
53 | ci := len(p.colors)
54 | p.vertices = p.vertices[0 : vi+vlenrequired]
55 | p.colors = p.colors[0 : ci+clenrequired]
56 | var (
57 | colors []uint8
58 | vertices []int32
59 | )
60 | for _, s := range ss {
61 | a := uint8((s.Alpha * p.ca / M16) >> 8)
62 |
63 | colors = p.colors[ci:]
64 | colors[0] = p.cr
65 | colors[1] = p.cg
66 | colors[2] = p.cb
67 | colors[3] = a
68 | colors[4] = p.cr
69 | colors[5] = p.cg
70 | colors[6] = p.cb
71 | colors[7] = a
72 | ci += 8
73 | vertices = p.vertices[vi:]
74 | vertices[0] = int32(s.X0)
75 | vertices[1] = int32(s.Y)
76 | vertices[2] = int32(s.X1)
77 | vertices[3] = int32(s.Y)
78 | vi += 4
79 | }
80 | }
81 |
82 | func (p *Painter) Flush() {
83 | if len(p.vertices) != 0 {
84 | gl.EnableClientState(gl.COLOR_ARRAY)
85 | gl.EnableClientState(gl.VERTEX_ARRAY)
86 | gl.ColorPointer(4, gl.UNSIGNED_BYTE, 0, gl.Ptr(p.colors))
87 | gl.VertexPointer(2, gl.INT, 0, gl.Ptr(p.vertices))
88 |
89 | // draw lines
90 | gl.DrawArrays(gl.LINES, 0, int32(len(p.vertices)/2))
91 | gl.DisableClientState(gl.VERTEX_ARRAY)
92 | gl.DisableClientState(gl.COLOR_ARRAY)
93 | p.vertices = p.vertices[0:0]
94 | p.colors = p.colors[0:0]
95 | }
96 | }
97 |
98 | // SetColor sets the color to paint the spans.
99 | func (p *Painter) SetColor(c color.Color) {
100 | r, g, b, a := c.RGBA()
101 | if a == 0 {
102 | p.cr = 0
103 | p.cg = 0
104 | p.cb = 0
105 | p.ca = a
106 | } else {
107 | p.cr = uint8((r * M16 / a) >> 8)
108 | p.cg = uint8((g * M16 / a) >> 8)
109 | p.cb = uint8((b * M16 / a) >> 8)
110 | p.ca = a
111 | }
112 | }
113 |
114 | // NewRGBAPainter creates a new RGBAPainter for the given image.
115 | func NewPainter() *Painter {
116 | p := new(Painter)
117 | p.vertices = make([]int32, 0, 1024)
118 | p.colors = make([]uint8, 0, 1024)
119 | return p
120 | }
121 |
122 | type GraphicContext struct {
123 | *draw2dbase.StackGraphicContext
124 | painter *Painter
125 | fillRasterizer *raster.Rasterizer
126 | strokeRasterizer *raster.Rasterizer
127 | FontCache draw2d.FontCache
128 | glyphCache draw2dbase.GlyphCache
129 | glyphBuf *truetype.GlyphBuf
130 | DPI int
131 | }
132 |
133 | // NewGraphicContext creates a new Graphic context from an image.
134 | func NewGraphicContext(width, height int) *GraphicContext {
135 | gc := &GraphicContext{
136 | draw2dbase.NewStackGraphicContext(),
137 | NewPainter(),
138 | raster.NewRasterizer(width, height),
139 | raster.NewRasterizer(width, height),
140 | draw2d.GetGlobalFontCache(),
141 | draw2dbase.NewGlyphCache(),
142 | &truetype.GlyphBuf{},
143 | 92,
144 | }
145 | return gc
146 | }
147 |
148 | func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
149 | font, err := gc.FontCache.Load(gc.Current.FontData)
150 | if err != nil {
151 | font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
152 | }
153 | if font != nil {
154 | gc.SetFont(font)
155 | gc.SetFontSize(gc.Current.FontSize)
156 | }
157 | return font, err
158 | }
159 |
160 | func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
161 | if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
162 | return err
163 | }
164 | e0 := 0
165 | for _, e1 := range gc.glyphBuf.Ends {
166 | DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
167 | e0 = e1
168 | }
169 | return nil
170 | }
171 |
172 | // CreateStringPath creates a path from the string s at x, y, and returns the string width.
173 | // The text is placed so that the left edge of the em square of the first character of s
174 | // and the baseline intersect at x, y. The majority of the affected pixels will be
175 | // above and to the right of the point, but some may be below or to the left.
176 | // For example, drawing a string that starts with a 'J' in an italic font may
177 | // affect pixels below and left of the point.
178 | func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
179 | f, err := gc.loadCurrentFont()
180 | if err != nil {
181 | log.Println(err)
182 | return 0.0
183 | }
184 | startx := x
185 | prev, hasPrev := truetype.Index(0), false
186 | for _, rune := range s {
187 | index := f.Index(rune)
188 | if hasPrev {
189 | x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
190 | }
191 | err := gc.drawGlyph(index, x, y)
192 | if err != nil {
193 | log.Println(err)
194 | return startx - x
195 | }
196 | x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
197 | prev, hasPrev = index, true
198 | }
199 | return x - startx
200 | }
201 |
202 | // FillString draws the text at point (0, 0)
203 | func (gc *GraphicContext) FillString(text string) (width float64) {
204 | return gc.FillStringAt(text, 0, 0)
205 | }
206 |
207 | // FillStringAt draws the text at the specified point (x, y)
208 | func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
209 | f, err := gc.loadCurrentFont()
210 | if err != nil {
211 | log.Println(err)
212 | return 0.0
213 | }
214 | startx := x
215 | prev, hasPrev := truetype.Index(0), false
216 | fontName := gc.GetFontName()
217 | for _, r := range text {
218 | index := f.Index(r)
219 | if hasPrev {
220 | x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
221 | }
222 | glyph := gc.glyphCache.Fetch(gc, fontName, r)
223 | x += glyph.Fill(gc, x, y)
224 | prev, hasPrev = index, true
225 | }
226 | return x - startx
227 | }
228 |
229 | // GetStringBounds returns the approximate pixel bounds of the string s at x, y.
230 | // The the left edge of the em square of the first character of s
231 | // and the baseline intersect at 0, 0 in the returned coordinates.
232 | // Therefore the top and left coordinates may well be negative.
233 | func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
234 | f, err := gc.loadCurrentFont()
235 | if err != nil {
236 | log.Println(err)
237 | return 0, 0, 0, 0
238 | }
239 | top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
240 | cursor := 0.0
241 | prev, hasPrev := truetype.Index(0), false
242 | for _, rune := range s {
243 | index := f.Index(rune)
244 | if hasPrev {
245 | cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
246 | }
247 | if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
248 | log.Println(err)
249 | return 0, 0, 0, 0
250 | }
251 | e0 := 0
252 | for _, e1 := range gc.glyphBuf.Ends {
253 | ps := gc.glyphBuf.Points[e0:e1]
254 | for _, p := range ps {
255 | x, y := pointToF64Point(p)
256 | top = math.Min(top, y)
257 | bottom = math.Max(bottom, y)
258 | left = math.Min(left, x+cursor)
259 | right = math.Max(right, x+cursor)
260 | }
261 | }
262 | cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
263 | prev, hasPrev = index, true
264 | }
265 | return left, top, right, bottom
266 | }
267 |
268 | // StrokeString draws the contour of the text at point (0, 0)
269 | func (gc *GraphicContext) StrokeString(text string) (width float64) {
270 | return gc.StrokeStringAt(text, 0, 0)
271 | }
272 |
273 | // StrokeStringAt draws the contour of the text at point (x, y)
274 | func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
275 | f, err := gc.loadCurrentFont()
276 | if err != nil {
277 | log.Println(err)
278 | return 0.0
279 | }
280 | startx := x
281 | prev, hasPrev := truetype.Index(0), false
282 | fontName := gc.GetFontName()
283 | for _, r := range text {
284 | index := f.Index(r)
285 | if hasPrev {
286 | x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
287 | }
288 | glyph := gc.glyphCache.Fetch(gc, fontName, r)
289 | x += glyph.Stroke(gc, x, y)
290 | prev, hasPrev = index, true
291 | }
292 | return x - startx
293 | }
294 |
295 | // recalc recalculates scale and bounds values from the font size, screen
296 | // resolution and font metrics, and invalidates the glyph cache.
297 | func (gc *GraphicContext) recalc() {
298 | gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
299 | }
300 |
301 | func (gc *GraphicContext) SetDPI(dpi int) {
302 | gc.DPI = dpi
303 | gc.recalc()
304 | }
305 |
306 | // SetFont sets the font used to draw text.
307 | func (gc *GraphicContext) SetFont(font *truetype.Font) {
308 | gc.Current.Font = font
309 | }
310 |
311 | // SetFontSize sets the font size in points (as in ``a 12 point font'').
312 | func (gc *GraphicContext) SetFontSize(fontSize float64) {
313 | gc.Current.FontSize = fontSize
314 | gc.recalc()
315 | }
316 |
317 | func (gc *GraphicContext) GetDPI() int {
318 | return gc.DPI
319 | }
320 |
321 | //TODO
322 | func (gc *GraphicContext) Clear() {
323 | panic("not implemented")
324 | }
325 |
326 | //TODO
327 | func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
328 | panic("not implemented")
329 | }
330 |
331 | //TODO
332 | func (gc *GraphicContext) DrawImage(img image.Image) {
333 | panic("not implemented")
334 | }
335 |
336 | func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
337 | gc.painter.SetColor(color)
338 | rasterizer.Rasterize(gc.painter)
339 | rasterizer.Clear()
340 | gc.painter.Flush()
341 | gc.Current.Path.Clear()
342 | }
343 |
344 | func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
345 | paths = append(paths, gc.Current.Path)
346 | gc.strokeRasterizer.UseNonZeroWinding = true
347 |
348 | stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
349 | stroker.HalfLineWidth = gc.Current.LineWidth / 2
350 |
351 | var liner draw2dbase.Flattener
352 | if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
353 | liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
354 | } else {
355 | liner = stroker
356 | }
357 | for _, p := range paths {
358 | draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
359 | }
360 |
361 | gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
362 | }
363 |
364 | func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
365 | paths = append(paths, gc.Current.Path)
366 | gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
367 |
368 | /**** first method ****/
369 | flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
370 | for _, p := range paths {
371 | draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
372 | }
373 |
374 | gc.paint(gc.fillRasterizer, gc.Current.FillColor)
375 | }
376 |
377 | func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
378 | paths = append(paths, gc.Current.Path)
379 | gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
380 | gc.strokeRasterizer.UseNonZeroWinding = true
381 |
382 | flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}}
383 |
384 | stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}})
385 | stroker.HalfLineWidth = gc.Current.LineWidth / 2
386 |
387 | var liner draw2dbase.Flattener
388 | if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
389 | liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
390 | } else {
391 | liner = stroker
392 | }
393 |
394 | demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
395 | for _, p := range paths {
396 | draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
397 | }
398 |
399 | // Fill
400 | gc.paint(gc.fillRasterizer, gc.Current.FillColor)
401 | // Stroke
402 | gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
403 | }
404 |
405 | func useNonZeroWinding(f draw2d.FillRule) bool {
406 | switch f {
407 | case draw2d.FillRuleEvenOdd:
408 | return false
409 | case draw2d.FillRuleWinding:
410 | return true
411 | }
412 | return false
413 | }
414 |
--------------------------------------------------------------------------------
/draw2dgl/notes.md:
--------------------------------------------------------------------------------
1 | Rendering Vector Art OpenGl
2 | References:
3 | * https://www.youtube.com/watch?v=K5u8ZZBWSdw
4 | * http://http.developer.nvidia.com/GPUGems3/gpugems3_ch25.html
5 | * http://research.microsoft.com/en-us/um/people/cloop/loopblinn05.pdf
6 | * https://github.com/openframeworks/openFrameworks/issues/1190
7 |
--------------------------------------------------------------------------------
/draw2dgl/text.go:
--------------------------------------------------------------------------------
1 | package draw2dgl
2 |
3 | import (
4 | "github.com/golang/freetype/truetype"
5 | "github.com/llgcode/draw2d"
6 |
7 | "golang.org/x/image/math/fixed"
8 | )
9 |
10 | // DrawContour draws the given closed contour at the given sub-pixel offset.
11 | func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
12 | if len(ps) == 0 {
13 | return
14 | }
15 | startX, startY := pointToF64Point(ps[0])
16 | var others []truetype.Point
17 | if ps[0].Flags&0x01 != 0 {
18 | others = ps[1:]
19 | } else {
20 | lastX, lastY := pointToF64Point(ps[len(ps)-1])
21 | if ps[len(ps)-1].Flags&0x01 != 0 {
22 | startX, startY = lastX, lastY
23 | others = ps[:len(ps)-1]
24 | } else {
25 | startX = (startX + lastX) / 2
26 | startY = (startY + lastY) / 2
27 | others = ps
28 | }
29 | }
30 | path.MoveTo(startX+dx, startY+dy)
31 | q0X, q0Y, on0 := startX, startY, true
32 | for _, p := range others {
33 | qX, qY := pointToF64Point(p)
34 | on := p.Flags&0x01 != 0
35 | if on {
36 | if on0 {
37 | path.LineTo(qX+dx, qY+dy)
38 | } else {
39 | path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
40 | }
41 | } else {
42 | if on0 {
43 | // No-op.
44 | } else {
45 | midX := (q0X + qX) / 2
46 | midY := (q0Y + qY) / 2
47 | path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
48 | }
49 | }
50 | q0X, q0Y, on0 = qX, qY, on
51 | }
52 | // Close the curve.
53 | if on0 {
54 | path.LineTo(startX+dx, startY+dy)
55 | } else {
56 | path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
57 | }
58 | }
59 |
60 | func pointToF64Point(p truetype.Point) (x, y float64) {
61 | return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
62 | }
63 |
64 | func fUnitsToFloat64(x fixed.Int26_6) float64 {
65 | scaled := x << 2
66 | return float64(scaled/256) + float64(scaled%256)/256.0
67 | }
68 |
69 | // FontExtents contains font metric information.
70 | type FontExtents struct {
71 | // Ascent is the distance that the text
72 | // extends above the baseline.
73 | Ascent float64
74 |
75 | // Descent is the distance that the text
76 | // extends below the baseline. The descent
77 | // is given as a negative value.
78 | Descent float64
79 |
80 | // Height is the distance from the lowest
81 | // descending point to the highest ascending
82 | // point.
83 | Height float64
84 | }
85 |
86 | // Extents returns the FontExtents for a font.
87 | // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
88 | func Extents(font *truetype.Font, size float64) FontExtents {
89 | bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
90 | scale := size / float64(font.FUnitsPerEm())
91 | return FontExtents{
92 | Ascent: float64(bounds.Max.Y) * scale,
93 | Descent: float64(bounds.Min.Y) * scale,
94 | Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/draw2dimg/README.md:
--------------------------------------------------------------------------------
1 | draw2d/draw2dimg
2 | =================
3 |
4 | [](http://gocover.io/github.com/llgcode/draw2d/draw2dimg)
5 | [](https://godoc.org/github.com/llgcode/draw2d/draw2dimg)
6 |
7 |
8 | draw2d implementation that generates raster images using https://github.com/golang/freetype package.
9 |
--------------------------------------------------------------------------------
/draw2dimg/curve_limit_test.go:
--------------------------------------------------------------------------------
1 | package draw2dimg
2 |
3 | import (
4 | "fmt"
5 | "github.com/golang/freetype/truetype"
6 | "github.com/llgcode/draw2d"
7 | "github.com/llgcode/draw2d/draw2dkit"
8 | "golang.org/x/image/font/gofont/goregular"
9 | "image"
10 | "image/color"
11 | "testing"
12 | )
13 |
14 | // font generated from icomoon.io and converted to go byte slice
15 | // contains only two glyphs
16 | // \u2716 - which should look like a cross
17 | // \u25cb - which should look like an empty circle
18 | var icoTTF = []byte{
19 | 0, 1, 0, 0, 0, 12, 0, 128, 0, 3, 0, 64, 71, 83, 85, 66, 219, 7, 221, 185,
20 | 0, 0, 0, 204, 0, 0, 0, 188, 79, 83, 47, 50, 175, 17, 51, 150, 0, 0, 1, 136,
21 | 0, 0, 0, 96, 99, 109, 97, 112, 37, 204, 43, 67, 0, 0, 1, 232, 0, 0, 0, 148,
22 | 103, 97, 115, 112, 0, 0, 0, 16, 0, 0, 2, 124, 0, 0, 0, 8, 103, 108, 121, 102,
23 | 163, 112, 233, 32, 0, 0, 2, 132, 0, 0, 3, 64, 104, 101, 97, 100, 15, 49, 194, 135,
24 | 0, 0, 5, 196, 0, 0, 0, 54, 104, 104, 101, 97, 7, 194, 3, 217, 0, 0, 5, 252,
25 | 0, 0, 0, 36, 104, 109, 116, 120, 14, 0, 0, 2, 0, 0, 6, 32, 0, 0, 0, 96,
26 | 108, 111, 99, 97, 6, 168, 5, 226, 0, 0, 6, 128, 0, 0, 0, 50, 109, 97, 120, 112,
27 | 0, 27, 0, 86, 0, 0, 6, 180, 0, 0, 0, 32, 110, 97, 109, 101, 108, 36, 213, 69,
28 | 0, 0, 6, 212, 0, 0, 1, 170, 112, 111, 115, 116, 0, 3, 0, 0, 0, 0, 8, 128,
29 | 0, 0, 0, 32, 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 108, 97, 116, 110,
30 | 0, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 108, 105, 103, 97,
31 | 0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 4, 0, 0, 0, 1, 0, 10,
32 | 0, 0, 0, 1, 0, 12, 0, 3, 0, 22, 0, 54, 0, 120, 0, 1, 0, 3, 0, 8,
33 | 0, 17, 0, 23, 0, 2, 0, 6, 0, 18, 0, 22, 0, 5, 0, 17, 0, 16, 0, 18,
34 | 0, 18, 0, 22, 0, 6, 0, 6, 0, 15, 0, 8, 0, 10, 0, 14, 0, 2, 0, 6,
35 | 0, 38, 0, 21, 0, 15, 0, 6, 0, 9, 0, 12, 0, 16, 0, 4, 0, 20, 0, 15,
36 | 0, 8, 0, 11, 0, 10, 0, 8, 0, 13, 0, 10, 0, 9, 0, 21, 0, 13, 0, 6,
37 | 0, 9, 0, 12, 0, 16, 0, 4, 0, 7, 0, 20, 0, 19, 0, 19, 0, 16, 0, 15,
38 | 0, 5, 0, 1, 0, 4, 0, 22, 0, 2, 0, 23, 0, 3, 3, 85, 1, 144, 0, 5,
39 | 0, 0, 2, 153, 2, 204, 0, 0, 0, 143, 2, 153, 2, 204, 0, 0, 1, 235, 0, 51,
40 | 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
41 | 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 39, 23,
42 | 3, 192, 255, 192, 0, 64, 3, 192, 0, 64, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
43 | 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 28,
44 | 0, 1, 0, 3, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 28, 0, 4, 0, 120,
45 | 0, 0, 0, 26, 0, 16, 0, 3, 0, 10, 0, 1, 0, 32, 0, 45, 0, 51, 0, 101,
46 | 0, 105, 0, 108, 0, 111, 0, 117, 37, 203, 39, 23, 255, 253, 255, 255, 0, 0, 0, 0,
47 | 0, 32, 0, 45, 0, 51, 0, 97, 0, 104, 0, 107, 0, 110, 0, 114, 37, 203, 39, 22,
48 | 255, 253, 255, 255, 0, 1, 255, 227, 255, 215, 255, 210, 255, 165, 255, 163, 255, 162, 255, 161,
49 | 255, 159, 218, 74, 217, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
50 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
51 | 255, 255, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
52 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
53 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
54 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
55 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
56 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
57 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
58 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
59 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
60 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
61 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
62 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
63 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
64 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
65 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
66 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
67 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
68 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
69 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
70 | 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
71 | 1, 0, 0, 0, 0, 2, 0, 0, 255, 192, 4, 0, 3, 192, 0, 27, 0, 55, 0, 0,
72 | 1, 34, 7, 14, 1, 7, 6, 21, 20, 23, 30, 1, 23, 22, 51, 50, 55, 62, 1, 55,
73 | 54, 53, 52, 39, 46, 1, 39, 38, 3, 34, 39, 46, 1, 39, 38, 53, 52, 55, 62, 1,
74 | 55, 54, 51, 50, 23, 30, 1, 23, 22, 21, 20, 7, 14, 1, 7, 6, 2, 0, 106, 93,
75 | 94, 139, 40, 40, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40, 40, 139, 94,
76 | 93, 106, 80, 69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30,
77 | 30, 30, 105, 70, 69, 3, 192, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40,
78 | 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 252, 128, 30, 30, 105, 70, 69, 80, 80,
79 | 69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30, 0, 0, 0,
80 | 0, 1, 0, 2, 255, 194, 3, 254, 3, 190, 0, 83, 0, 0, 37, 56, 1, 49, 9, 1,
81 | 56, 1, 49, 62, 1, 55, 54, 38, 47, 1, 46, 1, 7, 14, 1, 7, 56, 1, 49, 9,
82 | 1, 56, 1, 49, 46, 1, 39, 38, 6, 15, 1, 14, 1, 23, 30, 1, 23, 56, 1, 49,
83 | 9, 1, 56, 1, 49, 14, 1, 7, 6, 22, 31, 1, 30, 1, 55, 62, 1, 55, 56, 1,
84 | 49, 9, 1, 56, 1, 49, 30, 1, 23, 22, 54, 63, 1, 62, 1, 39, 46, 1, 3, 247,
85 | 254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 254, 201, 254,
86 | 201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1, 55, 254, 201, 2, 4,
87 | 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 1, 55, 1, 55, 2, 6, 3, 9, 18,
88 | 7, 147, 7, 3, 3, 1, 4, 137, 1, 55, 1, 55, 2, 6, 3, 9, 18, 7, 147, 7,
89 | 3, 3, 1, 4, 2, 254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3,
90 | 6, 2, 254, 201, 254, 201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1,
91 | 55, 254, 201, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 0, 0, 1, 0, 0,
92 | 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57, 1, 0, 0, 0, 0, 1, 0, 0,
93 | 0, 1, 0, 0, 32, 120, 21, 165, 95, 15, 60, 245, 0, 11, 4, 0, 0, 0, 0, 0,
94 | 214, 9, 63, 5, 0, 0, 0, 0, 214, 9, 63, 5, 0, 0, 255, 192, 4, 0, 3, 192,
95 | 0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 192, 255, 192,
96 | 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
97 | 0, 0, 0, 0, 0, 0, 0, 24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
98 | 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
99 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
101 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 2,
102 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60,
103 | 0, 70, 0, 80, 0, 90, 0, 100, 0, 110, 0, 120, 0, 130, 0, 140, 0, 150, 0, 160,
104 | 0, 170, 0, 180, 0, 190, 0, 200, 1, 32, 1, 150, 1, 160, 0, 0, 0, 1, 0, 0,
105 | 0, 24, 0, 84, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
106 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 174, 0, 1, 0, 0, 0, 0,
107 | 0, 1, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 7, 0, 123, 0, 1,
108 | 0, 0, 0, 0, 0, 3, 0, 10, 0, 63, 0, 1, 0, 0, 0, 0, 0, 4, 0, 10,
109 | 0, 144, 0, 1, 0, 0, 0, 0, 0, 5, 0, 11, 0, 30, 0, 1, 0, 0, 0, 0,
110 | 0, 6, 0, 10, 0, 93, 0, 1, 0, 0, 0, 0, 0, 10, 0, 26, 0, 174, 0, 3,
111 | 0, 1, 4, 9, 0, 1, 0, 20, 0, 10, 0, 3, 0, 1, 4, 9, 0, 2, 0, 14,
112 | 0, 130, 0, 3, 0, 1, 4, 9, 0, 3, 0, 20, 0, 73, 0, 3, 0, 1, 4, 9,
113 | 0, 4, 0, 20, 0, 154, 0, 3, 0, 1, 4, 9, 0, 5, 0, 22, 0, 41, 0, 3,
114 | 0, 1, 4, 9, 0, 6, 0, 20, 0, 103, 0, 3, 0, 1, 4, 9, 0, 10, 0, 52,
115 | 0, 200, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45,
116 | 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 86, 101, 114, 115, 105, 111, 110, 32,
117 | 49, 46, 48, 0, 86, 0, 101, 0, 114, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0,
118 | 49, 0, 46, 0, 48, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0,
119 | 111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 105, 99, 111, 45, 112,
120 | 101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0,
121 | 101, 0, 103, 0, 111, 82, 101, 103, 117, 108, 97, 114, 0, 82, 0, 101, 0, 103, 0, 117,
122 | 0, 108, 0, 97, 0, 114, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99,
123 | 0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 70, 111, 110, 116,
124 | 32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 73, 99, 111, 77, 111, 111,
125 | 110, 46, 0, 70, 0, 111, 0, 110, 0, 116, 0, 32, 0, 103, 0, 101, 0, 110, 0, 101,
126 | 0, 114, 0, 97, 0, 116, 0, 101, 0, 100, 0, 32, 0, 98, 0, 121, 0, 32, 0, 73,
127 | 0, 99, 0, 111, 0, 77, 0, 111, 0, 111, 0, 110, 0, 46, 0, 0, 0, 3, 0, 0,
128 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129 | 0, 0, 0, 0, 0, 0, 0, 0,
130 | }
131 |
132 | type customFontCache map[string]*truetype.Font
133 |
134 | func (fc customFontCache) Store(fd draw2d.FontData, font *truetype.Font) {
135 | fc[fd.Name] = font
136 | }
137 |
138 | func (fc customFontCache) Load(fd draw2d.FontData) (*truetype.Font, error) {
139 | font, stored := fc[fd.Name]
140 | if !stored {
141 | return nil, fmt.Errorf("font %s is not stored in font cache", fd.Name)
142 | }
143 | return font, nil
144 | }
145 |
146 | func initFontCache() { // init font cache
147 | fontCache := customFontCache{}
148 | // add gofont to cache
149 | gofont, err := truetype.Parse(goregular.TTF)
150 | if err != nil {
151 | panic(err)
152 | }
153 | fontCache.Store(draw2d.FontData{Name: "goregular"}, gofont)
154 | // add icofont to cache
155 | icofont, err := truetype.Parse(icoTTF)
156 | if err != nil {
157 | panic(err)
158 | }
159 | fontCache.Store(draw2d.FontData{Name: "ico"}, icofont)
160 |
161 | draw2d.SetFontCache(fontCache)
162 | }
163 |
164 | func TestCurveIndexOutOfRange(t *testing.T) {
165 |
166 | initFontCache()
167 |
168 | // Initialize the graphic context on an RGBA image
169 | dest := image.NewRGBA(image.Rect(0, 0, 512, 512))
170 | gc := NewGraphicContext(dest)
171 |
172 | // background
173 | gc.SetFillColor(color.RGBA{0xef, 0xef, 0xef, 0xff})
174 | draw2dkit.Rectangle(gc, 0, 0, 512, 512)
175 | gc.Fill()
176 |
177 | // text
178 | gc.SetFontSize(20)
179 | gc.SetFillColor(color.RGBA{0x10, 0x10, 0x10, 0xff})
180 | gc.SetFontData(draw2d.FontData{Name: "goregular"})
181 |
182 | // gc.FillStringAt("Hello", 128, 120) // this works well
183 |
184 | gc.SetFontData(draw2d.FontData{Name: "ico"})
185 | gc.FillStringAt("\u25cb", 128, 150) // this also works
186 | gc.FillStringAt("\u2716", 128, 170) // Works now
187 |
188 | SaveToPngFile("_test_hello.png", dest)
189 | }
190 |
--------------------------------------------------------------------------------
/draw2dimg/fileutil.go:
--------------------------------------------------------------------------------
1 | package draw2dimg
2 |
3 | import (
4 | "bufio"
5 | "image"
6 | "image/png"
7 | "os"
8 | )
9 |
10 | // SaveToPngFile create and save an image to a file using PNG format
11 | func SaveToPngFile(filePath string, m image.Image) error {
12 | // Create the file
13 | f, err := os.Create(filePath)
14 | if err != nil {
15 | return err
16 | }
17 | defer f.Close()
18 | // Create Writer from file
19 | b := bufio.NewWriter(f)
20 | // Write the image into the buffer
21 | err = png.Encode(b, m)
22 | if err != nil {
23 | return err
24 | }
25 | err = b.Flush()
26 | if err != nil {
27 | return err
28 | }
29 | return nil
30 | }
31 |
32 | // LoadFromPngFile Open a png file
33 | func LoadFromPngFile(filePath string) (image.Image, error) {
34 | // Open file
35 | f, err := os.OpenFile(filePath, 0, 0)
36 | if err != nil {
37 | return nil, err
38 | }
39 | defer f.Close()
40 | b := bufio.NewReader(f)
41 | img, err := png.Decode(b)
42 | if err != nil {
43 | return nil, err
44 | }
45 | return img, nil
46 | }
47 |
--------------------------------------------------------------------------------
/draw2dimg/ftpath.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | package draw2dimg
5 |
6 | import (
7 | "github.com/golang/freetype/raster"
8 | "golang.org/x/image/math/fixed"
9 | )
10 |
11 | type FtLineBuilder struct {
12 | Adder raster.Adder
13 | }
14 |
15 | func (liner FtLineBuilder) MoveTo(x, y float64) {
16 | liner.Adder.Start(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
17 | }
18 |
19 | func (liner FtLineBuilder) LineTo(x, y float64) {
20 | liner.Adder.Add1(fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)})
21 | }
22 |
23 | func (liner FtLineBuilder) LineJoin() {
24 | }
25 |
26 | func (liner FtLineBuilder) Close() {
27 | }
28 |
29 | func (liner FtLineBuilder) End() {
30 | }
31 |
--------------------------------------------------------------------------------
/draw2dimg/text.go:
--------------------------------------------------------------------------------
1 | package draw2dimg
2 |
3 | import (
4 | "github.com/golang/freetype/truetype"
5 | "github.com/llgcode/draw2d"
6 |
7 | "golang.org/x/image/math/fixed"
8 | )
9 |
10 | // DrawContour draws the given closed contour at the given sub-pixel offset.
11 | func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
12 | if len(ps) == 0 {
13 | return
14 | }
15 | startX, startY := pointToF64Point(ps[0])
16 | var others []truetype.Point
17 | if ps[0].Flags&0x01 != 0 {
18 | others = ps[1:]
19 | } else {
20 | lastX, lastY := pointToF64Point(ps[len(ps)-1])
21 | if ps[len(ps)-1].Flags&0x01 != 0 {
22 | startX, startY = lastX, lastY
23 | others = ps[:len(ps)-1]
24 | } else {
25 | startX = (startX + lastX) / 2
26 | startY = (startY + lastY) / 2
27 | others = ps
28 | }
29 | }
30 | path.MoveTo(startX+dx, startY+dy)
31 | q0X, q0Y, on0 := startX, startY, true
32 | for _, p := range others {
33 | qX, qY := pointToF64Point(p)
34 | on := p.Flags&0x01 != 0
35 | if on {
36 | if on0 {
37 | path.LineTo(qX+dx, qY+dy)
38 | } else {
39 | path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
40 | }
41 | } else {
42 | if on0 {
43 | // No-op.
44 | } else {
45 | midX := (q0X + qX) / 2
46 | midY := (q0Y + qY) / 2
47 | path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
48 | }
49 | }
50 | q0X, q0Y, on0 = qX, qY, on
51 | }
52 | // Close the curve.
53 | if on0 {
54 | path.LineTo(startX+dx, startY+dy)
55 | } else {
56 | path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
57 | }
58 | }
59 |
60 | func pointToF64Point(p truetype.Point) (x, y float64) {
61 | return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
62 | }
63 |
64 | func fUnitsToFloat64(x fixed.Int26_6) float64 {
65 | scaled := x << 2
66 | return float64(scaled/256) + float64(scaled%256)/256.0
67 | }
68 |
69 | // FontExtents contains font metric information.
70 | type FontExtents struct {
71 | // Ascent is the distance that the text
72 | // extends above the baseline.
73 | Ascent float64
74 |
75 | // Descent is the distance that the text
76 | // extends below the baseline. The descent
77 | // is given as a negative value.
78 | Descent float64
79 |
80 | // Height is the distance from the lowest
81 | // descending point to the highest ascending
82 | // point.
83 | Height float64
84 | }
85 |
86 | // Extents returns the FontExtents for a font.
87 | // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
88 | func Extents(font *truetype.Font, size float64) FontExtents {
89 | bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
90 | scale := size / float64(font.FUnitsPerEm())
91 | return FontExtents{
92 | Ascent: float64(bounds.Max.Y) * scale,
93 | Descent: float64(bounds.Min.Y) * scale,
94 | Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/draw2dkit/README.md:
--------------------------------------------------------------------------------
1 | draw2d/draw2dkit
2 | =================
3 |
4 | [](http://gocover.io/github.com/llgcode/draw2d/draw2dkit)
5 | [](https://godoc.org/github.com/llgcode/draw2d/draw2dkit)
6 |
7 | Util package that help drawing common vectorial draw.
8 |
--------------------------------------------------------------------------------
/draw2dkit/draw2dkit.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | // Package draw2dkit provides helpers to draw common figures using a Path
5 | package draw2dkit
6 |
7 | import (
8 | "math"
9 |
10 | "github.com/llgcode/draw2d"
11 | )
12 |
13 | // Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
14 | func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
15 | path.MoveTo(x1, y1)
16 | path.LineTo(x2, y1)
17 | path.LineTo(x2, y2)
18 | path.LineTo(x1, y2)
19 | path.Close()
20 | }
21 |
22 | // RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
23 | func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
24 | arcWidth = arcWidth / 2
25 | arcHeight = arcHeight / 2
26 | path.MoveTo(x1, y1+arcHeight)
27 | path.QuadCurveTo(x1, y1, x1+arcWidth, y1)
28 | path.LineTo(x2-arcWidth, y1)
29 | path.QuadCurveTo(x2, y1, x2, y1+arcHeight)
30 | path.LineTo(x2, y2-arcHeight)
31 | path.QuadCurveTo(x2, y2, x2-arcWidth, y2)
32 | path.LineTo(x1+arcWidth, y2)
33 | path.QuadCurveTo(x1, y2, x1, y2-arcHeight)
34 | path.Close()
35 | }
36 |
37 | // Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
38 | func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
39 | path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
40 | path.Close()
41 | }
42 |
43 | // Circle draws a circle using a path with center (cx,cy) and radius
44 | func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
45 | path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
46 | path.Close()
47 | }
48 |
--------------------------------------------------------------------------------
/draw2dkit/draw2dkit_test.go:
--------------------------------------------------------------------------------
1 | package draw2dkit
2 |
3 | import (
4 | "image"
5 | "image/color"
6 | "testing"
7 |
8 | "github.com/llgcode/draw2d/draw2dimg"
9 | )
10 |
11 | func TestCircle(t *testing.T) {
12 | width := 200
13 | height := 200
14 | img := image.NewRGBA(image.Rect(0, 0, width, height))
15 | gc := draw2dimg.NewGraphicContext(img)
16 |
17 | gc.SetStrokeColor(color.NRGBA{255, 255, 255, 255})
18 | gc.SetFillColor(color.NRGBA{255, 255, 255, 255})
19 | gc.Clear()
20 |
21 | gc.SetStrokeColor(color.NRGBA{255, 0, 0, 255})
22 | gc.SetLineWidth(1)
23 |
24 | // Draw a circle
25 | Circle(gc, 100, 100, 50)
26 | gc.Stroke()
27 |
28 | draw2dimg.SaveToPngFile("../output/draw2dkit/TestCircle.png", img)
29 | }
30 |
--------------------------------------------------------------------------------
/draw2dpdf/README.md:
--------------------------------------------------------------------------------
1 | draw2d pdf
2 | ==========
3 |
4 | Package draw2dpdf provides a graphic context that can draw vector graphics and text on pdf file with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
5 |
6 | Quick Start
7 | -----------
8 |
9 | The following Go code generates a simple drawing and saves it to a pdf document:
10 | ```go
11 | // Initialize the graphic context on an RGBA image
12 | dest := draw2dpdf.NewPdf("L", "mm", "A4")
13 | gc := draw2dpdf.NewGraphicContext(dest)
14 |
15 | // Set some properties
16 | gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
17 | gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
18 | gc.SetLineWidth(5)
19 |
20 | // Draw a closed shape
21 | gc.MoveTo(10, 10) // should always be called first for a new path
22 | gc.LineTo(100, 50)
23 | gc.QuadCurveTo(100, 10, 10, 10)
24 | gc.Close()
25 | gc.FillStroke()
26 |
27 | // Save to file
28 | draw2dpdf.SaveToPdfFile("hello.pdf", dest)
29 | ```
30 |
31 | There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
32 |
33 | Alternative backends
34 | --------------------
35 |
36 | - Drawing on images is provided by the draw2d package.
37 | - Drawing on opengl is provided by the draw2dgl package.
38 |
39 | Acknowledgments
40 | ---------------
41 |
42 | The pdf backend uses https://github.com/jung-kurt/gofpdf
43 |
--------------------------------------------------------------------------------
/draw2dpdf/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 |
4 | // Package draw2dpdf provides a graphic context that can draw vector
5 | // graphics and text on pdf file with the gofpdf package.
6 | //
7 | // Quick Start
8 | //
9 | // The following Go code generates a simple drawing and saves it to a
10 | // pdf document:
11 | // // Initialize the graphic context on an RGBA image
12 | // dest := draw2dpdf.NewPdf("L", "mm", "A4")
13 | // gc := draw2dpdf.NewGraphicContext(dest)
14 | //
15 | // // Set some properties
16 | // gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
17 | // gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
18 | // gc.SetLineWidth(5)
19 | //
20 | // // Draw a closed shape
21 | // gc.MoveTo(10, 10) // should always be called first for a new path
22 | // gc.LineTo(100, 50)
23 | // gc.QuadCurveTo(100, 10, 10, 10)
24 | // gc.Close()
25 | // gc.FillStroke()
26 | //
27 | // // Save to file
28 | // draw2dpdf.SaveToPdfFile("hello.pdf", dest)
29 | //
30 | // There are more examples here:
31 | // https://github.com/llgcode/draw2d/tree/master/samples
32 | //
33 | // Alternative backends
34 | //
35 | // Drawing on images is provided by the draw2d package.
36 | // Drawing on opengl is provided by the draw2dgl package.
37 | //
38 | // Acknowledgments
39 | //
40 | // The pdf backend uses https://github.com/jung-kurt/gofpdf
41 | package draw2dpdf
42 |
--------------------------------------------------------------------------------
/draw2dpdf/fileutil.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 |
4 | package draw2dpdf
5 |
6 | import "github.com/jung-kurt/gofpdf"
7 |
8 | // SaveToPdfFile creates and saves a pdf document to a file
9 | func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
10 | return pdf.OutputFileAndClose(filePath)
11 | }
12 |
--------------------------------------------------------------------------------
/draw2dpdf/gc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 | // TODO: dashed line
4 |
5 | package draw2dpdf
6 |
7 | import (
8 | "bytes"
9 | "image"
10 | "image/color"
11 | "image/png"
12 | "log"
13 | "math"
14 | "os"
15 | "strconv"
16 |
17 | "github.com/golang/freetype/truetype"
18 |
19 | "github.com/jung-kurt/gofpdf"
20 | "github.com/llgcode/draw2d"
21 | "github.com/llgcode/draw2d/draw2dbase"
22 | "github.com/llgcode/draw2d/draw2dkit"
23 | )
24 |
25 | const (
26 | // DPI of a pdf document is fixed at 72.
27 | DPI = 72
28 | c255 = 255.0 / 65535.0
29 | )
30 |
31 | var (
32 | caps = map[draw2d.LineCap]string{
33 | draw2d.RoundCap: "round",
34 | draw2d.ButtCap: "butt",
35 | draw2d.SquareCap: "square"}
36 | joins = map[draw2d.LineJoin]string{
37 | draw2d.RoundJoin: "round",
38 | draw2d.BevelJoin: "bevel",
39 | draw2d.MiterJoin: "miter",
40 | }
41 | imageCount uint32
42 | white color.Color = color.RGBA{255, 255, 255, 255}
43 | )
44 |
45 | // NewPdf creates a new pdf document with the draw2d fontfolder, adds
46 | // a page and set fill color to white.
47 | func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
48 | pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
49 | // to be compatible with draw2d
50 | pdf.SetMargins(0, 0, 0)
51 | pdf.SetDrawColor(0, 0, 0)
52 | pdf.SetFillColor(255, 255, 255)
53 | pdf.SetLineCapStyle("round")
54 | pdf.SetLineJoinStyle("round")
55 | pdf.SetLineWidth(1)
56 | pdf.AddPage()
57 | return pdf
58 | }
59 |
60 | // rgb converts a color (used by draw2d) into 3 int (used by gofpdf)
61 | func rgb(c color.Color) (int, int, int) {
62 | r, g, b, _ := c.RGBA()
63 | return int(float64(r) * c255), int(float64(g) * c255), int(float64(b) * c255)
64 | }
65 |
66 | // clearRect draws a white rectangle
67 | func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
68 | // save state
69 | f := gc.Current.FillColor
70 | x, y := gc.pdf.GetXY()
71 | // cover page with white rectangle
72 | gc.SetFillColor(white)
73 | draw2dkit.Rectangle(gc, x1, y1, x2, y2)
74 | gc.Fill()
75 | // restore state
76 | gc.SetFillColor(f)
77 | gc.pdf.MoveTo(x, y)
78 | }
79 |
80 | // GraphicContext implements the draw2d.GraphicContext interface
81 | // It provides draw2d with a pdf backend (based on gofpdf)
82 | type GraphicContext struct {
83 | *draw2dbase.StackGraphicContext
84 | pdf *gofpdf.Fpdf
85 | DPI int
86 | }
87 |
88 | // NewGraphicContext creates a new pdf GraphicContext
89 | func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
90 | gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
91 | gc.SetDPI(DPI)
92 | return gc
93 | }
94 |
95 | // DrawImage draws an image as PNG
96 | // TODO: add type (tp) as parameter to argument list?
97 | func (gc *GraphicContext) DrawImage(image image.Image) {
98 | name := strconv.Itoa(int(imageCount))
99 | imageCount++
100 | tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
101 | b := &bytes.Buffer{}
102 | png.Encode(b, image)
103 | gc.pdf.RegisterImageReader(name, tp, b)
104 | bounds := image.Bounds()
105 | x0, y0 := float64(bounds.Min.X), float64(bounds.Min.Y)
106 | w, h := float64(bounds.Dx()), float64(bounds.Dy())
107 | gc.pdf.Image(name, x0, y0, w, h, false, tp, 0, "")
108 | }
109 |
110 | // Clear draws a white rectangle over the whole page
111 | func (gc *GraphicContext) Clear() {
112 | width, height := gc.pdf.GetPageSize()
113 | clearRect(gc, 0, 0, width, height)
114 | }
115 |
116 | // ClearRect draws a white rectangle over the specified area.
117 | // Samples: line.
118 | func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
119 | clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
120 | }
121 |
122 | // recalc recalculates scale and bounds values from the font size, screen
123 | // resolution and font metrics, and invalidates the glyph cache.
124 | func (gc *GraphicContext) recalc() {
125 | // TODO: resolve properly the font size for pdf and bitmap
126 | gc.Current.Scale = 3 * float64(gc.DPI) / 72
127 | }
128 |
129 | // SetDPI sets the DPI which influences the font size.
130 | func (gc *GraphicContext) SetDPI(dpi int) {
131 | gc.DPI = dpi
132 | gc.recalc()
133 | }
134 |
135 | // GetDPI returns the DPI which influences the font size.
136 | // (Note that gofpdf uses a fixed dpi of 72:
137 | // https://godoc.org/code.google.com/p/gofpdf#Fpdf.PointConvert)
138 | func (gc *GraphicContext) GetDPI() int {
139 | return gc.DPI
140 | }
141 |
142 | // GetStringBounds returns the approximate pixel bounds of the string s at x, y.
143 | // The left edge of the em square of the first character of s
144 | // and the baseline intersect at 0, 0 in the returned coordinates.
145 | // Therefore the top and left coordinates may well be negative.
146 | func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
147 | _, h := gc.pdf.GetFontSize()
148 | d := gc.pdf.GetFontDesc("", "")
149 | if d.Ascent == 0 {
150 | // not defined (standard font?), use average of 81%
151 | top = 0.81 * h
152 | } else {
153 | top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
154 | }
155 | return 0, top, gc.pdf.GetStringWidth(s), top + h
156 | }
157 |
158 | // CreateStringPath creates a path from the string s at x, y, and returns the string width.
159 | func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
160 | //fpdf uses the top left corner
161 | left, top, right, bottom := gc.GetStringBounds(text)
162 | w := right - left
163 | h := bottom - top
164 | // gc.pdf.SetXY(x, y-h) do not use this as y-h might be negative
165 | margin := gc.pdf.GetCellMargin()
166 | gc.pdf.MoveTo(x-left-margin, y+top)
167 | gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
168 | return w
169 | }
170 |
171 | // FillString draws a string at 0, 0
172 | func (gc *GraphicContext) FillString(text string) (cursor float64) {
173 | return gc.FillStringAt(text, 0, 0)
174 | }
175 |
176 | // FillStringAt draws a string at x, y
177 | func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
178 | return gc.CreateStringPath(text, x, y)
179 | }
180 |
181 | // StrokeString draws a string at 0, 0 (stroking is unsupported,
182 | // string will be filled)
183 | func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
184 | return gc.StrokeStringAt(text, 0, 0)
185 | }
186 |
187 | // StrokeStringAt draws a string at x, y (stroking is unsupported,
188 | // string will be filled)
189 | func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
190 | return gc.CreateStringPath(text, x, y)
191 | }
192 |
193 | // Stroke strokes the paths with the color specified by SetStrokeColor
194 | func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
195 | _, _, _, alphaS := gc.Current.StrokeColor.RGBA()
196 | gc.draw("D", alphaS, paths...)
197 | gc.Current.Path.Clear()
198 | }
199 |
200 | // Fill fills the paths with the color specified by SetFillColor
201 | func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
202 | style := "F"
203 | if gc.Current.FillRule != draw2d.FillRuleWinding {
204 | style += "*"
205 | }
206 | _, _, _, alphaF := gc.Current.FillColor.RGBA()
207 | gc.draw(style, alphaF, paths...)
208 | gc.Current.Path.Clear()
209 | }
210 |
211 | // FillStroke first fills the paths and than strokes them
212 | func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
213 | var rule string
214 | if gc.Current.FillRule != draw2d.FillRuleWinding {
215 | rule = "*"
216 | }
217 | _, _, _, alphaS := gc.Current.StrokeColor.RGBA()
218 | _, _, _, alphaF := gc.Current.FillColor.RGBA()
219 | if alphaS == alphaF {
220 | gc.draw("FD"+rule, alphaF, paths...)
221 | } else {
222 | gc.draw("F"+rule, alphaF, paths...)
223 | gc.draw("S", alphaS, paths...)
224 | }
225 | gc.Current.Path.Clear()
226 | }
227 |
228 | var logger = log.New(os.Stdout, "", log.Lshortfile)
229 |
230 | const alphaMax = float64(0xFFFF)
231 |
232 | // draw fills and/or strokes paths
233 | func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
234 | paths = append(paths, gc.Current.Path)
235 | for _, p := range paths {
236 | ConvertPath(p, gc.pdf)
237 | }
238 | a := float64(alpha) / alphaMax
239 | current, blendMode := gc.pdf.GetAlpha()
240 | if a != current {
241 | gc.pdf.SetAlpha(a, blendMode)
242 | }
243 | gc.pdf.DrawPath(style)
244 | }
245 |
246 | // overwrite StackGraphicContext methods
247 |
248 | // SetStrokeColor sets the stroke color
249 | func (gc *GraphicContext) SetStrokeColor(c color.Color) {
250 | gc.StackGraphicContext.SetStrokeColor(c)
251 | gc.pdf.SetDrawColor(rgb(c))
252 | }
253 |
254 | // SetFillColor sets the fill and text color
255 | func (gc *GraphicContext) SetFillColor(c color.Color) {
256 | gc.StackGraphicContext.SetFillColor(c)
257 | gc.pdf.SetFillColor(rgb(c))
258 | gc.pdf.SetTextColor(rgb(c))
259 | }
260 |
261 | // SetFont is unsupported by the pdf graphic context, use SetFontData
262 | // instead.
263 | func (gc *GraphicContext) SetFont(font *truetype.Font) {
264 | // TODO: what to do with this api conflict between draw2d and gofpdf?!
265 | }
266 |
267 | // SetFontData sets the current font used to draw text. Always use
268 | // this method, as SetFont is unsupported by the pdf graphic context.
269 | // It is mandatory to call this method at least once before printing
270 | // text or the resulting document will not be valid.
271 | // It is necessary to generate a font definition file first with the
272 | // makefont utility. It is not necessary to call this function for the
273 | // core PDF fonts (courier, helvetica, times, zapfdingbats).
274 | // go get github.com/jung-kurt/gofpdf/makefont
275 | // http://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.AddFont
276 | func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
277 | // TODO: call Makefont embed if json file does not exist yet
278 | gc.StackGraphicContext.SetFontData(fontData)
279 | var style string
280 | if fontData.Style&draw2d.FontStyleBold != 0 {
281 | style += "B"
282 | }
283 | if fontData.Style&draw2d.FontStyleItalic != 0 {
284 | style += "I"
285 | }
286 | fn := draw2d.FontFileName(fontData)
287 | fn = fn[:len(fn)-4]
288 | size, _ := gc.pdf.GetFontSize()
289 | gc.pdf.AddFont(fontData.Name, style, fn+".json")
290 | gc.pdf.SetFont(fontData.Name, style, size)
291 | }
292 |
293 | // SetFontSize sets the font size in points (as in ``a 12 point font'').
294 | // TODO: resolve this with ImgGraphicContext (now done with gc.Current.Scale)
295 | func (gc *GraphicContext) SetFontSize(fontSize float64) {
296 | gc.StackGraphicContext.SetFontSize(fontSize)
297 | gc.recalc()
298 | gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
299 | }
300 |
301 | // SetLineDash sets the line dash pattern
302 | func (gc *GraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
303 | gc.StackGraphicContext.SetLineDash(Dash, DashOffset)
304 | gc.pdf.SetDashPattern(Dash, DashOffset)
305 | }
306 |
307 | // SetLineWidth sets the line width
308 | func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
309 | gc.StackGraphicContext.SetLineWidth(LineWidth)
310 | gc.pdf.SetLineWidth(LineWidth)
311 | }
312 |
313 | // SetLineCap sets the line cap (round, but or square)
314 | func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
315 | gc.StackGraphicContext.SetLineCap(Cap)
316 | gc.pdf.SetLineCapStyle(caps[Cap])
317 | }
318 |
319 | // SetLineJoin sets the line cap (round, bevel or miter)
320 | func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
321 | gc.StackGraphicContext.SetLineJoin(Join)
322 | gc.pdf.SetLineJoinStyle(joins[Join])
323 | }
324 |
325 | // Transformations
326 |
327 | // Scale generally scales the following text, drawings and images.
328 | // sx and sy are the scaling factors for width and height.
329 | // This must be placed between gc.Save() and gc.Restore(), otherwise
330 | // the pdf is invalid.
331 | func (gc *GraphicContext) Scale(sx, sy float64) {
332 | gc.StackGraphicContext.Scale(sx, sy)
333 | gc.pdf.TransformScale(sx*100, sy*100, 0, 0)
334 | }
335 |
336 | // Rotate rotates the following text, drawings and images.
337 | // Angle is specified in radians and measured clockwise from the
338 | // 3 o'clock position.
339 | // This must be placed between gc.Save() and gc.Restore(), otherwise
340 | // the pdf is invalid.
341 | func (gc *GraphicContext) Rotate(angle float64) {
342 | gc.StackGraphicContext.Rotate(angle)
343 | gc.pdf.TransformRotate(-angle*180/math.Pi, 0, 0)
344 | }
345 |
346 | // Translate moves the following text, drawings and images
347 | // horizontally and vertically by the amounts specified by tx and ty.
348 | // This must be placed between gc.Save() and gc.Restore(), otherwise
349 | // the pdf is invalid.
350 | func (gc *GraphicContext) Translate(tx, ty float64) {
351 | gc.StackGraphicContext.Translate(tx, ty)
352 | gc.pdf.TransformTranslate(tx, ty)
353 | }
354 |
355 | // Save saves the current context stack
356 | // (transformation, font, color,...).
357 | func (gc *GraphicContext) Save() {
358 | gc.StackGraphicContext.Save()
359 | gc.pdf.TransformBegin()
360 | }
361 |
362 | // Restore restores the current context stack
363 | // (transformation, color,...). Restoring the font is not supported.
364 | func (gc *GraphicContext) Restore() {
365 | gc.pdf.TransformEnd()
366 | gc.StackGraphicContext.Restore()
367 | c := gc.Current
368 | gc.SetFontSize(c.FontSize)
369 | // gc.SetFontData(c.FontData) unsupported, causes bug (do not enable)
370 | gc.SetLineWidth(c.LineWidth)
371 | gc.SetStrokeColor(c.StrokeColor)
372 | gc.SetFillColor(c.FillColor)
373 | gc.SetFillRule(c.FillRule)
374 | // gc.SetLineDash(c.Dash, c.DashOffset) // TODO
375 | gc.SetLineCap(c.Cap)
376 | gc.SetLineJoin(c.Join)
377 | // c.Path unsupported
378 | // c.Font unsupported
379 | }
380 |
--------------------------------------------------------------------------------
/draw2dpdf/path_converter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 |
4 | package draw2dpdf
5 |
6 | import (
7 | "math"
8 |
9 | "github.com/llgcode/draw2d"
10 | )
11 |
12 | const deg = 180 / math.Pi
13 |
14 | // ConvertPath converts a paths to the pdf api
15 | func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
16 | var startX, startY float64 = 0, 0
17 | i := 0
18 | for _, cmp := range path.Components {
19 | switch cmp {
20 | case draw2d.MoveToCmp:
21 | startX, startY = path.Points[i], path.Points[i+1]
22 | pdf.MoveTo(startX, startY)
23 | i += 2
24 | case draw2d.LineToCmp:
25 | pdf.LineTo(path.Points[i], path.Points[i+1])
26 | i += 2
27 | case draw2d.QuadCurveToCmp:
28 | pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
29 | i += 4
30 | case draw2d.CubicCurveToCmp:
31 | 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])
32 | i += 6
33 | case draw2d.ArcToCmp:
34 | pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
35 | 0, // degRotate
36 | path.Points[i+4]*deg, // degStart = startAngle
37 | (path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
38 | i += 6
39 | case draw2d.CloseCmp:
40 | pdf.LineTo(startX, startY)
41 | pdf.ClosePath()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/draw2dpdf/samples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 | // See also test_test.go
4 |
5 | package draw2dpdf_test
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/llgcode/draw2d"
11 | "github.com/llgcode/draw2d/samples/android"
12 | "github.com/llgcode/draw2d/samples/frameimage"
13 | "github.com/llgcode/draw2d/samples/geometry"
14 | "github.com/llgcode/draw2d/samples/gopher"
15 | "github.com/llgcode/draw2d/samples/gopher2"
16 | "github.com/llgcode/draw2d/samples/helloworld"
17 | "github.com/llgcode/draw2d/samples/line"
18 | "github.com/llgcode/draw2d/samples/linecapjoin"
19 | "github.com/llgcode/draw2d/samples/postscript"
20 | )
21 |
22 | func TestSampleAndroid(t *testing.T) {
23 | test(t, android.Main)
24 | }
25 |
26 | // TODO: FillString: w (width) is incorrect
27 | func TestSampleGeometry(t *testing.T) {
28 | // Set the global folder for searching fonts
29 | // The pdf backend needs for every ttf file its corresponding
30 | // json/.z file which is generated by gofpdf/makefont.
31 | draw2d.SetFontFolder("../resource/font")
32 | test(t, geometry.Main)
33 | }
34 |
35 | func TestSampleGopher(t *testing.T) {
36 | test(t, gopher.Main)
37 | }
38 |
39 | func TestSampleGopher2(t *testing.T) {
40 | test(t, gopher2.Main)
41 | }
42 |
43 | func TestSampleHelloWorld(t *testing.T) {
44 | // Set the global folder for searching fonts
45 | // The pdf backend needs for every ttf file its corresponding
46 | // json/.z file which is generated by gofpdf/makefont.
47 | draw2d.SetFontFolder("../resource/font")
48 | test(t, helloworld.Main)
49 | }
50 |
51 | func TestSampleFrameImage(t *testing.T) {
52 | test(t, frameimage.Main)
53 | }
54 |
55 | func TestSampleLine(t *testing.T) {
56 | test(t, line.Main)
57 | }
58 |
59 | func TestSampleLineCap(t *testing.T) {
60 | test(t, linecapjoin.Main)
61 | }
62 |
63 | func TestSamplePostscript(t *testing.T) {
64 | test(t, postscript.Main)
65 | }
66 |
--------------------------------------------------------------------------------
/draw2dpdf/test_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 |
4 | // Package draw2dpdf_test gives test coverage with the command:
5 | // go test -cover ./... | grep -v "no test"
6 | // (It should be run from its parent draw2d directory.)
7 | package draw2dpdf_test
8 |
9 | import (
10 | "testing"
11 |
12 | "github.com/llgcode/draw2d"
13 | "github.com/llgcode/draw2d/draw2dpdf"
14 | )
15 |
16 | type sample func(gc draw2d.GraphicContext, ext string) (string, error)
17 |
18 | func test(t *testing.T, draw sample) {
19 | // Initialize the graphic context on an pdf document
20 | dest := draw2dpdf.NewPdf("L", "mm", "A4")
21 | gc := draw2dpdf.NewGraphicContext(dest)
22 | // Draw sample
23 | output, err := draw(gc, "pdf")
24 | if err != nil {
25 | t.Errorf("Drawing %q failed: %v", output, err)
26 | return
27 | }
28 | /*
29 | // Save to pdf only if it doesn't exist because of git
30 | if _, err = os.Stat(output); err == nil {
31 | t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", output)
32 | return
33 | }
34 | */
35 | err = draw2dpdf.SaveToPdfFile(output, dest)
36 | if err != nil {
37 | t.Errorf("Saving %q failed: %v", output, err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/draw2dpdf/vectorizer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 |
4 | package draw2dpdf
5 |
6 | // Vectorizer defines the minimal interface for gofpdf.Fpdf
7 | // to be passed to a PathConvertor.
8 | // It is also implemented by for example VertexMatrixTransform
9 | type Vectorizer interface {
10 | // MoveTo creates a new subpath that start at the specified point
11 | MoveTo(x, y float64)
12 | // LineTo adds a line to the current subpath
13 | LineTo(x, y float64)
14 | // CurveTo adds a quadratic bezier curve to the current subpath
15 | CurveTo(cx, cy, x, y float64)
16 | // CurveTo adds a cubic bezier curve to the current subpath
17 | CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
18 | // ArcTo adds an arc to the current subpath
19 | ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
20 | // ClosePath closes the subpath
21 | ClosePath()
22 | }
23 |
--------------------------------------------------------------------------------
/draw2dsvg/converters.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednářpackage draw2dsvg
3 |
4 | package draw2dsvg
5 |
6 | import (
7 | "bytes"
8 | "encoding/base64"
9 | "fmt"
10 | "github.com/llgcode/draw2d"
11 | "image"
12 | "image/color"
13 | "image/png"
14 | "math"
15 | "strconv"
16 | "strings"
17 | )
18 |
19 | func toSvgRGBA(c color.Color) string {
20 | r, g, b, a := c.RGBA()
21 | r, g, b, a = r>>8, g>>8, b>>8, a>>8
22 | if a == 255 {
23 | return optiSprintf("#%02X%02X%02X", r, g, b)
24 | }
25 | return optiSprintf("rgba(%v,%v,%v,%f)", r, g, b, float64(a)/255)
26 | }
27 |
28 | func toSvgLength(l float64) string {
29 | if math.IsInf(l, 1) {
30 | return "100%"
31 | }
32 | return optiSprintf("%f", l)
33 | }
34 |
35 | func toSvgArray(nums []float64) string {
36 | arr := make([]string, len(nums))
37 | for i, num := range nums {
38 | arr[i] = optiSprintf("%f", num)
39 | }
40 | return strings.Join(arr, ",")
41 | }
42 |
43 | func toSvgFillRule(rule draw2d.FillRule) string {
44 | return map[draw2d.FillRule]string{
45 | draw2d.FillRuleEvenOdd: "evenodd",
46 | draw2d.FillRuleWinding: "nonzero",
47 | }[rule]
48 | }
49 |
50 | func toSvgPathDesc(p *draw2d.Path) string {
51 | parts := make([]string, len(p.Components))
52 | ps := p.Points
53 | for i, cmp := range p.Components {
54 | switch cmp {
55 | case draw2d.MoveToCmp:
56 | parts[i] = optiSprintf("M %f,%f", ps[0], ps[1])
57 | ps = ps[2:]
58 | case draw2d.LineToCmp:
59 | parts[i] = optiSprintf("L %f,%f", ps[0], ps[1])
60 | ps = ps[2:]
61 | case draw2d.QuadCurveToCmp:
62 | parts[i] = optiSprintf("Q %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3])
63 | ps = ps[4:]
64 | case draw2d.CubicCurveToCmp:
65 | parts[i] = optiSprintf("C %f,%f %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5])
66 | ps = ps[6:]
67 | case draw2d.ArcToCmp:
68 | cx, cy := ps[0], ps[1] // center
69 | rx, ry := ps[2], ps[3] // radii
70 | fi := ps[4] + ps[5] // startAngle + angle
71 |
72 | // compute endpoint
73 | sinfi, cosfi := math.Sincos(fi)
74 | nom := math.Hypot(ry*cosfi, rx*sinfi)
75 | x := cx + (rx*ry*cosfi)/nom
76 | y := cy + (rx*ry*sinfi)/nom
77 |
78 | // compute large and sweep flags
79 | large := 0
80 | sweep := 0
81 | if math.Abs(ps[5]) > math.Pi {
82 | large = 1
83 | }
84 | if !math.Signbit(ps[5]) {
85 | sweep = 1
86 | }
87 | // dirty hack to ensure whole arc is drawn
88 | // if start point equals end point
89 | if sweep == 1 {
90 | x += 0.01 * sinfi
91 | y += 0.01 * -cosfi
92 | } else {
93 | x += 0.01 * sinfi
94 | y += 0.01 * cosfi
95 | }
96 |
97 | // rx ry x-axis-rotation large-arc-flag sweep-flag x y
98 | parts[i] = optiSprintf("A %f %f %v %v %v %F %F",
99 | rx, ry, 0, large, sweep, x, y,
100 | )
101 | ps = ps[6:]
102 | case draw2d.CloseCmp:
103 | parts[i] = "Z"
104 | }
105 | }
106 | return strings.Join(parts, " ")
107 | }
108 |
109 | func toSvgTransform(mat draw2d.Matrix) string {
110 | if mat.IsIdentity() {
111 | return ""
112 | }
113 | if mat.IsTranslation() {
114 | x, y := mat.GetTranslation()
115 | return optiSprintf("translate(%f,%f)", x, y)
116 | }
117 | return optiSprintf("matrix(%f,%f,%f,%f,%f,%f)",
118 | mat[0], mat[1], mat[2], mat[3], mat[4], mat[5],
119 | )
120 | }
121 |
122 | func imageToSvgHref(image image.Image) string {
123 | out := "data:image/png;base64,"
124 | pngBuf := &bytes.Buffer{}
125 | png.Encode(pngBuf, image)
126 | out += base64.RawStdEncoding.EncodeToString(pngBuf.Bytes())
127 | return out
128 | }
129 |
130 | // Do the same thing as fmt.Sprintf
131 | // except it uses the optimal precition for floats: (0-3) for f and (0-6) for F
132 | // eg.:
133 | // optiSprintf("%f", 3.0) => fmt.Sprintf("%.0f", 3.0)
134 | // optiSprintf("%f", 3.33) => fmt.Sprintf("%.2f", 3.33)
135 | // optiSprintf("%f", 3.3001) => fmt.Sprintf("%.1f", 3.3001)
136 | // optiSprintf("%f", 3.333333333333333) => fmt.Sprintf("%.3f", 3.333333333333333)
137 | // optiSprintf("%F", 3.333333333333333) => fmt.Sprintf("%.6f", 3.333333333333333)
138 | func optiSprintf(format string, a ...interface{}) string {
139 | chunks := strings.Split(format, "%")
140 | newChunks := make([]string, len(chunks))
141 | for i, chunk := range chunks {
142 | if i != 0 {
143 | verb := chunk[0]
144 | if verb == 'f' || verb == 'F' {
145 | num := a[i-1].(float64)
146 | p := strconv.Itoa(getPrec(num, verb == 'F'))
147 | chunk = strings.Replace(chunk, string(verb), "."+p+"f", 1)
148 | }
149 | }
150 | newChunks[i] = chunk
151 | }
152 | format = strings.Join(newChunks, "%")
153 | return fmt.Sprintf(format, a...)
154 | }
155 |
156 | // TODO needs test, since it is not quiet right
157 | func getPrec(num float64, better bool) int {
158 | num = math.Abs(num)
159 | max := 3
160 | eps := 0.0005
161 | if better {
162 | max = 6
163 | eps = 0.0000005
164 | }
165 | prec := 0
166 | for math.Mod(num, 1) > eps {
167 | num *= 10
168 | eps *= 10
169 | prec++
170 | }
171 |
172 | if max < prec {
173 | return max
174 | }
175 | return prec
176 | }
177 |
--------------------------------------------------------------------------------
/draw2dsvg/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednář
3 |
4 | // Package draw2svg provides a graphic context that can draw
5 | // vector graphics and text on svg file.
6 | //
7 | // Quick Start
8 | // The following Go code geneartes a simple drawing and saves it
9 | // to a svg document:
10 | // TODO
11 | package draw2dsvg
12 |
--------------------------------------------------------------------------------
/draw2dsvg/fileutil.go:
--------------------------------------------------------------------------------
1 | package draw2dsvg
2 |
3 | import (
4 | "encoding/xml"
5 | _ "errors"
6 | "io"
7 | "os"
8 | )
9 |
10 | func WriteSvg(w io.Writer, svg *Svg) error {
11 | _, err := w.Write([]byte(xml.Header))
12 | if err != nil {
13 | return err
14 | }
15 | encoder := xml.NewEncoder(w)
16 | encoder.Indent("", "\t")
17 | return encoder.Encode(svg)
18 | }
19 |
20 | func SaveToSvgFile(filePath string, svg *Svg) error {
21 | f, err := os.Create(filePath)
22 | if err != nil {
23 | return err
24 | }
25 | defer f.Close()
26 | return WriteSvg(f, svg)
27 | }
28 |
--------------------------------------------------------------------------------
/draw2dsvg/gc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednář
3 |
4 | package draw2dsvg
5 |
6 | import (
7 | "github.com/golang/freetype/truetype"
8 | "github.com/llgcode/draw2d"
9 | "github.com/llgcode/draw2d/draw2dbase"
10 | "golang.org/x/image/font"
11 | "golang.org/x/image/math/fixed"
12 | "image"
13 | "log"
14 | "math"
15 | "strconv"
16 | "strings"
17 | )
18 |
19 | type drawType int
20 |
21 | const (
22 | filled drawType = 1 << iota
23 | stroked
24 | )
25 |
26 | // GraphicContext implements the draw2d.GraphicContext interface
27 | // It provides draw2d with a svg backend
28 | type GraphicContext struct {
29 | *draw2dbase.StackGraphicContext
30 | FontCache draw2d.FontCache
31 | glyphCache draw2dbase.GlyphCache
32 | glyphBuf *truetype.GlyphBuf
33 | svg *Svg
34 | DPI int
35 | }
36 |
37 | func NewGraphicContext(svg *Svg) *GraphicContext {
38 | gc := &GraphicContext{
39 | draw2dbase.NewStackGraphicContext(),
40 | draw2d.GetGlobalFontCache(),
41 | draw2dbase.NewGlyphCache(),
42 | &truetype.GlyphBuf{},
43 | svg,
44 | 92,
45 | }
46 | return gc
47 | }
48 |
49 | // Clear fills the current canvas with a default transparent color
50 | func (gc *GraphicContext) Clear() {
51 | gc.svg.Groups = nil
52 | }
53 |
54 | // Stroke strokes the paths with the color specified by SetStrokeColor
55 | func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
56 | gc.drawPaths(stroked, paths...)
57 | gc.Current.Path.Clear()
58 | }
59 |
60 | // Fill fills the paths with the color specified by SetFillColor
61 | func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
62 | gc.drawPaths(filled, paths...)
63 | gc.Current.Path.Clear()
64 | }
65 |
66 | // FillStroke first fills the paths and than strokes them
67 | func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
68 | gc.drawPaths(filled|stroked, paths...)
69 | gc.Current.Path.Clear()
70 | }
71 |
72 | // FillString draws the text at point (0, 0)
73 | func (gc *GraphicContext) FillString(text string) (cursor float64) {
74 | return gc.FillStringAt(text, 0, 0)
75 | }
76 |
77 | // FillStringAt draws the text at the specified point (x, y)
78 | func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
79 | return gc.drawString(text, filled, x, y)
80 | }
81 |
82 | // StrokeString draws the contour of the text at point (0, 0)
83 | func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
84 | return gc.StrokeStringAt(text, 0, 0)
85 | }
86 |
87 | // StrokeStringAt draws the contour of the text at point (x, y)
88 | func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
89 | return gc.drawString(text, stroked, x, y)
90 | }
91 |
92 | // Save the context and push it to the context stack
93 | func (gc *GraphicContext) Save() {
94 | gc.StackGraphicContext.Save()
95 | // TODO use common transformation group for multiple elements
96 | }
97 |
98 | // Restore remove the current context and restore the last one
99 | func (gc *GraphicContext) Restore() {
100 | gc.StackGraphicContext.Restore()
101 | // TODO use common transformation group for multiple elements
102 | }
103 |
104 | func (gc *GraphicContext) SetDPI(dpi int) {
105 | gc.DPI = dpi
106 | gc.recalc()
107 | }
108 |
109 | func (gc *GraphicContext) GetDPI() int {
110 | return gc.DPI
111 | }
112 |
113 | // SetFont sets the font used to draw text.
114 | func (gc *GraphicContext) SetFont(font *truetype.Font) {
115 | gc.Current.Font = font
116 | }
117 |
118 | // SetFontSize sets the font size in points (as in “a 12 point font”).
119 | func (gc *GraphicContext) SetFontSize(fontSize float64) {
120 | gc.Current.FontSize = fontSize
121 | gc.recalc()
122 | }
123 |
124 | // DrawImage draws the raster image in the current canvas
125 | func (gc *GraphicContext) DrawImage(image image.Image) {
126 | bounds := image.Bounds()
127 |
128 | svgImage := &Image{Href: imageToSvgHref(image)}
129 | svgImage.X = float64(bounds.Min.X)
130 | svgImage.Y = float64(bounds.Min.Y)
131 | svgImage.Width = toSvgLength(float64(bounds.Max.X - bounds.Min.X))
132 | svgImage.Height = toSvgLength(float64(bounds.Max.Y - bounds.Min.Y))
133 | gc.newGroup(0).Image = svgImage
134 | }
135 |
136 | // ClearRect fills the specified rectangle with a default transparent color
137 | func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
138 | mask := gc.newMask(x1, y1, x2-x1, y2-y1)
139 |
140 | newGroup := &Group{
141 | Groups: gc.svg.Groups,
142 | Mask: "url(#" + mask.Id + ")",
143 | }
144 |
145 | // replace groups with new masked group
146 | gc.svg.Groups = []*Group{newGroup}
147 | }
148 |
149 | // NOTE following two functions and soe other further below copied from dwra2d{img|gl}
150 | // TODO move them all to common draw2dbase?
151 |
152 | // CreateStringPath creates a path from the string s at x, y, and returns the string width.
153 | // The text is placed so that the left edge of the em square of the first character of s
154 | // and the baseline intersect at x, y. The majority of the affected pixels will be
155 | // above and to the right of the point, but some may be below or to the left.
156 | // For example, drawing a string that starts with a 'J' in an italic font may
157 | // affect pixels below and left of the point.
158 | func (gc *GraphicContext) CreateStringPath(s string, x, y float64) (cursor float64) {
159 | f, err := gc.loadCurrentFont()
160 | if err != nil {
161 | log.Println(err)
162 | return 0.0
163 | }
164 | startx := x
165 | prev, hasPrev := truetype.Index(0), false
166 | for _, rune := range s {
167 | index := f.Index(rune)
168 | if hasPrev {
169 | x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
170 | }
171 | err := gc.drawGlyph(index, x, y)
172 | if err != nil {
173 | log.Println(err)
174 | return startx - x
175 | }
176 | x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
177 | prev, hasPrev = index, true
178 | }
179 |
180 | return x - startx
181 | }
182 |
183 | // GetStringBounds returns the approximate pixel bounds of the string s at x, y.
184 | // The the left edge of the em square of the first character of s
185 | // and the baseline intersect at 0, 0 in the returned coordinates.
186 | // Therefore the top and left coordinates may well be negative.
187 | func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
188 | f, err := gc.loadCurrentFont()
189 | if err != nil {
190 | log.Println(err)
191 | return 0, 0, 0, 0
192 | }
193 | if gc.Current.Scale == 0 {
194 | panic("zero scale")
195 | }
196 | top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
197 | cursor := 0.0
198 | prev, hasPrev := truetype.Index(0), false
199 | for _, rune := range s {
200 | index := f.Index(rune)
201 | if hasPrev {
202 | cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
203 | }
204 | if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
205 | log.Println(err)
206 | return 0, 0, 0, 0
207 | }
208 | e0 := 0
209 | for _, e1 := range gc.glyphBuf.Ends {
210 | ps := gc.glyphBuf.Points[e0:e1]
211 | for _, p := range ps {
212 | x, y := pointToF64Point(p)
213 | top = math.Min(top, y)
214 | bottom = math.Max(bottom, y)
215 | left = math.Min(left, x+cursor)
216 | right = math.Max(right, x+cursor)
217 | }
218 | }
219 | cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
220 | prev, hasPrev = index, true
221 | }
222 | return left, top, right, bottom
223 | }
224 |
225 | ////////////////////
226 | // private funcitons
227 |
228 | func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
229 | // create elements
230 | svgPath := Path{}
231 | group := gc.newGroup(drawType)
232 |
233 | // set attrs to path element
234 | paths = append(paths, gc.Current.Path)
235 | svgPathsDesc := make([]string, len(paths))
236 | // multiple pathes has to be joined to single svg path description
237 | // because fill-rule wont work for whole group as excepted
238 | for i, path := range paths {
239 | svgPathsDesc[i] = toSvgPathDesc(path)
240 | }
241 | svgPath.Desc = strings.Join(svgPathsDesc, " ")
242 |
243 | // attach to group
244 | group.Paths = []*Path{&svgPath}
245 | }
246 |
247 | // Add text element to svg and returns its expected width
248 | func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
249 | switch gc.svg.FontMode {
250 | case PathFontMode:
251 | w := gc.CreateStringPath(text, x, y)
252 | gc.drawPaths(drawType)
253 | gc.Current.Path.Clear()
254 | return w
255 | case SvgFontMode:
256 | gc.embedSvgFont(text)
257 | }
258 |
259 | // create elements
260 | svgText := Text{}
261 | group := gc.newGroup(drawType)
262 |
263 | // set attrs to text element
264 | svgText.Text = text
265 | svgText.FontSize = gc.Current.FontSize
266 | svgText.X = x
267 | svgText.Y = y
268 | svgText.FontFamily = gc.Current.FontData.Name
269 |
270 | // attach to group
271 | group.Texts = []*Text{&svgText}
272 | left, _, right, _ := gc.GetStringBounds(text)
273 | return right - left
274 | }
275 |
276 | // Creates new group from current context
277 | // attach it to svg and return
278 | func (gc *GraphicContext) newGroup(drawType drawType) *Group {
279 | group := Group{}
280 | // set attrs to group
281 | if drawType&stroked == stroked {
282 | group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
283 | group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
284 | group.StrokeLinecap = gc.Current.Cap.String()
285 | group.StrokeLinejoin = gc.Current.Join.String()
286 | if len(gc.Current.Dash) > 0 {
287 | group.StrokeDasharray = toSvgArray(gc.Current.Dash)
288 | group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
289 | }
290 | }
291 |
292 | if drawType&filled == filled {
293 | group.Fill = toSvgRGBA(gc.Current.FillColor)
294 | group.FillRule = toSvgFillRule(gc.Current.FillRule)
295 | }
296 |
297 | group.Transform = toSvgTransform(gc.Current.Tr)
298 |
299 | // attach
300 | gc.svg.Groups = append(gc.svg.Groups, &group)
301 |
302 | return &group
303 | }
304 |
305 | // creates new mask attached to svg
306 | func (gc *GraphicContext) newMask(x, y, width, height int) *Mask {
307 | mask := &Mask{}
308 | mask.X = float64(x)
309 | mask.Y = float64(y)
310 | mask.Width = toSvgLength(float64(width))
311 | mask.Height = toSvgLength(float64(height))
312 |
313 | // attach mask
314 | gc.svg.Masks = append(gc.svg.Masks, mask)
315 | mask.Id = "mask-" + strconv.Itoa(len(gc.svg.Masks))
316 | return mask
317 | }
318 |
319 | // Embed svg font definition to svg tree itself
320 | // Or update existing if already exists for curent font data
321 | func (gc *GraphicContext) embedSvgFont(text string) *Font {
322 | fontName := gc.Current.FontData.Name
323 | gc.loadCurrentFont()
324 |
325 | // find or create font Element
326 | svgFont := (*Font)(nil)
327 | for _, font := range gc.svg.Fonts {
328 | if font.Name == fontName {
329 | svgFont = font
330 | break
331 | }
332 | }
333 | if svgFont == nil {
334 | // create new
335 | svgFont = &Font{}
336 | // and attach
337 | gc.svg.Fonts = append(gc.svg.Fonts, svgFont)
338 | }
339 |
340 | // fill with glyphs
341 |
342 | gc.Save()
343 | defer gc.Restore()
344 | gc.SetFontSize(2048)
345 | defer gc.SetDPI(gc.GetDPI())
346 | gc.SetDPI(92)
347 | filling:
348 | for _, rune := range text {
349 | for _, g := range svgFont.Glyphs {
350 | if g.Rune == Rune(rune) {
351 | continue filling
352 | }
353 | }
354 | glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune)
355 | // glyphCache.Load indirectly calls CreateStringPath for single rune string
356 |
357 | glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe
358 | svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{
359 | Rune: Rune(rune),
360 | Desc: toSvgPathDesc(glypPath),
361 | HorizAdvX: glyph.Width,
362 | })
363 | }
364 |
365 | // set attrs
366 | svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts))
367 | svgFont.Name = fontName
368 |
369 | // TODO use css @font-face with id instead of this
370 | svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048}
371 | return svgFont
372 | }
373 |
374 | func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
375 | font, err := gc.FontCache.Load(gc.Current.FontData)
376 | if err != nil {
377 | font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
378 | }
379 | if font != nil {
380 | gc.SetFont(font)
381 | gc.SetFontSize(gc.Current.FontSize)
382 | }
383 | return font, err
384 | }
385 |
386 | func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
387 | if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
388 | return err
389 | }
390 | e0 := 0
391 | for _, e1 := range gc.glyphBuf.Ends {
392 | DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
393 | e0 = e1
394 | }
395 | return nil
396 | }
397 |
398 | // recalc recalculates scale and bounds values from the font size, screen
399 | // resolution and font metrics, and invalidates the glyph cache.
400 | func (gc *GraphicContext) recalc() {
401 | gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
402 | }
403 |
--------------------------------------------------------------------------------
/draw2dsvg/samples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 26/06/2015 by Stani Michiels
3 | // See also test_test.go
4 |
5 | package draw2dsvg_test
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/llgcode/draw2d"
11 | "github.com/llgcode/draw2d/samples/android"
12 | "github.com/llgcode/draw2d/samples/frameimage"
13 | "github.com/llgcode/draw2d/samples/geometry"
14 | "github.com/llgcode/draw2d/samples/gopher"
15 | "github.com/llgcode/draw2d/samples/gopher2"
16 | "github.com/llgcode/draw2d/samples/helloworld"
17 | "github.com/llgcode/draw2d/samples/line"
18 | "github.com/llgcode/draw2d/samples/linecapjoin"
19 | "github.com/llgcode/draw2d/samples/postscript"
20 | )
21 |
22 | func TestSampleAndroid(t *testing.T) {
23 | test(t, android.Main)
24 | }
25 |
26 | // TODO: FillString: w (width) is incorrect
27 | func TestSampleGeometry(t *testing.T) {
28 | // Set the global folder for searching fonts
29 | // The pdf backend needs for every ttf file its corresponding
30 | // json/.z file which is generated by gofpdf/makefont.
31 | draw2d.SetFontFolder("../resource/font")
32 | test(t, geometry.Main)
33 | }
34 |
35 | func TestSampleGopher(t *testing.T) {
36 | test(t, gopher.Main)
37 | }
38 |
39 | func TestSampleGopher2(t *testing.T) {
40 | test(t, gopher2.Main)
41 | }
42 |
43 | func TestSampleHelloWorld(t *testing.T) {
44 | // Set the global folder for searching fonts
45 | // The pdf backend needs for every ttf file its corresponding
46 | // json/.z file which is generated by gofpdf/makefont.
47 | draw2d.SetFontFolder("../resource/font")
48 | test(t, helloworld.Main)
49 | }
50 |
51 | func TestSampleFrameImage(t *testing.T) {
52 | test(t, frameimage.Main)
53 | }
54 |
55 | func TestSampleLine(t *testing.T) {
56 | test(t, line.Main)
57 | }
58 |
59 | func TestSampleLineCap(t *testing.T) {
60 | test(t, linecapjoin.Main)
61 | }
62 |
63 | func TestSamplePostscript(t *testing.T) {
64 | test(t, postscript.Main)
65 | }
66 |
--------------------------------------------------------------------------------
/draw2dsvg/svg.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednář
3 |
4 | package draw2dsvg
5 |
6 | import (
7 | "encoding/xml"
8 | )
9 |
10 | /* svg elements */
11 |
12 | type FontMode int
13 |
14 | // Modes of font handling in svg
15 | const (
16 | // Does nothing special
17 | // Makes sense only for common system fonts
18 | SysFontMode FontMode = 1 << iota
19 |
20 | // Links font files in css def
21 | // Requires distribution of font files with outputed svg
22 | LinkFontMode // TODO implement
23 |
24 | // Embeds glyphs definition in svg file itself in svg font format
25 | // Has poor browser support
26 | SvgFontMode
27 |
28 | // Embeds font definiton in svg file itself in woff format as part of css def
29 | CssFontMode // TODO implement
30 |
31 | // Converts texts to paths
32 | PathFontMode
33 | )
34 |
35 | type Svg struct {
36 | XMLName xml.Name `xml:"svg"`
37 | Xmlns string `xml:"xmlns,attr"`
38 | Width string `xml:"width,attr,omitempty"`
39 | Height string `xml:"height,attr,omitempty"`
40 | ViewBox string `xml:"viewBox,attr,omitempty"`
41 | Fonts []*Font `xml:"defs>font"`
42 | Masks []*Mask `xml:"defs>mask"`
43 | Groups []*Group `xml:"g"`
44 | FontMode FontMode `xml:"-"`
45 | FillStroke
46 | }
47 |
48 | func NewSvg() *Svg {
49 | return &Svg{
50 | Xmlns: "http://www.w3.org/2000/svg",
51 | FillStroke: FillStroke{Fill: "none", Stroke: "none"},
52 | FontMode: PathFontMode,
53 | }
54 | }
55 |
56 | type Group struct {
57 | FillStroke
58 | Transform string `xml:"transform,attr,omitempty"`
59 | Groups []*Group `xml:"g"`
60 | Paths []*Path `xml:"path"`
61 | Texts []*Text `xml:"text"`
62 | Image *Image `xml:"image"`
63 | Mask string `xml:"mask,attr,omitempty"`
64 | }
65 |
66 | type Path struct {
67 | FillStroke
68 | Desc string `xml:"d,attr"`
69 | }
70 |
71 | type Text struct {
72 | FillStroke
73 | Position
74 | FontSize float64 `xml:"font-size,attr,omitempty"`
75 | FontFamily string `xml:"font-family,attr,omitempty"`
76 | Text string `xml:",innerxml"`
77 | Style string `xml:"style,attr,omitempty"`
78 | }
79 |
80 | type Image struct {
81 | Position
82 | Dimension
83 | Href string `xml:"href,attr"`
84 | }
85 |
86 | type Mask struct {
87 | Identity
88 | Position
89 | Dimension
90 | }
91 |
92 | type Rect struct {
93 | Position
94 | Dimension
95 | FillStroke
96 | }
97 |
98 | func (m Mask) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
99 | bigRect := Rect{}
100 | bigRect.X, bigRect.Y = 0, 0
101 | bigRect.Width, bigRect.Height = "100%", "100%"
102 | bigRect.Fill = "#fff"
103 | rect := Rect{}
104 | rect.X, rect.Y = m.X, m.Y
105 | rect.Width, rect.Height = m.Width, m.Height
106 | rect.Fill = "#000"
107 |
108 | return e.EncodeElement(struct {
109 | XMLName xml.Name `xml:"mask"`
110 | Rects [2]Rect `xml:"rect"`
111 | Id string `xml:"id,attr"`
112 | }{
113 | Rects: [2]Rect{bigRect, rect},
114 | Id: m.Id,
115 | }, start)
116 | }
117 |
118 | /* font related elements */
119 |
120 | type Font struct {
121 | Identity
122 | Face *Face `xml:"font-face"`
123 | Glyphs []*Glyph `xml:"glyph"`
124 | }
125 |
126 | type Face struct {
127 | Family string `xml:"font-family,attr"`
128 | Units int `xml:"units-per-em,attr"`
129 | HorizAdvX float64 `xml:"horiz-adv-x,attr"`
130 | // TODO add other attrs, like style, variant, weight...
131 | }
132 |
133 | type Glyph struct {
134 | Rune Rune `xml:"unicode,attr"`
135 | Desc string `xml:"d,attr"`
136 | HorizAdvX float64 `xml:"horiz-adv-x,attr"`
137 | }
138 |
139 | type Rune rune
140 |
141 | func (r Rune) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
142 | return xml.Attr{
143 | Name: name,
144 | Value: string(rune(r)),
145 | }, nil
146 | }
147 |
148 | /* shared attrs */
149 |
150 | type Identity struct {
151 | Id string `xml:"id,attr"`
152 | Name string `xml:"name,attr"`
153 | }
154 |
155 | type Position struct {
156 | X float64 `xml:"x,attr,omitempty"`
157 | Y float64 `xml:"y,attr,omitempty"`
158 | }
159 |
160 | type Dimension struct {
161 | Width string `xml:"width,attr"`
162 | Height string `xml:"height,attr"`
163 | }
164 |
165 | type FillStroke struct {
166 | Fill string `xml:"fill,attr,omitempty"`
167 | FillRule string `xml:"fill-rule,attr,omitempty"`
168 |
169 | Stroke string `xml:"stroke,attr,omitempty"`
170 | StrokeWidth string `xml:"stroke-width,attr,omitempty"`
171 | StrokeLinecap string `xml:"stroke-linecap,attr,omitempty"`
172 | StrokeLinejoin string `xml:"stroke-linejoin,attr,omitempty"`
173 | StrokeDasharray string `xml:"stroke-dasharray,attr,omitempty"`
174 | StrokeDashoffset string `xml:"stroke-dashoffset,attr,omitempty"`
175 | }
176 |
--------------------------------------------------------------------------------
/draw2dsvg/test_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednář
3 |
4 | // Package draw2dsvg_test gives test coverage with the command:
5 | // go test -cover ./... | grep -v "no test"
6 | // (It should be run from its parent draw2d directory.)
7 | package draw2dsvg_test
8 |
9 | import (
10 | "testing"
11 |
12 | "github.com/llgcode/draw2d"
13 | "github.com/llgcode/draw2d/draw2dsvg"
14 | )
15 |
16 | type sample func(gc draw2d.GraphicContext, ext string) (string, error)
17 |
18 | func test(t *testing.T, draw sample) {
19 | // Initialize the graphic context on an pdf document
20 | dest := draw2dsvg.NewSvg()
21 | gc := draw2dsvg.NewGraphicContext(dest)
22 | // Draw sample
23 | output, err := draw(gc, "svg")
24 | if err != nil {
25 | t.Errorf("Drawing %q failed: %v", output, err)
26 | return
27 | }
28 | err = draw2dsvg.SaveToSvgFile(output, dest)
29 | if err != nil {
30 | t.Errorf("Saving %q failed: %v", output, err)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/draw2dsvg/text.go:
--------------------------------------------------------------------------------
1 | // NOTE that this is identical copy of draw2dgl/text.go and draw2dimg/text.go
2 | package draw2dsvg
3 |
4 | import (
5 | "github.com/golang/freetype/truetype"
6 | "github.com/llgcode/draw2d"
7 | "golang.org/x/image/math/fixed"
8 | )
9 |
10 | // DrawContour draws the given closed contour at the given sub-pixel offset.
11 | func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
12 | if len(ps) == 0 {
13 | return
14 | }
15 | startX, startY := pointToF64Point(ps[0])
16 | var others []truetype.Point
17 | if ps[0].Flags&0x01 != 0 {
18 | others = ps[1:]
19 | } else {
20 | lastX, lastY := pointToF64Point(ps[len(ps)-1])
21 | if ps[len(ps)-1].Flags&0x01 != 0 {
22 | startX, startY = lastX, lastY
23 | others = ps[:len(ps)-1]
24 | } else {
25 | startX = (startX + lastX) / 2
26 | startY = (startY + lastY) / 2
27 | others = ps
28 | }
29 | }
30 | path.MoveTo(startX+dx, startY+dy)
31 | q0X, q0Y, on0 := startX, startY, true
32 | for _, p := range others {
33 | qX, qY := pointToF64Point(p)
34 | on := p.Flags&0x01 != 0
35 | if on {
36 | if on0 {
37 | path.LineTo(qX+dx, qY+dy)
38 | } else {
39 | path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
40 | }
41 | } else {
42 | if on0 {
43 | // No-op.
44 | } else {
45 | midX := (q0X + qX) / 2
46 | midY := (q0Y + qY) / 2
47 | path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
48 | }
49 | }
50 | q0X, q0Y, on0 = qX, qY, on
51 | }
52 | // Close the curve.
53 | if on0 {
54 | path.LineTo(startX+dx, startY+dy)
55 | } else {
56 | path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
57 | }
58 | }
59 |
60 | func pointToF64Point(p truetype.Point) (x, y float64) {
61 | return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
62 | }
63 |
64 | func fUnitsToFloat64(x fixed.Int26_6) float64 {
65 | scaled := x << 2
66 | return float64(scaled/256) + float64(scaled%256)/256.0
67 | }
68 |
69 | // FontExtents contains font metric information.
70 | type FontExtents struct {
71 | // Ascent is the distance that the text
72 | // extends above the baseline.
73 | Ascent float64
74 |
75 | // Descent is the distance that the text
76 | // extends below the baseline. The descent
77 | // is given as a negative value.
78 | Descent float64
79 |
80 | // Height is the distance from the lowest
81 | // descending point to the highest ascending
82 | // point.
83 | Height float64
84 | }
85 |
86 | // Extents returns the FontExtents for a font.
87 | // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
88 | func Extents(font *truetype.Font, size float64) FontExtents {
89 | bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
90 | scale := size / float64(font.FUnitsPerEm())
91 | return FontExtents{
92 | Ascent: float64(bounds.Max.Y) * scale,
93 | Descent: float64(bounds.Min.Y) * scale,
94 | Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/draw2dsvg/xml_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 The draw2d Authors. All rights reserved.
2 | // created: 16/12/2017 by Drahoslav Bednář
3 |
4 | // Package draw2dsvg_test gives test coverage with the command:
5 | // go test -cover ./... | grep -v "no test"
6 | // (It should be run from its parent draw2d directory.)
7 | package draw2dsvg
8 |
9 | import (
10 | "encoding/xml"
11 | "testing"
12 | )
13 |
14 | // Test basic encoding of svg/xml elements
15 | func TestXml(t *testing.T) {
16 |
17 | svg := NewSvg()
18 | svg.Groups = []*Group{&Group{
19 | Groups: []*Group{
20 | &Group{}, // nested groups
21 | &Group{},
22 | },
23 | Texts: []*Text{
24 | &Text{Text: "Hello"}, // text
25 | &Text{Text: "world", Style: "opacity: 0.5"}, // text with style
26 | },
27 | Paths: []*Path{
28 | &Path{Desc: "M100,200 C100,100 250,100 250,200 S400,300 400,200"}, // simple path
29 | &Path{}, // empty path
30 | },
31 | }}
32 |
33 | expectedOut := ``
44 |
45 | out, err := xml.MarshalIndent(svg, "", " ")
46 |
47 | if err != nil {
48 | t.Error(err)
49 | }
50 | if string(out) != expectedOut {
51 | t.Errorf("svg output is not as expected\n"+
52 | "got:\n%s\n\n"+
53 | "want:\n%s\n",
54 | string(out),
55 | expectedOut,
56 | )
57 | }
58 | }
59 |
60 | func TestXml_WidthHeight(t *testing.T) {
61 | expect := ``
64 | svg := NewSvg()
65 | subtest := func(t *testing.T) {
66 | out, err := xml.MarshalIndent(svg, "", " ")
67 | if err != nil {
68 | t.Fatal(err)
69 | }
70 | if string(out) != expect {
71 | t.Errorf("svg output is not as expected\n"+
72 | "got:\n%s\n\n"+
73 | "want:\n%s\n",
74 | string(out),
75 | expect)
76 | }
77 | }
78 | t.Run("no width, height", subtest)
79 |
80 | expect = ``
83 | svg.Width, svg.Height = "640px", "480"
84 | t.Run("with width, height", subtest)
85 | }
86 |
87 | func TestXml_ViewBox(t *testing.T) {
88 | expect := ``
91 |
92 | svg := NewSvg()
93 | subtest := func(t *testing.T) {
94 | out, err := xml.MarshalIndent(svg, "", " ")
95 | if err != nil {
96 | t.Fatal(err)
97 | }
98 | if string(out) != expect {
99 | t.Errorf("svg output is not as expected\n"+
100 | "got:\n%s\n\n"+
101 | "want:\n%s\n",
102 | string(out),
103 | expect)
104 | }
105 | }
106 | svg.ViewBox = "0 100 -10 -100"
107 | t.Run("with viewBox", subtest)
108 | }
109 |
--------------------------------------------------------------------------------
/font.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 13/12/2010 by Laurent Le Goff
3 |
4 | package draw2d
5 |
6 | import (
7 | "log"
8 | "os"
9 | "path/filepath"
10 |
11 | "github.com/golang/freetype/truetype"
12 | "sync"
13 | )
14 |
15 | // FontStyle defines bold and italic styles for the font
16 | // It is possible to combine values for mixed styles, eg.
17 | // FontData.Style = FontStyleBold | FontStyleItalic
18 | type FontStyle byte
19 |
20 | const (
21 | FontStyleNormal FontStyle = iota
22 | FontStyleBold
23 | FontStyleItalic
24 | )
25 |
26 | type FontFamily byte
27 |
28 | const (
29 | FontFamilySans FontFamily = iota
30 | FontFamilySerif
31 | FontFamilyMono
32 | )
33 |
34 | type FontData struct {
35 | Name string
36 | Family FontFamily
37 | Style FontStyle
38 | }
39 |
40 | type FontFileNamer func(fontData FontData) string
41 |
42 | func FontFileName(fontData FontData) string {
43 | fontFileName := fontData.Name
44 | switch fontData.Family {
45 | case FontFamilySans:
46 | fontFileName += "s"
47 | case FontFamilySerif:
48 | fontFileName += "r"
49 | case FontFamilyMono:
50 | fontFileName += "m"
51 | }
52 | if fontData.Style&FontStyleBold != 0 {
53 | fontFileName += "b"
54 | } else {
55 | fontFileName += "r"
56 | }
57 |
58 | if fontData.Style&FontStyleItalic != 0 {
59 | fontFileName += "i"
60 | }
61 | fontFileName += ".ttf"
62 | return fontFileName
63 | }
64 |
65 | func RegisterFont(fontData FontData, font *truetype.Font) {
66 | fontCache.Store(fontData, font)
67 | }
68 |
69 | func GetFont(fontData FontData) (font *truetype.Font) {
70 | var err error
71 |
72 | if font, err = fontCache.Load(fontData); err != nil {
73 | log.Println(err)
74 | }
75 |
76 | return
77 | }
78 |
79 | func GetFontFolder() string {
80 | return defaultFonts.folder
81 | }
82 |
83 | func SetFontFolder(folder string) {
84 | defaultFonts.setFolder(filepath.Clean(folder))
85 | }
86 |
87 | func GetGlobalFontCache() FontCache {
88 | return fontCache
89 | }
90 |
91 | func SetFontNamer(fn FontFileNamer) {
92 | defaultFonts.setNamer(fn)
93 | }
94 |
95 | // Types implementing this interface can be passed to SetFontCache to change the
96 | // way fonts are being stored and retrieved.
97 | type FontCache interface {
98 | // Loads a truetype font represented by the FontData object passed as
99 | // argument.
100 | // The method returns an error if the font could not be loaded, either
101 | // because it didn't exist or the resource it was loaded from was corrupted.
102 | Load(FontData) (*truetype.Font, error)
103 |
104 | // Sets the truetype font that will be returned by Load when given the font
105 | // data passed as first argument.
106 | Store(FontData, *truetype.Font)
107 | }
108 |
109 | // Changes the font cache backend used by the package. After calling this
110 | // functionSetFontFolder and SetFontNamer will not affect anymore how fonts are
111 | // loaded.
112 | // To restore the default font cache, call this function passing nil as argument.
113 | func SetFontCache(cache FontCache) {
114 | if cache == nil {
115 | fontCache = defaultFonts
116 | } else {
117 | fontCache = cache
118 | }
119 | }
120 |
121 | // FolderFontCache can Load font from folder
122 | type FolderFontCache struct {
123 | fonts map[string]*truetype.Font
124 | folder string
125 | namer FontFileNamer
126 | }
127 |
128 | // NewFolderFontCache creates FolderFontCache
129 | func NewFolderFontCache(folder string) *FolderFontCache {
130 | return &FolderFontCache{
131 | fonts: make(map[string]*truetype.Font),
132 | folder: folder,
133 | namer: FontFileName,
134 | }
135 | }
136 |
137 | // Load a font from cache if exists otherwise it will load the font from file
138 | func (cache *FolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
139 | if font = cache.fonts[cache.namer(fontData)]; font != nil {
140 | return font, nil
141 | }
142 |
143 | var data []byte
144 | var file = cache.namer(fontData)
145 |
146 | if data, err = os.ReadFile(filepath.Join(cache.folder, file)); err != nil {
147 | return
148 | }
149 |
150 | if font, err = truetype.Parse(data); err != nil {
151 | return
152 | }
153 |
154 | cache.fonts[file] = font
155 | return
156 | }
157 |
158 | // Store a font to this cache
159 | func (cache *FolderFontCache) Store(fontData FontData, font *truetype.Font) {
160 | cache.fonts[cache.namer(fontData)] = font
161 | }
162 |
163 | // SyncFolderFontCache can Load font from folder
164 | type SyncFolderFontCache struct {
165 | sync.RWMutex
166 | fonts map[string]*truetype.Font
167 | folder string
168 | namer FontFileNamer
169 | }
170 |
171 | // NewSyncFolderFontCache creates SyncFolderFontCache
172 | func NewSyncFolderFontCache(folder string) *SyncFolderFontCache {
173 | return &SyncFolderFontCache{
174 | fonts: make(map[string]*truetype.Font),
175 | folder: folder,
176 | namer: FontFileName,
177 | }
178 | }
179 |
180 | func (cache *SyncFolderFontCache) setFolder(folder string) {
181 | cache.Lock()
182 | cache.folder = folder
183 | cache.Unlock()
184 | }
185 |
186 | func (cache *SyncFolderFontCache) setNamer(namer FontFileNamer) {
187 | cache.Lock()
188 | cache.namer = namer
189 | cache.Unlock()
190 | }
191 |
192 | // Load a font from cache if exists otherwise it will load the font from file
193 | func (cache *SyncFolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
194 | cache.RLock()
195 | font = cache.fonts[cache.namer(fontData)]
196 | cache.RUnlock()
197 |
198 | if font != nil {
199 | return font, nil
200 | }
201 |
202 | var data []byte
203 | var file = cache.namer(fontData)
204 |
205 | if data, err = os.ReadFile(filepath.Join(cache.folder, file)); err != nil {
206 | return
207 | }
208 |
209 | if font, err = truetype.Parse(data); err != nil {
210 | return
211 | }
212 | cache.Lock()
213 | cache.fonts[file] = font
214 | cache.Unlock()
215 | return
216 | }
217 |
218 | // Store a font to this cache
219 | func (cache *SyncFolderFontCache) Store(fontData FontData, font *truetype.Font) {
220 | cache.Lock()
221 | cache.fonts[cache.namer(fontData)] = font
222 | cache.Unlock()
223 | }
224 |
225 | var (
226 | defaultFonts = NewSyncFolderFontCache("../resource/font")
227 |
228 | fontCache FontCache = defaultFonts
229 | )
230 |
--------------------------------------------------------------------------------
/gc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | package draw2d
5 |
6 | import (
7 | "image"
8 | "image/color"
9 | )
10 |
11 | // GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
12 | type GraphicContext interface {
13 | // PathBuilder describes the interface for path drawing
14 | PathBuilder
15 | // BeginPath creates a new path
16 | BeginPath()
17 | // GetPath copies the current path, then returns it
18 | GetPath() Path
19 | // GetMatrixTransform returns the current transformation matrix
20 | GetMatrixTransform() Matrix
21 | // SetMatrixTransform sets the current transformation matrix
22 | SetMatrixTransform(tr Matrix)
23 | // ComposeMatrixTransform composes the current transformation matrix with tr
24 | ComposeMatrixTransform(tr Matrix)
25 | // Rotate applies a rotation to the current transformation matrix. angle is in radian.
26 | Rotate(angle float64)
27 | // Translate applies a translation to the current transformation matrix.
28 | Translate(tx, ty float64)
29 | // Scale applies a scale to the current transformation matrix.
30 | Scale(sx, sy float64)
31 | // SetStrokeColor sets the current stroke color
32 | SetStrokeColor(c color.Color)
33 | // SetFillColor sets the current fill color
34 | SetFillColor(c color.Color)
35 | // SetFillRule sets the current fill rule
36 | SetFillRule(f FillRule)
37 | // SetLineWidth sets the current line width
38 | SetLineWidth(lineWidth float64)
39 | // SetLineCap sets the current line cap
40 | SetLineCap(cap LineCap)
41 | // SetLineJoin sets the current line join
42 | SetLineJoin(join LineJoin)
43 | // SetLineDash sets the current dash
44 | SetLineDash(dash []float64, dashOffset float64)
45 | // SetFontSize sets the current font size
46 | SetFontSize(fontSize float64)
47 | // GetFontSize gets the current font size
48 | GetFontSize() float64
49 | // SetFontData sets the current FontData
50 | SetFontData(fontData FontData)
51 | // GetFontData gets the current FontData
52 | GetFontData() FontData
53 | // GetFontName gets the current FontData as a string
54 | GetFontName() string
55 | // DrawImage draws the raster image in the current canvas
56 | DrawImage(image image.Image)
57 | // Save the context and push it to the context stack
58 | Save()
59 | // Restore remove the current context and restore the last one
60 | Restore()
61 | // Clear fills the current canvas with a default transparent color
62 | Clear()
63 | // ClearRect fills the specified rectangle with a default transparent color
64 | ClearRect(x1, y1, x2, y2 int)
65 | // SetDPI sets the current DPI
66 | SetDPI(dpi int)
67 | // GetDPI gets the current DPI
68 | GetDPI() int
69 | // GetStringBounds gets pixel bounds(dimensions) of given string
70 | GetStringBounds(s string) (left, top, right, bottom float64)
71 | // CreateStringPath creates a path from the string s at x, y
72 | CreateStringPath(text string, x, y float64) (cursor float64)
73 | // FillString draws the text at point (0, 0)
74 | FillString(text string) (cursor float64)
75 | // FillStringAt draws the text at the specified point (x, y)
76 | FillStringAt(text string, x, y float64) (cursor float64)
77 | // StrokeString draws the contour of the text at point (0, 0)
78 | StrokeString(text string) (cursor float64)
79 | // StrokeStringAt draws the contour of the text at point (x, y)
80 | StrokeStringAt(text string, x, y float64) (cursor float64)
81 | // Stroke strokes the paths with the color specified by SetStrokeColor
82 | Stroke(paths ...*Path)
83 | // Fill fills the paths with the color specified by SetFillColor
84 | Fill(paths ...*Path)
85 | // FillStroke first fills the paths and than strokes them
86 | FillStroke(paths ...*Path)
87 | }
88 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/llgcode/draw2d
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
7 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af
8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
9 | github.com/jung-kurt/gofpdf v1.16.2
10 | github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e
11 | golang.org/x/image v0.18.0
12 | )
13 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
4 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
5 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af h1:V4DLCrN57QoLQYtwnlmGQvAIM6DMU5eMrR9VQhmxPPs=
6 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af/go.mod h1:wyvWpaEu9B/VQiV1jsPs7Mha9I7yto/HqIBw197ZAzk=
7 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
9 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
10 | github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
11 | github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
12 | github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4yCXUNiFKefEhH0qfImDDD0/8=
13 | github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
14 | github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
15 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
18 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
19 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
20 | golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
21 | golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
22 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
23 |
--------------------------------------------------------------------------------
/matrix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | package draw2d
5 |
6 | import (
7 | "math"
8 | )
9 |
10 | // Matrix represents an affine transformation
11 | type Matrix [6]float64
12 |
13 | const (
14 | epsilon = 1e-6
15 | )
16 |
17 | // Determinant compute the determinant of the matrix
18 | func (tr Matrix) Determinant() float64 {
19 | return tr[0]*tr[3] - tr[1]*tr[2]
20 | }
21 |
22 | // Transform applies the transformation matrix to points. It modify the points passed in parameter.
23 | func (tr Matrix) Transform(points []float64) {
24 | for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
25 | x := points[i]
26 | y := points[j]
27 | points[i] = x*tr[0] + y*tr[2] + tr[4]
28 | points[j] = x*tr[1] + y*tr[3] + tr[5]
29 | }
30 | }
31 |
32 | // TransformPoint applies the transformation matrix to point. It returns the point the transformed point.
33 | func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) {
34 | xres = x*tr[0] + y*tr[2] + tr[4]
35 | yres = x*tr[1] + y*tr[3] + tr[5]
36 | return xres, yres
37 | }
38 |
39 | func minMax(x, y float64) (min, max float64) {
40 | if x > y {
41 | return y, x
42 | }
43 | return x, y
44 | }
45 |
46 | // Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle
47 | func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) {
48 | points := []float64{x0, y0, x2, y0, x2, y2, x0, y2}
49 | tr.Transform(points)
50 | points[0], points[2] = minMax(points[0], points[2])
51 | points[4], points[6] = minMax(points[4], points[6])
52 | points[1], points[3] = minMax(points[1], points[3])
53 | points[5], points[7] = minMax(points[5], points[7])
54 |
55 | nx0 = math.Min(points[0], points[4])
56 | ny0 = math.Min(points[1], points[5])
57 | nx2 = math.Max(points[2], points[6])
58 | ny2 = math.Max(points[3], points[7])
59 | return nx0, ny0, nx2, ny2
60 | }
61 |
62 | // InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle
63 | func (tr Matrix) InverseTransform(points []float64) {
64 | d := tr.Determinant() // matrix determinant
65 | for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
66 | x := points[i]
67 | y := points[j]
68 | points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
69 | points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
70 | }
71 | }
72 |
73 | // InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point.
74 | func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) {
75 | d := tr.Determinant() // matrix determinant
76 | xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d
77 | yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d
78 | return xres, yres
79 | }
80 |
81 | // VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix.
82 | // It modify the points passed in parameter.
83 | func (tr Matrix) VectorTransform(points []float64) {
84 | for i, j := 0, 1; j < len(points); i, j = i+2, j+2 {
85 | x := points[i]
86 | y := points[j]
87 | points[i] = x*tr[0] + y*tr[2]
88 | points[j] = x*tr[1] + y*tr[3]
89 | }
90 | }
91 |
92 | // NewIdentityMatrix creates an identity transformation matrix.
93 | func NewIdentityMatrix() Matrix {
94 | return Matrix{1, 0, 0, 1, 0, 0}
95 | }
96 |
97 | // NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter
98 | func NewTranslationMatrix(tx, ty float64) Matrix {
99 | return Matrix{1, 0, 0, 1, tx, ty}
100 | }
101 |
102 | // NewScaleMatrix creates a transformation matrix with a sx, sy scale factor
103 | func NewScaleMatrix(sx, sy float64) Matrix {
104 | return Matrix{sx, 0, 0, sy, 0, 0}
105 | }
106 |
107 | // NewRotationMatrix creates a rotation transformation matrix. angle is in radian
108 | func NewRotationMatrix(angle float64) Matrix {
109 | c := math.Cos(angle)
110 | s := math.Sin(angle)
111 | return Matrix{c, s, -s, c, 0, 0}
112 | }
113 |
114 | // NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2.
115 | func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix {
116 | xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0])
117 | yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1])
118 | xOffset := rectangle2[0] - (rectangle1[0] * xScale)
119 | yOffset := rectangle2[1] - (rectangle1[1] * yScale)
120 | return Matrix{xScale, 0, 0, yScale, xOffset, yOffset}
121 | }
122 |
123 | // Inverse computes the inverse matrix
124 | func (tr *Matrix) Inverse() {
125 | d := tr.Determinant() // matrix determinant
126 | tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
127 | tr[0] = tr3 / d
128 | tr[1] = -tr1 / d
129 | tr[2] = -tr2 / d
130 | tr[3] = tr0 / d
131 | tr[4] = (tr2*tr5 - tr3*tr4) / d
132 | tr[5] = (tr1*tr4 - tr0*tr5) / d
133 | }
134 |
135 | func (tr Matrix) Copy() Matrix {
136 | var result Matrix
137 | copy(result[:], tr[:])
138 | return result
139 | }
140 |
141 | // Compose multiplies trToConcat x tr
142 | func (tr *Matrix) Compose(trToCompose Matrix) {
143 | tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5]
144 | tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2
145 | tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1
146 | tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2
147 | tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1
148 | tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4
149 | tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5
150 | }
151 |
152 | // Scale adds a scale to the matrix
153 | func (tr *Matrix) Scale(sx, sy float64) {
154 | tr[0] = sx * tr[0]
155 | tr[1] = sx * tr[1]
156 | tr[2] = sy * tr[2]
157 | tr[3] = sy * tr[3]
158 | }
159 |
160 | // Translate adds a translation to the matrix
161 | func (tr *Matrix) Translate(tx, ty float64) {
162 | tr[4] = tx*tr[0] + ty*tr[2] + tr[4]
163 | tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
164 | }
165 |
166 | // Rotate adds a rotation to the matrix. angle is in radian
167 | func (tr *Matrix) Rotate(angle float64) {
168 | c := math.Cos(angle)
169 | s := math.Sin(angle)
170 | t0 := c*tr[0] + s*tr[2]
171 | t1 := s*tr[3] + c*tr[1]
172 | t2 := c*tr[2] - s*tr[0]
173 | t3 := c*tr[3] - s*tr[1]
174 | tr[0] = t0
175 | tr[1] = t1
176 | tr[2] = t2
177 | tr[3] = t3
178 | }
179 |
180 | // GetTranslation
181 | func (tr Matrix) GetTranslation() (x, y float64) {
182 | return tr[4], tr[5]
183 | }
184 |
185 | // GetScaling
186 | func (tr Matrix) GetScaling() (x, y float64) {
187 | return tr[0], tr[3]
188 | }
189 |
190 | // GetScale computes a scale for the matrix
191 | func (tr Matrix) GetScale() float64 {
192 | x := 0.707106781*tr[0] + 0.707106781*tr[1]
193 | y := 0.707106781*tr[2] + 0.707106781*tr[3]
194 | return math.Sqrt(x*x + y*y)
195 | }
196 |
197 | // ******************** Testing ********************
198 |
199 | // Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements.
200 | func (tr1 Matrix) Equals(tr2 Matrix) bool {
201 | for i := 0; i < 6; i = i + 1 {
202 | if !fequals(tr1[i], tr2[i]) {
203 | return false
204 | }
205 | }
206 | return true
207 | }
208 |
209 | // IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements.
210 | func (tr Matrix) IsIdentity() bool {
211 | return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation()
212 | }
213 |
214 | // IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements.
215 | func (tr Matrix) IsTranslation() bool {
216 | return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1)
217 | }
218 |
219 | // fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise
220 | func fequals(float1, float2 float64) bool {
221 | return math.Abs(float1-float2) <= epsilon
222 | }
223 |
--------------------------------------------------------------------------------
/output/README.md:
--------------------------------------------------------------------------------
1 | Demo output
2 | ===========
3 |
4 | These folders are empty when you check out the git repository. The output is generated by the tests:
5 | ```
6 | go test ./...
7 | ```
8 | or with coverage:
9 | ```
10 | go test -cover ./... | grep -v "no test"
11 | ```
12 |
--------------------------------------------------------------------------------
/output/curve/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/output/draw2dkit/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/output/raster/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
--------------------------------------------------------------------------------
/output/samples/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 | !geometry.png
6 | !postscript.png
--------------------------------------------------------------------------------
/output/samples/geometry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/output/samples/geometry.png
--------------------------------------------------------------------------------
/output/samples/postscript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/output/samples/postscript.png
--------------------------------------------------------------------------------
/path.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | package draw2d
5 |
6 | import (
7 | "fmt"
8 | "math"
9 | )
10 |
11 | // PathBuilder describes the interface for path drawing.
12 | type PathBuilder interface {
13 | // LastPoint returns the current point of the current sub path
14 | LastPoint() (x, y float64)
15 | // MoveTo creates a new subpath that start at the specified point
16 | MoveTo(x, y float64)
17 | // LineTo adds a line to the current subpath
18 | LineTo(x, y float64)
19 | // QuadCurveTo adds a quadratic Bézier curve to the current subpath
20 | QuadCurveTo(cx, cy, x, y float64)
21 | // CubicCurveTo adds a cubic Bézier curve to the current subpath
22 | CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
23 | // ArcTo adds an arc to the current subpath
24 | ArcTo(cx, cy, rx, ry, startAngle, angle float64)
25 | // Close creates a line from the current point to the last MoveTo
26 | // point (if not the same) and mark the path as closed so the
27 | // first and last lines join nicely.
28 | Close()
29 | }
30 |
31 | // PathCmp represents component of a path
32 | type PathCmp int
33 |
34 | const (
35 | // MoveToCmp is a MoveTo component in a Path
36 | MoveToCmp PathCmp = iota
37 | // LineToCmp is a LineTo component in a Path
38 | LineToCmp
39 | // QuadCurveToCmp is a QuadCurveTo component in a Path
40 | QuadCurveToCmp
41 | // CubicCurveToCmp is a CubicCurveTo component in a Path
42 | CubicCurveToCmp
43 | // ArcToCmp is a ArcTo component in a Path
44 | ArcToCmp
45 | // CloseCmp is a ArcTo component in a Path
46 | CloseCmp
47 | )
48 |
49 | // Path stores points
50 | type Path struct {
51 | // Components is a slice of PathCmp in a Path and mark the role of each points in the Path
52 | Components []PathCmp
53 | // Points are combined with Components to have a specific role in the path
54 | Points []float64
55 | // Last Point of the Path
56 | x, y float64
57 | }
58 |
59 | func (p *Path) appendToPath(cmd PathCmp, points ...float64) {
60 | p.Components = append(p.Components, cmd)
61 | p.Points = append(p.Points, points...)
62 | }
63 |
64 | // LastPoint returns the current point of the current path
65 | func (p *Path) LastPoint() (x, y float64) {
66 | return p.x, p.y
67 | }
68 |
69 | // MoveTo starts a new path at (x, y) position
70 | func (p *Path) MoveTo(x, y float64) {
71 | p.appendToPath(MoveToCmp, x, y)
72 | p.x = x
73 | p.y = y
74 | }
75 |
76 | // LineTo adds a line to the current path
77 | func (p *Path) LineTo(x, y float64) {
78 | if len(p.Components) == 0 { //special case when no move has been done
79 | p.MoveTo(x, y)
80 | } else {
81 | p.appendToPath(LineToCmp, x, y)
82 | }
83 | p.x = x
84 | p.y = y
85 | }
86 |
87 | // QuadCurveTo adds a quadratic bezier curve to the current path
88 | func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
89 | if len(p.Components) == 0 { //special case when no move has been done
90 | p.MoveTo(x, y)
91 | } else {
92 | p.appendToPath(QuadCurveToCmp, cx, cy, x, y)
93 | }
94 | p.x = x
95 | p.y = y
96 | }
97 |
98 | // CubicCurveTo adds a cubic bezier curve to the current path
99 | func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
100 | if len(p.Components) == 0 { //special case when no move has been done
101 | p.MoveTo(x, y)
102 | } else {
103 | p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y)
104 | }
105 | p.x = x
106 | p.y = y
107 | }
108 |
109 | // ArcTo adds an arc to the path
110 | func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) {
111 | endAngle := startAngle + angle
112 | clockWise := true
113 | if angle < 0 {
114 | clockWise = false
115 | }
116 | // normalize
117 | if clockWise {
118 | for endAngle < startAngle {
119 | endAngle += math.Pi * 2.0
120 | }
121 | } else {
122 | for startAngle < endAngle {
123 | startAngle += math.Pi * 2.0
124 | }
125 | }
126 | startX := cx + math.Cos(startAngle)*rx
127 | startY := cy + math.Sin(startAngle)*ry
128 | if len(p.Components) > 0 {
129 | p.LineTo(startX, startY)
130 | } else {
131 | p.MoveTo(startX, startY)
132 | }
133 | p.appendToPath(ArcToCmp, cx, cy, rx, ry, startAngle, angle)
134 | p.x = cx + math.Cos(endAngle)*rx
135 | p.y = cy + math.Sin(endAngle)*ry
136 | }
137 |
138 | // Close closes the current path
139 | func (p *Path) Close() {
140 | p.appendToPath(CloseCmp)
141 | }
142 |
143 | // Copy make a clone of the current path and return it
144 | func (p *Path) Copy() (dest *Path) {
145 | dest = new(Path)
146 | dest.Components = make([]PathCmp, len(p.Components))
147 | copy(dest.Components, p.Components)
148 | dest.Points = make([]float64, len(p.Points))
149 | copy(dest.Points, p.Points)
150 | dest.x, dest.y = p.x, p.y
151 | return dest
152 | }
153 |
154 | // Clear reset the path
155 | func (p *Path) Clear() {
156 | p.Components = p.Components[0:0]
157 | p.Points = p.Points[0:0]
158 | return
159 | }
160 |
161 | // IsEmpty returns true if the path is empty
162 | func (p *Path) IsEmpty() bool {
163 | return len(p.Components) == 0
164 | }
165 |
166 | // String returns a debug text view of the path
167 | func (p *Path) String() string {
168 | s := ""
169 | j := 0
170 | for _, cmd := range p.Components {
171 | switch cmd {
172 | case MoveToCmp:
173 | s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1])
174 | j = j + 2
175 | case LineToCmp:
176 | s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1])
177 | j = j + 2
178 | case QuadCurveToCmp:
179 | s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3])
180 | j = j + 4
181 | case CubicCurveToCmp:
182 | s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
183 | j = j + 6
184 | case ArcToCmp:
185 | s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5])
186 | j = j + 6
187 | case CloseCmp:
188 | s += "Close\n"
189 | }
190 | }
191 | return s
192 | }
193 |
194 | // Returns new Path with flipped y axes
195 | func (path *Path) VerticalFlip() *Path {
196 | p := path.Copy()
197 | j := 0
198 | for _, cmd := range p.Components {
199 | switch cmd {
200 | case MoveToCmp, LineToCmp:
201 | p.Points[j+1] = -p.Points[j+1]
202 | j = j + 2
203 | case QuadCurveToCmp:
204 | p.Points[j+1] = -p.Points[j+1]
205 | p.Points[j+3] = -p.Points[j+3]
206 | j = j + 4
207 | case CubicCurveToCmp:
208 | p.Points[j+1] = -p.Points[j+1]
209 | p.Points[j+3] = -p.Points[j+3]
210 | p.Points[j+5] = -p.Points[j+5]
211 | j = j + 6
212 | case ArcToCmp:
213 | p.Points[j+1] = -p.Points[j+1]
214 | p.Points[j+3] = -p.Points[j+3]
215 | p.Points[j+4] = -p.Points[j+4] // start angle
216 | p.Points[j+5] = -p.Points[j+5] // angle
217 | j = j + 6
218 | case CloseCmp:
219 | }
220 | }
221 | p.y = -p.y
222 | return p
223 | }
224 |
--------------------------------------------------------------------------------
/resource/font/COPYING:
--------------------------------------------------------------------------------
1 | Luxi fonts copyright (c) 2001 by Bigelow & Holmes Inc. Luxi font
2 | instruction code copyright (c) 2001 by URW++ GmbH. All Rights
3 | Reserved. Luxi is a registered trademark of Bigelow & Holmes Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of these Fonts and associated documentation files (the "Font
7 | Software"), to deal in the Font Software, including without
8 | limitation the rights to use, copy, merge, publish, distribute,
9 | sublicense, and/or sell copies of the Font Software, and to permit
10 | persons to whom the Font Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright and trademark notices and this permission notice
14 | shall be included in all copies of one or more of the Font Software.
15 |
16 | The Font Software may not be modified, altered, or added to, and in
17 | particular the designs of glyphs or characters in the Fonts may not
18 | be modified nor may additional glyphs or characters be added to the
19 | Fonts. This License becomes null and void when the Fonts or Font
20 | Software have been modified.
21 |
22 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
25 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
26 | BIGELOW & HOLMES INC. OR URW++ GMBH. BE LIABLE FOR ANY CLAIM, DAMAGES
27 | OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
28 | INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF
29 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
30 | INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT
31 | SOFTWARE.
32 |
33 | Except as contained in this notice, the names of Bigelow & Holmes
34 | Inc. and URW++ GmbH. shall not be used in advertising or otherwise to
35 | promote the sale, use or other dealings in this Font Software without
36 | prior written authorization from Bigelow & Holmes Inc. and URW++ GmbH.
37 |
38 | For further information, contact:
39 |
40 | info@urwpp.de
41 | or
42 | design@bigelowandholmes.com
43 |
--------------------------------------------------------------------------------
/resource/font/README:
--------------------------------------------------------------------------------
1 |
2 | All questions regarding this software should be directed at the
3 | Xorg mailing list:
4 |
5 | http://lists.freedesktop.org/mailman/listinfo/xorg
6 |
7 | Please submit bug reports to the Xorg bugzilla:
8 |
9 | https://bugs.freedesktop.org/enter_bug.cgi?product=xorg
10 |
11 | The master development code repository can be found at:
12 |
13 | git://anongit.freedesktop.org/git/xorg/font/bh-ttf
14 |
15 | http://cgit.freedesktop.org/xorg/font/bh-ttf
16 |
17 | For patch submission instructions, see:
18 |
19 | http://www.x.org/wiki/Development/Documentation/SubmittingPatches
20 |
21 | For more information on the git code manager, see:
22 |
23 | http://wiki.x.org/wiki/GitPage
24 |
25 |
--------------------------------------------------------------------------------
/resource/font/luximb.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luximb.ttf
--------------------------------------------------------------------------------
/resource/font/luximbi.json:
--------------------------------------------------------------------------------
1 | {"Tp":"TrueType","Name":"LuxiMono-BoldOblique","Desc":{"Ascent":783,"Descent":-205,"CapHeight":783,"Flags":97,"FontBBox":{"Xmin":-29,"Ymin":-211,"Xmax":764,"Ymax":1012},"ItalicAngle":-8,"StemV":120,"MissingWidth":600},"Up":0,"Ut":0,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,653,600,653,600,600,653,600,653,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600],"Enc":"cp1252","Diff":"","File":"luximbi.z","Size1":0,"Size2":0,"OriginalSize":69872,"I":0,"N":0,"DiffN":0}
--------------------------------------------------------------------------------
/resource/font/luximbi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luximbi.ttf
--------------------------------------------------------------------------------
/resource/font/luximbi.z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luximbi.z
--------------------------------------------------------------------------------
/resource/font/luximr.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luximr.ttf
--------------------------------------------------------------------------------
/resource/font/luximri.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luximri.ttf
--------------------------------------------------------------------------------
/resource/font/luxirb.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxirb.ttf
--------------------------------------------------------------------------------
/resource/font/luxirbi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxirbi.ttf
--------------------------------------------------------------------------------
/resource/font/luxirr.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxirr.ttf
--------------------------------------------------------------------------------
/resource/font/luxirri.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxirri.ttf
--------------------------------------------------------------------------------
/resource/font/luxisb.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxisb.ttf
--------------------------------------------------------------------------------
/resource/font/luxisbi.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxisbi.ttf
--------------------------------------------------------------------------------
/resource/font/luxisr.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxisr.ttf
--------------------------------------------------------------------------------
/resource/font/luxisri.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/font/luxisri.ttf
--------------------------------------------------------------------------------
/resource/image/geometry.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/image/geometry.pdf
--------------------------------------------------------------------------------
/resource/image/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/image/gopher.png
--------------------------------------------------------------------------------
/resource/image/postscript.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/image/postscript.pdf
--------------------------------------------------------------------------------
/resource/result/TestAndroid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestAndroid.png
--------------------------------------------------------------------------------
/resource/result/TestBigPicture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestBigPicture.png
--------------------------------------------------------------------------------
/resource/result/TestBubble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestBubble.png
--------------------------------------------------------------------------------
/resource/result/TestCurveRectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestCurveRectangle.png
--------------------------------------------------------------------------------
/resource/result/TestDash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestDash.png
--------------------------------------------------------------------------------
/resource/result/TestDrawArc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestDrawArc.png
--------------------------------------------------------------------------------
/resource/result/TestDrawArcNegative.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestDrawArcNegative.png
--------------------------------------------------------------------------------
/resource/result/TestDrawCubicCurve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestDrawCubicCurve.png
--------------------------------------------------------------------------------
/resource/result/TestDrawImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestDrawImage.png
--------------------------------------------------------------------------------
/resource/result/TestFillString.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestFillString.png
--------------------------------------------------------------------------------
/resource/result/TestFillStroke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestFillStroke.png
--------------------------------------------------------------------------------
/resource/result/TestFillStyle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestFillStyle.png
--------------------------------------------------------------------------------
/resource/result/TestGopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestGopher.png
--------------------------------------------------------------------------------
/resource/result/TestLineCap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestLineCap.png
--------------------------------------------------------------------------------
/resource/result/TestLineJoin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestLineJoin.png
--------------------------------------------------------------------------------
/resource/result/TestMultiSegmentCaps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestMultiSegmentCaps.png
--------------------------------------------------------------------------------
/resource/result/TestPath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestPath.png
--------------------------------------------------------------------------------
/resource/result/TestPathTransform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestPathTransform.png
--------------------------------------------------------------------------------
/resource/result/TestRoundRectangle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestRoundRectangle.png
--------------------------------------------------------------------------------
/resource/result/TestStar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestStar.png
--------------------------------------------------------------------------------
/resource/result/TestTransform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/llgcode/draw2d/0ed1ff131195ddb59f6c689414724fe25436d2fc/resource/result/TestTransform.png
--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | draw2d samples
2 | ==============
3 |
4 | Various samples for using draw2d
5 |
6 | Using the image backend
7 | -----------------------
8 |
9 | The following Go code draws the android sample on a png image:
10 |
11 | ```
12 | package main
13 |
14 | import "log"
15 | import "image"
16 | import "github.com/llgcode/draw2d/draw2dimg"
17 | import "github.com/llgcode/draw2d/samples/android"
18 |
19 | func main(){
20 | // Initialize the graphic context on an RGBA image
21 | dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
22 | gc := draw2dimg.NewGraphicContext(dest)
23 | // Draw Android logo
24 | fn, err := android.Main(gc, "png")
25 | if err != nil {
26 | log.Printf("Drawing %q failed: %v", fn, err)
27 | return
28 | }
29 | // Save to png
30 | err = draw2dimg.SaveToPngFile(fn, dest)
31 | if err != nil {
32 | log.Printf("Saving %q failed: %v", fn, err)
33 | return
34 | }
35 |
36 | log.Printf("Succesfully created %q", fn)
37 | }
38 | ```
39 |
40 | Using the pdf backend
41 | ---------------------
42 |
43 | The following Go code draws the android sample on a pdf document:
44 |
45 | ```
46 | import (
47 | "image"
48 |
49 | "github.com/llgcode/draw2d/draw2dpdf"
50 | "github.com/llgcode/draw2d/samples/android"
51 | )
52 |
53 | function main(){}
54 | // Initialize the graphic context on a pdf document
55 | dest := draw2dpdf.NewPdf("L", "mm", "A4")
56 | gc := draw2dpdf.NewGraphicContext(dest)
57 | // Draw Android logo
58 | fn, err := android.Main(gc, "png")
59 | if err != nil {
60 | t.Errorf("Drawing %q failed: %v", fn, err)
61 | return
62 | }
63 | // Save to pdf
64 | err = draw2dpdf.SaveToPdfFile(fn, dest)
65 | if err != nil {
66 | t.Errorf("Saving %q failed: %v", fn, err)
67 | }
68 | }
69 | ```
70 |
71 | Testing
72 | -------
73 |
74 | These samples are run as tests from the root package folder `draw2d` by:
75 | ```
76 | go test ./...
77 | ```
78 | Or if you want to run with test coverage:
79 | ```
80 | go test -cover ./... | grep -v "no test"
81 | ```
82 | The following files are responsible to run the image tests:
83 | ```
84 | draw2d/test_test.go
85 | draw2d/samples_test.go
86 | ```
87 | The following files are responsible to run the pdf tests:
88 | ```
89 | draw2d/pdf/test_test.go
90 | draw2dpdf/samples_test.go
91 | ```
92 |
--------------------------------------------------------------------------------
/samples/android/android.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | // Package android draws an android avatar.
5 | package android
6 |
7 | import (
8 | "image/color"
9 | "math"
10 |
11 | "github.com/llgcode/draw2d"
12 | "github.com/llgcode/draw2d/draw2dkit"
13 | "github.com/llgcode/draw2d/samples"
14 | )
15 |
16 | // Main draws a droid and returns the filename. This should only be
17 | // used during testing.
18 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
19 | // Draw the droid
20 | Draw(gc, 65, 0)
21 |
22 | // Return the output filename
23 | return samples.Output("android", ext), nil
24 | }
25 |
26 | // Draw the droid on a certain position.
27 | func Draw(gc draw2d.GraphicContext, x, y float64) {
28 | // set the fill and stroke color of the droid
29 | gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
30 | gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
31 |
32 | // set line properties
33 | gc.SetLineCap(draw2d.RoundCap)
34 | gc.SetLineWidth(5)
35 |
36 | // head
37 | gc.MoveTo(x+30, y+70)
38 | gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 180*(math.Pi/180))
39 | gc.Close()
40 | gc.FillStroke()
41 | gc.MoveTo(x+60, y+25)
42 | gc.LineTo(x+50, y+10)
43 | gc.MoveTo(x+100, y+25)
44 | gc.LineTo(x+110, y+10)
45 | gc.Stroke()
46 |
47 | // left eye
48 | draw2dkit.Circle(gc, x+60, y+45, 5)
49 | gc.FillStroke()
50 |
51 | // right eye
52 | draw2dkit.Circle(gc, x+100, y+45, 5)
53 | gc.FillStroke()
54 |
55 | // body
56 | draw2dkit.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
57 | gc.FillStroke()
58 | draw2dkit.Rectangle(gc, x+30, y+75, x+30+100, y+75+80)
59 | gc.FillStroke()
60 |
61 | // left arm
62 | draw2dkit.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
63 | gc.FillStroke()
64 |
65 | // right arm
66 | draw2dkit.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
67 | gc.FillStroke()
68 |
69 | // left leg
70 | draw2dkit.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
71 | gc.FillStroke()
72 |
73 | // right leg
74 | draw2dkit.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
75 | gc.FillStroke()
76 | }
77 |
--------------------------------------------------------------------------------
/samples/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | application: draw2d-test
2 | version: 1
3 | runtime: go
4 | api_version: go1
5 |
6 | handlers:
7 | - url: /.*
8 | script: _go_app
--------------------------------------------------------------------------------
/samples/appengine/server.go:
--------------------------------------------------------------------------------
1 | // +build appengine
2 |
3 | // Package gae demonstrates draw2d on a Google appengine server.
4 | package gae
5 |
6 | import (
7 | "fmt"
8 | "image"
9 | "image/png"
10 | "net/http"
11 |
12 | "github.com/llgcode/draw2d/draw2dimg"
13 | "github.com/llgcode/draw2d/draw2dpdf"
14 | "github.com/llgcode/draw2d/samples/android"
15 |
16 | "appengine"
17 | )
18 |
19 | type appError struct {
20 | Error error
21 | Message string
22 | Code int
23 | }
24 |
25 | type appHandler func(http.ResponseWriter, *http.Request) *appError
26 |
27 | func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
28 | if e := fn(w, r); e != nil { // e is *appError, not os.Error.
29 | c := appengine.NewContext(r)
30 | c.Errorf("%v", e.Error)
31 | http.Error(w, e.Message, e.Code)
32 | }
33 | }
34 |
35 | func init() {
36 | http.Handle("/pdf", appHandler(pdf))
37 | http.Handle("/png", appHandler(imgPng))
38 | }
39 |
40 | func pdf(w http.ResponseWriter, r *http.Request) *appError {
41 | w.Header().Set("Content-type", "application/pdf")
42 |
43 | // Initialize the graphic context on an pdf document
44 | dest := draw2dpdf.NewPdf("L", "mm", "A4")
45 | gc := draw2dpdf.NewGraphicContext(dest)
46 |
47 | // Draw sample
48 | android.Draw(gc, 65, 0)
49 |
50 | err := dest.Output(w)
51 | if err != nil {
52 | return &appError{err, fmt.Sprintf("Can't write: %s", err), 500}
53 | }
54 | return nil
55 | }
56 |
57 | func imgPng(w http.ResponseWriter, r *http.Request) *appError {
58 | w.Header().Set("Content-type", "image/png")
59 |
60 | // Initialize the graphic context on an RGBA image
61 | dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
62 | gc := draw2dimg.NewGraphicContext(dest)
63 |
64 | // Draw sample
65 | android.Draw(gc, 65, 0)
66 |
67 | err := png.Encode(w, dest)
68 | if err != nil {
69 | return &appError{err, fmt.Sprintf("Can't encode: %s", err), 500}
70 | }
71 |
72 | return nil
73 | }
74 |
--------------------------------------------------------------------------------
/samples/frameimage/frameimage.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff, Stani Michiels
3 |
4 | // Package frameimage centers a png image and rotates it.
5 | package frameimage
6 |
7 | import (
8 | "math"
9 |
10 | "github.com/llgcode/draw2d"
11 | "github.com/llgcode/draw2d/draw2dimg"
12 | "github.com/llgcode/draw2d/draw2dkit"
13 | "github.com/llgcode/draw2d/samples"
14 | )
15 |
16 | // Main draws the image frame and returns the filename.
17 | // This should only be used during testing.
18 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
19 | // Margin between the image and the frame
20 | const margin = 30
21 | // Line width od the frame
22 | const lineWidth = 3
23 |
24 | // Gopher image
25 | gopher := samples.Resource("image", "gopher.png", ext)
26 |
27 | // Draw gopher
28 | err := Draw(gc, gopher, 297, 210, margin, lineWidth)
29 |
30 | // Return the output filename
31 | return samples.Output("frameimage", ext), err
32 | }
33 |
34 | // Draw the image frame with certain parameters.
35 | func Draw(gc draw2d.GraphicContext, png string,
36 | dw, dh, margin, lineWidth float64) error {
37 | // Draw frame
38 | draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
39 | gc.SetLineWidth(lineWidth)
40 | gc.FillStroke()
41 |
42 | // load the source image
43 | source, err := draw2dimg.LoadFromPngFile(png)
44 | if err != nil {
45 | return err
46 | }
47 | // Size of source image
48 | sw, sh := float64(source.Bounds().Dx()), float64(source.Bounds().Dy())
49 | // Draw image to fit in the frame
50 | // TODO Seems to have a transform bug here on draw image
51 | scale := math.Min((dw-margin*2)/sw, (dh-margin*2)/sh)
52 | gc.Save()
53 | gc.Translate((dw-sw*scale)/2, (dh-sh*scale)/2)
54 | gc.Scale(scale, scale)
55 | gc.Rotate(0.2)
56 |
57 | gc.DrawImage(source)
58 | gc.Restore()
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/samples/geometry/geometry.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | // Package geometry draws some geometric tests.
5 | package geometry
6 |
7 | import (
8 | "image"
9 | "image/color"
10 | "math"
11 |
12 | "github.com/llgcode/draw2d"
13 | "github.com/llgcode/draw2d/draw2dkit"
14 | "github.com/llgcode/draw2d/samples"
15 | "github.com/llgcode/draw2d/samples/gopher2"
16 | )
17 |
18 | // Main draws geometry and returns the filename. This should only be
19 | // used during testing.
20 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
21 | // Draw the droid
22 | Draw(gc, 297, 210)
23 |
24 | // Return the output filename
25 | return samples.Output("geometry", ext), nil
26 | }
27 |
28 | // Bubble draws a text balloon.
29 | func Bubble(gc draw2d.GraphicContext, x, y, width, height float64) {
30 | sx, sy := width/100, height/100
31 | gc.MoveTo(x+sx*50, y)
32 | gc.QuadCurveTo(x, y, x, y+sy*37.5)
33 | gc.QuadCurveTo(x, y+sy*75, x+sx*25, y+sy*75)
34 | gc.QuadCurveTo(x+sx*25, y+sy*95, x+sx*5, y+sy*100)
35 | gc.QuadCurveTo(x+sx*35, y+sy*95, x+sx*40, y+sy*75)
36 | gc.QuadCurveTo(x+sx*100, y+sy*75, x+sx*100, y+sy*37.5)
37 | gc.QuadCurveTo(x+sx*100, y, x+sx*50, y)
38 | gc.Stroke()
39 | }
40 |
41 | // CurveRectangle draws a rectangle with bezier curves (not rounded rectangle).
42 | func CurveRectangle(gc draw2d.GraphicContext, x0, y0,
43 | rectWidth, rectHeight float64, stroke, fill color.Color) {
44 | radius := (rectWidth + rectHeight) / 4
45 |
46 | x1 := x0 + rectWidth
47 | y1 := y0 + rectHeight
48 | if rectWidth/2 < radius {
49 | if rectHeight/2 < radius {
50 | gc.MoveTo(x0, (y0+y1)/2)
51 | gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
52 | gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
53 | gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
54 | gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
55 | } else {
56 | gc.MoveTo(x0, y0+radius)
57 | gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
58 | gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
59 | gc.LineTo(x1, y1-radius)
60 | gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
61 | gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
62 | }
63 | } else {
64 | if rectHeight/2 < radius {
65 | gc.MoveTo(x0, (y0+y1)/2)
66 | gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
67 | gc.LineTo(x1-radius, y0)
68 | gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
69 | gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
70 | gc.LineTo(x0+radius, y1)
71 | gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
72 | } else {
73 | gc.MoveTo(x0, y0+radius)
74 | gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
75 | gc.LineTo(x1-radius, y0)
76 | gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
77 | gc.LineTo(x1, y1-radius)
78 | gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
79 | gc.LineTo(x0+radius, y1)
80 | gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
81 | }
82 | }
83 | gc.Close()
84 | gc.SetStrokeColor(stroke)
85 | gc.SetFillColor(fill)
86 | gc.SetLineWidth(10.0)
87 | gc.FillStroke()
88 | }
89 |
90 | // Dash draws a line with a dash pattern
91 | func Dash(gc draw2d.GraphicContext, x, y, width, height float64) {
92 | sx, sy := width/162, height/205
93 | gc.SetStrokeColor(image.Black)
94 | gc.SetLineDash([]float64{height / 10, height / 50, height / 50, height / 50}, -50.0)
95 | gc.SetLineCap(draw2d.ButtCap)
96 | gc.SetLineJoin(draw2d.RoundJoin)
97 | gc.SetLineWidth(height / 50)
98 |
99 | gc.MoveTo(x+sx*60.0, y)
100 | gc.LineTo(x+sx*60.0, y)
101 | gc.LineTo(x+sx*162, y+sy*205)
102 | rLineTo(gc, sx*-102.4, 0)
103 | gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
104 | gc.Stroke()
105 | gc.SetLineDash(nil, 0.0)
106 | }
107 |
108 | // Arc draws an arc with a positive angle (clockwise)
109 | func Arc(gc draw2d.GraphicContext, xc, yc, width, height float64) {
110 | // draw an arc
111 | xc += width / 2
112 | yc += height / 2
113 | radiusX, radiusY := width/2, height/2
114 | startAngle := 45 * (math.Pi / 180.0) /* angles are specified */
115 | angle := 135 * (math.Pi / 180.0) /* clockwise in radians */
116 | gc.SetLineWidth(width / 10)
117 | gc.SetLineCap(draw2d.ButtCap)
118 | gc.SetStrokeColor(image.Black)
119 | gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
120 | gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
121 | gc.Stroke()
122 |
123 | // fill a circle
124 | gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
125 | gc.SetFillColor(color.NRGBA{255, 0x33, 0x33, 0x80})
126 | gc.SetLineWidth(width / 20)
127 |
128 | gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
129 | gc.LineTo(xc, yc)
130 | gc.LineTo(xc-radiusX, yc)
131 | gc.Stroke()
132 |
133 | gc.MoveTo(xc, yc)
134 | gc.ArcTo(xc, yc, width/10.0, height/10.0, 0, 2*math.Pi)
135 | gc.Fill()
136 | }
137 |
138 | // ArcNegative draws an arc with a negative angle (anti clockwise).
139 | func ArcNegative(gc draw2d.GraphicContext, xc, yc, width, height float64) {
140 | xc += width / 2
141 | yc += height / 2
142 | radiusX, radiusY := width/2, height/2
143 | startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
144 | angle := -225 * (math.Pi / 180.0) /* clockwise in radians */
145 | gc.SetLineWidth(width / 10)
146 | gc.SetLineCap(draw2d.ButtCap)
147 | gc.SetStrokeColor(image.Black)
148 |
149 | gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
150 | gc.Stroke()
151 | // fill a circle
152 | gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
153 | gc.SetFillColor(color.NRGBA{255, 0x33, 0x33, 0x80})
154 | gc.SetLineWidth(width / 20)
155 |
156 | gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
157 | gc.LineTo(xc, yc)
158 | gc.LineTo(xc-radiusX, yc)
159 | gc.Stroke()
160 |
161 | gc.ArcTo(xc, yc, width/10.0, height/10.0, 0, 2*math.Pi)
162 | gc.Fill()
163 | }
164 |
165 | // CubicCurve draws a cubic curve with its control points.
166 | func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) {
167 | sx, sy := width/162, height/205
168 | x0, y0 := x, y+sy*100.0
169 | x1, y1 := x+sx*75, y+sy*205
170 | x2, y2 := x+sx*125, y
171 | x3, y3 := x+sx*205, y+sy*100
172 |
173 | gc.SetStrokeColor(image.Black)
174 | gc.SetFillColor(color.NRGBA{0xAA, 0xAA, 0xAA, 0xFF})
175 | gc.SetLineWidth(width / 10)
176 | gc.MoveTo(x0, y0)
177 | gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
178 | gc.Stroke()
179 |
180 | gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0x88})
181 |
182 | gc.SetLineWidth(width / 20)
183 | // draw segment of curve
184 | gc.MoveTo(x0, y0)
185 | gc.LineTo(x1, y1)
186 | gc.LineTo(x2, y2)
187 | gc.LineTo(x3, y3)
188 | gc.Stroke()
189 | }
190 |
191 | // FillString draws a filled and stroked string.
192 | // And filles/stroked path created from string. Which may have different - unselectable - output in non raster gc implementations.
193 | func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
194 | sx, sy := width/100, height/100
195 | gc.Save()
196 | gc.SetStrokeColor(image.Black)
197 | gc.SetLineWidth(1)
198 | draw2dkit.RoundedRectangle(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
199 | gc.FillStroke()
200 | gc.SetFillColor(image.Black)
201 | gc.SetFontSize(height / 6)
202 | gc.Translate(x+sx*6, y+sy*52)
203 | gc.SetFontData(draw2d.FontData{
204 | Name: "luxi",
205 | Family: draw2d.FontFamilyMono,
206 | Style: draw2d.FontStyleBold | draw2d.FontStyleItalic,
207 | })
208 | w := gc.FillString("Hug")
209 | gc.Translate(w+sx, 0)
210 | left, top, right, bottom := gc.GetStringBounds("cou")
211 | gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
212 | draw2dkit.Rectangle(gc, left, top, right, bottom)
213 | gc.SetLineWidth(height / 50)
214 | gc.Stroke()
215 | gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
216 | gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
217 | gc.SetLineWidth(height / 100)
218 | gc.StrokeString("Hug")
219 |
220 | gc.Translate(-(w + sx), sy*24)
221 | w = gc.CreateStringPath("Hug", 0, 0)
222 | gc.Fill()
223 | gc.Translate(w+sx, 0)
224 | gc.CreateStringPath("Hug", 0, 0)
225 | path := gc.GetPath()
226 | gc.Stroke((&path).VerticalFlip())
227 | gc.Restore()
228 | }
229 |
230 | // FillStroke first fills and afterwards strokes a path.
231 | func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
232 | sx, sy := width/210, height/215
233 | gc.MoveTo(x+sx*113.0, y)
234 | gc.LineTo(x+sx*215.0, y+sy*215)
235 | rLineTo(gc, sx*-100, 0)
236 | gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
237 | gc.Close()
238 |
239 | gc.MoveTo(x+sx*50.0, y)
240 | rLineTo(gc, sx*51.2, sy*51.2)
241 | rLineTo(gc, sx*-51.2, sy*51.2)
242 | rLineTo(gc, sx*-51.2, sy*-51.2)
243 | gc.Close()
244 |
245 | gc.SetLineWidth(width / 20.0)
246 | gc.SetFillColor(color.NRGBA{0, 0, 0xFF, 0xFF})
247 | gc.SetStrokeColor(image.Black)
248 | gc.FillStroke()
249 | }
250 |
251 | func rLineTo(path draw2d.PathBuilder, x, y float64) {
252 | x0, y0 := path.LastPoint()
253 | path.LineTo(x0+x, y0+y)
254 | }
255 |
256 | // FillStyle demonstrates the difference between even odd and non zero winding rule.
257 | func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
258 | sx, sy := width/232, height/220
259 | gc.SetLineWidth(width / 40)
260 |
261 | draw2dkit.Rectangle(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
262 |
263 | var wheel1, wheel2 draw2d.Path
264 | wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
265 | wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
266 |
267 | gc.SetFillRule(draw2d.FillRuleEvenOdd)
268 | gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
269 |
270 | gc.SetStrokeColor(image.Black)
271 | gc.FillStroke(&wheel1, &wheel2)
272 |
273 | draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
274 | wheel1.Clear()
275 | wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
276 | wheel2.Clear()
277 | wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
278 |
279 | gc.SetFillRule(draw2d.FillRuleWinding)
280 | gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
281 | gc.FillStroke(&wheel1, &wheel2)
282 | }
283 |
284 | // PathTransform scales a path differently in horizontal and vertical direction.
285 | func PathTransform(gc draw2d.GraphicContext, x, y, width, height float64) {
286 | gc.Save()
287 | gc.SetLineWidth(width / 10)
288 | gc.Translate(x+width/2, y+height/2)
289 | gc.Scale(1, 4)
290 | gc.ArcTo(0, 0, width/8, height/8, 0, math.Pi*2)
291 | gc.Close()
292 | gc.Stroke()
293 | gc.Restore()
294 | }
295 |
296 | // Star draws many lines from a center.
297 | func Star(gc draw2d.GraphicContext, x, y, width, height float64) {
298 | gc.Save()
299 | gc.Translate(x+width/2, y+height/2)
300 | gc.SetLineWidth(width / 40)
301 | for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
302 | gc.Save() // Keep rotations temporary
303 | gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
304 | gc.MoveTo(0, 0)
305 | gc.LineTo(width/2, 0)
306 | gc.Stroke()
307 | gc.Restore()
308 | }
309 | gc.Restore()
310 | }
311 |
312 | // Draw all figures in a nice 4x3 grid.
313 | func Draw(gc draw2d.GraphicContext, width, height float64) {
314 | mx, my := width*0.025, height*0.025 // margin
315 | dx, dy := (width-2*mx)/4, (height-2*my)/3
316 | w, h := dx-2*mx, dy-2*my
317 | x0, y := 2*mx, 2*my
318 | x := x0
319 | Bubble(gc, x, y, w, h)
320 | x += dx
321 | CurveRectangle(gc, x, y, w, h, color.NRGBA{0x80, 0, 0, 0x80}, color.NRGBA{0x80, 0x80, 0xFF, 0xFF})
322 | x += dx
323 | Dash(gc, x, y, w, h)
324 | x += dx
325 | Arc(gc, x, y, w, h)
326 | x = x0
327 | y += dy
328 | ArcNegative(gc, x, y, w, h)
329 | x += dx
330 | CubicCurve(gc, x, y, w, h)
331 | x += dx
332 | FillString(gc, x, y, w, h)
333 | x += dx
334 | FillStroke(gc, x, y, w, h)
335 | x = x0
336 | y += dy
337 | FillStyle(gc, x, y, w, h)
338 | x += dx
339 | PathTransform(gc, x, y, w, h)
340 | x += dx
341 | Star(gc, x, y, w, h)
342 | x += dx
343 | gopher2.Draw(gc, x, y, w, h/2)
344 | }
345 |
--------------------------------------------------------------------------------
/samples/gopher/gopher.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | // Package gopher draws a gopher avatar based on a svg of:
5 | // https://github.com/golang-samples/gopher-vector/
6 | package gopher
7 |
8 | import (
9 | "image/color"
10 |
11 | "github.com/llgcode/draw2d"
12 | "github.com/llgcode/draw2d/samples"
13 | )
14 |
15 | // Main draws a left hand and ear of a gopher. Afterwards it returns
16 | // the filename. This should only be used during testing.
17 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
18 | gc.Save()
19 | gc.Scale(0.5, 0.5)
20 | // Draw a (partial) gopher
21 | Draw(gc)
22 | gc.Restore()
23 |
24 | // Return the output filename
25 | return samples.Output("gopher", ext), nil
26 | }
27 |
28 | // Draw a left hand and ear of a gopher using a gc thanks to
29 | // https://github.com/golang-samples/gopher-vector/
30 | func Draw(gc draw2d.GraphicContext) {
31 | // Initialize Stroke Attribute
32 | gc.SetLineWidth(3)
33 | gc.SetLineCap(draw2d.RoundCap)
34 | gc.SetStrokeColor(color.Black)
35 |
36 | // Left hand
37 | //
40 | gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
41 | gc.MoveTo(10.634, 300.493)
42 | rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
43 | rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
44 | rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
45 | rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
46 | gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
47 | gc.FillStroke()
48 |
49 | //
51 | gc.MoveTo(10.634, 300.493)
52 | rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
53 | gc.Stroke()
54 |
55 | // Left Ear
56 | //
58 | gc.MoveTo(46.997, 112.853)
59 | gc.CubicCurveTo(-13.3, 95.897, 31.536, 19.189, 79.956, 50.74)
60 | gc.LineTo(46.997, 112.853)
61 | gc.Close()
62 | gc.Stroke()
63 | }
64 |
65 | func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) {
66 | x, y := path.LastPoint()
67 | path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
68 | }
69 |
70 | func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
71 | x, y := path.LastPoint()
72 | path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
73 | }
74 |
--------------------------------------------------------------------------------
/samples/gopher2/gopher2.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | // Package gopher2 draws a gopher avatar based on a svg of:
5 | // https://github.com/golang-samples/gopher-vector/
6 | package gopher2
7 |
8 | import (
9 | "image"
10 | "image/color"
11 | "math"
12 |
13 | "github.com/llgcode/draw2d"
14 | "github.com/llgcode/draw2d/draw2dkit"
15 | "github.com/llgcode/draw2d/samples"
16 | )
17 |
18 | // Main draws a rotated face of the gopher. Afterwards it returns
19 | // the filename. This should only be used during testing.
20 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
21 | gc.SetStrokeColor(image.Black)
22 | gc.SetFillColor(image.White)
23 | gc.Save()
24 | // Draw a (partial) gopher
25 | gc.Translate(-60, 65)
26 | gc.Rotate(-30 * (math.Pi / 180.0))
27 | Draw(gc, 48, 48, 240, 72)
28 | gc.Restore()
29 |
30 | // Return the output filename
31 | return samples.Output("gopher2", ext), nil
32 | }
33 |
34 | // Draw a gopher head (not rotated)
35 | func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
36 | h23 := (h * 2) / 3
37 |
38 | blf := color.RGBA{0, 0, 0, 0xff} // black
39 | wf := color.RGBA{0xff, 0xff, 0xff, 0xff} // white
40 | nf := color.RGBA{0x8B, 0x45, 0x13, 0xff} // brown opaque
41 | brf := color.RGBA{0x8B, 0x45, 0x13, 0x99} // brown transparant
42 | brb := color.RGBA{0x8B, 0x45, 0x13, 0xBB} // brown transparant
43 |
44 | // round head top
45 | gc.MoveTo(x, y+h*1.002)
46 | gc.CubicCurveTo(x+w/4, y-h/3, x+3*w/4, y-h/3, x+w, y+h*1.002)
47 | gc.Close()
48 | gc.SetFillColor(brb)
49 | gc.Fill()
50 |
51 | // rectangle head bottom
52 | draw2dkit.RoundedRectangle(gc, x, y+h, x+w, y+h+h, h/5, h/5)
53 | gc.Fill()
54 |
55 | // left ear outside
56 | draw2dkit.Circle(gc, x, y+h, w/12)
57 | gc.SetFillColor(brf)
58 | gc.Fill()
59 |
60 | // left ear inside
61 | draw2dkit.Circle(gc, x, y+h, 0.5*w/12)
62 | gc.SetFillColor(nf)
63 | gc.Fill()
64 |
65 | // right ear outside
66 | draw2dkit.Circle(gc, x+w, y+h, w/12)
67 | gc.SetFillColor(brf)
68 | gc.Fill()
69 |
70 | // right ear inside
71 | draw2dkit.Circle(gc, x+w, y+h, 0.5*w/12)
72 | gc.SetFillColor(nf)
73 | gc.Fill()
74 |
75 | // left eye outside white
76 | draw2dkit.Circle(gc, x+w/3, y+h23, w/9)
77 | gc.SetFillColor(wf)
78 | gc.Fill()
79 |
80 | // left eye black
81 | draw2dkit.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
82 | gc.SetFillColor(blf)
83 | gc.Fill()
84 |
85 | // left eye inside white
86 | draw2dkit.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
87 | gc.SetFillColor(wf)
88 | gc.Fill()
89 |
90 | // right eye outside white
91 | draw2dkit.Circle(gc, x+w-w/3, y+h23, w/9)
92 | gc.Fill()
93 |
94 | // right eye black
95 | draw2dkit.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
96 | gc.SetFillColor(blf)
97 | gc.Fill()
98 |
99 | // right eye inside white
100 | draw2dkit.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
101 | gc.SetFillColor(wf)
102 | gc.Fill()
103 |
104 | // left tooth
105 | gc.SetFillColor(wf)
106 | draw2dkit.RoundedRectangle(gc, x+w/2-w/8, y+h+h/2.5, x+w/2-w/8+w/8, y+h+h/2.5+w/6, w/10, w/10)
107 | gc.Fill()
108 |
109 | // right tooth
110 | draw2dkit.RoundedRectangle(gc, x+w/2, y+h+h/2.5, x+w/2+w/8, y+h+h/2.5+w/6, w/10, w/10)
111 | gc.Fill()
112 |
113 | // snout
114 | draw2dkit.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
115 | gc.SetFillColor(nf)
116 | gc.Fill()
117 |
118 | // nose
119 | draw2dkit.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
120 | gc.SetFillColor(blf)
121 | gc.Fill()
122 | }
123 |
--------------------------------------------------------------------------------
/samples/helloworld/helloworld.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff, Stani Michiels
3 |
4 | // Package helloworld displays multiple "Hello World" (one rotated)
5 | // in a rounded rectangle.
6 | package helloworld
7 |
8 | import (
9 | "fmt"
10 | "image"
11 |
12 | "github.com/llgcode/draw2d"
13 | "github.com/llgcode/draw2d/draw2dkit"
14 | "github.com/llgcode/draw2d/samples"
15 | )
16 |
17 | // Main draws "Hello World" and returns the filename. This should only be
18 | // used during testing.
19 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
20 | // Draw hello world
21 | Draw(gc, fmt.Sprintf("Hello World %d dpi", gc.GetDPI()))
22 |
23 | // Return the output filename
24 | return samples.Output("helloworld", ext), nil
25 | }
26 |
27 | // Draw "Hello World"
28 | func Draw(gc draw2d.GraphicContext, text string) {
29 | // Draw a rounded rectangle using default colors
30 | draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
31 | gc.FillStroke()
32 |
33 | // Set the font luximbi.ttf
34 | gc.SetFontData(draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
35 | // Set the fill text color to black
36 | gc.SetFillColor(image.Black)
37 | gc.SetFontSize(14)
38 | // Display Hello World
39 | gc.FillStringAt("Hello World", 8, 52)
40 | }
41 |
--------------------------------------------------------------------------------
/samples/helloworldgl/helloworldgl.go:
--------------------------------------------------------------------------------
1 | // Open an OpenGl window and display a rectangle using a OpenGl GraphicContext
2 | package main
3 |
4 | import (
5 | "image/color"
6 | "log"
7 | "runtime"
8 |
9 | "github.com/go-gl/gl/v2.1/gl"
10 | "github.com/go-gl/glfw/v3.1/glfw"
11 | "github.com/llgcode/draw2d"
12 | "github.com/llgcode/draw2d/draw2dgl"
13 | "github.com/llgcode/draw2d/draw2dkit"
14 | )
15 |
16 | var (
17 | // global rotation
18 | rotate int
19 | width, height int
20 | redraw = true
21 | font draw2d.FontData
22 | )
23 |
24 | func reshape(window *glfw.Window, w, h int) {
25 | gl.ClearColor(1, 1, 1, 1)
26 | /* Establish viewing area to cover entire window. */
27 | gl.Viewport(0, 0, int32(w), int32(h))
28 | /* PROJECTION Matrix mode. */
29 | gl.MatrixMode(gl.PROJECTION)
30 | /* Reset project matrix. */
31 | gl.LoadIdentity()
32 | /* Map abstract coords directly to window coords. */
33 | gl.Ortho(0, float64(w), 0, float64(h), -1, 1)
34 | /* Invert Y axis so increasing Y goes down. */
35 | gl.Scalef(1, -1, 1)
36 | /* Shift origin up to upper-left corner. */
37 | gl.Translatef(0, float32(-h), 0)
38 | gl.Enable(gl.BLEND)
39 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
40 | gl.Disable(gl.DEPTH_TEST)
41 | width, height = w, h
42 | redraw = true
43 | }
44 |
45 | // Ask to refresh
46 | func invalidate() {
47 | redraw = true
48 | }
49 |
50 | func display() {
51 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
52 |
53 | gl.LineWidth(1)
54 | gc := draw2dgl.NewGraphicContext(width, height)
55 | gc.SetFontData(draw2d.FontData{
56 | Name: "luxi",
57 | Family: draw2d.FontFamilyMono,
58 | Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
59 |
60 | gc.BeginPath()
61 | draw2dkit.RoundedRectangle(gc, 200, 200, 600, 600, 100, 100)
62 |
63 | gc.SetFillColor(color.RGBA{0, 0, 0, 0xff})
64 | gc.Fill()
65 |
66 | gl.Flush() /* Single buffered, so needs a flush. */
67 | }
68 |
69 | func init() {
70 | runtime.LockOSThread()
71 | }
72 |
73 | func main() {
74 | err := glfw.Init()
75 | if err != nil {
76 | panic(err)
77 | }
78 | defer glfw.Terminate()
79 | width, height = 800, 800
80 | window, err := glfw.CreateWindow(width, height, "Show RoundedRect", nil, nil)
81 | if err != nil {
82 | panic(err)
83 | }
84 |
85 | window.MakeContextCurrent()
86 | window.SetSizeCallback(reshape)
87 | window.SetKeyCallback(onKey)
88 | window.SetCharCallback(onChar)
89 |
90 | glfw.SwapInterval(1)
91 |
92 | err = gl.Init()
93 | if err != nil {
94 | panic(err)
95 | }
96 |
97 | reshape(window, width, height)
98 | for !window.ShouldClose() {
99 | if redraw {
100 | display()
101 | window.SwapBuffers()
102 | redraw = false
103 | }
104 | glfw.PollEvents()
105 | // time.Sleep(2 * time.Second)
106 | }
107 | }
108 |
109 | func onChar(w *glfw.Window, char rune) {
110 | log.Println(char)
111 | }
112 |
113 | func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
114 | switch {
115 | case key == glfw.KeyEscape && action == glfw.Press,
116 | key == glfw.KeyQ && action == glfw.Press:
117 | w.SetShouldClose(true)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/samples/line/line.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff, Stani Michiels
3 |
4 | // Package line draws vertically spaced lines.
5 | package line
6 |
7 | import (
8 | "image/color"
9 |
10 | "github.com/llgcode/draw2d"
11 | "github.com/llgcode/draw2d/draw2dkit"
12 | "github.com/llgcode/draw2d/samples"
13 | )
14 |
15 | // Main draws vertically spaced lines and returns the filename.
16 | // This should only be used during testing.
17 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
18 | gc.SetFillRule(draw2d.FillRuleWinding)
19 | gc.Clear()
20 | // Draw the line
21 | for x := 5.0; x < 297; x += 10 {
22 | Draw(gc, x, 0, x, 210)
23 | }
24 | gc.ClearRect(100, 75, 197, 135)
25 | draw2dkit.Ellipse(gc, 148.5, 105, 35, 25)
26 | gc.SetFillColor(color.RGBA{0xff, 0xff, 0x44, 0xff})
27 | gc.FillStroke()
28 |
29 | // Return the output filename
30 | return samples.Output("line", ext), nil
31 | }
32 |
33 | // Draw vertically spaced lines
34 | func Draw(gc draw2d.GraphicContext, x0, y0, x1, y1 float64) {
35 | // Draw a line
36 | gc.MoveTo(x0, y0)
37 | gc.LineTo(x1, y1)
38 | gc.Stroke()
39 | }
40 |
--------------------------------------------------------------------------------
/samples/linecapjoin/linecapjoin.go:
--------------------------------------------------------------------------------
1 | // Copyright 2010 The draw2d Authors. All rights reserved.
2 | // created: 21/11/2010 by Laurent Le Goff
3 |
4 | // Package linecapjoin demonstrates the different line caps and joins.
5 | package linecapjoin
6 |
7 | import (
8 | "image/color"
9 |
10 | "github.com/llgcode/draw2d"
11 | "github.com/llgcode/draw2d/samples"
12 | )
13 |
14 | // Main draws the different line caps and joins.
15 | // This should only be used during testing.
16 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
17 | // Draw the line
18 | const offset = 75.0
19 | x := 35.0
20 | caps := []draw2d.LineCap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap}
21 | joins := []draw2d.LineJoin{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin}
22 | for i := range caps {
23 | Draw(gc, caps[i], joins[i], x, 50, x, 160, offset)
24 | x += offset
25 | }
26 |
27 | // Return the output filename
28 | return samples.Output("linecapjoin", ext), nil
29 | }
30 |
31 | // Draw a line with an angle with specified line cap and join
32 | func Draw(gc draw2d.GraphicContext, cap draw2d.LineCap, join draw2d.LineJoin,
33 | x0, y0, x1, y1, offset float64) {
34 | gc.SetLineCap(cap)
35 | gc.SetLineJoin(join)
36 |
37 | // Draw thick line
38 | gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0x33, 0xFF})
39 | gc.SetLineWidth(30.0)
40 | gc.MoveTo(x0, y0)
41 | gc.LineTo((x0+x1)/2+offset, (y0+y1)/2)
42 | gc.LineTo(x1, y1)
43 | gc.Stroke()
44 |
45 | // Draw thin helping line
46 | gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
47 | gc.SetLineWidth(2.56)
48 | gc.MoveTo(x0, y0)
49 | gc.LineTo((x0+x1)/2+offset, (y0+y1)/2)
50 | gc.LineTo(x1, y1)
51 | gc.Stroke()
52 | }
53 |
--------------------------------------------------------------------------------
/samples/postscript/postscript.go:
--------------------------------------------------------------------------------
1 | // Package postscript reads the tiger.ps file and draws it to a backend.
2 | package postscript
3 |
4 | import (
5 | "io"
6 | "os"
7 | "strings"
8 |
9 | "github.com/llgcode/ps"
10 |
11 | "github.com/llgcode/draw2d"
12 | "github.com/llgcode/draw2d/samples"
13 | )
14 |
15 | // Main draws the tiger
16 | func Main(gc draw2d.GraphicContext, ext string) (string, error) {
17 | gc.Save()
18 |
19 | // flip the image
20 | gc.Translate(0, 200)
21 | gc.Scale(0.35, -0.35)
22 | gc.Translate(70, -200)
23 |
24 | // Tiger postscript drawing
25 | tiger := samples.Resource("image", "tiger.ps", ext)
26 |
27 | // Draw tiger
28 | Draw(gc, tiger)
29 | gc.Restore()
30 |
31 | // Return the output filename
32 | return samples.Output("postscript", ext), nil
33 | }
34 |
35 | // Draw a tiger
36 | func Draw(gc draw2d.GraphicContext, filename string) {
37 | // Open the postscript
38 | src, err := os.OpenFile(filename, 0, 0)
39 | if err != nil {
40 | panic(err)
41 | }
42 | defer src.Close()
43 | bytes, err := io.ReadAll(src)
44 | reader := strings.NewReader(string(bytes))
45 |
46 | // Initialize and interpret the postscript
47 | interpreter := ps.NewInterpreter(gc)
48 | interpreter.Execute(reader)
49 | }
50 |
--------------------------------------------------------------------------------
/samples/postscriptgl/postscriptgl.go:
--------------------------------------------------------------------------------
1 | // Open a OpenGL window and display a tiger interpreting a postscript file
2 | package main
3 |
4 | import (
5 | "io"
6 | "log"
7 | "math"
8 | "os"
9 | "runtime"
10 | "strings"
11 | "time"
12 |
13 | "github.com/go-gl/gl/v2.1/gl"
14 | "github.com/go-gl/glfw/v3.1/glfw"
15 | "github.com/llgcode/draw2d/draw2dgl"
16 | "github.com/llgcode/ps"
17 | )
18 |
19 | var postscriptContent string
20 |
21 | var (
22 | width, height int
23 | rotate int
24 | window *glfw.Window
25 | )
26 |
27 | func reshape(window *glfw.Window, w, h int) {
28 | gl.ClearColor(1, 1, 1, 1)
29 | //fmt.Println(gl.GetString(gl.EXTENSIONS))
30 | gl.Viewport(0, 0, int32(w), int32(h)) /* Establish viewing area to cover entire window. */
31 | gl.MatrixMode(gl.PROJECTION) /* Start modifying the projection matrix. */
32 | gl.LoadIdentity() /* Reset project matrix. */
33 | gl.Ortho(0, float64(w), 0, float64(h), -1, 1) /* Map abstract coords directly to window coords. */
34 | gl.Scalef(1, -1, 1) /* Invert Y axis so increasing Y goes down. */
35 | gl.Translatef(0, float32(-h), 0) /* Shift origin up to upper-left corner. */
36 | gl.Enable(gl.BLEND)
37 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
38 | gl.Disable(gl.DEPTH_TEST)
39 | width, height = w, h
40 | }
41 |
42 | func display() {
43 |
44 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
45 |
46 | lastTime := time.Now()
47 | gl.LineWidth(1)
48 | gc := draw2dgl.NewGraphicContext(width, height)
49 |
50 | gc.Translate(380, 400)
51 | gc.Scale(1, -1)
52 | rotate = (rotate + 1) % 360
53 | gc.Rotate(float64(rotate) * math.Pi / 180)
54 | gc.Translate(-380, -400)
55 |
56 | interpreter := ps.NewInterpreter(gc)
57 | reader := strings.NewReader(postscriptContent)
58 |
59 | interpreter.Execute(reader)
60 | dt := time.Now().Sub(lastTime)
61 | log.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
62 | gl.Flush() /* Single buffered, so needs a flush. */
63 | }
64 |
65 | func main() {
66 | src, err := os.OpenFile("tiger.ps", 0, 0)
67 | if err != nil {
68 | log.Println("can't find postscript file.")
69 | return
70 | }
71 | defer src.Close()
72 | bytes, err := io.ReadAll(src)
73 | postscriptContent = string(bytes)
74 | err = glfw.Init()
75 | if err != nil {
76 | panic(err)
77 | }
78 | defer glfw.Terminate()
79 |
80 | window, err = glfw.CreateWindow(800, 800, "Show Tiger in OpenGL", nil, nil)
81 | if err != nil {
82 | panic(err)
83 | }
84 |
85 | window.MakeContextCurrent()
86 | window.SetSizeCallback(reshape)
87 | window.SetKeyCallback(onKey)
88 |
89 | glfw.SwapInterval(1)
90 |
91 | err = gl.Init()
92 | if err != nil {
93 | panic(err)
94 | }
95 | reshape(window, 800, 800)
96 | for !window.ShouldClose() {
97 | display()
98 | window.SwapBuffers()
99 | glfw.PollEvents()
100 | // time.Sleep(2 * time.Second)
101 | }
102 | }
103 |
104 | func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
105 | switch {
106 | case key == glfw.KeyEscape && action == glfw.Press,
107 | key == glfw.KeyQ && action == glfw.Press:
108 | w.SetShouldClose(true)
109 | }
110 | }
111 |
112 | func init() {
113 | runtime.LockOSThread()
114 | }
115 |
--------------------------------------------------------------------------------
/samples/samples.go:
--------------------------------------------------------------------------------
1 | // Package samples provides examples which can be used with different
2 | // backends. They are also used for testing and coverage of the
3 | // draw2d package.
4 | package samples
5 |
6 | import "fmt"
7 |
8 | // Resource returns a resource filename for testing.
9 | func Resource(folder, filename, ext string) string {
10 | var root string
11 | if ext == "pdf" || ext == "svg" {
12 | root = "../"
13 | }
14 | return fmt.Sprintf("%sresource/%s/%s", root, folder, filename)
15 | }
16 |
17 | // Output returns the output filename for testing.
18 | func Output(name, ext string) string {
19 | var root string
20 | if ext == "pdf" || ext == "svg" {
21 | root = "../"
22 | }
23 | return fmt.Sprintf("%soutput/samples/%s.%s", root, name, ext)
24 | }
25 |
--------------------------------------------------------------------------------
/samples_test.go:
--------------------------------------------------------------------------------
1 | // See also test_test.go
2 |
3 | package draw2d_test
4 |
5 | import (
6 | "testing"
7 |
8 | "github.com/llgcode/draw2d"
9 | "github.com/llgcode/draw2d/samples/android"
10 | "github.com/llgcode/draw2d/samples/frameimage"
11 | "github.com/llgcode/draw2d/samples/geometry"
12 | "github.com/llgcode/draw2d/samples/gopher"
13 | "github.com/llgcode/draw2d/samples/gopher2"
14 | "github.com/llgcode/draw2d/samples/helloworld"
15 | "github.com/llgcode/draw2d/samples/line"
16 | "github.com/llgcode/draw2d/samples/linecapjoin"
17 | "github.com/llgcode/draw2d/samples/postscript"
18 | )
19 |
20 | func TestSampleAndroid(t *testing.T) {
21 | test(t, android.Main)
22 | }
23 |
24 | func TestSampleGeometry(t *testing.T) {
25 | // Set the global folder for searching fonts
26 | // The pdf backend needs for every ttf file its corresponding
27 | // json/.z file which is generated by gofpdf/makefont.
28 | draw2d.SetFontFolder("resource/font")
29 | test(t, geometry.Main)
30 | }
31 |
32 | func TestSampleGopher(t *testing.T) {
33 | test(t, gopher.Main)
34 | }
35 |
36 | func TestSampleGopher2(t *testing.T) {
37 | test(t, gopher2.Main)
38 | }
39 |
40 | func TestSampleHelloWorld(t *testing.T) {
41 | // Set the global folder for searching fonts
42 | draw2d.SetFontFolder("resource/font")
43 | test(t, helloworld.Main)
44 | }
45 |
46 | func TestSampleFrameImage(t *testing.T) {
47 | test(t, frameimage.Main)
48 | }
49 |
50 | func TestSampleLine(t *testing.T) {
51 | test(t, line.Main)
52 | }
53 |
54 | func TestSampleLineCapJoin(t *testing.T) {
55 | test(t, linecapjoin.Main)
56 | }
57 |
58 | func TestSamplePostscript(t *testing.T) {
59 | test(t, postscript.Main)
60 | }
61 |
--------------------------------------------------------------------------------
/sync_test.go:
--------------------------------------------------------------------------------
1 | // go test -race -test.v sync_test.go
2 |
3 | package draw2d_test
4 |
5 | import (
6 | "fmt"
7 | "github.com/llgcode/draw2d"
8 | "github.com/llgcode/draw2d/draw2dimg"
9 | "github.com/llgcode/draw2d/draw2dkit"
10 | "image"
11 | "testing"
12 | )
13 |
14 | func TestSync(t *testing.T) {
15 | ch := make(chan int)
16 | limit := 2
17 | for i := 0; i < limit; i++ {
18 | go Draw(i, ch)
19 | }
20 |
21 | for i := 0; i < limit; i++ {
22 | counter := <-ch
23 | t.Logf("Goroutine %d returned\n", counter)
24 | }
25 | }
26 |
27 | func Draw(i int, ch chan<- int) {
28 | draw2d.SetFontFolder("./resource/font")
29 | // Draw a rounded rectangle using default colors
30 | dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
31 | gc := draw2dimg.NewGraphicContext(dest)
32 |
33 | draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
34 | gc.FillStroke()
35 |
36 | // Set the fill text color to black
37 | gc.SetFillColor(image.Black)
38 | gc.SetFontSize(14)
39 |
40 | // Display Hello World dimensions
41 | x1, y1, x2, y2 := gc.GetStringBounds("Hello world")
42 | gc.FillStringAt(fmt.Sprintf("%.2f %.2f %.2f %.2f", x1, y1, x2, y2), 0, 0)
43 |
44 | ch <- i
45 | }
46 |
--------------------------------------------------------------------------------
/test:
--------------------------------------------------------------------------------
1 | echo golint
2 | golint ./... | grep "draw2dpdf\|samples\|^advanced_path\|^arc\|draw2d[.]\|fileutil\|^gc\|math\|^path[.]\|rgba_interpolation\|test\|vertex2d"
3 | echo
4 | echo go vet
5 | go vet ./...
6 | echo
7 | echo go test
8 | go test -cover ./... | grep -v "no test"
--------------------------------------------------------------------------------
/test_test.go:
--------------------------------------------------------------------------------
1 | // Package draw2d_test gives test coverage with the command:
2 | // go test -cover ./... | grep -v "no test"
3 | package draw2d_test
4 |
5 | import (
6 | "image"
7 | "testing"
8 |
9 | "github.com/llgcode/draw2d"
10 | "github.com/llgcode/draw2d/draw2dimg"
11 | )
12 |
13 | type sample func(gc draw2d.GraphicContext, ext string) (string, error)
14 |
15 | func test(t *testing.T, draw sample) {
16 | // Initialize the graphic context on an RGBA image
17 | dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
18 | gc := draw2dimg.NewGraphicContext(dest)
19 | // Draw Android logo
20 | output, err := draw(gc, "png")
21 | if err != nil {
22 | t.Errorf("Drawing %q failed: %v", output, err)
23 | return
24 | }
25 | // Save to png
26 | err = draw2dimg.SaveToPngFile(output, dest)
27 | if err != nil {
28 | t.Errorf("Saving %q failed: %v", output, err)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------