| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- // 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 (
- "encoding/xml"
- "errors"
- "log"
- "strings"
- "github.com/srwiley/rasterx"
- "golang.org/x/image/math/fixed"
- )
- // svgFunc defines function interface to use as drawing implementation.
- type svgFunc func(c *IconCursor, attrs []xml.Attr) error
- var (
- drawFuncs = map[string]svgFunc{
- "svg": svgF,
- "g": gF,
- "line": lineF,
- "stop": stopF,
- "rect": rectF,
- "circle": circleF,
- "ellipse": circleF, //circleF handles ellipse also
- "polyline": polylineF,
- "polygon": polygonF,
- "path": pathF,
- "desc": descF,
- "defs": defsF,
- "style": styleF,
- "title": titleF,
- "linearGradient": linearGradientF,
- "radialGradient": radialGradientF,
- }
- svgF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.icon.ViewBox.X = 0
- c.icon.ViewBox.Y = 0
- c.icon.ViewBox.W = 0
- c.icon.ViewBox.H = 0
- var width, height float64
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "viewBox":
- err = c.GetPoints(attr.Value)
- if len(c.points) != 4 {
- return errParamMismatch
- }
- c.icon.ViewBox.X = c.points[0]
- c.icon.ViewBox.Y = c.points[1]
- c.icon.ViewBox.W = c.points[2]
- c.icon.ViewBox.H = c.points[3]
- case "width":
- width, err = parseFloat(attr.Value, 64)
- case "height":
- height, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- if c.icon.ViewBox.W == 0 {
- c.icon.ViewBox.W = width
- }
- if c.icon.ViewBox.H == 0 {
- c.icon.ViewBox.H = height
- }
- return nil
- }
- gF svgFunc = func(*IconCursor, []xml.Attr) error { return nil } // g does nothing but push the style
- rectF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var x, y, w, h, rx, ry float64
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "x":
- x, err = parseFloat(attr.Value, 64)
- case "y":
- y, err = parseFloat(attr.Value, 64)
- case "width":
- w, err = parseFloat(attr.Value, 64)
- case "height":
- h, err = parseFloat(attr.Value, 64)
- case "rx":
- rx, err = parseFloat(attr.Value, 64)
- case "ry":
- ry, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- if w == 0 || h == 0 {
- return nil
- }
- rasterx.AddRoundRect(x, y, w+x, h+y, rx, ry, 0, rasterx.RoundGap, &c.Path)
- return nil
- }
- circleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var cx, cy, rx, ry float64
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "cx":
- cx, err = parseFloat(attr.Value, 64)
- case "cy":
- cy, err = parseFloat(attr.Value, 64)
- case "r":
- rx, err = parseFloat(attr.Value, 64)
- ry = rx
- case "rx":
- rx, err = parseFloat(attr.Value, 64)
- case "ry":
- ry, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- if rx == 0 || ry == 0 { // not drawn, but not an error
- return nil
- }
- c.EllipseAt(cx, cy, rx, ry)
- return nil
- }
- lineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var x1, x2, y1, y2 float64
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "x1":
- x1, err = parseFloat(attr.Value, 64)
- case "x2":
- x2, err = parseFloat(attr.Value, 64)
- case "y1":
- y1, err = parseFloat(attr.Value, 64)
- case "y2":
- y2, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- c.Path.Start(fixed.Point26_6{
- X: fixed.Int26_6((x1) * 64),
- Y: fixed.Int26_6((y1) * 64)})
- c.Path.Line(fixed.Point26_6{
- X: fixed.Int26_6((x2) * 64),
- Y: fixed.Int26_6((y2) * 64)})
- return nil
- }
- polylineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "points":
- err = c.GetPoints(attr.Value)
- if len(c.points)%2 != 0 {
- return errors.New("polygon has odd number of points")
- }
- }
- if err != nil {
- return err
- }
- }
- if len(c.points) > 4 {
- c.Path.Start(fixed.Point26_6{
- X: fixed.Int26_6((c.points[0]) * 64),
- Y: fixed.Int26_6((c.points[1]) * 64)})
- for i := 2; i < len(c.points)-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)})
- }
- }
- return nil
- }
- polygonF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- err := polylineF(c, attrs)
- if len(c.points) > 4 {
- c.Path.Stop(true)
- }
- return err
- }
- pathF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "d":
- err = c.CompilePath(attr.Value)
- }
- if err != nil {
- return err
- }
- }
- return nil
- }
- descF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.inDescText = true
- c.icon.Descriptions = append(c.icon.Descriptions, "")
- return nil
- }
- titleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.inTitleText = true
- c.icon.Titles = append(c.icon.Titles, "")
- return nil
- }
- defsF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.inDefs = true
- return nil
- }
- styleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.inDefsStyle = true
- return nil
- }
- linearGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var err error
- c.inGrad = true
- c.grad = &rasterx.Gradient{Points: [5]float64{0, 0, 1, 0, 0},
- IsRadial: false, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "id":
- id := attr.Value
- if len(id) >= 0 {
- c.icon.Grads[id] = c.grad
- } else {
- return errZeroLengthID
- }
- case "x1":
- c.grad.Points[0], err = readFraction(attr.Value)
- case "y1":
- c.grad.Points[1], err = readFraction(attr.Value)
- case "x2":
- c.grad.Points[2], err = readFraction(attr.Value)
- case "y2":
- c.grad.Points[3], err = readFraction(attr.Value)
- default:
- err = c.ReadGradAttr(attr)
- }
- if err != nil {
- return err
- }
- }
- return nil
- }
- radialGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- c.inGrad = true
- c.grad = &rasterx.Gradient{Points: [5]float64{0.5, 0.5, 0.5, 0.5, 0.5},
- IsRadial: true, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
- var setFx, setFy bool
- var err error
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "id":
- id := attr.Value
- if len(id) >= 0 {
- c.icon.Grads[id] = c.grad
- } else {
- return errZeroLengthID
- }
- case "r":
- c.grad.Points[4], err = readFraction(attr.Value)
- case "cx":
- c.grad.Points[0], err = readFraction(attr.Value)
- case "cy":
- c.grad.Points[1], err = readFraction(attr.Value)
- case "fx":
- setFx = true
- c.grad.Points[2], err = readFraction(attr.Value)
- case "fy":
- setFy = true
- c.grad.Points[3], err = readFraction(attr.Value)
- default:
- err = c.ReadGradAttr(attr)
- }
- if err != nil {
- return err
- }
- }
- if !setFx { // set fx to cx by default
- c.grad.Points[2] = c.grad.Points[0]
- }
- if !setFy { // set fy to cy by default
- c.grad.Points[3] = c.grad.Points[1]
- }
- return nil
- }
- stopF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var err error
- if c.inGrad {
- stop := rasterx.GradStop{Opacity: 1.0}
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "offset":
- stop.Offset, err = readFraction(attr.Value)
- case "stop-color":
- //todo: add current color inherit
- stop.StopColor, err = ParseSVGColor(attr.Value)
- case "stop-opacity":
- stop.Opacity, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- c.grad.Stops = append(c.grad.Stops, stop)
- }
- return nil
- }
- useF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
- var (
- href string
- x, y float64
- err error
- )
- for _, attr := range attrs {
- switch attr.Name.Local {
- case "href":
- href = attr.Value
- case "x":
- x, err = parseFloat(attr.Value, 64)
- case "y":
- y, err = parseFloat(attr.Value, 64)
- }
- if err != nil {
- return err
- }
- }
- // Translate the Style adder matrix by use's x and y
- c.StyleStack[len(c.StyleStack)-1].mAdder.M =
- c.StyleStack[len(c.StyleStack)-1].mAdder.M.Translate(x, y)
- if href == "" {
- return errors.New("only use tags with href is supported")
- }
- if !strings.HasPrefix(href, "#") {
- return errors.New("only the ID CSS selector is supported")
- }
- defs, ok := c.icon.Defs[href[1:]]
- if !ok {
- return errors.New("href ID in use statement was not found in saved defs")
- }
- for _, def := range defs {
- if def.Tag == "endg" {
- // pop style
- c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
- continue
- }
- if err = c.PushStyle(def.Attrs); err != nil {
- return err
- }
- df, ok := drawFuncs[def.Tag]
- if !ok {
- errStr := "Cannot process svg element " + def.Tag
- if c.ErrorMode == StrictErrorMode {
- return errors.New(errStr)
- } else if c.ErrorMode == WarnErrorMode {
- log.Println(errStr)
- }
- return nil
- }
- if err := df(c, def.Attrs); err != nil {
- return err
- }
- //Did c.Path get added to during the drawFunction call iteration?
- if len(c.Path) > 0 {
- //The cursor parsed a path from the xml element
- pathCopy := make(rasterx.Path, len(c.Path))
- copy(pathCopy, c.Path)
- c.icon.SVGPaths = append(c.icon.SVGPaths, SvgPath{c.StyleStack[len(c.StyleStack)-1], pathCopy})
- c.Path = c.Path[:0]
- }
- if def.Tag != "g" {
- // pop style
- c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
- }
- }
- return nil
- }
- )
- func init() {
- // avoids cyclical static declaration
- // called on package initialization
- drawFuncs["use"] = useF
- }
|