diff --git a/drawinginstruction.go b/drawinginstruction.go
index 322158d..80284de 100644
--- a/drawinginstruction.go
+++ b/drawinginstruction.go
@@ -1,5 +1,7 @@
package svg
+import "fmt"
+
// InstructionType tells our path drawing library which function it has
// to call
type InstructionType int
@@ -37,3 +39,60 @@ type DrawingInstruction struct {
StrokeLineCap *string
StrokeLineJoin *string
}
+
+func (di *DrawingInstruction) String() string {
+ switch di.Kind {
+ case MoveInstruction:
+ return fmt.Sprintf("M%v %v", di.M[0], di.M[1])
+ case CircleInstruction:
+ return fmt.Sprintf("circle R=%v", *di.Radius)
+ case CurveInstruction:
+ c := di.CurvePoints
+ return fmt.Sprintf("C%v %v %v %v %v %v",
+ c.C1[0], c.C1[1], c.C2[0], c.C2[1], c.T[0], c.T[1])
+ case LineInstruction:
+ return fmt.Sprintf("L%v %v", di.M[0], di.M[1])
+ case CloseInstruction:
+ return "Z"
+ case PaintInstruction:
+ pt := ""
+ if di.Fill != nil {
+ pt = fmt.Sprintf("%vfill=\"%v\" ", pt, *di.Fill)
+ }
+ if di.Stroke != nil {
+ pt = fmt.Sprintf("%vstroke=\"%v\" ", pt, *di.Stroke)
+ }
+ if di.StrokeWidth != nil {
+ pt = fmt.Sprintf("%vstroke-width=\"%v\" ", pt, *di.StrokeWidth)
+ }
+ if di.StrokeLineCap != nil {
+ pt = fmt.Sprintf("%vstroke-linecap=\"%v\" ", pt, *di.StrokeLineCap)
+ }
+ if di.StrokeLineJoin != nil {
+ pt = fmt.Sprintf("%vstroke-linejoin=\"%v\" ", pt, *di.StrokeLineJoin)
+ }
+ return pt
+ }
+ return ""
+}
+
+// PathStringFromDrawingInstructions converts drawing instructions obtained
+// from svg element back into form
+func PathStringFromDrawingInstructions(dis []*DrawingInstruction) string {
+ data := " "
+ sep := ""
+ var paint *DrawingInstruction
+ for _, di := range dis {
+ if di.Kind == PaintInstruction {
+ paint = di
+ } else {
+ data += sep + di.String()
+ sep = " "
+ }
+ }
+ pt := ""
+ if paint != nil {
+ pt = paint.String()
+ }
+ return ``
+}
diff --git a/parser.go b/parser.go
index d577985..8b4f553 100644
--- a/parser.go
+++ b/parser.go
@@ -54,24 +54,39 @@ func parseTuple(l *gl.Lexer) (Tuple, error) {
}
func parseTransform(tstring string) (mt.Transform, error) {
+ var x *mt.Transform
lexer, _ := gl.Lex("tlexer", tstring)
for {
i := lexer.NextItem()
+ var t mt.Transform
+ var err error
switch i.Type {
case gl.ItemEOS:
- return mt.Identity(),
- fmt.Errorf("transform parse failed")
+ if x == nil {
+ return mt.Identity(),
+ fmt.Errorf("transform parse failed")
+ }
+ return *x, nil
case gl.ItemWord:
switch i.Value {
case "matrix":
- return parseMatrix(lexer)
+ t, err = parseMatrix(lexer)
case "translate":
- return parseTranslate(lexer)
+ t, err = parseTranslate(lexer)
case "rotate":
- return parseRotate(lexer)
+ t, err = parseRotate(lexer)
case "scale":
- return parseScale(lexer)
+ t, err = parseScale(lexer)
}
+ case gl.ItemWSP:
+ continue
+ }
+ if err != nil {
+ return t, err
+ } else if x == nil {
+ x = &t
+ } else {
+ x.MultiplyWith(t) // see https://www.w3.org/TR/SVGTiny12
}
}
}
diff --git a/parser_test.go b/parser_test.go
index 44a64a6..969edc2 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,6 +1,8 @@
package svg
import (
+ "fmt"
+ "os"
"strings"
"testing"
@@ -29,3 +31,191 @@ func TestParse(t *testing.T) {
is.NoErr(err)
is.NotNil(svg)
}
+
+func TestTransform(t *testing.T) {
+ content := `
+
+
+ `
+ s, err := ParseSvg(content, "transformed heart", 0)
+ if err != nil {
+ t.Fatalf("cannot parse svg %v", content)
+ }
+ if len(s.Groups) < 1 {
+ t.Fatal("group not found")
+ }
+ g := s.Groups[0]
+ t.Logf("original: transform=\"%v\"", g.TransformString)
+ m := *g.Transform
+ a, c, e := m[0][0], m[0][1], m[0][2]
+ b, d, f := m[1][0], m[1][1], m[1][2]
+ // see https://www.w3.org/TR/SVGTiny12 for [a b c d e f] vector notation
+ t.Logf("accumulated: transform=\"matrix(%v %v %v %v %v %v)\"", a, b, c, d, e, f)
+ if !(a == 0.1 && d == -0.1 && f == 630) {
+ t.Error("mismatch expected transform matrix(0.1 0 0 -0.1 0 630)")
+ }
+}
+
+func Test2Curves(t *testing.T) {
+ content := `
+
+
+ `
+ s, err := ParseSvg(content, "heart", 1)
+ if err != nil {
+ t.Fatalf("cannot parse svg %v", content)
+ }
+ dis, _ := s.ParseDrawingInstructions()
+ strux := []*DrawingInstruction{}
+ for di := range dis {
+ strux = append(strux, di)
+ }
+ curveIdx := 2
+ di := strux[curveIdx]
+ if di.Kind != CurveInstruction {
+ t.Fatalf("expect curve drawing instructions, got %v", di)
+ }
+ p := di.CurvePoints // 400 100 775 100 700 200
+ if !(p.C1[0] == 400 && p.C2[0] == 775 && p.T[0] == 700) {
+ t.Fatalf("expect [400 100] [775 100] [700 200], got %v %v %v", *p.C1, *p.C2, *p.T)
+ }
+}
+
+func TestPathRelScale(t *testing.T) {
+ content := `
+
+
+ `
+ s, err := ParseSvg(content, "", 1)
+ if err != nil {
+ t.Fatalf("cannot parse svg %v", content)
+ }
+ dis, _ := s.ParseDrawingInstructions()
+ strux := []*DrawingInstruction{}
+ for di := range dis {
+ strux = append(strux, di)
+ }
+ curveIdx := 1 // Move Curve*2 Line Close Paint
+ di := strux[curveIdx]
+ if di.Kind != CurveInstruction {
+ t.Fatalf("expect curve (c) instruction at %v, got %v", curveIdx, di)
+ }
+ if di.CurvePoints.T[1] != -200 { // [100+300, 200+0] scale by [1, -1]
+ t.Fatalf("expect 1st curve terminating at [400, -200], got %v",
+ *di.CurvePoints.T)
+ }
+}
+
+func TestPathRelTranslate(t *testing.T) {
+ content := `
+
+
+ `
+ s, err := ParseSvg(content, "", 1)
+ if err != nil {
+ t.Fatalf("cannot parse svg %v", content)
+ }
+ dis, errChan := s.ParseDrawingInstructions()
+ for e := range errChan {
+ t.Fatalf("parse drawing instruction: %v", e)
+ }
+ strux := []*DrawingInstruction{}
+ for di := range dis {
+ strux = append(strux, di)
+ }
+ c1 := strux[1]
+ c3 := strux[3]
+ if c1.CurvePoints.C1[1] != 816 || c3.CurvePoints.T[1] != 591 {
+ expect := "C95 816 33 764 9 691 C2 669 1 660 1 629 C1 605 1.2 601 3 591"
+ got := fmt.Sprintf("C%v %v ... %v", c1.CurvePoints.C1[0],
+ c1.CurvePoints.C1[1], c3.CurvePoints.T[1])
+ t.Fatalf("expect %v: got %v", expect, got)
+ }
+}
+
+func TestParsedPathString(t *testing.T) {
+ header := `
+
+
+ `
+ content := header + path + tail
+ s, err := ParseSvg(content, "heart", 1)
+ if err != nil {
+ t.Fatalf("cannot parse svg %v", content)
+ }
+ dis, errChan := s.ParseDrawingInstructions()
+ for e := range errChan {
+ t.Fatalf("drawing instruction error: %v", e)
+ }
+ strux := []*DrawingInstruction{}
+ for di := range dis {
+ strux = append(strux, di)
+ }
+ tmpdir := os.TempDir() + string(os.PathSeparator)
+ f0 := tmpdir + "heart.svg"
+ f, err := os.Create(f0)
+ if err != nil {
+ t.Errorf("error %v", err)
+ }
+ f.WriteString(content)
+ f.Close()
+ t.Logf("original shape in %v", f0)
+
+ parsed := PathStringFromDrawingInstructions(strux)
+ f1 := tmpdir + "parsedheart.svg"
+ f, err = os.Create(f1)
+ if err != nil {
+ t.Errorf("error %v", err)
+ }
+ f.WriteString(header + parsed + tail)
+ f.Close()
+ t.Logf("parsed shape in %v", f1)
+ t.Log("Please check consistency of above files, with web browser or eog")
+}
diff --git a/path.go b/path.go
index 15c9249..13d2ead 100644
--- a/path.go
+++ b/path.go
@@ -695,12 +695,19 @@ func (pdp *pathDescriptionParser) parseCurveToRelDI() error {
tuples = append(tuples, t)
pdp.lex.ConsumeWhiteSpace()
}
- x, y := pdp.transform.Apply(pdp.x, pdp.y)
-
+ for j := 0; j < len(tuples)/3; j++ { // convert to absolute
+ j3 := j * 3
+ for i := 0; i < 3; i++ {
+ tuples[j3+i][0] += pdp.x
+ tuples[j3+i][1] += pdp.y
+ }
+ t := tuples[j3+2]
+ pdp.x, pdp.y = t[0], t[1]
+ }
for j := 0; j < len(tuples)/3; j++ {
- c1x, c1y := pdp.transform.Apply(x+tuples[j*3][0], y+tuples[j*3][1])
- c2x, c2y := pdp.transform.Apply(x+tuples[j*3+1][0], y+tuples[j*3+1][1])
- tx, ty := pdp.transform.Apply(x+tuples[j*3+2][0], y+tuples[j*3+2][1])
+ c1x, c1y := pdp.transform.Apply(tuples[j*3][0], tuples[j*3][1])
+ c2x, c2y := pdp.transform.Apply(tuples[j*3+1][0], tuples[j*3+1][1])
+ tx, ty := pdp.transform.Apply(tuples[j*3+2][0], tuples[j*3+2][1])
pdp.p.instructions <- &DrawingInstruction{
Kind: CurveInstruction,
@@ -709,10 +716,6 @@ func (pdp *pathDescriptionParser) parseCurveToRelDI() error {
T: &Tuple{tx, ty},
},
}
-
- pdp.x += tuples[j*3+2][0]
- pdp.y += tuples[j*3+2][1]
- x, y = pdp.transform.Apply(pdp.x, pdp.y)
}
return nil
@@ -773,10 +776,7 @@ func (pdp *pathDescriptionParser) parseCurveToRel() error {
}
func (pdp *pathDescriptionParser) parseCurveToAbsDI() error {
- var (
- tuples []Tuple
- instrTuples []Tuple
- )
+ var tuples []Tuple
pdp.lex.ConsumeWhiteSpace()
for pdp.lex.PeekItem().Type == gl.ItemNumber {
@@ -790,6 +790,7 @@ func (pdp *pathDescriptionParser) parseCurveToAbsDI() error {
}
for j := 0; j < len(tuples)/3; j++ {
+ instrTuples := []Tuple{}
for _, nt := range tuples[j*3 : (j+1)*3] {
pdp.x = nt[0]
pdp.y = nt[1]
@@ -812,10 +813,7 @@ func (pdp *pathDescriptionParser) parseCurveToAbsDI() error {
}
func (pdp *pathDescriptionParser) parseCurveToAbs() error {
- var (
- tuples []Tuple
- instrTuples []Tuple
- )
+ var tuples []Tuple
pdp.lex.ConsumeWhiteSpace()
for pdp.lex.PeekItem().Type == gl.ItemNumber {
@@ -832,6 +830,7 @@ func (pdp *pathDescriptionParser) parseCurveToAbs() error {
pdp.currentsegment.addPoint([2]float64{x, y})
for j := 0; j < len(tuples)/3; j++ {
+ instrTuples := []Tuple{}
var cb cubicBezier
cb.controlpoints[0][0] = pdp.x
cb.controlpoints[0][1] = pdp.y