| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- // 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"
- "fmt"
- "image/color"
- "log"
- "math"
- "strings"
- "github.com/srwiley/rasterx"
- )
- // IconCursor is used while parsing SVG files.
- type IconCursor struct {
- PathCursor
- icon *SvgIcon
- StyleStack []PathStyle
- grad *rasterx.Gradient
- inTitleText, inDescText, inGrad, inDefs, inDefsStyle bool
- currentDef []definition
- }
- // ReadGradURL reads an SVG format gradient url
- // Since the context of the gradient can affect the colors
- // the current fill or line color is passed in and used in
- // the case of a nil stopClor value
- func (c *IconCursor) ReadGradURL(v string, defaultColor interface{}) (grad rasterx.Gradient, ok bool) {
- if strings.HasPrefix(v, "url(") && strings.HasSuffix(v, ")") {
- urlStr := strings.TrimSpace(v[4 : len(v)-1])
- if strings.HasPrefix(urlStr, "#") {
- var g *rasterx.Gradient
- g, ok = c.icon.Grads[urlStr[1:]]
- if ok {
- grad = localizeGradIfStopClrNil(g, defaultColor)
- }
- }
- }
- return
- }
- // ReadGradAttr reads an SVG gradient attribute
- func (c *IconCursor) ReadGradAttr(attr xml.Attr) (err error) {
- switch attr.Name.Local {
- case "gradientTransform":
- c.grad.Matrix, err = c.parseTransform(attr.Value)
- case "gradientUnits":
- switch strings.TrimSpace(attr.Value) {
- case "userSpaceOnUse":
- c.grad.Units = rasterx.UserSpaceOnUse
- case "objectBoundingBox":
- c.grad.Units = rasterx.ObjectBoundingBox
- }
- case "spreadMethod":
- switch strings.TrimSpace(attr.Value) {
- case "pad":
- c.grad.Spread = rasterx.PadSpread
- case "reflect":
- c.grad.Spread = rasterx.ReflectSpread
- case "repeat":
- c.grad.Spread = rasterx.RepeatSpread
- }
- }
- return
- }
- // PushStyle parses the style element, and push it on the style stack. Only color and opacity are supported
- // for fill. Note that this parses both the contents of a style attribute plus
- // direct fill and opacity attributes.
- func (c *IconCursor) PushStyle(attrs []xml.Attr) error {
- var pairs []string
- className := ""
- for _, attr := range attrs {
- switch strings.ToLower(attr.Name.Local) {
- case "style":
- pairs = append(pairs, strings.Split(attr.Value, ";")...)
- case "class":
- className = attr.Value
- default:
- pairs = append(pairs, attr.Name.Local+":"+attr.Value)
- }
- }
- // Make a copy of the top style
- curStyle := c.StyleStack[len(c.StyleStack)-1]
- for _, pair := range pairs {
- kv := strings.Split(pair, ":")
- if len(kv) >= 2 {
- k := strings.ToLower(kv[0])
- k = strings.TrimSpace(k)
- v := strings.TrimSpace(kv[1])
- err := c.readStyleAttr(&curStyle, k, v)
- if err != nil {
- return err
- }
- }
- }
- c.adaptClasses(&curStyle, className)
- c.StyleStack = append(c.StyleStack, curStyle) // Push style onto stack
- return nil
- }
- func (c *IconCursor) readTransformAttr(m1 rasterx.Matrix2D, k string) (rasterx.Matrix2D, error) {
- ln := len(c.points)
- switch k {
- case "rotate":
- if ln == 1 {
- m1 = m1.Rotate(c.points[0] * math.Pi / 180)
- } else if ln == 3 {
- m1 = m1.Translate(c.points[1], c.points[2]).
- Rotate(c.points[0]*math.Pi/180).
- Translate(-c.points[1], -c.points[2])
- } else {
- return m1, errParamMismatch
- }
- case "translate":
- if ln == 1 {
- m1 = m1.Translate(c.points[0], 0)
- } else if ln == 2 {
- m1 = m1.Translate(c.points[0], c.points[1])
- } else {
- return m1, errParamMismatch
- }
- case "skewx":
- if ln == 1 {
- m1 = m1.SkewX(c.points[0] * math.Pi / 180)
- } else {
- return m1, errParamMismatch
- }
- case "skewy":
- if ln == 1 {
- m1 = m1.SkewY(c.points[0] * math.Pi / 180)
- } else {
- return m1, errParamMismatch
- }
- case "scale":
- if ln == 1 {
- m1 = m1.Scale(c.points[0], 0)
- } else if ln == 2 {
- m1 = m1.Scale(c.points[0], c.points[1])
- } else {
- return m1, errParamMismatch
- }
- case "matrix":
- if ln == 6 {
- m1 = m1.Mult(rasterx.Matrix2D{
- A: c.points[0],
- B: c.points[1],
- C: c.points[2],
- D: c.points[3],
- E: c.points[4],
- F: c.points[5]})
- } else {
- return m1, errParamMismatch
- }
- default:
- return m1, errParamMismatch
- }
- return m1, nil
- }
- func (c *IconCursor) parseTransform(v string) (rasterx.Matrix2D, error) {
- ts := strings.Split(v, ")")
- m1 := c.StyleStack[len(c.StyleStack)-1].mAdder.M
- for _, t := range ts {
- t = strings.TrimSpace(t)
- if len(t) == 0 {
- continue
- }
- d := strings.Split(t, "(")
- if len(d) != 2 || len(d[1]) < 1 {
- return m1, errParamMismatch // badly formed transformation
- }
- err := c.GetPoints(d[1])
- if err != nil {
- return m1, err
- }
- m1, err = c.readTransformAttr(m1, strings.ToLower(strings.TrimSpace(d[0])))
- if err != nil {
- return m1, err
- }
- }
- return m1, nil
- }
- func (c *IconCursor) readStyleAttr(curStyle *PathStyle, k, v string) error {
- switch k {
- case "fill":
- gradient, ok := c.ReadGradURL(v, curStyle.fillerColor)
- if ok {
- curStyle.fillerColor = gradient
- break
- }
- var err error
- curStyle.fillerColor, err = ParseSVGColor(v)
- return err
- case "stroke":
- gradient, ok := c.ReadGradURL(v, curStyle.linerColor)
- if ok {
- curStyle.linerColor = gradient
- break
- }
- col, errc := ParseSVGColor(v)
- if errc != nil {
- return errc
- }
- if col != nil {
- curStyle.linerColor = col.(color.NRGBA)
- } else {
- curStyle.linerColor = nil
- }
- case "stroke-linegap":
- switch v {
- case "flat":
- curStyle.LineGap = rasterx.FlatGap
- case "round":
- curStyle.LineGap = rasterx.RoundGap
- case "cubic":
- curStyle.LineGap = rasterx.CubicGap
- case "quadratic":
- curStyle.LineGap = rasterx.QuadraticGap
- }
- case "stroke-leadlinecap":
- switch v {
- case "butt":
- curStyle.LeadLineCap = rasterx.ButtCap
- case "round":
- curStyle.LeadLineCap = rasterx.RoundCap
- case "square":
- curStyle.LeadLineCap = rasterx.SquareCap
- case "cubic":
- curStyle.LeadLineCap = rasterx.CubicCap
- case "quadratic":
- curStyle.LeadLineCap = rasterx.QuadraticCap
- }
- case "stroke-linecap":
- switch v {
- case "butt":
- curStyle.LineCap = rasterx.ButtCap
- case "round":
- curStyle.LineCap = rasterx.RoundCap
- case "square":
- curStyle.LineCap = rasterx.SquareCap
- case "cubic":
- curStyle.LineCap = rasterx.CubicCap
- case "quadratic":
- curStyle.LineCap = rasterx.QuadraticCap
- }
- case "stroke-linejoin":
- switch v {
- case "miter":
- curStyle.LineJoin = rasterx.Miter
- case "miter-clip":
- curStyle.LineJoin = rasterx.MiterClip
- case "arc-clip":
- curStyle.LineJoin = rasterx.ArcClip
- case "round":
- curStyle.LineJoin = rasterx.Round
- case "arc":
- curStyle.LineJoin = rasterx.Arc
- case "bevel":
- curStyle.LineJoin = rasterx.Bevel
- }
- case "stroke-miterlimit":
- mLimit, err := parseFloat(v, 64)
- if err != nil {
- return err
- }
- curStyle.MiterLimit = mLimit
- case "stroke-width":
- width, err := parseFloat(v, 64)
- if err != nil {
- return err
- }
- curStyle.LineWidth = width
- case "stroke-dashoffset":
- dashOffset, err := parseFloat(v, 64)
- if err != nil {
- return err
- }
- curStyle.DashOffset = dashOffset
- case "stroke-dasharray":
- if v != "none" {
- dashes := splitOnCommaOrSpace(v)
- dList := make([]float64, len(dashes))
- for i, dstr := range dashes {
- d, err := parseFloat(strings.TrimSpace(dstr), 64)
- if err != nil {
- return err
- }
- dList[i] = d
- }
- curStyle.Dash = dList
- break
- }
- case "opacity", "stroke-opacity", "fill-opacity":
- op, err := parseFloat(v, 64)
- if err != nil {
- return err
- }
- if k != "stroke-opacity" {
- curStyle.FillOpacity *= op
- }
- if k != "fill-opacity" {
- curStyle.LineOpacity *= op
- }
- case "transform":
- m, err := c.parseTransform(v)
- if err != nil {
- return err
- }
- curStyle.mAdder.M = m
- }
- return nil
- }
- func (c *IconCursor) readStartElement(se xml.StartElement) (err error) {
- var skipDef bool
- if se.Name.Local == "radialGradient" || se.Name.Local == "linearGradient" || c.inGrad {
- skipDef = true
- }
- if c.inDefs && !skipDef {
- ID := ""
- for _, attr := range se.Attr {
- if attr.Name.Local == "id" {
- ID = attr.Value
- }
- }
- if ID != "" && len(c.currentDef) > 0 {
- c.icon.Defs[c.currentDef[0].ID] = c.currentDef
- c.currentDef = make([]definition, 0)
- }
- c.currentDef = append(c.currentDef, definition{
- ID: ID,
- Tag: se.Name.Local,
- Attrs: se.Attr,
- })
- return nil
- }
- df, ok := drawFuncs[se.Name.Local]
- if !ok {
- errStr := "Cannot process svg element " + se.Name.Local
- if c.returnError(errStr) {
- return errors.New(errStr)
- }
- return nil
- }
- err = df(c, se.Attr)
- if err != nil {
- e := fmt.Sprintf("error during processing svg element %s: %s", se.Name.Local, err.Error())
- if c.returnError(e) {
- err = errors.New(e)
- }
- err = nil
- }
- 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]
- }
- return
- }
- func (c *IconCursor) adaptClasses(pathStyle *PathStyle, className string) {
- if className == "" || len(c.icon.classes) == 0 {
- return
- }
- for k, v := range c.icon.classes[className] {
- c.readStyleAttr(pathStyle, k, v)
- }
- }
- func (c *IconCursor) returnError(errMsg string) bool {
- if c.ErrorMode == StrictErrorMode {
- return true
- }
- if c.ErrorMode == WarnErrorMode {
- log.Println(errMsg)
- }
- return false
- }
|