wt-huatu 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +223 -0
- package/component/HuatuComponentCache.js +89 -0
- package/component/HuatuComponentDef.js +249 -0
- package/component/HuatuComponentPlacement.js +172 -0
- package/component/HuatuComponentPort.js +58 -0
- package/component/IHuatuComponent.js +2 -0
- package/for-node/node-cli-funcs.js +36 -0
- package/geometry/AngDeg.js +68 -0
- package/geometry/Angle.js +77 -0
- package/geometry/Bezier.js +623 -0
- package/geometry/Circle.js +143 -0
- package/geometry/Ellipse.js +546 -0
- package/geometry/EllipticalArc.js +337 -0
- package/geometry/IGeometry.js +1 -0
- package/geometry/Intersection.js +601 -0
- package/geometry/Line.js +136 -0
- package/geometry/LineSeg.js +179 -0
- package/geometry/Point.js +88 -0
- package/geometry/PolyLine.js +97 -0
- package/geometry/Polygon.js +122 -0
- package/geometry/Rect.js +149 -0
- package/geometry/Ring.js +91 -0
- package/geometry/SegmentModel.js +206 -0
- package/geometry/Triangle.js +168 -0
- package/geometry/Vector.js +92 -0
- package/huatu/Huatu.js +4279 -0
- package/huatu/HuatuAngleMark.js +400 -0
- package/huatu/HuatuBlock.js +26 -0
- package/huatu/HuatuClipPath.js +29 -0
- package/huatu/HuatuCompDrawing.js +77 -0
- package/huatu/HuatuDimMark.js +579 -0
- package/huatu/HuatuDrawing.js +185 -0
- package/huatu/HuatuDrawingGroup.js +52 -0
- package/huatu/HuatuJob.js +347 -0
- package/huatu/HuatuLabel.js +311 -0
- package/huatu/HuatuLabelStyle.js +190 -0
- package/huatu/HuatuLayer.js +10 -0
- package/huatu/HuatuMarker.js +209 -0
- package/huatu/HuatuParser.js +435 -0
- package/huatu/HuatuPath.js +131 -0
- package/huatu/HuatuPathSeries.js +51 -0
- package/huatu/HuatuPattern.js +54 -0
- package/huatu/HuatuPoint.js +20 -0
- package/huatu/HuatuPointSeries.js +67 -0
- package/huatu/HuatuStyle.js +394 -0
- package/huatu/HuatuSymbol.js +81 -0
- package/huatu/HuatuSymbolArray.js +235 -0
- package/huatu/HuatuTransform.js +113 -0
- package/huatu/HuatuVar.js +17 -0
- package/huatu/HuatuYml.js +271 -0
- package/huatu/IHuatu.js +2 -0
- package/huatu/IHuatuYml.js +1 -0
- package/huatu/IndentParsingStack.js +82 -0
- package/index.d.ts +1747 -0
- package/index.js +247 -0
- package/math/Complex.js +72 -0
- package/math/Matrix.js +31 -0
- package/math/NumberRange.js +38 -0
- package/math/VarFuncs.js +24 -0
- package/math/WtMath.js +217 -0
- package/package.json +34 -0
- package/path/PathBezier2.js +75 -0
- package/path/PathBezier3.js +89 -0
- package/path/PathCircle.js +82 -0
- package/path/PathConfig.js +81 -0
- package/path/PathContinuousSegmentedPath.js +390 -0
- package/path/PathEllipse.js +99 -0
- package/path/PathEllipticalArc.js +111 -0
- package/path/PathGeneric.js +59 -0
- package/path/PathLine.js +75 -0
- package/path/PathLines.js +46 -0
- package/path/PathPolygon.js +142 -0
- package/path/PathPolyline.js +266 -0
- package/path/PathRect.js +125 -0
- package/path/PathRing.js +51 -0
- package/path/PathSegmentedPath.js +199 -0
- package/path/PathTriangle.js +90 -0
- package/presets/marker.js +37 -0
- package/presets/pattern.js +85 -0
- package/shape/SvgShapeArrowHead.js +92 -0
- package/shape/SvgShapeCircle.js +26 -0
- package/shape/SvgShapeCross.js +43 -0
- package/shape/SvgShapeEllipse.js +28 -0
- package/shape/SvgShapeGeneric.js +50 -0
- package/shape/SvgShapeHeart.js +40 -0
- package/shape/SvgShapeIsoscelesTriangle.js +52 -0
- package/shape/SvgShapePolygon.js +39 -0
- package/shape/SvgShapeRect.js +26 -0
- package/shape/SvgShapeRhombus.js +27 -0
- package/shape/SvgShapeStar.js +103 -0
- package/svg/ISvg.js +1 -0
- package/svg/SvgBBox.js +149 -0
- package/svg/SvgConstants.js +14 -0
- package/svg/SvgGradient.js +97 -0
- package/svg/SvgMarker.js +221 -0
- package/svg/SvgPattern.js +228 -0
- package/svg/SvgPoint.js +33 -0
- package/svg/SvgPointSet.js +41 -0
- package/templates/lines.js +35 -0
- package/tools/gen-id.js +2 -0
- package/tools/html.js +2 -0
- package/tools/katex.js +75 -0
- package/tools/rand-str.js +122 -0
- package/tools/regex.js +15 -0
- package/tools/utils.js +438 -0
- package/tools/webColor.js +700 -0
- package/wire/HuatuNet.js +22 -0
- package/wire/HuatuWire.js +415 -0
- package/wire/HuatuWireDrawing.js +17 -0
- package/wire/IHuatuWire.js +1 -0
package/huatu/Huatu.js
ADDED
|
@@ -0,0 +1,4279 @@
|
|
|
1
|
+
import { WT_REGEX } from "../tools/regex.js";
|
|
2
|
+
import { booleanCheck, evalExpr2Number, parseStrIntoKeyValuePair, replaceMathPiAndFuncs, resolvePointLike, assignOnlyDefined, fitNumInRange } from "../tools/utils.js";
|
|
3
|
+
import { HuatuPoint } from "./HuatuPoint.js";
|
|
4
|
+
import { HuatuVar } from "./HuatuVar.js";
|
|
5
|
+
import { SvgPoint } from "../svg/SvgPoint.js";
|
|
6
|
+
import { SvgCsts } from "../svg/SvgConstants.js";
|
|
7
|
+
import { WtMath } from "../math/WtMath.js";
|
|
8
|
+
import { NumberRange } from "../math/NumberRange.js";
|
|
9
|
+
import { AngDeg } from "../geometry/AngDeg.js";
|
|
10
|
+
import Point from "../geometry/Point.js";
|
|
11
|
+
import Circle from "../geometry/Circle.js";
|
|
12
|
+
import Rect from "../geometry/Rect.js";
|
|
13
|
+
import Triangle from "../geometry/Triangle.js";
|
|
14
|
+
import Line from "../geometry/Line.js";
|
|
15
|
+
import LineSeg from "../geometry/LineSeg.js";
|
|
16
|
+
import Polygon from "../geometry/Polygon.js";
|
|
17
|
+
import Ellipse from "../geometry/Ellipse.js";
|
|
18
|
+
import { Intersection } from "../geometry/Intersection.js";
|
|
19
|
+
import { Bezier2, Bezier3 } from "../geometry/Bezier.js";
|
|
20
|
+
import { EllipticalArc } from "../geometry/EllipticalArc.js";
|
|
21
|
+
import { HuatuPath } from "./HuatuPath.js";
|
|
22
|
+
import { PathSegmentedPath } from "../path/PathSegmentedPath.js";
|
|
23
|
+
import PolyLine from "../geometry/PolyLine.js";
|
|
24
|
+
import { isValidColor } from "../tools/webColor.js";
|
|
25
|
+
import { HuatuPathSeries } from "./HuatuPathSeries.js";
|
|
26
|
+
import { HuatuSymbol } from "./HuatuSymbol.js";
|
|
27
|
+
import { HuatuStyle } from "./HuatuStyle.js";
|
|
28
|
+
import { HuatuSymbolArray } from "./HuatuSymbolArray.js";
|
|
29
|
+
import { SvgBBox } from "../svg/SvgBBox.js";
|
|
30
|
+
import { HuatuLabel } from "./HuatuLabel.js";
|
|
31
|
+
import { HuatuDrawing } from "./HuatuDrawing.js";
|
|
32
|
+
import { HuatuBlock } from "./HuatuBlock.js";
|
|
33
|
+
import { HuatuLayer } from "./HuatuLayer.js";
|
|
34
|
+
import { HuatuTransform } from "./HuatuTransform.js";
|
|
35
|
+
import { predefinedPatterns } from "../presets/pattern.js";
|
|
36
|
+
import { predefinedMarkers } from "../presets/marker.js";
|
|
37
|
+
import { HuatuPointSeries } from "./HuatuPointSeries.js";
|
|
38
|
+
import { HuatuClipPath } from "./HuatuClipPath.js";
|
|
39
|
+
import { HuatuPattern } from "./HuatuPattern.js";
|
|
40
|
+
import { HuatuMarker } from "./HuatuMarker.js";
|
|
41
|
+
import { HuatuJob } from "./HuatuJob.js";
|
|
42
|
+
import { HuatuLabelStyle } from "./HuatuLabelStyle.js";
|
|
43
|
+
import Ring from "../geometry/Ring.js";
|
|
44
|
+
import { PathPolyline } from "../path/PathPolyline.js";
|
|
45
|
+
import { PathPolygon } from "../path/PathPolygon.js";
|
|
46
|
+
import { loadVarFuncsToGlobalThis } from "../math/VarFuncs.js";
|
|
47
|
+
import { PathRect } from "../path/PathRect.js";
|
|
48
|
+
import { PathTriangle } from "../path/PathTriangle.js";
|
|
49
|
+
import { PathCircle } from "../path/PathCircle.js";
|
|
50
|
+
import { PathEllipse } from "../path/PathEllipse.js";
|
|
51
|
+
import { HuatuComponentDef } from "../component/HuatuComponentDef.js";
|
|
52
|
+
import { HuatuComponentPlacement } from "../component/HuatuComponentPlacement.js";
|
|
53
|
+
import { HuatuComponentPort } from "../component/HuatuComponentPort.js";
|
|
54
|
+
import { HuatuCompDrawing } from "./HuatuCompDrawing.js";
|
|
55
|
+
import { HuatuWire } from "../wire/HuatuWire.js";
|
|
56
|
+
import { HuatuWireDrawing } from "../wire/HuatuWireDrawing.js";
|
|
57
|
+
import { HuatuAngleMark } from "./HuatuAngleMark.js";
|
|
58
|
+
import { HuatuDimMark } from "./HuatuDimMark.js";
|
|
59
|
+
import { genHex } from "../tools/gen-id.js";
|
|
60
|
+
export class WtHuatu {
|
|
61
|
+
title;
|
|
62
|
+
desc;
|
|
63
|
+
config;
|
|
64
|
+
width;
|
|
65
|
+
height;
|
|
66
|
+
viewBox;
|
|
67
|
+
vars = [];
|
|
68
|
+
points = [];
|
|
69
|
+
pointSeries = [];
|
|
70
|
+
paths = [];
|
|
71
|
+
pathSeries = [];
|
|
72
|
+
clips = [];
|
|
73
|
+
patterns = [];
|
|
74
|
+
markers = [];
|
|
75
|
+
styles = [];
|
|
76
|
+
labelStyles = [];
|
|
77
|
+
arrays = [];
|
|
78
|
+
defines = [];
|
|
79
|
+
symbols = [];
|
|
80
|
+
blocks = [];
|
|
81
|
+
labels = [];
|
|
82
|
+
layers = [];
|
|
83
|
+
compDefs = [];
|
|
84
|
+
compPlacements = [];
|
|
85
|
+
// This is for Component placement's use
|
|
86
|
+
compPorts = [];
|
|
87
|
+
// Reserve Solved X-Points to avoid re-computing
|
|
88
|
+
solvedXPoints = [];
|
|
89
|
+
// This is for Resolving wires
|
|
90
|
+
nets = [];
|
|
91
|
+
// Jobs
|
|
92
|
+
draw;
|
|
93
|
+
jobs = [];
|
|
94
|
+
infos = [];
|
|
95
|
+
warnings = [];
|
|
96
|
+
resolvingErrors = [];
|
|
97
|
+
varErrors = [];
|
|
98
|
+
pointErrors = [];
|
|
99
|
+
constructor() {
|
|
100
|
+
/* Load Var Funcs to Global if not */
|
|
101
|
+
if (!globalThis.wtVarFuncs) {
|
|
102
|
+
loadVarFuncsToGlobalThis();
|
|
103
|
+
}
|
|
104
|
+
// TODO: If something else need to be added, please append here
|
|
105
|
+
}
|
|
106
|
+
static REGEX_LABEL_DEF = /^label\s*[,;\s](.*)\|\|\|(.*)$/i;
|
|
107
|
+
static REGEX_WIRE_DEF = /^wire\s*[,;\s](.*)$/i;
|
|
108
|
+
static REGEX_ANGLE_MARK_DEF = /^angleMark\s*[,;\s](.*)$/i;
|
|
109
|
+
static REGEX_DIM_MARK_DEF = /^dimMark\s*[,;\s](.*)$/i;
|
|
110
|
+
static REGEX_SHAPE_AND_DRAWING_DEF = /^([^\|]*)\|\|\|(.*)$/i;
|
|
111
|
+
/**
|
|
112
|
+
* The shortcut function to replace all the vars in an expression with actual values.
|
|
113
|
+
* @param expr The expression to be replaced
|
|
114
|
+
* @returns The result after replaced all the vars
|
|
115
|
+
*/
|
|
116
|
+
replaceVars(expr) {
|
|
117
|
+
// If already a number, then skip the replacing
|
|
118
|
+
if (WT_REGEX.FLOAT_NUMBER.test(expr))
|
|
119
|
+
return expr;
|
|
120
|
+
const sortedVars = this.vars
|
|
121
|
+
.map(v => ({ name: v.name, value: v.value }))
|
|
122
|
+
.sort((a, b) => b.name.length - a.name.length); // Make the longest var be replaced firstly
|
|
123
|
+
for (let i = 0; i < sortedVars.length; i++) {
|
|
124
|
+
const varRegex = new RegExp(`(^|[^.])\\b${sortedVars[i].name}\\b`, "g");
|
|
125
|
+
expr = expr.replace(varRegex, (m, g1) => `${g1}(${sortedVars[i].value})`);
|
|
126
|
+
}
|
|
127
|
+
const unReplacedNames = [...expr.matchAll(/(^|[^.])\b([a-z][a-z0-9_]*)\b([^.]|$)/ig)]
|
|
128
|
+
.map(x => x[2]);
|
|
129
|
+
if (unReplacedNames.length > 0) {
|
|
130
|
+
unReplacedNames.forEach(x => {
|
|
131
|
+
this.varErrors.push(`Var [${x}] has not been defined!`);
|
|
132
|
+
});
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return expr;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A shortcut function to replace all the occurred point coordinates in a point composing expression
|
|
141
|
+
* @param expr The point coordinate consisting of something like `pA.x`
|
|
142
|
+
* @returns The expression with `pA.x` replaced with 3 and `pA.y` with 4,
|
|
143
|
+
* if `pA = (3, 4)`
|
|
144
|
+
*/
|
|
145
|
+
replaceCoors(expr) {
|
|
146
|
+
let exprCopy = expr;
|
|
147
|
+
const reGetCoors = /\b([a-z][a-z0-9_]*)[.](x|y)\b/ig;
|
|
148
|
+
const notFoundPoints = [];
|
|
149
|
+
let reExec = reGetCoors.exec(expr);
|
|
150
|
+
while (reExec) {
|
|
151
|
+
let pointName = reExec[1];
|
|
152
|
+
let point = this.points.find(x => x.name === pointName);
|
|
153
|
+
if (point) {
|
|
154
|
+
exprCopy = exprCopy.replace(new RegExp(`\\b${point.name}[.]x\\b`), `(${point.x})`)
|
|
155
|
+
.replace(new RegExp(`\\b${point.name}[.]y\\b`), `(${point.y})`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (!notFoundPoints.includes(pointName))
|
|
159
|
+
notFoundPoints.push(pointName);
|
|
160
|
+
}
|
|
161
|
+
// Search For Next
|
|
162
|
+
reExec = exprCopy ? reGetCoors.exec(expr) : null;
|
|
163
|
+
}
|
|
164
|
+
if (notFoundPoints.length > 0) {
|
|
165
|
+
notFoundPoints.forEach(x => this.pointErrors.push(`Point "${x}" has not been defined!`));
|
|
166
|
+
exprCopy = "";
|
|
167
|
+
}
|
|
168
|
+
return exprCopy;
|
|
169
|
+
}
|
|
170
|
+
replaceModelParams(expr) {
|
|
171
|
+
let exprCopy = expr;
|
|
172
|
+
const reGetParam = /\b([a-z][a-z0-9_]*)[.](r|rx|ry|rin|rout|rotdeg|rotang|angs|ange|angrange|w|h|len|deg|degr|degs|degsr|dege|deger|clens|clene)\b/ig;
|
|
173
|
+
const notFoundModels = [];
|
|
174
|
+
const notFoundScalars = [];
|
|
175
|
+
let reExec = reGetParam.exec(expr);
|
|
176
|
+
while (reExec) {
|
|
177
|
+
let modelName = reExec[1];
|
|
178
|
+
let paramName = reExec[2];
|
|
179
|
+
let path = this.paths.find(x => x.name === modelName);
|
|
180
|
+
if (path && path.model) {
|
|
181
|
+
let param = path.model.getParam(paramName);
|
|
182
|
+
if (param !== undefined) {
|
|
183
|
+
const re = new RegExp(`\\b${modelName}[.]${paramName}\\b`);
|
|
184
|
+
exprCopy = exprCopy.replace(re, `(${param})`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
let nfScalar = `${modelName}.${paramName}`;
|
|
188
|
+
if (!notFoundScalars.includes(nfScalar))
|
|
189
|
+
notFoundScalars.push(nfScalar);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.varErrors.push(`"${modelName}" has not been defined as a shape!`);
|
|
194
|
+
if (!notFoundModels.includes(modelName))
|
|
195
|
+
notFoundModels.push(modelName);
|
|
196
|
+
}
|
|
197
|
+
// Search For Next
|
|
198
|
+
reExec = exprCopy ? reGetParam.exec(expr) : null;
|
|
199
|
+
}
|
|
200
|
+
if (notFoundModels.length > 0 || notFoundScalars.length > 0) {
|
|
201
|
+
notFoundModels.forEach(x => this.varErrors.push(`"${x}" has not been defined as a shape!`));
|
|
202
|
+
notFoundScalars.forEach(x => this.varErrors.push(`"${x}" cannot to be resolved to any scalar!`));
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
return exprCopy;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The function to resolve a user input VAR
|
|
209
|
+
* @param expr The user input of a VAR
|
|
210
|
+
* @returns the resolved result of the VAR
|
|
211
|
+
*/
|
|
212
|
+
resolveVarExpr(expr) {
|
|
213
|
+
// If the expression has some comma or semicolon, then stop resolving
|
|
214
|
+
if (expr.match(/[,;]/)) {
|
|
215
|
+
this.varErrors.push(`Var Definition mustn't have comma(,) or semi-colon(,)!`);
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
// Replace PI and Math functions, like, sin, cos, exp, pi, etc.
|
|
219
|
+
expr = replaceMathPiAndFuncs(expr);
|
|
220
|
+
// Replace GeometryModel parameters
|
|
221
|
+
expr = this.replaceModelParams(expr);
|
|
222
|
+
if (!expr)
|
|
223
|
+
return undefined;
|
|
224
|
+
// Replace Var names to values
|
|
225
|
+
expr = this.replaceVars(expr);
|
|
226
|
+
if (!expr)
|
|
227
|
+
return undefined;
|
|
228
|
+
// Evaluate the final expression
|
|
229
|
+
return evalExpr2Number(expr);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Resolve the var provided, and add it into var list.
|
|
233
|
+
* If some error encountered, then some error message will be returned.
|
|
234
|
+
* @param name Name of the var
|
|
235
|
+
* @param value Presentation of the var
|
|
236
|
+
*/
|
|
237
|
+
resolveVar(name, value) {
|
|
238
|
+
if (WtHuatu.RESERVED_NAMES.includes(name.toLowerCase())) {
|
|
239
|
+
return [`Function Names (here you input "${name}") cannot be used as Var Name!`];
|
|
240
|
+
}
|
|
241
|
+
if (this.vars.find(v => v.name === name)) {
|
|
242
|
+
return [`There has already been a var named as ${name}`];
|
|
243
|
+
}
|
|
244
|
+
if (WT_REGEX.FLOAT_NUMBER.test(value)) {
|
|
245
|
+
this.vars.push(new HuatuVar(name, parseFloat(value), "direct"));
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
this.varErrors = [];
|
|
249
|
+
const varRes = this.resolveVarExpr(value);
|
|
250
|
+
if (varRes !== undefined) {
|
|
251
|
+
this.vars.push(new HuatuVar(name, varRes, "derived"));
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
return [...this.varErrors];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
addPoint(point) {
|
|
259
|
+
this.points.push(point);
|
|
260
|
+
// Sort the points by which having longest name be the first
|
|
261
|
+
this.points.sort((p1, p2) => p2.name.length - p1.name.length);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The unified method to resolve/calculate the X/Y coordinate of a Point.
|
|
265
|
+
* It can resolve some thing like `pA.x + sin(b) * (5 + r) + pB.y`
|
|
266
|
+
* It will replace the coors of other point(s) and replace functions and vars
|
|
267
|
+
* Then finally evaluate the result.
|
|
268
|
+
* @param expr The expression of X/Y coordinate of a Point
|
|
269
|
+
* @returns The resolved coordinate, a number
|
|
270
|
+
*/
|
|
271
|
+
resolvePointCoorsExpr(expr) {
|
|
272
|
+
if (WT_REGEX.FLOAT_NUMBER.test(expr.trim())) {
|
|
273
|
+
return parseFloat(expr.trim());
|
|
274
|
+
}
|
|
275
|
+
// Replace p.X and p.Y to numbers
|
|
276
|
+
expr = this.replaceCoors(expr);
|
|
277
|
+
if (!expr)
|
|
278
|
+
return undefined;
|
|
279
|
+
// Resolve as Var Expression
|
|
280
|
+
return this.resolveVarExpr(expr);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Resolve a point from some PointSeries(with index) or Some Point (by name), or
|
|
284
|
+
* from the definition of XY coordinates.
|
|
285
|
+
* @param expr The string to query of build a point
|
|
286
|
+
* @returns The identified point or undefined
|
|
287
|
+
*/
|
|
288
|
+
resolvePointFromExpr(expr) {
|
|
289
|
+
// Check if it is referring some defined Point
|
|
290
|
+
const definedPoint = this.points.find(p => p.name === expr);
|
|
291
|
+
if (definedPoint) {
|
|
292
|
+
return definedPoint.toPoint();
|
|
293
|
+
}
|
|
294
|
+
// Check if the expr is like pathCirc.cin or rect.ne, etc
|
|
295
|
+
const cpTry = /^([a-z][a-z0-9_]*)[.](n|e|s|w|ne|nw|se|sw|a|b|c|o|m|cin|cout|cg|[0-9]+)$/i.exec(expr);
|
|
296
|
+
if (cpTry) {
|
|
297
|
+
const shapeName = cpTry[1];
|
|
298
|
+
const pathTry = this.paths.find(x => x.name === shapeName);
|
|
299
|
+
if (pathTry && pathTry.model) {
|
|
300
|
+
let pTag = cpTry[2];
|
|
301
|
+
if (/[0-9]+/.exec(pTag)) {
|
|
302
|
+
pTag = parseInt(pTag);
|
|
303
|
+
}
|
|
304
|
+
return pathTry.model.getPoint(pTag);
|
|
305
|
+
}
|
|
306
|
+
else if (pathTry?.path instanceof PathSegmentedPath) {
|
|
307
|
+
return pathTry.path.getPoint(cpTry[2].toLowerCase());
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Check if it is referring some point in a Point Series
|
|
314
|
+
const match = expr.match(WT_REGEX.VAR_WITH_INDEXING);
|
|
315
|
+
if (match) {
|
|
316
|
+
const vName = match[1];
|
|
317
|
+
const vIndex = parseInt(match[3] || match[4]);
|
|
318
|
+
const ps = this.pointSeries.find(s => s.name === vName);
|
|
319
|
+
if (ps) {
|
|
320
|
+
return ps.getPoint(vIndex - 1);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
let path = this.paths.find(p => p.name === vName);
|
|
324
|
+
if (path && (path.path instanceof PathPolygon ||
|
|
325
|
+
path.path instanceof PathPolyline)) {
|
|
326
|
+
return vIndex >= 1 && vIndex <= path.path.points.length
|
|
327
|
+
? path.path.points[vIndex - 1]
|
|
328
|
+
: undefined;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Check if it defined as form of a point XY pair
|
|
333
|
+
const pXY = resolvePointLike(expr);
|
|
334
|
+
if (pXY) {
|
|
335
|
+
const pX = this.resolvePointCoorsExpr(pXY.x);
|
|
336
|
+
const pY = this.resolvePointCoorsExpr(pXY.y);
|
|
337
|
+
if (pX !== undefined && pY !== undefined) {
|
|
338
|
+
return new SvgPoint(pX, pY);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Resolve the point directly defined, by supplying name and definition
|
|
345
|
+
* @param name name of the point
|
|
346
|
+
* @param value Presented form to calculate the point
|
|
347
|
+
*/
|
|
348
|
+
resolvePoint(name, value) {
|
|
349
|
+
if (this.points.find(p => p.name === name)) {
|
|
350
|
+
return [`There has already been a point named as ${name}`];
|
|
351
|
+
}
|
|
352
|
+
const pXY = resolvePointLike(value);
|
|
353
|
+
if (!pXY) {
|
|
354
|
+
return [`Format [${value}] of the definition is incorrect!`];
|
|
355
|
+
}
|
|
356
|
+
;
|
|
357
|
+
if (WT_REGEX.FLOAT_NUMBER.test(pXY.x) && WT_REGEX.FLOAT_NUMBER.test(pXY.y)) {
|
|
358
|
+
this.addPoint(new HuatuPoint(parseFloat(pXY.x), parseFloat(pXY.y), name, "direct"));
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
this.varErrors = [];
|
|
362
|
+
this.pointErrors = [];
|
|
363
|
+
const pX = this.resolvePointCoorsExpr(pXY.x);
|
|
364
|
+
if (pX === undefined) {
|
|
365
|
+
this.pointErrors.push(`X part [${pXY.x}] is incorrect!`);
|
|
366
|
+
this.pointErrors.push(...this.varErrors);
|
|
367
|
+
this.varErrors = [];
|
|
368
|
+
}
|
|
369
|
+
const pY = this.resolvePointCoorsExpr(pXY.y);
|
|
370
|
+
if (pY === undefined) {
|
|
371
|
+
this.pointErrors.push(`Y part [${pXY.y}] is incorrect!`);
|
|
372
|
+
this.pointErrors.push(...this.varErrors);
|
|
373
|
+
this.varErrors = [];
|
|
374
|
+
}
|
|
375
|
+
if (pX !== undefined && pY !== undefined) {
|
|
376
|
+
this.addPoint(new HuatuPoint(pX, pY, name, "derived"));
|
|
377
|
+
}
|
|
378
|
+
return [...this.pointErrors];
|
|
379
|
+
}
|
|
380
|
+
resolveSeparatedNumbers(def) {
|
|
381
|
+
return def.split(/[,]/).map(x => x.trim()).filter(x => !!x)
|
|
382
|
+
.map(x => this.resolveVarExpr(x))
|
|
383
|
+
.filter(x => x !== undefined);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* This function is some unified method to resolve the (Geometry) Model of the shape defined by user.
|
|
387
|
+
* This function is normally only be used under this module.
|
|
388
|
+
* @param def The Definition string to build/specify one Shape, e.g. "circle;p0;5" means making some circle
|
|
389
|
+
* centered at point p0 and having radius of 5
|
|
390
|
+
* @returns Error Message if errored or the resolved Model of the shape
|
|
391
|
+
*/
|
|
392
|
+
resolveShapeDef(def) {
|
|
393
|
+
const params = def.split(";").map(p => p.trim()).filter(p => !!p);
|
|
394
|
+
if (params.length < 2)
|
|
395
|
+
return `Parameters are too less to build a shape/path!`;
|
|
396
|
+
const shapeType = params[0].toLowerCase();
|
|
397
|
+
let p1, p2, p3, p4, rx, ry;
|
|
398
|
+
let line, deg, l1, l2, ln;
|
|
399
|
+
let x1, y1, width, height;
|
|
400
|
+
let ps, pts;
|
|
401
|
+
let pathReferred, pathDerived;
|
|
402
|
+
let dx, dy, pMoveOrAt, rotDeg;
|
|
403
|
+
switch (shapeType) {
|
|
404
|
+
case "ln": // Line, LineSeg, and PolyLine, Unified definition
|
|
405
|
+
case "line": // Line, LineSeg, and PolyLine, Unified definition
|
|
406
|
+
if (params.length < 3)
|
|
407
|
+
return `Parameters are too less to build a Line/LineSeg!`;
|
|
408
|
+
// Try first param, to see it if is defined Line
|
|
409
|
+
line = this.paths.find(s => s.name === params[1]);
|
|
410
|
+
if (line) { // The def is like: line; ln1; l1; l2;
|
|
411
|
+
if (!(line.model instanceof Line))
|
|
412
|
+
return `"${params[1]}" is not a Line!`;
|
|
413
|
+
const l1 = this.resolveVarExpr(params[2]);
|
|
414
|
+
if (l1 === undefined)
|
|
415
|
+
return `Length from Line (${params[2]}) is invalid`;
|
|
416
|
+
const l2 = params.length > 3 ? this.resolveVarExpr(params[3]) : undefined;
|
|
417
|
+
return line.model.buildSeg(l1, l2);
|
|
418
|
+
}
|
|
419
|
+
// Now the first param should be a point
|
|
420
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
421
|
+
if (!p1)
|
|
422
|
+
return `The first parameter should be a Point to define a Line/LineSeg/PolyLine!`;
|
|
423
|
+
p2 = this.resolvePointFromExpr(params[2]);
|
|
424
|
+
if (p2) { // In this case, the def is like: line; p1; p2; p3; ... (only valid points picked)
|
|
425
|
+
if (params.length < 4)
|
|
426
|
+
return new LineSeg(p1, p2);
|
|
427
|
+
const pts = [p1, p2];
|
|
428
|
+
for (let idx = 3; idx < params.length; idx++) {
|
|
429
|
+
const p = this.resolvePointFromExpr(params[idx]);
|
|
430
|
+
if (p)
|
|
431
|
+
pts.push(p);
|
|
432
|
+
}
|
|
433
|
+
return new PolyLine(...pts);
|
|
434
|
+
}
|
|
435
|
+
deg = this.resolveVarExpr(params[2]);
|
|
436
|
+
if (deg === undefined)
|
|
437
|
+
return `Slope Angle (${params[2]}) is incorrect!`;
|
|
438
|
+
// The def is like: line; p; deg; l1 (optional); l2 (optional); ... (the rest discarded)
|
|
439
|
+
ln = new Line(p1, AngDeg.fitDeg(deg));
|
|
440
|
+
if (params.length < 4) { // It is just a line
|
|
441
|
+
return ln;
|
|
442
|
+
}
|
|
443
|
+
l1 = this.resolveVarExpr(params[3]);
|
|
444
|
+
if (l1 === undefined)
|
|
445
|
+
return ln; // If the length definition is invalid, then just return the Line
|
|
446
|
+
l2 = params.length > 4 ? this.resolveVarExpr(params[4]) : undefined;
|
|
447
|
+
return ln.buildSeg(l1, l2);
|
|
448
|
+
case "ls":
|
|
449
|
+
case "lineseg":
|
|
450
|
+
if (params.length < 3)
|
|
451
|
+
return `Parameters are too less to build a Line/LineSeg!`;
|
|
452
|
+
// Format of Line Segment:
|
|
453
|
+
// - lineseg; lineName; l1; l2;
|
|
454
|
+
// - lineseg; (x1, y1); (x2, y2);
|
|
455
|
+
// - lineseg; (x, y); deg; l1; l2;
|
|
456
|
+
line = this.paths.find(s => s.name === params[1]);
|
|
457
|
+
if (line) {
|
|
458
|
+
if (!(line.model instanceof Line))
|
|
459
|
+
return `The Shape you picked (${params[1]}) to derive a Line Seg is not a Line!`;
|
|
460
|
+
const l1 = this.resolveVarExpr(params[2]);
|
|
461
|
+
if (l1 === undefined)
|
|
462
|
+
return `Length from Line (${params[2]}) is invalid`;
|
|
463
|
+
const l2 = params.length > 3 ? this.resolveVarExpr(params[3]) : undefined;
|
|
464
|
+
return line.model.buildSeg(l1, l2);
|
|
465
|
+
}
|
|
466
|
+
const pStart = this.resolvePointFromExpr(params[1]);
|
|
467
|
+
const pEnd = this.resolvePointFromExpr(params[2]);
|
|
468
|
+
if (!pStart)
|
|
469
|
+
return `Start Point (${params[1]}) of the Line Seg is invalid!`;
|
|
470
|
+
if (pEnd)
|
|
471
|
+
return new LineSeg(pStart, pEnd);
|
|
472
|
+
deg = this.resolveVarExpr(params[2]);
|
|
473
|
+
if (deg === undefined)
|
|
474
|
+
return `Line Slope degree (${params[2]}) is incorrect!`;
|
|
475
|
+
ln = new Line(pStart, AngDeg.fitDeg(deg));
|
|
476
|
+
if (params.length < 4) {
|
|
477
|
+
return "Length definition from Line is incorrect, no length specified!";
|
|
478
|
+
}
|
|
479
|
+
l1 = this.resolveVarExpr(params[3]);
|
|
480
|
+
if (l1 === undefined)
|
|
481
|
+
return `Line Length (${params[3]}) is incorrect!`;
|
|
482
|
+
l2 = params.length > 4 ? this.resolveVarExpr(params[4]) : undefined;
|
|
483
|
+
return ln.buildSeg(l1, l2);
|
|
484
|
+
case "pln":
|
|
485
|
+
case "pline":
|
|
486
|
+
case "polyline":
|
|
487
|
+
// Check if the first param is point series
|
|
488
|
+
ps = this.pointSeries.find(x => x.name === params[1]);
|
|
489
|
+
if (ps) {
|
|
490
|
+
if (ps.points.length < 2)
|
|
491
|
+
return `Point Series [${params[1]}] has only one point!`;
|
|
492
|
+
else
|
|
493
|
+
pts = ps.points;
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
pts = params.filter((p, i) => i > 0)
|
|
497
|
+
.map(p => this.resolvePointFromExpr(p))
|
|
498
|
+
.filter(p => !!p);
|
|
499
|
+
}
|
|
500
|
+
if (pts.length < 2)
|
|
501
|
+
return `Points provided are too less, need to be 2 (valid) points at least!`;
|
|
502
|
+
return new PolyLine(...pts);
|
|
503
|
+
case "circ":
|
|
504
|
+
case "circle":
|
|
505
|
+
if (params.length < 3)
|
|
506
|
+
return `Parameters are too less to build a Circle!`;
|
|
507
|
+
// Format of a circle define: circle; (x, y); r
|
|
508
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
509
|
+
if (!p1)
|
|
510
|
+
return `Circle center (${params[1]}) is invalid!`;
|
|
511
|
+
let radius = this.resolveVarExpr(params[2]);
|
|
512
|
+
if (radius === undefined)
|
|
513
|
+
return `Circle radius (${params[2]}) is invalid!`;
|
|
514
|
+
if (WtMath.isZero(radius))
|
|
515
|
+
return `Circle radius (${params[2]}) resolved is zero, which is invalid!`;
|
|
516
|
+
if (radius < 0)
|
|
517
|
+
this.warnings.push(`Circle radius used some negative value('${radius}' calculated from '${params[2]}'), its absolute will be used!`);
|
|
518
|
+
return new Circle(p1.x, p1.y, radius);
|
|
519
|
+
case "ring":
|
|
520
|
+
if (params.length < 4)
|
|
521
|
+
return `Parameters are too less to build a Ring!`;
|
|
522
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
523
|
+
let r1 = this.resolveVarExpr(params[2]);
|
|
524
|
+
let r2 = this.resolveVarExpr(params[3]);
|
|
525
|
+
if (!p1)
|
|
526
|
+
return `Center (${params[1]}) is invalid to build a Ring!`;
|
|
527
|
+
if (!r1 || !r2)
|
|
528
|
+
return `Inner/Outer radius for a ring must both be set (and positive)!`;
|
|
529
|
+
return new Ring(p1.x, p1.y, r1, r2);
|
|
530
|
+
case "rect":
|
|
531
|
+
case "rectangle":
|
|
532
|
+
if (params.length < 3)
|
|
533
|
+
return `Parameters are too less to build a Rectangle!`;
|
|
534
|
+
// Format of Rectangle:
|
|
535
|
+
// - rect; bycenter; (x, y); w; h
|
|
536
|
+
// - rect; (x1, y1); (x2, y2)
|
|
537
|
+
// - rect; (x1, y1); w; h
|
|
538
|
+
if (params[1].toLowerCase() === "bycenter" && params.length >= 5) {
|
|
539
|
+
const pCenter = this.resolvePointFromExpr(params[2]);
|
|
540
|
+
if (!pCenter)
|
|
541
|
+
return `Center definition (${params[2]}) is invalid.`;
|
|
542
|
+
const w = this.resolveVarExpr(params[3]);
|
|
543
|
+
if (!w || w <= 0)
|
|
544
|
+
return `Width must be greater than Zero, it is now '${w}' (resolved from '${params[3]}')`;
|
|
545
|
+
const h = this.resolveVarExpr(params[4]);
|
|
546
|
+
if (!h || h <= 0)
|
|
547
|
+
return `Height must be greater than Zero, it is now '${h}' (resolved from '${params[4]}')`;
|
|
548
|
+
x1 = pCenter.x - w / 2;
|
|
549
|
+
y1 = pCenter.y - h / 2;
|
|
550
|
+
width = w;
|
|
551
|
+
height = h;
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
555
|
+
if (!p1)
|
|
556
|
+
return `The start point (${params[1]}) of the rectangle is invalid`;
|
|
557
|
+
p2 = this.resolvePointFromExpr(params[2]);
|
|
558
|
+
const w = this.resolveVarExpr(params[2]);
|
|
559
|
+
if (p2) {
|
|
560
|
+
width = Math.abs(p1.x - p2.x);
|
|
561
|
+
height = Math.abs(p1.y - p2.y);
|
|
562
|
+
if (WtMath.isZero(width))
|
|
563
|
+
return "Rectangle Width is zero!";
|
|
564
|
+
if (WtMath.isZero(height))
|
|
565
|
+
return "Rectangle Height is zero!";
|
|
566
|
+
x1 = Math.min(p1.x, p2.x);
|
|
567
|
+
y1 = Math.min(p1.y, p2.y);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
if (!w || w <= 0)
|
|
571
|
+
return `Width must be greater than Zero, it is now '${w}' (resolved from '${params[3]}')`;
|
|
572
|
+
width = w;
|
|
573
|
+
if (params.length >= 4) {
|
|
574
|
+
const h = this.resolveVarExpr(params[3]);
|
|
575
|
+
if (!h || h <= 0)
|
|
576
|
+
return `Height must be greater than Zero, it is now '${h}' (resolved from '${params[4]}')`;
|
|
577
|
+
height = h;
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
height = w; // If Height is not given, then it is a square, height = width
|
|
581
|
+
return `Rectangle Height might be undefined!`;
|
|
582
|
+
}
|
|
583
|
+
x1 = p1.x;
|
|
584
|
+
y1 = p1.y;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return new Rect(x1, y1, width, height);
|
|
588
|
+
case "tri":
|
|
589
|
+
case "triangle":
|
|
590
|
+
// Format of Triangle: triangle; (x1, y1); (x2, y2); (x3, y3)
|
|
591
|
+
if (params.length < 4)
|
|
592
|
+
return `Parameters to build a triangle are not sufficient!`;
|
|
593
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
594
|
+
p2 = this.resolvePointFromExpr(params[2]);
|
|
595
|
+
p3 = this.resolvePointFromExpr(params[3]);
|
|
596
|
+
if (!p1)
|
|
597
|
+
return `The first point (${params[1]}) is invalid!`;
|
|
598
|
+
if (!p2)
|
|
599
|
+
return `The second point (${params[2]}) is invalid!`;
|
|
600
|
+
if (!p3)
|
|
601
|
+
return `The third point (${params[3]}) is invalid!`;
|
|
602
|
+
return new Triangle(p1, p2, p3);
|
|
603
|
+
case "plg":
|
|
604
|
+
case "polygon":
|
|
605
|
+
// Check if the first param is point series
|
|
606
|
+
ps = this.pointSeries.find(x => x.name === params[1]);
|
|
607
|
+
if (ps) {
|
|
608
|
+
if (ps.points.length < 2)
|
|
609
|
+
return `Point Series [${params[1]}] has only one point!`;
|
|
610
|
+
else
|
|
611
|
+
pts = ps.points;
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// Format of the polygons: plg; (x1, y1); (x2, y2); (x3, y3); ...;(xn, yn)
|
|
615
|
+
if (params.length < 4)
|
|
616
|
+
return `Parameters for the Polygon is insufficient!`;
|
|
617
|
+
pts = params.filter((p, i) => i > 0)
|
|
618
|
+
.map(p => {
|
|
619
|
+
const tryPoint = this.resolvePointFromExpr(p);
|
|
620
|
+
if (!tryPoint)
|
|
621
|
+
this.warnings.push(`When building a Polygon, '${p}' resolved to no Point!`);
|
|
622
|
+
return tryPoint;
|
|
623
|
+
})
|
|
624
|
+
.filter(p => !!p);
|
|
625
|
+
}
|
|
626
|
+
if (pts.length < 3)
|
|
627
|
+
return `The valid points are less than 3, no polygon is defined!`;
|
|
628
|
+
return new Polygon(...pts);
|
|
629
|
+
case "plgn":
|
|
630
|
+
case "npolygon":
|
|
631
|
+
// Format of the regular polygon N: plgn; N; (cx, cy); radius; start_deg;
|
|
632
|
+
if (params.length < 5)
|
|
633
|
+
return `Parameters for Regular N-Polygon is insufficient`;
|
|
634
|
+
const N = parseInt(params[1]);
|
|
635
|
+
if (!N || N < 0)
|
|
636
|
+
return `Please give a integer for N, [${params[1]}] is invalid!`;
|
|
637
|
+
p1 = this.resolvePointFromExpr(params[2]);
|
|
638
|
+
if (!p1)
|
|
639
|
+
return `Center for the Regular N-Polygon is invalid!`;
|
|
640
|
+
rx = this.resolveVarExpr(params[3]);
|
|
641
|
+
if (rx === undefined)
|
|
642
|
+
return `Radius for the regular N-Polygon is invalid!`;
|
|
643
|
+
deg = this.resolveVarExpr(params[4]) ?? 0;
|
|
644
|
+
return Polygon.buildRegularPolygon(N, p1, rx, deg);
|
|
645
|
+
case "ell":
|
|
646
|
+
case "ellipse":
|
|
647
|
+
// Format of the Ellipse; ellipse; (x, y); rx; ry; deg
|
|
648
|
+
if (params.length < 4)
|
|
649
|
+
return `Parameters for the Ellipse is insufficient!`;
|
|
650
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
651
|
+
rx = this.resolveVarExpr(params[2]);
|
|
652
|
+
ry = this.resolveVarExpr(params[3]);
|
|
653
|
+
deg = params.length > 4 ? this.resolveVarExpr(params[4]) : 0;
|
|
654
|
+
if (!p1)
|
|
655
|
+
return `Ellipse Center (${params[1]}) is invalid!`;
|
|
656
|
+
if (!rx || rx <= 0)
|
|
657
|
+
return `Ellipse RX (${params[2]}, resolved as '${rx}') is invalid!`;
|
|
658
|
+
if (!ry || ry <= 0)
|
|
659
|
+
return `Ellipse RY (${params[3]}, resolved as '${ry}') is invalid!`;
|
|
660
|
+
if (deg === undefined)
|
|
661
|
+
return `Ellipse Rotating Degree (${params[4]}) cannot be resolved!`;
|
|
662
|
+
return new Ellipse(p1.x, p1.y, rx, ry, AngDeg.fitDeg(deg));
|
|
663
|
+
case "b2":
|
|
664
|
+
case "bezier2":
|
|
665
|
+
// Format of Bezier2: bezier2; (xStart, yStart); (xCtrl, yCtrl); (xEnd, yEnd)
|
|
666
|
+
if (params.length < 4)
|
|
667
|
+
return `Parameters to build Bezier2 are not sufficient!`;
|
|
668
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
669
|
+
p2 = this.resolvePointFromExpr(params[2]);
|
|
670
|
+
p3 = this.resolvePointFromExpr(params[3]);
|
|
671
|
+
if (!p1)
|
|
672
|
+
return `The Start point (${params[1]}) is invalid!`;
|
|
673
|
+
if (!p2)
|
|
674
|
+
return `The Control point (${params[2]}) is invalid!`;
|
|
675
|
+
if (!p3)
|
|
676
|
+
return `The End point (${params[3]}) is invalid!`;
|
|
677
|
+
return new Bezier2(p1, p2, p3);
|
|
678
|
+
case "b3":
|
|
679
|
+
case "bezier3":
|
|
680
|
+
// Format of Bezier3: bezier3; (xStart, yStart); (xCtrl1, yCtrl1); (xCtrl2, yCtrl2); (xEnd, yEnd)
|
|
681
|
+
if (params.length < 5)
|
|
682
|
+
return `Parameters to build Bezier3 are not sufficient!`;
|
|
683
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
684
|
+
p2 = this.resolvePointFromExpr(params[2]);
|
|
685
|
+
p3 = this.resolvePointFromExpr(params[3]);
|
|
686
|
+
p4 = this.resolvePointFromExpr(params[4]);
|
|
687
|
+
if (!p1)
|
|
688
|
+
return `The Start point (${params[1]}) is invalid!`;
|
|
689
|
+
if (!p2)
|
|
690
|
+
return `The Control Point 1 (${params[2]}) is invalid!`;
|
|
691
|
+
if (!p3)
|
|
692
|
+
return `The Control Point 2 (${params[3]}) is invalid!`;
|
|
693
|
+
if (!p4)
|
|
694
|
+
return `The End point (${params[4]}) is invalid!`;
|
|
695
|
+
return new Bezier3(p1, p2, p3, p4);
|
|
696
|
+
case "arc":
|
|
697
|
+
// Possible formats:
|
|
698
|
+
// - arc; circ1; start_deg; end_deg; ccw | cw
|
|
699
|
+
// - arc; ell1; start_deg; end_deg; ccw | cw
|
|
700
|
+
// - arc; pStart; rx; ry; rot_deg; ccw | cw; small | large; pEnd (the cw-mark or largeArc-mark could be interchanged in position)
|
|
701
|
+
// - arc; pCenter; rx; ry; rot_deg; start_deg, end_deg, ccw | cw;
|
|
702
|
+
if (params.length < 5)
|
|
703
|
+
return `Parameters to build an Arc are not sufficient!`;
|
|
704
|
+
// Make a function to build arc from Circle/Ellipse
|
|
705
|
+
const buildArcFromEllipse = (model, paramDegStart, paramDegEnd, markCW, ellName) => {
|
|
706
|
+
const degStart = this.resolveVarExpr(paramDegStart);
|
|
707
|
+
if (degStart === undefined)
|
|
708
|
+
return `Start Degree (${paramDegStart}) is invalid!`;
|
|
709
|
+
const degEnd = this.resolveVarExpr(paramDegEnd);
|
|
710
|
+
if (degEnd === undefined)
|
|
711
|
+
return `End Degree (${paramDegEnd}) is invalid!`;
|
|
712
|
+
const dirMark = markCW.toLowerCase();
|
|
713
|
+
if (dirMark !== "cw" && dirMark !== "ccw")
|
|
714
|
+
return `The Direction must be specified, either cw or ccw!`;
|
|
715
|
+
const isCW = dirMark === "cw";
|
|
716
|
+
if (model instanceof Circle) {
|
|
717
|
+
return EllipticalArc.fromCircle(model, degStart, degEnd, isCW);
|
|
718
|
+
}
|
|
719
|
+
if (model instanceof Ellipse) {
|
|
720
|
+
return EllipticalArc.fromEllipse(model, degStart, degEnd, isCW);
|
|
721
|
+
}
|
|
722
|
+
return `The Shape (${ellName}) is not a Circle/Ellipse!`;
|
|
723
|
+
};
|
|
724
|
+
// Check if Arc derived from some Circle/Ellipse
|
|
725
|
+
const shape = this.paths.find(s => s.name === params[1]);
|
|
726
|
+
if (shape) {
|
|
727
|
+
if (shape.model &&
|
|
728
|
+
((shape.model instanceof Circle) || (shape.model instanceof Ellipse))) {
|
|
729
|
+
return buildArcFromEllipse(shape.model, params[2], params[3], params[4], params[1]);
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
return `Shape [${params[1]}] is not a Circle or an Ellipse!`;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (params.length < 8)
|
|
736
|
+
return `Parameters are insufficient to build an Arc!`;
|
|
737
|
+
// Arc built in parametric way
|
|
738
|
+
p1 = this.resolvePointFromExpr(params[1]);
|
|
739
|
+
rx = this.resolveVarExpr(params[2]);
|
|
740
|
+
ry = this.resolveVarExpr(params[3]);
|
|
741
|
+
deg = this.resolveVarExpr(params[4]);
|
|
742
|
+
if (!p1)
|
|
743
|
+
return `The Ellipse/Circle Center or Arc Start point must be valid!`;
|
|
744
|
+
if (!rx || rx <= 0)
|
|
745
|
+
return `RX (${params[2]}) is invalid!`;
|
|
746
|
+
if (!ry || ry <= 0)
|
|
747
|
+
return `RY (${params[3]}) is invalid!`;
|
|
748
|
+
if (deg === undefined)
|
|
749
|
+
return `Ellipse/Circle Rotating Degree (${params[3]}) is invalid!`;
|
|
750
|
+
let cwMark = undefined;
|
|
751
|
+
let largeArcMark = undefined;
|
|
752
|
+
const mark1 = params[5].toLowerCase();
|
|
753
|
+
const mark2 = params[6].toLowerCase();
|
|
754
|
+
if (mark1 === "cw" || mark1 === "ccw")
|
|
755
|
+
cwMark = mark1 === "cw";
|
|
756
|
+
if (mark2 === "cw" || mark2 === "ccw")
|
|
757
|
+
cwMark = mark2 === "cw";
|
|
758
|
+
if (mark1 === "large" || mark1 === "small")
|
|
759
|
+
largeArcMark = mark1 === "large";
|
|
760
|
+
if (mark2 === "large" || mark2 === "small")
|
|
761
|
+
largeArcMark = mark2 === "large";
|
|
762
|
+
if (cwMark !== undefined && largeArcMark !== undefined) { // This the parametric design from start/end points
|
|
763
|
+
p2 = this.resolvePointFromExpr(params[7]);
|
|
764
|
+
if (!p2)
|
|
765
|
+
return `The Arc End point (${params[7]}) is invalid!`;
|
|
766
|
+
if (Point.areIdentical(p1, p2, 4e-3))
|
|
767
|
+
return `Arc Point Start (${params[1]}) and End(${params[7]}) are identical, thus cannot make any arc!`;
|
|
768
|
+
const arc = EllipticalArc.fromStartEndPoints(p1, p2, rx, ry, largeArcMark, cwMark, deg);
|
|
769
|
+
if (!arc.auxEllipse)
|
|
770
|
+
this.warnings.push(`Arc definition '${def}' resolved to no valid Ellipse, thus no arc is resolved!`);
|
|
771
|
+
if (arc.isEnlarged)
|
|
772
|
+
this.warnings.push(`Arc is enlarged to (Rx:${arc.rx.toFixed(3)}, Ry:${arc.ry.toFixed(3)}), because with the given values(Rx:${rx}, Ry:${ry}) no arc could be solved out.`);
|
|
773
|
+
return arc;
|
|
774
|
+
}
|
|
775
|
+
if (cwMark === undefined && largeArcMark === undefined) { // In this case, this two parameters should be start/end degrees
|
|
776
|
+
const ell = new Ellipse(p1.x, p1.y, rx, ry, AngDeg.fitDeg(deg));
|
|
777
|
+
return buildArcFromEllipse(ell, params[5], params[6], params[7], params.filter((p, i) => i > 0 && i < 5).join(", "));
|
|
778
|
+
}
|
|
779
|
+
return `Bad configuration to build an Arc!`;
|
|
780
|
+
case "movex":
|
|
781
|
+
// Format is: moveX; pathDefined; dx
|
|
782
|
+
if (params.length < 3)
|
|
783
|
+
return `Parameters too less to make Path MoveX derivation!`;
|
|
784
|
+
pathReferred = this.paths.find(x => x.name === params[1]);
|
|
785
|
+
if (!pathReferred)
|
|
786
|
+
return `Shape You referred(${params[1]}) doesn't exist!`;
|
|
787
|
+
dx = this.resolveVarExpr(params[2]);
|
|
788
|
+
if (dx === undefined)
|
|
789
|
+
return `DX definition '${params[2]}' cannot resolve to a valid number!`;
|
|
790
|
+
pathDerived = pathReferred.model?.moveBy(dx, 0);
|
|
791
|
+
if (!pathDerived)
|
|
792
|
+
return `'${def}' cannot resolve to valid model/shape/path!`;
|
|
793
|
+
return pathDerived;
|
|
794
|
+
case "movey":
|
|
795
|
+
// Format is: moveY; pathDefined; dy
|
|
796
|
+
if (params.length < 3)
|
|
797
|
+
return `Parameters too less to make Path MoveY derivation!`;
|
|
798
|
+
pathReferred = this.paths.find(x => x.name === params[1]);
|
|
799
|
+
if (!pathReferred)
|
|
800
|
+
return `Shape You referred(${params[1]}) doesn't exist!`;
|
|
801
|
+
dy = this.resolveVarExpr(params[2]);
|
|
802
|
+
if (dy === undefined)
|
|
803
|
+
return `DY definition '${params[2]}' cannot resolve to a valid number!`;
|
|
804
|
+
pathDerived = pathReferred.model?.moveBy(0, dy);
|
|
805
|
+
if (!pathDerived)
|
|
806
|
+
return `'${def}' cannot resolve to valid model/shape/path!`;
|
|
807
|
+
return pathDerived;
|
|
808
|
+
case "move":
|
|
809
|
+
case "moveby":
|
|
810
|
+
// Format is: moveBy; pathDefined; dx,dy
|
|
811
|
+
if (params.length < 3)
|
|
812
|
+
return `Parameters too less to make Path MoveBy derivation!`;
|
|
813
|
+
pathReferred = this.paths.find(x => x.name === params[1]);
|
|
814
|
+
if (!pathReferred)
|
|
815
|
+
return `Shape You referred(${params[1]}) doesn't exist!`;
|
|
816
|
+
pMoveOrAt = this.resolvePointFromExpr(params[2]);
|
|
817
|
+
if (pMoveOrAt === undefined)
|
|
818
|
+
return `MoveBy definition '${params[2]}' cannot resolve to a valid Point/Offset (dx, dy) like!`;
|
|
819
|
+
pathDerived = pathReferred.model?.moveBy(pMoveOrAt.x, pMoveOrAt.y);
|
|
820
|
+
if (!pathDerived)
|
|
821
|
+
return `'${def}' cannot resolve to valid model/shape/path!`;
|
|
822
|
+
return pathDerived;
|
|
823
|
+
case "rotate":
|
|
824
|
+
case "rotateby":
|
|
825
|
+
// Format is: rotateBy; pathDefined; rotDeg; at
|
|
826
|
+
if (params.length < 3)
|
|
827
|
+
return `Parameters too less to make Path Rotate By derivation!`;
|
|
828
|
+
pathReferred = this.paths.find(x => x.name === params[1]);
|
|
829
|
+
if (!pathReferred)
|
|
830
|
+
return `Shape You referred(${params[1]}) doesn't exist!`;
|
|
831
|
+
rotDeg = this.resolveVarExpr(params[2]);
|
|
832
|
+
if (rotDeg === undefined)
|
|
833
|
+
return `Rotation Deg (${params[2]}) cannot resolve to a number to make Path-Rotate-By transform!`;
|
|
834
|
+
pMoveOrAt = params[3] ? this.resolvePointFromExpr(params[3]) : undefined;
|
|
835
|
+
if (pMoveOrAt === undefined)
|
|
836
|
+
this.warnings.push(`When Resolve '${def}': Path Rotate-by will rotate at the center of ORIGIN since no center point specified!`);
|
|
837
|
+
pathDerived = pathReferred.model?.rotateBy(rotDeg, pMoveOrAt);
|
|
838
|
+
if (!pathDerived)
|
|
839
|
+
return `'${def}' cannot resolve to valid model/shape/path!`;
|
|
840
|
+
return pathDerived;
|
|
841
|
+
case "subof":
|
|
842
|
+
// Format is: subof; pathDefined; pointStart; pointEnd
|
|
843
|
+
if (params.length < 4)
|
|
844
|
+
return `Parameters too less to make Sub-Shape/Path!`;
|
|
845
|
+
pathReferred = this.paths.find(x => x.name === params[1]);
|
|
846
|
+
if (!pathReferred)
|
|
847
|
+
return `Shape You referred(${params[1]}) doesn't exist!`;
|
|
848
|
+
if (!pathReferred.path || !PathSegmentedPath.isSegment(pathReferred.path))
|
|
849
|
+
return `Only LineSeg/Bezier2/Bezier3/Arc could make sub-shape, '${params[1]}' is not!`;
|
|
850
|
+
p1 = this.resolvePointFromExpr(params[2]);
|
|
851
|
+
p2 = this.resolvePointFromExpr(params[3]);
|
|
852
|
+
if (!p1)
|
|
853
|
+
return `Start Point (${params[2]}) resolved failed for making Sub-Shape/Path!`;
|
|
854
|
+
if (!p2)
|
|
855
|
+
return `End Point (${params[3]}) resolved failed for making Sub-Shape/Path!`;
|
|
856
|
+
pathDerived = pathReferred.path.makeSubPath(p1, p2);
|
|
857
|
+
if (!pathDerived)
|
|
858
|
+
return `Make sub path/shape of '${params[1]}' from '${params[2]}' to '${params[3]}' failed!`;
|
|
859
|
+
return pathDerived.auxModel;
|
|
860
|
+
default:
|
|
861
|
+
return `Shape type ${params[0]} is invalid!`;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* The sub function to resolve the input of duplication-to defines
|
|
866
|
+
* @param def The definition string for the duplication
|
|
867
|
+
* @param withLineJoin A switch to determine whether introducing Line Join features, this is specialized for Segged-Path
|
|
868
|
+
* Because for basic types, there will be no such issue, it will always be smooth and consistent
|
|
869
|
+
* @returns
|
|
870
|
+
*/
|
|
871
|
+
tryAsPathDuplication(def, withLineJoin) {
|
|
872
|
+
// Format is: dupTo; pathDefined; in/out/right/left; distance
|
|
873
|
+
const params = def.split(";").map(p => p.trim()).filter(p => !!p);
|
|
874
|
+
// If not dupTo, do nothing
|
|
875
|
+
if (params[0].toLowerCase() !== "dupto")
|
|
876
|
+
return undefined;
|
|
877
|
+
// Continue as dupTo
|
|
878
|
+
if (params.length < 4)
|
|
879
|
+
return `Parameters too less to make Path Rotate By derivation!`;
|
|
880
|
+
let pathReferred = this.paths.find(x => x.name === params[1]);
|
|
881
|
+
if (!pathReferred)
|
|
882
|
+
return `The shape You referred(${params[1]}) doesn't exist (when duplicating a shape)!`;
|
|
883
|
+
let dir = params[2].toLowerCase();
|
|
884
|
+
if (!["in", "out", "right", "left"].includes(dir))
|
|
885
|
+
return `Direction Keyword (${params[2]}) to duplicate a shape is invalid.`;
|
|
886
|
+
let distance = this.resolveVarExpr(params[3]);
|
|
887
|
+
if (!distance)
|
|
888
|
+
return `Distance '${params[3]}' is invalid to duplicate a shape, it should be greater than zero!`;
|
|
889
|
+
let lineJoin = undefined;
|
|
890
|
+
if (withLineJoin) {
|
|
891
|
+
lineJoin = params[4];
|
|
892
|
+
if (lineJoin)
|
|
893
|
+
lineJoin = lineJoin.toLowerCase();
|
|
894
|
+
lineJoin = SvgCsts.LINE_JOINS.includes(lineJoin) ? lineJoin : undefined;
|
|
895
|
+
}
|
|
896
|
+
let pathDuped = pathReferred.path?.duplicateTo(distance, dir, lineJoin);
|
|
897
|
+
if (!pathDuped)
|
|
898
|
+
return `Failed to duplicate '${pathReferred.name}' with parameters '${dir}' and '${params[3]}'`;
|
|
899
|
+
return pathDuped;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Resolve the shape definitions
|
|
903
|
+
* @param name Name of the shape
|
|
904
|
+
* @param value Presentation to define the shape
|
|
905
|
+
* @returns Error message
|
|
906
|
+
*/
|
|
907
|
+
resolveShape(name, value) {
|
|
908
|
+
// Check Names
|
|
909
|
+
if (this.paths.find(s => s.name === name))
|
|
910
|
+
return [`There has already been a shape named as ${name}!`];
|
|
911
|
+
if (this.pathSeries.find(s => s.name === name))
|
|
912
|
+
return [`There is some Shape Series which is named as ${name}!`];
|
|
913
|
+
// Try to see if it is duplication of existing shapes
|
|
914
|
+
const dupTo = this.tryAsPathDuplication(value);
|
|
915
|
+
if (typeof dupTo === "string")
|
|
916
|
+
return [dupTo];
|
|
917
|
+
if (dupTo) {
|
|
918
|
+
const duppedPath = new HuatuPath(dupTo, name);
|
|
919
|
+
// If the duplicated path is singled modelled, then assign the model to path
|
|
920
|
+
if (dupTo.models.length === 1) {
|
|
921
|
+
duppedPath.model = dupTo.models[0];
|
|
922
|
+
}
|
|
923
|
+
this.paths.push(duppedPath);
|
|
924
|
+
this.defines.push(duppedPath);
|
|
925
|
+
return [];
|
|
926
|
+
}
|
|
927
|
+
const model = this.resolveShapeDef(value);
|
|
928
|
+
if (typeof model === "string")
|
|
929
|
+
return [model];
|
|
930
|
+
const path = HuatuPath.fromModel(model, name);
|
|
931
|
+
this.paths.push(path);
|
|
932
|
+
// Add to Definable
|
|
933
|
+
this.defines.push(path);
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
tryPointOrPointSeries(def) {
|
|
937
|
+
if (!def)
|
|
938
|
+
return undefined;
|
|
939
|
+
const ps = this.pointSeries.find(s => s.name === def);
|
|
940
|
+
if (ps)
|
|
941
|
+
return ps.points;
|
|
942
|
+
const p = this.resolvePointFromExpr(def);
|
|
943
|
+
if (p)
|
|
944
|
+
return [p];
|
|
945
|
+
return undefined;
|
|
946
|
+
}
|
|
947
|
+
tryNumberSeries(def) {
|
|
948
|
+
if (!def)
|
|
949
|
+
return [];
|
|
950
|
+
const range = this.resolveRange(def);
|
|
951
|
+
if (range)
|
|
952
|
+
return range.numbers;
|
|
953
|
+
else
|
|
954
|
+
return this.resolveSeparatedNumbers(def);
|
|
955
|
+
}
|
|
956
|
+
resolveShapesFromDef(def) {
|
|
957
|
+
const params = def.split(";").map(p => p.trim()).filter(p => !!p);
|
|
958
|
+
if (params.length < 3)
|
|
959
|
+
return `Parameters are too less to build ShapeSeries!`;
|
|
960
|
+
if (this.pathSeries.find(x => x.name === params[0]))
|
|
961
|
+
return `'${params[0]}' is a ShapeSeries, ShapeSeries can only be derived from simple models!`;
|
|
962
|
+
const shapeType = params[0].toLowerCase();
|
|
963
|
+
function extendArray(arr, len) {
|
|
964
|
+
if (Array.isArray(arr)) {
|
|
965
|
+
if (arr.length >= len) {
|
|
966
|
+
return arr.filter((x, i) => i < len);
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
for (let i = arr.length; i < len; i++) {
|
|
970
|
+
arr.push(arr[0]); // Always add with the first element;
|
|
971
|
+
}
|
|
972
|
+
return arr;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
return [...Array(len).keys()].map(x => arr);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function alignParams(...pms) {
|
|
980
|
+
// 1. pick out the undefined the undefined elements in the array and return undefined if array is empty
|
|
981
|
+
let array = pms
|
|
982
|
+
.map(x => {
|
|
983
|
+
if (Array.isArray(x)) {
|
|
984
|
+
const arrDefined = x.filter(y => y !== undefined);
|
|
985
|
+
if (arrDefined.length > 0)
|
|
986
|
+
return arrDefined;
|
|
987
|
+
else
|
|
988
|
+
return undefined;
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
return x;
|
|
992
|
+
}
|
|
993
|
+
});
|
|
994
|
+
// 2. If some of the elements is undefined, then no necessity to continue
|
|
995
|
+
if (array.filter(x => x === undefined).length > 0)
|
|
996
|
+
return undefined;
|
|
997
|
+
const arrayDefined = array;
|
|
998
|
+
let lens = arrayDefined.map(x => Array.isArray(x) ? x.length : 1).filter(x => x > 1);
|
|
999
|
+
let minArrayLen = lens.length > 0 ? Math.min(...lens) : 1;
|
|
1000
|
+
return {
|
|
1001
|
+
arr: arrayDefined.map(x => extendArray(x, minArrayLen)),
|
|
1002
|
+
len: minArrayLen
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const getLineSegMethod = (method) => {
|
|
1006
|
+
if (!method || method === "zip" || method === "pair")
|
|
1007
|
+
return "zip";
|
|
1008
|
+
if (method === "interleave" || method === "cross")
|
|
1009
|
+
return "cross";
|
|
1010
|
+
return undefined;
|
|
1011
|
+
};
|
|
1012
|
+
switch (shapeType) {
|
|
1013
|
+
case "ln": // Line, LineSeg
|
|
1014
|
+
case "line": // Line, LineSeg
|
|
1015
|
+
case "ls":
|
|
1016
|
+
case "lineseg": // Try first param, to see it if is defined Line
|
|
1017
|
+
// Possible Formats of the define string, this can only build LineSegs
|
|
1018
|
+
// - ln/line; lineName; l1(1 to N); l2(1 to N)
|
|
1019
|
+
const line = this.paths.find(s => s.name === params[1]);
|
|
1020
|
+
if (line) { // The def is like: line; ln1; l1; l2;
|
|
1021
|
+
if (!(line.model instanceof Line))
|
|
1022
|
+
return `"${params[1]}" is not a Line!`;
|
|
1023
|
+
// Guess param2/param3 as range or var number
|
|
1024
|
+
let l1s = this.tryNumberSeries(params[2]);
|
|
1025
|
+
if (l1s.length < 1) {
|
|
1026
|
+
this.warnings.push(`No shape resolved from '${def}'! Length1 is empty!`);
|
|
1027
|
+
}
|
|
1028
|
+
let l2s = this.tryNumberSeries(params[3]);
|
|
1029
|
+
const lens = alignParams(l1s, l2s);
|
|
1030
|
+
if (lens) {
|
|
1031
|
+
return lens.arr[0].map((x, i) => line.model.buildSeg(x, lens.arr[1][i]));
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
return l1s.map(x => line.model.buildSeg(x));
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
// Now we guess the first param is a point series or a single point
|
|
1038
|
+
// It might be defining Lines first. Possible formats:
|
|
1039
|
+
// - line; pointSeries (N points); deg (1 or N) ; l1, l2
|
|
1040
|
+
// - line; point(1 points); deg (1 or N) ; l1, l2
|
|
1041
|
+
// - line/ls/lineseg; point(1 points); deg (1 or N)
|
|
1042
|
+
// point and deg will compose line(s), witch each pair (l1, l2) to build multiple LineSegs
|
|
1043
|
+
// If no (l1, l2) present, then return lines
|
|
1044
|
+
const anchors = this.tryPointOrPointSeries(params[1]);
|
|
1045
|
+
if (anchors) {
|
|
1046
|
+
// Check if still point(s) followed, then it is defining line segs
|
|
1047
|
+
const lsp2 = this.tryPointOrPointSeries(params[2]);
|
|
1048
|
+
if (lsp2) {
|
|
1049
|
+
const method = getLineSegMethod(params[3]);
|
|
1050
|
+
let segs;
|
|
1051
|
+
if (method === "cross") {
|
|
1052
|
+
segs = anchors.map(x => lsp2.map(y => new LineSeg(x, y))).flat();
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
const lsAligned = alignParams(anchors, lsp2);
|
|
1056
|
+
segs = lsAligned
|
|
1057
|
+
? [...Array(lsAligned.len).keys()]
|
|
1058
|
+
.map(i => new LineSeg(lsAligned.arr[0][i], lsAligned.arr[1][i]))
|
|
1059
|
+
: [];
|
|
1060
|
+
}
|
|
1061
|
+
if (segs.length < 1)
|
|
1062
|
+
return `No shaped resolved from '${def}'!`;
|
|
1063
|
+
this.infos.push(`${segs.length} lines resolved from '${params.filter((x, i) => i > 0).join("; ")}'`);
|
|
1064
|
+
return segs;
|
|
1065
|
+
}
|
|
1066
|
+
// Check if Line definition(s) followed
|
|
1067
|
+
const degs = this.tryNumberSeries(params[2]);
|
|
1068
|
+
const lnDegAligned = alignParams(anchors, degs);
|
|
1069
|
+
const lines = lnDegAligned?.arr[0]
|
|
1070
|
+
.map((a, i) => new Line(a, AngDeg.fitDeg(lnDegAligned?.arr[1][i])));
|
|
1071
|
+
if (!lines || lines.length < 1)
|
|
1072
|
+
return `No Shape resolved from '${def}'!`;
|
|
1073
|
+
const l1s = this.tryNumberSeries(params[3]);
|
|
1074
|
+
const l2s = this.tryNumberSeries(params[4]);
|
|
1075
|
+
if (l1s.length < 1) { // l1 is empty
|
|
1076
|
+
if (shapeType === "ls" || shapeType === "lineseg") {
|
|
1077
|
+
return `By define LineSeg Series, you need to specify lengths after a line!`;
|
|
1078
|
+
}
|
|
1079
|
+
return `You cannot define Line Series(from '${def}'), which is infinite in length thus cannot be drawn, Please define as LineSeg Series instead!`;
|
|
1080
|
+
}
|
|
1081
|
+
const linesAligned = l2s.length > 0
|
|
1082
|
+
? alignParams(lines, l1s, l2s)
|
|
1083
|
+
: alignParams(lines, l1s);
|
|
1084
|
+
if (linesAligned) {
|
|
1085
|
+
const segs = linesAligned.arr[0]
|
|
1086
|
+
.map((l, i) => l.buildSeg(linesAligned.arr[1][i], l2s.length > 0 ? linesAligned.arr[2][i] : undefined));
|
|
1087
|
+
if (segs.length > 0) {
|
|
1088
|
+
this.infos.push(`${segs.length} lines resolved from '${params.filter((x, i) => i > 0).join("; ")}'`);
|
|
1089
|
+
return segs;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
return `No Shape resolved from '${def}'!`;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
return `To define Line(Seg)s, first parameter should be point(s)`;
|
|
1098
|
+
}
|
|
1099
|
+
case "circ":
|
|
1100
|
+
case "circle":
|
|
1101
|
+
// Format of a circle define: circle; (x, y); r
|
|
1102
|
+
const cs = this.tryPointOrPointSeries(params[1]);
|
|
1103
|
+
if (!cs)
|
|
1104
|
+
return `'${params[1]}' cannot resolve to be some point (series)!`;
|
|
1105
|
+
let rs = this.tryNumberSeries(params[2]);
|
|
1106
|
+
if (rs.length < 1)
|
|
1107
|
+
return `'${params[2]}' cannot resolve to be some radius!`;
|
|
1108
|
+
const crs = alignParams(cs, rs);
|
|
1109
|
+
if (!crs)
|
|
1110
|
+
return `Parameters '${params[1]}; ${params[2]}' invalid to resolve Circle Series!`;
|
|
1111
|
+
return [...Array(crs.len).keys()]
|
|
1112
|
+
.map(i => new Circle(crs.arr[0][i].x, crs.arr[0][i].y, crs.arr[1][i]));
|
|
1113
|
+
case "ell":
|
|
1114
|
+
case "ellipse":
|
|
1115
|
+
// Format of the Ellipse; ellipse; (x, y); rx; ry; deg
|
|
1116
|
+
if (params.length < 4)
|
|
1117
|
+
return `Parameters for the Ellipse is insufficient!`;
|
|
1118
|
+
const ecs = this.tryPointOrPointSeries(params[1]);
|
|
1119
|
+
if (!ecs)
|
|
1120
|
+
`Center Definition '${params[1]}' for Ellipse Series is incorrect!`;
|
|
1121
|
+
let rxs = this.tryNumberSeries(params[2]);
|
|
1122
|
+
if (rxs.length < 1)
|
|
1123
|
+
`RX definition '${params[2]}' resolved to no values!`;
|
|
1124
|
+
let rys = this.tryNumberSeries(params[3]);
|
|
1125
|
+
if (rys.length < 1)
|
|
1126
|
+
`RY definition '${params[3]}' resolved to no values!`;
|
|
1127
|
+
let degs = params[4] ? this.tryNumberSeries(params[4]) : 0;
|
|
1128
|
+
if (Array.isArray(degs) && degs.length < 1)
|
|
1129
|
+
return `Degree Definition '${params[4]}' to build Ellipse Series is incorrect!`;
|
|
1130
|
+
const ellParams = alignParams(ecs, rxs, rys, degs);
|
|
1131
|
+
if (!ellParams)
|
|
1132
|
+
return `Definition '${def}' resolved to no Ellipse Series!`;
|
|
1133
|
+
return [...Array(ellParams.len).keys()]
|
|
1134
|
+
.map(i => new Ellipse(ellParams.arr[0][i].x, ellParams.arr[0][i].y, ellParams.arr[1][i], ellParams.arr[2][i], AngDeg.fitDeg(ellParams.arr[3][i])));
|
|
1135
|
+
case "rect":
|
|
1136
|
+
case "rectangle":
|
|
1137
|
+
// Format of Rectangle:
|
|
1138
|
+
// - rect; bycenter; (x, y); w; h
|
|
1139
|
+
// - rect; (x1, y1); (x2, y2)
|
|
1140
|
+
// - rect; (x1, y1); w; h
|
|
1141
|
+
if (params[1].toLowerCase() === "bycenter" && params.length >= 5) {
|
|
1142
|
+
const rcs = this.tryPointOrPointSeries(params[2]);
|
|
1143
|
+
if (!rcs || rcs.length < 1)
|
|
1144
|
+
return `No Centers resolved for rects!`;
|
|
1145
|
+
const ws = this.tryNumberSeries(params[3]);
|
|
1146
|
+
if (ws.length < 1)
|
|
1147
|
+
return `No width resolved from '${params[3]}'!`;
|
|
1148
|
+
const hs = this.tryNumberSeries(params[4]);
|
|
1149
|
+
if (hs.length < 1)
|
|
1150
|
+
return `No Height resolved from '${params[4]}'!`;
|
|
1151
|
+
const rectParams = alignParams(rcs, ws, hs);
|
|
1152
|
+
if (rectParams) {
|
|
1153
|
+
return [...Array(rectParams.len).keys()]
|
|
1154
|
+
.map(i => Rect.fromCenterWH(rectParams.arr[0][i], rectParams.arr[1][i], rectParams.arr[2][i]));
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
const p1s = this.tryPointOrPointSeries(params[1]);
|
|
1159
|
+
if (!p1s || p1s.length < 1)
|
|
1160
|
+
return `No Point resolved from '${params[1]}'`;
|
|
1161
|
+
const p2s = this.tryPointOrPointSeries(params[2]);
|
|
1162
|
+
if (p2s) { // It is made by two points
|
|
1163
|
+
const rectParams = alignParams(p1s, p2s);
|
|
1164
|
+
if (rectParams) {
|
|
1165
|
+
return [...Array(rectParams.len).keys()]
|
|
1166
|
+
.map(i => Rect.fromTwoPoints(rectParams.arr[0][i], rectParams.arr[1][i]))
|
|
1167
|
+
.filter(x => {
|
|
1168
|
+
if (x)
|
|
1169
|
+
return true;
|
|
1170
|
+
else {
|
|
1171
|
+
this.warnings.push(`Zero-Width/Height Rectangle resolved and discarded!`);
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
// Now guess it is made by anchor/w/h
|
|
1178
|
+
const ws = this.tryNumberSeries(params[2]);
|
|
1179
|
+
if (ws.length < 1)
|
|
1180
|
+
return `No width resolved from '${params[2]}'!`;
|
|
1181
|
+
const hs = this.tryNumberSeries(params[3]);
|
|
1182
|
+
if (hs.length < 1)
|
|
1183
|
+
return `No Height resolved from '${params[3]}'!`;
|
|
1184
|
+
const rectParams = alignParams(p1s, ws, hs);
|
|
1185
|
+
if (rectParams) {
|
|
1186
|
+
return [...Array(rectParams.len).keys()]
|
|
1187
|
+
.map(i => {
|
|
1188
|
+
const pTL = rectParams.arr[0][i];
|
|
1189
|
+
const w = rectParams.arr[1][i];
|
|
1190
|
+
const h = rectParams.arr[2][i];
|
|
1191
|
+
return new Rect(pTL.x, pTL.y, w, h);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
// Default return error message
|
|
1196
|
+
return `No Rectangle resolved from '${def}'!`;
|
|
1197
|
+
case "pln":
|
|
1198
|
+
case "pline":
|
|
1199
|
+
case "polyline":
|
|
1200
|
+
const defPoints = params.filter((p, i) => i > 0)
|
|
1201
|
+
.map(p => this.tryPointOrPointSeries(p))
|
|
1202
|
+
.filter((p, i) => {
|
|
1203
|
+
if (p)
|
|
1204
|
+
return true;
|
|
1205
|
+
else {
|
|
1206
|
+
this.warnings.push(`${params[i + 1]} resolved to no point(s)!`);
|
|
1207
|
+
return false;
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
if (defPoints.length < 2)
|
|
1211
|
+
return `Insufficient point(group/series)s to build polyline!`;
|
|
1212
|
+
const plnAligned = alignParams(...defPoints);
|
|
1213
|
+
if (plnAligned) {
|
|
1214
|
+
return [...Array(plnAligned.len).keys()]
|
|
1215
|
+
.map(i => new PolyLine(...plnAligned.arr.map(p => p[i])));
|
|
1216
|
+
}
|
|
1217
|
+
return `No Polyline resolved from given string!`;
|
|
1218
|
+
case "tri":
|
|
1219
|
+
case "triangle":
|
|
1220
|
+
const pA = this.tryPointOrPointSeries(params[1]);
|
|
1221
|
+
const pB = this.tryPointOrPointSeries(params[2]);
|
|
1222
|
+
const pC = this.tryPointOrPointSeries(params[3]);
|
|
1223
|
+
if (!pA)
|
|
1224
|
+
return `Point A definition '${params[1]}' is invalid!`;
|
|
1225
|
+
if (!pB)
|
|
1226
|
+
return `Point A definition '${params[2]}' is invalid!`;
|
|
1227
|
+
if (!pC)
|
|
1228
|
+
return `Point A definition '${params[3]}' is invalid!`;
|
|
1229
|
+
const triAligned = alignParams(pA, pB, pC);
|
|
1230
|
+
if (triAligned) {
|
|
1231
|
+
const tris = [...Array(triAligned.len).keys()]
|
|
1232
|
+
.map(i => new Triangle(triAligned.arr[0][i], triAligned.arr[1][i], triAligned.arr[2][i]))
|
|
1233
|
+
.filter(t => t.isValid);
|
|
1234
|
+
if (tris.length > 0)
|
|
1235
|
+
return tris;
|
|
1236
|
+
}
|
|
1237
|
+
return `No triangles resolved from '${def}'!`;
|
|
1238
|
+
case "plg":
|
|
1239
|
+
case "polygon":
|
|
1240
|
+
const plgPoints = params.filter((p, i) => i > 0)
|
|
1241
|
+
.map(p => this.tryPointOrPointSeries(p))
|
|
1242
|
+
.filter((p, i) => {
|
|
1243
|
+
if (p)
|
|
1244
|
+
return true;
|
|
1245
|
+
{
|
|
1246
|
+
this.warnings.push(`${params[i + 1]} resolved to no point(s)!`);
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
if (plgPoints.length < 2)
|
|
1251
|
+
return `Insufficient point(group/series)s to build polygon!`;
|
|
1252
|
+
const plgAligned = alignParams(...plgPoints);
|
|
1253
|
+
if (plgAligned) {
|
|
1254
|
+
return [...Array(plgAligned.len).keys()]
|
|
1255
|
+
.map(i => new Polygon(...plgAligned.arr.map(p => p[i])));
|
|
1256
|
+
}
|
|
1257
|
+
return `No Polygon resolved from given string!`;
|
|
1258
|
+
case "plgn":
|
|
1259
|
+
case "npolygon":
|
|
1260
|
+
const Ns = this.tryNumberSeries(params[1])
|
|
1261
|
+
.filter(x => Number.isInteger(x) && x >= 3);
|
|
1262
|
+
if (Ns.length < 1)
|
|
1263
|
+
return `Please give a integer for N (N >= 3), [${params[1]}] is invalid!`;
|
|
1264
|
+
const plgnCs = this.tryPointOrPointSeries(params[2]);
|
|
1265
|
+
if (!plgnCs)
|
|
1266
|
+
return `The center(s) specified for Regular Polygon '${params[2]}' is invalid!`;
|
|
1267
|
+
const plgnRs = this.tryNumberSeries(params[3]);
|
|
1268
|
+
if (plgnRs.length < 1)
|
|
1269
|
+
return `Please specify Radius for Regular Polygon design!`;
|
|
1270
|
+
let plgnDegs;
|
|
1271
|
+
if (params[4]) {
|
|
1272
|
+
plgnDegs = this.tryNumberSeries(params[4]);
|
|
1273
|
+
if (plgnDegs.length < 1)
|
|
1274
|
+
return `Start Degrees '${params[4]}' is invalid!`;
|
|
1275
|
+
}
|
|
1276
|
+
else {
|
|
1277
|
+
plgnDegs = [0];
|
|
1278
|
+
}
|
|
1279
|
+
const plgnAligned = alignParams(Ns, plgnCs, plgnRs, plgnDegs);
|
|
1280
|
+
if (plgnAligned) {
|
|
1281
|
+
return [...Array(plgnAligned.len).keys()]
|
|
1282
|
+
.map(i => Polygon.buildRegularPolygon(plgnAligned.arr[0][i], plgnAligned.arr[1][i], plgnAligned.arr[2][i], plgnAligned.arr[3][i]));
|
|
1283
|
+
}
|
|
1284
|
+
return `No Regular Polygon resolved from '${def}'!`;
|
|
1285
|
+
case "b2":
|
|
1286
|
+
case "bezier2":
|
|
1287
|
+
// Format of Bezier2: bezier2; (xStart, yStart); (xCtrl, yCtrl); (xEnd, yEnd)
|
|
1288
|
+
if (params.length < 4)
|
|
1289
|
+
return `Parameters to build Bezier2 are not sufficient!`;
|
|
1290
|
+
const b2p1 = this.tryPointOrPointSeries(params[1]);
|
|
1291
|
+
const b2p2 = this.tryPointOrPointSeries(params[2]);
|
|
1292
|
+
const b2p3 = this.tryPointOrPointSeries(params[3]);
|
|
1293
|
+
if (!b2p1)
|
|
1294
|
+
return `The Start point (${params[1]}) is invalid!`;
|
|
1295
|
+
if (!b2p2)
|
|
1296
|
+
return `The Control point (${params[2]}) is invalid!`;
|
|
1297
|
+
if (!b2p3)
|
|
1298
|
+
return `The End point (${params[3]}) is invalid!`;
|
|
1299
|
+
const b2Aligned = alignParams(b2p1, b2p2, b2p3);
|
|
1300
|
+
if (b2Aligned) {
|
|
1301
|
+
return [...Array(b2Aligned.len).keys()]
|
|
1302
|
+
.map(i => new Bezier2(b2Aligned.arr[0][i], b2Aligned.arr[1][i], b2Aligned.arr[2][i]));
|
|
1303
|
+
}
|
|
1304
|
+
return `No Quadratic Bezier Curve resolved from '${def}'!`;
|
|
1305
|
+
case "b3":
|
|
1306
|
+
case "bezier3":
|
|
1307
|
+
// Format of Bezier3: bezier3; (xStart, yStart); (xCtrl1, yCtrl1); (xCtrl2, yCtrl2); (xEnd, yEnd)
|
|
1308
|
+
if (params.length < 5)
|
|
1309
|
+
return `Parameters to build Bezier2 are not sufficient!`;
|
|
1310
|
+
const b3p1 = this.tryPointOrPointSeries(params[1]);
|
|
1311
|
+
const b3p2 = this.tryPointOrPointSeries(params[2]);
|
|
1312
|
+
const b3p3 = this.tryPointOrPointSeries(params[3]);
|
|
1313
|
+
const b3p4 = this.tryPointOrPointSeries(params[4]);
|
|
1314
|
+
if (!b3p1)
|
|
1315
|
+
return `The Start point (${params[1]}) is invalid!`;
|
|
1316
|
+
if (!b3p2)
|
|
1317
|
+
return `The Control point 1 (${params[2]}) is invalid!`;
|
|
1318
|
+
if (!b3p3)
|
|
1319
|
+
return `The Control point 2 (${params[3]}) is invalid!`;
|
|
1320
|
+
if (!b3p4)
|
|
1321
|
+
return `The End point (${params[4]}) is invalid!`;
|
|
1322
|
+
const b3Aligned = alignParams(b3p1, b3p2, b3p3, b3p4);
|
|
1323
|
+
if (b3Aligned) {
|
|
1324
|
+
return [...Array(b3Aligned.len).keys()]
|
|
1325
|
+
.map(i => new Bezier3(b3Aligned.arr[0][i], b3Aligned.arr[1][i], b3Aligned.arr[2][i], b3Aligned.arr[3][i]));
|
|
1326
|
+
}
|
|
1327
|
+
return `No Cubic Bezier Curve resolved from '${def}'!`;
|
|
1328
|
+
case "arc":
|
|
1329
|
+
// Possible formats:
|
|
1330
|
+
// - arc; circ1; start_deg; end_deg; ccw | cw
|
|
1331
|
+
// - arc; ell1; start_deg; end_deg; ccw | cw
|
|
1332
|
+
if (params.length < 5)
|
|
1333
|
+
return `Parameters to build an Arc are not sufficient!`;
|
|
1334
|
+
const dirMark = params[4].toLowerCase();
|
|
1335
|
+
if (dirMark !== "cw" && dirMark !== "ccw")
|
|
1336
|
+
return `CW/CCW mark not specified!`;
|
|
1337
|
+
let arcModels;
|
|
1338
|
+
let ells = this.pathSeries.find(p => p.name === params[1]);
|
|
1339
|
+
if (ells) {
|
|
1340
|
+
if (ells.models.length > 0 &&
|
|
1341
|
+
(ells.models[0] instanceof Circle || ells.models[0] instanceof Ellipse)) {
|
|
1342
|
+
arcModels = ells.models;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
let ell = this.paths.find(p => p.name === params[1]);
|
|
1347
|
+
if (ell && ell.model &&
|
|
1348
|
+
(ell.model instanceof Circle || ell.model instanceof Ellipse)) {
|
|
1349
|
+
arcModels = [ell.model];
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (!ells)
|
|
1353
|
+
return `No Circle/Ellipse (series) by '${params[1]}'`;
|
|
1354
|
+
const startDegs = this.tryNumberSeries(params[2]);
|
|
1355
|
+
const endDegs = this.tryNumberSeries(params[3]);
|
|
1356
|
+
if (startDegs.length < 1)
|
|
1357
|
+
return `Start Degs (${params[2]}) for Arcs are invalid!`;
|
|
1358
|
+
if (endDegs.length < 1)
|
|
1359
|
+
return `End Degs (${params[3]}) for Arcs are invalid!`;
|
|
1360
|
+
const arcAligned = alignParams(arcModels, startDegs, endDegs);
|
|
1361
|
+
if (arcAligned) {
|
|
1362
|
+
return [...Array(arcAligned.len).keys()]
|
|
1363
|
+
.map(i => {
|
|
1364
|
+
const model = arcAligned.arr[0][i];
|
|
1365
|
+
if (model instanceof Circle) {
|
|
1366
|
+
return EllipticalArc.fromCircle(model, arcAligned.arr[1][i], arcAligned.arr[2][i], dirMark === "cw");
|
|
1367
|
+
}
|
|
1368
|
+
else if (model instanceof Ellipse) {
|
|
1369
|
+
return EllipticalArc.fromEllipse(model, arcAligned.arr[1][i], arcAligned.arr[2][i], dirMark === "cw");
|
|
1370
|
+
}
|
|
1371
|
+
else
|
|
1372
|
+
return undefined;
|
|
1373
|
+
})
|
|
1374
|
+
.filter(x => !!x);
|
|
1375
|
+
}
|
|
1376
|
+
return `No Arc(s) resolved from '${def}'!`;
|
|
1377
|
+
default:
|
|
1378
|
+
return `Shape type ${params[0]} is invalid!`;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Resolve the Shape Series defined
|
|
1383
|
+
* @param name Name of the shape series
|
|
1384
|
+
* @param value The definition of the series
|
|
1385
|
+
* @returns Error message if any
|
|
1386
|
+
*/
|
|
1387
|
+
resolveShapeSeries(name, value) {
|
|
1388
|
+
// Check Names
|
|
1389
|
+
if (this.paths.find(s => s.name === name))
|
|
1390
|
+
return [`There is some shape named as ${name}!`];
|
|
1391
|
+
if (this.pathSeries.find(s => s.name === name))
|
|
1392
|
+
return [`There is already some Shape Series named as ${name}!`];
|
|
1393
|
+
const models = this.resolveShapesFromDef(value);
|
|
1394
|
+
if (typeof models === "string")
|
|
1395
|
+
return [models];
|
|
1396
|
+
if (models.length < 1)
|
|
1397
|
+
return [`No shape resolved from '${value}' for '${name}'!`];
|
|
1398
|
+
const series = HuatuPathSeries.fromModels(models, name);
|
|
1399
|
+
this.pathSeries.push(series);
|
|
1400
|
+
// Add to Definable
|
|
1401
|
+
this.defines.push(series);
|
|
1402
|
+
return [];
|
|
1403
|
+
}
|
|
1404
|
+
getResolvedXPoints(id1, id2) {
|
|
1405
|
+
const solved = this.solvedXPoints.find(p => (p.path1 === id1 && p.path2 === id2) || (p.path1 === id2 && p.path2 === id1));
|
|
1406
|
+
return solved?.points;
|
|
1407
|
+
}
|
|
1408
|
+
makeXPoints(id1, id2) {
|
|
1409
|
+
const ifResolved = this.getResolvedXPoints(id1, id2);
|
|
1410
|
+
if (ifResolved)
|
|
1411
|
+
return {
|
|
1412
|
+
result: "Success",
|
|
1413
|
+
errors: [],
|
|
1414
|
+
points: ifResolved
|
|
1415
|
+
};
|
|
1416
|
+
const errors = [];
|
|
1417
|
+
const md1 = this.paths.find(s => s.name === id1), md2 = this.paths.find(s => s.name === id2);
|
|
1418
|
+
if (!md1)
|
|
1419
|
+
errors.push(`Shape "${id1}" has not been defined!`);
|
|
1420
|
+
if (!md2)
|
|
1421
|
+
errors.push(`Shape "${id2}" has not been defined!`);
|
|
1422
|
+
if (!md1 || !md2)
|
|
1423
|
+
return {
|
|
1424
|
+
result: "Error",
|
|
1425
|
+
errors,
|
|
1426
|
+
points: []
|
|
1427
|
+
};
|
|
1428
|
+
if (!md1.path && !md1.model)
|
|
1429
|
+
errors.push(`Shape ${id1} is not well defined!`);
|
|
1430
|
+
if (!md2.path && !md2.model)
|
|
1431
|
+
errors.push(`Shape ${id2} is not well defined!`);
|
|
1432
|
+
if (errors.length > 0)
|
|
1433
|
+
return {
|
|
1434
|
+
result: "Invalid",
|
|
1435
|
+
errors: errors,
|
|
1436
|
+
points: []
|
|
1437
|
+
};
|
|
1438
|
+
let xps = (md1.path && md2.path) ? md1.path.models.map(x1 => md2.path.models.map(x2 => Intersection.getXpoints(x1, x2)).flat()).flat()
|
|
1439
|
+
: (md1.path && md2.model) ? md1.path.models.map(x1 => Intersection.getXpoints(x1, md2.model)).flat()
|
|
1440
|
+
: (md1.model && md2.path) ? md2.path.models.map(x2 => Intersection.getXpoints(md1.model, x2)).flat()
|
|
1441
|
+
: Intersection.getXpoints(md1.model, md2.model);
|
|
1442
|
+
xps = Point.deDupPoints(xps);
|
|
1443
|
+
// Push to reserve
|
|
1444
|
+
this.solvedXPoints.push({
|
|
1445
|
+
path1: id1,
|
|
1446
|
+
path2: id2,
|
|
1447
|
+
points: xps
|
|
1448
|
+
});
|
|
1449
|
+
// Return the successful result
|
|
1450
|
+
return {
|
|
1451
|
+
result: "Success",
|
|
1452
|
+
errors: [],
|
|
1453
|
+
points: xps,
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Resolve the Intersection point
|
|
1458
|
+
* @param name Name of the intersection point
|
|
1459
|
+
* @param value Presentation to resolve the point
|
|
1460
|
+
* @returns Error message
|
|
1461
|
+
*/
|
|
1462
|
+
resolveXPoint(name, value) {
|
|
1463
|
+
// Check Name
|
|
1464
|
+
if (this.points.find(s => s.name === name))
|
|
1465
|
+
return [`There has already been a point named as ${name}!`];
|
|
1466
|
+
if (this.pointSeries.find(s => s.name === name))
|
|
1467
|
+
return [`There is some Point Series which is named as ${name}!`];
|
|
1468
|
+
const params = value.split(";").map(p => p.trim()).filter(p => !!p);
|
|
1469
|
+
if (params.length < 2)
|
|
1470
|
+
return [`To calculate Intersection, you need give two shapes!`];
|
|
1471
|
+
const xpRes = this.makeXPoints(params[0], params[1]);
|
|
1472
|
+
if (xpRes.result === "Error" || xpRes.result === "Invalid")
|
|
1473
|
+
return xpRes.errors;
|
|
1474
|
+
const xp = Intersection.pickPoint(xpRes.points, params[2]);
|
|
1475
|
+
if (!xp)
|
|
1476
|
+
return [`No intersection resolved for ${name}!`];
|
|
1477
|
+
this.addPoint(new HuatuPoint(xp.x, xp.y, name, "intersection"));
|
|
1478
|
+
return [];
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Resolve Calculated Point, this normally will get e.g. some point on a circle,
|
|
1482
|
+
* center(s) of a triangle, middle point of some line-segment, etc.
|
|
1483
|
+
* @param name Name of the Calculated point
|
|
1484
|
+
* @param value Presentation to resolve the point, the possible format of the CPoint:
|
|
1485
|
+
* - circle; deg deg running along the circle, e.g. 'c1;30'
|
|
1486
|
+
* - ellipse; deg deg running along the ellipse e.g. 'ell1; -90'
|
|
1487
|
+
* - line; l, l could be any value
|
|
1488
|
+
* - lineseg; t t in [0, 1]
|
|
1489
|
+
* - bezier2; t t in [0, 1]
|
|
1490
|
+
* - bezier3; t t in [0, 1]
|
|
1491
|
+
* - polygon; N; [t] t-th point on the N-th side, t has the same meaning of lineseg, if t is not present, then N is the N-th vertex
|
|
1492
|
+
* - polyline; N; [t] t- th point on the N-th seg, t has the same meaning of Lineseg, if t is not present, then N is the N-th point
|
|
1493
|
+
* - arc; t t-th point on the arc, from start angle to end ang, i.e. t=0 means start, and t=1 means end
|
|
1494
|
+
* - triangle; 1/2/3; [t] t-th point on the 1/2/3-th side, t has the same meaning of lineseg, is t is not present, then it means vertex
|
|
1495
|
+
* - triangle; cIn/cOut Center of the inner/outer circle
|
|
1496
|
+
* - triangle; cG Center of gravity
|
|
1497
|
+
* - triangle; A/B/C 1/2/3-th vertex of the triangle
|
|
1498
|
+
* - rectangle; nw/sw/se/ne nw/sw/se/ne vertex point of the rect
|
|
1499
|
+
* - rectangle; n/w/s/e; t t-th point on the N/W/S/E side, t has the same meaning of lineseg
|
|
1500
|
+
* - rectangle; nm/wm/sm/em middle point on N/W/S/E side, easy to see that 'nm' means 'n; 0.5'
|
|
1501
|
+
* @returns Error message
|
|
1502
|
+
*/
|
|
1503
|
+
resolveCPoint(name, value) {
|
|
1504
|
+
// Check Name
|
|
1505
|
+
if (this.points.find(s => s.name === name))
|
|
1506
|
+
return [`There has already been a point named as ${name}!`];
|
|
1507
|
+
if (this.pointSeries.find(s => s.name === name))
|
|
1508
|
+
return [`There is some Point Series which is named as ${name}!`];
|
|
1509
|
+
const params = value.split(";").map(p => p.trim()).filter(p => !!p);
|
|
1510
|
+
if (params.length < 2)
|
|
1511
|
+
return [`To Get a point from model, you need at least two parameters!`];
|
|
1512
|
+
const md = this.paths.find(s => s.name === params[0]);
|
|
1513
|
+
if (!md || !md.model)
|
|
1514
|
+
return [`Shape "${params[0]}" has not been defined!`];
|
|
1515
|
+
const p1num = this.resolveVarExpr(params[1]); // This is the first parameter, and in most cases, it is enough
|
|
1516
|
+
const p2num = params[2] ? this.resolveVarExpr(params[2]) : undefined;
|
|
1517
|
+
const p1str = params[1].toLowerCase(); // It is some dedicated string to specify points, like p1, ne, sw, etc. then use it, in lower case
|
|
1518
|
+
const point = p1num === undefined ? md.model.getPoint(p1str, p2num) : md.model.getPoint(p1num, p2num);
|
|
1519
|
+
if (point) {
|
|
1520
|
+
this.addPoint(new HuatuPoint(point.x, point.y, name, "calculated"));
|
|
1521
|
+
return [];
|
|
1522
|
+
}
|
|
1523
|
+
// Check if find normal point
|
|
1524
|
+
if (p1str === "np" && md.path && PathSegmentedPath.isSegment(md.path)) {
|
|
1525
|
+
let npT = this.resolveVarExpr(params[2]);
|
|
1526
|
+
let npDir = params[3].toLowerCase();
|
|
1527
|
+
let npDist = this.resolveVarExpr(params[4]);
|
|
1528
|
+
if (npT !== undefined && WtMath.isIn0and1(npT) &&
|
|
1529
|
+
(npDir === "left" || npDir === "right") &&
|
|
1530
|
+
npDist !== undefined) {
|
|
1531
|
+
let np = md.path.auxModel?.getNormalPoint(npT, npDist, npDir);
|
|
1532
|
+
if (np) {
|
|
1533
|
+
this.addPoint(new HuatuPoint(np.x, np.y, name, "calculated"));
|
|
1534
|
+
return [];
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
return [`Failed to calculate Normal Point from path '${params[0]}'`];
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
else {
|
|
1541
|
+
return [`Parameters incorrect to Calculate Normal Point from path '${params[0]}'`];
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
// Default return of error message
|
|
1545
|
+
return [`No valid result to calculate CPoint '${name}' from '${value}'`];
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* A small function to resolve the Range definition input by use
|
|
1549
|
+
* @param def The definition string for a range, it should be like 'start|step|end'
|
|
1550
|
+
* @returns The resolved range, or undefined if resolve failed
|
|
1551
|
+
*/
|
|
1552
|
+
resolveRange(def) {
|
|
1553
|
+
if (!def)
|
|
1554
|
+
return undefined;
|
|
1555
|
+
const match = def.match(WT_REGEX.RANGE);
|
|
1556
|
+
if (!match)
|
|
1557
|
+
return undefined;
|
|
1558
|
+
const valStart = this.resolveVarExpr(match[1].trim()), valStep = this.resolveVarExpr(match[2].trim()), valEnd = this.resolveVarExpr(match[3].trim());
|
|
1559
|
+
if (valStart === undefined) {
|
|
1560
|
+
this.warnings.push(`'${match[1]}' is not a valid Range Start!`);
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1563
|
+
if (valStep === undefined) {
|
|
1564
|
+
this.warnings.push(`'${match[2]}' is not a valid Range Step!`);
|
|
1565
|
+
return undefined;
|
|
1566
|
+
}
|
|
1567
|
+
if (valEnd === undefined) {
|
|
1568
|
+
this.warnings.push(`'${match[3]}' is not a valid Range End!`);
|
|
1569
|
+
return undefined;
|
|
1570
|
+
}
|
|
1571
|
+
const range = new NumberRange(valStart, valStep, valEnd);
|
|
1572
|
+
if (WtMath.isZero(range.step)) {
|
|
1573
|
+
this.warnings.push(`Range step is zero ([${def}], step is zero, or start == end), will use the only number of [Range Start]!`);
|
|
1574
|
+
}
|
|
1575
|
+
return range;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Resolve the Point Series definitions
|
|
1579
|
+
* @param name Name of the Point Series
|
|
1580
|
+
* @param value The definition string of the Point Series, e.g. "circ1; 0 | 15 | 60", which means get the points on
|
|
1581
|
+
* Circle "circ1", with the degrees starting from 0 to 60(not included) with interval of 15.
|
|
1582
|
+
* And we can the format of "0 | 15 | 60" a range
|
|
1583
|
+
* The possible formats that could produce point series are:
|
|
1584
|
+
* - p1; p2; p3; p4; ... Just simple list of defined points
|
|
1585
|
+
* - circle; deg[] or deg_range Discrete degs or deg range running along the circle, e.g. 'c1;0 | 5 | 50' or 'c1; 0, 23, 33, 44'
|
|
1586
|
+
* - ellipse; deg[] or deg_range Discrete degs or deg range running along the Ellipse, e.g. 'ell1; 20 | 5 | 180' or 'ell1; 3, 4, 8, 90'
|
|
1587
|
+
* - line; l[] or l_range, Discrete length collection or a range of length
|
|
1588
|
+
* - lineseg; t[] or t_range Discrete t's or range of t, t need to be in [0, 1];
|
|
1589
|
+
* - bezier2; t[] or t_range Discrete t's or range of t, t need to be in [0, 1];
|
|
1590
|
+
* - bezier3; t[] or t_range Discrete t's or range of t, t need to be in [0, 1];
|
|
1591
|
+
* - polygon; N; t[] Discrete t's or range of t, on the N-th side of Polygon
|
|
1592
|
+
* - polygon; n1, n2, n3,... The n-th vertex of the polygon, n1, n2, n3... need to be in [1, N]
|
|
1593
|
+
* - polyline; N; [t] Discrete t's or range of t, on the N-th segment of Polyline, N needs to be in [1, N-1]
|
|
1594
|
+
* - polyline; n1, n2, n3,... The n-th point of Polyline, n1, n2, n3... needs to be in [1, N]
|
|
1595
|
+
* - arc; t[] or t_range Discrete t's or range of t, t need to be in [0, 1];
|
|
1596
|
+
* - triangle; [1/2/3/A/B/C/cIn/cOut/cG] The 1/2/3/A/B/C/cIn/cOut/cG point of the triangle, in collection
|
|
1597
|
+
* - rectangle; [nw/sw/se/ne/nm/wm/sm/em/n/e/s/w] nw/sw/se/ne/nm/wm/sm/em/n/e/s/w point of the rect, in collection, same meaning as in CPoint resolving
|
|
1598
|
+
* - rectangle; north/west/south/east; t[] or t_range t's or range of t points on the N/W/S/E side, t is in [0, 1]
|
|
1599
|
+
* @returns Error Message if any
|
|
1600
|
+
*/
|
|
1601
|
+
resolvePointSeries(name, value) {
|
|
1602
|
+
// Check Name
|
|
1603
|
+
if (this.points.find(s => s.name === name))
|
|
1604
|
+
return [`There is a point named as ${name}!`];
|
|
1605
|
+
if (this.pointSeries.find(s => s.name === name))
|
|
1606
|
+
return [`There is already some Point Series named as ${name}!`];
|
|
1607
|
+
const makeT = (t) => {
|
|
1608
|
+
const num = this.resolveVarExpr(t);
|
|
1609
|
+
if (num !== undefined)
|
|
1610
|
+
return num;
|
|
1611
|
+
return t.toLowerCase();
|
|
1612
|
+
};
|
|
1613
|
+
const params = value.split(";").map(p => p.trim()).filter(p => !!p);
|
|
1614
|
+
if (params.length < 2)
|
|
1615
|
+
return [`To make point series from Model, you need at least two parameters!`];
|
|
1616
|
+
// Guess the first param is some Shape series
|
|
1617
|
+
const guessShapeSeries = this.pathSeries.find(ps => ps.name === params[0]);
|
|
1618
|
+
if (guessShapeSeries) {
|
|
1619
|
+
const posRange = this.resolveRange(params[1]);
|
|
1620
|
+
let posVals = posRange ? posRange.numbers : params[1].split(/[,\s]/).map(x => makeT(x));
|
|
1621
|
+
const tVals = this.tryNumberSeries(params[2]);
|
|
1622
|
+
const pts = [];
|
|
1623
|
+
// Iterate and push to array
|
|
1624
|
+
for (let i = 0; i < guessShapeSeries.models.length; i++) {
|
|
1625
|
+
const model = guessShapeSeries.models[i];
|
|
1626
|
+
for (let j = 0; j < posVals.length; j++) {
|
|
1627
|
+
if (tVals.length > 0) {
|
|
1628
|
+
for (let k = 0; k < tVals.length; k++) {
|
|
1629
|
+
const p = model.getPoint(posVals[j], tVals[k]);
|
|
1630
|
+
if (p)
|
|
1631
|
+
pts.push(p);
|
|
1632
|
+
else
|
|
1633
|
+
this.warnings.push(`Point not found by specifying '${params[0]}; ${posVals[j]}; ${tVals[k]}' !`);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
else {
|
|
1637
|
+
const p = model.getPoint(posVals[j]);
|
|
1638
|
+
if (p)
|
|
1639
|
+
pts.push(p);
|
|
1640
|
+
else
|
|
1641
|
+
this.warnings.push(`Point not found by specifying '${params[0]}; ${posVals[j]}' !`);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (pts.length < 1) {
|
|
1646
|
+
this.warnings.push(`No points Resolved based on Shape Series '${params[0]}'`);
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
this.pointSeries.push(new HuatuPointSeries(name, ...pts));
|
|
1650
|
+
}
|
|
1651
|
+
return [];
|
|
1652
|
+
}
|
|
1653
|
+
// Guess the first parameter is a point, then the whole list should be points
|
|
1654
|
+
const p0Point = this.resolvePointFromExpr(params[0]);
|
|
1655
|
+
const validPoints = [];
|
|
1656
|
+
if (p0Point) { // The first is a point, so assume the def string is a list of points
|
|
1657
|
+
params.forEach((param, i) => {
|
|
1658
|
+
const p = this.resolvePointFromExpr(param);
|
|
1659
|
+
if (!p) {
|
|
1660
|
+
this.warnings.push(`'${param}' cannot be resolved to a point!`);
|
|
1661
|
+
}
|
|
1662
|
+
else {
|
|
1663
|
+
validPoints.push(p);
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
else {
|
|
1668
|
+
// Guess the first param is a model/shape
|
|
1669
|
+
const md = this.paths.find(s => s.name === params[0]);
|
|
1670
|
+
if (!md)
|
|
1671
|
+
return [`Shape "${params[0]}" has not been defined!`];
|
|
1672
|
+
// Check if the pointSeries is calculating intersections
|
|
1673
|
+
if (params[1].toLocaleLowerCase() === 'x') {
|
|
1674
|
+
if (!params[2])
|
|
1675
|
+
return [`Model 2 is not presented when resolving intersections!`];
|
|
1676
|
+
const xpRes = this.makeXPoints(params[0], params[2]);
|
|
1677
|
+
if (xpRes.result === "Error" || xpRes.result === "Invalid")
|
|
1678
|
+
return xpRes.errors;
|
|
1679
|
+
if (xpRes.points.length < 1) {
|
|
1680
|
+
this.warnings.push(`No points found for Intersections of '${params[0]}' and '${params[2]}'!`);
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
this.pointSeries.push(new HuatuPointSeries(name, ...xpRes.points));
|
|
1684
|
+
}
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
if (!md.model)
|
|
1688
|
+
return [`Shape "${params[0]}" is not a BASIC SHAPE, cannot make point series from it!`];
|
|
1689
|
+
let pointsGot;
|
|
1690
|
+
const p1Range = this.resolveRange(params[1]); // Guess the first parameter as a range
|
|
1691
|
+
if (p1Range && p1Range.count() > 0) {
|
|
1692
|
+
pointsGot = p1Range.numbers
|
|
1693
|
+
.map(t => md.model.getPoint(t))
|
|
1694
|
+
.filter(p => !!p);
|
|
1695
|
+
}
|
|
1696
|
+
else {
|
|
1697
|
+
const p1Array = params[1].split(/[,\s/]/).map(x => x.trim()).filter(x => !!x).map(x => makeT(x));
|
|
1698
|
+
let ts = this.tryNumberSeries(params[2]);
|
|
1699
|
+
if (ts.length > 0) {
|
|
1700
|
+
pointsGot = p1Array.map(p1 => ts.map(t => md.model.getPoint(p1, t)))
|
|
1701
|
+
.flat()
|
|
1702
|
+
.filter(x => !!x);
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
// In this case, all the parameters are not splitted in to two parts, and they should be treated as a flat list of parameter 1
|
|
1706
|
+
pointsGot = params.filter((p, i) => i > 0)
|
|
1707
|
+
.map(p => p.split(/[,\s]/)
|
|
1708
|
+
.map(x => x.trim())
|
|
1709
|
+
.filter(x => !!x)
|
|
1710
|
+
.map(x => makeT(x)))
|
|
1711
|
+
.flat()
|
|
1712
|
+
.map(t => md.model.getPoint(t))
|
|
1713
|
+
.filter(x => !!x);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
validPoints.push(...pointsGot);
|
|
1717
|
+
}
|
|
1718
|
+
if (validPoints.length < 1)
|
|
1719
|
+
return [`No Point resolved with '${value}'for '${name}' as PointSeries!`];
|
|
1720
|
+
this.pointSeries.push(new HuatuPointSeries(name, ...validPoints));
|
|
1721
|
+
this.infos.push(`${validPoints.length} points resolved for Point Series '${name}'!`);
|
|
1722
|
+
return [];
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* A shortcut function to resolve a single segment which is one of the compound path.
|
|
1726
|
+
* Only the Valid segment types, i.e. LineSeg, Arc, Bezier2 and Bezier3 are accepted
|
|
1727
|
+
* as segments
|
|
1728
|
+
* @param def The input of the segment
|
|
1729
|
+
* @returns The resolved segmented path, or close mark
|
|
1730
|
+
*/
|
|
1731
|
+
resolveSegmentedPath(def) {
|
|
1732
|
+
// If the segment definition starts with three dots, it will be parsed as expanded segments from defined models
|
|
1733
|
+
if (def.startsWith("...")) {
|
|
1734
|
+
def = def.substring(3).trim();
|
|
1735
|
+
const posSemiColon = def.indexOf(";");
|
|
1736
|
+
const modelName = posSemiColon > 0 ? def.substring(0, posSemiColon).trim() : def;
|
|
1737
|
+
const htPath = this.paths.find(x => x.name === modelName);
|
|
1738
|
+
if (!htPath) {
|
|
1739
|
+
this.warnings.push(`Path Segment [${def}] Failure: "${modelName}" not found!`);
|
|
1740
|
+
return undefined;
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
const params = parseStrIntoKeyValuePair(def.substring(posSemiColon), {
|
|
1744
|
+
keys: [
|
|
1745
|
+
["radius"],
|
|
1746
|
+
["cwOrCcw"],
|
|
1747
|
+
["reverse"]
|
|
1748
|
+
],
|
|
1749
|
+
ints: [],
|
|
1750
|
+
numbers: [],
|
|
1751
|
+
booleans: ["reverse"],
|
|
1752
|
+
allowAppend: [],
|
|
1753
|
+
qualifiers: {
|
|
1754
|
+
"radius": "number[]",
|
|
1755
|
+
},
|
|
1756
|
+
blindGuess: {
|
|
1757
|
+
"cwOrCcw": (x) => {
|
|
1758
|
+
let xLc = x.toLowerCase();
|
|
1759
|
+
if (xLc === "cw" || xLc === "ccw")
|
|
1760
|
+
return xLc;
|
|
1761
|
+
else
|
|
1762
|
+
return undefined;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
if (htPath.path instanceof PathTriangle ||
|
|
1767
|
+
htPath.path instanceof PathRect ||
|
|
1768
|
+
htPath.path instanceof PathPolygon ||
|
|
1769
|
+
htPath.path instanceof PathPolyline ||
|
|
1770
|
+
htPath.path instanceof PathCircle ||
|
|
1771
|
+
htPath.path instanceof PathEllipse) {
|
|
1772
|
+
return htPath.path.toPathSegments(params.body.radius, params.body.cwOrCcw);
|
|
1773
|
+
}
|
|
1774
|
+
else if (htPath.path instanceof PathSegmentedPath) {
|
|
1775
|
+
return htPath.path.getSegs(params.body.reverse, params.body.radius);
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
this.warnings.push(`Path Segment [${def}] Failure: "${modelName}" is a type that not allowed to be expanded!`);
|
|
1779
|
+
return undefined;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
// Check if duplicating operation
|
|
1784
|
+
if (/\s*(dupTo)\s*;.*/i.test(def)) {
|
|
1785
|
+
let dupped = this.tryAsPathDuplication(def, true);
|
|
1786
|
+
if (typeof dupped === "string") {
|
|
1787
|
+
this.warnings.push(dupped);
|
|
1788
|
+
return undefined;
|
|
1789
|
+
}
|
|
1790
|
+
if (dupped) {
|
|
1791
|
+
if (PathSegmentedPath.isSegment(dupped))
|
|
1792
|
+
return dupped;
|
|
1793
|
+
if (dupped instanceof PathSegmentedPath)
|
|
1794
|
+
return dupped.segments;
|
|
1795
|
+
}
|
|
1796
|
+
this.warnings.push(`Cannot make duplicated path(seg)s by '${def}'!`);
|
|
1797
|
+
return undefined;
|
|
1798
|
+
}
|
|
1799
|
+
// Treat as Segment Definitions
|
|
1800
|
+
const params = def.split(";").map(x => x.trim()).filter(x => !!x);
|
|
1801
|
+
if (params[0].toLowerCase() === "close")
|
|
1802
|
+
return "CLOSE_MARK";
|
|
1803
|
+
const definedModel = this.paths.find(s => s.name === params[0]);
|
|
1804
|
+
const isReverse = !!definedModel && !!params[1] && params[1].toLowerCase() === "reverse";
|
|
1805
|
+
const model = definedModel ? definedModel.model : this.resolveShapeDef(def);
|
|
1806
|
+
if (typeof model === "string" || !model) {
|
|
1807
|
+
this.warnings.push(`Path Segment [${def}] Failure: "${model}"!`);
|
|
1808
|
+
return undefined;
|
|
1809
|
+
}
|
|
1810
|
+
;
|
|
1811
|
+
const path = PathSegmentedPath.fromModel(model);
|
|
1812
|
+
return isReverse ? path?.reverse() : path;
|
|
1813
|
+
}
|
|
1814
|
+
tryPathTransform(def, name) {
|
|
1815
|
+
const params = def.split(";").map(x => x.trim()).filter(x => !!x);
|
|
1816
|
+
if (params.length < 3)
|
|
1817
|
+
return undefined;
|
|
1818
|
+
const path = this.paths.find(x => x.name === params[1]);
|
|
1819
|
+
if (!path)
|
|
1820
|
+
return undefined;
|
|
1821
|
+
const transType = params[0].toLowerCase();
|
|
1822
|
+
if (transType === "movex") {
|
|
1823
|
+
const dx = this.resolveVarExpr(params[2]);
|
|
1824
|
+
return dx !== undefined ? path.moveBy(dx, 0, name) : undefined;
|
|
1825
|
+
}
|
|
1826
|
+
else if (transType === "movey") {
|
|
1827
|
+
const dy = this.resolveVarExpr(params[2]);
|
|
1828
|
+
return dy !== undefined ? path.moveBy(0, dy, name) : undefined;
|
|
1829
|
+
}
|
|
1830
|
+
else if (transType === "moveby" || transType === "move") {
|
|
1831
|
+
const pt = this.resolvePointFromExpr(params[2]);
|
|
1832
|
+
return pt !== undefined ? path.moveBy(pt.x, pt.y, name) : undefined;
|
|
1833
|
+
}
|
|
1834
|
+
else if (transType === "rotate" || transType === "rotateby") {
|
|
1835
|
+
const deg = this.resolveVarExpr(params[2]);
|
|
1836
|
+
const at = params[3] ? this.resolvePointFromExpr(params[3]) : undefined;
|
|
1837
|
+
return deg === undefined ? undefined : path.rotateBy(deg, at, name);
|
|
1838
|
+
}
|
|
1839
|
+
else {
|
|
1840
|
+
return undefined;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Resolve Path definition, the input might be a string, or string array
|
|
1845
|
+
* @param name Name of the Calculated point
|
|
1846
|
+
* @param value Presentation to resolve the path
|
|
1847
|
+
* @returns Error message
|
|
1848
|
+
*/
|
|
1849
|
+
resolvePath(name, value) {
|
|
1850
|
+
// Check Name
|
|
1851
|
+
if (this.paths.find(s => s.name === name))
|
|
1852
|
+
return [`There has already been a Path named as ${name}!`];
|
|
1853
|
+
if (this.pathSeries.find(s => s.name === name))
|
|
1854
|
+
return [`There is some Path Series named as ${name}!`];
|
|
1855
|
+
// Check if modeled path, the value is the model name if true
|
|
1856
|
+
if (typeof value === "string") {
|
|
1857
|
+
let path = this.tryPathTransform(value, name);
|
|
1858
|
+
if (!path) {
|
|
1859
|
+
const tryAsSegPath = this.resolveSegmentedPath(value);
|
|
1860
|
+
if (!tryAsSegPath) {
|
|
1861
|
+
const model = this.resolveShapeDef(value);
|
|
1862
|
+
if (typeof model === "string")
|
|
1863
|
+
return [`Shape (${value}) resolving failed: ${model}`];
|
|
1864
|
+
path = HuatuPath.fromModel(model, name);
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
if (tryAsSegPath === "CLOSE_MARK")
|
|
1868
|
+
return [`You cannot define a path by just a 'close' mark!`];
|
|
1869
|
+
const segPath = new PathSegmentedPath();
|
|
1870
|
+
if (Array.isArray(tryAsSegPath)) {
|
|
1871
|
+
segPath.addSeg(...tryAsSegPath);
|
|
1872
|
+
}
|
|
1873
|
+
else {
|
|
1874
|
+
segPath.addSeg(tryAsSegPath);
|
|
1875
|
+
}
|
|
1876
|
+
path = new HuatuPath(segPath, name);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
this.paths.push(path);
|
|
1880
|
+
// Add to Definable
|
|
1881
|
+
this.defines.push(path);
|
|
1882
|
+
return []; // Resolved if model matched
|
|
1883
|
+
}
|
|
1884
|
+
// It should be treated as Segmented path with each list item as one segment
|
|
1885
|
+
// If some path consisting of multiple Line(seg)s, then please organize it in the way of
|
|
1886
|
+
// Segmented Path, i.e. write it in a list
|
|
1887
|
+
const path = new PathSegmentedPath();
|
|
1888
|
+
const models = value.map(s => this.resolveSegmentedPath(s))
|
|
1889
|
+
.filter(m => !!m);
|
|
1890
|
+
for (let idx = 0; idx < models.length; idx++) {
|
|
1891
|
+
const seg = models[idx];
|
|
1892
|
+
if (typeof seg === "string") {
|
|
1893
|
+
if (seg === "CLOSE_MARK" && path.segments.length > 0) {
|
|
1894
|
+
path.segments[path.segments.length - 1].closePath = true;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
else {
|
|
1898
|
+
if (Array.isArray(seg)) {
|
|
1899
|
+
path.addSeg(...seg);
|
|
1900
|
+
}
|
|
1901
|
+
else {
|
|
1902
|
+
path.addSeg(seg);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
if (path.segments.length < 1)
|
|
1907
|
+
return [`No path segment(s) resolved for (${name})!`];
|
|
1908
|
+
const segPath = new HuatuPath(path, name);
|
|
1909
|
+
this.paths.push(segPath);
|
|
1910
|
+
// Add to Definable
|
|
1911
|
+
this.defines.push(segPath);
|
|
1912
|
+
return [];
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* Check if the user has given a valid string to specify the pattern, if it is a name for "predefined"
|
|
1916
|
+
* pattern, then the pattern will be copied into the in-use collection with the same name. Or it
|
|
1917
|
+
* needs to be some pattern defined by user manually.
|
|
1918
|
+
* @param ptn The defined name or pre-defined name for the Pattern user wants to use
|
|
1919
|
+
* @returns The Pattern or undefined if not found
|
|
1920
|
+
*/
|
|
1921
|
+
checkPattern(ptn) {
|
|
1922
|
+
if (!ptn)
|
|
1923
|
+
return undefined;
|
|
1924
|
+
const tryPattern = this.patterns.find(p => p.name === ptn);
|
|
1925
|
+
if (tryPattern) {
|
|
1926
|
+
tryPattern.refCount++;
|
|
1927
|
+
return tryPattern;
|
|
1928
|
+
}
|
|
1929
|
+
const sysPattern = predefinedPatterns[ptn];
|
|
1930
|
+
if (sysPattern) {
|
|
1931
|
+
const pattern = new HuatuPattern(ptn, sysPattern);
|
|
1932
|
+
this.patterns.push(pattern);
|
|
1933
|
+
pattern.refCount++;
|
|
1934
|
+
this.defines.push(pattern);
|
|
1935
|
+
return pattern;
|
|
1936
|
+
}
|
|
1937
|
+
return undefined;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Check if the user has given a valid string to specify the marker, if it is a name for "predefined"
|
|
1941
|
+
* marker, then the marker will be copied into the in-use collection with the same name. Or it
|
|
1942
|
+
* needs to be some marker defined by user manually.
|
|
1943
|
+
* @param ptn The defined name or pre-defined name for the Marker user wants to use
|
|
1944
|
+
* @returns The Marker or undefined if not found
|
|
1945
|
+
*/
|
|
1946
|
+
checkMarker(mkr) {
|
|
1947
|
+
if (!mkr)
|
|
1948
|
+
return undefined;
|
|
1949
|
+
const tryMarker = this.markers.find(p => p.name === mkr);
|
|
1950
|
+
if (tryMarker) {
|
|
1951
|
+
tryMarker.refCount++;
|
|
1952
|
+
return tryMarker;
|
|
1953
|
+
}
|
|
1954
|
+
const sysMarker = predefinedMarkers[mkr];
|
|
1955
|
+
if (sysMarker) {
|
|
1956
|
+
const marker = new HuatuMarker(mkr, sysMarker);
|
|
1957
|
+
this.markers.push(marker);
|
|
1958
|
+
marker.refCount++;
|
|
1959
|
+
this.defines.push(marker);
|
|
1960
|
+
return marker;
|
|
1961
|
+
}
|
|
1962
|
+
return undefined;
|
|
1963
|
+
}
|
|
1964
|
+
parseStyleFromKeyValueResults(parseRes) {
|
|
1965
|
+
const params = parseRes.body;
|
|
1966
|
+
// Handle Orphans
|
|
1967
|
+
const realOrphans = [];
|
|
1968
|
+
parseRes.orphans.forEach(orphan => {
|
|
1969
|
+
const sty = this.styles.find(x => x.name === orphan);
|
|
1970
|
+
if (sty) {
|
|
1971
|
+
if (params.styles)
|
|
1972
|
+
params.styles += `,${orphan}`;
|
|
1973
|
+
else
|
|
1974
|
+
params.styles = orphan;
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
realOrphans.push(orphan);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
realOrphans.push(...parseRes.unluckyOrphans);
|
|
1981
|
+
if (realOrphans.length > 0) {
|
|
1982
|
+
this.warnings.push(`Options [${realOrphans.map(x => `"${x}"`).join(",")}] cannot be resolved to make a drawing style!`);
|
|
1983
|
+
}
|
|
1984
|
+
const failedPairs = [...parseRes.failedPairs];
|
|
1985
|
+
// Handle Sub Keys
|
|
1986
|
+
let paramFill = {}, paramStroke = {}, paramMarker = {};
|
|
1987
|
+
if (parseRes.subKeys && parseRes.subKeys["fill"]) {
|
|
1988
|
+
let subRes = parseRes.subKeys["fill"];
|
|
1989
|
+
assignOnlyDefined(paramFill, subRes.body);
|
|
1990
|
+
if (subRes.orphans.length > 0) {
|
|
1991
|
+
this.warnings.push(`Options [${subRes.orphans.map(x => `"${x}"`).join(",")}] cannot be resolved to make 'Fill' attributes!`);
|
|
1992
|
+
}
|
|
1993
|
+
failedPairs.push(...subRes.failedPairs);
|
|
1994
|
+
}
|
|
1995
|
+
if (parseRes.subKeys && parseRes.subKeys["stroke"]) {
|
|
1996
|
+
let subRes = parseRes.subKeys["stroke"];
|
|
1997
|
+
assignOnlyDefined(paramStroke, subRes.body);
|
|
1998
|
+
if (subRes.unluckyOrphans.length > 0) {
|
|
1999
|
+
subRes.unluckyOrphans.forEach(x => {
|
|
2000
|
+
if (x === "round" && !paramStroke.lineJoin) {
|
|
2001
|
+
paramStroke.lineJoin = "round";
|
|
2002
|
+
}
|
|
2003
|
+
else {
|
|
2004
|
+
subRes.orphans.push(x);
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
if (subRes.orphans.length > 0) {
|
|
2009
|
+
const realStrokeOrphans = [];
|
|
2010
|
+
for (let i = 0; i < subRes.orphans.length; i++) {
|
|
2011
|
+
if (!paramStroke.width) {
|
|
2012
|
+
let tryWidth = this.resolveVarExpr(subRes.orphans[i]);
|
|
2013
|
+
if (tryWidth !== undefined && tryWidth > 0) {
|
|
2014
|
+
paramStroke.width = tryWidth;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
else {
|
|
2018
|
+
realStrokeOrphans.push(subRes.orphans[i]);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
if (realStrokeOrphans.length > 0) {
|
|
2022
|
+
this.warnings.push(`Options [${realStrokeOrphans.map(x => `"${x}"`).join(",")}] cannot be resolved to make 'Stroke' attributes!`);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
failedPairs.push(...subRes.failedPairs);
|
|
2026
|
+
}
|
|
2027
|
+
if (parseRes.subKeys && parseRes.subKeys["marker"]) {
|
|
2028
|
+
let subRes = parseRes.subKeys["marker"];
|
|
2029
|
+
assignOnlyDefined(paramMarker, subRes.body);
|
|
2030
|
+
let i = 0;
|
|
2031
|
+
while (subRes.orphans.length > 0 && i < 3) {
|
|
2032
|
+
if (!paramMarker.end) {
|
|
2033
|
+
paramMarker.end = subRes.orphans.shift();
|
|
2034
|
+
}
|
|
2035
|
+
else if (!paramMarker.start) {
|
|
2036
|
+
paramMarker.start = subRes.orphans.shift();
|
|
2037
|
+
}
|
|
2038
|
+
else if (!paramMarker.mid) {
|
|
2039
|
+
paramMarker.mid = subRes.orphans.shift();
|
|
2040
|
+
}
|
|
2041
|
+
i++;
|
|
2042
|
+
}
|
|
2043
|
+
if (subRes.orphans.length > 0) {
|
|
2044
|
+
this.warnings.push(`Options [${subRes.orphans.map(x => `"${x}"`).join(",")}] cannot be resolved to make 'Marker' attributes!`);
|
|
2045
|
+
}
|
|
2046
|
+
failedPairs.push(...subRes.failedPairs);
|
|
2047
|
+
}
|
|
2048
|
+
failedPairs.forEach(x => this.warnings.push(`['${x.key}': '${x.value}'] are not valid for Style Design!`));
|
|
2049
|
+
// Now pick the attributes in the results to build a style
|
|
2050
|
+
const style = new HuatuStyle();
|
|
2051
|
+
if (params.styles) {
|
|
2052
|
+
const styles = params.styles
|
|
2053
|
+
.split(/[,\s]/)
|
|
2054
|
+
.map(x => x.trim())
|
|
2055
|
+
.filter(x => !!x)
|
|
2056
|
+
.map(x => {
|
|
2057
|
+
const sty = this.styles.find(s => s.name === x);
|
|
2058
|
+
if (!sty)
|
|
2059
|
+
this.warnings.push(`Style '${x}' is not defined, or it is not a style!`);
|
|
2060
|
+
return sty;
|
|
2061
|
+
})
|
|
2062
|
+
.filter(x => !!x);
|
|
2063
|
+
if (styles.length > 0)
|
|
2064
|
+
style.refStyles.push(...styles);
|
|
2065
|
+
}
|
|
2066
|
+
if (params.fillColor)
|
|
2067
|
+
style.fillColor = params.fillColor;
|
|
2068
|
+
else if (paramFill.color)
|
|
2069
|
+
style.fillColor = paramFill.color;
|
|
2070
|
+
if (params.fillOpacity)
|
|
2071
|
+
style.fillOpacity = fitNumInRange(params.fillOpacity, 0, 1);
|
|
2072
|
+
else if (paramFill.opacity)
|
|
2073
|
+
style.fillOpacity = fitNumInRange(paramFill.opacity, 0, 1);
|
|
2074
|
+
if (params.pattern) {
|
|
2075
|
+
const ptn = this.checkPattern(params.pattern);
|
|
2076
|
+
if (ptn)
|
|
2077
|
+
style.fillPattern = ptn;
|
|
2078
|
+
else
|
|
2079
|
+
this.warnings.push(`'${params.pattern}' is not a pattern!`);
|
|
2080
|
+
}
|
|
2081
|
+
else if (paramFill.pattern) {
|
|
2082
|
+
const ptn = this.checkPattern(paramFill.pattern);
|
|
2083
|
+
if (ptn)
|
|
2084
|
+
style.fillPattern = ptn;
|
|
2085
|
+
else
|
|
2086
|
+
this.warnings.push(`'${paramFill.pattern}' is not a pattern!`);
|
|
2087
|
+
}
|
|
2088
|
+
if (params.fillRule)
|
|
2089
|
+
style.fillRule = params.fillRule;
|
|
2090
|
+
else if (paramFill.rule)
|
|
2091
|
+
style.fillRule = paramFill.rule;
|
|
2092
|
+
if (params.gradient) {
|
|
2093
|
+
style.fillGradient = params.gradient;
|
|
2094
|
+
// TODO: add when Gradient implemented
|
|
2095
|
+
}
|
|
2096
|
+
else if (paramFill.gradient) {
|
|
2097
|
+
style.fillGradient = paramFill.gradient;
|
|
2098
|
+
// TODO: add when gradient implemented
|
|
2099
|
+
}
|
|
2100
|
+
if (params.strokeColor)
|
|
2101
|
+
style.strokeColor = params.strokeColor;
|
|
2102
|
+
else if (paramStroke.color)
|
|
2103
|
+
style.strokeColor = paramStroke.color;
|
|
2104
|
+
if (params.strokeWidth)
|
|
2105
|
+
style.strokeWidth = params.strokeWidth;
|
|
2106
|
+
else if (paramStroke.width)
|
|
2107
|
+
style.strokeWidth = paramStroke.width;
|
|
2108
|
+
if (params.strokeOpacity)
|
|
2109
|
+
style.strokeOpacity = params.strokeOpacity;
|
|
2110
|
+
else if (paramStroke.opacity)
|
|
2111
|
+
style.strokeOpacity = paramStroke.opacity;
|
|
2112
|
+
if (params.lineCap)
|
|
2113
|
+
style.strokeLineCap = params.lineCap;
|
|
2114
|
+
else if (paramStroke.lineCap)
|
|
2115
|
+
style.strokeLineCap = paramStroke.lineCap;
|
|
2116
|
+
if (params.lineJoin)
|
|
2117
|
+
style.strokeLineJoin = params.lineJoin;
|
|
2118
|
+
else if (paramStroke.lineJoin)
|
|
2119
|
+
style.strokeLineJoin = paramStroke.lineJoin;
|
|
2120
|
+
if (params.dashArray && params.dashArray.length > 0)
|
|
2121
|
+
style.strokeDashArray = params.dashArray;
|
|
2122
|
+
else if (paramStroke.dashArray && paramStroke.dashArray.length > 0)
|
|
2123
|
+
style.strokeDashArray = paramStroke.dashArray;
|
|
2124
|
+
if (params.dashOffset)
|
|
2125
|
+
style.strokeDashOffset = params.dashOffset;
|
|
2126
|
+
else if (paramStroke.dashOffset)
|
|
2127
|
+
style.strokeDashOffset = paramStroke.dashOffset;
|
|
2128
|
+
let mkrId;
|
|
2129
|
+
mkrId = paramMarker.start ? paramMarker.start
|
|
2130
|
+
: params.markerStart ? params.markerStart
|
|
2131
|
+
: undefined;
|
|
2132
|
+
if (mkrId) {
|
|
2133
|
+
const mkr = this.checkMarker(mkrId);
|
|
2134
|
+
if (mkr)
|
|
2135
|
+
style.markerStart = mkr;
|
|
2136
|
+
else
|
|
2137
|
+
this.warnings.push(`Marker '${mkrId}' is not a marker!`);
|
|
2138
|
+
}
|
|
2139
|
+
mkrId = paramMarker.mid ? paramMarker.mid
|
|
2140
|
+
: params.markerMid ? params.markerMid
|
|
2141
|
+
: undefined;
|
|
2142
|
+
if (mkrId) {
|
|
2143
|
+
const mkr = this.checkMarker(mkrId);
|
|
2144
|
+
if (mkr)
|
|
2145
|
+
style.markerMid = mkr;
|
|
2146
|
+
else
|
|
2147
|
+
this.warnings.push(`Marker '${mkrId}' is not a marker!`);
|
|
2148
|
+
}
|
|
2149
|
+
mkrId = paramMarker.end ? paramMarker.end
|
|
2150
|
+
: params.markerEnd ? params.markerEnd
|
|
2151
|
+
: undefined;
|
|
2152
|
+
if (mkrId) {
|
|
2153
|
+
const mkr = this.checkMarker(mkrId);
|
|
2154
|
+
if (mkr)
|
|
2155
|
+
style.markerEnd = mkr;
|
|
2156
|
+
else
|
|
2157
|
+
this.warnings.push(`Marker '${mkrId}' is not a marker!`);
|
|
2158
|
+
}
|
|
2159
|
+
if (params.clip) {
|
|
2160
|
+
let clip = this.clips.find(c => c.name === params.clip);
|
|
2161
|
+
if (clip) {
|
|
2162
|
+
style.clipPath = clip;
|
|
2163
|
+
clip.refCount++;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
// Handle global opacity var `opaque`
|
|
2167
|
+
if (params.opaque && WtMath.isIn0and1(params.opaque)) {
|
|
2168
|
+
if (style.fillOpacity === undefined)
|
|
2169
|
+
style.fillOpacity = params.opaque;
|
|
2170
|
+
if (style.strokeOpacity === undefined)
|
|
2171
|
+
style.strokeOpacity = params.opaque;
|
|
2172
|
+
}
|
|
2173
|
+
return style;
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Resolve Style definition, the input might be a string, or a style object
|
|
2177
|
+
* @param name Name of the style
|
|
2178
|
+
* @param value Presentation to resolve the style
|
|
2179
|
+
* @returns Error message
|
|
2180
|
+
*/
|
|
2181
|
+
resolveStyle(name, value) {
|
|
2182
|
+
const parseRes = parseStrIntoKeyValuePair(value, HuatuStyle.optsStyle);
|
|
2183
|
+
const style = this.parseStyleFromKeyValueResults(parseRes);
|
|
2184
|
+
style.name = name;
|
|
2185
|
+
style.copyRefAttrs();
|
|
2186
|
+
// TODO: Mask will not be included
|
|
2187
|
+
this.styles.push(style);
|
|
2188
|
+
return [];
|
|
2189
|
+
}
|
|
2190
|
+
/**
|
|
2191
|
+
* The entry function to resolve a LabelStyle defined
|
|
2192
|
+
* @param name Name of the LabelStyle defined
|
|
2193
|
+
* @param value The definition string/object for the LabelStyle
|
|
2194
|
+
* @returns Error message if any
|
|
2195
|
+
*/
|
|
2196
|
+
resolveLabelStyle(name, value) {
|
|
2197
|
+
const optsLabelStyle = {
|
|
2198
|
+
keys: [
|
|
2199
|
+
["refs", "styles", "style", "ref"],
|
|
2200
|
+
["textAlign", "align"],
|
|
2201
|
+
["bold"],
|
|
2202
|
+
["italic"],
|
|
2203
|
+
["addLine", "line"],
|
|
2204
|
+
["fontName", "font"],
|
|
2205
|
+
["color", "fontColor"],
|
|
2206
|
+
["fontSize", "size", "fs"],
|
|
2207
|
+
],
|
|
2208
|
+
booleans: ["bold", "italic"],
|
|
2209
|
+
ints: [],
|
|
2210
|
+
numbers: [
|
|
2211
|
+
"fontSize", "size", "fs",
|
|
2212
|
+
],
|
|
2213
|
+
allowAppend: ["refs", "styles", "style", "ref"],
|
|
2214
|
+
qualifiers: {
|
|
2215
|
+
"color": isValidColor,
|
|
2216
|
+
"fontColor": isValidColor,
|
|
2217
|
+
"textAlign": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2218
|
+
"align": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2219
|
+
"addLine": HuatuLabelStyle.DECORATION_LINES,
|
|
2220
|
+
"line": HuatuLabelStyle.DECORATION_LINES,
|
|
2221
|
+
},
|
|
2222
|
+
blindGuess: {
|
|
2223
|
+
"textAlign": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2224
|
+
"addLine": [
|
|
2225
|
+
"over", "overline",
|
|
2226
|
+
"under", "underline",
|
|
2227
|
+
"middleline", "through", "strike-through", "strikethrough"
|
|
2228
|
+
],
|
|
2229
|
+
"fontName": HuatuLabelStyle.PREDEFINED_FONT_NAMES,
|
|
2230
|
+
"color": isValidColor,
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
let res;
|
|
2234
|
+
let lsParams;
|
|
2235
|
+
if (typeof value === "string") {
|
|
2236
|
+
res = parseStrIntoKeyValuePair(value, optsLabelStyle);
|
|
2237
|
+
lsParams = res.body;
|
|
2238
|
+
const realOrphans = [];
|
|
2239
|
+
res.orphans.forEach(orphan => {
|
|
2240
|
+
let orphanAdopted = false;
|
|
2241
|
+
if (this.labelStyles.find(x => x.name === orphan)) {
|
|
2242
|
+
lsParams.refs = lsParams.refs ? orphan : (lsParams.refs + `, ${orphan}`);
|
|
2243
|
+
orphanAdopted = true;
|
|
2244
|
+
}
|
|
2245
|
+
if (!orphanAdopted) {
|
|
2246
|
+
let fsStr = /^([1-9]\d*)p[xt]$/i.exec(orphan);
|
|
2247
|
+
if (fsStr && !lsParams.fontSize) {
|
|
2248
|
+
lsParams.fontSize = parseFloat(fsStr[1]);
|
|
2249
|
+
orphanAdopted = true;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
if (!orphanAdopted) {
|
|
2253
|
+
realOrphans.push(orphan);
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
const orphans = realOrphans.map(x => `'${x}'`).join(", ");
|
|
2257
|
+
this.warnings.push(`[Resolving Label Style '${name}']: ${orphans} could not be resolved!`);
|
|
2258
|
+
}
|
|
2259
|
+
else {
|
|
2260
|
+
res = parseStrIntoKeyValuePair(value, optsLabelStyle);
|
|
2261
|
+
lsParams = res.body;
|
|
2262
|
+
res.failedPairs.forEach(p => {
|
|
2263
|
+
this.warnings.push(`[Resolving Label Style '${name}']: {'${p.key}': '${p.value}'} could not be understood!`);
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
const lblStyle = new HuatuLabelStyle(name);
|
|
2267
|
+
if (lsParams.refs) {
|
|
2268
|
+
lsParams.refs.split(/[,\s]/i)
|
|
2269
|
+
.map(x => x.trim())
|
|
2270
|
+
.filter(x => !!x)
|
|
2271
|
+
.forEach(x => {
|
|
2272
|
+
const sty = this.labelStyles.find(s => s.name === x);
|
|
2273
|
+
if (sty) {
|
|
2274
|
+
if (!lblStyle.refStyles.find(s => s === sty)) {
|
|
2275
|
+
lblStyle.refStyles.push(sty);
|
|
2276
|
+
}
|
|
2277
|
+
else {
|
|
2278
|
+
this.warnings.push(`[Resolving Label Style]: Style '${x}' has been already added to the label!`);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
else {
|
|
2282
|
+
this.warnings.push(`[Resolving Label Style]: '${x}' is not a valid defined Label Style!`);
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
if (lsParams.textAlign)
|
|
2287
|
+
lblStyle.textAlign = lsParams.textAlign;
|
|
2288
|
+
if (lsParams.color)
|
|
2289
|
+
lblStyle.color = lsParams.color;
|
|
2290
|
+
if (lsParams.fontName)
|
|
2291
|
+
lblStyle.font = lsParams.fontName;
|
|
2292
|
+
if (lsParams.bold)
|
|
2293
|
+
lblStyle.bold = lsParams.bold;
|
|
2294
|
+
if (lsParams.italic)
|
|
2295
|
+
lblStyle.italic = lsParams.italic;
|
|
2296
|
+
if (lsParams.addLine)
|
|
2297
|
+
lblStyle.addLine = lsParams.addLine;
|
|
2298
|
+
if (lsParams.fontSize)
|
|
2299
|
+
lblStyle.fontSize = lsParams.fontSize;
|
|
2300
|
+
this.labelStyles.push(lblStyle);
|
|
2301
|
+
return [];
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Resolve Symbol definition, the input might be a string, or a string list
|
|
2305
|
+
* @param name Name of the Symbol
|
|
2306
|
+
* @param value Presentation to resolve the symbol
|
|
2307
|
+
* @returns Error message
|
|
2308
|
+
*/
|
|
2309
|
+
resolveSymbol(name, value) {
|
|
2310
|
+
// Check Name
|
|
2311
|
+
const nameError = this.isDrawingNameValid(name, "Symbol");
|
|
2312
|
+
if (nameError)
|
|
2313
|
+
return [nameError];
|
|
2314
|
+
// A symbol will accept path/pathSeries and symbol
|
|
2315
|
+
const allowedTypes = ["path", "pathSeries", "symbol"];
|
|
2316
|
+
// If it is only one line, i.d. the symbol has only one definition, We assume that it is the shape it contains
|
|
2317
|
+
if (typeof value === "string") {
|
|
2318
|
+
const item = this.resolveAsDrawingItem(value, allowedTypes);
|
|
2319
|
+
if (typeof item === "string")
|
|
2320
|
+
return [item]; // Return the error message
|
|
2321
|
+
const symbol = new HuatuSymbol(name);
|
|
2322
|
+
symbol.addElement(item);
|
|
2323
|
+
this.defines.push(symbol); // Add to Definable
|
|
2324
|
+
this.symbols.push(symbol);
|
|
2325
|
+
return [];
|
|
2326
|
+
}
|
|
2327
|
+
// If the definition is a list, we assume that it contains multiple drawing definitions for the Symbol
|
|
2328
|
+
const errs = [];
|
|
2329
|
+
const symbol = new HuatuSymbol(name);
|
|
2330
|
+
for (let i = 0; i < value.length; i++) {
|
|
2331
|
+
const def = value[i];
|
|
2332
|
+
// Check if Desc
|
|
2333
|
+
const tryDesc = WtHuatu.tryDesc(def);
|
|
2334
|
+
if (tryDesc) {
|
|
2335
|
+
symbol.desc = tryDesc;
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
// Check if ViewBox
|
|
2339
|
+
const tryViewBox = this.tryViewBox(def);
|
|
2340
|
+
if (typeof tryViewBox === "string")
|
|
2341
|
+
errs.push(tryViewBox);
|
|
2342
|
+
else if (tryViewBox instanceof SvgBBox) {
|
|
2343
|
+
symbol.viewBox = tryViewBox;
|
|
2344
|
+
}
|
|
2345
|
+
else {
|
|
2346
|
+
// Assume it is some drawing item
|
|
2347
|
+
const item = this.resolveAsDrawingItem(def, allowedTypes);
|
|
2348
|
+
if (typeof item === "string")
|
|
2349
|
+
errs.push(item);
|
|
2350
|
+
else
|
|
2351
|
+
symbol.addElement(item);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
this.defines.push(symbol); // Add to Definable
|
|
2355
|
+
this.symbols.push(symbol);
|
|
2356
|
+
// Return the errors when resolving
|
|
2357
|
+
return errs;
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* A tool function to convert the user-defined radius-/scale-function into Function
|
|
2361
|
+
* and check if the function could produce valid result, which should be positive number.
|
|
2362
|
+
* @param funcDef The definition of the function, e.g. 'x => (x + 2)*1.5
|
|
2363
|
+
* @param paramCount How many inputs the function will take, normally, 1 or 2
|
|
2364
|
+
* @returns The function or undefined
|
|
2365
|
+
*/
|
|
2366
|
+
testUserFunc(funcDef, paramCount) {
|
|
2367
|
+
try {
|
|
2368
|
+
const tryFunc = eval(funcDef);
|
|
2369
|
+
if (!tryFunc || typeof tryFunc !== "function")
|
|
2370
|
+
return undefined;
|
|
2371
|
+
if (paramCount === 1) {
|
|
2372
|
+
let x = tryFunc(1);
|
|
2373
|
+
// Return the function if no error
|
|
2374
|
+
if (typeof x === "number" && x > 0)
|
|
2375
|
+
return tryFunc;
|
|
2376
|
+
}
|
|
2377
|
+
else if (paramCount === 2) {
|
|
2378
|
+
let x = tryFunc(1, 2);
|
|
2379
|
+
// Return the function if no error
|
|
2380
|
+
if (typeof x === "number" && x > 0)
|
|
2381
|
+
return tryFunc;
|
|
2382
|
+
}
|
|
2383
|
+
// NOTE: For paramCount greater than 2, no definition, will be filtered out
|
|
2384
|
+
}
|
|
2385
|
+
catch (error) {
|
|
2386
|
+
return undefined;
|
|
2387
|
+
}
|
|
2388
|
+
return undefined;
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Resolve Array definition, the input might be a string, or the object describing the array
|
|
2392
|
+
* @param name Name of the Array
|
|
2393
|
+
* @param value Presentation to resolve the array, shortcut string, or the object
|
|
2394
|
+
* @returns Error message
|
|
2395
|
+
*/
|
|
2396
|
+
resolveArray(name, value) {
|
|
2397
|
+
// Check Name
|
|
2398
|
+
const nameError = this.isDrawingNameValid(name, "Block"); // Symbol array actually some group/block
|
|
2399
|
+
if (nameError)
|
|
2400
|
+
return [nameError];
|
|
2401
|
+
const parseRes = parseStrIntoKeyValuePair(value, HuatuSymbolArray.optsSymbolArray);
|
|
2402
|
+
const param = parseRes.body;
|
|
2403
|
+
let symbol;
|
|
2404
|
+
const realOrphans = [];
|
|
2405
|
+
if (parseRes.failedPairs.length > 0) {
|
|
2406
|
+
const pairs = parseRes.failedPairs.map(x => `[${x.key}: ${x.value}]`).join(",");
|
|
2407
|
+
this.warnings.push(`${pairs} are not valid to design a Symbol Array!`);
|
|
2408
|
+
}
|
|
2409
|
+
if (parseRes.unluckyOrphans.length > 0) {
|
|
2410
|
+
this.warnings.push(`${parseRes.unluckyOrphans.map(x => `'${x}`).join(",")} might be repeating to build a Symbol Array!`);
|
|
2411
|
+
}
|
|
2412
|
+
parseRes.orphans.forEach(orphan => {
|
|
2413
|
+
const lcOrphan = orphan.toLowerCase();
|
|
2414
|
+
// Try if a symbol
|
|
2415
|
+
const sym = this.symbols.find(x => x.name === orphan);
|
|
2416
|
+
if (sym) {
|
|
2417
|
+
if (!symbol)
|
|
2418
|
+
symbol = sym;
|
|
2419
|
+
else
|
|
2420
|
+
this.warnings.push(`[Symbol Array resolving]: Symbol already set, '${orphan}' will be discarded!`);
|
|
2421
|
+
}
|
|
2422
|
+
// Try if a Point
|
|
2423
|
+
else if (this.resolvePointFromExpr(orphan)) {
|
|
2424
|
+
if (!param.startPoint)
|
|
2425
|
+
param.startPoint = orphan;
|
|
2426
|
+
else if (!param.center)
|
|
2427
|
+
param.center = orphan;
|
|
2428
|
+
else
|
|
2429
|
+
realOrphans.push(orphan);
|
|
2430
|
+
}
|
|
2431
|
+
// Try if method in Upcase
|
|
2432
|
+
else if (!param.method && HuatuSymbolArray.METHODS.includes(lcOrphan)) {
|
|
2433
|
+
param.method = lcOrphan;
|
|
2434
|
+
}
|
|
2435
|
+
else {
|
|
2436
|
+
realOrphans.push(orphan);
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
if (!param.method)
|
|
2440
|
+
return [`Arraying Method should be specified to build a Symbol Array!`];
|
|
2441
|
+
if (!param.symbol && !symbol)
|
|
2442
|
+
return [`Symbol should be specified when building a Symbol Array!`];
|
|
2443
|
+
if (param.symbol) {
|
|
2444
|
+
const sym = this.symbols.find(x => x.name === param.symbol);
|
|
2445
|
+
if (sym)
|
|
2446
|
+
symbol = sym;
|
|
2447
|
+
else if (!symbol)
|
|
2448
|
+
return [`'${param.symbol}' is not a valid Symbol!`];
|
|
2449
|
+
}
|
|
2450
|
+
if (realOrphans.length > 0) {
|
|
2451
|
+
this.warnings.push(`${realOrphans.map(x => `'${x}'`).join(",")} are not valid to build a Symbol Array!`);
|
|
2452
|
+
}
|
|
2453
|
+
// Now assign the params to build an array
|
|
2454
|
+
const pStr = param.startPoint || param.center;
|
|
2455
|
+
if (!pStr)
|
|
2456
|
+
return [`For Symbol Array, Start/Center Point must be specified!`];
|
|
2457
|
+
const point = this.resolvePointFromExpr(pStr);
|
|
2458
|
+
if (!point)
|
|
2459
|
+
return [`Start Point '${pStr}' could not be resolved into a point!`];
|
|
2460
|
+
let array = new HuatuSymbolArray(name, symbol, param.desc);
|
|
2461
|
+
if (param.alignCenter)
|
|
2462
|
+
array.alignCenter = param.alignCenter;
|
|
2463
|
+
let count = 1;
|
|
2464
|
+
if (param.count)
|
|
2465
|
+
count = param.count;
|
|
2466
|
+
if (param.method === "line" || param.method === "2d") {
|
|
2467
|
+
let dx, dy, rotDeg;
|
|
2468
|
+
if (param.deltaX) {
|
|
2469
|
+
let tryVar = this.resolveVarExpr(param.deltaX);
|
|
2470
|
+
if (tryVar)
|
|
2471
|
+
dx = tryVar;
|
|
2472
|
+
}
|
|
2473
|
+
if (param.deltaY) {
|
|
2474
|
+
let tryVar = this.resolveVarExpr(param.deltaY);
|
|
2475
|
+
if (tryVar)
|
|
2476
|
+
dy = tryVar;
|
|
2477
|
+
}
|
|
2478
|
+
if (param.rotDeg) {
|
|
2479
|
+
let deg = this.resolveVarExpr(param.rotDeg);
|
|
2480
|
+
if (deg)
|
|
2481
|
+
rotDeg = deg;
|
|
2482
|
+
}
|
|
2483
|
+
if (param.method === "line") {
|
|
2484
|
+
let scale;
|
|
2485
|
+
if (param.scale) {
|
|
2486
|
+
const tryFunc = this.testUserFunc(param.scale, 1);
|
|
2487
|
+
if (tryFunc)
|
|
2488
|
+
scale = tryFunc;
|
|
2489
|
+
else {
|
|
2490
|
+
const tryNum = this.resolveVarExpr(param.scale);
|
|
2491
|
+
if (tryNum)
|
|
2492
|
+
scale = tryNum;
|
|
2493
|
+
else
|
|
2494
|
+
return [`The Scale '${param.scale}' cannot be resolved!`];
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
array.configLine(point, count, dx, dy, scale, rotDeg);
|
|
2498
|
+
}
|
|
2499
|
+
else {
|
|
2500
|
+
let countX = 1, countY = 1;
|
|
2501
|
+
if (param.countX)
|
|
2502
|
+
countX = param.countX;
|
|
2503
|
+
if (param.countY)
|
|
2504
|
+
countY = param.countY;
|
|
2505
|
+
let scale;
|
|
2506
|
+
if (param.scale) {
|
|
2507
|
+
const tryFunc = this.testUserFunc(param.scale, 2);
|
|
2508
|
+
if (tryFunc)
|
|
2509
|
+
scale = tryFunc;
|
|
2510
|
+
else {
|
|
2511
|
+
const tryNum = this.resolveVarExpr(param.scale);
|
|
2512
|
+
if (tryNum)
|
|
2513
|
+
scale = tryNum;
|
|
2514
|
+
else
|
|
2515
|
+
return [`The Scale '${param.scale}' cannot be resolved!`];
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
array.config2D(point, countX, countY, dx, dy, scale, rotDeg);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
else { // This is polar
|
|
2522
|
+
let radius, scale, startDeg, deltaDeg;
|
|
2523
|
+
if (param.radius) {
|
|
2524
|
+
const tryFunc = this.testUserFunc(param.radius, 1);
|
|
2525
|
+
if (tryFunc)
|
|
2526
|
+
radius = tryFunc;
|
|
2527
|
+
else {
|
|
2528
|
+
const tryNum = this.resolveVarExpr(param.radius);
|
|
2529
|
+
if (tryNum)
|
|
2530
|
+
radius = tryNum;
|
|
2531
|
+
else
|
|
2532
|
+
return [`The Radius '${param.radius}' cannot be resolved!`];
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
else {
|
|
2536
|
+
radius = symbol.bbox.h * 10; // Make the default radius if not specified
|
|
2537
|
+
this.warnings.push(`No Radius specified for Symbol Array, will use default, which is 10x Symbol BBOX height!`);
|
|
2538
|
+
}
|
|
2539
|
+
if (param.scale) {
|
|
2540
|
+
const tryFunc = this.testUserFunc(param.scale, 1);
|
|
2541
|
+
if (tryFunc)
|
|
2542
|
+
scale = tryFunc;
|
|
2543
|
+
else {
|
|
2544
|
+
const tryNum = this.resolveVarExpr(param.scale);
|
|
2545
|
+
if (tryNum && tryNum > 0)
|
|
2546
|
+
scale = tryNum;
|
|
2547
|
+
else
|
|
2548
|
+
return [`Scale definition '${param.scale}' resolves to no result!`];
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
if (param.startDeg) {
|
|
2552
|
+
let tryDeg = this.resolveVarExpr(param.startDeg);
|
|
2553
|
+
if (tryDeg)
|
|
2554
|
+
startDeg = tryDeg;
|
|
2555
|
+
}
|
|
2556
|
+
if (param.deltaDeg) {
|
|
2557
|
+
let tryDeg = this.resolveVarExpr(param.deltaDeg);
|
|
2558
|
+
if (tryDeg)
|
|
2559
|
+
deltaDeg = tryDeg;
|
|
2560
|
+
}
|
|
2561
|
+
array.configPolar(count, point, radius, scale, startDeg, deltaDeg, param.notRotate);
|
|
2562
|
+
}
|
|
2563
|
+
this.arrays.push(array);
|
|
2564
|
+
return [];
|
|
2565
|
+
}
|
|
2566
|
+
/**
|
|
2567
|
+
* Resolve Clip Path definition, the input might be a string, or a string list
|
|
2568
|
+
* @param name Name of the Clip Path
|
|
2569
|
+
* @param value Presentation to resolve the clip path
|
|
2570
|
+
* @returns Error message
|
|
2571
|
+
*/
|
|
2572
|
+
resolveClip(name, value) {
|
|
2573
|
+
// Check Name
|
|
2574
|
+
if (this.clips.find(c => c.name === name))
|
|
2575
|
+
return [`Clip '${name}' has already been defined, please choose another name!`];
|
|
2576
|
+
const resolveClipPath = (def) => {
|
|
2577
|
+
const matchClipRule = def.match(/^(clip-)?rule[\s]*[,;\s][\s]*(nonzero|evenodd)$/i);
|
|
2578
|
+
if (matchClipRule)
|
|
2579
|
+
return matchClipRule[2];
|
|
2580
|
+
const tryPath = this.paths.find(p => p.name === def);
|
|
2581
|
+
if (tryPath && tryPath.model)
|
|
2582
|
+
return [tryPath.model];
|
|
2583
|
+
const tryPathSeries = this.pathSeries.find(ps => ps.name === def);
|
|
2584
|
+
if (tryPathSeries && tryPathSeries.models.length > 0)
|
|
2585
|
+
return tryPathSeries.models;
|
|
2586
|
+
const resolvedShape = this.resolveShapeDef(def);
|
|
2587
|
+
if (typeof resolvedShape === "string")
|
|
2588
|
+
return resolvedShape;
|
|
2589
|
+
else
|
|
2590
|
+
return [resolvedShape];
|
|
2591
|
+
};
|
|
2592
|
+
const defs = typeof value === "string"
|
|
2593
|
+
? [value]
|
|
2594
|
+
: value;
|
|
2595
|
+
const errs = [];
|
|
2596
|
+
const models = [];
|
|
2597
|
+
let clipRule;
|
|
2598
|
+
defs.map(d => resolveClipPath(d.trim()))
|
|
2599
|
+
.forEach(x => {
|
|
2600
|
+
if (typeof x === "string") {
|
|
2601
|
+
let xlc = x.toLowerCase();
|
|
2602
|
+
if (xlc === "nonzero" || xlc === "evenodd") {
|
|
2603
|
+
clipRule = xlc;
|
|
2604
|
+
}
|
|
2605
|
+
else
|
|
2606
|
+
errs.push(x);
|
|
2607
|
+
}
|
|
2608
|
+
else
|
|
2609
|
+
models.push(...x);
|
|
2610
|
+
});
|
|
2611
|
+
if (models.length > 0) {
|
|
2612
|
+
const paths = models.map(m => HuatuPath.model2Path(m))
|
|
2613
|
+
.filter(p => p !== undefined);
|
|
2614
|
+
if (paths.length !== models.length)
|
|
2615
|
+
this.warnings.push(`Some Model to path conversion failed when resolving clip path '${name}'!`);
|
|
2616
|
+
if (paths.length < 1)
|
|
2617
|
+
errs.push(`No Path resolved for clip '${name}'!`);
|
|
2618
|
+
const clip = new HuatuClipPath(name, paths);
|
|
2619
|
+
if (clipRule)
|
|
2620
|
+
clip.clipRule = clipRule;
|
|
2621
|
+
this.defines.push(clip);
|
|
2622
|
+
this.clips.push(clip);
|
|
2623
|
+
}
|
|
2624
|
+
// Return the errors when resolving
|
|
2625
|
+
return errs;
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Resolve Pattern definition, the input might be an object, describe how the pattern is designed
|
|
2629
|
+
* @param name Name of the Pattern
|
|
2630
|
+
* @param value Presentation to resolve the pattern
|
|
2631
|
+
* @returns Error message
|
|
2632
|
+
*/
|
|
2633
|
+
resolvePattern(name, value) {
|
|
2634
|
+
// Check name duplicating
|
|
2635
|
+
if (this.patterns.find(p => p.name === name))
|
|
2636
|
+
return [`Pattern '${name}' has already been defined!`];
|
|
2637
|
+
const opts = {
|
|
2638
|
+
keys: [
|
|
2639
|
+
["type"],
|
|
2640
|
+
["lineDensity", "density", "dotsDensity", "squareDensity"],
|
|
2641
|
+
["rotDeg", "deg", "lineDeg"],
|
|
2642
|
+
["lineWeight", "lineWidth"],
|
|
2643
|
+
["lineStyle", "style"],
|
|
2644
|
+
["dashDensity", "dashedDensity", "dottedDensity"],
|
|
2645
|
+
["size"],
|
|
2646
|
+
["color"],
|
|
2647
|
+
["opacity"],
|
|
2648
|
+
["bgColor", "background"],
|
|
2649
|
+
["bgOpacity"],
|
|
2650
|
+
["sparseK"]
|
|
2651
|
+
],
|
|
2652
|
+
allowAppend: [],
|
|
2653
|
+
booleans: [],
|
|
2654
|
+
numbers: [
|
|
2655
|
+
"lineDensity", "density", "dotsDensity", "squareDensity",
|
|
2656
|
+
"rotDeg", "deg", "lineDeg",
|
|
2657
|
+
"lineWeight", "lineWidth",
|
|
2658
|
+
"dashDensity", "dashedDensity", "dottedDensity",
|
|
2659
|
+
"size",
|
|
2660
|
+
"opacity",
|
|
2661
|
+
"bgOpacity",
|
|
2662
|
+
"sparseK",
|
|
2663
|
+
],
|
|
2664
|
+
ints: [],
|
|
2665
|
+
qualifiers: {
|
|
2666
|
+
"color": isValidColor,
|
|
2667
|
+
"bgColor": isValidColor,
|
|
2668
|
+
"background": isValidColor,
|
|
2669
|
+
},
|
|
2670
|
+
blindGuess: {
|
|
2671
|
+
"lineStyle": ["solid", "dashed", "dotted"],
|
|
2672
|
+
"color": isValidColor,
|
|
2673
|
+
"type": HuatuPattern.getAllPatternTypes(),
|
|
2674
|
+
}
|
|
2675
|
+
};
|
|
2676
|
+
const ptnParams = {};
|
|
2677
|
+
if (typeof value === "string") {
|
|
2678
|
+
// If the definition is presented in a string
|
|
2679
|
+
const res = parseStrIntoKeyValuePair(value, opts);
|
|
2680
|
+
assignOnlyDefined(ptnParams, res.body);
|
|
2681
|
+
if (res.orphans.length > 0) {
|
|
2682
|
+
this.warnings.push(`Segments [${res.orphans.map(x => `'${x}'`).join(",")}] were not able to be resolved when making a Pattern!`);
|
|
2683
|
+
}
|
|
2684
|
+
if (res.unluckyOrphans.length > 0) {
|
|
2685
|
+
this.warnings.push(`[${res.unluckyOrphans.map(x => `'${x}'`).join(",")}] were failed to set to it's owner key in default, maybe repeated assigning!`);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
else {
|
|
2689
|
+
// If the definition is in object form
|
|
2690
|
+
const res = parseStrIntoKeyValuePair(value, opts);
|
|
2691
|
+
Object.assign(ptnParams, res.body);
|
|
2692
|
+
if (res.failedPairs.length > 0) {
|
|
2693
|
+
const warnings = res.failedPairs.map((x, i) => `${i + 1}) '${x.key}: ${x.value}'`).join("\n");
|
|
2694
|
+
this.warnings.push(`Parameters failed when making a pattern: \n${warnings}`);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
const ptn = HuatuPattern.buildPatternFromParams(ptnParams);
|
|
2698
|
+
if (typeof ptn === "string")
|
|
2699
|
+
return [ptn];
|
|
2700
|
+
this.patterns.push(new HuatuPattern(name, ptn));
|
|
2701
|
+
return [];
|
|
2702
|
+
}
|
|
2703
|
+
/**
|
|
2704
|
+
* Resolve Marker definition, the input might be an object, describe how the marker is designed
|
|
2705
|
+
* @param name Name of the Marker
|
|
2706
|
+
* @param value Presentation to resolve the Marker
|
|
2707
|
+
* @returns Error message
|
|
2708
|
+
*/
|
|
2709
|
+
resolveMarker(name, value) {
|
|
2710
|
+
// Check name duplicating
|
|
2711
|
+
if (this.markers.find(p => p.name === name))
|
|
2712
|
+
return [`Marker '${name}' has already been defined!`];
|
|
2713
|
+
const opts = {
|
|
2714
|
+
keys: [
|
|
2715
|
+
["type"],
|
|
2716
|
+
["size"],
|
|
2717
|
+
["width", "w"],
|
|
2718
|
+
["height", "h"],
|
|
2719
|
+
["r"],
|
|
2720
|
+
["rx"],
|
|
2721
|
+
["ry"],
|
|
2722
|
+
["n"],
|
|
2723
|
+
["direction"],
|
|
2724
|
+
["centerEmpty"],
|
|
2725
|
+
["rIn"],
|
|
2726
|
+
["rOut"],
|
|
2727
|
+
["degOffset", "offset"],
|
|
2728
|
+
["degOpen", "angOpen"],
|
|
2729
|
+
["len", "length"],
|
|
2730
|
+
["k"],
|
|
2731
|
+
["closeArrow"],
|
|
2732
|
+
["makeSmooth", "smooth"],
|
|
2733
|
+
["primaryColor", "priColor", "primeColor", "color"],
|
|
2734
|
+
["secondaryColor", "secColor"],
|
|
2735
|
+
["fillColor", "fill"],
|
|
2736
|
+
["fillOpacity", "opacity"],
|
|
2737
|
+
["strokeColor", "stroke"],
|
|
2738
|
+
["strokeWidth"],
|
|
2739
|
+
],
|
|
2740
|
+
allowAppend: [],
|
|
2741
|
+
booleans: ["centerEmpty", "closeArrow", "makeSmooth", "smooth"],
|
|
2742
|
+
numbers: [
|
|
2743
|
+
"fillOpacity", "opacity",
|
|
2744
|
+
"r", "rx", "ry", "rIn", "rOut",
|
|
2745
|
+
"width", "height", "size",
|
|
2746
|
+
"degOffset", "offset",
|
|
2747
|
+
"degOpen", "angOpen",
|
|
2748
|
+
"len", "length",
|
|
2749
|
+
"k",
|
|
2750
|
+
"fillOpacity", "strokeWidth",
|
|
2751
|
+
],
|
|
2752
|
+
ints: ["n"],
|
|
2753
|
+
qualifiers: {
|
|
2754
|
+
"direction": ["up", "down", "left", "right"],
|
|
2755
|
+
"color": isValidColor,
|
|
2756
|
+
"fillColor": isValidColor,
|
|
2757
|
+
"stroke": isValidColor,
|
|
2758
|
+
"strokeColor": isValidColor,
|
|
2759
|
+
"primaryColor": isValidColor,
|
|
2760
|
+
"priColor": isValidColor,
|
|
2761
|
+
"primeColor": isValidColor,
|
|
2762
|
+
"secondaryColor": isValidColor,
|
|
2763
|
+
"secColor": isValidColor,
|
|
2764
|
+
},
|
|
2765
|
+
blindGuess: {
|
|
2766
|
+
"direction": ["up", "down", "left", "right"],
|
|
2767
|
+
"type": HuatuMarker.getAllMarkerTypeWords(),
|
|
2768
|
+
"primaryColor": isValidColor,
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
const mkrParams = {};
|
|
2772
|
+
if (typeof value === "string") {
|
|
2773
|
+
// If the definition is presented in a string
|
|
2774
|
+
const res = parseStrIntoKeyValuePair(value, opts);
|
|
2775
|
+
Object.assign(mkrParams, res.body);
|
|
2776
|
+
if (res.orphans.length > 0) {
|
|
2777
|
+
this.warnings.push(`Segments [${res.orphans.map(x => `'${x}'`).join(",")}] were not able to be resolved when making a Marker!`);
|
|
2778
|
+
}
|
|
2779
|
+
if (res.unluckyOrphans.length > 0) {
|
|
2780
|
+
this.warnings.push(`[${res.unluckyOrphans.map(x => `'${x}'`).join(",")}] were failed to set to it's owner key in default, maybe repeated assigning!`);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
else {
|
|
2784
|
+
// If the definition is in object form
|
|
2785
|
+
const res = parseStrIntoKeyValuePair(value, opts);
|
|
2786
|
+
Object.assign(mkrParams, res.body);
|
|
2787
|
+
if (res.failedPairs.length > 0) {
|
|
2788
|
+
const warnings = res.failedPairs.map((x, i) => `${i + 1}) '${x.key}: ${x.value}'`).join("\n");
|
|
2789
|
+
this.warnings.push(`Parameters failed when making a marker: \n${warnings}`);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
const mkr = HuatuMarker.buildMarkerFromParams(name, mkrParams);
|
|
2793
|
+
if (typeof mkr === "string")
|
|
2794
|
+
return [mkr];
|
|
2795
|
+
const huatuMkr = new HuatuMarker(name, mkr);
|
|
2796
|
+
huatuMkr.primaryColor = mkrParams.primaryColor;
|
|
2797
|
+
huatuMkr.secondaryColor = mkrParams.secondaryColor;
|
|
2798
|
+
this.markers.push(huatuMkr);
|
|
2799
|
+
return [];
|
|
2800
|
+
}
|
|
2801
|
+
parseLabelDef(format, text) {
|
|
2802
|
+
// Label Format should be: label; [format] ||| [text]
|
|
2803
|
+
// For the format part, it should be like
|
|
2804
|
+
// (location | w); w | h; at; (location); rot; 35; (anchor)pos; center; align; center; [anchor]offset; font; lishu; bold; italic; addline; overline;
|
|
2805
|
+
const optsLabel = {
|
|
2806
|
+
keys: [
|
|
2807
|
+
["refs", "styles", "style", "ref"],
|
|
2808
|
+
["location", "at", "position", "pos", "loc"],
|
|
2809
|
+
["offset"],
|
|
2810
|
+
["anchor"],
|
|
2811
|
+
["width", "w"],
|
|
2812
|
+
["height", "h"],
|
|
2813
|
+
["textAlign", "align"],
|
|
2814
|
+
["bold"],
|
|
2815
|
+
["italic"],
|
|
2816
|
+
["addLine", "line"],
|
|
2817
|
+
["lineHeight", "lnh"],
|
|
2818
|
+
["fontName", "font"],
|
|
2819
|
+
["color", "fontColor"],
|
|
2820
|
+
["fontSize", "size", "fs"],
|
|
2821
|
+
["rotDeg", "deg"],
|
|
2822
|
+
["bgColor", "background", "bg"],
|
|
2823
|
+
["borderColor", "bdrColor", "bdr"],
|
|
2824
|
+
],
|
|
2825
|
+
booleans: ["bold", "italic"],
|
|
2826
|
+
ints: [],
|
|
2827
|
+
numbers: [
|
|
2828
|
+
"fontSize", "size", "fs",
|
|
2829
|
+
"rotDeg", "deg",
|
|
2830
|
+
"lineHeight", "lnh"
|
|
2831
|
+
],
|
|
2832
|
+
allowAppend: ["refs", "styles", "style", "ref"],
|
|
2833
|
+
qualifiers: {
|
|
2834
|
+
"color": isValidColor,
|
|
2835
|
+
"fontColor": isValidColor,
|
|
2836
|
+
"anchor": HuatuLabel.ANCHOR_POSITIONS,
|
|
2837
|
+
"textAlign": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2838
|
+
"align": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2839
|
+
"addLine": HuatuLabelStyle.DECORATION_LINES,
|
|
2840
|
+
"bgColor": isValidColor,
|
|
2841
|
+
"background": isValidColor,
|
|
2842
|
+
"bg": isValidColor,
|
|
2843
|
+
"borderColor": isValidColor,
|
|
2844
|
+
"bdrColor": isValidColor,
|
|
2845
|
+
"bdr": isValidColor,
|
|
2846
|
+
},
|
|
2847
|
+
blindGuess: {
|
|
2848
|
+
"textAlign": HuatuLabelStyle.TEXT_ALIGNS,
|
|
2849
|
+
"anchor": HuatuLabel.ANCHOR_POSITIONS,
|
|
2850
|
+
"addLine": [
|
|
2851
|
+
"over", "overline",
|
|
2852
|
+
"under", "underline",
|
|
2853
|
+
"middleline", "through", "strike-through", "strikethrough"
|
|
2854
|
+
],
|
|
2855
|
+
"fontName": HuatuLabelStyle.PREDEFINED_FONT_NAMES,
|
|
2856
|
+
"color": isValidColor,
|
|
2857
|
+
"bgColor": isValidColor,
|
|
2858
|
+
}
|
|
2859
|
+
};
|
|
2860
|
+
const res = parseStrIntoKeyValuePair(format, optsLabel);
|
|
2861
|
+
const resBody = res.body;
|
|
2862
|
+
// Handle Unlucky orphans
|
|
2863
|
+
res.unluckyOrphans.forEach(orphan => {
|
|
2864
|
+
// To see if it is the keyword 'Center'
|
|
2865
|
+
if (orphan === "center") {
|
|
2866
|
+
if (resBody.textAlign === "center" && !resBody.anchor) {
|
|
2867
|
+
resBody.anchor = "center";
|
|
2868
|
+
}
|
|
2869
|
+
else {
|
|
2870
|
+
this.warnings.push(`[Label Resolving]: '${orphan}' could not be used, maybe repeating.`);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
else {
|
|
2874
|
+
// Throw to orphans to reprocess
|
|
2875
|
+
res.orphans.push(orphan);
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
// Re-check Orphans
|
|
2879
|
+
const realOrphans = [];
|
|
2880
|
+
res.orphans.forEach(orphan => {
|
|
2881
|
+
let orphanProcessed = false;
|
|
2882
|
+
// To see if it font size definition
|
|
2883
|
+
let fsStr = /^([1-9]\d*)p[xt]$/i.exec(orphan);
|
|
2884
|
+
if (fsStr && !resBody.fontSize) {
|
|
2885
|
+
resBody.fontSize = parseFloat(fsStr[1]);
|
|
2886
|
+
orphanProcessed = true;
|
|
2887
|
+
}
|
|
2888
|
+
// To see if it is some label style
|
|
2889
|
+
if (!orphanProcessed) {
|
|
2890
|
+
let sty = this.labelStyles.find(s => s.name === orphan);
|
|
2891
|
+
if (sty) {
|
|
2892
|
+
if (resBody.refs)
|
|
2893
|
+
resBody.refs += `, ${orphan}`;
|
|
2894
|
+
else
|
|
2895
|
+
resBody.refs = orphan;
|
|
2896
|
+
orphanProcessed = true;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
// To see if it is some Point-Like
|
|
2900
|
+
if (!orphanProcessed) {
|
|
2901
|
+
let p = this.resolvePointFromExpr(orphan);
|
|
2902
|
+
if (p) {
|
|
2903
|
+
if (!resBody.location)
|
|
2904
|
+
resBody.location = orphan;
|
|
2905
|
+
else if (!resBody.offset)
|
|
2906
|
+
resBody.offset = orphan;
|
|
2907
|
+
else
|
|
2908
|
+
this.warnings.push(`[Label Resolving]: Too many point assigning '${orphan}'!`);
|
|
2909
|
+
orphanProcessed = true;
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
// Throw to Orphan if it is not processed
|
|
2913
|
+
if (!orphanProcessed) {
|
|
2914
|
+
realOrphans.push(orphan);
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
// Add warning about Orphans
|
|
2918
|
+
if (realOrphans.length > 0) {
|
|
2919
|
+
const orphans = realOrphans.map(x => `'${x}'`).join(", ");
|
|
2920
|
+
this.warnings.push(`[Label Resolving]: segments ${orphans} could not be resolved!`);
|
|
2921
|
+
}
|
|
2922
|
+
let width = resBody.width ? this.resolveVarExpr(resBody.width) : undefined;
|
|
2923
|
+
let height = resBody.height ? this.resolveVarExpr(resBody.height) : undefined;
|
|
2924
|
+
const label = new HuatuLabel(text, width, height);
|
|
2925
|
+
if (resBody.location) {
|
|
2926
|
+
let p = this.resolvePointFromExpr(resBody.location);
|
|
2927
|
+
if (p)
|
|
2928
|
+
label.location = p;
|
|
2929
|
+
else {
|
|
2930
|
+
this.warnings.push(`Label Location '${resBody.location}' cannot resolve to some point, Will use (0, 0)`);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
if (resBody.offset) {
|
|
2934
|
+
let p = this.resolvePointFromExpr(resBody.offset);
|
|
2935
|
+
if (p)
|
|
2936
|
+
label.anchorOffset = p;
|
|
2937
|
+
}
|
|
2938
|
+
if (resBody.refs) {
|
|
2939
|
+
resBody.refs.split(/[,\s]/)
|
|
2940
|
+
.map(x => x.trim())
|
|
2941
|
+
.filter(x => !!x)
|
|
2942
|
+
.forEach(x => {
|
|
2943
|
+
const sty = this.labelStyles.find(s => s.name === x);
|
|
2944
|
+
if (sty) {
|
|
2945
|
+
if (!label.styles.find(s => s === sty)) {
|
|
2946
|
+
label.styles.push(sty);
|
|
2947
|
+
}
|
|
2948
|
+
else {
|
|
2949
|
+
this.warnings.push(`Style '${x}' has been already added to the label!`);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
else {
|
|
2953
|
+
this.warnings.push(`'${x}' is not valid defined Label Style!`);
|
|
2954
|
+
}
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
if (resBody.anchor)
|
|
2958
|
+
label.setAnchor(resBody.anchor);
|
|
2959
|
+
if (resBody.textAlign)
|
|
2960
|
+
label.textAlign = resBody.textAlign;
|
|
2961
|
+
if (resBody.bold)
|
|
2962
|
+
label.bold = resBody.bold;
|
|
2963
|
+
if (resBody.italic)
|
|
2964
|
+
label.italic = resBody.italic;
|
|
2965
|
+
if (resBody.addLine)
|
|
2966
|
+
label.addLine = resBody.addLine;
|
|
2967
|
+
if (resBody.fontName)
|
|
2968
|
+
label.font = resBody.fontName;
|
|
2969
|
+
if (resBody.color)
|
|
2970
|
+
label.color = resBody.color;
|
|
2971
|
+
if (resBody.fontSize)
|
|
2972
|
+
label.fontSize = resBody.fontSize;
|
|
2973
|
+
if (resBody.lineHeight)
|
|
2974
|
+
label.lineHeight = resBody.lineHeight;
|
|
2975
|
+
if (resBody.rotDeg)
|
|
2976
|
+
label.rotDeg = resBody.rotDeg;
|
|
2977
|
+
if (resBody.bgColor)
|
|
2978
|
+
label.bgColor = resBody.bgColor;
|
|
2979
|
+
if (resBody.borderColor)
|
|
2980
|
+
label.borderColor = resBody.borderColor;
|
|
2981
|
+
return label;
|
|
2982
|
+
}
|
|
2983
|
+
parseStyleAndTransformString(def) {
|
|
2984
|
+
const parseRes = parseStrIntoKeyValuePair(def, HuatuDrawing.optsDrawingStyleAndTransform);
|
|
2985
|
+
const params = parseRes.body;
|
|
2986
|
+
// Transform
|
|
2987
|
+
const transform = new HuatuTransform();
|
|
2988
|
+
if (params.at) {
|
|
2989
|
+
const p = this.resolvePointFromExpr(params.at);
|
|
2990
|
+
if (p) {
|
|
2991
|
+
transform.rotCx = p.x;
|
|
2992
|
+
transform.rotCy = p.y;
|
|
2993
|
+
}
|
|
2994
|
+
else {
|
|
2995
|
+
this.warnings.push(`'${params.at}' cannot be resolved to a point!`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
if (params.move) {
|
|
2999
|
+
const p = this.resolvePointFromExpr(params.move);
|
|
3000
|
+
if (p) {
|
|
3001
|
+
transform.x = p.x;
|
|
3002
|
+
transform.y = p.y;
|
|
3003
|
+
}
|
|
3004
|
+
else {
|
|
3005
|
+
this.warnings.push(`'${params.move}' cannot be resolved to a point!`);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
if (params.rotDeg)
|
|
3009
|
+
transform.rotDeg = this.resolveVarExpr(params.rotDeg);
|
|
3010
|
+
if (params.scale) {
|
|
3011
|
+
if (params.scale > 0)
|
|
3012
|
+
transform.scale = params.scale;
|
|
3013
|
+
else
|
|
3014
|
+
this.warnings.push(`Value of Scale could not be negative!`);
|
|
3015
|
+
}
|
|
3016
|
+
const style = this.parseStyleFromKeyValueResults(parseRes);
|
|
3017
|
+
let smooth;
|
|
3018
|
+
if (parseRes.subKeys && parseRes.subKeys["smooth"]) {
|
|
3019
|
+
let smoothVar = parseRes.subKeys["smooth"].body;
|
|
3020
|
+
smooth = {
|
|
3021
|
+
mode: smoothVar.mode,
|
|
3022
|
+
factor: smoothVar.factor,
|
|
3023
|
+
};
|
|
3024
|
+
// Make lineJoin round if not set
|
|
3025
|
+
if (!style.strokeLineJoin) {
|
|
3026
|
+
style.strokeLineJoin = "round";
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
return {
|
|
3030
|
+
style,
|
|
3031
|
+
transform,
|
|
3032
|
+
alignCenter: params.alignCenter,
|
|
3033
|
+
smooth,
|
|
3034
|
+
radius: params.radius,
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
/**
|
|
3038
|
+
* Resolve the "modelled" drawing by definition
|
|
3039
|
+
* @param def The definition of the drawing of a shape, It might have two formats:
|
|
3040
|
+
* - Ad-Hoc Shape definitions and style/transform settings
|
|
3041
|
+
* - Referring a defined (named) module, and specifying style/transform
|
|
3042
|
+
* @param allowedTypes The allowed sets of module that this function call will search, it is designed in the way that
|
|
3043
|
+
* Different Drawing Group will accept different types of modules *
|
|
3044
|
+
* @returns The resolved Drawing or error message
|
|
3045
|
+
*/
|
|
3046
|
+
resolveDrawingOfShapes(def, allowedTypes) {
|
|
3047
|
+
// let format, style, transform, alignCenter;
|
|
3048
|
+
const tryShapeAndDrawing = WtHuatu.REGEX_SHAPE_AND_DRAWING_DEF.exec(def);
|
|
3049
|
+
if (tryShapeAndDrawing) {
|
|
3050
|
+
const shape = this.resolveShapeDef(tryShapeAndDrawing[1]);
|
|
3051
|
+
if (typeof shape === "string")
|
|
3052
|
+
return shape;
|
|
3053
|
+
const fmtRes = this.parseStyleAndTransformString(tryShapeAndDrawing[2]);
|
|
3054
|
+
let path = HuatuPath.model2Path(shape);
|
|
3055
|
+
if (path instanceof PathPolyline && fmtRes.smooth) {
|
|
3056
|
+
path = path.toSmoothed(fmtRes.smooth.factor, fmtRes.smooth.mode);
|
|
3057
|
+
}
|
|
3058
|
+
else if (fmtRes.radius &&
|
|
3059
|
+
(path instanceof PathPolyline ||
|
|
3060
|
+
path instanceof PathPolygon ||
|
|
3061
|
+
path instanceof PathTriangle ||
|
|
3062
|
+
path instanceof PathRect)) {
|
|
3063
|
+
path = path.toAngleArced(fmtRes.radius);
|
|
3064
|
+
}
|
|
3065
|
+
const htPath = new HuatuPath(path);
|
|
3066
|
+
htPath.setModel(shape);
|
|
3067
|
+
return new HuatuDrawing(htPath, fmtRes.style, fmtRes.transform);
|
|
3068
|
+
}
|
|
3069
|
+
// If it is not a ad-hoc shape (and thus path) define, then it should refer some other defined modules
|
|
3070
|
+
// And the first parameter is the name
|
|
3071
|
+
let name, nameIdx = undefined, fmtStr;
|
|
3072
|
+
const scPos = def.indexOf(";");
|
|
3073
|
+
if (scPos < 0) {
|
|
3074
|
+
name = def.trim();
|
|
3075
|
+
fmtStr = undefined;
|
|
3076
|
+
}
|
|
3077
|
+
else {
|
|
3078
|
+
name = def.substring(0, scPos).trim();
|
|
3079
|
+
fmtStr = def.substring(scPos + 1);
|
|
3080
|
+
}
|
|
3081
|
+
if (!name) {
|
|
3082
|
+
return `A module (name) to draw needs to be specified!`;
|
|
3083
|
+
}
|
|
3084
|
+
// Check if it is referring some point in a Point Series
|
|
3085
|
+
let originalName = name;
|
|
3086
|
+
const match = name.match(WT_REGEX.VAR_WITH_INDEXING);
|
|
3087
|
+
if (match) {
|
|
3088
|
+
name = match[1];
|
|
3089
|
+
nameIdx = parseInt(match[3] || match[4]);
|
|
3090
|
+
}
|
|
3091
|
+
// Check if the item is PointSeries, if so, it will be converted to PathSeries of circles
|
|
3092
|
+
const ps = this.pointSeries.find(x => x.name === name);
|
|
3093
|
+
if (ps) {
|
|
3094
|
+
let psR;
|
|
3095
|
+
const psSty = new HuatuStyle();
|
|
3096
|
+
if (fmtStr) {
|
|
3097
|
+
const parseRes = parseStrIntoKeyValuePair(fmtStr, HuatuPointSeries.optsPointSeriesDrawing);
|
|
3098
|
+
const params = parseRes.body;
|
|
3099
|
+
if (params.size) {
|
|
3100
|
+
let rEval = this.resolveVarExpr(params.size);
|
|
3101
|
+
if (!rEval || rEval < 0) {
|
|
3102
|
+
this.warnings.push(`Size/Radius (${params.size}) when drawing Point Series not valid, will use default (1)!`);
|
|
3103
|
+
}
|
|
3104
|
+
else {
|
|
3105
|
+
psR = rEval;
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
const subFill = parseRes.subKeys ? parseRes.subKeys["fill"] : undefined;
|
|
3109
|
+
const subStroke = parseRes.subKeys ? parseRes.subKeys["stroke"] : undefined;
|
|
3110
|
+
if (params.fillColor)
|
|
3111
|
+
psSty.fillColor = params.fillColor;
|
|
3112
|
+
else if (subFill && subFill.body["color"])
|
|
3113
|
+
psSty.fillColor = subFill.body["color"];
|
|
3114
|
+
if (params.fillOpacity !== undefined && WtMath.isIn0and1(params.fillOpacity))
|
|
3115
|
+
psSty.fillOpacity = params.fillOpacity;
|
|
3116
|
+
else if (subFill &&
|
|
3117
|
+
subFill.body["opacity"] !== undefined &&
|
|
3118
|
+
WtMath.isIn0and1(subFill.body["opacity"]))
|
|
3119
|
+
psSty.fillOpacity = subFill.body["opacity"];
|
|
3120
|
+
if (params.strokeColor)
|
|
3121
|
+
psSty.strokeColor = params.strokeColor;
|
|
3122
|
+
else if (subStroke && subStroke.body["color"])
|
|
3123
|
+
psSty.strokeColor = subStroke.body["color"];
|
|
3124
|
+
if (params.strokeWidth)
|
|
3125
|
+
psSty.strokeWidth = params.strokeWidth;
|
|
3126
|
+
else if (subStroke && subStroke.body["width"])
|
|
3127
|
+
psSty.strokeWidth = subStroke.body["width"];
|
|
3128
|
+
if (params.strokeOpacity !== undefined && WtMath.isIn0and1(params.strokeOpacity))
|
|
3129
|
+
psSty.fillOpacity = params.fillOpacity;
|
|
3130
|
+
else if (subStroke &&
|
|
3131
|
+
subStroke.body["opacity"] !== undefined &&
|
|
3132
|
+
WtMath.isIn0and1(subStroke.body["opacity"]))
|
|
3133
|
+
psSty.strokeOpacity = subStroke.body["opacity"];
|
|
3134
|
+
if (params.dashArray)
|
|
3135
|
+
psSty.strokeDashArray = params.dashArray;
|
|
3136
|
+
else if (subStroke && subStroke.body["dashArray"])
|
|
3137
|
+
psSty.strokeDashArray = subStroke.body["dashArray"];
|
|
3138
|
+
let notUsed = [];
|
|
3139
|
+
if (parseRes.orphans.length > 0) {
|
|
3140
|
+
notUsed = parseRes.orphans.map(x => `"${x}"`);
|
|
3141
|
+
}
|
|
3142
|
+
if (parseRes.failedPairs.length > 0) {
|
|
3143
|
+
notUsed.push(...parseRes.failedPairs.map(x => `"[${x.key}]:${x.value}"`));
|
|
3144
|
+
}
|
|
3145
|
+
if (subFill && subFill.orphans) {
|
|
3146
|
+
notUsed.push(...subFill.orphans.map(x => `"fill:${x}"`));
|
|
3147
|
+
}
|
|
3148
|
+
if (subStroke && subStroke.orphans) {
|
|
3149
|
+
notUsed.push(...subStroke.orphans.map(x => `"fill:${x}"`));
|
|
3150
|
+
}
|
|
3151
|
+
if (notUsed.length > 0) {
|
|
3152
|
+
this.warnings.push(`Parameters [${notUsed.join(",")}] not used for PointSeries drawing`);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
if (nameIdx !== undefined && (nameIdx < 1 || nameIdx > ps.points.length))
|
|
3156
|
+
return `Index out of range when drawing one point from PointSeries (${originalName})`;
|
|
3157
|
+
const circles = (nameIdx ? [ps.points[nameIdx - 1]] : ps.points).map(p => new Circle(p.x, p.y, psR || 1));
|
|
3158
|
+
const psDrawing = HuatuPathSeries.fromModels(circles);
|
|
3159
|
+
if (!psSty.fillColor)
|
|
3160
|
+
psSty.fillColor = "currentColor";
|
|
3161
|
+
return new HuatuDrawing(psDrawing, psSty);
|
|
3162
|
+
}
|
|
3163
|
+
// Shape/Path referring
|
|
3164
|
+
let fmtRes = fmtStr ? this.parseStyleAndTransformString(fmtStr) : undefined;
|
|
3165
|
+
let elemSources = [];
|
|
3166
|
+
if (allowedTypes.includes("shape") || allowedTypes.includes("path")) {
|
|
3167
|
+
elemSources.push(...this.paths);
|
|
3168
|
+
}
|
|
3169
|
+
if (allowedTypes.includes("shapeSeries") || allowedTypes.includes("pathSeries")) {
|
|
3170
|
+
elemSources.push(...this.pathSeries);
|
|
3171
|
+
}
|
|
3172
|
+
if (allowedTypes.includes("symbol")) {
|
|
3173
|
+
elemSources.push(...this.symbols);
|
|
3174
|
+
}
|
|
3175
|
+
if (allowedTypes.includes("array")) {
|
|
3176
|
+
elemSources.push(...this.arrays);
|
|
3177
|
+
}
|
|
3178
|
+
if (allowedTypes.includes("block")) {
|
|
3179
|
+
elemSources.push(...this.blocks);
|
|
3180
|
+
}
|
|
3181
|
+
if (allowedTypes.includes("layer")) {
|
|
3182
|
+
elemSources.push(...this.layers);
|
|
3183
|
+
}
|
|
3184
|
+
if (allowedTypes.includes("component")) {
|
|
3185
|
+
elemSources.push(...this.compPlacements);
|
|
3186
|
+
}
|
|
3187
|
+
const element = elemSources.find(x => x.name === name);
|
|
3188
|
+
if (element) {
|
|
3189
|
+
if (element.refCount)
|
|
3190
|
+
element.refCount++;
|
|
3191
|
+
let eleFound = element;
|
|
3192
|
+
if (nameIdx !== undefined) {
|
|
3193
|
+
if (!(element instanceof HuatuPathSeries))
|
|
3194
|
+
return `Draw (${originalName}) failed, because ${name} is not ShapeSeries`;
|
|
3195
|
+
if (nameIdx < 1 || nameIdx > element.paths.length)
|
|
3196
|
+
return `Index out of range when drawing one path from PathSeries (${originalName})`;
|
|
3197
|
+
eleFound = new HuatuPath(element.paths[nameIdx - 1]);
|
|
3198
|
+
}
|
|
3199
|
+
if (fmtRes && element instanceof HuatuPath && element.path) {
|
|
3200
|
+
if (fmtRes.smooth && element.path instanceof PathPolyline) {
|
|
3201
|
+
// If Polyline and smooth, then reset the path to smoothed path
|
|
3202
|
+
eleFound = new HuatuPath(element.path.toSmoothed(fmtRes.smooth.factor, fmtRes.smooth.mode));
|
|
3203
|
+
}
|
|
3204
|
+
else if (fmtRes.radius &&
|
|
3205
|
+
(element.path instanceof PathPolygon ||
|
|
3206
|
+
element.path instanceof PathPolyline ||
|
|
3207
|
+
element.path instanceof PathRect ||
|
|
3208
|
+
element.path instanceof PathTriangle)) {
|
|
3209
|
+
// If radius specified, then make radius at angles
|
|
3210
|
+
eleFound = new HuatuPath(element.path.toAngleArced(fmtRes.radius));
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
// Make component placement some drawing
|
|
3214
|
+
if (element instanceof HuatuComponentPlacement) {
|
|
3215
|
+
eleFound = new HuatuCompDrawing(element);
|
|
3216
|
+
// Remove Transform and AlignCenter settings for Component placement
|
|
3217
|
+
if (fmtRes) {
|
|
3218
|
+
const msgs = [];
|
|
3219
|
+
if (fmtRes.transform) {
|
|
3220
|
+
if (fmtRes.transform.rotCx || fmtRes.transform.rotCy || fmtRes.transform.rotDeg) {
|
|
3221
|
+
let msgDeg = fmtRes.transform.rotDeg ? ` ${fmtRes.transform.rotDeg.toFixed(2)} deg` : ``;
|
|
3222
|
+
let msgLoc = fmtRes.transform.rotCx || fmtRes.transform.rotCy
|
|
3223
|
+
? ` at (${fmtRes.transform.rotCx ? fmtRes.transform.rotCx.toFixed(2) : 0}, ${fmtRes.transform.rotCy ? fmtRes.transform.rotCy.toFixed(2) : 0})`
|
|
3224
|
+
: ``;
|
|
3225
|
+
msgs.push(`Rotate${msgDeg}${msgLoc}`);
|
|
3226
|
+
}
|
|
3227
|
+
if (fmtRes.transform.scale) {
|
|
3228
|
+
msgs.push(`Scale(${fmtRes.transform.scale})`);
|
|
3229
|
+
}
|
|
3230
|
+
if (fmtRes.transform.x || fmtRes.transform.y) {
|
|
3231
|
+
msgs.push(`Move to (${fmtRes.transform.x ? fmtRes.transform.x.toFixed(2) : 0}, ${fmtRes.transform.y ? fmtRes.transform.y.toFixed(2) : 0})`);
|
|
3232
|
+
}
|
|
3233
|
+
fmtRes.transform = undefined;
|
|
3234
|
+
}
|
|
3235
|
+
if (fmtRes.alignCenter) {
|
|
3236
|
+
msgs.push("alignCenter");
|
|
3237
|
+
fmtRes.alignCenter = undefined;
|
|
3238
|
+
}
|
|
3239
|
+
if (msgs.length > 0) {
|
|
3240
|
+
this.warnings.push(`No Transform(scale/move/rotate) and alignCenter supported for a component placement, what you specified (${msgs.join(';')}) will be ignored!`);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
return new HuatuDrawing(eleFound, fmtRes?.style, fmtRes?.transform, fmtRes?.alignCenter);
|
|
3245
|
+
}
|
|
3246
|
+
else {
|
|
3247
|
+
return `[${name}] is not found in defined modules!`;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
resolveWireTerminal(def) {
|
|
3251
|
+
if (!def)
|
|
3252
|
+
return undefined;
|
|
3253
|
+
// 1. Check if Component (placement) 's port
|
|
3254
|
+
const tryPlacementCall = /^([a-z][a-z0-9_]*)[.]([0-9a-zA-Z]+)$/i.exec(def);
|
|
3255
|
+
if (tryPlacementCall) {
|
|
3256
|
+
const plcName = tryPlacementCall[1];
|
|
3257
|
+
const portName = tryPlacementCall[2];
|
|
3258
|
+
const plc = this.compPlacements.find(x => x.name === plcName);
|
|
3259
|
+
if (plc) {
|
|
3260
|
+
const port = plc.portsSettled.find(p => p.name === portName);
|
|
3261
|
+
if (port) {
|
|
3262
|
+
return port;
|
|
3263
|
+
}
|
|
3264
|
+
else {
|
|
3265
|
+
this.warnings.push(`Port '${portName}' cannot found in Component Placement '${plcName}'`);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
const tryPointExpr = this.resolvePointFromExpr(def);
|
|
3270
|
+
if (tryPointExpr)
|
|
3271
|
+
return tryPointExpr;
|
|
3272
|
+
// TODO: implement it
|
|
3273
|
+
// 2. Check if it is a point referencing some shape/path, etc, or purely some point definition
|
|
3274
|
+
return undefined;
|
|
3275
|
+
}
|
|
3276
|
+
parseWireDef(def) {
|
|
3277
|
+
const res = parseStrIntoKeyValuePair(def, HuatuWire.optsHuatuWire);
|
|
3278
|
+
const params = res.body;
|
|
3279
|
+
if (!params.from) {
|
|
3280
|
+
return `You must provide 'From' terminal to define a wire. '${def}' lacks some!`;
|
|
3281
|
+
}
|
|
3282
|
+
let net;
|
|
3283
|
+
if (!params.to) {
|
|
3284
|
+
if (params.net) {
|
|
3285
|
+
net = this.nets.find(x => x.name === params.net);
|
|
3286
|
+
if (!net) {
|
|
3287
|
+
return `No 'To' terminal and the net you refer (${params.net}) doesn't exist`;
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
else {
|
|
3291
|
+
return `No 'To' terminal and no net defined for the wire '${def}'`;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
let errMsg = [];
|
|
3295
|
+
const termFrom = this.resolveWireTerminal(params.from);
|
|
3296
|
+
if (!termFrom) {
|
|
3297
|
+
errMsg.push(`Wire terminal 'From' (${params.from}) cannot resolve to a valid point!`);
|
|
3298
|
+
}
|
|
3299
|
+
let termTo;
|
|
3300
|
+
if (params.to) {
|
|
3301
|
+
termTo = this.resolveWireTerminal(params.to);
|
|
3302
|
+
if (!termTo) {
|
|
3303
|
+
errMsg.push(`Wire terminal 'To' (${params.to}) cannot resolve to a valid point!`);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
if (errMsg.length > 0)
|
|
3307
|
+
return errMsg.join("\n");
|
|
3308
|
+
const wire = new HuatuWire(termFrom, termTo, params.type);
|
|
3309
|
+
// Check other parameters of that wire
|
|
3310
|
+
if (params.color)
|
|
3311
|
+
wire.strokeColor = params.color;
|
|
3312
|
+
if (params.radius)
|
|
3313
|
+
wire.radius = params.radius;
|
|
3314
|
+
if (params.turn) {
|
|
3315
|
+
const turn = this.resolveVarExpr(params.turn);
|
|
3316
|
+
if (turn === undefined) {
|
|
3317
|
+
this.warnings.push(`Turn parameter '${params.turn}' of wire def is invalid, it cannot resolve to a number!`);
|
|
3318
|
+
}
|
|
3319
|
+
else {
|
|
3320
|
+
if (turn < 0 || turn > 1) {
|
|
3321
|
+
this.warnings.push(`Turn parameter should be in [0, 1], what you specified '${params.turn}' resolved to '${turn}'`);
|
|
3322
|
+
}
|
|
3323
|
+
wire.turn = turn;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
if (params.turnX) {
|
|
3327
|
+
const turnX = this.resolvePointCoorsExpr(params.turnX);
|
|
3328
|
+
if (turnX === undefined) {
|
|
3329
|
+
this.warnings.push(`TurnX parameter '${params.turnX}' of wire def is invalid, it cannot resolve to a number!`);
|
|
3330
|
+
}
|
|
3331
|
+
else {
|
|
3332
|
+
wire.turnX = turnX;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
if (params.turnY) {
|
|
3336
|
+
const turnY = this.resolvePointCoorsExpr(params.turnY);
|
|
3337
|
+
if (turnY === undefined) {
|
|
3338
|
+
this.warnings.push(`TurnY parameter '${params.turnY}' of wire def is invalid, it cannot resolve to a number!`);
|
|
3339
|
+
}
|
|
3340
|
+
else {
|
|
3341
|
+
wire.turnY = turnY;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (params.turnAt) {
|
|
3345
|
+
const turnAt = this.resolvePointFromExpr(params.turnAt);
|
|
3346
|
+
if (turnAt === undefined) {
|
|
3347
|
+
this.warnings.push(`TurnAt parameter '${params.turnY}' of wire def is invalid, it cannot resolve to a Point!`);
|
|
3348
|
+
}
|
|
3349
|
+
else {
|
|
3350
|
+
wire.turnAt = turnAt;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
if (params.net) {
|
|
3354
|
+
// TODO: handle Net
|
|
3355
|
+
}
|
|
3356
|
+
if (params.lineWidth) {
|
|
3357
|
+
const lw = this.resolveVarExpr(params.lineWidth);
|
|
3358
|
+
if (lw === undefined || lw < 0) {
|
|
3359
|
+
this.warnings.push(`lineWidth parameter '${params.lineWidth}' of wire def is invalid, it cannot resolve to a positive number!`);
|
|
3360
|
+
}
|
|
3361
|
+
else {
|
|
3362
|
+
wire.lineWidth = lw;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (params.joint) {
|
|
3366
|
+
wire.jointColor = params.joint;
|
|
3367
|
+
}
|
|
3368
|
+
if (params.jointSize) {
|
|
3369
|
+
const size = parseFloat(params.jointSize);
|
|
3370
|
+
if (size > 0) {
|
|
3371
|
+
wire.jointSize = size;
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
this.warnings.push(`Wire joint size should be positive, '${[params.jointSize]}' is not!`);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
if (params.squareJoint) {
|
|
3378
|
+
wire.isSquareJoint = true;
|
|
3379
|
+
}
|
|
3380
|
+
// Resolve Points
|
|
3381
|
+
wire.tryResolvePoints();
|
|
3382
|
+
return new HuatuWireDrawing(wire, this);
|
|
3383
|
+
}
|
|
3384
|
+
parseAngleMarkDef(def) {
|
|
3385
|
+
const res = parseStrIntoKeyValuePair(def, HuatuAngleMark.optsAngleMarkDef);
|
|
3386
|
+
const params = res.body;
|
|
3387
|
+
if (!params.pA)
|
|
3388
|
+
return `First Point (pA) not found when making Angle Mark: '${def}'`;
|
|
3389
|
+
if (!params.pB)
|
|
3390
|
+
return `Second Point (pB) not found when making Angle Mark: '${def}'`;
|
|
3391
|
+
if (!params.pC)
|
|
3392
|
+
return `Third Point (pC) not found when making Angle Mark: '${def}'`;
|
|
3393
|
+
const pA = this.resolvePointFromExpr(params.pA);
|
|
3394
|
+
if (!pA)
|
|
3395
|
+
return `First Point (pA, def: '${params.pA}') Cannot resolve to a Point`;
|
|
3396
|
+
const pB = this.resolvePointFromExpr(params.pB);
|
|
3397
|
+
if (!pB)
|
|
3398
|
+
return `Second Point (pB, def: '${params.pB}') Cannot resolve to a Point`;
|
|
3399
|
+
const pC = this.resolvePointFromExpr(params.pC);
|
|
3400
|
+
if (!pC)
|
|
3401
|
+
return `Third Point (pC, def: '${params.pC}') Cannot resolve to a Point`;
|
|
3402
|
+
const am = new HuatuAngleMark(pA, pB, pC);
|
|
3403
|
+
// Handle Stroke
|
|
3404
|
+
if (params.lineWidth) {
|
|
3405
|
+
const lw = this.resolveVarExpr(params.lineWidth);
|
|
3406
|
+
if (lw) {
|
|
3407
|
+
am.setStroke(lw, params.strokeColor);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
else if (params.strokeColor) {
|
|
3411
|
+
am.setStroke(HuatuAngleMark.DEFAULT_ARC_LINE_WIDTH, params.strokeColor);
|
|
3412
|
+
}
|
|
3413
|
+
// Handle Fill
|
|
3414
|
+
if (res.subKeys && res.subKeys["fill"]) {
|
|
3415
|
+
const fillVals = res.subKeys["fill"].body;
|
|
3416
|
+
if (!params.fillColor && fillVals["color"]) {
|
|
3417
|
+
params.fillColor = fillVals["color"];
|
|
3418
|
+
}
|
|
3419
|
+
if (params.fillOpacity === undefined && fillVals["opacity"] !== undefined && WtMath.isIn0and1(fillVals["opacity"])) {
|
|
3420
|
+
params.fillOpacity = fillVals["opacity"];
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
if (params.fillColor && params.fillColor.toLowerCase() !== "none") {
|
|
3424
|
+
am.setFill(params.fillColor, params.fillOpacity);
|
|
3425
|
+
}
|
|
3426
|
+
// Make mark
|
|
3427
|
+
const size = params.size ? this.resolveVarExpr(params.size) : undefined;
|
|
3428
|
+
am.makeMark(params.type || "line1", size || HuatuAngleMark.DEFAULT_ARC_SIZE, params.large, params.showPerp, params.startArrow, params.labelPos);
|
|
3429
|
+
// Handle Label
|
|
3430
|
+
am.setLabelText(params.label, params.showDeg, params.degDigits);
|
|
3431
|
+
const lw = params.labelWidth ? this.resolveVarExpr(params.labelWidth) : undefined;
|
|
3432
|
+
const lh = params.labelHeight ? this.resolveVarExpr(params.labelHeight) : undefined;
|
|
3433
|
+
const ts = params.textSize ? this.resolveVarExpr(params.textSize) : undefined;
|
|
3434
|
+
const offset = params.labelOffset ? this.resolvePointFromExpr(params.labelOffset) : undefined;
|
|
3435
|
+
am.makeLabel(params.anchor, lw, lh, ts, params.textColor, offset);
|
|
3436
|
+
// Make failed pairs to warnings
|
|
3437
|
+
const failed = res.failedPairs.map(x => `${x.key}: '${x.value}'`);
|
|
3438
|
+
failed.push(...res.orphans);
|
|
3439
|
+
if (failed.length > 0) {
|
|
3440
|
+
this.warnings.push(`Attributes not valid to make Angle Mark: [${failed.join(", ")}]`);
|
|
3441
|
+
}
|
|
3442
|
+
return am;
|
|
3443
|
+
}
|
|
3444
|
+
parseDimMarkDef(def) {
|
|
3445
|
+
const res = parseStrIntoKeyValuePair(def, HuatuDimMark.optsDimMarkDef);
|
|
3446
|
+
const params = res.body;
|
|
3447
|
+
if (!params.from)
|
|
3448
|
+
return `Start Point of the Dimension Mark not found in '${def}'`;
|
|
3449
|
+
if (!params.to)
|
|
3450
|
+
return `End Point of the Dimension Mark not found in '${def}'`;
|
|
3451
|
+
const pStart = this.resolvePointFromExpr(params.from);
|
|
3452
|
+
if (!pStart)
|
|
3453
|
+
return `Start Point (def: '${params.from}') Cannot resolve to a Point`;
|
|
3454
|
+
const pEnd = this.resolvePointFromExpr(params.to);
|
|
3455
|
+
if (!pEnd)
|
|
3456
|
+
return `End Point (def: '${params.to}') Cannot resolve to a Point`;
|
|
3457
|
+
const dimMark = new HuatuDimMark(pStart, pEnd);
|
|
3458
|
+
if (!dimMark.isValid)
|
|
3459
|
+
return `Dimension Mark '${def}' Cannot resolve to a valid mark, start/end points are possibly identical!`;
|
|
3460
|
+
if (dimMark.isZeroDim)
|
|
3461
|
+
return `Dimension Mark '${def}' has zero Length to mark!`;
|
|
3462
|
+
let type = (params.type || "line").toLowerCase();
|
|
3463
|
+
type = type.startsWith("h") ? "hori"
|
|
3464
|
+
: type.startsWith("v") ? "vert"
|
|
3465
|
+
: "line";
|
|
3466
|
+
const conf = {
|
|
3467
|
+
type: type,
|
|
3468
|
+
dir: params.dir || "right",
|
|
3469
|
+
};
|
|
3470
|
+
// Judge and assign configs
|
|
3471
|
+
if (params.gap) {
|
|
3472
|
+
let gap = this.resolveVarExpr(params.gap);
|
|
3473
|
+
if (gap !== undefined && gap >= 0) {
|
|
3474
|
+
conf.gap = gap;
|
|
3475
|
+
}
|
|
3476
|
+
else {
|
|
3477
|
+
this.warnings.push(`Gap def '${params.gap}' cannot resolve to some number, will be ignored!`);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
if (params.barWidth) {
|
|
3481
|
+
let width = this.resolveVarExpr(params.barWidth);
|
|
3482
|
+
if (width && width > 0) {
|
|
3483
|
+
conf.barWidth = width;
|
|
3484
|
+
}
|
|
3485
|
+
else {
|
|
3486
|
+
this.warnings.push(`Bar Width def '${params.barWidth}' cannot resolve to some number, will be ignored!`);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
if (params.lineWidth) {
|
|
3490
|
+
let width = this.resolveVarExpr(params.lineWidth);
|
|
3491
|
+
if (width && width > 0) {
|
|
3492
|
+
conf.lineWidth = width;
|
|
3493
|
+
}
|
|
3494
|
+
else {
|
|
3495
|
+
this.warnings.push(`Line Width def '${params.lineWidth}' cannot resolve to some number, will be ignored!`);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
if (params.barLen) {
|
|
3499
|
+
let len = this.resolveVarExpr(params.barLen);
|
|
3500
|
+
if (len && len > 0) {
|
|
3501
|
+
conf.barLength = len;
|
|
3502
|
+
}
|
|
3503
|
+
else {
|
|
3504
|
+
this.warnings.push(`Bar Length def '${params.barLen}' cannot resolve to some number, will be ignored!`);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
if (params.linePos !== undefined && WtMath.isIn0and1(params.linePos)) {
|
|
3508
|
+
conf.linePos = params.linePos;
|
|
3509
|
+
}
|
|
3510
|
+
else if (params.linePos) {
|
|
3511
|
+
this.warnings.push(`Dimension Mark Line Position should be in [0,1], your input '${params.linePos}' is invalid`);
|
|
3512
|
+
}
|
|
3513
|
+
if (!!params.noStartBar)
|
|
3514
|
+
conf.noStartBar = true;
|
|
3515
|
+
if (!!params.noEndBar)
|
|
3516
|
+
conf.noEndBar = true;
|
|
3517
|
+
if (!!params.noStartArrow)
|
|
3518
|
+
conf.noStartArrow = true;
|
|
3519
|
+
if (!!params.noEndArrow)
|
|
3520
|
+
conf.noEndArrow = true;
|
|
3521
|
+
if (params.barColor)
|
|
3522
|
+
conf.barColor = params.barColor;
|
|
3523
|
+
if (params.lineColor)
|
|
3524
|
+
conf.lineColor = params.lineColor;
|
|
3525
|
+
if (params.arrow)
|
|
3526
|
+
conf.arrowType = params.arrow;
|
|
3527
|
+
if (params.label)
|
|
3528
|
+
conf.labelText = params.label;
|
|
3529
|
+
if (params.labelWidth) {
|
|
3530
|
+
let width = this.resolveVarExpr(params.labelWidth);
|
|
3531
|
+
if (width && width > 0) {
|
|
3532
|
+
conf.labelWidth = width;
|
|
3533
|
+
}
|
|
3534
|
+
else {
|
|
3535
|
+
this.warnings.push(`Label Width ('${params.labelWidth}') of DimMark cannot resolve to some number, will be ignored!`);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
if (params.labelHeight) {
|
|
3539
|
+
let height = this.resolveVarExpr(params.labelHeight);
|
|
3540
|
+
if (height && height > 0) {
|
|
3541
|
+
conf.labelHeight = height;
|
|
3542
|
+
}
|
|
3543
|
+
else {
|
|
3544
|
+
this.warnings.push(`Label Height ('${params.labelHeight}') of DimMark cannot resolve to some number, will be ignored!`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
if (params.textSize) {
|
|
3548
|
+
let size = this.resolveVarExpr(params.textSize);
|
|
3549
|
+
if (size && size > 0) {
|
|
3550
|
+
conf.labelFontSize = size;
|
|
3551
|
+
}
|
|
3552
|
+
else {
|
|
3553
|
+
this.warnings.push(`Label Text Size ('${params.textSize}') of DimMark cannot resolve to some number, will be ignored!`);
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
if (params.textColor)
|
|
3557
|
+
conf.labelColor = params.textColor;
|
|
3558
|
+
if (!!params.labelOnLine)
|
|
3559
|
+
conf.labelOnLine = true;
|
|
3560
|
+
if (params.labelFill)
|
|
3561
|
+
conf.labelBackground = params.labelFill;
|
|
3562
|
+
if (params.labelBorder)
|
|
3563
|
+
conf.labelBorderColor = params.labelBorder;
|
|
3564
|
+
if (!!params.rotateLabel)
|
|
3565
|
+
conf.rotateLabel = true;
|
|
3566
|
+
if (params.remotePos) {
|
|
3567
|
+
let pos = this.resolvePointFromExpr(params.remotePos);
|
|
3568
|
+
if (pos) {
|
|
3569
|
+
conf.remotePos = pos;
|
|
3570
|
+
}
|
|
3571
|
+
else {
|
|
3572
|
+
this.warnings.push(`Remote Position ('${params.remotePos}') of DimMark cannot resolve to some Point, will be ignored!`);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (!!params.showLength)
|
|
3576
|
+
conf.showLength = true;
|
|
3577
|
+
if (params.lenDigits !== undefined)
|
|
3578
|
+
conf.lenDigits = params.lenDigits;
|
|
3579
|
+
// Make mark
|
|
3580
|
+
dimMark.makeMark(conf);
|
|
3581
|
+
if (!dimMark.label) {
|
|
3582
|
+
this.warnings.push(`Label or showLength should be specified for a DimMark to make it display something, but the def '${def}' didn't`);
|
|
3583
|
+
// Make it invalid
|
|
3584
|
+
dimMark.isValid = false;
|
|
3585
|
+
}
|
|
3586
|
+
// Make failed pairs to warnings
|
|
3587
|
+
const failed = res.failedPairs.map(x => `${x.key}: '${x.value}'`);
|
|
3588
|
+
failed.push(...res.orphans);
|
|
3589
|
+
if (failed.length > 0) {
|
|
3590
|
+
this.warnings.push(`Attributes not valid to make Dimension Mark: [${failed.join(", ")}]`);
|
|
3591
|
+
}
|
|
3592
|
+
return dimMark;
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Unified function to resolve drawing definition
|
|
3596
|
+
* @param def The unified definition of a "drawing", including a label, or named modules
|
|
3597
|
+
* @param allowedTypes See the explanation in function `resolveDrawingOfShapes`
|
|
3598
|
+
* @returns The final draw or error message
|
|
3599
|
+
*/
|
|
3600
|
+
resolveAsDrawingItem(def, allowedTypes) {
|
|
3601
|
+
if (typeof def !== "string")
|
|
3602
|
+
return `The Drawing define might be incorrect, it is [${JSON.stringify(def)}], please note the colon(:) sign which might be semi-colon(;)!`;
|
|
3603
|
+
// To see if it is a label presented
|
|
3604
|
+
const tryLabel = WtHuatu.REGEX_LABEL_DEF.exec(def);
|
|
3605
|
+
if (tryLabel) {
|
|
3606
|
+
const lblFormat = tryLabel[1];
|
|
3607
|
+
if (!lblFormat)
|
|
3608
|
+
return `Label Location/Format should be present!`;
|
|
3609
|
+
// Replace the ever escaped colon (:) in the form of "\:" if the user want to input multiple line text
|
|
3610
|
+
// with some colon contained
|
|
3611
|
+
const lblText = tryLabel[2].trim().replace(/\\:/g, ":");
|
|
3612
|
+
// For empty labels, will not return error, just leave it as empty
|
|
3613
|
+
// if (!lblText) return `Label Text could not be empty!`;
|
|
3614
|
+
return this.parseLabelDef(lblFormat, lblText);
|
|
3615
|
+
}
|
|
3616
|
+
// To see if it is an angle mark
|
|
3617
|
+
const tryAngleMark = WtHuatu.REGEX_ANGLE_MARK_DEF.exec(def);
|
|
3618
|
+
if (tryAngleMark) {
|
|
3619
|
+
return this.parseAngleMarkDef(tryAngleMark[1].trim());
|
|
3620
|
+
}
|
|
3621
|
+
// To see if it is an Dimension Mark
|
|
3622
|
+
const tryDimMark = WtHuatu.REGEX_DIM_MARK_DEF.exec(def);
|
|
3623
|
+
if (tryDimMark) {
|
|
3624
|
+
return this.parseDimMarkDef(tryDimMark[1].trim());
|
|
3625
|
+
}
|
|
3626
|
+
// To See if it is a WIRE
|
|
3627
|
+
const tryWire = WtHuatu.REGEX_WIRE_DEF.exec(def);
|
|
3628
|
+
if (tryWire) {
|
|
3629
|
+
if (!allowedTypes.includes("wire")) {
|
|
3630
|
+
return `Wire definition only allowed under Block/Layer/Job, others not!`;
|
|
3631
|
+
}
|
|
3632
|
+
return this.parseWireDef(tryWire[1].trim());
|
|
3633
|
+
}
|
|
3634
|
+
// If not label, then treat it as some shape drawing
|
|
3635
|
+
return this.resolveDrawingOfShapes(def, allowedTypes);
|
|
3636
|
+
}
|
|
3637
|
+
isDrawingNameValid(name, level) {
|
|
3638
|
+
if (this.paths.find(s => s.name === name))
|
|
3639
|
+
return `There has been a Path named as ${name}!`;
|
|
3640
|
+
if (this.pathSeries.find(s => s.name === name))
|
|
3641
|
+
return `There is some Path Series named as ${name}!`;
|
|
3642
|
+
if (this.symbols.find(s => s.name === name))
|
|
3643
|
+
return `There is some Symbol named as ${name}!`;
|
|
3644
|
+
if (level === "Block" || level === "Layer") {
|
|
3645
|
+
if (this.arrays.find(s => s.name === name))
|
|
3646
|
+
return `There is some Symbol Array named as ${name}!`;
|
|
3647
|
+
if (this.blocks.find(s => s.name === name))
|
|
3648
|
+
return `There is Block named as ${name}!`;
|
|
3649
|
+
if (level === "Layer") {
|
|
3650
|
+
if (this.layers.find(s => s.name === name))
|
|
3651
|
+
return `There is some Layer named as ${name}!`;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
/**
|
|
3656
|
+
* Resolve Block definition, the input might be a string, or a string list
|
|
3657
|
+
* @param name Name of the Block
|
|
3658
|
+
* @param value Presentation to resolve the Block
|
|
3659
|
+
* @returns Error message
|
|
3660
|
+
*/
|
|
3661
|
+
resolveBlock(name, value) {
|
|
3662
|
+
// Check Name
|
|
3663
|
+
const nameError = this.isDrawingNameValid(name, "Block");
|
|
3664
|
+
if (nameError)
|
|
3665
|
+
return [nameError];
|
|
3666
|
+
const allowedTypes = [
|
|
3667
|
+
"shape", "shapeSeries",
|
|
3668
|
+
"path", "pathSeries",
|
|
3669
|
+
"symbol", "array", "block",
|
|
3670
|
+
"component", "wire"
|
|
3671
|
+
];
|
|
3672
|
+
// If it is only one line, i.d. the block has only one definition, We assume that it is the shape it contains
|
|
3673
|
+
if (typeof value === "string") {
|
|
3674
|
+
const item = this.resolveAsDrawingItem(value, allowedTypes);
|
|
3675
|
+
if (typeof item === "string")
|
|
3676
|
+
return [item]; // Return the error message
|
|
3677
|
+
const block = new HuatuBlock(name);
|
|
3678
|
+
block.addElement(item);
|
|
3679
|
+
this.defines.push(block); // Add to Definable
|
|
3680
|
+
this.blocks.push(block);
|
|
3681
|
+
return [];
|
|
3682
|
+
}
|
|
3683
|
+
// If the definition is a list, we assume that it contains multiple drawing definitions for the Block
|
|
3684
|
+
const errs = [];
|
|
3685
|
+
const block = new HuatuBlock(name);
|
|
3686
|
+
for (let i = 0; i < value.length; i++) {
|
|
3687
|
+
const def = value[i];
|
|
3688
|
+
const tryDesc = WtHuatu.tryDesc(def);
|
|
3689
|
+
if (tryDesc) {
|
|
3690
|
+
block.desc = tryDesc;
|
|
3691
|
+
}
|
|
3692
|
+
else {
|
|
3693
|
+
const item = this.resolveAsDrawingItem(def, allowedTypes);
|
|
3694
|
+
if (typeof item === "string")
|
|
3695
|
+
errs.push(item);
|
|
3696
|
+
else
|
|
3697
|
+
block.addElement(item);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
this.defines.push(block); // Add to Definable
|
|
3701
|
+
this.blocks.push(block);
|
|
3702
|
+
// Return the errors when resolving
|
|
3703
|
+
return errs;
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Resolve Layer definition, the input might be a string, or a string list
|
|
3707
|
+
* @param name Name of the Layer
|
|
3708
|
+
* @param value Presentation to resolve the Layer
|
|
3709
|
+
* @returns Error message
|
|
3710
|
+
*/
|
|
3711
|
+
resolveLayer(name, value) {
|
|
3712
|
+
// Check Name
|
|
3713
|
+
const nameError = this.isDrawingNameValid(name, "Layer");
|
|
3714
|
+
if (nameError)
|
|
3715
|
+
return [nameError];
|
|
3716
|
+
const allowedTypes = [
|
|
3717
|
+
"shape", "shapeSeries",
|
|
3718
|
+
"path", "pathSeries",
|
|
3719
|
+
"symbol", "array", "block",
|
|
3720
|
+
"component", "wire"
|
|
3721
|
+
];
|
|
3722
|
+
// If it is only one line, i.d. the layer has only one definition, We assume that it is the shape it contains
|
|
3723
|
+
if (typeof value === "string") {
|
|
3724
|
+
const item = this.resolveAsDrawingItem(value, allowedTypes);
|
|
3725
|
+
if (typeof item === "string")
|
|
3726
|
+
return [item]; // Return the error message
|
|
3727
|
+
const layer = new HuatuLayer(name);
|
|
3728
|
+
layer.addElement(item);
|
|
3729
|
+
this.layers.push(layer);
|
|
3730
|
+
return [];
|
|
3731
|
+
}
|
|
3732
|
+
// If the definition is a list, we assume that it contains multiple drawing definitions for the Layer
|
|
3733
|
+
const errs = [];
|
|
3734
|
+
const layer = new HuatuLayer(name);
|
|
3735
|
+
for (let i = 0; i < value.length; i++) {
|
|
3736
|
+
const def = value[i];
|
|
3737
|
+
const tryDesc = WtHuatu.tryDesc(def);
|
|
3738
|
+
if (tryDesc) {
|
|
3739
|
+
layer.desc = tryDesc;
|
|
3740
|
+
}
|
|
3741
|
+
else {
|
|
3742
|
+
const item = this.resolveAsDrawingItem(def, allowedTypes);
|
|
3743
|
+
if (typeof item === "string")
|
|
3744
|
+
errs.push(item);
|
|
3745
|
+
else
|
|
3746
|
+
layer.addElement(item);
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
this.layers.push(layer);
|
|
3750
|
+
// Return the errors when resolving
|
|
3751
|
+
return errs;
|
|
3752
|
+
}
|
|
3753
|
+
resolveViewBoxFromDef(def) {
|
|
3754
|
+
const vbDefs = def.trim().split(";").map(x => x.trim()).filter(x => !!x);
|
|
3755
|
+
if (vbDefs.length < 2) {
|
|
3756
|
+
// Try to see if the def is organized in x,y,w,h form
|
|
3757
|
+
const numStrs = def.split(",").map(x => x.trim()).filter(x => !!x);
|
|
3758
|
+
const nums = numStrs
|
|
3759
|
+
.map(x => this.resolveVarExpr(x))
|
|
3760
|
+
.filter(x => x !== undefined);
|
|
3761
|
+
if (nums.length < 4)
|
|
3762
|
+
return `Parameters '${def}' insufficient/invalid to build a ViewBox!`;
|
|
3763
|
+
if (nums[2] > 0 && nums[3] > 0)
|
|
3764
|
+
return new SvgBBox(nums[0], nums[1], nums[2], nums[3]);
|
|
3765
|
+
else
|
|
3766
|
+
return `'${numStrs[2]}' or '${numStrs[3]}' resolves to non-positive value!`;
|
|
3767
|
+
}
|
|
3768
|
+
const p1 = this.resolvePointFromExpr(vbDefs[0]);
|
|
3769
|
+
if (!p1)
|
|
3770
|
+
return `Parameter '${vbDefs[0]}' is not a point!`;
|
|
3771
|
+
const p2 = this.resolvePointFromExpr(vbDefs[1]);
|
|
3772
|
+
if (p2)
|
|
3773
|
+
return SvgBBox.parseFromTwoPoints(p1, p2);
|
|
3774
|
+
if (vbDefs.length >= 3) {
|
|
3775
|
+
let w = this.resolveVarExpr(vbDefs[1]);
|
|
3776
|
+
let h = this.resolveVarExpr(vbDefs[2]);
|
|
3777
|
+
if (w && w > 0 && h && h > 0) {
|
|
3778
|
+
return new SvgBBox(p1.x, p1.y, w, h);
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
return `Width '${vbDefs[1]}' or Height '${vbDefs[2]}' resolved to no valid length!`;
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
return `Parameters insufficient to build a ViewBox!`;
|
|
3785
|
+
}
|
|
3786
|
+
tryViewBox(def) {
|
|
3787
|
+
const match = /^(viewbox|viewarea|viewbetween)\s*;(.*)$/i.exec(def);
|
|
3788
|
+
if (!match)
|
|
3789
|
+
return undefined;
|
|
3790
|
+
return this.resolveViewBoxFromDef(match[2])
|
|
3791
|
+
|| `'${def}' failed resolving to a valid view box!`;
|
|
3792
|
+
}
|
|
3793
|
+
trySize(def) {
|
|
3794
|
+
const match = /^(size)\s*;(.*)$/i.exec(def);
|
|
3795
|
+
if (!match)
|
|
3796
|
+
return undefined;
|
|
3797
|
+
const size = match[2].trim().split(";").map(x => x.trim()).filter(x => !!x);
|
|
3798
|
+
if (size.length < 1)
|
|
3799
|
+
return `'${match[2]}' has no size data presented!`;
|
|
3800
|
+
if (size.length >= 1) {
|
|
3801
|
+
const p = this.resolvePointFromExpr(size[0]);
|
|
3802
|
+
if (p) {
|
|
3803
|
+
if (p.x > 0 && p.y > 0)
|
|
3804
|
+
return p;
|
|
3805
|
+
else
|
|
3806
|
+
return `'${size[0]}' resolves to no valid(positive) Width/Height combination!`;
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
const w = this.resolveVarExpr(size[0]);
|
|
3810
|
+
if (!w || w <= 0)
|
|
3811
|
+
return `'${size[0]}' resolves to no valid positive length for width!`;
|
|
3812
|
+
if (size.length >= 2) {
|
|
3813
|
+
const h = this.resolveVarExpr(size[1]);
|
|
3814
|
+
if (h && h > 0)
|
|
3815
|
+
return new SvgPoint(w, h);
|
|
3816
|
+
else
|
|
3817
|
+
return `'${size[1]}' resolves to no valid positive length for Height!`;
|
|
3818
|
+
}
|
|
3819
|
+
return new SvgPoint(w, 0);
|
|
3820
|
+
}
|
|
3821
|
+
resolveJobDrawingItem(draw, allowedTypes) {
|
|
3822
|
+
// Try Description
|
|
3823
|
+
const tryDesc = WtHuatu.tryDesc(draw);
|
|
3824
|
+
if (tryDesc)
|
|
3825
|
+
return `desc: ${tryDesc}`;
|
|
3826
|
+
// TO see if it is ViewBox define
|
|
3827
|
+
const tryViewBox = this.tryViewBox(draw);
|
|
3828
|
+
if (tryViewBox !== undefined)
|
|
3829
|
+
return tryViewBox;
|
|
3830
|
+
// Try Width or Height
|
|
3831
|
+
const trySize = this.trySize(draw);
|
|
3832
|
+
if (trySize !== undefined)
|
|
3833
|
+
return trySize;
|
|
3834
|
+
// If not special meaning, then try drawing
|
|
3835
|
+
return this.resolveAsDrawingItem(draw, allowedTypes);
|
|
3836
|
+
}
|
|
3837
|
+
/**
|
|
3838
|
+
* Resolve the defined job(s) from YML design file, 'draw' is one of the jobs
|
|
3839
|
+
* @param jobName Name of the job, if it is 'draw', then the default global draw
|
|
3840
|
+
* @param def The drawing design string to compose the final draw/job
|
|
3841
|
+
* @returns Errors if any
|
|
3842
|
+
*/
|
|
3843
|
+
resolveJob(jobName, def) {
|
|
3844
|
+
if (!def)
|
|
3845
|
+
return [`Job '${jobName}' has no Definition!`];
|
|
3846
|
+
if (this.jobs.find(j => j.name === jobName))
|
|
3847
|
+
return [`Job '${jobName}' has already been defined!`];
|
|
3848
|
+
const errs = [];
|
|
3849
|
+
const job = new HuatuJob(this, jobName);
|
|
3850
|
+
const drawItems = [];
|
|
3851
|
+
if (typeof def === "object" && !Array.isArray(def)) { // Assume it is the IHuatuYmlJob data
|
|
3852
|
+
if (def.desc)
|
|
3853
|
+
job.desc = def.desc;
|
|
3854
|
+
if (def.config)
|
|
3855
|
+
job.config = WtHuatu.resolveConfig(def.config);
|
|
3856
|
+
if (def.size) {
|
|
3857
|
+
const size = this.resolvePointFromExpr(def.size);
|
|
3858
|
+
if (size) {
|
|
3859
|
+
if (size.x > 0)
|
|
3860
|
+
job.width = size.x;
|
|
3861
|
+
if (size.y > 0)
|
|
3862
|
+
job.height = size.y;
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
if (def.height) {
|
|
3866
|
+
const height = this.resolveVarExpr(def.height);
|
|
3867
|
+
if (height && height > 0)
|
|
3868
|
+
job.height = height;
|
|
3869
|
+
}
|
|
3870
|
+
if (def.width) {
|
|
3871
|
+
const width = this.resolveVarExpr(def.width);
|
|
3872
|
+
if (width && width > 0)
|
|
3873
|
+
job.width = width;
|
|
3874
|
+
}
|
|
3875
|
+
let vbDef = def.viewBox || def.viewArea || def.viewBetween;
|
|
3876
|
+
if (vbDef) {
|
|
3877
|
+
const vb = this.resolveViewBoxFromDef(vbDef);
|
|
3878
|
+
if (typeof vb === "string")
|
|
3879
|
+
errs.push(vb);
|
|
3880
|
+
else if (vb)
|
|
3881
|
+
job.viewBox = vb;
|
|
3882
|
+
else {
|
|
3883
|
+
this.warnings.push(`'${vbDef}' cannot resolve to a valid View Box!`);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
if (def.drawings) {
|
|
3887
|
+
if (Array.isArray(def.drawings))
|
|
3888
|
+
drawItems.push(...def.drawings.filter(x => !!x));
|
|
3889
|
+
else if (typeof def.drawings === "string")
|
|
3890
|
+
drawItems.push(def.drawings);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
else if (typeof def === "string") {
|
|
3894
|
+
drawItems.push(def);
|
|
3895
|
+
}
|
|
3896
|
+
else if (Array.isArray(def)) {
|
|
3897
|
+
drawItems.push(...def);
|
|
3898
|
+
}
|
|
3899
|
+
if (drawItems.length < 1)
|
|
3900
|
+
return [`Job '${jobName}' has no valid items presented!`];
|
|
3901
|
+
const allowedTypes = [
|
|
3902
|
+
"shape", "shapeSeries",
|
|
3903
|
+
"path", "pathSeries",
|
|
3904
|
+
"symbol", "array",
|
|
3905
|
+
"block", "layer",
|
|
3906
|
+
"component", "wire",
|
|
3907
|
+
];
|
|
3908
|
+
drawItems.forEach(d => {
|
|
3909
|
+
const draw = this.resolveJobDrawingItem(d, allowedTypes);
|
|
3910
|
+
if (typeof draw === "string") {
|
|
3911
|
+
if (draw.startsWith("desc"))
|
|
3912
|
+
job.desc = draw.substring(6);
|
|
3913
|
+
else
|
|
3914
|
+
errs.push(draw);
|
|
3915
|
+
}
|
|
3916
|
+
else if (draw instanceof SvgPoint) {
|
|
3917
|
+
job.width = draw.x;
|
|
3918
|
+
if (draw.y > 0)
|
|
3919
|
+
job.height = draw.y;
|
|
3920
|
+
}
|
|
3921
|
+
else if (draw instanceof SvgBBox) {
|
|
3922
|
+
job.viewBox = draw;
|
|
3923
|
+
job.vbExplicit = true;
|
|
3924
|
+
}
|
|
3925
|
+
else if (draw) {
|
|
3926
|
+
job.drawings.push(draw);
|
|
3927
|
+
}
|
|
3928
|
+
});
|
|
3929
|
+
// Re-Check ViewBox
|
|
3930
|
+
if (!job.viewBox) {
|
|
3931
|
+
if (job.huatu.viewBox) {
|
|
3932
|
+
job.viewBox = job.huatu.viewBox;
|
|
3933
|
+
job.vbExplicit = true;
|
|
3934
|
+
}
|
|
3935
|
+
else {
|
|
3936
|
+
job.viewBox = job.calcViewBox();
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
if (!job.desc)
|
|
3940
|
+
job.desc = job.huatu.desc;
|
|
3941
|
+
this.jobs.push(job);
|
|
3942
|
+
return errs;
|
|
3943
|
+
}
|
|
3944
|
+
buildSvg(buildType, jobName, withSize) {
|
|
3945
|
+
const job = this.jobs.find(j => j.name === (jobName ?? "draw"));
|
|
3946
|
+
if (job)
|
|
3947
|
+
return job.buildSvg(buildType, withSize);
|
|
3948
|
+
else
|
|
3949
|
+
return "";
|
|
3950
|
+
}
|
|
3951
|
+
buildSvgAsHtml(buildType, jobName, indent, withSize) {
|
|
3952
|
+
const job = this.jobs.find(j => j.name === (jobName ?? "draw"));
|
|
3953
|
+
if (job)
|
|
3954
|
+
return job.buildHtml(buildType, indent, withSize);
|
|
3955
|
+
else
|
|
3956
|
+
return "";
|
|
3957
|
+
}
|
|
3958
|
+
buildSvgStandalone(buildType, jobName, indent, withSize) {
|
|
3959
|
+
const job = this.jobs.find(j => j.name === (jobName ?? "draw"));
|
|
3960
|
+
if (job)
|
|
3961
|
+
return job.buildSvgStandalone(buildType, indent, withSize);
|
|
3962
|
+
else
|
|
3963
|
+
return "";
|
|
3964
|
+
}
|
|
3965
|
+
/** --------------- Component part ------------------------------------------------------------------------------ */
|
|
3966
|
+
resolveComponentDef(name, def) {
|
|
3967
|
+
const compDef = new HuatuComponentDef(name, def);
|
|
3968
|
+
if (!compDef.isValid) {
|
|
3969
|
+
return compDef.errors
|
|
3970
|
+
.map(x => x.msgs ? x.msgs.map(msg => `${x.title}: ${msg}`) : [x.title])
|
|
3971
|
+
.flat();
|
|
3972
|
+
}
|
|
3973
|
+
else {
|
|
3974
|
+
// Convert Errors to warnings if any
|
|
3975
|
+
if (compDef.errors.length > 0) {
|
|
3976
|
+
compDef.errors.forEach(e => {
|
|
3977
|
+
this.warnings.push(`Errors Found When resolving component "${name}" => ${e.title}: `
|
|
3978
|
+
+ (e.msgs && e.msgs.length > 0 ? e.msgs.map((m, idx) => `${idx + 1})${m}`).join("; ") : ""));
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
// Add the definition
|
|
3982
|
+
this.compDefs.push(compDef);
|
|
3983
|
+
return [];
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
resolveComponentPlacement(name, def) {
|
|
3987
|
+
const errors = [];
|
|
3988
|
+
let ivObj = {};
|
|
3989
|
+
let defToParse;
|
|
3990
|
+
const isIvKey = (key) => !!/\$[a-zA-Z][a-zA-Z0-9_]*$/i.exec(key);
|
|
3991
|
+
if (typeof def === "string") {
|
|
3992
|
+
// Filter out the segments of input vars
|
|
3993
|
+
const src = def.split(";")
|
|
3994
|
+
.map(x => x.trim())
|
|
3995
|
+
.filter(x => !!x);
|
|
3996
|
+
const filtered = [];
|
|
3997
|
+
let i = 0;
|
|
3998
|
+
while (i < src.length) {
|
|
3999
|
+
if (isIvKey(src[i])) {
|
|
4000
|
+
if ((i + 1) < src.length) {
|
|
4001
|
+
if (!isIvKey(src[i + 1])) {
|
|
4002
|
+
ivObj[src[i]] = src[i + 1];
|
|
4003
|
+
i += 2;
|
|
4004
|
+
}
|
|
4005
|
+
else {
|
|
4006
|
+
i += 1;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
else {
|
|
4011
|
+
filtered.push(src[i]);
|
|
4012
|
+
i++;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
defToParse = filtered.join(";");
|
|
4016
|
+
}
|
|
4017
|
+
else {
|
|
4018
|
+
defToParse = {};
|
|
4019
|
+
Object.keys(def).forEach(x => {
|
|
4020
|
+
if (x.startsWith("$")) {
|
|
4021
|
+
ivObj[x] = def[x];
|
|
4022
|
+
}
|
|
4023
|
+
else {
|
|
4024
|
+
defToParse[x] = def[x];
|
|
4025
|
+
}
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
// Parse it
|
|
4029
|
+
const parseRes = parseStrIntoKeyValuePair(defToParse, HuatuComponentPlacement.optsComponentPlacement);
|
|
4030
|
+
const params = parseRes.body;
|
|
4031
|
+
if (!params.component) {
|
|
4032
|
+
return [`Placement "${name}" Failed, no component definition specified!`];
|
|
4033
|
+
}
|
|
4034
|
+
const compDef = this.compDefs.find(x => x.name === params.component);
|
|
4035
|
+
if (!compDef) {
|
|
4036
|
+
return [`Component Definition "${params.component}" not found!`];
|
|
4037
|
+
}
|
|
4038
|
+
if (!params.location) {
|
|
4039
|
+
return [`Location is not provided for Placement "${name}"`];
|
|
4040
|
+
}
|
|
4041
|
+
const loc = this.resolvePointFromExpr(params.location);
|
|
4042
|
+
if (!loc) {
|
|
4043
|
+
return [`Component Placement location "${params.location}" cannot be resolved`];
|
|
4044
|
+
}
|
|
4045
|
+
// Report unhandled params to warnings
|
|
4046
|
+
if (parseRes.failedPairs.length > 0) {
|
|
4047
|
+
const pairs = parseRes.failedPairs.map((x, i) => `${i + 1}) "${x.key}": "${x.value}"`).join(", ");
|
|
4048
|
+
this.warnings.push(`Parameters unhandled when making component placement "${name}": ${pairs}`);
|
|
4049
|
+
}
|
|
4050
|
+
// Handle Input Vars
|
|
4051
|
+
const givenIvs = {};
|
|
4052
|
+
const ivNames = Object.keys(ivObj)
|
|
4053
|
+
.filter(x => x.startsWith("$"))
|
|
4054
|
+
.map(x => x.substring(1))
|
|
4055
|
+
.filter(x => !!x);
|
|
4056
|
+
ivNames.forEach(ivName => {
|
|
4057
|
+
const iv = compDef.inputVars.find(x => x.name === ivName);
|
|
4058
|
+
if (iv) {
|
|
4059
|
+
let ivVal = ivObj[`$${ivName}`];
|
|
4060
|
+
if (!ivVal || typeof ivVal !== "string" || !ivVal.trim()) {
|
|
4061
|
+
errors.push(`Input Var [$${ivName}] takes some empty/invalid value!`);
|
|
4062
|
+
}
|
|
4063
|
+
else {
|
|
4064
|
+
ivVal = ivVal.trim();
|
|
4065
|
+
let ivValAsColor = isValidColor(ivVal);
|
|
4066
|
+
if (iv.type === "color") {
|
|
4067
|
+
if (ivValAsColor) {
|
|
4068
|
+
givenIvs[ivName] = ivValAsColor;
|
|
4069
|
+
}
|
|
4070
|
+
else {
|
|
4071
|
+
errors.push(`"${ivVal}" is not some valid color for Input Var "${ivName}" which is defined as some color`);
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
else if (iv.type === "number") {
|
|
4075
|
+
const ivNumber = this.resolveVarExpr(ivVal);
|
|
4076
|
+
if (ivNumber !== undefined) {
|
|
4077
|
+
givenIvs[ivName] = ivNumber;
|
|
4078
|
+
}
|
|
4079
|
+
else {
|
|
4080
|
+
errors.push(`"${ivVal}" cannot resolve to be a number for Input Var "${ivName}"`);
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
else {
|
|
4084
|
+
// For Text vars, just copy it, no conversion needed
|
|
4085
|
+
givenIvs[ivName] = ivVal;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
else {
|
|
4090
|
+
errors.push(`Input Var [$${ivName}] doesn't exist in Component Definition!`);
|
|
4091
|
+
}
|
|
4092
|
+
});
|
|
4093
|
+
let rotDeg;
|
|
4094
|
+
if (params.rotDeg) {
|
|
4095
|
+
rotDeg = this.resolveVarExpr(params.rotDeg);
|
|
4096
|
+
if (rotDeg === undefined) {
|
|
4097
|
+
errors.push(`Component Placement Rotate Deg "${params.rotDeg}" cannot be resolved!`);
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
// Create the placement
|
|
4101
|
+
const placement = new HuatuComponentPlacement(name, loc, compDef, givenIvs, params.mirror, rotDeg, params.refPoint, params.rotateLabel);
|
|
4102
|
+
// Adjust mirroring and rotation
|
|
4103
|
+
if (!placement.isValid || !placement.huatu.draw) {
|
|
4104
|
+
errors.push(`Placement [${name}] cannot be resolved, there is no drawing body!`);
|
|
4105
|
+
}
|
|
4106
|
+
else {
|
|
4107
|
+
// Add the placement
|
|
4108
|
+
this.compPlacements.push(placement);
|
|
4109
|
+
}
|
|
4110
|
+
return errors;
|
|
4111
|
+
}
|
|
4112
|
+
// This function is to resolve the Component Port definitions after the component is defined/placed
|
|
4113
|
+
parseComponentPorts(obj) {
|
|
4114
|
+
if (!obj.ports)
|
|
4115
|
+
return [];
|
|
4116
|
+
if (!Array.isArray(obj.ports) || obj.ports.length < 1)
|
|
4117
|
+
return ["The Ports definition must be presented as a list!"];
|
|
4118
|
+
const errors = [];
|
|
4119
|
+
const portDefs = obj.ports;
|
|
4120
|
+
portDefs.forEach(p => {
|
|
4121
|
+
const parseRes = parseStrIntoKeyValuePair(p, HuatuComponentPort.optsComponentPort);
|
|
4122
|
+
if (!parseRes.body.name) {
|
|
4123
|
+
errors.push(`No Name resolved in port definition: "${p}"`);
|
|
4124
|
+
}
|
|
4125
|
+
else if (!/^[0-9a-zA-Z]+$/.test(parseRes.body.name)) {
|
|
4126
|
+
errors.push(`Port Name can only be combination of a to z, A to Z, and 0 -9, [${parseRes.body.name}] is not qualified, please pick another one!`);
|
|
4127
|
+
}
|
|
4128
|
+
else if (!parseRes.body.anchor) {
|
|
4129
|
+
errors.push(`No Anchor Point resolved in Port Definition "${p}"`);
|
|
4130
|
+
}
|
|
4131
|
+
else {
|
|
4132
|
+
const anchor = this.resolvePointFromExpr(parseRes.body.anchor);
|
|
4133
|
+
if (!anchor) {
|
|
4134
|
+
errors.push(`Anchor Point Definition "${parseRes.body.anchor}" cannot be resolved!`);
|
|
4135
|
+
}
|
|
4136
|
+
else if (this.compPorts.find(x => x.name === parseRes.body.name)) {
|
|
4137
|
+
errors.push(`Port "${parseRes.body.name}" has already been defined!`);
|
|
4138
|
+
}
|
|
4139
|
+
else {
|
|
4140
|
+
const newPort = new HuatuComponentPort(parseRes.body.name, anchor);
|
|
4141
|
+
if (parseRes.body.attr)
|
|
4142
|
+
newPort.attr = parseRes.body.attr;
|
|
4143
|
+
if (parseRes.body.rayDeg) {
|
|
4144
|
+
let deg = this.resolveVarExpr(parseRes.body.rayDeg);
|
|
4145
|
+
if (deg === undefined) {
|
|
4146
|
+
errors.push(`[comp port] Ray Deg expression '${parseRes.body.rayDeg}' cannot be resolved!`);
|
|
4147
|
+
}
|
|
4148
|
+
else {
|
|
4149
|
+
newPort.rayDeg = deg;
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
if (parseRes.body.minHoldLen) {
|
|
4153
|
+
let len = this.resolveVarExpr(parseRes.body.minHoldLen);
|
|
4154
|
+
if (len === undefined) {
|
|
4155
|
+
errors.push(`[comp port] Hold length expression '${parseRes.body.minHoldLen}' cannot be resolved!`);
|
|
4156
|
+
}
|
|
4157
|
+
else {
|
|
4158
|
+
newPort.minHoldLen = len;
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
this.compPorts.push(newPort);
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
});
|
|
4165
|
+
return errors;
|
|
4166
|
+
}
|
|
4167
|
+
/** --------------- Static Methods ------------------------------------------------------------------------------ */
|
|
4168
|
+
static tryDesc(def) {
|
|
4169
|
+
const match = /^desc\s*;(.*)$/i.exec(def);
|
|
4170
|
+
if (match)
|
|
4171
|
+
return match[1].trim();
|
|
4172
|
+
else
|
|
4173
|
+
return undefined;
|
|
4174
|
+
}
|
|
4175
|
+
static resolveConfig(cfg) {
|
|
4176
|
+
const config = {};
|
|
4177
|
+
config.labelBackground = isValidColor(cfg.labelBackground
|
|
4178
|
+
|| cfg.labelBackgroundColor
|
|
4179
|
+
|| cfg.labelBg
|
|
4180
|
+
|| cfg.labelBgColor);
|
|
4181
|
+
config.labelBorder = isValidColor(cfg.labelBorder || cfg.labelBorderColor);
|
|
4182
|
+
config.markCenters = booleanCheck(cfg.markCenters);
|
|
4183
|
+
config.markBezierAuxLines = booleanCheck(cfg.markBezierAuxLines);
|
|
4184
|
+
config.markNamedPoints = isValidColor(cfg.markNamedPoints);
|
|
4185
|
+
config.markPathPoints = isValidColor(cfg.markPathPoints);
|
|
4186
|
+
if (cfg.markSize)
|
|
4187
|
+
config.markSize = parseFloat(cfg.markSize) ?? 2; // Default Size of Marking Point
|
|
4188
|
+
config.markPositions = isValidColor(cfg.markPositions || cfg.markAnchors);
|
|
4189
|
+
config.printUnusedModule = booleanCheck(cfg.printUnusedModule);
|
|
4190
|
+
config.showBBox = isValidColor(cfg.showBBox);
|
|
4191
|
+
// TODO: For more configurations in the future, please append here
|
|
4192
|
+
return config;
|
|
4193
|
+
}
|
|
4194
|
+
static buildEmbeddedStyles() {
|
|
4195
|
+
if (!globalThis.wthtJobBuildId) {
|
|
4196
|
+
globalThis.wthtJobBuildId = genHex(8);
|
|
4197
|
+
}
|
|
4198
|
+
const styles = [];
|
|
4199
|
+
// Class label-border/box
|
|
4200
|
+
let attrs = [];
|
|
4201
|
+
const htConfig = globalThis.wtHuatuConfig;
|
|
4202
|
+
if (htConfig) {
|
|
4203
|
+
if (htConfig.labelBackground)
|
|
4204
|
+
attrs.push(["fill", htConfig.labelBackground]);
|
|
4205
|
+
else
|
|
4206
|
+
attrs.push(["fill", "none"]);
|
|
4207
|
+
if (htConfig.labelBorder)
|
|
4208
|
+
attrs.push(["stroke", htConfig.labelBorder], ["stroke-width", "0.2"] // Here, the label box border could only be 0.2
|
|
4209
|
+
);
|
|
4210
|
+
}
|
|
4211
|
+
else {
|
|
4212
|
+
attrs.push(["fill", "none"]);
|
|
4213
|
+
}
|
|
4214
|
+
const buildStyle = (clsName) => `.${clsName}-${globalThis.wthtJobBuildId} { ${attrs.map(a => `${a[0]}: ${a[1]};`).join(" ")} }`;
|
|
4215
|
+
styles.push(buildStyle("wthuatu-label-box"));
|
|
4216
|
+
// Label Container
|
|
4217
|
+
attrs = [
|
|
4218
|
+
["width", "100%"],
|
|
4219
|
+
["height", "100%"],
|
|
4220
|
+
["padding", "0"],
|
|
4221
|
+
["display", "flex"],
|
|
4222
|
+
];
|
|
4223
|
+
styles.push(buildStyle("huatu-label-container"));
|
|
4224
|
+
// Label Text
|
|
4225
|
+
attrs = [
|
|
4226
|
+
["margin", "auto 0"],
|
|
4227
|
+
["text-align", "center"],
|
|
4228
|
+
["max-height", "100%"],
|
|
4229
|
+
["width", "100%"],
|
|
4230
|
+
["line-height", "1.0"],
|
|
4231
|
+
];
|
|
4232
|
+
styles.push(buildStyle("huatu-label-text"));
|
|
4233
|
+
// Class of Position/Anchor Marks
|
|
4234
|
+
attrs = [];
|
|
4235
|
+
if (htConfig && htConfig.markPositions) {
|
|
4236
|
+
attrs.push(["fill", htConfig.markPositions]);
|
|
4237
|
+
styles.push(buildStyle("wthuatu-mark-position"));
|
|
4238
|
+
}
|
|
4239
|
+
// Class of Centers
|
|
4240
|
+
attrs = [];
|
|
4241
|
+
if (htConfig && htConfig.markCenters) {
|
|
4242
|
+
attrs.push(["stroke", "grey"], ["stroke-width", "0.1"]);
|
|
4243
|
+
styles.push(buildStyle("wthuatu-mark-centers"));
|
|
4244
|
+
}
|
|
4245
|
+
// Class of Bezier Control Lines and Points
|
|
4246
|
+
attrs = [];
|
|
4247
|
+
if (htConfig && htConfig.markBezierAuxLines) {
|
|
4248
|
+
attrs.push(["fill", "aqua"], ["stroke", "aqua"], ["stroke-width", "0.1"]);
|
|
4249
|
+
styles.push(buildStyle("wthuatu-mark-bezier-control"));
|
|
4250
|
+
}
|
|
4251
|
+
// Class of Named Points
|
|
4252
|
+
attrs = [];
|
|
4253
|
+
if (htConfig && htConfig.markNamedPoints) {
|
|
4254
|
+
attrs.push(["fill", htConfig.markNamedPoints]);
|
|
4255
|
+
styles.push(buildStyle("wthuatu-mark-named-points"));
|
|
4256
|
+
}
|
|
4257
|
+
// Class of Path Points
|
|
4258
|
+
attrs = [];
|
|
4259
|
+
if (htConfig && htConfig.markPathPoints) {
|
|
4260
|
+
attrs.push(["fill", htConfig.markPathPoints]);
|
|
4261
|
+
styles.push(buildStyle("wthuatu-mark-path-points"));
|
|
4262
|
+
}
|
|
4263
|
+
// Class of BBOX
|
|
4264
|
+
attrs = [];
|
|
4265
|
+
if (htConfig && htConfig.showBBox) {
|
|
4266
|
+
attrs.push(["fill", "none"], ["stroke", htConfig.showBBox], ["stroke-width", "0.05"]);
|
|
4267
|
+
styles.push(buildStyle("wthuatu-bbox-ref"));
|
|
4268
|
+
}
|
|
4269
|
+
// TODO: make more if required
|
|
4270
|
+
return styles.join("\n");
|
|
4271
|
+
}
|
|
4272
|
+
static RESERVED_NAMES = [
|
|
4273
|
+
"sin", "cos", "tan", "atan", "asin", "acos",
|
|
4274
|
+
"dsin", "dcos", "dtan", "datan", "dasin", "dacos",
|
|
4275
|
+
"exp", "log", "log10", "sqrt",
|
|
4276
|
+
"pi", "math",
|
|
4277
|
+
"this", "globalthis", "wtvarfuncs",
|
|
4278
|
+
];
|
|
4279
|
+
}
|