icon_cursor.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // Copyright 2017 The oksvg Authors. All rights reserved.
  2. // created: 2/12/2017 by S.R.Wiley
  3. //
  4. // utils.go implements translation of an SVG2.0 path into a rasterx Path.
  5. package oksvg
  6. import (
  7. "encoding/xml"
  8. "errors"
  9. "fmt"
  10. "image/color"
  11. "log"
  12. "math"
  13. "strings"
  14. "github.com/srwiley/rasterx"
  15. )
  16. // IconCursor is used while parsing SVG files.
  17. type IconCursor struct {
  18. PathCursor
  19. icon *SvgIcon
  20. StyleStack []PathStyle
  21. grad *rasterx.Gradient
  22. inTitleText, inDescText, inGrad, inDefs, inDefsStyle bool
  23. currentDef []definition
  24. }
  25. // ReadGradURL reads an SVG format gradient url
  26. // Since the context of the gradient can affect the colors
  27. // the current fill or line color is passed in and used in
  28. // the case of a nil stopClor value
  29. func (c *IconCursor) ReadGradURL(v string, defaultColor interface{}) (grad rasterx.Gradient, ok bool) {
  30. if strings.HasPrefix(v, "url(") && strings.HasSuffix(v, ")") {
  31. urlStr := strings.TrimSpace(v[4 : len(v)-1])
  32. if strings.HasPrefix(urlStr, "#") {
  33. var g *rasterx.Gradient
  34. g, ok = c.icon.Grads[urlStr[1:]]
  35. if ok {
  36. grad = localizeGradIfStopClrNil(g, defaultColor)
  37. }
  38. }
  39. }
  40. return
  41. }
  42. // ReadGradAttr reads an SVG gradient attribute
  43. func (c *IconCursor) ReadGradAttr(attr xml.Attr) (err error) {
  44. switch attr.Name.Local {
  45. case "gradientTransform":
  46. c.grad.Matrix, err = c.parseTransform(attr.Value)
  47. case "gradientUnits":
  48. switch strings.TrimSpace(attr.Value) {
  49. case "userSpaceOnUse":
  50. c.grad.Units = rasterx.UserSpaceOnUse
  51. case "objectBoundingBox":
  52. c.grad.Units = rasterx.ObjectBoundingBox
  53. }
  54. case "spreadMethod":
  55. switch strings.TrimSpace(attr.Value) {
  56. case "pad":
  57. c.grad.Spread = rasterx.PadSpread
  58. case "reflect":
  59. c.grad.Spread = rasterx.ReflectSpread
  60. case "repeat":
  61. c.grad.Spread = rasterx.RepeatSpread
  62. }
  63. }
  64. return
  65. }
  66. // PushStyle parses the style element, and push it on the style stack. Only color and opacity are supported
  67. // for fill. Note that this parses both the contents of a style attribute plus
  68. // direct fill and opacity attributes.
  69. func (c *IconCursor) PushStyle(attrs []xml.Attr) error {
  70. var pairs []string
  71. className := ""
  72. for _, attr := range attrs {
  73. switch strings.ToLower(attr.Name.Local) {
  74. case "style":
  75. pairs = append(pairs, strings.Split(attr.Value, ";")...)
  76. case "class":
  77. className = attr.Value
  78. default:
  79. pairs = append(pairs, attr.Name.Local+":"+attr.Value)
  80. }
  81. }
  82. // Make a copy of the top style
  83. curStyle := c.StyleStack[len(c.StyleStack)-1]
  84. for _, pair := range pairs {
  85. kv := strings.Split(pair, ":")
  86. if len(kv) >= 2 {
  87. k := strings.ToLower(kv[0])
  88. k = strings.TrimSpace(k)
  89. v := strings.TrimSpace(kv[1])
  90. err := c.readStyleAttr(&curStyle, k, v)
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. }
  96. c.adaptClasses(&curStyle, className)
  97. c.StyleStack = append(c.StyleStack, curStyle) // Push style onto stack
  98. return nil
  99. }
  100. func (c *IconCursor) readTransformAttr(m1 rasterx.Matrix2D, k string) (rasterx.Matrix2D, error) {
  101. ln := len(c.points)
  102. switch k {
  103. case "rotate":
  104. if ln == 1 {
  105. m1 = m1.Rotate(c.points[0] * math.Pi / 180)
  106. } else if ln == 3 {
  107. m1 = m1.Translate(c.points[1], c.points[2]).
  108. Rotate(c.points[0]*math.Pi/180).
  109. Translate(-c.points[1], -c.points[2])
  110. } else {
  111. return m1, errParamMismatch
  112. }
  113. case "translate":
  114. if ln == 1 {
  115. m1 = m1.Translate(c.points[0], 0)
  116. } else if ln == 2 {
  117. m1 = m1.Translate(c.points[0], c.points[1])
  118. } else {
  119. return m1, errParamMismatch
  120. }
  121. case "skewx":
  122. if ln == 1 {
  123. m1 = m1.SkewX(c.points[0] * math.Pi / 180)
  124. } else {
  125. return m1, errParamMismatch
  126. }
  127. case "skewy":
  128. if ln == 1 {
  129. m1 = m1.SkewY(c.points[0] * math.Pi / 180)
  130. } else {
  131. return m1, errParamMismatch
  132. }
  133. case "scale":
  134. if ln == 1 {
  135. m1 = m1.Scale(c.points[0], 0)
  136. } else if ln == 2 {
  137. m1 = m1.Scale(c.points[0], c.points[1])
  138. } else {
  139. return m1, errParamMismatch
  140. }
  141. case "matrix":
  142. if ln == 6 {
  143. m1 = m1.Mult(rasterx.Matrix2D{
  144. A: c.points[0],
  145. B: c.points[1],
  146. C: c.points[2],
  147. D: c.points[3],
  148. E: c.points[4],
  149. F: c.points[5]})
  150. } else {
  151. return m1, errParamMismatch
  152. }
  153. default:
  154. return m1, errParamMismatch
  155. }
  156. return m1, nil
  157. }
  158. func (c *IconCursor) parseTransform(v string) (rasterx.Matrix2D, error) {
  159. ts := strings.Split(v, ")")
  160. m1 := c.StyleStack[len(c.StyleStack)-1].mAdder.M
  161. for _, t := range ts {
  162. t = strings.TrimSpace(t)
  163. if len(t) == 0 {
  164. continue
  165. }
  166. d := strings.Split(t, "(")
  167. if len(d) != 2 || len(d[1]) < 1 {
  168. return m1, errParamMismatch // badly formed transformation
  169. }
  170. err := c.GetPoints(d[1])
  171. if err != nil {
  172. return m1, err
  173. }
  174. m1, err = c.readTransformAttr(m1, strings.ToLower(strings.TrimSpace(d[0])))
  175. if err != nil {
  176. return m1, err
  177. }
  178. }
  179. return m1, nil
  180. }
  181. func (c *IconCursor) readStyleAttr(curStyle *PathStyle, k, v string) error {
  182. switch k {
  183. case "fill":
  184. gradient, ok := c.ReadGradURL(v, curStyle.fillerColor)
  185. if ok {
  186. curStyle.fillerColor = gradient
  187. break
  188. }
  189. var err error
  190. curStyle.fillerColor, err = ParseSVGColor(v)
  191. return err
  192. case "stroke":
  193. gradient, ok := c.ReadGradURL(v, curStyle.linerColor)
  194. if ok {
  195. curStyle.linerColor = gradient
  196. break
  197. }
  198. col, errc := ParseSVGColor(v)
  199. if errc != nil {
  200. return errc
  201. }
  202. if col != nil {
  203. curStyle.linerColor = col.(color.NRGBA)
  204. } else {
  205. curStyle.linerColor = nil
  206. }
  207. case "stroke-linegap":
  208. switch v {
  209. case "flat":
  210. curStyle.LineGap = rasterx.FlatGap
  211. case "round":
  212. curStyle.LineGap = rasterx.RoundGap
  213. case "cubic":
  214. curStyle.LineGap = rasterx.CubicGap
  215. case "quadratic":
  216. curStyle.LineGap = rasterx.QuadraticGap
  217. }
  218. case "stroke-leadlinecap":
  219. switch v {
  220. case "butt":
  221. curStyle.LeadLineCap = rasterx.ButtCap
  222. case "round":
  223. curStyle.LeadLineCap = rasterx.RoundCap
  224. case "square":
  225. curStyle.LeadLineCap = rasterx.SquareCap
  226. case "cubic":
  227. curStyle.LeadLineCap = rasterx.CubicCap
  228. case "quadratic":
  229. curStyle.LeadLineCap = rasterx.QuadraticCap
  230. }
  231. case "stroke-linecap":
  232. switch v {
  233. case "butt":
  234. curStyle.LineCap = rasterx.ButtCap
  235. case "round":
  236. curStyle.LineCap = rasterx.RoundCap
  237. case "square":
  238. curStyle.LineCap = rasterx.SquareCap
  239. case "cubic":
  240. curStyle.LineCap = rasterx.CubicCap
  241. case "quadratic":
  242. curStyle.LineCap = rasterx.QuadraticCap
  243. }
  244. case "stroke-linejoin":
  245. switch v {
  246. case "miter":
  247. curStyle.LineJoin = rasterx.Miter
  248. case "miter-clip":
  249. curStyle.LineJoin = rasterx.MiterClip
  250. case "arc-clip":
  251. curStyle.LineJoin = rasterx.ArcClip
  252. case "round":
  253. curStyle.LineJoin = rasterx.Round
  254. case "arc":
  255. curStyle.LineJoin = rasterx.Arc
  256. case "bevel":
  257. curStyle.LineJoin = rasterx.Bevel
  258. }
  259. case "stroke-miterlimit":
  260. mLimit, err := parseFloat(v, 64)
  261. if err != nil {
  262. return err
  263. }
  264. curStyle.MiterLimit = mLimit
  265. case "stroke-width":
  266. width, err := parseFloat(v, 64)
  267. if err != nil {
  268. return err
  269. }
  270. curStyle.LineWidth = width
  271. case "stroke-dashoffset":
  272. dashOffset, err := parseFloat(v, 64)
  273. if err != nil {
  274. return err
  275. }
  276. curStyle.DashOffset = dashOffset
  277. case "stroke-dasharray":
  278. if v != "none" {
  279. dashes := splitOnCommaOrSpace(v)
  280. dList := make([]float64, len(dashes))
  281. for i, dstr := range dashes {
  282. d, err := parseFloat(strings.TrimSpace(dstr), 64)
  283. if err != nil {
  284. return err
  285. }
  286. dList[i] = d
  287. }
  288. curStyle.Dash = dList
  289. break
  290. }
  291. case "opacity", "stroke-opacity", "fill-opacity":
  292. op, err := parseFloat(v, 64)
  293. if err != nil {
  294. return err
  295. }
  296. if k != "stroke-opacity" {
  297. curStyle.FillOpacity *= op
  298. }
  299. if k != "fill-opacity" {
  300. curStyle.LineOpacity *= op
  301. }
  302. case "transform":
  303. m, err := c.parseTransform(v)
  304. if err != nil {
  305. return err
  306. }
  307. curStyle.mAdder.M = m
  308. }
  309. return nil
  310. }
  311. func (c *IconCursor) readStartElement(se xml.StartElement) (err error) {
  312. var skipDef bool
  313. if se.Name.Local == "radialGradient" || se.Name.Local == "linearGradient" || c.inGrad {
  314. skipDef = true
  315. }
  316. if c.inDefs && !skipDef {
  317. ID := ""
  318. for _, attr := range se.Attr {
  319. if attr.Name.Local == "id" {
  320. ID = attr.Value
  321. }
  322. }
  323. if ID != "" && len(c.currentDef) > 0 {
  324. c.icon.Defs[c.currentDef[0].ID] = c.currentDef
  325. c.currentDef = make([]definition, 0)
  326. }
  327. c.currentDef = append(c.currentDef, definition{
  328. ID: ID,
  329. Tag: se.Name.Local,
  330. Attrs: se.Attr,
  331. })
  332. return nil
  333. }
  334. df, ok := drawFuncs[se.Name.Local]
  335. if !ok {
  336. errStr := "Cannot process svg element " + se.Name.Local
  337. if c.returnError(errStr) {
  338. return errors.New(errStr)
  339. }
  340. return nil
  341. }
  342. err = df(c, se.Attr)
  343. if err != nil {
  344. e := fmt.Sprintf("error during processing svg element %s: %s", se.Name.Local, err.Error())
  345. if c.returnError(e) {
  346. err = errors.New(e)
  347. }
  348. err = nil
  349. }
  350. if len(c.Path) > 0 {
  351. //The cursor parsed a path from the xml element
  352. pathCopy := make(rasterx.Path, len(c.Path))
  353. copy(pathCopy, c.Path)
  354. c.icon.SVGPaths = append(c.icon.SVGPaths,
  355. SvgPath{c.StyleStack[len(c.StyleStack)-1], pathCopy})
  356. c.Path = c.Path[:0]
  357. }
  358. return
  359. }
  360. func (c *IconCursor) adaptClasses(pathStyle *PathStyle, className string) {
  361. if className == "" || len(c.icon.classes) == 0 {
  362. return
  363. }
  364. for k, v := range c.icon.classes[className] {
  365. c.readStyleAttr(pathStyle, k, v)
  366. }
  367. }
  368. func (c *IconCursor) returnError(errMsg string) bool {
  369. if c.ErrorMode == StrictErrorMode {
  370. return true
  371. }
  372. if c.ErrorMode == WarnErrorMode {
  373. log.Println(errMsg)
  374. }
  375. return false
  376. }