svgbasic.go 6.3 KB

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