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
|
@@ -28,62 +28,276 @@
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const {
|
|
32
|
+
abs: abs$1, acos: acos$1, asin: asin$1, atan: atan$1, atan2: atan2$1, ceil: ceil$1, cos: cos$1, exp: exp$1, floor: floor$1,
|
|
33
|
+
log: log$1, hypot, max: max$1, min: min$1, pow: pow$1, random: random$1, round: round$1, sin: sin$1, sqrt: sqrt$1, tan: tan$1, PI: PI$1
|
|
34
|
+
} = Math;
|
|
35
|
+
|
|
36
|
+
const rad2Deg = 180/Math.PI;
|
|
37
|
+
const deg2rad = Math.PI/180;
|
|
38
|
+
const root2 = 1.4142135623730951;
|
|
39
|
+
const svgNs = 'http://www.w3.org/2000/svg';
|
|
40
|
+
const dummySVG = `<svg id="svgInvalid" xmlns="${svgNs}" viewBox="0 0 1 1"><path d="M0 0 h0" /></svg>`;
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
// 1/2.54
|
|
43
|
+
const inch2cm = 0.39370078;
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
// 1/72
|
|
46
|
+
const inch2pt = 0.01388889;
|
|
47
|
+
|
|
48
|
+
function validateSVG(markup, allowed = {}) {
|
|
49
|
+
allowed = {
|
|
50
|
+
...{
|
|
51
|
+
|
|
52
|
+
useElsNested: 5000,
|
|
53
|
+
hasScripts: false,
|
|
54
|
+
hasEntity: false,
|
|
55
|
+
fileSizeKB: 10000,
|
|
56
|
+
isSymbolSprite: false,
|
|
57
|
+
isSvgFont: false
|
|
58
|
+
},
|
|
59
|
+
...allowed
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let fileReport = analyzeSVG(markup, allowed);
|
|
63
|
+
let isValid = true;
|
|
64
|
+
let log = [];
|
|
65
|
+
|
|
66
|
+
if (!fileReport.hasEls) {
|
|
67
|
+
log.push("no elements");
|
|
68
|
+
isValid = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (Object.keys(fileReport).length) {
|
|
72
|
+
if (fileReport.isBillionLaugh === true) {
|
|
73
|
+
log.push(`suspicious: might contain billion laugh attack`);
|
|
74
|
+
isValid = false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (let key in allowed) {
|
|
78
|
+
let val = allowed[key];
|
|
79
|
+
let valRep = fileReport[key];
|
|
80
|
+
if (typeof val === "number" && valRep > val) {
|
|
81
|
+
log.push(`allowed "${key}" exceeded: ${valRep} / ${val} `);
|
|
82
|
+
isValid = false;
|
|
83
|
+
}
|
|
84
|
+
if (valRep === true && val === false) {
|
|
85
|
+
log.push(`not allowed: "${key}" `);
|
|
86
|
+
isValid = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
isValid = false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
if (!isValid) {
|
|
95
|
+
log = ["SVG not valid"].concat(log);
|
|
96
|
+
|
|
97
|
+
if (Object.keys(fileReport).length) {
|
|
98
|
+
console.warn(fileReport);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
return { isValid, log, fileReport };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function analyzeSVG(markup, allowed = {}) {
|
|
107
|
+
markup = markup.trim();
|
|
108
|
+
let doc, svg;
|
|
109
|
+
let fileSizeKB = +(markup.length / 1024).toFixed(3);
|
|
110
|
+
|
|
111
|
+
let fileReport = {
|
|
112
|
+
totalEls: 1,
|
|
113
|
+
hasEls: true,
|
|
114
|
+
hasDefs: false,
|
|
115
|
+
geometryEls: [],
|
|
116
|
+
useEls: 0,
|
|
117
|
+
useElsNested: 0,
|
|
118
|
+
nonsensePaths: 0,
|
|
119
|
+
isSuspicious: false,
|
|
120
|
+
isBillionLaugh: false,
|
|
121
|
+
hasScripts: false,
|
|
122
|
+
hasPrologue: false,
|
|
123
|
+
hasEntity: false,
|
|
124
|
+
isPathData:false,
|
|
125
|
+
fileSizeKB,
|
|
126
|
+
hasXmlns: markup.includes("http://www.w3.org/2000/svg"),
|
|
127
|
+
isSymbolSprite: false,
|
|
128
|
+
isSvgFont: markup.includes("<glyph>")
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let maxNested = allowed.useElsNested ? allowed.useElsNested : 2000;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* analyze nestes use references
|
|
135
|
+
*/
|
|
136
|
+
const countUseRefs = (useEls, maxNested = 2000) => {
|
|
137
|
+
let nestedCount = 0;
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < useEls.length && nestedCount < maxNested; i++) {
|
|
140
|
+
let use = useEls[i];
|
|
141
|
+
let refId = use.getAttribute("xlink:href")
|
|
142
|
+
? use.getAttribute("xlink:href")
|
|
143
|
+
: use.getAttribute("href");
|
|
144
|
+
refId = refId ? refId.replace("#", "") : "";
|
|
145
|
+
|
|
146
|
+
use.setAttribute("href", "#" + refId);
|
|
147
|
+
|
|
148
|
+
let refEl = svg.getElementById(refId);
|
|
149
|
+
let nestedUse = refEl.querySelectorAll("use");
|
|
150
|
+
let nestedUseLength = nestedUse.length;
|
|
151
|
+
nestedCount += nestedUseLength;
|
|
152
|
+
|
|
153
|
+
// query nested use references
|
|
154
|
+
for (let n = 0; n < nestedUse.length && nestedCount < maxNested; n++) {
|
|
155
|
+
let nested = nestedUse[n];
|
|
156
|
+
let id1 = nested.getAttribute("href").replace("#", "");
|
|
157
|
+
let refEl1 = svg.getElementById(id1);
|
|
158
|
+
let nestedUse1 = refEl1.querySelectorAll("use");
|
|
159
|
+
nestedCount += nestedUse1.length;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
fileReport.useElsNested = nestedCount;
|
|
163
|
+
return nestedCount;
|
|
164
|
+
};
|
|
165
|
+
let hasEntity = /\<\!ENTITY/gi.test(markup);
|
|
166
|
+
let hasScripts = /\<script/gi.test(markup) ? true : false;
|
|
167
|
+
let hasUse = /\<use/gi.test(markup) ? true : false;
|
|
168
|
+
let hasEls = /[\<path|\<polygon|\<polyline|\<rect|\<circle|\<ellipse|\<line|\<text|\<foreignObject]/gi.test(markup);
|
|
169
|
+
let hasDefs = /[\<filter|\<linearGradient|\<radialGradient|\<pattern|\<animate|\<animateMotion|\<animateTransform|\<clipPath|\<mask|\<symbol|\<marker]/gi.test(markup);
|
|
170
|
+
|
|
171
|
+
let isPathData = (markup.startsWith('M') || markup.startsWith('m')) && !/[\<svg|\<\/svg]/gi.test(markup);
|
|
172
|
+
fileReport.isPathData = isPathData;
|
|
173
|
+
|
|
174
|
+
// seems OK
|
|
175
|
+
if (!hasEntity && !hasUse && !hasScripts && (hasEls || hasDefs) && fileSizeKB < allowed.fileSizeKB) {
|
|
176
|
+
fileReport.hasEls = hasEls;
|
|
177
|
+
fileReport.hasDefs = hasDefs;
|
|
178
|
+
|
|
179
|
+
return fileReport
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Contains xml entity definition: highly suspicious - stop parsing!
|
|
183
|
+
if (allowed.hasEntity === false && hasEntity) {
|
|
184
|
+
fileReport.hasEntity = true;
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* sanitizing for parsing:
|
|
190
|
+
* remove xml prologue and comments
|
|
191
|
+
*/
|
|
192
|
+
markup = markup
|
|
193
|
+
.replace(/\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/g, "")
|
|
194
|
+
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, "");
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Try to parse svg:
|
|
198
|
+
* invalid svg will return false via "catch"
|
|
199
|
+
*/
|
|
200
|
+
try {
|
|
201
|
+
|
|
202
|
+
doc = new DOMParser().parseFromString(markup, "text/html");
|
|
203
|
+
svg = doc.querySelector("svg");
|
|
204
|
+
|
|
205
|
+
// paths containing only a M command
|
|
206
|
+
let nonsensePaths = svg.querySelectorAll('path[d="M0,0"], path[d="M0 0"]').length;
|
|
207
|
+
let useEls = svg.querySelectorAll("use").length;
|
|
208
|
+
|
|
209
|
+
// create analyzing object
|
|
210
|
+
fileReport.totalEls = svg.querySelectorAll("*").length;
|
|
211
|
+
fileReport.geometryEls = svg.querySelectorAll(
|
|
212
|
+
"path, rect, circle, ellipse, polygon, polyline, line"
|
|
213
|
+
).length;
|
|
214
|
+
|
|
215
|
+
fileReport.hasScripts = hasScripts;
|
|
216
|
+
fileReport.useEls = useEls;
|
|
217
|
+
fileReport.nonsensePaths = nonsensePaths;
|
|
218
|
+
fileReport.isSuspicious = false;
|
|
219
|
+
fileReport.isBillionLaugh = false;
|
|
220
|
+
fileReport.hasXmlns = svg.getAttribute("xmlns")
|
|
221
|
+
? svg.getAttribute("xmlns") === "http://www.w3.org/2000/svg"
|
|
222
|
+
? true
|
|
223
|
+
: false
|
|
224
|
+
: false;
|
|
225
|
+
fileReport.isSymbolSprite =
|
|
226
|
+
svg.querySelectorAll("symbol").length &&
|
|
227
|
+
svg.querySelectorAll("use").length === 0
|
|
228
|
+
? true
|
|
229
|
+
: false;
|
|
230
|
+
fileReport.isSvgFont = svg.querySelectorAll("glyph").length ? true : false;
|
|
231
|
+
|
|
232
|
+
let totalEls = fileReport.totalEls;
|
|
233
|
+
let totalUseEls = fileReport.useEls;
|
|
234
|
+
let usePercentage = (100 / totalEls) * totalUseEls;
|
|
235
|
+
|
|
236
|
+
// if percentage of use elements is higher than 75% - suspicious
|
|
237
|
+
if (usePercentage > 75) {
|
|
238
|
+
fileReport.isSuspicious = true;
|
|
239
|
+
|
|
240
|
+
// check nested use references
|
|
241
|
+
let nestedCount = countUseRefs(svg.querySelectorAll("use"), maxNested);
|
|
242
|
+
if (nestedCount >= maxNested) {
|
|
243
|
+
fileReport.isBillionLaugh = true;
|
|
244
|
+
}
|
|
39
245
|
}
|
|
40
246
|
|
|
247
|
+
return fileReport;
|
|
248
|
+
} catch {
|
|
249
|
+
// svg file has malformed markup
|
|
250
|
+
console.warn("svg could not be parsed");
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
41
253
|
}
|
|
42
254
|
|
|
43
255
|
function detectInputType(input) {
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
256
|
+
let log = '';
|
|
257
|
+
let isValid = true;
|
|
258
|
+
|
|
259
|
+
let result = {
|
|
260
|
+
inputType:'',
|
|
261
|
+
isValid:true,
|
|
262
|
+
fileReport:{},
|
|
263
|
+
};
|
|
264
|
+
|
|
53
265
|
if (Array.isArray(input)) {
|
|
54
266
|
|
|
267
|
+
result.inputType = "array";
|
|
268
|
+
|
|
55
269
|
// nested array
|
|
56
270
|
if (Array.isArray(input[0])) {
|
|
57
271
|
|
|
58
272
|
if (input[0].length === 2) {
|
|
59
273
|
|
|
60
|
-
|
|
274
|
+
result.inputType = 'polyArray';
|
|
61
275
|
}
|
|
62
276
|
|
|
63
277
|
else if (Array.isArray(input[0][0]) && input[0][0].length === 2) {
|
|
64
278
|
|
|
65
|
-
|
|
279
|
+
result.inputType = 'polyComplexArray';
|
|
66
280
|
}
|
|
67
281
|
else if (input[0][0].x !== undefined && input[0][0].y !== undefined) {
|
|
68
282
|
|
|
69
|
-
|
|
283
|
+
result.inputType = 'polyComplexObjectArray';
|
|
70
284
|
}
|
|
285
|
+
|
|
71
286
|
}
|
|
72
287
|
|
|
73
288
|
// is point array
|
|
74
289
|
else if (input[0].x !== undefined && input[0].y !== undefined) {
|
|
75
290
|
|
|
76
|
-
|
|
291
|
+
result.inputType = 'polyObjectArray';
|
|
77
292
|
}
|
|
78
293
|
|
|
79
294
|
// path data array
|
|
80
295
|
else if (input[0]?.type && input[0]?.values
|
|
81
296
|
) {
|
|
82
|
-
|
|
83
|
-
|
|
297
|
+
result.inputType = "pathData";
|
|
84
298
|
}
|
|
85
299
|
|
|
86
|
-
return
|
|
300
|
+
return result;
|
|
87
301
|
}
|
|
88
302
|
|
|
89
303
|
if (typeof input === "string") {
|
|
@@ -95,36 +309,48 @@
|
|
|
95
309
|
let isJson = isNumberJson(input);
|
|
96
310
|
|
|
97
311
|
if (isSVG) {
|
|
98
|
-
|
|
312
|
+
let validate = validateSVG(input);
|
|
313
|
+
({isValid, log} = validate) ;
|
|
314
|
+
if(!isValid){
|
|
315
|
+
|
|
316
|
+
result.inputType = 'invalid';
|
|
317
|
+
result.isValid=false,
|
|
318
|
+
|
|
319
|
+
result.log = log;
|
|
320
|
+
}else {
|
|
321
|
+
result.inputType = 'svgMarkup';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
result.fileReport = validate.fileReport;
|
|
325
|
+
|
|
99
326
|
}
|
|
100
327
|
|
|
101
328
|
else if (isJson) {
|
|
102
|
-
|
|
329
|
+
result.inputType = 'json';
|
|
103
330
|
}
|
|
104
331
|
|
|
105
332
|
else if (isSymbol) {
|
|
106
|
-
|
|
333
|
+
result.inputType = 'symbol';
|
|
107
334
|
}
|
|
108
335
|
else if (isPathData) {
|
|
109
|
-
|
|
336
|
+
result.inputType = 'pathDataString';
|
|
110
337
|
}
|
|
111
338
|
else if (isPolyString) {
|
|
112
|
-
|
|
339
|
+
result.inputType = 'polyString';
|
|
113
340
|
}
|
|
114
341
|
|
|
115
342
|
else {
|
|
116
343
|
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
117
344
|
let dataUrl = input.startsWith('data:image');
|
|
118
|
-
|
|
345
|
+
result.inputType = url || dataUrl ? "url" : "string";
|
|
119
346
|
}
|
|
120
347
|
|
|
121
|
-
return
|
|
348
|
+
return result
|
|
122
349
|
}
|
|
123
350
|
|
|
124
|
-
|
|
125
|
-
let constructor = input.constructor.name;
|
|
351
|
+
result.inputType = (input.constructor.name || typeof input ).toLowerCase();
|
|
126
352
|
|
|
127
|
-
return
|
|
353
|
+
return result;
|
|
128
354
|
}
|
|
129
355
|
|
|
130
356
|
function isNumberJson(str) {
|
|
@@ -142,22 +368,6 @@
|
|
|
142
368
|
|
|
143
369
|
}
|
|
144
370
|
|
|
145
|
-
const {
|
|
146
|
-
abs: abs$1, acos: acos$1, asin: asin$1, atan: atan$1, atan2: atan2$1, ceil: ceil$1, cos: cos$1, exp: exp$1, floor: floor$1,
|
|
147
|
-
log: log$1, hypot, max: max$1, min: min$1, pow: pow$1, random: random$1, round: round$1, sin: sin$1, sqrt: sqrt$1, tan: tan$1, PI: PI$1
|
|
148
|
-
} = Math;
|
|
149
|
-
|
|
150
|
-
const rad2Deg = 180/Math.PI;
|
|
151
|
-
const deg2rad = Math.PI/180;
|
|
152
|
-
const root2 = 1.4142135623730951;
|
|
153
|
-
const svgNs = 'http://www.w3.org/2000/svg';
|
|
154
|
-
|
|
155
|
-
// 1/2.54
|
|
156
|
-
const inch2cm = 0.39370078;
|
|
157
|
-
|
|
158
|
-
// 1/72
|
|
159
|
-
const inch2pt = 0.01388889;
|
|
160
|
-
|
|
161
371
|
/*
|
|
162
372
|
import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
163
373
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
@@ -412,6 +622,7 @@
|
|
|
412
622
|
let t1 = 1 - t;
|
|
413
623
|
|
|
414
624
|
// cubic beziers
|
|
625
|
+
/*
|
|
415
626
|
if (isCubic) {
|
|
416
627
|
pt = {
|
|
417
628
|
x:
|
|
@@ -427,11 +638,29 @@
|
|
|
427
638
|
};
|
|
428
639
|
|
|
429
640
|
}
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
if (isCubic) {
|
|
644
|
+
pt = {
|
|
645
|
+
x:
|
|
646
|
+
t1 * t1 * t1 * p0.x +
|
|
647
|
+
3 * t1 * t1 * t * cp1.x +
|
|
648
|
+
3 * t1 * t * t * cp2.x +
|
|
649
|
+
t * t * t * p.x,
|
|
650
|
+
y:
|
|
651
|
+
t1 * t1 * t1 * p0.y +
|
|
652
|
+
3 * t1 * t1 * t * cp1.y +
|
|
653
|
+
3 * t1 * t * t * cp2.y +
|
|
654
|
+
t * t * t * p.y,
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
|
|
430
659
|
// quadratic beziers
|
|
431
660
|
else {
|
|
432
661
|
pt = {
|
|
433
|
-
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t
|
|
434
|
-
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t
|
|
662
|
+
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t * t * p.x,
|
|
663
|
+
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t * t * p.y,
|
|
435
664
|
};
|
|
436
665
|
}
|
|
437
666
|
|
|
@@ -747,12 +976,6 @@
|
|
|
747
976
|
return tArr;
|
|
748
977
|
}
|
|
749
978
|
|
|
750
|
-
/**
|
|
751
|
-
* based on Nikos M.'s answer
|
|
752
|
-
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
753
|
-
* https://stackoverflow.com/questions/87734/#75031511
|
|
754
|
-
* See also: https://github.com/foo123/Geometrize
|
|
755
|
-
*/
|
|
756
979
|
function getArcExtemes(p0, values) {
|
|
757
980
|
// compute point on ellipse from angle around ellipse (theta)
|
|
758
981
|
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
@@ -1621,6 +1844,36 @@
|
|
|
1621
1844
|
return `#${rHex}${gHex}${bHex}${aHex}`;
|
|
1622
1845
|
}
|
|
1623
1846
|
|
|
1847
|
+
/**
|
|
1848
|
+
* round path data
|
|
1849
|
+
* either by explicit decimal value or
|
|
1850
|
+
* based on suggested accuracy in path data
|
|
1851
|
+
*/
|
|
1852
|
+
function roundPathData(pathData, decimalsGlobal = -1) {
|
|
1853
|
+
|
|
1854
|
+
if (decimalsGlobal < 0) return pathData;
|
|
1855
|
+
|
|
1856
|
+
let len = pathData.length;
|
|
1857
|
+
let decimals = decimalsGlobal;
|
|
1858
|
+
let decimalsArc = decimals < 3 ? decimals + 2 : decimals;
|
|
1859
|
+
|
|
1860
|
+
for (let c = 0; c < len; c++) {
|
|
1861
|
+
let com = pathData[c];
|
|
1862
|
+
let { type, values } = com;
|
|
1863
|
+
let valLen = values.length;
|
|
1864
|
+
if (!valLen) continue
|
|
1865
|
+
|
|
1866
|
+
let isArc = type.toLowerCase() === 'a';
|
|
1867
|
+
|
|
1868
|
+
for (let v = 0; v < valLen; v++) {
|
|
1869
|
+
// allow higher accuracy for arc radii (... it's always arcs)
|
|
1870
|
+
pathData[c].values[v] = isArc && v < 2 ? roundTo(values[v], decimalsArc) : roundTo(values[v], decimals);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
return pathData;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1624
1877
|
function detectAccuracyPoly(pts) {
|
|
1625
1878
|
let dims = [];
|
|
1626
1879
|
|
|
@@ -1630,7 +1883,7 @@
|
|
|
1630
1883
|
let { p0 = null, p = null, dimA = 0 } = pt;
|
|
1631
1884
|
|
|
1632
1885
|
// use existing averave dimension value or calculate
|
|
1633
|
-
if (
|
|
1886
|
+
if (p && p0) {
|
|
1634
1887
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1635
1888
|
|
|
1636
1889
|
if (dimA) dims.push(dimA);
|
|
@@ -1662,29 +1915,71 @@
|
|
|
1662
1915
|
|
|
1663
1916
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1664
1917
|
|
|
1665
|
-
if (dimA) dims.push(dimA);
|
|
1918
|
+
if (dimA) dims.push(+dimA.toFixed(8));
|
|
1666
1919
|
|
|
1667
1920
|
}
|
|
1668
1921
|
|
|
1669
1922
|
}
|
|
1670
1923
|
|
|
1671
|
-
|
|
1924
|
+
dims = dims.sort();
|
|
1925
|
+
let len = dims.length;
|
|
1926
|
+
let dim_mid = dims[Math.floor(len*0.5)];
|
|
1927
|
+
|
|
1928
|
+
// smallest 25% of values
|
|
1929
|
+
let idx_q = Math.ceil(len*0.25);
|
|
1930
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1931
|
+
|
|
1932
|
+
// average smallest values with mid value
|
|
1933
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1934
|
+
|
|
1935
|
+
let threshold = 75;
|
|
1936
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1672
1937
|
|
|
1673
|
-
|
|
1938
|
+
// clamp
|
|
1939
|
+
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1940
|
+
|
|
1941
|
+
/*
|
|
1942
|
+
let dim_min = dims.sort()
|
|
1943
|
+
|
|
1944
|
+
let dim_mid = dim_min[Math.floor(dim_min.length*0.5)]
|
|
1945
|
+
|
|
1946
|
+
let sliceIdx = Math.ceil(dim_min.length / 4);
|
|
1674
1947
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1675
1948
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
1676
1949
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1950
|
+
// average with mid value
|
|
1951
|
+
minVal = (minVal+dim_mid)*0.5
|
|
1952
|
+
|
|
1953
|
+
let threshold = 75
|
|
1954
|
+
let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
|
|
1679
1955
|
|
|
1680
1956
|
// clamp
|
|
1681
1957
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1958
|
+
*/
|
|
1682
1959
|
|
|
1683
1960
|
}
|
|
1684
1961
|
|
|
1962
|
+
/**
|
|
1963
|
+
* rounding helper
|
|
1964
|
+
* allows for quantized rounding
|
|
1965
|
+
* e.g 0.5 decimals s
|
|
1966
|
+
*/
|
|
1685
1967
|
function roundTo(num = 0, decimals = 3) {
|
|
1686
|
-
if(decimals
|
|
1968
|
+
if (decimals < 0) return num;
|
|
1969
|
+
// Normal integer rounding
|
|
1687
1970
|
if (!decimals) return Math.round(num);
|
|
1971
|
+
|
|
1972
|
+
// stepped rounding
|
|
1973
|
+
let intPart = Math.floor(decimals);
|
|
1974
|
+
|
|
1975
|
+
if (intPart !== decimals) {
|
|
1976
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1977
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1978
|
+
|
|
1979
|
+
let step = 10 ** -intPart * f;
|
|
1980
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1688
1983
|
let factor = 10 ** decimals;
|
|
1689
1984
|
return Math.round(num * factor) / factor;
|
|
1690
1985
|
}
|
|
@@ -1694,50 +1989,21 @@
|
|
|
1694
1989
|
* floating point accuracy
|
|
1695
1990
|
* based on numeric value
|
|
1696
1991
|
*/
|
|
1697
|
-
function autoRound(val, integerThresh = 50){
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
if(val>integerThresh*2){
|
|
1701
|
-
decimals=0;
|
|
1702
|
-
}
|
|
1703
|
-
else if(val>integerThresh){
|
|
1704
|
-
decimals=1;
|
|
1705
|
-
}else {
|
|
1706
|
-
decimals=Math.ceil(500/val).toString().length;
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
let factor = 10 ** decimals;
|
|
1712
|
-
return Math.round(val * factor) / factor;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
/**
|
|
1716
|
-
* round path data
|
|
1717
|
-
* either by explicit decimal value or
|
|
1718
|
-
* based on suggested accuracy in path data
|
|
1719
|
-
*/
|
|
1720
|
-
function roundPathData(pathData, decimalsGlobal = -1) {
|
|
1721
|
-
|
|
1722
|
-
if (decimalsGlobal < 0) return pathData;
|
|
1723
|
-
|
|
1724
|
-
let len = pathData.length;
|
|
1992
|
+
function autoRound(val, integerThresh = 50) {
|
|
1993
|
+
let decimals = 8;
|
|
1725
1994
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
if (!valLen) continue
|
|
1995
|
+
if (val > integerThresh * 2) {
|
|
1996
|
+
decimals = 0;
|
|
1997
|
+
}
|
|
1998
|
+
else if (val > integerThresh) {
|
|
1999
|
+
decimals = 1;
|
|
2000
|
+
} else {
|
|
2001
|
+
decimals = Math.ceil(500 / val).toString().length;
|
|
1734
2002
|
|
|
1735
|
-
for (let v = 0; v < valLen; v++) {
|
|
1736
|
-
pathData[c].values[v] = roundTo(values[v], decimals);
|
|
1737
|
-
}
|
|
1738
2003
|
}
|
|
1739
2004
|
|
|
1740
|
-
|
|
2005
|
+
let factor = 10 ** decimals;
|
|
2006
|
+
return Math.round(val * factor) / factor;
|
|
1741
2007
|
}
|
|
1742
2008
|
|
|
1743
2009
|
/**
|
|
@@ -2179,7 +2445,7 @@
|
|
|
2179
2445
|
scale = height / 100;
|
|
2180
2446
|
}
|
|
2181
2447
|
else {
|
|
2182
|
-
scale = normalizedDiagonal ? scaleRoot / 100 :
|
|
2448
|
+
scale = normalizedDiagonal ? scaleRoot / 100 : width / 100;
|
|
2183
2449
|
}
|
|
2184
2450
|
break;
|
|
2185
2451
|
|
|
@@ -2248,12 +2514,14 @@
|
|
|
2248
2514
|
}
|
|
2249
2515
|
|
|
2250
2516
|
function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
|
|
2251
|
-
|
|
2517
|
+
|
|
2518
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
2252
2519
|
|
|
2253
2520
|
let atts={};
|
|
2254
2521
|
attributes.forEach(att=>{
|
|
2255
|
-
|
|
2256
|
-
|
|
2522
|
+
|
|
2523
|
+
let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
|
|
2524
|
+
atts[att] = value;
|
|
2257
2525
|
});
|
|
2258
2526
|
|
|
2259
2527
|
return atts
|
|
@@ -2998,7 +3266,7 @@
|
|
|
2998
3266
|
error += com.error;
|
|
2999
3267
|
|
|
3000
3268
|
// find next candidates
|
|
3001
|
-
for (let n = i +
|
|
3269
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
3002
3270
|
let comN = pathData[n];
|
|
3003
3271
|
|
|
3004
3272
|
if (comN.type !== 'C' ||
|
|
@@ -3008,6 +3276,7 @@
|
|
|
3008
3276
|
(keepExtremes && com.extreme)
|
|
3009
3277
|
)
|
|
3010
3278
|
) {
|
|
3279
|
+
|
|
3011
3280
|
break
|
|
3012
3281
|
}
|
|
3013
3282
|
|
|
@@ -3015,6 +3284,7 @@
|
|
|
3015
3284
|
|
|
3016
3285
|
// failure - could not be combined - exit loop
|
|
3017
3286
|
if (combined.length > 1) {
|
|
3287
|
+
|
|
3018
3288
|
break
|
|
3019
3289
|
}
|
|
3020
3290
|
|
|
@@ -3028,6 +3298,7 @@
|
|
|
3028
3298
|
|
|
3029
3299
|
// return combined
|
|
3030
3300
|
com = combined[0];
|
|
3301
|
+
|
|
3031
3302
|
}
|
|
3032
3303
|
|
|
3033
3304
|
pathDataN.push(com);
|
|
@@ -3077,9 +3348,9 @@
|
|
|
3077
3348
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
3078
3349
|
|
|
3079
3350
|
// test new point-at-t against original mid segment starting point
|
|
3080
|
-
let
|
|
3351
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
3081
3352
|
|
|
3082
|
-
let dist0 = getDistManhattan(com1.p,
|
|
3353
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
3083
3354
|
let dist1 = 0, dist2 = 0;
|
|
3084
3355
|
let close = dist0 < maxDist;
|
|
3085
3356
|
let success = false;
|
|
@@ -3094,29 +3365,40 @@
|
|
|
3094
3365
|
* to prevent distortions
|
|
3095
3366
|
*/
|
|
3096
3367
|
|
|
3097
|
-
//
|
|
3098
|
-
let
|
|
3368
|
+
// 1st segment mid
|
|
3369
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
3099
3370
|
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
let
|
|
3103
|
-
dist1 = getDistManhattan(
|
|
3371
|
+
let t2 = t * 0.5;
|
|
3372
|
+
// combined interpolated mid point
|
|
3373
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
3374
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
3104
3375
|
|
|
3105
3376
|
error += dist1;
|
|
3106
3377
|
|
|
3107
3378
|
if (dist1 < maxDist) {
|
|
3108
3379
|
|
|
3109
|
-
//
|
|
3110
|
-
let
|
|
3380
|
+
// 2nd segment mid
|
|
3381
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
3111
3382
|
|
|
3112
|
-
|
|
3113
|
-
let
|
|
3114
|
-
|
|
3383
|
+
// simplified path
|
|
3384
|
+
let t3 = (1 + t) * 0.5;
|
|
3385
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
3386
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
3115
3387
|
|
|
3116
3388
|
error += dist2;
|
|
3117
3389
|
|
|
3118
3390
|
if (error < maxDist) success = true;
|
|
3119
3391
|
|
|
3392
|
+
/*
|
|
3393
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
3394
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
3395
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
3396
|
+
|
|
3397
|
+
renderPoint(markers, com1.p, 'green')
|
|
3398
|
+
|
|
3399
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
3400
|
+
*/
|
|
3401
|
+
|
|
3120
3402
|
}
|
|
3121
3403
|
|
|
3122
3404
|
} // end 1st try
|
|
@@ -3130,11 +3412,19 @@
|
|
|
3130
3412
|
|
|
3131
3413
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
3132
3414
|
comS.type = 'C';
|
|
3415
|
+
|
|
3133
3416
|
comS.extreme = com2.extreme;
|
|
3134
3417
|
comS.directionChange = com2.directionChange;
|
|
3135
|
-
|
|
3136
3418
|
comS.corner = com2.corner;
|
|
3137
3419
|
|
|
3420
|
+
if (comS.extreme || comS.corner) ;
|
|
3421
|
+
|
|
3422
|
+
/*
|
|
3423
|
+
comS.extreme = com1.extreme;
|
|
3424
|
+
comS.directionChange = com1.directionChange;
|
|
3425
|
+
comS.corner = com1.corner;
|
|
3426
|
+
*/
|
|
3427
|
+
|
|
3138
3428
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
3139
3429
|
|
|
3140
3430
|
// relative error
|
|
@@ -3281,6 +3571,7 @@
|
|
|
3281
3571
|
let com = pathData[c - 1];
|
|
3282
3572
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
3283
3573
|
|
|
3574
|
+
let comPrev = pathData[c-2];
|
|
3284
3575
|
let comN = pathData[c] || null;
|
|
3285
3576
|
|
|
3286
3577
|
// init properties
|
|
@@ -3299,6 +3590,7 @@
|
|
|
3299
3590
|
|
|
3300
3591
|
// bezier types
|
|
3301
3592
|
let isBezier = type === 'Q' || type === 'C';
|
|
3593
|
+
let isArc = type === 'A';
|
|
3302
3594
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
3303
3595
|
|
|
3304
3596
|
/**
|
|
@@ -3345,6 +3637,22 @@
|
|
|
3345
3637
|
}
|
|
3346
3638
|
}
|
|
3347
3639
|
|
|
3640
|
+
// check extremes introduce by small arcs
|
|
3641
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3642
|
+
let distN = comN ? comN.dimA : 0;
|
|
3643
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
3644
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
3645
|
+
|
|
3646
|
+
if(isShort && smallRadius){
|
|
3647
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
3648
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
3649
|
+
hasExtremes = true;
|
|
3650
|
+
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3348
3656
|
if (hasExtremes) com.extreme = true;
|
|
3349
3657
|
|
|
3350
3658
|
// Corners and semi extremes
|
|
@@ -3902,50 +4210,10 @@
|
|
|
3902
4210
|
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3903
4211
|
}
|
|
3904
4212
|
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
toLonghands = true,
|
|
3910
|
-
|
|
3911
|
-
// not necessary unless you need cubics only
|
|
3912
|
-
quadraticToCubic = false,
|
|
3913
|
-
|
|
3914
|
-
// mostly a fallback if arc calculations fail
|
|
3915
|
-
arcToCubic = false,
|
|
3916
|
-
// arc to cubic precision - adds more segments for better precision
|
|
3917
|
-
arcAccuracy = 4,
|
|
3918
|
-
} = {}
|
|
3919
|
-
) {
|
|
3920
|
-
|
|
3921
|
-
// is already array
|
|
3922
|
-
let isArray = Array.isArray(d);
|
|
3923
|
-
|
|
3924
|
-
// normalize native pathData to regular array
|
|
3925
|
-
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3926
|
-
/*
|
|
3927
|
-
if (hasConstructor) {
|
|
3928
|
-
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3929
|
-
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3930
|
-
}
|
|
3931
|
-
*/
|
|
3932
|
-
|
|
3933
|
-
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3934
|
-
|
|
3935
|
-
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3936
|
-
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3937
|
-
|
|
3938
|
-
// normalize
|
|
3939
|
-
pathData = normalizePathData(pathData,
|
|
3940
|
-
{
|
|
3941
|
-
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3942
|
-
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3943
|
-
},
|
|
3944
|
-
);
|
|
3945
|
-
|
|
3946
|
-
return pathData;
|
|
3947
|
-
}
|
|
3948
|
-
|
|
4213
|
+
/**
|
|
4214
|
+
* wrapper function for
|
|
4215
|
+
* all path data conversion
|
|
4216
|
+
*/
|
|
3949
4217
|
function convertPathData(pathData, {
|
|
3950
4218
|
toShorthands = true,
|
|
3951
4219
|
toLonghands = false,
|
|
@@ -3997,22 +4265,24 @@
|
|
|
3997
4265
|
|
|
3998
4266
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3999
4267
|
|
|
4000
|
-
if(toMixed) toRelative = true;
|
|
4268
|
+
if (toMixed) toRelative = true;
|
|
4001
4269
|
|
|
4002
4270
|
// pre round - before relative conversion to minimize distortions
|
|
4003
4271
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
4004
4272
|
|
|
4005
4273
|
// clone absolute pathdata
|
|
4006
|
-
if(toMixed){
|
|
4274
|
+
if (toMixed) {
|
|
4007
4275
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
4008
4276
|
}
|
|
4009
4277
|
|
|
4010
4278
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
4279
|
+
|
|
4280
|
+
// final rounding
|
|
4011
4281
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
4012
4282
|
|
|
4013
4283
|
// choose most compact commands: relative or absolute
|
|
4014
|
-
if(toMixed){
|
|
4015
|
-
for(let i=0; i<pathData.length; i++){
|
|
4284
|
+
if (toMixed) {
|
|
4285
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
4016
4286
|
let com = pathData[i];
|
|
4017
4287
|
let comA = pathDataAbs[i];
|
|
4018
4288
|
// compare Lengths
|
|
@@ -4022,7 +4292,7 @@
|
|
|
4022
4292
|
let lenR = comStr.length;
|
|
4023
4293
|
let lenA = comStrA.length;
|
|
4024
4294
|
|
|
4025
|
-
if(lenA<lenR){
|
|
4295
|
+
if (lenA < lenR) {
|
|
4026
4296
|
|
|
4027
4297
|
pathData[i] = pathDataAbs[i];
|
|
4028
4298
|
}
|
|
@@ -4032,56 +4302,140 @@
|
|
|
4032
4302
|
return pathData
|
|
4033
4303
|
}
|
|
4034
4304
|
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4305
|
+
function parsePathDataNormalized(d,
|
|
4306
|
+
{
|
|
4307
|
+
// necessary for most calculations
|
|
4308
|
+
toAbsolute = true,
|
|
4309
|
+
toLonghands = true,
|
|
4310
|
+
|
|
4311
|
+
// not necessary unless you need cubics only
|
|
4312
|
+
quadraticToCubic = false,
|
|
4313
|
+
|
|
4314
|
+
// mostly a fallback if arc calculations fail
|
|
4315
|
+
arcToCubic = false,
|
|
4316
|
+
// arc to cubic precision - adds more segments for better precision
|
|
4317
|
+
arcAccuracy = 4,
|
|
4318
|
+
} = {}
|
|
4319
|
+
) {
|
|
4320
|
+
|
|
4321
|
+
// is already array
|
|
4322
|
+
let isArray = Array.isArray(d);
|
|
4323
|
+
|
|
4324
|
+
// normalize native pathData to regular array
|
|
4325
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
4326
|
+
/*
|
|
4327
|
+
if (hasConstructor) {
|
|
4328
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
4329
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
4330
|
+
}
|
|
4331
|
+
*/
|
|
4332
|
+
|
|
4333
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
4334
|
+
|
|
4335
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
4336
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
4337
|
+
|
|
4338
|
+
// normalize
|
|
4339
|
+
pathData = normalizePathData(pathData,
|
|
4340
|
+
{
|
|
4341
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
4342
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
4343
|
+
},
|
|
4344
|
+
);
|
|
4345
|
+
|
|
4346
|
+
return pathData;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
/**
|
|
4350
|
+
*
|
|
4351
|
+
* @param {*} pathData
|
|
4352
|
+
* @returns
|
|
4039
4353
|
*/
|
|
4040
4354
|
|
|
4041
4355
|
function optimizeArcPathData(pathData = []) {
|
|
4356
|
+
let l = pathData.length;
|
|
4357
|
+
let pathDataN = [];
|
|
4042
4358
|
|
|
4043
|
-
let
|
|
4044
|
-
|
|
4045
|
-
pathData.forEach((com, i) => {
|
|
4359
|
+
for (let i = 0; i < l; i++) {
|
|
4360
|
+
let com = pathData[i];
|
|
4046
4361
|
let { type, values } = com;
|
|
4047
|
-
if (type === 'A') {
|
|
4048
|
-
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4049
|
-
let comPrev = pathData[i - 1];
|
|
4050
|
-
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4051
|
-
let M = { x: x0, y: y0 };
|
|
4052
|
-
let p = { x, y };
|
|
4053
4362
|
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4363
|
+
if (type !== 'A') {
|
|
4364
|
+
pathDataN.push(com);
|
|
4365
|
+
continue
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4369
|
+
let comPrev = pathData[i - 1];
|
|
4370
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4371
|
+
let M = { x: x0, y: y0 };
|
|
4372
|
+
let p = { x, y };
|
|
4057
4373
|
|
|
4058
|
-
|
|
4374
|
+
if (rx === 0 || ry === 0) {
|
|
4375
|
+
pathData[i] = null;
|
|
4376
|
+
}
|
|
4059
4377
|
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4378
|
+
// test for elliptic
|
|
4379
|
+
let rat = rx / ry;
|
|
4380
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
4063
4381
|
|
|
4064
|
-
|
|
4065
|
-
if (diff < 0.01) {
|
|
4382
|
+
if (error > 0.01) {
|
|
4066
4383
|
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
let distM = getDistance(pMid, M);
|
|
4070
|
-
let rDiff = Math.abs(distM - rx) / rx;
|
|
4384
|
+
pathDataN.push(com);
|
|
4385
|
+
continue
|
|
4071
4386
|
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
4390
|
+
com.values[2] = 0;
|
|
4391
|
+
|
|
4392
|
+
/**
|
|
4393
|
+
* test semi circles
|
|
4394
|
+
* rx and ry are large enough
|
|
4395
|
+
*/
|
|
4396
|
+
|
|
4397
|
+
// 1. horizontal or vertical
|
|
4398
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
4399
|
+
let diffX = Math.abs(x - x0);
|
|
4400
|
+
let diffY = Math.abs(y - y0);
|
|
4401
|
+
|
|
4402
|
+
let isHorizontal = diffY < thresh;
|
|
4403
|
+
let isVertical = diffX < thresh;
|
|
4404
|
+
|
|
4405
|
+
// minify rx and ry
|
|
4406
|
+
if (isHorizontal || isVertical) {
|
|
4407
|
+
|
|
4408
|
+
// check if semi circle
|
|
4409
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
4410
|
+
|
|
4411
|
+
// is semicircle we can simplify rx
|
|
4412
|
+
if (!needsTrueR) {
|
|
4413
|
+
|
|
4414
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
4079
4415
|
}
|
|
4416
|
+
|
|
4417
|
+
com.values[0] = rx;
|
|
4418
|
+
com.values[1] = rx;
|
|
4419
|
+
pathDataN.push(com);
|
|
4420
|
+
continue
|
|
4421
|
+
|
|
4080
4422
|
}
|
|
4081
|
-
});
|
|
4082
4423
|
|
|
4083
|
-
|
|
4084
|
-
|
|
4424
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
4425
|
+
let r = getDistance(M, p) * 0.5;
|
|
4426
|
+
error = rx / r;
|
|
4427
|
+
|
|
4428
|
+
if (error < 0.5) {
|
|
4429
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
4430
|
+
}
|
|
4431
|
+
|
|
4432
|
+
com.values[0] = rx;
|
|
4433
|
+
com.values[1] = rx;
|
|
4434
|
+
pathDataN.push(com);
|
|
4435
|
+
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
return pathDataN;
|
|
4085
4439
|
}
|
|
4086
4440
|
|
|
4087
4441
|
/**
|
|
@@ -4146,6 +4500,44 @@
|
|
|
4146
4500
|
}
|
|
4147
4501
|
*/
|
|
4148
4502
|
|
|
4503
|
+
function convertSmallArcsToLinetos(pathData) {
|
|
4504
|
+
|
|
4505
|
+
let l = pathData.length;
|
|
4506
|
+
|
|
4507
|
+
// add fist command
|
|
4508
|
+
let pathDataN = [pathData[0]];
|
|
4509
|
+
|
|
4510
|
+
for (let i = 1; i < l; i++) {
|
|
4511
|
+
let com = pathData[i];
|
|
4512
|
+
let comPrev = pathData[i - 1];
|
|
4513
|
+
let comN = pathData[i + 1] || null;
|
|
4514
|
+
|
|
4515
|
+
if (!comN) {
|
|
4516
|
+
pathDataN.push(com);
|
|
4517
|
+
break
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
let { type, values, extreme = false, p0, p, dimA = 0 } = com;
|
|
4521
|
+
// for short segment detection
|
|
4522
|
+
let dimAN = comN.dimA;
|
|
4523
|
+
let dimA0 = comPrev.dimA + dimA + dimAN;
|
|
4524
|
+
let thresh = 0.05;
|
|
4525
|
+
let isShort = dimA < dimA0 * thresh;
|
|
4526
|
+
|
|
4527
|
+
if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
|
|
4528
|
+
|
|
4529
|
+
com.type = 'L';
|
|
4530
|
+
com.values = [p.x, p.y];
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
pathDataN.push(com);
|
|
4534
|
+
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
return pathDataN;
|
|
4538
|
+
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4149
4541
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
4150
4542
|
|
|
4151
4543
|
// test if cubic can be simplified to quadratic
|
|
@@ -6201,7 +6593,8 @@
|
|
|
6201
6593
|
let { type, values } = com;
|
|
6202
6594
|
let valsLen = values.length;
|
|
6203
6595
|
if (valsLen) {
|
|
6204
|
-
|
|
6596
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
6597
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
|
|
6205
6598
|
p.index = i;
|
|
6206
6599
|
indices.push(p);
|
|
6207
6600
|
}
|
|
@@ -6209,113 +6602,111 @@
|
|
|
6209
6602
|
|
|
6210
6603
|
// reorder to top left most
|
|
6211
6604
|
|
|
6212
|
-
indices = indices.sort((a, b) =>
|
|
6605
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
6213
6606
|
newIndex = indices[0].index;
|
|
6214
6607
|
|
|
6215
|
-
return
|
|
6608
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
6216
6609
|
}
|
|
6217
6610
|
|
|
6218
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
6611
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
6219
6612
|
|
|
6220
|
-
let
|
|
6613
|
+
let pathDataN = pathData;
|
|
6221
6614
|
let l = pathData.length;
|
|
6222
6615
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
6223
6616
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
6224
6617
|
|
|
6225
|
-
let
|
|
6226
|
-
|
|
6227
|
-
// check if order is ideal
|
|
6228
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
6618
|
+
let hasLinetos = false;
|
|
6229
6619
|
|
|
6620
|
+
// check if path is closed by explicit lineto
|
|
6621
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6230
6622
|
let penultimateCom = pathData[idxPenultimate];
|
|
6231
6623
|
let penultimateType = penultimateCom.type;
|
|
6232
6624
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6233
6625
|
|
|
6234
6626
|
// last L command ends at M
|
|
6235
|
-
let
|
|
6627
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6628
|
+
let lastIsLine = penultimateType === 'L';
|
|
6236
6629
|
|
|
6237
|
-
//
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
let valsLast = pathData[idxPenultimate].values
|
|
6243
|
-
let valsLastLen = valsLast.length;
|
|
6244
|
-
pathData[idxPenultimate].values[valsLastLen-2] = M.x
|
|
6245
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
6246
|
-
*/
|
|
6247
|
-
|
|
6248
|
-
pathData.push({type:'Z', values:[]});
|
|
6249
|
-
isClosed = true;
|
|
6250
|
-
l++;
|
|
6251
|
-
}
|
|
6630
|
+
// create index
|
|
6631
|
+
let indices = [];
|
|
6632
|
+
for (let i = 0; i < l; i++) {
|
|
6633
|
+
let com = pathData[i];
|
|
6634
|
+
let { type, values, p0, p } = com;
|
|
6252
6635
|
|
|
6253
|
-
|
|
6254
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
6255
|
-
skipReorder = false;
|
|
6636
|
+
if(type==='L') hasLinetos = true;
|
|
6256
6637
|
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
}
|
|
6638
|
+
// exclude Z
|
|
6639
|
+
if (values.length) {
|
|
6640
|
+
values.slice(-2);
|
|
6261
6641
|
|
|
6262
|
-
|
|
6642
|
+
let x = Math.min(p0.x, p.x);
|
|
6643
|
+
let y = Math.min(p0.y, p.y);
|
|
6263
6644
|
|
|
6264
|
-
|
|
6645
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
6646
|
+
let prevType = prevCom.type;
|
|
6265
6647
|
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
let { type, values } = com;
|
|
6270
|
-
if (values.length) {
|
|
6271
|
-
let valsL = values.slice(-2);
|
|
6272
|
-
let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
|
|
6273
|
-
let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
|
|
6274
|
-
let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
|
|
6275
|
-
let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
|
|
6276
|
-
let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom };
|
|
6277
|
-
p.index = i;
|
|
6278
|
-
indices.push(p);
|
|
6279
|
-
}
|
|
6648
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
6649
|
+
item.index = i;
|
|
6650
|
+
indices.push(item);
|
|
6280
6651
|
}
|
|
6281
6652
|
|
|
6282
|
-
|
|
6653
|
+
}
|
|
6283
6654
|
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6655
|
+
let xMin = Infinity;
|
|
6656
|
+
let yMin = Infinity;
|
|
6657
|
+
let idx_top = null;
|
|
6658
|
+
let len = indices.length;
|
|
6287
6659
|
|
|
6288
|
-
|
|
6660
|
+
for (let i = 0; i < len; i++) {
|
|
6661
|
+
let com = indices[i];
|
|
6662
|
+
let { type, index, x, y, prevType } = com;
|
|
6289
6663
|
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6664
|
+
if (hasLinetos && prevType === 'L') {
|
|
6665
|
+
if (x < xMin && y < yMin) {
|
|
6666
|
+
idx_top = index-1;
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
if (y < yMin) {
|
|
6670
|
+
yMin = y;
|
|
6671
|
+
}
|
|
6296
6672
|
|
|
6297
|
-
|
|
6298
|
-
|
|
6673
|
+
if (x < xMin) {
|
|
6674
|
+
xMin = x;
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6299
6677
|
}
|
|
6300
6678
|
|
|
6301
|
-
|
|
6679
|
+
// shift to better starting point
|
|
6680
|
+
if (idx_top) {
|
|
6681
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
6302
6682
|
|
|
6303
|
-
|
|
6683
|
+
// update penultimate - reorder might have added new close paths
|
|
6684
|
+
l = pathDataN.length;
|
|
6685
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
6686
|
+
|
|
6687
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6688
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
6689
|
+
penultimateType = penultimateCom.type;
|
|
6690
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6691
|
+
lastIsLine = penultimateType ==='L';
|
|
6304
6692
|
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
penultimateType = penultimateCom.type;
|
|
6308
|
-
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
6693
|
+
// last L command ends at M
|
|
6694
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6309
6695
|
|
|
6310
|
-
|
|
6696
|
+
}
|
|
6311
6697
|
|
|
6312
|
-
|
|
6313
|
-
|
|
6698
|
+
// remove unnecessary closing lineto
|
|
6699
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
6700
|
+
pathDataN.splice(l - 2, 1);
|
|
6314
6701
|
}
|
|
6315
6702
|
|
|
6316
|
-
|
|
6703
|
+
// add close path
|
|
6704
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
6705
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
6706
|
+
}
|
|
6707
|
+
|
|
6708
|
+
return pathDataN
|
|
6317
6709
|
|
|
6318
|
-
return pathDataNew
|
|
6319
6710
|
}
|
|
6320
6711
|
|
|
6321
6712
|
/**
|
|
@@ -6747,7 +7138,7 @@
|
|
|
6747
7138
|
} = {}) {
|
|
6748
7139
|
|
|
6749
7140
|
// is stringified flat point attribute
|
|
6750
|
-
if(typeof pts === 'string' && !isNaN(pts[0])){
|
|
7141
|
+
if (typeof pts === 'string' && !isNaN(pts[0])) {
|
|
6751
7142
|
pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
|
|
6752
7143
|
return pts
|
|
6753
7144
|
}
|
|
@@ -6757,8 +7148,9 @@
|
|
|
6757
7148
|
return poly
|
|
6758
7149
|
}
|
|
6759
7150
|
|
|
6760
|
-
function polyArrayToObject(pts) {
|
|
7151
|
+
function polyArrayToObject(pts = []) {
|
|
6761
7152
|
|
|
7153
|
+
if (!pts.length) return [];
|
|
6762
7154
|
// is point object array
|
|
6763
7155
|
if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
|
|
6764
7156
|
|
|
@@ -6776,7 +7168,7 @@
|
|
|
6776
7168
|
return poly
|
|
6777
7169
|
}
|
|
6778
7170
|
|
|
6779
|
-
else if(pts.length>3){
|
|
7171
|
+
else if (pts.length > 3) {
|
|
6780
7172
|
pts = toPointArray(pts);
|
|
6781
7173
|
return pts
|
|
6782
7174
|
}
|
|
@@ -6805,13 +7197,13 @@
|
|
|
6805
7197
|
function toPointArray(pts) {
|
|
6806
7198
|
let ptArr = [];
|
|
6807
7199
|
|
|
6808
|
-
if(pts[0].length===2){
|
|
6809
|
-
for (let i = 0, l = pts.length; i < l; i
|
|
7200
|
+
if (pts[0].length === 2) {
|
|
7201
|
+
for (let i = 0, l = pts.length; i < l; i++) {
|
|
6810
7202
|
let pt = pts[i];
|
|
6811
|
-
ptArr.push({ x: pt[0], y:pt[1] });
|
|
7203
|
+
ptArr.push({ x: pt[0], y: pt[1] });
|
|
6812
7204
|
}
|
|
6813
7205
|
|
|
6814
|
-
}else {
|
|
7206
|
+
} else {
|
|
6815
7207
|
for (let i = 1, l = pts.length; i < l; i += 2) {
|
|
6816
7208
|
ptArr.push({ x: pts[i - 1], y: pts[i] });
|
|
6817
7209
|
}
|
|
@@ -6828,7 +7220,7 @@
|
|
|
6828
7220
|
|
|
6829
7221
|
switch(type){
|
|
6830
7222
|
case 'path':
|
|
6831
|
-
let pathData = parsePathDataNormalized(
|
|
7223
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
6832
7224
|
bb=getPolyBBox(getPathDataPoly(pathData));
|
|
6833
7225
|
|
|
6834
7226
|
break;
|
|
@@ -6870,8 +7262,8 @@
|
|
|
6870
7262
|
autoRoundValues = false,
|
|
6871
7263
|
minifyRgbColors = false,
|
|
6872
7264
|
removeInvalid = true,
|
|
6873
|
-
allowDataAtts=true,
|
|
6874
|
-
allowAriaAtts=true,
|
|
7265
|
+
allowDataAtts = true,
|
|
7266
|
+
allowAriaAtts = true,
|
|
6875
7267
|
removeDefaults = true,
|
|
6876
7268
|
cleanUpStrokes = true,
|
|
6877
7269
|
normalizeTransforms = true,
|
|
@@ -6937,7 +7329,7 @@
|
|
|
6937
7329
|
*/
|
|
6938
7330
|
|
|
6939
7331
|
if (removeInvalid || removeDefaults || removeNameSpaced) {
|
|
6940
|
-
let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
7332
|
+
let propsFilteredObj = filterSvgElProps(nodeName, props, { allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
6941
7333
|
props = propsFilteredObj.propsFiltered;
|
|
6942
7334
|
remove.push(...propsFilteredObj.remove);
|
|
6943
7335
|
|
|
@@ -7030,10 +7422,10 @@
|
|
|
7030
7422
|
|
|
7031
7423
|
if (prop !== 'transforms') {
|
|
7032
7424
|
|
|
7033
|
-
if (
|
|
7425
|
+
if ((prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
|
|
7034
7426
|
normalizedDiagonal = true;
|
|
7035
7427
|
for (let i = 0; i < values.length; i++) {
|
|
7036
|
-
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize });
|
|
7428
|
+
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize, autoRoundValues });
|
|
7037
7429
|
valsNew.push(val);
|
|
7038
7430
|
}
|
|
7039
7431
|
}
|
|
@@ -7075,14 +7467,12 @@
|
|
|
7075
7467
|
if (prop === 'scale' && unit === '%') {
|
|
7076
7468
|
valAbs = valAbs * 0.01;
|
|
7077
7469
|
} else {
|
|
7078
|
-
if (prop === 'r')
|
|
7470
|
+
if (prop === 'r' && width!==height) normalizedDiagonal = true;
|
|
7079
7471
|
valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, normalizedDiagonal, fontSize });
|
|
7080
7472
|
|
|
7081
7473
|
if (autoRoundValues && isNumeric) {
|
|
7082
7474
|
valAbs = autoRound(valAbs);
|
|
7083
|
-
|
|
7084
7475
|
}
|
|
7085
|
-
|
|
7086
7476
|
}
|
|
7087
7477
|
}
|
|
7088
7478
|
valsNew.push(valAbs);
|
|
@@ -7283,6 +7673,7 @@
|
|
|
7283
7673
|
removeIds = false,
|
|
7284
7674
|
removeClassNames = false,
|
|
7285
7675
|
exclude = [],
|
|
7676
|
+
inheritedProps = null,
|
|
7286
7677
|
} = {}) {
|
|
7287
7678
|
let propsFiltered = {};
|
|
7288
7679
|
let remove = [];
|
|
@@ -7311,7 +7702,7 @@
|
|
|
7311
7702
|
let isMeta = prop === 'title';
|
|
7312
7703
|
let isAria = prop.startsWith('aria-');
|
|
7313
7704
|
|
|
7314
|
-
if
|
|
7705
|
+
if ((allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta)) continue
|
|
7315
7706
|
|
|
7316
7707
|
// filter out defaults
|
|
7317
7708
|
let isDefault = removeDefaults ?
|
|
@@ -7322,6 +7713,7 @@
|
|
|
7322
7713
|
|
|
7323
7714
|
if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
|
|
7324
7715
|
if (include.includes(prop)) isValid = true;
|
|
7716
|
+
if (exclude.includes(prop)) isValid = false;
|
|
7325
7717
|
|
|
7326
7718
|
if (isValid) {
|
|
7327
7719
|
propsFiltered[prop] = props[prop];
|
|
@@ -7554,6 +7946,357 @@
|
|
|
7554
7946
|
return "";
|
|
7555
7947
|
}
|
|
7556
7948
|
|
|
7949
|
+
// Legendre Gauss weight and abscissa values
|
|
7950
|
+
const waArr_global = [];
|
|
7951
|
+
|
|
7952
|
+
function getLength(pts, {
|
|
7953
|
+
t = 1,
|
|
7954
|
+
waArr = []
|
|
7955
|
+
} = {}) {
|
|
7956
|
+
|
|
7957
|
+
const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
|
|
7958
|
+
if (t === 0) {
|
|
7959
|
+
return 0;
|
|
7960
|
+
}
|
|
7961
|
+
|
|
7962
|
+
t = t > 1 ? 1 : t < 0 ? 0 : t;
|
|
7963
|
+
let t2 = t / 2;
|
|
7964
|
+
|
|
7965
|
+
/**
|
|
7966
|
+
* set higher legendre gauss weight abscissae values
|
|
7967
|
+
* by more accurate weight/abscissae lookups
|
|
7968
|
+
* https://pomax.github.io/bezierinfo/legendre-gauss.html
|
|
7969
|
+
*/
|
|
7970
|
+
|
|
7971
|
+
let sum = 0;
|
|
7972
|
+
|
|
7973
|
+
let x0 = p0.x, y0 = p0.y, cp1x = cp1.x, cp1y = cp1.y, cp2x = cp2.x, cp2y = cp2.y, px = p.x, py = p.y;
|
|
7974
|
+
|
|
7975
|
+
for (let i = 0, len = wa.length; i < len; i++) {
|
|
7976
|
+
// weight and abscissae
|
|
7977
|
+
let [w, a] = [wa[i][0], wa[i][1]];
|
|
7978
|
+
let ct1_t = t2 * a;
|
|
7979
|
+
let ct0 = -ct1_t + t2;
|
|
7980
|
+
|
|
7981
|
+
let xbase0 = base3(ct0, x0, cp1x, cp2x, px);
|
|
7982
|
+
let ybase0 = base3(ct0, y0, cp1y, cp2y, py);
|
|
7983
|
+
|
|
7984
|
+
let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
|
|
7985
|
+
|
|
7986
|
+
sum += w * Math.sqrt(comb0);
|
|
7987
|
+
|
|
7988
|
+
}
|
|
7989
|
+
return t2 * sum;
|
|
7990
|
+
};
|
|
7991
|
+
|
|
7992
|
+
const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
|
|
7993
|
+
if (t === 0) {
|
|
7994
|
+
return 0;
|
|
7995
|
+
}
|
|
7996
|
+
// is flat/linear – treat as line
|
|
7997
|
+
if (checkFlat) {
|
|
7998
|
+
let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
|
|
7999
|
+
let l2 = getDistance(p0, p);
|
|
8000
|
+
if (l1 === l2) {
|
|
8001
|
+
return l2;
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
|
|
8005
|
+
let a, b, c, d, e, e1, d1, v1x, v1y;
|
|
8006
|
+
v1x = cp1.x * 2;
|
|
8007
|
+
v1y = cp1.y * 2;
|
|
8008
|
+
d = p0.x - v1x + p.x;
|
|
8009
|
+
d1 = p0.y - v1y + p.y;
|
|
8010
|
+
e = v1x - 2 * p0.x;
|
|
8011
|
+
e1 = v1y - 2 * p0.y;
|
|
8012
|
+
a = 4 * (d * d + d1 * d1);
|
|
8013
|
+
b = 4 * (d * e + d1 * e1);
|
|
8014
|
+
c = e * e + e1 * e1;
|
|
8015
|
+
|
|
8016
|
+
const bt = b / (2 * a),
|
|
8017
|
+
ct = c / a,
|
|
8018
|
+
ut = t + bt,
|
|
8019
|
+
|
|
8020
|
+
k = ct - bt * bt;
|
|
8021
|
+
|
|
8022
|
+
return (
|
|
8023
|
+
(Math.sqrt(a) / 2) *
|
|
8024
|
+
(ut * Math.sqrt(ut * ut + k) -
|
|
8025
|
+
bt * Math.sqrt(bt * bt + k) +
|
|
8026
|
+
k *
|
|
8027
|
+
Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
|
|
8028
|
+
);
|
|
8029
|
+
};
|
|
8030
|
+
|
|
8031
|
+
let length;
|
|
8032
|
+
if (pts.length === 4) {
|
|
8033
|
+
length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr);
|
|
8034
|
+
|
|
8035
|
+
}
|
|
8036
|
+
else if (pts.length === 3) {
|
|
8037
|
+
length = quadraticBezierLength(pts[0], pts[1], pts[2], t);
|
|
8038
|
+
}
|
|
8039
|
+
else {
|
|
8040
|
+
length = getDistance(pts[0], pts[1]);
|
|
8041
|
+
}
|
|
8042
|
+
|
|
8043
|
+
return length;
|
|
8044
|
+
}
|
|
8045
|
+
|
|
8046
|
+
// LG weight/abscissae generator
|
|
8047
|
+
function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
8048
|
+
|
|
8049
|
+
let waArr = [];
|
|
8050
|
+
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
8051
|
+
const m = (n + 1) >> 1;
|
|
8052
|
+
xm = 0.5 * (x2 + x1);
|
|
8053
|
+
xl = 0.5 * (x2 - x1);
|
|
8054
|
+
|
|
8055
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
8056
|
+
z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
8057
|
+
do {
|
|
8058
|
+
p1 = 1;
|
|
8059
|
+
p2 = 0;
|
|
8060
|
+
for (let j = 0; j < n; j++) {
|
|
8061
|
+
|
|
8062
|
+
p3 = p2;
|
|
8063
|
+
p2 = p1;
|
|
8064
|
+
p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
|
|
8065
|
+
}
|
|
8066
|
+
|
|
8067
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
8068
|
+
z1 = z;
|
|
8069
|
+
z = z1 - p1 / pp; //Newton’s method
|
|
8070
|
+
|
|
8071
|
+
} while (Math.abs(z - z1) > 1.0e-14);
|
|
8072
|
+
|
|
8073
|
+
let weight = (2 * xl) / ((1 - z * z) * pp * pp);
|
|
8074
|
+
let abscissa = xm + xl * z;
|
|
8075
|
+
|
|
8076
|
+
waArr.push(
|
|
8077
|
+
[weight, -abscissa],
|
|
8078
|
+
[weight, abscissa],
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
return waArr;
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8085
|
+
function base3(t, p1, p2, p3, p4) {
|
|
8086
|
+
let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
|
|
8087
|
+
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
8088
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
8089
|
+
}
|
|
8090
|
+
|
|
8091
|
+
function getPolygonLength(pts=[], isPoly=false){
|
|
8092
|
+
|
|
8093
|
+
let len = 0;
|
|
8094
|
+
let l=pts.length;
|
|
8095
|
+
|
|
8096
|
+
for(let i=1; i<l; i++){
|
|
8097
|
+
let p1 = pts[i-1];
|
|
8098
|
+
let p2 = pts[i];
|
|
8099
|
+
len += getDistance(p1, p2);
|
|
8100
|
+
}
|
|
8101
|
+
if(isPoly){
|
|
8102
|
+
len += getDistance(pts[l-1], pts[0]);
|
|
8103
|
+
}
|
|
8104
|
+
return len
|
|
8105
|
+
}
|
|
8106
|
+
|
|
8107
|
+
/**
|
|
8108
|
+
* Ramanujan approximation
|
|
8109
|
+
* based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
|
|
8110
|
+
*/
|
|
8111
|
+
function getEllipseLength(rx=0, ry=0) {
|
|
8112
|
+
// is circle
|
|
8113
|
+
if (rx === ry) {
|
|
8114
|
+
|
|
8115
|
+
return 2 * Math.PI * rx;
|
|
8116
|
+
}
|
|
8117
|
+
|
|
8118
|
+
let c=rx+ry;
|
|
8119
|
+
let d = (rx - ry) / c;
|
|
8120
|
+
let h = d*d;
|
|
8121
|
+
|
|
8122
|
+
let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
|
|
8123
|
+
return totalLength;
|
|
8124
|
+
}
|
|
8125
|
+
|
|
8126
|
+
/**
|
|
8127
|
+
* ellipse helpers
|
|
8128
|
+
* approximate ellipse length
|
|
8129
|
+
* by Legendre-Gauss
|
|
8130
|
+
*/
|
|
8131
|
+
|
|
8132
|
+
function getCircleArcLength(r = 0, deltaAngle = 0) {
|
|
8133
|
+
if(r===0) {
|
|
8134
|
+
console.warn('Radius must be positive');
|
|
8135
|
+
return 0;
|
|
8136
|
+
}
|
|
8137
|
+
let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI));
|
|
8138
|
+
return len
|
|
8139
|
+
}
|
|
8140
|
+
|
|
8141
|
+
function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
|
|
8142
|
+
|
|
8143
|
+
// Transform [-1, 1] interval to [startAngle, endAngle]
|
|
8144
|
+
let halfInterval = (endAngle - startAngle) * 0.5;
|
|
8145
|
+
let midpoint = (endAngle + startAngle) * 0.5;
|
|
8146
|
+
|
|
8147
|
+
// Arc length integral approximation
|
|
8148
|
+
let arcLength = 0;
|
|
8149
|
+
for (let i = 0; i < wa.length; i++) {
|
|
8150
|
+
let [weight, abscissae] = wa[i];
|
|
8151
|
+
let theta = midpoint + halfInterval * abscissae;
|
|
8152
|
+
|
|
8153
|
+
let a = rx * Math.sin(theta);
|
|
8154
|
+
let b = ry * Math.cos(theta);
|
|
8155
|
+
let integrand = Math.sqrt(
|
|
8156
|
+
a * a + b * b
|
|
8157
|
+
);
|
|
8158
|
+
arcLength += weight * integrand;
|
|
8159
|
+
}
|
|
8160
|
+
|
|
8161
|
+
return Math.abs(halfInterval * arcLength)
|
|
8162
|
+
}
|
|
8163
|
+
|
|
8164
|
+
function getPathDataLength(pathData = []) {
|
|
8165
|
+
let len = 0;
|
|
8166
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
8167
|
+
|
|
8168
|
+
for (let i = 0; i < pathDataArr.length; i++) {
|
|
8169
|
+
let pathData = pathDataArr[i];
|
|
8170
|
+
|
|
8171
|
+
// add verbose point data if not present
|
|
8172
|
+
if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
|
|
8173
|
+
|
|
8174
|
+
// Calculate Legendre Gauss weight and abscissa values
|
|
8175
|
+
if (!waArr_global.length) {
|
|
8176
|
+
|
|
8177
|
+
let waArr = getLegendreGaussValues(48);
|
|
8178
|
+
waArr.forEach(wa => {
|
|
8179
|
+
waArr_global.push(wa);
|
|
8180
|
+
});
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8183
|
+
let waArr = waArr_global;
|
|
8184
|
+
|
|
8185
|
+
pathData.forEach(com => {
|
|
8186
|
+
let { type, values, p0, p, cp1 = null, cp2 = null } = com;
|
|
8187
|
+
let pts = [p0];
|
|
8188
|
+
if (type === 'C' || type === 'Q') pts.push(cp1);
|
|
8189
|
+
if (type === 'C') pts.push(cp2);
|
|
8190
|
+
pts.push(p);
|
|
8191
|
+
let comLen = 0;
|
|
8192
|
+
|
|
8193
|
+
if (type === 'A') {
|
|
8194
|
+
|
|
8195
|
+
// get parametrized arc properties
|
|
8196
|
+
let [largeArc, sweep] = [com.values[3], com.values[4]];
|
|
8197
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false);
|
|
8198
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData;
|
|
8199
|
+
|
|
8200
|
+
if (rx === ry) {
|
|
8201
|
+
comLen = getCircleArcLength(rx, Math.abs(deltaAngle));
|
|
8202
|
+
}
|
|
8203
|
+
|
|
8204
|
+
// is ellipse
|
|
8205
|
+
else {
|
|
8206
|
+
xAxisRotation = xAxisRotation * deg2rad;
|
|
8207
|
+
startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry);
|
|
8208
|
+
endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry);
|
|
8209
|
+
|
|
8210
|
+
// recalculate parametrized delta
|
|
8211
|
+
let deltaAngle_param = endAngle - startAngle;
|
|
8212
|
+
|
|
8213
|
+
let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
|
|
8214
|
+
|
|
8215
|
+
deltaAngle = signChange ? deltaAngle : deltaAngle_param;
|
|
8216
|
+
|
|
8217
|
+
// adjust end angle
|
|
8218
|
+
if (sweep && startAngle > endAngle) {
|
|
8219
|
+
endAngle += Math.PI * 2;
|
|
8220
|
+
}
|
|
8221
|
+
|
|
8222
|
+
if (!sweep && startAngle < endAngle) {
|
|
8223
|
+
endAngle -= Math.PI * 2;
|
|
8224
|
+
}
|
|
8225
|
+
comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr);
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
|
|
8229
|
+
else {
|
|
8230
|
+
comLen = getLength(pts, {
|
|
8231
|
+
t: 1,
|
|
8232
|
+
waArr
|
|
8233
|
+
});
|
|
8234
|
+
}
|
|
8235
|
+
len += comLen;
|
|
8236
|
+
});
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
return len;
|
|
8240
|
+
}
|
|
8241
|
+
|
|
8242
|
+
function getElementLength(el, {
|
|
8243
|
+
props = {},
|
|
8244
|
+
pathLength = 0,
|
|
8245
|
+
} = {}) {
|
|
8246
|
+
|
|
8247
|
+
let nodeName = el.nodeName;
|
|
8248
|
+
let len = 0;
|
|
8249
|
+
|
|
8250
|
+
props = JSON.parse(JSON.stringify(props));
|
|
8251
|
+
|
|
8252
|
+
for (let prop in props) {
|
|
8253
|
+
if (props[prop] && props[prop].length && props[prop].length === 1) {
|
|
8254
|
+
props[prop] = props[prop][0];
|
|
8255
|
+
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
|
|
8259
|
+
let { x = 0, y = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, width = 0, height = 0, r = 0, rx = 0, ry = 0, cx = 0, cy = 0 } = props;
|
|
8260
|
+
|
|
8261
|
+
let pts = nodeName === 'polygon' || nodeName === 'polyline' ? el.getAttribute('points') : [];
|
|
8262
|
+
let isPolygon = nodeName === 'polygon';
|
|
8263
|
+
if (pts.length) {
|
|
8264
|
+
pts = normalizePoly(pts);
|
|
8265
|
+
}
|
|
8266
|
+
|
|
8267
|
+
// we need to convert rects with corner rounding
|
|
8268
|
+
let pathData = [];
|
|
8269
|
+
if (nodeName === 'rect' && (rx || ry)) {
|
|
8270
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
8271
|
+
nodeName = 'path';
|
|
8272
|
+
}
|
|
8273
|
+
|
|
8274
|
+
switch (nodeName) {
|
|
8275
|
+
case 'line':
|
|
8276
|
+
len = getDistance({ x: x1, y: y1 }, { x: x2, y: y2 });
|
|
8277
|
+
break;
|
|
8278
|
+
case 'rect':
|
|
8279
|
+
len = width * 2 + height * 2;
|
|
8280
|
+
break;
|
|
8281
|
+
case 'circle':
|
|
8282
|
+
len = 2 * Math.PI * r;
|
|
8283
|
+
break;
|
|
8284
|
+
case 'ellipse':
|
|
8285
|
+
len = getEllipseLength(rx, ry);
|
|
8286
|
+
break;
|
|
8287
|
+
case 'polygon':
|
|
8288
|
+
case 'polyline':
|
|
8289
|
+
len = getPolygonLength(pts, isPolygon);
|
|
8290
|
+
break;
|
|
8291
|
+
case 'path':
|
|
8292
|
+
pathData = pathData.length ? pathData : parsePathDataNormalized(el.getAttribute('d'));
|
|
8293
|
+
len = getPathDataLength(pathData);
|
|
8294
|
+
break;
|
|
8295
|
+
}
|
|
8296
|
+
|
|
8297
|
+
return len
|
|
8298
|
+
}
|
|
8299
|
+
|
|
7557
8300
|
function removeHiddenSvgEls(svg) {
|
|
7558
8301
|
let els = svg.querySelectorAll('*');
|
|
7559
8302
|
els.forEach(el => {
|
|
@@ -7602,8 +8345,12 @@
|
|
|
7602
8345
|
*/
|
|
7603
8346
|
|
|
7604
8347
|
function removeSvgAtts(svg, remove = []) {
|
|
8348
|
+
removeAtts(svg, remove);
|
|
8349
|
+
}
|
|
8350
|
+
|
|
8351
|
+
function removeAtts(el, remove = []) {
|
|
7605
8352
|
remove.forEach(att => {
|
|
7606
|
-
|
|
8353
|
+
el.removeAttribute(att);
|
|
7607
8354
|
});
|
|
7608
8355
|
}
|
|
7609
8356
|
|
|
@@ -7701,22 +8448,108 @@
|
|
|
7701
8448
|
}
|
|
7702
8449
|
*/
|
|
7703
8450
|
|
|
8451
|
+
function setNormalizedTransformsToEl(el, {
|
|
8452
|
+
styleProps = {},
|
|
8453
|
+
} = {}) {
|
|
8454
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
8455
|
+
let name = el.nodeName.toLowerCase();
|
|
8456
|
+
|
|
8457
|
+
if(!matrix) return styleProps;
|
|
8458
|
+
|
|
8459
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8460
|
+
|
|
8461
|
+
// scale attributes instead of transform
|
|
8462
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8463
|
+
let unProportional = scaleX !== scaleY;
|
|
8464
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8465
|
+
|
|
8466
|
+
let needsTrans = (hasRot) || unProportional;
|
|
8467
|
+
needsTrans = true;
|
|
8468
|
+
|
|
8469
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8470
|
+
|
|
8471
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
8472
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8473
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8474
|
+
|
|
8475
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8476
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8477
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8478
|
+
|
|
8479
|
+
}
|
|
8480
|
+
else if (name === 'rect') {
|
|
8481
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8482
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8483
|
+
|
|
8484
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8485
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8486
|
+
|
|
8487
|
+
styleProps.x = [x];
|
|
8488
|
+
styleProps.y = [y];
|
|
8489
|
+
|
|
8490
|
+
styleProps.rx = [rx];
|
|
8491
|
+
styleProps.ry = [ry];
|
|
8492
|
+
|
|
8493
|
+
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8494
|
+
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8497
|
+
// remove now obsolete transform properties
|
|
8498
|
+
delete styleProps.matrix;
|
|
8499
|
+
delete styleProps.transformArr;
|
|
8500
|
+
delete styleProps.transComponents;
|
|
8501
|
+
|
|
8502
|
+
// mark transform attribute for removal
|
|
8503
|
+
styleProps.remove.push('transform');
|
|
8504
|
+
|
|
8505
|
+
// scale props like stroke width or dash-array
|
|
8506
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8507
|
+
|
|
8508
|
+
} else {
|
|
8509
|
+
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8510
|
+
|
|
8511
|
+
}
|
|
8512
|
+
|
|
8513
|
+
return styleProps
|
|
8514
|
+
|
|
8515
|
+
}
|
|
8516
|
+
|
|
8517
|
+
function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8518
|
+
if (scale === 1 || !props.length) return props;
|
|
8519
|
+
|
|
8520
|
+
for (let i = 0; i < props.length; i++) {
|
|
8521
|
+
let prop = props[i];
|
|
8522
|
+
|
|
8523
|
+
if (styleProps[prop] !== undefined) {
|
|
8524
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 3) : val * scale);
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
return styleProps
|
|
8528
|
+
}
|
|
8529
|
+
|
|
7704
8530
|
function convertPathLengthAtt(el, {
|
|
7705
8531
|
styleProps = {}
|
|
7706
|
-
}={}) {
|
|
8532
|
+
} = {}) {
|
|
7707
8533
|
|
|
7708
|
-
let pathLength =
|
|
8534
|
+
let pathLength = styleProps['pathLength'];
|
|
7709
8535
|
|
|
7710
|
-
if (pathLength
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
8536
|
+
if (pathLength) {
|
|
8537
|
+
|
|
8538
|
+
if ((styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
|
|
8539
|
+
let elLength = getElementLength(el, {
|
|
8540
|
+
pathLength,
|
|
8541
|
+
props: styleProps
|
|
8542
|
+
});
|
|
8543
|
+
|
|
8544
|
+
let scale = elLength / pathLength;
|
|
7715
8545
|
|
|
7716
|
-
|
|
8546
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
7717
8547
|
|
|
7718
|
-
|
|
7719
|
-
|
|
8548
|
+
// set absolute
|
|
8549
|
+
if (styleProps['stroke-dasharray']) el.setAttribute('stroke-dasharray', styleProps['stroke-dasharray'].join(' '));
|
|
8550
|
+
if (styleProps['stroke-dashoffset']) el.setAttribute('stroke-dashoffset', styleProps['stroke-dashoffset'][0]);
|
|
8551
|
+
|
|
8552
|
+
}
|
|
7720
8553
|
|
|
7721
8554
|
// tag for removal
|
|
7722
8555
|
delete styleProps['pathLength'];
|
|
@@ -8051,85 +8884,6 @@
|
|
|
8051
8884
|
return props
|
|
8052
8885
|
}
|
|
8053
8886
|
|
|
8054
|
-
function setNormalizedTransformsToEl(el, {
|
|
8055
|
-
styleProps = {},
|
|
8056
|
-
} = {}) {
|
|
8057
|
-
let { remove, matrix, transComponents } = styleProps;
|
|
8058
|
-
let name = el.nodeName.toLowerCase();
|
|
8059
|
-
|
|
8060
|
-
if(!matrix) return styleProps;
|
|
8061
|
-
|
|
8062
|
-
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8063
|
-
|
|
8064
|
-
// scale attributes instead of transform
|
|
8065
|
-
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8066
|
-
let unProportional = scaleX !== scaleY;
|
|
8067
|
-
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8068
|
-
|
|
8069
|
-
let needsTrans = (hasRot) || unProportional;
|
|
8070
|
-
needsTrans = true;
|
|
8071
|
-
|
|
8072
|
-
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8073
|
-
|
|
8074
|
-
if (name === 'circle' || name === 'ellipse') {
|
|
8075
|
-
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8076
|
-
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8077
|
-
|
|
8078
|
-
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8079
|
-
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8080
|
-
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8081
|
-
|
|
8082
|
-
}
|
|
8083
|
-
else if (name === 'rect') {
|
|
8084
|
-
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8085
|
-
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8086
|
-
|
|
8087
|
-
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8088
|
-
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8089
|
-
|
|
8090
|
-
styleProps.x = [x];
|
|
8091
|
-
styleProps.y = [y];
|
|
8092
|
-
|
|
8093
|
-
styleProps.rx = [rx];
|
|
8094
|
-
styleProps.ry = [ry];
|
|
8095
|
-
|
|
8096
|
-
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8097
|
-
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8098
|
-
}
|
|
8099
|
-
|
|
8100
|
-
// remove now obsolete transform properties
|
|
8101
|
-
delete styleProps.matrix;
|
|
8102
|
-
delete styleProps.transformArr;
|
|
8103
|
-
delete styleProps.transComponents;
|
|
8104
|
-
|
|
8105
|
-
// mark transform attribute for removal
|
|
8106
|
-
styleProps.remove.push('transform');
|
|
8107
|
-
|
|
8108
|
-
// scale props like stroke width or dash-array
|
|
8109
|
-
styleProps = scaleProps$1(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8110
|
-
|
|
8111
|
-
} else {
|
|
8112
|
-
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8113
|
-
|
|
8114
|
-
}
|
|
8115
|
-
|
|
8116
|
-
return styleProps
|
|
8117
|
-
|
|
8118
|
-
}
|
|
8119
|
-
|
|
8120
|
-
function scaleProps$1(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8121
|
-
if (scale === 1 || !props.length) return props;
|
|
8122
|
-
|
|
8123
|
-
for (let i = 0; i < props.length; i++) {
|
|
8124
|
-
let prop = props[i];
|
|
8125
|
-
|
|
8126
|
-
if (styleProps[prop] !== undefined) {
|
|
8127
|
-
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
|
|
8128
|
-
}
|
|
8129
|
-
}
|
|
8130
|
-
return styleProps
|
|
8131
|
-
}
|
|
8132
|
-
|
|
8133
8887
|
function cleanUpSVG(svgMarkup, {
|
|
8134
8888
|
removeHidden = true,
|
|
8135
8889
|
|
|
@@ -8159,7 +8913,10 @@
|
|
|
8159
8913
|
cleanupSVGAtts = true,
|
|
8160
8914
|
removeNameSpaced = true,
|
|
8161
8915
|
removeNameSpacedAtts = true,
|
|
8916
|
+
|
|
8917
|
+
// unit conversions
|
|
8162
8918
|
convertPathLength = false,
|
|
8919
|
+
toAbsoluteUnits = false,
|
|
8163
8920
|
|
|
8164
8921
|
// meta
|
|
8165
8922
|
allowMeta = false,
|
|
@@ -8184,10 +8941,10 @@
|
|
|
8184
8941
|
} = {}) {
|
|
8185
8942
|
|
|
8186
8943
|
// resolve dependencies
|
|
8187
|
-
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8188
|
-
|
|
8944
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8945
|
+
stylesToAttributes = true;
|
|
8189
8946
|
|
|
8190
|
-
if(stylesToAttributes) cleanUpStrokes = true;
|
|
8947
|
+
if (stylesToAttributes) cleanUpStrokes = true;
|
|
8191
8948
|
|
|
8192
8949
|
// replace namespaced refs
|
|
8193
8950
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
@@ -8232,7 +8989,7 @@
|
|
|
8232
8989
|
removeClassNames,
|
|
8233
8990
|
minifyRgbColors,
|
|
8234
8991
|
stylesheetProps: {},
|
|
8235
|
-
exclude:[]
|
|
8992
|
+
exclude: []
|
|
8236
8993
|
};
|
|
8237
8994
|
|
|
8238
8995
|
// root svg inline style properties
|
|
@@ -8314,9 +9071,13 @@
|
|
|
8314
9071
|
let stylePropsFiltered = {};
|
|
8315
9072
|
|
|
8316
9073
|
// convert pathLength before transforming
|
|
8317
|
-
if
|
|
9074
|
+
if(convertTransforms || attributesToGroup) convertPathLength=true;
|
|
9075
|
+
|
|
9076
|
+
if (convertPathLength ) {
|
|
9077
|
+
|
|
8318
9078
|
styleProps = convertPathLengthAtt(el, { styleProps });
|
|
8319
9079
|
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
9080
|
+
|
|
8320
9081
|
}
|
|
8321
9082
|
|
|
8322
9083
|
// get parent styles
|
|
@@ -8349,9 +9110,14 @@
|
|
|
8349
9110
|
if (stylePropsSVG['class']) delete stylePropsSVG['class'];
|
|
8350
9111
|
if (stylePropsSVG['id']) delete stylePropsSVG['id'];
|
|
8351
9112
|
|
|
9113
|
+
// add svg props
|
|
9114
|
+
inheritedProps = {
|
|
9115
|
+
...stylePropsSVG,
|
|
9116
|
+
...inheritedProps,
|
|
9117
|
+
};
|
|
9118
|
+
|
|
8352
9119
|
// merge with svg props
|
|
8353
9120
|
styleProps = {
|
|
8354
|
-
...stylePropsSVG,
|
|
8355
9121
|
...inheritedProps,
|
|
8356
9122
|
...styleProps
|
|
8357
9123
|
};
|
|
@@ -8393,6 +9159,40 @@
|
|
|
8393
9159
|
// general cleanup
|
|
8394
9160
|
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
8395
9161
|
|
|
9162
|
+
// all relative units to absolute
|
|
9163
|
+
if (toAbsoluteUnits) {
|
|
9164
|
+
normalizeTransforms = true;
|
|
9165
|
+
|
|
9166
|
+
/**
|
|
9167
|
+
* apply consolidated
|
|
9168
|
+
* element attributes
|
|
9169
|
+
* remove non-supported element props
|
|
9170
|
+
*/
|
|
9171
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
9172
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
9173
|
+
|
|
9174
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9175
|
+
let values = styleProps[prop];
|
|
9176
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
9177
|
+
el.setAttribute(prop, val);
|
|
9178
|
+
}
|
|
9179
|
+
|
|
9180
|
+
let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
9181
|
+
|
|
9182
|
+
// check if same value is in inherited
|
|
9183
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9184
|
+
let valInh = inheritedProps[prop] || [];
|
|
9185
|
+
let val = stylePropsFiltered.propsFiltered[prop] || [];
|
|
9186
|
+
if (valInh.join() === val.join()) {
|
|
9187
|
+
removeAttsEl.push(prop);
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
|
|
9191
|
+
// remove obsolete/inherited
|
|
9192
|
+
removeAtts(el, removeAttsEl);
|
|
9193
|
+
|
|
9194
|
+
}
|
|
9195
|
+
|
|
8396
9196
|
if (stylesToAttributes) {
|
|
8397
9197
|
|
|
8398
9198
|
/**
|
|
@@ -8411,7 +9211,7 @@
|
|
|
8411
9211
|
* remove non-supported element props
|
|
8412
9212
|
*/
|
|
8413
9213
|
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
8414
|
-
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
9214
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
8415
9215
|
|
|
8416
9216
|
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
8417
9217
|
|
|
@@ -8425,12 +9225,14 @@
|
|
|
8425
9225
|
* remove obsolete
|
|
8426
9226
|
* attributes
|
|
8427
9227
|
*/
|
|
9228
|
+
removeAtts(el, remove);
|
|
8428
9229
|
|
|
9230
|
+
/*
|
|
8429
9231
|
for (let i = 0; i < remove.length; i++) {
|
|
8430
9232
|
let att = remove[i];
|
|
8431
|
-
|
|
8432
|
-
el.removeAttribute(att);
|
|
9233
|
+
el.removeAttribute(att)
|
|
8433
9234
|
}
|
|
9235
|
+
*/
|
|
8434
9236
|
|
|
8435
9237
|
} // endof style processing
|
|
8436
9238
|
|
|
@@ -8453,7 +9255,7 @@
|
|
|
8453
9255
|
|
|
8454
9256
|
// scale props like stroke width or dash-array before conversion
|
|
8455
9257
|
if (matrix && transComponents) {
|
|
8456
|
-
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
9258
|
+
['stroke-width', 'stroke-dasharray', 'stroke-dashoffset'].forEach(att => {
|
|
8457
9259
|
let attVal = el.getAttribute(att);
|
|
8458
9260
|
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : [];
|
|
8459
9261
|
if (vals.length) el.setAttribute(att, vals.join(' '));
|
|
@@ -8515,15 +9317,15 @@
|
|
|
8515
9317
|
let values = stylePropsFiltered[prop];
|
|
8516
9318
|
let val = values.length ? values.join(' ') : values[0];
|
|
8517
9319
|
|
|
8518
|
-
if(prop!=='class' && prop!=='id'){
|
|
9320
|
+
if (prop !== 'class' && prop !== 'id') {
|
|
8519
9321
|
|
|
8520
9322
|
let propShort = toShortStr(prop);
|
|
8521
9323
|
let valShort = toShortStr(val);
|
|
8522
9324
|
let propStr = `${propShort}-${valShort}`;
|
|
8523
|
-
|
|
9325
|
+
|
|
8524
9326
|
// store in node property
|
|
8525
9327
|
if (!el.styleSet) el.styleSet = new Set();
|
|
8526
|
-
if(propStr) el.styleSet.add(propStr);
|
|
9328
|
+
if (propStr) el.styleSet.add(propStr);
|
|
8527
9329
|
}
|
|
8528
9330
|
}
|
|
8529
9331
|
|
|
@@ -8572,7 +9374,7 @@
|
|
|
8572
9374
|
}
|
|
8573
9375
|
|
|
8574
9376
|
function removeEmptyClassAtts(svg) {
|
|
8575
|
-
let emptyClassEls = svg.querySelectorAll('[class=""');
|
|
9377
|
+
let emptyClassEls = svg.querySelectorAll('[class=""]');
|
|
8576
9378
|
emptyClassEls.forEach(el => {
|
|
8577
9379
|
el.removeAttribute('class');
|
|
8578
9380
|
});
|
|
@@ -8585,7 +9387,7 @@
|
|
|
8585
9387
|
|
|
8586
9388
|
let els = svg.querySelectorAll(renderedEls.join(', '));
|
|
8587
9389
|
let len = els.length;
|
|
8588
|
-
if(len===1) return;
|
|
9390
|
+
if (len === 1) return;
|
|
8589
9391
|
|
|
8590
9392
|
let el0 = els[0] || null;
|
|
8591
9393
|
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
|
|
@@ -8661,7 +9463,7 @@
|
|
|
8661
9463
|
if (children.length === 1) continue
|
|
8662
9464
|
|
|
8663
9465
|
// create new group
|
|
8664
|
-
if (!groupEl || groups.length>1) {
|
|
9466
|
+
if (!groupEl || groups.length > 1) {
|
|
8665
9467
|
|
|
8666
9468
|
groupEl = document.createElementNS(svgNs, 'g');
|
|
8667
9469
|
child0.parentNode.insertBefore(groupEl, child0);
|
|
@@ -8743,7 +9545,9 @@
|
|
|
8743
9545
|
bb0.bottom = y + height;
|
|
8744
9546
|
|
|
8745
9547
|
els.forEach(el => {
|
|
9548
|
+
|
|
8746
9549
|
let bb = getElBBox(el);
|
|
9550
|
+
|
|
8747
9551
|
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom;
|
|
8748
9552
|
if (outside) el.remove();
|
|
8749
9553
|
});
|
|
@@ -8852,8 +9656,130 @@
|
|
|
8852
9656
|
});
|
|
8853
9657
|
}
|
|
8854
9658
|
|
|
9659
|
+
function getArcFromPoly(pts, precise = false) {
|
|
9660
|
+
if (pts.length < 3) return false
|
|
9661
|
+
|
|
9662
|
+
// Pick 3 well-spaced points
|
|
9663
|
+
let len = pts.length;
|
|
9664
|
+
let idx1 = Math.floor(len * 0.333);
|
|
9665
|
+
let idx2 = Math.floor(len * 0.666);
|
|
9666
|
+
let idx3 = Math.floor(len * 0.5);
|
|
9667
|
+
|
|
9668
|
+
let p1 = pts[0];
|
|
9669
|
+
let p2 = pts[idx3];
|
|
9670
|
+
let p3 = pts[len - 1];
|
|
9671
|
+
|
|
9672
|
+
// Radius (use start point)
|
|
9673
|
+
let pts1 = [p1, p2, p3];
|
|
9674
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
9675
|
+
|
|
9676
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
9677
|
+
|
|
9678
|
+
// check if radii are consistent
|
|
9679
|
+
if (precise) {
|
|
9680
|
+
|
|
9681
|
+
/**
|
|
9682
|
+
* check multiple centroids
|
|
9683
|
+
* if the polyline can be expressed as
|
|
9684
|
+
* an arc - all centroids should be close
|
|
9685
|
+
*/
|
|
9686
|
+
|
|
9687
|
+
if (len > 3) {
|
|
9688
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
9689
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
9690
|
+
|
|
9691
|
+
if (!centroid1 || !centroid2) return false;
|
|
9692
|
+
|
|
9693
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
9694
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
9695
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
9696
|
+
let errorCentroid = (dist1 + dist2);
|
|
9697
|
+
|
|
9698
|
+
// centroids diverging too much
|
|
9699
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
9700
|
+
|
|
9701
|
+
return false
|
|
9702
|
+
}
|
|
9703
|
+
|
|
9704
|
+
}
|
|
9705
|
+
|
|
9706
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
9707
|
+
|
|
9708
|
+
for (let i = 0; i < len; i++) {
|
|
9709
|
+
let pt = pts[i];
|
|
9710
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
9711
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
9712
|
+
|
|
9713
|
+
if (error > 0.0025) {
|
|
9714
|
+
/*
|
|
9715
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
9716
|
+
renderPoint(markers, centroid, 'orange')
|
|
9717
|
+
renderPoint(markers, p1, 'green')
|
|
9718
|
+
renderPoint(markers, p2)
|
|
9719
|
+
renderPoint(markers, p3, 'purple')
|
|
9720
|
+
*/
|
|
9721
|
+
return false;
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
|
|
9725
|
+
// calculate proper radius
|
|
9726
|
+
r = Math.sqrt(rSqMid);
|
|
9727
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9728
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9729
|
+
|
|
9730
|
+
} else {
|
|
9731
|
+
r = getDistance(centroid, p1);
|
|
9732
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9733
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9734
|
+
}
|
|
9735
|
+
|
|
9736
|
+
return {
|
|
9737
|
+
centroid,
|
|
9738
|
+
r,
|
|
9739
|
+
startAngle,
|
|
9740
|
+
endAngle,
|
|
9741
|
+
deltaAngle
|
|
9742
|
+
};
|
|
9743
|
+
}
|
|
9744
|
+
|
|
9745
|
+
function getPolyArcCentroid(pts = []) {
|
|
9746
|
+
|
|
9747
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
9748
|
+
if (pts.length < 3) return false
|
|
9749
|
+
|
|
9750
|
+
let p1 = pts[0];
|
|
9751
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9752
|
+
let p3 = pts[pts.length - 1];
|
|
9753
|
+
|
|
9754
|
+
let x1 = p1.x, y1 = p1.y;
|
|
9755
|
+
let x2 = p2.x, y2 = p2.y;
|
|
9756
|
+
let x3 = p3.x, y3 = p3.y;
|
|
9757
|
+
|
|
9758
|
+
let a = x1 - x2;
|
|
9759
|
+
let b = y1 - y2;
|
|
9760
|
+
let c = x1 - x3;
|
|
9761
|
+
let d = y1 - y3;
|
|
9762
|
+
|
|
9763
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
9764
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
9765
|
+
|
|
9766
|
+
let det = a * d - b * c;
|
|
9767
|
+
|
|
9768
|
+
// colinear points
|
|
9769
|
+
if (Math.abs(det) < 1e-10) {
|
|
9770
|
+
return false;
|
|
9771
|
+
}
|
|
9772
|
+
|
|
9773
|
+
// find center of arc
|
|
9774
|
+
let cx = (d * e - b * f) / det;
|
|
9775
|
+
let cy = (-c * e + a * f) / det;
|
|
9776
|
+
let centroid = { x: cx, y: cy };
|
|
9777
|
+
return centroid
|
|
9778
|
+
}
|
|
9779
|
+
|
|
8855
9780
|
function refineRoundedCorners(pathData, {
|
|
8856
9781
|
threshold = 0,
|
|
9782
|
+
simplifyQuadraticCorners = false,
|
|
8857
9783
|
tolerance = 1
|
|
8858
9784
|
} = {}) {
|
|
8859
9785
|
|
|
@@ -8878,6 +9804,9 @@
|
|
|
8878
9804
|
let firstIsLine = pathData[1].type === 'L';
|
|
8879
9805
|
let firstIsBez = pathData[1].type === 'C';
|
|
8880
9806
|
|
|
9807
|
+
// in case we have simplified a corner connecting to the start
|
|
9808
|
+
let M_adj = null;
|
|
9809
|
+
|
|
8881
9810
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
8882
9811
|
|
|
8883
9812
|
// normalize closepath to lineto
|
|
@@ -8917,15 +9846,17 @@
|
|
|
8917
9846
|
// closing corner to start
|
|
8918
9847
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
8919
9848
|
comL1 = pathData[1];
|
|
9849
|
+
|
|
8920
9850
|
comBez = [pathData[l - lastOff]];
|
|
8921
9851
|
|
|
8922
9852
|
}
|
|
8923
9853
|
|
|
9854
|
+
// collect enclosed bezier segments
|
|
8924
9855
|
for (let j = i + 1; j < l; j++) {
|
|
8925
9856
|
let comN = pathData[j] ? pathData[j] : null;
|
|
8926
9857
|
let comPrev = pathData[j - 1];
|
|
8927
9858
|
|
|
8928
|
-
if (comPrev.type === 'C') {
|
|
9859
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
8929
9860
|
comBez.push(comPrev);
|
|
8930
9861
|
}
|
|
8931
9862
|
|
|
@@ -8956,39 +9887,67 @@
|
|
|
8956
9887
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
8957
9888
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
8958
9889
|
|
|
9890
|
+
/*
|
|
9891
|
+
*/
|
|
9892
|
+
|
|
8959
9893
|
if (comBez.length && !signChange && isSmall) {
|
|
8960
9894
|
|
|
8961
|
-
let
|
|
9895
|
+
let isSquare = false;
|
|
9896
|
+
|
|
9897
|
+
if (comBez.length === 1) {
|
|
9898
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
9899
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
9900
|
+
let diff = (dx - dy);
|
|
9901
|
+
let rat = Math.abs(diff / dx);
|
|
9902
|
+
isSquare = rat < 0.01;
|
|
9903
|
+
}
|
|
9904
|
+
|
|
9905
|
+
let preferArcs = true;
|
|
9906
|
+
preferArcs = false;
|
|
9907
|
+
|
|
9908
|
+
// if rectangular prefer arcs
|
|
9909
|
+
if (preferArcs && isSquare) {
|
|
9910
|
+
|
|
9911
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
9912
|
+
|
|
9913
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
9914
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
9915
|
+
|
|
9916
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
9917
|
+
|
|
9918
|
+
let largeArc = 0;
|
|
9919
|
+
|
|
9920
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
9921
|
+
|
|
9922
|
+
pathDataN.push(comL0, comArc);
|
|
9923
|
+
i += offset;
|
|
9924
|
+
continue
|
|
9925
|
+
|
|
9926
|
+
}
|
|
9927
|
+
|
|
9928
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
9929
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
9930
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
9931
|
+
|
|
8962
9932
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
8963
9933
|
|
|
8964
|
-
|
|
9934
|
+
// exit: is rather flat or has no intersection
|
|
9935
|
+
|
|
9936
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
8965
9937
|
pathDataN.push(com);
|
|
8966
9938
|
continue
|
|
8967
9939
|
}
|
|
8968
9940
|
|
|
8969
|
-
// check sign change
|
|
9941
|
+
// check sign change - exit if present
|
|
8970
9942
|
if (ptQ) {
|
|
8971
9943
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
8972
9944
|
let area0_abs = Math.abs(area0);
|
|
8973
9945
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
8974
9946
|
let area1_abs = Math.abs(area1);
|
|
8975
9947
|
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs;
|
|
8976
|
-
|
|
8977
|
-
/*
|
|
8978
|
-
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
8979
|
-
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
8980
|
-
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
8981
|
-
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
8982
|
-
if(!area0) {
|
|
8983
|
-
pathDataN.push(com);
|
|
8984
|
-
continue
|
|
8985
|
-
}
|
|
8986
|
-
*/
|
|
8987
|
-
|
|
8988
9948
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
8989
9949
|
|
|
8990
9950
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
8991
|
-
|
|
8992
9951
|
pathDataN.push(com);
|
|
8993
9952
|
continue
|
|
8994
9953
|
}
|
|
@@ -9003,24 +9962,67 @@
|
|
|
9003
9962
|
|
|
9004
9963
|
// not in tolerance – return original command
|
|
9005
9964
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
9006
|
-
|
|
9007
9965
|
pathDataN.push(com);
|
|
9008
9966
|
continue;
|
|
9009
9967
|
|
|
9010
|
-
}
|
|
9968
|
+
}
|
|
9011
9969
|
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
comQ.cp1 = ptQ;
|
|
9015
|
-
comQ.p = comL1.p0;
|
|
9970
|
+
// return simplified quadratic Bézier command
|
|
9971
|
+
let p_Q = comL1.p0;
|
|
9016
9972
|
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9973
|
+
// adjust previous end point to better fit the cubic curvature
|
|
9974
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
9975
|
+
|
|
9976
|
+
if (adjustQ) {
|
|
9977
|
+
|
|
9978
|
+
let t = 0.1666;
|
|
9979
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
9980
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
9981
|
+
|
|
9982
|
+
// round for large enough segments
|
|
9983
|
+
let isH = ptQ.y===comL0.p.y;
|
|
9984
|
+
let isV = ptQ.x===comL0.p.x;
|
|
9985
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
9986
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
9987
|
+
|
|
9988
|
+
if(isSquare && com.dimA>3){
|
|
9989
|
+
let dec = 0.5;
|
|
9990
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
9991
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
9992
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
9993
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
9994
|
+
}
|
|
9995
|
+
|
|
9996
|
+
/*
|
|
9997
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
9998
|
+
renderPoint(markers, p_Q, 'orange')
|
|
9999
|
+
renderPoint(markers, comL0.p, 'green')
|
|
10000
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
10001
|
+
*/
|
|
10002
|
+
|
|
10003
|
+
// set new M starting point
|
|
10004
|
+
if (i === l - lastOff - 1) {
|
|
10005
|
+
|
|
10006
|
+
M_adj = p_Q;
|
|
10007
|
+
}
|
|
10008
|
+
|
|
10009
|
+
// adjust previous lineto end point
|
|
10010
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
10011
|
+
comL0.p = p0_adj;
|
|
9020
10012
|
|
|
9021
|
-
continue;
|
|
9022
10013
|
}
|
|
9023
10014
|
|
|
10015
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
10016
|
+
comQ.cp1 = ptQ;
|
|
10017
|
+
comQ.p0 = comL0.p;
|
|
10018
|
+
comQ.p = p_Q;
|
|
10019
|
+
|
|
10020
|
+
// add quadratic command
|
|
10021
|
+
pathDataN.push(comL0, comQ);
|
|
10022
|
+
|
|
10023
|
+
i += offset;
|
|
10024
|
+
continue;
|
|
10025
|
+
|
|
9024
10026
|
}
|
|
9025
10027
|
}
|
|
9026
10028
|
}
|
|
@@ -9034,6 +10036,12 @@
|
|
|
9034
10036
|
|
|
9035
10037
|
}
|
|
9036
10038
|
|
|
10039
|
+
// correct starting point connecting with last corner rounding
|
|
10040
|
+
if (M_adj) {
|
|
10041
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
10042
|
+
pathDataN[0].p0 = M_adj;
|
|
10043
|
+
}
|
|
10044
|
+
|
|
9037
10045
|
// revert close path normalization
|
|
9038
10046
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
9039
10047
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -9043,51 +10051,143 @@
|
|
|
9043
10051
|
|
|
9044
10052
|
}
|
|
9045
10053
|
|
|
9046
|
-
function
|
|
9047
|
-
|
|
10054
|
+
function simplifyAdjacentRound(pathData, {
|
|
10055
|
+
threshold = 0,
|
|
10056
|
+
tolerance = 1,
|
|
10057
|
+
// take arcs or cubic beziers
|
|
10058
|
+
toCubic = false,
|
|
10059
|
+
debug = false
|
|
10060
|
+
} = {}) {
|
|
9048
10061
|
|
|
9049
|
-
//
|
|
9050
|
-
|
|
9051
|
-
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9052
|
-
let p3 = pts[pts.length - 1];
|
|
10062
|
+
// fix small Arcs
|
|
10063
|
+
pathData = convertSmallArcsToLinetos(pathData);
|
|
9053
10064
|
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
let x3 = p3.x, y3 = p3.y;
|
|
10065
|
+
// min size threshold for corners
|
|
10066
|
+
threshold *= tolerance;
|
|
9057
10067
|
|
|
9058
|
-
let
|
|
9059
|
-
let b = y1 - y2;
|
|
9060
|
-
let c = x1 - x3;
|
|
9061
|
-
let d = y1 - y3;
|
|
10068
|
+
let l = pathData.length;
|
|
9062
10069
|
|
|
9063
|
-
|
|
9064
|
-
let
|
|
10070
|
+
// add fist command
|
|
10071
|
+
let pathDataN = [pathData[0]];
|
|
9065
10072
|
|
|
9066
|
-
|
|
10073
|
+
// find adjacent cubics between extremes
|
|
9067
10074
|
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
10075
|
+
for (let i = 1; i < l; i++) {
|
|
10076
|
+
pathData[i - 1];
|
|
10077
|
+
let com = pathData[i];
|
|
10078
|
+
let comN = pathData[i + 1] || null;
|
|
9072
10079
|
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
10080
|
+
if (!comN) {
|
|
10081
|
+
pathDataN.push(com);
|
|
10082
|
+
break
|
|
10083
|
+
}
|
|
9077
10084
|
|
|
9078
|
-
|
|
9079
|
-
|
|
10085
|
+
let { type, extreme = false, p0, p, dimA = 0 } = com;
|
|
10086
|
+
// for short segment detection
|
|
10087
|
+
let dimAN = comN.dimA;
|
|
10088
|
+
let dimA0 = dimA + dimAN;
|
|
10089
|
+
let thresh = 0.1;
|
|
9080
10090
|
|
|
9081
|
-
|
|
9082
|
-
|
|
10091
|
+
// ignore short linetos
|
|
10092
|
+
let isShortN = dimAN < dimA0 * thresh;
|
|
9083
10093
|
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
10094
|
+
// adjacent cubic commands - accept short in between linetos
|
|
10095
|
+
if ((type === 'C') && (comN.type === 'C' || isShortN)) {
|
|
10096
|
+
|
|
10097
|
+
let candidates = [];
|
|
10098
|
+
|
|
10099
|
+
for (let j = i + 1; j < l; j++) {
|
|
10100
|
+
let comN = pathData[j];
|
|
10101
|
+
let { type, extreme = false, corner = false, dimA = 0 } = comN;
|
|
10102
|
+
let isShort = dimA < dimA0 * thresh;
|
|
10103
|
+
|
|
10104
|
+
// skip for type change(unless very short), extremes or corners
|
|
10105
|
+
/*
|
|
10106
|
+
if ( (comN.extreme || comN.corner) ) {
|
|
10107
|
+
if(!extreme && !corner) candidates.push(comN)
|
|
10108
|
+
break;
|
|
10109
|
+
}
|
|
10110
|
+
*/
|
|
10111
|
+
|
|
10112
|
+
if (extreme || corner) {
|
|
10113
|
+
|
|
10114
|
+
if (isShort && comN.type !== 'C') ;
|
|
10115
|
+
|
|
10116
|
+
if ((extreme && !corner)) {
|
|
10117
|
+
|
|
10118
|
+
candidates.push(comN);
|
|
10119
|
+
}
|
|
10120
|
+
|
|
10121
|
+
break;
|
|
10122
|
+
}
|
|
10123
|
+
|
|
10124
|
+
candidates.push(comN);
|
|
10125
|
+
}
|
|
10126
|
+
|
|
10127
|
+
// try to create arc command
|
|
10128
|
+
if (candidates.length > 1) {
|
|
10129
|
+
|
|
10130
|
+
let clen = candidates.length;
|
|
10131
|
+
let pts = [com.p0, com.p,];
|
|
10132
|
+
|
|
10133
|
+
// add interpolated points to prevent wrong arc replacements
|
|
10134
|
+
candidates.forEach(c => {
|
|
10135
|
+
if (c.type === 'C') {
|
|
10136
|
+
let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5);
|
|
10137
|
+
pts.push(pt);
|
|
10138
|
+
}
|
|
10139
|
+
pts.push(c.p);
|
|
10140
|
+
});
|
|
10141
|
+
|
|
10142
|
+
let precise = true;
|
|
10143
|
+
let arcProps = getArcFromPoly(pts, precise);
|
|
10144
|
+
|
|
10145
|
+
// could be combined
|
|
10146
|
+
if (arcProps) {
|
|
10147
|
+
|
|
10148
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
10149
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
10150
|
+
|
|
10151
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
10152
|
+
largeArc = 0;
|
|
10153
|
+
let comLast = candidates[clen - 1];
|
|
10154
|
+
let p = comLast.p;
|
|
10155
|
+
|
|
10156
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] };
|
|
10157
|
+
|
|
10158
|
+
comArc.dimA = getDistManhattan(p0, p);
|
|
10159
|
+
comArc.p0 = p0;
|
|
10160
|
+
comArc.p = p;
|
|
10161
|
+
comArc.error = 0;
|
|
10162
|
+
comArc.directionChange = comLast.directionChange;
|
|
10163
|
+
comArc.extreme = comLast.extreme;
|
|
10164
|
+
comArc.corner = comLast.corner;
|
|
10165
|
+
pathDataN.push(comArc);
|
|
10166
|
+
|
|
10167
|
+
i += candidates.length;
|
|
10168
|
+
continue
|
|
10169
|
+
|
|
10170
|
+
}
|
|
10171
|
+
|
|
10172
|
+
// arc radius calculation failed - return original
|
|
10173
|
+
else {
|
|
10174
|
+
pathDataN.push(com);
|
|
10175
|
+
}
|
|
10176
|
+
}
|
|
10177
|
+
|
|
10178
|
+
// could not be simplified – return original command
|
|
10179
|
+
else {
|
|
10180
|
+
pathDataN.push(com);
|
|
10181
|
+
}
|
|
10182
|
+
|
|
10183
|
+
}
|
|
10184
|
+
// all other commands
|
|
10185
|
+
else {
|
|
10186
|
+
pathDataN.push(com);
|
|
10187
|
+
}
|
|
10188
|
+
}
|
|
10189
|
+
|
|
10190
|
+
return pathDataN
|
|
9091
10191
|
}
|
|
9092
10192
|
|
|
9093
10193
|
function refineRoundSegments(pathData, {
|
|
@@ -9106,9 +10206,6 @@
|
|
|
9106
10206
|
// add fist command
|
|
9107
10207
|
let pathDataN = [pathData[0]];
|
|
9108
10208
|
|
|
9109
|
-
// just for debugging
|
|
9110
|
-
let pathDataTest = [];
|
|
9111
|
-
|
|
9112
10209
|
for (let i = 1; i < l; i++) {
|
|
9113
10210
|
let com = pathData[i];
|
|
9114
10211
|
let { type } = com;
|
|
@@ -9135,11 +10232,12 @@
|
|
|
9135
10232
|
|
|
9136
10233
|
// 2. line-line-bezier-line-line
|
|
9137
10234
|
if (
|
|
10235
|
+
comN2 && comN3 &&
|
|
9138
10236
|
comP.type === 'L' &&
|
|
9139
10237
|
type === 'L' &&
|
|
9140
10238
|
comBez &&
|
|
9141
10239
|
comN2.type === 'L' &&
|
|
9142
|
-
|
|
10240
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
9143
10241
|
) {
|
|
9144
10242
|
|
|
9145
10243
|
L1 = [com.p0, com.p];
|
|
@@ -9166,10 +10264,10 @@
|
|
|
9166
10264
|
}
|
|
9167
10265
|
|
|
9168
10266
|
// 1. line-bezier-bezier-line
|
|
9169
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
10267
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
9170
10268
|
|
|
9171
10269
|
// 1.2 next is cubic next is lineto
|
|
9172
|
-
if (
|
|
10270
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
9173
10271
|
|
|
9174
10272
|
combine = true;
|
|
9175
10273
|
|
|
@@ -9228,16 +10326,19 @@
|
|
|
9228
10326
|
}
|
|
9229
10327
|
);
|
|
9230
10328
|
|
|
9231
|
-
if(bezierCommands.length === 1){
|
|
10329
|
+
if (bezierCommands.length === 1) {
|
|
9232
10330
|
|
|
9233
10331
|
// prefer more compact quadratic - otherwise arcs
|
|
9234
10332
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
9235
10333
|
|
|
9236
10334
|
if (comBezier.type === 'Q') {
|
|
9237
10335
|
toCubic = true;
|
|
10336
|
+
}else {
|
|
10337
|
+
comBezier = bezierCommands[0];
|
|
9238
10338
|
}
|
|
9239
10339
|
|
|
9240
10340
|
com = comBezier;
|
|
10341
|
+
|
|
9241
10342
|
}
|
|
9242
10343
|
|
|
9243
10344
|
// prefer arcs if 2 cubics are required
|
|
@@ -9257,25 +10358,28 @@
|
|
|
9257
10358
|
|
|
9258
10359
|
// test rendering
|
|
9259
10360
|
|
|
10361
|
+
/*
|
|
9260
10362
|
if (debug) {
|
|
9261
10363
|
// arcs
|
|
9262
10364
|
if (!toCubic) {
|
|
9263
10365
|
pathDataTest = [
|
|
9264
10366
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9265
10367
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
9266
|
-
]
|
|
10368
|
+
]
|
|
9267
10369
|
}
|
|
9268
10370
|
// cubics
|
|
9269
10371
|
else {
|
|
9270
10372
|
pathDataTest = [
|
|
9271
10373
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9272
10374
|
...bezierCommands
|
|
9273
|
-
]
|
|
10375
|
+
]
|
|
10376
|
+
|
|
9274
10377
|
}
|
|
9275
10378
|
|
|
9276
10379
|
let d = pathDataToD(pathDataTest);
|
|
9277
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
10380
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
9278
10381
|
}
|
|
10382
|
+
*/
|
|
9279
10383
|
|
|
9280
10384
|
pathDataN.push(com);
|
|
9281
10385
|
i++;
|
|
@@ -9407,7 +10511,6 @@
|
|
|
9407
10511
|
let com = pathData[c];
|
|
9408
10512
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
9409
10513
|
if (type === 'C') {
|
|
9410
|
-
|
|
9411
10514
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
9412
10515
|
if (comQ.type === 'Q') {
|
|
9413
10516
|
comQ.extreme = com.extreme;
|
|
@@ -11019,32 +12122,24 @@
|
|
|
11019
12122
|
}
|
|
11020
12123
|
|
|
11021
12124
|
// reverse paths
|
|
11022
|
-
for (let i = 0; i < l; i++) {
|
|
12125
|
+
for (let i = 0; l && i < l; i++) {
|
|
11023
12126
|
|
|
11024
12127
|
let poly = polys[i];
|
|
11025
12128
|
let { cw, includedIn, includes } = poly;
|
|
11026
12129
|
|
|
11027
|
-
|
|
11028
|
-
if (!includedIn.length && cw && !toClockwise
|
|
11029
|
-
|| !includedIn.length && !cw && toClockwise
|
|
11030
|
-
) {
|
|
11031
|
-
|
|
11032
|
-
pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
|
|
11033
|
-
polys[i].cw = polys[i].cw ? false : true;
|
|
11034
|
-
cw = polys[i].cw;
|
|
11035
|
-
|
|
11036
|
-
}
|
|
12130
|
+
let len = includes.length;
|
|
11037
12131
|
|
|
11038
12132
|
// reverse inner sub paths
|
|
11039
|
-
for (let j = 0; j <
|
|
12133
|
+
for (let j = 0; len && j < len; j++) {
|
|
11040
12134
|
let ind = includes[j];
|
|
11041
12135
|
let child = polys[ind];
|
|
11042
12136
|
|
|
11043
|
-
|
|
12137
|
+
// nothing to do
|
|
12138
|
+
if (child.cw !== cw) continue
|
|
12139
|
+
|
|
12140
|
+
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
12141
|
+
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11044
12142
|
|
|
11045
|
-
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
11046
|
-
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11047
|
-
}
|
|
11048
12143
|
}
|
|
11049
12144
|
}
|
|
11050
12145
|
|
|
@@ -11084,6 +12179,7 @@
|
|
|
11084
12179
|
allowAriaAtts: true,
|
|
11085
12180
|
|
|
11086
12181
|
convertPathLength: false,
|
|
12182
|
+
toAbsoluteUnits: false,
|
|
11087
12183
|
|
|
11088
12184
|
// custom removal
|
|
11089
12185
|
removeElements: [],
|
|
@@ -11112,6 +12208,7 @@
|
|
|
11112
12208
|
revertToQuadratics: true,
|
|
11113
12209
|
refineExtremes: false,
|
|
11114
12210
|
simplifyCorners: false,
|
|
12211
|
+
simplifyQuadraticCorners: false,
|
|
11115
12212
|
keepExtremes: true,
|
|
11116
12213
|
keepCorners: true,
|
|
11117
12214
|
keepInflections: false,
|
|
@@ -11168,7 +12265,7 @@
|
|
|
11168
12265
|
let isArray = Array.isArray(val);
|
|
11169
12266
|
|
|
11170
12267
|
if (isBoolean) val = false;
|
|
11171
|
-
else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
|
|
12268
|
+
else if (!isArray && isNum) val = val === 1 ? 1 : (prop === 'decimals' ? -1 : 0);
|
|
11172
12269
|
else if (isArray) val = [];
|
|
11173
12270
|
settingsNull[prop] = val;
|
|
11174
12271
|
}
|
|
@@ -11199,10 +12296,14 @@
|
|
|
11199
12296
|
...settingsDefaults,
|
|
11200
12297
|
...{
|
|
11201
12298
|
keepSmaller: false,
|
|
12299
|
+
convertPathLength:true,
|
|
11202
12300
|
toRelative: true,
|
|
11203
12301
|
toMixed: true,
|
|
11204
12302
|
toShorthands: true,
|
|
11205
12303
|
|
|
12304
|
+
allowMeta:true,
|
|
12305
|
+
allowDataAtts:true,
|
|
12306
|
+
allowAriaAtts:true,
|
|
11206
12307
|
legacyHref: true,
|
|
11207
12308
|
addViewBox: true,
|
|
11208
12309
|
addDimensions: true,
|
|
@@ -11270,19 +12371,23 @@
|
|
|
11270
12371
|
high: {
|
|
11271
12372
|
...settingsDefaults,
|
|
11272
12373
|
...{
|
|
11273
|
-
tolerance: 1.
|
|
12374
|
+
tolerance: 1.1,
|
|
11274
12375
|
toMixed: true,
|
|
11275
12376
|
refineExtremes: true,
|
|
11276
12377
|
simplifyCorners: true,
|
|
12378
|
+
simplifyQuadraticCorners: true,
|
|
12379
|
+
removeOrphanSubpaths: true,
|
|
11277
12380
|
simplifyRound: true,
|
|
11278
12381
|
removeClassNames: true,
|
|
11279
12382
|
cubicToArc: true,
|
|
12383
|
+
minifyD: 0,
|
|
11280
12384
|
removeComments: true,
|
|
11281
12385
|
removeHidden: true,
|
|
11282
|
-
removeOffCanvas: true,
|
|
11283
12386
|
addViewBox: true,
|
|
11284
12387
|
removeDimensions: true,
|
|
11285
|
-
|
|
12388
|
+
removeOffCanvas: true,
|
|
12389
|
+
/*
|
|
12390
|
+
*/
|
|
11286
12391
|
}
|
|
11287
12392
|
}
|
|
11288
12393
|
|
|
@@ -11440,18 +12545,44 @@
|
|
|
11440
12545
|
...settings
|
|
11441
12546
|
};
|
|
11442
12547
|
|
|
11443
|
-
let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts,
|
|
12548
|
+
let { getObject = false, removeComments, removeOffCanvas, unGroup, mergePaths, removeElements, removeDimensions, removeIds, removeClassNames, omitNamespace, cleanUpStrokes, addViewBox, addDimensions, removePrologue, removeHidden, removeUnused, cleanupDefs, cleanupClip, cleanupSVGAtts, removeNameSpaced, removeNameSpacedAtts, attributesToGroup, minifyRgbColors, stylesToAttributes, fixHref, legacyHref, allowMeta, allowDataAtts, allowAriaAtts, removeSVGAttributes, removeElAttributes, shapesToPaths, shapeConvert, convertShapes, simplifyBezier, optimizeOrder, autoClose, removeZeroLength, refineClosing, removeColinear, flatBezierToLinetos, revertToQuadratics, refineExtremes, simplifyCorners, fixDirections, keepExtremes, keepCorners, keepInflections, addExtremes, reversePath, toAbsolute, toRelative, toMixed, toShorthands, toLonghands, quadraticToCubic, arcToCubic, cubicToArc, lineToCubic, decimals, autoAccuracy, minifyD, tolerance, toPolygon, smoothPoly, polyFormat, precisionPoly, simplifyRD, simplifyRDP, harmonizeCpts, removeOrphanSubpaths, simplifyRound, simplifyQuadraticCorners, scale, scaleTo, crop, alignToOrigin, convertTransforms, keepSmaller, splitCompound, convertPathLength, toAbsoluteUnits } = settings;
|
|
11444
12549
|
|
|
11445
12550
|
// clamp tolerance and scale
|
|
11446
12551
|
tolerance = Math.max(0.1, tolerance);
|
|
11447
12552
|
scale = Math.max(0.001, scale);
|
|
11448
|
-
if(fixDirections) keepSmaller = false;
|
|
12553
|
+
if (fixDirections) keepSmaller = false;
|
|
11449
12554
|
if (scale !== 1 || scaleTo || crop || alignToOrigin) {
|
|
11450
12555
|
convertTransforms = true;
|
|
11451
12556
|
settings.convertTransforms = true;
|
|
11452
12557
|
}
|
|
11453
12558
|
|
|
11454
|
-
|
|
12559
|
+
/**
|
|
12560
|
+
* intercept
|
|
12561
|
+
* invalid inputs
|
|
12562
|
+
*/
|
|
12563
|
+
|
|
12564
|
+
let inputDetection = detectInputType(input);
|
|
12565
|
+
let { inputType, log } = inputDetection;
|
|
12566
|
+
|
|
12567
|
+
// invalid file
|
|
12568
|
+
if (inputType === 'invalid' || input === dummySVG) {
|
|
12569
|
+
// return dummy SVG to continue processing
|
|
12570
|
+
|
|
12571
|
+
let report = {
|
|
12572
|
+
original: 0,
|
|
12573
|
+
new: 0,
|
|
12574
|
+
saved: 0,
|
|
12575
|
+
svgSize: 0,
|
|
12576
|
+
svgSizeOpt: 0,
|
|
12577
|
+
compression: 0,
|
|
12578
|
+
decimals: 0,
|
|
12579
|
+
invalid: true
|
|
12580
|
+
};
|
|
12581
|
+
|
|
12582
|
+
return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
|
|
12583
|
+
|
|
12584
|
+
}
|
|
12585
|
+
|
|
11455
12586
|
let svg = '';
|
|
11456
12587
|
let svgSize = 0;
|
|
11457
12588
|
let svgSizeOpt = 0;
|
|
@@ -11571,10 +12702,7 @@
|
|
|
11571
12702
|
// convert all shapes to paths
|
|
11572
12703
|
if (shapesToPaths) {
|
|
11573
12704
|
shapeConvert = 'toPaths';
|
|
11574
|
-
|
|
11575
|
-
convert_ellipses = true;
|
|
11576
|
-
convert_poly = true;
|
|
11577
|
-
convert_lines = true;
|
|
12705
|
+
convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse'];
|
|
11578
12706
|
}
|
|
11579
12707
|
|
|
11580
12708
|
// sanitize SVG - clone/decouple settings
|
|
@@ -11608,6 +12736,9 @@
|
|
|
11608
12736
|
decimals,
|
|
11609
12737
|
};
|
|
11610
12738
|
|
|
12739
|
+
let comCount = 0;
|
|
12740
|
+
let comCountS = 0;
|
|
12741
|
+
|
|
11611
12742
|
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
11612
12743
|
|
|
11613
12744
|
let pathDataPlusArr = [];
|
|
@@ -11615,6 +12746,12 @@
|
|
|
11615
12746
|
let { d, el } = path;
|
|
11616
12747
|
let isPoly = false;
|
|
11617
12748
|
|
|
12749
|
+
// disable reordering for elements with stroke dash-array
|
|
12750
|
+
if (el && (el.hasAttribute('stroke-dasharray') || el.hasAttribute('stroke-dashoffset'))) {
|
|
12751
|
+
optimizeOrder = false;
|
|
12752
|
+
|
|
12753
|
+
}
|
|
12754
|
+
|
|
11618
12755
|
// if polygon we already heave absolute coordinates
|
|
11619
12756
|
|
|
11620
12757
|
let pathData = parsePathDataNormalized(d, { quadraticToCubic, arcToCubic });
|
|
@@ -11652,7 +12789,7 @@
|
|
|
11652
12789
|
}
|
|
11653
12790
|
|
|
11654
12791
|
// count commands for evaluation
|
|
11655
|
-
|
|
12792
|
+
comCount += pathData.length;
|
|
11656
12793
|
|
|
11657
12794
|
if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
11658
12795
|
|
|
@@ -11817,11 +12954,14 @@
|
|
|
11817
12954
|
if (simplifyCorners) {
|
|
11818
12955
|
|
|
11819
12956
|
let threshold = (bb.width + bb.height) * 0.1;
|
|
11820
|
-
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
12957
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners });
|
|
11821
12958
|
}
|
|
11822
12959
|
|
|
11823
12960
|
// refine round segment sequences
|
|
11824
|
-
if (simplifyRound)
|
|
12961
|
+
if (simplifyRound) {
|
|
12962
|
+
pathData = refineRoundSegments(pathData);
|
|
12963
|
+
pathData = simplifyAdjacentRound(pathData);
|
|
12964
|
+
}
|
|
11825
12965
|
|
|
11826
12966
|
// simplify to quadratics
|
|
11827
12967
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
@@ -11851,7 +12991,7 @@
|
|
|
11851
12991
|
let yMax = Math.max(...yArr);
|
|
11852
12992
|
|
|
11853
12993
|
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
11854
|
-
|
|
12994
|
+
bb_global.height > bb_global.width;
|
|
11855
12995
|
|
|
11856
12996
|
// fix path directions - before reordering
|
|
11857
12997
|
if (fixDirections) {
|
|
@@ -11860,7 +13000,23 @@
|
|
|
11860
13000
|
|
|
11861
13001
|
// prefer top to bottom priority for portrait aspect ratios
|
|
11862
13002
|
if (optimizeOrder) {
|
|
11863
|
-
|
|
13003
|
+
/*
|
|
13004
|
+
pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y)
|
|
13005
|
+
*/
|
|
13006
|
+
|
|
13007
|
+
// add missin bbox
|
|
13008
|
+
pathDataPlusArr.forEach(p => {
|
|
13009
|
+
if (p.bb.x === undefined) {
|
|
13010
|
+
p.bb = getPolyBBox(getPathDataVertices(p.pathData));
|
|
13011
|
+
}
|
|
13012
|
+
});
|
|
13013
|
+
|
|
13014
|
+
try {
|
|
13015
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
|
|
13016
|
+
|
|
13017
|
+
} catch {
|
|
13018
|
+
}
|
|
13019
|
+
|
|
11864
13020
|
}
|
|
11865
13021
|
|
|
11866
13022
|
// flatten compound paths
|
|
@@ -11923,7 +13079,7 @@
|
|
|
11923
13079
|
}
|
|
11924
13080
|
|
|
11925
13081
|
// compare command count
|
|
11926
|
-
|
|
13082
|
+
comCountS += pathData.length;
|
|
11927
13083
|
|
|
11928
13084
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
11929
13085
|
|
|
@@ -11998,6 +13154,9 @@
|
|
|
11998
13154
|
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
|
|
11999
13155
|
|
|
12000
13156
|
report = {
|
|
13157
|
+
original: comCount,
|
|
13158
|
+
new: comCountS,
|
|
13159
|
+
saved: comCount - comCountS,
|
|
12001
13160
|
svgSize,
|
|
12002
13161
|
svgSizeOpt,
|
|
12003
13162
|
compression,
|
|
@@ -12032,6 +13191,8 @@
|
|
|
12032
13191
|
if (typeof window !== 'undefined') {
|
|
12033
13192
|
window.svgPathSimplify = svgPathSimplify;
|
|
12034
13193
|
window.getElementTransform = getElementTransform;
|
|
13194
|
+
window.validateSVG = validateSVG;
|
|
13195
|
+
window.detectInputType = detectInputType;
|
|
12035
13196
|
|
|
12036
13197
|
window.getViewBox = getViewBox;
|
|
12037
13198
|
|
|
@@ -12045,6 +13206,7 @@
|
|
|
12045
13206
|
exports.atan2 = atan2$1;
|
|
12046
13207
|
exports.ceil = ceil$1;
|
|
12047
13208
|
exports.cos = cos$1;
|
|
13209
|
+
exports.detectInputType = detectInputType;
|
|
12048
13210
|
exports.exp = exp$1;
|
|
12049
13211
|
exports.floor = floor$1;
|
|
12050
13212
|
exports.getElementTransform = getElementTransform;
|
|
@@ -12060,5 +13222,6 @@
|
|
|
12060
13222
|
exports.sqrt = sqrt$1;
|
|
12061
13223
|
exports.svgPathSimplify = svgPathSimplify;
|
|
12062
13224
|
exports.tan = tan$1;
|
|
13225
|
+
exports.validateSVG = validateSVG;
|
|
12063
13226
|
|
|
12064
13227
|
})(this["svg-path-simplify"] = this["svg-path-simplify"] || {});
|