github.com/phpdave11/gofpdf@v1.4.2/svgbasic.go (about) 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 17 package gofpdf 18 19 import ( 20 "encoding/xml" 21 "fmt" 22 "io/ioutil" 23 "strconv" 24 "strings" 25 ) 26 27 var pathCmdSub *strings.Replacer 28 29 func 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 42 type SVGBasicSegmentType struct { 43 Cmd byte // See http://www.w3.org/TR/SVG/paths.html for path command structure 44 Arg [6]float64 45 } 46 47 func 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 113 func 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 194 type 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). 206 func 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. 239 func 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 }