]> source.dussan.org Git - svg.js.git/commitdiff
fix path parsing (#1145)
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Sat, 5 Sep 2020 13:06:25 +0000 (23:06 +1000)
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Sat, 5 Sep 2020 13:06:25 +0000 (23:06 +1000)
CHANGELOG.md
spec/spec/modules/core/regex.js
spec/spec/types/PathArray.js
spec/spec/utils/pathParser.js [new file with mode: 0644]
src/animation/Morphable.js
src/modules/core/regex.js
src/types/PathArray.js
src/utils/pathParser.js [new file with mode: 0644]

index 20b163c865805d4b1490cc1784af52d0ea122d32..0fcb9a4887ca255e00c18d3b4531b8573fc531c4 100644 (file)
@@ -38,6 +38,7 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
  - fixed animate attr which is also retargetable now
  - fixed internals of ObjectBag which can hold other Morphable values now
  - fixed animate transform which didnt change its origin on retarget for declaritive animations
+ - fixed path parsing (#1145)
 
 ### Added
  - added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058)
index e1d6dc9ae84bfe7069bbdf24f8b32a701baaf674..ddcebb99a917d5581df8a36d3f9fefb64072c552 100644 (file)
@@ -200,23 +200,6 @@ describe('regex.js', () => {
     })
   })
 
-  describe('hyphen', () => {
-    it('matches all hyphens which are preceeded by something but not an exponent', () => {
-      expect(regex.hyphen.test('a-')).toBe(true)
-      expect(regex.hyphen.test('e-')).toBe(false)
-    })
-
-    it('replaces all hyphens and their preceeding char which is not an exponent', () => {
-      expect('   -  e- ---E-'.replace(regex.hyphen, '.')).toBe('  .  e-..E-')
-    })
-  })
-
-  describe('pathLetters', () => {
-    it('replaces all path letters', () => {
-      expect('MLHVCSQTAZBImlhvcsqtazbi'.replace(regex.pathLetters, '.')).toBe('..........BI..........bi')
-    })
-  })
-
   describe('isPathLetter', () => {
     it('returns true if something is a path letter', () => {
       'MLHVCSQTAZmlhvcsqtaz'.split('').forEach((l) => {
@@ -230,17 +213,4 @@ describe('regex.js', () => {
       })
     })
   })
-
-  describe('numbersWithDots', () => {
-    it('matches numbers of the form 3.13.123', () => {
-      const match = '-123.23.45 +123.324 43.43.54.65 123 33 .24 .34.34 -.54 .54-.65 123.34e34.21'.match(regex.numbersWithDots)
-      expect(match).toEqual([ '3.23.45', '3.43.54.65', '.34.34', '3.34e34.21' ])
-    })
-  })
-
-  describe('dots', () => {
-    it('replaces all dots', () => {
-      expect('..asd..'.replace(regex.dots, 'a')).toBe('aaasdaa')
-    })
-  })
 })
index fd43480073f90c32e1802ad721220813ed189845..ed26bc7fa4d3a89e748d6db9e95f9f67086b7ae6 100644 (file)
@@ -3,29 +3,12 @@
 import { PathArray, Box } from '../../../src/main.js'
 
 describe('PathArray.js', () => {
-  let p1, p2, p3, p4, p5, p6, p7
+  let p1, p2, p3
 
   beforeEach(() => {
     p1 = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z')
     p2 = new PathArray('m10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z')
     p3 = new PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')
-    p4 = new PathArray('M215.458,245.23c0,0,77.403,0,94.274,0S405,216.451,405,138.054S329.581,15,287.9,15c-41.68,0-139.924,0-170.688,0C86.45,15,15,60.65,15,134.084c0,73.434,96.259,112.137,114.122,112.137C146.984,246.221,215.458,245.23,215.458,245.23z')
-    p5 = new PathArray('M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z')
-    p6 = new PathArray('m 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z')
-    p7 = new PathArray('m 0,0 a 45 45, 0, 0, 0, 125 125')
-  })
-
-  it('converts to absolute values', () => {
-    expect(p1.toString()).toBe('M10 10H90V90H10L310 490Z ')
-    expect(p2.toString()).toBe('M10 80C50 90 75 90 105 160S255 310 285 240T585 540Q637 550 680 620Z ')
-    expect(p3.toString()).toBe('M80 80A45 45 0 0 0 125 125L125 80Z ')
-    expect(p4.toString()).toBe('M215.458 245.23C215.458 245.23 292.861 245.23 309.73199999999997 245.23S405 216.451 405 138.054S329.581 15 287.9 15C246.21999999999997 15 147.97599999999997 15 117.21199999999999 15C86.45 15 15 60.65 15 134.084C15 207.518 111.259 246.221 129.122 246.221C146.984 246.221 215.458 245.23 215.458 245.23Z ')
-    expect(p6.toString()).toBe('M0 0L0 3189L2209 3189L2209 0L0 0ZM154 154L2055 154L2055 3035L154 3035L154 154Z ')
-    expect(p7.toString()).toBe('M0 0A45 45 0 0 0 125 125 ')
-  })
-
-  it('parses difficult syntax correctly', () => {
-    expect(p5.toString()).toBe('M10 10L-45 -30.5L0.5 0.89L0.02 0.5L0.5 -0.5C0.5 0.5 0.5 0.5 0.5 0.5L-3 -4Z ')
   })
 
   it('parses flat arrays correctly', () => {
@@ -45,14 +28,6 @@ describe('PathArray.js', () => {
     expect((new PathArray(p))).toEqual(p)
   })
 
-  it('can handle all formats which can be used', () => {
-    // when no command is specified after move, line is used automatically (specs say so)
-    expect(new PathArray('M10 10 80 80 30 30 Z').toString()).toBe('M10 10L80 80L30 30Z ')
-
-    // parsing can handle 0.5.3.3.2 stuff
-    expect(new PathArray('M10 10L.5.5.3.3Z').toString()).toBe('M10 10L0.5 0.5L0.3 0.3Z ')
-  })
-
   describe('move()', () => {
     it('moves all points in a straight path', () => {
       expect(p1.move(100, 200).toString()).toBe('M100 200H180V280H100L400 680Z ')
diff --git a/spec/spec/utils/pathParser.js b/spec/spec/utils/pathParser.js
new file mode 100644 (file)
index 0000000..cc3382d
--- /dev/null
@@ -0,0 +1,107 @@
+/* globals describe expect it */
+
+import { pathParser } from '../../../src/utils/pathParser.js'
+
+describe('pathParser.js', () => {
+  describe('pathParser()', () => {
+    it('parses all paths correctly', () => {
+      expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 011 0z')).toEqual([
+        [ 'M', 2, 0 ],
+        [ 'A', 2, 2, 0, 0, 0, 0, 2 ],
+        [ 'A', 2, 2, 0, 0, 0, 2, 4 ],
+        [ 'A', 0.5, 0.5, 0, 0, 1, 3, 4 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 111 0z')).toEqual([
+        [ 'M', 2, 0 ],
+        [ 'A', 2, 2, 0, 0, 0, 0, 2 ],
+        [ 'A', 2, 2, 0, 0, 0, 2, 4 ],
+        [ 'A', 0.5, 0.5, 0, 1, 1, 3, 4 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('m10 10 h 80 v 80 h -80 l 300 400 z')).toEqual([
+        [ 'M', 10, 10 ],
+        [ 'H', 90 ],
+        [ 'V', 90 ],
+        [ 'H', 10 ],
+        [ 'L', 310, 490 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('m10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z')).toEqual([
+        [ 'M', 10, 80 ],
+        [ 'C', 50, 90, 75, 90, 105, 160 ],
+        [ 'S', 255, 310, 285, 240 ],
+        [ 'T', 585, 540 ],
+        [ 'Q', 637, 550, 680, 620 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')).toEqual([
+        [ 'M', 80, 80 ],
+        [ 'A', 45, 45, 0, 0, 0, 125, 125 ],
+        [ 'L', 125, 80 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('M215.458,245.23c0,0,77.403,0,94.274,0S405,216.451,405,138.054S329.581,15,287.9,15c-41.68,0-139.924,0-170.688,0C86.45,15,15,60.65,15,134.084c0,73.434,96.259,112.137,114.122,112.137C146.984,246.221,215.458,245.23,215.458,245.23z')).toEqual([
+        [ 'M', 215.458, 245.23 ],
+        [ 'C', 215.458, 245.23, 292.861, 245.23, 309.73199999999997, 245.23 ],
+        [ 'S', 405, 216.451, 405, 138.054 ],
+        [ 'S', 329.581, 15, 287.9, 15 ],
+        [ 'C', 246.21999999999997, 15, 147.97599999999997, 15, 117.21199999999999, 15 ],
+        [ 'C', 86.45, 15, 15, 60.65, 15, 134.084 ],
+        [ 'C', 15, 207.518, 111.259, 246.221, 129.122, 246.221 ],
+        [ 'C', 146.984, 246.221, 215.458, 245.23, 215.458, 245.23 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('M10 10-45-30.5.5 .89L2e-2.5.5-.5C.5.5.5.5.5.5L-3-4z')).toEqual([
+        [ 'M', 10, 10 ],
+        [ 'L', -45, -30.5 ],
+        [ 'L', 0.5, 0.89 ],
+        [ 'L', 0.02, 0.5 ],
+        [ 'L', 0.5, -0.5 ],
+        [ 'C', 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 ],
+        [ 'L', -3, -4 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('m 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z')).toEqual([
+        [ 'M', 0, 0 ],
+        [ 'L', 0, 3189 ],
+        [ 'L', 2209, 3189 ],
+        [ 'L', 2209, 0 ],
+        [ 'L', 0, 0 ],
+        [ 'Z' ],
+        [ 'M', 154, 154 ],
+        [ 'L', 2055, 154 ],
+        [ 'L', 2055, 3035 ],
+        [ 'L', 154, 3035 ],
+        [ 'L', 154, 154 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('m 0,0 a 45 45, 0, 0, 0, 125 125')).toEqual([
+        [ 'M', 0, 0 ],
+        [ 'A', 45, 45, 0, 0, 0, 125, 125 ]
+      ])
+
+      expect(pathParser('M10 10 80 80 30 30 Z')).toEqual([
+        [ 'M', 10, 10 ],
+        [ 'L', 80, 80 ],
+        [ 'L', 30, 30 ],
+        [ 'Z' ]
+      ])
+
+      expect(pathParser('M10 10L.5.5.3.3Z')).toEqual([
+        [ 'M', 10, 10 ],
+        [ 'L', 0.5, 0.5 ],
+        [ 'L', 0.3, 0.3 ],
+        [ 'Z' ]
+      ])
+    })
+  })
+})
index 46dd16641147a4d4b28a766a5dbdc0741700466b..3344480548865f27ce8ebe17e943355f0daa024f 100644 (file)
@@ -2,7 +2,7 @@ import { Ease } from './Controller.js'
 import {
   delimiter,
   numberAndUnit,
-  pathLetters
+  isPathLetter
 } from '../modules/core/regex.js'
 import { extend } from '../utils/adopter.js'
 import Color from '../types/Color.js'
@@ -19,7 +19,7 @@ const getClassForType = (value) => {
     if (Color.isColor(value)) {
       return Color
     } else if (delimiter.test(value)) {
-      return pathLetters.test(value)
+      return isPathLetter.test(value)
         ? PathArray
         : SVGArray
     } else if (numberAndUnit.test(value)) {
index c32ac93e4f8691cdadd301e8fb361b85d2b8bd49..a18c69216551cae9312aeeca9161767977bf5af0 100644 (file)
@@ -34,19 +34,5 @@ export const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
 // split at whitespace and comma
 export const delimiter = /[\s,]+/
 
-// The following regex are used to parse the d attribute of a path
-
-// Matches all hyphens which are preceeded by something but not an exponent
-export const hyphen = /([^e])-/gi
-
-// Replaces and tests for all path letters
-export const pathLetters = /[MLHVCSQTAZ]/gi
-
-// yes we need this one, too
+// Test for path letter
 export const isPathLetter = /[MLHVCSQTAZ]/i
-
-// matches 0.154.23.45
-export const numbersWithDots = /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi
-
-// matches .
-export const dots = /\./g
index b7c3c33e10831df4402f16c53b1b6618c5b12231..e021238a7939a9d579cc9584ecc06cab0fea20bb 100644 (file)
@@ -1,19 +1,7 @@
-import {
-  delimiter,
-  dots,
-  hyphen,
-  isPathLetter,
-  numbersWithDots,
-  pathLetters
-} from '../modules/core/regex.js'
-import Point from './Point.js'
 import SVGArray from './SVGArray.js'
 import parser from '../modules/core/parser.js'
 import Box from './Box.js'
-
-export function pathRegReplace (a, b, c, d) {
-  return c + d.replace(dots, ' .')
-}
+import { pathParser } from '../utils/pathParser.js'
 
 function arrayToString (a) {
   for (var i = 0, il = a.length, s = ''; i < il; i++) {
@@ -51,79 +39,6 @@ function arrayToString (a) {
   return s + ' '
 }
 
-const pathHandlers = {
-  M: function (c, p, p0) {
-    p.x = p0.x = c[0]
-    p.y = p0.y = c[1]
-
-    return [ 'M', p.x, p.y ]
-  },
-  L: function (c, p) {
-    p.x = c[0]
-    p.y = c[1]
-    return [ 'L', c[0], c[1] ]
-  },
-  H: function (c, p) {
-    p.x = c[0]
-    return [ 'H', c[0] ]
-  },
-  V: function (c, p) {
-    p.y = c[0]
-    return [ 'V', c[0] ]
-  },
-  C: function (c, p) {
-    p.x = c[4]
-    p.y = c[5]
-    return [ 'C', c[0], c[1], c[2], c[3], c[4], c[5] ]
-  },
-  S: function (c, p) {
-    p.x = c[2]
-    p.y = c[3]
-    return [ 'S', c[0], c[1], c[2], c[3] ]
-  },
-  Q: function (c, p) {
-    p.x = c[2]
-    p.y = c[3]
-    return [ 'Q', c[0], c[1], c[2], c[3] ]
-  },
-  T: function (c, p) {
-    p.x = c[0]
-    p.y = c[1]
-    return [ 'T', c[0], c[1] ]
-  },
-  Z: function (c, p, p0) {
-    p.x = p0.x
-    p.y = p0.y
-    return [ 'Z' ]
-  },
-  A: function (c, p) {
-    p.x = c[5]
-    p.y = c[6]
-    return [ 'A', c[0], c[1], c[2], c[3], c[4], c[5], c[6] ]
-  }
-}
-
-const mlhvqtcsaz = 'mlhvqtcsaz'.split('')
-
-for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
-  pathHandlers[mlhvqtcsaz[i]] = (function (i) {
-    return function (c, p, p0) {
-      if (i === 'H') c[0] = c[0] + p.x
-      else if (i === 'V') c[0] = c[0] + p.y
-      else if (i === 'A') {
-        c[5] = c[5] + p.x
-        c[6] = c[6] + p.y
-      } else {
-        for (var j = 0, jl = c.length; j < jl; ++j) {
-          c[j] = c[j] + (j % 2 ? p.y : p.x)
-        }
-      }
-
-      return pathHandlers[i](c, p, p0)
-    }
-  })(mlhvqtcsaz[i].toUpperCase())
-}
-
 export default class PathArray extends SVGArray {
   // Get bounding box of path
   bbox () {
@@ -173,50 +88,12 @@ export default class PathArray extends SVGArray {
   }
 
   // Absolutize and parse path to array
-  parse (array = [ 'M', 0, 0 ]) {
-    // prepare for parsing
-    var s
-    var paramCnt = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 7, Z: 0 }
-
-    if (typeof array === 'string') {
-      array = array
-        .replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
-        .replace(pathLetters, ' $& ') // put some room between letters and numbers
-        .replace(hyphen, '$1 -') // add space before hyphen
-        .trim() // trim
-        .split(delimiter) // split into array
-    } else {
-      // Flatten array
-      array = Array.prototype.concat.apply([], array)
+  parse (d = 'M0 0') {
+    if (Array.isArray(d)) {
+      d = Array.prototype.concat.apply([], d).toString()
     }
 
-    // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
-    var result = []
-    var p = new Point()
-    var p0 = new Point()
-    var index = 0
-    var len = array.length
-
-    do {
-      // Test if we have a path letter
-      if (isPathLetter.test(array[index])) {
-        s = array[index]
-        ++index
-        // If last letter was a move command and we got no new, it defaults to [L]ine
-      } else if (s === 'M') {
-        s = 'L'
-      } else if (s === 'm') {
-        s = 'l'
-      }
-
-      result.push(pathHandlers[s].call(null,
-        array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
-        p, p0
-      )
-      )
-    } while (len > index)
-
-    return result
+    return pathParser(d)
   }
 
   // Resize path string
diff --git a/src/utils/pathParser.js b/src/utils/pathParser.js
new file mode 100644 (file)
index 0000000..1ab90a3
--- /dev/null
@@ -0,0 +1,234 @@
+import { isPathLetter } from '../modules/core/regex.js'
+import Point from '../types/Point.js'
+
+const segmentParameters = { M: 2, L: 2, H: 1, V: 1, C: 6, S: 4, Q: 4, T: 2, A: 7, Z: 0 }
+
+const pathHandlers = {
+  M: function (c, p, p0) {
+    p.x = p0.x = c[0]
+    p.y = p0.y = c[1]
+
+    return [ 'M', p.x, p.y ]
+  },
+  L: function (c, p) {
+    p.x = c[0]
+    p.y = c[1]
+    return [ 'L', c[0], c[1] ]
+  },
+  H: function (c, p) {
+    p.x = c[0]
+    return [ 'H', c[0] ]
+  },
+  V: function (c, p) {
+    p.y = c[0]
+    return [ 'V', c[0] ]
+  },
+  C: function (c, p) {
+    p.x = c[4]
+    p.y = c[5]
+    return [ 'C', c[0], c[1], c[2], c[3], c[4], c[5] ]
+  },
+  S: function (c, p) {
+    p.x = c[2]
+    p.y = c[3]
+    return [ 'S', c[0], c[1], c[2], c[3] ]
+  },
+  Q: function (c, p) {
+    p.x = c[2]
+    p.y = c[3]
+    return [ 'Q', c[0], c[1], c[2], c[3] ]
+  },
+  T: function (c, p) {
+    p.x = c[0]
+    p.y = c[1]
+    return [ 'T', c[0], c[1] ]
+  },
+  Z: function (c, p, p0) {
+    p.x = p0.x
+    p.y = p0.y
+    return [ 'Z' ]
+  },
+  A: function (c, p) {
+    p.x = c[5]
+    p.y = c[6]
+    return [ 'A', c[0], c[1], c[2], c[3], c[4], c[5], c[6] ]
+  }
+}
+
+const mlhvqtcsaz = 'mlhvqtcsaz'.split('')
+
+for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
+  pathHandlers[mlhvqtcsaz[i]] = (function (i) {
+    return function (c, p, p0) {
+      if (i === 'H') c[0] = c[0] + p.x
+      else if (i === 'V') c[0] = c[0] + p.y
+      else if (i === 'A') {
+        c[5] = c[5] + p.x
+        c[6] = c[6] + p.y
+      } else {
+        for (var j = 0, jl = c.length; j < jl; ++j) {
+          c[j] = c[j] + (j % 2 ? p.y : p.x)
+        }
+      }
+
+      return pathHandlers[i](c, p, p0)
+    }
+  })(mlhvqtcsaz[i].toUpperCase())
+}
+
+function makeAbsolut (parser) {
+  const command = parser.segment[0]
+  return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0)
+}
+
+function segmentComplete (parser) {
+  return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()]
+}
+
+function startNewSegment (parser, token) {
+  parser.inNumber && finalizeNumber(parser, false)
+  const pathLetter = isPathLetter.test(token)
+
+  if (pathLetter) {
+    parser.segment = [ token ]
+  } else {
+    const lastCommand = parser.lastCommand
+    const small = lastCommand.toLowerCase()
+    const isSmall = lastCommand === small
+    parser.segment = [ small === 'm' ? (isSmall ? 'l' : 'L') : lastCommand ]
+  }
+
+  parser.inSegment = true
+  parser.lastCommand = parser.segment[0]
+
+  return pathLetter
+}
+
+function finalizeNumber (parser, inNumber) {
+  if (!parser.inNumber) throw new Error('Parser Error')
+  parser.number && parser.segment.push(parseFloat(parser.number))
+  parser.inNumber = inNumber
+  parser.number = ''
+  parser.pointSeen = false
+  parser.hasExponent = false
+
+  if (segmentComplete(parser)) {
+    finalizeSegment(parser)
+  }
+}
+
+function finalizeSegment (parser) {
+  parser.inSegment = false
+  if (parser.absolute) {
+    parser.segment = makeAbsolut(parser)
+  }
+  parser.segments.push(parser.segment)
+}
+
+function isArcFlag (parser) {
+  if (!parser.segment.length) return false
+  const isArc = parser.segment[0].toUpperCase() === 'A'
+  const length = parser.segment.length
+
+  return isArc && (length === 4 || length === 5)
+}
+
+function isExponential (parser) {
+  return parser.lastToken.toUpperCase() === 'E'
+}
+
+export function pathParser (d, toAbsolute = true) {
+
+  let index = 0
+  let token = ''
+  const parser = {
+    segment: [],
+    inNumber: false,
+    number: '',
+    lastToken: '',
+    inSegment: false,
+    segments: [],
+    pointSeen: false,
+    hasExponent: false,
+    absolute: toAbsolute,
+    p0: new Point(),
+    p: new Point()
+  }
+
+  while ((parser.lastToken = token, token = d.charAt(index++))) {
+    if (!parser.inSegment) {
+      if (startNewSegment(parser, token)) {
+        continue
+      }
+    }
+
+    if (token === '.') {
+      if (parser.pointSeen || parser.hasExponent) {
+        finalizeNumber(parser, false)
+        --index
+        continue
+      }
+      parser.inNumber = true
+      parser.pointSeen = true
+      parser.number += token
+      continue
+    }
+
+    if (!isNaN(parseInt(token))) {
+
+      if (parser.number === '0' || (parser.inNumber && isArcFlag(parser))) {
+        finalizeNumber(parser, true)
+      }
+
+      parser.inNumber = true
+      parser.number += token
+      continue
+    }
+
+    if (token === ' ' || token === ',') {
+      if (parser.inNumber) {
+        finalizeNumber(parser, false)
+      }
+      continue
+    }
+
+    if (token === '-') {
+      if (parser.inNumber && !isExponential(parser)) {
+        finalizeNumber(parser, false)
+        --index
+        continue
+      }
+      parser.number += token
+      parser.inNumber = true
+      continue
+    }
+
+    if (token.toUpperCase() === 'E') {
+      parser.number += token
+      parser.hasExponent = true
+      continue
+    }
+
+    if (isPathLetter.test(token)) {
+      if (parser.inNumber) {
+        finalizeNumber(parser, false)
+      } else if (!segmentComplete(parser)) {
+        throw new Error('parser Error')
+      } else {
+        finalizeSegment(parser)
+      }
+      --index
+    }
+  }
+
+  if (parser.inNumber) {
+    finalizeNumber(parser, false)
+  }
+
+  if (parser.inSegment && segmentComplete(parser)) {
+    finalizeSegment(parser)
+  }
+
+  return parser.segments
+
+}