svg-path-simplify 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -0
- package/dist/svg-path-simplify.esm.js +1610 -495
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1611 -494
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +893 -456
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/index.html +58 -17
- 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 +26 -16
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +75 -20
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +15 -4
- package/src/svg-getAttributes.js +4 -2
- 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 +17 -1
- 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 +122 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/rounding.js +79 -78
- package/src/svgii/svg_cleanup.js +68 -20
- package/src/svgii/svg_cleanup_convertPathLength.js +22 -15
- 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);
|
|
@@ -1682,9 +1935,27 @@
|
|
|
1682
1935
|
|
|
1683
1936
|
}
|
|
1684
1937
|
|
|
1938
|
+
/**
|
|
1939
|
+
* rounding helper
|
|
1940
|
+
* allows for quantized rounding
|
|
1941
|
+
* e.g 0.5 decimals s
|
|
1942
|
+
*/
|
|
1685
1943
|
function roundTo(num = 0, decimals = 3) {
|
|
1686
|
-
if(decimals
|
|
1944
|
+
if (decimals < 0) return num;
|
|
1945
|
+
// Normal integer rounding
|
|
1687
1946
|
if (!decimals) return Math.round(num);
|
|
1947
|
+
|
|
1948
|
+
// stepped rounding
|
|
1949
|
+
let intPart = Math.floor(decimals);
|
|
1950
|
+
|
|
1951
|
+
if (intPart !== decimals) {
|
|
1952
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1953
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1954
|
+
|
|
1955
|
+
let step = 10 ** -intPart * f;
|
|
1956
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1688
1959
|
let factor = 10 ** decimals;
|
|
1689
1960
|
return Math.round(num * factor) / factor;
|
|
1690
1961
|
}
|
|
@@ -1694,50 +1965,21 @@
|
|
|
1694
1965
|
* floating point accuracy
|
|
1695
1966
|
* based on numeric value
|
|
1696
1967
|
*/
|
|
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;
|
|
1725
|
-
|
|
1726
|
-
let decimals = decimalsGlobal;
|
|
1727
|
-
|
|
1728
|
-
for (let c = 0; c < len; c++) {
|
|
1729
|
-
let com = pathData[c];
|
|
1730
|
-
let {values} = com;
|
|
1968
|
+
function autoRound(val, integerThresh = 50) {
|
|
1969
|
+
let decimals = 8;
|
|
1731
1970
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1971
|
+
if (val > integerThresh * 2) {
|
|
1972
|
+
decimals = 0;
|
|
1973
|
+
}
|
|
1974
|
+
else if (val > integerThresh) {
|
|
1975
|
+
decimals = 1;
|
|
1976
|
+
} else {
|
|
1977
|
+
decimals = Math.ceil(500 / val).toString().length;
|
|
1734
1978
|
|
|
1735
|
-
for (let v = 0; v < valLen; v++) {
|
|
1736
|
-
pathData[c].values[v] = roundTo(values[v], decimals);
|
|
1737
|
-
}
|
|
1738
1979
|
}
|
|
1739
1980
|
|
|
1740
|
-
|
|
1981
|
+
let factor = 10 ** decimals;
|
|
1982
|
+
return Math.round(val * factor) / factor;
|
|
1741
1983
|
}
|
|
1742
1984
|
|
|
1743
1985
|
/**
|
|
@@ -2179,7 +2421,7 @@
|
|
|
2179
2421
|
scale = height / 100;
|
|
2180
2422
|
}
|
|
2181
2423
|
else {
|
|
2182
|
-
scale = normalizedDiagonal ? scaleRoot / 100 :
|
|
2424
|
+
scale = normalizedDiagonal ? scaleRoot / 100 : width / 100;
|
|
2183
2425
|
}
|
|
2184
2426
|
break;
|
|
2185
2427
|
|
|
@@ -2248,11 +2490,13 @@
|
|
|
2248
2490
|
}
|
|
2249
2491
|
|
|
2250
2492
|
function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
|
|
2251
|
-
|
|
2493
|
+
|
|
2494
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
2252
2495
|
|
|
2253
2496
|
let atts={};
|
|
2254
2497
|
attributes.forEach(att=>{
|
|
2255
|
-
|
|
2498
|
+
|
|
2499
|
+
let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
|
|
2256
2500
|
atts[att.name] = value;
|
|
2257
2501
|
});
|
|
2258
2502
|
|
|
@@ -3077,9 +3321,9 @@
|
|
|
3077
3321
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
3078
3322
|
|
|
3079
3323
|
// test new point-at-t against original mid segment starting point
|
|
3080
|
-
let
|
|
3324
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
3081
3325
|
|
|
3082
|
-
let dist0 = getDistManhattan(com1.p,
|
|
3326
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
3083
3327
|
let dist1 = 0, dist2 = 0;
|
|
3084
3328
|
let close = dist0 < maxDist;
|
|
3085
3329
|
let success = false;
|
|
@@ -3094,29 +3338,40 @@
|
|
|
3094
3338
|
* to prevent distortions
|
|
3095
3339
|
*/
|
|
3096
3340
|
|
|
3097
|
-
//
|
|
3098
|
-
let
|
|
3341
|
+
// 1st segment mid
|
|
3342
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
3099
3343
|
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
let
|
|
3103
|
-
dist1 = getDistManhattan(
|
|
3344
|
+
let t2 = t * 0.5;
|
|
3345
|
+
// combined interpolated mid point
|
|
3346
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
3347
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
3104
3348
|
|
|
3105
3349
|
error += dist1;
|
|
3106
3350
|
|
|
3107
3351
|
if (dist1 < maxDist) {
|
|
3108
3352
|
|
|
3109
|
-
//
|
|
3110
|
-
let
|
|
3353
|
+
// 2nd segment mid
|
|
3354
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
3111
3355
|
|
|
3112
|
-
|
|
3113
|
-
let
|
|
3114
|
-
|
|
3356
|
+
// simplified path
|
|
3357
|
+
let t3 = (1 + t) * 0.5;
|
|
3358
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
3359
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
3115
3360
|
|
|
3116
3361
|
error += dist2;
|
|
3117
3362
|
|
|
3118
3363
|
if (error < maxDist) success = true;
|
|
3119
3364
|
|
|
3365
|
+
/*
|
|
3366
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
3367
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
3368
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
3369
|
+
|
|
3370
|
+
renderPoint(markers, com1.p, 'green')
|
|
3371
|
+
|
|
3372
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
3373
|
+
*/
|
|
3374
|
+
|
|
3120
3375
|
}
|
|
3121
3376
|
|
|
3122
3377
|
} // end 1st try
|
|
@@ -3281,6 +3536,7 @@
|
|
|
3281
3536
|
let com = pathData[c - 1];
|
|
3282
3537
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
3283
3538
|
|
|
3539
|
+
let comPrev = pathData[c-2];
|
|
3284
3540
|
let comN = pathData[c] || null;
|
|
3285
3541
|
|
|
3286
3542
|
// init properties
|
|
@@ -3299,6 +3555,7 @@
|
|
|
3299
3555
|
|
|
3300
3556
|
// bezier types
|
|
3301
3557
|
let isBezier = type === 'Q' || type === 'C';
|
|
3558
|
+
let isArc = type === 'A';
|
|
3302
3559
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
3303
3560
|
|
|
3304
3561
|
/**
|
|
@@ -3345,6 +3602,22 @@
|
|
|
3345
3602
|
}
|
|
3346
3603
|
}
|
|
3347
3604
|
|
|
3605
|
+
// check extremes introduce by small arcs
|
|
3606
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3607
|
+
let distN = comN ? comN.dimA : 0;
|
|
3608
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
3609
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
3610
|
+
|
|
3611
|
+
if(isShort && smallRadius){
|
|
3612
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
3613
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
3614
|
+
hasExtremes = true;
|
|
3615
|
+
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3348
3621
|
if (hasExtremes) com.extreme = true;
|
|
3349
3622
|
|
|
3350
3623
|
// Corners and semi extremes
|
|
@@ -3902,50 +4175,10 @@
|
|
|
3902
4175
|
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3903
4176
|
}
|
|
3904
4177
|
|
|
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
|
-
|
|
4178
|
+
/**
|
|
4179
|
+
* wrapper function for
|
|
4180
|
+
* all path data conversion
|
|
4181
|
+
*/
|
|
3949
4182
|
function convertPathData(pathData, {
|
|
3950
4183
|
toShorthands = true,
|
|
3951
4184
|
toLonghands = false,
|
|
@@ -3997,22 +4230,24 @@
|
|
|
3997
4230
|
|
|
3998
4231
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3999
4232
|
|
|
4000
|
-
if(toMixed) toRelative = true;
|
|
4233
|
+
if (toMixed) toRelative = true;
|
|
4001
4234
|
|
|
4002
4235
|
// pre round - before relative conversion to minimize distortions
|
|
4003
4236
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
4004
4237
|
|
|
4005
4238
|
// clone absolute pathdata
|
|
4006
|
-
if(toMixed){
|
|
4239
|
+
if (toMixed) {
|
|
4007
4240
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
4008
4241
|
}
|
|
4009
4242
|
|
|
4010
4243
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
4244
|
+
|
|
4245
|
+
// final rounding
|
|
4011
4246
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
4012
4247
|
|
|
4013
4248
|
// choose most compact commands: relative or absolute
|
|
4014
|
-
if(toMixed){
|
|
4015
|
-
for(let i=0; i<pathData.length; i++){
|
|
4249
|
+
if (toMixed) {
|
|
4250
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
4016
4251
|
let com = pathData[i];
|
|
4017
4252
|
let comA = pathDataAbs[i];
|
|
4018
4253
|
// compare Lengths
|
|
@@ -4022,7 +4257,7 @@
|
|
|
4022
4257
|
let lenR = comStr.length;
|
|
4023
4258
|
let lenA = comStrA.length;
|
|
4024
4259
|
|
|
4025
|
-
if(lenA<lenR){
|
|
4260
|
+
if (lenA < lenR) {
|
|
4026
4261
|
|
|
4027
4262
|
pathData[i] = pathDataAbs[i];
|
|
4028
4263
|
}
|
|
@@ -4032,56 +4267,140 @@
|
|
|
4032
4267
|
return pathData
|
|
4033
4268
|
}
|
|
4034
4269
|
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
function optimizeArcPathData(pathData = []) {
|
|
4270
|
+
function parsePathDataNormalized(d,
|
|
4271
|
+
{
|
|
4272
|
+
// necessary for most calculations
|
|
4273
|
+
toAbsolute = true,
|
|
4274
|
+
toLonghands = true,
|
|
4042
4275
|
|
|
4043
|
-
|
|
4276
|
+
// not necessary unless you need cubics only
|
|
4277
|
+
quadraticToCubic = false,
|
|
4044
4278
|
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
let M = { x: x0, y: y0 };
|
|
4052
|
-
let p = { x, y };
|
|
4279
|
+
// mostly a fallback if arc calculations fail
|
|
4280
|
+
arcToCubic = false,
|
|
4281
|
+
// arc to cubic precision - adds more segments for better precision
|
|
4282
|
+
arcAccuracy = 4,
|
|
4283
|
+
} = {}
|
|
4284
|
+
) {
|
|
4053
4285
|
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
remove.push(i);
|
|
4286
|
+
// is already array
|
|
4287
|
+
let isArray = Array.isArray(d);
|
|
4057
4288
|
|
|
4058
|
-
|
|
4289
|
+
// normalize native pathData to regular array
|
|
4290
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
4291
|
+
/*
|
|
4292
|
+
if (hasConstructor) {
|
|
4293
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
4294
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
4295
|
+
}
|
|
4296
|
+
*/
|
|
4059
4297
|
|
|
4060
|
-
|
|
4061
|
-
if (rx >= 1 && (x === x0 || y === y0)) {
|
|
4062
|
-
let diff = Math.abs(rx - ry) / rx;
|
|
4298
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
4063
4299
|
|
|
4064
|
-
|
|
4065
|
-
|
|
4300
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
4301
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
4066
4302
|
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4303
|
+
// normalize
|
|
4304
|
+
pathData = normalizePathData(pathData,
|
|
4305
|
+
{
|
|
4306
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
4307
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
4308
|
+
},
|
|
4309
|
+
);
|
|
4071
4310
|
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4311
|
+
return pathData;
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
/**
|
|
4315
|
+
*
|
|
4316
|
+
* @param {*} pathData
|
|
4317
|
+
* @returns
|
|
4318
|
+
*/
|
|
4319
|
+
|
|
4320
|
+
function optimizeArcPathData(pathData = []) {
|
|
4321
|
+
let l = pathData.length;
|
|
4322
|
+
let pathDataN = [];
|
|
4323
|
+
|
|
4324
|
+
for (let i = 0; i < l; i++) {
|
|
4325
|
+
let com = pathData[i];
|
|
4326
|
+
let { type, values } = com;
|
|
4327
|
+
|
|
4328
|
+
if (type !== 'A') {
|
|
4329
|
+
pathDataN.push(com);
|
|
4330
|
+
continue
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4334
|
+
let comPrev = pathData[i - 1];
|
|
4335
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4336
|
+
let M = { x: x0, y: y0 };
|
|
4337
|
+
let p = { x, y };
|
|
4338
|
+
|
|
4339
|
+
if (rx === 0 || ry === 0) {
|
|
4340
|
+
pathData[i] = null;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4343
|
+
// test for elliptic
|
|
4344
|
+
let rat = rx / ry;
|
|
4345
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
4346
|
+
|
|
4347
|
+
if (error > 0.01) {
|
|
4348
|
+
|
|
4349
|
+
pathDataN.push(com);
|
|
4350
|
+
continue
|
|
4351
|
+
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
4355
|
+
com.values[2] = 0;
|
|
4356
|
+
|
|
4357
|
+
/**
|
|
4358
|
+
* test semi circles
|
|
4359
|
+
* rx and ry are large enough
|
|
4360
|
+
*/
|
|
4361
|
+
|
|
4362
|
+
// 1. horizontal or vertical
|
|
4363
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
4364
|
+
let diffX = Math.abs(x - x0);
|
|
4365
|
+
let diffY = Math.abs(y - y0);
|
|
4366
|
+
|
|
4367
|
+
let isHorizontal = diffY < thresh;
|
|
4368
|
+
let isVertical = diffX < thresh;
|
|
4369
|
+
|
|
4370
|
+
// minify rx and ry
|
|
4371
|
+
if (isHorizontal || isVertical) {
|
|
4372
|
+
|
|
4373
|
+
// check if semi circle
|
|
4374
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
4375
|
+
|
|
4376
|
+
// is semicircle we can simplify rx
|
|
4377
|
+
if (!needsTrueR) {
|
|
4378
|
+
|
|
4379
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
4079
4380
|
}
|
|
4381
|
+
|
|
4382
|
+
com.values[0] = rx;
|
|
4383
|
+
com.values[1] = rx;
|
|
4384
|
+
pathDataN.push(com);
|
|
4385
|
+
continue
|
|
4386
|
+
|
|
4080
4387
|
}
|
|
4081
|
-
});
|
|
4082
4388
|
|
|
4083
|
-
|
|
4084
|
-
|
|
4389
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
4390
|
+
let r = getDistance(M, p) * 0.5;
|
|
4391
|
+
error = rx / r;
|
|
4392
|
+
|
|
4393
|
+
if (error < 0.5) {
|
|
4394
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
com.values[0] = rx;
|
|
4398
|
+
com.values[1] = rx;
|
|
4399
|
+
pathDataN.push(com);
|
|
4400
|
+
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
return pathDataN;
|
|
4085
4404
|
}
|
|
4086
4405
|
|
|
4087
4406
|
/**
|
|
@@ -4146,6 +4465,44 @@
|
|
|
4146
4465
|
}
|
|
4147
4466
|
*/
|
|
4148
4467
|
|
|
4468
|
+
function convertSmallArcsToLinetos(pathData) {
|
|
4469
|
+
|
|
4470
|
+
let l = pathData.length;
|
|
4471
|
+
|
|
4472
|
+
// add fist command
|
|
4473
|
+
let pathDataN = [pathData[0]];
|
|
4474
|
+
|
|
4475
|
+
for (let i = 1; i < l; i++) {
|
|
4476
|
+
let com = pathData[i];
|
|
4477
|
+
let comPrev = pathData[i - 1];
|
|
4478
|
+
let comN = pathData[i + 1] || null;
|
|
4479
|
+
|
|
4480
|
+
if (!comN) {
|
|
4481
|
+
pathDataN.push(com);
|
|
4482
|
+
break
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
let { type, values, extreme = false, p0, p, dimA = 0 } = com;
|
|
4486
|
+
// for short segment detection
|
|
4487
|
+
let dimAN = comN.dimA;
|
|
4488
|
+
let dimA0 = comPrev.dimA + dimA + dimAN;
|
|
4489
|
+
let thresh = 0.05;
|
|
4490
|
+
let isShort = dimA < dimA0 * thresh;
|
|
4491
|
+
|
|
4492
|
+
if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
|
|
4493
|
+
|
|
4494
|
+
com.type = 'L';
|
|
4495
|
+
com.values = [p.x, p.y];
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
pathDataN.push(com);
|
|
4499
|
+
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
return pathDataN;
|
|
4503
|
+
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4149
4506
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
4150
4507
|
|
|
4151
4508
|
// test if cubic can be simplified to quadratic
|
|
@@ -6201,7 +6558,7 @@
|
|
|
6201
6558
|
let { type, values } = com;
|
|
6202
6559
|
let valsLen = values.length;
|
|
6203
6560
|
if (valsLen) {
|
|
6204
|
-
let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0};
|
|
6561
|
+
let p = { type: type, x: values[valsLen - 2], y: values[valsLen - 1], index: 0 };
|
|
6205
6562
|
p.index = i;
|
|
6206
6563
|
indices.push(p);
|
|
6207
6564
|
}
|
|
@@ -6209,113 +6566,111 @@
|
|
|
6209
6566
|
|
|
6210
6567
|
// reorder to top left most
|
|
6211
6568
|
|
|
6212
|
-
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x
|
|
6569
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
6213
6570
|
newIndex = indices[0].index;
|
|
6214
6571
|
|
|
6215
|
-
return
|
|
6572
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
6216
6573
|
}
|
|
6217
6574
|
|
|
6218
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
6575
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
6219
6576
|
|
|
6220
|
-
let
|
|
6577
|
+
let pathDataN = pathData;
|
|
6221
6578
|
let l = pathData.length;
|
|
6222
6579
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
6223
6580
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
6224
6581
|
|
|
6225
|
-
let
|
|
6226
|
-
|
|
6227
|
-
// check if order is ideal
|
|
6228
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
6582
|
+
let hasLinetos = false;
|
|
6229
6583
|
|
|
6584
|
+
// check if path is closed by explicit lineto
|
|
6585
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6230
6586
|
let penultimateCom = pathData[idxPenultimate];
|
|
6231
6587
|
let penultimateType = penultimateCom.type;
|
|
6232
6588
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6233
6589
|
|
|
6234
6590
|
// last L command ends at M
|
|
6235
|
-
let
|
|
6236
|
-
|
|
6237
|
-
// add closepath Z to enable order optimizations
|
|
6238
|
-
if(!isClosed && autoClose && isClosingCommand){
|
|
6591
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6592
|
+
let lastIsLine = penultimateType === 'L';
|
|
6239
6593
|
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
let
|
|
6244
|
-
|
|
6245
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
6246
|
-
*/
|
|
6247
|
-
|
|
6248
|
-
pathData.push({type:'Z', values:[]});
|
|
6249
|
-
isClosed = true;
|
|
6250
|
-
l++;
|
|
6251
|
-
}
|
|
6594
|
+
// create index
|
|
6595
|
+
let indices = [];
|
|
6596
|
+
for (let i = 0; i < l; i++) {
|
|
6597
|
+
let com = pathData[i];
|
|
6598
|
+
let { type, values, p0, p } = com;
|
|
6252
6599
|
|
|
6253
|
-
|
|
6254
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
6255
|
-
skipReorder = false;
|
|
6600
|
+
if(type==='L') hasLinetos = true;
|
|
6256
6601
|
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
}
|
|
6602
|
+
// exclude Z
|
|
6603
|
+
if (values.length) {
|
|
6604
|
+
values.slice(-2);
|
|
6261
6605
|
|
|
6262
|
-
|
|
6606
|
+
let x = Math.min(p0.x, p.x);
|
|
6607
|
+
let y = Math.min(p0.y, p.y);
|
|
6263
6608
|
|
|
6264
|
-
|
|
6609
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
6610
|
+
let prevType = prevCom.type;
|
|
6265
6611
|
|
|
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
|
-
}
|
|
6612
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
6613
|
+
item.index = i;
|
|
6614
|
+
indices.push(item);
|
|
6280
6615
|
}
|
|
6281
6616
|
|
|
6282
|
-
|
|
6617
|
+
}
|
|
6283
6618
|
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6619
|
+
let xMin = Infinity;
|
|
6620
|
+
let yMin = Infinity;
|
|
6621
|
+
let idx_top = null;
|
|
6622
|
+
let len = indices.length;
|
|
6287
6623
|
|
|
6288
|
-
|
|
6624
|
+
for (let i = 0; i < len; i++) {
|
|
6625
|
+
let com = indices[i];
|
|
6626
|
+
let { type, index, x, y, prevType } = com;
|
|
6289
6627
|
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
newIndex = indices[0].index;
|
|
6295
|
-
}
|
|
6628
|
+
if (hasLinetos && prevType === 'L') {
|
|
6629
|
+
if (x < xMin && y < yMin) {
|
|
6630
|
+
idx_top = index-1;
|
|
6631
|
+
}
|
|
6296
6632
|
|
|
6297
|
-
|
|
6298
|
-
|
|
6633
|
+
if (y < yMin) {
|
|
6634
|
+
yMin = y;
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
if (x < xMin) {
|
|
6638
|
+
xMin = x;
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6299
6641
|
}
|
|
6300
6642
|
|
|
6301
|
-
|
|
6643
|
+
// shift to better starting point
|
|
6644
|
+
if (idx_top) {
|
|
6645
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
6302
6646
|
|
|
6303
|
-
|
|
6647
|
+
// update penultimate - reorder might have added new close paths
|
|
6648
|
+
l = pathDataN.length;
|
|
6649
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
6650
|
+
|
|
6651
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6652
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
6653
|
+
penultimateType = penultimateCom.type;
|
|
6654
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6655
|
+
lastIsLine = penultimateType ==='L';
|
|
6304
6656
|
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
penultimateType = penultimateCom.type;
|
|
6308
|
-
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
6657
|
+
// last L command ends at M
|
|
6658
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6309
6659
|
|
|
6310
|
-
|
|
6660
|
+
}
|
|
6311
6661
|
|
|
6312
|
-
|
|
6313
|
-
|
|
6662
|
+
// remove unnecessary closing lineto
|
|
6663
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
6664
|
+
pathDataN.splice(l - 2, 1);
|
|
6314
6665
|
}
|
|
6315
6666
|
|
|
6316
|
-
|
|
6667
|
+
// add close path
|
|
6668
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
6669
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
6670
|
+
}
|
|
6671
|
+
|
|
6672
|
+
return pathDataN
|
|
6317
6673
|
|
|
6318
|
-
return pathDataNew
|
|
6319
6674
|
}
|
|
6320
6675
|
|
|
6321
6676
|
/**
|
|
@@ -6828,7 +7183,7 @@
|
|
|
6828
7183
|
|
|
6829
7184
|
switch(type){
|
|
6830
7185
|
case 'path':
|
|
6831
|
-
let pathData = parsePathDataNormalized(
|
|
7186
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
6832
7187
|
bb=getPolyBBox(getPathDataPoly(pathData));
|
|
6833
7188
|
|
|
6834
7189
|
break;
|
|
@@ -6870,8 +7225,8 @@
|
|
|
6870
7225
|
autoRoundValues = false,
|
|
6871
7226
|
minifyRgbColors = false,
|
|
6872
7227
|
removeInvalid = true,
|
|
6873
|
-
allowDataAtts=true,
|
|
6874
|
-
allowAriaAtts=true,
|
|
7228
|
+
allowDataAtts = true,
|
|
7229
|
+
allowAriaAtts = true,
|
|
6875
7230
|
removeDefaults = true,
|
|
6876
7231
|
cleanUpStrokes = true,
|
|
6877
7232
|
normalizeTransforms = true,
|
|
@@ -6937,7 +7292,7 @@
|
|
|
6937
7292
|
*/
|
|
6938
7293
|
|
|
6939
7294
|
if (removeInvalid || removeDefaults || removeNameSpaced) {
|
|
6940
|
-
let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
7295
|
+
let propsFilteredObj = filterSvgElProps(nodeName, props, { allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
6941
7296
|
props = propsFilteredObj.propsFiltered;
|
|
6942
7297
|
remove.push(...propsFilteredObj.remove);
|
|
6943
7298
|
|
|
@@ -7030,10 +7385,10 @@
|
|
|
7030
7385
|
|
|
7031
7386
|
if (prop !== 'transforms') {
|
|
7032
7387
|
|
|
7033
|
-
if (
|
|
7388
|
+
if ((prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
|
|
7034
7389
|
normalizedDiagonal = true;
|
|
7035
7390
|
for (let i = 0; i < values.length; i++) {
|
|
7036
|
-
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize });
|
|
7391
|
+
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize, autoRoundValues });
|
|
7037
7392
|
valsNew.push(val);
|
|
7038
7393
|
}
|
|
7039
7394
|
}
|
|
@@ -7075,14 +7430,12 @@
|
|
|
7075
7430
|
if (prop === 'scale' && unit === '%') {
|
|
7076
7431
|
valAbs = valAbs * 0.01;
|
|
7077
7432
|
} else {
|
|
7078
|
-
if (prop === 'r')
|
|
7433
|
+
if (prop === 'r' && width!==height) normalizedDiagonal = true;
|
|
7079
7434
|
valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, normalizedDiagonal, fontSize });
|
|
7080
7435
|
|
|
7081
7436
|
if (autoRoundValues && isNumeric) {
|
|
7082
7437
|
valAbs = autoRound(valAbs);
|
|
7083
|
-
|
|
7084
7438
|
}
|
|
7085
|
-
|
|
7086
7439
|
}
|
|
7087
7440
|
}
|
|
7088
7441
|
valsNew.push(valAbs);
|
|
@@ -7283,6 +7636,7 @@
|
|
|
7283
7636
|
removeIds = false,
|
|
7284
7637
|
removeClassNames = false,
|
|
7285
7638
|
exclude = [],
|
|
7639
|
+
inheritedProps = null,
|
|
7286
7640
|
} = {}) {
|
|
7287
7641
|
let propsFiltered = {};
|
|
7288
7642
|
let remove = [];
|
|
@@ -7311,7 +7665,7 @@
|
|
|
7311
7665
|
let isMeta = prop === 'title';
|
|
7312
7666
|
let isAria = prop.startsWith('aria-');
|
|
7313
7667
|
|
|
7314
|
-
if
|
|
7668
|
+
if ((allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta)) continue
|
|
7315
7669
|
|
|
7316
7670
|
// filter out defaults
|
|
7317
7671
|
let isDefault = removeDefaults ?
|
|
@@ -7322,6 +7676,7 @@
|
|
|
7322
7676
|
|
|
7323
7677
|
if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
|
|
7324
7678
|
if (include.includes(prop)) isValid = true;
|
|
7679
|
+
if (exclude.includes(prop)) isValid = false;
|
|
7325
7680
|
|
|
7326
7681
|
if (isValid) {
|
|
7327
7682
|
propsFiltered[prop] = props[prop];
|
|
@@ -7554,6 +7909,358 @@
|
|
|
7554
7909
|
return "";
|
|
7555
7910
|
}
|
|
7556
7911
|
|
|
7912
|
+
// Legendre Gauss weight and abscissa values
|
|
7913
|
+
const waArr_global = [];
|
|
7914
|
+
|
|
7915
|
+
function getLength(pts, {
|
|
7916
|
+
t = 1,
|
|
7917
|
+
waArr = []
|
|
7918
|
+
} = {}) {
|
|
7919
|
+
|
|
7920
|
+
const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
|
|
7921
|
+
if (t === 0) {
|
|
7922
|
+
return 0;
|
|
7923
|
+
}
|
|
7924
|
+
|
|
7925
|
+
t = t > 1 ? 1 : t < 0 ? 0 : t;
|
|
7926
|
+
let t2 = t / 2;
|
|
7927
|
+
|
|
7928
|
+
/**
|
|
7929
|
+
* set higher legendre gauss weight abscissae values
|
|
7930
|
+
* by more accurate weight/abscissae lookups
|
|
7931
|
+
* https://pomax.github.io/bezierinfo/legendre-gauss.html
|
|
7932
|
+
*/
|
|
7933
|
+
|
|
7934
|
+
let sum = 0;
|
|
7935
|
+
|
|
7936
|
+
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;
|
|
7937
|
+
|
|
7938
|
+
for (let i = 0, len = wa.length; i < len; i++) {
|
|
7939
|
+
// weight and abscissae
|
|
7940
|
+
let [w, a] = [wa[i][0], wa[i][1]];
|
|
7941
|
+
let ct1_t = t2 * a;
|
|
7942
|
+
let ct0 = -ct1_t + t2;
|
|
7943
|
+
|
|
7944
|
+
let xbase0 = base3(ct0, x0, cp1x, cp2x, px);
|
|
7945
|
+
let ybase0 = base3(ct0, y0, cp1y, cp2y, py);
|
|
7946
|
+
|
|
7947
|
+
let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
|
|
7948
|
+
|
|
7949
|
+
sum += w * Math.sqrt(comb0);
|
|
7950
|
+
|
|
7951
|
+
}
|
|
7952
|
+
return t2 * sum;
|
|
7953
|
+
};
|
|
7954
|
+
|
|
7955
|
+
const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
|
|
7956
|
+
if (t === 0) {
|
|
7957
|
+
return 0;
|
|
7958
|
+
}
|
|
7959
|
+
// is flat/linear – treat as line
|
|
7960
|
+
if (checkFlat) {
|
|
7961
|
+
let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
|
|
7962
|
+
let l2 = getDistance(p0, p);
|
|
7963
|
+
if (l1 === l2) {
|
|
7964
|
+
return l2;
|
|
7965
|
+
}
|
|
7966
|
+
}
|
|
7967
|
+
|
|
7968
|
+
let a, b, c, d, e, e1, d1, v1x, v1y;
|
|
7969
|
+
v1x = cp1.x * 2;
|
|
7970
|
+
v1y = cp1.y * 2;
|
|
7971
|
+
d = p0.x - v1x + p.x;
|
|
7972
|
+
d1 = p0.y - v1y + p.y;
|
|
7973
|
+
e = v1x - 2 * p0.x;
|
|
7974
|
+
e1 = v1y - 2 * p0.y;
|
|
7975
|
+
a = 4 * (d * d + d1 * d1);
|
|
7976
|
+
b = 4 * (d * e + d1 * e1);
|
|
7977
|
+
c = e * e + e1 * e1;
|
|
7978
|
+
|
|
7979
|
+
const bt = b / (2 * a),
|
|
7980
|
+
ct = c / a,
|
|
7981
|
+
ut = t + bt,
|
|
7982
|
+
|
|
7983
|
+
k = ct - bt * bt;
|
|
7984
|
+
|
|
7985
|
+
return (
|
|
7986
|
+
(Math.sqrt(a) / 2) *
|
|
7987
|
+
(ut * Math.sqrt(ut * ut + k) -
|
|
7988
|
+
bt * Math.sqrt(bt * bt + k) +
|
|
7989
|
+
k *
|
|
7990
|
+
Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
|
|
7991
|
+
);
|
|
7992
|
+
};
|
|
7993
|
+
|
|
7994
|
+
let length;
|
|
7995
|
+
if (pts.length === 4) {
|
|
7996
|
+
length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr);
|
|
7997
|
+
|
|
7998
|
+
}
|
|
7999
|
+
else if (pts.length === 3) {
|
|
8000
|
+
length = quadraticBezierLength(pts[0], pts[1], pts[2], t);
|
|
8001
|
+
}
|
|
8002
|
+
else {
|
|
8003
|
+
length = getDistance(pts[0], pts[1]);
|
|
8004
|
+
}
|
|
8005
|
+
|
|
8006
|
+
return length;
|
|
8007
|
+
}
|
|
8008
|
+
|
|
8009
|
+
// LG weight/abscissae generator
|
|
8010
|
+
function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
8011
|
+
console.log('add new LG', n);
|
|
8012
|
+
|
|
8013
|
+
let waArr = [];
|
|
8014
|
+
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
8015
|
+
const m = (n + 1) >> 1;
|
|
8016
|
+
xm = 0.5 * (x2 + x1);
|
|
8017
|
+
xl = 0.5 * (x2 - x1);
|
|
8018
|
+
|
|
8019
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
8020
|
+
z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
8021
|
+
do {
|
|
8022
|
+
p1 = 1;
|
|
8023
|
+
p2 = 0;
|
|
8024
|
+
for (let j = 0; j < n; j++) {
|
|
8025
|
+
|
|
8026
|
+
p3 = p2;
|
|
8027
|
+
p2 = p1;
|
|
8028
|
+
p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
|
|
8029
|
+
}
|
|
8030
|
+
|
|
8031
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
8032
|
+
z1 = z;
|
|
8033
|
+
z = z1 - p1 / pp; //Newton’s method
|
|
8034
|
+
|
|
8035
|
+
} while (Math.abs(z - z1) > 1.0e-14);
|
|
8036
|
+
|
|
8037
|
+
let weight = (2 * xl) / ((1 - z * z) * pp * pp);
|
|
8038
|
+
let abscissa = xm + xl * z;
|
|
8039
|
+
|
|
8040
|
+
waArr.push(
|
|
8041
|
+
[weight, -abscissa],
|
|
8042
|
+
[weight, abscissa],
|
|
8043
|
+
);
|
|
8044
|
+
}
|
|
8045
|
+
|
|
8046
|
+
return waArr;
|
|
8047
|
+
}
|
|
8048
|
+
|
|
8049
|
+
function base3(t, p1, p2, p3, p4) {
|
|
8050
|
+
let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
|
|
8051
|
+
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
8052
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
8053
|
+
}
|
|
8054
|
+
|
|
8055
|
+
function getPolygonLength(pts=[], isPoly=false){
|
|
8056
|
+
|
|
8057
|
+
let len = 0;
|
|
8058
|
+
let l=pts.length;
|
|
8059
|
+
|
|
8060
|
+
for(let i=1; i<l; i++){
|
|
8061
|
+
let p1 = pts[i-1];
|
|
8062
|
+
let p2 = pts[i];
|
|
8063
|
+
len += getDistance(p1, p2);
|
|
8064
|
+
}
|
|
8065
|
+
if(isPoly){
|
|
8066
|
+
len += getDistance(pts[l-1], pts[0]);
|
|
8067
|
+
}
|
|
8068
|
+
return len
|
|
8069
|
+
}
|
|
8070
|
+
|
|
8071
|
+
/**
|
|
8072
|
+
* Ramanujan approximation
|
|
8073
|
+
* based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
|
|
8074
|
+
*/
|
|
8075
|
+
function getEllipseLength(rx=0, ry=0) {
|
|
8076
|
+
// is circle
|
|
8077
|
+
if (rx === ry) {
|
|
8078
|
+
|
|
8079
|
+
return 2 * Math.PI * rx;
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
let c=rx+ry;
|
|
8083
|
+
let d = (rx - ry) / c;
|
|
8084
|
+
let h = d*d;
|
|
8085
|
+
|
|
8086
|
+
let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
|
|
8087
|
+
return totalLength;
|
|
8088
|
+
}
|
|
8089
|
+
|
|
8090
|
+
/**
|
|
8091
|
+
* ellipse helpers
|
|
8092
|
+
* approximate ellipse length
|
|
8093
|
+
* by Legendre-Gauss
|
|
8094
|
+
*/
|
|
8095
|
+
|
|
8096
|
+
function getCircleArcLength(r = 0, deltaAngle = 0) {
|
|
8097
|
+
if(r===0) {
|
|
8098
|
+
console.warn('Radius must be positive');
|
|
8099
|
+
return 0;
|
|
8100
|
+
}
|
|
8101
|
+
let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI));
|
|
8102
|
+
return len
|
|
8103
|
+
}
|
|
8104
|
+
|
|
8105
|
+
function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
|
|
8106
|
+
|
|
8107
|
+
// Transform [-1, 1] interval to [startAngle, endAngle]
|
|
8108
|
+
let halfInterval = (endAngle - startAngle) * 0.5;
|
|
8109
|
+
let midpoint = (endAngle + startAngle) * 0.5;
|
|
8110
|
+
|
|
8111
|
+
// Arc length integral approximation
|
|
8112
|
+
let arcLength = 0;
|
|
8113
|
+
for (let i = 0; i < wa.length; i++) {
|
|
8114
|
+
let [weight, abscissae] = wa[i];
|
|
8115
|
+
let theta = midpoint + halfInterval * abscissae;
|
|
8116
|
+
|
|
8117
|
+
let a = rx * Math.sin(theta);
|
|
8118
|
+
let b = ry * Math.cos(theta);
|
|
8119
|
+
let integrand = Math.sqrt(
|
|
8120
|
+
a * a + b * b
|
|
8121
|
+
);
|
|
8122
|
+
arcLength += weight * integrand;
|
|
8123
|
+
}
|
|
8124
|
+
|
|
8125
|
+
return Math.abs(halfInterval * arcLength)
|
|
8126
|
+
}
|
|
8127
|
+
|
|
8128
|
+
function getPathDataLength(pathData = []) {
|
|
8129
|
+
let len = 0;
|
|
8130
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
8131
|
+
|
|
8132
|
+
for (let i = 0; i < pathDataArr.length; i++) {
|
|
8133
|
+
let pathData = pathDataArr[i];
|
|
8134
|
+
|
|
8135
|
+
// add verbose point data if not present
|
|
8136
|
+
if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
|
|
8137
|
+
|
|
8138
|
+
// Calculate Legendre Gauss weight and abscissa values
|
|
8139
|
+
if (!waArr_global.length) {
|
|
8140
|
+
|
|
8141
|
+
let waArr = getLegendreGaussValues(48);
|
|
8142
|
+
waArr.forEach(wa => {
|
|
8143
|
+
waArr_global.push(wa);
|
|
8144
|
+
});
|
|
8145
|
+
}
|
|
8146
|
+
|
|
8147
|
+
let waArr = waArr_global;
|
|
8148
|
+
|
|
8149
|
+
pathData.forEach(com => {
|
|
8150
|
+
let { type, values, p0, p, cp1 = null, cp2 = null } = com;
|
|
8151
|
+
let pts = [p0];
|
|
8152
|
+
if (type === 'C' || type === 'Q') pts.push(cp1);
|
|
8153
|
+
if (type === 'C') pts.push(cp2);
|
|
8154
|
+
pts.push(p);
|
|
8155
|
+
let comLen = 0;
|
|
8156
|
+
|
|
8157
|
+
if (type === 'A') {
|
|
8158
|
+
|
|
8159
|
+
// get parametrized arc properties
|
|
8160
|
+
let [largeArc, sweep] = [com.values[3], com.values[4]];
|
|
8161
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false);
|
|
8162
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData;
|
|
8163
|
+
|
|
8164
|
+
if (rx === ry) {
|
|
8165
|
+
comLen = getCircleArcLength(rx, Math.abs(deltaAngle));
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
// is ellipse
|
|
8169
|
+
else {
|
|
8170
|
+
xAxisRotation = xAxisRotation * deg2rad;
|
|
8171
|
+
startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry);
|
|
8172
|
+
endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry);
|
|
8173
|
+
|
|
8174
|
+
// recalculate parametrized delta
|
|
8175
|
+
let deltaAngle_param = endAngle - startAngle;
|
|
8176
|
+
|
|
8177
|
+
let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
|
|
8178
|
+
|
|
8179
|
+
deltaAngle = signChange ? deltaAngle : deltaAngle_param;
|
|
8180
|
+
|
|
8181
|
+
// adjust end angle
|
|
8182
|
+
if (sweep && startAngle > endAngle) {
|
|
8183
|
+
endAngle += Math.PI * 2;
|
|
8184
|
+
}
|
|
8185
|
+
|
|
8186
|
+
if (!sweep && startAngle < endAngle) {
|
|
8187
|
+
endAngle -= Math.PI * 2;
|
|
8188
|
+
}
|
|
8189
|
+
comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr);
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
|
|
8193
|
+
else {
|
|
8194
|
+
comLen = getLength(pts, {
|
|
8195
|
+
t: 1,
|
|
8196
|
+
waArr
|
|
8197
|
+
});
|
|
8198
|
+
}
|
|
8199
|
+
len += comLen;
|
|
8200
|
+
});
|
|
8201
|
+
}
|
|
8202
|
+
|
|
8203
|
+
return len;
|
|
8204
|
+
}
|
|
8205
|
+
|
|
8206
|
+
function getElementLength(el, {
|
|
8207
|
+
props = {},
|
|
8208
|
+
pathLength = 0,
|
|
8209
|
+
} = {}) {
|
|
8210
|
+
|
|
8211
|
+
let nodeName = el.nodeName;
|
|
8212
|
+
let len = 0;
|
|
8213
|
+
|
|
8214
|
+
props = JSON.parse(JSON.stringify(props));
|
|
8215
|
+
|
|
8216
|
+
for (let prop in props) {
|
|
8217
|
+
if (props[prop] && props[prop].length && props[prop].length === 1) {
|
|
8218
|
+
props[prop] = props[prop][0];
|
|
8219
|
+
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
|
|
8223
|
+
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;
|
|
8224
|
+
|
|
8225
|
+
let pts = nodeName === 'polygon' || nodeName === 'polyline' ? el.getAttribute('points') : [];
|
|
8226
|
+
let isPolygon = nodeName === 'polygon';
|
|
8227
|
+
if (pts.length) {
|
|
8228
|
+
pts = normalizePoly(pts);
|
|
8229
|
+
}
|
|
8230
|
+
|
|
8231
|
+
// we need to convert rects with corner rounding
|
|
8232
|
+
let pathData = [];
|
|
8233
|
+
if (nodeName === 'rect' && (rx || ry)) {
|
|
8234
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
8235
|
+
nodeName = 'path';
|
|
8236
|
+
}
|
|
8237
|
+
|
|
8238
|
+
switch (nodeName) {
|
|
8239
|
+
case 'line':
|
|
8240
|
+
len = getDistance({ x: x1, y: y1 }, { x: x2, y: y2 });
|
|
8241
|
+
break;
|
|
8242
|
+
case 'rect':
|
|
8243
|
+
len = width * 2 + height * 2;
|
|
8244
|
+
break;
|
|
8245
|
+
case 'circle':
|
|
8246
|
+
len = 2 * Math.PI * r;
|
|
8247
|
+
break;
|
|
8248
|
+
case 'ellipse':
|
|
8249
|
+
len = getEllipseLength(rx, ry);
|
|
8250
|
+
break;
|
|
8251
|
+
case 'polygon':
|
|
8252
|
+
case 'polyline':
|
|
8253
|
+
len = getPolygonLength(pts, isPolygon);
|
|
8254
|
+
break;
|
|
8255
|
+
case 'path':
|
|
8256
|
+
pathData = pathData.length ? pathData : parsePathDataNormalized(el.getAttribute('d'));
|
|
8257
|
+
len = getPathDataLength(pathData);
|
|
8258
|
+
break;
|
|
8259
|
+
}
|
|
8260
|
+
|
|
8261
|
+
return len
|
|
8262
|
+
}
|
|
8263
|
+
|
|
7557
8264
|
function removeHiddenSvgEls(svg) {
|
|
7558
8265
|
let els = svg.querySelectorAll('*');
|
|
7559
8266
|
els.forEach(el => {
|
|
@@ -7602,8 +8309,12 @@
|
|
|
7602
8309
|
*/
|
|
7603
8310
|
|
|
7604
8311
|
function removeSvgAtts(svg, remove = []) {
|
|
8312
|
+
removeAtts(svg, remove);
|
|
8313
|
+
}
|
|
8314
|
+
|
|
8315
|
+
function removeAtts(el, remove = []) {
|
|
7605
8316
|
remove.forEach(att => {
|
|
7606
|
-
|
|
8317
|
+
el.removeAttribute(att);
|
|
7607
8318
|
});
|
|
7608
8319
|
}
|
|
7609
8320
|
|
|
@@ -7701,22 +8412,107 @@
|
|
|
7701
8412
|
}
|
|
7702
8413
|
*/
|
|
7703
8414
|
|
|
8415
|
+
function setNormalizedTransformsToEl(el, {
|
|
8416
|
+
styleProps = {},
|
|
8417
|
+
} = {}) {
|
|
8418
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
8419
|
+
let name = el.nodeName.toLowerCase();
|
|
8420
|
+
|
|
8421
|
+
if(!matrix) return styleProps;
|
|
8422
|
+
|
|
8423
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8424
|
+
|
|
8425
|
+
// scale attributes instead of transform
|
|
8426
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8427
|
+
let unProportional = scaleX !== scaleY;
|
|
8428
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8429
|
+
|
|
8430
|
+
let needsTrans = (hasRot) || unProportional;
|
|
8431
|
+
needsTrans = true;
|
|
8432
|
+
|
|
8433
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8434
|
+
|
|
8435
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
8436
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8437
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8438
|
+
|
|
8439
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8440
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8441
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8442
|
+
|
|
8443
|
+
}
|
|
8444
|
+
else if (name === 'rect') {
|
|
8445
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8446
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8447
|
+
|
|
8448
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8449
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8450
|
+
|
|
8451
|
+
styleProps.x = [x];
|
|
8452
|
+
styleProps.y = [y];
|
|
8453
|
+
|
|
8454
|
+
styleProps.rx = [rx];
|
|
8455
|
+
styleProps.ry = [ry];
|
|
8456
|
+
|
|
8457
|
+
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8458
|
+
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8459
|
+
}
|
|
8460
|
+
|
|
8461
|
+
// remove now obsolete transform properties
|
|
8462
|
+
delete styleProps.matrix;
|
|
8463
|
+
delete styleProps.transformArr;
|
|
8464
|
+
delete styleProps.transComponents;
|
|
8465
|
+
|
|
8466
|
+
// mark transform attribute for removal
|
|
8467
|
+
styleProps.remove.push('transform');
|
|
8468
|
+
|
|
8469
|
+
// scale props like stroke width or dash-array
|
|
8470
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8471
|
+
|
|
8472
|
+
} else {
|
|
8473
|
+
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8474
|
+
|
|
8475
|
+
}
|
|
8476
|
+
|
|
8477
|
+
return styleProps
|
|
8478
|
+
|
|
8479
|
+
}
|
|
8480
|
+
|
|
8481
|
+
function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8482
|
+
if (scale === 1 || !props.length) return props;
|
|
8483
|
+
|
|
8484
|
+
for (let i = 0; i < props.length; i++) {
|
|
8485
|
+
let prop = props[i];
|
|
8486
|
+
|
|
8487
|
+
if (styleProps[prop] !== undefined) {
|
|
8488
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
return styleProps
|
|
8492
|
+
}
|
|
8493
|
+
|
|
7704
8494
|
function convertPathLengthAtt(el, {
|
|
7705
8495
|
styleProps = {}
|
|
7706
|
-
}={}) {
|
|
8496
|
+
} = {}) {
|
|
7707
8497
|
|
|
7708
|
-
let pathLength =
|
|
8498
|
+
let pathLength = styleProps['pathLength'];
|
|
7709
8499
|
|
|
7710
|
-
if (pathLength
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
8500
|
+
if (pathLength) {
|
|
8501
|
+
|
|
8502
|
+
if ((styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
|
|
8503
|
+
let elLength = getElementLength(el, {
|
|
8504
|
+
pathLength,
|
|
8505
|
+
props: styleProps
|
|
8506
|
+
});
|
|
8507
|
+
|
|
8508
|
+
let scale = elLength / pathLength;
|
|
8509
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
7715
8510
|
|
|
7716
|
-
|
|
8511
|
+
// set absolute
|
|
8512
|
+
if (styleProps['stroke-dasharray']) el.setAttribute('stroke-dasharray', styleProps['stroke-dasharray'].join(' '));
|
|
8513
|
+
if (styleProps['stroke-dashoffset']) el.setAttribute('stroke-dashoffset', styleProps['stroke-dashoffset'][0]);
|
|
7717
8514
|
|
|
7718
|
-
|
|
7719
|
-
[styleProps['stroke-dasharray'], styleProps['stroke-dashoffset']];
|
|
8515
|
+
}
|
|
7720
8516
|
|
|
7721
8517
|
// tag for removal
|
|
7722
8518
|
delete styleProps['pathLength'];
|
|
@@ -8051,85 +8847,6 @@
|
|
|
8051
8847
|
return props
|
|
8052
8848
|
}
|
|
8053
8849
|
|
|
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
8850
|
function cleanUpSVG(svgMarkup, {
|
|
8134
8851
|
removeHidden = true,
|
|
8135
8852
|
|
|
@@ -8159,7 +8876,10 @@
|
|
|
8159
8876
|
cleanupSVGAtts = true,
|
|
8160
8877
|
removeNameSpaced = true,
|
|
8161
8878
|
removeNameSpacedAtts = true,
|
|
8879
|
+
|
|
8880
|
+
// unit conversions
|
|
8162
8881
|
convertPathLength = false,
|
|
8882
|
+
toAbsoluteUnits = false,
|
|
8163
8883
|
|
|
8164
8884
|
// meta
|
|
8165
8885
|
allowMeta = false,
|
|
@@ -8184,10 +8904,10 @@
|
|
|
8184
8904
|
} = {}) {
|
|
8185
8905
|
|
|
8186
8906
|
// resolve dependencies
|
|
8187
|
-
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8188
|
-
|
|
8907
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8908
|
+
stylesToAttributes = true;
|
|
8189
8909
|
|
|
8190
|
-
if(stylesToAttributes) cleanUpStrokes = true;
|
|
8910
|
+
if (stylesToAttributes) cleanUpStrokes = true;
|
|
8191
8911
|
|
|
8192
8912
|
// replace namespaced refs
|
|
8193
8913
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
@@ -8232,7 +8952,7 @@
|
|
|
8232
8952
|
removeClassNames,
|
|
8233
8953
|
minifyRgbColors,
|
|
8234
8954
|
stylesheetProps: {},
|
|
8235
|
-
exclude:[]
|
|
8955
|
+
exclude: []
|
|
8236
8956
|
};
|
|
8237
8957
|
|
|
8238
8958
|
// root svg inline style properties
|
|
@@ -8349,9 +9069,14 @@
|
|
|
8349
9069
|
if (stylePropsSVG['class']) delete stylePropsSVG['class'];
|
|
8350
9070
|
if (stylePropsSVG['id']) delete stylePropsSVG['id'];
|
|
8351
9071
|
|
|
9072
|
+
// add svg props
|
|
9073
|
+
inheritedProps = {
|
|
9074
|
+
...stylePropsSVG,
|
|
9075
|
+
...inheritedProps,
|
|
9076
|
+
};
|
|
9077
|
+
|
|
8352
9078
|
// merge with svg props
|
|
8353
9079
|
styleProps = {
|
|
8354
|
-
...stylePropsSVG,
|
|
8355
9080
|
...inheritedProps,
|
|
8356
9081
|
...styleProps
|
|
8357
9082
|
};
|
|
@@ -8393,6 +9118,40 @@
|
|
|
8393
9118
|
// general cleanup
|
|
8394
9119
|
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
8395
9120
|
|
|
9121
|
+
// all relative units to absolute
|
|
9122
|
+
if (toAbsoluteUnits) {
|
|
9123
|
+
normalizeTransforms = true;
|
|
9124
|
+
|
|
9125
|
+
/**
|
|
9126
|
+
* apply consolidated
|
|
9127
|
+
* element attributes
|
|
9128
|
+
* remove non-supported element props
|
|
9129
|
+
*/
|
|
9130
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
9131
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
9132
|
+
|
|
9133
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9134
|
+
let values = styleProps[prop];
|
|
9135
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
9136
|
+
el.setAttribute(prop, val);
|
|
9137
|
+
}
|
|
9138
|
+
|
|
9139
|
+
let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
9140
|
+
|
|
9141
|
+
// check if same value is in inherited
|
|
9142
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9143
|
+
let valInh = inheritedProps[prop] || [];
|
|
9144
|
+
let val = stylePropsFiltered.propsFiltered[prop] || [];
|
|
9145
|
+
if (valInh.join() === val.join()) {
|
|
9146
|
+
removeAttsEl.push(prop);
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
|
|
9150
|
+
// remove obsolete/inherited
|
|
9151
|
+
removeAtts(el, removeAttsEl);
|
|
9152
|
+
|
|
9153
|
+
}
|
|
9154
|
+
|
|
8396
9155
|
if (stylesToAttributes) {
|
|
8397
9156
|
|
|
8398
9157
|
/**
|
|
@@ -8411,7 +9170,7 @@
|
|
|
8411
9170
|
* remove non-supported element props
|
|
8412
9171
|
*/
|
|
8413
9172
|
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
8414
|
-
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
9173
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
8415
9174
|
|
|
8416
9175
|
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
8417
9176
|
|
|
@@ -8425,12 +9184,14 @@
|
|
|
8425
9184
|
* remove obsolete
|
|
8426
9185
|
* attributes
|
|
8427
9186
|
*/
|
|
9187
|
+
removeAtts(el, remove);
|
|
8428
9188
|
|
|
9189
|
+
/*
|
|
8429
9190
|
for (let i = 0; i < remove.length; i++) {
|
|
8430
9191
|
let att = remove[i];
|
|
8431
|
-
|
|
8432
|
-
el.removeAttribute(att);
|
|
9192
|
+
el.removeAttribute(att)
|
|
8433
9193
|
}
|
|
9194
|
+
*/
|
|
8434
9195
|
|
|
8435
9196
|
} // endof style processing
|
|
8436
9197
|
|
|
@@ -8515,15 +9276,15 @@
|
|
|
8515
9276
|
let values = stylePropsFiltered[prop];
|
|
8516
9277
|
let val = values.length ? values.join(' ') : values[0];
|
|
8517
9278
|
|
|
8518
|
-
if(prop!=='class' && prop!=='id'){
|
|
9279
|
+
if (prop !== 'class' && prop !== 'id') {
|
|
8519
9280
|
|
|
8520
9281
|
let propShort = toShortStr(prop);
|
|
8521
9282
|
let valShort = toShortStr(val);
|
|
8522
9283
|
let propStr = `${propShort}-${valShort}`;
|
|
8523
|
-
|
|
9284
|
+
|
|
8524
9285
|
// store in node property
|
|
8525
9286
|
if (!el.styleSet) el.styleSet = new Set();
|
|
8526
|
-
if(propStr) el.styleSet.add(propStr);
|
|
9287
|
+
if (propStr) el.styleSet.add(propStr);
|
|
8527
9288
|
}
|
|
8528
9289
|
}
|
|
8529
9290
|
|
|
@@ -8572,7 +9333,7 @@
|
|
|
8572
9333
|
}
|
|
8573
9334
|
|
|
8574
9335
|
function removeEmptyClassAtts(svg) {
|
|
8575
|
-
let emptyClassEls = svg.querySelectorAll('[class=""');
|
|
9336
|
+
let emptyClassEls = svg.querySelectorAll('[class=""]');
|
|
8576
9337
|
emptyClassEls.forEach(el => {
|
|
8577
9338
|
el.removeAttribute('class');
|
|
8578
9339
|
});
|
|
@@ -8585,7 +9346,7 @@
|
|
|
8585
9346
|
|
|
8586
9347
|
let els = svg.querySelectorAll(renderedEls.join(', '));
|
|
8587
9348
|
let len = els.length;
|
|
8588
|
-
if(len===1) return;
|
|
9349
|
+
if (len === 1) return;
|
|
8589
9350
|
|
|
8590
9351
|
let el0 = els[0] || null;
|
|
8591
9352
|
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
|
|
@@ -8661,7 +9422,7 @@
|
|
|
8661
9422
|
if (children.length === 1) continue
|
|
8662
9423
|
|
|
8663
9424
|
// create new group
|
|
8664
|
-
if (!groupEl || groups.length>1) {
|
|
9425
|
+
if (!groupEl || groups.length > 1) {
|
|
8665
9426
|
|
|
8666
9427
|
groupEl = document.createElementNS(svgNs, 'g');
|
|
8667
9428
|
child0.parentNode.insertBefore(groupEl, child0);
|
|
@@ -8743,7 +9504,9 @@
|
|
|
8743
9504
|
bb0.bottom = y + height;
|
|
8744
9505
|
|
|
8745
9506
|
els.forEach(el => {
|
|
9507
|
+
|
|
8746
9508
|
let bb = getElBBox(el);
|
|
9509
|
+
|
|
8747
9510
|
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom;
|
|
8748
9511
|
if (outside) el.remove();
|
|
8749
9512
|
});
|
|
@@ -8852,8 +9615,130 @@
|
|
|
8852
9615
|
});
|
|
8853
9616
|
}
|
|
8854
9617
|
|
|
9618
|
+
function getArcFromPoly(pts, precise = false) {
|
|
9619
|
+
if (pts.length < 3) return false
|
|
9620
|
+
|
|
9621
|
+
// Pick 3 well-spaced points
|
|
9622
|
+
let len = pts.length;
|
|
9623
|
+
let idx1 = Math.floor(len * 0.333);
|
|
9624
|
+
let idx2 = Math.floor(len * 0.666);
|
|
9625
|
+
let idx3 = Math.floor(len * 0.5);
|
|
9626
|
+
|
|
9627
|
+
let p1 = pts[0];
|
|
9628
|
+
let p2 = pts[idx3];
|
|
9629
|
+
let p3 = pts[len - 1];
|
|
9630
|
+
|
|
9631
|
+
// Radius (use start point)
|
|
9632
|
+
let pts1 = [p1, p2, p3];
|
|
9633
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
9634
|
+
|
|
9635
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
9636
|
+
|
|
9637
|
+
// check if radii are consistent
|
|
9638
|
+
if (precise) {
|
|
9639
|
+
|
|
9640
|
+
/**
|
|
9641
|
+
* check multiple centroids
|
|
9642
|
+
* if the polyline can be expressed as
|
|
9643
|
+
* an arc - all centroids should be close
|
|
9644
|
+
*/
|
|
9645
|
+
|
|
9646
|
+
if (len > 3) {
|
|
9647
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
9648
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
9649
|
+
|
|
9650
|
+
if (!centroid1 || !centroid2) return false;
|
|
9651
|
+
|
|
9652
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
9653
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
9654
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
9655
|
+
let errorCentroid = (dist1 + dist2);
|
|
9656
|
+
|
|
9657
|
+
// centroids diverging too much
|
|
9658
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
9659
|
+
|
|
9660
|
+
return false
|
|
9661
|
+
}
|
|
9662
|
+
|
|
9663
|
+
}
|
|
9664
|
+
|
|
9665
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
9666
|
+
|
|
9667
|
+
for (let i = 0; i < len; i++) {
|
|
9668
|
+
let pt = pts[i];
|
|
9669
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
9670
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
9671
|
+
|
|
9672
|
+
if (error > 0.0025) {
|
|
9673
|
+
/*
|
|
9674
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
9675
|
+
renderPoint(markers, centroid, 'orange')
|
|
9676
|
+
renderPoint(markers, p1, 'green')
|
|
9677
|
+
renderPoint(markers, p2)
|
|
9678
|
+
renderPoint(markers, p3, 'purple')
|
|
9679
|
+
*/
|
|
9680
|
+
return false;
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
|
|
9684
|
+
// calculate proper radius
|
|
9685
|
+
r = Math.sqrt(rSqMid);
|
|
9686
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9687
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9688
|
+
|
|
9689
|
+
} else {
|
|
9690
|
+
r = getDistance(centroid, p1);
|
|
9691
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9692
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9693
|
+
}
|
|
9694
|
+
|
|
9695
|
+
return {
|
|
9696
|
+
centroid,
|
|
9697
|
+
r,
|
|
9698
|
+
startAngle,
|
|
9699
|
+
endAngle,
|
|
9700
|
+
deltaAngle
|
|
9701
|
+
};
|
|
9702
|
+
}
|
|
9703
|
+
|
|
9704
|
+
function getPolyArcCentroid(pts = []) {
|
|
9705
|
+
|
|
9706
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
9707
|
+
if (pts.length < 3) return false
|
|
9708
|
+
|
|
9709
|
+
let p1 = pts[0];
|
|
9710
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9711
|
+
let p3 = pts[pts.length - 1];
|
|
9712
|
+
|
|
9713
|
+
let x1 = p1.x, y1 = p1.y;
|
|
9714
|
+
let x2 = p2.x, y2 = p2.y;
|
|
9715
|
+
let x3 = p3.x, y3 = p3.y;
|
|
9716
|
+
|
|
9717
|
+
let a = x1 - x2;
|
|
9718
|
+
let b = y1 - y2;
|
|
9719
|
+
let c = x1 - x3;
|
|
9720
|
+
let d = y1 - y3;
|
|
9721
|
+
|
|
9722
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
9723
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
9724
|
+
|
|
9725
|
+
let det = a * d - b * c;
|
|
9726
|
+
|
|
9727
|
+
// colinear points
|
|
9728
|
+
if (Math.abs(det) < 1e-10) {
|
|
9729
|
+
return false;
|
|
9730
|
+
}
|
|
9731
|
+
|
|
9732
|
+
// find center of arc
|
|
9733
|
+
let cx = (d * e - b * f) / det;
|
|
9734
|
+
let cy = (-c * e + a * f) / det;
|
|
9735
|
+
let centroid = { x: cx, y: cy };
|
|
9736
|
+
return centroid
|
|
9737
|
+
}
|
|
9738
|
+
|
|
8855
9739
|
function refineRoundedCorners(pathData, {
|
|
8856
9740
|
threshold = 0,
|
|
9741
|
+
simplifyQuadraticCorners = false,
|
|
8857
9742
|
tolerance = 1
|
|
8858
9743
|
} = {}) {
|
|
8859
9744
|
|
|
@@ -8878,6 +9763,9 @@
|
|
|
8878
9763
|
let firstIsLine = pathData[1].type === 'L';
|
|
8879
9764
|
let firstIsBez = pathData[1].type === 'C';
|
|
8880
9765
|
|
|
9766
|
+
// in case we have simplified a corner connecting to the start
|
|
9767
|
+
let M_adj = null;
|
|
9768
|
+
|
|
8881
9769
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
8882
9770
|
|
|
8883
9771
|
// normalize closepath to lineto
|
|
@@ -8917,15 +9805,17 @@
|
|
|
8917
9805
|
// closing corner to start
|
|
8918
9806
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
8919
9807
|
comL1 = pathData[1];
|
|
9808
|
+
|
|
8920
9809
|
comBez = [pathData[l - lastOff]];
|
|
8921
9810
|
|
|
8922
9811
|
}
|
|
8923
9812
|
|
|
9813
|
+
// collect enclosed bezier segments
|
|
8924
9814
|
for (let j = i + 1; j < l; j++) {
|
|
8925
9815
|
let comN = pathData[j] ? pathData[j] : null;
|
|
8926
9816
|
let comPrev = pathData[j - 1];
|
|
8927
9817
|
|
|
8928
|
-
if (comPrev.type === 'C') {
|
|
9818
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
8929
9819
|
comBez.push(comPrev);
|
|
8930
9820
|
}
|
|
8931
9821
|
|
|
@@ -8956,39 +9846,67 @@
|
|
|
8956
9846
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
8957
9847
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
8958
9848
|
|
|
9849
|
+
/*
|
|
9850
|
+
*/
|
|
9851
|
+
|
|
8959
9852
|
if (comBez.length && !signChange && isSmall) {
|
|
8960
9853
|
|
|
8961
|
-
let
|
|
9854
|
+
let isSquare = false;
|
|
9855
|
+
|
|
9856
|
+
if (comBez.length === 1) {
|
|
9857
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
9858
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
9859
|
+
let diff = (dx - dy);
|
|
9860
|
+
let rat = Math.abs(diff / dx);
|
|
9861
|
+
isSquare = rat < 0.01;
|
|
9862
|
+
}
|
|
9863
|
+
|
|
9864
|
+
let preferArcs = true;
|
|
9865
|
+
preferArcs = false;
|
|
9866
|
+
|
|
9867
|
+
// if rectangular prefer arcs
|
|
9868
|
+
if (preferArcs && isSquare) {
|
|
9869
|
+
|
|
9870
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
9871
|
+
|
|
9872
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
9873
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
9874
|
+
|
|
9875
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
9876
|
+
|
|
9877
|
+
let largeArc = 0;
|
|
9878
|
+
|
|
9879
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
9880
|
+
|
|
9881
|
+
pathDataN.push(comL0, comArc);
|
|
9882
|
+
i += offset;
|
|
9883
|
+
continue
|
|
9884
|
+
|
|
9885
|
+
}
|
|
9886
|
+
|
|
9887
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
9888
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
9889
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
9890
|
+
|
|
8962
9891
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
8963
9892
|
|
|
8964
|
-
|
|
9893
|
+
// exit: is rather flat or has no intersection
|
|
9894
|
+
|
|
9895
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
8965
9896
|
pathDataN.push(com);
|
|
8966
9897
|
continue
|
|
8967
9898
|
}
|
|
8968
9899
|
|
|
8969
|
-
// check sign change
|
|
9900
|
+
// check sign change - exit if present
|
|
8970
9901
|
if (ptQ) {
|
|
8971
9902
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
8972
9903
|
let area0_abs = Math.abs(area0);
|
|
8973
9904
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
8974
9905
|
let area1_abs = Math.abs(area1);
|
|
8975
9906
|
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
9907
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
8989
9908
|
|
|
8990
9909
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
8991
|
-
|
|
8992
9910
|
pathDataN.push(com);
|
|
8993
9911
|
continue
|
|
8994
9912
|
}
|
|
@@ -9003,24 +9921,67 @@
|
|
|
9003
9921
|
|
|
9004
9922
|
// not in tolerance – return original command
|
|
9005
9923
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
9006
|
-
|
|
9007
9924
|
pathDataN.push(com);
|
|
9008
9925
|
continue;
|
|
9009
9926
|
|
|
9010
|
-
}
|
|
9927
|
+
}
|
|
9011
9928
|
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
comQ.cp1 = ptQ;
|
|
9015
|
-
comQ.p = comL1.p0;
|
|
9929
|
+
// return simplified quadratic Bézier command
|
|
9930
|
+
let p_Q = comL1.p0;
|
|
9016
9931
|
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9932
|
+
// adjust previous end point to better fit the cubic curvature
|
|
9933
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
9934
|
+
|
|
9935
|
+
if (adjustQ) {
|
|
9936
|
+
|
|
9937
|
+
let t = 0.1666;
|
|
9938
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
9939
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
9940
|
+
|
|
9941
|
+
// round for large enough segments
|
|
9942
|
+
let isH = ptQ.y===comL0.p.y;
|
|
9943
|
+
let isV = ptQ.x===comL0.p.x;
|
|
9944
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
9945
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
9946
|
+
|
|
9947
|
+
if(isSquare && com.dimA>3){
|
|
9948
|
+
let dec = 0.5;
|
|
9949
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
9950
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
9951
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
9952
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
9953
|
+
}
|
|
9954
|
+
|
|
9955
|
+
/*
|
|
9956
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
9957
|
+
renderPoint(markers, p_Q, 'orange')
|
|
9958
|
+
renderPoint(markers, comL0.p, 'green')
|
|
9959
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
9960
|
+
*/
|
|
9961
|
+
|
|
9962
|
+
// set new M starting point
|
|
9963
|
+
if (i === l - lastOff - 1) {
|
|
9964
|
+
|
|
9965
|
+
M_adj = p_Q;
|
|
9966
|
+
}
|
|
9967
|
+
|
|
9968
|
+
// adjust previous lineto end point
|
|
9969
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
9970
|
+
comL0.p = p0_adj;
|
|
9020
9971
|
|
|
9021
|
-
continue;
|
|
9022
9972
|
}
|
|
9023
9973
|
|
|
9974
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
9975
|
+
comQ.cp1 = ptQ;
|
|
9976
|
+
comQ.p0 = comL0.p;
|
|
9977
|
+
comQ.p = p_Q;
|
|
9978
|
+
|
|
9979
|
+
// add quadratic command
|
|
9980
|
+
pathDataN.push(comL0, comQ);
|
|
9981
|
+
|
|
9982
|
+
i += offset;
|
|
9983
|
+
continue;
|
|
9984
|
+
|
|
9024
9985
|
}
|
|
9025
9986
|
}
|
|
9026
9987
|
}
|
|
@@ -9034,6 +9995,12 @@
|
|
|
9034
9995
|
|
|
9035
9996
|
}
|
|
9036
9997
|
|
|
9998
|
+
// correct starting point connecting with last corner rounding
|
|
9999
|
+
if (M_adj) {
|
|
10000
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
10001
|
+
pathDataN[0].p0 = M_adj;
|
|
10002
|
+
}
|
|
10003
|
+
|
|
9037
10004
|
// revert close path normalization
|
|
9038
10005
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
9039
10006
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -9043,51 +10010,143 @@
|
|
|
9043
10010
|
|
|
9044
10011
|
}
|
|
9045
10012
|
|
|
9046
|
-
function
|
|
9047
|
-
|
|
10013
|
+
function simplifyAdjacentRound(pathData, {
|
|
10014
|
+
threshold = 0,
|
|
10015
|
+
tolerance = 1,
|
|
10016
|
+
// take arcs or cubic beziers
|
|
10017
|
+
toCubic = false,
|
|
10018
|
+
debug = false
|
|
10019
|
+
} = {}) {
|
|
9048
10020
|
|
|
9049
|
-
//
|
|
9050
|
-
|
|
9051
|
-
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9052
|
-
let p3 = pts[pts.length - 1];
|
|
10021
|
+
// fix small Arcs
|
|
10022
|
+
pathData = convertSmallArcsToLinetos(pathData);
|
|
9053
10023
|
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
let x3 = p3.x, y3 = p3.y;
|
|
10024
|
+
// min size threshold for corners
|
|
10025
|
+
threshold *= tolerance;
|
|
9057
10026
|
|
|
9058
|
-
let
|
|
9059
|
-
let b = y1 - y2;
|
|
9060
|
-
let c = x1 - x3;
|
|
9061
|
-
let d = y1 - y3;
|
|
10027
|
+
let l = pathData.length;
|
|
9062
10028
|
|
|
9063
|
-
|
|
9064
|
-
let
|
|
10029
|
+
// add fist command
|
|
10030
|
+
let pathDataN = [pathData[0]];
|
|
9065
10031
|
|
|
9066
|
-
|
|
10032
|
+
// find adjacent cubics between extremes
|
|
9067
10033
|
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
10034
|
+
for (let i = 1; i < l; i++) {
|
|
10035
|
+
pathData[i - 1];
|
|
10036
|
+
let com = pathData[i];
|
|
10037
|
+
let comN = pathData[i + 1] || null;
|
|
9072
10038
|
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
10039
|
+
if (!comN) {
|
|
10040
|
+
pathDataN.push(com);
|
|
10041
|
+
break
|
|
10042
|
+
}
|
|
9077
10043
|
|
|
9078
|
-
|
|
9079
|
-
|
|
10044
|
+
let { type, extreme = false, p0, p, dimA = 0 } = com;
|
|
10045
|
+
// for short segment detection
|
|
10046
|
+
let dimAN = comN.dimA;
|
|
10047
|
+
let dimA0 = dimA + dimAN;
|
|
10048
|
+
let thresh = 0.1;
|
|
9080
10049
|
|
|
9081
|
-
|
|
9082
|
-
|
|
10050
|
+
// ignore short linetos
|
|
10051
|
+
let isShortN = dimAN < dimA0 * thresh;
|
|
9083
10052
|
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
10053
|
+
// adjacent cubic commands - accept short in between linetos
|
|
10054
|
+
if ((type === 'C') && (comN.type === 'C' || isShortN)) {
|
|
10055
|
+
|
|
10056
|
+
let candidates = [];
|
|
10057
|
+
|
|
10058
|
+
for (let j = i + 1; j < l; j++) {
|
|
10059
|
+
let comN = pathData[j];
|
|
10060
|
+
let { type, extreme = false, corner = false, dimA = 0 } = comN;
|
|
10061
|
+
let isShort = dimA < dimA0 * thresh;
|
|
10062
|
+
|
|
10063
|
+
// skip for type change(unless very short), extremes or corners
|
|
10064
|
+
/*
|
|
10065
|
+
if ( (comN.extreme || comN.corner) ) {
|
|
10066
|
+
if(!extreme && !corner) candidates.push(comN)
|
|
10067
|
+
break;
|
|
10068
|
+
}
|
|
10069
|
+
*/
|
|
10070
|
+
|
|
10071
|
+
if (extreme || corner) {
|
|
10072
|
+
|
|
10073
|
+
if (isShort && comN.type !== 'C') ;
|
|
10074
|
+
|
|
10075
|
+
if ((extreme && !corner)) {
|
|
10076
|
+
|
|
10077
|
+
candidates.push(comN);
|
|
10078
|
+
}
|
|
10079
|
+
|
|
10080
|
+
break;
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
candidates.push(comN);
|
|
10084
|
+
}
|
|
10085
|
+
|
|
10086
|
+
// try to create arc command
|
|
10087
|
+
if (candidates.length > 1) {
|
|
10088
|
+
|
|
10089
|
+
let clen = candidates.length;
|
|
10090
|
+
let pts = [com.p0, com.p,];
|
|
10091
|
+
|
|
10092
|
+
// add interpolated points to prevent wrong arc replacements
|
|
10093
|
+
candidates.forEach(c => {
|
|
10094
|
+
if (c.type === 'C') {
|
|
10095
|
+
let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5);
|
|
10096
|
+
pts.push(pt);
|
|
10097
|
+
}
|
|
10098
|
+
pts.push(c.p);
|
|
10099
|
+
});
|
|
10100
|
+
|
|
10101
|
+
let precise = true;
|
|
10102
|
+
let arcProps = getArcFromPoly(pts, precise);
|
|
10103
|
+
|
|
10104
|
+
// could be combined
|
|
10105
|
+
if (arcProps) {
|
|
10106
|
+
|
|
10107
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
10108
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
10109
|
+
|
|
10110
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
10111
|
+
largeArc = 0;
|
|
10112
|
+
let comLast = candidates[clen - 1];
|
|
10113
|
+
let p = comLast.p;
|
|
10114
|
+
|
|
10115
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] };
|
|
10116
|
+
|
|
10117
|
+
comArc.dimA = getDistManhattan(p0, p);
|
|
10118
|
+
comArc.p0 = p0;
|
|
10119
|
+
comArc.p = p;
|
|
10120
|
+
comArc.error = 0;
|
|
10121
|
+
comArc.directionChange = comLast.directionChange;
|
|
10122
|
+
comArc.extreme = comLast.extreme;
|
|
10123
|
+
comArc.corner = comLast.corner;
|
|
10124
|
+
pathDataN.push(comArc);
|
|
10125
|
+
|
|
10126
|
+
i += candidates.length;
|
|
10127
|
+
continue
|
|
10128
|
+
|
|
10129
|
+
}
|
|
10130
|
+
|
|
10131
|
+
// arc radius calculation failed - return original
|
|
10132
|
+
else {
|
|
10133
|
+
pathDataN.push(com);
|
|
10134
|
+
}
|
|
10135
|
+
}
|
|
10136
|
+
|
|
10137
|
+
// could not be simplified – return original command
|
|
10138
|
+
else {
|
|
10139
|
+
pathDataN.push(com);
|
|
10140
|
+
}
|
|
10141
|
+
|
|
10142
|
+
}
|
|
10143
|
+
// all other commands
|
|
10144
|
+
else {
|
|
10145
|
+
pathDataN.push(com);
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
|
|
10149
|
+
return pathDataN
|
|
9091
10150
|
}
|
|
9092
10151
|
|
|
9093
10152
|
function refineRoundSegments(pathData, {
|
|
@@ -9106,9 +10165,6 @@
|
|
|
9106
10165
|
// add fist command
|
|
9107
10166
|
let pathDataN = [pathData[0]];
|
|
9108
10167
|
|
|
9109
|
-
// just for debugging
|
|
9110
|
-
let pathDataTest = [];
|
|
9111
|
-
|
|
9112
10168
|
for (let i = 1; i < l; i++) {
|
|
9113
10169
|
let com = pathData[i];
|
|
9114
10170
|
let { type } = com;
|
|
@@ -9135,11 +10191,12 @@
|
|
|
9135
10191
|
|
|
9136
10192
|
// 2. line-line-bezier-line-line
|
|
9137
10193
|
if (
|
|
10194
|
+
comN2 && comN3 &&
|
|
9138
10195
|
comP.type === 'L' &&
|
|
9139
10196
|
type === 'L' &&
|
|
9140
10197
|
comBez &&
|
|
9141
10198
|
comN2.type === 'L' &&
|
|
9142
|
-
|
|
10199
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
9143
10200
|
) {
|
|
9144
10201
|
|
|
9145
10202
|
L1 = [com.p0, com.p];
|
|
@@ -9166,10 +10223,10 @@
|
|
|
9166
10223
|
}
|
|
9167
10224
|
|
|
9168
10225
|
// 1. line-bezier-bezier-line
|
|
9169
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
10226
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
9170
10227
|
|
|
9171
10228
|
// 1.2 next is cubic next is lineto
|
|
9172
|
-
if (
|
|
10229
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
9173
10230
|
|
|
9174
10231
|
combine = true;
|
|
9175
10232
|
|
|
@@ -9228,16 +10285,19 @@
|
|
|
9228
10285
|
}
|
|
9229
10286
|
);
|
|
9230
10287
|
|
|
9231
|
-
if(bezierCommands.length === 1){
|
|
10288
|
+
if (bezierCommands.length === 1) {
|
|
9232
10289
|
|
|
9233
10290
|
// prefer more compact quadratic - otherwise arcs
|
|
9234
10291
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
9235
10292
|
|
|
9236
10293
|
if (comBezier.type === 'Q') {
|
|
9237
10294
|
toCubic = true;
|
|
10295
|
+
}else {
|
|
10296
|
+
comBezier = bezierCommands[0];
|
|
9238
10297
|
}
|
|
9239
10298
|
|
|
9240
10299
|
com = comBezier;
|
|
10300
|
+
|
|
9241
10301
|
}
|
|
9242
10302
|
|
|
9243
10303
|
// prefer arcs if 2 cubics are required
|
|
@@ -9257,25 +10317,28 @@
|
|
|
9257
10317
|
|
|
9258
10318
|
// test rendering
|
|
9259
10319
|
|
|
10320
|
+
/*
|
|
9260
10321
|
if (debug) {
|
|
9261
10322
|
// arcs
|
|
9262
10323
|
if (!toCubic) {
|
|
9263
10324
|
pathDataTest = [
|
|
9264
10325
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9265
10326
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
9266
|
-
]
|
|
10327
|
+
]
|
|
9267
10328
|
}
|
|
9268
10329
|
// cubics
|
|
9269
10330
|
else {
|
|
9270
10331
|
pathDataTest = [
|
|
9271
10332
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9272
10333
|
...bezierCommands
|
|
9273
|
-
]
|
|
10334
|
+
]
|
|
10335
|
+
|
|
9274
10336
|
}
|
|
9275
10337
|
|
|
9276
10338
|
let d = pathDataToD(pathDataTest);
|
|
9277
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
10339
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
9278
10340
|
}
|
|
10341
|
+
*/
|
|
9279
10342
|
|
|
9280
10343
|
pathDataN.push(com);
|
|
9281
10344
|
i++;
|
|
@@ -9407,7 +10470,6 @@
|
|
|
9407
10470
|
let com = pathData[c];
|
|
9408
10471
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
9409
10472
|
if (type === 'C') {
|
|
9410
|
-
|
|
9411
10473
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
9412
10474
|
if (comQ.type === 'Q') {
|
|
9413
10475
|
comQ.extreme = com.extreme;
|
|
@@ -11019,32 +12081,24 @@
|
|
|
11019
12081
|
}
|
|
11020
12082
|
|
|
11021
12083
|
// reverse paths
|
|
11022
|
-
for (let i = 0; i < l; i++) {
|
|
12084
|
+
for (let i = 0; l && i < l; i++) {
|
|
11023
12085
|
|
|
11024
12086
|
let poly = polys[i];
|
|
11025
12087
|
let { cw, includedIn, includes } = poly;
|
|
11026
12088
|
|
|
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
|
-
}
|
|
12089
|
+
let len = includes.length;
|
|
11037
12090
|
|
|
11038
12091
|
// reverse inner sub paths
|
|
11039
|
-
for (let j = 0; j <
|
|
12092
|
+
for (let j = 0; len && j < len; j++) {
|
|
11040
12093
|
let ind = includes[j];
|
|
11041
12094
|
let child = polys[ind];
|
|
11042
12095
|
|
|
11043
|
-
|
|
12096
|
+
// nothing to do
|
|
12097
|
+
if (child.cw !== cw) continue
|
|
12098
|
+
|
|
12099
|
+
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
12100
|
+
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11044
12101
|
|
|
11045
|
-
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
11046
|
-
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11047
|
-
}
|
|
11048
12102
|
}
|
|
11049
12103
|
}
|
|
11050
12104
|
|
|
@@ -11084,6 +12138,7 @@
|
|
|
11084
12138
|
allowAriaAtts: true,
|
|
11085
12139
|
|
|
11086
12140
|
convertPathLength: false,
|
|
12141
|
+
toAbsoluteUnits: false,
|
|
11087
12142
|
|
|
11088
12143
|
// custom removal
|
|
11089
12144
|
removeElements: [],
|
|
@@ -11112,6 +12167,7 @@
|
|
|
11112
12167
|
revertToQuadratics: true,
|
|
11113
12168
|
refineExtremes: false,
|
|
11114
12169
|
simplifyCorners: false,
|
|
12170
|
+
simplifyQuadraticCorners: false,
|
|
11115
12171
|
keepExtremes: true,
|
|
11116
12172
|
keepCorners: true,
|
|
11117
12173
|
keepInflections: false,
|
|
@@ -11168,7 +12224,7 @@
|
|
|
11168
12224
|
let isArray = Array.isArray(val);
|
|
11169
12225
|
|
|
11170
12226
|
if (isBoolean) val = false;
|
|
11171
|
-
else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
|
|
12227
|
+
else if (!isArray && isNum) val = val === 1 ? 1 : (prop === 'decimals' ? -1 : 0);
|
|
11172
12228
|
else if (isArray) val = [];
|
|
11173
12229
|
settingsNull[prop] = val;
|
|
11174
12230
|
}
|
|
@@ -11199,10 +12255,14 @@
|
|
|
11199
12255
|
...settingsDefaults,
|
|
11200
12256
|
...{
|
|
11201
12257
|
keepSmaller: false,
|
|
12258
|
+
convertPathLength:true,
|
|
11202
12259
|
toRelative: true,
|
|
11203
12260
|
toMixed: true,
|
|
11204
12261
|
toShorthands: true,
|
|
11205
12262
|
|
|
12263
|
+
allowMeta:true,
|
|
12264
|
+
allowDataAtts:true,
|
|
12265
|
+
allowAriaAtts:true,
|
|
11206
12266
|
legacyHref: true,
|
|
11207
12267
|
addViewBox: true,
|
|
11208
12268
|
addDimensions: true,
|
|
@@ -11270,19 +12330,24 @@
|
|
|
11270
12330
|
high: {
|
|
11271
12331
|
...settingsDefaults,
|
|
11272
12332
|
...{
|
|
11273
|
-
tolerance: 1.
|
|
12333
|
+
tolerance: 1.1,
|
|
11274
12334
|
toMixed: true,
|
|
11275
12335
|
refineExtremes: true,
|
|
11276
12336
|
simplifyCorners: true,
|
|
12337
|
+
simplifyQuadraticCorners: true,
|
|
12338
|
+
removeOrphanSubpaths: true,
|
|
11277
12339
|
simplifyRound: true,
|
|
11278
12340
|
removeClassNames: true,
|
|
11279
12341
|
cubicToArc: true,
|
|
12342
|
+
minifyD: 0,
|
|
11280
12343
|
removeComments: true,
|
|
11281
12344
|
removeHidden: true,
|
|
11282
|
-
removeOffCanvas: true,
|
|
11283
12345
|
addViewBox: true,
|
|
11284
12346
|
removeDimensions: true,
|
|
11285
|
-
|
|
12347
|
+
removeOffCanvas: true,
|
|
12348
|
+
|
|
12349
|
+
/*
|
|
12350
|
+
*/
|
|
11286
12351
|
}
|
|
11287
12352
|
}
|
|
11288
12353
|
|
|
@@ -11440,18 +12505,44 @@
|
|
|
11440
12505
|
...settings
|
|
11441
12506
|
};
|
|
11442
12507
|
|
|
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,
|
|
12508
|
+
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
12509
|
|
|
11445
12510
|
// clamp tolerance and scale
|
|
11446
12511
|
tolerance = Math.max(0.1, tolerance);
|
|
11447
12512
|
scale = Math.max(0.001, scale);
|
|
11448
|
-
if(fixDirections) keepSmaller = false;
|
|
12513
|
+
if (fixDirections) keepSmaller = false;
|
|
11449
12514
|
if (scale !== 1 || scaleTo || crop || alignToOrigin) {
|
|
11450
12515
|
convertTransforms = true;
|
|
11451
12516
|
settings.convertTransforms = true;
|
|
11452
12517
|
}
|
|
11453
12518
|
|
|
11454
|
-
|
|
12519
|
+
/**
|
|
12520
|
+
* intercept
|
|
12521
|
+
* invalid inputs
|
|
12522
|
+
*/
|
|
12523
|
+
|
|
12524
|
+
let inputDetection = detectInputType(input);
|
|
12525
|
+
let { inputType, log } = inputDetection;
|
|
12526
|
+
|
|
12527
|
+
// invalid file
|
|
12528
|
+
if (inputType === 'invalid' || input === dummySVG) {
|
|
12529
|
+
// return dummy SVG to continue processing
|
|
12530
|
+
|
|
12531
|
+
let report = {
|
|
12532
|
+
original: 0,
|
|
12533
|
+
new: 0,
|
|
12534
|
+
saved: 0,
|
|
12535
|
+
svgSize:0,
|
|
12536
|
+
svgSizeOpt:0,
|
|
12537
|
+
compression:0,
|
|
12538
|
+
decimals:0,
|
|
12539
|
+
invalid:true
|
|
12540
|
+
};
|
|
12541
|
+
|
|
12542
|
+
return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
|
|
12543
|
+
|
|
12544
|
+
}
|
|
12545
|
+
|
|
11455
12546
|
let svg = '';
|
|
11456
12547
|
let svgSize = 0;
|
|
11457
12548
|
let svgSizeOpt = 0;
|
|
@@ -11571,10 +12662,7 @@
|
|
|
11571
12662
|
// convert all shapes to paths
|
|
11572
12663
|
if (shapesToPaths) {
|
|
11573
12664
|
shapeConvert = 'toPaths';
|
|
11574
|
-
|
|
11575
|
-
convert_ellipses = true;
|
|
11576
|
-
convert_poly = true;
|
|
11577
|
-
convert_lines = true;
|
|
12665
|
+
convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse'];
|
|
11578
12666
|
}
|
|
11579
12667
|
|
|
11580
12668
|
// sanitize SVG - clone/decouple settings
|
|
@@ -11608,6 +12696,9 @@
|
|
|
11608
12696
|
decimals,
|
|
11609
12697
|
};
|
|
11610
12698
|
|
|
12699
|
+
let comCount = 0;
|
|
12700
|
+
let comCountS = 0;
|
|
12701
|
+
|
|
11611
12702
|
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
11612
12703
|
|
|
11613
12704
|
let pathDataPlusArr = [];
|
|
@@ -11652,7 +12743,7 @@
|
|
|
11652
12743
|
}
|
|
11653
12744
|
|
|
11654
12745
|
// count commands for evaluation
|
|
11655
|
-
|
|
12746
|
+
comCount += pathData.length;
|
|
11656
12747
|
|
|
11657
12748
|
if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
11658
12749
|
|
|
@@ -11817,11 +12908,14 @@
|
|
|
11817
12908
|
if (simplifyCorners) {
|
|
11818
12909
|
|
|
11819
12910
|
let threshold = (bb.width + bb.height) * 0.1;
|
|
11820
|
-
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
12911
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners });
|
|
11821
12912
|
}
|
|
11822
12913
|
|
|
11823
12914
|
// refine round segment sequences
|
|
11824
|
-
if (simplifyRound)
|
|
12915
|
+
if (simplifyRound) {
|
|
12916
|
+
pathData = refineRoundSegments(pathData);
|
|
12917
|
+
pathData = simplifyAdjacentRound(pathData);
|
|
12918
|
+
}
|
|
11825
12919
|
|
|
11826
12920
|
// simplify to quadratics
|
|
11827
12921
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
@@ -11851,7 +12945,7 @@
|
|
|
11851
12945
|
let yMax = Math.max(...yArr);
|
|
11852
12946
|
|
|
11853
12947
|
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
11854
|
-
|
|
12948
|
+
bb_global.height > bb_global.width;
|
|
11855
12949
|
|
|
11856
12950
|
// fix path directions - before reordering
|
|
11857
12951
|
if (fixDirections) {
|
|
@@ -11860,7 +12954,23 @@
|
|
|
11860
12954
|
|
|
11861
12955
|
// prefer top to bottom priority for portrait aspect ratios
|
|
11862
12956
|
if (optimizeOrder) {
|
|
11863
|
-
|
|
12957
|
+
/*
|
|
12958
|
+
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)
|
|
12959
|
+
*/
|
|
12960
|
+
|
|
12961
|
+
// add missin bbox
|
|
12962
|
+
pathDataPlusArr.forEach(p => {
|
|
12963
|
+
if (p.bb.x === undefined) {
|
|
12964
|
+
p.bb = getPolyBBox(getPathDataVertices(p.pathData));
|
|
12965
|
+
}
|
|
12966
|
+
});
|
|
12967
|
+
|
|
12968
|
+
try {
|
|
12969
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
|
|
12970
|
+
|
|
12971
|
+
} catch {
|
|
12972
|
+
}
|
|
12973
|
+
|
|
11864
12974
|
}
|
|
11865
12975
|
|
|
11866
12976
|
// flatten compound paths
|
|
@@ -11923,7 +13033,7 @@
|
|
|
11923
13033
|
}
|
|
11924
13034
|
|
|
11925
13035
|
// compare command count
|
|
11926
|
-
|
|
13036
|
+
comCountS += pathData.length;
|
|
11927
13037
|
|
|
11928
13038
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
11929
13039
|
|
|
@@ -11998,6 +13108,9 @@
|
|
|
11998
13108
|
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
|
|
11999
13109
|
|
|
12000
13110
|
report = {
|
|
13111
|
+
original: comCount,
|
|
13112
|
+
new: comCountS,
|
|
13113
|
+
saved: comCount - comCountS,
|
|
12001
13114
|
svgSize,
|
|
12002
13115
|
svgSizeOpt,
|
|
12003
13116
|
compression,
|
|
@@ -12032,6 +13145,8 @@
|
|
|
12032
13145
|
if (typeof window !== 'undefined') {
|
|
12033
13146
|
window.svgPathSimplify = svgPathSimplify;
|
|
12034
13147
|
window.getElementTransform = getElementTransform;
|
|
13148
|
+
window.validateSVG = validateSVG;
|
|
13149
|
+
window.detectInputType = detectInputType;
|
|
12035
13150
|
|
|
12036
13151
|
window.getViewBox = getViewBox;
|
|
12037
13152
|
|
|
@@ -12045,6 +13160,7 @@
|
|
|
12045
13160
|
exports.atan2 = atan2$1;
|
|
12046
13161
|
exports.ceil = ceil$1;
|
|
12047
13162
|
exports.cos = cos$1;
|
|
13163
|
+
exports.detectInputType = detectInputType;
|
|
12048
13164
|
exports.exp = exp$1;
|
|
12049
13165
|
exports.floor = floor$1;
|
|
12050
13166
|
exports.getElementTransform = getElementTransform;
|
|
@@ -12060,5 +13176,6 @@
|
|
|
12060
13176
|
exports.sqrt = sqrt$1;
|
|
12061
13177
|
exports.svgPathSimplify = svgPathSimplify;
|
|
12062
13178
|
exports.tan = tan$1;
|
|
13179
|
+
exports.validateSVG = validateSVG;
|
|
12063
13180
|
|
|
12064
13181
|
})(this["svg-path-simplify"] = this["svg-path-simplify"] || {});
|