aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2020-09-05 23:06:25 +1000
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2020-09-05 23:06:25 +1000
commit3f78cb81973f9c6c9a0754a555c939e81d24a1ff (patch)
tree3733c22051219b118ad5a09aff15838081be2820 /src
parentcfa5fd5b4138bdaea7b7afae3ccadac3735a3e7b (diff)
downloadsvg.js-3f78cb81973f9c6c9a0754a555c939e81d24a1ff.tar.gz
svg.js-3f78cb81973f9c6c9a0754a555c939e81d24a1ff.zip
fix path parsing (#1145)
Diffstat (limited to 'src')
-rw-r--r--src/animation/Morphable.js4
-rw-r--r--src/modules/core/regex.js16
-rw-r--r--src/types/PathArray.js133
-rw-r--r--src/utils/pathParser.js234
4 files changed, 242 insertions, 145 deletions
diff --git a/src/animation/Morphable.js b/src/animation/Morphable.js
index 46dd166..3344480 100644
--- a/src/animation/Morphable.js
+++ b/src/animation/Morphable.js
@@ -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)) {
diff --git a/src/modules/core/regex.js b/src/modules/core/regex.js
index c32ac93..a18c692 100644
--- a/src/modules/core/regex.js
+++ b/src/modules/core/regex.js
@@ -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
diff --git a/src/types/PathArray.js b/src/types/PathArray.js
index b7c3c33..e021238 100644
--- a/src/types/PathArray.js
+++ b/src/types/PathArray.js
@@ -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
index 0000000..1ab90a3
--- /dev/null
+++ b/src/utils/pathParser.js
@@ -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
+
+}