Merge branch 'master' into Clean-Up

Conflicts:
	arc.go
	curve/curve_test.go
	draw2dgl/gc.go
	draw2dimg/rgba_interpolation.go
	draw2dpdf/gc.go
	draw2dpdf/path_converter.go
	path_adder.go
	path_storage.go
	raster/raster_test.go
	vertex2d.go
This commit is contained in:
Laurent Le Goff 2015-08-14 21:40:32 +02:00
commit 7ef94ce784
47 changed files with 7074 additions and 216 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ _test*
**/*.dll **/*.dll
**/core*[0-9] **/core*[0-9]
.private .private

View file

@ -1 +1,2 @@
Laurent Le Goff Laurent Le Goff
Stani Michiels, gmail:stani.be

View file

@ -1,15 +1,19 @@
draw2d draw2d
====== ======
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. 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 (draw2dgl), which can also be used on the google app engine. It can be used as a pure go [Cairo](http://www.cairographics.org/) alternative. draw2d is released under the BSD license. See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details.
See the [documentation](http://godoc.org/github.com/llgcode/draw2d) for more details. [![geometry](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/geometry.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/geometry.pdf)[![postscript](https://raw.githubusercontent.com/llgcode/draw2d/master/output/samples/postscript.png)](https://raw.githubusercontent.com/llgcode/draw2d/master/resource/image/postscript.pdf)
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.
Features 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). 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).
Package draw2d follows the conventions of the [HTML Canvas 2D Context](http://www.w3.org/TR/2dcontext/) for coordinate system, angles, etc...
Installation Installation
------------ ------------
@ -42,7 +46,7 @@ gc.Close()
gc.FillStroke() gc.FillStroke()
// Save to file // Save to file
draw2d.SaveToPngFile(fn, dest) draw2d.SaveToPngFile("hello.png", dest)
``` ```
The same Go code can also generate a pdf document with package draw2dpdf: The same Go code can also generate a pdf document with package draw2dpdf:
@ -65,13 +69,26 @@ gc.Close()
gc.FillStroke() gc.FillStroke()
// Save to file // Save to file
draw2dpdf.SaveToPdfFile(fn, dest) draw2dpdf.SaveToPdfFile("hello.pdf", dest)
``` ```
There are more examples here: https://github.com/llgcode/draw2d.samples There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
Drawing on opengl is provided by the draw2dgl package. Drawing on opengl is provided by the draw2dgl package.
Testing
-------
The samples are run as tests from the root package folder `draw2d` by:
```
go test ./...
```
Or if you want to run with test coverage:
```
go test -cover ./... | grep -v "no test"
```
This will generate output by the different backends in the output folder.
Acknowledgments Acknowledgments
--------------- ---------------
@ -94,3 +111,4 @@ References
- [antigrain.com](http://www.antigrain.com) - [antigrain.com](http://www.antigrain.com)
- [freetype-go](http://code.google.com/p/freetype-go) - [freetype-go](http://code.google.com/p/freetype-go)
-

View file

@ -3,8 +3,9 @@
// Package draw2d is a pure go 2D vector graphics library with support // Package draw2d is a pure go 2D vector graphics library with support
// for multiple output devices such as images (draw2d), pdf documents // for multiple output devices such as images (draw2d), pdf documents
// (draw2dpdf) and opengl (draw2dopengl), which can also be used on the // (draw2dpdf) and opengl (draw2dgl), which can also be used on the
// google app engine. It can be used as a pure go Cairo alternative. // google app engine. It can be used as a pure go Cairo alternative.
// draw2d is released under the BSD license.
// //
// Features // Features
// //
@ -13,6 +14,8 @@
// All drawing operations can be transformed by affine transformations // All drawing operations can be transformed by affine transformations
// (scale, rotation, translation). // (scale, rotation, translation).
// //
// Package draw2d follows the conventions of http://www.w3.org/TR/2dcontext for coordinate system, angles, etc...
//
// Installation // Installation
// //
// To install or update the package draw2d on your system, run: // To install or update the package draw2d on your system, run:
@ -40,15 +43,25 @@
// gc.FillStroke() // gc.FillStroke()
// //
// // Save to file // // Save to file
// draw2d.SaveToPngFile(fn, dest) // draw2d.SaveToPngFile("hello.png", dest)
// //
// There are more examples here: // There are more examples here:
// https://github.com/llgcode/draw2d.samples // https://github.com/llgcode/draw2d/tree/master/samples
// //
// Drawing on pdf documents is provided by the draw2dpdf package. // Drawing on pdf documents is provided by the draw2dpdf package.
// Drawing on opengl is provided by the draw2dgl package. // Drawing on opengl is provided by the draw2dgl package.
// See subdirectories at the bottom of this page. // See subdirectories at the bottom of this page.
// //
// Testing
//
// The samples are run as tests from the root package folder `draw2d` by:
// go test ./...
//
// Or if you want to run with test coverage:
// go test -cover ./... | grep -v "no test"
//
// This will generate output by the different backends in the output folder.
//
// Acknowledgments // Acknowledgments
// //
// Laurent Le Goff wrote this library, inspired by Postscript and // Laurent Le Goff wrote this library, inspired by Postscript and
@ -58,9 +71,6 @@
// graphic context (https://github.com/llgcode/ps). Stani Michiels // graphic context (https://github.com/llgcode/ps). Stani Michiels
// implemented the pdf backend with the gofpdf package. // implemented the pdf backend with the gofpdf package.
// //
// The package depends on freetype-go package for its rasterization
// algorithm.
//
// Packages using draw2d // Packages using draw2d
// //
// - https://github.com/llgcode/ps: Postscript interpreter written in Go // - https://github.com/llgcode/ps: Postscript interpreter written in Go

View file

@ -46,9 +46,9 @@ func SubdivideCubic(c, c1, c2 []float64) {
c2[0], c2[1] = c1[6], c1[7] c2[0], c2[1] = c1[6], c1[7]
} }
// TraceCubic generate lines subdividing the cubic curve using a Flattener // TraceCubic generate lines subdividing the cubic curve using a Liner
// flattening_threshold helps determines the flattening expectation of the curve // flattening_threshold helps determines the flattening expectation of the curve
func TraceCubic(t Flattener, cubic []float64, flatteningThreshold float64) { func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) {
// Allocation curves // Allocation curves
var curves [CurveRecursionLimit * 8]float64 var curves [CurveRecursionLimit * 8]float64
copy(curves[0:8], cubic[0:8]) copy(curves[0:8], cubic[0:8])
@ -101,9 +101,9 @@ func SubdivideQuad(c, c1, c2 []float64) {
return return
} }
// TraceQuad generate lines subdividing the curve using a Flattener // TraceQuad generate lines subdividing the curve using a Liner
// flattening_threshold helps determines the flattening expectation of the curve // flattening_threshold helps determines the flattening expectation of the curve
func TraceQuad(t Flattener, quad []float64, flatteningThreshold float64) { func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) {
// Allocates curves stack // Allocates curves stack
var curves [CurveRecursionLimit * 6]float64 var curves [CurveRecursionLimit * 6]float64
copy(curves[0:6], quad[0:6]) copy(curves[0:6], quad[0:6])
@ -131,8 +131,8 @@ func TraceQuad(t Flattener, quad []float64, flatteningThreshold float64) {
} }
} }
// TraceArc trace an arc using a Flattener // TraceArc trace an arc using a Liner
func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { func TraceArc(t Liner, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
end := start + angle end := start + angle
clockWise := true clockWise := true
if angle < 0 { if angle < 0 {

View file

@ -36,7 +36,7 @@ var (
func init() { func init() {
os.Mkdir("test_results", 0666) os.Mkdir("test_results", 0666)
f, err := os.Create("test_results/_test.html") f, err := os.Create("../output/curve/_test.html")
if err != nil { if err != nil {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
@ -81,7 +81,7 @@ func TestCubicCurve(t *testing.T) {
raster.PolylineBresenham(img, image.Black, p.Points...) raster.PolylineBresenham(img, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) SaveToPngFile(fmt.Sprintf("../output/curve/_test%d.png", i/8), img)
log.Printf("Num of points: %d\n", len(p.Points)) log.Printf("Num of points: %d\n", len(p.Points))
} }
fmt.Println() fmt.Println()
@ -97,7 +97,7 @@ func TestQuadCurve(t *testing.T) {
raster.PolylineBresenham(img, image.Black, p.Points...) raster.PolylineBresenham(img, image.Black, p.Points...)
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) SaveToPngFile(fmt.Sprintf("../output/curve/_testQuad%d.png", i), img)
log.Printf("Num of points: %d\n", len(p.Points)) log.Printf("Num of points: %d\n", len(p.Points))
} }
fmt.Println() fmt.Println()

View file

@ -7,6 +7,12 @@ import (
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
) )
// Liner receive segment definition
type Liner interface {
// LineTo Draw a line from the current position to the point (x, y)
LineTo(x, y float64)
}
// Flattener receive segment definition // Flattener receive segment definition
type Flattener interface { type Flattener interface {
// MoveTo Start a New line from the point (x, y) // MoveTo Start a New line from the point (x, y)

View file

@ -1,80 +0,0 @@
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"
)
type Drawer struct {
matrix draw2d.Matrix
img draw.Image
painter Painter
fillRasterizer *raster.Rasterizer
strokeRasterizer *raster.Rasterizer
glyphBuf *truetype.GlyphBuf
}
func NewDrawer(img *image.RGBA) *Drawer {
width, height := img.Bounds().Dx(), img.Bounds().Dy()
return &Drawer{
draw2d.NewIdentityMatrix(),
img,
raster.NewRGBAPainter(img),
raster.NewRasterizer(width, height),
raster.NewRasterizer(width, height),
truetype.NewGlyphBuf(),
}
}
func (d *Drawer) Matrix() *draw2d.Matrix {
return &d.matrix
}
func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) {
switch fillStyle := style.(type) {
case draw2d.SolidFillStyle:
d.fillRasterizer.UseNonZeroWinding = fillStyle.FillRule == draw2d.FillRuleWinding
d.painter.SetColor(fillStyle.Color)
default:
panic("FillStyle not supported")
}
flattener := draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.fillRasterizer}}
draw2dbase.Flatten(path, flattener, d.matrix.GetScale())
d.fillRasterizer.Rasterize(d.painter)
d.fillRasterizer.Clear()
}
func (d *Drawer) Stroke(path *draw2d.Path, style draw2d.StrokeStyle) {
d.strokeRasterizer.UseNonZeroWinding = true
stroker := draw2dbase.NewLineStroker(style.LineCap, style.LineJoin, draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.strokeRasterizer}})
stroker.HalfLineWidth = style.Width / 2
var liner draw2dbase.Flattener
if style.Dash != nil && len(style.Dash) > 0 {
liner = draw2dbase.NewDashConverter(style.Dash, style.DashOffset, stroker)
} else {
liner = stroker
}
draw2dbase.Flatten(path, liner, d.matrix.GetScale())
d.painter.SetColor(style.Color)
d.strokeRasterizer.Rasterize(d.painter)
d.strokeRasterizer.Clear()
}
func (d *Drawer) Text(text string, x, y float64, style draw2d.TextStyle) {
}
func (d *Drawer) Image(image image.Image, x, y float64, scaling draw2d.ImageScaling) {
}

42
draw2dpdf/README.md Normal file
View file

@ -0,0 +1,42 @@
draw2d pdf
==========
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.
Quick Start
-----------
The following Go code generates a simple drawing and saves it to a pdf document:
```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("hello.pdf", dest)
```
There are more examples here: https://github.com/llgcode/draw2d/tree/master/samples
Alternative backends
--------------------
- 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

View file

@ -2,7 +2,7 @@
// created: 26/06/2015 by Stani Michiels // created: 26/06/2015 by Stani Michiels
// Package draw2dpdf provides a graphic context that can draw vector // Package draw2dpdf provides a graphic context that can draw vector
// graphics and text on pdf file. // graphics and text on pdf file with the gofpdf package.
// //
// Quick Start // Quick Start
// //
@ -25,10 +25,12 @@
// gc.FillStroke() // gc.FillStroke()
// //
// // Save to file // // Save to file
// draw2dpdf.SaveToPdfFile(fn, dest) // draw2dpdf.SaveToPdfFile("hello.pdf", dest)
// //
// There are more examples here: // There are more examples here:
// https://github.com/llgcode/draw2d.samples // https://github.com/llgcode/draw2d/tree/master/samples
//
// Alternative backends
// //
// Drawing on images is provided by the draw2d package. // Drawing on images is provided by the draw2d package.
// Drawing on opengl is provided by the draw2dgl package. // Drawing on opengl is provided by the draw2dgl package.

View file

@ -1,3 +1,6 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
package draw2dpdf package draw2dpdf
import "github.com/jung-kurt/gofpdf" import "github.com/jung-kurt/gofpdf"

View file

@ -1,6 +1,6 @@
// Copyright 2015 The draw2d Authors. All rights reserved. // Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels // created: 26/06/2015 by Stani Michiels
// TODO: fonts, dpi // TODO: dashed line
package draw2dpdf package draw2dpdf
@ -12,7 +12,6 @@ import (
"log" "log"
"math" "math"
"os" "os"
"path/filepath"
"strconv" "strconv"
"code.google.com/p/freetype-go/freetype/truetype" "code.google.com/p/freetype-go/freetype/truetype"
@ -47,8 +46,14 @@ var (
// a page and set fill color to white. // a page and set fill color to white.
func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf { func NewPdf(orientationStr, unitStr, sizeStr string) *gofpdf.Fpdf {
pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder()) pdf := gofpdf.New(orientationStr, unitStr, sizeStr, draw2d.GetFontFolder())
// to be compatible with draw2d
pdf.SetMargins(0, 0, 0)
pdf.SetDrawColor(0, 0, 0)
pdf.SetFillColor(255, 255, 255)
pdf.SetLineCapStyle("round")
pdf.SetLineJoinStyle("round")
pdf.SetLineWidth(1)
pdf.AddPage() pdf.AddPage()
pdf.SetFillColor(255, 255, 255) // to be compatible with draw2d
return pdf return pdf
} }
@ -91,7 +96,7 @@ func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
// TODO: add type (tp) as parameter to argument list? // TODO: add type (tp) as parameter to argument list?
func (gc *GraphicContext) DrawImage(image image.Image) { func (gc *GraphicContext) DrawImage(image image.Image) {
name := strconv.Itoa(int(imageCount)) name := strconv.Itoa(int(imageCount))
imageCount += 1 imageCount++
tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF" tp := "PNG" // "JPG", "JPEG", "PNG" and "GIF"
b := &bytes.Buffer{} b := &bytes.Buffer{}
png.Encode(b, image) png.Encode(b, image)
@ -108,7 +113,8 @@ func (gc *GraphicContext) Clear() {
clearRect(gc, 0, 0, width, height) clearRect(gc, 0, 0, width, height)
} }
// ClearRect draws a white rectangle over the specified area // ClearRect draws a white rectangle over the specified area.
// Samples: line.
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) { func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2)) clearRect(gc, float64(x1), float64(y1), float64(x2), float64(y2))
} }
@ -134,18 +140,31 @@ func (gc *GraphicContext) GetDPI() int {
} }
// GetStringBounds returns the approximate pixel bounds of the string s at x, y. // GetStringBounds returns the approximate pixel bounds of the string s at x, y.
// The left edge of the em square of the first character of s
// and the baseline intersect at 0, 0 in the returned coordinates.
// Therefore the top and left coordinates may well be negative.
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
_, h := gc.pdf.GetFontSize() _, h := gc.pdf.GetFontSize()
return 0, 0, gc.pdf.GetStringWidth(s), h d := gc.pdf.GetFontDesc("", "")
if d.Ascent == 0 {
// not defined (standard font?), use average of 81%
top = 0.81 * h
} else {
top = -float64(d.Ascent) * h / float64(d.Ascent-d.Descent)
}
return 0, top, gc.pdf.GetStringWidth(s), top + h
} }
// CreateStringPath creates a path from the string s at x, y, and returns the string width. // 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) { func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) {
_, _, w, h := gc.GetStringBounds(text) //fpdf uses the top left corner
left, top, right, bottom := gc.GetStringBounds(text)
w := right - left
h := bottom - top
// gc.pdf.SetXY(x, y-h) do not use this as y-h might be negative
margin := gc.pdf.GetCellMargin() margin := gc.pdf.GetCellMargin()
gc.pdf.MoveTo(x-margin, y+margin-0.82*h) gc.pdf.MoveTo(x-left-margin, y+top)
gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "") gc.pdf.CellFormat(w, h, text, "", 0, "BL", false, 0, "")
// gc.pdf.Cell(w, h, text)
return w return w
} }
@ -173,29 +192,53 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor floa
// Stroke strokes the paths with the color specified by SetStrokeColor // Stroke strokes the paths with the color specified by SetStrokeColor
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
gc.draw("D", paths...) _, _, _, alphaS := gc.Current.StrokeColor.RGBA()
gc.draw("D", alphaS, paths...)
gc.Current.Path.Clear()
} }
// Fill fills the paths with the color specified by SetFillColor // Fill fills the paths with the color specified by SetFillColor
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
gc.draw("F", paths...) style := "F"
if gc.Current.FillRule != draw2d.FillRuleWinding {
style += "*"
}
_, _, _, alphaF := gc.Current.FillColor.RGBA()
gc.draw(style, alphaF, paths...)
gc.Current.Path.Clear()
} }
// FillStroke first fills the paths and than strokes them // FillStroke first fills the paths and than strokes them
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
gc.draw("FD", paths...) var rule string
if gc.Current.FillRule != draw2d.FillRuleWinding {
rule = "*"
}
_, _, _, alphaS := gc.Current.StrokeColor.RGBA()
_, _, _, alphaF := gc.Current.FillColor.RGBA()
if alphaS == alphaF {
gc.draw("FD"+rule, alphaF, paths...)
} else {
gc.draw("F"+rule, alphaF, paths...)
gc.draw("S", alphaS, paths...)
}
gc.Current.Path.Clear()
} }
var logger = log.New(os.Stdout, "", log.Lshortfile) var logger = log.New(os.Stdout, "", log.Lshortfile)
const alphaMax = float64(0xFFFF)
// draw fills and/or strokes paths // draw fills and/or strokes paths
func (gc *GraphicContext) draw(style string, paths ...*draw2d.Path) { func (gc *GraphicContext) draw(style string, alpha uint32, paths ...*draw2d.Path) {
paths = append(paths, gc.Current.Path) paths = append(paths, gc.Current.Path)
for _, p := range paths { for _, p := range paths {
ConvertPath(p, gc.pdf) ConvertPath(p, gc.pdf)
} }
if gc.Current.FillRule == draw2d.FillRuleWinding { a := float64(alpha) / alphaMax
style += "*" current, blendMode := gc.pdf.GetAlpha()
if a != current {
gc.pdf.SetAlpha(a, blendMode)
} }
gc.pdf.DrawPath(style) gc.pdf.DrawPath(style)
} }
@ -242,8 +285,9 @@ func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
} }
fn := draw2d.FontFileName(fontData) fn := draw2d.FontFileName(fontData)
fn = fn[:len(fn)-4] fn = fn[:len(fn)-4]
jfn := filepath.Join(draw2d.GetFontFolder(), fn+".json") size, _ := gc.pdf.GetFontSize()
gc.pdf.AddFont(fn, style, jfn) gc.pdf.AddFont(fontData.Name, style, fn+".json")
gc.pdf.SetFont(fontData.Name, style, size)
} }
// SetFontSize sets the font size in points (as in ``a 12 point font''). // SetFontSize sets the font size in points (as in ``a 12 point font'').
@ -254,6 +298,12 @@ func (gc *GraphicContext) SetFontSize(fontSize float64) {
gc.pdf.SetFontSize(fontSize * gc.Current.Scale) gc.pdf.SetFontSize(fontSize * gc.Current.Scale)
} }
// SetLineDash sets the line dash pattern
func (gc *GraphicContext) SetLineDash(Dash []float64, DashOffset float64) {
gc.StackGraphicContext.SetLineDash(Dash, DashOffset)
gc.pdf.SetDashPattern(Dash, DashOffset)
}
// SetLineWidth sets the line width // SetLineWidth sets the line width
func (gc *GraphicContext) SetLineWidth(LineWidth float64) { func (gc *GraphicContext) SetLineWidth(LineWidth float64) {
gc.StackGraphicContext.SetLineWidth(LineWidth) gc.StackGraphicContext.SetLineWidth(LineWidth)

View file

@ -1,3 +1,5 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// See also test_test.go // See also test_test.go
package draw2dpdf_test package draw2dpdf_test
@ -6,29 +8,43 @@ import (
"testing" "testing"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d.samples" "github.com/llgcode/draw2d/samples/android"
"github.com/llgcode/draw2d.samples/android" "github.com/llgcode/draw2d/samples/frameimage"
"github.com/llgcode/draw2d.samples/frameimage" "github.com/llgcode/draw2d/samples/geometry"
"github.com/llgcode/draw2d.samples/gopher" "github.com/llgcode/draw2d/samples/gopher"
"github.com/llgcode/draw2d.samples/helloworld" "github.com/llgcode/draw2d/samples/gopher2"
"github.com/llgcode/draw2d.samples/line" "github.com/llgcode/draw2d/samples/helloworld"
"github.com/llgcode/draw2d.samples/linecapjoin" "github.com/llgcode/draw2d/samples/line"
"github.com/llgcode/draw2d.samples/postscript" "github.com/llgcode/draw2d/samples/linecapjoin"
"github.com/llgcode/draw2d/samples/postscript"
) )
func TestSampleAndroid(t *testing.T) { func TestSampleAndroid(t *testing.T) {
test(t, android.Main) test(t, android.Main)
} }
// TODO: FillString: w (width) is incorrect
func TestSampleGeometry(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("../resource/font")
test(t, geometry.Main)
}
func TestSampleGopher(t *testing.T) { func TestSampleGopher(t *testing.T) {
test(t, gopher.Main) test(t, gopher.Main)
} }
func TestSampleGopher2(t *testing.T) {
test(t, gopher2.Main)
}
func TestSampleHelloWorld(t *testing.T) { func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts // Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding json // The pdf backend needs for every ttf file its corresponding
// file which is generated by gofpdf/makefont. // json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder(samples.Dir("helloworld", "../")) draw2d.SetFontFolder("../resource/font")
test(t, helloworld.Main) test(t, helloworld.Main)
} }

View file

@ -1,10 +1,12 @@
// Copyright 2015 The draw2d Authors. All rights reserved.
// created: 26/06/2015 by Stani Michiels
// Package draw2dpdf_test gives test coverage with the command: // Package draw2dpdf_test gives test coverage with the command:
// go test -cover ./... | grep -v "no test" // go test -cover ./... | grep -v "no test"
// (It should be run from its parent draw2d directory.) // (It should be run from its parent draw2d directory.)
package draw2dpdf_test package draw2dpdf_test
import ( import (
"os"
"testing" "testing"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
@ -18,18 +20,20 @@ func test(t *testing.T, draw sample) {
dest := draw2dpdf.NewPdf("L", "mm", "A4") dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest) gc := draw2dpdf.NewGraphicContext(dest)
// Draw sample // Draw sample
fn, err := draw(gc, "pdf") output, err := draw(gc, "pdf")
if err != nil { if err != nil {
t.Errorf("Drawing %q failed: %v", fn, err) t.Errorf("Drawing %q failed: %v", output, err)
return return
} }
// Save to pdf only if it doesn't exist because of git /*
if _, err = os.Stat(fn); err == nil { // Save to pdf only if it doesn't exist because of git
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", fn) if _, err = os.Stat(output); err == nil {
return t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", output)
} return
err = draw2dpdf.SaveToPdfFile(fn, dest) }
*/
err = draw2dpdf.SaveToPdfFile(output, dest)
if err != nil { if err != nil {
t.Errorf("Saving %q failed: %v", fn, err) t.Errorf("Saving %q failed: %v", output, err)
} }
} }

18
font.go
View file

@ -7,13 +7,15 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"path" "path"
"path/filepath"
"code.google.com/p/freetype-go/freetype/truetype" "code.google.com/p/freetype-go/freetype/truetype"
) )
var ( var (
fontFolder = "../resource/font/" fontFolder = "../resource/font/"
fonts = make(map[string]*truetype.Font) fonts = make(map[string]*truetype.Font)
fontNamer FontFileNamer = FontFileName
) )
type FontStyle byte type FontStyle byte
@ -38,6 +40,8 @@ type FontData struct {
Style FontStyle Style FontStyle
} }
type FontFileNamer func(fontData FontData) string
func FontFileName(fontData FontData) string { func FontFileName(fontData FontData) string {
fontFileName := fontData.Name fontFileName := fontData.Name
switch fontData.Family { switch fontData.Family {
@ -62,11 +66,11 @@ func FontFileName(fontData FontData) string {
} }
func RegisterFont(fontData FontData, font *truetype.Font) { func RegisterFont(fontData FontData, font *truetype.Font) {
fonts[FontFileName(fontData)] = font fonts[fontNamer(fontData)] = font
} }
func GetFont(fontData FontData) *truetype.Font { func GetFont(fontData FontData) *truetype.Font {
fontFileName := FontFileName(fontData) fontFileName := fontNamer(fontData)
font := fonts[fontFileName] font := fonts[fontFileName]
if font != nil { if font != nil {
return font return font
@ -80,7 +84,11 @@ func GetFontFolder() string {
} }
func SetFontFolder(folder string) { func SetFontFolder(folder string) {
fontFolder = folder fontFolder = filepath.Clean(folder)
}
func SetFontNamer(fn FontFileNamer) {
fontNamer = fn
} }
func loadFont(fontFileName string) *truetype.Font { func loadFont(fontFileName string) *truetype.Font {

11
output/README.md Normal file
View file

@ -0,0 +1,11 @@
Demo output
===========
These folders are empty when you check out the git repository. The output is generated by the tests:
```
go test ./...
```
or with coverage:
```
go test -cover ./... | grep -v "no test"
```

4
output/curve/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

4
output/raster/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

6
output/samples/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
!geometry.png
!postscript.png

BIN
output/samples/geometry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -1,39 +1,16 @@
package raster package raster
import ( import (
"bufio"
"image" "image"
"image/color" "image/color"
"image/png"
"log"
"os"
"testing" "testing"
"code.google.com/p/freetype-go/freetype/raster" "code.google.com/p/freetype-go/freetype/raster"
"github.com/llgcode/draw2d/curve" "github.com/llgcode/draw2d/draw2dbase"
"github.com/llgcode/draw2d/draw2dimg"
) )
var flattening_threshold float64 = 0.5 var flatteningThreshold = 0.5
func savepng(filePath string, m image.Image) {
f, err := os.Create(filePath)
if err != nil {
log.Println(err)
os.Exit(1)
}
defer f.Close()
b := bufio.NewWriter(f)
err = png.Encode(b, m)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = b.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
}
type Path struct { type Path struct {
points []float64 points []float64
@ -54,53 +31,59 @@ func (p *Path) LineTo(x, y float64) {
func TestFreetype(t *testing.T) { func TestFreetype(t *testing.T) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200) rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = false rasterizer.UseNonZeroWinding = false
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)}) rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 { for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)}) rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
} }
painter := raster.NewRGBAPainter(img) painter := raster.NewRGBAPainter(img)
painter.SetColor(color) painter.SetColor(color)
rasterizer.Rasterize(painter) rasterizer.Rasterize(painter)
savepng("_testFreetype.png", img) draw2dimg.SaveToPngFile("../output/raster/TestFreetype.png", img)
} }
func TestFreetypeNonZeroWinding(t *testing.T) { func TestFreetypeNonZeroWinding(t *testing.T) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200) rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = true rasterizer.UseNonZeroWinding = true
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)}) rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 { for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)}) rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
} }
painter := raster.NewRGBAPainter(img) painter := raster.NewRGBAPainter(img)
painter.SetColor(color) painter.SetColor(color)
rasterizer.Rasterize(painter) rasterizer.Rasterize(painter)
savepng("_testFreetypeNonZeroWinding.png", img) draw2dimg.SaveToPngFile("../output/raster/TestFreetypeNonZeroWinding.png", img)
} }
func TestRasterizer(t *testing.T) { func TestRasterizer(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -108,15 +91,15 @@ func TestRasterizer(t *testing.T) {
//PolylineBresenham(img, image.Black, poly...) //PolylineBresenham(img, image.Black, poly...)
r.RenderEvenOdd(img, &color, &poly, tr) r.RenderEvenOdd(img, &color, &poly, tr)
savepng("_testRasterizer.png", img) draw2dimg.SaveToPngFile("../output/raster/TestRasterizer.png", img)
} }
func TestRasterizerNonZeroWinding(t *testing.T) { func TestRasterizerNonZeroWinding(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -124,14 +107,14 @@ func TestRasterizerNonZeroWinding(t *testing.T) {
//PolylineBresenham(img, image.Black, poly...) //PolylineBresenham(img, image.Black, poly...)
r.RenderNonZeroWinding(img, &color, &poly, tr) r.RenderNonZeroWinding(img, &color, &poly, tr)
savepng("_testRasterizerNonZeroWinding.png", img) draw2dimg.SaveToPngFile("../output/raster/TestRasterizerNonZeroWinding.png", img)
} }
func BenchmarkFreetype(b *testing.B) { func BenchmarkFreetype(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -139,20 +122,25 @@ func BenchmarkFreetype(b *testing.B) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200) rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = false rasterizer.UseNonZeroWinding = false
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)}) rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 { for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)}) rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
} }
painter := raster.NewRGBAPainter(img) painter := raster.NewRGBAPainter(img)
painter.SetColor(color) painter.SetColor(color)
rasterizer.Rasterize(painter) rasterizer.Rasterize(painter)
} }
} }
func BenchmarkFreetypeNonZeroWinding(b *testing.B) { func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
@ -160,9 +148,13 @@ func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
img := image.NewRGBA(image.Rect(0, 0, 200, 200)) img := image.NewRGBA(image.Rect(0, 0, 200, 200))
rasterizer := raster.NewRasterizer(200, 200) rasterizer := raster.NewRasterizer(200, 200)
rasterizer.UseNonZeroWinding = true rasterizer.UseNonZeroWinding = true
rasterizer.Start(raster.Point{raster.Fix32(10 * 256), raster.Fix32(190 * 256)}) rasterizer.Start(raster.Point{
X: raster.Fix32(10 * 256),
Y: raster.Fix32(190 * 256)})
for j := 0; j < len(poly); j = j + 2 { for j := 0; j < len(poly); j = j + 2 {
rasterizer.Add1(raster.Point{raster.Fix32(poly[j] * 256), raster.Fix32(poly[j+1] * 256)}) rasterizer.Add1(raster.Point{
X: raster.Fix32(poly[j] * 256),
Y: raster.Fix32(poly[j+1] * 256)})
} }
painter := raster.NewRGBAPainter(img) painter := raster.NewRGBAPainter(img)
painter.SetColor(color) painter.SetColor(color)
@ -173,8 +165,8 @@ func BenchmarkFreetypeNonZeroWinding(b *testing.B) {
func BenchmarkRasterizerNonZeroWinding(b *testing.B) { func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}
@ -188,8 +180,8 @@ func BenchmarkRasterizerNonZeroWinding(b *testing.B) {
func BenchmarkRasterizer(b *testing.B) { func BenchmarkRasterizer(b *testing.B) {
var p Path var p Path
p.LineTo(10, 190) p.LineTo(10, 190)
c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} draw2dbase.TraceCubic(&p, []float64{10, 190, 10, 10, 190, 10, 190, 190}, 0.5)
c.Trace(&p, flattening_threshold)
poly := Polygon(p.points) poly := Polygon(p.points)
color := color.RGBA{0, 0, 0, 0xff} color := color.RGBA{0, 0, 0, 0xff}
tr := [6]float64{1, 0, 0, 1, 0, 0} tr := [6]float64{1, 0, 0, 1, 0, 0}

View file

@ -0,0 +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}

BIN
resource/font/luximbi.z Normal file

Binary file not shown.

BIN
resource/image/geometry.pdf Normal file

Binary file not shown.

BIN
resource/image/gopher.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

2733
resource/image/tiger.ps Normal file

File diff suppressed because it is too large Load diff

88
samples/README.md Normal file
View file

@ -0,0 +1,88 @@
draw2d samples
==============
Various samples for using draw2d
Using the image backend
-----------------------
The following Go code draws the android sample on a png image:
```
import (
"image"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/samples/android"
)
function main(){}
// 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 := android.Main(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)
}
}
```
Using the pdf backend
---------------------
The following Go code draws the android sample on a pdf document:
```
import (
"image"
"github.com/llgcode/draw2d/draw2dpdf"
"github.com/llgcode/draw2d/samples/android"
)
function main(){}
// Initialize the graphic context on a pdf document
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Draw Android logo
fn, err := android.Main(gc, "png")
if err != nil {
t.Errorf("Drawing %q failed: %v", fn, err)
return
}
// Save to pdf
err = draw2dpdf.SaveToPdfFile(fn, dest)
if err != nil {
t.Errorf("Saving %q failed: %v", fn, err)
}
}
```
Testing
-------
These samples are run as tests from the root package folder `draw2d` by:
```
go test ./...
```
Or if you want to run with test coverage:
```
go test -cover ./... | grep -v "no test"
```
The following files are responsible to run the image tests:
```
draw2d/test_test.go
draw2d/samples_test.go
```
The following files are responsible to run the pdf tests:
```
draw2d/pdf/test_test.go
draw2dpdf/samples_test.go
```

View file

@ -0,0 +1,76 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package android draws an android avatar.
package android
import (
"image/color"
"math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
)
// Main draws a droid and returns the filename. This should only be
// used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw the droid
Draw(gc, 65, 0)
// Return the output filename
return samples.Output("android", ext), nil
}
// Draw the droid on a certain position.
func Draw(gc draw2d.GraphicContext, x, y float64) {
// set the fill and stroke color of the droid
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
// set line properties
gc.SetLineCap(draw2d.RoundCap)
gc.SetLineWidth(5)
// head
gc.MoveTo(x+30, y+70)
gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 180*(math.Pi/180))
gc.Close()
gc.FillStroke()
gc.MoveTo(x+60, y+25)
gc.LineTo(x+50, y+10)
gc.MoveTo(x+100, y+25)
gc.LineTo(x+110, y+10)
gc.Stroke()
// left eye
draw2dkit.Circle(gc, x+60, y+45, 5)
gc.FillStroke()
// right eye
draw2dkit.Circle(gc, x+100, y+45, 5)
gc.FillStroke()
// body
draw2dkit.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10)
gc.FillStroke()
draw2dkit.Rectangle(gc, x+30, y+75, x+30+100, y+75+80)
gc.FillStroke()
// left arm
draw2dkit.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10)
gc.FillStroke()
// right arm
draw2dkit.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10)
gc.FillStroke()
// left leg
draw2dkit.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10)
gc.FillStroke()
// right leg
draw2dkit.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10)
gc.FillStroke()
}

View file

@ -0,0 +1,8 @@
application: draw2d-test
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app

View file

@ -0,0 +1,73 @@
// +build appengine
// Package gae demonstrates draw2d on a Google appengine server.
package gae
import (
"fmt"
"image"
"image/png"
"net/http"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dpdf"
"github.com/llgcode/draw2d/samples/android"
"appengine"
)
type appError struct {
Error error
Message string
Code int
}
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
c := appengine.NewContext(r)
c.Errorf("%v", e.Error)
http.Error(w, e.Message, e.Code)
}
}
func init() {
http.Handle("/pdf", appHandler(pdf))
http.Handle("/png", appHandler(imgPng))
}
func pdf(w http.ResponseWriter, r *http.Request) *appError {
w.Header().Set("Content-type", "application/pdf")
// Initialize the graphic context on an pdf document
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
// Draw sample
android.Draw(gc, 65, 0)
err := dest.Output(w)
if err != nil {
return &appError{err, fmt.Sprintf("Can't write: %s", err), 500}
}
return nil
}
func imgPng(w http.ResponseWriter, r *http.Request) *appError {
w.Header().Set("Content-type", "image/png")
// Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest)
// Draw sample
android.Draw(gc, 65, 0)
err := png.Encode(w, dest)
if err != nil {
return &appError{err, fmt.Sprintf("Can't encode: %s", err), 500}
}
return nil
}

View file

@ -0,0 +1,60 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff, Stani Michiels
// Package frameimage centers a png image and rotates it.
package frameimage
import (
"math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
)
// Main draws the image frame and returns the filename.
// This should only be used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Margin between the image and the frame
const margin = 30
// Line width od the frame
const lineWidth = 3
// Gopher image
gopher := samples.Resource("image", "gopher.png", ext)
// Draw gopher
err := Draw(gc, gopher, 297, 210, margin, lineWidth)
// Return the output filename
return samples.Output("frameimage", ext), err
}
// Draw the image frame with certain parameters.
func Draw(gc draw2d.GraphicContext, png string,
dw, dh, margin, lineWidth float64) error {
// Draw frame
draw2dkit.RoundedRectangle(gc, lineWidth, lineWidth, dw-lineWidth, dh-lineWidth, 100, 100)
gc.SetLineWidth(lineWidth)
gc.FillStroke()
// load the source image
source, err := draw2dimg.LoadFromPngFile(png)
if err != nil {
return err
}
// Size of source image
sw, sh := float64(source.Bounds().Dx()), float64(source.Bounds().Dy())
// Draw image to fit in the frame
// TODO Seems to have a transform bug here on draw image
scale := math.Min((dw-margin*2)/sw, (dh-margin*2)/sh)
gc.Save()
gc.Translate((dw-sw*scale)/2, (dh-sh*scale)/2)
gc.Scale(scale, scale)
gc.Rotate(0.2)
gc.DrawImage(source)
gc.Restore()
return nil
}

View file

@ -0,0 +1,334 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package geometry draws some geometric tests.
package geometry
import (
"image"
"image/color"
"math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
"github.com/llgcode/draw2d/samples/gopher2"
)
// Main draws geometry and returns the filename. This should only be
// used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw the droid
Draw(gc, 297, 210)
// Return the output filename
return samples.Output("geometry", ext), nil
}
// Bubble draws a text balloon.
func Bubble(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/100, height/100
gc.MoveTo(x+sx*50, y)
gc.QuadCurveTo(x, y, x, y+sy*37.5)
gc.QuadCurveTo(x, y+sy*75, x+sx*25, y+sy*75)
gc.QuadCurveTo(x+sx*25, y+sy*95, x+sx*5, y+sy*100)
gc.QuadCurveTo(x+sx*35, y+sy*95, x+sx*40, y+sy*75)
gc.QuadCurveTo(x+sx*100, y+sy*75, x+sx*100, y+sy*37.5)
gc.QuadCurveTo(x+sx*100, y, x+sx*50, y)
gc.Stroke()
}
// CurveRectangle draws a rectangle with bezier curves (not rounded rectangle).
func CurveRectangle(gc draw2d.GraphicContext, x0, y0,
rectWidth, rectHeight float64, stroke, fill color.Color) {
radius := (rectWidth + rectHeight) / 4
x1 := x0 + rectWidth
y1 := y0 + rectHeight
if rectWidth/2 < radius {
if rectHeight/2 < radius {
gc.MoveTo(x0, (y0+y1)/2)
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
} else {
gc.MoveTo(x0, y0+radius)
gc.CubicCurveTo(x0, y0, x0, y0, (x0+x1)/2, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
gc.LineTo(x1, y1-radius)
gc.CubicCurveTo(x1, y1, x1, y1, (x1+x0)/2, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
}
} else {
if rectHeight/2 < radius {
gc.MoveTo(x0, (y0+y1)/2)
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
gc.LineTo(x1-radius, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, (y0+y1)/2)
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
gc.LineTo(x0+radius, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, (y0+y1)/2)
} else {
gc.MoveTo(x0, y0+radius)
gc.CubicCurveTo(x0, y0, x0, y0, x0+radius, y0)
gc.LineTo(x1-radius, y0)
gc.CubicCurveTo(x1, y0, x1, y0, x1, y0+radius)
gc.LineTo(x1, y1-radius)
gc.CubicCurveTo(x1, y1, x1, y1, x1-radius, y1)
gc.LineTo(x0+radius, y1)
gc.CubicCurveTo(x0, y1, x0, y1, x0, y1-radius)
}
}
gc.Close()
gc.SetStrokeColor(stroke)
gc.SetFillColor(fill)
gc.SetLineWidth(10.0)
gc.FillStroke()
}
// Dash draws a line with a dash pattern
func Dash(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/162, height/205
gc.SetStrokeColor(image.Black)
gc.SetLineDash([]float64{height / 10, height / 50, height / 50, height / 50}, -50.0)
gc.SetLineCap(draw2d.ButtCap)
gc.SetLineJoin(draw2d.RoundJoin)
gc.SetLineWidth(height / 50)
gc.MoveTo(x+sx*60.0, y)
gc.LineTo(x+sx*60.0, y)
gc.LineTo(x+sx*162, y+sy*205)
rLineTo(gc, sx*-102.4, 0)
gc.CubicCurveTo(x+sx*-17, y+sy*205, x+sx*-17, y+sy*103, x+sx*60.0, y+sy*103.0)
gc.Stroke()
gc.SetLineDash(nil, 0.0)
}
// Arc draws an arc with a positive angle (clockwise)
func Arc(gc draw2d.GraphicContext, xc, yc, width, height float64) {
// draw an arc
xc += width / 2
yc += height / 2
radiusX, radiusY := width/2, height/2
startAngle := 45 * (math.Pi / 180.0) /* angles are specified */
angle := 135 * (math.Pi / 180.0) /* clockwise in radians */
gc.SetLineWidth(width / 10)
gc.SetLineCap(draw2d.ButtCap)
gc.SetStrokeColor(image.Black)
gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
gc.Stroke()
// fill a circle
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
gc.SetFillColor(color.NRGBA{255, 0x33, 0x33, 0x80})
gc.SetLineWidth(width / 20)
gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
gc.LineTo(xc, yc)
gc.LineTo(xc-radiusX, yc)
gc.Stroke()
gc.MoveTo(xc, yc)
gc.ArcTo(xc, yc, width/10.0, height/10.0, 0, 2*math.Pi)
gc.Fill()
}
// ArcNegative draws an arc with a negative angle (anti clockwise).
func ArcNegative(gc draw2d.GraphicContext, xc, yc, width, height float64) {
xc += width / 2
yc += height / 2
radiusX, radiusY := width/2, height/2
startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
angle := -225 * (math.Pi / 180.0) /* clockwise in radians */
gc.SetLineWidth(width / 10)
gc.SetLineCap(draw2d.ButtCap)
gc.SetStrokeColor(image.Black)
gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
gc.Stroke()
// fill a circle
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
gc.SetFillColor(color.NRGBA{255, 0x33, 0x33, 0x80})
gc.SetLineWidth(width / 20)
gc.MoveTo(xc+math.Cos(startAngle)*radiusX, yc+math.Sin(startAngle)*radiusY)
gc.LineTo(xc, yc)
gc.LineTo(xc-radiusX, yc)
gc.Stroke()
gc.ArcTo(xc, yc, width/10.0, height/10.0, 0, 2*math.Pi)
gc.Fill()
}
// CubicCurve draws a cubic curve with its control points.
func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/162, height/205
x0, y0 := x, y+sy*100.0
x1, y1 := x+sx*75, y+sy*205
x2, y2 := x+sx*125, y
x3, y3 := x+sx*205, y+sy*100
gc.SetStrokeColor(image.Black)
gc.SetFillColor(color.NRGBA{0xAA, 0xAA, 0xAA, 0xFF})
gc.SetLineWidth(width / 10)
gc.MoveTo(x0, y0)
gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
gc.Stroke()
gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0x88})
gc.SetLineWidth(width / 20)
// draw segment of curve
gc.MoveTo(x0, y0)
gc.LineTo(x1, y1)
gc.LineTo(x2, y2)
gc.LineTo(x3, y3)
gc.Stroke()
}
// FillString draws a filled and stroked string.
func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/100, height/100
gc.Save()
gc.SetStrokeColor(image.Black)
gc.SetLineWidth(1)
draw2dkit.RoundedRectangle(gc, x+sx*5, y+sy*5, x+sx*95, y+sy*95, sx*10, sy*10)
gc.FillStroke()
gc.SetFillColor(image.Black)
gc.SetFontSize(height / 6)
gc.Translate(x+sx*6, y+sy*52)
gc.SetFontData(draw2d.FontData{
Name: "luxi",
Family: draw2d.FontFamilyMono,
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
w := gc.FillString("Hug")
gc.Translate(w+sx, 0)
left, top, right, bottom := gc.GetStringBounds("cou")
gc.SetStrokeColor(color.NRGBA{255, 0x33, 0x33, 0x80})
draw2dkit.Rectangle(gc, left, top, right, bottom)
gc.SetLineWidth(height / 50)
gc.Stroke()
gc.SetFillColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
gc.SetLineWidth(height / 100)
gc.StrokeString("Hug")
gc.Restore()
}
// FillStroke first fills and afterwards strokes a path.
func FillStroke(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/210, height/215
gc.MoveTo(x+sx*113.0, y)
gc.LineTo(x+sx*215.0, y+sy*215)
rLineTo(gc, sx*-100, 0)
gc.CubicCurveTo(x+sx*35, y+sy*215, x+sx*35, y+sy*113, x+sx*113.0, y+sy*113)
gc.Close()
gc.MoveTo(x+sx*50.0, y)
rLineTo(gc, sx*51.2, sy*51.2)
rLineTo(gc, sx*-51.2, sy*51.2)
rLineTo(gc, sx*-51.2, sy*-51.2)
gc.Close()
gc.SetLineWidth(width / 20.0)
gc.SetFillColor(color.NRGBA{0, 0, 0xFF, 0xFF})
gc.SetStrokeColor(image.Black)
gc.FillStroke()
}
func rLineTo(path draw2d.PathBuilder, x, y float64) {
x0, y0 := path.LastPoint()
path.LineTo(x0+x, y0+y)
}
// FillStyle demonstrates the difference between even odd and non zero winding rule.
func FillStyle(gc draw2d.GraphicContext, x, y, width, height float64) {
sx, sy := width/232, height/220
gc.SetLineWidth(width / 40)
draw2dkit.Rectangle(gc, x+sx*0, y+sy*12, x+sx*232, y+sy*70)
var wheel1, wheel2 draw2d.Path
wheel1.ArcTo(x+sx*52, y+sy*70, sx*40, sy*40, 0, 2*math.Pi)
wheel2.ArcTo(x+sx*180, y+sy*70, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleEvenOdd)
gc.SetFillColor(color.NRGBA{0, 0xB2, 0, 0xFF})
gc.SetStrokeColor(image.Black)
gc.FillStroke(&wheel1, &wheel2)
draw2dkit.Rectangle(gc, x, y+sy*140, x+sx*232, y+sy*198)
wheel1.Clear()
wheel1.ArcTo(x+sx*52, y+sy*198, sx*40, sy*40, 0, 2*math.Pi)
wheel2.Clear()
wheel2.ArcTo(x+sx*180, y+sy*198, sx*40, sy*40, 0, -2*math.Pi)
gc.SetFillRule(draw2d.FillRuleWinding)
gc.SetFillColor(color.NRGBA{0, 0, 0xE5, 0xFF})
gc.FillStroke(&wheel1, &wheel2)
}
// PathTransform scales a path differently in horizontal and vertical direction.
func PathTransform(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.Save()
gc.SetLineWidth(width / 10)
gc.Translate(x+width/2, y+height/2)
gc.Scale(1, 4)
gc.ArcTo(0, 0, width/8, height/8, 0, math.Pi*2)
gc.Close()
gc.Stroke()
gc.Restore()
}
// Star draws many lines from a center.
func Star(gc draw2d.GraphicContext, x, y, width, height float64) {
gc.Save()
gc.Translate(x+width/2, y+height/2)
gc.SetLineWidth(width / 40)
for i := 0.0; i < 360; i = i + 10 { // Go from 0 to 360 degrees in 10 degree steps
gc.Save() // Keep rotations temporary
gc.Rotate(i * (math.Pi / 180.0)) // Rotate by degrees on stack from 'for'
gc.MoveTo(0, 0)
gc.LineTo(width/2, 0)
gc.Stroke()
gc.Restore()
}
gc.Restore()
}
// Draw all figures in a nice 4x3 grid.
func Draw(gc draw2d.GraphicContext, width, height float64) {
mx, my := width*0.025, height*0.025 // margin
dx, dy := (width-2*mx)/4, (height-2*my)/3
w, h := dx-2*mx, dy-2*my
x0, y := 2*mx, 2*my
x := x0
Bubble(gc, x, y, w, h)
x += dx
CurveRectangle(gc, x, y, w, h, color.NRGBA{0x80, 0, 0, 0x80}, color.NRGBA{0x80, 0x80, 0xFF, 0xFF})
x += dx
Dash(gc, x, y, w, h)
x += dx
Arc(gc, x, y, w, h)
x = x0
y += dy
ArcNegative(gc, x, y, w, h)
x += dx
CubicCurve(gc, x, y, w, h)
x += dx
FillString(gc, x, y, w, h)
x += dx
FillStroke(gc, x, y, w, h)
x = x0
y += dy
FillStyle(gc, x, y, w, h)
x += dx
PathTransform(gc, x, y, w, h)
x += dx
Star(gc, x, y, w, h)
x += dx
gopher2.Draw(gc, x, y, w, h/2)
}

73
samples/gopher/gopher.go Normal file
View file

@ -0,0 +1,73 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package gopher draws a gopher avatar based on a svg of:
// https://github.com/golang-samples/gopher-vector/
package gopher
import (
"image/color"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/samples"
)
// Main draws a left hand and ear of a gopher. Afterwards it returns
// the filename. This should only be used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
gc.Save()
gc.Scale(0.5, 0.5)
// Draw a (partial) gopher
Draw(gc)
gc.Restore()
// Return the output filename
return samples.Output("gopher", ext), nil
}
// Draw a left hand and ear of a gopher using a gc thanks to
// https://github.com/golang-samples/gopher-vector/
func Draw(gc draw2d.GraphicContext) {
// Initialize Stroke Attribute
gc.SetLineWidth(3)
gc.SetLineCap(draw2d.RoundCap)
gc.SetStrokeColor(color.Black)
// Left hand
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#F6D2A2" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M10.634,300.493c0.764,15.751,16.499,8.463,23.626,3.539c6.765-4.675,8.743-0.789,9.337-10.015
// c0.389-6.064,1.088-12.128,0.744-18.216c-10.23-0.927-21.357,1.509-29.744,7.602C10.277,286.542,2.177,296.561,10.634,300.493"/>
gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff})
gc.MoveTo(10.634, 300.493)
rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539)
rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015)
rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216)
rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602)
gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493)
gc.FillStroke()
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#C6B198" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M10.634,300.493c2.29-0.852,4.717-1.457,6.271-3.528"/>
gc.MoveTo(10.634, 300.493)
rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528)
gc.Stroke()
// Left Ear
// <path fill-rule="evenodd" clip-rule="evenodd" fill="#6AD7E5" stroke="#000000" stroke-width="3" stroke-linecap="round" d="
// M46.997,112.853C-13.3,95.897,31.536,19.189,79.956,50.74L46.997,112.853z"/>
gc.MoveTo(46.997, 112.853)
gc.CubicCurveTo(-13.3, 95.897, 31.536, 19.189, 79.956, 50.74)
gc.LineTo(46.997, 112.853)
gc.Close()
gc.Stroke()
}
func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) {
x, y := path.LastPoint()
path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
}
func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) {
x, y := path.LastPoint()
path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
}

122
samples/gopher2/gopher2.go Normal file
View file

@ -0,0 +1,122 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package gopher2 draws a gopher avatar based on a svg of:
// https://github.com/golang-samples/gopher-vector/
package gopher2
import (
"image"
"image/color"
"math"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
)
// Main draws a rotated face of the gopher. Afterwards it returns
// the filename. This should only be used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
gc.SetStrokeColor(image.Black)
gc.SetFillColor(image.White)
gc.Save()
// Draw a (partial) gopher
gc.Translate(-60, 65)
gc.Rotate(-30 * (math.Pi / 180.0))
Draw(gc, 48, 48, 240, 72)
gc.Restore()
// Return the output filename
return samples.Output("gopher2", ext), nil
}
// Draw a gopher head (not rotated)
func Draw(gc draw2d.GraphicContext, x, y, w, h float64) {
h23 := (h * 2) / 3
blf := color.RGBA{0, 0, 0, 0xff} // black
wf := color.RGBA{0xff, 0xff, 0xff, 0xff} // white
nf := color.RGBA{0x8B, 0x45, 0x13, 0xff} // brown opaque
brf := color.RGBA{0x8B, 0x45, 0x13, 0x99} // brown transparant
brb := color.RGBA{0x8B, 0x45, 0x13, 0xBB} // brown transparant
// round head top
gc.MoveTo(x, y+h*1.002)
gc.CubicCurveTo(x+w/4, y-h/3, x+3*w/4, y-h/3, x+w, y+h*1.002)
gc.Close()
gc.SetFillColor(brb)
gc.Fill()
// rectangle head bottom
draw2dkit.RoundedRectangle(gc, x, y+h, x+w, y+h+h, h/5, h/5)
gc.Fill()
// left ear outside
draw2dkit.Circle(gc, x, y+h, w/12)
gc.SetFillColor(brf)
gc.Fill()
// left ear inside
draw2dkit.Circle(gc, x, y+h, 0.5*w/12)
gc.SetFillColor(nf)
gc.Fill()
// right ear outside
draw2dkit.Circle(gc, x+w, y+h, w/12)
gc.SetFillColor(brf)
gc.Fill()
// right ear inside
draw2dkit.Circle(gc, x+w, y+h, 0.5*w/12)
gc.SetFillColor(nf)
gc.Fill()
// left eye outside white
draw2dkit.Circle(gc, x+w/3, y+h23, w/9)
gc.SetFillColor(wf)
gc.Fill()
// left eye black
draw2dkit.Circle(gc, x+w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf)
gc.Fill()
// left eye inside white
draw2dkit.Circle(gc, x+w/3+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf)
gc.Fill()
// right eye outside white
draw2dkit.Circle(gc, x+w-w/3, y+h23, w/9)
gc.Fill()
// right eye black
draw2dkit.Circle(gc, x+w-w/3+w/24, y+h23, 0.5*w/9)
gc.SetFillColor(blf)
gc.Fill()
// right eye inside white
draw2dkit.Circle(gc, x+w-(w/3)+w/24+w/48, y+h23, 0.2*w/9)
gc.SetFillColor(wf)
gc.Fill()
// left tooth
gc.SetFillColor(wf)
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)
gc.Fill()
// right tooth
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)
gc.Fill()
// snout
draw2dkit.Ellipse(gc, x+(w/2), y+h+h/2.5, w/6, w/12)
gc.SetFillColor(nf)
gc.Fill()
// nose
draw2dkit.Ellipse(gc, x+(w/2), y+h+h/7, w/10, w/12)
gc.SetFillColor(blf)
gc.Fill()
}

View file

@ -0,0 +1,40 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff, Stani Michiels
// Package helloworld displays multiple "Hello World" (one rotated)
// in a rounded rectangle.
package helloworld
import (
"fmt"
"image"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
)
// Main draws "Hello World" and returns the filename. This should only be
// used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw hello world
Draw(gc, fmt.Sprintf("Hello World %d dpi", gc.GetDPI()))
// Return the output filename
return samples.Output("helloworld", ext), nil
}
// Draw "Hello World"
func Draw(gc draw2d.GraphicContext, text string) {
// Draw a rounded rectangle using default colors
draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
gc.FillStroke()
// Set the font luximbi.ttf
gc.SetFontData(draw2d.FontData{"luxi", draw2d.FontFamilyMono, draw2d.FontStyleBold | draw2d.FontStyleItalic})
// Set the fill text color to black
gc.SetFillColor(image.Black)
gc.SetFontSize(14)
// Display Hello World
gc.FillStringAt("Hello World", 8, 52)
}

View file

@ -0,0 +1,119 @@
// Open an OpenGl window and display a rectangle using a OpenGl GraphicContext
package main
import (
"image/color"
"log"
"runtime"
"github.com/go-gl/gl/v2.1/gl"
"github.com/go-gl/glfw/v3.1/glfw"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dgl"
"github.com/llgcode/draw2d/draw2dkit"
)
var (
// global rotation
rotate int
width, height int
redraw = true
font draw2d.FontData
)
func reshape(window *glfw.Window, w, h int) {
gl.ClearColor(1, 1, 1, 1)
/* Establish viewing area to cover entire window. */
gl.Viewport(0, 0, int32(w), int32(h))
/* PROJECTION Matrix mode. */
gl.MatrixMode(gl.PROJECTION)
/* Reset project matrix. */
gl.LoadIdentity()
/* Map abstract coords directly to window coords. */
gl.Ortho(0, float64(w), 0, float64(h), -1, 1)
/* Invert Y axis so increasing Y goes down. */
gl.Scalef(1, -1, 1)
/* Shift origin up to upper-left corner. */
gl.Translatef(0, float32(-h), 0)
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Disable(gl.DEPTH_TEST)
width, height = w, h
redraw = true
}
// Ask to refresh
func invalidate() {
redraw = true
}
func display() {
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.LineWidth(1)
gc := draw2dgl.NewGraphicContext(width, height)
gc.SetFontData(draw2d.FontData{
Name: "luxi",
Family: draw2d.FontFamilyMono,
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
gc.BeginPath()
draw2dkit.RoundedRectangle(gc, 200, 200, 600, 600, 100, 100)
gc.SetFillColor(color.RGBA{0, 0, 0, 0xff})
gc.Fill()
gl.Flush() /* Single buffered, so needs a flush. */
}
func init() {
runtime.LockOSThread()
}
func main() {
err := glfw.Init()
if err != nil {
panic(err)
}
defer glfw.Terminate()
width, height = 800, 800
window, err := glfw.CreateWindow(width, height, "Show RoundedRect", nil, nil)
if err != nil {
panic(err)
}
window.MakeContextCurrent()
window.SetSizeCallback(reshape)
window.SetKeyCallback(onKey)
window.SetCharCallback(onChar)
glfw.SwapInterval(1)
err = gl.Init()
if err != nil {
panic(err)
}
reshape(window, width, height)
for !window.ShouldClose() {
if redraw {
display()
window.SwapBuffers()
redraw = false
}
glfw.PollEvents()
// time.Sleep(2 * time.Second)
}
}
func onChar(w *glfw.Window, char rune) {
log.Println(char)
}
func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
switch {
case key == glfw.KeyEscape && action == glfw.Press,
key == glfw.KeyQ && action == glfw.Press:
w.SetShouldClose(true)
}
}

39
samples/line/line.go Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff, Stani Michiels
// Package line draws vertically spaced lines.
package line
import (
"image/color"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dkit"
"github.com/llgcode/draw2d/samples"
)
// Main draws vertically spaced lines and returns the filename.
// This should only be used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
gc.SetFillRule(draw2d.FillRuleWinding)
gc.Clear()
// Draw the line
for x := 5.0; x < 297; x += 10 {
Draw(gc, x, 0, x, 210)
}
gc.ClearRect(100, 75, 197, 135)
draw2dkit.Ellipse(gc, 148.5, 105, 35, 25)
gc.SetFillColor(color.RGBA{0xff, 0xff, 0x44, 0xff})
gc.FillStroke()
// Return the output filename
return samples.Output("line", ext), nil
}
// Draw vertically spaced lines
func Draw(gc draw2d.GraphicContext, x0, y0, x1, y1 float64) {
// Draw a line
gc.MoveTo(x0, y0)
gc.LineTo(x1, y1)
gc.Stroke()
}

View file

@ -0,0 +1,52 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 21/11/2010 by Laurent Le Goff
// Package linecapjoin demonstrates the different line caps and joins.
package linecapjoin
import (
"image/color"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/samples"
)
// Main draws the different line caps and joins.
// This should only be used during testing.
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
// Draw the line
const offset = 75.0
x := 35.0
caps := []draw2d.LineCap{draw2d.ButtCap, draw2d.SquareCap, draw2d.RoundCap}
joins := []draw2d.LineJoin{draw2d.BevelJoin, draw2d.MiterJoin, draw2d.RoundJoin}
for i := range caps {
Draw(gc, caps[i], joins[i], x, 50, x, 160, offset)
x += offset
}
// Return the output filename
return samples.Output("linecapjoin", ext), nil
}
// Draw a line with an angle with specified line cap and join
func Draw(gc draw2d.GraphicContext, cap draw2d.LineCap, join draw2d.LineJoin,
x0, y0, x1, y1, offset float64) {
gc.SetLineCap(cap)
gc.SetLineJoin(join)
// Draw thick line
gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0x33, 0xFF})
gc.SetLineWidth(30.0)
gc.MoveTo(x0, y0)
gc.LineTo((x0+x1)/2+offset, (y0+y1)/2)
gc.LineTo(x1, y1)
gc.Stroke()
// Draw thin helping line
gc.SetStrokeColor(color.NRGBA{0xFF, 0x33, 0x33, 0xFF})
gc.SetLineWidth(2.56)
gc.MoveTo(x0, y0)
gc.LineTo((x0+x1)/2+offset, (y0+y1)/2)
gc.LineTo(x1, y1)
gc.Stroke()
}

View file

@ -0,0 +1,49 @@
// Package postscript reads the tiger.ps file and draws it to a backend.
package postscript
import (
"io/ioutil"
"os"
"strings"
"github.com/llgcode/ps"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/samples"
)
// Main draws the tiger
func Main(gc draw2d.GraphicContext, ext string) (string, error) {
gc.Save()
// flip the image
gc.Translate(0, 200)
gc.Scale(0.35, -0.35)
gc.Translate(70, -200)
// Tiger postscript drawing
tiger := samples.Resource("image", "tiger.ps", ext)
// Draw tiger
Draw(gc, tiger)
gc.Restore()
// Return the output filename
return samples.Output("postscript", ext), nil
}
// Draw a tiger
func Draw(gc draw2d.GraphicContext, filename string) {
// Open the postscript
src, err := os.OpenFile(filename, 0, 0)
if err != nil {
panic(err)
}
defer src.Close()
bytes, err := ioutil.ReadAll(src)
reader := strings.NewReader(string(bytes))
// Initialize and interpret the postscript
interpreter := ps.NewInterpreter(gc)
interpreter.Execute(reader)
}

View file

@ -0,0 +1,114 @@
// Open a OpenGL window and display a tiger interpreting a postscript file
package main
import (
"io/ioutil"
"log"
"math"
"os"
"runtime"
"strings"
"time"
"github.com/go-gl/gl/v2.1/gl"
"github.com/go-gl/glfw/v3.1/glfw"
"github.com/llgcode/draw2d/draw2dgl"
"github.com/llgcode/ps"
)
var postscriptContent string
var (
width, height int
rotate int
window *glfw.Window
)
func reshape(window *glfw.Window, w, h int) {
gl.ClearColor(1, 1, 1, 1)
//fmt.Println(gl.GetString(gl.EXTENSIONS))
gl.Viewport(0, 0, int32(w), int32(h)) /* Establish viewing area to cover entire window. */
gl.MatrixMode(gl.PROJECTION) /* Start modifying the projection matrix. */
gl.LoadIdentity() /* Reset project matrix. */
gl.Ortho(0, float64(w), 0, float64(h), -1, 1) /* Map abstract coords directly to window coords. */
gl.Scalef(1, -1, 1) /* Invert Y axis so increasing Y goes down. */
gl.Translatef(0, float32(-h), 0) /* Shift origin up to upper-left corner. */
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Disable(gl.DEPTH_TEST)
width, height = w, h
}
func display() {
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
lastTime := time.Now()
gl.LineWidth(1)
gc := draw2dgl.NewGraphicContext(width, height)
gc.Translate(380, 400)
gc.Scale(1, -1)
rotate = (rotate + 1) % 360
gc.Rotate(float64(rotate) * math.Pi / 180)
gc.Translate(-380, -400)
interpreter := ps.NewInterpreter(gc)
reader := strings.NewReader(postscriptContent)
interpreter.Execute(reader)
dt := time.Now().Sub(lastTime)
log.Printf("Redraw in : %f ms\n", float64(dt)*1e-6)
gl.Flush() /* Single buffered, so needs a flush. */
}
func main() {
src, err := os.OpenFile("tiger.ps", 0, 0)
if err != nil {
log.Println("can't find postscript file.")
return
}
defer src.Close()
bytes, err := ioutil.ReadAll(src)
postscriptContent = string(bytes)
err = glfw.Init()
if err != nil {
panic(err)
}
defer glfw.Terminate()
window, err = glfw.CreateWindow(800, 800, "Show Tiger in OpenGL", nil, nil)
if err != nil {
panic(err)
}
window.MakeContextCurrent()
window.SetSizeCallback(reshape)
window.SetKeyCallback(onKey)
glfw.SwapInterval(1)
err = gl.Init()
if err != nil {
panic(err)
}
reshape(window, 800, 800)
for !window.ShouldClose() {
display()
window.SwapBuffers()
glfw.PollEvents()
// time.Sleep(2 * time.Second)
}
}
func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
switch {
case key == glfw.KeyEscape && action == glfw.Press,
key == glfw.KeyQ && action == glfw.Press:
w.SetShouldClose(true)
}
}
func init() {
runtime.LockOSThread()
}

File diff suppressed because it is too large Load diff

24
samples/samples.go Normal file
View file

@ -0,0 +1,24 @@
// Package samples provides examples which can be used with different
// backends. They are also used for testing and coverage of the
// draw2d package.
package samples
import "fmt"
// Resource returns a resource filename for testing.
func Resource(folder, filename, ext string) string {
var root string
if ext == "pdf" {
root = "../"
}
return fmt.Sprintf("%sresource/%s/%s", root, folder, filename)
}
// Output returns the output filename for testing.
func Output(name, ext string) string {
var root string
if ext == "pdf" {
root = "../"
}
return fmt.Sprintf("%soutput/samples/%s.%s", root, name, ext)
}

View file

@ -6,27 +6,40 @@ import (
"testing" "testing"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d.samples" "github.com/llgcode/draw2d/samples/android"
"github.com/llgcode/draw2d.samples/android" "github.com/llgcode/draw2d/samples/frameimage"
"github.com/llgcode/draw2d.samples/frameimage" "github.com/llgcode/draw2d/samples/geometry"
"github.com/llgcode/draw2d.samples/gopher" "github.com/llgcode/draw2d/samples/gopher"
"github.com/llgcode/draw2d.samples/helloworld" "github.com/llgcode/draw2d/samples/gopher2"
"github.com/llgcode/draw2d.samples/line" "github.com/llgcode/draw2d/samples/helloworld"
"github.com/llgcode/draw2d.samples/linecapjoin" "github.com/llgcode/draw2d/samples/line"
"github.com/llgcode/draw2d.samples/postscript" "github.com/llgcode/draw2d/samples/linecapjoin"
"github.com/llgcode/draw2d/samples/postscript"
) )
func TestSampleAndroid(t *testing.T) { func TestSampleAndroid(t *testing.T) {
test(t, android.Main) test(t, android.Main)
} }
func TestSampleGeometry(t *testing.T) {
// Set the global folder for searching fonts
// The pdf backend needs for every ttf file its corresponding
// json/.z file which is generated by gofpdf/makefont.
draw2d.SetFontFolder("resource/font")
test(t, geometry.Main)
}
func TestSampleGopher(t *testing.T) { func TestSampleGopher(t *testing.T) {
test(t, gopher.Main) test(t, gopher.Main)
} }
func TestSampleGopher2(t *testing.T) {
test(t, gopher2.Main)
}
func TestSampleHelloWorld(t *testing.T) { func TestSampleHelloWorld(t *testing.T) {
// Set the global folder for searching fonts // Set the global folder for searching fonts
draw2d.SetFontFolder(samples.Dir("helloworld", "")) draw2d.SetFontFolder("resource/font")
test(t, helloworld.Main) test(t, helloworld.Main)
} }
@ -38,7 +51,7 @@ func TestSampleLine(t *testing.T) {
test(t, line.Main) test(t, line.Main)
} }
func TestSampleLineCap(t *testing.T) { func TestSampleLineCapJoin(t *testing.T) {
test(t, linecapjoin.Main) test(t, linecapjoin.Main)
} }

8
test Normal file
View file

@ -0,0 +1,8 @@
echo golint
golint ./... | grep "draw2dpdf\|samples\|^advanced_path\|^arc\|draw2d[.]\|fileutil\|^gc\|math\|^path[.]\|rgba_interpolation\|test\|vertex2d"
echo
echo go vet
go vet ./...
echo
echo go test
go test -cover ./... | grep -v "no test"

View file

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/llgcode/draw2d" "github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
) )
type sample func(gc draw2d.GraphicContext, ext string) (string, error) type sample func(gc draw2d.GraphicContext, ext string) (string, error)
@ -14,16 +15,16 @@ type sample func(gc draw2d.GraphicContext, ext string) (string, error)
func test(t *testing.T, draw sample) { func test(t *testing.T, draw sample) {
// Initialize the graphic context on an RGBA image // Initialize the graphic context on an RGBA image
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0)) dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2d.NewGraphicContext(dest) gc := draw2dimg.NewGraphicContext(dest)
// Draw Android logo // Draw Android logo
fn, err := draw(gc, "png") output, err := draw(gc, "png")
if err != nil { if err != nil {
t.Errorf("Drawing %q failed: %v", fn, err) t.Errorf("Drawing %q failed: %v", output, err)
return return
} }
// Save to png // Save to png
err = draw2d.SaveToPngFile(fn, dest) err = draw2dimg.SaveToPngFile(output, dest)
if err != nil { if err != nil {
t.Errorf("Saving %q failed: %v", fn, err) t.Errorf("Saving %q failed: %v", output, err)
} }
} }