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
// 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,
coordsLength,
relative = false,
control,
current = new Point(); // the current position
function getCoord(index, coord, update) {
var val = 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);
var val = parseFloat(coords[index]);
if (relative)
val += current[coord];
if (update)
@ -125,36 +116,27 @@ var PathItem = Item.extend(/** @lends PathItem# */{
var part = parts[i],
cmd = part[0],
lower = cmd.toLowerCase();
// Split at white-space, commas but also before signs.
// Use positive lookahead to include signs.
// Note that we should also check for coordinate values that contain
// 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;
// Match all coordinate values
coords = part.match(/([+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)/g);
var length = coords && coords.length;
relative = cmd === lower;
switch (lower) {
case 'm':
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'](
getPoint(j, true));
break;
case 'h':
case 'v':
var coord = lower == 'h' ? 'x' : 'y';
for (var j = 0; j < coordsLength; j++) {
for (var j = 0; j < length; j++) {
getCoord(j, coord, true);
this.lineTo(current);
}
break;
case 'c':
for (var j = 0; j < coordsLength; j += 6) {
for (var j = 0; j < length; j += 6) {
this.cubicCurveTo(
getPoint(j),
control = getPoint(j + 2),
@ -163,7 +145,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{
break;
case 's':
// Shorthand cubic bezierCurveTo, absolute
for (var j = 0; j < coordsLength; j += 4) {
for (var j = 0; j < length; j += 4) {
this.cubicCurveTo(
// Calculate reflection of previous control points
current.multiply(2).subtract(control),
@ -172,14 +154,14 @@ var PathItem = Item.extend(/** @lends PathItem# */{
}
break;
case 'q':
for (var j = 0; j < coordsLength; j += 4) {
for (var j = 0; j < length; j += 4) {
this.quadraticCurveTo(
control = getPoint(j),
getPoint(j + 2, true));
}
break;
case 't':
for (var j = 0; j < coordsLength; j += 2) {
for (var j = 0; j < length; j += 2) {
this.quadraticCurveTo(
// Calculate reflection of previous control points
control = current.multiply(2).subtract(control),