Improve SVG path data parser.

This commit is contained in:
Jürg Lehni 2013-11-02 09:30:40 +01:00
parent c99d13178b
commit 97a29e6ada

View file

@ -85,25 +85,16 @@ var PathItem = Item.extend(/** @lends PathItem# */{
// This is a very compact SVG Path Data parser that works both for Path // This is a very compact SVG Path Data parser that works both for Path
// and CompoundPath. // and CompoundPath.
var parts = data.match(/[a-z][^a-z]*/ig), // First split the path data into parts of command-coordinates pairs
// Commands are any of these characters: mzlhvcsqta
var parts = data.match(/[mzlhvcsqta][^mzlhvcsqta]*/ig),
coords, coords,
coordsLength,
relative = false, relative = false,
control, control,
current = new Point(); // the current position current = new Point(); // the current position
function getCoord(index, coord, update) { function getCoord(index, coord, update) {
var val = coords[index]; var val = parseFloat(coords[index]);
// Before parsing the value, we might actually have to further split
// it, in case it contains two '.':
var match = val.match(/^([+-]?\d*\.\d+)(\.\d*)$/);
if (match) {
// Insert the 2nd half as the next value.
coords.splice(index + 1, 0, match[2]);
coordsLength++;
val = match[1];
}
val = parseFloat(val);
if (relative) if (relative)
val += current[coord]; val += current[coord];
if (update) if (update)
@ -125,36 +116,27 @@ var PathItem = Item.extend(/** @lends PathItem# */{
var part = parts[i], var part = parts[i],
cmd = part[0], cmd = part[0],
lower = cmd.toLowerCase(); lower = cmd.toLowerCase();
// Split at white-space, commas but also before signs. // Match all coordinate values
// Use positive lookahead to include signs. coords = part.match(/([+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)/g);
// Note that we should also check for coordinate values that contain var length = coords && coords.length;
// two decimal periods, as these should be plit into two separate
// values. With positive lookbehind, this would look like this:
// |(?<=\d*\.\d+)(?=\.\d+)
// Unfortunately, JavaScript does not have lookbehind in RegExps, so
// instead we leave these values that way and parse them further in
// getCoord() above. Note that coordsLength may increase as we're
// reading values.
coords = part.slice(1).trim().split(/[\s,]+|(?=[+-])/);
coordsLength = coords.length;
relative = cmd === lower; relative = cmd === lower;
switch (lower) { switch (lower) {
case 'm': case 'm':
case 'l': case 'l':
for (var j = 0; j < coordsLength; j += 2) for (var j = 0; j < length; j += 2)
this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo']( this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
getPoint(j, true)); getPoint(j, true));
break; break;
case 'h': case 'h':
case 'v': case 'v':
var coord = lower == 'h' ? 'x' : 'y'; var coord = lower == 'h' ? 'x' : 'y';
for (var j = 0; j < coordsLength; j++) { for (var j = 0; j < length; j++) {
getCoord(j, coord, true); getCoord(j, coord, true);
this.lineTo(current); this.lineTo(current);
} }
break; break;
case 'c': case 'c':
for (var j = 0; j < coordsLength; j += 6) { for (var j = 0; j < length; j += 6) {
this.cubicCurveTo( this.cubicCurveTo(
getPoint(j), getPoint(j),
control = getPoint(j + 2), control = getPoint(j + 2),
@ -163,7 +145,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
break; break;
case 's': case 's':
// Shorthand cubic bezierCurveTo, absolute // Shorthand cubic bezierCurveTo, absolute
for (var j = 0; j < coordsLength; j += 4) { for (var j = 0; j < length; j += 4) {
this.cubicCurveTo( this.cubicCurveTo(
// Calculate reflection of previous control points // Calculate reflection of previous control points
current.multiply(2).subtract(control), current.multiply(2).subtract(control),
@ -172,14 +154,14 @@ var PathItem = Item.extend(/** @lends PathItem# */{
} }
break; break;
case 'q': case 'q':
for (var j = 0; j < coordsLength; j += 4) { for (var j = 0; j < length; j += 4) {
this.quadraticCurveTo( this.quadraticCurveTo(
control = getPoint(j), control = getPoint(j),
getPoint(j + 2, true)); getPoint(j + 2, true));
} }
break; break;
case 't': case 't':
for (var j = 0; j < coordsLength; j += 2) { for (var j = 0; j < length; j += 2) {
this.quadraticCurveTo( this.quadraticCurveTo(
// Calculate reflection of previous control points // Calculate reflection of previous control points
control = current.multiply(2).subtract(control), control = current.multiply(2).subtract(control),