diff --git a/pdf2d/fileutil.go b/pdf2d/fileutil.go new file mode 100644 index 0000000..7df9da9 --- /dev/null +++ b/pdf2d/fileutil.go @@ -0,0 +1,8 @@ +package pdf2d + +import "github.com/stanim/gofpdf" + +// SaveToPdfFile create and save a pdf document to a file +func SaveToPdfFile(filePath string, pdf *gofpdf.Fpdf) error { + return pdf.OutputFileAndClose(filePath) +} diff --git a/pdf2d/graphiccontext.go b/pdf2d/graphiccontext.go new file mode 100644 index 0000000..03bd69c --- /dev/null +++ b/pdf2d/graphiccontext.go @@ -0,0 +1,144 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 26/06/2015 by Stani Michiels + +package pdf2d + +import ( + "fmt" + "image" + "image/color" + "log" + "os" + + "github.com/stanim/draw2d" + "github.com/stanim/gofpdf" +) + +const c255 = 254.0 / 65535.0 + +var ( + caps = map[draw2d.Cap]string{ + draw2d.RoundCap: "round", + draw2d.ButtCap: "butt", + draw2d.SquareCap: "square"} +) + +func notImplemented(method string) { + fmt.Printf("%s: not implemented\n", method) +} + +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) +} + +// 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 { + dpi := 92 + return &GraphicContext{draw2d.NewStackGraphicContext(), pdf, dpi} +} + +func (gc *GraphicContext) DrawImage(image image.Image) { + notImplemented("DrawImage") +} +func (gc *GraphicContext) Clear() { + notImplemented("Clear") +} + +func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) { + notImplemented("ClearRect") +} + +func (gc *GraphicContext) SetDPI(dpi int) { + gc.DPI = dpi +} + +func (gc *GraphicContext) GetDPI() int { + return gc.DPI +} + +func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { + notImplemented("GetStringBounds") + return 0, 0, 0, 0 +} + +func (gc *GraphicContext) CreateStringPath(text string, x, y float64) (cursor float64) { + notImplemented("CreateStringPath") + return 0 +} + +func (gc *GraphicContext) FillString(text string) (cursor float64) { + notImplemented("FillString") + return 0 +} + +func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) { + notImplemented("FillStringAt") + return 0 +} + +func (gc *GraphicContext) StrokeString(text string) (cursor float64) { + notImplemented("StrokeString") + return 0 +} + +func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { + notImplemented("StrokeStringAt") + return 0 +} + +func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { + gc.draw("D", paths...) +} + +func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { + gc.draw("F", paths...) +} + +func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { + gc.draw("FD", paths...) +} + +var logger *log.Logger = log.New(os.Stdout, "", log.Lshortfile) + +func (gc *GraphicContext) draw(style string, paths ...*draw2d.PathStorage) { + paths = append(paths, gc.Current.Path) + pathConverter := NewPathConverter( + NewVertexMatrixTransform(gc.Current.Tr, + NewPathLogger(logger, gc.pdf))) + pathConverter.Convert(paths...) + if gc.Current.FillRule.UseNonZeroWinding() { + style += "*" + } + gc.pdf.DrawPath(style) +} + +// overwrite StackGraphicContext methods + +func (gc *GraphicContext) SetStrokeColor(c color.Color) { + gc.StackGraphicContext.SetStrokeColor(c) + gc.pdf.SetDrawColor(rgb(c)) +} + +func (gc *GraphicContext) SetFillColor(c color.Color) { + gc.StackGraphicContext.SetFillColor(c) + gc.pdf.SetFillColor(rgb(c)) +} + +func (gc *GraphicContext) SetLineWidth(LineWidth float64) { + gc.StackGraphicContext.SetLineWidth(LineWidth) + gc.pdf.SetLineWidth(LineWidth) +} + +func (gc *GraphicContext) SetLineCap(Cap draw2d.Cap) { + gc.StackGraphicContext.SetLineCap(Cap) + gc.pdf.SetLineCapStyle(caps[Cap]) +} diff --git a/pdf2d/path_converter.go b/pdf2d/path_converter.go new file mode 100644 index 0000000..2f2ab36 --- /dev/null +++ b/pdf2d/path_converter.go @@ -0,0 +1,56 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 26/06/2015 by Stani Michiels + +package pdf2d + +import ( + "math" + + "github.com/stanim/draw2d" +) + +const deg = 180 / math.Pi + +type PathConverter struct { + pdf Vectorizer +} + +func NewPathConverter(pdf Vectorizer) *PathConverter { + return &PathConverter{pdf: pdf} +} + +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:]...) + } + } +} + +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 +} diff --git a/pdf2d/path_logger.go b/pdf2d/path_logger.go new file mode 100644 index 0000000..24faf49 --- /dev/null +++ b/pdf2d/path_logger.go @@ -0,0 +1,70 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 26/06/2015 by Stani Michiels + +package pdf2d + +import ( + "bytes" + "log" + "strconv" +) + +func ftoas(xs ...float64) string { + var buffer bytes.Buffer + for i, x := range xs { + if i > 0 { + buffer.WriteString(", ") + } + buffer.WriteString(strconv.FormatFloat(x, 'f', 2, 64)) + } + return buffer.String() +} + +// VertexMatrixTransform implements Vectorizer and applies the Matrix +// transformation tr. It is normally wrapped around gofpdf Fpdf. +type PathLogger struct { + logger *log.Logger + Next Vectorizer +} + +func NewPathLogger(logger *log.Logger, + vectorizer Vectorizer) *PathLogger { + return &PathLogger{logger, vectorizer} +} + +// MoveTo creates a new subpath that start at the specified point +func (pl *PathLogger) MoveTo(x, y float64) { + pl.logger.Printf("MoveTo(x=%.2f, y=%.2f)", x, y) + pl.Next.MoveTo(x, y) +} + +// LineTo adds a line to the current subpath +func (pl *PathLogger) LineTo(x, y float64) { + pl.logger.Printf("LineTo(x=%.2f, y=%.2f)", x, y) + pl.Next.LineTo(x, y) +} + +// CurveTo adds a quadratic bezier curve to the current subpath +func (pl *PathLogger) CurveTo(cx, cy, x, y float64) { + pl.logger.Printf("CurveTo(cx=%.2f, cy=%.2f, x=%.2f, y=%.2f)", cx, cy, x, y) + pl.Next.CurveTo(cx, cy, x, y) + +} + +// CurveTo adds a cubic bezier curve to the current subpath +func (pl *PathLogger) CurveBezierCubicTo(cx1, cy1, + cx2, cy2, x, y float64) { + pl.logger.Printf("CurveBezierCubicTo(cx1=%.2f, cy1=%.2f, cx2=%.2f, cy2=%.2f, x=%.2f, y=%.2f)", cx1, cy1, cx2, cy2, x, y) + pl.Next.CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y) +} + +// ArcTo adds an arc to the current subpath +func (pl *PathLogger) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) { + pl.logger.Printf("ArcTo(x=%.2f, y=%.2f, rx=%.2f, ry=%.2f, degRotate=%.2f, degStart=%.2f, degEnd=%.2f)", x, y, rx, ry, degRotate, degStart, degEnd) + pl.Next.ArcTo(x, y, rx, ry, degRotate, degStart, degEnd) +} + +// ClosePath closes the subpath +func (pl *PathLogger) ClosePath() { + pl.Next.ClosePath() +} diff --git a/pdf2d/samples/android.go b/pdf2d/samples/android.go new file mode 100644 index 0000000..91c8f56 --- /dev/null +++ b/pdf2d/samples/android.go @@ -0,0 +1,79 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +// Draw an android avatar to android.png +package main + +import ( + "fmt" + "image/color" + "math" + + "github.com/stanim/draw2d" + "github.com/stanim/draw2d/pdf2d" + "github.com/stanim/gofpdf" +) + +func main() { + // Initialize the graphic context on a pdf document + pdf := gofpdf.New("P", "mm", "A4", "../font") + pdf.AddPage() + gc := pdf2d.NewGraphicContext(pdf) + + // 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}) + + // Draw the droid + DrawDroid(gc, 10, 10) + + // Save to png + pdf2d.SaveToPdfFile("android.pdf", pdf) +} + +func DrawDroid(gc draw2d.GraphicContext, x, y float64) { + gc.SetLineCap(draw2d.RoundCap) + gc.SetLineWidth(5) + + fmt.Println("\nhead") + 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() + + fmt.Println("\nleft eye") + draw2d.Circle(gc, x+60, y+45, 5) + gc.FillStroke() + + fmt.Println("\nright eye") + draw2d.Circle(gc, x+100, y+45, 5) + gc.FillStroke() + + fmt.Println("\nbody") + draw2d.RoundRect(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) + gc.FillStroke() + draw2d.Rect(gc, x+30, y+75, x+30+100, y+75+80) + gc.FillStroke() + + fmt.Println("\nleft arm") + draw2d.RoundRect(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) + gc.FillStroke() + + fmt.Println("\nright arm") + draw2d.RoundRect(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) + gc.FillStroke() + + fmt.Println("\nleft leg") + draw2d.RoundRect(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) + gc.FillStroke() + + fmt.Println("\nright leg") + draw2d.RoundRect(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) + gc.FillStroke() + +} diff --git a/pdf2d/transform.go b/pdf2d/transform.go new file mode 100644 index 0000000..1da048d --- /dev/null +++ b/pdf2d/transform.go @@ -0,0 +1,55 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 26/06/2015 by Stani Michiels + +package pdf2d + +import "github.com/stanim/draw2d" + +// VertexMatrixTransform implements Vectorizer and applies the Matrix +// transformation tr. It is normally wrapped around gofpdf Fpdf. +type VertexMatrixTransform struct { + tr draw2d.MatrixTransform + Next Vectorizer +} + +func NewVertexMatrixTransform(tr draw2d.MatrixTransform, + vectorizer Vectorizer) *VertexMatrixTransform { + return &VertexMatrixTransform{tr, vectorizer} +} + +// MoveTo creates a new subpath that start at the specified point +func (vmt *VertexMatrixTransform) MoveTo(x, y float64) { + vmt.tr.Transform(&x, &y) + vmt.Next.MoveTo(x, y) +} + +// LineTo adds a line to the current subpath +func (vmt *VertexMatrixTransform) LineTo(x, y float64) { + vmt.tr.Transform(&x, &y) + vmt.Next.LineTo(x, y) +} + +// CurveTo adds a quadratic bezier curve to the current subpath +func (vmt *VertexMatrixTransform) CurveTo(cx, cy, x, y float64) { + vmt.tr.Transform(&cx, &cy, &x, &y) + vmt.Next.CurveTo(cx, cy, x, y) + +} + +// CurveTo adds a cubic bezier curve to the current subpath +func (vmt *VertexMatrixTransform) CurveBezierCubicTo(cx1, cy1, + cx2, cy2, x, y float64) { + vmt.tr.Transform(&cx1, &cy1, &cx2, &cy2, &x, &y) + vmt.Next.CurveBezierCubicTo(cx1, cy1, cx2, cy2, x, y) +} + +// ArcTo adds an arc to the current subpath +func (vmt *VertexMatrixTransform) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) { + vmt.tr.Transform(&x, &y) + vmt.Next.ArcTo(x, y, rx, ry, degRotate, degStart, degEnd) +} + +// ClosePath closes the subpath +func (vmt *VertexMatrixTransform) ClosePath() { + vmt.Next.ClosePath() +} diff --git a/pdf2d/vectorizer.go b/pdf2d/vectorizer.go new file mode 100644 index 0000000..c7cc5a3 --- /dev/null +++ b/pdf2d/vectorizer.go @@ -0,0 +1,21 @@ +// Copyright 2015 The draw2d Authors. All rights reserved. +// created: 26/06/2015 by Stani Michiels + +package pdf2d + +// Vectorizer defines the minimal interface for gofpdf.Fpdf +// 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() +}