1/*
2 * Copyright (c) 2014 Kurt Jung (Gmail: kurt.w.jung)
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17package gofpdf
18
19import (
20	"encoding/xml"
21	"fmt"
22	"io/ioutil"
23	"strconv"
24	"strings"
25)
26
27var pathCmdSub *strings.Replacer
28
29func init() {
30	// Handle permitted constructions like "100L200,230"
31	pathCmdSub = strings.NewReplacer(",", " ",
32		"L", " L ", "l", " l ",
33		"C", " C ", "c", " c ",
34		"M", " M ", "m", " m ",
35		"H", " H ", "h", " h ",
36		"V", " V ", "v", " v ",
37		"Q", " Q ", "q", " q ",
38		"Z", " Z ", "z", " z ")
39}
40
41// SVGBasicSegmentType describes a single curve or position segment
42type SVGBasicSegmentType struct {
43	Cmd byte // See http://www.w3.org/TR/SVG/paths.html for path command structure
44	Arg [6]float64
45}
46
47func absolutizePath(segs []SVGBasicSegmentType) {
48	var x, y float64
49	var segPtr *SVGBasicSegmentType
50	adjust := func(pos int, adjX, adjY float64) {
51		segPtr.Arg[pos] += adjX
52		segPtr.Arg[pos+1] += adjY
53	}
54	for j, seg := range segs {
55		segPtr = &segs[j]
56		if j == 0 && seg.Cmd == 'm' {
57			segPtr.Cmd = 'M'
58		}
59		switch segPtr.Cmd {
60		case 'M':
61			x = seg.Arg[0]
62			y = seg.Arg[1]
63		case 'm':
64			adjust(0, x, y)
65			segPtr.Cmd = 'M'
66			x = segPtr.Arg[0]
67			y = segPtr.Arg[1]
68		case 'L':
69			x = seg.Arg[0]
70			y = seg.Arg[1]
71		case 'l':
72			adjust(0, x, y)
73			segPtr.Cmd = 'L'
74			x = segPtr.Arg[0]
75			y = segPtr.Arg[1]
76		case 'C':
77			x = seg.Arg[4]
78			y = seg.Arg[5]
79		case 'c':
80			adjust(0, x, y)
81			adjust(2, x, y)
82			adjust(4, x, y)
83			segPtr.Cmd = 'C'
84			x = segPtr.Arg[4]
85			y = segPtr.Arg[5]
86		case 'Q':
87			x = seg.Arg[2]
88			y = seg.Arg[3]
89		case 'q':
90			adjust(0, x, y)
91			adjust(2, x, y)
92			segPtr.Cmd = 'Q'
93			x = segPtr.Arg[2]
94			y = segPtr.Arg[3]
95		case 'H':
96			x = seg.Arg[0]
97		case 'h':
98			segPtr.Arg[0] += x
99			segPtr.Cmd = 'H'
100			x += seg.Arg[0]
101		case 'V':
102			y = seg.Arg[0]
103		case 'v':
104			segPtr.Arg[0] += y
105			segPtr.Cmd = 'V'
106			y += seg.Arg[0]
107		case 'z':
108			segPtr.Cmd = 'Z'
109		}
110	}
111}
112
113func pathParse(pathStr string) (segs []SVGBasicSegmentType, err error) {
114	var seg SVGBasicSegmentType
115	var j, argJ, argCount, prevArgCount int
116	setup := func(n int) {
117		// It is not strictly necessary to clear arguments, but result may be clearer
118		// to caller
119		for j := 0; j < len(seg.Arg); j++ {
120			seg.Arg[j] = 0.0
121		}
122		argJ = 0
123		argCount = n
124		prevArgCount = n
125	}
126	var str string
127	var c byte
128	pathStr = pathCmdSub.Replace(pathStr)
129	strList := strings.Fields(pathStr)
130	count := len(strList)
131	for j = 0; j < count && err == nil; j++ {
132		str = strList[j]
133		if argCount == 0 { // Look for path command or argument continuation
134			c = str[0]
135			if c == '-' || (c >= '0' && c <= '9') { // More arguments
136				if j > 0 {
137					setup(prevArgCount)
138					// Repeat previous action
139					if seg.Cmd == 'M' {
140						seg.Cmd = 'L'
141					} else if seg.Cmd == 'm' {
142						seg.Cmd = 'l'
143					}
144				} else {
145					err = fmt.Errorf("expecting SVG path command at first position, got %s", str)
146				}
147			}
148		}
149		if err == nil {
150			if argCount == 0 {
151				seg.Cmd = str[0]
152				switch seg.Cmd {
153				case 'M', 'm': // Absolute/relative moveto: x, y
154					setup(2)
155				case 'C', 'c': // Absolute/relative Bézier curve: cx0, cy0, cx1, cy1, x1, y1
156					setup(6)
157				case 'H', 'h': // Absolute/relative horizontal line to: x
158					setup(1)
159				case 'L', 'l': // Absolute/relative lineto: x, y
160					setup(2)
161				case 'Q', 'q': // Absolute/relative quadratic curve: x0, y0, x1, y1
162					setup(4)
163				case 'V', 'v': // Absolute/relative vertical line to: y
164					setup(1)
165				case 'Z', 'z': // closepath instruction (takes no arguments)
166					segs = append(segs, seg)
167				default:
168					err = fmt.Errorf("expecting SVG path command at position %d, got %s", j, str)
169				}
170			} else {
171				seg.Arg[argJ], err = strconv.ParseFloat(str, 64)
172				if err == nil {
173					argJ++
174					argCount--
175					if argCount == 0 {
176						segs = append(segs, seg)
177					}
178				}
179			}
180		}
181	}
182	if err == nil {
183		if argCount == 0 {
184			absolutizePath(segs)
185		} else {
186			err = fmt.Errorf("expecting additional (%d) numeric arguments", argCount)
187		}
188	}
189	return
190}
191
192// SVGBasicType aggregates the information needed to describe a multi-segment
193// basic vector image
194type SVGBasicType struct {
195	Wd, Ht   float64
196	Segments [][]SVGBasicSegmentType
197}
198
199// SVGBasicParse parses a simple scalable vector graphics (SVG) buffer into a
200// descriptor. Only a small subset of the SVG standard, in particular the path
201// information generated by jSignature, is supported. The returned path data
202// includes only the commands 'M' (absolute moveto: x, y), 'L' (absolute
203// lineto: x, y), 'C' (absolute cubic Bézier curve: cx0, cy0, cx1, cy1,
204// x1,y1), 'Q' (absolute quadratic Bézier curve: x0, y0, x1, y1) and 'Z'
205// (closepath).
206func SVGBasicParse(buf []byte) (sig SVGBasicType, err error) {
207	type pathType struct {
208		D string `xml:"d,attr"`
209	}
210	type srcType struct {
211		Wd    float64    `xml:"width,attr"`
212		Ht    float64    `xml:"height,attr"`
213		Paths []pathType `xml:"path"`
214	}
215	var src srcType
216	err = xml.Unmarshal(buf, &src)
217	if err == nil {
218		if src.Wd > 0 && src.Ht > 0 {
219			sig.Wd, sig.Ht = src.Wd, src.Ht
220			var segs []SVGBasicSegmentType
221			for _, path := range src.Paths {
222				if err == nil {
223					segs, err = pathParse(path.D)
224					if err == nil {
225						sig.Segments = append(sig.Segments, segs)
226					}
227				}
228			}
229		} else {
230			err = fmt.Errorf("unacceptable values for basic SVG extent: %.2f x %.2f",
231				sig.Wd, sig.Ht)
232		}
233	}
234	return
235}
236
237// SVGBasicFileParse parses a simple scalable vector graphics (SVG) file into a
238// basic descriptor. The SVGBasicWrite() example demonstrates this method.
239func SVGBasicFileParse(svgFileStr string) (sig SVGBasicType, err error) {
240	var buf []byte
241	buf, err = ioutil.ReadFile(svgFileStr)
242	if err == nil {
243		sig, err = SVGBasicParse(buf)
244	}
245	return
246}
247