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

3911 lines
122 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

/*
* (c) Copyright Ascensio System SIA 2010-2024
*
* 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
*
*/
"use strict";
(/**
* @param {Window} window
* @param {undefined} undefined
*/
function (window, undefined) {
const moveTo = 0;
const lineTo = 1;
const arcTo = 2;
const bezier3 = 3;
const bezier4 = 4;
const close = 5;
const ellipticalArcTo = 6;
const nurbsTo = 7;
// Import
const cToRad = AscFormat.cToRad;
const HitToArc = AscFormat.HitToArc;
const ArcToCurvers = AscFormat.ArcToCurvers;
const ArcToOnCanvas = AscFormat.ArcToOnCanvas;
const HitInLine = AscFormat.HitInLine;
const HitInBezier4 = AscFormat.HitInBezier4;
const HitInBezier3 = AscFormat.HitInBezier3;
const MOVE_DELTA = AscFormat.MOVE_DELTA;
const cToRad2 = (Math.PI / 60000) / 180;
const degToC = 60000;
const radToDeg = 180 / Math.PI;
const cToDeg = cToRad2 * radToDeg;
function CChangesDrawingsAddPathCommand(Class, oCommand, nIndex, bReverse) {
this.Type = AscDFH.historyitem_PathAddPathCommand;
this.Command = oCommand;
this.Index = nIndex;
this.bReverse = bReverse;
AscDFH.CChangesBase.call(this, Class);
}
CChangesDrawingsAddPathCommand.prototype = Object.create(AscDFH.CChangesBase.prototype);
CChangesDrawingsAddPathCommand.prototype.constructor = CChangesDrawingsAddPathCommand;
CChangesDrawingsAddPathCommand.prototype.Undo = function () {
if (this.bReverse) {
this.Class.ArrPathCommandInfo.splice(this.Index, 0, this.Command);
}
else {
this.Class.ArrPathCommandInfo.splice(this.Index, 1);
}
};
CChangesDrawingsAddPathCommand.prototype.Redo = function () {
if (this.bReverse) {
this.Class.ArrPathCommandInfo.splice(this.Index, 1);
}
else {
this.Class.ArrPathCommandInfo.splice(this.Index, 0, this.Command);
}
};
CChangesDrawingsAddPathCommand.prototype.WriteToBinary = function (Writer) {
Writer.WriteLong(this.Index);
Writer.WriteLong(this.Command.id);
Writer.WriteBool(!!this.bReverse);
switch (this.Command.id) {
case moveTo:
case lineTo: {
Writer.WriteString2(this.Command.X);
Writer.WriteString2(this.Command.Y);
break;
}
case bezier3: {
Writer.WriteString2(this.Command.X0);
Writer.WriteString2(this.Command.Y0);
Writer.WriteString2(this.Command.X1);
Writer.WriteString2(this.Command.Y1);
break;
}
case bezier4: {
Writer.WriteString2(this.Command.X0);
Writer.WriteString2(this.Command.Y0);
Writer.WriteString2(this.Command.X1);
Writer.WriteString2(this.Command.Y1);
Writer.WriteString2(this.Command.X2);
Writer.WriteString2(this.Command.Y2);
break;
}
case arcTo: {
Writer.WriteString2(this.Command.hR);
Writer.WriteString2(this.Command.wR);
Writer.WriteString2(this.Command.stAng);
Writer.WriteString2(this.Command.swAng);
break;
}
case close: {
break;
}
}
};
CChangesDrawingsAddPathCommand.prototype.ReadFromBinary = function (Reader) {
this.Index = Reader.GetLong();
this.Command = {};
this.Command.id = Reader.GetLong();
this.bReverse = Reader.GetBool();
switch (this.Command.id) {
case moveTo:
case lineTo: {
this.Command.X = Reader.GetString2();
this.Command.Y = Reader.GetString2();
break;
}
case bezier3: {
this.Command.X0 = Reader.GetString2();
this.Command.Y0 = Reader.GetString2();
this.Command.X1 = Reader.GetString2();
this.Command.Y1 = Reader.GetString2();
break;
}
case bezier4: {
this.Command.X0 = Reader.GetString2();
this.Command.Y0 = Reader.GetString2();
this.Command.X1 = Reader.GetString2();
this.Command.Y1 = Reader.GetString2();
this.Command.X2 = Reader.GetString2();
this.Command.Y2 = Reader.GetString2();
break;
}
case arcTo: {
this.Command.hR = Reader.GetString2();
this.Command.wR = Reader.GetString2();
this.Command.stAng = Reader.GetString2();
this.Command.swAng = Reader.GetString2();
break;
}
case close: {
break;
}
}
};
AscDFH.changesFactory[AscDFH.historyitem_PathSetStroke] = AscDFH.CChangesDrawingsBool;
AscDFH.changesFactory[AscDFH.historyitem_PathSetExtrusionOk] = AscDFH.CChangesDrawingsBool;
AscDFH.changesFactory[AscDFH.historyitem_PathSetFill] = AscDFH.CChangesDrawingsString;
AscDFH.changesFactory[AscDFH.historyitem_PathSetPathH] = AscDFH.CChangesDrawingsLong;
AscDFH.changesFactory[AscDFH.historyitem_PathSetPathW] = AscDFH.CChangesDrawingsLong;
AscDFH.changesFactory[AscDFH.historyitem_PathAddPathCommand] = CChangesDrawingsAddPathCommand;
AscDFH.drawingsChangesMap[AscDFH.historyitem_PathSetStroke] = function (oClass, value) {
oClass.stroke = value;
};
AscDFH.drawingsChangesMap[AscDFH.historyitem_PathSetExtrusionOk] = function (oClass, value) {
oClass.extrusionOk = value;
};
AscDFH.drawingsChangesMap[AscDFH.historyitem_PathSetFill] = function (oClass, value) {
oClass.fill = value;
};
AscDFH.drawingsChangesMap[AscDFH.historyitem_PathSetPathH] = function (oClass, value) {
oClass.pathH = value;
};
AscDFH.drawingsChangesMap[AscDFH.historyitem_PathSetPathW] = function (oClass, value) {
oClass.pathW = value;
};
function Path() {
AscFormat.CBaseFormatObject.call(this);
this.stroke = null;
this.extrusionOk = null;
this.fill = null;
this.pathH = null;
this.pathW = null;
this.ArrPathCommandInfo = [];
this.ArrPathCommand = [];
}
AscFormat.InitClass(Path, AscFormat.CBaseFormatObject, AscDFH.historyitem_type_Path);
Path.prototypeRefresh_RecalcData = function () {
};
Path.prototype.createDuplicate = function () {
let p = new Path();
p.setStroke(this.stroke);
p.setExtrusionOk(this.extrusionOk);
p.setFill(this.fill);
p.setPathH(this.pathH);
p.setPathW(this.pathW);
for (let i = 0; i < this.ArrPathCommandInfo.length; ++i) {
let command = this.ArrPathCommandInfo[i];
switch (command.id) {
case moveTo:
case lineTo: {
let x = command.X;
let y = command.Y;
p.addPathCommand({id: command.id, X: x, Y: y});
break;
}
case bezier3: {
let X0 = command.X0;
let Y0 = command.Y0;
let X1 = command.X1;
let Y1 = command.Y1;
p.addPathCommand({id: bezier3, X0: X0, Y0: Y0, X1: X1, Y1: Y1});
break;
}
case bezier4: {
let X0 = command.X0;
let Y0 = command.Y0;
let X1 = command.X1;
let Y1 = command.Y1;
let X2 = command.X2;
let Y2 = command.Y2;
p.addPathCommand({id: bezier4, X0: X0, Y0: Y0, X1: X1, Y1: Y1, X2: X2, Y2: Y2});
break;
}
case arcTo: {
let hR = command.hR;
let wR = command.wR;
let stAng = command.stAng;
let swAng = command.swAng;
p.addPathCommand({id: arcTo, hR: hR, wR: wR, stAng: stAng, swAng: swAng});
break;
}
case close: {
p.addPathCommand({id: close});
break;
}
case ellipticalArcTo: {
let a = command.a;
let b = command.b;
let c = command.c;
let d = command.d;
let x = command.x;
let y = command.y;
p.addPathCommand({id: ellipticalArcTo, a: a, b: b, c: c, d: d, x: x,
y: y});
break;
}
case nurbsTo: {
let nurbsToCopy = cloneSplineObject(command);
p.addPathCommand(nurbsToCopy);
break;
}
}
}
return p;
/**
* Deep clones a NURBS/spline-like object with the structure shown in the screenshot
* Object contains:
* - controlPoints: Array of {x: Number, y: Number} points
* - degree: Number
* - id: Number/String
* - knots: Array of Numbers
* - weights: Array of Numbers
*
* @param {Object} splineObj - The spline object to clone
* @return {Object} A deep copy of the spline object
*/
function cloneSplineObject(splineObj) {
let clone = {};
// Clone controlPoints if they exist
if (Array.isArray(splineObj.controlPoints)) {
clone.controlPoints = [];
for (let i = 0; i < splineObj.controlPoints.length; i++) {
let point = splineObj.controlPoints[i];
let pointClone = {};
// Copy all properties from the point (typically x and y)
for (let prop in point) {
if (point.hasOwnProperty(prop)) {
pointClone[prop] = point[prop];
}
}
clone.controlPoints.push(pointClone);
}
}
// Clone degree property
if (splineObj.hasOwnProperty('degree')) {
clone.degree = splineObj.degree;
}
// Clone id property
if (splineObj.hasOwnProperty('id')) {
clone.id = splineObj.id;
}
// Clone knots array if it exists
if (Array.isArray(splineObj.knots)) {
clone.knots = [];
for (let i = 0; i < splineObj.knots.length; i++) {
clone.knots.push(splineObj.knots[i]);
}
}
// Clone weights array if it exists
if (Array.isArray(splineObj.weights)) {
clone.weights = [];
for (let i = 0; i < splineObj.weights.length; i++) {
clone.weights.push(splineObj.weights[i]);
}
}
return clone;
}
};
Path.prototype.setStroke = function (pr) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new AscDFH.CChangesDrawingsBool(this, AscDFH.historyitem_PathSetStroke, this.stroke, pr));
this.stroke = pr;
};
Path.prototype.setExtrusionOk = function (pr) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new AscDFH.CChangesDrawingsBool(this, AscDFH.historyitem_PathSetExtrusionOk, this.extrusionOk, pr));
this.extrusionOk = pr;
};
Path.prototype.setFill = function (pr) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new AscDFH.CChangesDrawingsString(this, AscDFH.historyitem_PathSetFill, this.fill, pr));
this.fill = pr;
};
Path.prototype.setPathH = function (pr) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new AscDFH.CChangesDrawingsLong(this, AscDFH.historyitem_PathSetPathH, this.pathH, pr));
this.pathH = pr;
};
Path.prototype.setPathW = function (pr) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new AscDFH.CChangesDrawingsLong(this, AscDFH.historyitem_PathSetPathW, this.pathW, pr));
this.pathW = pr;
};
Path.prototype.addPathCommand = function (cmd) {
AscCommon.History.CanAddChanges() && AscCommon.History.Add(new CChangesDrawingsAddPathCommand(this, cmd, this.ArrPathCommandInfo.length));
this.ArrPathCommandInfo.push(cmd);
};
Path.prototype.moveTo = function (x, y) {
this.addPathCommand({id: moveTo, X: x, Y: y});
};
Path.prototype.lnTo = function (x, y) {
this.addPathCommand({id: lineTo, X: x, Y: y});
};
Path.prototype.arcTo = function (wR, hR, stAng, swAng, ellipseRotation) {
if (typeof ellipseRotation !== 'undefined') {
// to be sure for backwards compatibility
this.addPathCommand({
id: arcTo,
wR: wR,
hR: hR,
stAng: stAng,
swAng: swAng,
ellipseRotation: ellipseRotation
});
}
else {
this.addPathCommand({id: arcTo, wR: wR, hR: hR, stAng: stAng, swAng: swAng});
}
};
Path.prototype.quadBezTo = function (x0, y0, x1, y1) {
this.addPathCommand({id: bezier3, X0: x0, Y0: y0, X1: x1, Y1: y1});
};
Path.prototype.cubicBezTo = function (x0, y0, x1, y1, x2, y2) {
this.addPathCommand({id: bezier4, X0: x0, Y0: y0, X1: x1, Y1: y1, X2: x2, Y2: y2});
};
Path.prototype.close = function () {
if (this.ArrPathCommandInfo.length > 0) {
this.addPathCommand({id: close});
}
};
Path.prototype.ellipticalArcTo = function (x, y, a, b, c, d) {
this.addPathCommand({id: ellipticalArcTo, x: x, y: y, a: a, b: b, c: c, d: d});
};
/**
* curveOrder (4 for 3 degree NURBS) + points count = knots count
* @param {{x, y}[]} controlPoints
* @param {[]} weights
* @param {[]} knots
* @param {Number} degree
*/
Path.prototype.nurbsTo = function (controlPoints, weights, knots, degree) {
this.addPathCommand({
id: nurbsTo,
controlPoints: controlPoints,
weights: weights,
knots: knots,
degree: degree
});
};
/**
* Normalizes coordinates in ArrPathCommandInfo by scaling them relative to shape size
* @param {number} shapeWidth - shape width to normalize by
* @param {number} shapeHeight - shape height to normalize by
* @param {number} pathWidth - destination path width to scale to
* @param {number} pathHeight - destination path height to scale to
*/
Path.prototype.normalizeCoordinates = function (shapeWidth, shapeHeight, pathWidth, pathHeight) {
if (shapeWidth === 0 || shapeHeight === 0) {
return;
}
let command;
let controlPoint;
for (let i = 0; i < this.ArrPathCommandInfo.length; i++) {
command = this.ArrPathCommandInfo[i];
switch (command.id) {
case moveTo:
case lineTo:
command.X = Math.round((command.X / shapeWidth) * pathWidth);
command.Y = Math.round((command.Y / shapeHeight) * pathHeight);
break;
case arcTo:
command.wR = Math.round((command.wR / shapeWidth) * pathWidth);
command.hR = Math.round((command.hR / shapeHeight) * pathHeight);
break;
case bezier3:
command.X0 = Math.round((command.X0 / shapeWidth) * pathWidth);
command.Y0 = Math.round((command.Y0 / shapeHeight) * pathHeight);
command.X1 = Math.round((command.X1 / shapeWidth) * pathWidth);
command.Y1 = Math.round((command.Y1 / shapeHeight) * pathHeight);
break;
case bezier4:
command.X0 = Math.round((command.X0 / shapeWidth) * pathWidth);
command.Y0 = Math.round((command.Y0 / shapeHeight) * pathHeight);
command.X1 = Math.round((command.X1 / shapeWidth) * pathWidth);
command.Y1 = Math.round((command.Y1 / shapeHeight) * pathHeight);
command.X2 = Math.round((command.X2 / shapeWidth) * pathWidth);
command.Y2 = Math.round((command.Y2 / shapeHeight) * pathHeight);
break;
case ellipticalArcTo:
command.x = Math.round((command.x / shapeWidth) * pathWidth);
command.y = Math.round((command.y / shapeHeight) * pathHeight);
command.a = Math.round((command.a / shapeWidth) * pathWidth);
command.b = Math.round((command.b / shapeHeight) * pathHeight);
break;
case nurbsTo:
for (let j = 0; j < command.controlPoints.length; j++) {
controlPoint = command.controlPoints[j];
controlPoint.x = Math.round((controlPoint.x / shapeWidth) * pathWidth);
controlPoint.y = Math.round((controlPoint.y / shapeHeight) * pathHeight);
}
break;
case close:
//no coordinates to normalize
break;
}
}
};
Path.prototype.calculateCommandCoord = function (oGdLst, sFormula, dFormulaCoeff, dNumberCoeff) {
let dVal;
dVal = oGdLst[sFormula];
if (dVal !== undefined) {
return dVal * dFormulaCoeff;
}
return parseInt(sFormula, 10) * dNumberCoeff;
};
/**
* accepts angles in anti-clockwise system
* @param {number} startAngle from -180 to 180
* @param {number} endAngle from -180 to 180
* @param {number} ctrlAngle from -180 to 180
* @returns {number} sweep - positive sweep means anti-clockwise
*/
function computeSweep(startAngle, endAngle, ctrlAngle) {
// Normalize angles from 180…180 to [0, 360) interval
startAngle = (360 + startAngle) % 360;
endAngle = (360 + endAngle) % 360;
ctrlAngle = (360 + ctrlAngle) % 360;
if (startAngle === endAngle) {
return 0;
}
// different sweeps depending on where the control point is
let sweep;
if (startAngle < endAngle) {
if (startAngle < ctrlAngle && ctrlAngle < endAngle) {
// positive sweep - anti-clockwise
sweep = endAngle - startAngle;
}
else {
// negative sweep - clockwise
sweep = (endAngle - startAngle) - 360;
}
}
else {
if (endAngle < ctrlAngle && ctrlAngle < startAngle) {
// negative sweep - clockwise
sweep = endAngle - startAngle;
}
else {
// positive sweep - anti-clockwise
sweep = 360 - (startAngle - endAngle);
}
}
return sweep;
}
/**
* afin rotate clockwise
* @param {number} x
* @param {number} y
* @param {number} radiansRotateAngle radians Rotate ClockWise Angle. E.g. 30 degrees rotates does DOWN.
* @returns {{x: number, y: number}} point
*/
function rotatePointAroundCordsStartClockWise(x, y, radiansRotateAngle) {
let newX = x * Math.cos(radiansRotateAngle) + y * Math.sin(radiansRotateAngle);
let newY = x * (-1) * Math.sin(radiansRotateAngle) + y * Math.cos(radiansRotateAngle);
return {x: newX, y: newY};
}
/**
* Accepts visio params
* https://learn.microsoft.com/en-us/office/client-developer/visio/ellipticalarcto-row-geometry-section
* @param x0
* @param y0
* @param x
* @param y
* @param a
* @param b
* @param c
* @param d
* @returns {{stAng: number, ellipseRotation: number, hR: number, wR: number, swAng: number}}
* WARNING these are not ECMA params exactly, stAng and swAng angles are anti-clockwise
*/
function transformEllipticalArcParams(x0, y0, x, y, a, b, c, d) {
// https://www.figma.com/file/hs43oiAUyuoqFULVoJ5lyZ/EllipticArcConvert?type=design&node-id=1-2&mode=design&t=QJu8MtR3JV62WiW9-0
x0 = Number(x0);
y0 = Number(y0);
x = Number(x);
y = Number(y);
a = Number(a);
b = Number(b);
c = Number(c);
d = Number(d);
// translate points to ellipse angle
let startPoint = rotatePointAroundCordsStartClockWise(x0, y0, c);
let endPoint = rotatePointAroundCordsStartClockWise(x, y, c);
let controlPoint = rotatePointAroundCordsStartClockWise(a, b, c);
x0 = startPoint.x;
y0 = startPoint.y;
x = endPoint.x;
y = endPoint.y;
a = controlPoint.x;
b = controlPoint.y;
// http://visguy.com/vgforum/index.php?topic=2464.0
// can also be helpful https://stackoverflow.com/questions/6729056/mapping-svg-arcto-to-html-canvas-arcto
let onErrorResult = {wR: NaN, hR: NaN, stAng: NaN, swAng: NaN, ellipseRotation: NaN}
if (d === 0) {
return onErrorResult;
}
let d2 = d * d;
let cxDenominator = 2.0 * ((x0 - x) * (y - b) - (x - a) * (y0 - y));
if (cxDenominator === 0) {
return onErrorResult;
}
let cx = ((x0 - x) * (x0 + x) * (y - b) - (x - a) * (x + a) * (y0 - y) + d2 * (y0 - y) * (y - b) * (y0 - b)) / cxDenominator;
let cyDenominator = 2.0 * ((x - a) * (y0 - y) - (x0 - x) * (y - b));
if (cyDenominator === 0) {
return onErrorResult;
}
let cy = ((x0 - x) * (x - a) * (x0 - a) / d2 + (x - a) * (y0 - y) * (y0 + y) - (x0 - x) * (y - b) * (y + b)) / cyDenominator;
let rx = Math.sqrt(Math.pow(x0 - cx, 2) + Math.pow(y0 - cy, 2) * d2);
let ry = rx / d;
let ctrlAngle = Math.atan2(b - cy, a - cx) * radToDeg;
let startAngle = Math.atan2(y0 - cy, x0 - cx) * radToDeg;
let endAngle = Math.atan2(y - cy, x - cx) * radToDeg;
let sweep = computeSweep(startAngle, endAngle, ctrlAngle);
let ellipseRotationAngle = c * radToDeg;
// let mirrorVertically = false;
// if (mirrorVertically) {
// startAngle = 360 - startAngle;
// sweep = -sweep;
// ellipseRotationAngle = - ellipseRotationAngle;
// }
// WARNING these are not ECMA params exactly, stAng and swAng angles are anti-clockwise!
// about ECMA cord system:
// c is AntiClockwise so 30 deg go up in Visio
// but in ECMA it should be another angle
// because in ECMA angles are clockwise ang 30 deg go down.
// convert from anticlockwise angle system to clockwise
// angleEcma = 360 - angleVisio;
// using visio angles here but still multiply to degToC
// then cord system trasformed in sdkjs/draw/model/VisioDocument.js CVisioDocument.prototype.draw
let swAng = sweep * degToC;
let stAng = startAngle * degToC;
let ellipseRotationInC = ellipseRotationAngle * degToC;
let wR = rx;
let hR = ry;
return {wR: wR, hR: hR, stAng: stAng, swAng: swAng, ellipseRotation: ellipseRotationInC};
}
const COL_EPS_K = 2e-8;
/**
* Determines whether three points are collinear using an adaptive tolerance proportional to the coordinate scale.
* @param {number} ax
* @param {number} ay
* @param {number} bx
* @param {number} by
* @param {number} cx
* @param {number} cy
* @returns {boolean}
*/
function isCollinear(ax, ay, bx, by, cx, cy) {
// Fast path: any two points coincide degenerate triangle considered collinear
if ((ax === bx && ay === by) || (ax === cx && ay === cy) || (bx === cx && by === cy)) {
return true;
}
// Twice the signed area of the triangle (cross product of edge vectors)
const cross = (bx - ax) * (cy - ay) - (cx - ax) * (by - ay);
// Adaptive tolerance: scales with the largest coordinate delta
// Makes the check robust for very small and very large numbers.
const scale = Math.max(
Math.abs(bx - ax), Math.abs(by - ay),
Math.abs(cx - ax), Math.abs(cy - ay)
);
const scaleSquared = Math.pow(scale || 1, 2);
const tol = COL_EPS_K * scaleSquared;
return Math.abs(cross) <= tol;
}
Path.prototype.recalculate = function (gdLst, bResetPathsInfo) {
let ch, cw;
let dCustomPathCoeffW, dCustomPathCoeffH;
if (this.pathW != undefined) {
if (this.pathW > MOVE_DELTA) {
cw = (gdLst["w"] / this.pathW);
}
else {
cw = 0;
}
dCustomPathCoeffW = cw;
}
else {
cw = 1;
dCustomPathCoeffW = 1 / 36000;
}
if (this.pathH != undefined) {
if (this.pathH > MOVE_DELTA) {
ch = (gdLst["h"] / this.pathH);
}
else {
ch = 0;
}
dCustomPathCoeffH = ch;
}
else {
ch = 1;
dCustomPathCoeffH = 1 / 36000;
}
let APCI = this.ArrPathCommandInfo, n = APCI.length, cmd;
let x0, y0, x1, y1, x2, y2, wR, hR, stAng, swAng, ellipseRotation, lastX, lastY;
this.ArrPathCommand.length = 0;
for (let i = 0; i < n; ++i) {
cmd = APCI[i];
switch (cmd.id) {
case moveTo:
case lineTo: {
x0 = this.calculateCommandCoord(gdLst, cmd.X, cw, dCustomPathCoeffW);
y0 = this.calculateCommandCoord(gdLst, cmd.Y, ch, dCustomPathCoeffH);
this.ArrPathCommand.push({id: cmd.id, X: x0, Y: y0});
lastX = x0;
lastY = y0;
break;
}
case bezier3: {
x0 = this.calculateCommandCoord(gdLst, cmd.X0, cw, dCustomPathCoeffW);
y0 = this.calculateCommandCoord(gdLst, cmd.Y0, ch, dCustomPathCoeffH);
x1 = this.calculateCommandCoord(gdLst, cmd.X1, cw, dCustomPathCoeffW);
y1 = this.calculateCommandCoord(gdLst, cmd.Y1, ch, dCustomPathCoeffH);
this.ArrPathCommand.push({id: bezier3, X0: x0, Y0: y0, X1: x1, Y1: y1});
lastX = x1;
lastY = y1;
break;
}
case bezier4: {
x0 = this.calculateCommandCoord(gdLst, cmd.X0, cw, dCustomPathCoeffW);
y0 = this.calculateCommandCoord(gdLst, cmd.Y0, ch, dCustomPathCoeffH);
x1 = this.calculateCommandCoord(gdLst, cmd.X1, cw, dCustomPathCoeffW);
y1 = this.calculateCommandCoord(gdLst, cmd.Y1, ch, dCustomPathCoeffH);
x2 = this.calculateCommandCoord(gdLst, cmd.X2, cw, dCustomPathCoeffW);
y2 = this.calculateCommandCoord(gdLst, cmd.Y2, ch, dCustomPathCoeffH);
this.ArrPathCommand.push({id: bezier4, X0: x0, Y0: y0, X1: x1, Y1: y1, X2: x2, Y2: y2});
lastX = x2;
lastY = y2;
break;
}
case arcTo: {
wR = this.calculateCommandCoord(gdLst, cmd.wR, cw, dCustomPathCoeffW);
hR = this.calculateCommandCoord(gdLst, cmd.hR, ch, dCustomPathCoeffH);
stAng = gdLst[cmd.stAng];
if (stAng === undefined) {
stAng = parseInt(cmd.stAng, 10);
}
swAng = gdLst[cmd.swAng];
if (swAng === undefined) {
swAng = parseInt(cmd.swAng, 10);
}
let a1 = stAng;
let a2 = stAng + swAng;
let a3 = swAng;
stAng = Math.atan2(ch * Math.sin(a1 * cToRad), cw * Math.cos(a1 * cToRad)) / cToRad;
swAng = Math.atan2(ch * Math.sin(a2 * cToRad), cw * Math.cos(a2 * cToRad)) / cToRad - stAng;
if ((swAng > 0) && (a3 < 0)) swAng -= 21600000;
if ((swAng < 0) && (a3 > 0)) swAng += 21600000;
if (swAng == 0 && a3 != 0) swAng = 21600000;
// https://www.figma.com/file/hs43oiAUyuoqFULVoJ5lyZ/EllipticArcConvert?type=design&node-id=291-2&mode=design&t=jLr0jZ6jdV6YhG2S-0
let a = wR;
let b = hR;
let sin2 = Math.sin(stAng * cToRad);
let cos2 = Math.cos(stAng * cToRad);
let _xrad = cos2 / a;
let _yrad = sin2 / b;
let l = 1 / Math.sqrt(_xrad * _xrad + _yrad * _yrad);
let xc = lastX - l * cos2;
let yc = lastY - l * sin2;
let sin1 = Math.sin((stAng + swAng) * cToRad);
let cos1 = Math.cos((stAng + swAng) * cToRad);
let _xrad1 = cos1 / a;
let _yrad1 = sin1 / b;
let l1 = 1 / Math.sqrt(_xrad1 * _xrad1 + _yrad1 * _yrad1);
if (cmd.ellipseRotation === undefined) {
this.ArrPathCommand.push({
id: arcTo,
stX: lastX,
stY: lastY,
wR: wR,
hR: hR,
stAng: stAng * cToRad,
swAng: swAng * cToRad
});
lastX = xc + l1 * cos1;
lastY = yc + l1 * sin1;
}
else {
// do transformations with ellipseRotation by analogy. ellipseRotation is added later
// then calculate new end point
ellipseRotation = gdLst[cmd.ellipseRotation];
if (ellipseRotation === undefined) {
ellipseRotation = parseInt(cmd.ellipseRotation, 10);
}
let a4 = ellipseRotation;
ellipseRotation = Math.atan2(ch * Math.sin(a4 * cToRad), cw * Math.cos(a4 * cToRad)) / cToRad;
if ((ellipseRotation > 0) && (a4 < 0)) ellipseRotation -= 21600000;
if ((ellipseRotation < 0) && (a4 > 0)) ellipseRotation += 21600000;
if (ellipseRotation == 0 && a4 != 0) ellipseRotation = 21600000;
this.ArrPathCommand.push({
id: arcTo,
stX: lastX,
stY: lastY,
wR: wR,
hR: hR,
stAng: stAng * cToRad,
swAng: swAng * cToRad,
ellipseRotation: ellipseRotation * cToRad
});
// https://www.figma.com/file/hs43oiAUyuoqFULVoJ5lyZ/EllipticArcConvert?type=design&node-id=291-34&mode=design&t=LKiEAjzKEzKacCBc-0
// lets convert ECMA clockwise angle to trigonometrical
// (anti clockwise) angle to correctly calculate sin and cos
// of l1 angle to calculate l1 end point cords
let l1AntiClockWiseAngle = (360 - (stAng + swAng)) * cToRad;
// new cord system center is ellipse center and its y does up
let l1xNewCordSystem = l1 * Math.cos(l1AntiClockWiseAngle);
let l1yNewCordSystem = l1 * Math.sin(l1AntiClockWiseAngle);
// if no rotate it is:
// lastX = xc + l1xNewCordSystem;
// lastY = yc - l1yNewCordSystem;
// we invert y because start and calculate point coordinate systems are different. see figma
let l1xyRotatedEllipseNewCordSystem = rotatePointAroundCordsStartClockWise(l1xNewCordSystem, l1yNewCordSystem, ellipseRotation * cToRad);
let l1xRotatedEllipseOldCordSystem = l1xyRotatedEllipseNewCordSystem.x + xc;
let l1yRotatedEllipseOldCordSystem = yc - l1xyRotatedEllipseNewCordSystem.y;
// calculate last point offset after ellipse rotate
let lastXnewCordSystem = lastX - xc;
let lastYnewCordSystem = -lastY + yc;
// center of cord system now is the center of ellipse
// blue point
let rotatedLastXYnewCordSystem = rotatePointAroundCordsStartClockWise(lastXnewCordSystem, lastYnewCordSystem, ellipseRotation * cToRad);
let rotatedLastXoldCordSystem = rotatedLastXYnewCordSystem.x + xc;
let rotatedLastYoldCordSystem = yc - rotatedLastXYnewCordSystem.y;
// calculate vector
let lastPointOffsetVectorX = lastX - rotatedLastXoldCordSystem;
let lastPointOffsetVectorY = lastY - rotatedLastYoldCordSystem;
lastX = l1xRotatedEllipseOldCordSystem + lastPointOffsetVectorX;
lastY = l1yRotatedEllipseOldCordSystem + lastPointOffsetVectorY;
}
break;
}
case close: {
this.ArrPathCommand.push({id: close});
break;
}
case ellipticalArcTo: {
function onBadParams(path, x, y, a, b) {
AscCommon.consoleLog("tranform ellipticalArcTo to line. 2 catch.");
path.ArrPathCommand.push({id: lineTo, X: a, Y: b}); // go to control point first
path.ArrPathCommand.push({id: lineTo, X: x, Y: y});
lastX = x;
lastY = y;
}
if (isNaN(lastY)) {
lastY = 0;
}
if (isNaN(lastX)) {
lastX = 0;
}
// https://learn.microsoft.com/en-us/office/client-developer/visio/ellipticalarcto-row-geometry-section
// but with a length in EMUs units and an angle in C-units, which will be expected clockwise as in other functions.
let x, y, a, b, c, d;
x = this.calculateCommandCoord(gdLst, cmd.x, cw, dCustomPathCoeffW);
y = this.calculateCommandCoord(gdLst, cmd.y, ch, dCustomPathCoeffH);
a = this.calculateCommandCoord(gdLst, cmd.a, cw, dCustomPathCoeffW);
b = this.calculateCommandCoord(gdLst, cmd.b, ch, dCustomPathCoeffH);
// check if command arguments are wrong. Wrong arguments may refer to huge ellipse. It editor it can
// break scroll bars. So as visio does let's transform elliptical arc to line.
// see bug https://bugzilla.onlyoffice.com/show_bug.cgi?id=75317
// files from 4 to 6 should not be caught
// three points on one line refers to bad arguments
// see files:
// 1 simple lines and ellipses.vsdx
// 3 swAng ~=0 testFlatCurve.vsdx
// 7 triangleSquare === 0 cehck test Diagonal.vsdx
// 8 small square + NaN params in result, isCollinear cathces.vsdx
let isCollinearCheck = isCollinear(lastX, lastY, a, b, x, y);
if (isCollinearCheck) {
onBadParams(this, x, y, a, b);
break;
}
c = gdLst[cmd.c];
if (c === undefined) {
c = parseInt(cmd.c, 10);
}
// d is fraction
d = Number(cmd.d);
// normalize angle (The values are given in degrees)
// 360 * n —-> ~0
// 270 --> -90
// -290 -> 70
// -570 -> 150
// -286.47888333333333 —-> 73.52111666666667
// -572.9577833333333 —-> 147.04221666666672
// so c in degrees is from -180 to 180
let cRadians = Math.atan2(ch * Math.sin(c * cToRad), cw * Math.cos(c * cToRad));
// change ellipticalArcTo params to draw arc using old params
let newParams = transformEllipticalArcParams(lastX, lastY, x, y, a, b, cRadians, d);
// NaN in newParams refers to bad arguments.
// see files:
// 1 simple lines and ellipses.vsdx
// 7 triangleSquare === 0 cehck test Diagonal.vsdx
// 8 small square + NaN params in result, isCollinear cathces.vsdx
let isNaNInParams = isNaN(newParams.swAng) || isNaN(newParams.stAng) ||
isNaN(newParams.wR) || isNaN(newParams.hR) || isNaN(newParams.ellipseRotation);
if (isNaNInParams) {
onBadParams(this, x, y, a, b);
break;
}
// ~0 swing angle refers to bad arguments. check it
// see files:
// 1 simple lines and ellipses.vsdx
// 2 swAng === 0 check testFlatCurve Huge D.vsdx
// 3 swAng ~=0 testFlatCurve.vsdx
let swAngCheck = AscFormat.fApproxEqual(newParams.swAng, 0, 1e-7);
if (swAngCheck) {
onBadParams(this, x, y, a, b);
break;
}
this.ArrPathCommand.push({
id: ellipticalArcTo,
stX: lastX,
stY: lastY,
wR: newParams.wR,
hR: newParams.hR,
stAng: newParams.stAng * cToRad,
swAng: newParams.swAng * cToRad,
ellipseRotation: newParams.ellipseRotation * cToRad
});
lastX = x;
lastY = y;
break;
}
case nurbsTo: {
//TODO
// consider homogenous or euclidean weighted points weights
/**
* for NURBS to Bezier convert https://math.stackexchange.com/questions/417859/convert-a-b-spline-into-bezier-curves
* @param {{x: Number, y: Number, z? :Number}[]} controlPoints
* @param {Number[]} weights
* @param {Number[]} knots
* @param {Number} multiplicity
* @returns {{controlPoints: {x: Number, y: Number, z? :Number}[], weights: Number[], knots: Number[]} || null} new bezier data
*/
function duplicateKnots(controlPoints, weights, knots, multiplicity) {
/**
* http://preserve.mactech.com/articles/develop/issue_25/schneider.html
* can be found with pictures
* @param {{x: Number, y: Number, z? :Number}[]} controlPoints
* @param {Number[]} weights
* @param {Number[]} knots
* @param {Number} tNew
* @return {{controlPoints: {x: Number, y: Number, z? :Number}[], weights: Number[], knots: Number[]} || null} new bezier data
*/
function insertKnot(controlPoints, weights, knots, tNew) {
let n = controlPoints.length;
let order = knots.length - controlPoints.length;
let k = order;
let calculateZ = controlPoints[0].z !== undefined;
let newKnots = [];
let newWeights = [];
let newControlPoints = [];
// find index to insert tNew after
let i = -1;
for (let j = 0; j < n + k; j++) {
if (tNew > knots[j] && tNew <= knots[j + 1]) {
i = j;
break;
}
}
// insert tNew
if (i === -1) {
AscCommon.consoleLog("Not found position to insert new knot");
return null;
}
else {
// Copy knots to new array.
for (let j = 0; j < n + k + 1; j++) {
if (j <= i) {
newKnots[j] = knots[j];
}
else if (j === i + 1) {
newKnots[j] = tNew;
}
else {
newKnots[j] = knots[j - 1];
}
}
}
// Compute position of new control point and new positions of
// existing ones.
let alpha;
for (let j = 0; j < n + 1; j++) {
if (j <= i - k + 1) {
alpha = 1;
}
else if (i - k + 2 <= j && j <= i) {
if (knots[j + k - 1] - knots[j] == 0) {
alpha = 0;
}
else {
alpha = (tNew - knots[j]) / (knots[j + k - 1] - knots[j]);
}
}
else {
alpha = 0;
}
if (alpha == 0) {
newControlPoints[j] = controlPoints[j - 1];
newWeights[j] = weights[j - 1];
}
else if (alpha == 1) {
newControlPoints[j] = controlPoints[j];
newWeights[j] = weights[j];
}
else {
newControlPoints[j] = {};
newControlPoints[j].x = (1 - alpha) * controlPoints[j - 1].x + alpha * controlPoints[j].x;
newControlPoints[j].y = (1 - alpha) * controlPoints[j - 1].y + alpha * controlPoints[j].y;
if (calculateZ) {
newControlPoints[j].z = (1 - alpha) * controlPoints[j - 1].z + alpha * controlPoints[j].z;
}
newWeights[j] = (1 - alpha) * weights[j - 1] + alpha * weights[j];
}
}
return {
controlPoints: newControlPoints, weights: newWeights, knots: newKnots,
};
}
/**
* Checks if all numbers in an array are in increasing order (allows duplicates)
* @param {number[]} arr - The array to check
* @returns {boolean} - True if array is in increasing order, false otherwise
*/
function isIncreasingOrder(arr) {
if (arr.length < 2) {
return true; // An empty array or array with one element is considered in order
}
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
return false; // Found an element that's decreasing
}
}
return true;
}
if (multiplicity === undefined) {
AscCommon.consoleLog("Error: multiplicity is undefined");
return null;
}
if (!isIncreasingOrder(knots)) {
AscCommon.consoleLog("Error: Knots with decreasing elements is not supported. Knots: " + knots);
return null;
}
let knotValue = knots[0];
let knotIndex;
while (true) {
let knotsCount = 0;
for (let i = 0; i < knots.length; i++) {
knotsCount += knots[i] === knotValue ? 1 : 0;
}
knotIndex = knots.indexOf(knotValue);
let insertCount = multiplicity - knotsCount;
insertCount = insertCount < 0 ? 0 : insertCount;
for (let i = 0; i < insertCount; i++) {
let newNURBSdata = insertKnot(controlPoints, weights, knots, knotValue);
if (newNURBSdata == null) {
AscCommon.consoleLog('Unknown error. unexpected t');
return null;
}
controlPoints = newNURBSdata.controlPoints;
weights = newNURBSdata.weights;
knots = newNURBSdata.knots;
}
knotIndex = knotIndex + knotsCount + insertCount;
if (knotIndex === knots.length) {
// out of bounds
break;
}
knotValue = knots[knotIndex];
}
return {controlPoints: controlPoints, weights: weights, knots: knots};
}
/**
*
* @param {{x: Number, y: Number, z? :Number}[]} controlPoints
* @param {Number} degree
* @returns {{
* startPoint: {x: Number, y: Number, z? :Number},
* controlPoints: {x: Number, y: Number, z? :Number}[],
* endPoint: {x: Number, y: Number, z? :Number}
* }[]}
*/
function NURBSnormalizedToBezier(controlPoints, degree) {
let bezierArray = [];
// first Bezier
let nthBezier = {
startPoint: controlPoints[0], controlPoints: []
};
for (let i = 1; i < controlPoints.length; i++) {
const point = controlPoints[i];
if (i % degree === 0) {
nthBezier.endPoint = point;
let nthBezierCopy = JSON.parse(JSON.stringify(nthBezier));
bezierArray.push(nthBezierCopy);
nthBezier = {
startPoint: point, controlPoints: []
};
}
else {
nthBezier.controlPoints.push(point);
}
}
return bezierArray;
}
// Init arguments
let controlPoints = cmd.controlPoints;
let weights = cmd.weights;
let knots = cmd.knots;
let degree = cmd.degree;
for (let j = 0; j < controlPoints.length; j++) {
controlPoints[j].x = this.calculateCommandCoord(gdLst, controlPoints[j].x, cw, dCustomPathCoeffW);
controlPoints[j].y = this.calculateCommandCoord(gdLst, controlPoints[j].y, ch, dCustomPathCoeffH);
}
// if degree === 0 just draw line to SplineStart like visio does
if (degree === 0) {
AscCommon.consoleLog("transform nurbsTo to line because degree is 0");
this.ArrPathCommand.push({id: lineTo, X: controlPoints[1].x, Y: controlPoints[1].y});
lastX = controlPoints[1].x;
lastY = controlPoints[1].y;
break;
}
if (degree + 1 + controlPoints.length !== knots.length) {
AscCommon.consoleLog("Wrong arguments format.", "Degree + 1 + controlPoints.length !== knots.length", degree + 1 + controlPoints.length, "!==", knots.length);
break;
}
// round knots maybe start is clamped
// let precision = 10e13;
// for (let j = 0; j < knots.length; j++) {
// knots[j] = Math.round(knots[j] * precision) / precision;
// }
let clampedStart = true;
for (let j = 0; j < degree; j++) {
// compare first degree + 1 knots
clampedStart = clampedStart === false ? clampedStart : knots[j] === knots[j + 1];
}
if (!clampedStart) {
AscCommon.consoleLog("first degree + 1 knots are not equal. Non clamped start is not yet supported", "Degree is", degree, "knots:", knots);
break;
}
// Convert to Bezier
let newNURBSform = duplicateKnots(controlPoints, weights, knots, degree);
if (newNURBSform === null) {
AscCommon.consoleLog("duplicateKnots() error");
break;
}
let bezierArray = NURBSnormalizedToBezier(newNURBSform.controlPoints, degree);
// change nurbsTo params to draw using bezier
// nurbs degree is equal to each bezier degree
this.ArrPathCommand.push({id: nurbsTo, degree: degree, bezierArray: bezierArray});
lastX = bezierArray[bezierArray.length - 1].endPoint.x;
lastY = bezierArray[bezierArray.length - 1].endPoint.y;
break;
}
default: {
break;
}
}
}
if (bResetPathsInfo) {
delete this.ArrPathCommandInfo;
}
};
Path.prototype.recalculate2 = function (gdLst, bResetPathsInfo) {
let k = 10e-10;
let APCI = this.ArrPathCommandInfo, n = APCI.length, cmd;
let stAng, swAng, lastX, lastY;
for (let i = 0; i < n; ++i) {
cmd = APCI[i];
switch (cmd.id) {
case moveTo:
case lineTo: {
lastX = cmd.X * k;
lastY = cmd.Y * k;
this.ArrPathCommand[i] = {id: cmd.id, X: lastX, Y: lastY};
break;
}
case bezier3: {
lastX = cmd.X1;
lastY = cmd.Y1;
this.ArrPathCommand[i] = {id: bezier3, X0: cmd.X0 * k, Y0: cmd.Y0 * k, X1: lastX, Y1: lastY};
break;
}
case bezier4: {
lastX = cmd.X2;
lastY = cmd.Y2;
this.ArrPathCommand[i] = {
id: bezier4,
X0: cmd.X0 * k,
Y0: cmd.Y0 * k,
X1: cmd.X1 * k,
Y1: cmd.Y1 * k,
X2: lastX,
Y2: lastY
};
break;
}
case arcTo: {
let a1 = cmd.stAng;
let a2 = cmd.stAng + cmd.swAng;
let a3 = cmd.swAng;
stAng = Math.atan2(k * Math.sin(a1 * cToRad), k * Math.cos(a1 * cToRad)) / cToRad;
swAng = Math.atan2(k * Math.sin(a2 * cToRad), k * Math.cos(a2 * cToRad)) / cToRad - cmd.stAng;
if ((swAng > 0) && (a3 < 0)) swAng -= 21600000;
if ((swAng < 0) && (a3 > 0)) swAng += 21600000;
if (swAng == 0 && a3 != 0) swAng = 21600000;
let a = cmd.wR * k;
let b = cmd.hR * k;
let sin2 = Math.sin(stAng * cToRad);
let cos2 = Math.cos(stAng * cToRad);
let _xrad = cos2 / a;
let _yrad = sin2 / b;
let l = 1 / Math.sqrt(_xrad * _xrad + _yrad * _yrad);
let xc = lastX - l * cos2;
let yc = lastY - l * sin2;
let sin1 = Math.sin((stAng + swAng) * cToRad);
let cos1 = Math.cos((stAng + swAng) * cToRad);
let _xrad1 = cos1 / a;
let _yrad1 = sin1 / b;
let l1 = 1 / Math.sqrt(_xrad1 * _xrad1 + _yrad1 * _yrad1);
this.ArrPathCommand[i] = {
id: arcTo,
stX: lastX,
stY: lastY,
wR: cmd.wR * k,
hR: cmd.hR * k,
stAng: stAng * cToRad,
swAng: swAng * cToRad
};
lastX = xc + l1 * cos1;
lastY = yc + l1 * sin1;
break;
}
case close: {
this.ArrPathCommand[i] = {id: close};
break;
}
default: {
break;
}
}
}
// if(bResetPathsInfo)
{
delete this.ArrPathCommandInfo;
}
};
Path.prototype.draw = function (shape_drawer) {
if (shape_drawer.bIsCheckBounds === true && this.fill == "none") {
// это для текстур
return;
}
let bIsDrawLast = false;
let path = this.ArrPathCommand;
shape_drawer._s();
for (let j = 0, l = path.length; j < l; ++j) {
let cmd = path[j];
switch (cmd.id) {
case moveTo: {
bIsDrawLast = true;
shape_drawer._m(cmd.X, cmd.Y);
break;
}
case lineTo: {
bIsDrawLast = true;
shape_drawer._l(cmd.X, cmd.Y);
break;
}
case bezier3: {
bIsDrawLast = true;
shape_drawer._c2(cmd.X0, cmd.Y0, cmd.X1, cmd.Y1);
break;
}
case bezier4: {
bIsDrawLast = true;
shape_drawer._c(cmd.X0, cmd.Y0, cmd.X1, cmd.Y1, cmd.X2, cmd.Y2);
break;
}
case arcTo: {
bIsDrawLast = true;
ArcToCurvers(shape_drawer, cmd.stX, cmd.stY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng, cmd.ellipseRotation /*ellipseRotation added later*/);
break;
}
case close: {
shape_drawer._z();
break;
}
case ellipticalArcTo: {
bIsDrawLast = true;
ArcToCurvers(shape_drawer, cmd.stX, cmd.stY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng, cmd.ellipseRotation);
break;
}
case nurbsTo: {
bIsDrawLast = true;
cmd.bezierArray.forEach(function (bezier) {
if (cmd.degree === 2) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
shape_drawer._c2(cp1x, cp1y, endx, endy);
}
else if (cmd.degree === 3) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let cp2x = bezier.controlPoints[1].x;
let cp2y = bezier.controlPoints[1].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
shape_drawer._c(cp1x, cp1y, cp2x, cp2y, endx, endy);
}
else {
let startPoint = bezier.startPoint;
let controlPoints = bezier.controlPoints;
let endPoint = bezier.endPoint;
// unlike in other commands pass start point bcs other commands use canvas system
// end point for next command start point
// (which we cant get explicitly for _cN calculation)
shape_drawer._cN(startPoint, controlPoints, endPoint);
}
});
break;
}
}
}
if (bIsDrawLast) {
shape_drawer.drawFillStroke(true, this.fill, this.stroke && !shape_drawer.bIsNoStrokeAttack);
}
shape_drawer._e();
};
Path.prototype.check_bounds = function (checker, geom) {
let path = this.ArrPathCommand;
for (let j = 0, l = path.length; j < l; ++j) {
let cmd = path[j];
switch (cmd.id) {
case moveTo: {
checker._m(cmd.X, cmd.Y);
break;
}
case lineTo: {
checker._l(cmd.X, cmd.Y);
break;
}
case bezier3: {
checker._c2(cmd.X0, cmd.Y0, cmd.X1, cmd.Y1);
break;
}
case bezier4: {
checker._c(cmd.X0, cmd.Y0, cmd.X1, cmd.Y1, cmd.X2, cmd.Y2);
break;
}
case arcTo: {
ArcToCurvers(checker, cmd.stX, cmd.stY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng, cmd.ellipseRotation /*ellipseRotation added later*/);
break;
}
case close: {
checker._z();
break;
}
case ellipticalArcTo: {
ArcToCurvers(checker, cmd.stX, cmd.stY, cmd.wR, cmd.hR, cmd.stAng, cmd.swAng, cmd.ellipseRotation);
break;
}
case nurbsTo: {
cmd.bezierArray.forEach(function (bezier) {
if (cmd.degree === 2) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
checker._c2(cp1x, cp1y, endx, endy);
}
else if (cmd.degree === 3) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let cp2x = bezier.controlPoints[1].x;
let cp2y = bezier.controlPoints[1].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
checker._c(cp1x, cp1y, cp2x, cp2y, endx, endy);
}
else {
let startPoint = bezier.startPoint;
let controlPoints = bezier.controlPoints;
let endPoint = bezier.endPoint;
let pointsToCheck = controlPoints.concat(endPoint);
pointsToCheck = pointsToCheck.concat(startPoint);
checker.checkPoints(pointsToCheck);
}
});
break;
}
}
}
};
Path.prototype.hitInInnerArea = function (canvasContext, x, y) {
if (this.fill === "none") return false;
let _arr_commands = this.ArrPathCommand;
let _commands_count = _arr_commands.length;
let _command_index;
let _command;
canvasContext.beginPath();
for (_command_index = 0; _command_index < _commands_count; ++_command_index) {
_command = _arr_commands[_command_index];
switch (_command.id) {
case moveTo: {
canvasContext.moveTo(_command.X, _command.Y);
break;
}
case lineTo: {
canvasContext.lineTo(_command.X, _command.Y);
break;
}
case arcTo: {
ArcToOnCanvas(canvasContext, _command.stX, _command.stY, _command.wR, _command.hR, _command.stAng, _command.swAng);
break;
}
case bezier3: {
canvasContext.quadraticCurveTo(_command.X0, _command.Y0, _command.X1, _command.Y1);
break;
}
case bezier4: {
canvasContext.bezierCurveTo(_command.X0, _command.Y0, _command.X1, _command.Y1, _command.X2, _command.Y2);
break;
}
case close: {
canvasContext.closePath();
}
}
}
return !!canvasContext.isPointInPath(x, y);
};
Path.prototype.hitInPath = function (canvasContext, x, y, oAddingPoint, _path_index) {
let _arr_commands = this.ArrPathCommand;
let _commands_count = _arr_commands.length;
let _command_index;
let _command;
let _last_x, _last_y;
let _begin_x, _begin_y;
for (_command_index = 0; _command_index < _commands_count; ++_command_index) {
_command = _arr_commands[_command_index];
switch (_command.id) {
case moveTo: {
_last_x = _command.X;
_last_y = _command.Y;
_begin_x = _command.X;
_begin_y = _command.Y;
break;
}
case lineTo: {
if (HitInLine(canvasContext, x, y, _last_x, _last_y, _command.X, _command.Y)) return true;
_last_x = _command.X;
_last_y = _command.Y;
break;
}
case arcTo: {
if (HitToArc(canvasContext, x, y, _command.stX, _command.stY, _command.wR, _command.hR, _command.stAng, _command.swAng)) return true;
_last_x = (_command.stX - _command.wR * Math.cos(_command.stAng) + _command.wR * Math.cos(_command.swAng));
_last_y = (_command.stY - _command.hR * Math.sin(_command.stAng) + _command.hR * Math.sin(_command.swAng));
break;
}
case bezier3: {
if (HitInBezier3(canvasContext, x, y, _last_x, _last_y, _command.X0, _command.Y0, _command.X1, _command.Y1)) return true;
_last_x = _command.X1;
_last_y = _command.Y1;
break;
}
case bezier4: {
if (HitInBezier4(canvasContext, x, y, _last_x, _last_y, _command.X0, _command.Y0, _command.X1, _command.Y1, _command.X2, _command.Y2)) {
if (oAddingPoint) {
oAddingPoint.pathIndex = _path_index;
oAddingPoint.commandIndex = _command_index;
}
return true;
}
_last_x = _command.X2;
_last_y = _command.Y2;
break;
}
case close: {
if (HitInLine(canvasContext, x, y, _last_x, _last_y, _begin_x, _begin_y)) return true;
}
}
}
return false;
};
Path.prototype.isSmartLine = function () {
if (this.ArrPathCommand.length != 2) return false;
if (this.ArrPathCommand[0].id == moveTo && this.ArrPathCommand[1].id == lineTo) {
if (Math.abs(this.ArrPathCommand[0].X - this.ArrPathCommand[1].X) < 0.0001) return true;
if (Math.abs(this.ArrPathCommand[0].Y - this.ArrPathCommand[1].Y) < 0.0001) return true;
}
return false;
};
Path.prototype.isSmartRect = function () {
if (this.ArrPathCommand.length != 5) return false;
if (this.ArrPathCommand[0].id != moveTo || this.ArrPathCommand[1].id != lineTo || this.ArrPathCommand[2].id != lineTo || this.ArrPathCommand[3].id != lineTo || (this.ArrPathCommand[4].id != lineTo && this.ArrPathCommand[4].id != close)) return false;
let _float_eps = 0.0001;
if (Math.abs(this.ArrPathCommand[0].X - this.ArrPathCommand[1].X) < _float_eps) {
if (Math.abs(this.ArrPathCommand[1].Y - this.ArrPathCommand[2].Y) < _float_eps) {
if (Math.abs(this.ArrPathCommand[2].X - this.ArrPathCommand[3].X) < _float_eps && Math.abs(this.ArrPathCommand[3].Y - this.ArrPathCommand[0].Y) < _float_eps) {
if (this.ArrPathCommand[4].id == close) return true;
if (Math.abs(this.ArrPathCommand[0].X - this.ArrPathCommand[4].X) < _float_eps && Math.abs(this.ArrPathCommand[0].Y - this.ArrPathCommand[4].Y) < _float_eps) {
return true;
}
}
}
}
else if (Math.abs(this.ArrPathCommand[0].Y - this.ArrPathCommand[1].Y) < _float_eps) {
if (Math.abs(this.ArrPathCommand[1].X - this.ArrPathCommand[2].X) < _float_eps) {
if (Math.abs(this.ArrPathCommand[2].Y - this.ArrPathCommand[3].Y) < _float_eps && Math.abs(this.ArrPathCommand[3].X - this.ArrPathCommand[0].X) < _float_eps) {
if (this.ArrPathCommand[4].id == close) return true;
if (Math.abs(this.ArrPathCommand[0].X - this.ArrPathCommand[4].X) < _float_eps && Math.abs(this.ArrPathCommand[0].Y - this.ArrPathCommand[4].Y) < _float_eps) {
return true;
}
}
}
}
return false;
};
Path.prototype.drawSmart = function (shape_drawer) {
let _graphics = shape_drawer.Graphics;
let _full_trans = _graphics.m_oFullTransform;
if (!_graphics || !_full_trans || undefined == _graphics.m_bIntegerGrid || true === shape_drawer.bIsNoSmartAttack) return this.draw(shape_drawer);
let bIsTransformed = (_full_trans.shx == 0 && _full_trans.shy == 0) ? false : true;
if (bIsTransformed) return this.draw(shape_drawer);
let isLine = this.isSmartLine();
let isRect = false;
if (!isLine) isRect = this.isSmartRect();
if (window["NATIVE_EDITOR_ENJINE"] || (!isLine && !isRect)) return this.draw(shape_drawer);
let _old_int = _graphics.m_bIntegerGrid;
if (false == _old_int) _graphics.SetIntegerGrid(true);
let dKoefMMToPx = Math.max(_graphics.m_oCoordTransform.sx, 0.001);
let _ctx = _graphics.m_oContext;
let bIsStroke = (shape_drawer.bIsNoStrokeAttack || (this.stroke !== true)) ? false : true;
let bIsEven = false;
if (bIsStroke) {
let _lineWidth = Math.max((shape_drawer.StrokeWidth * dKoefMMToPx + 0.5) >> 0, 1);
_ctx.lineWidth = _lineWidth;
if ((_lineWidth & 0x01) == 0x01) bIsEven = true;
if (_graphics.dash_no_smart) {
for (let index = 0; index < _graphics.dash_no_smart.length; index++) _graphics.dash_no_smart[index] = (_graphics.m_oCoordTransform.sx * _graphics.dash_no_smart[index] + 0.5) >> 0;
_graphics.m_oContext.setLineDash(_graphics.dash_no_smart);
_graphics.dash_no_smart = null;
}
}
let bIsDrawLast = false;
let path = this.ArrPathCommand;
shape_drawer._s();
if (!isRect) {
for (let j = 0, l = path.length; j < l; ++j) {
let cmd = path[j];
switch (cmd.id) {
case moveTo: {
bIsDrawLast = true;
let _x = (_full_trans.TransformPointX(cmd.X, cmd.Y)) >> 0;
let _y = (_full_trans.TransformPointY(cmd.X, cmd.Y)) >> 0;
if (bIsEven) {
_x -= 0.5;
_y -= 0.5;
}
_ctx.moveTo(_x, _y);
break;
}
case lineTo: {
bIsDrawLast = true;
let _x = (_full_trans.TransformPointX(cmd.X, cmd.Y)) >> 0;
let _y = (_full_trans.TransformPointY(cmd.X, cmd.Y)) >> 0;
if (bIsEven) {
_x -= 0.5;
_y -= 0.5;
}
_ctx.lineTo(_x, _y);
break;
}
case close: {
_ctx.closePath();
break;
}
}
}
}
else {
let minX = 100000;
let minY = 100000;
let maxX = -100000;
let maxY = -100000;
bIsDrawLast = true;
for (let j = 0, l = path.length; j < l; ++j) {
let cmd = path[j];
switch (cmd.id) {
case moveTo:
case lineTo: {
if (minX > cmd.X) minX = cmd.X;
if (minY > cmd.Y) minY = cmd.Y;
if (maxX < cmd.X) maxX = cmd.X;
if (maxY < cmd.Y) maxY = cmd.Y;
break;
}
default:
break;
}
}
let _x1 = (_full_trans.TransformPointX(minX, minY)) >> 0;
let _y1 = (_full_trans.TransformPointY(minX, minY)) >> 0;
let _x2 = (_full_trans.TransformPointX(maxX, maxY)) >> 0;
let _y2 = (_full_trans.TransformPointY(maxX, maxY)) >> 0;
if (bIsEven) _ctx.rect(_x1 + 0.5, _y1 + 0.5, _x2 - _x1, _y2 - _y1); else _ctx.rect(_x1, _y1, _x2 - _x1, _y2 - _y1);
}
if (bIsDrawLast) {
shape_drawer.drawFillStroke(true, this.fill, bIsStroke);
}
shape_drawer._e();
if (false == _old_int) _graphics.SetIntegerGrid(false);
};
Path.prototype.isEmpty = function () {
return this.ArrPathCommandInfo.length <= 0;
};
Path.prototype.checkBetweenPolygons = function (oBoundsController, oPolygonWrapper1, oPolygonWrapper2) {
};
Path.prototype.checkByPolygon = function (oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds) {
};
Path.prototype.transform = function (oTransform, dKoeff) {
};
Path.prototype.getSVGPath = function (oTransform, dStartX, dStartY) {
let aCmds = this.ArrPathCommand;
let sSVG = "";
let oPresentation = editor.WordControl.m_oLogicDocument;
let dSlideWidth = oPresentation.GetWidthMM();
let dSlideHeight = oPresentation.GetHeightMM();
let calcX = function (dX, dY) {
let dX_ = oTransform.TransformPointX(dX, dY);
return ((((dX_ - dStartX) / dSlideWidth) * 1000 + 0.5 >> 0) / 1000) + "";
}
let calcY = function (dX, dY) {
let dY_ = oTransform.TransformPointY(dX, dY);
return ((((dY_ - dStartY) / dSlideHeight) * 1000 + 0.5 >> 0) / 1000) + "";
}
let nLastCmd = null, nLastX = null, nLastY = null;
for (let nCmd = 0; nCmd < aCmds.length; ++nCmd) {
let oCmd = aCmds[nCmd];
if (sSVG.length > 0) {
sSVG += " ";
}
switch (oCmd.id) {
case moveTo: {
if (nLastX !== null && nLastY !== null && AscFormat.fApproxEqual(nLastX, oCmd.X) && AscFormat.fApproxEqual(nLastY, oCmd.Y)) {
break;
}
sSVG += "M ";
sSVG += calcX(oCmd.X, oCmd.Y);
sSVG += " ";
sSVG += calcY(oCmd.X, oCmd.Y);
nLastX = oCmd.X;
nLastY = oCmd.Y;
break;
}
case lineTo: {
if (nLastX !== null && nLastY !== null && AscFormat.fApproxEqual(nLastX, oCmd.X) && AscFormat.fApproxEqual(nLastY, oCmd.Y)) {
break;
}
sSVG += "L ";
sSVG += calcX(oCmd.X, oCmd.Y);
sSVG += " ";
sSVG += calcY(oCmd.X, oCmd.Y);
nLastX = oCmd.X;
nLastY = oCmd.Y;
break;
}
case bezier4: {
sSVG += "C ";
sSVG += calcX(oCmd.X0, oCmd.Y0);
sSVG += " ";
sSVG += calcY(oCmd.X0, oCmd.Y0);
sSVG += " ";
sSVG += calcX(oCmd.X1, oCmd.Y1);
sSVG += " ";
sSVG += calcY(oCmd.X1, oCmd.Y1);
sSVG += " ";
sSVG += calcX(oCmd.X2, oCmd.Y2);
sSVG += " ";
sSVG += calcY(oCmd.X2, oCmd.Y2);
nLastX = oCmd.X2;
nLastY = oCmd.Y2;
break;
}
case close: {
sSVG += "Z";
nLastCmd = null;
nLastX = null;
nLastY = null;
break;
}
default: {
break;
}
}
nLastCmd = oCmd.id;
}
return sSVG;
};
Path.prototype.isInk = function () {
const nCmdCount = this.ArrPathCommandInfo.length;
for (let nCmd = 0; nCmd < nCmdCount; ++nCmd) {
let oCmd = this.ArrPathCommandInfo[nCmd];
if (oCmd.id === close) {
return false;
}
if (oCmd.id === arcTo) {
return false;
}
}
return true;
};
Path.prototype.convertToBezierCurves = function (oPath, oTransform, bConvertCurvesOnly) {
const nCmdCount = this.ArrPathCommandInfo.length;
let dX0, dY0, dX1, dY1, dX2, dY2;
let oLastMoveTo = null;
let dLastX, dLastY;
let oFirstCmd = this.ArrPathCommand[0];
if (!oFirstCmd) {
return null;
}
if (oFirstCmd.id !== moveTo) {
return null;
}
for (let nCmd = 0; nCmd < nCmdCount; ++nCmd) {
let oCmd = this.ArrPathCommand[nCmd];
switch (oCmd.id) {
case moveTo: {
dX0 = oTransform.TransformPointX(oCmd.X, oCmd.Y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oCmd.X, oCmd.Y) * 36000 >> 0;
oPath.moveTo(dX0, dY0);
oLastMoveTo = oCmd;
dLastX = oCmd.X;
dLastY = oCmd.Y;
break;
}
case lineTo: {
dX0 = oTransform.TransformPointX(oCmd.X, oCmd.Y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oCmd.X, oCmd.Y) * 36000 >> 0;
dX0 = oTransform.TransformPointX(dLastX + (oCmd.X - dLastX) * (1 / 3), dLastY + (oCmd.Y - dLastY) * (1 / 3)) * 36000 >> 0;
dY0 = oTransform.TransformPointY(dLastX + (oCmd.X - dLastX) * (1 / 3), dLastY + (oCmd.Y - dLastY) * (1 / 3)) * 36000 >> 0;
dX1 = oTransform.TransformPointX(dLastX + (oCmd.X - dLastX) * (2 / 3), dLastY + (oCmd.Y - dLastY) * (2 / 3)) * 36000 >> 0;
dY1 = oTransform.TransformPointY(dLastX + (oCmd.X - dLastX) * (2 / 3), dLastY + (oCmd.Y - dLastY) * (2 / 3)) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oCmd.X, oCmd.Y) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oCmd.X, oCmd.Y) * 36000 >> 0;
(bConvertCurvesOnly) ? oPath.lnTo(dX2, dY2) : oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oCmd.X;
dLastY = oCmd.Y;
break;
}
case bezier3: {
dX0 = oTransform.TransformPointX(oCmd.X0, oCmd.Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oCmd.X0, oCmd.Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(oCmd.X1, oCmd.Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(oCmd.X1, oCmd.Y1) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX1, dY1);
dLastX = oCmd.X1;
dLastY = oCmd.Y1;
break;
}
case bezier4: {
dX0 = oTransform.TransformPointX(oCmd.X0, oCmd.Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oCmd.X0, oCmd.Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(oCmd.X1, oCmd.Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(oCmd.X1, oCmd.Y1) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oCmd.X2, oCmd.Y2) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oCmd.X2, oCmd.Y2) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oCmd.X2;
dLastY = oCmd.Y2;
break;
}
case arcTo: {
let oPathAccumulator = new AscFormat.PathAccumulator();
ArcToCurvers(oPathAccumulator, oCmd.stX, oCmd.stY, oCmd.wR, oCmd.hR, oCmd.stAng, oCmd.swAng);
let aArcToCommands = oPathAccumulator.pathCommand;
for (let nArcCmd = 0; nArcCmd < aArcToCommands.length; ++nArcCmd) {
let oArcToCmd = aArcToCommands[nArcCmd];
switch (oArcToCmd.id) {
case AscFormat.moveTo: {
break;
}
case AscFormat.bezier4: {
dX0 = oTransform.TransformPointX(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oArcToCmd.X2;
dLastY = oArcToCmd.Y2;
break;
}
}
}
break;
}
case close: {
if (!bConvertCurvesOnly && oLastMoveTo) {
let dXM = oTransform.TransformPointX(oLastMoveTo.X, oLastMoveTo.Y);
let dYM = oTransform.TransformPointY(oLastMoveTo.X, oLastMoveTo.Y);
let dLastXM = oTransform.TransformPointX(dLastX, dLastY);
let dLastYM = oTransform.TransformPointY(dLastX, dLastY);
dX0 = (dLastXM + (dXM - dLastXM) / 4) * 36000 >> 0;
dY0 = (dLastYM + (dYM - dLastYM) / 4) * 36000 >> 0;
dX1 = (dLastXM + 3 * (dXM - dLastXM) / 4) * 36000 >> 0;
dY1 = (dLastYM + 3 * (dYM - dLastYM) / 4) * 36000 >> 0;
dX2 = (dXM) * 36000 >> 0;
dY2 = (dYM) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
}
oPath.close();
break;
}
case ellipticalArcTo: {
let oPathAccumulator = new AscFormat.PathAccumulator();
ArcToCurvers(oPathAccumulator, oCmd.stX, oCmd.stY, oCmd.wR, oCmd.hR, oCmd.stAng, oCmd.swAng, oCmd.ellipseRotation);
let aArcToCommands = oPathAccumulator.pathCommand;
for (let nArcCmd = 0; nArcCmd < aArcToCommands.length; ++nArcCmd) {
let oArcToCmd = aArcToCommands[nArcCmd];
switch (oArcToCmd.id) {
case AscFormat.moveTo: {
break;
}
case AscFormat.bezier4: {
dX0 = oTransform.TransformPointX(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oArcToCmd.X2;
dLastY = oArcToCmd.Y2;
break;
}
}
}
break;
}
case nurbsTo: {
oCmd.bezierArray.forEach(function (bezier) {
if (oCmd.degree === 2) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
dX0 = oTransform.TransformPointX(cp1x, cp1y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(cp1x, cp1y) * 36000 >> 0;
dX1 = oTransform.TransformPointX(endx, endy) * 36000 >> 0;
dY1 = oTransform.TransformPointY(endx, endy) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX1, dY1);
dLastX = endx;
dLastY = endy;
}
else if (oCmd.degree === 3) {
let cp1x = bezier.controlPoints[0].x;
let cp1y = bezier.controlPoints[0].y;
let cp2x = bezier.controlPoints[1].x;
let cp2y = bezier.controlPoints[1].y;
let endx = bezier.endPoint.x;
let endy = bezier.endPoint.y;
dX0 = oTransform.TransformPointX(cp1x, cp1y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(cp1x, cp1y) * 36000 >> 0;
dX1 = oTransform.TransformPointX(cp2x, cp2y) * 36000 >> 0;
dY1 = oTransform.TransformPointY(cp2x, cp2y) * 36000 >> 0;
dX2 = oTransform.TransformPointX(endx, endy) * 36000 >> 0;
dY2 = oTransform.TransformPointY(endx, endy) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = endx;
dLastY = endy;
}
else {
let startPoint = bezier.startPoint;
let controlPoints = bezier.controlPoints;
let endPoint = bezier.endPoint;
let pointsToCheck = controlPoints.concat(endPoint);
pointsToCheck = pointsToCheck.concat(startPoint);
for (let pt = 0; pt < pointsToCheck.length; ++pt) {
let oPt = pointsToCheck[pt];
dX0 = oTransform.TransformPointX(dLastX + (oPt.x - dLastX) * (1 / 3), dLastY + (oPt.y - dLastY) * (1 / 3)) * 36000 >> 0;
dY0 = oTransform.TransformPointY(dLastX + (oPt.x - dLastX) * (1 / 3), dLastY + (oPt.y - dLastY) * (1 / 3)) * 36000 >> 0;
dX1 = oTransform.TransformPointX(dLastX + (oPt.x - dLastX) * (2 / 3), dLastY + (oPt.y - dLastY) * (2 / 3)) * 36000 >> 0;
dY1 = oTransform.TransformPointY(dLastX + (oPt.x - dLastX) * (2 / 3), dLastY + (oPt.y - dLastY) * (2 / 3)) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oPt.x, oPt.y) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oPt.x, oPt.y) * 36000 >> 0;
(bConvertCurvesOnly) ? oPath.lnTo(dX2, dY2) : oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oPt.x;
dLastY = oPt.y;
}
}
});
break;
}
}
}
oPath.recalculate({}, true);
};
Path.prototype.isEqual = function (oPath) {
if (!oPath) {
return false;
}
if (this.stroke !== oPath.stroke) {
return false;
}
if (this.extrusionOk !== oPath.extrusionOk) {
return false;
}
if (this.fill !== oPath.fill) {
return false;
}
if (this.pathH !== oPath.pathH) {
return false;
}
if (this.pathW !== oPath.pathW) {
return false;
}
if (this.ArrPathCommandInfo.length !== oPath.ArrPathCommandInfo.length) {
return false;
}
for (let nCmd = 0; nCmd < this.ArrPathCommandInfo.length; ++nCmd) {
let oCmd1 = this.ArrPathCommandInfo[nCmd];
let oCmd2 = oPath.ArrPathCommandInfo[nCmd];
if (oCmd1.id !== oCmd2.id) {
return false;
}
switch (oCmd1.id) {
case moveTo:
case lineTo: {
if (oCmd1.X !== oCmd2.X) {
return false;
}
if (oCmd1.Y !== oCmd2.Y) {
return false;
}
break;
}
case bezier3: {
if (oCmd1.X0 !== oCmd2.X0) {
return false;
}
if (oCmd1.X1 !== oCmd2.X1) {
return false;
}
if (oCmd1.Y0 !== oCmd2.Y0) {
return false;
}
if (oCmd1.Y1 !== oCmd2.Y1) {
return false;
}
break;
}
case bezier4: {
if (oCmd1.X0 !== oCmd2.X0) {
return false;
}
if (oCmd1.X1 !== oCmd2.X1) {
return false;
}
if (oCmd1.X2 !== oCmd2.X2) {
return false;
}
if (oCmd1.Y0 !== oCmd2.Y0) {
return false;
}
if (oCmd1.Y1 !== oCmd2.Y1) {
return false;
}
if (oCmd1.Y2 !== oCmd2.Y2) {
return false;
}
break;
}
case arcTo: {
if (oCmd1.wR !== oCmd2.wR) {
return false;
}
if (oCmd1.hR !== oCmd2.hR) {
return false;
}
if (oCmd1.stAng !== oCmd2.stAng) {
return false;
}
if (oCmd1.swAng !== oCmd2.swAng) {
return false;
}
break;
}
case close: {
break;
}
default: {
break;
}
}
}
return true;
};
function getNonDuplicateCommands(path) {
const commands = [];
let prevCommand = null;
function isDuplicate(cmd1, cmd2) {
if (cmd1.id !== cmd2.id)
return false;
if (cmd1.id === AscFormat.moveTo || cmd1.id === AscFormat.lineTo)
return cmd1.X === cmd2.X && cmd1.Y === cmd2.Y;
if (cmd1.id === AscFormat.bezier3)
return cmd1.X0 === cmd2.X0 && cmd1.Y0 === cmd2.Y0 &&
cmd1.X1 === cmd2.X1 && cmd1.Y1 === cmd2.Y1;
if (cmd1.id === AscFormat.bezier4)
return cmd1.X0 === cmd2.X0 && cmd1.Y0 === cmd2.Y0 &&
cmd1.X1 === cmd2.X1 && cmd1.Y1 === cmd2.Y1 &&
cmd1.X2 === cmd2.X2 && cmd1.Y2 === cmd2.Y2;
if (cmd1.id === AscFormat.arcTo)
return cmd1.wR === cmd2.wR && cmd1.hR === cmd2.hR &&
cmd1.stAng === cmd2.stAng && cmd1.swAng === cmd2.swAng;
return false;
}
path.ArrPathCommand.forEach(function (command) {
if (prevCommand) {
if (command.id === AscFormat.moveTo && prevCommand.id === AscFormat.moveTo) {
prevCommand = command;
return;
}
if (isDuplicate(prevCommand, command)) {
return;
}
}
commands.push(command);
prevCommand = command;
});
return commands;
}
Path.prototype.getContinuousSubpaths = function () {
const convertedPath = new AscFormat.Path();
const transform = new AscCommon.CMatrix();
AscFormat.ExecuteNoHistory(this.convertToBezierCurves, this, [convertedPath, transform, true]);
// convertedPath contains cubicBezierTo, lineTo, and moveTo commands only
const subpaths = [];
let currentSubpath;
// Since we only draw geometries that start with a "moveTo" command,
// the first command in the 'commands' array is guaranteed to be "moveTo"
const commands = getNonDuplicateCommands(convertedPath);
commands.forEach(function (command, index) {
if (command.id === AscFormat.moveTo) {
if (currentSubpath) {
subpaths.push(currentSubpath);
}
currentSubpath = new Path();
}
currentSubpath.ArrPathCommand.push(command);
});
if (currentSubpath) {
subpaths.push(currentSubpath);
}
return subpaths;
};
function getClosestIntersectionWithPath(circleCenter, circleRadius, pathCommands, searchFromEnd) {
let prevPoint;
const allIntersections = [];
for (let i = 0; i < pathCommands.length; i++) {
const command = pathCommands[i];
let intersections = [];
if (command.id === AscFormat.moveTo) {
prevPoint = { x: command.X, y: command.Y };
continue;
}
if (command.id === AscFormat.lineTo) {
intersections = getCircleIntersectionsWithLine(circleCenter, circleRadius, prevPoint, { x: command.X, y: command.Y });
prevPoint = { x: command.X, y: command.Y };
}
if (command.id === AscFormat.bezier4) {
intersections = getCircleIntersectionsWithBezierCurve(
circleCenter, circleRadius,
prevPoint,
{ x: command.X0, y: command.Y0 },
{ x: command.X1, y: command.Y1 },
{ x: command.X2, y: command.Y2 },
);
prevPoint = { x: command.X2, y: command.Y2 };
}
if (intersections.length > 0) {
allIntersections.push(intersections);
}
}
if (allIntersections.length === 0) {
return null;
}
const targetIntersections = searchFromEnd
? allIntersections[allIntersections.length - 1]
: allIntersections[0];
const closestPoint = targetIntersections.reduce(function (best, current) {
const isCurrentBetter = searchFromEnd
? current.t > best.t
: current.t < best.t;
return isCurrentBetter ? current : best;
}, targetIntersections[0]);
return closestPoint;
}
function getCircleIntersectionsWithLine(circleCenter, circleRadius, lineStart, lineEnd) {
const cx = circleCenter.x, cy = circleCenter.y;
const x1 = lineStart.x, y1 = lineStart.y;
const x2 = lineEnd.x, y2 = lineEnd.y;
const dx = x2 - x1;
const dy = y2 - y1;
// At² + Bt + C = 0
const A = Math.pow(dx, 2) + Math.pow(dy, 2);
const B = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
const C = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - Math.pow(circleRadius, 2);
const D = Math.pow(B, 2) - 4 * A * C;
if (D < 0) {
return [];
}
const sqrtD = Math.sqrt(D);
const t1 = (-B - sqrtD) / (2 * A);
const t2 = (-B + sqrtD) / (2 * A);
const intersections = [];
// Проверяем, находятся ли точки пересечения в пределах отрезка (t в диапазоне [0,1])
if (t1 >= 0 && t1 <= 1) {
intersections.push({
t: (t1 + t2) / 2,
x: x1 + t1 * dx,
y: y1 + t1 * dy
});
}
if (t2 >= 0 && t2 <= 1) {
intersections.push({
t: (t1 + t2) / 2,
x: x1 + t2 * dx,
y: y1 + t2 * dy
});
}
return intersections;
}
function getCircleIntersectionsWithBezierCurve(circleCenter, circleRadius, p0, p1, p2, p3) {
// Method of Newton-Raphson (keldysh.ru/comma/html/nonlinear/newton.html)
function findIntersection() {
const intersections = [];
const epsilon = 1e-6;
const maxIterations = 100;
const step = 0.01;
for (let t = 0; t <= 1; t += step) {
const tLow = t;
const tHigh = t + step;
const lowPoint = getBezierCurvePointAt(tLow, p0, p1, p2, p3);
const highPoint = getBezierCurvePointAt(tHigh, p0, p1, p2, p3);
const lowDist = getLineLength(lowPoint, circleCenter) - circleRadius;
const highDist = getLineLength(highPoint, circleCenter) - circleRadius;
if (Math.abs(lowDist) < epsilon) {
intersections.push(lowPoint);
}
if (Math.abs(highDist) < epsilon) {
intersections.push(highPoint);
}
if (lowDist * highDist < 0) {
let tNew = (tLow + tHigh) / 2;
for (let i = 0; i < maxIterations; i++) {
const point = getBezierCurvePointAt(tNew, p0, p1, p2, p3);
const dist = getLineLength(point, circleCenter) - circleRadius;
if (Math.abs(dist) < epsilon) {
intersections.push(point);
break;
}
const dt = 1e-4;
const pointDt = getBezierCurvePointAt(tNew + dt, p0, p1, p2, p3);
const distDt = getLineLength(pointDt, circleCenter) - circleRadius;
const derivative = (distDt - dist) / dt;
if (Math.abs(derivative) < epsilon) {
break;
}
tNew = tNew - dist / derivative;
if (tNew < 0 || tNew > 1) {
break;
};
}
}
}
return intersections;
}
return findIntersection();
}
function getBezierCurvePointAt(t, p0, p1, p2, p3) {
const x = Math.pow(1 - t, 3) * p0.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * Math.pow(t, 2) * p2.x + Math.pow(t, 3) * p3.x;
const y = Math.pow(1 - t, 3) * p0.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * Math.pow(t, 2) * p2.y + Math.pow(t, 3) * p3.y;
return { x: x, y: y, t: t };
}
Path.prototype.getHeadArrowAngle = function (arrowLength) {
// This path should contain cubicBezierTo, lineTo, and moveTo commands only,
// describe a continuous curve and start with the "moveTo" command
if (!AscFormat.isRealNumber(arrowLength)) {
arrowLength = 0.01;
}
const commands = this.ArrPathCommand;
if (commands.length <= 1) {
return null;
}
const arrowTipPoint = { x: commands[0].X, y: commands[0].Y };
const arrowBasePoint = getClosestIntersectionWithPath(arrowTipPoint, arrowLength, commands, false);
if (!arrowBasePoint) {
return null;
}
const diffX = arrowTipPoint.x - arrowBasePoint.x;
const diffY = arrowTipPoint.y - arrowBasePoint.y;
const angleInRadians = Math.atan2(diffY, diffX);
const angleInDegrees = angleInRadians * 180 / Math.PI;
return angleInDegrees;
};
Path.prototype.getTailArrowAngle = function (arrowLength) {
// This path should contain cubicBezierTo, lineTo, and moveTo commands only,
// describe a continuous curve and start with the "moveTo" command
if (!AscFormat.isRealNumber(arrowLength)) {
arrowLength = 0.01;
}
const commands = this.ArrPathCommand;
if (commands.length <= 1) {
return null;
}
function getPathEndPoint(commands) {
for (let i = commands.length - 1; i >= 0; i--) {
const command = commands[i];
if (command.id === AscFormat.lineTo) {
return { x: command.X, y: command.Y };
}
if (command.id === AscFormat.bezier4) {
return { x: command.X2, y: command.Y2 };
}
}
return null;
}
const pathEndPoint = getPathEndPoint(commands);
const arrowTipPoint = { x: pathEndPoint.x, y: pathEndPoint.y };
const arrowBasePoint = getClosestIntersectionWithPath(arrowTipPoint, arrowLength, commands, true);
if (!arrowBasePoint) {
return null;
}
const diffX = arrowTipPoint.x - arrowBasePoint.x;
const diffY = arrowTipPoint.y - arrowBasePoint.y;
const angleInRadians = Math.atan2(diffY, diffX);
const angleInDegrees = angleInRadians * 180 / Math.PI;
return angleInDegrees;
};
Path.prototype.isValid = function () {
if(this.ArrPathCommand.length === 0) return false;
let oFirstCmd = this.ArrPathCommand[0];
return oFirstCmd.id === moveTo;
};
Path.prototype.clear = function () {};
function CPathCmd() {
AscFormat.CBaseNoIdObject.call(this);
this.pts = [];
}
AscFormat.InitClass(CPathCmd, AscFormat.CBaseNoIdObject, 0);
function CheckPointByPaths(dX, dY, dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2) {
let cX, cY, point, topX, topY, bottomX, bottomY;
cX = (dX - dMinX) / dWidth;
cY = (dY - dMinY) / dHeight;
if (cX > 1) {
cX = 1;
}
if (cX < 0) {
cX = 0;
}
point = oPolygonWrapper1.getPointOnPolygon(cX);
topX = point.x;
topY = point.y;
point = oPolygonWrapper2.getPointOnPolygon(cX);
bottomX = point.x;
bottomY = point.y;
return {x: topX + cY * (bottomX - topX), y: topY + cY * (bottomY - topY)};
}
function Path2(oPathMemory) {
this.stroke = null;
this.extrusionOk = null;
this.fill = null;
this.pathH = null;
this.pathW = null;
this.startPos = 0;
this.PathMemory = oPathMemory;
this.lastX = null;
this.lastY = null;
}
Path2.prototype.setParent = function (oParent) {
this.parent = oParent;
};
Path2.prototype.getMemoryLength = function () {
return this.getArrPathCommand()[this.startPos];
};
Path2.prototype.isEmpty = function () {
return this.getMemoryLength() === 0;
};
Path2.prototype.isInk = function () {
return false;
};
Path2.prototype.getArrPathCommand = function () {
return this.PathMemory.ArrPathCommand;
};
Path2.prototype.writeNumber = function (dValue) {
this.PathMemory.IncrementNumberInPos(this.startPos);
this.PathMemory.WriteNumber(dValue);
};
Path2.prototype.setStroke = function (pr) {
this.stroke = pr;
};
Path2.prototype.setExtrusionOk = function (pr) {
this.extrusionOk = pr;
};
Path2.prototype.setFill = function (pr) {
this.fill = pr;
};
Path2.prototype.setPathH = function (pr) {
this.pathH = pr;
};
Path2.prototype.setPathW = function (pr) {
this.pathW = pr;
};
Path2.prototype.addPathCommand = function (cmd) {
this.getArrPathCommand().push(cmd);
};
Path2.prototype.writeCommandType = function (cmd) {
this.writeNumber(cmd);
};
Path2.prototype.writePoint = function (x, y) {
this.lastX = x * 10e-10;
this.lastY = y * 10e-10;
this.writeNumber(this.lastX);
this.writeNumber(this.lastY);
};
Path2.prototype.moveTo = function (x, y) {
this.writeCommandType(moveTo);
this.writePoint(x, y);
};
Path2.prototype.lnTo = function (x, y) {
this.writeCommandType(lineTo);
this.writePoint(x, y);
};
Path2.prototype.arcTo = function (wR, hR, stAng, swAng) {
let nSign = swAng < 0 ? -1 : 1;
if (AscFormat.fApproxEqual(Math.abs(swAng), 21600000)) {
swAng = nSign * (21600000 - 1);
}
let a1 = stAng;
let a2 = stAng + swAng;
let a3 = swAng;
stAng = Math.atan2(10e-10 * Math.sin(a1 * cToRad), 10e-10 * Math.cos(a1 * cToRad)) / cToRad;
swAng = Math.atan2(10e-10 * Math.sin(a2 * cToRad), 10e-10 * Math.cos(a2 * cToRad)) / cToRad - stAng;
if ((swAng > 0) && (a3 < 0)) swAng -= 21600000;
if ((swAng < 0) && (a3 > 0)) swAng += 21600000;
if (swAng == 0 && a3 != 0) swAng = 21600000;
this.writeCommandType(arcTo);
//TODO: calculate correct the last point
this.writeNumber(this.lastX);
this.writeNumber(this.lastY);
this.writeNumber(wR * 10e-10);
this.writeNumber(hR * 10e-10);
this.writeNumber(stAng * cToRad);
this.writeNumber(swAng * cToRad);
};
Path2.prototype.quadBezTo = function (x0, y0, x1, y1) {
this.writeCommandType(bezier3);
this.writePoint(x0, y0);
this.writePoint(x1, y1);
};
Path2.prototype.cubicBezTo = function (x0, y0, x1, y1, x2, y2) {
this.writeCommandType(bezier4);
this.writePoint(x0, y0);
this.writePoint(x1, y1);
this.writePoint(x2, y2);
};
Path2.prototype.close = function () {
this.writeCommandType(close);
};
Path2.prototype.draw = function (shape_drawer) {
if (this.isEmpty()) {
return;
}
if (shape_drawer.bIsCheckBounds === true && this.fill == "none") {
// это для текстур
return;
}
let bIsDrawLast = false;
let path = this.getArrPathCommand();
shape_drawer._s();
let i = 0;
let len = this.getMemoryLength();
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
bIsDrawLast = true;
shape_drawer._m(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case lineTo: {
bIsDrawLast = true;
shape_drawer._l(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case bezier3: {
bIsDrawLast = true;
shape_drawer._c2(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5]);
i += 5;
break;
}
case bezier4: {
bIsDrawLast = true;
shape_drawer._c(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case arcTo: {
bIsDrawLast = true;
ArcToCurvers(shape_drawer, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case close: {
shape_drawer._z();
i += 1;
break;
}
}
}
if (bIsDrawLast) {
shape_drawer.drawFillStroke(true, "normal", this.stroke && !shape_drawer.bIsNoStrokeAttack);
}
shape_drawer._e();
};
Path2.prototype.hitInInnerArea = function (canvasContext, x, y) {
let path = this.getArrPathCommand();
canvasContext.beginPath();
let i = 0;
let len = this.getMemoryLength();
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
canvasContext.moveTo(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case lineTo: {
canvasContext.lineTo(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case bezier3: {
canvasContext.quadraticCurveTo(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5]);
i += 5;
break;
}
case bezier4: {
canvasContext.bezierCurveTo(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case arcTo: {
ArcToOnCanvas(canvasContext, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case close: {
canvasContext.closePath();
i += 1;
break;
}
}
}
canvasContext.closePath();
return !!canvasContext.isPointInPath(x, y);
};
Path2.prototype.hitInPath = function (canvasContext, x, y) {
let _last_x, _last_y;
let _begin_x, _begin_y;
let path = this.getArrPathCommand();
let i = 0;
let len = this.getMemoryLength();
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
canvasContext.moveTo(path[this.startPos + i + 2], path[this.startPos + i + 3]);
_last_x = path[this.startPos + i + 2];
_last_y = path[this.startPos + i + 3];
_begin_x = path[this.startPos + i + 2];
_begin_y = path[this.startPos + i + 3];
i += 3;
break;
}
case lineTo: {
if (HitInLine(canvasContext, x, y, _last_x, _last_y, path[this.startPos + i + 2], path[this.startPos + i + 3])) return true;
_last_x = path[this.startPos + i + 2];
_last_y = path[this.startPos + i + 3];
i += 3;
break;
}
case bezier3: {
canvasContext.quadraticCurveTo(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5]);
if (HitInBezier3(canvasContext, x, y, _last_x, _last_y, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5])) return true;
_last_x = path[this.startPos + i + 4];
_last_y = path[this.startPos + i + 5];
i += 5;
break;
}
case bezier4: {
if (HitInBezier4(canvasContext, x, y, _last_x, _last_y, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7])) return true;
_last_x = path[this.startPos + i + 6];
_last_y = path[this.startPos + i + 7];
i += 7;
break;
}
case arcTo: {
if (HitToArc(canvasContext, x, y, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7])) return true;
_last_x = (path[this.startPos + i + 2] - path[this.startPos + i + 4] * Math.cos(path[this.startPos + i + 6]) + path[this.startPos + i + 4] * Math.cos(path[this.startPos + i + 7]));
_last_y = (path[this.startPos + i + 3] - path[this.startPos + i + 5] * Math.sin(path[this.startPos + i + 6]) + path[this.startPos + i + 5] * Math.sin(path[this.startPos + i + 7]));
i += 7;
break;
}
case close: {
if (HitInLine(canvasContext, x, y, _last_x, _last_y, _begin_x, _begin_y)) return true;
i += 1;
break;
}
}
}
return false;
};
Path2.prototype.drawTracks = function (drawingDocument, transform) {
let oApi = Asc.editor || editor;
let isDrawHandles = oApi ? oApi.isShowShapeAdjustments() : true;
let i = 0;
let len = this.getMemoryLength();
let path = this.getArrPathCommand();
let dDist = 0;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i + 2] - dDist, path[this.startPos + i + 3] - dDist, 2 * dDist, 2 * dDist, false, false, undefined, isDrawHandles);
i += 3;
break;
}
case lineTo: {
drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i + 2] - dDist, path[this.startPos + i + 3] - dDist, 2 * dDist, 2 * dDist, false, false, undefined, isDrawHandles);
i += 3;
break;
}
case bezier3: {
// drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i+2] - dDist, path[this.startPos + i + 3] - dDist, 2*dDist, 2*dDist, false, false);
drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i + 4] - dDist, path[this.startPos + i + 5] - dDist, 2 * dDist, 2 * dDist, false, false, undefined, isDrawHandles);
i += 5;
break;
}
case bezier4: {
// drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i+2] - dDist, path[this.startPos + i + 3] - dDist, 2*dDist,2*dDist, false, false);
// drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i+4] - dDist, path[this.startPos + i + 5] - dDist, 2*dDist,2*dDist, false, false); i+=7;
drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, path[this.startPos + i + 6] - dDist, path[this.startPos + i + 7] - dDist, 2 * dDist, 2 * dDist, false, false, undefined, isDrawHandles);
i += 7;
break;
}
case arcTo: {
let path_accumulator = new AscFormat.PathAccumulator();
ArcToCurvers(path_accumulator, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
let arc_to_path_commands = path_accumulator.pathCommand;
let lastX, lastY;
for (let arc_to_path_index = 0; arc_to_path_index < arc_to_path_commands.length; ++arc_to_path_index) {
let cur_arc_to_command = arc_to_path_commands[arc_to_path_index];
switch (cur_arc_to_command.id) {
case AscFormat.moveTo: {
lastX = cur_arc_to_command.X;
lastY = cur_arc_to_command.Y;
// drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, cur_arc_to_command.X - dDist, cur_arc_to_command.Y - dDist, 2*dDist, 2*dDist, false, false);
break;
}
case AscFormat.bezier4: {
lastX = cur_arc_to_command.X2;
lastY = cur_arc_to_command.Y2;
//drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, cur_arc_to_command.X0 - dDist, cur_arc_to_command.Y0 - dDist, 2*dDist, 2*dDist, false, false);
//drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, cur_arc_to_command.X2 - dDist, cur_arc_to_command.Y2 - dDist, 2*dDist, 2*dDist, false, false);
break;
}
}
}
drawingDocument.DrawTrack(AscFormat.TYPE_TRACK.CHART_TEXT, transform, lastX - dDist, lastY - dDist, 2 * dDist, 2 * dDist, false, false, undefined, isDrawHandles);
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
};
Path2.prototype.getCommandByIndex = function (idx) {
let i = 0;
let path = this.getArrPathCommand();
let len = this.getMemoryLength();
let commandIndex = 0;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
if (idx === commandIndex) {
return {id: moveTo, X: path[this.startPos + i + 2], Y: path[this.startPos + i + 3]};
}
i += 3;
break;
}
case lineTo: {
if (idx === commandIndex) {
return {id: moveTo, X: path[this.startPos + i + 2], Y: path[this.startPos + i + 3]};
}
i += 3;
break;
}
case bezier3: {
if (idx === commandIndex) {
return {
id: bezier3,
X0: path[this.startPos + i + 2],
Y0: path[this.startPos + i + 3],
X1: path[this.startPos + i + 4],
Y1: path[this.startPos + i + 5]
};
}
i += 5;
break;
}
case bezier4: {
if (idx === commandIndex) {
return {
id: bezier4,
X0: path[this.startPos + i + 2],
Y0: path[this.startPos + i + 3],
X1: path[this.startPos + i + 4],
Y1: path[this.startPos + i + 5],
X2: path[this.startPos + i + 6],
Y2: path[this.startPos + i + 7]
};
}
i += 7;
break;
}
case arcTo: {
if (idx === commandIndex) {
return {
id: arcTo,
stX: path[this.startPos + i + 2],
stY: path[this.startPos + i + 3],
wR: path[this.startPos + i + 4],
hR: path[this.startPos + i + 5],
stAng: path[this.startPos + i + 6],
swAng: path[this.startPos + i + 7]
};
}
i += 7;
break;
}
case close: {
if (idx === commandIndex) {
return {id: close};
}
i += 1;
break;
}
}
++commandIndex;
}
return null;
};
Path2.prototype.check_bounds = function (shape_drawer) {
let path = this.getArrPathCommand();
let i = 0;
let len = this.getMemoryLength();
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
shape_drawer._m(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case lineTo: {
shape_drawer._l(path[this.startPos + i + 2], path[this.startPos + i + 3]);
i += 3;
break;
}
case bezier3: {
shape_drawer._c2(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5]);
i += 5;
break;
}
case bezier4: {
shape_drawer._c(path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case arcTo: {
ArcToCurvers(shape_drawer, path[this.startPos + i + 2], path[this.startPos + i + 3], path[this.startPos + i + 4], path[this.startPos + i + 5], path[this.startPos + i + 6], path[this.startPos + i + 7]);
i += 7;
break;
}
case close: {
shape_drawer._z();
i += 1;
break;
}
}
}
};
Path2.prototype.isSmartLine = function () {
let i = 0;
let path = this.getArrPathCommand();
let len = this.getMemoryLength();
let commandIndex = 0;
while (i < len) {
if (commandIndex > 1) {
return false;
}
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
if (0 !== commandIndex) {
return false;
}
i += 3;
break;
}
case lineTo: {
if (1 !== commandIndex) {
return false;
}
i += 3;
break;
}
default: {
return false;
}
}
++commandIndex;
}
return true;
};
Path2.prototype.isSmartRect = function () {
let i = 0;
let path = this.getArrPathCommand();
let len = path[this.startPos];
let commandIndex = 0;
let x0, y0, x1, y1, x2, y2, x3, y3, x4, y4, isCommand4Close = false;
while (i < len) {
if (commandIndex > 4) {
return false;
}
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
if (0 !== commandIndex) {
return false;
}
x0 = path[this.startPos + i + 2];
y0 = path[this.startPos + i + 3];
i += 3;
break;
}
case lineTo: {
if (commandIndex === 1) {
x1 = path[this.startPos + i + 2];
y1 = path[this.startPos + i + 3];
}
else if (commandIndex === 2) {
x2 = path[this.startPos + i + 2];
y2 = path[this.startPos + i + 3];
}
else if (commandIndex === 3) {
x3 = path[this.startPos + i + 2];
y3 = path[this.startPos + i + 3];
}
else if (commandIndex === 4) {
x4 = path[this.startPos + i + 2];
y4 = path[this.startPos + i + 3];
}
i += 3;
break;
}
case close: {
if (4 !== commandIndex) {
return false;
}
isCommand4Close = true;
break;
}
default: {
return false;
}
}
++commandIndex;
}
if (AscFormat.fApproxEqual(x0, x1)) {
if (AscFormat.fApproxEqual(y1, y2)) {
if (AscFormat.fApproxEqual(x2, x3) && AscFormat.fApproxEqual(y3, y0)) {
if (isCommand4Close) return true;
if (AscFormat.fApproxEqual(x0, x4) && AscFormat.fApproxEqual(y0, y4)) {
return true;
}
}
}
}
else if (AscFormat.fApproxEqual(y0, y1)) {
if (AscFormat.fApproxEqual(x1, x2)) {
if (AscFormat.fApproxEqual(y2, y3) && AscFormat.fApproxEqual(x3, x0)) {
if (isCommand4Close) return true;
if (AscFormat.fApproxEqual(x0, x4) && AscFormat.fApproxEqual(y0, y4)) {
return true;
}
}
}
}
return false;
};
Path2.prototype.drawSmart = function (shape_drawer) {
let _graphics = shape_drawer.Graphics;
let _full_trans = _graphics.m_oFullTransform;
if (!_graphics || !_full_trans || undefined == _graphics.m_bIntegerGrid || true === shape_drawer.bIsNoSmartAttack) return this.draw(shape_drawer);
let bIsTransformed = (_full_trans.shx == 0 && _full_trans.shy == 0) ? false : true;
if (bIsTransformed) return this.draw(shape_drawer);
let isLine = this.isSmartLine();
let isRect = false;
if (!isLine) isRect = this.isSmartRect();
if (window["NATIVE_EDITOR_ENJINE"] || (!isLine && !isRect && !shape_drawer.bDrawSmartAttack)) return this.draw(shape_drawer);
let _old_int = _graphics.m_bIntegerGrid;
if (false == _old_int) _graphics.SetIntegerGrid(true);
let dKoefMMToPx = Math.max(_graphics.m_oCoordTransform.sx, 0.001);
let _ctx = _graphics.m_oContext;
let bIsStroke = (shape_drawer.bIsNoStrokeAttack || (this.stroke !== true)) ? false : true;
let bIsEven = false;
if (bIsStroke) {
let _lineWidth = Math.max((shape_drawer.StrokeWidth * dKoefMMToPx + 0.5) >> 0, 1);
_ctx.lineWidth = _lineWidth;
if ((_lineWidth & 0x01) == 0x01) bIsEven = true;
if (_graphics.dash_no_smart) {
for (let index = 0; index < _graphics.dash_no_smart.length; index++) _graphics.dash_no_smart[index] = (_graphics.m_oCoordTransform.sx * _graphics.dash_no_smart[index] + 0.5) >> 0;
_graphics.m_oContext.setLineDash(_graphics.dash_no_smart);
_graphics.dash_no_smart = null;
}
}
let bIsDrawLast = false;
let path = this.getArrPathCommand();
shape_drawer._s();
if (!isRect) {
let i = 0;
let len = path[this.startPos];
let X, Y;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
bIsDrawLast = true;
X = path[this.startPos + i + 2];
Y = path[this.startPos + i + 3];
let _x = (_full_trans.TransformPointX(X, Y)) >> 0;
let _y = (_full_trans.TransformPointY(X, Y)) >> 0;
if (bIsEven) {
_x -= 0.5;
_y -= 0.5;
}
_ctx.moveTo(_x, _y);
i += 3;
break;
}
case lineTo: {
bIsDrawLast = true;
X = path[this.startPos + i + 2];
Y = path[this.startPos + i + 3];
let _x = (_full_trans.TransformPointX(X, Y)) >> 0;
let _y = (_full_trans.TransformPointY(X, Y)) >> 0;
if (bIsEven) {
_x -= 0.5;
_y -= 0.5;
}
_ctx.lineTo(_x, _y);
i += 3;
break;
}
case bezier3: {
bIsDrawLast = true;
i += 5;
break;
}
case bezier4: {
bIsDrawLast = true;
i += 7;
break;
}
case arcTo: {
bIsDrawLast = true;
i += 7;
break;
}
case close: {
_ctx.closePath();
i += 1;
break;
}
}
}
}
else {
let minX = 100000;
let minY = 100000;
let maxX = -100000;
let maxY = -100000;
bIsDrawLast = true;
let i = 0;
let len = path[this.startPos], X, Y;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo:
case lineTo: {
bIsDrawLast = true;
X = path[this.startPos + i + 2];
Y = path[this.startPos + i + 3];
if (minX > X) minX = X;
if (minY > Y) minY = Y;
if (maxX < X) maxX = X;
if (maxY < Y) maxY = Y;
i += 3;
break;
}
case bezier3: {
bIsDrawLast = true;
i += 5;
break;
}
case bezier4: {
bIsDrawLast = true;
i += 7;
break;
}
case arcTo: {
bIsDrawLast = true;
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
let _x1 = (_full_trans.TransformPointX(minX, minY)) >> 0;
let _y1 = (_full_trans.TransformPointY(minX, minY)) >> 0;
let _x2 = (_full_trans.TransformPointX(maxX, maxY)) >> 0;
let _y2 = (_full_trans.TransformPointY(maxX, maxY)) >> 0;
if (bIsEven) _ctx.rect(_x1 + 0.5, _y1 + 0.5, _x2 - _x1, _y2 - _y1); else _ctx.rect(_x1, _y1, _x2 - _x1, _y2 - _y1);
}
if (bIsDrawLast) {
shape_drawer.isArrPix = true;
shape_drawer.drawFillStroke(true, this.fill, bIsStroke);
shape_drawer.isArrPix = false;
}
shape_drawer._e();
if (false == _old_int) _graphics.SetIntegerGrid(false);
};
Path2.prototype.recalculate = function (gdLst, bResetPathsInfo) {
};
Path2.prototype.recalculate2 = function (gdLst, bResetPathsInfo) {
};
Path2.prototype.transformPointPolygon = function (x, y, oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds) {
let oRet = {x: 0, y: 0}, y0, y1, cX, oPointOnPolygon, x1t, y1t, dX, dY, x0t, y0t;
y0 = y;//dKoeff;
if (bFlag) {
y1 = 0;
if (oBounds) {
y0 -= oBounds.min_y;
}
}
else {
y1 = ContentHeight * dKoeff;
if (oBounds) {
y1 = (oBounds.max_y - oBounds.min_y);
y0 -= oBounds.min_y;
}
}
cX = x / XLimit;
oPointOnPolygon = oPolygon.getPointOnPolygon(cX, true);
x1t = oPointOnPolygon.x;
y1t = oPointOnPolygon.y;
dX = oPointOnPolygon.oP2.x - oPointOnPolygon.oP1.x;
dY = oPointOnPolygon.oP2.y - oPointOnPolygon.oP1.y;
if (bFlag) {
dX = -dX;
dY = -dY;
}
let dNorm = Math.sqrt(dX * dX + dY * dY);
if (bFlag) {
x0t = x1t + (dY / dNorm) * (y0);
y0t = y1t - (dX / dNorm) * (y0);
}
else {
x0t = x1t + (dY / dNorm) * (y1 - y0);
y0t = y1t - (dX / dNorm) * (y1 - y0);
}
oRet.x = x0t;
oRet.y = y0t;
return oRet;
};
Path2.prototype.checkBetweenPolygons = function (oBoundsController, oPolygonWrapper1, oPolygonWrapper2) {
let path = this.getArrPathCommand();
let i = 0;
let len = path[this.startPos];
let p;
let dMinX = oBoundsController.min_x, dMinY = oBoundsController.min_y,
dWidth = oBoundsController.max_x - oBoundsController.min_x,
dHeight = oBoundsController.max_y - oBoundsController.min_y;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo:
case lineTo: {
p = CheckPointByPaths(path[this.startPos + i + 2], path[this.startPos + i + 3], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
i += 3;
break;
}
case bezier3: {
p = CheckPointByPaths(path[this.startPos + i + 2], path[this.startPos + i + 3], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
p = CheckPointByPaths(path[this.startPos + i + 4], path[this.startPos + i + 5], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 4] = p.x;
path[this.startPos + i + 5] = p.y;
i += 5;
break;
}
case bezier4: {
p = CheckPointByPaths(path[this.startPos + i + 2], path[this.startPos + i + 3], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
p = CheckPointByPaths(path[this.startPos + i + 4], path[this.startPos + i + 5], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 4] = p.x;
path[this.startPos + i + 5] = p.y;
p = CheckPointByPaths(path[this.startPos + i + 6], path[this.startPos + i + 7], dWidth, dHeight, dMinX, dMinY, oPolygonWrapper1, oPolygonWrapper2);
path[this.startPos + i + 6] = p.x;
path[this.startPos + i + 7] = p.y;
i += 7;
break;
}
case arcTo: {
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
};
Path2.prototype.checkByPolygon = function (oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds) {
let path = this.getArrPathCommand();
let i = 0;
let len = path[this.startPos];
let p;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo:
case lineTo: {
p = this.transformPointPolygon(path[this.startPos + i + 2], path[this.startPos + i + 3], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
i += 3;
break;
}
case bezier3: {
p = this.transformPointPolygon(path[this.startPos + i + 2], path[this.startPos + i + 3], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
p = this.transformPointPolygon(path[this.startPos + i + 4], path[this.startPos + i + 5], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 4] = p.x;
path[this.startPos + i + 5] = p.y;
i += 5;
break;
}
case bezier4: {
p = this.transformPointPolygon(path[this.startPos + i + 2], path[this.startPos + i + 3], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 2] = p.x;
path[this.startPos + i + 3] = p.y;
p = this.transformPointPolygon(path[this.startPos + i + 4], path[this.startPos + i + 5], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 4] = p.x;
path[this.startPos + i + 5] = p.y;
p = this.transformPointPolygon(path[this.startPos + i + 6], path[this.startPos + i + 7], oPolygon, bFlag, XLimit, ContentHeight, dKoeff, oBounds);
path[this.startPos + i + 6] = p.x;
path[this.startPos + i + 7] = p.y;
i += 7;
break;
}
case arcTo: {
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
};
Path2.prototype.transform = function (oTransform, dKoeff) {
let path = this.getArrPathCommand();
let i = 0;
let len = path[this.startPos];
let p, x, y;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo:
case lineTo: {
x = oTransform.TransformPointX(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
path[this.startPos + i + 2] = x;
path[this.startPos + i + 3] = y;
i += 3;
break;
}
case bezier3: {
x = oTransform.TransformPointX(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
path[this.startPos + i + 2] = x;
path[this.startPos + i + 3] = y;
x = oTransform.TransformPointX(path[this.startPos + i + 4] * dKoeff, path[this.startPos + i + 5] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 4] * dKoeff, path[this.startPos + i + 5] * dKoeff);
path[this.startPos + i + 4] = x;
path[this.startPos + i + 5] = y;
i += 5;
break;
}
case bezier4: {
x = oTransform.TransformPointX(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 2] * dKoeff, path[this.startPos + i + 3] * dKoeff);
path[this.startPos + i + 2] = x;
path[this.startPos + i + 3] = y;
x = oTransform.TransformPointX(path[this.startPos + i + 4] * dKoeff, path[this.startPos + i + 5] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 4] * dKoeff, path[this.startPos + i + 5] * dKoeff);
path[this.startPos + i + 4] = x;
path[this.startPos + i + 5] = y;
x = oTransform.TransformPointX(path[this.startPos + i + 6] * dKoeff, path[this.startPos + i + 7] * dKoeff);
y = oTransform.TransformPointY(path[this.startPos + i + 6] * dKoeff, path[this.startPos + i + 7] * dKoeff);
path[this.startPos + i + 6] = x;
path[this.startPos + i + 7] = y;
i += 7;
break;
}
case arcTo: {
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
};
Path2.prototype.convertToBezierCurves = function (oPath, oTransform) {
let dX0, dY0, dX1, dY1, dX2, dY2;
let oLastMoveTo = null;
let dLastX, dLastY;
let path = this.getArrPathCommand();
let i = 0;
let len = path[this.startPos];
if (len === 0) {
return null;
}
let X, Y, X0, Y0, X1, Y1, X2, Y2, stX, stY, wR, hR, stAng, swAng;
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
X = path[this.startPos + i + 2];
Y = path[this.startPos + i + 3];
dX0 = oTransform.TransformPointX(X, Y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(X, Y) * 36000 >> 0;
oPath.moveTo(dX0, dY0);
oLastMoveTo = i;
dLastX = X;
dLastY = Y;
i += 3;
break;
}
case lineTo: {
X = path[this.startPos + i + 2];
Y = path[this.startPos + i + 3];
dX0 = oTransform.TransformPointX(X, Y) * 36000 >> 0;
dY0 = oTransform.TransformPointY(X, Y) * 36000 >> 0;
dX0 = oTransform.TransformPointX(dLastX + (X - dLastX) * (1 / 3), dLastY + (Y - dLastY) * (1 / 3)) * 36000 >> 0;
dY0 = oTransform.TransformPointY(dLastX + (X - dLastX) * (1 / 3), dLastY + (Y - dLastY) * (1 / 3)) * 36000 >> 0;
dX1 = oTransform.TransformPointX(dLastX + (X - dLastX) * (2 / 3), dLastY + (Y - dLastY) * (2 / 3)) * 36000 >> 0;
dY1 = oTransform.TransformPointY(dLastX + (X - dLastX) * (2 / 3), dLastY + (Y - dLastY) * (2 / 3)) * 36000 >> 0;
dX2 = oTransform.TransformPointX(X, Y) * 36000 >> 0;
dY2 = oTransform.TransformPointY(X, Y) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = X;
dLastY = Y;
i += 3;
break;
}
case bezier3: {
X0 = path[this.startPos + i + 2];
Y0 = path[this.startPos + i + 3];
X1 = path[this.startPos + i + 4];
Y1 = path[this.startPos + i + 5];
dX0 = oTransform.TransformPointX(X0, Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(X0, Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(X1, Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(X1, Y1) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX1, dY1);
dLastX = X1;
dLastY = Y1;
i += 5;
break;
}
case bezier4: {
X0 = path[this.startPos + i + 2];
Y0 = path[this.startPos + i + 3];
X1 = path[this.startPos + i + 4];
Y1 = path[this.startPos + i + 5];
X2 = path[this.startPos + i + 6];
Y2 = path[this.startPos + i + 7];
dX0 = oTransform.TransformPointX(X0, Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(X0, Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(X1, Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(X1, Y1) * 36000 >> 0;
dX2 = oTransform.TransformPointX(X2, Y2) * 36000 >> 0;
dY2 = oTransform.TransformPointY(X2, Y2) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = X2;
dLastY = Y2;
i += 7;
break;
}
case arcTo: {
stX = path[this.startPos + i + 2];
stY = path[this.startPos + i + 3]
wR = path[this.startPos + i + 4];
hR = path[this.startPos + i + 5];
stAng = path[this.startPos + i + 6];
swAng = path[this.startPos + i + 7];
let oPathAccumulator = new AscFormat.PathAccumulator();
ArcToCurvers(oPathAccumulator, stX, stY, wR, hR, stAng, swAng);
let aArcToCommands = oPathAccumulator.pathCommand;
for (let nArcCmd = 0; nArcCmd < aArcToCommands.length; ++nArcCmd) {
let oArcToCmd = aArcToCommands[nArcCmd];
switch (oArcToCmd.id) {
case AscFormat.moveTo: {
break;
}
case AscFormat.bezier4: {
dX0 = oTransform.TransformPointX(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dY0 = oTransform.TransformPointY(oArcToCmd.X0, oArcToCmd.Y0) * 36000 >> 0;
dX1 = oTransform.TransformPointX(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dY1 = oTransform.TransformPointY(oArcToCmd.X1, oArcToCmd.Y1) * 36000 >> 0;
dX2 = oTransform.TransformPointX(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
dY2 = oTransform.TransformPointY(oArcToCmd.X2, oArcToCmd.Y2) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
dLastX = oArcToCmd.X2;
dLastY = oArcToCmd.Y2;
break;
}
}
}
i += 7;
break;
}
case close: {
if (AscFormat.isRealNumber(oLastMoveTo)) {
X = path[this.startPos + oLastMoveTo + 2];
Y = path[this.startPos + oLastMoveTo + 3];
let dXM = oTransform.TransformPointX(X, Y);
let dYM = oTransform.TransformPointY(X, Y);
let dLastXM = oTransform.TransformPointX(dLastX, dLastY);
let dLastYM = oTransform.TransformPointY(dLastX, dLastY);
dX0 = (dLastXM + (dXM - dLastXM) / 4) * 36000 >> 0;
dY0 = (dLastYM + (dYM - dLastYM) / 4) * 36000 >> 0;
dX1 = (dLastXM + 3 * (dXM - dLastXM) / 4) * 36000 >> 0;
dY1 = (dLastYM + 3 * (dYM - dLastYM) / 4) * 36000 >> 0;
dX2 = (dXM) * 36000 >> 0;
dY2 = (dYM) * 36000 >> 0;
oPath.cubicBezTo(dX0, dY0, dX1, dY1, dX2, dY2);
}
oPath.close();
i += 1;
break;
}
}
}
oPath.recalculate({}, true);
};
Path2.prototype.isValid = function () {
if(this.isEmpty()) return false;
let path = this.getArrPathCommand();
return path[this.startPos + 1] === moveTo;
};
Path2.prototype.getArrPathCommandObjects = function () {
let i = 0;
let len = this.getMemoryLength();
let path = this.getArrPathCommand();
let arrPathCommand = [];
while (i < len) {
let cmd = path[this.startPos + i + 1];
switch (cmd) {
case moveTo: {
arrPathCommand.push({id:moveTo, X: path[this.startPos + i + 2], Y: path[this.startPos + i + 3]});
i += 3;
break;
}
case lineTo: {
arrPathCommand.push({id:lineTo, X: path[this.startPos + i + 2], Y: path[this.startPos + i + 3]});
i += 3;
break;
}
case bezier3: {
arrPathCommand.push({
id:bezier3,
X0: path[this.startPos + i + 2],
Y0: path[this.startPos + i + 3],
X1: path[this.startPos + i + 4],
Y1: path[this.startPos + i + 5]
});
i += 5;
break;
}
case bezier4: {
arrPathCommand.push({
id:bezier4,
X0: path[this.startPos + i + 2],
Y0: path[this.startPos + i + 3],
X1: path[this.startPos + i + 4],
Y1: path[this.startPos + i + 5],
X2: path[this.startPos + i + 6],
Y2: path[this.startPos + i + 7]
});
i += 7;
break;
}
case arcTo: {
arrPathCommand.push({
id:arcTo,
stX: path[this.startPos + i + 2],
stY: path[this.startPos + i + 3],
wR: path[this.startPos + i + 4],
hR: path[this.startPos + i + 5],
stAng: path[this.startPos + i + 6],
swAng: path[this.startPos + i + 7]
});
i += 7;
break;
}
case close: {
i += 1;
break;
}
}
}
};
Path2.prototype.executeWithPathCommands = function(fMethod, params) {
this.ArrPathCommand = this.getArrPathCommand();
let result = fMethod.apply(this, params);
this.ArrPathCommand = undefined;
return result;
};
Path2.prototype.getContinuousSubpaths = function () {
return this.executeWithPathCommands(Path.prototype.getContinuousSubpaths, []);
};
Path2.prototype.getHeadArrowAngle = function (arrowLength) {
return this.executeWithPathCommands(Path.prototype.getHeadArrowAngle, [arrowLength]);
};
Path2.prototype.getTailArrowAngle = function (arrowLength) {
return this.executeWithPathCommands(Path.prototype.getTailArrowAngle, [arrowLength]);
};
Path2.prototype.Write_ToBinary = function(writer) {
AscFormat.writeBool(writer, this.extrusionOk);
AscFormat.writeString(writer, this.fill);
AscFormat.writeLong(writer, this.pathH);
AscFormat.writeLong(writer, this.pathW);
AscFormat.writeLong(writer, this.startPos);
AscFormat.writeBool(writer, this.stroke);
};
Path2.prototype.Read_FromBinary = function(reader) {
this.extrusionOk = AscFormat.readBool(reader);
this.fill = AscFormat.readString(reader);
this.pathH = AscFormat.readLong(reader);
this.pathW = AscFormat.readLong(reader);
this.startPos = AscFormat.readLong(reader);
this.stroke = AscFormat.readBool(reader);
this.PathMemory = reader.pathMemory;
};
function partition_bezier3(x0, y0, x1, y1, x2, y2, epsilon) {
let dx01 = x1 - x0;
let dy01 = y1 - y0;
let dx12 = x2 - x1;
let dy12 = y2 - y1;
let r01 = Math.sqrt(dx01 * dx01 + dy01 * dy01);
let r12 = Math.sqrt(dx12 * dx12 + dy12 * dy12);
if (Math.max(r01, r12) < epsilon) {
return [{x: x0, y: y0}, {x: x1, y: y1}, {x: x2, y: y2}];
}
let x01 = (x0 + x1) * 0.5;
let y01 = (y0 + y1) * 0.5;
let x12 = (x1 + x2) * 0.5;
let y12 = (y1 + y2) * 0.5;
let x012 = (x01 + x12) * 0.5;
let y012 = (y01 + y12) * 0.5;
return partition_bezier3(x0, y0, x01, y01, x012, y012, epsilon).concat(partition_bezier3(x012, y012, x12, y12, x2, y2, epsilon));
}
function partition_bezier4(x0, y0, x1, y1, x2, y2, x3, y3, epsilon) {
let dx01 = x1 - x0;
let dy01 = y1 - y0;
let dx12 = x2 - x1;
let dy12 = y2 - y1;
let dx23 = x3 - x2;
let dy23 = y3 - y2;
let r01 = Math.sqrt(dx01 * dx01 + dy01 * dy01);
let r12 = Math.sqrt(dx12 * dx12 + dy12 * dy12);
let r23 = Math.sqrt(dx23 * dx23 + dy23 * dy23);
if (Math.max(r01, r12, r23) < epsilon) return [{x: x0, y: y0}, {x: x1, y: y1}, {x: x2, y: y2}, {x: x3, y: y3}];
let x01 = (x0 + x1) * 0.5;
let y01 = (y0 + y1) * 0.5;
let x12 = (x1 + x2) * 0.5;
let y12 = (y1 + y2) * 0.5;
let x23 = (x2 + x3) * 0.5;
let y23 = (y2 + y3) * 0.5;
let x012 = (x01 + x12) * 0.5;
let y012 = (y01 + y12) * 0.5;
let x123 = (x12 + x23) * 0.5;
let y123 = (y12 + y23) * 0.5;
let x0123 = (x012 + x123) * 0.5;
let y0123 = (y012 + y123) * 0.5;
return partition_bezier4(x0, y0, x01, y01, x012, y012, x0123, y0123, epsilon).concat(partition_bezier4(x0123, y0123, x123, y123, x23, y23, x3, y3, epsilon));
}
function splitBezier4(x0, y0, x1, y1, x2, y2, x3, y3, parameters) {
const aResult = [[x0, y0, x1, y1, x2, y2, x3, y3]];
if (!Array.isArray(parameters) || parameters.length === 0) {
return aResult;
}
const aWorkingParameters = [].concat(parameters);
aWorkingParameters.sort(function (a, b) {
return a - b
});
const isN = AscFormat.isRealNumber;
const isE = AscFormat.fApproxEqual;
let dLastParam = 1.0;
for (let nParamIdx = aWorkingParameters.length - 1; nParamIdx > -1; --nParamIdx) {
let dParam = aWorkingParameters[nParamIdx];
if (!isN(dParam) || dParam <= 0 || dParam >= 1.0 || isE(dParam, dLastParam)) {
aWorkingParameters.splice(nParamIdx, 1);
}
else {
dLastParam = dParam;
}
}
dLastParam = 1.0;
for (let nParamIdx = aWorkingParameters.length - 1; nParamIdx > -1; --nParamIdx) {
let dParam = aWorkingParameters[nParamIdx];
let dWorkingParam = dParam / dLastParam;
let oCurrentCurve = aResult[0];
let x0c = oCurrentCurve[0];
let y0c = oCurrentCurve[1];
let x1c = oCurrentCurve[2];
let y1c = oCurrentCurve[3];
let x2c = oCurrentCurve[4];
let y2c = oCurrentCurve[5];
let x3c = oCurrentCurve[6];
let y3c = oCurrentCurve[7];
let t = dWorkingParam;
//De Casteljau's algorithm
let x01 = x0c + (x1c - x0c) * t;
let y01 = y0c + (y1c - y0c) * t;
let x12 = x1c + (x2c - x1c) * t;
let y12 = y1c + (y2c - y1c) * t;
let x23 = x2c + (x3c - x2c) * t;
let y23 = y2c + (y3c - y2c) * t;
let x0112 = x01 + (x12 - x01) * t;
let y0112 = y01 + (y12 - y01) * t;
let x1223 = x12 + (x23 - x12) * t;
let y1223 = y12 + (y23 - y12) * t;
let x01121223 = x0112 + (x1223 - x0112) * t;
let y01121223 = y0112 + (y1223 - y0112) * t;
let oCurve1 = [x0c, y0c, x01, y01, x0112, y0112, x01121223, y01121223];
let oCurve2 = [x01121223, y01121223, x1223, y1223, x23, y23, x3c, y3c];
aResult.splice(0, 1, oCurve1, oCurve2);
dLastParam = dParam;
}
return aResult;
}
function splitBezier4OnParts(x0, y0, x1, y1, x2, y2, x3, y3, nPartsCount) {
if (!AscFormat.isRealNumber(nPartsCount) || nPartsCount < 2) {
return splitBezier4(x0, y0, x1, y1, x2, y2, x3, y3, []);
}
let aParameters = [];
const dDist = 1 / nPartsCount;
for (let nPartIdx = 1; nPartIdx < nPartsCount; ++nPartIdx) {
aParameters.push(nPartIdx * dDist);
}
return splitBezier4(x0, y0, x1, y1, x2, y2, x3, y3, aParameters);
}
function getEllipsePoint(dXCE, dYCE, dA, dB, dAlpha) {
return {
x: dXCE + dA * Math.cos(dAlpha), y: dYCE + dB * Math.sin(dAlpha)
};
}
function getLineLength(oPt1, oPt2) {
return Math.sqrt(Math.pow(oPt2.x - oPt1.x, 2) + Math.pow(oPt2.y - oPt1.y, 2));
}
function isInsideEllipse(dXCE, dYCE, dA, dB, p) {
let dX = p.x;
let dY = p.y;
let dXVal = dX - dXCE;
let dYVal = dY - dYCE;
return (dXVal * dXVal) / (dA * dA) + (dYVal * dYVal) / (dB * dB) < 1;
}
function ellipseCircleIntersection(dXCE, dYCE, dA, dB, dStartAngle, dR) {
const dTolerance = 0.001;
let dAlpha1 = dStartAngle;
let dAlpha2 = dStartAngle + Math.PI / 4;
let pStart = getEllipsePoint(dXCE, dYCE, dA, dB, dStartAngle);
let p1 = getEllipsePoint(dXCE, dYCE, dA, dB, dAlpha1);
let p2 = getEllipsePoint(dXCE, dYCE, dA, dB, dAlpha2);
let dAlphaMiddle = (dAlpha1 + dAlpha2) / 2.0;
while (Math.abs(dAlpha1 - dAlpha2) > dTolerance) {
dAlphaMiddle = (dAlpha1 + dAlpha2) / 2.0;
let pMiddle = getEllipsePoint(dXCE, dYCE, dA, dB, dAlphaMiddle);
if (isInsideEllipse(pStart.x, pStart.y, dR, dR, pMiddle)) {
p1 = pMiddle;
dAlpha1 = dAlphaMiddle;
}
else {
p2 = pMiddle;
dAlpha2 = dAlphaMiddle;
}
}
return {p: p1, alpha: dAlphaMiddle};
}
function circlesIntersection(x1, y1, r1, x2, y2, r2) {
let dDx = x1 - x2;
let dDy = y1 - y2;
let R = Math.sqrt(dDx * dDx + dDy * dDy);
if (!(Math.abs(r1 - r2) <= R && R <= r1 + r2)) {
return [];
}
let R2 = R * R;
let R4 = R2 * R2;
let a = (r1 * r1 - r2 * r2) / (2 * R2);
let r2r2 = (r1 * r1 - r2 * r2);
let c = Math.sqrt(2 * (r1 * r1 + r2 * r2) / R2 - (r2r2 * r2r2) / R4 - 1);
let fx = (x1 + x2) / 2 + a * (x2 - x1);
let gx = c * (y2 - y1) / 2;
let ix1 = fx + gx;
let ix2 = fx - gx;
let fy = (y1 + y2) / 2 + a * (y2 - y1);
let gy = c * (x1 - x2) / 2;
let iy1 = fy + gy;
let iy2 = fy - gy;
return [{x: ix1, y: iy1}, {x: ix2, y: iy2}];
}
//--------------------------------------------------------export----------------------------------------------------
window['AscFormat'] = window['AscFormat'] || {};
window['AscFormat'].moveTo = moveTo;
window['AscFormat'].lineTo = lineTo;
window['AscFormat'].arcTo = arcTo;
window['AscFormat'].bezier3 = bezier3;
window['AscFormat'].bezier4 = bezier4;
window['AscFormat'].close = close;
window['AscFormat'].cToRad2 = cToRad2;
window['AscFormat'].degToC = degToC;
window['AscFormat'].radToDeg = radToDeg;
window['AscFormat'].Path = Path;
window['AscFormat'].Path2 = Path2;
window['AscFormat'].CPathCmd = CPathCmd;
window['AscFormat'].partition_bezier3 = partition_bezier3;
window['AscFormat'].partition_bezier4 = partition_bezier4;
window['AscFormat'].splitBezier4 = splitBezier4;
window['AscFormat'].splitBezier4OnParts = splitBezier4OnParts;
window['AscFormat'].ellipseCircleIntersection = ellipseCircleIntersection;
window['AscFormat'].getLineLength = getLineLength;
window['AscFormat'].getEllipsePoint = getEllipsePoint;
window['AscFormat'].circlesIntersection = circlesIntersection;
})(window);