diff --git a/draw2d/.project b/draw2d/.project
new file mode 100644
index 0000000..6a8aaf2
--- /dev/null
+++ b/draw2d/.project
@@ -0,0 +1,17 @@
+
+
+ draw2d
+
+
+
+
+
+ com.googlecode.goclipse.goBuilder
+
+
+
+
+
+ goclipse.goNature
+
+
diff --git a/draw2d/src/cmd/testdraw2d.go b/draw2d/src/cmd/testdraw2d.go
new file mode 100644
index 0000000..b9ad1e0
--- /dev/null
+++ b/draw2d/src/cmd/testdraw2d.go
@@ -0,0 +1,412 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "bufio"
+ "time"
+
+ "math"
+ "image"
+ "image/png"
+ "draw2d"
+)
+
+const (
+ w, h = 256, 256
+)
+
+var (
+ lastTime int64
+ folder = "../../test_results/"
+)
+
+func initGc(w, h int) (image.Image, *draw2d.GraphicContext) {
+ i := image.NewRGBA(w, h)
+ gc := draw2d.NewGraphicContext(i)
+ lastTime = time.Nanoseconds()
+
+ gc.SetStrokeColor(image.Black)
+ gc.SetFillColor(image.White)
+ // fill the background
+ //gc.Clear()
+
+ return i, gc
+}
+
+func saveToPngFile(TestName string, m image.Image) {
+ dt := time.Nanoseconds() - lastTime
+ fmt.Printf("%s during: %f ms\n", TestName, float(dt)*10e-6)
+ filePath := folder + TestName + ".png"
+ f, err := os.Open(filePath, os.O_CREAT|os.O_WRONLY, 0600)
+ 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)
+ }
+ fmt.Printf("Wrote %s OK.\n", filePath)
+}
+
+/*
+
+*/
+func TestPath() {
+ i, gc := initGc(w, h)
+ gc.MoveTo(10.0, 10.0)
+ gc.LineTo(100.0, 10.0)
+ gc.LineTo(100.0, 100.0)
+ gc.LineTo(10.0, 100.0)
+ gc.LineTo(10.0, 10.0)
+ gc.FillStroke()
+ saveToPngFile("TestPath", i)
+}
+
+
+func cos(f float) float {
+ return float(math.Cos(float64(f)))
+}
+func sin(f float) float {
+ return float(math.Sin(float64(f)))
+}
+/*
+
+*/
+func TestDrawArc() {
+ i, gc := initGc(w, h)
+ // draw an arc
+ xc, yc := 128.0, 128.0
+ radiusX, radiusY := 100.0, 100.0
+ startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
+ angle := 135 * (math.Pi / 180.0) /* in radians */
+ gc.SetLineWidth(10)
+ gc.SetLineCap(draw2d.ButtCap)
+ gc.SetStrokeColor(image.Black)
+ gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
+ gc.Stroke()
+ // fill a circle
+ gc.SetStrokeColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
+ gc.SetFillColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
+ gc.SetLineWidth(6)
+
+ gc.MoveTo(xc, yc)
+ gc.LineTo(xc+cos(startAngle)*radiusX, yc+sin(startAngle)*radiusY)
+ gc.MoveTo(xc, yc)
+ gc.LineTo(xc-radiusX, yc)
+ gc.Stroke()
+
+ gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
+ gc.Fill()
+ saveToPngFile("TestDrawArc", i)
+}
+/*
+
+*/
+func TestDrawArcNegative() {
+ i, gc := initGc(w, h)
+ // draw an arc
+ xc, yc := 128.0, 128.0
+ radiusX, radiusY := 100.0, 100.0
+ startAngle := 45.0 * (math.Pi / 180.0) /* angles are specified */
+ angle := -225 * (math.Pi / 180.0) /* in radians */
+ gc.SetLineWidth(10)
+ gc.SetLineCap(draw2d.ButtCap)
+ gc.SetStrokeColor(image.Black)
+ gc.ArcTo(xc, yc, radiusX, radiusY, startAngle, angle)
+ gc.Stroke()
+ // fill a circle
+ gc.SetStrokeColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
+ gc.SetFillColor(image.RGBAColor{255, 0x33, 0x33, 0x80})
+ gc.SetLineWidth(6)
+
+ gc.MoveTo(xc, yc)
+ gc.LineTo(xc+cos(startAngle)*radiusX, yc+sin(startAngle)*radiusY)
+ gc.MoveTo(xc, yc)
+ gc.LineTo(xc-radiusX, yc)
+ gc.Stroke()
+
+ gc.ArcTo(xc, yc, 10.0, 10.0, 0, 2*math.Pi)
+ gc.Fill()
+ saveToPngFile("TestDrawArcNegative", i)
+}
+
+func TestCurveRectangle() {
+ i, gc := initGc(w, h)
+
+ /* a custom shape that could be wrapped in a function */
+ x0, y0 := 25.6, 25.6 /* parameters like cairo_rectangle */
+ rect_width, rect_height := 204.8, 204.8
+ radius := 102.4 /* and an approximate curvature radius */
+
+ x1 := x0 + rect_width
+ y1 := y0 + rect_height
+ if rect_width/2 < radius {
+ if rect_height/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 rect_height/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.SetFillColor(image.RGBAColor{0x80, 0x80, 0xFF, 0xFF})
+ gc.SetStrokeColor(image.RGBAColor{0x80, 0, 0, 0x80})
+ gc.SetLineWidth(10.0)
+ gc.FillStroke()
+
+ saveToPngFile("TestCurveRectangle", i)
+}
+/*
+
+*/
+func TestDrawCubicCurve() {
+ i, gc := initGc(w, h)
+ // draw a cubic curve
+ x, y := 25.6, 128.0
+ x1, y1 := 102.4, 230.4
+ x2, y2 := 153.6, 25.6
+ x3, y3 := 230.4, 128.0
+
+ gc.SetFillColor(image.RGBAColor{0xAA, 0xAA, 0xAA, 0xFF})
+ gc.SetLineWidth(10)
+ gc.MoveTo(x, y)
+ gc.CubicCurveTo(x1, y1, x2, y2, x3, y3)
+ gc.Stroke()
+
+ gc.SetStrokeColor(image.RGBAColor{0xFF, 0x33, 0x33, 0x88})
+
+ gc.SetLineWidth(6)
+ // draw segment of curve
+ gc.MoveTo(x, y)
+ gc.LineTo(x1, y1)
+ gc.LineTo(x2, y2)
+ gc.LineTo(x3, y3)
+ gc.Stroke()
+ saveToPngFile("TestDrawCubicCurve", i)
+}
+
+/*
+
+*/
+func TestDash() {
+ i, gc := initGc(w, h)
+ gc.SetLineDash([]float{50, 10, 10, 10}, -50.0)
+ gc.SetLineCap(draw2d.ButtCap)
+ gc.SetLineJoin(draw2d.BevelJoin)
+ gc.SetLineWidth(10)
+
+ gc.MoveTo(128.0, 25.6)
+ gc.LineTo(128.0, 25.6)
+ gc.LineTo(230.4, 230.4)
+ gc.RLineTo(-102.4, 0.0)
+ gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
+ gc.Stroke()
+ gc.SetLineDash(nil, 0.0)
+ saveToPngFile("TestDash", i)
+}
+
+
+/*
+
+*/
+func TestFillStroke() {
+ i, gc := initGc(w, h)
+ gc.MoveTo(128.0, 25.6)
+ gc.LineTo(230.4, 230.4)
+ gc.RLineTo(-102.4, 0.0)
+ gc.CubicCurveTo(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
+ gc.Close()
+
+ gc.MoveTo(64.0, 25.6)
+ gc.RLineTo(51.2, 51.2)
+ gc.RLineTo(-51.2, 51.2)
+ gc.RLineTo(-51.2, -51.2)
+ gc.Close()
+
+ gc.SetLineWidth(10.0)
+ gc.SetFillColor(image.RGBAColor{0, 0, 0xFF, 0xFF})
+ gc.SetStrokeColor(image.Black)
+ gc.FillStroke()
+ saveToPngFile("TestFillStroke", i)
+}
+
+/*
+
+*/
+func TestFillStyle() {
+ i, gc := initGc(w, h)
+ gc.SetLineWidth(6)
+
+ gc.Rect(12, 12, 244, 70)
+
+ wheel1 := new(draw2d.Path)
+ wheel1.ArcTo(64, 64, 40, 40, 0, 2*math.Pi)
+ wheel2 := new(draw2d.Path)
+ wheel2.ArcTo(192, 64, 40, 40, 0, 2*math.Pi)
+
+ gc.SetFillRule(draw2d.FillRuleEvenOdd)
+ gc.SetFillColor(image.RGBAColor{0, 0xB2, 0, 0xFF})
+
+ gc.SetStrokeColor(image.Black)
+ gc.FillStroke(wheel1, wheel2)
+
+ gc.Rect(12, 140, 244, 198)
+ wheel1 = new(draw2d.Path)
+ wheel1.ArcTo(64, 192, 40, 40, 0, 2*math.Pi)
+ wheel2 = new(draw2d.Path)
+ wheel2.ArcTo(192, 192, 40, 40, 0, -2*math.Pi)
+
+ gc.SetFillRule(draw2d.FillRuleWinding)
+ gc.SetFillColor(image.RGBAColor{0, 0, 0xE5, 0xFF})
+ gc.FillStroke(wheel1, wheel2)
+ saveToPngFile("TestFillStyle", i)
+}
+
+func TestMultiSegmentCaps() {
+ i, gc := initGc(w, h)
+ gc.MoveTo(50.0, 75.0)
+ gc.LineTo(200.0, 75.0)
+
+ gc.MoveTo(50.0, 125.0)
+ gc.LineTo(200.0, 125.0)
+
+ gc.MoveTo(50.0, 175.0)
+ gc.LineTo(200.0, 175.0)
+
+ gc.SetLineWidth(30.0)
+ gc.SetLineCap(draw2d.RoundCap)
+ gc.Stroke()
+ saveToPngFile("TestMultiSegmentCaps", i)
+}
+
+
+func TestRoundRectangle() {
+ i, gc := initGc(w, h)
+ /* a custom shape that could be wrapped in a function */
+ x, y := 25.6, 25.6
+ width, height := 204.8, 204.8
+ aspect := 1.0 /* aspect ratio */
+ corner_radius := height / 10.0 /* and corner curvature radius */
+
+ radius := corner_radius / aspect
+ degrees := math.Pi / 180.0
+
+ gc.ArcTo(x+width-radius, y+radius, radius, radius, -90*degrees, 90*degrees)
+ gc.ArcTo(x+width-radius, y+height-radius, radius, radius, 0*degrees, 90*degrees)
+ gc.ArcTo(x+radius, y+height-radius, radius, radius, 90*degrees, 90*degrees)
+ gc.ArcTo(x+radius, y+radius, radius, radius, 180*degrees, 90*degrees)
+ gc.Close()
+
+ gc.SetFillColor(image.RGBAColor{0x80, 0x80, 0xFF, 0xFF})
+ gc.SetStrokeColor(image.RGBAColor{0x80, 0, 0, 0x80})
+ gc.SetLineWidth(10.0)
+ gc.FillStroke()
+
+ saveToPngFile("TestRoundRectangle", i)
+}
+
+func TestLineCap() {
+ i, gc := initGc(w, h)
+ gc.SetLineWidth(30.0)
+ gc.SetLineCap(draw2d.ButtCap)
+ gc.MoveTo(64.0, 50.0)
+ gc.LineTo(64.0, 200.0)
+ gc.Stroke()
+ gc.SetLineCap(draw2d.RoundCap)
+ gc.MoveTo(128.0, 50.0)
+ gc.LineTo(128.0, 200.0)
+ gc.Stroke()
+ gc.SetLineCap(draw2d.SquareCap)
+ gc.MoveTo(192.0, 50.0)
+ gc.LineTo(192.0, 200.0)
+ gc.Stroke()
+
+ /* draw helping lines */
+ gc.SetStrokeColor(image.RGBAColor{0xFF, 0x33, 0x33, 0xFF})
+ gc.SetLineWidth(2.56)
+ gc.MoveTo(64.0, 50.0)
+ gc.LineTo(64.0, 200.0)
+ gc.MoveTo(128.0, 50.0)
+ gc.LineTo(128.0, 200.0)
+ gc.MoveTo(192.0, 50.0)
+ gc.LineTo(192.0, 200.0)
+ gc.Stroke()
+ saveToPngFile("TestLineCap", i)
+}
+func TestLineJoin() {
+ i, gc := initGc(w, h)
+ gc.SetLineWidth(40.96)
+ gc.MoveTo(76.8, 84.48)
+ gc.RLineTo(51.2, -51.2)
+ gc.RLineTo(51.2, 51.2)
+ gc.SetLineJoin(draw2d.MiterJoin) /* default */
+ gc.Stroke()
+
+ gc.MoveTo(76.8, 161.28)
+ gc.RLineTo(51.2, -51.2)
+ gc.RLineTo(51.2, 51.2)
+ gc.SetLineJoin(draw2d.BevelJoin)
+ gc.Stroke()
+
+ gc.MoveTo(76.8, 238.08)
+ gc.RLineTo(51.2, -51.2)
+ gc.RLineTo(51.2, 51.2)
+ gc.SetLineJoin(draw2d.RoundJoin)
+ gc.Stroke()
+ saveToPngFile("TestLineJoin", i)
+}
+
+
+func main() {
+ TestPath()
+ TestDrawArc()
+ TestDrawArcNegative()
+ TestDrawCubicCurve()
+ TestCurveRectangle()
+ TestDash()
+ TestFillStroke()
+ TestFillStyle()
+ TestMultiSegmentCaps()
+ TestRoundRectangle()
+ TestLineCap()
+ TestLineJoin()
+}
diff --git a/draw2d/src/cmd/testpath.go b/draw2d/src/cmd/testpath.go
new file mode 100644
index 0000000..7071341
--- /dev/null
+++ b/draw2d/src/cmd/testpath.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "draw2d"
+ "fmt"
+)
+
+func main() {
+ path := new(draw2d.Path)
+ path.MoveTo(2.0, 3.0)
+ path.LineTo(2.0, 3.0)
+ path.QuadCurveTo(2.0, 3.0, 10, 20)
+ path.CubicCurveTo(2.0, 3.0, 10, 20, 13, 23)
+ path.Rect(2.0, 3.0, 100, 200)
+ path.ArcTo(2.0, 3.0, 100, 200, 200, 300)
+ fmt.Printf("%v\n", path)
+}
diff --git a/draw2d/src/pkg/draw2d/arc.go b/draw2d/src/pkg/draw2d/arc.go
new file mode 100644
index 0000000..7c5b97e
--- /dev/null
+++ b/draw2d/src/pkg/draw2d/arc.go
@@ -0,0 +1,31 @@
+package draw2d
+
+
+func arc(t LineTracer, x, y, rx, ry, start, angle, scale float) {
+ end := start + angle
+ clockWise := true
+ if angle < 0 {
+ clockWise = false
+ }
+ ra := (fabs(rx) + fabs(ry)) / 2
+ da := acos(ra/(ra+0.125/scale)) * 2
+ //normalize
+ if !clockWise {
+ da = -da
+ }
+ angle = start + da
+ var curX, curY float
+ for {
+ if (angle < end-da/4) != clockWise {
+ curX = x + cos(end)*rx
+ curY = y + sin(end)*ry
+ t.LineTo(curX, curY)
+ break
+ }
+ curX = x + cos(angle)*rx
+ curY = y + sin(angle)*ry
+
+ angle += da
+ t.LineTo(curX, curY)
+ }
+}
diff --git a/draw2d/src/pkg/draw2d/curves.go b/draw2d/src/pkg/draw2d/curves.go
new file mode 100644
index 0000000..6c385a2
--- /dev/null
+++ b/draw2d/src/pkg/draw2d/curves.go
@@ -0,0 +1,339 @@
+package draw2d
+
+import (
+ "math"
+)
+
+var (
+ CurveRecursionLimit = 32
+ CurveCollinearityEpsilon = 1e-30
+ CurveAngleToleranceEpsilon = 0.01
+)
+
+/*
+ The function has the following parameters:
+ approximationScale :
+ Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one.
+ It always has some scaling coefficient.
+ The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels.
+ Usually it looks as follows:
+ curved.approximationScale(transform.scale());
+ where transform is the affine matrix that includes all the transformations, including viewport and zoom.
+ angleTolerance :
+ You set it in radians.
+ The less this value is the more accurate will be the approximation at sharp turns.
+ But 0 means that we don't consider angle conditions at all.
+ cuspLimit :
+ An angle in radians.
+ If 0, only the real cusps will have bevel cuts.
+ If more than 0, it will restrict the sharpness.
+ The more this value is the less sharp turns will be cut.
+ Typically it should not exceed 10-15 degrees.
+*/
+
+func cubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float) {
+ cuspLimit = computeCuspLimit(cuspLimit)
+ distanceToleranceSquare := 0.5 / approximationScale
+ distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
+ recursiveCubicBezier(v, x1, y1, x2, y2, x3, y3, x4, y4, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
+ v.LineTo(x4, y4)
+}
+
+
+/*
+ * see cubicBezier comments for approximationScale and angleTolerance definition
+ */
+func quadraticBezier(v LineTracer, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float) {
+ distanceToleranceSquare := 0.5 / approximationScale
+ distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
+
+ recursiveQuadraticBezierBezier(v, x1, y1, x2, y2, x3, y3, 0, distanceToleranceSquare, angleTolerance)
+ v.LineTo(x3, y3)
+}
+
+
+func computeCuspLimit(v float) (r float) {
+ if v == 0.0 {
+ r = 0.0
+ } else {
+ r = math.Pi - v
+ }
+ return
+}
+
+
+/**
+ * http://www.antigrain.com/research/adaptive_bezier/index.html
+ */
+func recursiveQuadraticBezierBezier(v LineTracer, x1, y1, x2, y2, x3, y3 float, level int, distanceToleranceSquare, angleTolerance float) {
+ if level > CurveRecursionLimit {
+ return
+ }
+
+ // Calculate all the mid-points of the line segments
+ //----------------------
+ x12 := (x1 + x2) / 2
+ y12 := (y1 + y2) / 2
+ x23 := (x2 + x3) / 2
+ y23 := (y2 + y3) / 2
+ x123 := (x12 + x23) / 2
+ y123 := (y12 + y23) / 2
+
+ dx := x3 - x1
+ dy := y3 - y1
+ d := fabs(((x2-x3)*dy - (y2-y3)*dx))
+
+ if d > CurveCollinearityEpsilon {
+ // Regular case
+ //-----------------
+ if d*d <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ // If the curvature doesn't exceed the distanceTolerance value
+ // we tend to finish subdivisions.
+ //----------------------
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ v.LineTo(x123, y123)
+ return
+ }
+
+ // Angle & Cusp Condition
+ //----------------------
+ da := fabs(atan2(y3-y2, x3-x2) - atan2(y2-y1, x2-x1))
+ if da >= math.Pi {
+ da = 2*math.Pi - da
+ }
+
+ if da < angleTolerance {
+ // Finally we can stop the recursion
+ //----------------------
+ v.LineTo(x123, y123)
+ return
+ }
+ }
+ } else {
+ // Collinear case
+ //------------------
+ da := dx*dx + dy*dy
+ if da == 0 {
+ d = squareDistance(x1, y1, x2, y2)
+ } else {
+ d = ((x2-x1)*dx + (y2-y1)*dy) / da
+ if d > 0 && d < 1 {
+ // Simple collinear case, 1---2---3
+ // We can leave just two endpoints
+ return
+ }
+ if d <= 0 {
+ d = squareDistance(x2, y2, x1, y1)
+ } else if d >= 1 {
+ d = squareDistance(x2, y2, x3, y3)
+ } else {
+ d = squareDistance(x2, y2, x1+d*dx, y1+d*dy)
+ }
+ }
+ if d < distanceToleranceSquare {
+ v.LineTo(x2, y2)
+ return
+ }
+ }
+
+ // Continue subdivision
+ //----------------------
+ recursiveQuadraticBezierBezier(v, x1, y1, x12, y12, x123, y123, level+1, distanceToleranceSquare, angleTolerance)
+ recursiveQuadraticBezierBezier(v, x123, y123, x23, y23, x3, y3, level+1, distanceToleranceSquare, angleTolerance)
+}
+
+/**
+ * http://www.antigrain.com/research/adaptive_bezier/index.html
+ */
+func recursiveCubicBezier(v LineTracer, x1, y1, x2, y2, x3, y3, x4, y4 float, level int, distanceToleranceSquare, angleTolerance, cuspLimit float) {
+ if level > CurveRecursionLimit {
+ return
+ }
+
+ // Calculate all the mid-points of the line segments
+ //----------------------
+ x12 := (x1 + x2) / 2
+ y12 := (y1 + y2) / 2
+ x23 := (x2 + x3) / 2
+ y23 := (y2 + y3) / 2
+ x34 := (x3 + x4) / 2
+ y34 := (y3 + y4) / 2
+ x123 := (x12 + x23) / 2
+ y123 := (y12 + y23) / 2
+ x234 := (x23 + x34) / 2
+ y234 := (y23 + y34) / 2
+ x1234 := (x123 + x234) / 2
+ y1234 := (y123 + y234) / 2
+
+ // Try to approximate the full cubic curve by a single straight line
+ //------------------
+ dx := x4 - x1
+ dy := y4 - y1
+
+ d2 := fabs(((x2-x4)*dy - (y2-y4)*dx))
+ d3 := fabs(((x3-x4)*dy - (y3-y4)*dx))
+
+ switch {
+ case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ // All collinear OR p1==p4
+ //----------------------
+ k := dx*dx + dy*dy
+ if k == 0 {
+ d2 = squareDistance(x1, y1, x2, y2)
+ d3 = squareDistance(x4, y4, x3, y3)
+ } else {
+ k = 1 / k
+ da1 := x2 - x1
+ da2 := y2 - y1
+ d2 = k * (da1*dx + da2*dy)
+ da1 = x3 - x1
+ da2 = y3 - y1
+ d3 = k * (da1*dx + da2*dy)
+ if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
+ // Simple collinear case, 1---2---3---4
+ // We can leave just two endpoints
+ return
+ }
+ if d2 <= 0 {
+ d2 = squareDistance(x2, y2, x1, y1)
+ } else if d2 >= 1 {
+ d2 = squareDistance(x2, y2, x4, y4)
+ } else {
+ d2 = squareDistance(x2, y2, x1+d2*dx, y1+d2*dy)
+ }
+
+ if d3 <= 0 {
+ d3 = squareDistance(x3, y3, x1, y1)
+ } else if d3 >= 1 {
+ d3 = squareDistance(x3, y3, x4, y4)
+ } else {
+ d3 = squareDistance(x3, y3, x1+d3*dx, y1+d3*dy)
+ }
+ }
+ if d2 > d3 {
+ if d2 < distanceToleranceSquare {
+ v.LineTo(x2, y2)
+ return
+ }
+ } else {
+ if d3 < distanceToleranceSquare {
+ v.LineTo(x3, y3)
+ return
+ }
+ }
+ break
+
+ case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ // p1,p2,p4 are collinear, p3 is significant
+ //----------------------
+ if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ v.LineTo(x23, y23)
+ return
+ }
+
+ // Angle Condition
+ //----------------------
+ da1 := fabs(atan2(y4-y3, x4-x3) - atan2(y3-y2, x3-x2))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ v.LineTo(x2, y2)
+ v.LineTo(x3, y3)
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ v.LineTo(x3, y3)
+ return
+ }
+ }
+ }
+ break
+
+ case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
+ // p1,p3,p4 are collinear, p2 is significant
+ //----------------------
+ if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ v.LineTo(x23, y23)
+ return
+ }
+
+ // Angle Condition
+ //----------------------
+ da1 := fabs(atan2(y3-y2, x3-x2) - atan2(y2-y1, x2-x1))
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+
+ if da1 < angleTolerance {
+ v.LineTo(x2, y2)
+ v.LineTo(x3, y3)
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ v.LineTo(x2, y2)
+ return
+ }
+ }
+ }
+ break
+
+ case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
+ // Regular case
+ //-----------------
+ if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
+ // If the curvature doesn't exceed the distanceTolerance value
+ // we tend to finish subdivisions.
+ //----------------------
+ if angleTolerance < CurveAngleToleranceEpsilon {
+ v.LineTo(x23, y23)
+ return
+ }
+
+ // Angle & Cusp Condition
+ //----------------------
+ k := atan2(y3-y2, x3-x2)
+ da1 := fabs(k - atan2(y2-y1, x2-x1))
+ da2 := fabs(atan2(y4-y3, x4-x3) - k)
+ if da1 >= math.Pi {
+ da1 = 2*math.Pi - da1
+ }
+ if da2 >= math.Pi {
+ da2 = 2*math.Pi - da2
+ }
+
+ if da1+da2 < angleTolerance {
+ // Finally we can stop the recursion
+ //----------------------
+ v.LineTo(x23, y23)
+ return
+ }
+
+ if cuspLimit != 0.0 {
+ if da1 > cuspLimit {
+ v.LineTo(x2, y2)
+ return
+ }
+
+ if da2 > cuspLimit {
+ v.LineTo(x3, y3)
+ return
+ }
+ }
+ }
+ break
+ }
+
+ // Continue subdivision
+ //----------------------
+ recursiveCubicBezier(v, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
+ recursiveCubicBezier(v, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
+
+}
diff --git a/draw2d/src/pkg/draw2d/draw2d.go b/draw2d/src/pkg/draw2d/draw2d.go
new file mode 100644
index 0000000..8355152
--- /dev/null
+++ b/draw2d/src/pkg/draw2d/draw2d.go
@@ -0,0 +1,337 @@
+package draw2d
+
+import (
+ "exp/draw"
+ "image"
+ "freetype-go.googlecode.com/hg/freetype/raster"
+)
+
+type FillRule int
+
+const (
+ FillRuleEvenOdd FillRule = iota
+ FillRuleWinding
+)
+
+type Cap int
+
+const (
+ RoundCap Cap = iota
+ ButtCap
+ SquareCap
+)
+
+type Join int
+
+const (
+ BevelJoin Join = iota
+ RoundJoin
+ MiterJoin
+)
+
+type GraphicContext struct {
+ PaintedImage *image.RGBA
+ rasterizer *raster.Rasterizer
+ current *contextStack
+}
+
+type contextStack struct {
+ path *Path
+ lineWidth float
+ dash []float
+ dashOffset float
+ strokeColor image.Color
+ fillColor image.Color
+ fillRule FillRule
+ cap Cap
+ join Join
+
+ previous *contextStack
+}
+
+/**
+ * Create a new Graphic context from an image
+ */
+func NewGraphicContext(pi *image.RGBA) *GraphicContext {
+ gc := new(GraphicContext)
+ gc.PaintedImage = pi
+ width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
+ gc.rasterizer = raster.NewRasterizer(width, height)
+
+ gc.current = new(contextStack)
+ gc.current.lineWidth = 1.0
+ gc.current.strokeColor = image.Black
+ gc.current.fillColor = image.White
+ gc.current.cap = RoundCap
+ gc.current.fillRule = FillRuleEvenOdd
+ gc.current.join = RoundJoin
+ gc.current.path = new(Path)
+ return gc
+}
+
+func (gc *GraphicContext) Clear() {
+ width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
+ gc.ClearRect(0, 0, width, height)
+}
+
+func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
+ imageColor := image.NewColorImage(gc.current.fillColor)
+ draw.Draw(gc.PaintedImage, gc.PaintedImage.Bounds(), imageColor, image.ZP)
+}
+
+func (gc *GraphicContext) SetStrokeColor(c image.Color) {
+ gc.current.strokeColor = c
+}
+
+func (gc *GraphicContext) SetFillColor(c image.Color) {
+ gc.current.fillColor = c
+}
+
+func (gc *GraphicContext) SetFillRule(f FillRule) {
+ gc.current.fillRule = f
+}
+
+func (gc *GraphicContext) SetLineWidth(lineWidth float) {
+ gc.current.lineWidth = lineWidth
+}
+
+func (gc *GraphicContext) SetLineCap(cap Cap) {
+ gc.current.cap = cap
+}
+
+func (gc *GraphicContext) SetLineJoin(join Join) {
+ gc.current.join = join
+}
+
+func (gc *GraphicContext) SetLineDash(dash []float, dashOffset float) {
+ gc.current.dash = dash
+ gc.current.dashOffset = dashOffset
+}
+
+func (gc *GraphicContext) Save() {
+ context := new(contextStack)
+ context.lineWidth = gc.current.lineWidth
+ context.strokeColor = gc.current.strokeColor
+ context.fillColor = gc.current.fillColor
+ context.fillRule = gc.current.fillRule
+ context.dash = gc.current.dash
+ context.dashOffset = gc.current.dashOffset
+ context.cap = gc.current.cap
+ context.join = gc.current.join
+ context.path = gc.current.path.Copy()
+ context.previous = gc.current
+ gc.current = context
+}
+
+func (gc *GraphicContext) Restore() {
+ if gc.current.previous != nil {
+ oldContext := gc.current
+ gc.current = gc.current.previous
+ oldContext.previous = nil
+ }
+}
+
+func (gc *GraphicContext) BeginPath() {
+ gc.current.path = new(Path)
+}
+
+func (gc *GraphicContext) MoveTo(x, y float) {
+ gc.current.path.MoveTo(x, y)
+}
+
+func (gc *GraphicContext) RMoveTo(dx, dy float) {
+ gc.current.path.RMoveTo(dx, dy)
+}
+
+func (gc *GraphicContext) LineTo(x, y float) {
+ gc.current.path.LineTo(x, y)
+}
+
+func (gc *GraphicContext) RLineTo(dx, dy float) {
+ gc.current.path.RLineTo(dx, dy)
+}
+
+func (gc *GraphicContext) Rect(x1, y1, x2, y2 float) {
+ gc.current.path.Rect(x1, y1, x2, y2)
+}
+
+func (gc *GraphicContext) RRect(dx1, dy1, dx2, dy2 float) {
+ gc.current.path.RRect(dx1, dy1, dx2, dy2)
+}
+
+func (gc *GraphicContext) QuadCurveTo(cx, cy, x, y float) {
+ gc.current.path.QuadCurveTo(cx, cy, x, y)
+}
+
+func (gc *GraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float) {
+ gc.current.path.RQuadCurveTo(dcx, dcy, dx, dy)
+}
+
+func (gc *GraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) {
+ gc.current.path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
+}
+
+func (gc *GraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) {
+ gc.current.path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
+}
+
+func (gc *GraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float) {
+ gc.current.path.ArcTo(cx, cy, rx, ry, startAngle, angle)
+}
+
+func (gc *GraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) {
+ gc.current.path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
+}
+
+func (gc *GraphicContext) Close() {
+ gc.current.path.Close()
+}
+
+func (gc *GraphicContext) paint(color image.Color) {
+ painter := raster.NewRGBAPainter(gc.PaintedImage)
+ painter.SetColor(color)
+ gc.rasterizer.Rasterize(painter)
+ gc.rasterizer.Clear()
+ gc.current.path = new(Path)
+}
+
+func (gc *GraphicContext) Stroke(paths ...*Path) {
+ paths = append(paths, gc.current.path)
+ rasterPath := tracePath(gc.current.dash, gc.current.dashOffset, paths...)
+ gc.rasterizer.UseNonZeroWinding = true
+ gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
+ gc.paint(gc.current.strokeColor)
+}
+
+func (gc *GraphicContext) Fill(paths ...*Path) {
+ paths = append(paths, gc.current.path)
+ rasterPath := tracePath(nil, 0, paths...)
+
+ gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
+ gc.rasterizer.AddPath(*rasterPath)
+ gc.paint(gc.current.fillColor)
+}
+
+func (gc *GraphicContext) FillStroke(paths ...*Path) {
+ paths = append(paths, gc.current.path)
+ rasterPath := tracePath(nil, 0, paths...)
+
+ gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
+ gc.rasterizer.AddPath(*rasterPath)
+ gc.paint(gc.current.fillColor)
+ if gc.current.dash != nil {
+ rasterPath = tracePath(gc.current.dash, gc.current.dashOffset, paths...)
+ }
+ gc.rasterizer.UseNonZeroWinding = true
+ gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
+ gc.paint(gc.current.strokeColor)
+}
+
+func (f FillRule) fillRule() bool {
+ switch f {
+ case FillRuleEvenOdd:
+ return false
+ case FillRuleWinding:
+ return true
+ }
+ return false
+}
+
+func (c Cap) capper() raster.Capper {
+ switch c {
+ case RoundCap:
+ return raster.RoundCapper
+ case ButtCap:
+ return raster.ButtCapper
+ case SquareCap:
+ return raster.SquareCapper
+ }
+ return raster.RoundCapper
+}
+
+func (j Join) joiner() raster.Joiner {
+ switch j {
+ case RoundJoin:
+ return raster.RoundJoiner
+ case BevelJoin:
+ return raster.BevelJoiner
+ }
+ return raster.RoundJoiner
+}
+
+
+type PathAdapter struct {
+ path *raster.Path
+ x, y, distance float
+ dash []float
+ currentDash int
+ dashOffset float
+}
+
+func tracePath(dash []float, dashOffset float, paths ...*Path) *raster.Path {
+ var adapter PathAdapter
+ if dash != nil && len(dash) > 0 {
+ adapter.dash = dash
+ } else {
+ adapter.dash = nil
+ }
+ adapter.currentDash = 0
+ adapter.dashOffset = dashOffset
+ adapter.path = new(raster.Path)
+ for _, path := range paths {
+ path.TraceLine(&adapter)
+ }
+ return adapter.path
+}
+
+func floatToPoint(x, y float) raster.Point {
+ return raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}
+}
+
+func (p *PathAdapter) MoveTo(x, y float) {
+ p.path.Start(floatToPoint(x, y))
+ p.x, p.y = x, y
+ p.distance = p.dashOffset
+ p.currentDash = 0
+}
+
+func (p *PathAdapter) LineTo(x, y float) {
+ if p.dash != nil {
+ rest := p.dash[p.currentDash] - p.distance
+ for rest < 0 {
+ p.distance = p.distance - p.dash[p.currentDash]
+ p.currentDash = (p.currentDash + 1) % len(p.dash)
+ rest = p.dash[p.currentDash] - p.distance
+ }
+ d := distance(p.x, p.y, x, y)
+ for d >= rest {
+ k := rest / d
+ lx := p.x + k*(x-p.x)
+ ly := p.y + k*(y-p.y)
+ if p.currentDash%2 == 0 {
+ // line
+ p.path.Add1(floatToPoint(lx, ly))
+ } else {
+ // gap
+ p.path.Start(floatToPoint(lx, ly))
+ }
+ d = d - rest
+ p.x, p.y = lx, ly
+ p.currentDash = (p.currentDash + 1) % len(p.dash)
+ rest = p.dash[p.currentDash]
+ }
+ p.distance = d
+ if p.currentDash%2 == 0 {
+ p.path.Add1(floatToPoint(x, y))
+ } else {
+ p.path.Start(floatToPoint(x, y))
+ }
+ if p.distance >= p.dash[p.currentDash] {
+ p.distance = p.distance - p.dash[p.currentDash]
+ p.currentDash = (p.currentDash + 1) % len(p.dash)
+ }
+ } else {
+ p.path.Add1(floatToPoint(x, y))
+ }
+ p.x, p.y = x, y
+}
diff --git a/draw2d/src/pkg/draw2d/math.go b/draw2d/src/pkg/draw2d/math.go
new file mode 100644
index 0000000..c30a234
--- /dev/null
+++ b/draw2d/src/pkg/draw2d/math.go
@@ -0,0 +1,41 @@
+package draw2d
+
+import (
+ "math"
+)
+
+func fabs(x float) float {
+ switch {
+ case x < 0:
+ return -x
+ case x == 0:
+ return 0 // return correctly fabs(-0)
+ }
+ return x
+}
+
+func cos(f float) float {
+ return float(math.Cos(float64(f)))
+}
+func sin(f float) float {
+ return float(math.Sin(float64(f)))
+}
+func acos(f float) float {
+ return float(math.Acos(float64(f)))
+}
+
+func atan2(x, y float) float {
+ return float(math.Atan2(float64(x), float64(y)))
+}
+
+func distance(x1, y1, x2, y2 float) float {
+ dx := x2 - x1
+ dy := y2 - y1
+ return float(math.Sqrt(float64(dx*dx + dy*dy)))
+}
+
+func squareDistance(x1, y1, x2, y2 float) float {
+ dx := x2 - x1
+ dy := y2 - y1
+ return dx*dx + dy*dy
+}
diff --git a/draw2d/src/pkg/draw2d/path.go b/draw2d/src/pkg/draw2d/path.go
new file mode 100644
index 0000000..2fa3e5c
--- /dev/null
+++ b/draw2d/src/pkg/draw2d/path.go
@@ -0,0 +1,222 @@
+package draw2d
+
+import (
+ "fmt"
+ "math"
+)
+
+type PathCmd int
+
+const (
+ MoveTo PathCmd = iota
+ LineTo
+ QuadCurveTo
+ CubicCurveTo
+ ArcTo
+ Close
+)
+
+type Path struct {
+ commands []PathCmd
+ vertices []float
+ x, y float
+}
+
+type LineTracer interface {
+ MoveTo(x, y float)
+ LineTo(x, y float)
+}
+
+func (p *Path) appendToPath(cmd PathCmd, vertices ...float) {
+ p.commands = append(p.commands, cmd)
+ p.vertices = append(p.vertices, vertices...)
+}
+
+func (src *Path) Copy() (dest *Path) {
+ dest = new(Path)
+ dest.commands = make([]PathCmd, len(src.commands))
+ copy(dest.commands, src.commands)
+ dest.vertices = make([]float, len(src.vertices))
+ copy(dest.vertices, src.vertices)
+ return dest
+}
+func (p *Path) LastPoint() (x, y float) {
+ return p.x, p.y
+}
+
+func (p *Path) Close() *Path {
+ p.appendToPath(Close)
+ return p
+}
+
+func (p *Path) MoveTo(x, y float) *Path {
+ p.appendToPath(MoveTo, x, y)
+ p.x = x
+ p.y = y
+ return p
+}
+
+func (p *Path) RMoveTo(dx, dy float) *Path {
+ x, y := p.LastPoint()
+ p.MoveTo(x+dx, y+dy)
+ return p
+}
+
+func (p *Path) LineTo(x, y float) *Path {
+ p.appendToPath(LineTo, x, y)
+ p.x = x
+ p.y = y
+ return p
+}
+
+func (p *Path) RLineTo(dx, dy float) *Path {
+ x, y := p.LastPoint()
+ p.LineTo(x+dx, y+dy)
+ return p
+}
+
+func (p *Path) Rect(x1, y1, x2, y2 float) *Path {
+ w, h := x2-x1, y2-y1
+ if len(p.commands) > 0 {
+ p.LineTo(x1, y1)
+ } else {
+ p.MoveTo(x1, y1)
+ }
+ p.LineTo(x1+w, y1)
+ p.LineTo(x1+w, y1+h)
+ p.LineTo(x1, y1+h)
+ p.LineTo(x1, y1)
+ return p
+}
+
+func (p *Path) RRect(dx1, dy1, dx2, dy2 float) *Path {
+ x, y := p.LastPoint()
+ p.Rect(x+dx1, y+dy1, x+dx2, y+dy2)
+ return p
+}
+
+func (p *Path) QuadCurveTo(cx, cy, x, y float) *Path {
+ p.appendToPath(QuadCurveTo, cx, cy, x, y)
+ p.x = x
+ p.y = y
+ return p
+}
+
+func (p *Path) RQuadCurveTo(dcx, dcy, dx, dy float) *Path {
+ x, y := p.LastPoint()
+ p.RQuadCurveTo(x+dcx, y+dcy, x+dx, y+dy)
+ return p
+}
+
+func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) *Path {
+ p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y)
+ p.x = x
+ p.y = y
+ return p
+}
+
+func (p *Path) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) *Path {
+ x, y := p.LastPoint()
+ p.RCubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy)
+ return p
+}
+
+func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float) *Path {
+ endAngle := startAngle + angle
+ clockWise := true
+ if angle < 0 {
+ clockWise = false
+ }
+ // normalize
+ if clockWise {
+ for endAngle < startAngle {
+ endAngle += math.Pi * 2.0
+ }
+ } else {
+ for startAngle < endAngle {
+ startAngle += math.Pi * 2.0
+ }
+ }
+ startX := cx + cos(startAngle)*rx
+ startY := cy + sin(startAngle)*ry
+ if len(p.commands) > 0 {
+ p.LineTo(startX, startY)
+ } else {
+ p.MoveTo(startX, startY)
+ }
+ p.appendToPath(ArcTo, cx, cy, rx, ry, startAngle, angle)
+ p.x = cx + cos(endAngle)*rx
+ p.y = cy + sin(endAngle)*ry
+ return p
+}
+
+func (p *Path) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) *Path {
+ x, y := p.LastPoint()
+ p.RArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle)
+ return p
+}
+
+func (p *Path) TraceLine(tracer LineTracer) {
+ j := 0
+ x, y := 0.0, 0.0
+ firstX, firstY := x, y
+ if len(p.commands) > 0 {
+ if p.commands[0] == MoveTo {
+ firstX, firstY = p.vertices[0], p.vertices[1]
+ }
+ }
+ for _, cmd := range p.commands {
+ switch cmd {
+ case MoveTo:
+ tracer.MoveTo(p.vertices[j], p.vertices[j+1])
+ x, y = p.vertices[j], p.vertices[j+1]
+ firstX, firstY = x, y
+ j = j + 2
+ case LineTo:
+ tracer.LineTo(p.vertices[j], p.vertices[j+1])
+ x, y = p.vertices[j], p.vertices[j+1]
+ j = j + 2
+ case QuadCurveTo:
+ quadraticBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], 1.0, 0.0)
+ x, y = p.vertices[j+2], p.vertices[j+3]
+ j = j + 4
+ case CubicCurveTo:
+ cubicBezier(tracer, x, y, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], 1.0, 0.0, 0.0)
+ x, y = p.vertices[j+4], p.vertices[j+5]
+ j = j + 6
+ case ArcTo:
+ arc(tracer, p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5], 1)
+ j = j + 6
+ case Close:
+ tracer.LineTo(firstX, firstY)
+ x, y = firstX, firstY
+ }
+ }
+}
+
+func (p *Path) String() string {
+ s := ""
+ j := 0
+ for _, cmd := range p.commands {
+ switch cmd {
+ case MoveTo:
+ s += fmt.Sprintf("MoveTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
+ j = j + 2
+ case LineTo:
+ s += fmt.Sprintf("LineTo: %f, %f\n", p.vertices[j], p.vertices[j+1])
+ j = j + 2
+ 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])
+ j = j + 4
+ 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])
+ j = j + 6
+ 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])
+ j = j + 6
+ case Close:
+ s += "Close\n"
+ }
+ }
+ return s
+}