draw.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. "log"
  10. "strings"
  11. "github.com/srwiley/rasterx"
  12. "golang.org/x/image/math/fixed"
  13. )
  14. // svgFunc defines function interface to use as drawing implementation.
  15. type svgFunc func(c *IconCursor, attrs []xml.Attr) error
  16. var (
  17. drawFuncs = map[string]svgFunc{
  18. "svg": svgF,
  19. "g": gF,
  20. "line": lineF,
  21. "stop": stopF,
  22. "rect": rectF,
  23. "circle": circleF,
  24. "ellipse": circleF, //circleF handles ellipse also
  25. "polyline": polylineF,
  26. "polygon": polygonF,
  27. "path": pathF,
  28. "desc": descF,
  29. "defs": defsF,
  30. "style": styleF,
  31. "title": titleF,
  32. "linearGradient": linearGradientF,
  33. "radialGradient": radialGradientF,
  34. }
  35. svgF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  36. c.icon.ViewBox.X = 0
  37. c.icon.ViewBox.Y = 0
  38. c.icon.ViewBox.W = 0
  39. c.icon.ViewBox.H = 0
  40. var width, height float64
  41. var err error
  42. for _, attr := range attrs {
  43. switch attr.Name.Local {
  44. case "viewBox":
  45. err = c.GetPoints(attr.Value)
  46. if len(c.points) != 4 {
  47. return errParamMismatch
  48. }
  49. c.icon.ViewBox.X = c.points[0]
  50. c.icon.ViewBox.Y = c.points[1]
  51. c.icon.ViewBox.W = c.points[2]
  52. c.icon.ViewBox.H = c.points[3]
  53. case "width":
  54. width, err = parseFloat(attr.Value, 64)
  55. case "height":
  56. height, err = parseFloat(attr.Value, 64)
  57. }
  58. if err != nil {
  59. return err
  60. }
  61. }
  62. if c.icon.ViewBox.W == 0 {
  63. c.icon.ViewBox.W = width
  64. }
  65. if c.icon.ViewBox.H == 0 {
  66. c.icon.ViewBox.H = height
  67. }
  68. return nil
  69. }
  70. gF svgFunc = func(*IconCursor, []xml.Attr) error { return nil } // g does nothing but push the style
  71. rectF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  72. var x, y, w, h, rx, ry float64
  73. var err error
  74. for _, attr := range attrs {
  75. switch attr.Name.Local {
  76. case "x":
  77. x, err = parseFloat(attr.Value, 64)
  78. case "y":
  79. y, err = parseFloat(attr.Value, 64)
  80. case "width":
  81. w, err = parseFloat(attr.Value, 64)
  82. case "height":
  83. h, err = parseFloat(attr.Value, 64)
  84. case "rx":
  85. rx, err = parseFloat(attr.Value, 64)
  86. case "ry":
  87. ry, err = parseFloat(attr.Value, 64)
  88. }
  89. if err != nil {
  90. return err
  91. }
  92. }
  93. if w == 0 || h == 0 {
  94. return nil
  95. }
  96. rasterx.AddRoundRect(x, y, w+x, h+y, rx, ry, 0, rasterx.RoundGap, &c.Path)
  97. return nil
  98. }
  99. circleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  100. var cx, cy, rx, ry float64
  101. var err error
  102. for _, attr := range attrs {
  103. switch attr.Name.Local {
  104. case "cx":
  105. cx, err = parseFloat(attr.Value, 64)
  106. case "cy":
  107. cy, err = parseFloat(attr.Value, 64)
  108. case "r":
  109. rx, err = parseFloat(attr.Value, 64)
  110. ry = rx
  111. case "rx":
  112. rx, err = parseFloat(attr.Value, 64)
  113. case "ry":
  114. ry, err = parseFloat(attr.Value, 64)
  115. }
  116. if err != nil {
  117. return err
  118. }
  119. }
  120. if rx == 0 || ry == 0 { // not drawn, but not an error
  121. return nil
  122. }
  123. c.EllipseAt(cx, cy, rx, ry)
  124. return nil
  125. }
  126. lineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  127. var x1, x2, y1, y2 float64
  128. var err error
  129. for _, attr := range attrs {
  130. switch attr.Name.Local {
  131. case "x1":
  132. x1, err = parseFloat(attr.Value, 64)
  133. case "x2":
  134. x2, err = parseFloat(attr.Value, 64)
  135. case "y1":
  136. y1, err = parseFloat(attr.Value, 64)
  137. case "y2":
  138. y2, err = parseFloat(attr.Value, 64)
  139. }
  140. if err != nil {
  141. return err
  142. }
  143. }
  144. c.Path.Start(fixed.Point26_6{
  145. X: fixed.Int26_6((x1) * 64),
  146. Y: fixed.Int26_6((y1) * 64)})
  147. c.Path.Line(fixed.Point26_6{
  148. X: fixed.Int26_6((x2) * 64),
  149. Y: fixed.Int26_6((y2) * 64)})
  150. return nil
  151. }
  152. polylineF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  153. var err error
  154. for _, attr := range attrs {
  155. switch attr.Name.Local {
  156. case "points":
  157. err = c.GetPoints(attr.Value)
  158. if len(c.points)%2 != 0 {
  159. return errors.New("polygon has odd number of points")
  160. }
  161. }
  162. if err != nil {
  163. return err
  164. }
  165. }
  166. if len(c.points) > 4 {
  167. c.Path.Start(fixed.Point26_6{
  168. X: fixed.Int26_6((c.points[0]) * 64),
  169. Y: fixed.Int26_6((c.points[1]) * 64)})
  170. for i := 2; i < len(c.points)-1; i += 2 {
  171. c.Path.Line(fixed.Point26_6{
  172. X: fixed.Int26_6((c.points[i]) * 64),
  173. Y: fixed.Int26_6((c.points[i+1]) * 64)})
  174. }
  175. }
  176. return nil
  177. }
  178. polygonF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  179. err := polylineF(c, attrs)
  180. if len(c.points) > 4 {
  181. c.Path.Stop(true)
  182. }
  183. return err
  184. }
  185. pathF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  186. var err error
  187. for _, attr := range attrs {
  188. switch attr.Name.Local {
  189. case "d":
  190. err = c.CompilePath(attr.Value)
  191. }
  192. if err != nil {
  193. return err
  194. }
  195. }
  196. return nil
  197. }
  198. descF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  199. c.inDescText = true
  200. c.icon.Descriptions = append(c.icon.Descriptions, "")
  201. return nil
  202. }
  203. titleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  204. c.inTitleText = true
  205. c.icon.Titles = append(c.icon.Titles, "")
  206. return nil
  207. }
  208. defsF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  209. c.inDefs = true
  210. return nil
  211. }
  212. styleF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  213. c.inDefsStyle = true
  214. return nil
  215. }
  216. linearGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  217. var err error
  218. c.inGrad = true
  219. c.grad = &rasterx.Gradient{Points: [5]float64{0, 0, 1, 0, 0},
  220. IsRadial: false, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
  221. for _, attr := range attrs {
  222. switch attr.Name.Local {
  223. case "id":
  224. id := attr.Value
  225. if len(id) >= 0 {
  226. c.icon.Grads[id] = c.grad
  227. } else {
  228. return errZeroLengthID
  229. }
  230. case "x1":
  231. c.grad.Points[0], err = readFraction(attr.Value)
  232. case "y1":
  233. c.grad.Points[1], err = readFraction(attr.Value)
  234. case "x2":
  235. c.grad.Points[2], err = readFraction(attr.Value)
  236. case "y2":
  237. c.grad.Points[3], err = readFraction(attr.Value)
  238. default:
  239. err = c.ReadGradAttr(attr)
  240. }
  241. if err != nil {
  242. return err
  243. }
  244. }
  245. return nil
  246. }
  247. radialGradientF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  248. c.inGrad = true
  249. c.grad = &rasterx.Gradient{Points: [5]float64{0.5, 0.5, 0.5, 0.5, 0.5},
  250. IsRadial: true, Bounds: c.icon.ViewBox, Matrix: rasterx.Identity}
  251. var setFx, setFy bool
  252. var err error
  253. for _, attr := range attrs {
  254. switch attr.Name.Local {
  255. case "id":
  256. id := attr.Value
  257. if len(id) >= 0 {
  258. c.icon.Grads[id] = c.grad
  259. } else {
  260. return errZeroLengthID
  261. }
  262. case "r":
  263. c.grad.Points[4], err = readFraction(attr.Value)
  264. case "cx":
  265. c.grad.Points[0], err = readFraction(attr.Value)
  266. case "cy":
  267. c.grad.Points[1], err = readFraction(attr.Value)
  268. case "fx":
  269. setFx = true
  270. c.grad.Points[2], err = readFraction(attr.Value)
  271. case "fy":
  272. setFy = true
  273. c.grad.Points[3], err = readFraction(attr.Value)
  274. default:
  275. err = c.ReadGradAttr(attr)
  276. }
  277. if err != nil {
  278. return err
  279. }
  280. }
  281. if !setFx { // set fx to cx by default
  282. c.grad.Points[2] = c.grad.Points[0]
  283. }
  284. if !setFy { // set fy to cy by default
  285. c.grad.Points[3] = c.grad.Points[1]
  286. }
  287. return nil
  288. }
  289. stopF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  290. var err error
  291. if c.inGrad {
  292. stop := rasterx.GradStop{Opacity: 1.0}
  293. for _, attr := range attrs {
  294. switch attr.Name.Local {
  295. case "offset":
  296. stop.Offset, err = readFraction(attr.Value)
  297. case "stop-color":
  298. //todo: add current color inherit
  299. stop.StopColor, err = ParseSVGColor(attr.Value)
  300. case "stop-opacity":
  301. stop.Opacity, err = parseFloat(attr.Value, 64)
  302. }
  303. if err != nil {
  304. return err
  305. }
  306. }
  307. c.grad.Stops = append(c.grad.Stops, stop)
  308. }
  309. return nil
  310. }
  311. useF svgFunc = func(c *IconCursor, attrs []xml.Attr) error {
  312. var (
  313. href string
  314. x, y float64
  315. err error
  316. )
  317. for _, attr := range attrs {
  318. switch attr.Name.Local {
  319. case "href":
  320. href = attr.Value
  321. case "x":
  322. x, err = parseFloat(attr.Value, 64)
  323. case "y":
  324. y, err = parseFloat(attr.Value, 64)
  325. }
  326. if err != nil {
  327. return err
  328. }
  329. }
  330. // Translate the Style adder matrix by use's x and y
  331. c.StyleStack[len(c.StyleStack)-1].mAdder.M =
  332. c.StyleStack[len(c.StyleStack)-1].mAdder.M.Translate(x, y)
  333. if href == "" {
  334. return errors.New("only use tags with href is supported")
  335. }
  336. if !strings.HasPrefix(href, "#") {
  337. return errors.New("only the ID CSS selector is supported")
  338. }
  339. defs, ok := c.icon.Defs[href[1:]]
  340. if !ok {
  341. return errors.New("href ID in use statement was not found in saved defs")
  342. }
  343. for _, def := range defs {
  344. if def.Tag == "endg" {
  345. // pop style
  346. c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
  347. continue
  348. }
  349. if err = c.PushStyle(def.Attrs); err != nil {
  350. return err
  351. }
  352. df, ok := drawFuncs[def.Tag]
  353. if !ok {
  354. errStr := "Cannot process svg element " + def.Tag
  355. if c.ErrorMode == StrictErrorMode {
  356. return errors.New(errStr)
  357. } else if c.ErrorMode == WarnErrorMode {
  358. log.Println(errStr)
  359. }
  360. return nil
  361. }
  362. if err := df(c, def.Attrs); err != nil {
  363. return err
  364. }
  365. //Did c.Path get added to during the drawFunction call iteration?
  366. if len(c.Path) > 0 {
  367. //The cursor parsed a path from the xml element
  368. pathCopy := make(rasterx.Path, len(c.Path))
  369. copy(pathCopy, c.Path)
  370. c.icon.SVGPaths = append(c.icon.SVGPaths, SvgPath{c.StyleStack[len(c.StyleStack)-1], pathCopy})
  371. c.Path = c.Path[:0]
  372. }
  373. if def.Tag != "g" {
  374. // pop style
  375. c.StyleStack = c.StyleStack[:len(c.StyleStack)-1]
  376. }
  377. }
  378. return nil
  379. }
  380. )
  381. func init() {
  382. // avoids cyclical static declaration
  383. // called on package initialization
  384. drawFuncs["use"] = useF
  385. }