3911 lines
122 KiB
JavaScript
3911 lines
122 KiB
JavaScript
/*
|
||
* (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);
|