svg-path-simplify 0.4.2 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +7 -4
- package/dist/svg-path-simplify.esm.js +3593 -1279
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +3594 -1278
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/docs/privacy-webapp.md +24 -0
- package/index.html +331 -152
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +76 -28
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +26 -16
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +304 -276
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +254 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +130 -4
- package/src/svg-getAttributes.js +4 -2
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +322 -5
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +253 -0
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +193 -89
- package/src/svgii/pathData_fix_directions.js +12 -14
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +66 -68
- package/src/svgii/pathData_reorder.js +122 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +80 -78
- package/src/svgii/svg_cleanup.js +421 -619
- package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +72 -47
- package/src/svgii/svg_getElementLength.js +67 -0
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
|
@@ -1,78 +1,327 @@
|
|
|
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
|
-
if(input[0].length===2){
|
|
228
|
+
if (input[0].length === 2) {
|
|
17
229
|
|
|
18
|
-
|
|
230
|
+
result.inputType = 'polyArray';
|
|
19
231
|
}
|
|
20
232
|
|
|
21
|
-
else if (Array.isArray(input[0][0]) && input[0][0].length === 2
|
|
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
|
-
else if (input[0].x!==undefined && input[0].y!==undefined) {
|
|
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") {
|
|
48
260
|
input = input.trim();
|
|
49
261
|
let isSVG = input.includes('<svg') && input.includes('</svg');
|
|
262
|
+
let isSymbol = input.startsWith('<symbol') && input.includes('</symbol');
|
|
50
263
|
let isPathData = input.startsWith('M') || input.startsWith('m');
|
|
51
264
|
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length - 1, input.length));
|
|
265
|
+
let isJson = isNumberJson(input);
|
|
52
266
|
|
|
53
267
|
if (isSVG) {
|
|
54
|
-
|
|
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
|
+
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
else if (isJson) {
|
|
285
|
+
result.inputType = 'json';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
else if (isSymbol) {
|
|
289
|
+
result.inputType = 'symbol';
|
|
55
290
|
}
|
|
56
291
|
else if (isPathData) {
|
|
57
|
-
|
|
292
|
+
result.inputType = 'pathDataString';
|
|
58
293
|
}
|
|
59
294
|
else if (isPolyString) {
|
|
60
|
-
|
|
295
|
+
result.inputType = 'polyString';
|
|
61
296
|
}
|
|
62
297
|
|
|
63
298
|
else {
|
|
64
299
|
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
65
300
|
let dataUrl = input.startsWith('data:image');
|
|
66
|
-
|
|
301
|
+
result.inputType = url || dataUrl ? "url" : "string";
|
|
67
302
|
}
|
|
68
303
|
|
|
69
|
-
return
|
|
304
|
+
return result
|
|
70
305
|
}
|
|
71
306
|
|
|
72
|
-
|
|
73
|
-
|
|
307
|
+
result.inputType = (input.constructor.name || typeof input ).toLowerCase();
|
|
308
|
+
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isNumberJson(str) {
|
|
313
|
+
|
|
314
|
+
str = str.trim();
|
|
315
|
+
|
|
316
|
+
let hasNumber = /\d/.test(str);
|
|
317
|
+
let hasInvalid = /[abcdfghijklmnopqrstuvwz]/gi.test(str);
|
|
318
|
+
if (!hasNumber || hasInvalid) return false
|
|
319
|
+
|
|
320
|
+
// is JSON like
|
|
321
|
+
let isJson = str.startsWith('[') && str.endsWith(']');
|
|
74
322
|
|
|
75
|
-
return
|
|
323
|
+
return isJson
|
|
324
|
+
|
|
76
325
|
}
|
|
77
326
|
|
|
78
327
|
function renderPoint(
|
|
@@ -102,18 +351,6 @@ function renderPoint(
|
|
|
102
351
|
}
|
|
103
352
|
}
|
|
104
353
|
|
|
105
|
-
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', opacity="1", render = true) {
|
|
106
|
-
|
|
107
|
-
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${opacity}" /> `;
|
|
108
|
-
|
|
109
|
-
if (render) {
|
|
110
|
-
svg.insertAdjacentHTML("beforeend", path);
|
|
111
|
-
} else {
|
|
112
|
-
return path;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
354
|
/*
|
|
118
355
|
import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
119
356
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
@@ -330,6 +567,7 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
330
567
|
let t1 = 1 - t;
|
|
331
568
|
|
|
332
569
|
// cubic beziers
|
|
570
|
+
/*
|
|
333
571
|
if (isCubic) {
|
|
334
572
|
pt = {
|
|
335
573
|
x:
|
|
@@ -345,11 +583,29 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
345
583
|
};
|
|
346
584
|
|
|
347
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
|
+
|
|
348
604
|
// quadratic beziers
|
|
349
605
|
else {
|
|
350
606
|
pt = {
|
|
351
|
-
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t
|
|
352
|
-
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,
|
|
353
609
|
};
|
|
354
610
|
}
|
|
355
611
|
|
|
@@ -384,7 +640,36 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
384
640
|
/**
|
|
385
641
|
* get vertices from path command final on-path points
|
|
386
642
|
*/
|
|
387
|
-
|
|
643
|
+
|
|
644
|
+
function getPathDataVertices(pathData=[], includeCpts = false, decimals = -1) {
|
|
645
|
+
let polyPoints = [];
|
|
646
|
+
|
|
647
|
+
pathData.forEach((com) => {
|
|
648
|
+
let { type, values } = com;
|
|
649
|
+
|
|
650
|
+
// get final on path point from last 2 values
|
|
651
|
+
if (values.length) {
|
|
652
|
+
|
|
653
|
+
// round
|
|
654
|
+
if (decimals > -1) values = values.map(val => +val.toFixed(decimals));
|
|
655
|
+
|
|
656
|
+
if (includeCpts) {
|
|
657
|
+
|
|
658
|
+
for (let i = 1; i < values.length; i += 2) {
|
|
659
|
+
polyPoints.push({ x: values[i - 1], y: values[i] });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
} else {
|
|
663
|
+
polyPoints.push({ x: values[values.length - 2], y: values[values.length - 1] });
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
return polyPoints;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/*
|
|
672
|
+
export function getPathDataVertices(pathData) {
|
|
388
673
|
let polyPoints = [];
|
|
389
674
|
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
390
675
|
|
|
@@ -399,22 +684,30 @@ function getPathDataVertices(pathData) {
|
|
|
399
684
|
}
|
|
400
685
|
});
|
|
401
686
|
return polyPoints;
|
|
402
|
-
}
|
|
687
|
+
};
|
|
688
|
+
*/
|
|
403
689
|
|
|
404
690
|
/**
|
|
405
691
|
* based on @cuixiping;
|
|
406
692
|
* https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
|
|
407
693
|
*/
|
|
408
|
-
|
|
694
|
+
|
|
695
|
+
function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2, y2, normalize = true
|
|
696
|
+
) {
|
|
409
697
|
|
|
410
698
|
// helper for angle calculation
|
|
411
|
-
const getAngle = (cx, cy, x, y) => {
|
|
412
|
-
|
|
699
|
+
const getAngle = (cx, cy, x, y, normalize = true) => {
|
|
700
|
+
let angle = Math.atan2(y - cy, x - cx);
|
|
701
|
+
if (normalize && angle < 0) angle += Math.PI * 2;
|
|
702
|
+
return angle
|
|
413
703
|
};
|
|
414
704
|
|
|
415
705
|
// make sure rx, ry are positive
|
|
416
|
-
rx = abs(rx);
|
|
417
|
-
ry = abs(ry);
|
|
706
|
+
rx = Math.abs(rx);
|
|
707
|
+
ry = Math.abs(ry);
|
|
708
|
+
|
|
709
|
+
// normalize xAxis rotation
|
|
710
|
+
xAxisRotation = rx === ry ? 0 : (xAxisRotation < 0 && normalize ? xAxisRotation + 360 : xAxisRotation);
|
|
418
711
|
|
|
419
712
|
// create data object
|
|
420
713
|
let arcData = {
|
|
@@ -438,53 +731,11 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
438
731
|
throw Error("rx and ry can not be 0");
|
|
439
732
|
}
|
|
440
733
|
|
|
441
|
-
let shortcut = true;
|
|
442
|
-
|
|
443
|
-
if (rx === ry && shortcut) {
|
|
444
|
-
|
|
445
|
-
// test semicircles
|
|
446
|
-
let diffX = Math.abs(x2 - x1);
|
|
447
|
-
let diffY = Math.abs(y2 - y1);
|
|
448
|
-
let r = diffX;
|
|
449
|
-
|
|
450
|
-
let xMin = Math.min(x1, x2),
|
|
451
|
-
yMin = Math.min(y1, y2),
|
|
452
|
-
PIHalf = Math.PI * 0.5;
|
|
453
|
-
|
|
454
|
-
// semi circles
|
|
455
|
-
if (diffX === 0 && diffY || diffY === 0 && diffX) {
|
|
456
|
-
|
|
457
|
-
r = diffX === 0 && diffY ? diffY / 2 : diffX / 2;
|
|
458
|
-
arcData.rx = r;
|
|
459
|
-
arcData.ry = r;
|
|
460
|
-
|
|
461
|
-
// verical
|
|
462
|
-
if (diffX === 0 && diffY) {
|
|
463
|
-
arcData.cx = x1;
|
|
464
|
-
arcData.cy = yMin + diffY / 2;
|
|
465
|
-
arcData.startAngle = y1 > y2 ? PIHalf : -PIHalf;
|
|
466
|
-
arcData.endAngle = y1 > y2 ? -PIHalf : PIHalf;
|
|
467
|
-
arcData.deltaAngle = sweep ? Math.PI : -Math.PI;
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
// horizontal
|
|
471
|
-
else if (diffY === 0 && diffX) {
|
|
472
|
-
arcData.cx = xMin + diffX / 2;
|
|
473
|
-
arcData.cy = y1;
|
|
474
|
-
arcData.startAngle = x1 > x2 ? Math.PI : 0;
|
|
475
|
-
arcData.endAngle = x1 > x2 ? -Math.PI : Math.PI;
|
|
476
|
-
arcData.deltaAngle = sweep ? Math.PI : -Math.PI;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return arcData;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
734
|
/**
|
|
484
735
|
* if rx===ry x-axis rotation is ignored
|
|
485
736
|
* otherwise convert degrees to radians
|
|
486
737
|
*/
|
|
487
|
-
let phi = rx === ry ? 0 :
|
|
738
|
+
let phi = rx === ry ? 0 : xAxisRotation * deg2rad;
|
|
488
739
|
let cx, cy;
|
|
489
740
|
|
|
490
741
|
let s_phi = !phi ? 0 : Math.sin(phi);
|
|
@@ -503,8 +754,9 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
503
754
|
// Step 3: Ensure radii are large enough
|
|
504
755
|
let lambda = (x1_ * x1_) / (rx * rx) + (y1_ * y1_) / (ry * ry);
|
|
505
756
|
if (lambda > 1) {
|
|
506
|
-
|
|
507
|
-
|
|
757
|
+
let lambdaRoot = Math.sqrt(lambda);
|
|
758
|
+
rx = rx * lambdaRoot;
|
|
759
|
+
ry = ry * lambdaRoot;
|
|
508
760
|
|
|
509
761
|
// save real rx/ry
|
|
510
762
|
arcData.rx = rx;
|
|
@@ -520,7 +772,7 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
520
772
|
throw Error("start point can not be same as end point");
|
|
521
773
|
}
|
|
522
774
|
let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
|
|
523
|
-
if (largeArc
|
|
775
|
+
if (largeArc === sweep) {
|
|
524
776
|
coe = -coe;
|
|
525
777
|
}
|
|
526
778
|
|
|
@@ -540,24 +792,33 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
540
792
|
* calculate angles between center point and
|
|
541
793
|
* commands starting and final on path point
|
|
542
794
|
*/
|
|
543
|
-
let startAngle = getAngle(cx, cy, x1, y1);
|
|
544
|
-
let endAngle = getAngle(cx, cy, x2, y2);
|
|
795
|
+
let startAngle = getAngle(cx, cy, x1, y1, normalize);
|
|
796
|
+
let endAngle = getAngle(cx, cy, x2, y2, normalize);
|
|
545
797
|
|
|
546
798
|
// adjust end angle
|
|
547
|
-
if (!sweep && endAngle > startAngle) {
|
|
548
|
-
|
|
549
|
-
endAngle -= Math.PI * 2;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (sweep && startAngle > endAngle) {
|
|
553
799
|
|
|
554
|
-
|
|
800
|
+
// Adjust angles based on sweep direction
|
|
801
|
+
if (sweep) {
|
|
802
|
+
// Clockwise
|
|
803
|
+
if (endAngle < startAngle) {
|
|
804
|
+
endAngle += Math.PI * 2;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
// Counterclockwise
|
|
808
|
+
if (endAngle > startAngle) {
|
|
809
|
+
endAngle -= Math.PI * 2;
|
|
810
|
+
}
|
|
555
811
|
}
|
|
556
812
|
|
|
557
813
|
let deltaAngle = endAngle - startAngle;
|
|
814
|
+
|
|
815
|
+
// The rest of your code remains the same
|
|
558
816
|
arcData.startAngle = startAngle;
|
|
817
|
+
arcData.startAngle_deg = startAngle * rad2Deg;
|
|
559
818
|
arcData.endAngle = endAngle;
|
|
819
|
+
arcData.endAngle_deg = endAngle * rad2Deg;
|
|
560
820
|
arcData.deltaAngle = deltaAngle;
|
|
821
|
+
arcData.deltaAngle_deg = deltaAngle * rad2Deg;
|
|
561
822
|
|
|
562
823
|
return arcData;
|
|
563
824
|
}
|
|
@@ -660,12 +921,6 @@ function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false }
|
|
|
660
921
|
return tArr;
|
|
661
922
|
}
|
|
662
923
|
|
|
663
|
-
/**
|
|
664
|
-
* based on Nikos M.'s answer
|
|
665
|
-
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
666
|
-
* https://stackoverflow.com/questions/87734/#75031511
|
|
667
|
-
* See also: https://github.com/foo123/Geometrize
|
|
668
|
-
*/
|
|
669
924
|
function getArcExtemes(p0, values) {
|
|
670
925
|
// compute point on ellipse from angle around ellipse (theta)
|
|
671
926
|
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
@@ -1063,7 +1318,7 @@ function getDistance(p1, p2, isArray = false) {
|
|
|
1063
1318
|
let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
|
|
1064
1319
|
let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
|
|
1065
1320
|
|
|
1066
|
-
return sqrt(dx * dx + dy * dy);
|
|
1321
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1067
1322
|
}
|
|
1068
1323
|
|
|
1069
1324
|
function getSquareDistance(p1, p2) {
|
|
@@ -1391,6 +1646,36 @@ function splitCommandAtTValues(p0, values, tArr, returnCommand = true) {
|
|
|
1391
1646
|
return segmentPoints;
|
|
1392
1647
|
}
|
|
1393
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
|
+
|
|
1394
1679
|
function detectAccuracy(pathData) {
|
|
1395
1680
|
let dims = [];
|
|
1396
1681
|
|
|
@@ -1412,7 +1697,7 @@ function detectAccuracy(pathData) {
|
|
|
1412
1697
|
|
|
1413
1698
|
let dim_min = dims.sort();
|
|
1414
1699
|
|
|
1415
|
-
let sliceIdx = Math.ceil(dim_min.length /
|
|
1700
|
+
let sliceIdx = Math.ceil(dim_min.length / 6);
|
|
1416
1701
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1417
1702
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
1418
1703
|
|
|
@@ -1424,38 +1709,29 @@ function detectAccuracy(pathData) {
|
|
|
1424
1709
|
|
|
1425
1710
|
}
|
|
1426
1711
|
|
|
1427
|
-
function roundTo(num = 0, decimals = 3) {
|
|
1428
|
-
if (!decimals) return Math.round(num);
|
|
1429
|
-
let factor = 10 ** decimals;
|
|
1430
|
-
return Math.round(num * factor) / factor;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
1712
|
/**
|
|
1434
|
-
*
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1713
|
+
* rounding helper
|
|
1714
|
+
* allows for quantized rounding
|
|
1715
|
+
* e.g 0.5 decimals s
|
|
1437
1716
|
*/
|
|
1438
|
-
function
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
let len = pathData.length;
|
|
1443
|
-
|
|
1444
|
-
let decimals = decimalsGlobal;
|
|
1717
|
+
function roundTo(num = 0, decimals = 3) {
|
|
1718
|
+
if (decimals < 0) return num;
|
|
1719
|
+
// Normal integer rounding
|
|
1720
|
+
if (!decimals) return Math.round(num);
|
|
1445
1721
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
let {values} = com;
|
|
1722
|
+
// stepped rounding
|
|
1723
|
+
let intPart = Math.floor(decimals);
|
|
1449
1724
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1725
|
+
if (intPart !== decimals) {
|
|
1726
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1727
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1452
1728
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}
|
|
1729
|
+
let step = 10 ** -intPart * f;
|
|
1730
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1456
1731
|
}
|
|
1457
1732
|
|
|
1458
|
-
|
|
1733
|
+
let factor = 10 ** decimals;
|
|
1734
|
+
return Math.round(num * factor) / factor;
|
|
1459
1735
|
}
|
|
1460
1736
|
|
|
1461
1737
|
/**
|
|
@@ -1843,20 +2119,24 @@ function getPolygonArea(points, absolute=false) {
|
|
|
1843
2119
|
* d attribute string
|
|
1844
2120
|
*/
|
|
1845
2121
|
|
|
1846
|
-
function pathDataToD(pathData,
|
|
1847
|
-
|
|
1848
|
-
optimize = parseFloat(optimize);
|
|
2122
|
+
function pathDataToD(pathData, mode = 0) {
|
|
1849
2123
|
|
|
2124
|
+
mode = parseFloat(mode);
|
|
2125
|
+
/*
|
|
2126
|
+
0 = max minification
|
|
2127
|
+
0.5 = safe
|
|
2128
|
+
1 = verbose
|
|
2129
|
+
2 = beautify
|
|
2130
|
+
*/
|
|
1850
2131
|
let len = pathData.length;
|
|
1851
|
-
let beautify = optimize > 1;
|
|
1852
|
-
let minify = beautify || optimize ? false : true;
|
|
1853
2132
|
|
|
1854
|
-
let d = '';
|
|
1855
2133
|
let valsString = pathData[0].values.join(" ");
|
|
1856
|
-
let separator_command =
|
|
1857
|
-
|
|
2134
|
+
let separator_command = mode > 1 ? `\n` :
|
|
2135
|
+
((mode < 1) ? '' : ' ');
|
|
2136
|
+
let separator_type = mode > 0.5 ? ' ' : '';
|
|
1858
2137
|
|
|
1859
|
-
|
|
2138
|
+
// 1st command
|
|
2139
|
+
let d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
|
|
1860
2140
|
|
|
1861
2141
|
for (let i = 1; i < len; i++) {
|
|
1862
2142
|
let com0 = pathData[i - 1];
|
|
@@ -1865,7 +2145,7 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1865
2145
|
valsString = '';
|
|
1866
2146
|
|
|
1867
2147
|
// Minify Arc commands (A/a) – actually sucks!
|
|
1868
|
-
if (
|
|
2148
|
+
if (!mode && (type === 'A' || type === 'a')) {
|
|
1869
2149
|
values = [
|
|
1870
2150
|
values[0], values[1], values[2],
|
|
1871
2151
|
`${values[3]}${values[4]}${values[5]}`,
|
|
@@ -1874,14 +2154,14 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1874
2154
|
}
|
|
1875
2155
|
|
|
1876
2156
|
// Omit type for repeated commands
|
|
1877
|
-
type = (
|
|
2157
|
+
type = ((mode < 1) && com0.type === com.type && com.type.toLowerCase() !== 'm')
|
|
1878
2158
|
? " "
|
|
1879
|
-
: (
|
|
2159
|
+
: ((mode < 1) && com0.type === "M" && com.type === "L"
|
|
1880
2160
|
? " "
|
|
1881
2161
|
: com.type);
|
|
1882
2162
|
|
|
1883
2163
|
// concatenate subsequent floating point values
|
|
1884
|
-
if (
|
|
2164
|
+
if (!mode) {
|
|
1885
2165
|
|
|
1886
2166
|
let prevWasFloat = false;
|
|
1887
2167
|
|
|
@@ -1905,22 +2185,23 @@ function pathDataToD(pathData, optimize = 0) {
|
|
|
1905
2185
|
prevWasFloat = isSmallFloat;
|
|
1906
2186
|
}
|
|
1907
2187
|
|
|
1908
|
-
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
1909
|
-
|
|
1910
2188
|
}
|
|
1911
2189
|
// regular non-minified output
|
|
1912
2190
|
else {
|
|
1913
|
-
|
|
2191
|
+
valsString = values.join(' ');
|
|
1914
2192
|
}
|
|
2193
|
+
|
|
2194
|
+
if(i===len-1) separator_command='';
|
|
2195
|
+
d += `${type}${separator_type}${valsString}${separator_command}`;
|
|
1915
2196
|
}
|
|
1916
2197
|
|
|
1917
|
-
if (
|
|
2198
|
+
if (mode < 1) {
|
|
1918
2199
|
d = d
|
|
1919
2200
|
.replace(/[A-Za-z]0(?=\.)/g, m => m[0])
|
|
1920
2201
|
.replace(/ 0\./g, " .") // Space before small decimals
|
|
1921
2202
|
.replace(/ -/g, "-") // Remove space before negatives
|
|
1922
2203
|
.replace(/-0\./g, "-.") // Remove leading zero from negative decimals
|
|
1923
|
-
.replace(/Z/g, "z");
|
|
2204
|
+
.replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
|
|
1924
2205
|
}
|
|
1925
2206
|
|
|
1926
2207
|
return d;
|
|
@@ -2260,9 +2541,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
2260
2541
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
2261
2542
|
|
|
2262
2543
|
// test new point-at-t against original mid segment starting point
|
|
2263
|
-
let
|
|
2544
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
2264
2545
|
|
|
2265
|
-
let dist0 = getDistManhattan(com1.p,
|
|
2546
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
2266
2547
|
let dist1 = 0, dist2 = 0;
|
|
2267
2548
|
let close = dist0 < maxDist;
|
|
2268
2549
|
let success = false;
|
|
@@ -2277,29 +2558,40 @@ function combineCubicPairs(com1, com2, {
|
|
|
2277
2558
|
* to prevent distortions
|
|
2278
2559
|
*/
|
|
2279
2560
|
|
|
2280
|
-
//
|
|
2281
|
-
let
|
|
2561
|
+
// 1st segment mid
|
|
2562
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
2282
2563
|
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
let
|
|
2286
|
-
dist1 = getDistManhattan(
|
|
2564
|
+
let t2 = t * 0.5;
|
|
2565
|
+
// combined interpolated mid point
|
|
2566
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
2567
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
2287
2568
|
|
|
2288
2569
|
error += dist1;
|
|
2289
2570
|
|
|
2290
2571
|
if (dist1 < maxDist) {
|
|
2291
2572
|
|
|
2292
|
-
//
|
|
2293
|
-
let
|
|
2573
|
+
// 2nd segment mid
|
|
2574
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
2294
2575
|
|
|
2295
|
-
|
|
2296
|
-
let
|
|
2297
|
-
|
|
2576
|
+
// simplified path
|
|
2577
|
+
let t3 = (1 + t) * 0.5;
|
|
2578
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
2579
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
2298
2580
|
|
|
2299
2581
|
error += dist2;
|
|
2300
2582
|
|
|
2301
2583
|
if (error < maxDist) success = true;
|
|
2302
2584
|
|
|
2585
|
+
/*
|
|
2586
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
2587
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
2588
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
2589
|
+
|
|
2590
|
+
renderPoint(markers, com1.p, 'green')
|
|
2591
|
+
|
|
2592
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
2593
|
+
*/
|
|
2594
|
+
|
|
2303
2595
|
}
|
|
2304
2596
|
|
|
2305
2597
|
} // end 1st try
|
|
@@ -2464,6 +2756,7 @@ function analyzePathData(pathData = [], {
|
|
|
2464
2756
|
let com = pathData[c - 1];
|
|
2465
2757
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
2466
2758
|
|
|
2759
|
+
let comPrev = pathData[c-2];
|
|
2467
2760
|
let comN = pathData[c] || null;
|
|
2468
2761
|
|
|
2469
2762
|
// init properties
|
|
@@ -2482,6 +2775,7 @@ function analyzePathData(pathData = [], {
|
|
|
2482
2775
|
|
|
2483
2776
|
// bezier types
|
|
2484
2777
|
let isBezier = type === 'Q' || type === 'C';
|
|
2778
|
+
let isArc = type === 'A';
|
|
2485
2779
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
2486
2780
|
|
|
2487
2781
|
/**
|
|
@@ -2528,6 +2822,22 @@ function analyzePathData(pathData = [], {
|
|
|
2528
2822
|
}
|
|
2529
2823
|
}
|
|
2530
2824
|
|
|
2825
|
+
// check extremes introduce by small arcs
|
|
2826
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
2827
|
+
let distN = comN ? comN.dimA : 0;
|
|
2828
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
2829
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
2830
|
+
|
|
2831
|
+
if(isShort && smallRadius){
|
|
2832
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
2833
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
2834
|
+
hasExtremes = true;
|
|
2835
|
+
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2531
2841
|
if (hasExtremes) com.extreme = true;
|
|
2532
2842
|
|
|
2533
2843
|
// Corners and semi extremes
|
|
@@ -2797,6 +3107,7 @@ const sanitizeArc = (val='', valueIndex=0) => {
|
|
|
2797
3107
|
};
|
|
2798
3108
|
|
|
2799
3109
|
function parsePathDataString(d, debug = true, limit=0) {
|
|
3110
|
+
if(!d) return []
|
|
2800
3111
|
d = d.trim();
|
|
2801
3112
|
|
|
2802
3113
|
if(limit) console.log('!!!limit', limit);
|
|
@@ -3080,50 +3391,10 @@ function parsePathDataString(d, debug = true, limit=0) {
|
|
|
3080
3391
|
|
|
3081
3392
|
}
|
|
3082
3393
|
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
toLonghands = true,
|
|
3088
|
-
|
|
3089
|
-
// not necessary unless you need cubics only
|
|
3090
|
-
quadraticToCubic = false,
|
|
3091
|
-
|
|
3092
|
-
// mostly a fallback if arc calculations fail
|
|
3093
|
-
arcToCubic = false,
|
|
3094
|
-
// arc to cubic precision - adds more segments for better precision
|
|
3095
|
-
arcAccuracy = 4,
|
|
3096
|
-
} = {}
|
|
3097
|
-
) {
|
|
3098
|
-
|
|
3099
|
-
// is already array
|
|
3100
|
-
let isArray = Array.isArray(d);
|
|
3101
|
-
|
|
3102
|
-
// normalize native pathData to regular array
|
|
3103
|
-
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3104
|
-
/*
|
|
3105
|
-
if (hasConstructor) {
|
|
3106
|
-
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3107
|
-
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3108
|
-
}
|
|
3109
|
-
*/
|
|
3110
|
-
|
|
3111
|
-
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3112
|
-
|
|
3113
|
-
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3114
|
-
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3115
|
-
|
|
3116
|
-
// normalize
|
|
3117
|
-
pathData = normalizePathData(pathData,
|
|
3118
|
-
{
|
|
3119
|
-
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3120
|
-
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3121
|
-
},
|
|
3122
|
-
);
|
|
3123
|
-
|
|
3124
|
-
return pathData;
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3394
|
+
/**
|
|
3395
|
+
* wrapper function for
|
|
3396
|
+
* all path data conversion
|
|
3397
|
+
*/
|
|
3127
3398
|
function convertPathData(pathData, {
|
|
3128
3399
|
toShorthands = true,
|
|
3129
3400
|
toLonghands = false,
|
|
@@ -3139,6 +3410,7 @@ function convertPathData(pathData, {
|
|
|
3139
3410
|
hasShorthands = true,
|
|
3140
3411
|
hasQuadratics = true,
|
|
3141
3412
|
hasArcs = true,
|
|
3413
|
+
isPoly = false,
|
|
3142
3414
|
optimizeArcs = true,
|
|
3143
3415
|
testTypes = false
|
|
3144
3416
|
|
|
@@ -3159,6 +3431,7 @@ function convertPathData(pathData, {
|
|
|
3159
3431
|
|
|
3160
3432
|
// some params exclude each other
|
|
3161
3433
|
toRelative = toAbsolute ? false : toRelative;
|
|
3434
|
+
|
|
3162
3435
|
toShorthands = toLonghands ? false : toShorthands;
|
|
3163
3436
|
|
|
3164
3437
|
if (toAbsolute) pathData = pathDataToAbsolute(pathData);
|
|
@@ -3173,22 +3446,24 @@ function convertPathData(pathData, {
|
|
|
3173
3446
|
|
|
3174
3447
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3175
3448
|
|
|
3176
|
-
if(toMixed) toRelative = true;
|
|
3449
|
+
if (toMixed) toRelative = true;
|
|
3177
3450
|
|
|
3178
3451
|
// pre round - before relative conversion to minimize distortions
|
|
3179
3452
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
3180
3453
|
|
|
3181
3454
|
// clone absolute pathdata
|
|
3182
|
-
if(toMixed){
|
|
3455
|
+
if (toMixed) {
|
|
3183
3456
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
3184
3457
|
}
|
|
3185
3458
|
|
|
3186
3459
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
3460
|
+
|
|
3461
|
+
// final rounding
|
|
3187
3462
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
3188
3463
|
|
|
3189
3464
|
// choose most compact commands: relative or absolute
|
|
3190
|
-
if(toMixed){
|
|
3191
|
-
for(let i=0; i<pathData.length; i++){
|
|
3465
|
+
if (toMixed) {
|
|
3466
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
3192
3467
|
let com = pathData[i];
|
|
3193
3468
|
let comA = pathDataAbs[i];
|
|
3194
3469
|
// compare Lengths
|
|
@@ -3198,7 +3473,7 @@ function convertPathData(pathData, {
|
|
|
3198
3473
|
let lenR = comStr.length;
|
|
3199
3474
|
let lenA = comStrA.length;
|
|
3200
3475
|
|
|
3201
|
-
if(lenA<lenR){
|
|
3476
|
+
if (lenA < lenR) {
|
|
3202
3477
|
|
|
3203
3478
|
pathData[i] = pathDataAbs[i];
|
|
3204
3479
|
}
|
|
@@ -3208,81 +3483,147 @@ function convertPathData(pathData, {
|
|
|
3208
3483
|
return pathData
|
|
3209
3484
|
}
|
|
3210
3485
|
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
function optimizeArcPathData(pathData = []) {
|
|
3218
|
-
|
|
3219
|
-
let remove =[];
|
|
3486
|
+
function parsePathDataNormalized(d,
|
|
3487
|
+
{
|
|
3488
|
+
// necessary for most calculations
|
|
3489
|
+
toAbsolute = true,
|
|
3490
|
+
toLonghands = true,
|
|
3220
3491
|
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
if (type === 'A') {
|
|
3224
|
-
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
3225
|
-
let comPrev = pathData[i - 1];
|
|
3226
|
-
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
3227
|
-
let M = { x: x0, y: y0 };
|
|
3228
|
-
let p = { x, y };
|
|
3492
|
+
// not necessary unless you need cubics only
|
|
3493
|
+
quadraticToCubic = false,
|
|
3229
3494
|
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3495
|
+
// mostly a fallback if arc calculations fail
|
|
3496
|
+
arcToCubic = false,
|
|
3497
|
+
// arc to cubic precision - adds more segments for better precision
|
|
3498
|
+
arcAccuracy = 4,
|
|
3499
|
+
} = {}
|
|
3500
|
+
) {
|
|
3233
3501
|
|
|
3234
|
-
|
|
3502
|
+
// is already array
|
|
3503
|
+
let isArray = Array.isArray(d);
|
|
3235
3504
|
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3505
|
+
// normalize native pathData to regular array
|
|
3506
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3507
|
+
/*
|
|
3508
|
+
if (hasConstructor) {
|
|
3509
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3510
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3511
|
+
}
|
|
3512
|
+
*/
|
|
3239
3513
|
|
|
3240
|
-
|
|
3241
|
-
if (diff < 0.01) {
|
|
3514
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3242
3515
|
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
let distM = getDistance(pMid, M);
|
|
3246
|
-
let rDiff = Math.abs(distM - rx) / rx;
|
|
3516
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3517
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3247
3518
|
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
});
|
|
3519
|
+
// normalize
|
|
3520
|
+
pathData = normalizePathData(pathData,
|
|
3521
|
+
{
|
|
3522
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3523
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3524
|
+
},
|
|
3525
|
+
);
|
|
3258
3526
|
|
|
3259
|
-
if(remove.length) pathData = pathData.filter(Boolean);
|
|
3260
3527
|
return pathData;
|
|
3261
3528
|
}
|
|
3262
3529
|
|
|
3263
3530
|
/**
|
|
3264
|
-
*
|
|
3531
|
+
*
|
|
3532
|
+
* @param {*} pathData
|
|
3533
|
+
* @returns
|
|
3265
3534
|
*/
|
|
3266
3535
|
|
|
3267
|
-
function
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
toLonghands = true,
|
|
3271
|
-
quadraticToCubic = false,
|
|
3272
|
-
arcToCubic = false,
|
|
3273
|
-
arcAccuracy = 2,
|
|
3536
|
+
function optimizeArcPathData(pathData = []) {
|
|
3537
|
+
let l = pathData.length;
|
|
3538
|
+
let pathDataN = [];
|
|
3274
3539
|
|
|
3275
|
-
|
|
3276
|
-
|
|
3540
|
+
for (let i = 0; i < l; i++) {
|
|
3541
|
+
let com = pathData[i];
|
|
3542
|
+
let { type, values } = com;
|
|
3277
3543
|
|
|
3278
|
-
|
|
3279
|
-
)
|
|
3544
|
+
if (type !== 'A') {
|
|
3545
|
+
pathDataN.push(com);
|
|
3546
|
+
continue
|
|
3547
|
+
}
|
|
3280
3548
|
|
|
3281
|
-
|
|
3549
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
3550
|
+
let comPrev = pathData[i - 1];
|
|
3551
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
3552
|
+
let M = { x: x0, y: y0 };
|
|
3553
|
+
let p = { x, y };
|
|
3554
|
+
|
|
3555
|
+
if (rx === 0 || ry === 0) {
|
|
3556
|
+
pathData[i] = null;
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
// test for elliptic
|
|
3560
|
+
let rat = rx / ry;
|
|
3561
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
3562
|
+
|
|
3563
|
+
if (error > 0.01) {
|
|
3564
|
+
|
|
3565
|
+
pathDataN.push(com);
|
|
3566
|
+
continue
|
|
3567
|
+
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
3571
|
+
com.values[2] = 0;
|
|
3572
|
+
|
|
3573
|
+
/**
|
|
3574
|
+
* test semi circles
|
|
3575
|
+
* rx and ry are large enough
|
|
3576
|
+
*/
|
|
3577
|
+
|
|
3578
|
+
// 1. horizontal or vertical
|
|
3579
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
3580
|
+
let diffX = Math.abs(x - x0);
|
|
3581
|
+
let diffY = Math.abs(y - y0);
|
|
3582
|
+
|
|
3583
|
+
let isHorizontal = diffY < thresh;
|
|
3584
|
+
let isVertical = diffX < thresh;
|
|
3585
|
+
|
|
3586
|
+
// minify rx and ry
|
|
3587
|
+
if (isHorizontal || isVertical) {
|
|
3588
|
+
|
|
3589
|
+
// check if semi circle
|
|
3590
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
3591
|
+
|
|
3592
|
+
// is semicircle we can simplify rx
|
|
3593
|
+
if (!needsTrueR) {
|
|
3594
|
+
|
|
3595
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
com.values[0] = rx;
|
|
3599
|
+
com.values[1] = rx;
|
|
3600
|
+
pathDataN.push(com);
|
|
3601
|
+
continue
|
|
3602
|
+
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
3606
|
+
let r = getDistance(M, p) * 0.5;
|
|
3607
|
+
error = rx / r;
|
|
3608
|
+
|
|
3609
|
+
if (error < 0.5) {
|
|
3610
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
com.values[0] = rx;
|
|
3614
|
+
com.values[1] = rx;
|
|
3615
|
+
pathDataN.push(com);
|
|
3616
|
+
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
return pathDataN;
|
|
3282
3620
|
}
|
|
3283
3621
|
|
|
3284
|
-
|
|
3285
|
-
|
|
3622
|
+
/**
|
|
3623
|
+
* parse normalized
|
|
3624
|
+
*/
|
|
3625
|
+
|
|
3626
|
+
function normalizePathData(pathData = [],
|
|
3286
3627
|
{
|
|
3287
3628
|
toAbsolute = true,
|
|
3288
3629
|
toLonghands = true,
|
|
@@ -3296,31 +3637,8 @@ export function normalizePathData(pathData = [],
|
|
|
3296
3637
|
} = {}
|
|
3297
3638
|
) {
|
|
3298
3639
|
|
|
3299
|
-
|
|
3300
|
-
if (testTypes) {
|
|
3301
|
-
|
|
3302
|
-
let commands = Array.from(new Set(pathData.map(com => com.type))).join('');
|
|
3303
|
-
hasRelatives = /[lcqamts]/gi.test(commands);
|
|
3304
|
-
hasQuadratics = /[qt]/gi.test(commands);
|
|
3305
|
-
hasArcs = /[a]/gi.test(commands);
|
|
3306
|
-
hasShorthands = /[vhst]/gi.test(commands);
|
|
3307
|
-
isPoly = /[mlz]/gi.test(commands);
|
|
3308
|
-
}
|
|
3309
|
-
|
|
3310
|
-
if ((hasQuadratics && quadraticToCubic) || (hasArcs && arcToCubic)) {
|
|
3311
|
-
toLonghands = true
|
|
3312
|
-
toAbsolute = true
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
|
-
if (hasRelatives && toAbsolute) pathData = pathDataToAbsoluteOrRelative(pathData, false);
|
|
3316
|
-
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData, -1, false);
|
|
3317
|
-
if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData, arcAccuracy);
|
|
3318
|
-
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3319
|
-
|
|
3320
|
-
return pathData;
|
|
3321
|
-
|
|
3640
|
+
return convertPathData(pathData, { toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy, hasRelatives, hasShorthands, hasQuadratics, hasArcs, testTypes, decimals: -1 })
|
|
3322
3641
|
}
|
|
3323
|
-
*/
|
|
3324
3642
|
|
|
3325
3643
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
3326
3644
|
|
|
@@ -4201,7 +4519,7 @@ function pathDataToTopLeft(pathData) {
|
|
|
4201
4519
|
let { type, values } = com;
|
|
4202
4520
|
let valsLen = values.length;
|
|
4203
4521
|
if (valsLen) {
|
|
4204
|
-
let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0};
|
|
4522
|
+
let p = { type: type, x: values[valsLen - 2], y: values[valsLen - 1], index: 0 };
|
|
4205
4523
|
p.index = i;
|
|
4206
4524
|
indices.push(p);
|
|
4207
4525
|
}
|
|
@@ -4209,113 +4527,111 @@ function pathDataToTopLeft(pathData) {
|
|
|
4209
4527
|
|
|
4210
4528
|
// reorder to top left most
|
|
4211
4529
|
|
|
4212
|
-
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x
|
|
4530
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
4213
4531
|
newIndex = indices[0].index;
|
|
4214
4532
|
|
|
4215
|
-
return
|
|
4533
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
4216
4534
|
}
|
|
4217
4535
|
|
|
4218
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
4536
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
4219
4537
|
|
|
4220
|
-
let
|
|
4538
|
+
let pathDataN = pathData;
|
|
4221
4539
|
let l = pathData.length;
|
|
4222
4540
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
4223
4541
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4224
4542
|
|
|
4225
|
-
let
|
|
4226
|
-
|
|
4227
|
-
// check if order is ideal
|
|
4228
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
4543
|
+
let hasLinetos = false;
|
|
4229
4544
|
|
|
4545
|
+
// check if path is closed by explicit lineto
|
|
4546
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
4230
4547
|
let penultimateCom = pathData[idxPenultimate];
|
|
4231
4548
|
let penultimateType = penultimateCom.type;
|
|
4232
4549
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
4233
4550
|
|
|
4234
4551
|
// last L command ends at M
|
|
4235
|
-
let
|
|
4552
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4553
|
+
let lastIsLine = penultimateType === 'L';
|
|
4236
4554
|
|
|
4237
|
-
//
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
let valsLast = pathData[idxPenultimate].values
|
|
4243
|
-
let valsLastLen = valsLast.length;
|
|
4244
|
-
pathData[idxPenultimate].values[valsLastLen-2] = M.x
|
|
4245
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
4246
|
-
*/
|
|
4247
|
-
|
|
4248
|
-
pathData.push({type:'Z', values:[]});
|
|
4249
|
-
isClosed = true;
|
|
4250
|
-
l++;
|
|
4251
|
-
}
|
|
4555
|
+
// create index
|
|
4556
|
+
let indices = [];
|
|
4557
|
+
for (let i = 0; i < l; i++) {
|
|
4558
|
+
let com = pathData[i];
|
|
4559
|
+
let { type, values, p0, p } = com;
|
|
4252
4560
|
|
|
4253
|
-
|
|
4254
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
4255
|
-
skipReorder = false;
|
|
4561
|
+
if(type==='L') hasLinetos = true;
|
|
4256
4562
|
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
}
|
|
4563
|
+
// exclude Z
|
|
4564
|
+
if (values.length) {
|
|
4565
|
+
values.slice(-2);
|
|
4261
4566
|
|
|
4262
|
-
|
|
4567
|
+
let x = Math.min(p0.x, p.x);
|
|
4568
|
+
let y = Math.min(p0.y, p.y);
|
|
4263
4569
|
|
|
4264
|
-
|
|
4570
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
4571
|
+
let prevType = prevCom.type;
|
|
4265
4572
|
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
let { type, values } = com;
|
|
4270
|
-
if (values.length) {
|
|
4271
|
-
let valsL = values.slice(-2);
|
|
4272
|
-
let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
|
|
4273
|
-
let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
|
|
4274
|
-
let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
|
|
4275
|
-
let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
|
|
4276
|
-
let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom };
|
|
4277
|
-
p.index = i;
|
|
4278
|
-
indices.push(p);
|
|
4279
|
-
}
|
|
4573
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
4574
|
+
item.index = i;
|
|
4575
|
+
indices.push(item);
|
|
4280
4576
|
}
|
|
4281
4577
|
|
|
4282
|
-
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
let xMin = Infinity;
|
|
4581
|
+
let yMin = Infinity;
|
|
4582
|
+
let idx_top = null;
|
|
4583
|
+
let len = indices.length;
|
|
4283
4584
|
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4585
|
+
for (let i = 0; i < len; i++) {
|
|
4586
|
+
let com = indices[i];
|
|
4587
|
+
let { type, index, x, y, prevType } = com;
|
|
4287
4588
|
|
|
4288
|
-
|
|
4589
|
+
if (hasLinetos && prevType === 'L') {
|
|
4590
|
+
if (x < xMin && y < yMin) {
|
|
4591
|
+
idx_top = index-1;
|
|
4592
|
+
}
|
|
4289
4593
|
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
4294
|
-
newIndex = indices[0].index;
|
|
4295
|
-
}
|
|
4594
|
+
if (y < yMin) {
|
|
4595
|
+
yMin = y;
|
|
4596
|
+
}
|
|
4296
4597
|
|
|
4297
|
-
|
|
4298
|
-
|
|
4598
|
+
if (x < xMin) {
|
|
4599
|
+
xMin = x;
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4299
4602
|
}
|
|
4300
4603
|
|
|
4301
|
-
|
|
4604
|
+
// shift to better starting point
|
|
4605
|
+
if (idx_top) {
|
|
4606
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
4302
4607
|
|
|
4303
|
-
|
|
4608
|
+
// update penultimate - reorder might have added new close paths
|
|
4609
|
+
l = pathDataN.length;
|
|
4610
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
4304
4611
|
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4612
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
4613
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
4614
|
+
penultimateType = penultimateCom.type;
|
|
4615
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
4616
|
+
lastIsLine = penultimateType ==='L';
|
|
4617
|
+
|
|
4618
|
+
// last L command ends at M
|
|
4619
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4620
|
+
|
|
4621
|
+
}
|
|
4309
4622
|
|
|
4310
|
-
|
|
4623
|
+
// remove unnecessary closing lineto
|
|
4624
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
4625
|
+
pathDataN.splice(l - 2, 1);
|
|
4626
|
+
}
|
|
4311
4627
|
|
|
4312
|
-
|
|
4313
|
-
|
|
4628
|
+
// add close path
|
|
4629
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
4630
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
4314
4631
|
}
|
|
4315
4632
|
|
|
4316
|
-
|
|
4633
|
+
return pathDataN
|
|
4317
4634
|
|
|
4318
|
-
return pathDataNew
|
|
4319
4635
|
}
|
|
4320
4636
|
|
|
4321
4637
|
/**
|
|
@@ -4525,8 +4841,130 @@ function refineAdjacentExtremes(pathData, {
|
|
|
4525
4841
|
|
|
4526
4842
|
}
|
|
4527
4843
|
|
|
4844
|
+
function getArcFromPoly(pts, precise = false) {
|
|
4845
|
+
if (pts.length < 3) return false
|
|
4846
|
+
|
|
4847
|
+
// Pick 3 well-spaced points
|
|
4848
|
+
let len = pts.length;
|
|
4849
|
+
let idx1 = Math.floor(len * 0.333);
|
|
4850
|
+
let idx2 = Math.floor(len * 0.666);
|
|
4851
|
+
let idx3 = Math.floor(len * 0.5);
|
|
4852
|
+
|
|
4853
|
+
let p1 = pts[0];
|
|
4854
|
+
let p2 = pts[idx3];
|
|
4855
|
+
let p3 = pts[len - 1];
|
|
4856
|
+
|
|
4857
|
+
// Radius (use start point)
|
|
4858
|
+
let pts1 = [p1, p2, p3];
|
|
4859
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
4860
|
+
|
|
4861
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
4862
|
+
|
|
4863
|
+
// check if radii are consistent
|
|
4864
|
+
if (precise) {
|
|
4865
|
+
|
|
4866
|
+
/**
|
|
4867
|
+
* check multiple centroids
|
|
4868
|
+
* if the polyline can be expressed as
|
|
4869
|
+
* an arc - all centroids should be close
|
|
4870
|
+
*/
|
|
4871
|
+
|
|
4872
|
+
if (len > 3) {
|
|
4873
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
4874
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
4875
|
+
|
|
4876
|
+
if (!centroid1 || !centroid2) return false;
|
|
4877
|
+
|
|
4878
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
4879
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
4880
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
4881
|
+
let errorCentroid = (dist1 + dist2);
|
|
4882
|
+
|
|
4883
|
+
// centroids diverging too much
|
|
4884
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
4885
|
+
|
|
4886
|
+
return false
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
4892
|
+
|
|
4893
|
+
for (let i = 0; i < len; i++) {
|
|
4894
|
+
let pt = pts[i];
|
|
4895
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
4896
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
4897
|
+
|
|
4898
|
+
if (error > 0.0025) {
|
|
4899
|
+
/*
|
|
4900
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
4901
|
+
renderPoint(markers, centroid, 'orange')
|
|
4902
|
+
renderPoint(markers, p1, 'green')
|
|
4903
|
+
renderPoint(markers, p2)
|
|
4904
|
+
renderPoint(markers, p3, 'purple')
|
|
4905
|
+
*/
|
|
4906
|
+
return false;
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
// calculate proper radius
|
|
4911
|
+
r = Math.sqrt(rSqMid);
|
|
4912
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
4913
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
4914
|
+
|
|
4915
|
+
} else {
|
|
4916
|
+
r = getDistance(centroid, p1);
|
|
4917
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
4918
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
return {
|
|
4922
|
+
centroid,
|
|
4923
|
+
r,
|
|
4924
|
+
startAngle,
|
|
4925
|
+
endAngle,
|
|
4926
|
+
deltaAngle
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
function getPolyArcCentroid(pts = []) {
|
|
4931
|
+
|
|
4932
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
4933
|
+
if (pts.length < 3) return false
|
|
4934
|
+
|
|
4935
|
+
let p1 = pts[0];
|
|
4936
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
4937
|
+
let p3 = pts[pts.length - 1];
|
|
4938
|
+
|
|
4939
|
+
let x1 = p1.x, y1 = p1.y;
|
|
4940
|
+
let x2 = p2.x, y2 = p2.y;
|
|
4941
|
+
let x3 = p3.x, y3 = p3.y;
|
|
4942
|
+
|
|
4943
|
+
let a = x1 - x2;
|
|
4944
|
+
let b = y1 - y2;
|
|
4945
|
+
let c = x1 - x3;
|
|
4946
|
+
let d = y1 - y3;
|
|
4947
|
+
|
|
4948
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
4949
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
4950
|
+
|
|
4951
|
+
let det = a * d - b * c;
|
|
4952
|
+
|
|
4953
|
+
// colinear points
|
|
4954
|
+
if (Math.abs(det) < 1e-10) {
|
|
4955
|
+
return false;
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
// find center of arc
|
|
4959
|
+
let cx = (d * e - b * f) / det;
|
|
4960
|
+
let cy = (-c * e + a * f) / det;
|
|
4961
|
+
let centroid = { x: cx, y: cy };
|
|
4962
|
+
return centroid
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4528
4965
|
function refineRoundedCorners(pathData, {
|
|
4529
4966
|
threshold = 0,
|
|
4967
|
+
simplifyQuadraticCorners = false,
|
|
4530
4968
|
tolerance = 1
|
|
4531
4969
|
} = {}) {
|
|
4532
4970
|
|
|
@@ -4551,6 +4989,9 @@ function refineRoundedCorners(pathData, {
|
|
|
4551
4989
|
let firstIsLine = pathData[1].type === 'L';
|
|
4552
4990
|
let firstIsBez = pathData[1].type === 'C';
|
|
4553
4991
|
|
|
4992
|
+
// in case we have simplified a corner connecting to the start
|
|
4993
|
+
let M_adj = null;
|
|
4994
|
+
|
|
4554
4995
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
4555
4996
|
|
|
4556
4997
|
// normalize closepath to lineto
|
|
@@ -4590,15 +5031,17 @@ function refineRoundedCorners(pathData, {
|
|
|
4590
5031
|
// closing corner to start
|
|
4591
5032
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
4592
5033
|
comL1 = pathData[1];
|
|
5034
|
+
|
|
4593
5035
|
comBez = [pathData[l - lastOff]];
|
|
4594
5036
|
|
|
4595
5037
|
}
|
|
4596
5038
|
|
|
5039
|
+
// collect enclosed bezier segments
|
|
4597
5040
|
for (let j = i + 1; j < l; j++) {
|
|
4598
5041
|
let comN = pathData[j] ? pathData[j] : null;
|
|
4599
5042
|
let comPrev = pathData[j - 1];
|
|
4600
5043
|
|
|
4601
|
-
if (comPrev.type === 'C') {
|
|
5044
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
4602
5045
|
comBez.push(comPrev);
|
|
4603
5046
|
}
|
|
4604
5047
|
|
|
@@ -4629,39 +5072,67 @@ function refineRoundedCorners(pathData, {
|
|
|
4629
5072
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
4630
5073
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
4631
5074
|
|
|
5075
|
+
/*
|
|
5076
|
+
*/
|
|
5077
|
+
|
|
4632
5078
|
if (comBez.length && !signChange && isSmall) {
|
|
4633
5079
|
|
|
4634
|
-
let
|
|
5080
|
+
let isSquare = false;
|
|
5081
|
+
|
|
5082
|
+
if (comBez.length === 1) {
|
|
5083
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
5084
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
5085
|
+
let diff = (dx - dy);
|
|
5086
|
+
let rat = Math.abs(diff / dx);
|
|
5087
|
+
isSquare = rat < 0.01;
|
|
5088
|
+
}
|
|
5089
|
+
|
|
5090
|
+
let preferArcs = true;
|
|
5091
|
+
preferArcs = false;
|
|
5092
|
+
|
|
5093
|
+
// if rectangular prefer arcs
|
|
5094
|
+
if (preferArcs && isSquare) {
|
|
5095
|
+
|
|
5096
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
5097
|
+
|
|
5098
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
5099
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
5100
|
+
|
|
5101
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
5102
|
+
|
|
5103
|
+
let largeArc = 0;
|
|
5104
|
+
|
|
5105
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
5106
|
+
|
|
5107
|
+
pathDataN.push(comL0, comArc);
|
|
5108
|
+
i += offset;
|
|
5109
|
+
continue
|
|
5110
|
+
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
5114
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
5115
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
5116
|
+
|
|
4635
5117
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
4636
5118
|
|
|
4637
|
-
|
|
5119
|
+
// exit: is rather flat or has no intersection
|
|
5120
|
+
|
|
5121
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
4638
5122
|
pathDataN.push(com);
|
|
4639
5123
|
continue
|
|
4640
5124
|
}
|
|
4641
5125
|
|
|
4642
|
-
// check sign change
|
|
5126
|
+
// check sign change - exit if present
|
|
4643
5127
|
if (ptQ) {
|
|
4644
5128
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
4645
5129
|
let area0_abs = Math.abs(area0);
|
|
4646
5130
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
4647
5131
|
let area1_abs = Math.abs(area1);
|
|
4648
5132
|
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs;
|
|
4649
|
-
|
|
4650
|
-
/*
|
|
4651
|
-
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
4652
|
-
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
4653
|
-
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
4654
|
-
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
4655
|
-
if(!area0) {
|
|
4656
|
-
pathDataN.push(com);
|
|
4657
|
-
continue
|
|
4658
|
-
}
|
|
4659
|
-
*/
|
|
4660
|
-
|
|
4661
5133
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
4662
5134
|
|
|
4663
5135
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
4664
|
-
|
|
4665
5136
|
pathDataN.push(com);
|
|
4666
5137
|
continue
|
|
4667
5138
|
}
|
|
@@ -4676,24 +5147,67 @@ function refineRoundedCorners(pathData, {
|
|
|
4676
5147
|
|
|
4677
5148
|
// not in tolerance – return original command
|
|
4678
5149
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
4679
|
-
|
|
4680
5150
|
pathDataN.push(com);
|
|
4681
5151
|
continue;
|
|
4682
5152
|
|
|
4683
|
-
}
|
|
5153
|
+
}
|
|
4684
5154
|
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
comQ.cp1 = ptQ;
|
|
4688
|
-
comQ.p = comL1.p0;
|
|
5155
|
+
// return simplified quadratic Bézier command
|
|
5156
|
+
let p_Q = comL1.p0;
|
|
4689
5157
|
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
5158
|
+
// adjust previous end point to better fit the cubic curvature
|
|
5159
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
5160
|
+
|
|
5161
|
+
if (adjustQ) {
|
|
5162
|
+
|
|
5163
|
+
let t = 0.1666;
|
|
5164
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
5165
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
5166
|
+
|
|
5167
|
+
// round for large enough segments
|
|
5168
|
+
let isH = ptQ.y===comL0.p.y;
|
|
5169
|
+
let isV = ptQ.x===comL0.p.x;
|
|
5170
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
5171
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
5172
|
+
|
|
5173
|
+
if(isSquare && com.dimA>3){
|
|
5174
|
+
let dec = 0.5;
|
|
5175
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
5176
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
5177
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
5178
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
/*
|
|
5182
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
5183
|
+
renderPoint(markers, p_Q, 'orange')
|
|
5184
|
+
renderPoint(markers, comL0.p, 'green')
|
|
5185
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
5186
|
+
*/
|
|
5187
|
+
|
|
5188
|
+
// set new M starting point
|
|
5189
|
+
if (i === l - lastOff - 1) {
|
|
5190
|
+
|
|
5191
|
+
M_adj = p_Q;
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
// adjust previous lineto end point
|
|
5195
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
5196
|
+
comL0.p = p0_adj;
|
|
4693
5197
|
|
|
4694
|
-
continue;
|
|
4695
5198
|
}
|
|
4696
5199
|
|
|
5200
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
5201
|
+
comQ.cp1 = ptQ;
|
|
5202
|
+
comQ.p0 = comL0.p;
|
|
5203
|
+
comQ.p = p_Q;
|
|
5204
|
+
|
|
5205
|
+
// add quadratic command
|
|
5206
|
+
pathDataN.push(comL0, comQ);
|
|
5207
|
+
|
|
5208
|
+
i += offset;
|
|
5209
|
+
continue;
|
|
5210
|
+
|
|
4697
5211
|
}
|
|
4698
5212
|
}
|
|
4699
5213
|
}
|
|
@@ -4707,6 +5221,12 @@ function refineRoundedCorners(pathData, {
|
|
|
4707
5221
|
|
|
4708
5222
|
}
|
|
4709
5223
|
|
|
5224
|
+
// correct starting point connecting with last corner rounding
|
|
5225
|
+
if (M_adj) {
|
|
5226
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
5227
|
+
pathDataN[0].p0 = M_adj;
|
|
5228
|
+
}
|
|
5229
|
+
|
|
4710
5230
|
// revert close path normalization
|
|
4711
5231
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
4712
5232
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -4716,51 +5236,101 @@ function refineRoundedCorners(pathData, {
|
|
|
4716
5236
|
|
|
4717
5237
|
}
|
|
4718
5238
|
|
|
4719
|
-
function
|
|
4720
|
-
|
|
5239
|
+
function refineClosingCommand(pathData = [], {
|
|
5240
|
+
threshold = 0,
|
|
5241
|
+
} = {}) {
|
|
4721
5242
|
|
|
4722
|
-
|
|
4723
|
-
let
|
|
4724
|
-
let
|
|
4725
|
-
let
|
|
5243
|
+
let l = pathData.length;
|
|
5244
|
+
let comLast = pathData[l - 1];
|
|
5245
|
+
let isClosed = comLast.type.toLowerCase() === 'z';
|
|
5246
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
5247
|
+
let comPenultimate = isClosed ? pathData[idxPenultimate] : pathData[idxPenultimate];
|
|
5248
|
+
let valsPen = comPenultimate.values.slice(-2);
|
|
4726
5249
|
|
|
4727
|
-
let
|
|
4728
|
-
let
|
|
4729
|
-
let
|
|
5250
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
5251
|
+
let pPen = { x: valsPen[0], y: valsPen[1] };
|
|
5252
|
+
let dist = getDistAv(M, pPen);
|
|
4730
5253
|
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
let c = x1 - x3;
|
|
4734
|
-
let d = y1 - y3;
|
|
5254
|
+
// adjust last coordinates for better reordering
|
|
5255
|
+
if (dist && dist < threshold) {
|
|
4735
5256
|
|
|
4736
|
-
|
|
4737
|
-
|
|
5257
|
+
let valsLast = pathData[idxPenultimate].values;
|
|
5258
|
+
let valsLastLen = valsLast.length;
|
|
5259
|
+
pathData[idxPenultimate].values[valsLastLen - 2] = M.x;
|
|
5260
|
+
pathData[idxPenultimate].values[valsLastLen - 1] = M.y;
|
|
4738
5261
|
|
|
4739
|
-
|
|
5262
|
+
// adjust cpts
|
|
5263
|
+
let comFirst = pathData[1];
|
|
4740
5264
|
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
5265
|
+
if (comFirst.type === 'C' && comPenultimate.type === 'C') {
|
|
5266
|
+
let dx1 = Math.abs(comFirst.values[0] - comPenultimate.values[2]);
|
|
5267
|
+
let dy1 = Math.abs(comFirst.values[1] - comPenultimate.values[3]);
|
|
5268
|
+
|
|
5269
|
+
let dx2 = Math.abs(pathData[1].values[0] - comFirst.values[0]);
|
|
5270
|
+
let dy2 = Math.abs(pathData[1].values[1] - comFirst.values[1]);
|
|
5271
|
+
|
|
5272
|
+
let dx3 = Math.abs(pathData[1].values[0] - comPenultimate.values[2]);
|
|
5273
|
+
let dy3 = Math.abs(pathData[1].values[1] - comPenultimate.values[3]);
|
|
5274
|
+
|
|
5275
|
+
let ver = dx2 < threshold && dx3 < threshold && dy1;
|
|
5276
|
+
let hor = (dy2 < threshold && dy3 < threshold) && dx1;
|
|
5277
|
+
|
|
5278
|
+
if (dx1 && dx1 < threshold && ver) {
|
|
5279
|
+
|
|
5280
|
+
pathData[1].values[0] = M.x;
|
|
5281
|
+
pathData[idxPenultimate].values[2] = M.x;
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
if (dy1 && dy1 < threshold && hor) {
|
|
5285
|
+
|
|
5286
|
+
pathData[1].values[1] = M.y;
|
|
5287
|
+
pathData[idxPenultimate].values[3] = M.y;
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
}
|
|
4744
5291
|
}
|
|
4745
5292
|
|
|
4746
|
-
|
|
4747
|
-
let cx = (d * e - b * f) / det;
|
|
4748
|
-
let cy = (-c * e + a * f) / det;
|
|
4749
|
-
let centroid = { x: cx, y: cy };
|
|
5293
|
+
return pathData;
|
|
4750
5294
|
|
|
4751
|
-
|
|
4752
|
-
let r = getDistance(centroid, p1);
|
|
5295
|
+
}
|
|
4753
5296
|
|
|
4754
|
-
|
|
4755
|
-
let {deltaAngle, startAngle, endAngle} = angleData;
|
|
5297
|
+
function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
4756
5298
|
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
5299
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5300
|
+
let com = pathData[c];
|
|
5301
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5302
|
+
if (type === 'C') {
|
|
5303
|
+
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
5304
|
+
if (comQ.type === 'Q') {
|
|
5305
|
+
comQ.extreme = com.extreme;
|
|
5306
|
+
comQ.corner = com.corner;
|
|
5307
|
+
comQ.dimA = com.dimA;
|
|
5308
|
+
comQ.squareDist = com.squareDist;
|
|
5309
|
+
pathData[c] = comQ;
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
return pathData
|
|
5314
|
+
}
|
|
5315
|
+
|
|
5316
|
+
function pathDataLineToCubic(pathData) {
|
|
5317
|
+
|
|
5318
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5319
|
+
let com = pathData[c];
|
|
5320
|
+
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5321
|
+
if (type === 'L') {
|
|
5322
|
+
|
|
5323
|
+
let cp1 = interpolate(p0, p, 0.333);
|
|
5324
|
+
let cp2 = interpolate(p, p0, 0.333);
|
|
5325
|
+
|
|
5326
|
+
pathData[c].type = 'C';
|
|
5327
|
+
pathData[c].values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
5328
|
+
pathData[c].cp1 = cp1;
|
|
5329
|
+
pathData[c].cp2 = cp2;
|
|
5330
|
+
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
return pathData
|
|
4764
5334
|
}
|
|
4765
5335
|
|
|
4766
5336
|
function refineRoundSegments(pathData, {
|
|
@@ -4779,9 +5349,6 @@ function refineRoundSegments(pathData, {
|
|
|
4779
5349
|
// add fist command
|
|
4780
5350
|
let pathDataN = [pathData[0]];
|
|
4781
5351
|
|
|
4782
|
-
// just for debugging
|
|
4783
|
-
let pathDataTest = [];
|
|
4784
|
-
|
|
4785
5352
|
for (let i = 1; i < l; i++) {
|
|
4786
5353
|
let com = pathData[i];
|
|
4787
5354
|
let { type } = com;
|
|
@@ -4808,11 +5375,12 @@ function refineRoundSegments(pathData, {
|
|
|
4808
5375
|
|
|
4809
5376
|
// 2. line-line-bezier-line-line
|
|
4810
5377
|
if (
|
|
5378
|
+
comN2 && comN3 &&
|
|
4811
5379
|
comP.type === 'L' &&
|
|
4812
5380
|
type === 'L' &&
|
|
4813
5381
|
comBez &&
|
|
4814
5382
|
comN2.type === 'L' &&
|
|
4815
|
-
|
|
5383
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
4816
5384
|
) {
|
|
4817
5385
|
|
|
4818
5386
|
L1 = [com.p0, com.p];
|
|
@@ -4839,10 +5407,10 @@ function refineRoundSegments(pathData, {
|
|
|
4839
5407
|
}
|
|
4840
5408
|
|
|
4841
5409
|
// 1. line-bezier-bezier-line
|
|
4842
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
5410
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
4843
5411
|
|
|
4844
5412
|
// 1.2 next is cubic next is lineto
|
|
4845
|
-
if (
|
|
5413
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
4846
5414
|
|
|
4847
5415
|
combine = true;
|
|
4848
5416
|
|
|
@@ -4901,16 +5469,19 @@ function refineRoundSegments(pathData, {
|
|
|
4901
5469
|
}
|
|
4902
5470
|
);
|
|
4903
5471
|
|
|
4904
|
-
if(bezierCommands.length === 1){
|
|
5472
|
+
if (bezierCommands.length === 1) {
|
|
4905
5473
|
|
|
4906
5474
|
// prefer more compact quadratic - otherwise arcs
|
|
4907
5475
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
4908
5476
|
|
|
4909
5477
|
if (comBezier.type === 'Q') {
|
|
4910
5478
|
toCubic = true;
|
|
5479
|
+
}else {
|
|
5480
|
+
comBezier = bezierCommands[0];
|
|
4911
5481
|
}
|
|
4912
5482
|
|
|
4913
5483
|
com = comBezier;
|
|
5484
|
+
|
|
4914
5485
|
}
|
|
4915
5486
|
|
|
4916
5487
|
// prefer arcs if 2 cubics are required
|
|
@@ -4930,25 +5501,28 @@ function refineRoundSegments(pathData, {
|
|
|
4930
5501
|
|
|
4931
5502
|
// test rendering
|
|
4932
5503
|
|
|
5504
|
+
/*
|
|
4933
5505
|
if (debug) {
|
|
4934
5506
|
// arcs
|
|
4935
5507
|
if (!toCubic) {
|
|
4936
5508
|
pathDataTest = [
|
|
4937
5509
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
4938
5510
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
4939
|
-
]
|
|
5511
|
+
]
|
|
4940
5512
|
}
|
|
4941
5513
|
// cubics
|
|
4942
5514
|
else {
|
|
4943
5515
|
pathDataTest = [
|
|
4944
5516
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
4945
5517
|
...bezierCommands
|
|
4946
|
-
]
|
|
5518
|
+
]
|
|
5519
|
+
|
|
4947
5520
|
}
|
|
4948
5521
|
|
|
4949
5522
|
let d = pathDataToD(pathDataTest);
|
|
4950
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
5523
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
4951
5524
|
}
|
|
5525
|
+
*/
|
|
4952
5526
|
|
|
4953
5527
|
pathDataN.push(com);
|
|
4954
5528
|
i++;
|
|
@@ -4965,104 +5539,6 @@ function refineRoundSegments(pathData, {
|
|
|
4965
5539
|
return pathDataN;
|
|
4966
5540
|
}
|
|
4967
5541
|
|
|
4968
|
-
function refineClosingCommand(pathData = [], {
|
|
4969
|
-
threshold = 0,
|
|
4970
|
-
} = {}) {
|
|
4971
|
-
|
|
4972
|
-
let l = pathData.length;
|
|
4973
|
-
let comLast = pathData[l - 1];
|
|
4974
|
-
let isClosed = comLast.type.toLowerCase() === 'z';
|
|
4975
|
-
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
4976
|
-
let comPenultimate = isClosed ? pathData[idxPenultimate] : pathData[idxPenultimate];
|
|
4977
|
-
let valsPen = comPenultimate.values.slice(-2);
|
|
4978
|
-
|
|
4979
|
-
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
4980
|
-
let pPen = { x: valsPen[0], y: valsPen[1] };
|
|
4981
|
-
let dist = getDistAv(M, pPen);
|
|
4982
|
-
|
|
4983
|
-
// adjust last coordinates for better reordering
|
|
4984
|
-
if (dist && dist < threshold) {
|
|
4985
|
-
|
|
4986
|
-
let valsLast = pathData[idxPenultimate].values;
|
|
4987
|
-
let valsLastLen = valsLast.length;
|
|
4988
|
-
pathData[idxPenultimate].values[valsLastLen - 2] = M.x;
|
|
4989
|
-
pathData[idxPenultimate].values[valsLastLen - 1] = M.y;
|
|
4990
|
-
|
|
4991
|
-
// adjust cpts
|
|
4992
|
-
let comFirst = pathData[1];
|
|
4993
|
-
|
|
4994
|
-
if (comFirst.type === 'C' && comPenultimate.type === 'C') {
|
|
4995
|
-
let dx1 = Math.abs(comFirst.values[0] - comPenultimate.values[2]);
|
|
4996
|
-
let dy1 = Math.abs(comFirst.values[1] - comPenultimate.values[3]);
|
|
4997
|
-
|
|
4998
|
-
let dx2 = Math.abs(pathData[1].values[0] - comFirst.values[0]);
|
|
4999
|
-
let dy2 = Math.abs(pathData[1].values[1] - comFirst.values[1]);
|
|
5000
|
-
|
|
5001
|
-
let dx3 = Math.abs(pathData[1].values[0] - comPenultimate.values[2]);
|
|
5002
|
-
let dy3 = Math.abs(pathData[1].values[1] - comPenultimate.values[3]);
|
|
5003
|
-
|
|
5004
|
-
let ver = dx2 < threshold && dx3 < threshold && dy1;
|
|
5005
|
-
let hor = (dy2 < threshold && dy3 < threshold) && dx1;
|
|
5006
|
-
|
|
5007
|
-
if (dx1 && dx1 < threshold && ver) {
|
|
5008
|
-
|
|
5009
|
-
pathData[1].values[0] = M.x;
|
|
5010
|
-
pathData[idxPenultimate].values[2] = M.x;
|
|
5011
|
-
}
|
|
5012
|
-
|
|
5013
|
-
if (dy1 && dy1 < threshold && hor) {
|
|
5014
|
-
|
|
5015
|
-
pathData[1].values[1] = M.y;
|
|
5016
|
-
pathData[idxPenultimate].values[3] = M.y;
|
|
5017
|
-
}
|
|
5018
|
-
|
|
5019
|
-
}
|
|
5020
|
-
}
|
|
5021
|
-
|
|
5022
|
-
return pathData;
|
|
5023
|
-
|
|
5024
|
-
}
|
|
5025
|
-
|
|
5026
|
-
function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
5027
|
-
|
|
5028
|
-
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5029
|
-
let com = pathData[c];
|
|
5030
|
-
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5031
|
-
if (type === 'C') {
|
|
5032
|
-
|
|
5033
|
-
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
5034
|
-
if (comQ.type === 'Q') {
|
|
5035
|
-
comQ.extreme = com.extreme;
|
|
5036
|
-
comQ.corner = com.corner;
|
|
5037
|
-
comQ.dimA = com.dimA;
|
|
5038
|
-
comQ.squareDist = com.squareDist;
|
|
5039
|
-
pathData[c] = comQ;
|
|
5040
|
-
}
|
|
5041
|
-
}
|
|
5042
|
-
}
|
|
5043
|
-
return pathData
|
|
5044
|
-
}
|
|
5045
|
-
|
|
5046
|
-
function pathDataLineToCubic(pathData) {
|
|
5047
|
-
|
|
5048
|
-
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
5049
|
-
let com = pathData[c];
|
|
5050
|
-
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
5051
|
-
if (type === 'L') {
|
|
5052
|
-
|
|
5053
|
-
let cp1 = interpolate(p0, p, 0.333);
|
|
5054
|
-
let cp2 = interpolate(p, p0, 0.333);
|
|
5055
|
-
|
|
5056
|
-
pathData[c].type = 'C';
|
|
5057
|
-
pathData[c].values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
5058
|
-
pathData[c].cp1 = cp1;
|
|
5059
|
-
pathData[c].cp2 = cp2;
|
|
5060
|
-
|
|
5061
|
-
}
|
|
5062
|
-
}
|
|
5063
|
-
return pathData
|
|
5064
|
-
}
|
|
5065
|
-
|
|
5066
5542
|
function simplifyPathData(input = '', {
|
|
5067
5543
|
|
|
5068
5544
|
toAbsolute = true,
|
|
@@ -5120,7 +5596,10 @@ function simplifyPathData(input = '', {
|
|
|
5120
5596
|
let yArr = [];
|
|
5121
5597
|
|
|
5122
5598
|
// mode:0 – single path
|
|
5123
|
-
|
|
5599
|
+
|
|
5600
|
+
let inputDetection = detectInputType(input);
|
|
5601
|
+
let {inputType, log} = inputDetection;
|
|
5602
|
+
|
|
5124
5603
|
if (inputType === 'pathDataString') {
|
|
5125
5604
|
d = input;
|
|
5126
5605
|
} else if (inputType === 'polyString') {
|