Merge with master

This commit is contained in:
Laurent Le Goff 2015-07-09 18:06:14 +02:00
commit 04427cabf5
24 changed files with 858 additions and 104 deletions

3
.gitignore vendored
View file

@ -9,6 +9,8 @@
**/*.exe
**/*~
**/*.orig
**/*.out
**/*.test
core
_obj
_test
@ -17,3 +19,4 @@ _test*
**/*.dll
**/core*[0-9]
.private

View file

@ -1,29 +1,93 @@
draw2d
======
This package (written in [go](http://golang.org)) provides an API to draw 2d vector forms on [images](http://golang.org/pkg/image).
This library is inspired by [postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/).
Package draw2d is a pure [go](http://golang.org) 2D vector graphics library with support for multiple output devices such as [images](http://golang.org/pkg/image) (draw2d), pdf documents (draw2dpdf) and opengl (draw2dopengl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative.
The package depends on [freetype-go](http://code.google.com/p/freetype-go) package for its rasterization algorithm.
See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
Features
--------
Using
-----
Operations in draw2d include stroking and filling polygons, arcs, Bézier curves, drawing images and text rendering with truetype fonts. All drawing operations can be transformed by affine transformations (scale, rotation, translation).
Install [golang](http://golang.org/doc/install) and get `draw2d`
Installation
------------
Install [golang](http://golang.org/doc/install). To install or update the package draw2d on your system, run:
```
go get github.com/llgcode/draw2d
go get -u github.com/llgcode/draw2d
```
and start coding using one of the [Samples](https://github.com/llgcode/draw2d.samples).
Quick Start
-----------
The following Go code generates a simple drawing and saves it to an image file with package draw2d:
```go
// Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest)
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.MoveTo(10, 10) // should always be called first for a new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
// Save to file
draw2d.SaveToPngFile(fn, dest)
```
The same Go code can also generate a pdf document with package draw2dpdf:
```go
// Initialize the graphic context on an RGBA image
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2d.NewGraphicContext(dest)
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.MoveTo(10, 10) // should always be called first for a new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
// Save to file
draw2dpdf.SaveToPdfFile(fn, dest)
```
There are more examples here: https://github.com/llgcode/draw2d.samples
Drawing on opengl is provided by the draw2dgl package.
Acknowledgments
---------------
[Laurent Le Goff](https://github.com/llgcode) wrote this library, inspired by [Postscript](http://www.tailrecursive.org/postscript) and [HTML5 canvas](http://www.w3.org/TR/2dcontext/). He implemented the image and opengl backend with the [freetype-go](https://code.google.com/p/freetype-go/) package. Also he created a pure go [Postscript interpreter](https://github.com/llgcode/ps), which can read postscript images and draw to a draw2d graphic context. [Stani Michiels](https://github.com/stanim) implemented the pdf backend with the [gofpdf](https://github.com/jung-kurt/gofpdf) package.
Softwares and Packages using draw2d
-----------------------------------
- [golang postscript interpreter](https://github.com/llgcode/ps)
- [gonum plot](https://github.com/gonum/plot)
Packages using draw2d
---------------------
- [ps](https://github.com/llgcode/ps): Postscript interpreter written in Go
- [gonum/plot](https://github.com/gonum/plot): drawing plots in Go
- [go.uik](https://github.com/skelterjohn/go.uik): a concurrent UI kit written in pure go.
- [smartcrop](https://github.com/muesli/smartcrop): content aware image cropping
- [karta](https://github.com/peterhellberg/karta): drawing Voronoi diagrams
- [chart](https://github.com/vdobler/chart): basic charts in Go
References
---------

View file

@ -1,7 +1,77 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
// Package draw2d provides a Graphic Context that can draw vector form on canvas.
// Package draw2d is a pure go 2D vector graphics library with support
// for multiple output devices such as images (draw2d), pdf documents
// (draw2dpdf) and opengl (draw2dopengl), which can also be used on the
// google app engine. It can be used as a pure go Cairo alternative.
//
// Features
//
// Operations in draw2d include stroking and filling polygons, arcs,
// Bézier curves, drawing images and text rendering with truetype fonts.
// All drawing operations can be transformed by affine transformations
// (scale, rotation, translation).
//
// Installation
//
// To install or update the package draw2d on your system, run:
// go get -u github.com/llgcode/draw2d
//
// Quick Start
//
// Package draw2d itself provides a graphic context that can draw vector
// graphics and text on an image canvas. The following Go code
// generates a simple drawing and saves it to an image file:
// // Initialize the graphic context on an RGBA image
// dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
// gc := draw2d.NewGraphicContext(dest)
//
// // Set some properties
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
// gc.SetLineWidth(5)
//
// // Draw a closed shape
// gc.MoveTo(10, 10) // should always be called first for a new path
// gc.LineTo(100, 50)
// gc.QuadCurveTo(100, 10, 10, 10)
// gc.Close()
// gc.FillStroke()
//
// // Save to file
// draw2d.SaveToPngFile(fn, dest)
//
// There are more examples here:
// https://github.com/llgcode/draw2d.samples
//
// Drawing on pdf documents is provided by the draw2dpdf package.
// Drawing on opengl is provided by the draw2dgl package.
// See subdirectories at the bottom of this page.
//
// Acknowledgments
//
// Laurent Le Goff wrote this library, inspired by Postscript and
// HTML5 canvas. He implemented the image and opengl backend with the
// freetype-go package. Also he created a pure go Postscript
// interpreter, which can read postscript images and draw to a draw2d
// graphic context (https://github.com/llgcode/ps). Stani Michiels
// implemented the pdf backend with the gofpdf package.
//
// The package depends on freetype-go package for its rasterization
// algorithm.
//
// Packages using draw2d
//
// - https://github.com/llgcode/ps: Postscript interpreter written in Go
//
// - https://github.com/gonum/plot: drawing plots in Go
//
// - https://github.com/muesli/smartcrop: content aware image cropping
//
// - https://github.com/peterhellberg/karta: drawing Voronoi diagrams
//
// - https://github.com/vdobler/chart: basic charts in Go
package draw2d
import (
@ -9,7 +79,7 @@ import (
"image/color"
)
// FillRule defines the fill rule used when fill
// FillRule defines the type for fill rules
type FillRule int
const (
@ -81,7 +151,7 @@ type SolidFillStyle struct {
FillRule FillRule
}
// Vertical Alignment of the text
// Valign Vertical Alignment of the text
type Valign int
const (
@ -91,7 +161,7 @@ const (
ValignTopBaseline
)
// Horizontal Alignment of the text
// Halign Horizontal Alignment of the text
type Halign int
const (

View file

@ -8,13 +8,14 @@ import (
)
const (
// CurveRecursionLimit represents the maximum recursion that is really necessary to subsivide a curve into straight lines
CurveRecursionLimit = 32
)
// Cubic
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves.
// SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
// c1 and c2 parameters are the resulting curves
func SubdivideCubic(c, c1, c2 []float64) {
// First point of c is the first point of c1
@ -47,7 +48,7 @@ func SubdivideCubic(c, c1, c2 []float64) {
// TraceCubic generate lines subdividing the cubic curve using a Flattener
// flattening_threshold helps determines the flattening expectation of the curve
func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
func TraceCubic(t Flattener, cubic []float64, flatteningThreshold float64) {
// Allocation curves
var curves [CurveRecursionLimit * 8]float64
copy(curves[0:8], cubic[0:8])
@ -67,7 +68,7 @@ func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
// if it's flat then trace a line
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
if (d2+d3)*(d2+d3) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[6], c[7])
i--
} else {
@ -81,7 +82,7 @@ func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
// Quad
// x1, y1, cpx1, cpy2, x2, y2 float64
// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves.
// SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
// c1 and c2 parameters are the resulting curves
func SubdivideQuad(c, c1, c2 []float64) {
// First point of c is the first point of c1
@ -100,9 +101,9 @@ func SubdivideQuad(c, c1, c2 []float64) {
return
}
// Trace generate lines subdividing the curve using a Flattener
// TraceQuad generate lines subdividing the curve using a Flattener
// flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
func TraceQuad(t Flattener, quad []float64, flatteningThreshold float64) {
// Allocates curves stack
var curves [CurveRecursionLimit * 6]float64
copy(curves[0:6], quad[0:6])
@ -119,7 +120,7 @@ func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
// if it's flat then trace a line
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
if (d*d) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
t.LineTo(c[4], c[5])
i--
} else {
@ -157,5 +158,4 @@ func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, la
angle += da
t.LineTo(curX, curY)
}
return curX, curY
}

View file

@ -4,10 +4,11 @@
package draw2dbase
import (
"github.com/llgcode/draw2d"
"image"
"image/color"
"github.com/llgcode/draw2d"
"code.google.com/p/freetype-go/freetype/truetype"
)
@ -34,7 +35,7 @@ type ContextStack struct {
Font *truetype.Font
// fontSize and dpi are used to calculate scale. scale is the number of
// 26.6 fixed point units in 1 em.
Scale int32
Scale float64
Previous *ContextStack
}

3
draw2dgl/doc.go Normal file
View file

@ -0,0 +1,3 @@
// Package draw2dgl provides a graphic context that can draw vector
// graphics and text on OpenGL.
package draw2dgl

View file

@ -1,12 +1,13 @@
package draw2dimg
import (
"image"
"image/draw"
"code.google.com/p/freetype-go/freetype/raster"
"code.google.com/p/freetype-go/freetype/truetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"image"
"image/draw"
)
type Drawer struct {
@ -37,7 +38,7 @@ func (d *Drawer) Matrix() *draw2d.Matrix {
func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) {
switch fillStyle := style.(type) {
case draw2d.SolidFillStyle:
d.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(fillStyle.FillRule)
d.fillRasterizer.UseNonZeroWinding = fillStyle.FillRule == draw2d.FillRuleWinding
d.painter.SetColor(fillStyle.Color)
default:
panic("FillStyle not supported")

View file

@ -5,23 +5,26 @@ package draw2dimg
import (
"errors"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"image"
"image/color"
"image/draw"
"log"
"math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"code.google.com/p/freetype-go/freetype/raster"
"code.google.com/p/freetype-go/freetype/truetype"
)
// Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter
type Painter interface {
raster.Painter
SetColor(color color.Color)
}
// GraphicContext is the implementation of draw2d.GraphicContext for a raster image
type GraphicContext struct {
*draw2dbase.StackGraphicContext
img draw.Image
@ -32,10 +35,9 @@ type GraphicContext struct {
DPI int
}
/**
* Create a new Graphic context from an image
*/
// NewGraphicContext creates a new Graphic context from an image.
func NewGraphicContext(img draw.Image) *GraphicContext {
var painter Painter
switch selectImage := img.(type) {
case *image.RGBA:
@ -46,7 +48,7 @@ func NewGraphicContext(img draw.Image) *GraphicContext {
return NewGraphicContextWithPainter(img, painter)
}
// Create a new Graphic context from an image and a Painter (see Freetype-go)
// NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go)
func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext {
width, height := img.Bounds().Dx(), img.Bounds().Dy()
dpi := 92
@ -62,38 +64,46 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicConte
return gc
}
// GetDPI returns the resolution of the Image GraphicContext
func (gc *GraphicContext) GetDPI() int {
return gc.DPI
}
// Clear fills the current canvas with a default transparent color
func (gc *GraphicContext) Clear() {
width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy()
gc.ClearRect(0, 0, width, height)
}
// ClearRect fills the current canvas with a default transparent color at the specified rectangle
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
imageColor := image.NewUniform(gc.Current.FillColor)
draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over)
}
// DrawImage draws the raster image in the current canvas
func (gc *GraphicContext) DrawImage(img image.Image) {
DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter)
}
// FillString draws the text at point (0, 0)
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0)
}
// FillStringAt draws the text at the specified point (x, y)
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y)
gc.Fill()
return width
}
// StrokeString draws the contour of the text at point (0, 0)
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws the contour of the text at point (x, y)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
width := gc.CreateStringPath(text, x, y)
gc.Stroke()
@ -118,7 +128,7 @@ func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
// going downwards.
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, glyph, truetype.NoHinting); err != nil {
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil {
return err
}
e0 := 0
@ -146,14 +156,14 @@ func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
for _, rune := range s {
index := font.Index(rune)
if hasPrev {
x += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
x += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
}
err := gc.drawGlyph(index, x, y)
if err != nil {
log.Println(err)
return startx - x
}
x += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
x += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return x - startx
@ -175,9 +185,10 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
for _, rune := range s {
index := font.Index(rune)
if hasPrev {
cursor += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index))
cursor += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
}
if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, index, truetype.NoHinting); err != nil {
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil {
log.Println(err)
return 0, 0, 0, 0
}
@ -192,7 +203,7 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
right = math.Max(right, x+cursor)
}
}
cursor += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth)
cursor += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
prev, hasPrev = index, true
}
return left, top, right, bottom
@ -201,7 +212,7 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
gc.Current.Scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0))
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
}
// SetDPI sets the screen resolution in dots per inch.
@ -228,11 +239,12 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color
gc.Current.Path.Clear()
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.strokeRasterizer.UseNonZeroWinding = true
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner draw2dbase.Flattener
@ -248,12 +260,13 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
/**** first method ****/
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}}
for _, p := range paths {
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
}
@ -261,14 +274,15 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
}
// FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding
gc.strokeRasterizer.UseNonZeroWinding = true
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}}
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}})
stroker.HalfLineWidth = gc.Current.LineWidth / 2
var liner draw2dbase.Flattener
@ -278,7 +292,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
liner = stroker
}
demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}}
demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}}
for _, p := range paths {
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
}
@ -288,13 +302,3 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
// Stroke
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
}
func useNonZeroWinding(f draw2d.FillRule) bool {
switch f {
case draw2d.FillRuleEvenOdd:
return false
case draw2d.FillRuleWinding:
return true
}
return false
}

View file

@ -5,19 +5,25 @@
package draw2dimg
import (
"github.com/llgcode/draw2d"
"image"
"image/color"
"image/draw"
"math"
"github.com/llgcode/draw2d"
)
// ImageFilter defines the type of filter to use
type ImageFilter int
const (
// LinearFilter defines a linear filter
LinearFilter ImageFilter = iota
// BilinearFilter defines a bilinear filter
BilinearFilter
// BicubicFilter defines a bicubic filter
BicubicFilter
// M is the maximum value for a rgb component
M = 1<<16 - 1
)
@ -48,14 +54,7 @@ func getColorBilinear(img image.Image, x, y float64) color.Color {
return color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), uint8(a >> 8)}
}
/**
-- LERP
-- /lerp/, vi.,n.
--
-- Quasi-acronym for Linear Interpolation, used as a verb or noun for
-- the operation. "Bresenham's algorithm lerps incrementally between the
-- two endpoints of the line." (From Jargon File (4.4.4, 14 Aug 2003)
*/
// lerp is a linear interpolation bertween 2 points
func lerp(v1, v2, ratio float64) float64 {
return v1*(1-ratio) + v2*ratio
}
@ -105,6 +104,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 {
(-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0)
}
// DrawImage draws an image into dest using an affine transformation matrix, an op and a filter
func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) {
bounds := src.Bounds()
x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y))

View file

@ -5,7 +5,7 @@ import (
"github.com/llgcode/draw2d"
)
// drawContour draws the given closed contour at the given sub-pixel offset.
// DrawContour draws the given closed contour at the given sub-pixel offset.
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
if len(ps) == 0 {
return
@ -67,8 +67,8 @@ type FontExtents struct {
Height float64
}
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
// Extents returns the FontExtents for a font.
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
func Extents(font *truetype.Font, size float64) FontExtents {
bounds := font.Bounds(font.FUnitsPerEm())
scale := size / float64(font.FUnitsPerEm())

View file

@ -1,15 +1,16 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
// Package draw2dkit provides helpers to draw common figures using a Path or a GraphicContext
// Package draw2dkit provides helpers to draw common figures using a Path
package draw2dkit
import (
"github.com/llgcode/draw2d"
"math"
"github.com/llgcode/draw2d"
)
// Rectangle draws a rectangle using a path
// Rectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
path.MoveTo(x1, y1)
path.LineTo(x2, y1)
@ -18,7 +19,7 @@ func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) {
path.Close()
}
// RoundedRectangle draws a rounded rectangle using a path
// RoundedRectangle draws a rectangle using a path between (x1,y1) and (x2,y2)
func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
arcWidth = arcWidth / 2
arcHeight = arcHeight / 2
@ -33,13 +34,13 @@ func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeig
path.Close()
}
// Ellipse draws an ellipse using a path
// Ellipse draws an ellipse using a path with center (cx,cy) and radius (rx,ry)
func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) {
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
path.Close()
}
// Circle draws a circle using a path
// Circle draws a circle using a path with center (cx,cy) and radius
func Circle(path draw2d.PathBuilder, cx, cy, radius float64) {
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
path.Close()

View file

@ -1,8 +1,9 @@
package draw2dkit
import (
"github.com/llgcode/draw2d"
"math"
"github.com/llgcode/draw2d"
)
// Droid draws a droid at specified position

39
draw2dpdf/doc.go Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// Package draw2dpdf provides a graphic context that can draw vector
// graphics and text on pdf file.
//
// Quick Start
//
// The following Go code generates a simple drawing and saves it to a
// pdf document:
// // Initialize the graphic context on an RGBA image
// dest := draw2dpdf.NewPdf("L", "mm", "A4")
// gc := draw2d.NewGraphicContext(dest)
//
// // Set some properties
// gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
// gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
// gc.SetLineWidth(5)
//
// // Draw a closed shape
// gc.MoveTo(10, 10) // should always be called first for a new path
// gc.LineTo(100, 50)
// gc.QuadCurveTo(100, 10, 10, 10)
// gc.Close()
// gc.FillStroke()
//
// // Save to file
// draw2dpdf.SaveToPdfFile(fn, dest)
//
// There are more examples here:
// https://github.com/llgcode/draw2d.samples
//
// Drawing on images is provided by the draw2d package.
// Drawing on opengl is provided by the draw2dgl package.
//
// Acknowledgments
//
// The pdf backend uses https://github.com/jung-kurt/gofpdf
package draw2dpdf

8
draw2dpdf/fileutil.go Normal file
View file

@ -0,0 +1,8 @@
package draw2dpdf
import "github.com/jung-kurt/gofpdf"
// SaveToPdfFile creates and saves a pdf document to a file
func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
return pdf.OutputFileAndClose(filePath)
}

329
draw2dpdf/gc.go Normal file
View file

@ -0,0 +1,329 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// TODO: fonts, dpi
package draw2dpdf
import (
"bytes"
"image"
"image/color"
"image/png"
"log"
"math"
"os"
"path/filepath"
"strconv"
"code.google.com/p/freetype-go/freetype/truetype"
"github.com/jung-kurt/gofpdf"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dbase"
"github.com/llgcode/draw2d/draw2dkit"
)
const (
// DPI of a pdf document is fixed at 72.
DPI = 72
c255 = 255.0 / 65535.0
)
var (
caps = map[draw2d.LineCap]string{
draw2d.RoundCap: "round",
draw2d.ButtCap: "butt",
draw2d.SquareCap: "square"}
joins = map[draw2d.LineJoin]string{
draw2d.RoundJoin: "round",
draw2d.BevelJoin: "bevel",
draw2d.MiterJoin: "miter",
}
imageCount uint32
white color.Color = color.RGBA{255, 255, 255, 255}
)
// NewPdf creates a new pdf document with the draw2d fontfolder, adds
// a page and set fill color to white.
func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
pdf.AddPage()
pdf.SetFillColor(255, 255, 255) // to be compatible with draw2d
return pdf
}
// rgb converts a color (used by draw2d) into 3 int (used by gofpdf)
func rgb(c color.Color) (int, int, int) {
r, g, b, _ := c.RGBA()
return int(float64(r) * c255), int(float64(g) * c255), int(float64(b) * c255)
}
// clearRect draws a white rectangle
func clearRect(gc *GraphicContext, x1, y1, x2, y2 float64) {
// save state
f := gc.Current.FillColor
x, y := gc.pdf.GetXY()
// cover page with white rectangle
gc.SetFillColor(white)
draw2dkit.Rectangle(gc, x1, y1, x2, y2)
gc.Fill()
// restore state
gc.SetFillColor(f)
gc.pdf.MoveTo(x, y)
}
// GraphicContext implements the draw2d.GraphicContext interface
// It provides draw2d with a pdf backend (based on gofpdf)
type GraphicContext struct {
*draw2dbase.StackGraphicContext
pdf *gofpdf.Fpdf
DPI int
}
// NewGraphicContext creates a new pdf GraphicContext
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
gc := &GraphicContext{draw2dbase.NewStackGraphicContext(), pdf, DPI}
gc.SetDPI(DPI)
return gc
}
// DrawImage draws an image as PNG
// TODO: add type (tp) as parameter to argument list?
func (gc *GraphicContext) DrawImage(image image.Image) {
name := strconv.Itoa(int(imageCount))
imageCount += 1
tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
b := &bytes.Buffer{}
png.Encode(b, image)
gc.pdf.RegisterImageReader(name, tp, b)
bounds := image.Bounds()
x0, y0 := float64(bounds.Min.X), float64(bounds.Min.Y)
w, h := float64(bounds.Dx()), float64(bounds.Dy())
gc.pdf.Image(name, x0, y0, w, h, false, tp, 0, "")
}
// Clear draws a white rectangle over the whole page
func (gc *GraphicContext) Clear() {
width, height := gc.pdf.GetPageSize()
clearRect(gc, 0, 0, width, height)
}
// ClearRect draws a white rectangle over the specified area
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
}
// recalc recalculates scale and bounds values from the font size, screen
// resolution and font metrics, and invalidates the glyph cache.
func (gc *GraphicContext) recalc() {
// TODO: resolve properly the font size for pdf and bitmap
gc.Current.Scale = 3 * float64(gc.DPI) / 72
}
// SetDPI sets the DPI which influences the font size.
func (gc *GraphicContext) SetDPI(dpi int) {
gc.DPI = dpi
gc.recalc()
}
// GetDPI returns the DPI which influences the font size.
// (Note that gofpdf uses a fixed dpi of 72:
// https://godoc.org/code.google.com/p/gofpdf#Fpdf.PointConvert)
func (gc *GraphicContext) GetDPI() int {
return gc.DPI
}
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
_, h := gc.pdf.GetFontSize()
return 0, 0, gc.pdf.GetStringWidth(s), h
}
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
_, _, w, h := gc.GetStringBounds(text)
margin := gc.pdf.GetCellMargin()
gc.pdf.MoveTo(x-margin, y+margin-0.82*h)
gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
// gc.pdf.Cell(w, h, text)
return w
}
// FillString draws a string at 0, 0
func (gc *GraphicContext) FillString(text string) (cursor float64) {
return gc.FillStringAt(text, 0, 0)
}
// FillStringAt draws a string at x, y
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
return gc.CreateStringPath(text, x, y)
}
// StrokeString draws a string at 0, 0 (stroking is unsupported,
// string will be filled)
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
return gc.StrokeStringAt(text, 0, 0)
}
// StrokeStringAt draws a string at x, y (stroking is unsupported,
// string will be filled)
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
return gc.CreateStringPath(text, x, y)
}
// Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
gc.draw("D", paths...)
}
// Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
gc.draw("F", paths...)
}
// FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.draw("FD", paths...)
}
var logger = log.New(os.Stdout, "", log.Lshortfile)
// draw fills and/or strokes paths
func (gc *GraphicContext) draw(style string, paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path)
for _, p := range paths {
ConvertPath(p, gc.pdf)
}
if gc.Current.FillRule == draw2d.FillRuleWinding {
style += "*"
}
gc.pdf.DrawPath(style)
}
// overwrite StackGraphicContext methods
// SetStrokeColor sets the stroke color
func (gc *GraphicContext) SetStrokeColor(c color.Color) {
gc.StackGraphicContext.SetStrokeColor(c)
gc.pdf.SetDrawColor(rgb(c))
}
// SetFillColor sets the fill and text color
func (gc *GraphicContext) SetFillColor(c color.Color) {
gc.StackGraphicContext.SetFillColor(c)
gc.pdf.SetFillColor(rgb(c))
gc.pdf.SetTextColor(rgb(c))
}
// SetFont is unsupported by the pdf graphic context, use SetFontData
// instead.
func (gc *GraphicContext) SetFont(font *truetype.Font) {
// TODO: what to do with this api conflict between draw2d and gofpdf?!
}
// SetFontData sets the current font used to draw text. Always use
// this method, as SetFont is unsupported by the pdf graphic context.
// It is mandatory to call this method at least once before printing
// text or the resulting document will not be valid.
// It is necessary to generate a font definition file first with the
// makefont utility. It is not necessary to call this function for the
// core PDF fonts (courier, helvetica, times, zapfdingbats).
// go get github.com/jung-kurt/gofpdf/makefont
// http://godoc.org/github.com/jung-kurt/gofpdf#Fpdf.AddFont
func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
// TODO: call Makefont embed if json file does not exist yet
gc.StackGraphicContext.SetFontData(fontData)
var style string
if fontData.Style&draw2d.FontStyleBold != 0 {
style += "B"
}
if fontData.Style&draw2d.FontStyleItalic != 0 {
style += "I"
}
fn := draw2d.FontFileName(fontData)
fn = fn[:len(fn)-4]
jfn := filepath.Join(draw2d.GetFontFolder(), fn+".json")
gc.pdf.AddFont(fn, style, jfn)
}
// SetFontSize sets the font size in points (as in ``a 12 point font'').
// TODO: resolve this with ImgGraphicContext (now done with gc.Current.Scale)
func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.StackGraphicContext.SetFontSize(fontSize)
gc.recalc()
gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
}
// SetLineWidth sets the line width
func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
gc.StackGraphicContext.SetLineWidth(LineWidth)
gc.pdf.SetLineWidth(LineWidth)
}
// SetLineCap sets the line cap (round, but or square)
func (gc *GraphicContext) SetLineCap(Cap draw2d.LineCap) {
gc.StackGraphicContext.SetLineCap(Cap)
gc.pdf.SetLineCapStyle(caps[Cap])
}
// SetLineJoin sets the line cap (round, bevel or miter)
func (gc *GraphicContext) SetLineJoin(Join draw2d.LineJoin) {
gc.StackGraphicContext.SetLineJoin(Join)
gc.pdf.SetLineJoinStyle(joins[Join])
}
// Transformations
// Scale generally scales the following text, drawings and images.
// sx and sy are the scaling factors for width and height.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Scale(sx, sy float64) {
gc.StackGraphicContext.Scale(sx, sy)
gc.pdf.TransformScale(sx*100, sy*100, 0, 0)
}
// Rotate rotates the following text, drawings and images.
// Angle is specified in radians and measured clockwise from the
// 3 o'clock position.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Rotate(angle float64) {
gc.StackGraphicContext.Rotate(angle)
gc.pdf.TransformRotate(-angle*180/math.Pi, 0, 0)
}
// Translate moves the following text, drawings and images
// horizontally and vertically by the amounts specified by tx and ty.
// This must be placed between gc.Save() and gc.Restore(), otherwise
// the pdf is invalid.
func (gc *GraphicContext) Translate(tx, ty float64) {
gc.StackGraphicContext.Translate(tx, ty)
gc.pdf.TransformTranslate(tx, ty)
}
// Save saves the current context stack
// (transformation, font, color,...).
func (gc *GraphicContext) Save() {
gc.StackGraphicContext.Save()
gc.pdf.TransformBegin()
}
// Restore restores the current context stack
// (transformation, color,...). Restoring the font is not supported.
func (gc *GraphicContext) Restore() {
gc.pdf.TransformEnd()
gc.StackGraphicContext.Restore()
c := gc.Current
gc.SetFontSize(c.FontSize)
// gc.SetFontData(c.FontData) unsupported, causes bug (do not enable)
gc.SetLineWidth(c.LineWidth)
gc.SetStrokeColor(c.StrokeColor)
gc.SetFillColor(c.FillColor)
gc.SetFillRule(c.FillRule)
// gc.SetLineDash(c.Dash, c.DashOffset) // TODO
gc.SetLineCap(c.Cap)
gc.SetLineJoin(c.Join)
// c.Path unsupported
// c.Font unsupported
}

View file

@ -0,0 +1,44 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf
import (
"math"
"github.com/llgcode/draw2d"
)
const deg = 180 / math.Pi
// ConvertPath converts a paths to the pdf api
func ConvertPath(path *draw2d.Path, pdf Vectorizer) {
var startX, startY float64 = 0, 0
i := 0
for _, cmp := range path.Components {
switch cmp {
case draw2d.MoveToCmp:
startX, startY = path.Points[i], path.Points[i+1]
pdf.MoveTo(startX, startY)
i += 2
case draw2d.LineToCmp:
pdf.LineTo(path.Points[i], path.Points[i+1])
i += 2
case draw2d.QuadCurveToCmp:
pdf.CurveTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3])
i += 4
case draw2d.CubicCurveToCmp:
pdf.CurveBezierCubicTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5])
i += 6
case draw2d.ArcToCmp:
pdf.ArcTo(path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3],
0, // degRotate
path.Points[i+4]*deg, // degStart = startAngle
(path.Points[i+4]-path.Points[i+5])*deg) // degEnd = startAngle-angle
i += 6
case draw2d.CloseCmp:
pdf.LineTo(startX, startY)
pdf.ClosePath()
}
}
}

49
draw2dpdf/samples_test.go Normal file
View file

@ -0,0 +1,49 @@
// See also test_test.go
package draw2dpdf_test
import (
"testing"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d.samples"
"github.com/llgcode/draw2d.samples/android"
"github.com/llgcode/draw2d.samples/frameimage"
"github.com/llgcode/draw2d.samples/gopher"
"github.com/llgcode/draw2d.samples/helloworld"
"github.com/llgcode/draw2d.samples/line"
"github.com/llgcode/draw2d.samples/linecapjoin"
"github.com/llgcode/draw2d.samples/postscript"
)
func TestSampleAndroid(t *testing.T) {
test(t, android.Main)
}
func TestSampleGopher(t *testing.T) {
test(t, gopher.Main)
}
func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding json
// file which is generated by gofpdf/makefont.
draw2d.SetFontFolder(samples.Dir("helloworld", "../"))
test(t, helloworld.Main)
}
func TestSampleFrameImage(t *testing.T) {
test(t, frameimage.Main)
}
func TestSampleLine(t *testing.T) {
test(t, line.Main)
}
func TestSampleLineCap(t *testing.T) {
test(t, linecapjoin.Main)
}
func TestSamplePostscript(t *testing.T) {
test(t, postscript.Main)
}

35
draw2dpdf/test_test.go Normal file
View file

@ -0,0 +1,35 @@
// Package draw2dpdf_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test"
// (It should be run from its parent draw2d directory.)
package draw2dpdf_test
import (
"os"
"testing"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dpdf"
)
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) {
// Initialize the graphic context on an pdf document
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Draw sample
fn, err := draw(gc, "pdf")
if err != nil {
t.Errorf("Drawing %q failed: %v", fn, err)
return
}
// Save to pdf only if it doesn't exist because of git
if _, err = os.Stat(fn); err == nil {
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", fn)
return
}
err = draw2dpdf.SaveToPdfFile(fn, dest)
if err != nil {
t.Errorf("Saving %q failed: %v", fn, err)
}
}

22
draw2dpdf/vectorizer.go Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf
// Vectorizer defines the minimal interface for gofpdf.Fpdf
// to be passed to a PathConvertor.
// It is also implemented by for example VertexMatrixTransform
type Vectorizer interface {
// MoveTo creates a new subpath that start at the specified point
MoveTo(x, y float64)
// LineTo adds a line to the current subpath
LineTo(x, y float64)
// CurveTo adds a quadratic bezier curve to the current subpath
CurveTo(cx, cy, x, y float64)
// CurveTo adds a cubic bezier curve to the current subpath
CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
// ArcTo adds an arc to the current subpath
ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
// ClosePath closes the subpath
ClosePath()
}

View file

@ -38,7 +38,7 @@ type FontData struct {
Style FontStyle
}
func fontFileName(fontData FontData) string {
func FontFileName(fontData FontData) string {
fontFileName := fontData.Name
switch fontData.Family {
case FontFamilySans:
@ -62,11 +62,11 @@ func fontFileName(fontData FontData) string {
}
func RegisterFont(fontData FontData, font *truetype.Font) {
fonts[fontFileName(fontData)] = font
fonts[FontFileName(fontData)] = font
}
func GetFont(fontData FontData) *truetype.Font {
fontFileName := fontFileName(fontData)
fontFileName := FontFileName(fontData)
font := fonts[fontFileName]
if font != nil {
return font

1
gc.go
View file

@ -8,6 +8,7 @@ import (
"image/color"
)
// GraphicContext describes the interface for the various backends (images, pdf, opengl, ...)
type GraphicContext interface {
PathBuilder
// BeginPath creates a new path

47
path.go
View file

@ -1,7 +1,6 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package path implements function to build path
package draw2d
import (
@ -9,27 +8,23 @@ import (
"math"
)
// PathBuilder defines methods that creates path
// PathBuilder describes the interface for path drawing.
type PathBuilder interface {
// LastPoint returns the current point of the current path
// LastPoint returns the current point of the current sub path
LastPoint() (x, y float64)
// MoveTo starts a new path at (x, y) position
// MoveTo creates a new subpath that start at the specified point
MoveTo(x, y float64)
// LineTo adds a line to the current path
// LineTo adds a line to the current subpath
LineTo(x, y float64)
// QuadCurveTo adds a quadratic bezier curve to the current path
// QuadCurveTo adds a quadratic Bézier curve to the current subpath
QuadCurveTo(cx, cy, x, y float64)
// CubicCurveTo adds a cubic bezier curve to the current path
// CubicCurveTo adds a cubic Bézier curve to the current subpath
CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64)
// ArcTo adds an arc to the path
// ArcTo adds an arc to the current subpath
ArcTo(cx, cy, rx, ry, startAngle, angle float64)
// Close closes the current path
// Close creates a line from the current point to the last MoveTo
// point (if not the same) and mark the path as closed so the
// first and last lines join nicely.
Close()
}
@ -37,18 +32,27 @@ type PathBuilder interface {
type PathCmp int
const (
// MoveToCmp is a MoveTo component in a Path
MoveToCmp PathCmp = iota
// LineToCmp is a LineTo component in a Path
LineToCmp
// QuadCurveToCmp is a QuadCurveTo component in a Path
QuadCurveToCmp
// CubicCurveToCmp is a CubicCurveTo component in a Path
CubicCurveToCmp
// ArcToCmp is a ArcTo component in a Path
ArcToCmp
// CloseCmp is a ArcTo component in a Path
CloseCmp
)
// Path stores points
type Path struct {
// Components is a slice of PathCmp in a Path and mark the role of each points in the Path
Components []PathCmp
// Points are combined with Components to have a specific role in the path
Points []float64
// Last Point of the Path
x, y float64
}
@ -65,7 +69,6 @@ func (p *Path) LastPoint() (x, y float64) {
// MoveTo starts a new path at (x, y) position
func (p *Path) MoveTo(x, y float64) {
p.appendToPath(MoveToCmp, x, y)
p.x = x
p.y = y
}
@ -135,13 +138,13 @@ func (p *Path) Close() {
}
// Copy make a clone of the current path and return it
func (src *Path) Copy() (dest *Path) {
func (p *Path) Copy() (dest *Path) {
dest = new(Path)
dest.Components = make([]PathCmp, len(src.Components))
copy(dest.Components, src.Components)
dest.Points = make([]float64, len(src.Points))
copy(dest.Points, src.Points)
dest.x, dest.y = src.x, src.y
dest.Components = make([]PathCmp, len(p.Components))
copy(dest.Components, p.Components)
dest.Points = make([]float64, len(p.Points))
copy(dest.Points, p.Points)
dest.x, dest.y = p.x, p.y
return dest
}

47
samples_test.go Normal file
View file

@ -0,0 +1,47 @@
// See also test_test.go
package draw2d_test
import (
"testing"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d.samples"
"github.com/llgcode/draw2d.samples/android"
"github.com/llgcode/draw2d.samples/frameimage"
"github.com/llgcode/draw2d.samples/gopher"
"github.com/llgcode/draw2d.samples/helloworld"
"github.com/llgcode/draw2d.samples/line"
"github.com/llgcode/draw2d.samples/linecapjoin"
"github.com/llgcode/draw2d.samples/postscript"
)
func TestSampleAndroid(t *testing.T) {
test(t, android.Main)
}
func TestSampleGopher(t *testing.T) {
test(t, gopher.Main)
}
func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts
draw2d.SetFontFolder(samples.Dir("helloworld", ""))
test(t, helloworld.Main)
}
func TestSampleFrameImage(t *testing.T) {
test(t, frameimage.Main)
}
func TestSampleLine(t *testing.T) {
test(t, line.Main)
}
func TestSampleLineCap(t *testing.T) {
test(t, linecapjoin.Main)
}
func TestSamplePostscript(t *testing.T) {
test(t, postscript.Main)
}

29
test_test.go Normal file
View file

@ -0,0 +1,29 @@
// Package draw2d_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test"
package draw2d_test
import (
"image"
"testing"
"github.com/llgcode/draw2d"
)
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) {
// Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest)
// Draw Android logo
fn, err := draw(gc, "png")
if err != nil {
t.Errorf("Drawing %q failed: %v", fn, err)
return
}
// Save to png
err = draw2d.SaveToPngFile(fn, dest)
if err != nil {
t.Errorf("Saving %q failed: %v", fn, err)
}
}