svg-path-simplify 0.4.3 → 0.4.5
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/CHANGELOG.md +21 -0
- package/README.md +2 -1
- package/dist/svg-path-simplify.esm.js +1670 -509
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1671 -508
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +936 -463
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/index.html +60 -20
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/detect_input.js +47 -29
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +46 -18
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +81 -20
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +14 -4
- package/src/svg-getAttributes.js +5 -3
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +140 -2
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +18 -2
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +188 -88
- package/src/svgii/pathData_fix_directions.js +10 -18
- package/src/svgii/pathData_reorder.js +123 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/poly_normalize.js +9 -8
- package/src/svgii/rounding.js +112 -80
- package/src/svgii/svg_cleanup.js +75 -22
- package/src/svgii/svg_cleanup_convertPathLength.js +27 -15
- package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
- package/src/svgii/svg_el_parse_style_props.js +13 -10
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
|
@@ -1,47 +1,259 @@
|
|
|
1
|
+
const rad2Deg = 180/Math.PI;
|
|
2
|
+
const deg2rad = Math.PI/180;
|
|
3
|
+
|
|
4
|
+
function validateSVG(markup, allowed = {}) {
|
|
5
|
+
allowed = {
|
|
6
|
+
...{
|
|
7
|
+
|
|
8
|
+
useElsNested: 5000,
|
|
9
|
+
hasScripts: false,
|
|
10
|
+
hasEntity: false,
|
|
11
|
+
fileSizeKB: 10000,
|
|
12
|
+
isSymbolSprite: false,
|
|
13
|
+
isSvgFont: false
|
|
14
|
+
},
|
|
15
|
+
...allowed
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let fileReport = analyzeSVG(markup, allowed);
|
|
19
|
+
let isValid = true;
|
|
20
|
+
let log = [];
|
|
21
|
+
|
|
22
|
+
if (!fileReport.hasEls) {
|
|
23
|
+
log.push("no elements");
|
|
24
|
+
isValid = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (Object.keys(fileReport).length) {
|
|
28
|
+
if (fileReport.isBillionLaugh === true) {
|
|
29
|
+
log.push(`suspicious: might contain billion laugh attack`);
|
|
30
|
+
isValid = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (let key in allowed) {
|
|
34
|
+
let val = allowed[key];
|
|
35
|
+
let valRep = fileReport[key];
|
|
36
|
+
if (typeof val === "number" && valRep > val) {
|
|
37
|
+
log.push(`allowed "${key}" exceeded: ${valRep} / ${val} `);
|
|
38
|
+
isValid = false;
|
|
39
|
+
}
|
|
40
|
+
if (valRep === true && val === false) {
|
|
41
|
+
log.push(`not allowed: "${key}" `);
|
|
42
|
+
isValid = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
isValid = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
if (!isValid) {
|
|
51
|
+
log = ["SVG not valid"].concat(log);
|
|
52
|
+
|
|
53
|
+
if (Object.keys(fileReport).length) {
|
|
54
|
+
console.warn(fileReport);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
return { isValid, log, fileReport };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function analyzeSVG(markup, allowed = {}) {
|
|
63
|
+
markup = markup.trim();
|
|
64
|
+
let doc, svg;
|
|
65
|
+
let fileSizeKB = +(markup.length / 1024).toFixed(3);
|
|
66
|
+
|
|
67
|
+
let fileReport = {
|
|
68
|
+
totalEls: 1,
|
|
69
|
+
hasEls: true,
|
|
70
|
+
hasDefs: false,
|
|
71
|
+
geometryEls: [],
|
|
72
|
+
useEls: 0,
|
|
73
|
+
useElsNested: 0,
|
|
74
|
+
nonsensePaths: 0,
|
|
75
|
+
isSuspicious: false,
|
|
76
|
+
isBillionLaugh: false,
|
|
77
|
+
hasScripts: false,
|
|
78
|
+
hasPrologue: false,
|
|
79
|
+
hasEntity: false,
|
|
80
|
+
isPathData:false,
|
|
81
|
+
fileSizeKB,
|
|
82
|
+
hasXmlns: markup.includes("http://www.w3.org/2000/svg"),
|
|
83
|
+
isSymbolSprite: false,
|
|
84
|
+
isSvgFont: markup.includes("<glyph>")
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let maxNested = allowed.useElsNested ? allowed.useElsNested : 2000;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* analyze nestes use references
|
|
91
|
+
*/
|
|
92
|
+
const countUseRefs = (useEls, maxNested = 2000) => {
|
|
93
|
+
let nestedCount = 0;
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < useEls.length && nestedCount < maxNested; i++) {
|
|
96
|
+
let use = useEls[i];
|
|
97
|
+
let refId = use.getAttribute("xlink:href")
|
|
98
|
+
? use.getAttribute("xlink:href")
|
|
99
|
+
: use.getAttribute("href");
|
|
100
|
+
refId = refId ? refId.replace("#", "") : "";
|
|
101
|
+
|
|
102
|
+
use.setAttribute("href", "#" + refId);
|
|
103
|
+
|
|
104
|
+
let refEl = svg.getElementById(refId);
|
|
105
|
+
let nestedUse = refEl.querySelectorAll("use");
|
|
106
|
+
let nestedUseLength = nestedUse.length;
|
|
107
|
+
nestedCount += nestedUseLength;
|
|
108
|
+
|
|
109
|
+
// query nested use references
|
|
110
|
+
for (let n = 0; n < nestedUse.length && nestedCount < maxNested; n++) {
|
|
111
|
+
let nested = nestedUse[n];
|
|
112
|
+
let id1 = nested.getAttribute("href").replace("#", "");
|
|
113
|
+
let refEl1 = svg.getElementById(id1);
|
|
114
|
+
let nestedUse1 = refEl1.querySelectorAll("use");
|
|
115
|
+
nestedCount += nestedUse1.length;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
fileReport.useElsNested = nestedCount;
|
|
119
|
+
return nestedCount;
|
|
120
|
+
};
|
|
121
|
+
let hasEntity = /\<\!ENTITY/gi.test(markup);
|
|
122
|
+
let hasScripts = /\<script/gi.test(markup) ? true : false;
|
|
123
|
+
let hasUse = /\<use/gi.test(markup) ? true : false;
|
|
124
|
+
let hasEls = /[\<path|\<polygon|\<polyline|\<rect|\<circle|\<ellipse|\<line|\<text|\<foreignObject]/gi.test(markup);
|
|
125
|
+
let hasDefs = /[\<filter|\<linearGradient|\<radialGradient|\<pattern|\<animate|\<animateMotion|\<animateTransform|\<clipPath|\<mask|\<symbol|\<marker]/gi.test(markup);
|
|
126
|
+
|
|
127
|
+
let isPathData = (markup.startsWith('M') || markup.startsWith('m')) && !/[\<svg|\<\/svg]/gi.test(markup);
|
|
128
|
+
fileReport.isPathData = isPathData;
|
|
129
|
+
|
|
130
|
+
// seems OK
|
|
131
|
+
if (!hasEntity && !hasUse && !hasScripts && (hasEls || hasDefs) && fileSizeKB < allowed.fileSizeKB) {
|
|
132
|
+
fileReport.hasEls = hasEls;
|
|
133
|
+
fileReport.hasDefs = hasDefs;
|
|
134
|
+
|
|
135
|
+
return fileReport
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Contains xml entity definition: highly suspicious - stop parsing!
|
|
139
|
+
if (allowed.hasEntity === false && hasEntity) {
|
|
140
|
+
fileReport.hasEntity = true;
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* sanitizing for parsing:
|
|
146
|
+
* remove xml prologue and comments
|
|
147
|
+
*/
|
|
148
|
+
markup = markup
|
|
149
|
+
.replace(/\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/g, "")
|
|
150
|
+
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, "");
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Try to parse svg:
|
|
154
|
+
* invalid svg will return false via "catch"
|
|
155
|
+
*/
|
|
156
|
+
try {
|
|
157
|
+
|
|
158
|
+
doc = new DOMParser().parseFromString(markup, "text/html");
|
|
159
|
+
svg = doc.querySelector("svg");
|
|
160
|
+
|
|
161
|
+
// paths containing only a M command
|
|
162
|
+
let nonsensePaths = svg.querySelectorAll('path[d="M0,0"], path[d="M0 0"]').length;
|
|
163
|
+
let useEls = svg.querySelectorAll("use").length;
|
|
164
|
+
|
|
165
|
+
// create analyzing object
|
|
166
|
+
fileReport.totalEls = svg.querySelectorAll("*").length;
|
|
167
|
+
fileReport.geometryEls = svg.querySelectorAll(
|
|
168
|
+
"path, rect, circle, ellipse, polygon, polyline, line"
|
|
169
|
+
).length;
|
|
170
|
+
|
|
171
|
+
fileReport.hasScripts = hasScripts;
|
|
172
|
+
fileReport.useEls = useEls;
|
|
173
|
+
fileReport.nonsensePaths = nonsensePaths;
|
|
174
|
+
fileReport.isSuspicious = false;
|
|
175
|
+
fileReport.isBillionLaugh = false;
|
|
176
|
+
fileReport.hasXmlns = svg.getAttribute("xmlns")
|
|
177
|
+
? svg.getAttribute("xmlns") === "http://www.w3.org/2000/svg"
|
|
178
|
+
? true
|
|
179
|
+
: false
|
|
180
|
+
: false;
|
|
181
|
+
fileReport.isSymbolSprite =
|
|
182
|
+
svg.querySelectorAll("symbol").length &&
|
|
183
|
+
svg.querySelectorAll("use").length === 0
|
|
184
|
+
? true
|
|
185
|
+
: false;
|
|
186
|
+
fileReport.isSvgFont = svg.querySelectorAll("glyph").length ? true : false;
|
|
187
|
+
|
|
188
|
+
let totalEls = fileReport.totalEls;
|
|
189
|
+
let totalUseEls = fileReport.useEls;
|
|
190
|
+
let usePercentage = (100 / totalEls) * totalUseEls;
|
|
191
|
+
|
|
192
|
+
// if percentage of use elements is higher than 75% - suspicious
|
|
193
|
+
if (usePercentage > 75) {
|
|
194
|
+
fileReport.isSuspicious = true;
|
|
195
|
+
|
|
196
|
+
// check nested use references
|
|
197
|
+
let nestedCount = countUseRefs(svg.querySelectorAll("use"), maxNested);
|
|
198
|
+
if (nestedCount >= maxNested) {
|
|
199
|
+
fileReport.isBillionLaugh = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return fileReport;
|
|
204
|
+
} catch {
|
|
205
|
+
// svg file has malformed markup
|
|
206
|
+
console.warn("svg could not be parsed");
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
1
211
|
function detectInputType(input) {
|
|
2
|
-
let
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
212
|
+
let log = '';
|
|
213
|
+
let isValid = true;
|
|
214
|
+
|
|
215
|
+
let result = {
|
|
216
|
+
inputType:'',
|
|
217
|
+
isValid:true,
|
|
218
|
+
fileReport:{},
|
|
219
|
+
};
|
|
220
|
+
|
|
11
221
|
if (Array.isArray(input)) {
|
|
12
222
|
|
|
223
|
+
result.inputType = "array";
|
|
224
|
+
|
|
13
225
|
// nested array
|
|
14
226
|
if (Array.isArray(input[0])) {
|
|
15
227
|
|
|
16
228
|
if (input[0].length === 2) {
|
|
17
229
|
|
|
18
|
-
|
|
230
|
+
result.inputType = 'polyArray';
|
|
19
231
|
}
|
|
20
232
|
|
|
21
233
|
else if (Array.isArray(input[0][0]) && input[0][0].length === 2) {
|
|
22
234
|
|
|
23
|
-
|
|
235
|
+
result.inputType = 'polyComplexArray';
|
|
24
236
|
}
|
|
25
237
|
else if (input[0][0].x !== undefined && input[0][0].y !== undefined) {
|
|
26
238
|
|
|
27
|
-
|
|
239
|
+
result.inputType = 'polyComplexObjectArray';
|
|
28
240
|
}
|
|
241
|
+
|
|
29
242
|
}
|
|
30
243
|
|
|
31
244
|
// is point array
|
|
32
245
|
else if (input[0].x !== undefined && input[0].y !== undefined) {
|
|
33
246
|
|
|
34
|
-
|
|
247
|
+
result.inputType = 'polyObjectArray';
|
|
35
248
|
}
|
|
36
249
|
|
|
37
250
|
// path data array
|
|
38
251
|
else if (input[0]?.type && input[0]?.values
|
|
39
252
|
) {
|
|
40
|
-
|
|
41
|
-
|
|
253
|
+
result.inputType = "pathData";
|
|
42
254
|
}
|
|
43
255
|
|
|
44
|
-
return
|
|
256
|
+
return result;
|
|
45
257
|
}
|
|
46
258
|
|
|
47
259
|
if (typeof input === "string") {
|
|
@@ -53,36 +265,48 @@ function detectInputType(input) {
|
|
|
53
265
|
let isJson = isNumberJson(input);
|
|
54
266
|
|
|
55
267
|
if (isSVG) {
|
|
56
|
-
|
|
268
|
+
let validate = validateSVG(input);
|
|
269
|
+
({isValid, log} = validate) ;
|
|
270
|
+
if(!isValid){
|
|
271
|
+
|
|
272
|
+
result.inputType = 'invalid';
|
|
273
|
+
result.isValid=false,
|
|
274
|
+
|
|
275
|
+
result.log = log;
|
|
276
|
+
}else {
|
|
277
|
+
result.inputType = 'svgMarkup';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
result.fileReport = validate.fileReport;
|
|
281
|
+
|
|
57
282
|
}
|
|
58
283
|
|
|
59
284
|
else if (isJson) {
|
|
60
|
-
|
|
285
|
+
result.inputType = 'json';
|
|
61
286
|
}
|
|
62
287
|
|
|
63
288
|
else if (isSymbol) {
|
|
64
|
-
|
|
289
|
+
result.inputType = 'symbol';
|
|
65
290
|
}
|
|
66
291
|
else if (isPathData) {
|
|
67
|
-
|
|
292
|
+
result.inputType = 'pathDataString';
|
|
68
293
|
}
|
|
69
294
|
else if (isPolyString) {
|
|
70
|
-
|
|
295
|
+
result.inputType = 'polyString';
|
|
71
296
|
}
|
|
72
297
|
|
|
73
298
|
else {
|
|
74
299
|
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
75
300
|
let dataUrl = input.startsWith('data:image');
|
|
76
|
-
|
|
301
|
+
result.inputType = url || dataUrl ? "url" : "string";
|
|
77
302
|
}
|
|
78
303
|
|
|
79
|
-
return
|
|
304
|
+
return result
|
|
80
305
|
}
|
|
81
306
|
|
|
82
|
-
|
|
83
|
-
let constructor = input.constructor.name;
|
|
307
|
+
result.inputType = (input.constructor.name || typeof input ).toLowerCase();
|
|
84
308
|
|
|
85
|
-
return
|
|
309
|
+
return result;
|
|
86
310
|
}
|
|
87
311
|
|
|
88
312
|
function isNumberJson(str) {
|
|
@@ -100,9 +324,6 @@ function isNumberJson(str) {
|
|
|
100
324
|
|
|
101
325
|
}
|
|
102
326
|
|
|
103
|
-
const rad2Deg = 180/Math.PI;
|
|
104
|
-
const deg2rad = Math.PI/180;
|
|
105
|
-
|
|
106
327
|
function renderPoint(
|
|
107
328
|
svg,
|
|
108
329
|
coords,
|
|
@@ -130,18 +351,6 @@ function renderPoint(
|
|
|
130
351
|
}
|
|
131
352
|
}
|
|
132
353
|
|
|
133
|
-
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', opacity="1", render = true) {
|
|
134
|
-
|
|
135
|
-
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${opacity}" /> `;
|
|
136
|
-
|
|
137
|
-
if (render) {
|
|
138
|
-
svg.insertAdjacentHTML("beforeend", path);
|
|
139
|
-
} else {
|
|
140
|
-
return path;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
354
|
/*
|
|
146
355
|
import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
147
356
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
@@ -358,6 +567,7 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
358
567
|
let t1 = 1 - t;
|
|
359
568
|
|
|
360
569
|
// cubic beziers
|
|
570
|
+
/*
|
|
361
571
|
if (isCubic) {
|
|
362
572
|
pt = {
|
|
363
573
|
x:
|
|
@@ -373,11 +583,29 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
373
583
|
};
|
|
374
584
|
|
|
375
585
|
}
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
if (isCubic) {
|
|
589
|
+
pt = {
|
|
590
|
+
x:
|
|
591
|
+
t1 * t1 * t1 * p0.x +
|
|
592
|
+
3 * t1 * t1 * t * cp1.x +
|
|
593
|
+
3 * t1 * t * t * cp2.x +
|
|
594
|
+
t * t * t * p.x,
|
|
595
|
+
y:
|
|
596
|
+
t1 * t1 * t1 * p0.y +
|
|
597
|
+
3 * t1 * t1 * t * cp1.y +
|
|
598
|
+
3 * t1 * t * t * cp2.y +
|
|
599
|
+
t * t * t * p.y,
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
}
|
|
603
|
+
|
|
376
604
|
// quadratic beziers
|
|
377
605
|
else {
|
|
378
606
|
pt = {
|
|
379
|
-
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t
|
|
380
|
-
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t
|
|
607
|
+
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t * t * p.x,
|
|
608
|
+
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t * t * p.y,
|
|
381
609
|
};
|
|
382
610
|
}
|
|
383
611
|
|
|
@@ -693,12 +921,6 @@ function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false }
|
|
|
693
921
|
return tArr;
|
|
694
922
|
}
|
|
695
923
|
|
|
696
|
-
/**
|
|
697
|
-
* based on Nikos M.'s answer
|
|
698
|
-
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
699
|
-
* https://stackoverflow.com/questions/87734/#75031511
|
|
700
|
-
* See also: https://github.com/foo123/Geometrize
|
|
701
|
-
*/
|
|
702
924
|
function getArcExtemes(p0, values) {
|
|
703
925
|
// compute point on ellipse from angle around ellipse (theta)
|
|
704
926
|
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
@@ -1424,6 +1646,36 @@ function splitCommandAtTValues(p0, values, tArr, returnCommand = true) {
|
|
|
1424
1646
|
return segmentPoints;
|
|
1425
1647
|
}
|
|
1426
1648
|
|
|
1649
|
+
/**
|
|
1650
|
+
* round path data
|
|
1651
|
+
* either by explicit decimal value or
|
|
1652
|
+
* based on suggested accuracy in path data
|
|
1653
|
+
*/
|
|
1654
|
+
function roundPathData(pathData, decimalsGlobal = -1) {
|
|
1655
|
+
|
|
1656
|
+
if (decimalsGlobal < 0) return pathData;
|
|
1657
|
+
|
|
1658
|
+
let len = pathData.length;
|
|
1659
|
+
let decimals = decimalsGlobal;
|
|
1660
|
+
let decimalsArc = decimals < 3 ? decimals + 2 : decimals;
|
|
1661
|
+
|
|
1662
|
+
for (let c = 0; c < len; c++) {
|
|
1663
|
+
let com = pathData[c];
|
|
1664
|
+
let { type, values } = com;
|
|
1665
|
+
let valLen = values.length;
|
|
1666
|
+
if (!valLen) continue
|
|
1667
|
+
|
|
1668
|
+
let isArc = type.toLowerCase() === 'a';
|
|
1669
|
+
|
|
1670
|
+
for (let v = 0; v < valLen; v++) {
|
|
1671
|
+
// allow higher accuracy for arc radii (... it's always arcs)
|
|
1672
|
+
pathData[c].values[v] = isArc && v < 2 ? roundTo(values[v], decimalsArc) : roundTo(values[v], decimals);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
return pathData;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1427
1679
|
function detectAccuracy(pathData) {
|
|
1428
1680
|
let dims = [];
|
|
1429
1681
|
|
|
@@ -1437,59 +1689,73 @@ function detectAccuracy(pathData) {
|
|
|
1437
1689
|
|
|
1438
1690
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1439
1691
|
|
|
1440
|
-
if (dimA) dims.push(dimA);
|
|
1692
|
+
if (dimA) dims.push(+dimA.toFixed(8));
|
|
1441
1693
|
|
|
1442
1694
|
}
|
|
1443
1695
|
|
|
1444
1696
|
}
|
|
1445
1697
|
|
|
1446
|
-
|
|
1698
|
+
dims = dims.sort();
|
|
1699
|
+
let len = dims.length;
|
|
1700
|
+
let dim_mid = dims[Math.floor(len*0.5)];
|
|
1701
|
+
|
|
1702
|
+
// smallest 25% of values
|
|
1703
|
+
let idx_q = Math.ceil(len*0.25);
|
|
1704
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1705
|
+
|
|
1706
|
+
// average smallest values with mid value
|
|
1707
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1708
|
+
|
|
1709
|
+
let threshold = 75;
|
|
1710
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1447
1711
|
|
|
1448
|
-
|
|
1712
|
+
// clamp
|
|
1713
|
+
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1714
|
+
|
|
1715
|
+
/*
|
|
1716
|
+
let dim_min = dims.sort()
|
|
1717
|
+
|
|
1718
|
+
let dim_mid = dim_min[Math.floor(dim_min.length*0.5)]
|
|
1719
|
+
|
|
1720
|
+
let sliceIdx = Math.ceil(dim_min.length / 4);
|
|
1449
1721
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1450
1722
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
1451
1723
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1724
|
+
// average with mid value
|
|
1725
|
+
minVal = (minVal+dim_mid)*0.5
|
|
1726
|
+
|
|
1727
|
+
let threshold = 75
|
|
1728
|
+
let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
|
|
1454
1729
|
|
|
1455
1730
|
// clamp
|
|
1456
1731
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1732
|
+
*/
|
|
1457
1733
|
|
|
1458
1734
|
}
|
|
1459
1735
|
|
|
1460
|
-
function roundTo(num = 0, decimals = 3) {
|
|
1461
|
-
if(decimals<=-1) return num;
|
|
1462
|
-
if (!decimals) return Math.round(num);
|
|
1463
|
-
let factor = 10 ** decimals;
|
|
1464
|
-
return Math.round(num * factor) / factor;
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
1736
|
/**
|
|
1468
|
-
*
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1737
|
+
* rounding helper
|
|
1738
|
+
* allows for quantized rounding
|
|
1739
|
+
* e.g 0.5 decimals s
|
|
1471
1740
|
*/
|
|
1472
|
-
function
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
let len = pathData.length;
|
|
1477
|
-
|
|
1478
|
-
let decimals = decimalsGlobal;
|
|
1741
|
+
function roundTo(num = 0, decimals = 3) {
|
|
1742
|
+
if (decimals < 0) return num;
|
|
1743
|
+
// Normal integer rounding
|
|
1744
|
+
if (!decimals) return Math.round(num);
|
|
1479
1745
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
let {values} = com;
|
|
1746
|
+
// stepped rounding
|
|
1747
|
+
let intPart = Math.floor(decimals);
|
|
1483
1748
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1749
|
+
if (intPart !== decimals) {
|
|
1750
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1751
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1486
1752
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
}
|
|
1753
|
+
let step = 10 ** -intPart * f;
|
|
1754
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1490
1755
|
}
|
|
1491
1756
|
|
|
1492
|
-
|
|
1757
|
+
let factor = 10 ** decimals;
|
|
1758
|
+
return Math.round(num * factor) / factor;
|
|
1493
1759
|
}
|
|
1494
1760
|
|
|
1495
1761
|
/**
|
|
@@ -2220,7 +2486,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2220
2486
|
error += com.error;
|
|
2221
2487
|
|
|
2222
2488
|
// find next candidates
|
|
2223
|
-
for (let n = i +
|
|
2489
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
2224
2490
|
let comN = pathData[n];
|
|
2225
2491
|
|
|
2226
2492
|
if (comN.type !== 'C' ||
|
|
@@ -2230,6 +2496,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2230
2496
|
(keepExtremes && com.extreme)
|
|
2231
2497
|
)
|
|
2232
2498
|
) {
|
|
2499
|
+
|
|
2233
2500
|
break
|
|
2234
2501
|
}
|
|
2235
2502
|
|
|
@@ -2237,6 +2504,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2237
2504
|
|
|
2238
2505
|
// failure - could not be combined - exit loop
|
|
2239
2506
|
if (combined.length > 1) {
|
|
2507
|
+
|
|
2240
2508
|
break
|
|
2241
2509
|
}
|
|
2242
2510
|
|
|
@@ -2250,6 +2518,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2250
2518
|
|
|
2251
2519
|
// return combined
|
|
2252
2520
|
com = combined[0];
|
|
2521
|
+
|
|
2253
2522
|
}
|
|
2254
2523
|
|
|
2255
2524
|
pathDataN.push(com);
|
|
@@ -2299,9 +2568,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
2299
2568
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
2300
2569
|
|
|
2301
2570
|
// test new point-at-t against original mid segment starting point
|
|
2302
|
-
let
|
|
2571
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
2303
2572
|
|
|
2304
|
-
let dist0 = getDistManhattan(com1.p,
|
|
2573
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
2305
2574
|
let dist1 = 0, dist2 = 0;
|
|
2306
2575
|
let close = dist0 < maxDist;
|
|
2307
2576
|
let success = false;
|
|
@@ -2316,29 +2585,40 @@ function combineCubicPairs(com1, com2, {
|
|
|
2316
2585
|
* to prevent distortions
|
|
2317
2586
|
*/
|
|
2318
2587
|
|
|
2319
|
-
//
|
|
2320
|
-
let
|
|
2588
|
+
// 1st segment mid
|
|
2589
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
2321
2590
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
let
|
|
2325
|
-
dist1 = getDistManhattan(
|
|
2591
|
+
let t2 = t * 0.5;
|
|
2592
|
+
// combined interpolated mid point
|
|
2593
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
2594
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
2326
2595
|
|
|
2327
2596
|
error += dist1;
|
|
2328
2597
|
|
|
2329
2598
|
if (dist1 < maxDist) {
|
|
2330
2599
|
|
|
2331
|
-
//
|
|
2332
|
-
let
|
|
2600
|
+
// 2nd segment mid
|
|
2601
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
2333
2602
|
|
|
2334
|
-
|
|
2335
|
-
let
|
|
2336
|
-
|
|
2603
|
+
// simplified path
|
|
2604
|
+
let t3 = (1 + t) * 0.5;
|
|
2605
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
2606
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
2337
2607
|
|
|
2338
2608
|
error += dist2;
|
|
2339
2609
|
|
|
2340
2610
|
if (error < maxDist) success = true;
|
|
2341
2611
|
|
|
2612
|
+
/*
|
|
2613
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
2614
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
2615
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
2616
|
+
|
|
2617
|
+
renderPoint(markers, com1.p, 'green')
|
|
2618
|
+
|
|
2619
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
2620
|
+
*/
|
|
2621
|
+
|
|
2342
2622
|
}
|
|
2343
2623
|
|
|
2344
2624
|
} // end 1st try
|
|
@@ -2352,11 +2632,19 @@ function combineCubicPairs(com1, com2, {
|
|
|
2352
2632
|
|
|
2353
2633
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
2354
2634
|
comS.type = 'C';
|
|
2635
|
+
|
|
2355
2636
|
comS.extreme = com2.extreme;
|
|
2356
2637
|
comS.directionChange = com2.directionChange;
|
|
2357
|
-
|
|
2358
2638
|
comS.corner = com2.corner;
|
|
2359
2639
|
|
|
2640
|
+
if (comS.extreme || comS.corner) ;
|
|
2641
|
+
|
|
2642
|
+
/*
|
|
2643
|
+
comS.extreme = com1.extreme;
|
|
2644
|
+
comS.directionChange = com1.directionChange;
|
|
2645
|
+
comS.corner = com1.corner;
|
|
2646
|
+
*/
|
|
2647
|
+
|
|
2360
2648
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
2361
2649
|
|
|
2362
2650
|
// relative error
|
|
@@ -2503,6 +2791,7 @@ function analyzePathData(pathData = [], {
|
|
|
2503
2791
|
let com = pathData[c - 1];
|
|
2504
2792
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
2505
2793
|
|
|
2794
|
+
let comPrev = pathData[c-2];
|
|
2506
2795
|
let comN = pathData[c] || null;
|
|
2507
2796
|
|
|
2508
2797
|
// init properties
|
|
@@ -2521,6 +2810,7 @@ function analyzePathData(pathData = [], {
|
|
|
2521
2810
|
|
|
2522
2811
|
// bezier types
|
|
2523
2812
|
let isBezier = type === 'Q' || type === 'C';
|
|
2813
|
+
let isArc = type === 'A';
|
|
2524
2814
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
2525
2815
|
|
|
2526
2816
|
/**
|
|
@@ -2567,6 +2857,22 @@ function analyzePathData(pathData = [], {
|
|
|
2567
2857
|
}
|
|
2568
2858
|
}
|
|
2569
2859
|
|
|
2860
|
+
// check extremes introduce by small arcs
|
|
2861
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
2862
|
+
let distN = comN ? comN.dimA : 0;
|
|
2863
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
2864
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
2865
|
+
|
|
2866
|
+
if(isShort && smallRadius){
|
|
2867
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
2868
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
2869
|
+
hasExtremes = true;
|
|
2870
|
+
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2570
2876
|
if (hasExtremes) com.extreme = true;
|
|
2571
2877
|
|
|
2572
2878
|
// Corners and semi extremes
|
|
@@ -3120,50 +3426,10 @@ function parsePathDataString(d, debug = true, limit=0) {
|
|
|
3120
3426
|
|
|
3121
3427
|
}
|
|
3122
3428
|
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
toLonghands = true,
|
|
3128
|
-
|
|
3129
|
-
// not necessary unless you need cubics only
|
|
3130
|
-
quadraticToCubic = false,
|
|
3131
|
-
|
|
3132
|
-
// mostly a fallback if arc calculations fail
|
|
3133
|
-
arcToCubic = false,
|
|
3134
|
-
// arc to cubic precision - adds more segments for better precision
|
|
3135
|
-
arcAccuracy = 4,
|
|
3136
|
-
} = {}
|
|
3137
|
-
) {
|
|
3138
|
-
|
|
3139
|
-
// is already array
|
|
3140
|
-
let isArray = Array.isArray(d);
|
|
3141
|
-
|
|
3142
|
-
// normalize native pathData to regular array
|
|
3143
|
-
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3144
|
-
/*
|
|
3145
|
-
if (hasConstructor) {
|
|
3146
|
-
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3147
|
-
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3148
|
-
}
|
|
3149
|
-
*/
|
|
3150
|
-
|
|
3151
|
-
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3152
|
-
|
|
3153
|
-
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3154
|
-
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3155
|
-
|
|
3156
|
-
// normalize
|
|
3157
|
-
pathData = normalizePathData(pathData,
|
|
3158
|
-
{
|
|
3159
|
-
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3160
|
-
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3161
|
-
},
|
|
3162
|
-
);
|
|
3163
|
-
|
|
3164
|
-
return pathData;
|
|
3165
|
-
}
|
|
3166
|
-
|
|
3429
|
+
/**
|
|
3430
|
+
* wrapper function for
|
|
3431
|
+
* all path data conversion
|
|
3432
|
+
*/
|
|
3167
3433
|
function convertPathData(pathData, {
|
|
3168
3434
|
toShorthands = true,
|
|
3169
3435
|
toLonghands = false,
|
|
@@ -3215,22 +3481,24 @@ function convertPathData(pathData, {
|
|
|
3215
3481
|
|
|
3216
3482
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3217
3483
|
|
|
3218
|
-
if(toMixed) toRelative = true;
|
|
3484
|
+
if (toMixed) toRelative = true;
|
|
3219
3485
|
|
|
3220
3486
|
// pre round - before relative conversion to minimize distortions
|
|
3221
3487
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
3222
3488
|
|
|
3223
3489
|
// clone absolute pathdata
|
|
3224
|
-
if(toMixed){
|
|
3490
|
+
if (toMixed) {
|
|
3225
3491
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
3226
3492
|
}
|
|
3227
3493
|
|
|
3228
3494
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
3495
|
+
|
|
3496
|
+
// final rounding
|
|
3229
3497
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
3230
3498
|
|
|
3231
3499
|
// choose most compact commands: relative or absolute
|
|
3232
|
-
if(toMixed){
|
|
3233
|
-
for(let i=0; i<pathData.length; i++){
|
|
3500
|
+
if (toMixed) {
|
|
3501
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
3234
3502
|
let com = pathData[i];
|
|
3235
3503
|
let comA = pathDataAbs[i];
|
|
3236
3504
|
// compare Lengths
|
|
@@ -3240,7 +3508,7 @@ function convertPathData(pathData, {
|
|
|
3240
3508
|
let lenR = comStr.length;
|
|
3241
3509
|
let lenA = comStrA.length;
|
|
3242
3510
|
|
|
3243
|
-
if(lenA<lenR){
|
|
3511
|
+
if (lenA < lenR) {
|
|
3244
3512
|
|
|
3245
3513
|
pathData[i] = pathDataAbs[i];
|
|
3246
3514
|
}
|
|
@@ -3250,6 +3518,50 @@ function convertPathData(pathData, {
|
|
|
3250
3518
|
return pathData
|
|
3251
3519
|
}
|
|
3252
3520
|
|
|
3521
|
+
function parsePathDataNormalized(d,
|
|
3522
|
+
{
|
|
3523
|
+
// necessary for most calculations
|
|
3524
|
+
toAbsolute = true,
|
|
3525
|
+
toLonghands = true,
|
|
3526
|
+
|
|
3527
|
+
// not necessary unless you need cubics only
|
|
3528
|
+
quadraticToCubic = false,
|
|
3529
|
+
|
|
3530
|
+
// mostly a fallback if arc calculations fail
|
|
3531
|
+
arcToCubic = false,
|
|
3532
|
+
// arc to cubic precision - adds more segments for better precision
|
|
3533
|
+
arcAccuracy = 4,
|
|
3534
|
+
} = {}
|
|
3535
|
+
) {
|
|
3536
|
+
|
|
3537
|
+
// is already array
|
|
3538
|
+
let isArray = Array.isArray(d);
|
|
3539
|
+
|
|
3540
|
+
// normalize native pathData to regular array
|
|
3541
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3542
|
+
/*
|
|
3543
|
+
if (hasConstructor) {
|
|
3544
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3545
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3546
|
+
}
|
|
3547
|
+
*/
|
|
3548
|
+
|
|
3549
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3550
|
+
|
|
3551
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3552
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3553
|
+
|
|
3554
|
+
// normalize
|
|
3555
|
+
pathData = normalizePathData(pathData,
|
|
3556
|
+
{
|
|
3557
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3558
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3559
|
+
},
|
|
3560
|
+
);
|
|
3561
|
+
|
|
3562
|
+
return pathData;
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3253
3565
|
/**
|
|
3254
3566
|
*
|
|
3255
3567
|
* @param {*} pathData
|
|
@@ -3257,75 +3569,97 @@ function convertPathData(pathData, {
|
|
|
3257
3569
|
*/
|
|
3258
3570
|
|
|
3259
3571
|
function optimizeArcPathData(pathData = []) {
|
|
3572
|
+
let l = pathData.length;
|
|
3573
|
+
let pathDataN = [];
|
|
3260
3574
|
|
|
3261
|
-
let
|
|
3262
|
-
|
|
3263
|
-
pathData.forEach((com, i) => {
|
|
3575
|
+
for (let i = 0; i < l; i++) {
|
|
3576
|
+
let com = pathData[i];
|
|
3264
3577
|
let { type, values } = com;
|
|
3265
|
-
if (type === 'A') {
|
|
3266
|
-
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
3267
|
-
let comPrev = pathData[i - 1];
|
|
3268
|
-
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
3269
|
-
let M = { x: x0, y: y0 };
|
|
3270
|
-
let p = { x, y };
|
|
3271
3578
|
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3579
|
+
if (type !== 'A') {
|
|
3580
|
+
pathDataN.push(com);
|
|
3581
|
+
continue
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
3585
|
+
let comPrev = pathData[i - 1];
|
|
3586
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
3587
|
+
let M = { x: x0, y: y0 };
|
|
3588
|
+
let p = { x, y };
|
|
3275
3589
|
|
|
3276
|
-
|
|
3590
|
+
if (rx === 0 || ry === 0) {
|
|
3591
|
+
pathData[i] = null;
|
|
3592
|
+
}
|
|
3277
3593
|
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3594
|
+
// test for elliptic
|
|
3595
|
+
let rat = rx / ry;
|
|
3596
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
3281
3597
|
|
|
3282
|
-
|
|
3283
|
-
if (diff < 0.01) {
|
|
3598
|
+
if (error > 0.01) {
|
|
3284
3599
|
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
let distM = getDistance(pMid, M);
|
|
3288
|
-
let rDiff = Math.abs(distM - rx) / rx;
|
|
3600
|
+
pathDataN.push(com);
|
|
3601
|
+
continue
|
|
3289
3602
|
|
|
3290
|
-
// half distance between mid and start point should be ~ equal
|
|
3291
|
-
if(rDiff<0.01){
|
|
3292
|
-
pathData[i].values[0] = 1;
|
|
3293
|
-
pathData[i].values[1] = 1;
|
|
3294
|
-
pathData[i].values[2] = 0;
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
3603
|
}
|
|
3299
|
-
});
|
|
3300
3604
|
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
}
|
|
3605
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
3606
|
+
com.values[2] = 0;
|
|
3304
3607
|
|
|
3305
|
-
/**
|
|
3306
|
-
|
|
3307
|
-
|
|
3608
|
+
/**
|
|
3609
|
+
* test semi circles
|
|
3610
|
+
* rx and ry are large enough
|
|
3611
|
+
*/
|
|
3308
3612
|
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
quadraticToCubic = false,
|
|
3314
|
-
arcToCubic = false,
|
|
3315
|
-
arcAccuracy = 2,
|
|
3613
|
+
// 1. horizontal or vertical
|
|
3614
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
3615
|
+
let diffX = Math.abs(x - x0);
|
|
3616
|
+
let diffY = Math.abs(y - y0);
|
|
3316
3617
|
|
|
3317
|
-
|
|
3318
|
-
|
|
3618
|
+
let isHorizontal = diffY < thresh;
|
|
3619
|
+
let isVertical = diffX < thresh;
|
|
3319
3620
|
|
|
3320
|
-
|
|
3321
|
-
) {
|
|
3621
|
+
// minify rx and ry
|
|
3622
|
+
if (isHorizontal || isVertical) {
|
|
3322
3623
|
|
|
3323
|
-
|
|
3324
|
-
|
|
3624
|
+
// check if semi circle
|
|
3625
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
3325
3626
|
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3627
|
+
// is semicircle we can simplify rx
|
|
3628
|
+
if (!needsTrueR) {
|
|
3629
|
+
|
|
3630
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
com.values[0] = rx;
|
|
3634
|
+
com.values[1] = rx;
|
|
3635
|
+
pathDataN.push(com);
|
|
3636
|
+
continue
|
|
3637
|
+
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
3641
|
+
let r = getDistance(M, p) * 0.5;
|
|
3642
|
+
error = rx / r;
|
|
3643
|
+
|
|
3644
|
+
if (error < 0.5) {
|
|
3645
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
com.values[0] = rx;
|
|
3649
|
+
com.values[1] = rx;
|
|
3650
|
+
pathDataN.push(com);
|
|
3651
|
+
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
return pathDataN;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
/**
|
|
3658
|
+
* parse normalized
|
|
3659
|
+
*/
|
|
3660
|
+
|
|
3661
|
+
function normalizePathData(pathData = [],
|
|
3662
|
+
{
|
|
3329
3663
|
toAbsolute = true,
|
|
3330
3664
|
toLonghands = true,
|
|
3331
3665
|
quadraticToCubic = false,
|
|
@@ -3338,31 +3672,8 @@ export function normalizePathData(pathData = [],
|
|
|
3338
3672
|
} = {}
|
|
3339
3673
|
) {
|
|
3340
3674
|
|
|
3341
|
-
|
|
3342
|
-
if (testTypes) {
|
|
3343
|
-
|
|
3344
|
-
let commands = Array.from(new Set(pathData.map(com => com.type))).join('');
|
|
3345
|
-
hasRelatives = /[lcqamts]/gi.test(commands);
|
|
3346
|
-
hasQuadratics = /[qt]/gi.test(commands);
|
|
3347
|
-
hasArcs = /[a]/gi.test(commands);
|
|
3348
|
-
hasShorthands = /[vhst]/gi.test(commands);
|
|
3349
|
-
isPoly = /[mlz]/gi.test(commands);
|
|
3350
|
-
}
|
|
3351
|
-
|
|
3352
|
-
if ((hasQuadratics && quadraticToCubic) || (hasArcs && arcToCubic)) {
|
|
3353
|
-
toLonghands = true
|
|
3354
|
-
toAbsolute = true
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
if (hasRelatives && toAbsolute) pathData = pathDataToAbsoluteOrRelative(pathData, false);
|
|
3358
|
-
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData, -1, false);
|
|
3359
|
-
if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData, arcAccuracy);
|
|
3360
|
-
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3361
|
-
|
|
3362
|
-
return pathData;
|
|
3363
|
-
|
|
3675
|
+
return convertPathData(pathData, { toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy, hasRelatives, hasShorthands, hasQuadratics, hasArcs, testTypes, decimals: -1 })
|
|
3364
3676
|
}
|
|
3365
|
-
*/
|
|
3366
3677
|
|
|
3367
3678
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
3368
3679
|
|
|
@@ -4243,7 +4554,8 @@ function pathDataToTopLeft(pathData) {
|
|
|
4243
4554
|
let { type, values } = com;
|
|
4244
4555
|
let valsLen = values.length;
|
|
4245
4556
|
if (valsLen) {
|
|
4246
|
-
|
|
4557
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
4558
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
|
|
4247
4559
|
p.index = i;
|
|
4248
4560
|
indices.push(p);
|
|
4249
4561
|
}
|
|
@@ -4251,113 +4563,111 @@ function pathDataToTopLeft(pathData) {
|
|
|
4251
4563
|
|
|
4252
4564
|
// reorder to top left most
|
|
4253
4565
|
|
|
4254
|
-
indices = indices.sort((a, b) =>
|
|
4566
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
4255
4567
|
newIndex = indices[0].index;
|
|
4256
4568
|
|
|
4257
|
-
return
|
|
4569
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
4258
4570
|
}
|
|
4259
4571
|
|
|
4260
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
4572
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
4261
4573
|
|
|
4262
|
-
let
|
|
4574
|
+
let pathDataN = pathData;
|
|
4263
4575
|
let l = pathData.length;
|
|
4264
4576
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
4265
4577
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4266
4578
|
|
|
4267
|
-
let
|
|
4268
|
-
|
|
4269
|
-
// check if order is ideal
|
|
4270
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
4579
|
+
let hasLinetos = false;
|
|
4271
4580
|
|
|
4581
|
+
// check if path is closed by explicit lineto
|
|
4582
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
4272
4583
|
let penultimateCom = pathData[idxPenultimate];
|
|
4273
4584
|
let penultimateType = penultimateCom.type;
|
|
4274
4585
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
4275
4586
|
|
|
4276
4587
|
// last L command ends at M
|
|
4277
|
-
let
|
|
4588
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4589
|
+
let lastIsLine = penultimateType === 'L';
|
|
4278
4590
|
|
|
4279
|
-
//
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
let valsLast = pathData[idxPenultimate].values
|
|
4285
|
-
let valsLastLen = valsLast.length;
|
|
4286
|
-
pathData[idxPenultimate].values[valsLastLen-2] = M.x
|
|
4287
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
4288
|
-
*/
|
|
4289
|
-
|
|
4290
|
-
pathData.push({type:'Z', values:[]});
|
|
4291
|
-
isClosed = true;
|
|
4292
|
-
l++;
|
|
4293
|
-
}
|
|
4591
|
+
// create index
|
|
4592
|
+
let indices = [];
|
|
4593
|
+
for (let i = 0; i < l; i++) {
|
|
4594
|
+
let com = pathData[i];
|
|
4595
|
+
let { type, values, p0, p } = com;
|
|
4294
4596
|
|
|
4295
|
-
|
|
4296
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
4297
|
-
skipReorder = false;
|
|
4597
|
+
if(type==='L') hasLinetos = true;
|
|
4298
4598
|
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
}
|
|
4599
|
+
// exclude Z
|
|
4600
|
+
if (values.length) {
|
|
4601
|
+
values.slice(-2);
|
|
4303
4602
|
|
|
4304
|
-
|
|
4603
|
+
let x = Math.min(p0.x, p.x);
|
|
4604
|
+
let y = Math.min(p0.y, p.y);
|
|
4305
4605
|
|
|
4306
|
-
|
|
4606
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
4607
|
+
let prevType = prevCom.type;
|
|
4307
4608
|
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
let { type, values } = com;
|
|
4312
|
-
if (values.length) {
|
|
4313
|
-
let valsL = values.slice(-2);
|
|
4314
|
-
let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
|
|
4315
|
-
let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
|
|
4316
|
-
let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
|
|
4317
|
-
let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
|
|
4318
|
-
let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom };
|
|
4319
|
-
p.index = i;
|
|
4320
|
-
indices.push(p);
|
|
4321
|
-
}
|
|
4609
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
4610
|
+
item.index = i;
|
|
4611
|
+
indices.push(item);
|
|
4322
4612
|
}
|
|
4323
4613
|
|
|
4324
|
-
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
let xMin = Infinity;
|
|
4617
|
+
let yMin = Infinity;
|
|
4618
|
+
let idx_top = null;
|
|
4619
|
+
let len = indices.length;
|
|
4325
4620
|
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4621
|
+
for (let i = 0; i < len; i++) {
|
|
4622
|
+
let com = indices[i];
|
|
4623
|
+
let { type, index, x, y, prevType } = com;
|
|
4329
4624
|
|
|
4330
|
-
|
|
4625
|
+
if (hasLinetos && prevType === 'L') {
|
|
4626
|
+
if (x < xMin && y < yMin) {
|
|
4627
|
+
idx_top = index-1;
|
|
4628
|
+
}
|
|
4331
4629
|
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
4336
|
-
newIndex = indices[0].index;
|
|
4337
|
-
}
|
|
4630
|
+
if (y < yMin) {
|
|
4631
|
+
yMin = y;
|
|
4632
|
+
}
|
|
4338
4633
|
|
|
4339
|
-
|
|
4340
|
-
|
|
4634
|
+
if (x < xMin) {
|
|
4635
|
+
xMin = x;
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4341
4638
|
}
|
|
4342
4639
|
|
|
4343
|
-
|
|
4640
|
+
// shift to better starting point
|
|
4641
|
+
if (idx_top) {
|
|
4642
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
4344
4643
|
|
|
4345
|
-
|
|
4644
|
+
// update penultimate - reorder might have added new close paths
|
|
4645
|
+
l = pathDataN.length;
|
|
4646
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
4647
|
+
|
|
4648
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
4649
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
4650
|
+
penultimateType = penultimateCom.type;
|
|
4651
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
4652
|
+
lastIsLine = penultimateType ==='L';
|
|
4653
|
+
|
|
4654
|
+
// last L command ends at M
|
|
4655
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4346
4656
|
|
|
4347
|
-
|
|
4348
|
-
penultimateCom = pathData[l - 2];
|
|
4349
|
-
penultimateType = penultimateCom.type;
|
|
4350
|
-
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
4657
|
+
}
|
|
4351
4658
|
|
|
4352
|
-
|
|
4659
|
+
// remove unnecessary closing lineto
|
|
4660
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
4661
|
+
pathDataN.splice(l - 2, 1);
|
|
4662
|
+
}
|
|
4353
4663
|
|
|
4354
|
-
|
|
4355
|
-
|
|
4664
|
+
// add close path
|
|
4665
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
4666
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
4356
4667
|
}
|
|
4357
4668
|
|
|
4358
|
-
|
|
4669
|
+
return pathDataN
|
|
4359
4670
|
|
|
4360
|
-
return pathDataNew
|
|
4361
4671
|
}
|
|
4362
4672
|
|
|
4363
4673
|
/**
|
|
@@ -4567,8 +4877,130 @@ function refineAdjacentExtremes(pathData, {
|
|
|
4567
4877
|
|
|
4568
4878
|
}
|
|
4569
4879
|
|
|
4880
|
+
function getArcFromPoly(pts, precise = false) {
|
|
4881
|
+
if (pts.length < 3) return false
|
|
4882
|
+
|
|
4883
|
+
// Pick 3 well-spaced points
|
|
4884
|
+
let len = pts.length;
|
|
4885
|
+
let idx1 = Math.floor(len * 0.333);
|
|
4886
|
+
let idx2 = Math.floor(len * 0.666);
|
|
4887
|
+
let idx3 = Math.floor(len * 0.5);
|
|
4888
|
+
|
|
4889
|
+
let p1 = pts[0];
|
|
4890
|
+
let p2 = pts[idx3];
|
|
4891
|
+
let p3 = pts[len - 1];
|
|
4892
|
+
|
|
4893
|
+
// Radius (use start point)
|
|
4894
|
+
let pts1 = [p1, p2, p3];
|
|
4895
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
4896
|
+
|
|
4897
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
4898
|
+
|
|
4899
|
+
// check if radii are consistent
|
|
4900
|
+
if (precise) {
|
|
4901
|
+
|
|
4902
|
+
/**
|
|
4903
|
+
* check multiple centroids
|
|
4904
|
+
* if the polyline can be expressed as
|
|
4905
|
+
* an arc - all centroids should be close
|
|
4906
|
+
*/
|
|
4907
|
+
|
|
4908
|
+
if (len > 3) {
|
|
4909
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
4910
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
4911
|
+
|
|
4912
|
+
if (!centroid1 || !centroid2) return false;
|
|
4913
|
+
|
|
4914
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
4915
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
4916
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
4917
|
+
let errorCentroid = (dist1 + dist2);
|
|
4918
|
+
|
|
4919
|
+
// centroids diverging too much
|
|
4920
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
4921
|
+
|
|
4922
|
+
return false
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
}
|
|
4926
|
+
|
|
4927
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
4928
|
+
|
|
4929
|
+
for (let i = 0; i < len; i++) {
|
|
4930
|
+
let pt = pts[i];
|
|
4931
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
4932
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
4933
|
+
|
|
4934
|
+
if (error > 0.0025) {
|
|
4935
|
+
/*
|
|
4936
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
4937
|
+
renderPoint(markers, centroid, 'orange')
|
|
4938
|
+
renderPoint(markers, p1, 'green')
|
|
4939
|
+
renderPoint(markers, p2)
|
|
4940
|
+
renderPoint(markers, p3, 'purple')
|
|
4941
|
+
*/
|
|
4942
|
+
return false;
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4946
|
+
// calculate proper radius
|
|
4947
|
+
r = Math.sqrt(rSqMid);
|
|
4948
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
4949
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
4950
|
+
|
|
4951
|
+
} else {
|
|
4952
|
+
r = getDistance(centroid, p1);
|
|
4953
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
4954
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
return {
|
|
4958
|
+
centroid,
|
|
4959
|
+
r,
|
|
4960
|
+
startAngle,
|
|
4961
|
+
endAngle,
|
|
4962
|
+
deltaAngle
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4965
|
+
|
|
4966
|
+
function getPolyArcCentroid(pts = []) {
|
|
4967
|
+
|
|
4968
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
4969
|
+
if (pts.length < 3) return false
|
|
4970
|
+
|
|
4971
|
+
let p1 = pts[0];
|
|
4972
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
4973
|
+
let p3 = pts[pts.length - 1];
|
|
4974
|
+
|
|
4975
|
+
let x1 = p1.x, y1 = p1.y;
|
|
4976
|
+
let x2 = p2.x, y2 = p2.y;
|
|
4977
|
+
let x3 = p3.x, y3 = p3.y;
|
|
4978
|
+
|
|
4979
|
+
let a = x1 - x2;
|
|
4980
|
+
let b = y1 - y2;
|
|
4981
|
+
let c = x1 - x3;
|
|
4982
|
+
let d = y1 - y3;
|
|
4983
|
+
|
|
4984
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
4985
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
4986
|
+
|
|
4987
|
+
let det = a * d - b * c;
|
|
4988
|
+
|
|
4989
|
+
// colinear points
|
|
4990
|
+
if (Math.abs(det) < 1e-10) {
|
|
4991
|
+
return false;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
// find center of arc
|
|
4995
|
+
let cx = (d * e - b * f) / det;
|
|
4996
|
+
let cy = (-c * e + a * f) / det;
|
|
4997
|
+
let centroid = { x: cx, y: cy };
|
|
4998
|
+
return centroid
|
|
4999
|
+
}
|
|
5000
|
+
|
|
4570
5001
|
function refineRoundedCorners(pathData, {
|
|
4571
5002
|
threshold = 0,
|
|
5003
|
+
simplifyQuadraticCorners = false,
|
|
4572
5004
|
tolerance = 1
|
|
4573
5005
|
} = {}) {
|
|
4574
5006
|
|
|
@@ -4593,6 +5025,9 @@ function refineRoundedCorners(pathData, {
|
|
|
4593
5025
|
let firstIsLine = pathData[1].type === 'L';
|
|
4594
5026
|
let firstIsBez = pathData[1].type === 'C';
|
|
4595
5027
|
|
|
5028
|
+
// in case we have simplified a corner connecting to the start
|
|
5029
|
+
let M_adj = null;
|
|
5030
|
+
|
|
4596
5031
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
4597
5032
|
|
|
4598
5033
|
// normalize closepath to lineto
|
|
@@ -4632,15 +5067,17 @@ function refineRoundedCorners(pathData, {
|
|
|
4632
5067
|
// closing corner to start
|
|
4633
5068
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
4634
5069
|
comL1 = pathData[1];
|
|
5070
|
+
|
|
4635
5071
|
comBez = [pathData[l - lastOff]];
|
|
4636
5072
|
|
|
4637
5073
|
}
|
|
4638
5074
|
|
|
5075
|
+
// collect enclosed bezier segments
|
|
4639
5076
|
for (let j = i + 1; j < l; j++) {
|
|
4640
5077
|
let comN = pathData[j] ? pathData[j] : null;
|
|
4641
5078
|
let comPrev = pathData[j - 1];
|
|
4642
5079
|
|
|
4643
|
-
if (comPrev.type === 'C') {
|
|
5080
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
4644
5081
|
comBez.push(comPrev);
|
|
4645
5082
|
}
|
|
4646
5083
|
|
|
@@ -4671,39 +5108,67 @@ function refineRoundedCorners(pathData, {
|
|
|
4671
5108
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
4672
5109
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
4673
5110
|
|
|
5111
|
+
/*
|
|
5112
|
+
*/
|
|
5113
|
+
|
|
4674
5114
|
if (comBez.length && !signChange && isSmall) {
|
|
4675
5115
|
|
|
4676
|
-
let
|
|
5116
|
+
let isSquare = false;
|
|
5117
|
+
|
|
5118
|
+
if (comBez.length === 1) {
|
|
5119
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
5120
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
5121
|
+
let diff = (dx - dy);
|
|
5122
|
+
let rat = Math.abs(diff / dx);
|
|
5123
|
+
isSquare = rat < 0.01;
|
|
5124
|
+
}
|
|
5125
|
+
|
|
5126
|
+
let preferArcs = true;
|
|
5127
|
+
preferArcs = false;
|
|
5128
|
+
|
|
5129
|
+
// if rectangular prefer arcs
|
|
5130
|
+
if (preferArcs && isSquare) {
|
|
5131
|
+
|
|
5132
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
5133
|
+
|
|
5134
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
5135
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
5136
|
+
|
|
5137
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
5138
|
+
|
|
5139
|
+
let largeArc = 0;
|
|
5140
|
+
|
|
5141
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
5142
|
+
|
|
5143
|
+
pathDataN.push(comL0, comArc);
|
|
5144
|
+
i += offset;
|
|
5145
|
+
continue
|
|
5146
|
+
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5149
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
5150
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
5151
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
5152
|
+
|
|
4677
5153
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
4678
5154
|
|
|
4679
|
-
|
|
5155
|
+
// exit: is rather flat or has no intersection
|
|
5156
|
+
|
|
5157
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
4680
5158
|
pathDataN.push(com);
|
|
4681
5159
|
continue
|
|
4682
5160
|
}
|
|
4683
5161
|
|
|
4684
|
-
// check sign change
|
|
5162
|
+
// check sign change - exit if present
|
|
4685
5163
|
if (ptQ) {
|
|
4686
5164
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
4687
5165
|
let area0_abs = Math.abs(area0);
|
|
4688
5166
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
4689
5167
|
let area1_abs = Math.abs(area1);
|
|
4690
5168
|
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs;
|
|
4691
|
-
|
|
4692
|
-
/*
|
|
4693
|
-
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
4694
|
-
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
4695
|
-
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
4696
|
-
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
4697
|
-
if(!area0) {
|
|
4698
|
-
pathDataN.push(com);
|
|
4699
|
-
continue
|
|
4700
|
-
}
|
|
4701
|
-
*/
|
|
4702
|
-
|
|
4703
5169
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
4704
5170
|
|
|
4705
5171
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
4706
|
-
|
|
4707
5172
|
pathDataN.push(com);
|
|
4708
5173
|
continue
|
|
4709
5174
|
}
|
|
@@ -4718,24 +5183,67 @@ function refineRoundedCorners(pathData, {
|
|
|
4718
5183
|
|
|
4719
5184
|
// not in tolerance – return original command
|
|
4720
5185
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
4721
|
-
|
|
4722
5186
|
pathDataN.push(com);
|
|
4723
5187
|
continue;
|
|
4724
5188
|
|
|
4725
|
-
}
|
|
5189
|
+
}
|
|
4726
5190
|
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
comQ.cp1 = ptQ;
|
|
4730
|
-
comQ.p = comL1.p0;
|
|
5191
|
+
// return simplified quadratic Bézier command
|
|
5192
|
+
let p_Q = comL1.p0;
|
|
4731
5193
|
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
5194
|
+
// adjust previous end point to better fit the cubic curvature
|
|
5195
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
5196
|
+
|
|
5197
|
+
if (adjustQ) {
|
|
5198
|
+
|
|
5199
|
+
let t = 0.1666;
|
|
5200
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
5201
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
5202
|
+
|
|
5203
|
+
// round for large enough segments
|
|
5204
|
+
let isH = ptQ.y===comL0.p.y;
|
|
5205
|
+
let isV = ptQ.x===comL0.p.x;
|
|
5206
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
5207
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
5208
|
+
|
|
5209
|
+
if(isSquare && com.dimA>3){
|
|
5210
|
+
let dec = 0.5;
|
|
5211
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
5212
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
5213
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
5214
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
/*
|
|
5218
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
5219
|
+
renderPoint(markers, p_Q, 'orange')
|
|
5220
|
+
renderPoint(markers, comL0.p, 'green')
|
|
5221
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
5222
|
+
*/
|
|
5223
|
+
|
|
5224
|
+
// set new M starting point
|
|
5225
|
+
if (i === l - lastOff - 1) {
|
|
5226
|
+
|
|
5227
|
+
M_adj = p_Q;
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5230
|
+
// adjust previous lineto end point
|
|
5231
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
5232
|
+
comL0.p = p0_adj;
|
|
4735
5233
|
|
|
4736
|
-
continue;
|
|
4737
5234
|
}
|
|
4738
5235
|
|
|
5236
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
5237
|
+
comQ.cp1 = ptQ;
|
|
5238
|
+
comQ.p0 = comL0.p;
|
|
5239
|
+
comQ.p = p_Q;
|
|
5240
|
+
|
|
5241
|
+
// add quadratic command
|
|
5242
|
+
pathDataN.push(comL0, comQ);
|
|
5243
|
+
|
|
5244
|
+
i += offset;
|
|
5245
|
+
continue;
|
|
5246
|
+
|
|
4739
5247
|
}
|
|
4740
5248
|
}
|
|
4741
5249
|
}
|
|
@@ -4749,6 +5257,12 @@ function refineRoundedCorners(pathData, {
|
|
|
4749
5257
|
|
|
4750
5258
|
}
|
|
4751
5259
|
|
|
5260
|
+
// correct starting point connecting with last corner rounding
|
|
5261
|
+
if (M_adj) {
|
|
5262
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
5263
|
+
pathDataN[0].p0 = M_adj;
|
|
5264
|
+
}
|
|
5265
|
+
|
|
4752
5266
|
// revert close path normalization
|
|
4753
5267
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
4754
5268
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -4758,51 +5272,101 @@ function refineRoundedCorners(pathData, {
|
|
|
4758
5272
|
|
|
4759
5273
|
}
|
|
4760
5274
|
|
|
4761
|
-
function
|
|
4762
|
-
|
|
5275
|
+
function refineClosingCommand(pathData = [], {
|
|
5276
|
+
threshold = 0,
|
|
5277
|
+
} = {}) {
|
|
4763
5278
|
|
|
4764
|
-
|
|
4765
|
-
let
|
|
4766
|
-
let
|
|
4767
|
-
let
|
|
5279
|
+
let l = pathData.length;
|
|
5280
|
+
let comLast = pathData[l - 1];
|
|
5281
|
+
let isClosed = comLast.type.toLowerCase() === 'z';
|
|
5282
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
5283
|
+
let comPenultimate = isClosed ? pathData[idxPenultimate] : pathData[idxPenultimate];
|
|
5284
|
+
let valsPen = comPenultimate.values.slice(-2);
|
|
5285
|
+
|
|
5286
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
5287
|
+
let pPen = { x: valsPen[0], y: valsPen[1] };
|
|
5288
|
+
let dist = getDistAv(M, pPen);
|
|
5289
|
+
|
|
5290
|
+
// adjust last coordinates for better reordering
|
|
5291
|
+
if (dist && dist < threshold) {
|
|
4768
5292
|
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
5293
|
+
let valsLast = pathData[idxPenultimate].values;
|
|
5294
|
+
let valsLastLen = valsLast.length;
|
|
5295
|
+
pathData[idxPenultimate].values[valsLastLen - 2] = M.x;
|
|
5296
|
+
pathData[idxPenultimate].values[valsLastLen - 1] = M.y;
|
|
4772
5297
|
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
let c = x1 - x3;
|
|
4776
|
-
let d = y1 - y3;
|
|
5298
|
+
// adjust cpts
|
|
5299
|
+
let comFirst = pathData[1];
|
|
4777
5300
|
|
|
4778
|
-
|
|
4779
|
-
|
|
5301
|
+
if (comFirst.type === 'C' && comPenultimate.type === 'C') {
|
|
5302
|
+
let dx1 = Math.abs(comFirst.values[0] - comPenultimate.values[2]);
|
|
5303
|
+
let dy1 = Math.abs(comFirst.values[1] - comPenultimate.values[3]);
|
|
4780
5304
|
|
|
4781
|
-
|
|
5305
|
+
let dx2 = Math.abs(pathData[1].values[0] - comFirst.values[0]);
|
|
5306
|
+
let dy2 = Math.abs(pathData[1].values[1] - comFirst.values[1]);
|
|
4782
5307
|
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
5308
|
+
let dx3 = Math.abs(pathData[1].values[0] - comPenultimate.values[2]);
|
|
5309
|
+
let dy3 = Math.abs(pathData[1].values[1] - comPenultimate.values[3]);
|
|
5310
|
+
|
|
5311
|
+
let ver = dx2 < threshold && dx3 < threshold && dy1;
|
|
5312
|
+
let hor = (dy2 < threshold && dy3 < threshold) && dx1;
|
|
5313
|
+
|
|
5314
|
+
if (dx1 && dx1 < threshold && ver) {
|
|
5315
|
+
|
|
5316
|
+
pathData[1].values[0] = M.x;
|
|
5317
|
+
pathData[idxPenultimate].values[2] = M.x;
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
if (dy1 && dy1 < threshold && hor) {
|
|
5321
|
+
|
|
5322
|
+
pathData[1].values[1] = M.y;
|
|
5323
|
+
pathData[idxPenultimate].values[3] = M.y;
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
}
|
|
4786
5327
|
}
|
|
4787
5328
|
|
|
4788
|
-
|
|
4789
|
-
let cx = (d * e - b * f) / det;
|
|
4790
|
-
let cy = (-c * e + a * f) / det;
|
|
4791
|
-
let centroid = { x: cx, y: cy };
|
|
5329
|
+
return pathData;
|
|
4792
5330
|
|
|
4793
|
-
|
|
4794
|
-
let r = getDistance(centroid, p1);
|
|
5331
|
+
}
|
|
4795
5332
|
|
|
4796
|
-
|
|
4797
|
-
let {deltaAngle, startAngle, endAngle} = angleData;
|
|
5333
|
+
function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
4798
5334
|
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
5335
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5336
|
+
let com = pathData[c];
|
|
5337
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5338
|
+
if (type === 'C') {
|
|
5339
|
+
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
5340
|
+
if (comQ.type === 'Q') {
|
|
5341
|
+
comQ.extreme = com.extreme;
|
|
5342
|
+
comQ.corner = com.corner;
|
|
5343
|
+
comQ.dimA = com.dimA;
|
|
5344
|
+
comQ.squareDist = com.squareDist;
|
|
5345
|
+
pathData[c] = comQ;
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
return pathData
|
|
5350
|
+
}
|
|
5351
|
+
|
|
5352
|
+
function pathDataLineToCubic(pathData) {
|
|
5353
|
+
|
|
5354
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5355
|
+
let com = pathData[c];
|
|
5356
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5357
|
+
if (type === 'L') {
|
|
5358
|
+
|
|
5359
|
+
let cp1 = interpolate(p0, p, 0.333);
|
|
5360
|
+
let cp2 = interpolate(p, p0, 0.333);
|
|
5361
|
+
|
|
5362
|
+
pathData[c].type = 'C';
|
|
5363
|
+
pathData[c].values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
5364
|
+
pathData[c].cp1 = cp1;
|
|
5365
|
+
pathData[c].cp2 = cp2;
|
|
5366
|
+
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
return pathData
|
|
4806
5370
|
}
|
|
4807
5371
|
|
|
4808
5372
|
function refineRoundSegments(pathData, {
|
|
@@ -4821,9 +5385,6 @@ function refineRoundSegments(pathData, {
|
|
|
4821
5385
|
// add fist command
|
|
4822
5386
|
let pathDataN = [pathData[0]];
|
|
4823
5387
|
|
|
4824
|
-
// just for debugging
|
|
4825
|
-
let pathDataTest = [];
|
|
4826
|
-
|
|
4827
5388
|
for (let i = 1; i < l; i++) {
|
|
4828
5389
|
let com = pathData[i];
|
|
4829
5390
|
let { type } = com;
|
|
@@ -4850,11 +5411,12 @@ function refineRoundSegments(pathData, {
|
|
|
4850
5411
|
|
|
4851
5412
|
// 2. line-line-bezier-line-line
|
|
4852
5413
|
if (
|
|
5414
|
+
comN2 && comN3 &&
|
|
4853
5415
|
comP.type === 'L' &&
|
|
4854
5416
|
type === 'L' &&
|
|
4855
5417
|
comBez &&
|
|
4856
5418
|
comN2.type === 'L' &&
|
|
4857
|
-
|
|
5419
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
4858
5420
|
) {
|
|
4859
5421
|
|
|
4860
5422
|
L1 = [com.p0, com.p];
|
|
@@ -4881,10 +5443,10 @@ function refineRoundSegments(pathData, {
|
|
|
4881
5443
|
}
|
|
4882
5444
|
|
|
4883
5445
|
// 1. line-bezier-bezier-line
|
|
4884
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
5446
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
4885
5447
|
|
|
4886
5448
|
// 1.2 next is cubic next is lineto
|
|
4887
|
-
if (
|
|
5449
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
4888
5450
|
|
|
4889
5451
|
combine = true;
|
|
4890
5452
|
|
|
@@ -4943,16 +5505,19 @@ function refineRoundSegments(pathData, {
|
|
|
4943
5505
|
}
|
|
4944
5506
|
);
|
|
4945
5507
|
|
|
4946
|
-
if(bezierCommands.length === 1){
|
|
5508
|
+
if (bezierCommands.length === 1) {
|
|
4947
5509
|
|
|
4948
5510
|
// prefer more compact quadratic - otherwise arcs
|
|
4949
5511
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
4950
5512
|
|
|
4951
5513
|
if (comBezier.type === 'Q') {
|
|
4952
5514
|
toCubic = true;
|
|
5515
|
+
}else {
|
|
5516
|
+
comBezier = bezierCommands[0];
|
|
4953
5517
|
}
|
|
4954
5518
|
|
|
4955
5519
|
com = comBezier;
|
|
5520
|
+
|
|
4956
5521
|
}
|
|
4957
5522
|
|
|
4958
5523
|
// prefer arcs if 2 cubics are required
|
|
@@ -4972,25 +5537,28 @@ function refineRoundSegments(pathData, {
|
|
|
4972
5537
|
|
|
4973
5538
|
// test rendering
|
|
4974
5539
|
|
|
5540
|
+
/*
|
|
4975
5541
|
if (debug) {
|
|
4976
5542
|
// arcs
|
|
4977
5543
|
if (!toCubic) {
|
|
4978
5544
|
pathDataTest = [
|
|
4979
5545
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
4980
5546
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
4981
|
-
]
|
|
5547
|
+
]
|
|
4982
5548
|
}
|
|
4983
5549
|
// cubics
|
|
4984
5550
|
else {
|
|
4985
5551
|
pathDataTest = [
|
|
4986
5552
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
4987
5553
|
...bezierCommands
|
|
4988
|
-
]
|
|
5554
|
+
]
|
|
5555
|
+
|
|
4989
5556
|
}
|
|
4990
5557
|
|
|
4991
5558
|
let d = pathDataToD(pathDataTest);
|
|
4992
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
5559
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
4993
5560
|
}
|
|
5561
|
+
*/
|
|
4994
5562
|
|
|
4995
5563
|
pathDataN.push(com);
|
|
4996
5564
|
i++;
|
|
@@ -5007,104 +5575,6 @@ function refineRoundSegments(pathData, {
|
|
|
5007
5575
|
return pathDataN;
|
|
5008
5576
|
}
|
|
5009
5577
|
|
|
5010
|
-
function refineClosingCommand(pathData = [], {
|
|
5011
|
-
threshold = 0,
|
|
5012
|
-
} = {}) {
|
|
5013
|
-
|
|
5014
|
-
let l = pathData.length;
|
|
5015
|
-
let comLast = pathData[l - 1];
|
|
5016
|
-
let isClosed = comLast.type.toLowerCase() === 'z';
|
|
5017
|
-
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
5018
|
-
let comPenultimate = isClosed ? pathData[idxPenultimate] : pathData[idxPenultimate];
|
|
5019
|
-
let valsPen = comPenultimate.values.slice(-2);
|
|
5020
|
-
|
|
5021
|
-
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
5022
|
-
let pPen = { x: valsPen[0], y: valsPen[1] };
|
|
5023
|
-
let dist = getDistAv(M, pPen);
|
|
5024
|
-
|
|
5025
|
-
// adjust last coordinates for better reordering
|
|
5026
|
-
if (dist && dist < threshold) {
|
|
5027
|
-
|
|
5028
|
-
let valsLast = pathData[idxPenultimate].values;
|
|
5029
|
-
let valsLastLen = valsLast.length;
|
|
5030
|
-
pathData[idxPenultimate].values[valsLastLen - 2] = M.x;
|
|
5031
|
-
pathData[idxPenultimate].values[valsLastLen - 1] = M.y;
|
|
5032
|
-
|
|
5033
|
-
// adjust cpts
|
|
5034
|
-
let comFirst = pathData[1];
|
|
5035
|
-
|
|
5036
|
-
if (comFirst.type === 'C' && comPenultimate.type === 'C') {
|
|
5037
|
-
let dx1 = Math.abs(comFirst.values[0] - comPenultimate.values[2]);
|
|
5038
|
-
let dy1 = Math.abs(comFirst.values[1] - comPenultimate.values[3]);
|
|
5039
|
-
|
|
5040
|
-
let dx2 = Math.abs(pathData[1].values[0] - comFirst.values[0]);
|
|
5041
|
-
let dy2 = Math.abs(pathData[1].values[1] - comFirst.values[1]);
|
|
5042
|
-
|
|
5043
|
-
let dx3 = Math.abs(pathData[1].values[0] - comPenultimate.values[2]);
|
|
5044
|
-
let dy3 = Math.abs(pathData[1].values[1] - comPenultimate.values[3]);
|
|
5045
|
-
|
|
5046
|
-
let ver = dx2 < threshold && dx3 < threshold && dy1;
|
|
5047
|
-
let hor = (dy2 < threshold && dy3 < threshold) && dx1;
|
|
5048
|
-
|
|
5049
|
-
if (dx1 && dx1 < threshold && ver) {
|
|
5050
|
-
|
|
5051
|
-
pathData[1].values[0] = M.x;
|
|
5052
|
-
pathData[idxPenultimate].values[2] = M.x;
|
|
5053
|
-
}
|
|
5054
|
-
|
|
5055
|
-
if (dy1 && dy1 < threshold && hor) {
|
|
5056
|
-
|
|
5057
|
-
pathData[1].values[1] = M.y;
|
|
5058
|
-
pathData[idxPenultimate].values[3] = M.y;
|
|
5059
|
-
}
|
|
5060
|
-
|
|
5061
|
-
}
|
|
5062
|
-
}
|
|
5063
|
-
|
|
5064
|
-
return pathData;
|
|
5065
|
-
|
|
5066
|
-
}
|
|
5067
|
-
|
|
5068
|
-
function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
5069
|
-
|
|
5070
|
-
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5071
|
-
let com = pathData[c];
|
|
5072
|
-
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5073
|
-
if (type === 'C') {
|
|
5074
|
-
|
|
5075
|
-
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
5076
|
-
if (comQ.type === 'Q') {
|
|
5077
|
-
comQ.extreme = com.extreme;
|
|
5078
|
-
comQ.corner = com.corner;
|
|
5079
|
-
comQ.dimA = com.dimA;
|
|
5080
|
-
comQ.squareDist = com.squareDist;
|
|
5081
|
-
pathData[c] = comQ;
|
|
5082
|
-
}
|
|
5083
|
-
}
|
|
5084
|
-
}
|
|
5085
|
-
return pathData
|
|
5086
|
-
}
|
|
5087
|
-
|
|
5088
|
-
function pathDataLineToCubic(pathData) {
|
|
5089
|
-
|
|
5090
|
-
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5091
|
-
let com = pathData[c];
|
|
5092
|
-
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5093
|
-
if (type === 'L') {
|
|
5094
|
-
|
|
5095
|
-
let cp1 = interpolate(p0, p, 0.333);
|
|
5096
|
-
let cp2 = interpolate(p, p0, 0.333);
|
|
5097
|
-
|
|
5098
|
-
pathData[c].type = 'C';
|
|
5099
|
-
pathData[c].values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
5100
|
-
pathData[c].cp1 = cp1;
|
|
5101
|
-
pathData[c].cp2 = cp2;
|
|
5102
|
-
|
|
5103
|
-
}
|
|
5104
|
-
}
|
|
5105
|
-
return pathData
|
|
5106
|
-
}
|
|
5107
|
-
|
|
5108
5578
|
function simplifyPathData(input = '', {
|
|
5109
5579
|
|
|
5110
5580
|
toAbsolute = true,
|
|
@@ -5162,7 +5632,10 @@ function simplifyPathData(input = '', {
|
|
|
5162
5632
|
let yArr = [];
|
|
5163
5633
|
|
|
5164
5634
|
// mode:0 – single path
|
|
5165
|
-
|
|
5635
|
+
|
|
5636
|
+
let inputDetection = detectInputType(input);
|
|
5637
|
+
let {inputType, log} = inputDetection;
|
|
5638
|
+
|
|
5166
5639
|
if (inputType === 'pathDataString') {
|
|
5167
5640
|
d = input;
|
|
5168
5641
|
} else if (inputType === 'polyString') {
|