move flattening code in draw2dbase
This commit is contained in:
parent
ee83fedb10
commit
82ef300f1d
13 changed files with 472 additions and 179 deletions
161
draw2dbase/curve.go
Normal file
161
draw2dbase/curve.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 17/05/2011 by Laurent Le Goff
|
||||||
|
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CurveRecursionLimit = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cubic
|
||||||
|
// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64
|
||||||
|
|
||||||
|
// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
||||||
|
// c1 and c2 parameters are the resulting curves
|
||||||
|
func SubdivideCubic(c, c1, c2 []float64) {
|
||||||
|
// First point of c is the first point of c1
|
||||||
|
c1[0], c1[1] = c[0], c[1]
|
||||||
|
// Last point of c is the last point of c2
|
||||||
|
c2[6], c2[7] = c[6], c[7]
|
||||||
|
|
||||||
|
// Subdivide segment using midpoints
|
||||||
|
c1[2] = (c[0] + c[2]) / 2
|
||||||
|
c1[3] = (c[1] + c[3]) / 2
|
||||||
|
|
||||||
|
midX := (c[2] + c[4]) / 2
|
||||||
|
midY := (c[3] + c[5]) / 2
|
||||||
|
|
||||||
|
c2[4] = (c[4] + c[6]) / 2
|
||||||
|
c2[5] = (c[5] + c[7]) / 2
|
||||||
|
|
||||||
|
c1[4] = (c1[2] + midX) / 2
|
||||||
|
c1[5] = (c1[3] + midY) / 2
|
||||||
|
|
||||||
|
c2[2] = (midX + c2[4]) / 2
|
||||||
|
c2[3] = (midY + c2[5]) / 2
|
||||||
|
|
||||||
|
c1[6] = (c1[4] + c2[2]) / 2
|
||||||
|
c1[7] = (c1[5] + c2[3]) / 2
|
||||||
|
|
||||||
|
// Last Point of c1 is equal to the first point of c2
|
||||||
|
c2[0], c2[1] = c1[6], c1[7]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceCubic generate lines subdividing the cubic curve using a Flattener
|
||||||
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
|
func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) {
|
||||||
|
// Allocation curves
|
||||||
|
var curves [CurveRecursionLimit * 8]float64
|
||||||
|
copy(curves[0:8], cubic[0:8])
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
// current curve
|
||||||
|
var c []float64
|
||||||
|
|
||||||
|
var dx, dy, d2, d3 float64
|
||||||
|
|
||||||
|
for i >= 0 {
|
||||||
|
c = curves[i*8:]
|
||||||
|
dx = c[6] - c[0]
|
||||||
|
dy = c[7] - c[1]
|
||||||
|
|
||||||
|
d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx)
|
||||||
|
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
||||||
|
|
||||||
|
// if it's flat then trace a line
|
||||||
|
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
|
t.LineTo(c[6], c[7])
|
||||||
|
i--
|
||||||
|
} else {
|
||||||
|
// second half of bezier go lower onto the stack
|
||||||
|
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quad
|
||||||
|
// x1, y1, cpx1, cpy2, x2, y2 float64
|
||||||
|
|
||||||
|
// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves.
|
||||||
|
// c1 and c2 parameters are the resulting curves
|
||||||
|
func SubdivideQuad(c, c1, c2 []float64) {
|
||||||
|
// First point of c is the first point of c1
|
||||||
|
c1[0], c1[1] = c[0], c[1]
|
||||||
|
// Last point of c is the last point of c2
|
||||||
|
c2[4], c2[5] = c[4], c[5]
|
||||||
|
|
||||||
|
// Subdivide segment using midpoints
|
||||||
|
c1[2] = (c[0] + c[2]) / 2
|
||||||
|
c1[3] = (c[1] + c[3]) / 2
|
||||||
|
c2[2] = (c[2] + c[4]) / 2
|
||||||
|
c2[3] = (c[3] + c[5]) / 2
|
||||||
|
c1[4] = (c1[2] + c2[2]) / 2
|
||||||
|
c1[5] = (c1[3] + c2[3]) / 2
|
||||||
|
c2[0], c2[1] = c1[4], c1[5]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace generate lines subdividing the curve using a Flattener
|
||||||
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
|
func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) {
|
||||||
|
// Allocates curves stack
|
||||||
|
var curves [CurveRecursionLimit * 6]float64
|
||||||
|
copy(curves[0:6], quad[0:6])
|
||||||
|
i := 0
|
||||||
|
// current curve
|
||||||
|
var c []float64
|
||||||
|
var dx, dy, d float64
|
||||||
|
|
||||||
|
for i >= 0 {
|
||||||
|
c = curves[i*6:]
|
||||||
|
dx = c[4] - c[0]
|
||||||
|
dy = c[5] - c[1]
|
||||||
|
|
||||||
|
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
||||||
|
|
||||||
|
// if it's flat then trace a line
|
||||||
|
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
|
t.LineTo(c[4], c[5])
|
||||||
|
i--
|
||||||
|
} else {
|
||||||
|
// second half of bezier go lower onto the stack
|
||||||
|
SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceArc trace an arc using a Flattener
|
||||||
|
func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) {
|
||||||
|
end := start + angle
|
||||||
|
clockWise := true
|
||||||
|
if angle < 0 {
|
||||||
|
clockWise = false
|
||||||
|
}
|
||||||
|
ra := (math.Abs(rx) + math.Abs(ry)) / 2
|
||||||
|
da := math.Acos(ra/(ra+0.125/scale)) * 2
|
||||||
|
//normalize
|
||||||
|
if !clockWise {
|
||||||
|
da = -da
|
||||||
|
}
|
||||||
|
angle = start + da
|
||||||
|
var curX, curY float64
|
||||||
|
for {
|
||||||
|
if (angle < end-da/4) != clockWise {
|
||||||
|
curX = x + math.Cos(end)*rx
|
||||||
|
curY = y + math.Sin(end)*ry
|
||||||
|
return curX, curY
|
||||||
|
}
|
||||||
|
curX = x + math.Cos(angle)*rx
|
||||||
|
curY = y + math.Sin(angle)*ry
|
||||||
|
|
||||||
|
angle += da
|
||||||
|
t.LineTo(curX, curY)
|
||||||
|
}
|
||||||
|
return curX, curY
|
||||||
|
}
|
136
draw2dbase/curve_test.go
Normal file
136
draw2dbase/curve_test.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/llgcode/draw2d/raster"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flattening_threshold float64 = 0.5
|
||||||
|
testsCubicFloat64 = []float64{
|
||||||
|
100, 100, 200, 100, 100, 200, 200, 200,
|
||||||
|
100, 100, 300, 200, 200, 200, 300, 100,
|
||||||
|
100, 100, 0, 300, 200, 0, 300, 300,
|
||||||
|
150, 290, 10, 10, 290, 10, 150, 290,
|
||||||
|
10, 290, 10, 10, 290, 10, 290, 290,
|
||||||
|
100, 290, 290, 10, 10, 10, 200, 290,
|
||||||
|
}
|
||||||
|
testsQuadFloat64 = []float64{
|
||||||
|
100, 100, 200, 100, 200, 200,
|
||||||
|
100, 100, 290, 200, 290, 100,
|
||||||
|
100, 100, 0, 290, 200, 290,
|
||||||
|
150, 290, 10, 10, 290, 290,
|
||||||
|
10, 290, 10, 10, 290, 290,
|
||||||
|
100, 290, 290, 10, 120, 290,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
os.Mkdir("test_results", 0666)
|
||||||
|
f, err := os.Create("test_results/_test.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
log.Printf("Create html viewer")
|
||||||
|
f.Write([]byte("<html><body>"))
|
||||||
|
for i := 0; i < len(testsCubicFloat64)/8; i++ {
|
||||||
|
f.Write([]byte(fmt.Sprintf("<div><img src='_test%d.png'/></div>\n", i)))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(testsQuadFloat64); i++ {
|
||||||
|
f.Write([]byte(fmt.Sprintf("<div><img src='_testQuad%d.png'/>\n</div>\n", i)))
|
||||||
|
}
|
||||||
|
f.Write([]byte("</body></html>"))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image {
|
||||||
|
for i := 0; i < len(s); i += 2 {
|
||||||
|
x, y := int(s[i]+0.5), int(s[i+1]+0.5)
|
||||||
|
img.Set(x, y, c)
|
||||||
|
img.Set(x, y+1, c)
|
||||||
|
img.Set(x, y-1, c)
|
||||||
|
img.Set(x+1, y, c)
|
||||||
|
img.Set(x+1, y+1, c)
|
||||||
|
img.Set(x+1, y-1, c)
|
||||||
|
img.Set(x-1, y, c)
|
||||||
|
img.Set(x-1, y+1, c)
|
||||||
|
img.Set(x-1, y-1, c)
|
||||||
|
|
||||||
|
}
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCubicCurve(t *testing.T) {
|
||||||
|
for i := 0; i < len(testsCubicFloat64); i += 8 {
|
||||||
|
var p SegmentedPath
|
||||||
|
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
|
||||||
|
TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold)
|
||||||
|
img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
|
||||||
|
raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...)
|
||||||
|
raster.PolylineBresenham(img, image.Black, p.Points...)
|
||||||
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||||
|
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
|
||||||
|
SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img)
|
||||||
|
log.Printf("Num of points: %d\n", len(p.Points))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuadCurve(t *testing.T) {
|
||||||
|
for i := 0; i < len(testsQuadFloat64); i += 6 {
|
||||||
|
var p SegmentedPath
|
||||||
|
p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1])
|
||||||
|
TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold)
|
||||||
|
img := image.NewNRGBA(image.Rect(0, 0, 300, 300))
|
||||||
|
raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...)
|
||||||
|
raster.PolylineBresenham(img, image.Black, p.Points...)
|
||||||
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...)
|
||||||
|
drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...)
|
||||||
|
SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img)
|
||||||
|
log.Printf("Num of points: %d\n", len(p.Points))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCubicCurve(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for i := 0; i < len(testsCubicFloat64); i += 8 {
|
||||||
|
var p SegmentedPath
|
||||||
|
p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1])
|
||||||
|
TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToPngFile create and save an image to a file using PNG format
|
||||||
|
func SaveToPngFile(filePath string, m image.Image) error {
|
||||||
|
// Create the file
|
||||||
|
f, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
// Create Writer from file
|
||||||
|
b := bufio.NewWriter(f)
|
||||||
|
// Write the image into the buffer
|
||||||
|
err = png.Encode(b, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -3,24 +3,20 @@
|
||||||
|
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DashVertexConverter struct {
|
type DashVertexConverter struct {
|
||||||
next draw2d.Flattener
|
next Flattener
|
||||||
x, y, distance float64
|
x, y, distance float64
|
||||||
dash []float64
|
dash []float64
|
||||||
currentDash int
|
currentDash int
|
||||||
dashOffset float64
|
dashOffset float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashConverter(dash []float64, dashOffset float64, converter draw2d.Flattener) *DashVertexConverter {
|
func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter {
|
||||||
var dasher DashVertexConverter
|
var dasher DashVertexConverter
|
||||||
dasher.dash = dash
|
dasher.dash = dash
|
||||||
dasher.currentDash = 0
|
dasher.currentDash = 0
|
||||||
dasher.dashOffset = dashOffset
|
dasher.dashOffset = dashOffset
|
||||||
dasher.next = converter
|
dasher.next = flattener
|
||||||
return &dasher
|
return &dasher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/llgcode/draw2d"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DemuxFlattener struct {
|
type DemuxFlattener struct {
|
||||||
Flatteners []draw2d.Flattener
|
Flatteners []Flattener
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc DemuxFlattener) MoveTo(x, y float64) {
|
func (dc DemuxFlattener) MoveTo(x, y float64) {
|
||||||
|
|
121
draw2dbase/flattener.go
Normal file
121
draw2dbase/flattener.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||||
|
// created: 06/12/2010 by Laurent Le Goff
|
||||||
|
|
||||||
|
package draw2dbase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flattener receive segment definition
|
||||||
|
type Flattener interface {
|
||||||
|
// MoveTo Start a New line from the point (x, y)
|
||||||
|
MoveTo(x, y float64)
|
||||||
|
// LineTo Draw a line from the current position to the point (x, y)
|
||||||
|
LineTo(x, y float64)
|
||||||
|
// LineJoin add the most recent starting point to close the path to create a polygon
|
||||||
|
LineJoin()
|
||||||
|
// Close add the most recent starting point to close the path to create a polygon
|
||||||
|
Close()
|
||||||
|
// End mark the current line as finished so we can draw caps
|
||||||
|
End()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten convert curves into straight segments keeping join segments info
|
||||||
|
func Flatten(path *draw2d.Path, flattener Flattener, scale float64) {
|
||||||
|
// First Point
|
||||||
|
var startX, startY float64 = 0, 0
|
||||||
|
// Current Point
|
||||||
|
var x, y float64 = 0, 0
|
||||||
|
i := 0
|
||||||
|
for _, cmp := range path.Components {
|
||||||
|
switch cmp {
|
||||||
|
case draw2d.MoveToCmp:
|
||||||
|
x, y = path.Points[i], path.Points[i+1]
|
||||||
|
startX, startY = x, y
|
||||||
|
if i != 0 {
|
||||||
|
flattener.End()
|
||||||
|
}
|
||||||
|
flattener.MoveTo(x, y)
|
||||||
|
i += 2
|
||||||
|
case draw2d.LineToCmp:
|
||||||
|
x, y = path.Points[i], path.Points[i+1]
|
||||||
|
flattener.LineTo(x, y)
|
||||||
|
flattener.LineJoin()
|
||||||
|
i += 2
|
||||||
|
case draw2d.QuadCurveToCmp:
|
||||||
|
TraceQuad(flattener, path.Points[i-2:], 0.5)
|
||||||
|
x, y = path.Points[i+2], path.Points[i+3]
|
||||||
|
flattener.LineTo(x, y)
|
||||||
|
i += 4
|
||||||
|
case draw2d.CubicCurveToCmp:
|
||||||
|
TraceCubic(flattener, path.Points[i-2:], 0.5)
|
||||||
|
x, y = path.Points[i+4], path.Points[i+5]
|
||||||
|
flattener.LineTo(x, y)
|
||||||
|
i += 6
|
||||||
|
case draw2d.ArcToCmp:
|
||||||
|
x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale)
|
||||||
|
flattener.LineTo(x, y)
|
||||||
|
i += 6
|
||||||
|
case draw2d.CloseCmp:
|
||||||
|
flattener.LineTo(startX, startY)
|
||||||
|
flattener.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flattener.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformer apply the Matrix transformation tr
|
||||||
|
type Transformer struct {
|
||||||
|
Tr draw2d.MatrixTransform
|
||||||
|
Flattener Flattener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Transformer) MoveTo(x, y float64) {
|
||||||
|
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
|
||||||
|
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
|
||||||
|
t.Flattener.MoveTo(u, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Transformer) LineTo(x, y float64) {
|
||||||
|
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
|
||||||
|
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
|
||||||
|
t.Flattener.LineTo(u, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Transformer) LineJoin() {
|
||||||
|
t.Flattener.LineJoin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Transformer) Close() {
|
||||||
|
t.Flattener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Transformer) End() {
|
||||||
|
t.Flattener.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SegmentedPath struct {
|
||||||
|
Points []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SegmentedPath) MoveTo(x, y float64) {
|
||||||
|
p.Points = append(p.Points, x, y)
|
||||||
|
// TODO need to mark this point as moveto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SegmentedPath) LineTo(x, y float64) {
|
||||||
|
p.Points = append(p.Points, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SegmentedPath) LineJoin() {
|
||||||
|
// TODO need to mark the current point as linejoin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SegmentedPath) Close() {
|
||||||
|
// TODO Close
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SegmentedPath) End() {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ func toFtJoin(j draw2d.LineJoin) raster.Joiner {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LineStroker struct {
|
type LineStroker struct {
|
||||||
Next draw2d.Flattener
|
Flattener Flattener
|
||||||
HalfLineWidth float64
|
HalfLineWidth float64
|
||||||
Cap draw2d.LineCap
|
Cap draw2d.LineCap
|
||||||
Join draw2d.LineJoin
|
Join draw2d.LineJoin
|
||||||
|
@ -41,9 +41,9 @@ type LineStroker struct {
|
||||||
x, y, nx, ny float64
|
x, y, nx, ny float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener draw2d.Flattener) *LineStroker {
|
func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener Flattener) *LineStroker {
|
||||||
l := new(LineStroker)
|
l := new(LineStroker)
|
||||||
l.Next = flattener
|
l.Flattener = flattener
|
||||||
l.HalfLineWidth = 0.5
|
l.HalfLineWidth = 0.5
|
||||||
l.Cap = c
|
l.Cap = c
|
||||||
l.Join = j
|
l.Join = j
|
||||||
|
@ -82,18 +82,18 @@ func (l *LineStroker) Close() {
|
||||||
|
|
||||||
func (l *LineStroker) End() {
|
func (l *LineStroker) End() {
|
||||||
if len(l.vertices) > 1 {
|
if len(l.vertices) > 1 {
|
||||||
l.Next.MoveTo(l.vertices[0], l.vertices[1])
|
l.Flattener.MoveTo(l.vertices[0], l.vertices[1])
|
||||||
for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
|
for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 {
|
||||||
l.Next.LineTo(l.vertices[i], l.vertices[j])
|
l.Flattener.LineTo(l.vertices[i], l.vertices[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
|
for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 {
|
||||||
l.Next.LineTo(l.rewind[i], l.rewind[j])
|
l.Flattener.LineTo(l.rewind[i], l.rewind[j])
|
||||||
}
|
}
|
||||||
if len(l.vertices) > 1 {
|
if len(l.vertices) > 1 {
|
||||||
l.Next.LineTo(l.vertices[0], l.vertices[1])
|
l.Flattener.LineTo(l.vertices[0], l.vertices[1])
|
||||||
}
|
}
|
||||||
l.Next.End()
|
l.Flattener.End()
|
||||||
// reinit vertices
|
// reinit vertices
|
||||||
l.vertices = l.vertices[0:0]
|
l.vertices = l.vertices[0:0]
|
||||||
l.rewind = l.rewind[0:0]
|
l.rewind = l.rewind[0:0]
|
||||||
|
|
|
@ -188,17 +188,17 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2d.Flattener
|
var liner draw2dbase.Flattener
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
} else {
|
} else {
|
||||||
liner = stroker
|
liner = stroker
|
||||||
}
|
}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(liner, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
|
@ -209,9 +209,9 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
|
|
||||||
/**** first method ****/
|
/**** first method ****/
|
||||||
flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(flattener, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||||
|
@ -222,21 +222,21 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2d.Flattener
|
var liner draw2dbase.Flattener
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
} else {
|
} else {
|
||||||
liner = stroker
|
liner = stroker
|
||||||
}
|
}
|
||||||
|
|
||||||
demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}}
|
demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(demux, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
|
|
|
@ -276,17 +276,17 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
||||||
paths = append(paths, gc.Current.Path)
|
paths = append(paths, gc.Current.Path)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2d.Flattener
|
var liner draw2dbase.Flattener
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
} else {
|
} else {
|
||||||
liner = stroker
|
liner = stroker
|
||||||
}
|
}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(liner, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor)
|
||||||
|
@ -297,9 +297,9 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
|
|
||||||
/**** first method ****/
|
/**** first method ****/
|
||||||
flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(flattener, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
gc.paint(gc.fillRasterizer, gc.Current.FillColor)
|
||||||
|
@ -310,21 +310,21 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
||||||
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule)
|
||||||
gc.strokeRasterizer.UseNonZeroWinding = true
|
gc.strokeRasterizer.UseNonZeroWinding = true
|
||||||
|
|
||||||
flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}}
|
||||||
|
|
||||||
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}})
|
||||||
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
stroker.HalfLineWidth = gc.Current.LineWidth / 2
|
||||||
|
|
||||||
var liner draw2d.Flattener
|
var liner draw2dbase.Flattener
|
||||||
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 {
|
||||||
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker)
|
||||||
} else {
|
} else {
|
||||||
liner = stroker
|
liner = stroker
|
||||||
}
|
}
|
||||||
|
|
||||||
demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}}
|
demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}}
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
p.Flatten(demux, gc.Current.Tr.GetScale())
|
draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rectangle draw a rectangle using a PathBuilder
|
// Rectangle draws a rectangle using a PathBuilder
|
||||||
func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) {
|
func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) {
|
||||||
path.MoveTo(x1, y1)
|
path.MoveTo(x1, y1)
|
||||||
path.LineTo(x2, y1)
|
path.LineTo(x2, y1)
|
||||||
|
@ -16,7 +16,7 @@ func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) {
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundedRectangle draw a rounded rectangle using a PathBuilder
|
// RoundedRectangle draws a rounded rectangle using a PathBuilder
|
||||||
func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) {
|
||||||
arcWidth = arcWidth / 2
|
arcWidth = arcWidth / 2
|
||||||
arcHeight = arcHeight / 2
|
arcHeight = arcHeight / 2
|
||||||
|
@ -31,13 +31,13 @@ func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight floa
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ellipse draw an ellipse using a PathBuilder
|
// Ellipse draws an ellipse using a PathBuilder
|
||||||
func Ellipse(path PathBuilder, cx, cy, rx, ry float64) {
|
func Ellipse(path PathBuilder, cx, cy, rx, ry float64) {
|
||||||
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Circle draw an circle using a PathBuilder
|
// Circle draws a circle using a PathBuilder
|
||||||
func Circle(path PathBuilder, cx, cy, radius float64) {
|
func Circle(path PathBuilder, cx, cy, radius float64) {
|
||||||
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2)
|
||||||
path.Close()
|
path.Close()
|
||||||
|
|
59
flattener.go
59
flattener.go
|
@ -1,59 +0,0 @@
|
||||||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 06/12/2010 by Laurent Le Goff
|
|
||||||
|
|
||||||
package draw2d
|
|
||||||
|
|
||||||
// Flattener receive segment definition
|
|
||||||
type Flattener interface {
|
|
||||||
// MoveTo Start a New line from the point (x, y)
|
|
||||||
MoveTo(x, y float64)
|
|
||||||
// LineTo Draw a line from the current position to the point (x, y)
|
|
||||||
LineTo(x, y float64)
|
|
||||||
// LineJoin add the most recent starting point to close the path to create a polygon
|
|
||||||
LineJoin()
|
|
||||||
// Close add the most recent starting point to close the path to create a polygon
|
|
||||||
Close()
|
|
||||||
// End mark the current line as finished so we can draw caps
|
|
||||||
End()
|
|
||||||
}
|
|
||||||
|
|
||||||
type SegmentedPath struct {
|
|
||||||
Points []float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SegmentedPath) MoveTo(x, y float64) {
|
|
||||||
p.Points = append(p.Points, x, y)
|
|
||||||
// TODO need to mark this point as moveto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SegmentedPath) LineTo(x, y float64) {
|
|
||||||
p.Points = append(p.Points, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SegmentedPath) LineJoin() {
|
|
||||||
// TODO need to mark the current point as linejoin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SegmentedPath) Close() {
|
|
||||||
// TODO Close
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SegmentedPath) End() {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
type LineCap int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RoundCap LineCap = iota
|
|
||||||
ButtCap
|
|
||||||
SquareCap
|
|
||||||
)
|
|
||||||
|
|
||||||
type LineJoin int
|
|
||||||
|
|
||||||
const (
|
|
||||||
BevelJoin LineJoin = iota
|
|
||||||
RoundJoin
|
|
||||||
MiterJoin
|
|
||||||
)
|
|
16
gc.go
16
gc.go
|
@ -15,6 +15,22 @@ const (
|
||||||
FillRuleWinding
|
FillRuleWinding
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LineCap int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoundCap LineCap = iota
|
||||||
|
ButtCap
|
||||||
|
SquareCap
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineJoin int
|
||||||
|
|
||||||
|
const (
|
||||||
|
BevelJoin LineJoin = iota
|
||||||
|
RoundJoin
|
||||||
|
MiterJoin
|
||||||
|
)
|
||||||
|
|
||||||
type GraphicContext interface {
|
type GraphicContext interface {
|
||||||
PathBuilder
|
PathBuilder
|
||||||
// Create a new path
|
// Create a new path
|
||||||
|
|
48
path.go
48
path.go
|
@ -9,7 +9,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PathBuilder define method that create path
|
// PathBuilder defines methods that creates path
|
||||||
type PathBuilder interface {
|
type PathBuilder interface {
|
||||||
// LastPoint returns the current point of the current path
|
// LastPoint returns the current point of the current path
|
||||||
LastPoint() (x, y float64)
|
LastPoint() (x, y float64)
|
||||||
|
@ -33,7 +33,7 @@ type PathBuilder interface {
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathCmp represent components of a path
|
// PathCmp represents component of a path
|
||||||
type PathCmp int
|
type PathCmp int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -174,47 +174,3 @@ func (p *Path) String() string {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flatten convert curves into straight segments keeping join segments info
|
|
||||||
func (path *Path) Flatten(flattener Flattener, scale float64) {
|
|
||||||
// First Point
|
|
||||||
var startX, startY float64 = 0, 0
|
|
||||||
// Current Point
|
|
||||||
var x, y float64 = 0, 0
|
|
||||||
i := 0
|
|
||||||
for _, cmd := range path.Components {
|
|
||||||
switch cmd {
|
|
||||||
case MoveToCmp:
|
|
||||||
x, y = path.Points[i], path.Points[i+1]
|
|
||||||
startX, startY = x, y
|
|
||||||
if i != 0 {
|
|
||||||
flattener.End()
|
|
||||||
}
|
|
||||||
flattener.MoveTo(x, y)
|
|
||||||
i += 2
|
|
||||||
case LineToCmp:
|
|
||||||
x, y = path.Points[i], path.Points[i+1]
|
|
||||||
flattener.LineTo(x, y)
|
|
||||||
flattener.LineJoin()
|
|
||||||
i += 2
|
|
||||||
case QuadCurveToCmp:
|
|
||||||
TraceQuad(flattener, path.Points[i-2:], 0.5)
|
|
||||||
x, y = path.Points[i+2], path.Points[i+3]
|
|
||||||
flattener.LineTo(x, y)
|
|
||||||
i += 4
|
|
||||||
case CubicCurveToCmp:
|
|
||||||
TraceCubic(flattener, path.Points[i-2:], 0.5)
|
|
||||||
x, y = path.Points[i+4], path.Points[i+5]
|
|
||||||
flattener.LineTo(x, y)
|
|
||||||
i += 6
|
|
||||||
case ArcToCmp:
|
|
||||||
x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale)
|
|
||||||
flattener.LineTo(x, y)
|
|
||||||
i += 6
|
|
||||||
case CloseCmp:
|
|
||||||
flattener.LineTo(startX, startY)
|
|
||||||
flattener.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flattener.End()
|
|
||||||
}
|
|
||||||
|
|
30
transform.go
30
transform.go
|
@ -246,33 +246,3 @@ func (tr MatrixTransform) IsTranslation() bool {
|
||||||
func fequals(float1, float2 float64) bool {
|
func fequals(float1, float2 float64) bool {
|
||||||
return math.Abs(float1-float2) <= epsilon
|
return math.Abs(float1-float2) <= epsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transformer apply the Matrix transformation tr
|
|
||||||
type Transformer struct {
|
|
||||||
Tr MatrixTransform
|
|
||||||
Flattener Flattener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Transformer) MoveTo(x, y float64) {
|
|
||||||
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
|
|
||||||
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
|
|
||||||
t.Flattener.MoveTo(u, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Transformer) LineTo(x, y float64) {
|
|
||||||
u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4]
|
|
||||||
v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5]
|
|
||||||
t.Flattener.LineTo(u, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Transformer) LineJoin() {
|
|
||||||
t.Flattener.LineJoin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Transformer) Close() {
|
|
||||||
t.Flattener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Transformer) End() {
|
|
||||||
t.Flattener.End()
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue