path_cursor.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. package oksvg
  6. import (
  7. "errors"
  8. "log"
  9. "math"
  10. "unicode"
  11. "github.com/srwiley/rasterx"
  12. "golang.org/x/image/math/fixed"
  13. )
  14. type (
  15. // ErrorMode is the for setting how the parser reacts to unparsed elements
  16. ErrorMode uint8
  17. // PathCursor is used to parse SVG format path strings into a rasterx Path
  18. PathCursor struct {
  19. rasterx.Path
  20. placeX, placeY float64
  21. cntlPtX, cntlPtY float64
  22. pathStartX, pathStartY float64
  23. points []float64
  24. lastKey uint8
  25. ErrorMode ErrorMode
  26. inPath bool
  27. }
  28. )
  29. const (
  30. // IgnoreErrorMode skips un-parsed SVG elements.
  31. IgnoreErrorMode ErrorMode = iota
  32. // WarnErrorMode outputs a warning when an un-parsed SVG element is found.
  33. WarnErrorMode
  34. // StrictErrorMode causes an error when an un-parsed SVG element is found.
  35. StrictErrorMode
  36. )
  37. var (
  38. errParamMismatch = errors.New("param mismatch")
  39. errCommandUnknown = errors.New("unknown command")
  40. errZeroLengthID = errors.New("zero length id")
  41. )
  42. // ReadFloat reads a floating point value and adds it to the cursor's points slice.
  43. func (c *PathCursor) ReadFloat(numStr string) error {
  44. last := 0
  45. isFirst := true
  46. for i, n := range numStr {
  47. if n == '.' {
  48. if isFirst {
  49. isFirst = false
  50. continue
  51. }
  52. f, err := parseFloat(numStr[last:i], 64)
  53. if err != nil {
  54. return err
  55. }
  56. c.points = append(c.points, f)
  57. last = i
  58. }
  59. }
  60. f, err := parseFloat(numStr[last:], 64)
  61. if err != nil {
  62. return err
  63. }
  64. c.points = append(c.points, f)
  65. return nil
  66. }
  67. // GetPoints reads a set of floating point values from the SVG format number string,
  68. // and add them to the cursor's points slice.
  69. func (c *PathCursor) GetPoints(dataPoints string) error {
  70. lastIndex := -1
  71. c.points = c.points[0:0]
  72. lr := ' '
  73. for i, r := range dataPoints {
  74. if !unicode.IsNumber(r) && r != '.' && !(r == '-' && lr == 'e') && r != 'e' {
  75. if lastIndex != -1 {
  76. if err := c.ReadFloat(dataPoints[lastIndex:i]); err != nil {
  77. return err
  78. }
  79. }
  80. if r == '-' {
  81. lastIndex = i
  82. } else {
  83. lastIndex = -1
  84. }
  85. } else if lastIndex == -1 {
  86. lastIndex = i
  87. }
  88. lr = r
  89. }
  90. if lastIndex != -1 && lastIndex != len(dataPoints) {
  91. if err := c.ReadFloat(dataPoints[lastIndex:]); err != nil {
  92. return err
  93. }
  94. }
  95. return nil
  96. }
  97. // EllipseAt adds a path of an elipse centered at cx, cy of radius rx and ry
  98. // to the PathCursor
  99. func (c *PathCursor) EllipseAt(cx, cy, rx, ry float64) {
  100. c.placeX, c.placeY = cx+rx, cy
  101. c.points = c.points[0:0]
  102. c.points = append(c.points, rx, ry, 0.0, 1.0, 0.0, c.placeX, c.placeY)
  103. c.Path.Start(fixed.Point26_6{
  104. X: fixed.Int26_6(c.placeX * 64),
  105. Y: fixed.Int26_6(c.placeY * 64)})
  106. c.placeX, c.placeY = rasterx.AddArc(c.points, cx, cy, c.placeX, c.placeY, &c.Path)
  107. c.Path.Stop(true)
  108. }
  109. // AddArcFromA adds a path of an arc element to the cursor path to the PathCursor
  110. func (c *PathCursor) AddArcFromA(points []float64) {
  111. cx, cy := rasterx.FindEllipseCenter(&points[0], &points[1], points[2]*math.Pi/180, c.placeX,
  112. c.placeY, points[5], points[6], points[4] == 0, points[3] == 0)
  113. c.placeX, c.placeY = rasterx.AddArc(c.points, cx, cy, c.placeX, c.placeY, &c.Path)
  114. }
  115. // CompilePath translates the svgPath description string into a rasterx path.
  116. // All valid SVG path elements are interpreted to rasterx equivalents.
  117. // The resulting path element is stored in the PathCursor.
  118. func (c *PathCursor) CompilePath(svgPath string) error {
  119. c.init()
  120. lastIndex := -1
  121. for i, v := range svgPath {
  122. if unicode.IsLetter(v) && v != 'e' {
  123. if lastIndex != -1 {
  124. if err := c.addSeg(svgPath[lastIndex:i]); err != nil {
  125. return err
  126. }
  127. }
  128. lastIndex = i
  129. }
  130. }
  131. if lastIndex != -1 {
  132. if err := c.addSeg(svgPath[lastIndex:]); err != nil {
  133. return err
  134. }
  135. }
  136. return nil
  137. }
  138. func reflect(px, py, rx, ry float64) (x, y float64) {
  139. return px*2 - rx, py*2 - ry
  140. }
  141. func (c *PathCursor) valsToAbs(last float64) {
  142. for i := 0; i < len(c.points); i++ {
  143. last += c.points[i]
  144. c.points[i] = last
  145. }
  146. }
  147. func (c *PathCursor) pointsToAbs(sz int) {
  148. lastX := c.placeX
  149. lastY := c.placeY
  150. for j := 0; j < len(c.points); j += sz {
  151. for i := 0; i < sz; i += 2 {
  152. c.points[i+j] += lastX
  153. c.points[i+1+j] += lastY
  154. }
  155. lastX = c.points[(j+sz)-2]
  156. lastY = c.points[(j+sz)-1]
  157. }
  158. }
  159. func (c *PathCursor) hasSetsOrMore(sz int, rel bool) bool {
  160. if !(len(c.points) >= sz && len(c.points)%sz == 0) {
  161. return false
  162. }
  163. if rel {
  164. c.pointsToAbs(sz)
  165. }
  166. return true
  167. }
  168. func (c *PathCursor) reflectControlQuad() {
  169. switch c.lastKey {
  170. case 'q', 'Q', 'T', 't':
  171. c.cntlPtX, c.cntlPtY = reflect(c.placeX, c.placeY, c.cntlPtX, c.cntlPtY)
  172. default:
  173. c.cntlPtX, c.cntlPtY = c.placeX, c.placeY
  174. }
  175. }
  176. func (c *PathCursor) reflectControlCube() {
  177. switch c.lastKey {
  178. case 'c', 'C', 's', 'S':
  179. c.cntlPtX, c.cntlPtY = reflect(c.placeX, c.placeY, c.cntlPtX, c.cntlPtY)
  180. default:
  181. c.cntlPtX, c.cntlPtY = c.placeX, c.placeY
  182. }
  183. }
  184. // addSeg decodes an SVG seqment string into equivalent raster path commands saved
  185. // in the cursor's Path
  186. func (c *PathCursor) addSeg(segString string) error {
  187. // Parse the string describing the numeric points in SVG format
  188. if err := c.GetPoints(segString[1:]); err != nil {
  189. return err
  190. }
  191. l := len(c.points)
  192. k := segString[0]
  193. rel := false
  194. switch k {
  195. case 'z':
  196. fallthrough
  197. case 'Z':
  198. if len(c.points) != 0 {
  199. return errParamMismatch
  200. }
  201. if c.inPath {
  202. c.Path.Stop(true)
  203. c.placeX = c.pathStartX
  204. c.placeY = c.pathStartY
  205. c.inPath = false
  206. }
  207. case 'm':
  208. rel = true
  209. fallthrough
  210. case 'M':
  211. if !c.hasSetsOrMore(2, rel) {
  212. return errParamMismatch
  213. }
  214. c.pathStartX, c.pathStartY = c.points[0], c.points[1]
  215. c.inPath = true
  216. c.Path.Start(fixed.Point26_6{X: fixed.Int26_6((c.pathStartX) * 64), Y: fixed.Int26_6((c.pathStartY) * 64)})
  217. for i := 2; i < l-1; i += 2 {
  218. c.Path.Line(fixed.Point26_6{
  219. X: fixed.Int26_6((c.points[i]) * 64),
  220. Y: fixed.Int26_6((c.points[i+1]) * 64)})
  221. }
  222. c.placeX = c.points[l-2]
  223. c.placeY = c.points[l-1]
  224. case 'l':
  225. rel = true
  226. fallthrough
  227. case 'L':
  228. if !c.hasSetsOrMore(2, rel) {
  229. return errParamMismatch
  230. }
  231. for i := 0; i < l-1; i += 2 {
  232. c.Path.Line(fixed.Point26_6{
  233. X: fixed.Int26_6((c.points[i]) * 64),
  234. Y: fixed.Int26_6((c.points[i+1]) * 64)})
  235. }
  236. c.placeX = c.points[l-2]
  237. c.placeY = c.points[l-1]
  238. case 'v':
  239. c.valsToAbs(c.placeY)
  240. fallthrough
  241. case 'V':
  242. if !c.hasSetsOrMore(1, false) {
  243. return errParamMismatch
  244. }
  245. for _, p := range c.points {
  246. c.Path.Line(fixed.Point26_6{
  247. X: fixed.Int26_6((c.placeX) * 64),
  248. Y: fixed.Int26_6((p) * 64)})
  249. }
  250. c.placeY = c.points[l-1]
  251. case 'h':
  252. c.valsToAbs(c.placeX)
  253. fallthrough
  254. case 'H':
  255. if !c.hasSetsOrMore(1, false) {
  256. return errParamMismatch
  257. }
  258. for _, p := range c.points {
  259. c.Path.Line(fixed.Point26_6{
  260. X: fixed.Int26_6((p) * 64),
  261. Y: fixed.Int26_6((c.placeY) * 64)})
  262. }
  263. c.placeX = c.points[l-1]
  264. case 'q':
  265. rel = true
  266. fallthrough
  267. case 'Q':
  268. if !c.hasSetsOrMore(4, rel) {
  269. return errParamMismatch
  270. }
  271. for i := 0; i < l-3; i += 4 {
  272. c.Path.QuadBezier(
  273. fixed.Point26_6{
  274. X: fixed.Int26_6((c.points[i]) * 64),
  275. Y: fixed.Int26_6((c.points[i+1]) * 64)},
  276. fixed.Point26_6{
  277. X: fixed.Int26_6((c.points[i+2]) * 64),
  278. Y: fixed.Int26_6((c.points[i+3]) * 64)})
  279. }
  280. c.cntlPtX, c.cntlPtY = c.points[l-4], c.points[l-3]
  281. c.placeX = c.points[l-2]
  282. c.placeY = c.points[l-1]
  283. case 't':
  284. rel = true
  285. fallthrough
  286. case 'T':
  287. if !c.hasSetsOrMore(2, rel) {
  288. return errParamMismatch
  289. }
  290. for i := 0; i < l-1; i += 2 {
  291. c.reflectControlQuad()
  292. c.Path.QuadBezier(
  293. fixed.Point26_6{
  294. X: fixed.Int26_6((c.cntlPtX) * 64),
  295. Y: fixed.Int26_6((c.cntlPtY) * 64)},
  296. fixed.Point26_6{
  297. X: fixed.Int26_6((c.points[i]) * 64),
  298. Y: fixed.Int26_6((c.points[i+1]) * 64)})
  299. c.lastKey = k
  300. c.placeX = c.points[i]
  301. c.placeY = c.points[i+1]
  302. }
  303. case 'c':
  304. rel = true
  305. fallthrough
  306. case 'C':
  307. if !c.hasSetsOrMore(6, rel) {
  308. return errParamMismatch
  309. }
  310. for i := 0; i < l-5; i += 6 {
  311. c.Path.CubeBezier(
  312. fixed.Point26_6{
  313. X: fixed.Int26_6((c.points[i]) * 64),
  314. Y: fixed.Int26_6((c.points[i+1]) * 64)},
  315. fixed.Point26_6{
  316. X: fixed.Int26_6((c.points[i+2]) * 64),
  317. Y: fixed.Int26_6((c.points[i+3]) * 64)},
  318. fixed.Point26_6{
  319. X: fixed.Int26_6((c.points[i+4]) * 64),
  320. Y: fixed.Int26_6((c.points[i+5]) * 64)})
  321. }
  322. c.cntlPtX, c.cntlPtY = c.points[l-4], c.points[l-3]
  323. c.placeX = c.points[l-2]
  324. c.placeY = c.points[l-1]
  325. case 's':
  326. rel = true
  327. fallthrough
  328. case 'S':
  329. if !c.hasSetsOrMore(4, rel) {
  330. return errParamMismatch
  331. }
  332. for i := 0; i < l-3; i += 4 {
  333. c.reflectControlCube()
  334. c.Path.CubeBezier(fixed.Point26_6{
  335. X: fixed.Int26_6((c.cntlPtX) * 64), Y: fixed.Int26_6((c.cntlPtY) * 64)},
  336. fixed.Point26_6{
  337. X: fixed.Int26_6((c.points[i]) * 64), Y: fixed.Int26_6((c.points[i+1]) * 64)},
  338. fixed.Point26_6{
  339. X: fixed.Int26_6((c.points[i+2]) * 64), Y: fixed.Int26_6((c.points[i+3]) * 64)})
  340. c.lastKey = k
  341. c.cntlPtX, c.cntlPtY = c.points[i], c.points[i+1]
  342. c.placeX = c.points[i+2]
  343. c.placeY = c.points[i+3]
  344. }
  345. case 'a', 'A':
  346. if !c.hasSetsOrMore(7, false) {
  347. return errParamMismatch
  348. }
  349. for i := 0; i < l-6; i += 7 {
  350. if k == 'a' {
  351. c.points[i+5] += c.placeX
  352. c.points[i+6] += c.placeY
  353. }
  354. c.AddArcFromA(c.points[i:])
  355. }
  356. default:
  357. if c.ErrorMode == StrictErrorMode {
  358. return errCommandUnknown
  359. }
  360. if c.ErrorMode == WarnErrorMode {
  361. log.Println("Ignoring svg command " + string(k))
  362. }
  363. }
  364. // So we know how to extend some segment types
  365. c.lastKey = k
  366. return nil
  367. }
  368. func (c *PathCursor) init() {
  369. c.placeX = 0.0
  370. c.placeY = 0.0
  371. c.points = c.points[0:0]
  372. c.lastKey = ' '
  373. c.Path.Clear()
  374. c.inPath = false
  375. }