Files
Yajbir Singh f1b860b25c
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

/*
* (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);