Files
Yajbir Singh f1b860b25c
Some checks failed
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

4968 lines
152 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
(function (window) {
function InitClassWithStatics(fClass, fBase) {
fClass.prototype = Object.create(fBase.prototype);
fClass.prototype.superclass = fBase;
fClass.prototype.constructor = fClass;
Object.getOwnPropertyNames(fBase).forEach(function (prop) {
if (['prototype', 'name', 'length'].includes(prop) || Function.prototype.hasOwnProperty(prop)) { return; }
Object.defineProperty(fClass, prop, Object.getOwnPropertyDescriptor(fBase, prop));
});
fClass.prototype.initialize = fClass;
}
const OPERATIONS = {
divide: 0,
unite: 1,
intersect: 2,
subtract: 3,
exclude: 4,
};
const CURVE_TYPES = {
serpentine: 0,
loop: 1,
arc: 2,
line: 3,
quadratic: 4,
cusp: 5,
};
const CollisionDetection = {
findItemBoundsCollisions: function (items1, items2, tolerance) {
function getBounds(items) {
const bounds = new Array(items.length);
for (let i = 0; i < items.length; i++) {
const rect = items[i].getBounds();
bounds[i] = [rect.getLeft(), rect.getTop(), rect.getRight(), rect.getBottom()];
}
return bounds;
}
const bounds1 = getBounds(items1);
const bounds2 = (!items2 || items2 === items1) ? bounds1 : getBounds(items2);
return this.findBoundsCollisions(bounds1, bounds2, tolerance);
},
findCurveBoundsCollisions: function (curves1, curves2, tolerance, bothAxis) {
function getBounds(curves) {
const bounds = new Array(curves.length);
for (let i = 0; i < curves.length; i++) {
const v = curves[i];
bounds[i] = [
Math.min(v[0], v[2], v[4], v[6]),
Math.min(v[1], v[3], v[5], v[7]),
Math.max(v[0], v[2], v[4], v[6]),
Math.max(v[1], v[3], v[5], v[7])
];
}
return bounds;
}
const bounds1 = getBounds(curves1);
const bounds2 = (!curves2 || curves2 === curves1) ? bounds1 : getBounds(curves2);
if (bothAxis) {
const hor = this.findBoundsCollisions(bounds1, bounds2, tolerance || 0, false, true);
const ver = this.findBoundsCollisions(bounds1, bounds2, tolerance || 0, true, true);
const list = [];
for (let i = 0, l = hor.length; i < l; i++) {
list[i] = { hor: hor[i], ver: ver[i] };
}
return list;
}
return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0);
},
findBoundsCollisions: function (boundsA, boundsB, tolerance, sweepVertical, onlySweepAxisCollisions) {
const self = !boundsB || boundsA === boundsB;
const allBounds = self ? boundsA : boundsA.concat(boundsB);
function binarySearch(indices, coord, value) {
let lo = 0;
let hi = indices.length;
while (lo < hi) {
const mid = (hi + lo) >>> 1;
allBounds[indices[mid]][coord] < value ? lo = mid + 1 : hi = mid;
}
return lo - 1;
}
const pri0 = sweepVertical ? 1 : 0;
const pri1 = pri0 + 2;
const sec0 = sweepVertical ? 0 : 1;
const sec1 = sec0 + 2;
const allIndicesByPri0 = new Array(allBounds.length);
for (let i = 0; i < allBounds.length; i++) {
allIndicesByPri0[i] = i;
}
allIndicesByPri0.sort(function (i1, i2) {
return allBounds[i1][pri0] - allBounds[i2][pri0];
});
const activeIndicesByPri1 = [];
const allCollisions = new Array(boundsA.length);
for (let i = 0; i < allBounds.length; i++) {
const curIndex = allIndicesByPri0[i];
const curBounds = allBounds[curIndex];
const origIndex = self ? curIndex : curIndex - boundsA.length;
const isCurrentA = curIndex < boundsA.length;
const isCurrentB = self || !isCurrentA;
let curCollisions = isCurrentA ? [] : null;
if (activeIndicesByPri1.length) {
const pruneCount = binarySearch(activeIndicesByPri1, pri1, curBounds[pri0] - tolerance) + 1;
activeIndicesByPri1.splice(0, pruneCount);
if (self && onlySweepAxisCollisions) {
curCollisions = curCollisions.concat(activeIndicesByPri1);
for (let j = 0; j < activeIndicesByPri1.length; j++) {
const activeIndex = activeIndicesByPri1[j];
allCollisions[activeIndex].push(origIndex);
}
} else {
for (let j = 0; j < activeIndicesByPri1.length; j++) {
const activeIndex = activeIndicesByPri1[j];
const activeBounds = allBounds[activeIndex];
const isActiveA = activeIndex < boundsA.length;
const isActiveB = self || activeIndex >= boundsA.length;
const isMatchingPairA = isCurrentA && isActiveB;
const isMatchingPairB = isCurrentB && isActiveA;
const hasBoundaryOverlap = (
curBounds[sec1] >= activeBounds[sec0] - tolerance &&
curBounds[sec0] <= activeBounds[sec1] + tolerance
);
const shouldCheckCollision = onlySweepAxisCollisions || (isMatchingPairA || isMatchingPairB) && hasBoundaryOverlap;
if (shouldCheckCollision) {
if (isMatchingPairA) {
curCollisions.push(self ? activeIndex : activeIndex - boundsA.length);
}
if (isMatchingPairB) {
allCollisions[activeIndex].push(origIndex);
}
}
}
}
}
if (isCurrentA) {
if (boundsA === boundsB) {
curCollisions.push(curIndex);
}
allCollisions[curIndex] = curCollisions;
}
if (activeIndicesByPri1.length) {
const curPri1 = curBounds[pri1];
const index = binarySearch(activeIndicesByPri1, pri1, curPri1);
activeIndicesByPri1.splice(index + 1, 0, curIndex);
} else {
activeIndicesByPri1.push(curIndex);
}
}
for (let i = 0; i < allCollisions.length; i++) {
const collisions = allCollisions[i];
if (collisions) {
collisions.sort(function (i1, i2) { return i1 - i2; });
}
}
return allCollisions;
},
};
const Numerical = new function () {
const abscissas = [
[0.5773502691896257645091488],
[0, 0.7745966692414833770358531],
[0.3399810435848562648026658, 0.8611363115940525752239465],
[0, 0.5384693101056830910363144, 0.9061798459386639927976269],
[0.2386191860831969086305017, 0.6612093864662645136613996, 0.9324695142031520278123016],
[0, 0.4058451513773971669066064, 0.7415311855993944398638648, 0.9491079123427585245261897],
[0.1834346424956498049394761, 0.5255324099163289858177390, 0.7966664774136267395915539, 0.9602898564975362316835609],
[0, 0.3242534234038089290385380, 0.6133714327005903973087020, 0.8360311073266357942994298, 0.9681602395076260898355762],
[0.1488743389816312108848260, 0.4333953941292471907992659, 0.6794095682990244062343274, 0.8650633666889845107320967, 0.9739065285171717200779640],
[0, 0.2695431559523449723315320, 0.5190961292068118159257257, 0.7301520055740493240934163, 0.8870625997680952990751578, 0.9782286581460569928039380],
[0.1252334085114689154724414, 0.3678314989981801937526915, 0.5873179542866174472967024, 0.7699026741943046870368938, 0.9041172563704748566784659, 0.9815606342467192506905491],
[0, 0.2304583159551347940655281, 0.4484927510364468528779129, 0.6423493394403402206439846, 0.8015780907333099127942065, 0.9175983992229779652065478, 0.9841830547185881494728294],
[0.1080549487073436620662447, 0.3191123689278897604356718, 0.5152486363581540919652907, 0.6872929048116854701480198, 0.8272013150697649931897947, 0.9284348836635735173363911, 0.9862838086968123388415973],
[0, 0.2011940939974345223006283, 0.3941513470775633698972074, 0.5709721726085388475372267, 0.7244177313601700474161861, 0.8482065834104272162006483, 0.9372733924007059043077589, 0.9879925180204854284895657],
[0.0950125098376374401853193, 0.2816035507792589132304605, 0.4580167776572273863424194, 0.6178762444026437484466718, 0.7554044083550030338951012, 0.8656312023878317438804679, 0.9445750230732325760779884, 0.9894009349916499325961542]
];
const weights = [
[1],
[0.8888888888888888888888889, 0.5555555555555555555555556],
[0.6521451548625461426269361, 0.3478548451374538573730639],
[0.5688888888888888888888889, 0.4786286704993664680412915, 0.2369268850561890875142640],
[0.4679139345726910473898703, 0.3607615730481386075698335, 0.1713244923791703450402961],
[0.4179591836734693877551020, 0.3818300505051189449503698, 0.2797053914892766679014678, 0.1294849661688696932706114],
[0.3626837833783619829651504, 0.3137066458778872873379622, 0.2223810344533744705443560, 0.1012285362903762591525314],
[0.3302393550012597631645251, 0.3123470770400028400686304, 0.2606106964029354623187429, 0.1806481606948574040584720, 0.0812743883615744119718922],
[0.2955242247147528701738930, 0.2692667193099963550912269, 0.2190863625159820439955349, 0.1494513491505805931457763, 0.0666713443086881375935688],
[0.2729250867779006307144835, 0.2628045445102466621806889, 0.2331937645919904799185237, 0.1862902109277342514260976, 0.1255803694649046246346943, 0.0556685671161736664827537],
[0.2491470458134027850005624, 0.2334925365383548087608499, 0.2031674267230659217490645, 0.1600783285433462263346525, 0.1069393259953184309602547, 0.0471753363865118271946160],
[0.2325515532308739101945895, 0.2262831802628972384120902, 0.2078160475368885023125232, 0.1781459807619457382800467, 0.1388735102197872384636018, 0.0921214998377284479144218, 0.0404840047653158795200216],
[0.2152638534631577901958764, 0.2051984637212956039659241, 0.1855383974779378137417166, 0.1572031671581935345696019, 0.1215185706879031846894148, 0.0801580871597602098056333, 0.0351194603317518630318329],
[0.2025782419255612728806202, 0.1984314853271115764561183, 0.1861610000155622110268006, 0.1662692058169939335532009, 0.1395706779261543144478048, 0.1071592204671719350118695, 0.0703660474881081247092674, 0.0307532419961172683546284],
[0.1894506104550684962853967, 0.1826034150449235888667637, 0.1691565193950025381893121, 0.1495959888165767320815017, 0.1246289712555338720524763, 0.0951585116824927848099251, 0.0622535239386478928628438, 0.0271524594117540948517806]
];
const EPSILON = 1e-12;
const MACHINE_EPSILON = 1.12e-16;
const log2 = function (x) { return Math.log(x) * Math.LOG2E; }
const clamp = function (value, min, max) { return value < min ? min : value > max ? max : value; }
function getDiscriminant(a, b, c) {
function split(v) {
const x = v * 134217729;
const y = v - x;
const hi = y + x;
const lo = v - hi;
return [hi, lo];
}
let D = b * b - a * c;
const E = b * b + a * c;
if (Math.abs(D) * 3 < E) {
const ad = split(a);
const bd = split(b);
const cd = split(c);
const p = b * b;
const dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1];
const q = a * c;
const dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0]) + ad[1] * cd[1];
D = (p - q) + (dp - dq);
}
return D;
}
function getNormalizationFactor() {
const norm = Math.max.apply(Math, arguments);
return norm && (norm < 1e-8 || norm > 1e8) ? Math.pow(2, -Math.round(log2(norm))) : 0;
}
return {
EPSILON: EPSILON,
MACHINE_EPSILON: MACHINE_EPSILON,
CURVETIME_EPSILON: 1e-8,
GEOMETRIC_EPSILON: 1e-7,
isZero: function (val) { return val >= -EPSILON && val <= EPSILON; },
isMachineZero: function (val) { return val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON; },
clamp: clamp,
integrate: function (f, a, b, n) {
const x = abscissas[n - 2];
const w = weights[n - 2];
const A = (b - a) * 0.5;
const B = A + a;
const m = (n + 1) >> 1;
let i = 0;
let sum = n & 1 ? w[i++] * f(B) : 0;
while (i < m) {
const Ax = A * x[i];
sum += w[i++] * (f(B + Ax) + f(B - Ax));
}
return A * sum;
},
findRoot: function (f, df, x, a, b, n, tolerance) {
for (let i = 0; i < n; i++) {
const fx = f(x);
const dx = fx / df(x);
const nx = x - dx;
if (Math.abs(dx) < tolerance) { x = nx; break; }
if (fx > 0) {
b = x; x = nx <= a ? (a + b) * 0.5 : nx;
} else { a = x; x = nx >= b ? (a + b) * 0.5 : nx; }
}
return clamp(x, a, b);
},
solveQuadratic: function (a, b, c, roots, min, max) {
let x1;
let x2 = Infinity;
if (Math.abs(a) < EPSILON) {
if (Math.abs(b) < EPSILON)
return Math.abs(c) < EPSILON ? -1 : 0;
x1 = -c / b;
} else {
b *= -0.5;
let D = getDiscriminant(a, b, c);
if (D && Math.abs(D) < MACHINE_EPSILON) {
const f = getNormalizationFactor(Math.abs(a), Math.abs(b), Math.abs(c));
if (f) {
a *= f;
b *= f;
c *= f;
D = getDiscriminant(a, b, c);
}
}
if (D >= -MACHINE_EPSILON) {
const Q = D < 0 ? 0 : Math.sqrt(D);
const R = b + (b < 0 ? -Q : Q);
x1 = (R === 0) ? c / a : R / a;
x2 = (R === 0) ? -x1 : c / R;
}
}
let count = 0;
const boundless = min == null;
const minB = min - EPSILON;
const maxB = max + EPSILON;
if (isFinite(x1) && (boundless || x1 > minB && x1 < maxB))
roots[count++] = boundless ? x1 : clamp(x1, min, max);
if (x2 !== x1 && isFinite(x2) && (boundless || x2 > minB && x2 < maxB))
roots[count++] = boundless ? x2 : clamp(x2, min, max);
return count;
},
solveCubic: function (a, b, c, d, roots, min, max) {
const f = getNormalizationFactor(Math.abs(a), Math.abs(b), Math.abs(c), Math.abs(d));
if (f) {
a *= f;
b *= f;
c *= f;
d *= f;
}
let x, b1, c2, qd, q;
function evaluate(x0) {
x = x0;
const tmp = a * x;
b1 = tmp + b;
c2 = b1 * x + c;
qd = (tmp + b1) * x + c2;
q = c2 * x + d;
}
if (Math.abs(a) < EPSILON) {
a = b;
b1 = c;
c2 = d;
x = Infinity;
} else if (Math.abs(d) < EPSILON) {
b1 = b;
c2 = c;
x = 0;
} else {
evaluate(-(b / a) / 3);
const t = q / a;
const r = Math.pow(Math.abs(t), 1 / 3);
const s = t < 0 ? -1 : 1;
const td = -qd / a;
const rd = td > 0 ? 1.324717957244746 * Math.max(r, Math.sqrt(td)) : r;
let x0 = x - s * rd;
if (x0 !== x) {
do {
evaluate(x0);
x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON);
} while (s * x0 > s * x);
if (Math.abs(a) * x * x > Math.abs(d / x)) {
c2 = -d / x;
b1 = (c2 - c) / x;
}
}
}
let count = Numerical.solveQuadratic(a, b1, c2, roots, min, max);
const boundless = min == null;
const isUniqueRoot = (count === 0 || count > 0 && x !== roots[0] && x !== roots[1]);
const isWithinBounds = boundless || x > min - EPSILON && x < max + EPSILON;
if (isFinite(x) && isUniqueRoot && isWithinBounds)
roots[count++] = boundless ? x : clamp(x, min, max);
return count;
},
};
};
const UID = {
id: 1,
pools: {},
get: function (name) {
if (name) {
let pool = this.pools[name];
if (!pool)
pool = this.pools[name] = { _id: 1 };
return pool._id++;
} else {
return this.id++;
}
}
};
const Base = function () { };
Base.each = function (obj, iter, bind) {
if (obj) {
const descriptor = Object.getOwnPropertyDescriptor(obj, 'length');
const forIn = function (iter, bind) {
for (let i in this) {
if (this.hasOwnProperty(i))
iter.call(bind, this[i], i, this);
}
};
const iterFunction = descriptor && typeof descriptor.value === 'number' ? Array.prototype.forEach : forIn;
iterFunction.call(obj, iter, bind = bind || obj);
}
return bind;
};
Base.isPlainObject = function (obj) {
const ctor = obj != null && obj.constructor;
return ctor && (ctor === Object || ctor === Base || ctor.name === 'Object');
};
Base.pick = function (a, b) {
return a !== undefined ? a : b;
};
Base.slice = function (list, begin, end) {
return Array.prototype.slice.call(list, begin, end);
};
Base.equals = function (obj1, obj2) {
if (obj1 === obj2)
return true;
if (obj1 && obj1.equals)
return obj1.equals(obj2);
if (obj2 && obj2.equals)
return obj2.equals(obj1);
if (obj1 && obj2
&& typeof obj1 === 'object' && typeof obj2 === 'object') {
if (Array.isArray(obj1) && Array.isArray(obj2)) {
let length = obj1.length;
if (length !== obj2.length)
return false;
while (length--) {
if (!Base.equals(obj1[length], obj2[length]))
return false;
}
} else {
const keys = Object.keys(obj1);
let length = keys.length;
if (length !== Object.keys(obj2).length)
return false;
while (length--) {
const key = keys[length];
if (!(obj2.hasOwnProperty(key) && Base.equals(obj1[key], obj2[key])))
return false;
}
}
return true;
}
return false;
};
Base.read = function (list, start, options, amount) {
if (this === Base) {
const value = list[list.__index = start || list.__index || 0];
list.__index++;
return value;
}
const readIndex = this === Point || this === Rectangle;
const begin = start || readIndex && list.__index || 0;
let obj = list[begin];
amount = amount || list.length - begin;
if (obj instanceof this || options && options.readNull && obj == null && amount <= 1) {
if (readIndex) { list.__index = begin + 1; }
return obj && options && options.clone ? obj.clone() : obj;
}
obj = Object.create(this.prototype);
if (readIndex)
obj.__read = true;
obj = obj.initialize.apply(obj, begin > 0 || begin + amount < list.length
? Base.slice(list, begin, begin + amount)
: list) || obj;
if (readIndex) {
list.__index = begin + obj.__read;
obj.__read = undefined;
}
return obj;
};
Base.readList = function (list, start, options, amount) {
const res = [];
const begin = start || 0;
const end = amount ? begin + amount : list.length;
for (let i = begin; i < end; i++) {
const entry = list[i];
res.push(Array.isArray(entry) ? this.read(entry, 0, options) : this.read(list, i, options, 1));
}
return res;
};
Base.filter = function (dest, source, exclude, prioritize) {
let processed;
function handleKey(key) {
if (!(exclude && key in exclude) && !(processed && key in processed)) {
const value = source[key];
if (value !== undefined) dest[key] = value;
}
}
if (prioritize) {
const keys = {};
for (let i = 0, l = prioritize.length; i < l; i++) {
const key = prioritize[i];
if (key in source) {
handleKey(key);
keys[key] = true;
}
}
processed = keys;
}
Object.keys(source).forEach(handleKey);
return dest;
};
Base.isPlainValue = function (obj, asString) {
return Base.isPlainObject(obj) || Array.isArray(obj) || asString && typeof obj === 'string';
};
Base.splice = function (list, items, index, remove) {
const amount = items && items.length;
const append = index === undefined;
index = append ? list.length : index;
if (index > list.length)
index = list.length;
for (let i = 0; i < amount; i++)
items[i]._index = index + i;
if (append) {
list.push.apply(list, items);
return [];
} else {
const args = [index, remove];
if (items)
args.push.apply(args, items);
const removed = list.splice.apply(list, args);
for (let i = 0, l = removed.length; i < l; i++)
removed[i]._index = undefined;
for (let i = index + amount, l = list.length; i < l; i++)
list[i]._index = i;
return removed;
}
};
function isRealNumber(n) {
return typeof n === "number" && !isNaN(n) && isFinite(n);
}
const Point = function (arg0, arg1, owner) {
const type = typeof arg0;
const isReading = this.__read;
let readCount = 0;
if (type === 'number') {
const hasY = typeof arg1 === 'number';
this._set(arg0, hasY ? arg1 : arg0);
if (isReading) { readCount = hasY ? 2 : 1; }
} else if (type === 'undefined' || arg0 === null) {
this._set(0, 0);
if (isReading) { readCount = arg0 === null ? 1 : 0; }
} else {
readCount = 1;
if (Array.isArray(arg0)) {
this._set(+arg0[0], +(arg0.length > 1 ? arg0[1] : arg0[0]));
} else if (isRealNumber(arg0.x)) {
this._set(arg0.x || 0, arg0.y || 0);
} else {
this._set(0, 0);
readCount = 0;
}
}
if (isReading) { this.__read = readCount; }
if (owner) this._owner = owner;
return this;
};
InitClassWithStatics(Point, Base);
Point.prototype.set = Point;
Point.prototype._set = function (x, y) {
this.x = x;
this.y = y;
if (this._owner) this._owner._changed(this);
return this;
};
Point.prototype.getX = function () {
return this.x;
};
Point.prototype.getY = function () {
return this.y;
};
Point.prototype.equals = function (point) {
return this === point || point && (
this.x === point.x && this.y === point.y ||
Array.isArray(point) && this.x === point[0] && this.y === point[1]
);
};
Point.prototype.clone = function () {
return new Point(this.x, this.y);
};
Point.prototype.getLength = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Point.prototype.getAngle = function () {
return this.getAngleInRadians.apply(this, arguments) * 180 / Math.PI;
};
Point.prototype.getAngleInRadians = function () {
if (!arguments.length) {
return this.isZero()
? this._angle || 0
: this._angle = Math.atan2(this.y, this.x);
} else {
const point = Point.read(arguments);
const div = this.getLength() * point.getLength();
if (Numerical.isZero(div)) {
return NaN;
} else {
const a = this.dot(point) / div;
return Math.acos(a < -1 ? -1 : a > 1 ? 1 : a);
}
}
};
Point.prototype.getDistance = function () {
const point = Point.read(arguments);
const x = point.x - this.x;
const y = point.y - this.y;
const d = x * x + y * y;
const squared = Base.read(arguments);
return squared ? d : Math.sqrt(d);
};
Point.prototype.normalize = function (length) {
if (length === undefined)
length = 1;
const current = this.getLength();
const scale = current !== 0 ? length / current : 0;
const point = new Point(this.x * scale, this.y * scale);
if (scale >= 0)
point._angle = this._angle;
return point;
};
Point.prototype.rotate = function (angle, center) {
if (angle === 0)
return this.clone();
angle = angle * Math.PI / 180;
let point = center ? this.subtract(center) : this;
const sin = Math.sin(angle);
const cos = Math.cos(angle);
point = new Point(
point.x * cos - point.y * sin,
point.x * sin + point.y * cos
);
return center ? point.add(center) : point;
};
Point.prototype.transform = function (matrix) {
return matrix ? matrix._transformPoint(this) : this;
};
Point.prototype.add = function () {
const point = Point.read(arguments);
return new Point(this.x + point.x, this.y + point.y);
};
Point.prototype.subtract = function () {
const point = Point.read(arguments);
return new Point(this.x - point.x, this.y - point.y);
};
Point.prototype.multiply = function () {
const point = Point.read(arguments);
return new Point(this.x * point.x, this.y * point.y);
};
Point.prototype.divide = function () {
const point = Point.read(arguments);
return new Point(this.x / point.x, this.y / point.y);
};
Point.prototype.negate = function () {
return new Point(-this.x, -this.y);
};
Point.prototype.isInside = function () {
return Rectangle.read(arguments).contains(this);
};
Point.prototype.isClose = function () {
const point = Point.read(arguments);
const tolerance = Base.read(arguments);
return this.getDistance(point) <= tolerance;
};
Point.prototype.isCollinear = function () {
const point = Point.read(arguments);
return Point.isCollinear(this.x, this.y, point.x, point.y);
};
Point.prototype.isOrthogonal = function () {
const point = Point.read(arguments);
return Point.isOrthogonal(this.x, this.y, point.x, point.y);
};
Point.prototype.isZero = function () {
return Numerical.isZero(this.x) && Numerical.isZero(this.y);
};
Point.prototype.isNaN = function () {
return isNaN(this.x) || isNaN(this.y);
};
Point.prototype.dot = function () {
const point = Point.read(arguments);
return this.x * point.x + this.y * point.y;
};
Point.prototype.cross = function () {
const point = Point.read(arguments);
return this.x * point.y - this.y * point.x;
};
Point.isCollinear = function (x1, y1, x2, y2) {
return Math.abs(x1 * y2 - y1 * x2)
<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) * 1e-8;
};
Point.isOrthogonal = function (x1, y1, x2, y2) {
return Math.abs(x1 * x2 + y1 * y2)
<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) * 1e-8;
};
const Rectangle = function (arg0, arg1, arg2, arg3) {
const type = typeof arg0;
let read;
if (type === 'number') {
this._set(arg0, arg1, arg2, arg3);
read = 4;
} else if (type === 'undefined' || arg0 === null) {
this._set(0, 0, 0, 0);
read = arg0 === null ? 1 : 0;
}
if (this.__read) { this.__read = read; }
return this;
};
InitClassWithStatics(Rectangle, Base);
Rectangle.prototype._set = function (x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
return this;
};
Rectangle.prototype.clone = function () {
return new Rectangle(this.x, this.y, this.width, this.height);
};
Rectangle.prototype.equals = function (rect) {
const rt = Base.isPlainValue(rect) ? Rectangle.read(arguments) : rect;
return rt === this || rt
&& this.x === rt.x
&& this.y === rt.y
&& this.width === rt.width
&& this.height === rt.height;
};
Rectangle.prototype.getPoint = function (_dontLink) {
return new Point(this.x, this.y, this);
};
Rectangle.prototype.getLeft = Rectangle.prototype.getX = function () {
return this.x;
};
Rectangle.prototype.getTop = Rectangle.prototype.getY = function () {
return this.y;
};
Rectangle.prototype.getRight = function () {
return this.x + this.width;
};
Rectangle.prototype.getBottom = function () {
return this.y + this.height;
};
Rectangle.prototype.getWidth = function () {
return this.width;
};
Rectangle.prototype.getHeight = function () {
return this.height;
};
Rectangle.prototype.getCenterX = function () {
return this.getLeft() + this.getWidth() / 2;
};
Rectangle.prototype.getCenterY = function () {
return this.getTop() + this.getHeight() / 2;
};
Rectangle.prototype.getCenter = function (_dontLink) {
return new Point(this.getCenterX(), this.getCenterY());
};
Rectangle.prototype.getTopLeft = function (_dontLink) {
return new Point(this.getLeft(), this.getTop());
};
Rectangle.prototype.getArea = function () {
return this.width * this.height;
};
Rectangle.prototype.isEmpty = function () {
return this.width === 0 || this.height === 0;
};
Rectangle.prototype.contains = function (arg) {
return arg && arg.width !== undefined || (Array.isArray(arg) ? arg : arguments).length === 4
? this._containsRectangle(Rectangle.read(arguments))
: this._containsPoint(Point.read(arguments));
};
Rectangle.prototype._containsPoint = function (point) {
const x = point.x;
const y = point.y;
return x >= this.x && y >= this.y
&& x <= this.x + this.width
&& y <= this.y + this.height;
};
Rectangle.prototype._containsRectangle = function (rect) {
const x = rect.x;
const y = rect.y;
return x >= this.x && y >= this.y
&& x + rect.width <= this.x + this.width
&& y + rect.height <= this.y + this.height;
};
Rectangle.prototype.intersects = function () {
const rect = Rectangle.read(arguments);
const epsilon = Base.read(arguments) || 0;
return rect.x + rect.width > this.x - epsilon
&& rect.y + rect.height > this.y - epsilon
&& rect.x < this.x + this.width + epsilon
&& rect.y < this.y + this.height + epsilon;
};
const Matrix = function (arg, _dontNotify) {
const count = arguments.length;
if (count >= 6) {
this._set.apply(this, arguments);
} else if (count === 1 || count === 2) {
if (arg instanceof Matrix) {
this._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty, _dontNotify);
} else if (Array.isArray(arg)) {
this._set.apply(this, _dontNotify ? arg.concat([_dontNotify]) : arg);
}
} else if (!count) {
this.reset();
}
return this;
};
InitClassWithStatics(Matrix, Base);
Matrix.prototype.set = Matrix;
Matrix.prototype._set = function (a, b, c, d, tx, ty, _dontNotify) {
this._a = a;
this._b = b;
this._c = c;
this._d = d;
this._tx = tx;
this._ty = ty;
if (!_dontNotify)
this._changed();
return this;
};
Matrix.prototype._changed = function () {
if (this._owner) {
if (this._owner._applyMatrix) {
this._owner.transform(null, true);
} else {
this._owner._changed(25);
}
}
};
Matrix.prototype.clone = function () {
return new Matrix(this._a, this._b, this._c, this._d, this._tx, this._ty);
};
Matrix.prototype.equals = function (mx) {
return mx === this || mx
&& this._a === mx._a
&& this._b === mx._b
&& this._c === mx._c
&& this._d === mx._d
&& this._tx === mx._tx
&& this._ty === mx._ty;
};
Matrix.prototype.reset = function (_dontNotify) {
this._a = this._d = 1;
this._b = this._c = this._tx = this._ty = 0;
if (!_dontNotify)
this._changed();
return this;
};
Matrix.prototype.apply = function (recursively, _setApplyMatrix) {
if (!this._owner) { return false; }
this._owner.transform(null, Base.pick(recursively, true), _setApplyMatrix);
return this.isIdentity();
};
Matrix.prototype.translate = function () {
const point = Point.read(arguments);
this._tx += point.x * this._a + point.y * this._c;
this._ty += point.x * this._b + point.y * this._d;
this._changed();
return this;
};
Matrix.prototype.scale = function () {
const scale = Point.read(arguments);
const center = Point.read(arguments, 0, { readNull: true });
if (center) { this.translate(center); }
this._a *= scale.x;
this._b *= scale.x;
this._c *= scale.y;
this._d *= scale.y;
if (center) { this.translate(center.negate()); }
this._changed();
return this;
};
Matrix.prototype.rotate = function (angle) {
angle *= Math.PI / 180;
const center = Point.read(arguments, 1);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const tx = center.x - center.x * cos + center.y * sin;
const ty = center.y - center.x * sin - center.y * cos;
const a = this._a, b = this._b, c = this._c, d = this._d;
this._a = cos * a + sin * c;
this._b = cos * b + sin * d;
this._c = -sin * a + cos * c;
this._d = -sin * b + cos * d;
this._tx += tx * a + ty * c;
this._ty += tx * b + ty * d;
this._changed();
return this;
};
Matrix.prototype.shear = function () {
const shear = Point.read(arguments);
const center = Point.read(arguments, 0, { readNull: true });
if (center) { this.translate(center); }
const a = this._a, b = this._b;
this._a += shear.y * this._c;
this._b += shear.y * this._d;
this._c += shear.x * a;
this._d += shear.x * b;
if (center) { this.translate(center.negate()); }
this._changed();
return this;
};
Matrix.prototype.skew = function () {
const skew = Point.read(arguments);
const center = Point.read(arguments, 0, { readNull: true });
const toRadians = Math.PI / 180;
const shear = new Point(Math.tan(skew.x * toRadians), Math.tan(skew.y * toRadians));
return this.shear(shear, center);
};
Matrix.prototype.append = function (mx, _dontNotify) {
if (mx) {
const a1 = this._a, b1 = this._b, c1 = this._c, d1 = this._d;
const a2 = mx._a, b2 = mx._c, c2 = mx._b, d2 = mx._d;
const tx2 = mx._tx, ty2 = mx._ty;
this._a = a2 * a1 + c2 * c1;
this._c = b2 * a1 + d2 * c1;
this._b = a2 * b1 + c2 * d1;
this._d = b2 * b1 + d2 * d1;
this._tx += tx2 * a1 + ty2 * c1;
this._ty += tx2 * b1 + ty2 * d1;
if (!_dontNotify)
this._changed();
}
return this;
};
Matrix.prototype.prepend = function (mx, _dontNotify) {
if (mx) {
const a1 = this._a, b1 = this._b, c1 = this._c, d1 = this._d;
const a2 = mx._a, b2 = mx._c, c2 = mx._b, d2 = mx._d;
const tx1 = this._tx, ty1 = this._ty;
const tx2 = mx._tx, ty2 = mx._ty;
this._a = a2 * a1 + b2 * b1;
this._c = a2 * c1 + b2 * d1;
this._b = c2 * a1 + d2 * b1;
this._d = c2 * c1 + d2 * d1;
this._tx = a2 * tx1 + b2 * ty1 + tx2;
this._ty = c2 * tx1 + d2 * ty1 + ty2;
if (!_dontNotify)
this._changed();
}
return this;
};
Matrix.prototype.appended = function (mx) {
return this.clone().append(mx);
};
Matrix.prototype.prepended = function (mx) {
return this.clone().prepend(mx);
};
Matrix.prototype._orNullIfIdentity = function () {
return this.isIdentity() ? null : this;
};
Matrix.prototype.isIdentity = function () {
return this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1
&& this._tx === 0 && this._ty === 0;
};
Matrix.prototype.isInvertible = function () {
const det = this._a * this._d - this._c * this._b;
return det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty);
};
Matrix.prototype.transform = function (src, dst, count) {
return arguments.length < 3
? this._transformPoint(Point.read(arguments))
: this._transformCoordinates(src, dst, count);
};
Matrix.prototype._transformPoint = function (point, dest, _dontNotify) {
if (!dest)
dest = new Point();
return dest._set(
point.x * this._a + point.y * this._c + this._tx,
point.x * this._b + point.y * this._d + this._ty,
_dontNotify
);
};
Matrix.prototype._transformCoordinates = function (src, dst, count) {
for (let i = 0, max = 2 * count; i < max; i += 2) {
const x = src[i];
const y = src[i + 1];
dst[i] = x * this._a + y * this._c + this._tx;
dst[i + 1] = x * this._b + y * this._d + this._ty;
}
return dst;
};
Matrix.prototype._transformCorners = function (rect) {
const x1 = rect.x;
const y1 = rect.y;
const x2 = x1 + rect.width;
const y2 = y1 + rect.height;
const coords = [x1, y1, x2, y1, x2, y2, x1, y2];
return this._transformCoordinates(coords, coords, 4);
};
Matrix.prototype._transformBounds = function (bounds, dest, _dontNotify) {
const coords = this._transformCorners(bounds);
const min = coords.slice(0, 2);
const max = min.slice();
for (let i = 2; i < 8; i++) {
const val = coords[i];
const j = i & 1;
if (val < min[j]) {
min[j] = val;
} else if (val > max[j]) {
max[j] = val;
}
}
if (!dest)
dest = new Rectangle();
return dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1], _dontNotify);
};
Matrix.prototype._inverseTransform = function (point, dest, _dontNotify) {
const a = this._a, b = this._b, c = this._c, d = this._d;
const tx = this._tx, ty = this._ty;
const det = a * d - b * c;
let res = null;
if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {
const x = point.x - this._tx;
const y = point.y - this._ty;
if (!dest)
dest = new Point();
res = dest._set(
(x * d - y * c) / det,
(y * a - x * b) / det,
_dontNotify
);
}
return res;
};
Matrix.prototype.decompose = function () {
const a = this._a, b = this._b, c = this._c, d = this._d;
const det = a * d - b * c;
const degrees = 180 / Math.PI;
let rotate, scale, skew;
if (a !== 0 || b !== 0) {
const r = Math.sqrt(a * a + b * b);
rotate = Math.acos(a / r) * (b > 0 ? 1 : -1);
scale = [r, det / r];
skew = [Math.atan2(a * c + b * d, r * r), 0];
} else if (c !== 0 || d !== 0) {
const s = Math.sqrt(c * c + d * d);
rotate = Math.asin(c / s) * (d > 0 ? 1 : -1);
scale = [det / s, s];
skew = [0, Math.atan2(a * c + b * d, s * s)];
} else {
rotate = 0;
skew = scale = [0, 0];
}
return {
translation: this.getTranslation(),
rotation: rotate * degrees,
scaling: new Point(scale),
skewing: new Point(skew[0] * degrees, skew[1] * degrees)
};
};
Matrix.prototype.getValues = function () {
return [this._a, this._b, this._c, this._d, this._tx, this._ty];
};
Matrix.prototype.getTranslation = function () {
return new Point(this._tx, this._ty);
};
Matrix.prototype.getRotation = function () {
return this.decompose().rotation;
};
const Line = function Line(arg0, arg1, arg2, arg3, arg4) {
let asVector = false;
if (arguments.length >= 4) {
this._px = arg0;
this._py = arg1;
this._vx = arg2;
this._vy = arg3;
asVector = arg4;
} else {
this._px = arg0.x;
this._py = arg0.y;
this._vx = arg1.x;
this._vy = arg1.y;
asVector = arg2;
}
if (!asVector) {
this._vx -= this._px;
this._vy -= this._py;
}
};
InitClassWithStatics(Line, Base);
Line.prototype.getPoint = function () {
return new Point(this._px, this._py);
};
Line.prototype.getVector = function () {
return new Point(this._vx, this._vy);
};
Line.prototype.getLength = function () {
return this.getVector().getLength();
};
Line.prototype.intersect = function (line, isInfinite) {
return Line.intersect(
this._px, this._py, this._vx, this._vy,
line._px, line._py, line._vx, line._vy,
true, isInfinite);
};
Line.prototype.getSide = function (point, isInfinite) {
return Line.getSide(
this._px, this._py, this._vx, this._vy,
point.x, point.y, true, isInfinite);
};
Line.prototype.getDistance = function (point) {
return Math.abs(this.getSignedDistance(point));
};
Line.prototype.getSignedDistance = function (point) {
return Line.getSignedDistance(this._px, this._py, this._vx, this._vy,
point.x, point.y, true);
};
Line.prototype.isCollinear = function (line) {
return Point.isCollinear(this._vx, this._vy, line._vx, line._vy);
};
Line.prototype.isOrthogonal = function (line) {
return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy);
};
Line.intersect = function (p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector,
isInfinite) {
if (!asVector) {
v1x -= p1x;
v1y -= p1y;
v2x -= p2x;
v2y -= p2y;
}
const cross = v1x * v2y - v1y * v2x;
if (!Numerical.isMachineZero(cross)) {
const dx = p1x - p2x;
const dy = p1y - p2y;
let u1 = (v2x * dy - v2y * dx) / cross;
let u2 = (v1x * dy - v1y * dx) / cross;
const uMin = -Numerical.EPSILON;
const uMax = 1 + Numerical.EPSILON;
if (isInfinite || uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) {
if (!isInfinite) {
u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1;
}
return new Point(p1x + u1 * v1x, p1y + u1 * v1y);
}
}
};
Line.getSide = function (px, py, vx, vy, x, y, asVector, isInfinite) {
if (!asVector) {
vx -= px;
vy -= py;
}
const v2x = x - px;
const v2y = y - py;
let ccw = v2x * vy - v2y * vx;
if (!isInfinite && Numerical.isMachineZero(ccw)) {
ccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy);
if (ccw >= 0 && ccw <= 1) {
ccw = 0;
}
}
return ccw < 0 ? -1 : ccw > 0 ? 1 : 0;
};
Line.getSignedDistance = function (px, py, vx, vy, x, y, asVector) {
if (!asVector) {
vx -= px;
vy -= py;
}
return vx === 0 ? (vy > 0 ? x - px : px - x)
: vy === 0 ? (vx < 0 ? y - py : py - y)
: ((x - px) * vy - (y - py) * vx) / (
vy > vx
? vy * Math.sqrt(1 + (vx * vx) / (vy * vy))
: vx * Math.sqrt(1 + (vy * vy) / (vx * vx))
);
};
Line.getDistance = function (px, py, vx, vy, x, y, asVector) {
return Math.abs(Line.getSignedDistance(px, py, vx, vy, x, y, asVector));
};
const Item = function () { };
InitClassWithStatics(Item, Base);
Item.prototype._applyMatrix = true;
Item.prototype._canApplyMatrix = true;
Item.prototype._pivot = null;
Item.prototype._initialize = function (props, point) {
const hasProps = props && Base.isPlainObject(props);
const isInternal = hasProps && props.internal === true;
const matrix = this._matrix = new Matrix();
const settings = {
applyMatrix: true,
};
this._id = isInternal ? null : UID.get();
this._parent = this._index = null;
this._applyMatrix = this._canApplyMatrix && settings.applyMatrix;
if (point)
matrix.translate(point);
matrix._owner = this;
return hasProps;
};
Item.prototype._changed = function (flags) {
if (flags & 8) {
this._bounds = this._position = this._decomposed = undefined;
}
if (flags & 16) {
this._globalMatrix = undefined;
}
if (this._parent && (flags & 72)) {
Item._clearBoundsCache(this._parent);
}
if (flags & 2) {
Item._clearBoundsCache(this);
}
};
Item.prototype.getPosition = function (_dontLink) {
const position = this._position || (this._position = this._getPositionFromBounds());
return new Point(position.x, position.y, this);
};
Item.prototype.setPosition = function () {
this.translate(Point.read(arguments).subtract(this.getPosition(true)));
};
Item.prototype._getPositionFromBounds = function (bounds) {
return this._pivot
? this._matrix._transformPoint(this._pivot)
: (bounds || this.getBounds()).getCenter(true);
};
Item.prototype.setPivot = function () {
this._pivot = Point.read(arguments, 0, { clone: true, readNull: true });
this._position = undefined;
};
Item.prototype.getBounds = function (matrix) {
const opts = Object.assign({}, matrix);
opts.cacheItem = this;
const rect = this._getCachedBounds(false, opts).rect;
return !!arguments.length
? rect
: new Rectangle(rect.x, rect.y, rect.width, rect.height);
};
Item.prototype.setBounds = function () {
const rect = Rectangle.read(arguments);
let bounds = this.getBounds();
const matrix = new Matrix();
let center = rect.getCenter();
matrix.translate(center);
if (rect.width != bounds.width || rect.height != bounds.height) {
if (!this._matrix.isInvertible()) {
this._matrix.set(this._matrix._backup || new Matrix().translate(this._matrix.getTranslation()));
bounds = this.getBounds();
}
matrix.scale(
bounds.width !== 0 ? rect.width / bounds.width : 0,
bounds.height !== 0 ? rect.height / bounds.height : 0);
}
center = bounds.getCenter();
matrix.translate(-center.x, -center.y);
this.transform(matrix);
};
Item.prototype._getBounds = function (matrix, options) {
if (!this._children || !this._children.length) {
return new Rectangle();
}
Item._updateBoundsCache(this, options.cacheItem);
return Item._getBounds(this._children, matrix, options);
};
Item.prototype._getBoundsCacheKey = function (options, internal) {
return [
options.stroke ? 1 : 0,
options.handle ? 1 : 0,
internal ? 1 : 0
].join('');
};
Item.prototype._getCachedBounds = function (matrix, options, noInternal) {
matrix = matrix && matrix._orNullIfIdentity();
const isInternal = options.internal && !noInternal;
const cacheItem = options.cacheItem;
const _matrix = isInternal ? null : this._matrix._orNullIfIdentity();
const cacheKey = cacheItem && (!matrix || matrix.equals(_matrix)) && this._getBoundsCacheKey(options, isInternal);
Item._updateBoundsCache(this._parent, cacheItem);
let cached;
if (cacheKey && this._bounds && cacheKey in this._bounds) {
cached = this._bounds[cacheKey];
return {
rect: cached.rect.clone(),
nonscaling: cached.nonscaling
};
}
const res = this._getBounds(matrix || _matrix, options);
const rect = res.rect || res;
const nonscaling = res.nonscaling;
if (cacheKey) {
if (!this._bounds) {
this._bounds = this._bounds = {};
}
cached = this._bounds[cacheKey] = {
rect: rect.clone(),
nonscaling: nonscaling,
internal: isInternal
};
}
return {
rect: rect,
nonscaling: nonscaling
};
};
Item.prototype._decompose = function () {
return this._applyMatrix
? null
: this._decomposed || (this._decomposed = this._matrix.decompose());
};
Item.prototype.getRotation = function () {
const decomposed = this._decompose();
return decomposed ? decomposed.rotation : 0;
};
Item.prototype.setApplyMatrix = function (apply) {
if (this._applyMatrix = this._canApplyMatrix && !!apply)
this.transform(null, true);
};
Item.prototype._getOwner = Item.prototype.getParent = function () {
return this._parent;
};
Item.prototype.getChildren = function () {
return this._children;
};
Item.prototype.setChildren = function (items) {
this.removeChildren();
this.addChildren(items);
};
Item.prototype.getFirstChild = function () {
return this._children && this._children[0] || null;
};
Item.prototype.getLastChild = function () {
return this._children && this._children[this._children.length - 1]
|| null;
};
Item.prototype.getNextSibling = function () {
const owner = this._getOwner();
return owner && owner._children[this._index + 1] || null;
};
Item.prototype.getPreviousSibling = function () {
const owner = this._getOwner();
return owner && owner._children[this._index - 1] || null;
};
Item.prototype.getIndex = function () {
return this._index;
};
Item.prototype.equals = function (item) {
return item === this || item
&& this._matrix.equals(item._matrix)
&& this._equals(item);
};
Item.prototype._equals = function (item) {
return Base.equals(this._children, item._children);
};
Item.prototype.clone = function (options) {
const copy = new this.constructor({ insert: false });
if (this._children)
copy.copyAttributes(this);
const deep = Base.pick(options ? options.deep : undefined, true);
if (!this._children || deep)
copy.copyContent(this);
if (!this._children)
copy.copyAttributes(this);
const shouldInsert = Base.pick(options ? options.insert : undefined, options === undefined || options === true);
if (shouldInsert)
copy.insertAbove(this);
return copy;
};
Item.prototype.copyContent = function (source) {
for (let i = 0, l = source._children && source._children.length; i < l; i++) {
this.addChild(source._children[i].clone(false), true);
}
};
Item.prototype.copyAttributes = function (source, excludeMatrix) {
if (!excludeMatrix)
this._matrix.set(source._matrix, true);
this.setApplyMatrix(source._applyMatrix);
this.setPivot(source._pivot);
const data = source._data;
this._data = data ? Object.assign(new data.constructor(), data) : null;
};
Item.prototype.contains = function () {
return (
this._matrix.isInvertible() &&
!!this._contains(this._matrix._inverseTransform(Point.read(arguments)))
);
};
Item.prototype._contains = function (point) {
if (this._children) {
for (let i = this._children.length - 1; i >= 0; i--) {
if (this._children[i].contains(point))
return true;
}
return false;
}
return point.isInside(this.getInternalBounds());
};
Item.prototype.isInside = function () {
return Rectangle.read(arguments).contains(this.getBounds());
};
Item.prototype.addChild = function (item) {
return this.insertChild(undefined, item);
};
Item.prototype.insertChild = function (index, item) {
const res = item ? this.insertChildren(index, [item]) : null;
return res && res[0];
};
Item.prototype.addChildren = function (items) {
return this.insertChildren(this._children.length, items);
};
Item.prototype.insertChildren = function (index, items) {
if (this._children && items && items.length > 0) {
items = Base.slice(items);
const inserted = {};
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i],
id = item && item._id;
if (!item || inserted[id]) {
items.splice(i, 1);
} else {
item._remove(false, true);
inserted[id] = true;
}
}
Base.splice(this._children, items, index, 0);
for (let i = 0, l = items.length; i < l; i++) {
const item = items[i];
item._parent = this;
}
this._changed(11);
} else {
items = null;
}
return items;
};
Item.prototype._insertAt = function (item, offset) {
const owner = item && item._getOwner(),
res = item !== this && owner ? this : null;
if (res) {
res._remove(false, true);
owner._insertChild(item._index + offset, res);
}
return res;
};
Item.prototype._insertChild = function(index, item) {
var res = item ? this.insertChildren(index, [item]) : null;
return res && res[0];
};
Item.prototype.insertAbove = function (item) {
return this._insertAt(item, 1);
};
Item.prototype.insertBelow = function (item) {
return this._insertAt(item, 0);
};
Item.prototype.reduce = function (options) {
if (this._children && this._children.length === 1) {
const child = this._children[0].reduce(options);
if (this._parent) {
child.insertAbove(this);
this.remove();
} else {
child.remove();
}
return child;
}
return this;
};
Item.prototype._remove = function (notifySelf, notifyParent) {
const owner = this._getOwner();
if (owner) {
if (this._index != null) {
Base.splice(owner._children, null, this._index, 1);
}
if (notifyParent)
owner._changed(11, this);
this._parent = null;
return true;
}
return false;
};
Item.prototype.remove = function () {
return this._remove(true, true);
};
Item.prototype.replaceWith = function (item) {
const ok = item && item.insertBelow(this);
if (ok) { this.remove(); }
return ok;
};
Item.prototype.clear = Item.prototype.removeChildren = function (start, end) {
if (!this._children) { return null; }
start = start || 0;
end = Base.pick(end, this._children.length);
const removed = Base.splice(this._children, null, start, end - start);
for (let i = removed.length - 1; i >= 0; i--) {
removed[i]._remove(true, false);
}
if (removed.length > 0)
this._changed(11);
return removed;
};
Item.prototype.isEmpty = function (recursively) {
const numChildren = this._children ? this._children.length : 0;
if (recursively) {
for (let i = 0; i < numChildren; i++) {
if (!this._children[i].isEmpty(recursively)) {
return false;
}
}
return true;
}
return !numChildren;
};
Item.prototype._getOrder = function (item) {
function getList(item) {
const list = [];
do {
list.unshift(item);
} while (item = item._parent);
return list;
}
const list1 = getList(this);
const list2 = getList(item);
for (let i = 0, l = Math.min(list1.length, list2.length); i < l; i++) {
if (list1[i] != list2[i]) {
return list1[i]._index < list2[i]._index ? 1 : -1;
}
}
return 0;
};
Item.prototype.translate = function () {
const mx = new Matrix();
return this.transform(mx.translate.apply(mx, arguments));
};
Item.prototype.transform = function (matrix, _applyRecursively, _setApplyMatrix) {
const _matrix = this._matrix;
const transformMatrix = matrix && !matrix.isIdentity();
let applyMatrix = (
_setApplyMatrix && this._canApplyMatrix ||
this._applyMatrix && (transformMatrix || !_matrix.isIdentity() || _applyRecursively && this._children)
);
if (!transformMatrix && !applyMatrix) { return this; }
if (transformMatrix) {
if (!matrix.isInvertible() && _matrix.isInvertible())
_matrix._backup = _matrix.getValues();
_matrix.prepend(matrix, true);
}
if (applyMatrix && (applyMatrix = this._transformContent(_matrix, _applyRecursively, _setApplyMatrix))) {
if (this._pivot)
_matrix._transformPoint(this._pivot, this._pivot, true);
_matrix.reset(true);
if (_setApplyMatrix && this._canApplyMatrix)
this._applyMatrix = true;
}
const bounds = this._bounds;
const position = this._position;
if (transformMatrix || applyMatrix) {
this._changed(25);
}
const decomp = transformMatrix && bounds && matrix.decompose();
if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) {
for (let key in bounds) {
const cache = bounds[key];
if (cache.nonscaling) {
delete bounds[key];
} else if (applyMatrix || !cache.internal) {
matrix._transformBounds(cache.rect, cache.rect);
}
}
this._bounds = bounds;
const cached = bounds['000'];
if (cached) {
this._position = this._getPositionFromBounds(cached.rect);
}
} else if (transformMatrix && position && this._pivot) {
this._position = matrix._transformPoint(position, position);
}
return this;
};
Item.prototype._transformContent = function (matrix, applyRecursively, setApplyMatrix) {
const children = this._children;
if (children) {
for (let i = 0, l = children.length; i < l; i++) {
children[i].transform(matrix, applyRecursively, setApplyMatrix);
}
return true;
}
};
Item._updateBoundsCache = function (parent, item) {
if (parent && item) {
const id = item._id;
const ref = parent._boundsCache = parent._boundsCache || { ids: {}, list: [] };
if (!ref.ids[id]) {
ref.list.push(item);
ref.ids[id] = item;
}
}
};
Item._clearBoundsCache = function (item) {
const cache = item._boundsCache;
if (cache) {
item._bounds = item._position = item._boundsCache = undefined;
for (let i = 0, list = cache.list, l = list.length; i < l; i++) {
const other = list[i];
if (other !== item) {
other._bounds = other._position = undefined;
if (other._boundsCache)
Item._clearBoundsCache(other);
}
}
}
};
Item._getBounds = function (items, matrix, options) {
let x1 = Infinity;
let x2 = -x1;
let y1 = x1;
let y2 = x2;
let nonscaling = false;
options = options || {};
for (let i = 0, l = items.length; i < l; i++) {
const item = items[i];
if (!item.isEmpty(true)) {
const bounds = item._getCachedBounds(matrix && matrix.appended(item._matrix), options, true);
x1 = Math.min(bounds.rect.x, x1);
y1 = Math.min(bounds.rect.y, y1);
x2 = Math.max(bounds.rect.x + bounds.rect.width, x2);
y2 = Math.max(bounds.rect.y + bounds.rect.height, y2);
if (bounds.nonscaling)
nonscaling = true;
}
}
return {
rect: isFinite(x1)
? new Rectangle(x1, y1, x2 - x1, y2 - y1)
: new Rectangle(),
nonscaling: nonscaling
};
};
const Segment = function (arg0, arg1, arg2, arg3, arg4, arg5) {
for (let i = 0, l = arguments.length; i < l; i++) {
const src = arguments[i];
if (src)
Object.assign(this, src);
}
const count = arguments.length;
let point, handleIn, handleOut;
if (count > 0) {
if (arg0 == null || typeof arg0 === 'object') {
if (count === 1 && arg0 && arg0.point) {
point = arg0.point;
handleIn = arg0.handleIn;
handleOut = arg0.handleOut;
} else {
point = arg0;
handleIn = arg1;
handleOut = arg2;
}
} else {
point = [arg0, arg1];
handleIn = arg2 !== undefined ? [arg2, arg3] : null;
handleOut = arg4 !== undefined ? [arg4, arg5] : null;
}
}
this._point = new Point(point, this);
this._handleIn = new Point(handleIn, this);
this._handleOut = new Point(handleOut, this);
};
InitClassWithStatics(Segment, Base);
Segment.prototype._changed = function (point) {
if (!this._path) { return; }
const curves = this._path._curves;
const index = this._index;
let curve;
if (curves) {
if ((!point || point === this._point || point === this._handleIn) && (curve = index > 0 ? curves[index - 1] : this._path._closed ? curves[curves.length - 1] : null))
curve._changed();
if ((!point || point === this._point || point === this._handleOut) && (curve = curves[index]))
curve._changed();
}
this._path._changed(41);
};
Segment.prototype.getPoint = function () {
return this._point;
};
Segment.prototype.getHandleIn = function () {
return this._handleIn;
};
Segment.prototype.setHandleIn = function () {
this._handleIn.set(Point.read(arguments));
};
Segment.prototype.getHandleOut = function () {
return this._handleOut;
};
Segment.prototype.setHandleOut = function () {
const newPoint = Point.read(arguments)
this._handleOut.set(newPoint);
};
Segment.prototype.hasHandles = function () {
return !this._handleIn.isZero() || !this._handleOut.isZero();
};
Segment.prototype.isSmooth = function () {
return !this._handleIn.isZero() && !this._handleOut.isZero() && this._handleIn.isCollinear(this._handleOut);
};
Segment.prototype.clearHandles = function () {
this._handleIn._set(0, 0);
this._handleOut._set(0, 0);
};
Segment.prototype.getIndex = function () {
return this._index !== undefined ? this._index : null;
};
Segment.prototype.getPath = function () {
return this._path || null;
};
Segment.prototype.getCurve = function () {
const path = this._path;
let index = this._index;
if (path) {
if (index > 0 && !path._closed
&& index === path._segments.length - 1)
index--;
return path.getCurves()[index] || null;
}
return null;
};
Segment.prototype.getLocation = function () {
const curve = this.getCurve();
return curve ? new CurveLocation(curve, this === curve._segment1 ? 0 : 1) : null;
};
Segment.prototype.getNext = function () {
const segments = this._path && this._path._segments;
return segments && (segments[this._index + 1] || this._path._closed && segments[0]) || null;
};
Segment.prototype.getPrevious = function () {
const segments = this._path && this._path._segments;
return segments && (segments[this._index - 1] || this._path._closed && segments[segments.length - 1]) || null;
};
Segment.prototype.isFirst = function () {
return !this._index;
};
Segment.prototype.isLast = function () {
return this._path && this._index === this._path._segments.length - 1 || false;
};
Segment.prototype.reverse = function () {
const handleIn = this._handleIn;
const handleOut = this._handleOut;
const tmp = handleIn.clone();
handleIn.set(handleOut);
handleOut.set(tmp);
};
Segment.prototype.reversed = function () {
return new Segment(this._point, this._handleOut, this._handleIn);
};
Segment.prototype.remove = function () {
return this._path ? !!this._path.removeSegment(this._index) : false;
};
Segment.prototype.clone = function () {
return new Segment(this._point, this._handleIn, this._handleOut);
};
Segment.prototype.equals = function (segment) {
return segment === this || segment
&& this._point.equals(segment._point)
&& this._handleIn.equals(segment._handleIn)
&& this._handleOut.equals(segment._handleOut)
|| false;
};
Segment.prototype.transform = function (matrix) {
this._transformCoordinates(matrix, new Array(6), true);
this._changed();
};
Segment.prototype._transformCoordinates = function (matrix, coords, change) {
const handleIn = !change || !this._handleIn.isZero() ? this._handleIn : null;
const handleOut = !change || !this._handleOut.isZero() ? this._handleOut : null;
let x = this._point.getX();
let y = this._point.getY();
let i = 2;
coords[0] = x;
coords[1] = y;
if (handleIn) {
coords[i++] = handleIn.getX() + x;
coords[i++] = handleIn.getY() + y;
}
if (handleOut) {
coords[i++] = handleOut.getX() + x;
coords[i++] = handleOut.getY() + y;
}
if (matrix) {
matrix._transformCoordinates(coords, coords, i / 2);
x = coords[0];
y = coords[1];
if (change) {
this._point.x = x;
this._point.y = y;
i = 2;
if (handleIn) {
handleIn.x = coords[i++] - x;
handleIn.y = coords[i++] - y;
}
if (handleOut) {
handleOut.x = coords[i++] - x;
handleOut.y = coords[i++] - y;
}
} else {
if (!handleIn) {
coords[i++] = x;
coords[i++] = y;
}
if (!handleOut) {
coords[i++] = x;
coords[i++] = y;
}
}
}
return coords;
};
const Curve = function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
const count = arguments.length;
let seg1, seg2;
let point1, point2;
let handle1, handle2;
if (count === 3) {
this._path = arg0;
seg1 = arg1;
seg2 = arg2;
} else if (!count) {
seg1 = new Segment();
seg2 = new Segment();
} else if (count === 1) {
if (arg0.segment1) {
seg1 = new Segment(arg0.segment1);
seg2 = new Segment(arg0.segment2);
} else if (arg0.point1) {
point1 = arg0.point1;
handle1 = arg0.handle1;
handle2 = arg0.handle2;
point2 = arg0.point2;
} else if (Array.isArray(arg0)) {
point1 = [arg0[0], arg0[1]];
point2 = [arg0[6], arg0[7]];
handle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]];
handle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]];
}
} else if (count === 2) {
seg1 = new Segment(arg0);
seg2 = new Segment(arg1);
} else if (count === 4) {
point1 = arg0;
handle1 = arg1;
handle2 = arg2;
point2 = arg3;
} else if (count === 8) {
point1 = [arg0, arg1];
point2 = [arg6, arg7];
handle1 = [arg2 - arg0, arg3 - arg1];
handle2 = [arg4 - arg6, arg5 - arg7];
}
this._segment1 = seg1 || new Segment(point1, null, handle1);
this._segment2 = seg2 || new Segment(point2, handle2, null);
};
InitClassWithStatics(Curve, Base);
Curve.prototype._changed = function () {
this._length = this._bounds = undefined;
};
Curve.prototype.clone = function () {
return new Curve(this._segment1, this._segment2);
};
Curve.prototype.classify = function () {
return Curve.classify(this.getValues());
};
Curve.prototype.remove = function () {
let removed = false;
if (this._path) {
const segment2 = this._segment2;
const handleOut = segment2._handleOut;
removed = segment2.remove();
if (removed)
this._segment1._handleOut.set(handleOut);
}
return removed;
};
Curve.prototype.getPoint1 = function () {
return this._segment1._point;
};
Curve.prototype.getPoint2 = function () {
return this._segment2._point;
};
Curve.prototype.getSegment1 = function () {
return this._segment1;
};
Curve.prototype.getSegment2 = function () {
return this._segment2;
};
Curve.prototype.getPath = function () {
return this._path;
};
Curve.prototype.getIndex = function () {
return this._segment1._index;
};
Curve.prototype.getNext = function () {
const curves = this._path && this._path._curves;
return curves && (curves[this._segment1._index + 1]
|| this._path._closed && curves[0]) || null;
};
Curve.prototype.getPrevious = function () {
const curves = this._path && this._path._curves;
return curves && (curves[this._segment1._index - 1]
|| this._path._closed && curves[curves.length - 1]) || null;
};
Curve.prototype.isFirst = function () {
return !this._segment1._index;
};
Curve.prototype.isLast = function () {
const path = this._path;
return path && this._segment1._index === path._curves.length - 1
|| false;
};
Curve.prototype.getValues = function (matrix) {
return Curve.getValues(this._segment1, this._segment2, matrix);
};
Curve.prototype.getLength = function () {
if (this._length == null)
this._length = Curve.getLength(this.getValues(), 0, 1);
return this._length;
};
Curve.prototype.getArea = function () {
return Curve.getArea(this.getValues());
};
Curve.prototype.getLine = function () {
return new Line(this._segment1._point, this._segment2._point);
};
Curve.prototype.getPart = function (from, to) {
return new Curve(Curve.getPart(this.getValues(), from, to));
};
Curve.prototype.getPartLength = function (from, to) {
return Curve.getLength(this.getValues(), from, to);
};
Curve.prototype.divideAtTime = function (time, _setHandles) {
const tMin = 1e-8, tMax = 1 - tMin;
let res = null;
if (time >= tMin && time <= tMax) {
const parts = Curve.subdivide(this.getValues(), time);
const left = parts[0];
const right = parts[1];
const setHandles = _setHandles || this.hasHandles();
const seg1 = this._segment1;
const seg2 = this._segment2;
const path = this._path;
if (setHandles) {
seg1._handleOut._set(left[2] - left[0], left[3] - left[1]);
seg2._handleIn._set(right[4] - right[6], right[5] - right[7]);
}
const x = left[6];
const y = left[7];
const segment = new Segment(new Point(x, y),
setHandles && new Point(left[4] - x, left[5] - y),
setHandles && new Point(right[2] - x, right[3] - y)
);
if (path) {
path.insert(seg1._index + 1, segment);
res = this.getNext();
} else {
this._segment2 = segment;
this._changed();
res = new Curve(segment, seg2);
}
}
return res;
};
Curve.prototype.divide = function (offset, isTime) {
return this.divideAtTime(offset === undefined ? 0.5 : isTime ? offset
: this.getTimeAt(offset));
};
Curve.prototype.reversed = function () {
return new Curve(this._segment2.reversed(), this._segment1.reversed());
};
Curve.prototype.clearHandles = function () {
this._segment1._handleOut._set(0, 0);
this._segment2._handleIn._set(0, 0);
};
Curve.prototype.hasHandles = function () {
return !this._segment1._handleOut.isZero()
|| !this._segment2._handleIn.isZero();
};
Curve.prototype.hasLength = function (epsilon) {
return (!this.getPoint1().equals(this.getPoint2()) || this.hasHandles())
&& this.getLength() > (epsilon || 0);
};
Curve.prototype.isCollinear = function (curve) {
return curve && this.isStraight() && curve.isStraight()
&& this.getLine().isCollinear(curve.getLine());
};
Curve.prototype.isStraight = function (epsilon) {
const test = function (p1, h1, h2, p2) {
if (h1.isZero() && h2.isZero()) {
return true;
} else {
const v = p2.subtract(p1);
if (v.isZero()) { return false; }
if (v.isCollinear(h1) && v.isCollinear(h2)) {
const l = new Line(p1, p2);
const epsilon = 1e-7;
if (l.getDistance(p1.add(h1)) < epsilon && l.getDistance(p2.add(h2)) < epsilon) {
const div = v.dot(v);
const s1 = v.dot(h1) / div;
const s2 = v.dot(h2) / div;
return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1;
}
}
}
return false;
}
return test(this._segment1._point, this._segment1._handleOut, this._segment2._handleIn, this._segment2._point, epsilon);
};
Curve.prototype.getLocationAt = function (offset, _isTime) {
return this.getLocationAtTime(_isTime ? offset : this.getTimeAt(offset));
};
Curve.prototype.getLocationAtTime = function (t) {
return t != null && t >= 0 && t <= 1
? new CurveLocation(this, t)
: null;
};
Curve.prototype.getTimeAt = function (offset, start) {
return Curve.getTimeAt(this.getValues(), offset, start);
};
Curve.prototype.getTimesWithTangent = function () {
const tangent = Point.read(arguments);
return !tangent.isZero() ? Curve.getTimesWithTangent(this.getValues(), tangent) : [];
};
Curve.prototype.getTimeOf = function () {
return Curve.getTimeOf(this.getValues(), Point.read(arguments));
};
Curve.prototype.getNearestLocation = function () {
const point = Point.read(arguments);
const values = this.getValues();
const t = Curve.getNearestTime(values, point);
const pt = Curve.getPoint(values, t);
return new CurveLocation(this, t, pt, null, point.getDistance(pt));
};
Curve.prototype.getNearestPoint = function () {
const loc = this.getNearestLocation.apply(this, arguments);
return loc ? loc.getPoint() : loc;
};
Curve.prototype.getPointAt = function (location, _isTime) {
const values = this.getValues();
return Curve.getPoint(values, _isTime ? location : Curve.getTimeAt(values, location));
};
Curve.prototype.getPointAtTime = function (time) {
return Curve.getPoint(this.getValues(), time);
};
Curve.prototype.getTangentAt = function (location, _isTime) {
const values = this.getValues();
return Curve.getTangent(values, _isTime ? location : Curve.getTimeAt(values, location));
};
Curve.prototype.getTangentAtTime = function (time) {
return Curve.getTangent(this.getValues(), time);
};
Curve.prototype.getNormalAt = function (location, _isTime) {
const values = this.getValues();
return Curve.getNormal(values, _isTime ? location : Curve.getTimeAt(values, location));
};
Curve.prototype.getNormalAtTime = function (time) {
return Curve.getNormal(this.getValues(), time);
};
Curve.prototype.getWeightedTangentAt = function (location) {
const values = this.getValues();
return Curve.getWeightedTangent(values, location);
};
Curve.prototype.getWeightedNormalAt = function (location) {
const values = this.getValues();
return Curve.getWeightedNormal(values, location);
};
Curve.prototype.getCurvatureAt = function (location) {
const values = this.getValues();
return Curve.getCurvature(values, location);
};
Curve.prototype.getIntersections = function (curve) {
const v1 = this.getValues();
const v2 = curve && curve !== this && curve.getValues();
return v2
? Curve.getCurveIntersections(v1, v2, this, curve, [])
: Curve.getSelfIntersection(v1, this, []);
};
Curve.getValues = function (segment1, segment2, matrix, straight) {
const p1 = segment1._point;
const h1 = segment1._handleOut;
const h2 = segment2._handleIn;
const p2 = segment2._point;
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const values = straight
? [x1, y1, x1, y1, x2, y2, x2, y2]
: [x1, y1, x1 + h1.getX(), y1 + h1.getY(), x2 + h2.getX(), y2 + h2.getY(), x2, y2];
if (matrix) { matrix._transformCoordinates(values, values, 4); }
return values;
};
Curve.subdivide = function (v, t) {
if (t === undefined) { t = 0.5; }
const x0 = v[0], y0 = v[1];
const x1 = v[2], y1 = v[3];
const x2 = v[4], y2 = v[5];
const x3 = v[6], y3 = v[7];
const u = 1 - t;
const x4 = u * x0 + t * x1;
const y4 = u * y0 + t * y1;
const x5 = u * x1 + t * x2;
const y5 = u * y1 + t * y2;
const x6 = u * x2 + t * x3;
const y6 = u * y2 + t * y3;
const x7 = u * x4 + t * x5;
const y7 = u * y4 + t * y5;
const x8 = u * x5 + t * x6;
const y8 = u * y5 + t * y6;
const x9 = u * x7 + t * x8;
const y9 = u * y7 + t * y8;
return [
[x0, y0, x4, y4, x7, y7, x9, y9],
[x9, y9, x8, y8, x6, y6, x3, y3]
];
};
Curve.getMonoCurves = function (v, dir) {
const curves = [];
const io = dir ? 0 : 1;
const o0 = v[io + 0];
const o1 = v[io + 2];
const o2 = v[io + 4];
const o3 = v[io + 6];
if ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3) || Curve.isStraight(v)) {
curves.push(v);
} else {
const a = 3 * (o1 - o2) - o0 + o3;
const b = 2 * (o0 + o2) - 4 * o1;
const c = o1 - o0;
const tMin = 1e-8;
const tMax = 1 - tMin;
const roots = [];
const n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);
if (!n) {
curves.push(v);
} else {
roots.sort(function (a, b) {return a - b});
let t = roots[0];
let parts = Curve.subdivide(v, t);
curves.push(parts[0]);
if (n > 1) {
t = (roots[1] - t) / (1 - t);
parts = Curve.subdivide(parts[1], t);
curves.push(parts[0]);
}
curves.push(parts[1]);
}
}
return curves;
};
Curve.solveCubic = function (v, coord, val, roots, min, max) {
const v0 = v[coord];
const v1 = v[coord + 2];
const v2 = v[coord + 4];
const v3 = v[coord + 6];
let res = 0;
if (!(v0 < val && v3 < val && v1 < val && v2 < val || v0 > val && v3 > val && v1 > val && v2 > val)) {
const c = 3 * (v1 - v0);
const b = 3 * (v2 - v1) - c;
const a = v3 - v0 - c - b;
res = Numerical.solveCubic(a, b, c, v0 - val, roots, min, max);
}
return res;
};
Curve.getTimeOf = function (v, point) {
const p0 = new Point(v[0], v[1]);
const p3 = new Point(v[6], v[7]);
const t = point.isClose(p0, Numerical.EPSILON) ? 0
: point.isClose(p3, Numerical.EPSILON) ? 1
: null;
if (t === null) {
const coords = [point.x, point.y];
const roots = [];
for (let c = 0; c < 2; c++) {
const count = Curve.solveCubic(v, c, coords[c], roots, 0, 1);
for (let i = 0; i < count; i++) {
const u = roots[i];
if (point.isClose(Curve.getPoint(v, u), Numerical.GEOMETRIC_EPSILON))
return u;
}
}
}
return point.isClose(p0, Numerical.GEOMETRIC_EPSILON) ? 0
: point.isClose(p3, Numerical.GEOMETRIC_EPSILON) ? 1
: null;
};
Curve.getNearestTime = function (v, point) {
if (Curve.isStraight(v)) {
const x0 = v[0], y0 = v[1];
const x3 = v[6], y3 = v[7];
const vx = x3 - x0, vy = y3 - y0;
const det = vx * vx + vy * vy;
if (det === 0) { return 0; }
const u = ((point.x - x0) * vx + (point.y - y0) * vy) / det;
return u < Numerical.EPSILON ? 0
: u > 0.999999999999 ? 1
: Curve.getTimeOf(v, new Point(x0 + u * vx, y0 + u * vy));
}
const count = 100;
let minDist = Infinity;
let minT = 0;
function refine(t) {
if (t >= 0 && t <= 1) {
const dist = point.getDistance(Curve.getPoint(v, t), true);
if (dist < minDist) {
minDist = dist;
minT = t;
return true;
}
}
}
for (let i = 0; i <= count; i++)
refine(i / count);
let step = 1 / (count * 2);
while (step > 1e-8) {
if (!refine(minT - step) && !refine(minT + step))
step /= 2;
}
return minT;
};
Curve.getPart = function (v, from, to) {
const flip = from > to;
if (flip) {
const tmp = from;
from = to;
to = tmp;
}
if (from > 0)
v = Curve.subdivide(v, from)[1];
if (to < 1)
v = Curve.subdivide(v, (to - from) / (1 - from))[0];
return flip ? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]] : v;
};
Curve.getArea = function (v) {
const x0 = v[0], y0 = v[1];
const x1 = v[2], y1 = v[3];
const x2 = v[4], y2 = v[5];
const x3 = v[6], y3 = v[7];
return 3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)
+ y1 * (x0 - x2) - x1 * (y0 - y2)
+ y3 * (x2 + x0 / 3) - x3 * (y2 + y0 / 3)) / 20;
};
Curve.getBounds = function (v) {
const min = v.slice(0, 2);
const max = min.slice();
const roots = [0, 0];
for (let i = 0; i < 2; i++)
Curve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6], i, 0, min, max, roots);
return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
};
Curve._addBounds = function (v0, v1, v2, v3, coord, padding, min, max, roots) {
function add(value, padding) {
const left = value - padding;
const right = value + padding;
if (left < min[coord])
min[coord] = left;
if (right > max[coord])
max[coord] = right;
}
padding /= 2;
const minPad = min[coord] + padding;
const maxPad = max[coord] - padding;
if (v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad ||
v0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) {
if (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) {
add(v0, 0);
add(v3, 0);
} else {
const a = 3 * (v1 - v2) - v0 + v3;
const b = 2 * (v0 + v2) - 4 * v1;
const c = v1 - v0;
const count = Numerical.solveQuadratic(a, b, c, roots);
const tMin = 1e-8;
const tMax = 1 - tMin;
add(v3, 0);
for (let i = 0; i < count; i++) {
const t = roots[i];
const u = 1 - t;
if (tMin <= t && t <= tMax)
add(u * u * u * v0
+ 3 * u * u * t * v1
+ 3 * u * t * t * v2
+ t * t * t * v3,
padding
);
}
}
}
};
Curve.isStraight = function (v, epsilon) {
const x0 = v[0];
const y0 = v[1];
const x3 = v[6];
const y3 = v[7];
function test(p1, h1, h2, p2) {
if (h1.isZero() && h2.isZero()) {
return true;
} else {
const v = p2.subtract(p1);
if (v.isZero()) { return false; }
if (v.isCollinear(h1) && v.isCollinear(h2)) {
const l = new Line(p1, p2);
if (l.getDistance(p1.add(h1)) < Numerical.GEOMETRIC_EPSILON &&
l.getDistance(p2.add(h2)) < Numerical.GEOMETRIC_EPSILON) {
const div = v.dot(v);
const s1 = v.dot(h1) / div;
const s2 = v.dot(h2) / div;
return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1;
}
}
}
return false;
};
return test(
new Point(x0, y0),
new Point(v[2] - x0, v[3] - y0),
new Point(v[4] - x3, v[5] - y3),
new Point(x3, y3), epsilon);
};
Curve.getLengthIntegrand = function (v) {
const x0 = v[0], y0 = v[1];
const x1 = v[2], y1 = v[3];
const x2 = v[4], y2 = v[5];
const x3 = v[6], y3 = v[7];
const ax = 9 * (x1 - x2) + 3 * (x3 - x0);
const bx = 6 * (x0 + x2) - 12 * x1;
const cx = 3 * (x1 - x0);
const ay = 9 * (y1 - y2) + 3 * (y3 - y0);
const by = 6 * (y0 + y2) - 12 * y1;
const cy = 3 * (y1 - y0);
return function (t) {
const dx = (ax * t + bx) * t + cx;
const dy = (ay * t + by) * t + cy;
return Math.sqrt(dx * dx + dy * dy);
};
};
Curve.getIterations = function (a, b) {
return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
};
Curve.evaluate = function (v, t, type, normalized) {
if (t == null || t < 0 || t > 1) { return null; }
let x0 = v[0];
let y0 = v[1];
let x1 = v[2];
let y1 = v[3];
let x2 = v[4];
let y2 = v[5];
let x3 = v[6];
let y3 = v[7];
if (Numerical.isZero(x1 - x0) && Numerical.isZero(y1 - y0)) {
x1 = x0;
y1 = y0;
}
if (Numerical.isZero(x2 - x3) && Numerical.isZero(y2 - y3)) {
x2 = x3;
y2 = y3;
}
const cx = 3 * (x1 - x0);
const bx = 3 * (x2 - x1) - cx;
const ax = x3 - x0 - cx - bx;
const cy = 3 * (y1 - y0);
const by = 3 * (y2 - y1) - cy;
const ay = y3 - y0 - cy - by;
let x, y;
if (type === 0) {
x = t === 0
? x0
: t === 1
? x3
: ((ax * t + bx) * t + cx) * t + x0;
y = t === 0
? y0
: t === 1
? y3
: ((ay * t + by) * t + cy) * t + y0;
} else {
const tMin = 1e-8;
const tMax = 1 - tMin;
if (t < tMin) {
x = cx;
y = cy;
} else if (t > tMax) {
x = 3 * (x3 - x2);
y = 3 * (y3 - y2);
} else {
x = (3 * ax * t + 2 * bx) * t + cx;
y = (3 * ay * t + 2 * by) * t + cy;
}
if (normalized) {
if (x === 0 && y === 0 && (t < tMin || t > tMax)) {
x = x2 - x1;
y = y2 - y1;
}
const len = Math.sqrt(x * x + y * y);
if (len) {
x /= len;
y /= len;
}
}
if (type === 3) {
const _x2 = 6 * ax * t + 2 * bx;
const _y2 = 6 * ay * t + 2 * by;
const d = Math.pow(x * x + y * y, 3 / 2);
x = d !== 0 ? (x * _y2 - y * _x2) / d : 0;
y = 0;
}
}
return type === 2 ? new Point(y, -x) : new Point(x, y);
};
Curve.classify = function (v) {
const x0 = v[0], y0 = v[1];
const x1 = v[2], y1 = v[3];
const x2 = v[4], y2 = v[5];
const x3 = v[6], y3 = v[7];
const a1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2;
const a2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3;
const a3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0;
let d3 = 3 * a3;
let d2 = d3 - a2;
let d1 = d2 - a2 + a1;
const l = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
const s = l !== 0 ? 1 / l : 0;
d1 *= s;
d2 *= s;
d3 *= s;
function type(type, t1, t2) {
const hasRoots = t1 !== undefined;
let t1Ok = hasRoots && t1 > 0 && t1 < 1;
let t2Ok = hasRoots && t2 > 0 && t2 < 1;
if (hasRoots && (!(t1Ok || t2Ok) || type === CURVE_TYPES.loop && !(t1Ok && t2Ok))) {
type = CURVE_TYPES.arc;
t1Ok = t2Ok = false;
}
return {
type: type,
roots: t1Ok || t2Ok
? t1Ok && t2Ok
? t1 < t2
? [t1, t2]
: [t2, t1]
: [t1Ok ? t1 : t2]
: null
};
}
if (Numerical.isZero(d1)) {
return Numerical.isZero(d2)
? type(Numerical.isZero(d3) ? CURVE_TYPES.line : CURVE_TYPES.quadratic)
: type(CURVE_TYPES.serpentine, d3 / (3 * d2));
}
const d = 3 * d2 * d2 - 4 * d1 * d3;
if (Numerical.isZero(d)) {
return type(CURVE_TYPES.cusp, d2 / (2 * d1));
}
const f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d);
const f2 = 2 * d1;
return type(d > 0 ? CURVE_TYPES.serpentine : CURVE_TYPES.loop, (d2 + f1) / f2, (d2 - f1) / f2);
};
Curve.getLength = function (v, a, b, ds) {
if (a === undefined) { a = 0; }
if (b === undefined) { b = 1; }
if (Curve.isStraight(v)) {
let c = v;
if (b < 1) {
c = Curve.subdivide(c, b)[0];
a /= b;
}
if (a > 0) {
c = Curve.subdivide(c, a)[1];
}
const dx = c[6] - c[0];
const dy = c[7] - c[1];
return Math.sqrt(dx * dx + dy * dy);
}
return Numerical.integrate(ds || Curve.getLengthIntegrand(v), a, b, Curve.getIterations(a, b));
};
Curve.getTimeAt = function (v, offset, start) {
if (start === undefined) { start = offset < 0 ? 1 : 0; }
if (offset === 0) { return start; }
const forward = offset > 0;
const a = forward ? start : 0;
const b = forward ? 1 : start;
const ds = Curve.getLengthIntegrand(v);
const rangeLength = Curve.getLength(v, a, b, ds);
const diff = Math.abs(offset) - rangeLength;
if (Math.abs(diff) < Numerical.EPSILON) {
return forward ? b : a;
} else if (diff > Numerical.EPSILON) {
return null;
}
let length = 0;
function f(t) {
length += Numerical.integrate(ds, start, t, Curve.getIterations(start, t));
start = t;
return length - offset;
}
const guess = offset / rangeLength;
return Numerical.findRoot(f, ds, start + guess, a, b, 32, Numerical.EPSILON);
};
Curve.getPoint = function (v, t) {
return Curve.evaluate(v, t, 0, false);
};
Curve.getTangent = function (v, t) {
return Curve.evaluate(v, t, 1, true);
};
Curve.getNormal = function (v, t) {
return Curve.evaluate(v, t, 2, true);
};
Curve.getWeightedNormal = function (v, t) {
return Curve.evaluate(v, t, 2, false);
};
Curve.getCurvature = function (v, t) {
return Curve.evaluate(v, t, 3, false).x;
};
Curve.getPeaks = function (v) {
const x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7];
const ax = -x0 + 3 * x1 - 3 * x2 + x3;
const bx = 3 * x0 - 6 * x1 + 3 * x2;
const cx = -3 * x0 + 3 * x1;
const ay = -y0 + 3 * y1 - 3 * y2 + y3;
const by = 3 * y0 - 6 * y1 + 3 * y2;
const cy = -3 * y0 + 3 * y1;
const tMin = 1e-8;
const tMax = 1 - tMin;
const roots = [];
Numerical.solveCubic(
9 * (ax * ax + ay * ay),
9 * (ax * bx + by * ay),
2 * (bx * bx + by * by) + 3 * (cx * ax + cy * ay),
(cx * bx + by * cy),
roots, tMin, tMax
);
return roots.sort(function (a, b) {return a - b});
};
Curve.addLocation = function (locations, include, c1, t1, c2, t2, overlap) {
const excludeStart = !overlap && c1.getPrevious() === c2;
const excludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2;
const tMin = 1e-8;
const tMax = 1 - tMin;
if (t1 !== null && t1 >= (excludeStart ? tMin : 0) && t1 <= (excludeEnd ? tMax : 1)) {
if (t2 !== null && t2 >= (excludeEnd ? tMin : 0) && t2 <= (excludeStart ? tMax : 1)) {
const loc1 = new CurveLocation(c1, t1, null, overlap);
const loc2 = new CurveLocation(c2, t2, null, overlap);
loc1._intersection = loc2;
loc2._intersection = loc1;
if (!include || include(loc1)) {
CurveLocation.insert(locations, loc1, true);
}
}
}
};
Curve.addCurveIntersections = function (v1, v2, c1, c2, locations, include, flip, recursion, calls, tMin, tMax, uMin, uMax) {
if (++calls >= 4096 || ++recursion >= 40)
return calls;
const fatLineEpsilon = 1e-9;
const q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7];
const d1 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]);
const d2 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]);
const factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9;
const dMin = factor * Math.min(0, d1, d2);
const dMax = factor * Math.max(0, d1, d2);
const dp0 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v1[0], v1[1]);
const dp1 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]);
const dp2 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]);
const dp3 = Line.getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]);
const hull = Curve.getConvexHull(dp0, dp1, dp2, dp3);
const top = hull[0];
const bottom = hull[1];
let tMinClip;
let tMaxClip;
if (d1 === 0 && d2 === 0 && dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0
|| (tMinClip = Curve.clipConvexHull(top, bottom, dMin, dMax)) == null
|| (tMaxClip = Curve.clipConvexHull(top.reverse(), bottom.reverse(), dMin, dMax)) == null) {
return calls;
}
const tMinNew = tMin + (tMax - tMin) * tMinClip;
const tMaxNew = tMin + (tMax - tMin) * tMaxClip;
if (Math.max(uMax - uMin, tMaxNew - tMinNew) < fatLineEpsilon) {
const t = (tMinNew + tMaxNew) / 2;
const u = (uMin + uMax) / 2;
Curve.addLocation(locations, include, flip ? c2 : c1, flip ? u : t, flip ? c1 : c2, flip ? t : u);
} else {
v1 = Curve.getPart(v1, tMinClip, tMaxClip);
const uDiff = uMax - uMin;
if (tMaxClip - tMinClip > 0.8) {
if (tMaxNew - tMinNew > uDiff) {
const parts = Curve.subdivide(v1, 0.5);
const t = (tMinNew + tMaxNew) / 2;
calls = Curve.addCurveIntersections(
v2, parts[0], c2, c1, locations, include, !flip,
recursion, calls, uMin, uMax, tMinNew, t);
calls = Curve.addCurveIntersections(
v2, parts[1], c2, c1, locations, include, !flip,
recursion, calls, uMin, uMax, t, tMaxNew);
} else {
const parts = Curve.subdivide(v2, 0.5);
const u = (uMin + uMax) / 2;
calls = Curve.addCurveIntersections(
parts[0], v1, c2, c1, locations, include, !flip,
recursion, calls, uMin, u, tMinNew, tMaxNew);
calls = Curve.addCurveIntersections(
parts[1], v1, c2, c1, locations, include, !flip,
recursion, calls, u, uMax, tMinNew, tMaxNew);
}
} else {
if (uDiff === 0 || uDiff >= fatLineEpsilon) {
calls = Curve.addCurveIntersections(
v2, v1, c2, c1, locations, include, !flip,
recursion, calls, uMin, uMax, tMinNew, tMaxNew);
} else {
calls = Curve.addCurveIntersections(
v1, v2, c1, c2, locations, include, flip,
recursion, calls, tMinNew, tMaxNew, uMin, uMax);
}
}
}
return calls;
};
Curve.getConvexHull = function (dq0, dq1, dq2, dq3) {
const p0 = [0, dq0];
const p1 = [1 / 3, dq1];
const p2 = [2 / 3, dq2];
const p3 = [1, dq3];
const dist1 = dq1 - (2 * dq0 + dq3) / 3;
const dist2 = dq2 - (dq0 + 2 * dq3) / 3;
let hull;
if (dist1 * dist2 < 0) {
hull = [[p0, p1, p3], [p0, p2, p3]];
} else {
const distRatio = dist1 / dist2;
hull = [
distRatio >= 2
? [p0, p1, p3]
: distRatio <= 0.5
? [p0, p2, p3]
: [p0, p1, p2, p3],
[p0, p3]
];
}
return (dist1 || dist2) < 0 ? hull.reverse() : hull;
};
Curve.clipConvexHull = function (hullTop, hullBottom, dMin, dMax) {
if (hullTop[0][1] < dMin) {
return Curve.clipConvexHullPart(hullTop, true, dMin);
} else if (hullBottom[0][1] > dMax) {
return Curve.clipConvexHullPart(hullBottom, false, dMax);
} else {
return hullTop[0][0];
}
};
Curve.clipConvexHullPart = function (part, top, threshold) {
let px = part[0][0];
let py = part[0][1];
for (let i = 1, l = part.length; i < l; i++) {
const qx = part[i][0];
const qy = part[i][1];
if (top ? qy >= threshold : qy <= threshold) {
return qy === threshold ? qx : px + (threshold - py) * (qx - px) / (qy - py);
}
px = qx;
py = qy;
}
return null;
};
Curve.getCurveLineIntersections = function (v, px, py, vx, vy) {
if (Numerical.isZero(vx) && Numerical.isZero(vy)) {
const t = Curve.getTimeOf(v, new Point(px, py));
return t === null ? [] : [t];
}
const angle = Math.atan2(-vy, vx);
const sin = Math.sin(angle);
const cos = Math.cos(angle);
const rv = [];
const roots = [];
for (let i = 0; i < 8; i += 2) {
const x = v[i] - px;
const y = v[i + 1] - py;
rv.push(x * cos - y * sin, x * sin + y * cos);
}
Curve.solveCubic(rv, 1, 0, roots, 0, 1);
return roots;
};
Curve.addCurveLineIntersections = function (v1, v2, c1, c2, locations, include, flip) {
const x1 = v2[0];
const y1 = v2[1];
const x2 = v2[6];
const y2 = v2[7];
const roots = Curve.getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1);
for (let i = 0, l = roots.length; i < l; i++) {
const t1 = roots[i];
const p1 = Curve.getPoint(v1, t1);
const t2 = Curve.getTimeOf(v2, p1);
if (t2 !== null) {
flip
? Curve.addLocation(locations, include, c2, t2, c1, t1)
: Curve.addLocation(locations, include, c1, t1, c2, t2);
}
}
};
Curve.addLineIntersection = function (v1, v2, c1, c2, locations, include) {
const pt = Line.intersect(v1[0], v1[1], v1[6], v1[7], v2[0], v2[1], v2[6], v2[7]);
if (pt) {
Curve.addLocation(locations, include,
c1, Curve.getTimeOf(v1, pt),
c2, Curve.getTimeOf(v2, pt)
);
}
};
Curve.getCurveIntersections = function (v1, v2, c1, c2, locations, include) {
if (Math.max(v1[0], v1[2], v1[4], v1[6]) + Numerical.EPSILON > Math.min(v2[0], v2[2], v2[4], v2[6])
&& Math.min(v1[0], v1[2], v1[4], v1[6]) - Numerical.EPSILON < Math.max(v2[0], v2[2], v2[4], v2[6])
&& Math.max(v1[1], v1[3], v1[5], v1[7]) + Numerical.EPSILON > Math.min(v2[1], v2[3], v2[5], v2[7])
&& Math.min(v1[1], v1[3], v1[5], v1[7]) - Numerical.EPSILON < Math.max(v2[1], v2[3], v2[5], v2[7])) {
const overlaps = Curve.getOverlaps(v1, v2);
if (overlaps) {
for (let i = 0; i < 2; i++) {
const overlap = overlaps[i];
Curve.addLocation(locations, include, c1, overlap[0], c2, overlap[1], true);
}
} else {
const straight1 = Curve.isStraight(v1);
const straight2 = Curve.isStraight(v2);
const straight = straight1 && straight2;
const flip = straight1 && !straight2;
const before = locations.length;
const addIntersectionsFunction = straight
? Curve.addLineIntersection
: straight1 || straight2
? Curve.addCurveLineIntersections
: Curve.addCurveIntersections
flip
? addIntersectionsFunction(v2, v1, c2, c1, locations, include, flip, 0, 0, 0, 1, 0, 1)
: addIntersectionsFunction(v1, v2, c1, c2, locations, include, flip, 0, 0, 0, 1, 0, 1);
if (!straight || locations.length === before) {
for (let i = 0; i < 4; i++) {
const t1 = i >> 1;
const t2 = i & 1;
const i1 = t1 * 6;
const i2 = t2 * 6;
const p1 = new Point(v1[i1], v1[i1 + 1]);
const p2 = new Point(v2[i2], v2[i2 + 1]);
if (p1.isClose(p2, Numerical.EPSILON)) {
Curve.addLocation(locations, include, c1, t1, c2, t2);
}
}
}
}
}
return locations;
};
Curve.getSelfIntersection = function (v1, c1, locations, include) {
const info = Curve.classify(v1);
if (info.type === CURVE_TYPES.loop) {
const roots = info.roots;
Curve.addLocation(locations, include, c1, roots[0], c1, roots[1]);
}
return locations;
};
Curve.getIntersections = function (curves1, curves2, include, matrix1, matrix2, _returnFirst) {
const epsilon = 1e-7;
const self = !curves2;
if (self)
curves2 = curves1;
const values1 = new Array(curves1.length);
const values2 = self ? values1 : new Array(curves2.length);
const locations = [];
for (let i = 0; i < curves1.length; i++) {
values1[i] = curves1[i].getValues(matrix1);
}
if (!self) {
for (let i = 0; i < curves2.length; i++) {
values2[i] = curves2[i].getValues(matrix2);
}
}
const boundsCollisions = CollisionDetection.findCurveBoundsCollisions(values1, values2, epsilon);
for (let index1 = 0; index1 < curves1.length; index1++) {
const curve1 = curves1[index1];
const v1 = values1[index1];
if (self) { Curve.getSelfIntersection(v1, curve1, locations, include); }
const collisions1 = boundsCollisions[index1];
if (collisions1) {
for (let j = 0; j < collisions1.length; j++) {
if (_returnFirst && locations.length)
return locations;
const index2 = collisions1[j];
if (!self || index2 > index1) {
const curve2 = curves2[index2];
const v2 = values2[index2];
Curve.getCurveIntersections(v1, v2, curve1, curve2, locations, include);
}
}
}
}
return locations;
};
Curve.getOverlaps = function (v1, v2) {
function getSquaredLineLength(v) {
const x = v[6] - v[0];
const y = v[7] - v[1];
return x * x + y * y;
}
const timeEpsilon = 1e-8;
const geomEpsilon = 1e-7;
let straight1 = Curve.isStraight(v1);
let straight2 = Curve.isStraight(v2);
let straightBoth = straight1 && straight2;
const flip = getSquaredLineLength(v1) < getSquaredLineLength(v2);
const l1 = flip ? v2 : v1;
const l2 = flip ? v1 : v2;
const px = l1[0], py = l1[1];
const vx = l1[6] - px, vy = l1[7] - py;
if (Line.getDistance(px, py, vx, vy, l2[0], l2[1], true) < geomEpsilon
&& Line.getDistance(px, py, vx, vy, l2[6], l2[7], true) < geomEpsilon) {
if (!straightBoth
&& Line.getDistance(px, py, vx, vy, l1[2], l1[3], true) < geomEpsilon
&& Line.getDistance(px, py, vx, vy, l1[4], l1[5], true) < geomEpsilon
&& Line.getDistance(px, py, vx, vy, l2[2], l2[3], true) < geomEpsilon
&& Line.getDistance(px, py, vx, vy, l2[4], l2[5], true) < geomEpsilon) {
straight1 = straight2 = straightBoth = true;
}
} else if (straightBoth) {
return null;
}
if (straight1 ^ straight2) {
return null;
}
const v = [v1, v2];
let pairs = [];
for (let i = 0; i < 4 && pairs.length < 2; i++) {
const i1 = i & 1;
const i2 = i1 ^ 1;
const t1 = i >> 1;
const t2 = Curve.getTimeOf(v[i1], new Point(v[i2][t1 ? 6 : 0], v[i2][t1 ? 7 : 1]));
if (t2 != null) {
const pair = i1 ? [t1, t2] : [t2, t1];
if (!pairs.length || Math.abs(pair[0] - pairs[0][0]) > timeEpsilon && Math.abs(pair[1] - pairs[0][1]) > timeEpsilon) {
pairs.push(pair);
}
}
if (i > 2 && !pairs.length)
break;
}
if (pairs.length !== 2) {
pairs = null;
} else if (!straightBoth) {
const o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]);
const o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]);
if (Math.abs(o2[2] - o1[2]) > geomEpsilon
|| Math.abs(o2[3] - o1[3]) > geomEpsilon
|| Math.abs(o2[4] - o1[4]) > geomEpsilon
|| Math.abs(o2[5] - o1[5]) > geomEpsilon)
pairs = null;
}
return pairs;
};
Curve.getTimesWithTangent = function (v, tangent) {
const x0 = v[0], y0 = v[1], x1 = v[2], y1 = v[3], x2 = v[4], y2 = v[5], x3 = v[6], y3 = v[7];
const normalized = tangent.normalize();
const tx = normalized.x;
const ty = normalized.y;
const ax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0;
const ay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0;
const bx = 6 * x2 - 12 * x1 + 6 * x0;
const by = 6 * y2 - 12 * y1 + 6 * y0;
const cx = 3 * x1 - 3 * x0;
const cy = 3 * y1 - 3 * y0;
let den = 2 * ax * ty - 2 * ay * tx;
const times = [];
if (Math.abs(den) < Numerical.CURVETIME_EPSILON) {
const num = ax * cy - ay * cx;
den = ax * by - ay * bx;
if (den != 0) {
const t = -num / den;
if (t >= 0 && t <= 1) times.push(t);
}
} else {
const delta = (bx * bx - 4 * ax * cx) * ty * ty +
(-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty +
(by * by - 4 * ay * cy) * tx * tx;
const k = bx * ty - by * tx;
if (delta >= 0 && den != 0) {
const d = Math.sqrt(delta);
const t0 = -(k + d) / den;
const t1 = (-k + d) / den;
if (t0 >= 0 && t0 <= 1) times.push(t0);
if (t1 >= 0 && t1 <= 1) times.push(t1);
}
}
return times;
};
const CurveLocation = function (curve, time, point, _overlap, _distance) {
if (time >= 0.99999999) {
const next = curve.getNext();
if (next) {
time = 0;
curve = next;
}
}
this._setCurve(curve);
this._time = time;
this._point = point || curve.getPointAtTime(time);
this._overlap = _overlap;
this._distance = _distance;
this._intersection = this._next = this._previous = null;
};
InitClassWithStatics(CurveLocation, Base);
CurveLocation.prototype._setPath = function (path) {
this._path = path;
this._version = path ? path._version : 0;
};
CurveLocation.prototype._setCurve = function (curve) {
this._setPath(curve._path);
this._curve = curve;
this._segment = null;
this._segment1 = curve._segment1;
this._segment2 = curve._segment2;
};
CurveLocation.prototype._setSegment = function (segment) {
const curve = segment.getCurve();
if (curve) {
this._setCurve(curve);
} else {
this._setPath(segment._path);
this._segment1 = segment;
this._segment2 = null;
}
this._segment = segment;
this._time = segment === this._segment1 ? 0 : 1;
this._point = segment._point.clone();
};
CurveLocation.prototype.getSegment = function () {
let segment = this._segment;
if (!segment) {
const curve = this.getCurve();
const time = this.getTime();
if (time === 0) {
segment = curve._segment1;
} else if (time === 1) {
segment = curve._segment2;
} else if (time != null) {
segment = curve.getPartLength(0, time) < curve.getPartLength(time, 1)
? curve._segment1
: curve._segment2;
}
this._segment = segment;
}
return segment;
};
CurveLocation.prototype.getCurve = function () {
if (this._path && this._path._version !== this._version) {
this._time = this._offset = this._curveOffset = this._curve = null;
}
const that = this;
function trySegment(segment) {
const curve = segment && segment.getCurve();
if (curve && (that._time = curve.getTimeOf(that._point)) != null) {
that._setCurve(curve);
return curve;
}
}
return this._curve
|| trySegment(this._segment)
|| trySegment(this._segment1)
|| trySegment(this._segment2.getPrevious());
};
CurveLocation.prototype.getPath = function () {
const curve = this.getCurve();
return curve && curve._path;
};
CurveLocation.prototype.getIndex = function () {
const curve = this.getCurve();
return curve && curve.getIndex();
};
CurveLocation.prototype.getTime = function () {
const curve = this.getCurve();
return curve && this._time == null
? this._time = curve.getTimeOf(this._point)
: this._time;
};
CurveLocation.prototype.getPoint = function () {
return this._point;
};
CurveLocation.prototype.getOffset = function () {
let offset = this._offset;
if (offset == null) {
offset = 0;
const path = this.getPath();
const index = this.getIndex();
if (path && index != null) {
const curves = path.getCurves();
for (let i = 0; i < index; i++)
offset += curves[i].getLength();
}
this._offset = offset += this.getCurveOffset();
}
return offset;
};
CurveLocation.prototype.getCurveOffset = function () {
let offset = this._curveOffset;
if (offset == null) {
const curve = this.getCurve();
const time = this.getTime();
this._curveOffset = offset = time != null && curve && curve.getPartLength(0, time);
}
return offset;
};
CurveLocation.prototype.getIntersection = function () {
return this._intersection;
};
CurveLocation.prototype.getDistance = function () {
return this._distance;
};
CurveLocation.prototype.divide = function () {
const curve = this.getCurve();
const res = curve && curve.divideAtTime(this.getTime());
if (res) { this._setSegment(res._segment1); }
return res;
};
CurveLocation.prototype.equals = function (loc, _ignoreOther) {
if (this === loc) return true;
if (!(loc instanceof CurveLocation)) return false;
const curve1 = this.getCurve();
const curve2 = loc.getCurve();
const samePath = curve1._path === curve2._path;
if (!samePath) return false;
const offsetDifference = Math.abs(this.getOffset() - loc.getOffset());
const closeOffsets = (
offsetDifference < Numerical.GEOMETRIC_EPSILON ||
(curve1._path && Math.abs(curve1._path.getLength() - offsetDifference) < Numerical.GEOMETRIC_EPSILON)
);
const intersection1 = !_ignoreOther && this._intersection;
const intersection2 = !_ignoreOther && loc._intersection;
const matchingIntersections = !intersection1 && !intersection2 || (intersection1 && intersection2 && intersection1.equals(intersection2, true));
return closeOffsets && matchingIntersections;
};
CurveLocation.prototype.isTouching = function () {
if (this._intersection && this.getTangent().isCollinear(this._intersection.getTangent())) {
const curve1 = this.getCurve();
const curve2 = this._intersection.getCurve();
return !(curve1.isStraight() && curve2.isStraight() && curve1.getLine().intersect(curve2.getLine()));
}
return false;
};
CurveLocation.prototype.isCrossing = function () {
if (!this._intersection) { return false; }
const t1 = this.getTime();
const t2 = this._intersection.getTime();
const tMin = 1e-8;
const tMax = 1 - tMin;
const t1Inside = t1 >= tMin && t1 <= tMax;
const t2Inside = t2 >= tMin && t2 <= tMax;
if (t1Inside && t2Inside)
return !this.isTouching();
let c2 = this.getCurve();
let c1 = c2 && t1 < tMin ? c2.getPrevious() : c2;
let c4 = this._intersection.getCurve();
let c3 = c4 && t2 < tMin ? c4.getPrevious() : c4;
if (t1 > tMax)
c2 = c2.getNext();
if (t2 > tMax)
c4 = c4.getNext();
if (!c1 || !c2 || !c3 || !c4)
return false;
const offsets = [];
function addOffsets(curve, end) {
const v = curve.getValues();
const roots = Curve.classify(v).roots || Curve.getPeaks(v);
const count = roots.length;
const offset = Curve.getLength(v, end && count ? roots[count - 1] : 0, !end && count ? roots[0] : 1);
offsets.push(count ? offset : offset / 32);
}
function isInRange(angle, min, max) {
return min < max
? angle > min && angle < max
: angle > min || angle < max;
}
if (!t1Inside) {
addOffsets(c1, true);
addOffsets(c2, false);
}
if (!t2Inside) {
addOffsets(c3, true);
addOffsets(c4, false);
}
const pt = this.getPoint();
const offset = Math.min.apply(Math, offsets);
const v2 = t1Inside ? c2.getTangentAtTime(t1) : c2.getPointAt(offset).subtract(pt);
const v1 = t1Inside ? v2.negate() : c1.getPointAt(-offset).subtract(pt);
const v4 = t2Inside ? c4.getTangentAtTime(t2) : c4.getPointAt(offset).subtract(pt);
const v3 = t2Inside ? v4.negate() : c3.getPointAt(-offset).subtract(pt);
const a1 = v1.getAngle();
const a2 = v2.getAngle();
const a3 = v3.getAngle();
const a4 = v4.getAngle();
return !!(t1Inside
? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) && (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3))
: (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) && (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1)));
};
CurveLocation.prototype.hasOverlap = function () {
return !!this._overlap;
};
CurveLocation.prototype.getTangent = function () {
const curve = this.getCurve();
const time = this.getTime();
return time != null && curve && curve.getTangentAt(time, true);
};
CurveLocation.prototype.getNormal = function () {
const curve = this.getCurve();
const time = this.getTime();
return time != null && curve && curve.getNormalAt(time, true);
};
CurveLocation.prototype.getWeightedTangent = function () {
const curve = this.getCurve();
const time = this.getTime();
return time != null && curve && curve.getWeightedTangentAt(time);
};
CurveLocation.prototype.getWeightedNormal = function () {
const curve = this.getCurve();
const time = this.getTime();
return time != null && curve && curve.getWeightedNormalAt(time);
};
CurveLocation.prototype.getCurvature = function () {
const curve = this.getCurve();
const time = this.getTime();
return time != null && curve && curve.getCurvatureAt(time);
};
CurveLocation.insert = function (locations, loc, merge) {
const length = locations.length;
function search(index, dir) {
for (let i = index + dir; i >= -1 && i <= length; i += dir) {
const loc2 = locations[((i % length) + length) % length];
if (!loc.getPoint().isClose(loc2.getPoint(), 1e-7))
break;
if (loc.equals(loc2))
return loc2;
}
return null;
}
let l = 0;
let r = length - 1;
while (l <= r) {
const m = (l + r) >>> 1;
const loc2 = locations[m];
let found;
if (merge && (found = loc.equals(loc2) ? loc2 : (search(m, -1) || search(m, 1)))) {
if (loc._overlap) {
found._overlap = found._intersection._overlap = true;
}
return found;
}
const path1 = loc.getPath();
const path2 = loc2.getPath();
const diff = path1 !== path2
? path1._id - path2._id
: (loc.getIndex() + loc.getTime()) - (loc2.getIndex() + loc2.getTime());
diff < 0 ? (r = m - 1) : (l = m + 1);
}
locations.splice(l, 0, loc);
return loc;
};
CurveLocation.expand = function (locations) {
const expanded = locations.slice();
for (let i = locations.length - 1; i >= 0; i--) {
CurveLocation.insert(expanded, locations[i]._intersection, false);
}
return expanded;
};
const PathItem = function PathItem() { };
InitClassWithStatics(PathItem, Item);
PathItem.prototype.isClockwise = function () {
return this.getArea() >= 0;
};
PathItem.prototype.setClockwise = function (clockwise) {
if (this.isClockwise() != (clockwise = !!clockwise))
this.reverse();
};
PathItem.prototype._contains = function (point) {
const winding = point.isInside(this.getBounds({ internal: true, handle: true }))
? this._getWinding(point)
: {};
return winding.onPath || !!(winding.winding);
};
PathItem.prototype.getIntersections = function (path, include, _matrix, _returnFirst) {
const self = this === path || !path;
const matrix1 = this._matrix._orNullIfIdentity();
const matrix2 = self ? matrix1 : (_matrix || path._matrix)._orNullIfIdentity();
return self || this.getBounds(matrix1).intersects(path.getBounds(matrix2), 1e-12)
? Curve.getIntersections(this.getCurves(), !self && path.getCurves(), include, matrix1, matrix2, _returnFirst)
: [];
};
PathItem.prototype.getNearestLocation = function () {
const point = Point.read(arguments);
const curves = this.getCurves();
let minDist = Infinity;
let minLoc = null;
for (let i = 0, l = curves.length; i < l; i++) {
const loc = curves[i].getNearestLocation(point);
if (loc._distance < minDist) {
minDist = loc._distance;
minLoc = loc;
}
}
return minLoc;
};
PathItem.prototype.getNearestPoint = function () {
const loc = this.getNearestLocation.apply(this, arguments);
return loc ? loc.getPoint() : loc;
};
PathItem.prototype.compare = function (path) {
if (!path) return false;
const paths1 = this._children || [this];
const paths2 = path._children ? path._children.slice() : [path];
const length1 = paths1.length;
const length2 = paths2.length;
const boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON);
let matched = Array(length2).fill(false);
let matchCount = 0;
let allMatched = true;
for (let i1 = length1 - 1; i1 >= 0 && allMatched; i1--) {
const path1 = paths1[i1];
const pathBoundsOverlaps = boundsOverlaps[i1];
let pathMatched = false;
if (pathBoundsOverlaps) {
for (let i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !pathMatched; i2--) {
const pathIndex = pathBoundsOverlaps[i2];
if (path1.compare(paths2[pathIndex])) {
if (!matched[pathIndex]) {
matched[pathIndex] = true;
matchCount++;
}
pathMatched = true;
}
}
}
if (!pathMatched) allMatched = false;
}
return allMatched && matchCount === length2;
};
PathItem.prototype._getWinding = function (point, dir, closed) {
return PathItem.getWinding(point, this.getCurves(), dir, closed);
};
PathItem.prototype.unite = function (path) {
return PathItem.traceBoolean(this, path, OPERATIONS.unite);
};
PathItem.prototype.intersect = function (path) {
return PathItem.traceBoolean(this, path, OPERATIONS.intersect);
};
PathItem.prototype.subtract = function (path) {
return PathItem.traceBoolean(this, path, OPERATIONS.subtract);
};
PathItem.prototype.exclude = function (path) {
return PathItem.traceBoolean(this, path, OPERATIONS.exclude);
};
PathItem.prototype.divide = function (argument) {
// Original version with only two paths
if (!Array.isArray(argument)) {
const path = argument;
return PathItem.createResult([
this.exclude(path),
this.intersect(path)
], true, this, path);
}
// Version for multiple paths
const paths = argument;
function calculateUniversumBounds(paths) {
const pathBounds = paths.map(function (path) { return path.getBounds(); });
const left = Math.min.apply(null, pathBounds.map(function (bounds) { return bounds.getLeft(); }));
const top = Math.min.apply(null, pathBounds.map(function (bounds) { return bounds.getTop(); }));
const right = Math.max.apply(null, pathBounds.map(function (bounds) { return bounds.getLeft() + bounds.getWidth(); }));
const bottom = Math.max.apply(null, pathBounds.map(function (bounds) { return bounds.getTop() + bounds.getHeight(); }));
return [left, top, right, bottom];
}
const bounds = calculateUniversumBounds(paths);
const delta = 1; // Expand universum so that it accurately includes all paths
const universum = new Path.Rectangle(bounds[0] - delta, bounds[1] - delta, bounds[2] + delta, bounds[3] + delta);
const areas = [];
for (let option = 1, totalCombinations = Math.pow(2, paths.length); option < totalCombinations; option++) {
let result = universum;
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
const isBitSet = (option & (1 << i)) !== 0;
if (isBitSet) {
result = result.intersect(path);
} else {
result = result.intersect(universum.subtract(path));
}
}
if (!result.isEmpty()) {
result._option = option;
areas.push(result);
}
}
function splitCompoundPath(compoundPath) {
const split = [];
const paths = compoundPath.getChildren().slice();
const visited = new Set();
paths.forEach(function (path) {
if (visited.has(path))
return;
const group = findConnectedGroup(path);
if (group.length === 1) {
split.push(group[0]);
} else {
let compound = new CompoundPath();
compound.addChildren(group);
compound = compound.reduce();
compound.copyAttributes(group[0]);
split.push(compound);
}
});
function findConnectedGroup(startPath) {
const queue = [startPath];
const group = [];
while (queue.length) {
const path = queue.pop();
if (visited.has(path)) continue;
visited.add(path);
group.push(path);
paths.forEach(function (otherPath) {
if (!visited.has(otherPath) && hasIntersection(path, otherPath)) {
queue.push(otherPath);
}
});
}
return group;
}
function hasIntersection(p1, p2) {
return !p1.intersect(p2).isEmpty();
};
return split;
}
const fragments = areas.flatMap(function (area) {
if (area instanceof Path) { return [area]; }
if (area instanceof CompoundPath) { return splitCompoundPath(area); }
});
return fragments;
};
PathItem.prototype.resolveCrossings = function () {
let paths = this._children || [this];
let hasOverlaps = false;
let hasCrossings = false;
function hasOverlap(seg, path) {
const inter = seg && seg._intersection;
return inter && inter._overlap && inter._path === path;
}
let intersections = this.getIntersections(null, function (inter) {
return (inter.hasOverlap() && (hasOverlaps = true)) ||
(inter.isCrossing() && (hasCrossings = true));
});
const clearCurves = hasOverlaps && hasCrossings ? [] : null;
intersections = CurveLocation.expand(intersections);
if (hasOverlaps) {
const overlaps = PathItem.divideLocations(intersections, function (inter) {
return inter.hasOverlap();
}, clearCurves);
for (let i = overlaps.length - 1; i >= 0; i--) {
const overlap = overlaps[i];
const path = overlap._path;
const seg = overlap._segment;
const prev = seg.getPrevious();
const next = seg.getNext();
if (hasOverlap(prev, path) && hasOverlap(next, path)) {
seg.remove();
prev._handleOut._set(0, 0);
next._handleIn._set(0, 0);
if (prev !== seg && !prev.getCurve().hasLength()) {
next._handleIn.set(prev._handleIn);
prev.remove();
}
}
}
}
if (hasCrossings) {
PathItem.divideLocations(intersections, hasOverlaps && function (inter) {
const curve1 = inter.getCurve();
const seg1 = inter.getSegment();
const other = inter._intersection;
const curve2 = other._curve;
const seg2 = other._segment;
if (curve1 && curve2 && curve1._path && curve2._path) { return true; }
if (seg1) { seg1._intersection = null; }
if (seg2) { seg2._intersection = null; }
}, clearCurves);
if (clearCurves) PathItem.clearCurveHandles(clearCurves);
paths = PathItem.tracePaths(Base.each(paths, function (path) {
this.push.apply(this, path._segments);
}, []));
}
let item;
const length = paths.length;
if (length > 1 && this._children) {
if (paths !== this._children) { this.setChildren(paths); }
item = this;
} else if (length === 1 && !this._children) {
if (paths[0] !== this) { this.setSegments(paths[0].removeSegments()); }
item = this;
}
if (!item) {
item = new CompoundPath({ insert: false });
item.addChildren(paths);
item = item.reduce();
item.copyAttributes(this);
this.replaceWith(item);
}
return item;
};
PathItem.prototype.reorient = function (nonZero, clockwise) {
if (this._children && this._children.length) {
const reorientedChildren = PathItem.reorientPaths(
this.removeChildren(),
function (w) {
return !!(nonZero ? w : w & 1);
},
clockwise
);
this.setChildren(reorientedChildren);
} else if (clockwise !== undefined) {
this.setClockwise(clockwise);
}
return this;
};
PathItem.prototype.getInteriorPoint = function () {
const bounds = this.getBounds();
const point = bounds.getCenter(true);
if (this.contains(point)) return point;
const curves = this.getCurves();
const y = point.y;
const intercepts = [];
const roots = [];
curves.forEach(function (curve) {
const v = curve.getValues();
const [o0, o1, o2, o3] = [v[1], v[3], v[5], v[7]];
if (y >= Math.min(o0, o1, o2, o3) && y <= Math.max(o0, o1, o2, o3)) {
const monoCurves = Curve.getMonoCurves(v);
monoCurves.forEach(function (mv) {
const mo0 = mv[1];
const mo3 = mv[7];
if (mo0 !== mo3 && (y >= Math.min(mo0, mo3) && y <= Math.max(mo0, mo3))) {
const x = y === mo0
? mv[0]
: y === mo3
? mv[6]
: Curve.solveCubic(mv, 1, y, roots, 0, 1) === 1
? Curve.getPoint(mv, roots[0]).x
: (mv[0] + mv[6]) / 2;
intercepts.push(x);
}
});
}
});
if (intercepts.length > 1) {
intercepts.sort(function (a, b) { return a - b; });
point.x = (intercepts[0] + intercepts[1]) / 2;
}
return point;
};
PathItem.getPaths = function (path) {
return path._children || [path];
};
PathItem.preparePath = function (path) {
let res = path
.clone(false)
.reduce({ simplify: true })
.transform(null, true, true);
const paths = PathItem.getPaths(res);
for (let i = 0, l = paths.length; i < l; i++) {
const path = paths[i];
if (!path._closed && !path.isEmpty()) {
path.closePath(Numerical.EPSILON);
path.getFirstSegment().setHandleIn(0, 0);
path.getLastSegment().setHandleOut(0, 0);
}
}
return res.resolveCrossings().reorient(true, true);
};
PathItem.createResult = function (paths, simplify, path1, path2) {
let result = new CompoundPath({ insert: false });
result.addChildren(paths, true);
result = result.reduce({ simplify: simplify });
result.copyAttributes(path1, true);
return result;
};
PathItem.filterIntersection = function (inter) {
return inter.hasOverlap() || inter.isCrossing();
};
PathItem.traceBoolean = function (path1, path2, operation) {
const operators = {
'1': { '1': true, '2': true, 'unite': true },
'2': { '2': true, 'intersect': true },
'3': { '1': true, 'subtract': true },
'4': { '1': true, '-1': true, 'exclude': true },
};
const operator = operators[operation];
const _path1 = PathItem.preparePath(path1);
const _path2 = path2 && path1 !== path2
? PathItem.preparePath(path2)
: null;
if (_path2 && (operator['subtract'] || operator['exclude']) ^ (_path2.isClockwise() ^ _path1.isClockwise())) {
_path2.reverse();
}
const crossings = PathItem.divideLocations(
CurveLocation.expand(_path1.getIntersections(_path2, PathItem.filterIntersection))
);
const paths1 = PathItem.getPaths(_path1);
const paths2 = _path2 ? PathItem.getPaths(_path2) : null;
const segments = [];
const curves = [];
function collectPaths(paths) {
for (let i = 0, l = paths.length; i < l; i++) {
const path = paths[i];
segments.push.apply(segments, path._segments);
curves.push.apply(curves, path.getCurves());
path._overlapsOnly = true;
}
}
function getCurves(indices) {
const list = [];
for (let i = 0, l = indices && indices.length; i < l; i++) {
list.push(curves[indices[i]]);
}
return list;
}
let paths;
if (crossings.length) {
collectPaths(paths1);
if (paths2) collectPaths(paths2);
const curvesValues = new Array(curves.length);
for (let i = 0, l = curves.length; i < l; i++) {
curvesValues[i] = curves[i].getValues();
}
const curveCollisions = CollisionDetection.findCurveBoundsCollisions(curvesValues, curvesValues, 0, true);
const curveCollisionsMap = {};
for (let i = 0; i < curves.length; i++) {
const curve = curves[i];
const id = curve._path._id;
const map = curveCollisionsMap[id] = curveCollisionsMap[id] || {};
map[curve.getIndex()] = {
hor: getCurves(curveCollisions[i].hor),
ver: getCurves(curveCollisions[i].ver)
};
}
for (let i = 0, l = crossings.length; i < l; i++) {
PathItem.propagateWinding(crossings[i]._segment, _path1, _path2, curveCollisionsMap, operator);
}
for (let i = 0, l = segments.length; i < l; i++) {
const segment = segments[i];
const inter = segment._intersection;
if (!segment._winding) {
PathItem.propagateWinding(segment, _path1, _path2, curveCollisionsMap, operator);
}
if (!(inter && inter._overlap)) {
segment._path._overlapsOnly = false;
}
}
paths = PathItem.tracePaths(segments, operator);
} else {
paths = PathItem.reorientPaths(
paths2 ? paths1.concat(paths2) : paths1.slice(),
function (w) {
return !!operator[w];
}
);
}
return PathItem.createResult(paths, true, path1, path2);
};
PathItem.linkIntersections = function (from, to) {
let prev = from;
while (prev) {
if (prev === to)
return;
prev = prev._previous;
}
while (from._next && from._next !== to)
from = from._next;
if (!from._next) {
while (to._previous)
to = to._previous;
from._next = to;
to._previous = from;
}
};
PathItem.clearCurveHandles = function (curves) {
for (let i = curves.length - 1; i >= 0; i--)
curves[i].clearHandles();
};
PathItem.reorientPaths = function (paths, isInside, clockwise) {
const length = paths ? paths.length : 0;
if (!length) { return paths; }
const lookup = Base.each(paths, function (path, i) {
this[path._id] = {
container: null,
winding: path.isClockwise() ? 1 : -1,
index: i
};
}, {});
// Сортировка путей по площади
const sorted = paths.slice().sort(function (a, b) {
return Math.abs(b.getArea()) - Math.abs(a.getArea());
});
const first = sorted[0];
if (clockwise == null) { clockwise = first.isClockwise(); }
const collisions = CollisionDetection.findItemBoundsCollisions(sorted, null, Numerical.GEOMETRIC_EPSILON);
for (let i = 0; i < length; i++) {
const path1 = sorted[i];
const entry1 = lookup[path1._id];
let containerWinding = 0;
const indices = collisions[i];
if (indices) {
let point = null;
for (let j = indices.length - 1; j >= 0; j--) {
if (indices[j] < i) {
point = point || path1.getInteriorPoint();
const path2 = sorted[indices[j]];
if (path2.contains(point)) {
const entry2 = lookup[path2._id];
containerWinding = entry2.winding;
entry1.winding += containerWinding;
entry1.container = entry2['exclude'] ? entry2.container : path2;
break;
}
}
}
}
if (isInside(entry1.winding) === isInside(containerWinding)) {
entry1['exclude'] = true;
paths[entry1.index] = null;
} else {
path1.setClockwise(entry1.container ? !entry1.container.isClockwise() : clockwise);
}
}
return paths;
};
PathItem.divideLocations = function (locations, include, clearLater) {
const results = include && [];
const tMin = 1e-8;
const tMax = 1 - tMin;
const clearCurves = clearLater || [];
const clearLookup = clearLater && {};
let clearHandles = false;
let renormalizeLocs;
let prevCurve;
let prevTime;
function getId(curve) {
return curve._path._id + '.' + curve._segment1._index;
}
for (let i = (clearLater && clearLater.length) - 1; i >= 0; i--) {
const curve = clearLater[i];
if (curve._path) { clearLookup[getId(curve)] = true; }
}
for (let i = locations.length - 1; i >= 0; i--) {
const loc = locations[i];
const origTime = loc._time;
let time = loc._time;
if (loc._curve) {
if (loc._curve !== prevCurve) {
clearHandles = !loc._curve.hasHandles() || clearLookup && clearLookup[getId(loc._curve)];
renormalizeLocs = [];
prevTime = null;
prevCurve = loc._curve;
} else if (prevTime >= tMin) {
time /= prevTime;
}
}
const exclude = include && !include(loc);
if (exclude) {
if (renormalizeLocs) { renormalizeLocs.push(loc); }
continue;
} else if (include) {
results.unshift(loc);
}
prevTime = origTime;
let segment;
if (time < tMin) {
segment = loc._curve._segment1;
} else if (time > tMax) {
segment = loc._curve._segment2;
} else {
const newCurve = loc._curve.divideAtTime(time, true);
if (clearHandles)
clearCurves.push(loc._curve, newCurve);
segment = newCurve._segment1;
for (let j = renormalizeLocs.length - 1; j >= 0; j--) {
const l = renormalizeLocs[j];
l._time = (l._time - time) / (1 - time);
}
}
loc._setSegment(segment);
const inter = segment._intersection;
const dest = loc._intersection;
if (inter) {
PathItem.linkIntersections(inter, dest);
let other = inter;
while (other) {
PathItem.linkIntersections(other._intersection, inter);
other = other._next;
}
} else {
segment._intersection = dest;
}
}
if (!clearLater) { PathItem.clearCurveHandles(clearCurves); }
return results || locations;
};
PathItem.getWinding = function (point, curves, dir, closed, dontFlip) {
const curvesList = Array.isArray(curves) ? curves : (dir ? curves.hor : curves.ver);
const ia = dir ? 1 : 0;
const io = ia ^ 1;
const pv = [point.x, point.y];
const pa = pv[ia];
const po = pv[io];
const windingEpsilon = 1e-9;
const qualityEpsilon = 1e-6;
const paL = pa - windingEpsilon;
const paR = pa + windingEpsilon;
let windingL = 0, windingR = 0, pathWindingL = 0, pathWindingR = 0;
let onPath = false, onAnyPath = false, quality = 1;
let roots = [], vPrev, vClose;
function addWinding(v) {
const o0 = v[io + 0];
const o3 = v[io + 6];
if (po < Math.min(o0, o3) || po > Math.max(o0, o3)) { return; }
const a0 = v[ia + 0];
const a1 = v[ia + 2];
const a2 = v[ia + 4];
const a3 = v[ia + 6];
if (o0 === o3) {
if ((a0 < paR && a3 > paL) || (a3 < paR && a0 > paL)) { onPath = true; }
return;
}
const t = po === o0 ? 0 : (po === o3 ? 1 :
(paL > Math.max(a0, a1, a2, a3) || paR < Math.min(a0, a1, a2, a3) ? 1 :
Curve.solveCubic(v, io, po, roots, 0, 1) > 0 ? roots[0] : 1));
const a = t === 0 ? a0 : (t === 1 ? a3 : (dir ? Curve.getPoint(v, t).y : Curve.getPoint(v, t).x));
const winding = o0 > o3 ? 1 : -1;
const windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1;
const a3Prev = vPrev[ia + 6];
if (po !== o0) {
if (a < paL) {
pathWindingL += winding;
} else if (a > paR) {
pathWindingR += winding;
} else {
onPath = true;
}
if (a > pa - qualityEpsilon && a < pa + qualityEpsilon) {
quality /= 2;
}
} else {
if (winding !== windingPrev) {
if (a0 < paL) {
pathWindingL += winding;
} else if (a0 > paR) {
pathWindingR += winding;
}
} else if (a0 != a3Prev) {
if (a3Prev < paR && a > paR) {
pathWindingR += winding;
onPath = true;
} else if (a3Prev > paL && a < paL) {
pathWindingL += winding;
onPath = true;
}
}
quality /= 4;
}
vPrev = v;
return !dontFlip && a > paL && a < paR
&& (dir ? (Curve.getTangent(v, t).x === 0) : (Curve.getTangent(v, t).y === 0))
&& PathItem.getWinding(point, curves, !dir, closed, true);
}
function handleCurve(v) {
const o0 = v[io + 0];
const o1 = v[io + 2];
const o2 = v[io + 4];
const o3 = v[io + 6];
if (po <= Math.max(o0, o1, o2, o3) && po >= Math.min(o0, o1, o2, o3)) {
const a0 = v[ia + 0];
const a1 = v[ia + 2];
const a2 = v[ia + 4];
const a3 = v[ia + 6];
const monoCurves = (paL > Math.max(a0, a1, a2, a3) || paR < Math.min(a0, a1, a2, a3))
? [v]
: Curve.getMonoCurves(v, dir);
let res;
for (let i = 0, l = monoCurves.length; i < l; i++) {
if (res = addWinding(monoCurves[i])) {
return res;
}
}
}
}
for (let i = 0, l = curvesList.length; i < l; i++) {
const curve = curvesList[i];
const path = curve._path;
const v = curve.getValues();
if (!i || curvesList[i - 1]._path !== path) {
vPrev = null;
if (!path._closed) {
vClose = Curve.getValues(
path.getLastCurve().getSegment2(),
curve.getSegment1(),
null, !closed
);
if (vClose[io] !== vClose[io + 6]) {
vPrev = vClose;
}
}
if (!vPrev) {
vPrev = v;
let prev = path.getLastCurve();
while (prev && prev !== curve) {
const v2 = prev.getValues();
if (v2[io] !== v2[io + 6]) {
vPrev = v2;
break;
}
prev = prev.getPrevious();
}
}
}
let res;
if (res = handleCurve(v)) { return res; }
if (i + 1 === l || curvesList[i + 1]._path !== path) {
if (vClose && (res = handleCurve(vClose))) {
return res;
}
if (onPath && !pathWindingL && !pathWindingR) {
pathWindingL = pathWindingR = path.isClockwise(closed) ^ dir ? 1 : -1;
}
windingL += pathWindingL;
windingR += pathWindingR;
pathWindingL = pathWindingR = 0;
if (onPath) {
onAnyPath = true;
onPath = false;
}
vClose = null;
}
}
windingL = Math.abs(windingL);
windingR = Math.abs(windingR);
return {
winding: Math.max(windingL, windingR),
windingL: windingL,
windingR: windingR,
quality: quality,
onPath: onAnyPath
};
};
PathItem.propagateWinding = function (segment, path1, path2, curveCollisionsMap, operator) {
const chain = [];
const start = segment;
let totalLength = 0;
let winding = { winding: 0, quality: -1 };
do {
const curve = segment.getCurve();
if (curve) {
const length = curve.getLength();
chain.push({ segment, curve, length });
totalLength += length
}
segment = segment.getNext();
} while (segment && !segment._intersection && segment !== start);
const offsets = [0.5, 0.25, 0.75];
const tMin = 1e-3;
const tMax = 1 - tMin;
for (let i = 0; i < offsets.length && winding.quality < 0.5; i++) {
let lengthAtOffset = totalLength * offsets[i];
for (let j = 0, l = chain.length; j < l; j++) {
const entry = chain[j];
const curveLength = entry.length;
if (lengthAtOffset <= curveLength) {
const curve = entry.curve;
const path = curve._path;
const parent = path._parent;
const operand = parent instanceof CompoundPath ? parent : path;
const t = Numerical.clamp(curve.getTimeAt(lengthAtOffset), tMin, tMax);
const pt = curve.getPointAtTime(t);
const dir = Math.abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2;
let wind = null;
if (operator['subtract'] && path2) {
const otherPath = operand === path1 ? path2 : path1;
const pathWinding = otherPath._getWinding(pt, dir, true);
if (operand === path1 && pathWinding.winding ||
operand === path2 && !pathWinding.winding) {
if (pathWinding.quality < 1) {
continue;
} else {
wind = { winding: 0, quality: 1 };
}
}
}
wind = wind || PathItem.getWinding(pt, curveCollisionsMap[path._id][curve.getIndex()], dir, true);
if (wind.quality > winding.quality) {
winding = wind;
}
break;
}
lengthAtOffset -= curveLength;
}
}
for (let j = chain.length - 1; j >= 0; j--) {
chain[j].segment._winding = winding;
}
};
PathItem.tracePaths = function (segments, operator) {
const paths = [];
let starts;
function isValid(seg) {
let winding;
return !!(seg && !seg._visited && (!operator
|| operator[(winding = seg._winding || {}).winding]
&& !(operator['unite'] && winding.winding === 2
&& winding.windingL && winding.windingR)));
}
function isStart(seg) {
if (seg) {
for (let i = 0, l = starts.length; i < l; i++) {
if (seg === starts[i]) { return true; }
}
}
return false;
}
function visitPath(path) {
for (let i = 0, l = path._segments.length; i < l; i++) {
path._segments[i]._visited = true;
}
}
function getCrossingSegments(segment, collectStarts) {
let inter = segment._intersection;
const start = inter;
const crossings = [];
if (collectStarts)
starts = [segment];
function collect(inter, end) {
while (inter && inter !== end) {
const other = inter._segment;
const path = other && other._path;
if (path) {
const next = other.getNext() || path.getFirstSegment();
const nextInter = next._intersection;
const isCrossingValid = isStart(other) || isStart(next) || next && (isValid(other) && (isValid(next) || nextInter && isValid(nextInter._segment)));
if (other !== segment && isCrossingValid) { crossings.push(other); }
if (collectStarts) { starts.push(other); }
}
inter = inter._next;
}
}
if (inter) {
collect(inter);
while (inter && inter._previous)
inter = inter._previous;
collect(inter, start);
}
return crossings;
}
segments.sort(function (seg1, seg2) {
const inter1 = seg1._intersection, inter2 = seg2._intersection;
const over1 = inter1 ? inter1._overlap : false;
const over2 = inter2 ? inter2._overlap : false;
if (over1 ^ over2) { return over1 ? 1 : -1; }
if (!inter1 ^ !inter2) { return inter1 ? 1 : -1; }
if (seg1._path !== seg2._path) { return seg1._path._id - seg2._path._id; }
return seg1._index - seg2._index;
});
for (let i = 0; i < segments.length; i++) {
let segment = segments[i];
let isValidSegment = isValid(segment);
if (isValidSegment && segment._path._overlapsOnly) {
const path1 = segment._path;
const path2 = segment._intersection._segment._path;
if (path1.compare(path2)) {
if (path1.getArea()) {
paths.push(path1.clone(false));
}
visitPath(path1);
visitPath(path2);
isValidSegment = false;
}
}
let visitedSegments;
let branches = [];
let currentPath = null;
let isFinished = false;
let isClosed = true;
let branch, handleIn;
while (isValidSegment) {
const isFirstSegment = !currentPath;
const crossings = getCrossingSegments(segment, isFirstSegment);
const otherSegment = crossings.shift();
isFinished = !isFirstSegment && (isStart(segment) || isStart(otherSegment));
const isCrossing = !isFinished && otherSegment;
if (isFirstSegment) {
currentPath = new Path({ insert: false });
branch = null;
}
if (isFinished) {
if (segment.isFirst() || segment.isLast()) { isClosed = segment._path._closed; }
segment._visited = true;
break;
}
if (isCrossing && branch) {
branches.push(branch);
branch = null;
}
if (!branch) {
if (isCrossing) { crossings.push(segment); }
branch = {
start: currentPath._segments.length,
crossings: crossings,
visited: visitedSegments = [],
handleIn: handleIn
};
}
if (isCrossing) { segment = otherSegment; }
if (!isValid(segment)) {
currentPath.removeSegments(branch.start);
visitedSegments.forEach(function (segment) { segment._visited = false; })
visitedSegments.length = 0;
do {
segment = branch && branch.crossings.shift();
if (!segment || !segment._path) {
segment = null;
branch = branches.pop();
if (branch) {
visitedSegments = branch.visited;
handleIn = branch.handleIn;
}
}
} while (branch && !isValid(segment));
if (!segment) {
break;
}
}
const nextSegment = segment.getNext();
currentPath.add(new Segment(segment._point, handleIn, nextSegment && segment._handleOut));
segment._visited = true;
visitedSegments.push(segment);
segment = nextSegment || segment._path.getFirstSegment();
handleIn = nextSegment && nextSegment._handleIn;
}
if (isFinished && isClosed) {
currentPath.getFirstSegment().setHandleIn(handleIn);
currentPath.setClosed(isClosed);
}
if (isFinished && currentPath.getArea() !== 0) {
paths.push(currentPath);
}
}
return paths;
};
const Path = function (arg) {
this._closed = false;
this._segments = [];
this._version = 0;
const isArrayArg = Array.isArray(arg);
const isObjectElement = isArrayArg && typeof arg[0] === 'object';
const isValidObject = arg && (arg.size === undefined && (arg.x !== undefined || arg.point !== undefined));
const segments = isArrayArg
? isObjectElement ? arg : arguments
: isValidObject ? arguments : null;
segments && segments.length > 0
? this.setSegments(segments)
: this._curves = undefined;
this._initialize(!segments && arg);
};
InitClassWithStatics(Path, PathItem);
Path.prototype._equals = function (item) {
return this._closed === item._closed
&& Base.equals(this._segments, item._segments);
};
Path.prototype.copyContent = function (source) {
this.setSegments(source._segments);
this._closed = source._closed;
};
Path.prototype._changed = function _changed(flags) {
Item.prototype._changed.call(this, flags);
if (flags & 8) {
this._length = this._area = undefined;
if (flags & 32) {
this._version++;
} else if (this._curves) {
for (let i = 0, l = this._curves.length; i < l; i++)
this._curves[i]._changed();
}
} else if (flags & 64) {
this._bounds = undefined;
}
};
Path.prototype.getSegments = function () {
return this._segments;
};
Path.prototype.setSegments = function (segments) {
this._segments.length = 0;
this._curves = undefined;
let length = segments && segments.length;
if (!length) { return; }
const last = segments[length - 1];
if (typeof last === 'boolean') {
this.setClosed(last);
length--;
}
this._add(Segment.readList(segments, 0, {}, length));
};
Path.prototype.getFirstSegment = function () {
return this._segments[0];
};
Path.prototype.getLastSegment = function () {
return this._segments[this._segments.length - 1];
};
Path.prototype.getCurves = function () {
let curves = this._curves;
let segments = this._segments;
if (!curves) {
const length = this._countCurves();
curves = this._curves = new Array(length);
for (let i = 0; i < length; i++)
curves[i] = new Curve(this, segments[i],
segments[i + 1] || segments[0]);
}
return curves;
};
Path.prototype.getFirstCurve = function () {
return this.getCurves()[0];
};
Path.prototype.getLastCurve = function () {
const curves = this.getCurves();
return curves[curves.length - 1];
};
Path.prototype.isClosed = function () {
return this._closed;
};
Path.prototype.setClosed = function (closed) {
if (this._closed != (closed = !!closed)) {
this._closed = closed;
if (this._curves) {
const length = this._curves.length = this._countCurves();
if (closed)
this._curves[length - 1] = new Curve(this,
this._segments[length - 1], this._segments[0]);
}
this._changed(41);
}
};
Path.prototype.isEmpty = function () {
return !this._segments.length;
};
Path.prototype._transformContent = function (matrix) {
const segments = this._segments;
const coords = new Array(6);
for (let i = 0, l = segments.length; i < l; i++)
segments[i]._transformCoordinates(matrix, coords, true);
return true;
};
Path.prototype._add = function (segs, index) {
const segments = this._segments;
const curves = this._curves;
const amount = segs.length;
const append = index == null;
index = append ? segments.length : index;
for (let i = 0; i < amount; i++) {
let segment = segs[i];
if (segment._path) {
segment = segs[i] = segment.clone();
}
segment._path = this;
segment._index = index + i;
}
if (append) {
segments.push.apply(segments, segs);
} else {
segments.splice.apply(segments, [index, 0].concat(segs));
for (let i = index + amount, l = segments.length; i < l; i++) {
segments[i]._index = i;
}
}
if (curves) {
const total = this._countCurves();
const start = index > 0 && index + amount - 1 === total ? index - 1 : index;
let insert = start;
const end = Math.min(start + amount, total);
if (segs._curves) {
curves.splice.apply(curves, [start, 0].concat(segs._curves));
insert += segs._curves.length;
}
for (let i = insert; i < end; i++) {
curves.splice(i, 0, new Curve(this, null, null));
}
this._adjustCurves(start, end);
}
this._changed(41);
return segs;
};
Path.prototype._adjustCurves = function (start, end) {
const segments = this._segments;
const curves = this._curves;
let curve;
for (let i = start; i < end; i++) {
curve = curves[i];
curve._path = this;
curve._segment1 = segments[i];
curve._segment2 = segments[i + 1] || segments[0];
curve._changed();
}
if (curve = curves[this._closed && !start ? segments.length - 1 : start - 1]) {
curve._segment2 = segments[start] || segments[0];
curve._changed();
}
if (curve = curves[end]) {
curve._segment1 = segments[end];
curve._changed();
}
};
Path.prototype._countCurves = function () {
const length = this._segments.length;
return !this._closed && length > 0 ? length - 1 : length;
};
Path.prototype.add = function (segment1) {
return arguments.length > 1 && typeof segment1 !== 'number'
? this._add(Segment.readList(arguments))
: this._add([Segment.read(arguments)])[0];
};
Path.prototype.insert = function (index, segment1) {
return arguments.length > 2 && typeof segment1 !== 'number'
? this._add(Segment.readList(arguments, 1), index)
: this._add([Segment.read(arguments, 1)], index)[0];
};
Path.prototype.addSegment = function () {
return this._add([Segment.read(arguments)])[0];
};
Path.prototype.removeSegment = function (index) {
return this.removeSegments(index, index + 1)[0] || null;
};
Path.prototype.removeSegments = function (start, end, _includeCurves) {
if (start == null) start = 0;
if (end == null) end = this._segments.length;
const segments = this._segments;
const curves = this._curves;
const count = segments.length;
const removed = segments.splice(start, end - start);
const amount = removed.length;
if (!amount) return removed;
for (let i = 0; i < amount; i++) {
removed[i]._index = removed[i]._path = null;
}
for (let i = start, l = segments.length; i < l; i++) {
segments[i]._index = i;
}
if (curves) {
const index = (start > 0 && end === count + (this._closed ? 1 : 0)) ? start - 1 : start;
const removedCurves = curves.splice(index, amount);
for (let i = removedCurves.length - 1; i >= 0; i--) {
// Есть баг с файлом "shapesMerge - remove curves _path bug" (загрузил к себе в личные документы на nct)
// removedCurves[i]._path = null;
}
if (_includeCurves) {
removed._curves = removedCurves.slice(1);
}
this._adjustCurves(index, index);
}
this._changed(41);
return removed;
};
Path.prototype.hasHandles = function () {
for (let i = 0, l = this._segments.length; i < l; i++) {
if (this._segments[i].hasHandles()) { return true; }
}
return false;
};
Path.prototype.clearHandles = function () {
for (let i = 0, l = this._segments.length; i < l; i++)
this._segments[i].clearHandles();
};
Path.prototype.getLength = function () {
if (this._length == null) {
const curves = this.getCurves();
let length = 0;
for (let i = 0, l = curves.length; i < l; i++)
length += curves[i].getLength();
this._length = length;
}
return this._length;
};
Path.prototype.getArea = function () {
if (this._area != null) { return this._area; }
let area = 0;
const length = this._segments.length;
for (let i = 0; i < length; i++) {
const nextIndex = (i + 1) % length;
const isLastSegment = (i === length - 1);
area += Curve.getArea(Curve.getValues(
this._segments[i],
this._segments[isLastSegment ? 0 : nextIndex],
null, isLastSegment && !this._closed
));
}
this._area = area;
return area;
};
Path.prototype.join = function (path, tolerance) {
const epsilon = tolerance || 0;
if (path && path !== this) {
let last1 = this.getLastSegment();
let last2 = path.getLastSegment();
if (!last2) { return this; }
if (last1 && last1._point.isClose(last2._point, epsilon)) { path.reverse(); }
const first2 = path.getFirstSegment();
if (last1 && last1._point.isClose(first2._point, epsilon)) {
last1.setHandleOut(first2._handleOut);
this._add(path._segments.slice(1));
} else {
const first1 = this.getFirstSegment();
if (first1 && first1._point.isClose(first2._point, epsilon)) { path.reverse(); }
last2 = path.getLastSegment();
if (first1 && first1._point.isClose(last2._point, epsilon)) {
first1.setHandleIn(last2._handleIn);
this._add(path._segments.slice(0, path._segments.length - 1), 0);
} else {
this._add(path._segments.slice());
}
}
if (path._closed) { this._add([path._segments[0]]); }
path.remove();
}
const first = this.getFirstSegment();
const last = this.getLastSegment();
if (first !== last && first._point.isClose(last._point, epsilon)) {
first.setHandleIn(last._handleIn);
last.remove();
this.setClosed(true);
}
return this;
};
Path.prototype.reduce = function (options) {
const curves = this.getCurves();
const simplify = options && options.simplify;
const tolerance = simplify ? 1e-7 : 0;
for (let i = curves.length - 1; i >= 0; i--) {
const curve = curves[i];
if (!curve.hasHandles() && (!curve.hasLength(tolerance) || simplify && curve.isCollinear(curve.getNext())))
curve.remove();
}
return this;
};
Path.prototype.reverse = function () {
this._segments.reverse();
for (let i = 0, l = this._segments.length; i < l; i++) {
const segment = this._segments[i];
const handleIn = segment._handleIn;
segment._handleIn = segment._handleOut;
segment._handleOut = handleIn;
segment._index = i;
}
this._curves = null;
this._changed(9);
};
Path.prototype.compare = function (path) {
if (!path || path instanceof CompoundPath)
return PathItem.prototype.compare.call(this, path);
const curves1 = this.getCurves();
const curves2 = path.getCurves();
if (!curves1.length || !curves2.length) {
return curves1.length == curves2.length;
}
let v1 = curves1[0].getValues();
const values2 = [];
let pos1 = 0, pos2;
let end1 = 0, end2;
for (let i = 0; i < curves2.length; i++) {
const v2 = curves2[i].getValues();
values2.push(v2);
const overlaps = Curve.getOverlaps(v1, v2);
if (overlaps) {
pos2 = !i && overlaps[0][0] > 0 ? curves2.length - 1 : i;
end2 = overlaps[0][1];
break;
}
}
let v2 = values2[pos2];
let start2;
while (v1 && v2) {
const overlaps = Curve.getOverlaps(v1, v2);
if (overlaps) {
const t1 = overlaps[0][0];
if (Math.abs(t1 - end1) < 1e-8) {
end1 = overlaps[1][0];
if (end1 === 1) {
v1 = ++pos1 < curves1.length ? curves1[pos1].getValues() : null;
end1 = 0;
}
const t2 = overlaps[0][1];
if (Math.abs(t2 - end2) < 1e-8) {
if (!start2)
start2 = [pos2, t2];
end2 = overlaps[1][1];
if (end2 === 1) {
if (++pos2 >= curves2.length)
pos2 = 0;
v2 = values2[pos2] || curves2[pos2].getValues();
end2 = 0;
}
if (!v1) {
return start2[0] === pos2 && start2[1] === end2;
}
continue;
}
}
}
break;
}
return false;
};
Path.prototype.getLocationAt = function (offset) {
if (typeof offset === 'number') {
const curves = this.getCurves();
let length = 0;
for (let i = 0, l = curves.length; i < l; i++) {
const start = length;
const curve = curves[i];
length += curve.getLength();
if (length > offset) {
return curve.getLocationAt(offset - start);
}
}
if (curves.length > 0 && offset <= this.getLength()) {
return new CurveLocation(curves[curves.length - 1], 1);
}
} else if (offset && offset.getPath && offset.getPath() === this) {
return offset;
}
return null;
};
Path.prototype.getPointAt = function (offset) {
const loc = this.getLocationAt(offset);
return loc && loc.getPoint();
};
Path.prototype.getTangentAt = function (offset) {
const loc = this.getLocationAt(offset);
return loc && loc.getTangent();
};
Path.prototype.getNormalAt = function (offset) {
const loc = this.getLocationAt(offset);
return loc && loc.getNormal();
};
Path.prototype.getCurvatureAt = function (offset) {
const loc = this.getLocationAt(offset);
return loc && loc.getCurvature();
};
Path.prototype.moveTo = function () {
if (this._segments.length === 1)
this.removeSegment(0);
if (!this._segments.length)
this._add([new Segment(Point.read(arguments))]);
};
Path.prototype.lineTo = function () {
this._add([new Segment(Point.read(arguments))]);
};
Path.prototype.cubicCurveTo = function () {
const handle1 = Point.read(arguments);
const handle2 = Point.read(arguments);
const to = Point.read(arguments);
const current = Path.getCurrentSegment(this);
current.setHandleOut(handle1.subtract(current._point));
this._add([new Segment(to, handle2.subtract(to))]);
};
Path.prototype.quadraticCurveTo = function () {
const handle = Point.read(arguments);
const to = Point.read(arguments);
const current = Path.getCurrentSegment(this)._point;
this.cubicCurveTo(
handle.add(current.subtract(handle).multiply(1 / 3)),
handle.add(to.subtract(handle).multiply(1 / 3)),
to
);
};
Path.prototype.closePath = function (tolerance) {
this.setClosed(true);
this.join(this, tolerance);
};
Path.prototype._getBounds = function (matrix, options) {
return options.handle
? Path.getHandleBounds(this._segments, this._closed, this, matrix, options)
: Path.getBounds(this._segments, this._closed, this, matrix, options);
};
Path.getCurrentSegment = function (that) {
const segments = that._segments;
if (!segments.length) { throw new Error('Use a moveTo() command first'); }
return segments[segments.length - 1];
};
Path.getBounds = function (segments, closed, _path, matrix) {
const first = segments[0];
if (!first) { return new Rectangle(); }
let coords = new Array(6);
let prevCoords = first._transformCoordinates(matrix, new Array(6));
const min = prevCoords.slice(0, 2);
const max = min.slice();
const roots = new Array(2);
function processSegment(segment) {
segment._transformCoordinates(matrix, coords);
for (let i = 0; i < 2; i++) {
Curve._addBounds(
prevCoords[i],
prevCoords[i + 4],
coords[i + 2],
coords[i],
i, 0, min, max, roots);
}
const tmp = prevCoords;
prevCoords = coords;
coords = tmp;
}
for (let i = 1, l = segments.length; i < l; i++)
processSegment(segments[i]);
if (closed)
processSegment(first);
return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
};
Path.getHandleBounds = function (segments, _closed, _path, matrix) {
const coords = new Array(6);
let x1 = Infinity;
let x2 = -x1;
let y1 = x1;
let y2 = x2;
for (let i = 0, l = segments.length; i < l; i++) {
segments[i]._transformCoordinates(matrix, coords);
for (let j = 0; j < 6; j += 2) {
const x = coords[j];
const y = coords[j + 1];
if (x < x1) x1 = x;
if (x > x2) x2 = x;
if (y < y1) y1 = y;
if (y > y2) y2 = y;
}
}
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
};
Path.Rectangle = function (left, top, right, bottom) {
const path = new Path();
path.moveTo(left, top);
path.lineTo(right, top);
path.lineTo(right, bottom);
path.lineTo(left, bottom);
path.closePath();
return path;
};
const CompoundPath = function (arg) {
this._children = [];
this._namedChildren = {};
if (!this._initialize(arg)) {
this.addChildren(Array.isArray(arg) ? arg : arguments);
}
};
InitClassWithStatics(CompoundPath, PathItem);
CompoundPath.prototype.insertChildren = function insertChildren(index, items) {
let list = items;
const first = list[0];
if (first && typeof first[0] === 'number')
list = [list];
for (let i = items.length - 1; i >= 0; i--) {
const item = list[i];
if (list === items && !(item instanceof Path))
list = Base.slice(list);
if (Array.isArray(item)) {
list[i] = new Path({ segments: item, insert: false });
} else if (item instanceof CompoundPath) {
list.splice.apply(list, [i, 1].concat(item.removeChildren()));
item.remove();
}
}
return Item.prototype.insertChildren.call(this, index, list);
};
CompoundPath.prototype.reduce = function reduce(options) {
for (let i = this._children.length - 1; i >= 0; i--) {
const path = this._children[i].reduce(options);
if (path.isEmpty())
path.remove();
}
if (!this._children.length) {
const path = new Path({ insert: false });
path.copyAttributes(this);
path.insertAbove(this);
this.remove();
return path;
}
return Item.prototype.reduce.call(this);
};
CompoundPath.prototype.isClosed = function () {
for (let i = 0, l = this._children.length; i < l; i++) {
if (!this._children[i]._closed)
return false;
}
return true;
};
CompoundPath.prototype.setClosed = function (closed) {
for (let i = 0, l = this._children.length; i < l; i++) {
this._children[i].setClosed(closed);
}
};
CompoundPath.prototype.getFirstSegment = function () {
const first = this.getFirstChild();
return first && first.getFirstSegment();
};
CompoundPath.prototype.getLastSegment = function () {
const last = this.getLastChild();
return last && last.getLastSegment();
};
CompoundPath.prototype.getCurves = function () {
const curves = [];
for (let i = 0, l = this._children.length; i < l; i++) {
curves.push.apply(curves, this._children[i].getCurves());
}
return curves;
};
CompoundPath.prototype.getFirstCurve = function () {
const first = this.getFirstChild();
return first && first.getFirstCurve();
};
CompoundPath.prototype.getLastCurve = function () {
const last = this.getLastChild();
return last && last.getLastCurve();
};
CompoundPath.prototype.getArea = function () {
let area = 0;
for (let i = 0, l = this._children.length; i < l; i++)
area += this._children[i].getArea();
return area;
};
CompoundPath.prototype.isEmpty = function () {
let empty = true;
for (let i = 0, l = this._children.length; i < l; i++)
empty *= this._children[i].isEmpty();
return empty;
};
CompoundPath.prototype.getLength = function () {
let length = 0;
for (let i = 0, l = this._children.length; i < l; i++)
length += this._children[i].getLength();
return length;
};
CompoundPath.prototype.moveTo = function () {
const current = CompoundPath.getCurrentPath(this);
const path = current && current.isEmpty() ? current : new Path({ insert: false });
if (path !== current) { this.addChild(path); }
path.moveTo.apply(path, arguments);
};
CompoundPath.prototype.closePath = function (tolerance) {
CompoundPath.getCurrentPath(this, true).closePath(tolerance);
};
CompoundPath.prototype.lineTo = function () {
const path = CompoundPath.getCurrentPath(this, true);
path.lineTo.apply(path, arguments);
};
CompoundPath.prototype.cubicCurveTo = function () {
const path = CompoundPath.getCurrentPath(this, true);
path.cubicCurveTo.apply(path, arguments);
};
CompoundPath.prototype.quadraticCurveTo = function () {
const path = CompoundPath.getCurrentPath(this, true);
path.quadraticCurveTo.apply(path, arguments);
};
CompoundPath.prototype.reverse = function (param) {
let res;
for (let i = 0, l = this._children.length; i < l; i++) {
res = this._children[i].reverse(param) || res;
}
return res;
};
CompoundPath.getCurrentPath = function (that, check) {
if (check && !that._children.length)
throw new Error('Use a moveTo() command first');
return that._children[that._children.length - 1];
};
// EXPORTS
window['AscCommon'] = window['AscCommon'] || {};
window['AscCommon']['PathBoolean'] = {}
window['AscCommon']['PathBoolean']['CompoundPath'] = CompoundPath;
CompoundPath.prototype['divide'] = PathItem.prototype.divide;
CompoundPath.prototype['unite'] = PathItem.prototype.unite;
CompoundPath.prototype['intersect'] = PathItem.prototype.intersect;
CompoundPath.prototype['subtract'] = PathItem.prototype.subtract;
CompoundPath.prototype['exclude'] = PathItem.prototype.exclude;
Path.prototype['divide'] = PathItem.prototype.divide;
Path.prototype['unite'] = PathItem.prototype.unite;
Path.prototype['intersect'] = PathItem.prototype.intersect;
Path.prototype['subtract'] = PathItem.prototype.subtract;
Path.prototype['exclude'] = PathItem.prototype.exclude;
CompoundPath.prototype['moveTo'] = CompoundPath.prototype.moveTo;
CompoundPath.prototype['lineTo'] = CompoundPath.prototype.lineTo;
CompoundPath.prototype['cubicCurveTo'] = CompoundPath.prototype.cubicCurveTo;
CompoundPath.prototype['closePath'] = CompoundPath.prototype.closePath;
CompoundPath.prototype['getChildren'] = Item.prototype.getChildren;
CompoundPath.prototype['getBounds'] = Item.prototype.getBounds;
CompoundPath.prototype['getPosition'] = Item.prototype.getPosition;
CompoundPath.prototype['setPosition'] = Item.prototype.setPosition;
Path.prototype['getSegments'] = Path.prototype.getSegments;
Path.prototype['isClosed'] = Path.prototype.isClosed;
Path.prototype['getBounds'] = Item.prototype.getBounds;
Path.prototype['getPosition'] = Item.prototype.getPosition;
Path.prototype['setPosition'] = Item.prototype.setPosition;
Segment.prototype['isFirst'] = Segment.prototype.isFirst;
Segment.prototype['isLast'] = Segment.prototype.isLast;
Segment.prototype['getPrevious'] = Segment.prototype.getPrevious;
Segment.prototype['getNext'] = Segment.prototype.getNext;
Segment.prototype['getPoint'] = Segment.prototype.getPoint;
Segment.prototype['getHandleOut'] = Segment.prototype.getHandleOut;
Segment.prototype['getHandleIn'] = Segment.prototype.getHandleIn;
Rectangle.prototype['getTopLeft'] = Rectangle.prototype.getTopLeft;
Rectangle.prototype['getWidth'] = Rectangle.prototype.getWidth;
Rectangle.prototype['getHeight'] = Rectangle.prototype.getHeight;
Rectangle.prototype['getLeft'] = Rectangle.prototype.getLeft;
Rectangle.prototype['getTop'] = Rectangle.prototype.getTop;
Point.prototype['subtract'] = Point.prototype.subtract;
Point.prototype['getX'] = Point.prototype.getX;
Point.prototype['getY'] = Point.prototype.getY;
})(window);