commit
7a817d6c88
19 changed files with 672 additions and 53 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,6 +9,8 @@
|
||||||
**/*.exe
|
**/*.exe
|
||||||
**/*~
|
**/*~
|
||||||
**/*.orig
|
**/*.orig
|
||||||
|
**/*.out
|
||||||
|
**/*.test
|
||||||
core
|
core
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
@ -17,3 +19,4 @@ _test*
|
||||||
|
|
||||||
**/*.dll
|
**/*.dll
|
||||||
**/core*[0-9]
|
**/core*[0-9]
|
||||||
|
.private
|
||||||
|
|
|
@ -32,11 +32,13 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Ellipse(path Path, cx, cy, rx, ry float64) {
|
func Ellipse(path Path, cx, cy, rx, ry float64) {
|
||||||
|
path.MoveTo(cx-rx, cy)
|
||||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Circle(path Path, cx, cy, radius float64) {
|
func Circle(path Path, cx, cy, radius float64) {
|
||||||
|
path.MoveTo(cx-radius, cy)
|
||||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
5
draw2dpdf/doc.go
Normal file
5
draw2dpdf/doc.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// 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 form on pdf file.
|
||||||
|
package draw2dpdf
|
8
draw2dpdf/fileutil.go
Normal file
8
draw2dpdf/fileutil.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
import "github.com/jung-kurt/gofpdf"
|
||||||
|
|
||||||
|
// SaveToPdfFile creates and saves a pdf document to a file
|
||||||
|
func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error {
|
||||||
|
return pdf.OutputFileAndClose(filePath)
|
||||||
|
}
|
326
draw2dpdf/graphiccontext.go
Normal file
326
draw2dpdf/graphiccontext.go
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
// 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/llgcode/draw2d"
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
c255 = 255.0 / 65535.0
|
||||||
|
// DPI of a pdf document is fixed at 72.
|
||||||
|
DPI = 72
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
caps = map[draw2d.Cap]string{
|
||||||
|
draw2d.RoundCap: "round",
|
||||||
|
draw2d.ButtCap: "butt",
|
||||||
|
draw2d.SquareCap: "square"}
|
||||||
|
joins = map[draw2d.Join]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)
|
||||||
|
draw2d.Rect(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 {
|
||||||
|
*draw2d.StackGraphicContext
|
||||||
|
pdf *gofpdf.Fpdf
|
||||||
|
DPI int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGraphicContext creates a new pdf GraphicContext
|
||||||
|
func NewGraphicContext(pdf *gofpdf.Fpdf) *GraphicContext {
|
||||||
|
gc := &GraphicContext{draw2d.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))
|
||||||
|
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
|
||||||
|
func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) {
|
||||||
|
gc.draw("D", paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill strokes the paths
|
||||||
|
func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) {
|
||||||
|
gc.draw("F", paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillStroke first fills the paths and than strokes them
|
||||||
|
func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) {
|
||||||
|
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.PathStorage) {
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
pathConverter := NewPathConverter(gc.pdf)
|
||||||
|
// pathConverter := NewPathConverter(NewPathLogger(logger, gc.pdf))
|
||||||
|
pathConverter.Convert(paths...)
|
||||||
|
if gc.Current.FillRule.UseNonZeroWinding() {
|
||||||
|
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.Cap) {
|
||||||
|
gc.StackGraphicContext.SetLineCap(Cap)
|
||||||
|
gc.pdf.SetLineCapStyle(caps[Cap])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLineJoin sets the line cap (round, bevel or miter)
|
||||||
|
func (gc *GraphicContext) SetLineJoin(Join draw2d.Join) {
|
||||||
|
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
|
||||||
|
}
|
35
draw2dpdf/graphiccontext_test.go
Normal file
35
draw2dpdf/graphiccontext_test.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleGraphicContext() {
|
||||||
|
// Initialize the graphic context on a pdf document
|
||||||
|
pdf := gofpdf.New("P", "mm", "A4", "../font")
|
||||||
|
pdf.AddPage()
|
||||||
|
gc := NewGraphicContext(pdf)
|
||||||
|
|
||||||
|
// some properties
|
||||||
|
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
|
||||||
|
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
|
||||||
|
gc.SetLineCap(draw2d.RoundCap)
|
||||||
|
gc.SetLineWidth(5)
|
||||||
|
|
||||||
|
// draw something
|
||||||
|
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()
|
||||||
|
fmt.Println(gc.LastPoint())
|
||||||
|
|
||||||
|
// draw2dpdf.SaveToPdfFile("example.pdf", pdf)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 10 10
|
||||||
|
}
|
60
draw2dpdf/path_converter.go
Normal file
60
draw2dpdf/path_converter.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// PathConverter converts the paths to the pdf api
|
||||||
|
type PathConverter struct {
|
||||||
|
pdf Vectorizer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPathConverter constructs a PathConverter from a pdf vectorizer
|
||||||
|
func NewPathConverter(pdf Vectorizer) *PathConverter {
|
||||||
|
return &PathConverter{pdf: pdf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert converts the paths to the pdf api
|
||||||
|
func (c *PathConverter) Convert(paths ...*draw2d.PathStorage) {
|
||||||
|
for _, path := range paths {
|
||||||
|
j := 0
|
||||||
|
for _, cmd := range path.Commands {
|
||||||
|
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertCommand converts a single path segment to the pdf api
|
||||||
|
func (c *PathConverter) ConvertCommand(cmd draw2d.PathCmd, vertices ...float64) int {
|
||||||
|
switch cmd {
|
||||||
|
case draw2d.MoveTo:
|
||||||
|
c.pdf.MoveTo(vertices[0], vertices[1])
|
||||||
|
return 2
|
||||||
|
case draw2d.LineTo:
|
||||||
|
c.pdf.LineTo(vertices[0], vertices[1])
|
||||||
|
return 2
|
||||||
|
case draw2d.QuadCurveTo:
|
||||||
|
c.pdf.CurveTo(vertices[0], vertices[1], vertices[2], vertices[3])
|
||||||
|
return 4
|
||||||
|
case draw2d.CubicCurveTo:
|
||||||
|
c.pdf.CurveBezierCubicTo(vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5])
|
||||||
|
return 6
|
||||||
|
case draw2d.ArcTo:
|
||||||
|
c.pdf.ArcTo(vertices[0], vertices[1], vertices[2], vertices[3],
|
||||||
|
0, // degRotate
|
||||||
|
vertices[4]*deg, // degStart = startAngle
|
||||||
|
(vertices[4]-vertices[5])*deg) // degEnd = startAngle-angle
|
||||||
|
return 6
|
||||||
|
case draw2d.Close:
|
||||||
|
c.pdf.ClosePath()
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
49
draw2dpdf/samples_test.go
Normal file
49
draw2dpdf/samples_test.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// See also test_test.go
|
||||||
|
|
||||||
|
package draw2dpdf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d.samples"
|
||||||
|
"github.com/llgcode/draw2d.samples/android"
|
||||||
|
"github.com/llgcode/draw2d.samples/frameimage"
|
||||||
|
"github.com/llgcode/draw2d.samples/gopher"
|
||||||
|
"github.com/llgcode/draw2d.samples/helloworld"
|
||||||
|
"github.com/llgcode/draw2d.samples/line"
|
||||||
|
"github.com/llgcode/draw2d.samples/linecapjoin"
|
||||||
|
"github.com/llgcode/draw2d.samples/postscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
test(t, android.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher(t *testing.T) {
|
||||||
|
test(t, gopher.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleHelloWorld(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
// The pdf backend needs for every ttf file its corresponding json
|
||||||
|
// file which is generated by gofpdf/makefont.
|
||||||
|
draw2d.SetFontFolder(samples.Dir("helloworld", "../"))
|
||||||
|
test(t, helloworld.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleFrameImage(t *testing.T) {
|
||||||
|
test(t, frameimage.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLine(t *testing.T) {
|
||||||
|
test(t, line.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLineCap(t *testing.T) {
|
||||||
|
test(t, linecapjoin.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplePostscript(t *testing.T) {
|
||||||
|
test(t, postscript.Main)
|
||||||
|
}
|
33
draw2dpdf/test_test.go
Normal file
33
draw2dpdf/test_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func test(t *testing.T, sample draw2d.Sample) {
|
||||||
|
// Initialize the graphic context on an pdf document
|
||||||
|
dest := draw2dpdf.NewPdf("L", "mm", "A4")
|
||||||
|
gc := draw2dpdf.NewGraphicContext(dest)
|
||||||
|
// Draw sample
|
||||||
|
fn, err := sample(gc, "pdf")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Drawing %q failed: %v", fn, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Save to pdf only if it doesn't exist because of git
|
||||||
|
if _, err = os.Stat(fn); err == nil {
|
||||||
|
t.Skipf("Saving %q skipped, as it exists already. (Git would consider it modified.)", fn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = draw2dpdf.SaveToPdfFile(fn, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Saving %q failed: %v", fn, err)
|
||||||
|
}
|
||||||
|
}
|
22
draw2dpdf/vectorizer.go
Normal file
22
draw2dpdf/vectorizer.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2015 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 26/06/2015 by Stani Michiels
|
||||||
|
|
||||||
|
package draw2dpdf
|
||||||
|
|
||||||
|
// Vectorizer defines the minimal interface for gofpdf.Fpdf
|
||||||
|
// to be passed to a PathConvertor.
|
||||||
|
// It is also implemented by for example VertexMatrixTransform
|
||||||
|
type Vectorizer interface {
|
||||||
|
// MoveTo creates a new subpath that start at the specified point
|
||||||
|
MoveTo(x, y float64)
|
||||||
|
// LineTo adds a line to the current subpath
|
||||||
|
LineTo(x, y float64)
|
||||||
|
// CurveTo adds a quadratic bezier curve to the current subpath
|
||||||
|
CurveTo(cx, cy, x, y float64)
|
||||||
|
// CurveTo adds a cubic bezier curve to the current subpath
|
||||||
|
CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y float64)
|
||||||
|
// ArcTo adds an arc to the current subpath
|
||||||
|
ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
|
||||||
|
// ClosePath closes the subpath
|
||||||
|
ClosePath()
|
||||||
|
}
|
6
font.go
6
font.go
|
@ -38,7 +38,7 @@ type FontData struct {
|
||||||
Style FontStyle
|
Style FontStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
func fontFileName(fontData FontData) string {
|
func FontFileName(fontData FontData) string {
|
||||||
fontFileName := fontData.Name
|
fontFileName := fontData.Name
|
||||||
switch fontData.Family {
|
switch fontData.Family {
|
||||||
case FontFamilySans:
|
case FontFamilySans:
|
||||||
|
@ -62,11 +62,11 @@ func fontFileName(fontData FontData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFont(fontData FontData, font *truetype.Font) {
|
func RegisterFont(fontData FontData, font *truetype.Font) {
|
||||||
fonts[fontFileName(fontData)] = font
|
fonts[FontFileName(fontData)] = font
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFont(fontData FontData) *truetype.Font {
|
func GetFont(fontData FontData) *truetype.Font {
|
||||||
fontFileName := fontFileName(fontData)
|
fontFileName := FontFileName(fontData)
|
||||||
font := fonts[fontFileName]
|
font := fonts[fontFileName]
|
||||||
if font != nil {
|
if font != nil {
|
||||||
return font
|
return font
|
||||||
|
|
2
gc.go
2
gc.go
|
@ -15,6 +15,8 @@ const (
|
||||||
FillRuleWinding
|
FillRuleWinding
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Sample func(gc GraphicContext, ext string) (string, error)
|
||||||
|
|
||||||
type GraphicContext interface {
|
type GraphicContext interface {
|
||||||
Path
|
Path
|
||||||
// Create a new path
|
// Create a new path
|
||||||
|
|
16
image.go
16
image.go
|
@ -164,7 +164,7 @@ func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
||||||
if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, glyph, truetype.NoHinting); err != nil {
|
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), glyph, truetype.NoHinting); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
e0 := 0
|
e0 := 0
|
||||||
|
@ -192,14 +192,14 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64
|
||||||
for _, rune := range s {
|
for _, rune := range s {
|
||||||
index := font.Index(rune)
|
index := font.Index(rune)
|
||||||
if hasPrev {
|
if hasPrev {
|
||||||
x += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index))
|
x += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
|
||||||
}
|
}
|
||||||
err := gc.drawGlyph(index, x, y)
|
err := gc.drawGlyph(index, x, y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return startx - x
|
return startx - x
|
||||||
}
|
}
|
||||||
x += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth)
|
x += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
||||||
prev, hasPrev = index, true
|
prev, hasPrev = index, true
|
||||||
}
|
}
|
||||||
return x - startx
|
return x - startx
|
||||||
|
@ -221,9 +221,9 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
|
||||||
for _, rune := range s {
|
for _, rune := range s {
|
||||||
index := font.Index(rune)
|
index := font.Index(rune)
|
||||||
if hasPrev {
|
if hasPrev {
|
||||||
cursor += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index))
|
cursor += fUnitsToFloat64(font.Kerning(int32(gc.Current.Scale), prev, index))
|
||||||
}
|
}
|
||||||
if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, index, truetype.NoHinting); err != nil {
|
if err := gc.glyphBuf.Load(gc.Current.Font, int32(gc.Current.Scale), index, truetype.NoHinting); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return 0, 0, 0, 0
|
return 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
|
||||||
right = math.Max(right, x+cursor)
|
right = math.Max(right, x+cursor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cursor += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth)
|
cursor += fUnitsToFloat64(font.HMetric(int32(gc.Current.Scale), index).AdvanceWidth)
|
||||||
prev, hasPrev = index, true
|
prev, hasPrev = index, true
|
||||||
}
|
}
|
||||||
return left, top, right, bottom
|
return left, top, right, bottom
|
||||||
|
@ -247,7 +247,7 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
// recalc recalculates scale and bounds values from the font size, screen
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
// resolution and font metrics, and invalidates the glyph cache.
|
||||||
func (gc *ImageGraphicContext) recalc() {
|
func (gc *ImageGraphicContext) recalc() {
|
||||||
gc.Current.scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0))
|
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDPI sets the screen resolution in dots per inch.
|
// SetDPI sets the screen resolution in dots per inch.
|
||||||
|
@ -258,7 +258,7 @@ func (gc *ImageGraphicContext) SetDPI(dpi int) {
|
||||||
|
|
||||||
// SetFont sets the font used to draw text.
|
// SetFont sets the font used to draw text.
|
||||||
func (gc *ImageGraphicContext) SetFont(font *truetype.Font) {
|
func (gc *ImageGraphicContext) SetFont(font *truetype.Font) {
|
||||||
gc.Current.font = font
|
gc.Current.Font = font
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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'').
|
||||||
|
|
|
@ -43,23 +43,23 @@ func NewPathAdder(adder raster.Adder) *PathAdder {
|
||||||
func (pathAdder *PathAdder) Convert(paths ...*PathStorage) {
|
func (pathAdder *PathAdder) Convert(paths ...*PathStorage) {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
j := 0
|
j := 0
|
||||||
for _, cmd := range path.commands {
|
for _, cmd := range path.Commands {
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case MoveTo:
|
case MoveTo:
|
||||||
pathAdder.firstPoint = raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}
|
pathAdder.firstPoint = raster.Point{raster.Fix32(path.Vertices[j] * 256), raster.Fix32(path.Vertices[j+1] * 256)}
|
||||||
pathAdder.adder.Start(pathAdder.firstPoint)
|
pathAdder.adder.Start(pathAdder.firstPoint)
|
||||||
j += 2
|
j += 2
|
||||||
case LineTo:
|
case LineTo:
|
||||||
pathAdder.adder.Add1(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)})
|
pathAdder.adder.Add1(raster.Point{raster.Fix32(path.Vertices[j] * 256), raster.Fix32(path.Vertices[j+1] * 256)})
|
||||||
j += 2
|
j += 2
|
||||||
case QuadCurveTo:
|
case QuadCurveTo:
|
||||||
pathAdder.adder.Add2(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)})
|
pathAdder.adder.Add2(raster.Point{raster.Fix32(path.Vertices[j] * 256), raster.Fix32(path.Vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.Vertices[j+2] * 256), raster.Fix32(path.Vertices[j+3] * 256)})
|
||||||
j += 4
|
j += 4
|
||||||
case CubicCurveTo:
|
case CubicCurveTo:
|
||||||
pathAdder.adder.Add3(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)}, raster.Point{raster.Fix32(path.vertices[j+4] * 256), raster.Fix32(path.vertices[j+5] * 256)})
|
pathAdder.adder.Add3(raster.Point{raster.Fix32(path.Vertices[j] * 256), raster.Fix32(path.Vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.Vertices[j+2] * 256), raster.Fix32(path.Vertices[j+3] * 256)}, raster.Point{raster.Fix32(path.Vertices[j+4] * 256), raster.Fix32(path.Vertices[j+5] * 256)})
|
||||||
j += 6
|
j += 6
|
||||||
case ArcTo:
|
case ArcTo:
|
||||||
lastPoint := arcAdder(pathAdder.adder, path.vertices[j], path.vertices[j+1], path.vertices[j+2], path.vertices[j+3], path.vertices[j+4], path.vertices[j+5], pathAdder.ApproximationScale)
|
lastPoint := arcAdder(pathAdder.adder, path.Vertices[j], path.Vertices[j+1], path.Vertices[j+2], path.Vertices[j+3], path.Vertices[j+4], path.Vertices[j+5], pathAdder.ApproximationScale)
|
||||||
pathAdder.adder.Add1(lastPoint)
|
pathAdder.adder.Add1(lastPoint)
|
||||||
j += 6
|
j += 6
|
||||||
case Close:
|
case Close:
|
||||||
|
|
|
@ -20,8 +20,8 @@ func NewPathConverter(converter VertexConverter) *PathConverter {
|
||||||
func (c *PathConverter) Convert(paths ...*PathStorage) {
|
func (c *PathConverter) Convert(paths ...*PathStorage) {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
j := 0
|
j := 0
|
||||||
for _, cmd := range path.commands {
|
for _, cmd := range path.Commands {
|
||||||
j = j + c.ConvertCommand(cmd, path.vertices[j:]...)
|
j = j + c.ConvertCommand(cmd, path.Vertices[j:]...)
|
||||||
}
|
}
|
||||||
c.converter.NextCommand(VertexStopCommand)
|
c.converter.NextCommand(VertexStopCommand)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,45 +20,45 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PathStorage struct {
|
type PathStorage struct {
|
||||||
commands []PathCmd
|
Commands []PathCmd
|
||||||
vertices []float64
|
Vertices []float64
|
||||||
x, y float64
|
x, y float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathStorage() (p *PathStorage) {
|
func NewPathStorage() (p *PathStorage) {
|
||||||
p = new(PathStorage)
|
p = new(PathStorage)
|
||||||
p.commands = make([]PathCmd, 0, 256)
|
p.Commands = make([]PathCmd, 0, 256)
|
||||||
p.vertices = make([]float64, 0, 256)
|
p.Vertices = make([]float64, 0, 256)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PathStorage) Clear() {
|
func (p *PathStorage) Clear() {
|
||||||
p.commands = p.commands[0:0]
|
p.Commands = p.Commands[0:0]
|
||||||
p.vertices = p.vertices[0:0]
|
p.Vertices = p.Vertices[0:0]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PathStorage) appendToPath(cmd PathCmd, vertices ...float64) {
|
func (p *PathStorage) appendToPath(cmd PathCmd, Vertices ...float64) {
|
||||||
if cap(p.vertices) <= len(p.vertices)+6 {
|
if cap(p.Vertices) <= len(p.Vertices)+6 {
|
||||||
a := make([]PathCmd, len(p.commands), cap(p.commands)+256)
|
a := make([]PathCmd, len(p.Commands), cap(p.Commands)+256)
|
||||||
b := make([]float64, len(p.vertices), cap(p.vertices)+256)
|
b := make([]float64, len(p.Vertices), cap(p.Vertices)+256)
|
||||||
copy(a, p.commands)
|
copy(a, p.Commands)
|
||||||
p.commands = a
|
p.Commands = a
|
||||||
copy(b, p.vertices)
|
copy(b, p.Vertices)
|
||||||
p.vertices = b
|
p.Vertices = b
|
||||||
}
|
}
|
||||||
p.commands = p.commands[0 : len(p.commands)+1]
|
p.Commands = p.Commands[0 : len(p.Commands)+1]
|
||||||
p.commands[len(p.commands)-1] = cmd
|
p.Commands[len(p.Commands)-1] = cmd
|
||||||
copy(p.vertices[len(p.vertices):len(p.vertices)+len(vertices)], vertices)
|
copy(p.Vertices[len(p.Vertices):len(p.Vertices)+len(Vertices)], Vertices)
|
||||||
p.vertices = p.vertices[0 : len(p.vertices)+len(vertices)]
|
p.Vertices = p.Vertices[0 : len(p.Vertices)+len(Vertices)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (src *PathStorage) Copy() (dest *PathStorage) {
|
func (src *PathStorage) Copy() (dest *PathStorage) {
|
||||||
dest = new(PathStorage)
|
dest = new(PathStorage)
|
||||||
dest.commands = make([]PathCmd, len(src.commands))
|
dest.Commands = make([]PathCmd, len(src.Commands))
|
||||||
copy(dest.commands, src.commands)
|
copy(dest.Commands, src.Commands)
|
||||||
dest.vertices = make([]float64, len(src.vertices))
|
dest.Vertices = make([]float64, len(src.Vertices))
|
||||||
copy(dest.vertices, src.vertices)
|
copy(dest.Vertices, src.Vertices)
|
||||||
return dest
|
return dest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func (p *PathStorage) LastPoint() (x, y float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PathStorage) IsEmpty() bool {
|
func (p *PathStorage) IsEmpty() bool {
|
||||||
return len(p.commands) == 0
|
return len(p.Commands) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PathStorage) Close() *PathStorage {
|
func (p *PathStorage) Close() *PathStorage {
|
||||||
|
@ -145,7 +145,7 @@ func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathStor
|
||||||
}
|
}
|
||||||
startX := cx + math.Cos(startAngle)*rx
|
startX := cx + math.Cos(startAngle)*rx
|
||||||
startY := cy + math.Sin(startAngle)*ry
|
startY := cy + math.Sin(startAngle)*ry
|
||||||
if len(p.commands) > 0 {
|
if len(p.Commands) > 0 {
|
||||||
p.LineTo(startX, startY)
|
p.LineTo(startX, startY)
|
||||||
} else {
|
} else {
|
||||||
p.MoveTo(startX, startY)
|
p.MoveTo(startX, startY)
|
||||||
|
@ -165,22 +165,22 @@ func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *PathS
|
||||||
func (p *PathStorage) String() string {
|
func (p *PathStorage) String() string {
|
||||||
s := ""
|
s := ""
|
||||||
j := 0
|
j := 0
|
||||||
for _, cmd := range p.commands {
|
for _, cmd := range p.Commands {
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case MoveTo:
|
case MoveTo:
|
||||||
s += fmt.Sprintf("MoveTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
|
s += fmt.Sprintf("MoveTo: %f, %f\n", p.Vertices[j], p.Vertices[j+1])
|
||||||
j = j + 2
|
j = j + 2
|
||||||
case LineTo:
|
case LineTo:
|
||||||
s += fmt.Sprintf("LineTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
|
s += fmt.Sprintf("LineTo: %f, %f\n", p.Vertices[j], p.Vertices[j+1])
|
||||||
j = j + 2
|
j = j + 2
|
||||||
case QuadCurveTo:
|
case QuadCurveTo:
|
||||||
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3])
|
s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3])
|
||||||
j = j + 4
|
j = j + 4
|
||||||
case CubicCurveTo:
|
case CubicCurveTo:
|
||||||
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5])
|
s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3], p.Vertices[j+4], p.Vertices[j+5])
|
||||||
j = j + 6
|
j = j + 6
|
||||||
case ArcTo:
|
case ArcTo:
|
||||||
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5])
|
s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Vertices[j], p.Vertices[j+1], p.Vertices[j+2], p.Vertices[j+3], p.Vertices[j+4], p.Vertices[j+5])
|
||||||
j = j + 6
|
j = j + 6
|
||||||
case Close:
|
case Close:
|
||||||
s += "Close\n"
|
s += "Close\n"
|
||||||
|
|
47
samples_test.go
Normal file
47
samples_test.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// See also test_test.go
|
||||||
|
|
||||||
|
package draw2d_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d.samples"
|
||||||
|
"github.com/llgcode/draw2d.samples/android"
|
||||||
|
"github.com/llgcode/draw2d.samples/frameimage"
|
||||||
|
"github.com/llgcode/draw2d.samples/gopher"
|
||||||
|
"github.com/llgcode/draw2d.samples/helloworld"
|
||||||
|
"github.com/llgcode/draw2d.samples/line"
|
||||||
|
"github.com/llgcode/draw2d.samples/linecapjoin"
|
||||||
|
"github.com/llgcode/draw2d.samples/postscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
test(t, android.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleGopher(t *testing.T) {
|
||||||
|
test(t, gopher.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleHelloWorld(t *testing.T) {
|
||||||
|
// Set the global folder for searching fonts
|
||||||
|
draw2d.SetFontFolder(samples.Dir("helloworld", ""))
|
||||||
|
test(t, helloworld.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleFrameImage(t *testing.T) {
|
||||||
|
test(t, frameimage.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLine(t *testing.T) {
|
||||||
|
test(t, line.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleLineCap(t *testing.T) {
|
||||||
|
test(t, linecapjoin.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSamplePostscript(t *testing.T) {
|
||||||
|
test(t, postscript.Main)
|
||||||
|
}
|
|
@ -28,10 +28,10 @@ type ContextStack struct {
|
||||||
FontSize float64
|
FontSize float64
|
||||||
FontData FontData
|
FontData FontData
|
||||||
|
|
||||||
font *truetype.Font
|
Font *truetype.Font
|
||||||
// fontSize and dpi are used to calculate scale. scale is the number of
|
// fontSize and dpi are used to calculate scale. scale is the number of
|
||||||
// 26.6 fixed point units in 1 em.
|
// 26.6 fixed point units in 1 em.
|
||||||
scale int32
|
Scale float64
|
||||||
|
|
||||||
previous *ContextStack
|
previous *ContextStack
|
||||||
}
|
}
|
||||||
|
@ -193,8 +193,8 @@ func (gc *StackGraphicContext) Save() {
|
||||||
context.Cap = gc.Current.Cap
|
context.Cap = gc.Current.Cap
|
||||||
context.Join = gc.Current.Join
|
context.Join = gc.Current.Join
|
||||||
context.Path = gc.Current.Path.Copy()
|
context.Path = gc.Current.Path.Copy()
|
||||||
context.font = gc.Current.font
|
context.Font = gc.Current.Font
|
||||||
context.scale = gc.Current.scale
|
context.Scale = gc.Current.Scale
|
||||||
copy(context.Tr[:], gc.Current.Tr[:])
|
copy(context.Tr[:], gc.Current.Tr[:])
|
||||||
context.previous = gc.Current
|
context.previous = gc.Current
|
||||||
gc.Current = context
|
gc.Current = context
|
||||||
|
|
27
test_test.go
Normal file
27
test_test.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func test(t *testing.T, draw draw2d.Sample) {
|
||||||
|
// Initialize the graphic context on an RGBA image
|
||||||
|
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
||||||
|
gc := draw2d.NewGraphicContext(dest)
|
||||||
|
// Draw Android logo
|
||||||
|
fn, err := draw(gc, "png")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Drawing %q failed: %v", fn, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Save to png
|
||||||
|
err = draw2d.SaveToPngFile(fn, dest)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Saving %q failed: %v", fn, err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue