You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
666 lines
16 KiB
666 lines
16 KiB
2 years ago
|
// SVG Path transformations library
|
||
|
//
|
||
|
// Usage:
|
||
|
//
|
||
|
// SvgPath('...')
|
||
|
// .translate(-150, -100)
|
||
|
// .scale(0.5)
|
||
|
// .translate(-150, -100)
|
||
|
// .toFixed(1)
|
||
|
// .toString()
|
||
|
//
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
|
||
|
var pathParse = require('./path_parse');
|
||
|
var transformParse = require('./transform_parse');
|
||
|
var matrix = require('./matrix');
|
||
|
var a2c = require('./a2c');
|
||
|
var ellipse = require('./ellipse');
|
||
|
|
||
|
|
||
|
// Class constructor
|
||
|
//
|
||
|
function SvgPath(path) {
|
||
|
if (!(this instanceof SvgPath)) { return new SvgPath(path); }
|
||
|
|
||
|
var pstate = pathParse(path);
|
||
|
|
||
|
// Array of path segments.
|
||
|
// Each segment is array [command, param1, param2, ...]
|
||
|
this.segments = pstate.segments;
|
||
|
|
||
|
// Error message on parse error.
|
||
|
this.err = pstate.err;
|
||
|
|
||
|
// Transforms stack for lazy evaluation
|
||
|
this.__stack = [];
|
||
|
}
|
||
|
|
||
|
SvgPath.from = function (src) {
|
||
|
if (typeof src === 'string') return new SvgPath(src);
|
||
|
|
||
|
if (src instanceof SvgPath) {
|
||
|
// Create empty object
|
||
|
var s = new SvgPath('');
|
||
|
|
||
|
// Clone properies
|
||
|
s.err = src.err;
|
||
|
s.segments = src.segments.map(function (sgm) { return sgm.slice(); });
|
||
|
s.__stack = src.__stack.map(function (m) {
|
||
|
return matrix().matrix(m.toArray());
|
||
|
});
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
throw new Error('SvgPath.from: invalid param type ' + src);
|
||
|
};
|
||
|
|
||
|
|
||
|
SvgPath.prototype.__matrix = function (m) {
|
||
|
var self = this, i;
|
||
|
|
||
|
// Quick leave for empty matrix
|
||
|
if (!m.queue.length) { return; }
|
||
|
|
||
|
this.iterate(function (s, index, x, y) {
|
||
|
var p, result, name, isRelative;
|
||
|
|
||
|
switch (s[0]) {
|
||
|
|
||
|
// Process 'assymetric' commands separately
|
||
|
case 'v':
|
||
|
p = m.calc(0, s[1], true);
|
||
|
result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ];
|
||
|
break;
|
||
|
|
||
|
case 'V':
|
||
|
p = m.calc(x, s[1], false);
|
||
|
result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ];
|
||
|
break;
|
||
|
|
||
|
case 'h':
|
||
|
p = m.calc(s[1], 0, true);
|
||
|
result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ];
|
||
|
break;
|
||
|
|
||
|
case 'H':
|
||
|
p = m.calc(s[1], y, false);
|
||
|
result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ];
|
||
|
break;
|
||
|
|
||
|
case 'a':
|
||
|
case 'A':
|
||
|
// ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
|
||
|
|
||
|
// Drop segment if arc is empty (end point === start point)
|
||
|
/*if ((s[0] === 'A' && s[6] === x && s[7] === y) ||
|
||
|
(s[0] === 'a' && s[6] === 0 && s[7] === 0)) {
|
||
|
return [];
|
||
|
}*/
|
||
|
|
||
|
// Transform rx, ry and the x-axis-rotation
|
||
|
var ma = m.toArray();
|
||
|
var e = ellipse(s[1], s[2], s[3]).transform(ma);
|
||
|
|
||
|
// flip sweep-flag if matrix is not orientation-preserving
|
||
|
if (ma[0] * ma[3] - ma[1] * ma[2] < 0) {
|
||
|
s[5] = s[5] ? '0' : '1';
|
||
|
}
|
||
|
|
||
|
// Transform end point as usual (without translation for relative notation)
|
||
|
p = m.calc(s[6], s[7], s[0] === 'a');
|
||
|
|
||
|
// Empty arcs can be ignored by renderer, but should not be dropped
|
||
|
// to avoid collisions with `S A S` and so on. Replace with empty line.
|
||
|
if ((s[0] === 'A' && s[6] === x && s[7] === y) ||
|
||
|
(s[0] === 'a' && s[6] === 0 && s[7] === 0)) {
|
||
|
result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if the resulting ellipse is (almost) a segment ...
|
||
|
if (e.isDegenerate()) {
|
||
|
// replace the arc by a line
|
||
|
result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ];
|
||
|
} else {
|
||
|
// if it is a real ellipse
|
||
|
// s[0], s[4] and s[5] are not modified
|
||
|
result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ];
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 'm':
|
||
|
// Edge case. The very first `m` should be processed as absolute, if happens.
|
||
|
// Make sense for coord shift transforms.
|
||
|
isRelative = index > 0;
|
||
|
|
||
|
p = m.calc(s[1], s[2], isRelative);
|
||
|
result = [ 'm', p[0], p[1] ];
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
name = s[0];
|
||
|
result = [ name ];
|
||
|
isRelative = (name.toLowerCase() === name);
|
||
|
|
||
|
// Apply transformations to the segment
|
||
|
for (i = 1; i < s.length; i += 2) {
|
||
|
p = m.calc(s[i], s[i + 1], isRelative);
|
||
|
result.push(p[0], p[1]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.segments[index] = result;
|
||
|
}, true);
|
||
|
};
|
||
|
|
||
|
|
||
|
// Apply stacked commands
|
||
|
//
|
||
|
SvgPath.prototype.__evaluateStack = function () {
|
||
|
var m, i;
|
||
|
|
||
|
if (!this.__stack.length) { return; }
|
||
|
|
||
|
if (this.__stack.length === 1) {
|
||
|
this.__matrix(this.__stack[0]);
|
||
|
this.__stack = [];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m = matrix();
|
||
|
i = this.__stack.length;
|
||
|
|
||
|
while (--i >= 0) {
|
||
|
m.matrix(this.__stack[i].toArray());
|
||
|
}
|
||
|
|
||
|
this.__matrix(m);
|
||
|
this.__stack = [];
|
||
|
};
|
||
|
|
||
|
|
||
|
// Convert processed SVG Path back to string
|
||
|
//
|
||
|
SvgPath.prototype.toString = function () {
|
||
|
var result = '', prevCmd = '', cmdSkipped = false;
|
||
|
|
||
|
this.__evaluateStack();
|
||
|
|
||
|
for (var i = 0, len = this.segments.length; i < len; i++) {
|
||
|
var segment = this.segments[i];
|
||
|
var cmd = segment[0];
|
||
|
|
||
|
// Command not repeating => store
|
||
|
if (cmd !== prevCmd || cmd === 'm' || cmd === 'M') {
|
||
|
// workaround for FontForge SVG importing bug, keep space between "z m".
|
||
|
if (cmd === 'm' && prevCmd === 'z') result += ' ';
|
||
|
result += cmd;
|
||
|
|
||
|
cmdSkipped = false;
|
||
|
} else {
|
||
|
cmdSkipped = true;
|
||
|
}
|
||
|
|
||
|
// Store segment params
|
||
|
for (var pos = 1; pos < segment.length; pos++) {
|
||
|
var val = segment[pos];
|
||
|
// Space can be skipped
|
||
|
// 1. After command (always)
|
||
|
// 2. For negative value (with '-' at start)
|
||
|
if (pos === 1) {
|
||
|
if (cmdSkipped && val >= 0) result += ' ';
|
||
|
} else if (val >= 0) result += ' ';
|
||
|
|
||
|
result += val;
|
||
|
}
|
||
|
|
||
|
prevCmd = cmd;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Translate path to (x [, y])
|
||
|
//
|
||
|
SvgPath.prototype.translate = function (x, y) {
|
||
|
this.__stack.push(matrix().translate(x, y || 0));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Scale path to (sx [, sy])
|
||
|
// sy = sx if not defined
|
||
|
//
|
||
|
SvgPath.prototype.scale = function (sx, sy) {
|
||
|
this.__stack.push(matrix().scale(sx, (!sy && (sy !== 0)) ? sx : sy));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Rotate path around point (sx [, sy])
|
||
|
// sy = sx if not defined
|
||
|
//
|
||
|
SvgPath.prototype.rotate = function (angle, rx, ry) {
|
||
|
this.__stack.push(matrix().rotate(angle, rx || 0, ry || 0));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Skew path along the X axis by `degrees` angle
|
||
|
//
|
||
|
SvgPath.prototype.skewX = function (degrees) {
|
||
|
this.__stack.push(matrix().skewX(degrees));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Skew path along the Y axis by `degrees` angle
|
||
|
//
|
||
|
SvgPath.prototype.skewY = function (degrees) {
|
||
|
this.__stack.push(matrix().skewY(degrees));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Apply matrix transform (array of 6 elements)
|
||
|
//
|
||
|
SvgPath.prototype.matrix = function (m) {
|
||
|
this.__stack.push(matrix().matrix(m));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Transform path according to "transform" attr of SVG spec
|
||
|
//
|
||
|
SvgPath.prototype.transform = function (transformString) {
|
||
|
if (!transformString.trim()) {
|
||
|
return this;
|
||
|
}
|
||
|
this.__stack.push(transformParse(transformString));
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Round coords with given decimal precition.
|
||
|
// 0 by default (to integers)
|
||
|
//
|
||
|
SvgPath.prototype.round = function (d) {
|
||
|
var contourStartDeltaX = 0, contourStartDeltaY = 0, deltaX = 0, deltaY = 0, l;
|
||
|
|
||
|
d = d || 0;
|
||
|
|
||
|
this.__evaluateStack();
|
||
|
|
||
|
this.segments.forEach(function (s) {
|
||
|
var isRelative = (s[0].toLowerCase() === s[0]);
|
||
|
|
||
|
switch (s[0]) {
|
||
|
case 'H':
|
||
|
case 'h':
|
||
|
if (isRelative) { s[1] += deltaX; }
|
||
|
deltaX = s[1] - s[1].toFixed(d);
|
||
|
s[1] = +s[1].toFixed(d);
|
||
|
return;
|
||
|
|
||
|
case 'V':
|
||
|
case 'v':
|
||
|
if (isRelative) { s[1] += deltaY; }
|
||
|
deltaY = s[1] - s[1].toFixed(d);
|
||
|
s[1] = +s[1].toFixed(d);
|
||
|
return;
|
||
|
|
||
|
case 'Z':
|
||
|
case 'z':
|
||
|
deltaX = contourStartDeltaX;
|
||
|
deltaY = contourStartDeltaY;
|
||
|
return;
|
||
|
|
||
|
case 'M':
|
||
|
case 'm':
|
||
|
if (isRelative) {
|
||
|
s[1] += deltaX;
|
||
|
s[2] += deltaY;
|
||
|
}
|
||
|
|
||
|
deltaX = s[1] - s[1].toFixed(d);
|
||
|
deltaY = s[2] - s[2].toFixed(d);
|
||
|
|
||
|
contourStartDeltaX = deltaX;
|
||
|
contourStartDeltaY = deltaY;
|
||
|
|
||
|
s[1] = +s[1].toFixed(d);
|
||
|
s[2] = +s[2].toFixed(d);
|
||
|
return;
|
||
|
|
||
|
case 'A':
|
||
|
case 'a':
|
||
|
// [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
|
||
|
if (isRelative) {
|
||
|
s[6] += deltaX;
|
||
|
s[7] += deltaY;
|
||
|
}
|
||
|
|
||
|
deltaX = s[6] - s[6].toFixed(d);
|
||
|
deltaY = s[7] - s[7].toFixed(d);
|
||
|
|
||
|
s[1] = +s[1].toFixed(d);
|
||
|
s[2] = +s[2].toFixed(d);
|
||
|
s[3] = +s[3].toFixed(d + 2); // better precision for rotation
|
||
|
s[6] = +s[6].toFixed(d);
|
||
|
s[7] = +s[7].toFixed(d);
|
||
|
return;
|
||
|
|
||
|
default:
|
||
|
// a c l q s t
|
||
|
l = s.length;
|
||
|
|
||
|
if (isRelative) {
|
||
|
s[l - 2] += deltaX;
|
||
|
s[l - 1] += deltaY;
|
||
|
}
|
||
|
|
||
|
deltaX = s[l - 2] - s[l - 2].toFixed(d);
|
||
|
deltaY = s[l - 1] - s[l - 1].toFixed(d);
|
||
|
|
||
|
s.forEach(function (val, i) {
|
||
|
if (!i) { return; }
|
||
|
s[i] = +s[i].toFixed(d);
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Apply iterator function to all segments. If function returns result,
|
||
|
// current segment will be replaced to array of returned segments.
|
||
|
// If empty array is returned, current regment will be deleted.
|
||
|
//
|
||
|
SvgPath.prototype.iterate = function (iterator, keepLazyStack) {
|
||
|
var segments = this.segments,
|
||
|
replacements = {},
|
||
|
needReplace = false,
|
||
|
lastX = 0,
|
||
|
lastY = 0,
|
||
|
countourStartX = 0,
|
||
|
countourStartY = 0;
|
||
|
var i, j, newSegments;
|
||
|
|
||
|
if (!keepLazyStack) {
|
||
|
this.__evaluateStack();
|
||
|
}
|
||
|
|
||
|
segments.forEach(function (s, index) {
|
||
|
|
||
|
var res = iterator(s, index, lastX, lastY);
|
||
|
|
||
|
if (Array.isArray(res)) {
|
||
|
replacements[index] = res;
|
||
|
needReplace = true;
|
||
|
}
|
||
|
|
||
|
var isRelative = (s[0] === s[0].toLowerCase());
|
||
|
|
||
|
// calculate absolute X and Y
|
||
|
switch (s[0]) {
|
||
|
case 'm':
|
||
|
case 'M':
|
||
|
lastX = s[1] + (isRelative ? lastX : 0);
|
||
|
lastY = s[2] + (isRelative ? lastY : 0);
|
||
|
countourStartX = lastX;
|
||
|
countourStartY = lastY;
|
||
|
return;
|
||
|
|
||
|
case 'h':
|
||
|
case 'H':
|
||
|
lastX = s[1] + (isRelative ? lastX : 0);
|
||
|
return;
|
||
|
|
||
|
case 'v':
|
||
|
case 'V':
|
||
|
lastY = s[1] + (isRelative ? lastY : 0);
|
||
|
return;
|
||
|
|
||
|
case 'z':
|
||
|
case 'Z':
|
||
|
// That make sence for multiple contours
|
||
|
lastX = countourStartX;
|
||
|
lastY = countourStartY;
|
||
|
return;
|
||
|
|
||
|
default:
|
||
|
lastX = s[s.length - 2] + (isRelative ? lastX : 0);
|
||
|
lastY = s[s.length - 1] + (isRelative ? lastY : 0);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Replace segments if iterator return results
|
||
|
|
||
|
if (!needReplace) { return this; }
|
||
|
|
||
|
newSegments = [];
|
||
|
|
||
|
for (i = 0; i < segments.length; i++) {
|
||
|
if (typeof replacements[i] !== 'undefined') {
|
||
|
for (j = 0; j < replacements[i].length; j++) {
|
||
|
newSegments.push(replacements[i][j]);
|
||
|
}
|
||
|
} else {
|
||
|
newSegments.push(segments[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.segments = newSegments;
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Converts segments from relative to absolute
|
||
|
//
|
||
|
SvgPath.prototype.abs = function () {
|
||
|
|
||
|
this.iterate(function (s, index, x, y) {
|
||
|
var name = s[0],
|
||
|
nameUC = name.toUpperCase(),
|
||
|
i;
|
||
|
|
||
|
// Skip absolute commands
|
||
|
if (name === nameUC) { return; }
|
||
|
|
||
|
s[0] = nameUC;
|
||
|
|
||
|
switch (name) {
|
||
|
case 'v':
|
||
|
// v has shifted coords parity
|
||
|
s[1] += y;
|
||
|
return;
|
||
|
|
||
|
case 'a':
|
||
|
// ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
|
||
|
// touch x, y only
|
||
|
s[6] += x;
|
||
|
s[7] += y;
|
||
|
return;
|
||
|
|
||
|
default:
|
||
|
for (i = 1; i < s.length; i++) {
|
||
|
s[i] += i % 2 ? x : y; // odd values are X, even - Y
|
||
|
}
|
||
|
}
|
||
|
}, true);
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Converts segments from absolute to relative
|
||
|
//
|
||
|
SvgPath.prototype.rel = function () {
|
||
|
|
||
|
this.iterate(function (s, index, x, y) {
|
||
|
var name = s[0],
|
||
|
nameLC = name.toLowerCase(),
|
||
|
i;
|
||
|
|
||
|
// Skip relative commands
|
||
|
if (name === nameLC) { return; }
|
||
|
|
||
|
// Don't touch the first M to avoid potential confusions.
|
||
|
if (index === 0 && name === 'M') { return; }
|
||
|
|
||
|
s[0] = nameLC;
|
||
|
|
||
|
switch (name) {
|
||
|
case 'V':
|
||
|
// V has shifted coords parity
|
||
|
s[1] -= y;
|
||
|
return;
|
||
|
|
||
|
case 'A':
|
||
|
// ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
|
||
|
// touch x, y only
|
||
|
s[6] -= x;
|
||
|
s[7] -= y;
|
||
|
return;
|
||
|
|
||
|
default:
|
||
|
for (i = 1; i < s.length; i++) {
|
||
|
s[i] -= i % 2 ? x : y; // odd values are X, even - Y
|
||
|
}
|
||
|
}
|
||
|
}, true);
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Converts arcs to cubic bézier curves
|
||
|
//
|
||
|
SvgPath.prototype.unarc = function () {
|
||
|
this.iterate(function (s, index, x, y) {
|
||
|
var new_segments, nextX, nextY, result = [], name = s[0];
|
||
|
|
||
|
// Skip anything except arcs
|
||
|
if (name !== 'A' && name !== 'a') { return null; }
|
||
|
|
||
|
if (name === 'a') {
|
||
|
// convert relative arc coordinates to absolute
|
||
|
nextX = x + s[6];
|
||
|
nextY = y + s[7];
|
||
|
} else {
|
||
|
nextX = s[6];
|
||
|
nextY = s[7];
|
||
|
}
|
||
|
|
||
|
new_segments = a2c(x, y, nextX, nextY, s[4], s[5], s[1], s[2], s[3]);
|
||
|
|
||
|
// Degenerated arcs can be ignored by renderer, but should not be dropped
|
||
|
// to avoid collisions with `S A S` and so on. Replace with empty line.
|
||
|
if (new_segments.length === 0) {
|
||
|
return [ [ s[0] === 'a' ? 'l' : 'L', s[6], s[7] ] ];
|
||
|
}
|
||
|
|
||
|
new_segments.forEach(function (s) {
|
||
|
result.push([ 'C', s[2], s[3], s[4], s[5], s[6], s[7] ]);
|
||
|
});
|
||
|
|
||
|
return result;
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Converts smooth curves (with missed control point) to generic curves
|
||
|
//
|
||
|
SvgPath.prototype.unshort = function () {
|
||
|
var segments = this.segments;
|
||
|
var prevControlX, prevControlY, prevSegment;
|
||
|
var curControlX, curControlY;
|
||
|
|
||
|
// TODO: add lazy evaluation flag when relative commands supported
|
||
|
|
||
|
this.iterate(function (s, idx, x, y) {
|
||
|
var name = s[0], nameUC = name.toUpperCase(), isRelative;
|
||
|
|
||
|
// First command MUST be M|m, it's safe to skip.
|
||
|
// Protect from access to [-1] for sure.
|
||
|
if (!idx) { return; }
|
||
|
|
||
|
if (nameUC === 'T') { // quadratic curve
|
||
|
isRelative = (name === 't');
|
||
|
|
||
|
prevSegment = segments[idx - 1];
|
||
|
|
||
|
if (prevSegment[0] === 'Q') {
|
||
|
prevControlX = prevSegment[1] - x;
|
||
|
prevControlY = prevSegment[2] - y;
|
||
|
} else if (prevSegment[0] === 'q') {
|
||
|
prevControlX = prevSegment[1] - prevSegment[3];
|
||
|
prevControlY = prevSegment[2] - prevSegment[4];
|
||
|
} else {
|
||
|
prevControlX = 0;
|
||
|
prevControlY = 0;
|
||
|
}
|
||
|
|
||
|
curControlX = -prevControlX;
|
||
|
curControlY = -prevControlY;
|
||
|
|
||
|
if (!isRelative) {
|
||
|
curControlX += x;
|
||
|
curControlY += y;
|
||
|
}
|
||
|
|
||
|
segments[idx] = [
|
||
|
isRelative ? 'q' : 'Q',
|
||
|
curControlX, curControlY,
|
||
|
s[1], s[2]
|
||
|
];
|
||
|
|
||
|
} else if (nameUC === 'S') { // cubic curve
|
||
|
isRelative = (name === 's');
|
||
|
|
||
|
prevSegment = segments[idx - 1];
|
||
|
|
||
|
if (prevSegment[0] === 'C') {
|
||
|
prevControlX = prevSegment[3] - x;
|
||
|
prevControlY = prevSegment[4] - y;
|
||
|
} else if (prevSegment[0] === 'c') {
|
||
|
prevControlX = prevSegment[3] - prevSegment[5];
|
||
|
prevControlY = prevSegment[4] - prevSegment[6];
|
||
|
} else {
|
||
|
prevControlX = 0;
|
||
|
prevControlY = 0;
|
||
|
}
|
||
|
|
||
|
curControlX = -prevControlX;
|
||
|
curControlY = -prevControlY;
|
||
|
|
||
|
if (!isRelative) {
|
||
|
curControlX += x;
|
||
|
curControlY += y;
|
||
|
}
|
||
|
|
||
|
segments[idx] = [
|
||
|
isRelative ? 'c' : 'C',
|
||
|
curControlX, curControlY,
|
||
|
s[1], s[2], s[3], s[4]
|
||
|
];
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
|
||
|
module.exports = SvgPath;
|