fabric-pure-browser
Version:
Fabric.js package with no node-specific dependencies (node-canvas, jsdom). The project is published once a day (in case if a new version appears) from 'master' branch of https://github.com/fabricjs/fabric.js repository. You can keep original imports in
830 lines (781 loc) • 26.4 kB
JavaScript
(function() {
var _join = Array.prototype.join,
commandLengths = {
m: 2,
l: 2,
h: 1,
v: 1,
c: 6,
s: 4,
q: 4,
t: 2,
a: 7
},
repeatedCommands = {
m: 'l',
M: 'L'
};
function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
var costh2 = fabric.util.cos(th2),
sinth2 = fabric.util.sin(th2),
costh3 = fabric.util.cos(th3),
sinth3 = fabric.util.sin(th3),
toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2),
cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
return ['C',
cp1X, cp1Y,
cp2X, cp2Y,
toX, toY
];
}
/* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
* by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
* http://mozilla.org/MPL/2.0/
*/
function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
var PI = Math.PI, th = rotateX * PI / 180,
sinTh = fabric.util.sin(th),
cosTh = fabric.util.cos(th),
fromX = 0, fromY = 0;
rx = Math.abs(rx);
ry = Math.abs(ry);
var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
root = 0;
if (pl < 0) {
var s = Math.sqrt(1 - pl / (rx2 * ry2));
rx *= s;
ry *= s;
}
else {
root = (large === sweep ? -1.0 : 1.0) *
Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
}
var cx = root * rx * py / ry,
cy = -root * ry * px / rx,
cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
if (sweep === 0 && dtheta > 0) {
dtheta -= 2 * PI;
}
else if (sweep === 1 && dtheta < 0) {
dtheta += 2 * PI;
}
// Convert into cubic bezier segments <= 90deg
var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
result = [], mDelta = dtheta / segments,
mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
th3 = mTheta + mDelta;
for (var i = 0; i < segments; i++) {
result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
fromX = result[i][5];
fromY = result[i][6];
mTheta = th3;
th3 += mDelta;
}
return result;
}
/*
* Private
*/
function calcVectorAngle(ux, uy, vx, vy) {
var ta = Math.atan2(uy, ux),
tb = Math.atan2(vy, vx);
if (tb >= ta) {
return tb - ta;
}
else {
return 2 * Math.PI - (ta - tb);
}
}
/**
* Calculate bounding box of a beziercurve
* @param {Number} x0 starting point
* @param {Number} y0
* @param {Number} x1 first control point
* @param {Number} y1
* @param {Number} x2 secondo control point
* @param {Number} y2
* @param {Number} x3 end of bezier
* @param {Number} y3
*/
// taken from http://jsbin.com/ivomiq/56/edit no credits available for that.
// TODO: can we normalize this with the starting points set at 0 and then translated the bbox?
function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
var argsString;
if (fabric.cachesBoundsOfCurve) {
argsString = _join.call(arguments);
if (fabric.boundsOfCurveCache[argsString]) {
return fabric.boundsOfCurveCache[argsString];
}
}
var sqrt = Math.sqrt,
min = Math.min, max = Math.max,
abs = Math.abs, tvalues = [],
bounds = [[], []],
a, b, c, t, t1, t2, b2ac, sqrtb2ac;
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
for (var i = 0; i < 2; ++i) {
if (i > 0) {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (abs(a) < 1e-12) {
if (abs(b) < 1e-12) {
continue;
}
t = -c / b;
if (0 < t && t < 1) {
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
if (b2ac < 0) {
continue;
}
sqrtb2ac = sqrt(b2ac);
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1) {
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1) {
tvalues.push(t2);
}
}
var x, y, j = tvalues.length, jlen = j, mt;
while (j--) {
t = tvalues[j];
mt = 1 - t;
x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
bounds[0][j] = x;
y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
bounds[1][j] = y;
}
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
var result = [
{
x: min.apply(null, bounds[0]),
y: min.apply(null, bounds[1])
},
{
x: max.apply(null, bounds[0]),
y: max.apply(null, bounds[1])
}
];
if (fabric.cachesBoundsOfCurve) {
fabric.boundsOfCurveCache[argsString] = result;
}
return result;
}
/**
* Converts arc to a bunch of bezier curves
* @param {Number} fx starting point x
* @param {Number} fy starting point y
* @param {Array} coords Arc command
*/
function fromArcToBeziers(fx, fy, coords) {
var rx = coords[1],
ry = coords[2],
rot = coords[3],
large = coords[4],
sweep = coords[5],
tx = coords[6],
ty = coords[7],
segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
for (var i = 0, len = segsNorm.length; i < len; i++) {
segsNorm[i][1] += fx;
segsNorm[i][2] += fy;
segsNorm[i][3] += fx;
segsNorm[i][4] += fy;
segsNorm[i][5] += fx;
segsNorm[i][6] += fy;
}
return segsNorm;
};
/**
* This function take a parsed SVG path and make it simpler for fabricJS logic.
* simplification consist of: only UPPERCASE absolute commands ( relative converted to absolute )
* S converted in C, T converted in Q, A converted in C.
* @param {Array} path the array of commands of a parsed svg path for fabric.Path
* @return {Array} the simplified array of commands of a parsed svg path for fabric.Path
*/
function makePathSimpler(path) {
// x and y represent the last point of the path. the previous command point.
// we add them to each relative command to make it an absolute comment.
// we also swap the v V h H with L, because are easier to transform.
var x = 0, y = 0, len = path.length,
// x1 and y1 represent the last point of the subpath. the subpath is started with
// m or M command. When a z or Z command is drawn, x and y need to be resetted to
// the last x1 and y1.
x1 = 0, y1 = 0, current, i, converted,
// previous will host the letter of the previous command, to handle S and T.
// controlX and controlY will host the previous reflected control point
destinationPath = [], previous, controlX, controlY;
for (i = 0; i < len; ++i) {
converted = false;
current = path[i].slice(0);
switch (current[0]) { // first letter
case 'l': // lineto, relative
current[0] = 'L';
current[1] += x;
current[2] += y;
// falls through
case 'L':
x = current[1];
y = current[2];
break;
case 'h': // horizontal lineto, relative
current[1] += x;
// falls through
case 'H':
current[0] = 'L';
current[2] = y;
x = current[1];
break;
case 'v': // vertical lineto, relative
current[1] += y;
// falls through
case 'V':
current[0] = 'L';
y = current[1];
current[1] = x;
current[2] = y;
break;
case 'm': // moveTo, relative
current[0] = 'M';
current[1] += x;
current[2] += y;
// falls through
case 'M':
x = current[1];
y = current[2];
x1 = current[1];
y1 = current[2];
break;
case 'c': // bezierCurveTo, relative
current[0] = 'C';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
current[5] += x;
current[6] += y;
// falls through
case 'C':
controlX = current[3];
controlY = current[4];
x = current[5];
y = current[6];
break;
case 's': // shorthand cubic bezierCurveTo, relative
current[0] = 'S';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'S':
// would be sScC but since we are swapping sSc for C, we check just that.
if (previous === 'C') {
// calculate reflection of previous control points
controlX = 2 * x - controlX;
controlY = 2 * y - controlY;
}
else {
// If there is no previous command or if the previous command was not a C, c, S, or s,
// the control point is coincident with the current point
controlX = x;
controlY = y;
}
x = current[3];
y = current[4];
current[0] = 'C';
current[5] = current[3];
current[6] = current[4];
current[3] = current[1];
current[4] = current[2];
current[1] = controlX;
current[2] = controlY;
// current[3] and current[4] are NOW the second control point.
// we keep it for the next reflection.
controlX = current[3];
controlY = current[4];
break;
case 'q': // quadraticCurveTo, relative
current[0] = 'Q';
current[1] += x;
current[2] += y;
current[3] += x;
current[4] += y;
// falls through
case 'Q':
controlX = current[1];
controlY = current[2];
x = current[3];
y = current[4];
break;
case 't': // shorthand quadraticCurveTo, relative
current[0] = 'T';
current[1] += x;
current[2] += y;
// falls through
case 'T':
if (previous === 'Q') {
// calculate reflection of previous control point
controlX = 2 * x - controlX;
controlY = 2 * y - controlY;
}
else {
// If there is no previous command or if the previous command was not a Q, q, T or t,
// assume the control point is coincident with the current point
controlX = x;
controlY = y;
}
current[0] = 'Q';
x = current[1];
y = current[2];
current[1] = controlX;
current[2] = controlY;
current[3] = x;
current[4] = y;
break;
case 'a':
current[0] = 'A';
current[6] += x;
current[7] += y;
// falls through
case 'A':
converted = true;
destinationPath = destinationPath.concat(fromArcToBeziers(x, y, current));
x = current[6];
y = current[7];
break;
case 'z':
case 'Z':
x = x1;
y = y1;
break;
default:
}
if (!converted) {
destinationPath.push(current);
}
previous = current[0];
}
return destinationPath;
};
/**
* Calc length from point x1,y1 to x2,y2
* @param {Number} x1 starting point x
* @param {Number} y1 starting point y
* @param {Number} x2 starting point x
* @param {Number} y2 starting point y
* @return {Number} length of segment
*/
function calcLineLength(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
// functions for the Cubic beizer
// taken from: https://github.com/konvajs/konva/blob/7.0.5/src/shapes/Path.ts#L350
function CB1(t) {
return t * t * t;
}
function CB2(t) {
return 3 * t * t * (1 - t);
}
function CB3(t) {
return 3 * t * (1 - t) * (1 - t);
}
function CB4(t) {
return (1 - t) * (1 - t) * (1 - t);
}
function getPointOnCubicBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
return function(pct) {
var c1 = CB1(pct), c2 = CB2(pct), c3 = CB3(pct), c4 = CB4(pct);
return {
x: p4x * c1 + p3x * c2 + p2x * c3 + p1x * c4,
y: p4y * c1 + p3y * c2 + p2y * c3 + p1y * c4
};
};
}
function getTangentCubicIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) {
return function (pct) {
var invT = 1 - pct,
tangentX = (3 * invT * invT * (p2x - p1x)) + (6 * invT * pct * (p3x - p2x)) +
(3 * pct * pct * (p4x - p3x)),
tangentY = (3 * invT * invT * (p2y - p1y)) + (6 * invT * pct * (p3y - p2y)) +
(3 * pct * pct * (p4y - p3y));
return Math.atan2(tangentY, tangentX);
};
}
function QB1(t) {
return t * t;
}
function QB2(t) {
return 2 * t * (1 - t);
}
function QB3(t) {
return (1 - t) * (1 - t);
}
function getPointOnQuadraticBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y) {
return function(pct) {
var c1 = QB1(pct), c2 = QB2(pct), c3 = QB3(pct);
return {
x: p3x * c1 + p2x * c2 + p1x * c3,
y: p3y * c1 + p2y * c2 + p1y * c3
};
};
}
function getTangentQuadraticIterator(p1x, p1y, p2x, p2y, p3x, p3y) {
return function (pct) {
var invT = 1 - pct,
tangentX = (2 * invT * (p2x - p1x)) + (2 * pct * (p3x - p2x)),
tangentY = (2 * invT * (p2y - p1y)) + (2 * pct * (p3y - p2y));
return Math.atan2(tangentY, tangentX);
};
}
// this will run over a path segment ( a cubic or quadratic segment) and approximate it
// with 100 segemnts. This will good enough to calculate the length of the curve
function pathIterator(iterator, x1, y1) {
var tempP = { x: x1, y: y1 }, p, tmpLen = 0, perc;
for (perc = 1; perc <= 100; perc += 1) {
p = iterator(perc / 100);
tmpLen += calcLineLength(tempP.x, tempP.y, p.x, p.y);
tempP = p;
}
return tmpLen;
}
/**
* Given a pathInfo, and a distance in pixels, find the percentage from 0 to 1
* that correspond to that pixels run over the path.
* The percentage will be then used to find the correct point on the canvas for the path.
* @param {Array} segInfo fabricJS collection of information on a parsed path
* @param {Number} distance from starting point, in pixels.
* @return {Object} info object with x and y ( the point on canvas ) and angle, the tangent on that point;
*/
function findPercentageForDistance(segInfo, distance) {
var perc = 0, tmpLen = 0, iterator = segInfo.iterator, tempP = { x: segInfo.x, y: segInfo.y },
p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc;
// nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100
// the path
while (tmpLen < distance && nextStep > 0.0001) {
p = iterator(perc);
lastPerc = perc;
nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y);
// compare tmpLen each cycle with distance, decide next perc to test.
if ((nextLen + tmpLen) > distance) {
// we discard this step and we make smaller steps.
perc -= nextStep;
nextStep /= 2;
}
else {
tempP = p;
perc += nextStep;
tmpLen += nextLen;
}
}
p.angle = angleFinder(lastPerc);
return p;
}
/**
* Run over a parsed and simplifed path and extrac some informations.
* informations are length of each command and starting point
* @param {Array} path fabricJS parsed path commands
* @return {Array} path commands informations
*/
function getPathSegmentsInfo(path) {
var totalLength = 0, len = path.length, current,
//x2 and y2 are the coords of segment start
//x1 and y1 are the coords of the current point
x1 = 0, y1 = 0, x2 = 0, y2 = 0, info = [], iterator, tempInfo, angleFinder;
for (var i = 0; i < len; i++) {
current = path[i];
tempInfo = {
x: x1,
y: y1,
command: current[0],
};
switch (current[0]) { //first letter
case 'M':
tempInfo.length = 0;
x2 = x1 = current[1];
y2 = y1 = current[2];
break;
case 'L':
tempInfo.length = calcLineLength(x1, y1, current[1], current[2]);
x1 = current[1];
y1 = current[2];
break;
case 'C':
iterator = getPointOnCubicBezierIterator(
x1,
y1,
current[1],
current[2],
current[3],
current[4],
current[5],
current[6]
);
angleFinder = getTangentCubicIterator(
x1,
y1,
current[1],
current[2],
current[3],
current[4],
current[5],
current[6]
);
tempInfo.iterator = iterator;
tempInfo.angleFinder = angleFinder;
tempInfo.length = pathIterator(iterator, x1, y1);
x1 = current[5];
y1 = current[6];
break;
case 'Q':
iterator = getPointOnQuadraticBezierIterator(
x1,
y1,
current[1],
current[2],
current[3],
current[4]
);
angleFinder = getTangentQuadraticIterator(
x1,
y1,
current[1],
current[2],
current[3],
current[4]
);
tempInfo.iterator = iterator;
tempInfo.angleFinder = angleFinder;
tempInfo.length = pathIterator(iterator, x1, y1);
x1 = current[3];
y1 = current[4];
break;
case 'Z':
case 'z':
// we add those in order to ease calculations later
tempInfo.destX = x2;
tempInfo.destY = y2;
tempInfo.length = calcLineLength(x1, y1, x2, y2);
x1 = x2;
y1 = y2;
break;
}
totalLength += tempInfo.length;
info.push(tempInfo);
}
info.push({ length: totalLength, x: x1, y: y1 });
return info;
}
function getPointOnPath(path, distance, infos) {
if (!infos) {
infos = getPathSegmentsInfo(path);
}
var i = 0;
while ((distance - infos[i].length > 0) && i < (infos.length - 2)) {
distance -= infos[i].length;
i++;
}
// var distance = infos[infos.length - 1] * perc;
var segInfo = infos[i], segPercent = distance / segInfo.length,
command = segInfo.command, segment = path[i], info;
switch (command) {
case 'M':
return { x: segInfo.x, y: segInfo.y, angle: 0 };
case 'Z':
case 'z':
info = new fabric.Point(segInfo.x, segInfo.y).lerp(
new fabric.Point(segInfo.destX, segInfo.destY),
segPercent
);
info.angle = Math.atan2(segInfo.destY - segInfo.y, segInfo.destX - segInfo.x);
return info;
case 'L':
info = new fabric.Point(segInfo.x, segInfo.y).lerp(
new fabric.Point(segment[1], segment[2]),
segPercent
);
info.angle = Math.atan2(segment[2] - segInfo.y, segment[1] - segInfo.x);
return info;
case 'C':
return findPercentageForDistance(segInfo, distance);
case 'Q':
return findPercentageForDistance(segInfo, distance);
}
}
/**
*
* @param {string} pathString
* @return {(string|number)[][]} An array of SVG path commands
* @example <caption>Usage</caption>
* parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [
* ['M', 3, 4],
* ['Q', 3, 5, 2, 1, 4, 0],
* ['Q', 9, 12, 2, 1, 4, 0],
* ];
*
*/
function parsePath(pathString) {
var result = [],
coords = [],
currentPath,
parsed,
re = fabric.rePathCommand,
rNumber = '[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?\\s*',
rNumberCommaWsp = '(' + rNumber + ')' + fabric.commaWsp,
rFlagCommaWsp = '([01])' + fabric.commaWsp + '?',
rArcSeq = rNumberCommaWsp + '?' + rNumberCommaWsp + '?' + rNumberCommaWsp + rFlagCommaWsp + rFlagCommaWsp +
rNumberCommaWsp + '?(' + rNumber + ')',
regArcArgumentSequence = new RegExp(rArcSeq, 'g'),
match,
coordsStr,
// one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
path;
if (!pathString || !pathString.match) {
return result;
}
path = pathString.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
for (var i = 0, coordsParsed, len = path.length; i < len; i++) {
currentPath = path[i];
coordsStr = currentPath.slice(1).trim();
coords.length = 0;
var command = currentPath.charAt(0);
coordsParsed = [command];
if (command.toLowerCase() === 'a') {
// arcs have special flags that apparently don't require spaces so handle special
for (var args; (args = regArcArgumentSequence.exec(coordsStr));) {
for (var j = 1; j < args.length; j++) {
coords.push(args[j]);
}
}
}
else {
while ((match = re.exec(coordsStr))) {
coords.push(match[0]);
}
}
for (var j = 0, jlen = coords.length; j < jlen; j++) {
parsed = parseFloat(coords[j]);
if (!isNaN(parsed)) {
coordsParsed.push(parsed);
}
}
var commandLength = commandLengths[command.toLowerCase()],
repeatedCommand = repeatedCommands[command] || command;
if (coordsParsed.length - 1 > commandLength) {
for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) {
result.push([command].concat(coordsParsed.slice(k, k + commandLength)));
command = repeatedCommand;
}
}
else {
result.push(coordsParsed);
}
}
return result;
};
/**
*
* Converts points to a smooth SVG path
* @param {{ x: number,y: number }[]} points Array of points
* @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value.
* @return {(string|number)[][]} An array of SVG path commands
*/
function getSmoothPathFromPoints(points, correction) {
var path = [], i,
p1 = new fabric.Point(points[0].x, points[0].y),
p2 = new fabric.Point(points[1].x, points[1].y),
len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2;
correction = correction || 0;
if (manyPoints) {
multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1;
multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1;
}
path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]);
for (i = 1; i < len; i++) {
if (!p1.eq(p2)) {
var midPoint = p1.midPointFrom(p2);
// p1 is our bezier control point
// midpoint is our endpoint
// start point is p(i-1) value.
path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]);
}
p1 = points[i];
if ((i + 1) < points.length) {
p2 = points[i + 1];
}
}
if (manyPoints) {
multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1;
multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1;
}
path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]);
return path;
}
/**
* Transform a path by transforming each segment.
* it has to be a simplified path or it won't work.
* WARNING: this depends from pathOffset for correct operation
* @param {Array} path fabricJS parsed and simplified path commands
* @param {Array} transform matrix that represent the transformation
* @param {Object} [pathOffset] the fabric.Path pathOffset
* @param {Number} pathOffset.x
* @param {Number} pathOffset.y
* @returns {Array} the transformed path
*/
function transformPath(path, transform, pathOffset) {
if (pathOffset) {
transform = fabric.util.multiplyTransformMatrices(
transform,
[1, 0, 0, 1, -pathOffset.x, -pathOffset.y]
);
}
return path.map(function(pathSegment) {
var newSegment = pathSegment.slice(0), point = {};
for (var i = 1; i < pathSegment.length - 1; i += 2) {
point.x = pathSegment[i];
point.y = pathSegment[i + 1];
point = fabric.util.transformPoint(point, transform);
newSegment[i] = point.x;
newSegment[i + 1] = point.y;
}
return newSegment;
});
}
/**
* Join path commands to go back to svg format
* @param {Array} pathData fabricJS parsed path commands
* @return {String} joined path 'M 0 0 L 20 30'
*/
fabric.util.joinPath = function(pathData) {
return pathData.map(function (segment) { return segment.join(' '); }).join(' ');
};
fabric.util.parsePath = parsePath;
fabric.util.makePathSimpler = makePathSimpler;
fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints;
fabric.util.getPathSegmentsInfo = getPathSegmentsInfo;
fabric.util.getBoundsOfCurve = getBoundsOfCurve;
fabric.util.getPointOnPath = getPointOnPath;
fabric.util.transformPath = transformPath;
})();