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
|
@@ -25,62 +25,276 @@ function renderPoint(
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const {
|
|
29
|
+
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,
|
|
30
|
+
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
|
|
31
|
+
} = Math;
|
|
32
|
+
|
|
33
|
+
const rad2Deg = 180/Math.PI;
|
|
34
|
+
const deg2rad = Math.PI/180;
|
|
35
|
+
const root2 = 1.4142135623730951;
|
|
36
|
+
const svgNs = 'http://www.w3.org/2000/svg';
|
|
37
|
+
const dummySVG = `<svg id="svgInvalid" xmlns="${svgNs}" viewBox="0 0 1 1"><path d="M0 0 h0" /></svg>`;
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
// 1/2.54
|
|
40
|
+
const inch2cm = 0.39370078;
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
// 1/72
|
|
43
|
+
const inch2pt = 0.01388889;
|
|
44
|
+
|
|
45
|
+
function validateSVG(markup, allowed = {}) {
|
|
46
|
+
allowed = {
|
|
47
|
+
...{
|
|
48
|
+
|
|
49
|
+
useElsNested: 5000,
|
|
50
|
+
hasScripts: false,
|
|
51
|
+
hasEntity: false,
|
|
52
|
+
fileSizeKB: 10000,
|
|
53
|
+
isSymbolSprite: false,
|
|
54
|
+
isSvgFont: false
|
|
55
|
+
},
|
|
56
|
+
...allowed
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let fileReport = analyzeSVG(markup, allowed);
|
|
60
|
+
let isValid = true;
|
|
61
|
+
let log = [];
|
|
62
|
+
|
|
63
|
+
if (!fileReport.hasEls) {
|
|
64
|
+
log.push("no elements");
|
|
65
|
+
isValid = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Object.keys(fileReport).length) {
|
|
69
|
+
if (fileReport.isBillionLaugh === true) {
|
|
70
|
+
log.push(`suspicious: might contain billion laugh attack`);
|
|
71
|
+
isValid = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let key in allowed) {
|
|
75
|
+
let val = allowed[key];
|
|
76
|
+
let valRep = fileReport[key];
|
|
77
|
+
if (typeof val === "number" && valRep > val) {
|
|
78
|
+
log.push(`allowed "${key}" exceeded: ${valRep} / ${val} `);
|
|
79
|
+
isValid = false;
|
|
80
|
+
}
|
|
81
|
+
if (valRep === true && val === false) {
|
|
82
|
+
log.push(`not allowed: "${key}" `);
|
|
83
|
+
isValid = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
isValid = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
if (!isValid) {
|
|
92
|
+
log = ["SVG not valid"].concat(log);
|
|
93
|
+
|
|
94
|
+
if (Object.keys(fileReport).length) {
|
|
95
|
+
console.warn(fileReport);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
return { isValid, log, fileReport };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function analyzeSVG(markup, allowed = {}) {
|
|
104
|
+
markup = markup.trim();
|
|
105
|
+
let doc, svg;
|
|
106
|
+
let fileSizeKB = +(markup.length / 1024).toFixed(3);
|
|
107
|
+
|
|
108
|
+
let fileReport = {
|
|
109
|
+
totalEls: 1,
|
|
110
|
+
hasEls: true,
|
|
111
|
+
hasDefs: false,
|
|
112
|
+
geometryEls: [],
|
|
113
|
+
useEls: 0,
|
|
114
|
+
useElsNested: 0,
|
|
115
|
+
nonsensePaths: 0,
|
|
116
|
+
isSuspicious: false,
|
|
117
|
+
isBillionLaugh: false,
|
|
118
|
+
hasScripts: false,
|
|
119
|
+
hasPrologue: false,
|
|
120
|
+
hasEntity: false,
|
|
121
|
+
isPathData:false,
|
|
122
|
+
fileSizeKB,
|
|
123
|
+
hasXmlns: markup.includes("http://www.w3.org/2000/svg"),
|
|
124
|
+
isSymbolSprite: false,
|
|
125
|
+
isSvgFont: markup.includes("<glyph>")
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
let maxNested = allowed.useElsNested ? allowed.useElsNested : 2000;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* analyze nestes use references
|
|
132
|
+
*/
|
|
133
|
+
const countUseRefs = (useEls, maxNested = 2000) => {
|
|
134
|
+
let nestedCount = 0;
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < useEls.length && nestedCount < maxNested; i++) {
|
|
137
|
+
let use = useEls[i];
|
|
138
|
+
let refId = use.getAttribute("xlink:href")
|
|
139
|
+
? use.getAttribute("xlink:href")
|
|
140
|
+
: use.getAttribute("href");
|
|
141
|
+
refId = refId ? refId.replace("#", "") : "";
|
|
142
|
+
|
|
143
|
+
use.setAttribute("href", "#" + refId);
|
|
144
|
+
|
|
145
|
+
let refEl = svg.getElementById(refId);
|
|
146
|
+
let nestedUse = refEl.querySelectorAll("use");
|
|
147
|
+
let nestedUseLength = nestedUse.length;
|
|
148
|
+
nestedCount += nestedUseLength;
|
|
149
|
+
|
|
150
|
+
// query nested use references
|
|
151
|
+
for (let n = 0; n < nestedUse.length && nestedCount < maxNested; n++) {
|
|
152
|
+
let nested = nestedUse[n];
|
|
153
|
+
let id1 = nested.getAttribute("href").replace("#", "");
|
|
154
|
+
let refEl1 = svg.getElementById(id1);
|
|
155
|
+
let nestedUse1 = refEl1.querySelectorAll("use");
|
|
156
|
+
nestedCount += nestedUse1.length;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
fileReport.useElsNested = nestedCount;
|
|
160
|
+
return nestedCount;
|
|
161
|
+
};
|
|
162
|
+
let hasEntity = /\<\!ENTITY/gi.test(markup);
|
|
163
|
+
let hasScripts = /\<script/gi.test(markup) ? true : false;
|
|
164
|
+
let hasUse = /\<use/gi.test(markup) ? true : false;
|
|
165
|
+
let hasEls = /[\<path|\<polygon|\<polyline|\<rect|\<circle|\<ellipse|\<line|\<text|\<foreignObject]/gi.test(markup);
|
|
166
|
+
let hasDefs = /[\<filter|\<linearGradient|\<radialGradient|\<pattern|\<animate|\<animateMotion|\<animateTransform|\<clipPath|\<mask|\<symbol|\<marker]/gi.test(markup);
|
|
167
|
+
|
|
168
|
+
let isPathData = (markup.startsWith('M') || markup.startsWith('m')) && !/[\<svg|\<\/svg]/gi.test(markup);
|
|
169
|
+
fileReport.isPathData = isPathData;
|
|
170
|
+
|
|
171
|
+
// seems OK
|
|
172
|
+
if (!hasEntity && !hasUse && !hasScripts && (hasEls || hasDefs) && fileSizeKB < allowed.fileSizeKB) {
|
|
173
|
+
fileReport.hasEls = hasEls;
|
|
174
|
+
fileReport.hasDefs = hasDefs;
|
|
175
|
+
|
|
176
|
+
return fileReport
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Contains xml entity definition: highly suspicious - stop parsing!
|
|
180
|
+
if (allowed.hasEntity === false && hasEntity) {
|
|
181
|
+
fileReport.hasEntity = true;
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* sanitizing for parsing:
|
|
187
|
+
* remove xml prologue and comments
|
|
188
|
+
*/
|
|
189
|
+
markup = markup
|
|
190
|
+
.replace(/\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/g, "")
|
|
191
|
+
.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, "");
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Try to parse svg:
|
|
195
|
+
* invalid svg will return false via "catch"
|
|
196
|
+
*/
|
|
197
|
+
try {
|
|
198
|
+
|
|
199
|
+
doc = new DOMParser().parseFromString(markup, "text/html");
|
|
200
|
+
svg = doc.querySelector("svg");
|
|
201
|
+
|
|
202
|
+
// paths containing only a M command
|
|
203
|
+
let nonsensePaths = svg.querySelectorAll('path[d="M0,0"], path[d="M0 0"]').length;
|
|
204
|
+
let useEls = svg.querySelectorAll("use").length;
|
|
205
|
+
|
|
206
|
+
// create analyzing object
|
|
207
|
+
fileReport.totalEls = svg.querySelectorAll("*").length;
|
|
208
|
+
fileReport.geometryEls = svg.querySelectorAll(
|
|
209
|
+
"path, rect, circle, ellipse, polygon, polyline, line"
|
|
210
|
+
).length;
|
|
211
|
+
|
|
212
|
+
fileReport.hasScripts = hasScripts;
|
|
213
|
+
fileReport.useEls = useEls;
|
|
214
|
+
fileReport.nonsensePaths = nonsensePaths;
|
|
215
|
+
fileReport.isSuspicious = false;
|
|
216
|
+
fileReport.isBillionLaugh = false;
|
|
217
|
+
fileReport.hasXmlns = svg.getAttribute("xmlns")
|
|
218
|
+
? svg.getAttribute("xmlns") === "http://www.w3.org/2000/svg"
|
|
219
|
+
? true
|
|
220
|
+
: false
|
|
221
|
+
: false;
|
|
222
|
+
fileReport.isSymbolSprite =
|
|
223
|
+
svg.querySelectorAll("symbol").length &&
|
|
224
|
+
svg.querySelectorAll("use").length === 0
|
|
225
|
+
? true
|
|
226
|
+
: false;
|
|
227
|
+
fileReport.isSvgFont = svg.querySelectorAll("glyph").length ? true : false;
|
|
228
|
+
|
|
229
|
+
let totalEls = fileReport.totalEls;
|
|
230
|
+
let totalUseEls = fileReport.useEls;
|
|
231
|
+
let usePercentage = (100 / totalEls) * totalUseEls;
|
|
232
|
+
|
|
233
|
+
// if percentage of use elements is higher than 75% - suspicious
|
|
234
|
+
if (usePercentage > 75) {
|
|
235
|
+
fileReport.isSuspicious = true;
|
|
236
|
+
|
|
237
|
+
// check nested use references
|
|
238
|
+
let nestedCount = countUseRefs(svg.querySelectorAll("use"), maxNested);
|
|
239
|
+
if (nestedCount >= maxNested) {
|
|
240
|
+
fileReport.isBillionLaugh = true;
|
|
241
|
+
}
|
|
36
242
|
}
|
|
37
243
|
|
|
244
|
+
return fileReport;
|
|
245
|
+
} catch {
|
|
246
|
+
// svg file has malformed markup
|
|
247
|
+
console.warn("svg could not be parsed");
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
38
250
|
}
|
|
39
251
|
|
|
40
252
|
function detectInputType(input) {
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
253
|
+
let log = '';
|
|
254
|
+
let isValid = true;
|
|
255
|
+
|
|
256
|
+
let result = {
|
|
257
|
+
inputType:'',
|
|
258
|
+
isValid:true,
|
|
259
|
+
fileReport:{},
|
|
260
|
+
};
|
|
261
|
+
|
|
50
262
|
if (Array.isArray(input)) {
|
|
51
263
|
|
|
264
|
+
result.inputType = "array";
|
|
265
|
+
|
|
52
266
|
// nested array
|
|
53
267
|
if (Array.isArray(input[0])) {
|
|
54
268
|
|
|
55
269
|
if (input[0].length === 2) {
|
|
56
270
|
|
|
57
|
-
|
|
271
|
+
result.inputType = 'polyArray';
|
|
58
272
|
}
|
|
59
273
|
|
|
60
274
|
else if (Array.isArray(input[0][0]) && input[0][0].length === 2) {
|
|
61
275
|
|
|
62
|
-
|
|
276
|
+
result.inputType = 'polyComplexArray';
|
|
63
277
|
}
|
|
64
278
|
else if (input[0][0].x !== undefined && input[0][0].y !== undefined) {
|
|
65
279
|
|
|
66
|
-
|
|
280
|
+
result.inputType = 'polyComplexObjectArray';
|
|
67
281
|
}
|
|
282
|
+
|
|
68
283
|
}
|
|
69
284
|
|
|
70
285
|
// is point array
|
|
71
286
|
else if (input[0].x !== undefined && input[0].y !== undefined) {
|
|
72
287
|
|
|
73
|
-
|
|
288
|
+
result.inputType = 'polyObjectArray';
|
|
74
289
|
}
|
|
75
290
|
|
|
76
291
|
// path data array
|
|
77
292
|
else if (input[0]?.type && input[0]?.values
|
|
78
293
|
) {
|
|
79
|
-
|
|
80
|
-
|
|
294
|
+
result.inputType = "pathData";
|
|
81
295
|
}
|
|
82
296
|
|
|
83
|
-
return
|
|
297
|
+
return result;
|
|
84
298
|
}
|
|
85
299
|
|
|
86
300
|
if (typeof input === "string") {
|
|
@@ -92,36 +306,48 @@ function detectInputType(input) {
|
|
|
92
306
|
let isJson = isNumberJson(input);
|
|
93
307
|
|
|
94
308
|
if (isSVG) {
|
|
95
|
-
|
|
309
|
+
let validate = validateSVG(input);
|
|
310
|
+
({isValid, log} = validate) ;
|
|
311
|
+
if(!isValid){
|
|
312
|
+
|
|
313
|
+
result.inputType = 'invalid';
|
|
314
|
+
result.isValid=false,
|
|
315
|
+
|
|
316
|
+
result.log = log;
|
|
317
|
+
}else {
|
|
318
|
+
result.inputType = 'svgMarkup';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
result.fileReport = validate.fileReport;
|
|
322
|
+
|
|
96
323
|
}
|
|
97
324
|
|
|
98
325
|
else if (isJson) {
|
|
99
|
-
|
|
326
|
+
result.inputType = 'json';
|
|
100
327
|
}
|
|
101
328
|
|
|
102
329
|
else if (isSymbol) {
|
|
103
|
-
|
|
330
|
+
result.inputType = 'symbol';
|
|
104
331
|
}
|
|
105
332
|
else if (isPathData) {
|
|
106
|
-
|
|
333
|
+
result.inputType = 'pathDataString';
|
|
107
334
|
}
|
|
108
335
|
else if (isPolyString) {
|
|
109
|
-
|
|
336
|
+
result.inputType = 'polyString';
|
|
110
337
|
}
|
|
111
338
|
|
|
112
339
|
else {
|
|
113
340
|
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
114
341
|
let dataUrl = input.startsWith('data:image');
|
|
115
|
-
|
|
342
|
+
result.inputType = url || dataUrl ? "url" : "string";
|
|
116
343
|
}
|
|
117
344
|
|
|
118
|
-
return
|
|
345
|
+
return result
|
|
119
346
|
}
|
|
120
347
|
|
|
121
|
-
|
|
122
|
-
let constructor = input.constructor.name;
|
|
348
|
+
result.inputType = (input.constructor.name || typeof input ).toLowerCase();
|
|
123
349
|
|
|
124
|
-
return
|
|
350
|
+
return result;
|
|
125
351
|
}
|
|
126
352
|
|
|
127
353
|
function isNumberJson(str) {
|
|
@@ -139,22 +365,6 @@ function isNumberJson(str) {
|
|
|
139
365
|
|
|
140
366
|
}
|
|
141
367
|
|
|
142
|
-
const {
|
|
143
|
-
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,
|
|
144
|
-
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
|
|
145
|
-
} = Math;
|
|
146
|
-
|
|
147
|
-
const rad2Deg = 180/Math.PI;
|
|
148
|
-
const deg2rad = Math.PI/180;
|
|
149
|
-
const root2 = 1.4142135623730951;
|
|
150
|
-
const svgNs = 'http://www.w3.org/2000/svg';
|
|
151
|
-
|
|
152
|
-
// 1/2.54
|
|
153
|
-
const inch2cm = 0.39370078;
|
|
154
|
-
|
|
155
|
-
// 1/72
|
|
156
|
-
const inch2pt = 0.01388889;
|
|
157
|
-
|
|
158
368
|
/*
|
|
159
369
|
import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
|
|
160
370
|
log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
|
|
@@ -409,6 +619,7 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
409
619
|
let t1 = 1 - t;
|
|
410
620
|
|
|
411
621
|
// cubic beziers
|
|
622
|
+
/*
|
|
412
623
|
if (isCubic) {
|
|
413
624
|
pt = {
|
|
414
625
|
x:
|
|
@@ -424,11 +635,29 @@ function pointAtT(pts, t = 0.5, getTangent = false, getCpts = false, returnArray
|
|
|
424
635
|
};
|
|
425
636
|
|
|
426
637
|
}
|
|
638
|
+
*/
|
|
639
|
+
|
|
640
|
+
if (isCubic) {
|
|
641
|
+
pt = {
|
|
642
|
+
x:
|
|
643
|
+
t1 * t1 * t1 * p0.x +
|
|
644
|
+
3 * t1 * t1 * t * cp1.x +
|
|
645
|
+
3 * t1 * t * t * cp2.x +
|
|
646
|
+
t * t * t * p.x,
|
|
647
|
+
y:
|
|
648
|
+
t1 * t1 * t1 * p0.y +
|
|
649
|
+
3 * t1 * t1 * t * cp1.y +
|
|
650
|
+
3 * t1 * t * t * cp2.y +
|
|
651
|
+
t * t * t * p.y,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
}
|
|
655
|
+
|
|
427
656
|
// quadratic beziers
|
|
428
657
|
else {
|
|
429
658
|
pt = {
|
|
430
|
-
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t
|
|
431
|
-
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t
|
|
659
|
+
x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t * t * p.x,
|
|
660
|
+
y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t * t * p.y,
|
|
432
661
|
};
|
|
433
662
|
}
|
|
434
663
|
|
|
@@ -744,12 +973,6 @@ function getBezierExtremeT(pts, { addExtremes = true, addSemiExtremes = false }
|
|
|
744
973
|
return tArr;
|
|
745
974
|
}
|
|
746
975
|
|
|
747
|
-
/**
|
|
748
|
-
* based on Nikos M.'s answer
|
|
749
|
-
* how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
|
|
750
|
-
* https://stackoverflow.com/questions/87734/#75031511
|
|
751
|
-
* See also: https://github.com/foo123/Geometrize
|
|
752
|
-
*/
|
|
753
976
|
function getArcExtemes(p0, values) {
|
|
754
977
|
// compute point on ellipse from angle around ellipse (theta)
|
|
755
978
|
const arc = (theta, cx, cy, rx, ry, alpha) => {
|
|
@@ -1618,6 +1841,36 @@ function rgba2Hex({ r = 0, g = 0, b = 0, a = 255, values = [] }) {
|
|
|
1618
1841
|
return `#${rHex}${gHex}${bHex}${aHex}`;
|
|
1619
1842
|
}
|
|
1620
1843
|
|
|
1844
|
+
/**
|
|
1845
|
+
* round path data
|
|
1846
|
+
* either by explicit decimal value or
|
|
1847
|
+
* based on suggested accuracy in path data
|
|
1848
|
+
*/
|
|
1849
|
+
function roundPathData(pathData, decimalsGlobal = -1) {
|
|
1850
|
+
|
|
1851
|
+
if (decimalsGlobal < 0) return pathData;
|
|
1852
|
+
|
|
1853
|
+
let len = pathData.length;
|
|
1854
|
+
let decimals = decimalsGlobal;
|
|
1855
|
+
let decimalsArc = decimals < 3 ? decimals+2 : decimals;
|
|
1856
|
+
|
|
1857
|
+
for (let c = 0; c < len; c++) {
|
|
1858
|
+
let com = pathData[c];
|
|
1859
|
+
let { type, values } = com;
|
|
1860
|
+
let valLen = values.length;
|
|
1861
|
+
if (!valLen) continue
|
|
1862
|
+
|
|
1863
|
+
let isArc = type.toLowerCase() === 'a';
|
|
1864
|
+
|
|
1865
|
+
for (let v = 0; v < valLen; v++) {
|
|
1866
|
+
// allow higher accuracy for arc radii (... it's always arcs)
|
|
1867
|
+
pathData[c].values[v] = isArc && v < 2 ? roundTo(values[v], decimalsArc) : roundTo(values[v], decimals);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
return pathData;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1621
1874
|
function detectAccuracyPoly(pts) {
|
|
1622
1875
|
let dims = [];
|
|
1623
1876
|
|
|
@@ -1627,7 +1880,7 @@ function detectAccuracyPoly(pts) {
|
|
|
1627
1880
|
let { p0 = null, p = null, dimA = 0 } = pt;
|
|
1628
1881
|
|
|
1629
1882
|
// use existing averave dimension value or calculate
|
|
1630
|
-
if (
|
|
1883
|
+
if (p && p0) {
|
|
1631
1884
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1632
1885
|
|
|
1633
1886
|
if (dimA) dims.push(dimA);
|
|
@@ -1679,9 +1932,27 @@ function detectAccuracy(pathData) {
|
|
|
1679
1932
|
|
|
1680
1933
|
}
|
|
1681
1934
|
|
|
1935
|
+
/**
|
|
1936
|
+
* rounding helper
|
|
1937
|
+
* allows for quantized rounding
|
|
1938
|
+
* e.g 0.5 decimals s
|
|
1939
|
+
*/
|
|
1682
1940
|
function roundTo(num = 0, decimals = 3) {
|
|
1683
|
-
if(decimals
|
|
1941
|
+
if (decimals < 0) return num;
|
|
1942
|
+
// Normal integer rounding
|
|
1684
1943
|
if (!decimals) return Math.round(num);
|
|
1944
|
+
|
|
1945
|
+
// stepped rounding
|
|
1946
|
+
let intPart = Math.floor(decimals);
|
|
1947
|
+
|
|
1948
|
+
if (intPart !== decimals) {
|
|
1949
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1950
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1951
|
+
|
|
1952
|
+
let step = 10 ** -intPart * f;
|
|
1953
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1685
1956
|
let factor = 10 ** decimals;
|
|
1686
1957
|
return Math.round(num * factor) / factor;
|
|
1687
1958
|
}
|
|
@@ -1691,50 +1962,21 @@ function roundTo(num = 0, decimals = 3) {
|
|
|
1691
1962
|
* floating point accuracy
|
|
1692
1963
|
* based on numeric value
|
|
1693
1964
|
*/
|
|
1694
|
-
function autoRound(val, integerThresh = 50){
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if(val>integerThresh*2){
|
|
1698
|
-
decimals=0;
|
|
1699
|
-
}
|
|
1700
|
-
else if(val>integerThresh){
|
|
1701
|
-
decimals=1;
|
|
1702
|
-
}else {
|
|
1703
|
-
decimals=Math.ceil(500/val).toString().length;
|
|
1704
|
-
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
let factor = 10 ** decimals;
|
|
1709
|
-
return Math.round(val * factor) / factor;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
/**
|
|
1713
|
-
* round path data
|
|
1714
|
-
* either by explicit decimal value or
|
|
1715
|
-
* based on suggested accuracy in path data
|
|
1716
|
-
*/
|
|
1717
|
-
function roundPathData(pathData, decimalsGlobal = -1) {
|
|
1718
|
-
|
|
1719
|
-
if (decimalsGlobal < 0) return pathData;
|
|
1720
|
-
|
|
1721
|
-
let len = pathData.length;
|
|
1722
|
-
|
|
1723
|
-
let decimals = decimalsGlobal;
|
|
1724
|
-
|
|
1725
|
-
for (let c = 0; c < len; c++) {
|
|
1726
|
-
let com = pathData[c];
|
|
1727
|
-
let {values} = com;
|
|
1965
|
+
function autoRound(val, integerThresh = 50) {
|
|
1966
|
+
let decimals = 8;
|
|
1728
1967
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1968
|
+
if (val > integerThresh * 2) {
|
|
1969
|
+
decimals = 0;
|
|
1970
|
+
}
|
|
1971
|
+
else if (val > integerThresh) {
|
|
1972
|
+
decimals = 1;
|
|
1973
|
+
} else {
|
|
1974
|
+
decimals = Math.ceil(500 / val).toString().length;
|
|
1731
1975
|
|
|
1732
|
-
for (let v = 0; v < valLen; v++) {
|
|
1733
|
-
pathData[c].values[v] = roundTo(values[v], decimals);
|
|
1734
|
-
}
|
|
1735
1976
|
}
|
|
1736
1977
|
|
|
1737
|
-
|
|
1978
|
+
let factor = 10 ** decimals;
|
|
1979
|
+
return Math.round(val * factor) / factor;
|
|
1738
1980
|
}
|
|
1739
1981
|
|
|
1740
1982
|
/**
|
|
@@ -2176,7 +2418,7 @@ function normalizeUnits(value = null, {
|
|
|
2176
2418
|
scale = height / 100;
|
|
2177
2419
|
}
|
|
2178
2420
|
else {
|
|
2179
|
-
scale = normalizedDiagonal ? scaleRoot / 100 :
|
|
2421
|
+
scale = normalizedDiagonal ? scaleRoot / 100 : width / 100;
|
|
2180
2422
|
}
|
|
2181
2423
|
break;
|
|
2182
2424
|
|
|
@@ -2245,11 +2487,13 @@ function isNumericValue(val = '') {
|
|
|
2245
2487
|
}
|
|
2246
2488
|
|
|
2247
2489
|
function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
|
|
2248
|
-
|
|
2490
|
+
|
|
2491
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
2249
2492
|
|
|
2250
2493
|
let atts={};
|
|
2251
2494
|
attributes.forEach(att=>{
|
|
2252
|
-
|
|
2495
|
+
|
|
2496
|
+
let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
|
|
2253
2497
|
atts[att.name] = value;
|
|
2254
2498
|
});
|
|
2255
2499
|
|
|
@@ -3074,9 +3318,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
3074
3318
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
3075
3319
|
|
|
3076
3320
|
// test new point-at-t against original mid segment starting point
|
|
3077
|
-
let
|
|
3321
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
3078
3322
|
|
|
3079
|
-
let dist0 = getDistManhattan(com1.p,
|
|
3323
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
3080
3324
|
let dist1 = 0, dist2 = 0;
|
|
3081
3325
|
let close = dist0 < maxDist;
|
|
3082
3326
|
let success = false;
|
|
@@ -3091,29 +3335,40 @@ function combineCubicPairs(com1, com2, {
|
|
|
3091
3335
|
* to prevent distortions
|
|
3092
3336
|
*/
|
|
3093
3337
|
|
|
3094
|
-
//
|
|
3095
|
-
let
|
|
3338
|
+
// 1st segment mid
|
|
3339
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
3096
3340
|
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
let
|
|
3100
|
-
dist1 = getDistManhattan(
|
|
3341
|
+
let t2 = t * 0.5;
|
|
3342
|
+
// combined interpolated mid point
|
|
3343
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
3344
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
3101
3345
|
|
|
3102
3346
|
error += dist1;
|
|
3103
3347
|
|
|
3104
3348
|
if (dist1 < maxDist) {
|
|
3105
3349
|
|
|
3106
|
-
//
|
|
3107
|
-
let
|
|
3350
|
+
// 2nd segment mid
|
|
3351
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
3108
3352
|
|
|
3109
|
-
|
|
3110
|
-
let
|
|
3111
|
-
|
|
3353
|
+
// simplified path
|
|
3354
|
+
let t3 = (1 + t) * 0.5;
|
|
3355
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
3356
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
3112
3357
|
|
|
3113
3358
|
error += dist2;
|
|
3114
3359
|
|
|
3115
3360
|
if (error < maxDist) success = true;
|
|
3116
3361
|
|
|
3362
|
+
/*
|
|
3363
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
3364
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
3365
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
3366
|
+
|
|
3367
|
+
renderPoint(markers, com1.p, 'green')
|
|
3368
|
+
|
|
3369
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
3370
|
+
*/
|
|
3371
|
+
|
|
3117
3372
|
}
|
|
3118
3373
|
|
|
3119
3374
|
} // end 1st try
|
|
@@ -3278,6 +3533,7 @@ function analyzePathData(pathData = [], {
|
|
|
3278
3533
|
let com = pathData[c - 1];
|
|
3279
3534
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
3280
3535
|
|
|
3536
|
+
let comPrev = pathData[c-2];
|
|
3281
3537
|
let comN = pathData[c] || null;
|
|
3282
3538
|
|
|
3283
3539
|
// init properties
|
|
@@ -3296,6 +3552,7 @@ function analyzePathData(pathData = [], {
|
|
|
3296
3552
|
|
|
3297
3553
|
// bezier types
|
|
3298
3554
|
let isBezier = type === 'Q' || type === 'C';
|
|
3555
|
+
let isArc = type === 'A';
|
|
3299
3556
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
3300
3557
|
|
|
3301
3558
|
/**
|
|
@@ -3342,6 +3599,22 @@ function analyzePathData(pathData = [], {
|
|
|
3342
3599
|
}
|
|
3343
3600
|
}
|
|
3344
3601
|
|
|
3602
|
+
// check extremes introduce by small arcs
|
|
3603
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3604
|
+
let distN = comN ? comN.dimA : 0;
|
|
3605
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
3606
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
3607
|
+
|
|
3608
|
+
if(isShort && smallRadius){
|
|
3609
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
3610
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
3611
|
+
hasExtremes = true;
|
|
3612
|
+
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3345
3618
|
if (hasExtremes) com.extreme = true;
|
|
3346
3619
|
|
|
3347
3620
|
// Corners and semi extremes
|
|
@@ -3899,50 +4172,10 @@ function stringifyPathData(pathData) {
|
|
|
3899
4172
|
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3900
4173
|
}
|
|
3901
4174
|
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
toLonghands = true,
|
|
3907
|
-
|
|
3908
|
-
// not necessary unless you need cubics only
|
|
3909
|
-
quadraticToCubic = false,
|
|
3910
|
-
|
|
3911
|
-
// mostly a fallback if arc calculations fail
|
|
3912
|
-
arcToCubic = false,
|
|
3913
|
-
// arc to cubic precision - adds more segments for better precision
|
|
3914
|
-
arcAccuracy = 4,
|
|
3915
|
-
} = {}
|
|
3916
|
-
) {
|
|
3917
|
-
|
|
3918
|
-
// is already array
|
|
3919
|
-
let isArray = Array.isArray(d);
|
|
3920
|
-
|
|
3921
|
-
// normalize native pathData to regular array
|
|
3922
|
-
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
3923
|
-
/*
|
|
3924
|
-
if (hasConstructor) {
|
|
3925
|
-
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
3926
|
-
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
3927
|
-
}
|
|
3928
|
-
*/
|
|
3929
|
-
|
|
3930
|
-
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
3931
|
-
|
|
3932
|
-
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
3933
|
-
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
3934
|
-
|
|
3935
|
-
// normalize
|
|
3936
|
-
pathData = normalizePathData(pathData,
|
|
3937
|
-
{
|
|
3938
|
-
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
3939
|
-
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
3940
|
-
},
|
|
3941
|
-
);
|
|
3942
|
-
|
|
3943
|
-
return pathData;
|
|
3944
|
-
}
|
|
3945
|
-
|
|
4175
|
+
/**
|
|
4176
|
+
* wrapper function for
|
|
4177
|
+
* all path data conversion
|
|
4178
|
+
*/
|
|
3946
4179
|
function convertPathData(pathData, {
|
|
3947
4180
|
toShorthands = true,
|
|
3948
4181
|
toLonghands = false,
|
|
@@ -3994,22 +4227,24 @@ function convertPathData(pathData, {
|
|
|
3994
4227
|
|
|
3995
4228
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3996
4229
|
|
|
3997
|
-
if(toMixed) toRelative = true;
|
|
4230
|
+
if (toMixed) toRelative = true;
|
|
3998
4231
|
|
|
3999
4232
|
// pre round - before relative conversion to minimize distortions
|
|
4000
4233
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
4001
4234
|
|
|
4002
4235
|
// clone absolute pathdata
|
|
4003
|
-
if(toMixed){
|
|
4236
|
+
if (toMixed) {
|
|
4004
4237
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
4005
4238
|
}
|
|
4006
4239
|
|
|
4007
4240
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
4241
|
+
|
|
4242
|
+
// final rounding
|
|
4008
4243
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
4009
4244
|
|
|
4010
4245
|
// choose most compact commands: relative or absolute
|
|
4011
|
-
if(toMixed){
|
|
4012
|
-
for(let i=0; i<pathData.length; i++){
|
|
4246
|
+
if (toMixed) {
|
|
4247
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
4013
4248
|
let com = pathData[i];
|
|
4014
4249
|
let comA = pathDataAbs[i];
|
|
4015
4250
|
// compare Lengths
|
|
@@ -4019,7 +4254,7 @@ function convertPathData(pathData, {
|
|
|
4019
4254
|
let lenR = comStr.length;
|
|
4020
4255
|
let lenA = comStrA.length;
|
|
4021
4256
|
|
|
4022
|
-
if(lenA<lenR){
|
|
4257
|
+
if (lenA < lenR) {
|
|
4023
4258
|
|
|
4024
4259
|
pathData[i] = pathDataAbs[i];
|
|
4025
4260
|
}
|
|
@@ -4029,56 +4264,140 @@ function convertPathData(pathData, {
|
|
|
4029
4264
|
return pathData
|
|
4030
4265
|
}
|
|
4031
4266
|
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
function optimizeArcPathData(pathData = []) {
|
|
4267
|
+
function parsePathDataNormalized(d,
|
|
4268
|
+
{
|
|
4269
|
+
// necessary for most calculations
|
|
4270
|
+
toAbsolute = true,
|
|
4271
|
+
toLonghands = true,
|
|
4039
4272
|
|
|
4040
|
-
|
|
4273
|
+
// not necessary unless you need cubics only
|
|
4274
|
+
quadraticToCubic = false,
|
|
4041
4275
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
let M = { x: x0, y: y0 };
|
|
4049
|
-
let p = { x, y };
|
|
4276
|
+
// mostly a fallback if arc calculations fail
|
|
4277
|
+
arcToCubic = false,
|
|
4278
|
+
// arc to cubic precision - adds more segments for better precision
|
|
4279
|
+
arcAccuracy = 4,
|
|
4280
|
+
} = {}
|
|
4281
|
+
) {
|
|
4050
4282
|
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
remove.push(i);
|
|
4283
|
+
// is already array
|
|
4284
|
+
let isArray = Array.isArray(d);
|
|
4054
4285
|
|
|
4055
|
-
|
|
4286
|
+
// normalize native pathData to regular array
|
|
4287
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
4288
|
+
/*
|
|
4289
|
+
if (hasConstructor) {
|
|
4290
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
4291
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
4292
|
+
}
|
|
4293
|
+
*/
|
|
4056
4294
|
|
|
4057
|
-
|
|
4058
|
-
if (rx >= 1 && (x === x0 || y === y0)) {
|
|
4059
|
-
let diff = Math.abs(rx - ry) / rx;
|
|
4295
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
4060
4296
|
|
|
4061
|
-
|
|
4062
|
-
|
|
4297
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
4298
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
4063
4299
|
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4300
|
+
// normalize
|
|
4301
|
+
pathData = normalizePathData(pathData,
|
|
4302
|
+
{
|
|
4303
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
4304
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
4305
|
+
},
|
|
4306
|
+
);
|
|
4068
4307
|
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4308
|
+
return pathData;
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
/**
|
|
4312
|
+
*
|
|
4313
|
+
* @param {*} pathData
|
|
4314
|
+
* @returns
|
|
4315
|
+
*/
|
|
4316
|
+
|
|
4317
|
+
function optimizeArcPathData(pathData = []) {
|
|
4318
|
+
let l = pathData.length;
|
|
4319
|
+
let pathDataN = [];
|
|
4320
|
+
|
|
4321
|
+
for (let i = 0; i < l; i++) {
|
|
4322
|
+
let com = pathData[i];
|
|
4323
|
+
let { type, values } = com;
|
|
4324
|
+
|
|
4325
|
+
if (type !== 'A') {
|
|
4326
|
+
pathDataN.push(com);
|
|
4327
|
+
continue
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4331
|
+
let comPrev = pathData[i - 1];
|
|
4332
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4333
|
+
let M = { x: x0, y: y0 };
|
|
4334
|
+
let p = { x, y };
|
|
4335
|
+
|
|
4336
|
+
if (rx === 0 || ry === 0) {
|
|
4337
|
+
pathData[i] = null;
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
// test for elliptic
|
|
4341
|
+
let rat = rx / ry;
|
|
4342
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
4343
|
+
|
|
4344
|
+
if (error > 0.01) {
|
|
4345
|
+
|
|
4346
|
+
pathDataN.push(com);
|
|
4347
|
+
continue
|
|
4348
|
+
|
|
4349
|
+
}
|
|
4350
|
+
|
|
4351
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
4352
|
+
com.values[2] = 0;
|
|
4353
|
+
|
|
4354
|
+
/**
|
|
4355
|
+
* test semi circles
|
|
4356
|
+
* rx and ry are large enough
|
|
4357
|
+
*/
|
|
4358
|
+
|
|
4359
|
+
// 1. horizontal or vertical
|
|
4360
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
4361
|
+
let diffX = Math.abs(x - x0);
|
|
4362
|
+
let diffY = Math.abs(y - y0);
|
|
4363
|
+
|
|
4364
|
+
let isHorizontal = diffY < thresh;
|
|
4365
|
+
let isVertical = diffX < thresh;
|
|
4366
|
+
|
|
4367
|
+
// minify rx and ry
|
|
4368
|
+
if (isHorizontal || isVertical) {
|
|
4369
|
+
|
|
4370
|
+
// check if semi circle
|
|
4371
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
4372
|
+
|
|
4373
|
+
// is semicircle we can simplify rx
|
|
4374
|
+
if (!needsTrueR) {
|
|
4375
|
+
|
|
4376
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
4076
4377
|
}
|
|
4378
|
+
|
|
4379
|
+
com.values[0] = rx;
|
|
4380
|
+
com.values[1] = rx;
|
|
4381
|
+
pathDataN.push(com);
|
|
4382
|
+
continue
|
|
4383
|
+
|
|
4077
4384
|
}
|
|
4078
|
-
});
|
|
4079
4385
|
|
|
4080
|
-
|
|
4081
|
-
|
|
4386
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
4387
|
+
let r = getDistance(M, p) * 0.5;
|
|
4388
|
+
error = rx / r;
|
|
4389
|
+
|
|
4390
|
+
if (error < 0.5) {
|
|
4391
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
com.values[0] = rx;
|
|
4395
|
+
com.values[1] = rx;
|
|
4396
|
+
pathDataN.push(com);
|
|
4397
|
+
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
return pathDataN;
|
|
4082
4401
|
}
|
|
4083
4402
|
|
|
4084
4403
|
/**
|
|
@@ -4143,6 +4462,44 @@ export function normalizePathData(pathData = [],
|
|
|
4143
4462
|
}
|
|
4144
4463
|
*/
|
|
4145
4464
|
|
|
4465
|
+
function convertSmallArcsToLinetos(pathData) {
|
|
4466
|
+
|
|
4467
|
+
let l = pathData.length;
|
|
4468
|
+
|
|
4469
|
+
// add fist command
|
|
4470
|
+
let pathDataN = [pathData[0]];
|
|
4471
|
+
|
|
4472
|
+
for (let i = 1; i < l; i++) {
|
|
4473
|
+
let com = pathData[i];
|
|
4474
|
+
let comPrev = pathData[i - 1];
|
|
4475
|
+
let comN = pathData[i + 1] || null;
|
|
4476
|
+
|
|
4477
|
+
if (!comN) {
|
|
4478
|
+
pathDataN.push(com);
|
|
4479
|
+
break
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
let { type, values, extreme = false, p0, p, dimA = 0 } = com;
|
|
4483
|
+
// for short segment detection
|
|
4484
|
+
let dimAN = comN.dimA;
|
|
4485
|
+
let dimA0 = comPrev.dimA + dimA + dimAN;
|
|
4486
|
+
let thresh = 0.05;
|
|
4487
|
+
let isShort = dimA < dimA0 * thresh;
|
|
4488
|
+
|
|
4489
|
+
if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
|
|
4490
|
+
|
|
4491
|
+
com.type = 'L';
|
|
4492
|
+
com.values = [p.x, p.y];
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
pathDataN.push(com);
|
|
4496
|
+
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4499
|
+
return pathDataN;
|
|
4500
|
+
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4146
4503
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
4147
4504
|
|
|
4148
4505
|
// test if cubic can be simplified to quadratic
|
|
@@ -6198,7 +6555,7 @@ function pathDataToTopLeft(pathData) {
|
|
|
6198
6555
|
let { type, values } = com;
|
|
6199
6556
|
let valsLen = values.length;
|
|
6200
6557
|
if (valsLen) {
|
|
6201
|
-
let p = { type: type, x: values[valsLen-2], y: values[valsLen-1], index: 0};
|
|
6558
|
+
let p = { type: type, x: values[valsLen - 2], y: values[valsLen - 1], index: 0 };
|
|
6202
6559
|
p.index = i;
|
|
6203
6560
|
indices.push(p);
|
|
6204
6561
|
}
|
|
@@ -6206,113 +6563,111 @@ function pathDataToTopLeft(pathData) {
|
|
|
6206
6563
|
|
|
6207
6564
|
// reorder to top left most
|
|
6208
6565
|
|
|
6209
|
-
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x
|
|
6566
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
6210
6567
|
newIndex = indices[0].index;
|
|
6211
6568
|
|
|
6212
|
-
return
|
|
6569
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
6213
6570
|
}
|
|
6214
6571
|
|
|
6215
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
6572
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
6216
6573
|
|
|
6217
|
-
let
|
|
6574
|
+
let pathDataN = pathData;
|
|
6218
6575
|
let l = pathData.length;
|
|
6219
6576
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
6220
6577
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
6221
6578
|
|
|
6222
|
-
let
|
|
6223
|
-
|
|
6224
|
-
// check if order is ideal
|
|
6225
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
6579
|
+
let hasLinetos = false;
|
|
6226
6580
|
|
|
6581
|
+
// check if path is closed by explicit lineto
|
|
6582
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6227
6583
|
let penultimateCom = pathData[idxPenultimate];
|
|
6228
6584
|
let penultimateType = penultimateCom.type;
|
|
6229
6585
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6230
6586
|
|
|
6231
6587
|
// last L command ends at M
|
|
6232
|
-
let
|
|
6233
|
-
|
|
6234
|
-
// add closepath Z to enable order optimizations
|
|
6235
|
-
if(!isClosed && autoClose && isClosingCommand){
|
|
6588
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6589
|
+
let lastIsLine = penultimateType === 'L';
|
|
6236
6590
|
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
let
|
|
6241
|
-
|
|
6242
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
6243
|
-
*/
|
|
6244
|
-
|
|
6245
|
-
pathData.push({type:'Z', values:[]});
|
|
6246
|
-
isClosed = true;
|
|
6247
|
-
l++;
|
|
6248
|
-
}
|
|
6591
|
+
// create index
|
|
6592
|
+
let indices = [];
|
|
6593
|
+
for (let i = 0; i < l; i++) {
|
|
6594
|
+
let com = pathData[i];
|
|
6595
|
+
let { type, values, p0, p } = com;
|
|
6249
6596
|
|
|
6250
|
-
|
|
6251
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
6252
|
-
skipReorder = false;
|
|
6597
|
+
if(type==='L') hasLinetos = true;
|
|
6253
6598
|
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
}
|
|
6599
|
+
// exclude Z
|
|
6600
|
+
if (values.length) {
|
|
6601
|
+
values.slice(-2);
|
|
6258
6602
|
|
|
6259
|
-
|
|
6603
|
+
let x = Math.min(p0.x, p.x);
|
|
6604
|
+
let y = Math.min(p0.y, p.y);
|
|
6260
6605
|
|
|
6261
|
-
|
|
6606
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
6607
|
+
let prevType = prevCom.type;
|
|
6262
6608
|
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
let { type, values } = com;
|
|
6267
|
-
if (values.length) {
|
|
6268
|
-
let valsL = values.slice(-2);
|
|
6269
|
-
let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
|
|
6270
|
-
let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
|
|
6271
|
-
let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
|
|
6272
|
-
let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
|
|
6273
|
-
let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom };
|
|
6274
|
-
p.index = i;
|
|
6275
|
-
indices.push(p);
|
|
6276
|
-
}
|
|
6609
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
6610
|
+
item.index = i;
|
|
6611
|
+
indices.push(item);
|
|
6277
6612
|
}
|
|
6278
6613
|
|
|
6279
|
-
|
|
6614
|
+
}
|
|
6280
6615
|
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6616
|
+
let xMin = Infinity;
|
|
6617
|
+
let yMin = Infinity;
|
|
6618
|
+
let idx_top = null;
|
|
6619
|
+
let len = indices.length;
|
|
6284
6620
|
|
|
6285
|
-
|
|
6621
|
+
for (let i = 0; i < len; i++) {
|
|
6622
|
+
let com = indices[i];
|
|
6623
|
+
let { type, index, x, y, prevType } = com;
|
|
6286
6624
|
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
newIndex = indices[0].index;
|
|
6292
|
-
}
|
|
6625
|
+
if (hasLinetos && prevType === 'L') {
|
|
6626
|
+
if (x < xMin && y < yMin) {
|
|
6627
|
+
idx_top = index-1;
|
|
6628
|
+
}
|
|
6293
6629
|
|
|
6294
|
-
|
|
6295
|
-
|
|
6630
|
+
if (y < yMin) {
|
|
6631
|
+
yMin = y;
|
|
6632
|
+
}
|
|
6633
|
+
|
|
6634
|
+
if (x < xMin) {
|
|
6635
|
+
xMin = x;
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6296
6638
|
}
|
|
6297
6639
|
|
|
6298
|
-
|
|
6640
|
+
// shift to better starting point
|
|
6641
|
+
if (idx_top) {
|
|
6642
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
6299
6643
|
|
|
6300
|
-
|
|
6644
|
+
// update penultimate - reorder might have added new close paths
|
|
6645
|
+
l = pathDataN.length;
|
|
6646
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
6647
|
+
|
|
6648
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6649
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
6650
|
+
penultimateType = penultimateCom.type;
|
|
6651
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6652
|
+
lastIsLine = penultimateType ==='L';
|
|
6301
6653
|
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
penultimateType = penultimateCom.type;
|
|
6305
|
-
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
6654
|
+
// last L command ends at M
|
|
6655
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6306
6656
|
|
|
6307
|
-
|
|
6657
|
+
}
|
|
6308
6658
|
|
|
6309
|
-
|
|
6310
|
-
|
|
6659
|
+
// remove unnecessary closing lineto
|
|
6660
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
6661
|
+
pathDataN.splice(l - 2, 1);
|
|
6311
6662
|
}
|
|
6312
6663
|
|
|
6313
|
-
|
|
6664
|
+
// add close path
|
|
6665
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
6666
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
return pathDataN
|
|
6314
6670
|
|
|
6315
|
-
return pathDataNew
|
|
6316
6671
|
}
|
|
6317
6672
|
|
|
6318
6673
|
/**
|
|
@@ -6825,7 +7180,7 @@ function getElBBox(el){
|
|
|
6825
7180
|
|
|
6826
7181
|
switch(type){
|
|
6827
7182
|
case 'path':
|
|
6828
|
-
let pathData = parsePathDataNormalized(
|
|
7183
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
6829
7184
|
bb=getPolyBBox(getPathDataPoly(pathData));
|
|
6830
7185
|
|
|
6831
7186
|
break;
|
|
@@ -6867,8 +7222,8 @@ function parseStylesProperties(el, {
|
|
|
6867
7222
|
autoRoundValues = false,
|
|
6868
7223
|
minifyRgbColors = false,
|
|
6869
7224
|
removeInvalid = true,
|
|
6870
|
-
allowDataAtts=true,
|
|
6871
|
-
allowAriaAtts=true,
|
|
7225
|
+
allowDataAtts = true,
|
|
7226
|
+
allowAriaAtts = true,
|
|
6872
7227
|
removeDefaults = true,
|
|
6873
7228
|
cleanUpStrokes = true,
|
|
6874
7229
|
normalizeTransforms = true,
|
|
@@ -6934,7 +7289,7 @@ function parseStylesProperties(el, {
|
|
|
6934
7289
|
*/
|
|
6935
7290
|
|
|
6936
7291
|
if (removeInvalid || removeDefaults || removeNameSpaced) {
|
|
6937
|
-
let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
7292
|
+
let propsFilteredObj = filterSvgElProps(nodeName, props, { allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
6938
7293
|
props = propsFilteredObj.propsFiltered;
|
|
6939
7294
|
remove.push(...propsFilteredObj.remove);
|
|
6940
7295
|
|
|
@@ -7027,10 +7382,10 @@ function parseStylesProperties(el, {
|
|
|
7027
7382
|
|
|
7028
7383
|
if (prop !== 'transforms') {
|
|
7029
7384
|
|
|
7030
|
-
if (
|
|
7385
|
+
if ((prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
|
|
7031
7386
|
normalizedDiagonal = true;
|
|
7032
7387
|
for (let i = 0; i < values.length; i++) {
|
|
7033
|
-
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize });
|
|
7388
|
+
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize, autoRoundValues });
|
|
7034
7389
|
valsNew.push(val);
|
|
7035
7390
|
}
|
|
7036
7391
|
}
|
|
@@ -7072,14 +7427,12 @@ function parseStylesProperties(el, {
|
|
|
7072
7427
|
if (prop === 'scale' && unit === '%') {
|
|
7073
7428
|
valAbs = valAbs * 0.01;
|
|
7074
7429
|
} else {
|
|
7075
|
-
if (prop === 'r')
|
|
7430
|
+
if (prop === 'r' && width!==height) normalizedDiagonal = true;
|
|
7076
7431
|
valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, normalizedDiagonal, fontSize });
|
|
7077
7432
|
|
|
7078
7433
|
if (autoRoundValues && isNumeric) {
|
|
7079
7434
|
valAbs = autoRound(valAbs);
|
|
7080
|
-
|
|
7081
7435
|
}
|
|
7082
|
-
|
|
7083
7436
|
}
|
|
7084
7437
|
}
|
|
7085
7438
|
valsNew.push(valAbs);
|
|
@@ -7280,6 +7633,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7280
7633
|
removeIds = false,
|
|
7281
7634
|
removeClassNames = false,
|
|
7282
7635
|
exclude = [],
|
|
7636
|
+
inheritedProps = null,
|
|
7283
7637
|
} = {}) {
|
|
7284
7638
|
let propsFiltered = {};
|
|
7285
7639
|
let remove = [];
|
|
@@ -7308,7 +7662,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7308
7662
|
let isMeta = prop === 'title';
|
|
7309
7663
|
let isAria = prop.startsWith('aria-');
|
|
7310
7664
|
|
|
7311
|
-
if
|
|
7665
|
+
if ((allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta)) continue
|
|
7312
7666
|
|
|
7313
7667
|
// filter out defaults
|
|
7314
7668
|
let isDefault = removeDefaults ?
|
|
@@ -7319,6 +7673,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7319
7673
|
|
|
7320
7674
|
if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
|
|
7321
7675
|
if (include.includes(prop)) isValid = true;
|
|
7676
|
+
if (exclude.includes(prop)) isValid = false;
|
|
7322
7677
|
|
|
7323
7678
|
if (isValid) {
|
|
7324
7679
|
propsFiltered[prop] = props[prop];
|
|
@@ -7551,6 +7906,358 @@ function formatXMLNode(node, level, indentSize) {
|
|
|
7551
7906
|
return "";
|
|
7552
7907
|
}
|
|
7553
7908
|
|
|
7909
|
+
// Legendre Gauss weight and abscissa values
|
|
7910
|
+
const waArr_global = [];
|
|
7911
|
+
|
|
7912
|
+
function getLength(pts, {
|
|
7913
|
+
t = 1,
|
|
7914
|
+
waArr = []
|
|
7915
|
+
} = {}) {
|
|
7916
|
+
|
|
7917
|
+
const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
|
|
7918
|
+
if (t === 0) {
|
|
7919
|
+
return 0;
|
|
7920
|
+
}
|
|
7921
|
+
|
|
7922
|
+
t = t > 1 ? 1 : t < 0 ? 0 : t;
|
|
7923
|
+
let t2 = t / 2;
|
|
7924
|
+
|
|
7925
|
+
/**
|
|
7926
|
+
* set higher legendre gauss weight abscissae values
|
|
7927
|
+
* by more accurate weight/abscissae lookups
|
|
7928
|
+
* https://pomax.github.io/bezierinfo/legendre-gauss.html
|
|
7929
|
+
*/
|
|
7930
|
+
|
|
7931
|
+
let sum = 0;
|
|
7932
|
+
|
|
7933
|
+
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;
|
|
7934
|
+
|
|
7935
|
+
for (let i = 0, len = wa.length; i < len; i++) {
|
|
7936
|
+
// weight and abscissae
|
|
7937
|
+
let [w, a] = [wa[i][0], wa[i][1]];
|
|
7938
|
+
let ct1_t = t2 * a;
|
|
7939
|
+
let ct0 = -ct1_t + t2;
|
|
7940
|
+
|
|
7941
|
+
let xbase0 = base3(ct0, x0, cp1x, cp2x, px);
|
|
7942
|
+
let ybase0 = base3(ct0, y0, cp1y, cp2y, py);
|
|
7943
|
+
|
|
7944
|
+
let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
|
|
7945
|
+
|
|
7946
|
+
sum += w * Math.sqrt(comb0);
|
|
7947
|
+
|
|
7948
|
+
}
|
|
7949
|
+
return t2 * sum;
|
|
7950
|
+
};
|
|
7951
|
+
|
|
7952
|
+
const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
|
|
7953
|
+
if (t === 0) {
|
|
7954
|
+
return 0;
|
|
7955
|
+
}
|
|
7956
|
+
// is flat/linear – treat as line
|
|
7957
|
+
if (checkFlat) {
|
|
7958
|
+
let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
|
|
7959
|
+
let l2 = getDistance(p0, p);
|
|
7960
|
+
if (l1 === l2) {
|
|
7961
|
+
return l2;
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
|
|
7965
|
+
let a, b, c, d, e, e1, d1, v1x, v1y;
|
|
7966
|
+
v1x = cp1.x * 2;
|
|
7967
|
+
v1y = cp1.y * 2;
|
|
7968
|
+
d = p0.x - v1x + p.x;
|
|
7969
|
+
d1 = p0.y - v1y + p.y;
|
|
7970
|
+
e = v1x - 2 * p0.x;
|
|
7971
|
+
e1 = v1y - 2 * p0.y;
|
|
7972
|
+
a = 4 * (d * d + d1 * d1);
|
|
7973
|
+
b = 4 * (d * e + d1 * e1);
|
|
7974
|
+
c = e * e + e1 * e1;
|
|
7975
|
+
|
|
7976
|
+
const bt = b / (2 * a),
|
|
7977
|
+
ct = c / a,
|
|
7978
|
+
ut = t + bt,
|
|
7979
|
+
|
|
7980
|
+
k = ct - bt * bt;
|
|
7981
|
+
|
|
7982
|
+
return (
|
|
7983
|
+
(Math.sqrt(a) / 2) *
|
|
7984
|
+
(ut * Math.sqrt(ut * ut + k) -
|
|
7985
|
+
bt * Math.sqrt(bt * bt + k) +
|
|
7986
|
+
k *
|
|
7987
|
+
Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
|
|
7988
|
+
);
|
|
7989
|
+
};
|
|
7990
|
+
|
|
7991
|
+
let length;
|
|
7992
|
+
if (pts.length === 4) {
|
|
7993
|
+
length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr);
|
|
7994
|
+
|
|
7995
|
+
}
|
|
7996
|
+
else if (pts.length === 3) {
|
|
7997
|
+
length = quadraticBezierLength(pts[0], pts[1], pts[2], t);
|
|
7998
|
+
}
|
|
7999
|
+
else {
|
|
8000
|
+
length = getDistance(pts[0], pts[1]);
|
|
8001
|
+
}
|
|
8002
|
+
|
|
8003
|
+
return length;
|
|
8004
|
+
}
|
|
8005
|
+
|
|
8006
|
+
// LG weight/abscissae generator
|
|
8007
|
+
function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
8008
|
+
console.log('add new LG', n);
|
|
8009
|
+
|
|
8010
|
+
let waArr = [];
|
|
8011
|
+
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
8012
|
+
const m = (n + 1) >> 1;
|
|
8013
|
+
xm = 0.5 * (x2 + x1);
|
|
8014
|
+
xl = 0.5 * (x2 - x1);
|
|
8015
|
+
|
|
8016
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
8017
|
+
z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
8018
|
+
do {
|
|
8019
|
+
p1 = 1;
|
|
8020
|
+
p2 = 0;
|
|
8021
|
+
for (let j = 0; j < n; j++) {
|
|
8022
|
+
|
|
8023
|
+
p3 = p2;
|
|
8024
|
+
p2 = p1;
|
|
8025
|
+
p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
|
|
8026
|
+
}
|
|
8027
|
+
|
|
8028
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
8029
|
+
z1 = z;
|
|
8030
|
+
z = z1 - p1 / pp; //Newton’s method
|
|
8031
|
+
|
|
8032
|
+
} while (Math.abs(z - z1) > 1.0e-14);
|
|
8033
|
+
|
|
8034
|
+
let weight = (2 * xl) / ((1 - z * z) * pp * pp);
|
|
8035
|
+
let abscissa = xm + xl * z;
|
|
8036
|
+
|
|
8037
|
+
waArr.push(
|
|
8038
|
+
[weight, -abscissa],
|
|
8039
|
+
[weight, abscissa],
|
|
8040
|
+
);
|
|
8041
|
+
}
|
|
8042
|
+
|
|
8043
|
+
return waArr;
|
|
8044
|
+
}
|
|
8045
|
+
|
|
8046
|
+
function base3(t, p1, p2, p3, p4) {
|
|
8047
|
+
let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
|
|
8048
|
+
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
8049
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
8050
|
+
}
|
|
8051
|
+
|
|
8052
|
+
function getPolygonLength(pts=[], isPoly=false){
|
|
8053
|
+
|
|
8054
|
+
let len = 0;
|
|
8055
|
+
let l=pts.length;
|
|
8056
|
+
|
|
8057
|
+
for(let i=1; i<l; i++){
|
|
8058
|
+
let p1 = pts[i-1];
|
|
8059
|
+
let p2 = pts[i];
|
|
8060
|
+
len += getDistance(p1, p2);
|
|
8061
|
+
}
|
|
8062
|
+
if(isPoly){
|
|
8063
|
+
len += getDistance(pts[l-1], pts[0]);
|
|
8064
|
+
}
|
|
8065
|
+
return len
|
|
8066
|
+
}
|
|
8067
|
+
|
|
8068
|
+
/**
|
|
8069
|
+
* Ramanujan approximation
|
|
8070
|
+
* based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
|
|
8071
|
+
*/
|
|
8072
|
+
function getEllipseLength(rx=0, ry=0) {
|
|
8073
|
+
// is circle
|
|
8074
|
+
if (rx === ry) {
|
|
8075
|
+
|
|
8076
|
+
return 2 * Math.PI * rx;
|
|
8077
|
+
}
|
|
8078
|
+
|
|
8079
|
+
let c=rx+ry;
|
|
8080
|
+
let d = (rx - ry) / c;
|
|
8081
|
+
let h = d*d;
|
|
8082
|
+
|
|
8083
|
+
let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
|
|
8084
|
+
return totalLength;
|
|
8085
|
+
}
|
|
8086
|
+
|
|
8087
|
+
/**
|
|
8088
|
+
* ellipse helpers
|
|
8089
|
+
* approximate ellipse length
|
|
8090
|
+
* by Legendre-Gauss
|
|
8091
|
+
*/
|
|
8092
|
+
|
|
8093
|
+
function getCircleArcLength(r = 0, deltaAngle = 0) {
|
|
8094
|
+
if(r===0) {
|
|
8095
|
+
console.warn('Radius must be positive');
|
|
8096
|
+
return 0;
|
|
8097
|
+
}
|
|
8098
|
+
let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI));
|
|
8099
|
+
return len
|
|
8100
|
+
}
|
|
8101
|
+
|
|
8102
|
+
function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
|
|
8103
|
+
|
|
8104
|
+
// Transform [-1, 1] interval to [startAngle, endAngle]
|
|
8105
|
+
let halfInterval = (endAngle - startAngle) * 0.5;
|
|
8106
|
+
let midpoint = (endAngle + startAngle) * 0.5;
|
|
8107
|
+
|
|
8108
|
+
// Arc length integral approximation
|
|
8109
|
+
let arcLength = 0;
|
|
8110
|
+
for (let i = 0; i < wa.length; i++) {
|
|
8111
|
+
let [weight, abscissae] = wa[i];
|
|
8112
|
+
let theta = midpoint + halfInterval * abscissae;
|
|
8113
|
+
|
|
8114
|
+
let a = rx * Math.sin(theta);
|
|
8115
|
+
let b = ry * Math.cos(theta);
|
|
8116
|
+
let integrand = Math.sqrt(
|
|
8117
|
+
a * a + b * b
|
|
8118
|
+
);
|
|
8119
|
+
arcLength += weight * integrand;
|
|
8120
|
+
}
|
|
8121
|
+
|
|
8122
|
+
return Math.abs(halfInterval * arcLength)
|
|
8123
|
+
}
|
|
8124
|
+
|
|
8125
|
+
function getPathDataLength(pathData = []) {
|
|
8126
|
+
let len = 0;
|
|
8127
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
8128
|
+
|
|
8129
|
+
for (let i = 0; i < pathDataArr.length; i++) {
|
|
8130
|
+
let pathData = pathDataArr[i];
|
|
8131
|
+
|
|
8132
|
+
// add verbose point data if not present
|
|
8133
|
+
if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
|
|
8134
|
+
|
|
8135
|
+
// Calculate Legendre Gauss weight and abscissa values
|
|
8136
|
+
if (!waArr_global.length) {
|
|
8137
|
+
|
|
8138
|
+
let waArr = getLegendreGaussValues(48);
|
|
8139
|
+
waArr.forEach(wa => {
|
|
8140
|
+
waArr_global.push(wa);
|
|
8141
|
+
});
|
|
8142
|
+
}
|
|
8143
|
+
|
|
8144
|
+
let waArr = waArr_global;
|
|
8145
|
+
|
|
8146
|
+
pathData.forEach(com => {
|
|
8147
|
+
let { type, values, p0, p, cp1 = null, cp2 = null } = com;
|
|
8148
|
+
let pts = [p0];
|
|
8149
|
+
if (type === 'C' || type === 'Q') pts.push(cp1);
|
|
8150
|
+
if (type === 'C') pts.push(cp2);
|
|
8151
|
+
pts.push(p);
|
|
8152
|
+
let comLen = 0;
|
|
8153
|
+
|
|
8154
|
+
if (type === 'A') {
|
|
8155
|
+
|
|
8156
|
+
// get parametrized arc properties
|
|
8157
|
+
let [largeArc, sweep] = [com.values[3], com.values[4]];
|
|
8158
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false);
|
|
8159
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData;
|
|
8160
|
+
|
|
8161
|
+
if (rx === ry) {
|
|
8162
|
+
comLen = getCircleArcLength(rx, Math.abs(deltaAngle));
|
|
8163
|
+
}
|
|
8164
|
+
|
|
8165
|
+
// is ellipse
|
|
8166
|
+
else {
|
|
8167
|
+
xAxisRotation = xAxisRotation * deg2rad;
|
|
8168
|
+
startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry);
|
|
8169
|
+
endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry);
|
|
8170
|
+
|
|
8171
|
+
// recalculate parametrized delta
|
|
8172
|
+
let deltaAngle_param = endAngle - startAngle;
|
|
8173
|
+
|
|
8174
|
+
let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
|
|
8175
|
+
|
|
8176
|
+
deltaAngle = signChange ? deltaAngle : deltaAngle_param;
|
|
8177
|
+
|
|
8178
|
+
// adjust end angle
|
|
8179
|
+
if (sweep && startAngle > endAngle) {
|
|
8180
|
+
endAngle += Math.PI * 2;
|
|
8181
|
+
}
|
|
8182
|
+
|
|
8183
|
+
if (!sweep && startAngle < endAngle) {
|
|
8184
|
+
endAngle -= Math.PI * 2;
|
|
8185
|
+
}
|
|
8186
|
+
comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr);
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
|
|
8190
|
+
else {
|
|
8191
|
+
comLen = getLength(pts, {
|
|
8192
|
+
t: 1,
|
|
8193
|
+
waArr
|
|
8194
|
+
});
|
|
8195
|
+
}
|
|
8196
|
+
len += comLen;
|
|
8197
|
+
});
|
|
8198
|
+
}
|
|
8199
|
+
|
|
8200
|
+
return len;
|
|
8201
|
+
}
|
|
8202
|
+
|
|
8203
|
+
function getElementLength(el, {
|
|
8204
|
+
props = {},
|
|
8205
|
+
pathLength = 0,
|
|
8206
|
+
} = {}) {
|
|
8207
|
+
|
|
8208
|
+
let nodeName = el.nodeName;
|
|
8209
|
+
let len = 0;
|
|
8210
|
+
|
|
8211
|
+
props = JSON.parse(JSON.stringify(props));
|
|
8212
|
+
|
|
8213
|
+
for (let prop in props) {
|
|
8214
|
+
if (props[prop] && props[prop].length && props[prop].length === 1) {
|
|
8215
|
+
props[prop] = props[prop][0];
|
|
8216
|
+
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
|
|
8220
|
+
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;
|
|
8221
|
+
|
|
8222
|
+
let pts = nodeName === 'polygon' || nodeName === 'polyline' ? el.getAttribute('points') : [];
|
|
8223
|
+
let isPolygon = nodeName === 'polygon';
|
|
8224
|
+
if (pts.length) {
|
|
8225
|
+
pts = normalizePoly(pts);
|
|
8226
|
+
}
|
|
8227
|
+
|
|
8228
|
+
// we need to convert rects with corner rounding
|
|
8229
|
+
let pathData = [];
|
|
8230
|
+
if (nodeName === 'rect' && (rx || ry)) {
|
|
8231
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
8232
|
+
nodeName = 'path';
|
|
8233
|
+
}
|
|
8234
|
+
|
|
8235
|
+
switch (nodeName) {
|
|
8236
|
+
case 'line':
|
|
8237
|
+
len = getDistance({ x: x1, y: y1 }, { x: x2, y: y2 });
|
|
8238
|
+
break;
|
|
8239
|
+
case 'rect':
|
|
8240
|
+
len = width * 2 + height * 2;
|
|
8241
|
+
break;
|
|
8242
|
+
case 'circle':
|
|
8243
|
+
len = 2 * Math.PI * r;
|
|
8244
|
+
break;
|
|
8245
|
+
case 'ellipse':
|
|
8246
|
+
len = getEllipseLength(rx, ry);
|
|
8247
|
+
break;
|
|
8248
|
+
case 'polygon':
|
|
8249
|
+
case 'polyline':
|
|
8250
|
+
len = getPolygonLength(pts, isPolygon);
|
|
8251
|
+
break;
|
|
8252
|
+
case 'path':
|
|
8253
|
+
pathData = pathData.length ? pathData : parsePathDataNormalized(el.getAttribute('d'));
|
|
8254
|
+
len = getPathDataLength(pathData);
|
|
8255
|
+
break;
|
|
8256
|
+
}
|
|
8257
|
+
|
|
8258
|
+
return len
|
|
8259
|
+
}
|
|
8260
|
+
|
|
7554
8261
|
function removeHiddenSvgEls(svg) {
|
|
7555
8262
|
let els = svg.querySelectorAll('*');
|
|
7556
8263
|
els.forEach(el => {
|
|
@@ -7599,8 +8306,12 @@ function removeSvgEls(svg, {
|
|
|
7599
8306
|
*/
|
|
7600
8307
|
|
|
7601
8308
|
function removeSvgAtts(svg, remove = []) {
|
|
8309
|
+
removeAtts(svg, remove);
|
|
8310
|
+
}
|
|
8311
|
+
|
|
8312
|
+
function removeAtts(el, remove = []) {
|
|
7602
8313
|
remove.forEach(att => {
|
|
7603
|
-
|
|
8314
|
+
el.removeAttribute(att);
|
|
7604
8315
|
});
|
|
7605
8316
|
}
|
|
7606
8317
|
|
|
@@ -7698,22 +8409,107 @@ function cleanSvgPrologue(svgString) {
|
|
|
7698
8409
|
}
|
|
7699
8410
|
*/
|
|
7700
8411
|
|
|
8412
|
+
function setNormalizedTransformsToEl(el, {
|
|
8413
|
+
styleProps = {},
|
|
8414
|
+
} = {}) {
|
|
8415
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
8416
|
+
let name = el.nodeName.toLowerCase();
|
|
8417
|
+
|
|
8418
|
+
if(!matrix) return styleProps;
|
|
8419
|
+
|
|
8420
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8421
|
+
|
|
8422
|
+
// scale attributes instead of transform
|
|
8423
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8424
|
+
let unProportional = scaleX !== scaleY;
|
|
8425
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8426
|
+
|
|
8427
|
+
let needsTrans = (hasRot) || unProportional;
|
|
8428
|
+
needsTrans = true;
|
|
8429
|
+
|
|
8430
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8431
|
+
|
|
8432
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
8433
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8434
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8435
|
+
|
|
8436
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8437
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8438
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8439
|
+
|
|
8440
|
+
}
|
|
8441
|
+
else if (name === 'rect') {
|
|
8442
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8443
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8444
|
+
|
|
8445
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8446
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8447
|
+
|
|
8448
|
+
styleProps.x = [x];
|
|
8449
|
+
styleProps.y = [y];
|
|
8450
|
+
|
|
8451
|
+
styleProps.rx = [rx];
|
|
8452
|
+
styleProps.ry = [ry];
|
|
8453
|
+
|
|
8454
|
+
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8455
|
+
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8456
|
+
}
|
|
8457
|
+
|
|
8458
|
+
// remove now obsolete transform properties
|
|
8459
|
+
delete styleProps.matrix;
|
|
8460
|
+
delete styleProps.transformArr;
|
|
8461
|
+
delete styleProps.transComponents;
|
|
8462
|
+
|
|
8463
|
+
// mark transform attribute for removal
|
|
8464
|
+
styleProps.remove.push('transform');
|
|
8465
|
+
|
|
8466
|
+
// scale props like stroke width or dash-array
|
|
8467
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8468
|
+
|
|
8469
|
+
} else {
|
|
8470
|
+
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8471
|
+
|
|
8472
|
+
}
|
|
8473
|
+
|
|
8474
|
+
return styleProps
|
|
8475
|
+
|
|
8476
|
+
}
|
|
8477
|
+
|
|
8478
|
+
function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8479
|
+
if (scale === 1 || !props.length) return props;
|
|
8480
|
+
|
|
8481
|
+
for (let i = 0; i < props.length; i++) {
|
|
8482
|
+
let prop = props[i];
|
|
8483
|
+
|
|
8484
|
+
if (styleProps[prop] !== undefined) {
|
|
8485
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
return styleProps
|
|
8489
|
+
}
|
|
8490
|
+
|
|
7701
8491
|
function convertPathLengthAtt(el, {
|
|
7702
8492
|
styleProps = {}
|
|
7703
|
-
}={}) {
|
|
8493
|
+
} = {}) {
|
|
7704
8494
|
|
|
7705
|
-
let pathLength =
|
|
8495
|
+
let pathLength = styleProps['pathLength'];
|
|
7706
8496
|
|
|
7707
|
-
if (pathLength
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
8497
|
+
if (pathLength) {
|
|
8498
|
+
|
|
8499
|
+
if ((styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
|
|
8500
|
+
let elLength = getElementLength(el, {
|
|
8501
|
+
pathLength,
|
|
8502
|
+
props: styleProps
|
|
8503
|
+
});
|
|
8504
|
+
|
|
8505
|
+
let scale = elLength / pathLength;
|
|
8506
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
7712
8507
|
|
|
7713
|
-
|
|
8508
|
+
// set absolute
|
|
8509
|
+
if (styleProps['stroke-dasharray']) el.setAttribute('stroke-dasharray', styleProps['stroke-dasharray'].join(' '));
|
|
8510
|
+
if (styleProps['stroke-dashoffset']) el.setAttribute('stroke-dashoffset', styleProps['stroke-dashoffset'][0]);
|
|
7714
8511
|
|
|
7715
|
-
|
|
7716
|
-
[styleProps['stroke-dasharray'], styleProps['stroke-dashoffset']];
|
|
8512
|
+
}
|
|
7717
8513
|
|
|
7718
8514
|
// tag for removal
|
|
7719
8515
|
delete styleProps['pathLength'];
|
|
@@ -8048,85 +8844,6 @@ function removeUnusedSelectors(parent=null, props={}){
|
|
|
8048
8844
|
return props
|
|
8049
8845
|
}
|
|
8050
8846
|
|
|
8051
|
-
function setNormalizedTransformsToEl(el, {
|
|
8052
|
-
styleProps = {},
|
|
8053
|
-
} = {}) {
|
|
8054
|
-
let { remove, matrix, transComponents } = styleProps;
|
|
8055
|
-
let name = el.nodeName.toLowerCase();
|
|
8056
|
-
|
|
8057
|
-
if(!matrix) return styleProps;
|
|
8058
|
-
|
|
8059
|
-
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8060
|
-
|
|
8061
|
-
// scale attributes instead of transform
|
|
8062
|
-
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8063
|
-
let unProportional = scaleX !== scaleY;
|
|
8064
|
-
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8065
|
-
|
|
8066
|
-
let needsTrans = (hasRot) || unProportional;
|
|
8067
|
-
needsTrans = true;
|
|
8068
|
-
|
|
8069
|
-
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8070
|
-
|
|
8071
|
-
if (name === 'circle' || name === 'ellipse') {
|
|
8072
|
-
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8073
|
-
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8074
|
-
|
|
8075
|
-
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8076
|
-
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8077
|
-
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8078
|
-
|
|
8079
|
-
}
|
|
8080
|
-
else if (name === 'rect') {
|
|
8081
|
-
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8082
|
-
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8083
|
-
|
|
8084
|
-
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8085
|
-
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8086
|
-
|
|
8087
|
-
styleProps.x = [x];
|
|
8088
|
-
styleProps.y = [y];
|
|
8089
|
-
|
|
8090
|
-
styleProps.rx = [rx];
|
|
8091
|
-
styleProps.ry = [ry];
|
|
8092
|
-
|
|
8093
|
-
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8094
|
-
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8095
|
-
}
|
|
8096
|
-
|
|
8097
|
-
// remove now obsolete transform properties
|
|
8098
|
-
delete styleProps.matrix;
|
|
8099
|
-
delete styleProps.transformArr;
|
|
8100
|
-
delete styleProps.transComponents;
|
|
8101
|
-
|
|
8102
|
-
// mark transform attribute for removal
|
|
8103
|
-
styleProps.remove.push('transform');
|
|
8104
|
-
|
|
8105
|
-
// scale props like stroke width or dash-array
|
|
8106
|
-
styleProps = scaleProps$1(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8107
|
-
|
|
8108
|
-
} else {
|
|
8109
|
-
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8110
|
-
|
|
8111
|
-
}
|
|
8112
|
-
|
|
8113
|
-
return styleProps
|
|
8114
|
-
|
|
8115
|
-
}
|
|
8116
|
-
|
|
8117
|
-
function scaleProps$1(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8118
|
-
if (scale === 1 || !props.length) return props;
|
|
8119
|
-
|
|
8120
|
-
for (let i = 0; i < props.length; i++) {
|
|
8121
|
-
let prop = props[i];
|
|
8122
|
-
|
|
8123
|
-
if (styleProps[prop] !== undefined) {
|
|
8124
|
-
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 2) : val * scale);
|
|
8125
|
-
}
|
|
8126
|
-
}
|
|
8127
|
-
return styleProps
|
|
8128
|
-
}
|
|
8129
|
-
|
|
8130
8847
|
function cleanUpSVG(svgMarkup, {
|
|
8131
8848
|
removeHidden = true,
|
|
8132
8849
|
|
|
@@ -8156,7 +8873,10 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8156
8873
|
cleanupSVGAtts = true,
|
|
8157
8874
|
removeNameSpaced = true,
|
|
8158
8875
|
removeNameSpacedAtts = true,
|
|
8876
|
+
|
|
8877
|
+
// unit conversions
|
|
8159
8878
|
convertPathLength = false,
|
|
8879
|
+
toAbsoluteUnits = false,
|
|
8160
8880
|
|
|
8161
8881
|
// meta
|
|
8162
8882
|
allowMeta = false,
|
|
@@ -8181,10 +8901,10 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8181
8901
|
} = {}) {
|
|
8182
8902
|
|
|
8183
8903
|
// resolve dependencies
|
|
8184
|
-
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8185
|
-
|
|
8904
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8905
|
+
stylesToAttributes = true;
|
|
8186
8906
|
|
|
8187
|
-
if(stylesToAttributes) cleanUpStrokes = true;
|
|
8907
|
+
if (stylesToAttributes) cleanUpStrokes = true;
|
|
8188
8908
|
|
|
8189
8909
|
// replace namespaced refs
|
|
8190
8910
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
@@ -8229,7 +8949,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8229
8949
|
removeClassNames,
|
|
8230
8950
|
minifyRgbColors,
|
|
8231
8951
|
stylesheetProps: {},
|
|
8232
|
-
exclude:[]
|
|
8952
|
+
exclude: []
|
|
8233
8953
|
};
|
|
8234
8954
|
|
|
8235
8955
|
// root svg inline style properties
|
|
@@ -8346,9 +9066,14 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8346
9066
|
if (stylePropsSVG['class']) delete stylePropsSVG['class'];
|
|
8347
9067
|
if (stylePropsSVG['id']) delete stylePropsSVG['id'];
|
|
8348
9068
|
|
|
9069
|
+
// add svg props
|
|
9070
|
+
inheritedProps = {
|
|
9071
|
+
...stylePropsSVG,
|
|
9072
|
+
...inheritedProps,
|
|
9073
|
+
};
|
|
9074
|
+
|
|
8349
9075
|
// merge with svg props
|
|
8350
9076
|
styleProps = {
|
|
8351
|
-
...stylePropsSVG,
|
|
8352
9077
|
...inheritedProps,
|
|
8353
9078
|
...styleProps
|
|
8354
9079
|
};
|
|
@@ -8390,6 +9115,40 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8390
9115
|
// general cleanup
|
|
8391
9116
|
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
8392
9117
|
|
|
9118
|
+
// all relative units to absolute
|
|
9119
|
+
if (toAbsoluteUnits) {
|
|
9120
|
+
normalizeTransforms = true;
|
|
9121
|
+
|
|
9122
|
+
/**
|
|
9123
|
+
* apply consolidated
|
|
9124
|
+
* element attributes
|
|
9125
|
+
* remove non-supported element props
|
|
9126
|
+
*/
|
|
9127
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
9128
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
9129
|
+
|
|
9130
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9131
|
+
let values = styleProps[prop];
|
|
9132
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
9133
|
+
el.setAttribute(prop, val);
|
|
9134
|
+
}
|
|
9135
|
+
|
|
9136
|
+
let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
9137
|
+
|
|
9138
|
+
// check if same value is in inherited
|
|
9139
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9140
|
+
let valInh = inheritedProps[prop] || [];
|
|
9141
|
+
let val = stylePropsFiltered.propsFiltered[prop] || [];
|
|
9142
|
+
if (valInh.join() === val.join()) {
|
|
9143
|
+
removeAttsEl.push(prop);
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
|
|
9147
|
+
// remove obsolete/inherited
|
|
9148
|
+
removeAtts(el, removeAttsEl);
|
|
9149
|
+
|
|
9150
|
+
}
|
|
9151
|
+
|
|
8393
9152
|
if (stylesToAttributes) {
|
|
8394
9153
|
|
|
8395
9154
|
/**
|
|
@@ -8408,7 +9167,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8408
9167
|
* remove non-supported element props
|
|
8409
9168
|
*/
|
|
8410
9169
|
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
8411
|
-
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
9170
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
8412
9171
|
|
|
8413
9172
|
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
8414
9173
|
|
|
@@ -8422,12 +9181,14 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8422
9181
|
* remove obsolete
|
|
8423
9182
|
* attributes
|
|
8424
9183
|
*/
|
|
9184
|
+
removeAtts(el, remove);
|
|
8425
9185
|
|
|
9186
|
+
/*
|
|
8426
9187
|
for (let i = 0; i < remove.length; i++) {
|
|
8427
9188
|
let att = remove[i];
|
|
8428
|
-
|
|
8429
|
-
el.removeAttribute(att);
|
|
9189
|
+
el.removeAttribute(att)
|
|
8430
9190
|
}
|
|
9191
|
+
*/
|
|
8431
9192
|
|
|
8432
9193
|
} // endof style processing
|
|
8433
9194
|
|
|
@@ -8512,15 +9273,15 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8512
9273
|
let values = stylePropsFiltered[prop];
|
|
8513
9274
|
let val = values.length ? values.join(' ') : values[0];
|
|
8514
9275
|
|
|
8515
|
-
if(prop!=='class' && prop!=='id'){
|
|
9276
|
+
if (prop !== 'class' && prop !== 'id') {
|
|
8516
9277
|
|
|
8517
9278
|
let propShort = toShortStr(prop);
|
|
8518
9279
|
let valShort = toShortStr(val);
|
|
8519
9280
|
let propStr = `${propShort}-${valShort}`;
|
|
8520
|
-
|
|
9281
|
+
|
|
8521
9282
|
// store in node property
|
|
8522
9283
|
if (!el.styleSet) el.styleSet = new Set();
|
|
8523
|
-
if(propStr) el.styleSet.add(propStr);
|
|
9284
|
+
if (propStr) el.styleSet.add(propStr);
|
|
8524
9285
|
}
|
|
8525
9286
|
}
|
|
8526
9287
|
|
|
@@ -8569,7 +9330,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8569
9330
|
}
|
|
8570
9331
|
|
|
8571
9332
|
function removeEmptyClassAtts(svg) {
|
|
8572
|
-
let emptyClassEls = svg.querySelectorAll('[class=""');
|
|
9333
|
+
let emptyClassEls = svg.querySelectorAll('[class=""]');
|
|
8573
9334
|
emptyClassEls.forEach(el => {
|
|
8574
9335
|
el.removeAttribute('class');
|
|
8575
9336
|
});
|
|
@@ -8582,7 +9343,7 @@ function sharedAttributesToGroup(svg) {
|
|
|
8582
9343
|
|
|
8583
9344
|
let els = svg.querySelectorAll(renderedEls.join(', '));
|
|
8584
9345
|
let len = els.length;
|
|
8585
|
-
if(len===1) return;
|
|
9346
|
+
if (len === 1) return;
|
|
8586
9347
|
|
|
8587
9348
|
let el0 = els[0] || null;
|
|
8588
9349
|
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
|
|
@@ -8658,7 +9419,7 @@ function sharedAttributesToGroup(svg) {
|
|
|
8658
9419
|
if (children.length === 1) continue
|
|
8659
9420
|
|
|
8660
9421
|
// create new group
|
|
8661
|
-
if (!groupEl || groups.length>1) {
|
|
9422
|
+
if (!groupEl || groups.length > 1) {
|
|
8662
9423
|
|
|
8663
9424
|
groupEl = document.createElementNS(svgNs, 'g');
|
|
8664
9425
|
child0.parentNode.insertBefore(groupEl, child0);
|
|
@@ -8740,7 +9501,9 @@ function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
|
8740
9501
|
bb0.bottom = y + height;
|
|
8741
9502
|
|
|
8742
9503
|
els.forEach(el => {
|
|
9504
|
+
|
|
8743
9505
|
let bb = getElBBox(el);
|
|
9506
|
+
|
|
8744
9507
|
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom;
|
|
8745
9508
|
if (outside) el.remove();
|
|
8746
9509
|
});
|
|
@@ -8849,8 +9612,130 @@ function hrefToXlink(svg) {
|
|
|
8849
9612
|
});
|
|
8850
9613
|
}
|
|
8851
9614
|
|
|
9615
|
+
function getArcFromPoly(pts, precise = false) {
|
|
9616
|
+
if (pts.length < 3) return false
|
|
9617
|
+
|
|
9618
|
+
// Pick 3 well-spaced points
|
|
9619
|
+
let len = pts.length;
|
|
9620
|
+
let idx1 = Math.floor(len * 0.333);
|
|
9621
|
+
let idx2 = Math.floor(len * 0.666);
|
|
9622
|
+
let idx3 = Math.floor(len * 0.5);
|
|
9623
|
+
|
|
9624
|
+
let p1 = pts[0];
|
|
9625
|
+
let p2 = pts[idx3];
|
|
9626
|
+
let p3 = pts[len - 1];
|
|
9627
|
+
|
|
9628
|
+
// Radius (use start point)
|
|
9629
|
+
let pts1 = [p1, p2, p3];
|
|
9630
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
9631
|
+
|
|
9632
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
9633
|
+
|
|
9634
|
+
// check if radii are consistent
|
|
9635
|
+
if (precise) {
|
|
9636
|
+
|
|
9637
|
+
/**
|
|
9638
|
+
* check multiple centroids
|
|
9639
|
+
* if the polyline can be expressed as
|
|
9640
|
+
* an arc - all centroids should be close
|
|
9641
|
+
*/
|
|
9642
|
+
|
|
9643
|
+
if (len > 3) {
|
|
9644
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
9645
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
9646
|
+
|
|
9647
|
+
if (!centroid1 || !centroid2) return false;
|
|
9648
|
+
|
|
9649
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
9650
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
9651
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
9652
|
+
let errorCentroid = (dist1 + dist2);
|
|
9653
|
+
|
|
9654
|
+
// centroids diverging too much
|
|
9655
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
9656
|
+
|
|
9657
|
+
return false
|
|
9658
|
+
}
|
|
9659
|
+
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9662
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
9663
|
+
|
|
9664
|
+
for (let i = 0; i < len; i++) {
|
|
9665
|
+
let pt = pts[i];
|
|
9666
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
9667
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
9668
|
+
|
|
9669
|
+
if (error > 0.0025) {
|
|
9670
|
+
/*
|
|
9671
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
9672
|
+
renderPoint(markers, centroid, 'orange')
|
|
9673
|
+
renderPoint(markers, p1, 'green')
|
|
9674
|
+
renderPoint(markers, p2)
|
|
9675
|
+
renderPoint(markers, p3, 'purple')
|
|
9676
|
+
*/
|
|
9677
|
+
return false;
|
|
9678
|
+
}
|
|
9679
|
+
}
|
|
9680
|
+
|
|
9681
|
+
// calculate proper radius
|
|
9682
|
+
r = Math.sqrt(rSqMid);
|
|
9683
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9684
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9685
|
+
|
|
9686
|
+
} else {
|
|
9687
|
+
r = getDistance(centroid, p1);
|
|
9688
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9689
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9690
|
+
}
|
|
9691
|
+
|
|
9692
|
+
return {
|
|
9693
|
+
centroid,
|
|
9694
|
+
r,
|
|
9695
|
+
startAngle,
|
|
9696
|
+
endAngle,
|
|
9697
|
+
deltaAngle
|
|
9698
|
+
};
|
|
9699
|
+
}
|
|
9700
|
+
|
|
9701
|
+
function getPolyArcCentroid(pts = []) {
|
|
9702
|
+
|
|
9703
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
9704
|
+
if (pts.length < 3) return false
|
|
9705
|
+
|
|
9706
|
+
let p1 = pts[0];
|
|
9707
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9708
|
+
let p3 = pts[pts.length - 1];
|
|
9709
|
+
|
|
9710
|
+
let x1 = p1.x, y1 = p1.y;
|
|
9711
|
+
let x2 = p2.x, y2 = p2.y;
|
|
9712
|
+
let x3 = p3.x, y3 = p3.y;
|
|
9713
|
+
|
|
9714
|
+
let a = x1 - x2;
|
|
9715
|
+
let b = y1 - y2;
|
|
9716
|
+
let c = x1 - x3;
|
|
9717
|
+
let d = y1 - y3;
|
|
9718
|
+
|
|
9719
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
9720
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
9721
|
+
|
|
9722
|
+
let det = a * d - b * c;
|
|
9723
|
+
|
|
9724
|
+
// colinear points
|
|
9725
|
+
if (Math.abs(det) < 1e-10) {
|
|
9726
|
+
return false;
|
|
9727
|
+
}
|
|
9728
|
+
|
|
9729
|
+
// find center of arc
|
|
9730
|
+
let cx = (d * e - b * f) / det;
|
|
9731
|
+
let cy = (-c * e + a * f) / det;
|
|
9732
|
+
let centroid = { x: cx, y: cy };
|
|
9733
|
+
return centroid
|
|
9734
|
+
}
|
|
9735
|
+
|
|
8852
9736
|
function refineRoundedCorners(pathData, {
|
|
8853
9737
|
threshold = 0,
|
|
9738
|
+
simplifyQuadraticCorners = false,
|
|
8854
9739
|
tolerance = 1
|
|
8855
9740
|
} = {}) {
|
|
8856
9741
|
|
|
@@ -8875,6 +9760,9 @@ function refineRoundedCorners(pathData, {
|
|
|
8875
9760
|
let firstIsLine = pathData[1].type === 'L';
|
|
8876
9761
|
let firstIsBez = pathData[1].type === 'C';
|
|
8877
9762
|
|
|
9763
|
+
// in case we have simplified a corner connecting to the start
|
|
9764
|
+
let M_adj = null;
|
|
9765
|
+
|
|
8878
9766
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
8879
9767
|
|
|
8880
9768
|
// normalize closepath to lineto
|
|
@@ -8914,15 +9802,17 @@ function refineRoundedCorners(pathData, {
|
|
|
8914
9802
|
// closing corner to start
|
|
8915
9803
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
8916
9804
|
comL1 = pathData[1];
|
|
9805
|
+
|
|
8917
9806
|
comBez = [pathData[l - lastOff]];
|
|
8918
9807
|
|
|
8919
9808
|
}
|
|
8920
9809
|
|
|
9810
|
+
// collect enclosed bezier segments
|
|
8921
9811
|
for (let j = i + 1; j < l; j++) {
|
|
8922
9812
|
let comN = pathData[j] ? pathData[j] : null;
|
|
8923
9813
|
let comPrev = pathData[j - 1];
|
|
8924
9814
|
|
|
8925
|
-
if (comPrev.type === 'C') {
|
|
9815
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
8926
9816
|
comBez.push(comPrev);
|
|
8927
9817
|
}
|
|
8928
9818
|
|
|
@@ -8953,39 +9843,67 @@ function refineRoundedCorners(pathData, {
|
|
|
8953
9843
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
8954
9844
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
8955
9845
|
|
|
9846
|
+
/*
|
|
9847
|
+
*/
|
|
9848
|
+
|
|
8956
9849
|
if (comBez.length && !signChange && isSmall) {
|
|
8957
9850
|
|
|
8958
|
-
let
|
|
9851
|
+
let isSquare = false;
|
|
9852
|
+
|
|
9853
|
+
if (comBez.length === 1) {
|
|
9854
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
9855
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
9856
|
+
let diff = (dx - dy);
|
|
9857
|
+
let rat = Math.abs(diff / dx);
|
|
9858
|
+
isSquare = rat < 0.01;
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
let preferArcs = true;
|
|
9862
|
+
preferArcs = false;
|
|
9863
|
+
|
|
9864
|
+
// if rectangular prefer arcs
|
|
9865
|
+
if (preferArcs && isSquare) {
|
|
9866
|
+
|
|
9867
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
9868
|
+
|
|
9869
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
9870
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
9871
|
+
|
|
9872
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
9873
|
+
|
|
9874
|
+
let largeArc = 0;
|
|
9875
|
+
|
|
9876
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
9877
|
+
|
|
9878
|
+
pathDataN.push(comL0, comArc);
|
|
9879
|
+
i += offset;
|
|
9880
|
+
continue
|
|
9881
|
+
|
|
9882
|
+
}
|
|
9883
|
+
|
|
9884
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
9885
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
9886
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
9887
|
+
|
|
8959
9888
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
8960
9889
|
|
|
8961
|
-
|
|
9890
|
+
// exit: is rather flat or has no intersection
|
|
9891
|
+
|
|
9892
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
8962
9893
|
pathDataN.push(com);
|
|
8963
9894
|
continue
|
|
8964
9895
|
}
|
|
8965
9896
|
|
|
8966
|
-
// check sign change
|
|
9897
|
+
// check sign change - exit if present
|
|
8967
9898
|
if (ptQ) {
|
|
8968
9899
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
8969
9900
|
let area0_abs = Math.abs(area0);
|
|
8970
9901
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
8971
9902
|
let area1_abs = Math.abs(area1);
|
|
8972
9903
|
let areaDiff = Math.abs(area0_abs - area1_abs) / area0_abs;
|
|
8973
|
-
|
|
8974
|
-
/*
|
|
8975
|
-
renderPoint(markers, comL0.p0, 'green', '0.5%', '0.5')
|
|
8976
|
-
renderPoint(markers, comL0.p, 'red', '1.5%', '0.5')
|
|
8977
|
-
renderPoint(markers, comL1.p0, 'blue', '0.5%', '0.5')
|
|
8978
|
-
renderPoint(markers, comL1.p, 'orange', '0.5%', '0.5')
|
|
8979
|
-
if(!area0) {
|
|
8980
|
-
pathDataN.push(com);
|
|
8981
|
-
continue
|
|
8982
|
-
}
|
|
8983
|
-
*/
|
|
8984
|
-
|
|
8985
9904
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
8986
9905
|
|
|
8987
9906
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
8988
|
-
|
|
8989
9907
|
pathDataN.push(com);
|
|
8990
9908
|
continue
|
|
8991
9909
|
}
|
|
@@ -9000,24 +9918,67 @@ function refineRoundedCorners(pathData, {
|
|
|
9000
9918
|
|
|
9001
9919
|
// not in tolerance – return original command
|
|
9002
9920
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
9003
|
-
|
|
9004
9921
|
pathDataN.push(com);
|
|
9005
9922
|
continue;
|
|
9006
9923
|
|
|
9007
|
-
}
|
|
9924
|
+
}
|
|
9008
9925
|
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
comQ.cp1 = ptQ;
|
|
9012
|
-
comQ.p = comL1.p0;
|
|
9926
|
+
// return simplified quadratic Bézier command
|
|
9927
|
+
let p_Q = comL1.p0;
|
|
9013
9928
|
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9929
|
+
// adjust previous end point to better fit the cubic curvature
|
|
9930
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
9931
|
+
|
|
9932
|
+
if (adjustQ) {
|
|
9933
|
+
|
|
9934
|
+
let t = 0.1666;
|
|
9935
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
9936
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
9937
|
+
|
|
9938
|
+
// round for large enough segments
|
|
9939
|
+
let isH = ptQ.y===comL0.p.y;
|
|
9940
|
+
let isV = ptQ.x===comL0.p.x;
|
|
9941
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
9942
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
9943
|
+
|
|
9944
|
+
if(isSquare && com.dimA>3){
|
|
9945
|
+
let dec = 0.5;
|
|
9946
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
9947
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
9948
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
9949
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
9950
|
+
}
|
|
9951
|
+
|
|
9952
|
+
/*
|
|
9953
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
9954
|
+
renderPoint(markers, p_Q, 'orange')
|
|
9955
|
+
renderPoint(markers, comL0.p, 'green')
|
|
9956
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
9957
|
+
*/
|
|
9958
|
+
|
|
9959
|
+
// set new M starting point
|
|
9960
|
+
if (i === l - lastOff - 1) {
|
|
9961
|
+
|
|
9962
|
+
M_adj = p_Q;
|
|
9963
|
+
}
|
|
9964
|
+
|
|
9965
|
+
// adjust previous lineto end point
|
|
9966
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
9967
|
+
comL0.p = p0_adj;
|
|
9017
9968
|
|
|
9018
|
-
continue;
|
|
9019
9969
|
}
|
|
9020
9970
|
|
|
9971
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
9972
|
+
comQ.cp1 = ptQ;
|
|
9973
|
+
comQ.p0 = comL0.p;
|
|
9974
|
+
comQ.p = p_Q;
|
|
9975
|
+
|
|
9976
|
+
// add quadratic command
|
|
9977
|
+
pathDataN.push(comL0, comQ);
|
|
9978
|
+
|
|
9979
|
+
i += offset;
|
|
9980
|
+
continue;
|
|
9981
|
+
|
|
9021
9982
|
}
|
|
9022
9983
|
}
|
|
9023
9984
|
}
|
|
@@ -9031,6 +9992,12 @@ function refineRoundedCorners(pathData, {
|
|
|
9031
9992
|
|
|
9032
9993
|
}
|
|
9033
9994
|
|
|
9995
|
+
// correct starting point connecting with last corner rounding
|
|
9996
|
+
if (M_adj) {
|
|
9997
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
9998
|
+
pathDataN[0].p0 = M_adj;
|
|
9999
|
+
}
|
|
10000
|
+
|
|
9034
10001
|
// revert close path normalization
|
|
9035
10002
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
9036
10003
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -9040,51 +10007,143 @@ function refineRoundedCorners(pathData, {
|
|
|
9040
10007
|
|
|
9041
10008
|
}
|
|
9042
10009
|
|
|
9043
|
-
function
|
|
9044
|
-
|
|
10010
|
+
function simplifyAdjacentRound(pathData, {
|
|
10011
|
+
threshold = 0,
|
|
10012
|
+
tolerance = 1,
|
|
10013
|
+
// take arcs or cubic beziers
|
|
10014
|
+
toCubic = false,
|
|
10015
|
+
debug = false
|
|
10016
|
+
} = {}) {
|
|
9045
10017
|
|
|
9046
|
-
//
|
|
9047
|
-
|
|
9048
|
-
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9049
|
-
let p3 = pts[pts.length - 1];
|
|
10018
|
+
// fix small Arcs
|
|
10019
|
+
pathData = convertSmallArcsToLinetos(pathData);
|
|
9050
10020
|
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
let x3 = p3.x, y3 = p3.y;
|
|
10021
|
+
// min size threshold for corners
|
|
10022
|
+
threshold *= tolerance;
|
|
9054
10023
|
|
|
9055
|
-
let
|
|
9056
|
-
let b = y1 - y2;
|
|
9057
|
-
let c = x1 - x3;
|
|
9058
|
-
let d = y1 - y3;
|
|
10024
|
+
let l = pathData.length;
|
|
9059
10025
|
|
|
9060
|
-
|
|
9061
|
-
let
|
|
10026
|
+
// add fist command
|
|
10027
|
+
let pathDataN = [pathData[0]];
|
|
9062
10028
|
|
|
9063
|
-
|
|
10029
|
+
// find adjacent cubics between extremes
|
|
9064
10030
|
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
10031
|
+
for (let i = 1; i < l; i++) {
|
|
10032
|
+
pathData[i - 1];
|
|
10033
|
+
let com = pathData[i];
|
|
10034
|
+
let comN = pathData[i + 1] || null;
|
|
9069
10035
|
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
10036
|
+
if (!comN) {
|
|
10037
|
+
pathDataN.push(com);
|
|
10038
|
+
break
|
|
10039
|
+
}
|
|
9074
10040
|
|
|
9075
|
-
|
|
9076
|
-
|
|
10041
|
+
let { type, extreme = false, p0, p, dimA = 0 } = com;
|
|
10042
|
+
// for short segment detection
|
|
10043
|
+
let dimAN = comN.dimA;
|
|
10044
|
+
let dimA0 = dimA + dimAN;
|
|
10045
|
+
let thresh = 0.1;
|
|
9077
10046
|
|
|
9078
|
-
|
|
9079
|
-
|
|
10047
|
+
// ignore short linetos
|
|
10048
|
+
let isShortN = dimAN < dimA0 * thresh;
|
|
9080
10049
|
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
10050
|
+
// adjacent cubic commands - accept short in between linetos
|
|
10051
|
+
if ((type === 'C') && (comN.type === 'C' || isShortN)) {
|
|
10052
|
+
|
|
10053
|
+
let candidates = [];
|
|
10054
|
+
|
|
10055
|
+
for (let j = i + 1; j < l; j++) {
|
|
10056
|
+
let comN = pathData[j];
|
|
10057
|
+
let { type, extreme = false, corner = false, dimA = 0 } = comN;
|
|
10058
|
+
let isShort = dimA < dimA0 * thresh;
|
|
10059
|
+
|
|
10060
|
+
// skip for type change(unless very short), extremes or corners
|
|
10061
|
+
/*
|
|
10062
|
+
if ( (comN.extreme || comN.corner) ) {
|
|
10063
|
+
if(!extreme && !corner) candidates.push(comN)
|
|
10064
|
+
break;
|
|
10065
|
+
}
|
|
10066
|
+
*/
|
|
10067
|
+
|
|
10068
|
+
if (extreme || corner) {
|
|
10069
|
+
|
|
10070
|
+
if (isShort && comN.type !== 'C') ;
|
|
10071
|
+
|
|
10072
|
+
if ((extreme && !corner)) {
|
|
10073
|
+
|
|
10074
|
+
candidates.push(comN);
|
|
10075
|
+
}
|
|
10076
|
+
|
|
10077
|
+
break;
|
|
10078
|
+
}
|
|
10079
|
+
|
|
10080
|
+
candidates.push(comN);
|
|
10081
|
+
}
|
|
10082
|
+
|
|
10083
|
+
// try to create arc command
|
|
10084
|
+
if (candidates.length > 1) {
|
|
10085
|
+
|
|
10086
|
+
let clen = candidates.length;
|
|
10087
|
+
let pts = [com.p0, com.p,];
|
|
10088
|
+
|
|
10089
|
+
// add interpolated points to prevent wrong arc replacements
|
|
10090
|
+
candidates.forEach(c => {
|
|
10091
|
+
if (c.type === 'C') {
|
|
10092
|
+
let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5);
|
|
10093
|
+
pts.push(pt);
|
|
10094
|
+
}
|
|
10095
|
+
pts.push(c.p);
|
|
10096
|
+
});
|
|
10097
|
+
|
|
10098
|
+
let precise = true;
|
|
10099
|
+
let arcProps = getArcFromPoly(pts, precise);
|
|
10100
|
+
|
|
10101
|
+
// could be combined
|
|
10102
|
+
if (arcProps) {
|
|
10103
|
+
|
|
10104
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
10105
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
10106
|
+
|
|
10107
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
10108
|
+
largeArc = 0;
|
|
10109
|
+
let comLast = candidates[clen - 1];
|
|
10110
|
+
let p = comLast.p;
|
|
10111
|
+
|
|
10112
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] };
|
|
10113
|
+
|
|
10114
|
+
comArc.dimA = getDistManhattan(p0, p);
|
|
10115
|
+
comArc.p0 = p0;
|
|
10116
|
+
comArc.p = p;
|
|
10117
|
+
comArc.error = 0;
|
|
10118
|
+
comArc.directionChange = comLast.directionChange;
|
|
10119
|
+
comArc.extreme = comLast.extreme;
|
|
10120
|
+
comArc.corner = comLast.corner;
|
|
10121
|
+
pathDataN.push(comArc);
|
|
10122
|
+
|
|
10123
|
+
i += candidates.length;
|
|
10124
|
+
continue
|
|
10125
|
+
|
|
10126
|
+
}
|
|
10127
|
+
|
|
10128
|
+
// arc radius calculation failed - return original
|
|
10129
|
+
else {
|
|
10130
|
+
pathDataN.push(com);
|
|
10131
|
+
}
|
|
10132
|
+
}
|
|
10133
|
+
|
|
10134
|
+
// could not be simplified – return original command
|
|
10135
|
+
else {
|
|
10136
|
+
pathDataN.push(com);
|
|
10137
|
+
}
|
|
10138
|
+
|
|
10139
|
+
}
|
|
10140
|
+
// all other commands
|
|
10141
|
+
else {
|
|
10142
|
+
pathDataN.push(com);
|
|
10143
|
+
}
|
|
10144
|
+
}
|
|
10145
|
+
|
|
10146
|
+
return pathDataN
|
|
9088
10147
|
}
|
|
9089
10148
|
|
|
9090
10149
|
function refineRoundSegments(pathData, {
|
|
@@ -9103,9 +10162,6 @@ function refineRoundSegments(pathData, {
|
|
|
9103
10162
|
// add fist command
|
|
9104
10163
|
let pathDataN = [pathData[0]];
|
|
9105
10164
|
|
|
9106
|
-
// just for debugging
|
|
9107
|
-
let pathDataTest = [];
|
|
9108
|
-
|
|
9109
10165
|
for (let i = 1; i < l; i++) {
|
|
9110
10166
|
let com = pathData[i];
|
|
9111
10167
|
let { type } = com;
|
|
@@ -9132,11 +10188,12 @@ function refineRoundSegments(pathData, {
|
|
|
9132
10188
|
|
|
9133
10189
|
// 2. line-line-bezier-line-line
|
|
9134
10190
|
if (
|
|
10191
|
+
comN2 && comN3 &&
|
|
9135
10192
|
comP.type === 'L' &&
|
|
9136
10193
|
type === 'L' &&
|
|
9137
10194
|
comBez &&
|
|
9138
10195
|
comN2.type === 'L' &&
|
|
9139
|
-
|
|
10196
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
9140
10197
|
) {
|
|
9141
10198
|
|
|
9142
10199
|
L1 = [com.p0, com.p];
|
|
@@ -9163,10 +10220,10 @@ function refineRoundSegments(pathData, {
|
|
|
9163
10220
|
}
|
|
9164
10221
|
|
|
9165
10222
|
// 1. line-bezier-bezier-line
|
|
9166
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
10223
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
9167
10224
|
|
|
9168
10225
|
// 1.2 next is cubic next is lineto
|
|
9169
|
-
if (
|
|
10226
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
9170
10227
|
|
|
9171
10228
|
combine = true;
|
|
9172
10229
|
|
|
@@ -9225,16 +10282,19 @@ function refineRoundSegments(pathData, {
|
|
|
9225
10282
|
}
|
|
9226
10283
|
);
|
|
9227
10284
|
|
|
9228
|
-
if(bezierCommands.length === 1){
|
|
10285
|
+
if (bezierCommands.length === 1) {
|
|
9229
10286
|
|
|
9230
10287
|
// prefer more compact quadratic - otherwise arcs
|
|
9231
10288
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
9232
10289
|
|
|
9233
10290
|
if (comBezier.type === 'Q') {
|
|
9234
10291
|
toCubic = true;
|
|
10292
|
+
}else {
|
|
10293
|
+
comBezier = bezierCommands[0];
|
|
9235
10294
|
}
|
|
9236
10295
|
|
|
9237
10296
|
com = comBezier;
|
|
10297
|
+
|
|
9238
10298
|
}
|
|
9239
10299
|
|
|
9240
10300
|
// prefer arcs if 2 cubics are required
|
|
@@ -9254,25 +10314,28 @@ function refineRoundSegments(pathData, {
|
|
|
9254
10314
|
|
|
9255
10315
|
// test rendering
|
|
9256
10316
|
|
|
10317
|
+
/*
|
|
9257
10318
|
if (debug) {
|
|
9258
10319
|
// arcs
|
|
9259
10320
|
if (!toCubic) {
|
|
9260
10321
|
pathDataTest = [
|
|
9261
10322
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9262
10323
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
9263
|
-
]
|
|
10324
|
+
]
|
|
9264
10325
|
}
|
|
9265
10326
|
// cubics
|
|
9266
10327
|
else {
|
|
9267
10328
|
pathDataTest = [
|
|
9268
10329
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9269
10330
|
...bezierCommands
|
|
9270
|
-
]
|
|
10331
|
+
]
|
|
10332
|
+
|
|
9271
10333
|
}
|
|
9272
10334
|
|
|
9273
10335
|
let d = pathDataToD(pathDataTest);
|
|
9274
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
10336
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
9275
10337
|
}
|
|
10338
|
+
*/
|
|
9276
10339
|
|
|
9277
10340
|
pathDataN.push(com);
|
|
9278
10341
|
i++;
|
|
@@ -9404,7 +10467,6 @@ function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
|
9404
10467
|
let com = pathData[c];
|
|
9405
10468
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
9406
10469
|
if (type === 'C') {
|
|
9407
|
-
|
|
9408
10470
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
9409
10471
|
if (comQ.type === 'Q') {
|
|
9410
10472
|
comQ.extreme = com.extreme;
|
|
@@ -11016,32 +12078,24 @@ function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
11016
12078
|
}
|
|
11017
12079
|
|
|
11018
12080
|
// reverse paths
|
|
11019
|
-
for (let i = 0; i < l; i++) {
|
|
12081
|
+
for (let i = 0; l && i < l; i++) {
|
|
11020
12082
|
|
|
11021
12083
|
let poly = polys[i];
|
|
11022
12084
|
let { cw, includedIn, includes } = poly;
|
|
11023
12085
|
|
|
11024
|
-
|
|
11025
|
-
if (!includedIn.length && cw && !toClockwise
|
|
11026
|
-
|| !includedIn.length && !cw && toClockwise
|
|
11027
|
-
) {
|
|
11028
|
-
|
|
11029
|
-
pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
|
|
11030
|
-
polys[i].cw = polys[i].cw ? false : true;
|
|
11031
|
-
cw = polys[i].cw;
|
|
11032
|
-
|
|
11033
|
-
}
|
|
12086
|
+
let len = includes.length;
|
|
11034
12087
|
|
|
11035
12088
|
// reverse inner sub paths
|
|
11036
|
-
for (let j = 0; j <
|
|
12089
|
+
for (let j = 0; len && j < len; j++) {
|
|
11037
12090
|
let ind = includes[j];
|
|
11038
12091
|
let child = polys[ind];
|
|
11039
12092
|
|
|
11040
|
-
|
|
12093
|
+
// nothing to do
|
|
12094
|
+
if (child.cw !== cw) continue
|
|
12095
|
+
|
|
12096
|
+
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
12097
|
+
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11041
12098
|
|
|
11042
|
-
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
11043
|
-
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11044
|
-
}
|
|
11045
12099
|
}
|
|
11046
12100
|
}
|
|
11047
12101
|
|
|
@@ -11081,6 +12135,7 @@ let settingsDefaults = {
|
|
|
11081
12135
|
allowAriaAtts: true,
|
|
11082
12136
|
|
|
11083
12137
|
convertPathLength: false,
|
|
12138
|
+
toAbsoluteUnits: false,
|
|
11084
12139
|
|
|
11085
12140
|
// custom removal
|
|
11086
12141
|
removeElements: [],
|
|
@@ -11109,6 +12164,7 @@ let settingsDefaults = {
|
|
|
11109
12164
|
revertToQuadratics: true,
|
|
11110
12165
|
refineExtremes: false,
|
|
11111
12166
|
simplifyCorners: false,
|
|
12167
|
+
simplifyQuadraticCorners: false,
|
|
11112
12168
|
keepExtremes: true,
|
|
11113
12169
|
keepCorners: true,
|
|
11114
12170
|
keepInflections: false,
|
|
@@ -11165,7 +12221,7 @@ for (let prop in settingsDefaults) {
|
|
|
11165
12221
|
let isArray = Array.isArray(val);
|
|
11166
12222
|
|
|
11167
12223
|
if (isBoolean) val = false;
|
|
11168
|
-
else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
|
|
12224
|
+
else if (!isArray && isNum) val = val === 1 ? 1 : (prop === 'decimals' ? -1 : 0);
|
|
11169
12225
|
else if (isArray) val = [];
|
|
11170
12226
|
settingsNull[prop] = val;
|
|
11171
12227
|
}
|
|
@@ -11196,10 +12252,14 @@ const presetSettings = {
|
|
|
11196
12252
|
...settingsDefaults,
|
|
11197
12253
|
...{
|
|
11198
12254
|
keepSmaller: false,
|
|
12255
|
+
convertPathLength:true,
|
|
11199
12256
|
toRelative: true,
|
|
11200
12257
|
toMixed: true,
|
|
11201
12258
|
toShorthands: true,
|
|
11202
12259
|
|
|
12260
|
+
allowMeta:true,
|
|
12261
|
+
allowDataAtts:true,
|
|
12262
|
+
allowAriaAtts:true,
|
|
11203
12263
|
legacyHref: true,
|
|
11204
12264
|
addViewBox: true,
|
|
11205
12265
|
addDimensions: true,
|
|
@@ -11267,19 +12327,24 @@ const presetSettings = {
|
|
|
11267
12327
|
high: {
|
|
11268
12328
|
...settingsDefaults,
|
|
11269
12329
|
...{
|
|
11270
|
-
tolerance: 1.
|
|
12330
|
+
tolerance: 1.1,
|
|
11271
12331
|
toMixed: true,
|
|
11272
12332
|
refineExtremes: true,
|
|
11273
12333
|
simplifyCorners: true,
|
|
12334
|
+
simplifyQuadraticCorners: true,
|
|
12335
|
+
removeOrphanSubpaths: true,
|
|
11274
12336
|
simplifyRound: true,
|
|
11275
12337
|
removeClassNames: true,
|
|
11276
12338
|
cubicToArc: true,
|
|
12339
|
+
minifyD: 0,
|
|
11277
12340
|
removeComments: true,
|
|
11278
12341
|
removeHidden: true,
|
|
11279
|
-
removeOffCanvas: true,
|
|
11280
12342
|
addViewBox: true,
|
|
11281
12343
|
removeDimensions: true,
|
|
11282
|
-
|
|
12344
|
+
removeOffCanvas: true,
|
|
12345
|
+
|
|
12346
|
+
/*
|
|
12347
|
+
*/
|
|
11283
12348
|
}
|
|
11284
12349
|
}
|
|
11285
12350
|
|
|
@@ -11437,18 +12502,44 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11437
12502
|
...settings
|
|
11438
12503
|
};
|
|
11439
12504
|
|
|
11440
|
-
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,
|
|
12505
|
+
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;
|
|
11441
12506
|
|
|
11442
12507
|
// clamp tolerance and scale
|
|
11443
12508
|
tolerance = Math.max(0.1, tolerance);
|
|
11444
12509
|
scale = Math.max(0.001, scale);
|
|
11445
|
-
if(fixDirections) keepSmaller = false;
|
|
12510
|
+
if (fixDirections) keepSmaller = false;
|
|
11446
12511
|
if (scale !== 1 || scaleTo || crop || alignToOrigin) {
|
|
11447
12512
|
convertTransforms = true;
|
|
11448
12513
|
settings.convertTransforms = true;
|
|
11449
12514
|
}
|
|
11450
12515
|
|
|
11451
|
-
|
|
12516
|
+
/**
|
|
12517
|
+
* intercept
|
|
12518
|
+
* invalid inputs
|
|
12519
|
+
*/
|
|
12520
|
+
|
|
12521
|
+
let inputDetection = detectInputType(input);
|
|
12522
|
+
let { inputType, log } = inputDetection;
|
|
12523
|
+
|
|
12524
|
+
// invalid file
|
|
12525
|
+
if (inputType === 'invalid' || input === dummySVG) {
|
|
12526
|
+
// return dummy SVG to continue processing
|
|
12527
|
+
|
|
12528
|
+
let report = {
|
|
12529
|
+
original: 0,
|
|
12530
|
+
new: 0,
|
|
12531
|
+
saved: 0,
|
|
12532
|
+
svgSize:0,
|
|
12533
|
+
svgSizeOpt:0,
|
|
12534
|
+
compression:0,
|
|
12535
|
+
decimals:0,
|
|
12536
|
+
invalid:true
|
|
12537
|
+
};
|
|
12538
|
+
|
|
12539
|
+
return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
|
|
12540
|
+
|
|
12541
|
+
}
|
|
12542
|
+
|
|
11452
12543
|
let svg = '';
|
|
11453
12544
|
let svgSize = 0;
|
|
11454
12545
|
let svgSizeOpt = 0;
|
|
@@ -11568,10 +12659,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11568
12659
|
// convert all shapes to paths
|
|
11569
12660
|
if (shapesToPaths) {
|
|
11570
12661
|
shapeConvert = 'toPaths';
|
|
11571
|
-
|
|
11572
|
-
convert_ellipses = true;
|
|
11573
|
-
convert_poly = true;
|
|
11574
|
-
convert_lines = true;
|
|
12662
|
+
convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse'];
|
|
11575
12663
|
}
|
|
11576
12664
|
|
|
11577
12665
|
// sanitize SVG - clone/decouple settings
|
|
@@ -11605,6 +12693,9 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11605
12693
|
decimals,
|
|
11606
12694
|
};
|
|
11607
12695
|
|
|
12696
|
+
let comCount = 0;
|
|
12697
|
+
let comCountS = 0;
|
|
12698
|
+
|
|
11608
12699
|
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
11609
12700
|
|
|
11610
12701
|
let pathDataPlusArr = [];
|
|
@@ -11649,7 +12740,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11649
12740
|
}
|
|
11650
12741
|
|
|
11651
12742
|
// count commands for evaluation
|
|
11652
|
-
|
|
12743
|
+
comCount += pathData.length;
|
|
11653
12744
|
|
|
11654
12745
|
if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
11655
12746
|
|
|
@@ -11814,11 +12905,14 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11814
12905
|
if (simplifyCorners) {
|
|
11815
12906
|
|
|
11816
12907
|
let threshold = (bb.width + bb.height) * 0.1;
|
|
11817
|
-
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
12908
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners });
|
|
11818
12909
|
}
|
|
11819
12910
|
|
|
11820
12911
|
// refine round segment sequences
|
|
11821
|
-
if (simplifyRound)
|
|
12912
|
+
if (simplifyRound) {
|
|
12913
|
+
pathData = refineRoundSegments(pathData);
|
|
12914
|
+
pathData = simplifyAdjacentRound(pathData);
|
|
12915
|
+
}
|
|
11822
12916
|
|
|
11823
12917
|
// simplify to quadratics
|
|
11824
12918
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
@@ -11848,7 +12942,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11848
12942
|
let yMax = Math.max(...yArr);
|
|
11849
12943
|
|
|
11850
12944
|
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
11851
|
-
|
|
12945
|
+
bb_global.height > bb_global.width;
|
|
11852
12946
|
|
|
11853
12947
|
// fix path directions - before reordering
|
|
11854
12948
|
if (fixDirections) {
|
|
@@ -11857,7 +12951,23 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11857
12951
|
|
|
11858
12952
|
// prefer top to bottom priority for portrait aspect ratios
|
|
11859
12953
|
if (optimizeOrder) {
|
|
11860
|
-
|
|
12954
|
+
/*
|
|
12955
|
+
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)
|
|
12956
|
+
*/
|
|
12957
|
+
|
|
12958
|
+
// add missin bbox
|
|
12959
|
+
pathDataPlusArr.forEach(p => {
|
|
12960
|
+
if (p.bb.x === undefined) {
|
|
12961
|
+
p.bb = getPolyBBox(getPathDataVertices(p.pathData));
|
|
12962
|
+
}
|
|
12963
|
+
});
|
|
12964
|
+
|
|
12965
|
+
try {
|
|
12966
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
|
|
12967
|
+
|
|
12968
|
+
} catch {
|
|
12969
|
+
}
|
|
12970
|
+
|
|
11861
12971
|
}
|
|
11862
12972
|
|
|
11863
12973
|
// flatten compound paths
|
|
@@ -11920,7 +13030,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11920
13030
|
}
|
|
11921
13031
|
|
|
11922
13032
|
// compare command count
|
|
11923
|
-
|
|
13033
|
+
comCountS += pathData.length;
|
|
11924
13034
|
|
|
11925
13035
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
11926
13036
|
|
|
@@ -11995,6 +13105,9 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11995
13105
|
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
|
|
11996
13106
|
|
|
11997
13107
|
report = {
|
|
13108
|
+
original: comCount,
|
|
13109
|
+
new: comCountS,
|
|
13110
|
+
saved: comCount - comCountS,
|
|
11998
13111
|
svgSize,
|
|
11999
13112
|
svgSizeOpt,
|
|
12000
13113
|
compression,
|
|
@@ -12029,9 +13142,11 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12029
13142
|
if (typeof window !== 'undefined') {
|
|
12030
13143
|
window.svgPathSimplify = svgPathSimplify;
|
|
12031
13144
|
window.getElementTransform = getElementTransform;
|
|
13145
|
+
window.validateSVG = validateSVG;
|
|
13146
|
+
window.detectInputType = detectInputType;
|
|
12032
13147
|
|
|
12033
13148
|
window.getViewBox = getViewBox;
|
|
12034
13149
|
|
|
12035
13150
|
}
|
|
12036
13151
|
|
|
12037
|
-
export { PI$1 as PI, abs$1 as abs, acos$1 as acos, asin$1 as asin, atan$1 as atan, atan2$1 as atan2, ceil$1 as ceil, cos$1 as cos, exp$1 as exp, floor$1 as floor, getElementTransform, getViewBox, hypot, log$1 as log, max$1 as max, min$1 as min, pow$1 as pow, random$1 as random, round$1 as round, sin$1 as sin, sqrt$1 as sqrt, svgPathSimplify, tan$1 as tan };
|
|
13152
|
+
export { PI$1 as PI, abs$1 as abs, acos$1 as acos, asin$1 as asin, atan$1 as atan, atan2$1 as atan2, ceil$1 as ceil, cos$1 as cos, detectInputType, exp$1 as exp, floor$1 as floor, getElementTransform, getViewBox, hypot, log$1 as log, max$1 as max, min$1 as min, pow$1 as pow, random$1 as random, round$1 as round, sin$1 as sin, sqrt$1 as sqrt, svgPathSimplify, tan$1 as tan, validateSVG };
|