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