| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- // Copyright 2017 The oksvg Authors. All rights reserved.
- // created: 2/12/2017 by S.R.Wiley
- //
- // utils.go implements translation of an SVG2.0 path into a rasterx Path.
- package oksvg
- import (
- "errors"
- "log"
- "math"
- "unicode"
- "github.com/srwiley/rasterx"
- "golang.org/x/image/math/fixed"
- )
- type (
- // ErrorMode is the for setting how the parser reacts to unparsed elements
- ErrorMode uint8
- // PathCursor is used to parse SVG format path strings into a rasterx Path
- PathCursor struct {
- rasterx.Path
- placeX, placeY float64
- cntlPtX, cntlPtY float64
- pathStartX, pathStartY float64
- points []float64
- lastKey uint8
- ErrorMode ErrorMode
- inPath bool
- }
- )
- const (
- // IgnoreErrorMode skips un-parsed SVG elements.
- IgnoreErrorMode ErrorMode = iota
- // WarnErrorMode outputs a warning when an un-parsed SVG element is found.
- WarnErrorMode
- // StrictErrorMode causes an error when an un-parsed SVG element is found.
- StrictErrorMode
- )
- var (
- errParamMismatch = errors.New("param mismatch")
- errCommandUnknown = errors.New("unknown command")
- errZeroLengthID = errors.New("zero length id")
- )
- // ReadFloat reads a floating point value and adds it to the cursor's points slice.
- func (c *PathCursor) ReadFloat(numStr string) error {
- last := 0
- isFirst := true
- for i, n := range numStr {
- if n == '.' {
- if isFirst {
- isFirst = false
- continue
- }
- f, err := parseFloat(numStr[last:i], 64)
- if err != nil {
- return err
- }
- c.points = append(c.points, f)
- last = i
- }
- }
- f, err := parseFloat(numStr[last:], 64)
- if err != nil {
- return err
- }
- c.points = append(c.points, f)
- return nil
- }
- // GetPoints reads a set of floating point values from the SVG format number string,
- // and add them to the cursor's points slice.
- func (c *PathCursor) GetPoints(dataPoints string) error {
- lastIndex := -1
- c.points = c.points[0:0]
- lr := ' '
- for i, r := range dataPoints {
- if !unicode.IsNumber(r) && r != '.' && !(r == '-' && lr == 'e') && r != 'e' {
- if lastIndex != -1 {
- if err := c.ReadFloat(dataPoints[lastIndex:i]); err != nil {
- return err
- }
- }
- if r == '-' {
- lastIndex = i
- } else {
- lastIndex = -1
- }
- } else if lastIndex == -1 {
- lastIndex = i
- }
- lr = r
- }
- if lastIndex != -1 && lastIndex != len(dataPoints) {
- if err := c.ReadFloat(dataPoints[lastIndex:]); err != nil {
- return err
- }
- }
- return nil
- }
- // EllipseAt adds a path of an elipse centered at cx, cy of radius rx and ry
- // to the PathCursor
- func (c *PathCursor) EllipseAt(cx, cy, rx, ry float64) {
- c.placeX, c.placeY = cx+rx, cy
- c.points = c.points[0:0]
- c.points = append(c.points, rx, ry, 0.0, 1.0, 0.0, c.placeX, c.placeY)
- c.Path.Start(fixed.Point26_6{
- X: fixed.Int26_6(c.placeX * 64),
- Y: fixed.Int26_6(c.placeY * 64)})
- c.placeX, c.placeY = rasterx.AddArc(c.points, cx, cy, c.placeX, c.placeY, &c.Path)
- c.Path.Stop(true)
- }
- // AddArcFromA adds a path of an arc element to the cursor path to the PathCursor
- func (c *PathCursor) AddArcFromA(points []float64) {
- cx, cy := rasterx.FindEllipseCenter(&points[0], &points[1], points[2]*math.Pi/180, c.placeX,
- c.placeY, points[5], points[6], points[4] == 0, points[3] == 0)
- c.placeX, c.placeY = rasterx.AddArc(c.points, cx, cy, c.placeX, c.placeY, &c.Path)
- }
- // CompilePath translates the svgPath description string into a rasterx path.
- // All valid SVG path elements are interpreted to rasterx equivalents.
- // The resulting path element is stored in the PathCursor.
- func (c *PathCursor) CompilePath(svgPath string) error {
- c.init()
- lastIndex := -1
- for i, v := range svgPath {
- if unicode.IsLetter(v) && v != 'e' {
- if lastIndex != -1 {
- if err := c.addSeg(svgPath[lastIndex:i]); err != nil {
- return err
- }
- }
- lastIndex = i
- }
- }
- if lastIndex != -1 {
- if err := c.addSeg(svgPath[lastIndex:]); err != nil {
- return err
- }
- }
- return nil
- }
- func reflect(px, py, rx, ry float64) (x, y float64) {
- return px*2 - rx, py*2 - ry
- }
- func (c *PathCursor) valsToAbs(last float64) {
- for i := 0; i < len(c.points); i++ {
- last += c.points[i]
- c.points[i] = last
- }
- }
- func (c *PathCursor) pointsToAbs(sz int) {
- lastX := c.placeX
- lastY := c.placeY
- for j := 0; j < len(c.points); j += sz {
- for i := 0; i < sz; i += 2 {
- c.points[i+j] += lastX
- c.points[i+1+j] += lastY
- }
- lastX = c.points[(j+sz)-2]
- lastY = c.points[(j+sz)-1]
- }
- }
- func (c *PathCursor) hasSetsOrMore(sz int, rel bool) bool {
- if !(len(c.points) >= sz && len(c.points)%sz == 0) {
- return false
- }
- if rel {
- c.pointsToAbs(sz)
- }
- return true
- }
- func (c *PathCursor) reflectControlQuad() {
- switch c.lastKey {
- case 'q', 'Q', 'T', 't':
- c.cntlPtX, c.cntlPtY = reflect(c.placeX, c.placeY, c.cntlPtX, c.cntlPtY)
- default:
- c.cntlPtX, c.cntlPtY = c.placeX, c.placeY
- }
- }
- func (c *PathCursor) reflectControlCube() {
- switch c.lastKey {
- case 'c', 'C', 's', 'S':
- c.cntlPtX, c.cntlPtY = reflect(c.placeX, c.placeY, c.cntlPtX, c.cntlPtY)
- default:
- c.cntlPtX, c.cntlPtY = c.placeX, c.placeY
- }
- }
- // addSeg decodes an SVG seqment string into equivalent raster path commands saved
- // in the cursor's Path
- func (c *PathCursor) addSeg(segString string) error {
- // Parse the string describing the numeric points in SVG format
- if err := c.GetPoints(segString[1:]); err != nil {
- return err
- }
- l := len(c.points)
- k := segString[0]
- rel := false
- switch k {
- case 'z':
- fallthrough
- case 'Z':
- if len(c.points) != 0 {
- return errParamMismatch
- }
- if c.inPath {
- c.Path.Stop(true)
- c.placeX = c.pathStartX
- c.placeY = c.pathStartY
- c.inPath = false
- }
- case 'm':
- rel = true
- fallthrough
- case 'M':
- if !c.hasSetsOrMore(2, rel) {
- return errParamMismatch
- }
- c.pathStartX, c.pathStartY = c.points[0], c.points[1]
- c.inPath = true
- c.Path.Start(fixed.Point26_6{X: fixed.Int26_6((c.pathStartX) * 64), Y: fixed.Int26_6((c.pathStartY) * 64)})
- for i := 2; i < l-1; i += 2 {
- c.Path.Line(fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64),
- Y: fixed.Int26_6((c.points[i+1]) * 64)})
- }
- c.placeX = c.points[l-2]
- c.placeY = c.points[l-1]
- case 'l':
- rel = true
- fallthrough
- case 'L':
- if !c.hasSetsOrMore(2, rel) {
- return errParamMismatch
- }
- for i := 0; i < l-1; i += 2 {
- c.Path.Line(fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64),
- Y: fixed.Int26_6((c.points[i+1]) * 64)})
- }
- c.placeX = c.points[l-2]
- c.placeY = c.points[l-1]
- case 'v':
- c.valsToAbs(c.placeY)
- fallthrough
- case 'V':
- if !c.hasSetsOrMore(1, false) {
- return errParamMismatch
- }
- for _, p := range c.points {
- c.Path.Line(fixed.Point26_6{
- X: fixed.Int26_6((c.placeX) * 64),
- Y: fixed.Int26_6((p) * 64)})
- }
- c.placeY = c.points[l-1]
- case 'h':
- c.valsToAbs(c.placeX)
- fallthrough
- case 'H':
- if !c.hasSetsOrMore(1, false) {
- return errParamMismatch
- }
- for _, p := range c.points {
- c.Path.Line(fixed.Point26_6{
- X: fixed.Int26_6((p) * 64),
- Y: fixed.Int26_6((c.placeY) * 64)})
- }
- c.placeX = c.points[l-1]
- case 'q':
- rel = true
- fallthrough
- case 'Q':
- if !c.hasSetsOrMore(4, rel) {
- return errParamMismatch
- }
- for i := 0; i < l-3; i += 4 {
- c.Path.QuadBezier(
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64),
- Y: fixed.Int26_6((c.points[i+1]) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i+2]) * 64),
- Y: fixed.Int26_6((c.points[i+3]) * 64)})
- }
- c.cntlPtX, c.cntlPtY = c.points[l-4], c.points[l-3]
- c.placeX = c.points[l-2]
- c.placeY = c.points[l-1]
- case 't':
- rel = true
- fallthrough
- case 'T':
- if !c.hasSetsOrMore(2, rel) {
- return errParamMismatch
- }
- for i := 0; i < l-1; i += 2 {
- c.reflectControlQuad()
- c.Path.QuadBezier(
- fixed.Point26_6{
- X: fixed.Int26_6((c.cntlPtX) * 64),
- Y: fixed.Int26_6((c.cntlPtY) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64),
- Y: fixed.Int26_6((c.points[i+1]) * 64)})
- c.lastKey = k
- c.placeX = c.points[i]
- c.placeY = c.points[i+1]
- }
- case 'c':
- rel = true
- fallthrough
- case 'C':
- if !c.hasSetsOrMore(6, rel) {
- return errParamMismatch
- }
- for i := 0; i < l-5; i += 6 {
- c.Path.CubeBezier(
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64),
- Y: fixed.Int26_6((c.points[i+1]) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i+2]) * 64),
- Y: fixed.Int26_6((c.points[i+3]) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i+4]) * 64),
- Y: fixed.Int26_6((c.points[i+5]) * 64)})
- }
- c.cntlPtX, c.cntlPtY = c.points[l-4], c.points[l-3]
- c.placeX = c.points[l-2]
- c.placeY = c.points[l-1]
- case 's':
- rel = true
- fallthrough
- case 'S':
- if !c.hasSetsOrMore(4, rel) {
- return errParamMismatch
- }
- for i := 0; i < l-3; i += 4 {
- c.reflectControlCube()
- c.Path.CubeBezier(fixed.Point26_6{
- X: fixed.Int26_6((c.cntlPtX) * 64), Y: fixed.Int26_6((c.cntlPtY) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i]) * 64), Y: fixed.Int26_6((c.points[i+1]) * 64)},
- fixed.Point26_6{
- X: fixed.Int26_6((c.points[i+2]) * 64), Y: fixed.Int26_6((c.points[i+3]) * 64)})
- c.lastKey = k
- c.cntlPtX, c.cntlPtY = c.points[i], c.points[i+1]
- c.placeX = c.points[i+2]
- c.placeY = c.points[i+3]
- }
- case 'a', 'A':
- if !c.hasSetsOrMore(7, false) {
- return errParamMismatch
- }
- for i := 0; i < l-6; i += 7 {
- if k == 'a' {
- c.points[i+5] += c.placeX
- c.points[i+6] += c.placeY
- }
- c.AddArcFromA(c.points[i:])
- }
- default:
- if c.ErrorMode == StrictErrorMode {
- return errCommandUnknown
- }
- if c.ErrorMode == WarnErrorMode {
- log.Println("Ignoring svg command " + string(k))
- }
- }
- // So we know how to extend some segment types
- c.lastKey = k
- return nil
- }
- func (c *PathCursor) init() {
- c.placeX = 0.0
- c.placeY = 0.0
- c.points = c.points[0:0]
- c.lastKey = ' '
- c.Path.Clear()
- c.inPath = false
- }
|