draw.go

  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}