4968 lines
152 KiB
JavaScript
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);
|