svg-path-simplify 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -1
- package/dist/svg-path-simplify.esm.js +1670 -509
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +1671 -508
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +936 -463
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/index.html +60 -20
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/detect_input.js +47 -29
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +46 -18
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +81 -20
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +14 -4
- package/src/svg-getAttributes.js +5 -3
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +140 -2
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +18 -2
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +188 -88
- package/src/svgii/pathData_fix_directions.js +10 -18
- package/src/svgii/pathData_reorder.js +123 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/poly_normalize.js +9 -8
- package/src/svgii/rounding.js +112 -80
- package/src/svgii/svg_cleanup.js +75 -22
- package/src/svgii/svg_cleanup_convertPathLength.js +27 -15
- package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +6 -1
- package/src/svgii/svg_el_parse_style_props.js +13 -10
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
|
@@ -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);
|
|
@@ -1659,29 +1912,71 @@ function detectAccuracy(pathData) {
|
|
|
1659
1912
|
|
|
1660
1913
|
dimA = dimA ? dimA : getDistManhattan(p0, p);
|
|
1661
1914
|
|
|
1662
|
-
if (dimA) dims.push(dimA);
|
|
1915
|
+
if (dimA) dims.push(+dimA.toFixed(8));
|
|
1663
1916
|
|
|
1664
1917
|
}
|
|
1665
1918
|
|
|
1666
1919
|
}
|
|
1667
1920
|
|
|
1668
|
-
|
|
1921
|
+
dims = dims.sort();
|
|
1922
|
+
let len = dims.length;
|
|
1923
|
+
let dim_mid = dims[Math.floor(len*0.5)];
|
|
1924
|
+
|
|
1925
|
+
// smallest 25% of values
|
|
1926
|
+
let idx_q = Math.ceil(len*0.25);
|
|
1927
|
+
let dims_min = dims.slice(0, idx_q);
|
|
1928
|
+
|
|
1929
|
+
// average smallest values with mid value
|
|
1930
|
+
let dim_min = ((dims_min.reduce((a, b) => a + b, 0) / idx_q) + dim_mid) * 0.5;
|
|
1931
|
+
|
|
1932
|
+
let threshold = 75;
|
|
1933
|
+
let decimalsAuto = dim_min > threshold * 1.5 ? 0 : Math.floor(threshold / dim_min).toString().length;
|
|
1669
1934
|
|
|
1670
|
-
|
|
1935
|
+
// clamp
|
|
1936
|
+
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1937
|
+
|
|
1938
|
+
/*
|
|
1939
|
+
let dim_min = dims.sort()
|
|
1940
|
+
|
|
1941
|
+
let dim_mid = dim_min[Math.floor(dim_min.length*0.5)]
|
|
1942
|
+
|
|
1943
|
+
let sliceIdx = Math.ceil(dim_min.length / 4);
|
|
1671
1944
|
dim_min = dim_min.slice(0, sliceIdx);
|
|
1672
1945
|
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
1673
1946
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1947
|
+
// average with mid value
|
|
1948
|
+
minVal = (minVal+dim_mid)*0.5
|
|
1949
|
+
|
|
1950
|
+
let threshold = 75
|
|
1951
|
+
let decimalsAuto = minVal > threshold * 1.5 ? 0 : Math.floor(threshold / minVal).toString().length
|
|
1676
1952
|
|
|
1677
1953
|
// clamp
|
|
1678
1954
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
1955
|
+
*/
|
|
1679
1956
|
|
|
1680
1957
|
}
|
|
1681
1958
|
|
|
1959
|
+
/**
|
|
1960
|
+
* rounding helper
|
|
1961
|
+
* allows for quantized rounding
|
|
1962
|
+
* e.g 0.5 decimals s
|
|
1963
|
+
*/
|
|
1682
1964
|
function roundTo(num = 0, decimals = 3) {
|
|
1683
|
-
if(decimals
|
|
1965
|
+
if (decimals < 0) return num;
|
|
1966
|
+
// Normal integer rounding
|
|
1684
1967
|
if (!decimals) return Math.round(num);
|
|
1968
|
+
|
|
1969
|
+
// stepped rounding
|
|
1970
|
+
let intPart = Math.floor(decimals);
|
|
1971
|
+
|
|
1972
|
+
if (intPart !== decimals) {
|
|
1973
|
+
let f = +(decimals - intPart).toFixed(2);
|
|
1974
|
+
f = f > 0.5 ? (Math.floor((f) / 0.5) * 0.5) : f;
|
|
1975
|
+
|
|
1976
|
+
let step = 10 ** -intPart * f;
|
|
1977
|
+
return +(Math.round(num / step) * step).toFixed(8);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1685
1980
|
let factor = 10 ** decimals;
|
|
1686
1981
|
return Math.round(num * factor) / factor;
|
|
1687
1982
|
}
|
|
@@ -1691,50 +1986,21 @@ function roundTo(num = 0, decimals = 3) {
|
|
|
1691
1986
|
* floating point accuracy
|
|
1692
1987
|
* based on numeric value
|
|
1693
1988
|
*/
|
|
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;
|
|
1989
|
+
function autoRound(val, integerThresh = 50) {
|
|
1990
|
+
let decimals = 8;
|
|
1722
1991
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
if (!valLen) continue
|
|
1992
|
+
if (val > integerThresh * 2) {
|
|
1993
|
+
decimals = 0;
|
|
1994
|
+
}
|
|
1995
|
+
else if (val > integerThresh) {
|
|
1996
|
+
decimals = 1;
|
|
1997
|
+
} else {
|
|
1998
|
+
decimals = Math.ceil(500 / val).toString().length;
|
|
1731
1999
|
|
|
1732
|
-
for (let v = 0; v < valLen; v++) {
|
|
1733
|
-
pathData[c].values[v] = roundTo(values[v], decimals);
|
|
1734
|
-
}
|
|
1735
2000
|
}
|
|
1736
2001
|
|
|
1737
|
-
|
|
2002
|
+
let factor = 10 ** decimals;
|
|
2003
|
+
return Math.round(val * factor) / factor;
|
|
1738
2004
|
}
|
|
1739
2005
|
|
|
1740
2006
|
/**
|
|
@@ -2176,7 +2442,7 @@ function normalizeUnits(value = null, {
|
|
|
2176
2442
|
scale = height / 100;
|
|
2177
2443
|
}
|
|
2178
2444
|
else {
|
|
2179
|
-
scale = normalizedDiagonal ? scaleRoot / 100 :
|
|
2445
|
+
scale = normalizedDiagonal ? scaleRoot / 100 : width / 100;
|
|
2180
2446
|
}
|
|
2181
2447
|
break;
|
|
2182
2448
|
|
|
@@ -2245,12 +2511,14 @@ function isNumericValue(val = '') {
|
|
|
2245
2511
|
}
|
|
2246
2512
|
|
|
2247
2513
|
function getElementAtts(el, {x=0, y=0, width=0, height=0}={}){
|
|
2248
|
-
|
|
2514
|
+
|
|
2515
|
+
let attributes = [...el.attributes].map(att=>att.name);
|
|
2249
2516
|
|
|
2250
2517
|
let atts={};
|
|
2251
2518
|
attributes.forEach(att=>{
|
|
2252
|
-
|
|
2253
|
-
|
|
2519
|
+
|
|
2520
|
+
let value = normalizeUnits(el.getAttribute(att), {x, y, width, height});
|
|
2521
|
+
atts[att] = value;
|
|
2254
2522
|
});
|
|
2255
2523
|
|
|
2256
2524
|
return atts
|
|
@@ -2995,7 +3263,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
2995
3263
|
error += com.error;
|
|
2996
3264
|
|
|
2997
3265
|
// find next candidates
|
|
2998
|
-
for (let n = i +
|
|
3266
|
+
for (let n = i + offset; error < tolerance && n < l; n++) {
|
|
2999
3267
|
let comN = pathData[n];
|
|
3000
3268
|
|
|
3001
3269
|
if (comN.type !== 'C' ||
|
|
@@ -3005,6 +3273,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3005
3273
|
(keepExtremes && com.extreme)
|
|
3006
3274
|
)
|
|
3007
3275
|
) {
|
|
3276
|
+
|
|
3008
3277
|
break
|
|
3009
3278
|
}
|
|
3010
3279
|
|
|
@@ -3012,6 +3281,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3012
3281
|
|
|
3013
3282
|
// failure - could not be combined - exit loop
|
|
3014
3283
|
if (combined.length > 1) {
|
|
3284
|
+
|
|
3015
3285
|
break
|
|
3016
3286
|
}
|
|
3017
3287
|
|
|
@@ -3025,6 +3295,7 @@ function simplifyPathDataCubic(pathData, {
|
|
|
3025
3295
|
|
|
3026
3296
|
// return combined
|
|
3027
3297
|
com = combined[0];
|
|
3298
|
+
|
|
3028
3299
|
}
|
|
3029
3300
|
|
|
3030
3301
|
pathDataN.push(com);
|
|
@@ -3074,9 +3345,9 @@ function combineCubicPairs(com1, com2, {
|
|
|
3074
3345
|
let comS = getExtrapolatedCommand(com1, com2, t);
|
|
3075
3346
|
|
|
3076
3347
|
// test new point-at-t against original mid segment starting point
|
|
3077
|
-
let
|
|
3348
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t);
|
|
3078
3349
|
|
|
3079
|
-
let dist0 = getDistManhattan(com1.p,
|
|
3350
|
+
let dist0 = getDistManhattan(com1.p, ptI);
|
|
3080
3351
|
let dist1 = 0, dist2 = 0;
|
|
3081
3352
|
let close = dist0 < maxDist;
|
|
3082
3353
|
let success = false;
|
|
@@ -3091,29 +3362,40 @@ function combineCubicPairs(com1, com2, {
|
|
|
3091
3362
|
* to prevent distortions
|
|
3092
3363
|
*/
|
|
3093
3364
|
|
|
3094
|
-
//
|
|
3095
|
-
let
|
|
3365
|
+
// 1st segment mid
|
|
3366
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5);
|
|
3096
3367
|
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
let
|
|
3100
|
-
dist1 = getDistManhattan(
|
|
3368
|
+
let t2 = t * 0.5;
|
|
3369
|
+
// combined interpolated mid point
|
|
3370
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2);
|
|
3371
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1);
|
|
3101
3372
|
|
|
3102
3373
|
error += dist1;
|
|
3103
3374
|
|
|
3104
3375
|
if (dist1 < maxDist) {
|
|
3105
3376
|
|
|
3106
|
-
//
|
|
3107
|
-
let
|
|
3377
|
+
// 2nd segment mid
|
|
3378
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5);
|
|
3108
3379
|
|
|
3109
|
-
|
|
3110
|
-
let
|
|
3111
|
-
|
|
3380
|
+
// simplified path
|
|
3381
|
+
let t3 = (1 + t) * 0.5;
|
|
3382
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3);
|
|
3383
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2);
|
|
3112
3384
|
|
|
3113
3385
|
error += dist2;
|
|
3114
3386
|
|
|
3115
3387
|
if (error < maxDist) success = true;
|
|
3116
3388
|
|
|
3389
|
+
/*
|
|
3390
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
3391
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
3392
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
3393
|
+
|
|
3394
|
+
renderPoint(markers, com1.p, 'green')
|
|
3395
|
+
|
|
3396
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
3397
|
+
*/
|
|
3398
|
+
|
|
3117
3399
|
}
|
|
3118
3400
|
|
|
3119
3401
|
} // end 1st try
|
|
@@ -3127,11 +3409,19 @@ function combineCubicPairs(com1, com2, {
|
|
|
3127
3409
|
|
|
3128
3410
|
comS.dimA = getDistManhattan(comS.p0, comS.p);
|
|
3129
3411
|
comS.type = 'C';
|
|
3412
|
+
|
|
3130
3413
|
comS.extreme = com2.extreme;
|
|
3131
3414
|
comS.directionChange = com2.directionChange;
|
|
3132
|
-
|
|
3133
3415
|
comS.corner = com2.corner;
|
|
3134
3416
|
|
|
3417
|
+
if (comS.extreme || comS.corner) ;
|
|
3418
|
+
|
|
3419
|
+
/*
|
|
3420
|
+
comS.extreme = com1.extreme;
|
|
3421
|
+
comS.directionChange = com1.directionChange;
|
|
3422
|
+
comS.corner = com1.corner;
|
|
3423
|
+
*/
|
|
3424
|
+
|
|
3135
3425
|
comS.values = [comS.cp1.x, comS.cp1.y, comS.cp2.x, comS.cp2.y, comS.p.x, comS.p.y];
|
|
3136
3426
|
|
|
3137
3427
|
// relative error
|
|
@@ -3278,6 +3568,7 @@ function analyzePathData(pathData = [], {
|
|
|
3278
3568
|
let com = pathData[c - 1];
|
|
3279
3569
|
let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
|
|
3280
3570
|
|
|
3571
|
+
let comPrev = pathData[c-2];
|
|
3281
3572
|
let comN = pathData[c] || null;
|
|
3282
3573
|
|
|
3283
3574
|
// init properties
|
|
@@ -3296,6 +3587,7 @@ function analyzePathData(pathData = [], {
|
|
|
3296
3587
|
|
|
3297
3588
|
// bezier types
|
|
3298
3589
|
let isBezier = type === 'Q' || type === 'C';
|
|
3590
|
+
let isArc = type === 'A';
|
|
3299
3591
|
let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
|
|
3300
3592
|
|
|
3301
3593
|
/**
|
|
@@ -3342,6 +3634,22 @@ function analyzePathData(pathData = [], {
|
|
|
3342
3634
|
}
|
|
3343
3635
|
}
|
|
3344
3636
|
|
|
3637
|
+
// check extremes introduce by small arcs
|
|
3638
|
+
else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
|
|
3639
|
+
let distN = comN ? comN.dimA : 0;
|
|
3640
|
+
let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
|
|
3641
|
+
let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1);
|
|
3642
|
+
|
|
3643
|
+
if(isShort && smallRadius){
|
|
3644
|
+
let bb = getPolyBBox([comPrev.p0, comN.p]);
|
|
3645
|
+
if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
|
|
3646
|
+
hasExtremes = true;
|
|
3647
|
+
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3345
3653
|
if (hasExtremes) com.extreme = true;
|
|
3346
3654
|
|
|
3347
3655
|
// Corners and semi extremes
|
|
@@ -3899,50 +4207,10 @@ function stringifyPathData(pathData) {
|
|
|
3899
4207
|
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
3900
4208
|
}
|
|
3901
4209
|
|
|
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
|
-
|
|
4210
|
+
/**
|
|
4211
|
+
* wrapper function for
|
|
4212
|
+
* all path data conversion
|
|
4213
|
+
*/
|
|
3946
4214
|
function convertPathData(pathData, {
|
|
3947
4215
|
toShorthands = true,
|
|
3948
4216
|
toLonghands = false,
|
|
@@ -3994,22 +4262,24 @@ function convertPathData(pathData, {
|
|
|
3994
4262
|
|
|
3995
4263
|
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
3996
4264
|
|
|
3997
|
-
if(toMixed) toRelative = true;
|
|
4265
|
+
if (toMixed) toRelative = true;
|
|
3998
4266
|
|
|
3999
4267
|
// pre round - before relative conversion to minimize distortions
|
|
4000
4268
|
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
4001
4269
|
|
|
4002
4270
|
// clone absolute pathdata
|
|
4003
|
-
if(toMixed){
|
|
4271
|
+
if (toMixed) {
|
|
4004
4272
|
pathDataAbs = JSON.parse(JSON.stringify(pathData));
|
|
4005
4273
|
}
|
|
4006
4274
|
|
|
4007
4275
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
4276
|
+
|
|
4277
|
+
// final rounding
|
|
4008
4278
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
4009
4279
|
|
|
4010
4280
|
// choose most compact commands: relative or absolute
|
|
4011
|
-
if(toMixed){
|
|
4012
|
-
for(let i=0; i<pathData.length; i++){
|
|
4281
|
+
if (toMixed) {
|
|
4282
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
4013
4283
|
let com = pathData[i];
|
|
4014
4284
|
let comA = pathDataAbs[i];
|
|
4015
4285
|
// compare Lengths
|
|
@@ -4019,7 +4289,7 @@ function convertPathData(pathData, {
|
|
|
4019
4289
|
let lenR = comStr.length;
|
|
4020
4290
|
let lenA = comStrA.length;
|
|
4021
4291
|
|
|
4022
|
-
if(lenA<lenR){
|
|
4292
|
+
if (lenA < lenR) {
|
|
4023
4293
|
|
|
4024
4294
|
pathData[i] = pathDataAbs[i];
|
|
4025
4295
|
}
|
|
@@ -4029,56 +4299,140 @@ function convertPathData(pathData, {
|
|
|
4029
4299
|
return pathData
|
|
4030
4300
|
}
|
|
4031
4301
|
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4302
|
+
function parsePathDataNormalized(d,
|
|
4303
|
+
{
|
|
4304
|
+
// necessary for most calculations
|
|
4305
|
+
toAbsolute = true,
|
|
4306
|
+
toLonghands = true,
|
|
4307
|
+
|
|
4308
|
+
// not necessary unless you need cubics only
|
|
4309
|
+
quadraticToCubic = false,
|
|
4310
|
+
|
|
4311
|
+
// mostly a fallback if arc calculations fail
|
|
4312
|
+
arcToCubic = false,
|
|
4313
|
+
// arc to cubic precision - adds more segments for better precision
|
|
4314
|
+
arcAccuracy = 4,
|
|
4315
|
+
} = {}
|
|
4316
|
+
) {
|
|
4317
|
+
|
|
4318
|
+
// is already array
|
|
4319
|
+
let isArray = Array.isArray(d);
|
|
4320
|
+
|
|
4321
|
+
// normalize native pathData to regular array
|
|
4322
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function';
|
|
4323
|
+
/*
|
|
4324
|
+
if (hasConstructor) {
|
|
4325
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
4326
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
4327
|
+
}
|
|
4328
|
+
*/
|
|
4329
|
+
|
|
4330
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
4331
|
+
|
|
4332
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
4333
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
4334
|
+
|
|
4335
|
+
// normalize
|
|
4336
|
+
pathData = normalizePathData(pathData,
|
|
4337
|
+
{
|
|
4338
|
+
toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
4339
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
4340
|
+
},
|
|
4341
|
+
);
|
|
4342
|
+
|
|
4343
|
+
return pathData;
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
/**
|
|
4347
|
+
*
|
|
4348
|
+
* @param {*} pathData
|
|
4349
|
+
* @returns
|
|
4036
4350
|
*/
|
|
4037
4351
|
|
|
4038
4352
|
function optimizeArcPathData(pathData = []) {
|
|
4353
|
+
let l = pathData.length;
|
|
4354
|
+
let pathDataN = [];
|
|
4039
4355
|
|
|
4040
|
-
let
|
|
4041
|
-
|
|
4042
|
-
pathData.forEach((com, i) => {
|
|
4356
|
+
for (let i = 0; i < l; i++) {
|
|
4357
|
+
let com = pathData[i];
|
|
4043
4358
|
let { type, values } = com;
|
|
4044
|
-
if (type === 'A') {
|
|
4045
|
-
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4046
|
-
let comPrev = pathData[i - 1];
|
|
4047
|
-
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4048
|
-
let M = { x: x0, y: y0 };
|
|
4049
|
-
let p = { x, y };
|
|
4050
4359
|
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4360
|
+
if (type !== 'A') {
|
|
4361
|
+
pathDataN.push(com);
|
|
4362
|
+
continue
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
|
|
4366
|
+
let comPrev = pathData[i - 1];
|
|
4367
|
+
let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
|
|
4368
|
+
let M = { x: x0, y: y0 };
|
|
4369
|
+
let p = { x, y };
|
|
4054
4370
|
|
|
4055
|
-
|
|
4371
|
+
if (rx === 0 || ry === 0) {
|
|
4372
|
+
pathData[i] = null;
|
|
4373
|
+
}
|
|
4056
4374
|
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4375
|
+
// test for elliptic
|
|
4376
|
+
let rat = rx / ry;
|
|
4377
|
+
let error = rx !== ry ? Math.abs(1 - rat) : 0;
|
|
4060
4378
|
|
|
4061
|
-
|
|
4062
|
-
if (diff < 0.01) {
|
|
4379
|
+
if (error > 0.01) {
|
|
4063
4380
|
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
let distM = getDistance(pMid, M);
|
|
4067
|
-
let rDiff = Math.abs(distM - rx) / rx;
|
|
4381
|
+
pathDataN.push(com);
|
|
4382
|
+
continue
|
|
4068
4383
|
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
// xAxis rotation is futile for circular arcs - reset
|
|
4387
|
+
com.values[2] = 0;
|
|
4388
|
+
|
|
4389
|
+
/**
|
|
4390
|
+
* test semi circles
|
|
4391
|
+
* rx and ry are large enough
|
|
4392
|
+
*/
|
|
4393
|
+
|
|
4394
|
+
// 1. horizontal or vertical
|
|
4395
|
+
let thresh = getDistManhattan(M, p) * 0.001;
|
|
4396
|
+
let diffX = Math.abs(x - x0);
|
|
4397
|
+
let diffY = Math.abs(y - y0);
|
|
4398
|
+
|
|
4399
|
+
let isHorizontal = diffY < thresh;
|
|
4400
|
+
let isVertical = diffX < thresh;
|
|
4401
|
+
|
|
4402
|
+
// minify rx and ry
|
|
4403
|
+
if (isHorizontal || isVertical) {
|
|
4404
|
+
|
|
4405
|
+
// check if semi circle
|
|
4406
|
+
let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
|
|
4407
|
+
|
|
4408
|
+
// is semicircle we can simplify rx
|
|
4409
|
+
if (!needsTrueR) {
|
|
4410
|
+
|
|
4411
|
+
rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
|
|
4076
4412
|
}
|
|
4413
|
+
|
|
4414
|
+
com.values[0] = rx;
|
|
4415
|
+
com.values[1] = rx;
|
|
4416
|
+
pathDataN.push(com);
|
|
4417
|
+
continue
|
|
4418
|
+
|
|
4077
4419
|
}
|
|
4078
|
-
});
|
|
4079
4420
|
|
|
4080
|
-
|
|
4081
|
-
|
|
4421
|
+
// 2. get true radius - if rx ~= diameter/distance we have a semicircle
|
|
4422
|
+
let r = getDistance(M, p) * 0.5;
|
|
4423
|
+
error = rx / r;
|
|
4424
|
+
|
|
4425
|
+
if (error < 0.5) {
|
|
4426
|
+
rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
com.values[0] = rx;
|
|
4430
|
+
com.values[1] = rx;
|
|
4431
|
+
pathDataN.push(com);
|
|
4432
|
+
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
return pathDataN;
|
|
4082
4436
|
}
|
|
4083
4437
|
|
|
4084
4438
|
/**
|
|
@@ -4143,6 +4497,44 @@ export function normalizePathData(pathData = [],
|
|
|
4143
4497
|
}
|
|
4144
4498
|
*/
|
|
4145
4499
|
|
|
4500
|
+
function convertSmallArcsToLinetos(pathData) {
|
|
4501
|
+
|
|
4502
|
+
let l = pathData.length;
|
|
4503
|
+
|
|
4504
|
+
// add fist command
|
|
4505
|
+
let pathDataN = [pathData[0]];
|
|
4506
|
+
|
|
4507
|
+
for (let i = 1; i < l; i++) {
|
|
4508
|
+
let com = pathData[i];
|
|
4509
|
+
let comPrev = pathData[i - 1];
|
|
4510
|
+
let comN = pathData[i + 1] || null;
|
|
4511
|
+
|
|
4512
|
+
if (!comN) {
|
|
4513
|
+
pathDataN.push(com);
|
|
4514
|
+
break
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
let { type, values, extreme = false, p0, p, dimA = 0 } = com;
|
|
4518
|
+
// for short segment detection
|
|
4519
|
+
let dimAN = comN.dimA;
|
|
4520
|
+
let dimA0 = comPrev.dimA + dimA + dimAN;
|
|
4521
|
+
let thresh = 0.05;
|
|
4522
|
+
let isShort = dimA < dimA0 * thresh;
|
|
4523
|
+
|
|
4524
|
+
if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
|
|
4525
|
+
|
|
4526
|
+
com.type = 'L';
|
|
4527
|
+
com.values = [p.x, p.y];
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
pathDataN.push(com);
|
|
4531
|
+
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
return pathDataN;
|
|
4535
|
+
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4146
4538
|
function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}, tolerance = 1) {
|
|
4147
4539
|
|
|
4148
4540
|
// test if cubic can be simplified to quadratic
|
|
@@ -6198,7 +6590,8 @@ function pathDataToTopLeft(pathData) {
|
|
|
6198
6590
|
let { type, values } = com;
|
|
6199
6591
|
let valsLen = values.length;
|
|
6200
6592
|
if (valsLen) {
|
|
6201
|
-
|
|
6593
|
+
// we need rounding otherwise sorting may crash due to e notation
|
|
6594
|
+
let p = { type: type, x: +values[valsLen - 2].toFixed(8), y: +values[valsLen - 1].toFixed(8), index: 0 };
|
|
6202
6595
|
p.index = i;
|
|
6203
6596
|
indices.push(p);
|
|
6204
6597
|
}
|
|
@@ -6206,113 +6599,111 @@ function pathDataToTopLeft(pathData) {
|
|
|
6206
6599
|
|
|
6207
6600
|
// reorder to top left most
|
|
6208
6601
|
|
|
6209
|
-
indices = indices.sort((a, b) =>
|
|
6602
|
+
indices = indices.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
6210
6603
|
newIndex = indices[0].index;
|
|
6211
6604
|
|
|
6212
|
-
return
|
|
6605
|
+
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
6213
6606
|
}
|
|
6214
6607
|
|
|
6215
|
-
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
6608
|
+
function optimizeClosePath(pathData, { removeFinalLineto = true, autoClose = true } = {}) {
|
|
6216
6609
|
|
|
6217
|
-
let
|
|
6610
|
+
let pathDataN = pathData;
|
|
6218
6611
|
let l = pathData.length;
|
|
6219
6612
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
6220
6613
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
6221
6614
|
|
|
6222
|
-
let
|
|
6223
|
-
|
|
6224
|
-
// check if order is ideal
|
|
6225
|
-
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
6615
|
+
let hasLinetos = false;
|
|
6226
6616
|
|
|
6617
|
+
// check if path is closed by explicit lineto
|
|
6618
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6227
6619
|
let penultimateCom = pathData[idxPenultimate];
|
|
6228
6620
|
let penultimateType = penultimateCom.type;
|
|
6229
6621
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6230
6622
|
|
|
6231
6623
|
// last L command ends at M
|
|
6232
|
-
let
|
|
6624
|
+
let hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6625
|
+
let lastIsLine = penultimateType === 'L';
|
|
6233
6626
|
|
|
6234
|
-
//
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
let valsLast = pathData[idxPenultimate].values
|
|
6240
|
-
let valsLastLen = valsLast.length;
|
|
6241
|
-
pathData[idxPenultimate].values[valsLastLen-2] = M.x
|
|
6242
|
-
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
6243
|
-
*/
|
|
6244
|
-
|
|
6245
|
-
pathData.push({type:'Z', values:[]});
|
|
6246
|
-
isClosed = true;
|
|
6247
|
-
l++;
|
|
6248
|
-
}
|
|
6627
|
+
// create index
|
|
6628
|
+
let indices = [];
|
|
6629
|
+
for (let i = 0; i < l; i++) {
|
|
6630
|
+
let com = pathData[i];
|
|
6631
|
+
let { type, values, p0, p } = com;
|
|
6249
6632
|
|
|
6250
|
-
|
|
6251
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
6252
|
-
skipReorder = false;
|
|
6633
|
+
if(type==='L') hasLinetos = true;
|
|
6253
6634
|
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
}
|
|
6635
|
+
// exclude Z
|
|
6636
|
+
if (values.length) {
|
|
6637
|
+
values.slice(-2);
|
|
6258
6638
|
|
|
6259
|
-
|
|
6639
|
+
let x = Math.min(p0.x, p.x);
|
|
6640
|
+
let y = Math.min(p0.y, p.y);
|
|
6260
6641
|
|
|
6261
|
-
|
|
6642
|
+
let prevCom = pathData[i - 1] ? pathData[i - 1] : pathData[idxPenultimate];
|
|
6643
|
+
let prevType = prevCom.type;
|
|
6262
6644
|
|
|
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
|
-
}
|
|
6645
|
+
let item = { type: type, x, y, index: 0, prevType };
|
|
6646
|
+
item.index = i;
|
|
6647
|
+
indices.push(item);
|
|
6277
6648
|
}
|
|
6278
6649
|
|
|
6279
|
-
|
|
6650
|
+
}
|
|
6280
6651
|
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6652
|
+
let xMin = Infinity;
|
|
6653
|
+
let yMin = Infinity;
|
|
6654
|
+
let idx_top = null;
|
|
6655
|
+
let len = indices.length;
|
|
6284
6656
|
|
|
6285
|
-
|
|
6657
|
+
for (let i = 0; i < len; i++) {
|
|
6658
|
+
let com = indices[i];
|
|
6659
|
+
let { type, index, x, y, prevType } = com;
|
|
6286
6660
|
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6661
|
+
if (hasLinetos && prevType === 'L') {
|
|
6662
|
+
if (x < xMin && y < yMin) {
|
|
6663
|
+
idx_top = index-1;
|
|
6664
|
+
}
|
|
6665
|
+
|
|
6666
|
+
if (y < yMin) {
|
|
6667
|
+
yMin = y;
|
|
6668
|
+
}
|
|
6293
6669
|
|
|
6294
|
-
|
|
6295
|
-
|
|
6670
|
+
if (x < xMin) {
|
|
6671
|
+
xMin = x;
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6296
6674
|
}
|
|
6297
6675
|
|
|
6298
|
-
|
|
6676
|
+
// shift to better starting point
|
|
6677
|
+
if (idx_top) {
|
|
6678
|
+
pathDataN = shiftSvgStartingPoint(pathDataN, idx_top);
|
|
6299
6679
|
|
|
6300
|
-
|
|
6680
|
+
// update penultimate - reorder might have added new close paths
|
|
6681
|
+
l = pathDataN.length;
|
|
6682
|
+
M = { x: +pathDataN[0].values[0].toFixed(8), y: +pathDataN[0].values[1].toFixed(8) };
|
|
6683
|
+
|
|
6684
|
+
idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
6685
|
+
penultimateCom = pathDataN[idxPenultimate];
|
|
6686
|
+
penultimateType = penultimateCom.type;
|
|
6687
|
+
penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
6688
|
+
lastIsLine = penultimateType ==='L';
|
|
6301
6689
|
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
penultimateType = penultimateCom.type;
|
|
6305
|
-
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
6690
|
+
// last L command ends at M
|
|
6691
|
+
hasClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
6306
6692
|
|
|
6307
|
-
|
|
6693
|
+
}
|
|
6308
6694
|
|
|
6309
|
-
|
|
6310
|
-
|
|
6695
|
+
// remove unnecessary closing lineto
|
|
6696
|
+
if (removeFinalLineto && hasClosingCommand && lastIsLine) {
|
|
6697
|
+
pathDataN.splice(l - 2, 1);
|
|
6311
6698
|
}
|
|
6312
6699
|
|
|
6313
|
-
|
|
6700
|
+
// add close path
|
|
6701
|
+
if (autoClose && !isClosed && hasClosingCommand) {
|
|
6702
|
+
pathDataN.push({ type: 'Z', values: [] });
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6705
|
+
return pathDataN
|
|
6314
6706
|
|
|
6315
|
-
return pathDataNew
|
|
6316
6707
|
}
|
|
6317
6708
|
|
|
6318
6709
|
/**
|
|
@@ -6744,7 +7135,7 @@ function normalizePoly(pts, {
|
|
|
6744
7135
|
} = {}) {
|
|
6745
7136
|
|
|
6746
7137
|
// is stringified flat point attribute
|
|
6747
|
-
if(typeof pts === 'string' && !isNaN(pts[0])){
|
|
7138
|
+
if (typeof pts === 'string' && !isNaN(pts[0])) {
|
|
6748
7139
|
pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
|
|
6749
7140
|
return pts
|
|
6750
7141
|
}
|
|
@@ -6754,8 +7145,9 @@ function normalizePoly(pts, {
|
|
|
6754
7145
|
return poly
|
|
6755
7146
|
}
|
|
6756
7147
|
|
|
6757
|
-
function polyArrayToObject(pts) {
|
|
7148
|
+
function polyArrayToObject(pts = []) {
|
|
6758
7149
|
|
|
7150
|
+
if (!pts.length) return [];
|
|
6759
7151
|
// is point object array
|
|
6760
7152
|
if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
|
|
6761
7153
|
|
|
@@ -6773,7 +7165,7 @@ function polyArrayToObject(pts) {
|
|
|
6773
7165
|
return poly
|
|
6774
7166
|
}
|
|
6775
7167
|
|
|
6776
|
-
else if(pts.length>3){
|
|
7168
|
+
else if (pts.length > 3) {
|
|
6777
7169
|
pts = toPointArray(pts);
|
|
6778
7170
|
return pts
|
|
6779
7171
|
}
|
|
@@ -6802,13 +7194,13 @@ function polyPtsToArray(pts) {
|
|
|
6802
7194
|
function toPointArray(pts) {
|
|
6803
7195
|
let ptArr = [];
|
|
6804
7196
|
|
|
6805
|
-
if(pts[0].length===2){
|
|
6806
|
-
for (let i = 0, l = pts.length; i < l; i
|
|
7197
|
+
if (pts[0].length === 2) {
|
|
7198
|
+
for (let i = 0, l = pts.length; i < l; i++) {
|
|
6807
7199
|
let pt = pts[i];
|
|
6808
|
-
ptArr.push({ x: pt[0], y:pt[1] });
|
|
7200
|
+
ptArr.push({ x: pt[0], y: pt[1] });
|
|
6809
7201
|
}
|
|
6810
7202
|
|
|
6811
|
-
}else {
|
|
7203
|
+
} else {
|
|
6812
7204
|
for (let i = 1, l = pts.length; i < l; i += 2) {
|
|
6813
7205
|
ptArr.push({ x: pts[i - 1], y: pts[i] });
|
|
6814
7206
|
}
|
|
@@ -6825,7 +7217,7 @@ function getElBBox(el){
|
|
|
6825
7217
|
|
|
6826
7218
|
switch(type){
|
|
6827
7219
|
case 'path':
|
|
6828
|
-
let pathData = parsePathDataNormalized(
|
|
7220
|
+
let pathData = parsePathDataNormalized(el.getAttribute('d'));
|
|
6829
7221
|
bb=getPolyBBox(getPathDataPoly(pathData));
|
|
6830
7222
|
|
|
6831
7223
|
break;
|
|
@@ -6867,8 +7259,8 @@ function parseStylesProperties(el, {
|
|
|
6867
7259
|
autoRoundValues = false,
|
|
6868
7260
|
minifyRgbColors = false,
|
|
6869
7261
|
removeInvalid = true,
|
|
6870
|
-
allowDataAtts=true,
|
|
6871
|
-
allowAriaAtts=true,
|
|
7262
|
+
allowDataAtts = true,
|
|
7263
|
+
allowAriaAtts = true,
|
|
6872
7264
|
removeDefaults = true,
|
|
6873
7265
|
cleanUpStrokes = true,
|
|
6874
7266
|
normalizeTransforms = true,
|
|
@@ -6934,7 +7326,7 @@ function parseStylesProperties(el, {
|
|
|
6934
7326
|
*/
|
|
6935
7327
|
|
|
6936
7328
|
if (removeInvalid || removeDefaults || removeNameSpaced) {
|
|
6937
|
-
let propsFilteredObj = filterSvgElProps(nodeName, props, {allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
7329
|
+
let propsFilteredObj = filterSvgElProps(nodeName, props, { allowDataAtts, allowAriaAtts, removeIds, removeClassNames, removeDefaults, removeNameSpaced, exclude, cleanUpStrokes, include: [...transformsStandalone, ...include], cleanUpStrokes: false });
|
|
6938
7330
|
props = propsFilteredObj.propsFiltered;
|
|
6939
7331
|
remove.push(...propsFilteredObj.remove);
|
|
6940
7332
|
|
|
@@ -7027,10 +7419,10 @@ function parseStylesProperties(el, {
|
|
|
7027
7419
|
|
|
7028
7420
|
if (prop !== 'transforms') {
|
|
7029
7421
|
|
|
7030
|
-
if (
|
|
7422
|
+
if ((prop === 'stroke-dasharray' || prop === 'stroke-dashoffset')) {
|
|
7031
7423
|
normalizedDiagonal = true;
|
|
7032
7424
|
for (let i = 0; i < values.length; i++) {
|
|
7033
|
-
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize });
|
|
7425
|
+
let val = normalizeUnits(values[i].value, { unit: values[i].unit, width, height, normalizedDiagonal, fontSize, autoRoundValues });
|
|
7034
7426
|
valsNew.push(val);
|
|
7035
7427
|
}
|
|
7036
7428
|
}
|
|
@@ -7072,14 +7464,12 @@ function parseStylesProperties(el, {
|
|
|
7072
7464
|
if (prop === 'scale' && unit === '%') {
|
|
7073
7465
|
valAbs = valAbs * 0.01;
|
|
7074
7466
|
} else {
|
|
7075
|
-
if (prop === 'r')
|
|
7467
|
+
if (prop === 'r' && width!==height) normalizedDiagonal = true;
|
|
7076
7468
|
valAbs = normalizeUnits(val.value, { unit, width, height, isHorizontal, isVertical, normalizedDiagonal, fontSize });
|
|
7077
7469
|
|
|
7078
7470
|
if (autoRoundValues && isNumeric) {
|
|
7079
7471
|
valAbs = autoRound(valAbs);
|
|
7080
|
-
|
|
7081
7472
|
}
|
|
7082
|
-
|
|
7083
7473
|
}
|
|
7084
7474
|
}
|
|
7085
7475
|
valsNew.push(valAbs);
|
|
@@ -7280,6 +7670,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7280
7670
|
removeIds = false,
|
|
7281
7671
|
removeClassNames = false,
|
|
7282
7672
|
exclude = [],
|
|
7673
|
+
inheritedProps = null,
|
|
7283
7674
|
} = {}) {
|
|
7284
7675
|
let propsFiltered = {};
|
|
7285
7676
|
let remove = [];
|
|
@@ -7308,7 +7699,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7308
7699
|
let isMeta = prop === 'title';
|
|
7309
7700
|
let isAria = prop.startsWith('aria-');
|
|
7310
7701
|
|
|
7311
|
-
if
|
|
7702
|
+
if ((allowDataAtts && isDataAtt) || (allowAriaAtts && isAria) || (allowMeta && isMeta)) continue
|
|
7312
7703
|
|
|
7313
7704
|
// filter out defaults
|
|
7314
7705
|
let isDefault = removeDefaults ?
|
|
@@ -7319,6 +7710,7 @@ function filterSvgElProps(elNodename = '', props = {}, {
|
|
|
7319
7710
|
|
|
7320
7711
|
if (isDefault || isDataAtt || isMeta || isAria || isFutileStroke) isValid = false;
|
|
7321
7712
|
if (include.includes(prop)) isValid = true;
|
|
7713
|
+
if (exclude.includes(prop)) isValid = false;
|
|
7322
7714
|
|
|
7323
7715
|
if (isValid) {
|
|
7324
7716
|
propsFiltered[prop] = props[prop];
|
|
@@ -7551,6 +7943,357 @@ function formatXMLNode(node, level, indentSize) {
|
|
|
7551
7943
|
return "";
|
|
7552
7944
|
}
|
|
7553
7945
|
|
|
7946
|
+
// Legendre Gauss weight and abscissa values
|
|
7947
|
+
const waArr_global = [];
|
|
7948
|
+
|
|
7949
|
+
function getLength(pts, {
|
|
7950
|
+
t = 1,
|
|
7951
|
+
waArr = []
|
|
7952
|
+
} = {}) {
|
|
7953
|
+
|
|
7954
|
+
const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
|
|
7955
|
+
if (t === 0) {
|
|
7956
|
+
return 0;
|
|
7957
|
+
}
|
|
7958
|
+
|
|
7959
|
+
t = t > 1 ? 1 : t < 0 ? 0 : t;
|
|
7960
|
+
let t2 = t / 2;
|
|
7961
|
+
|
|
7962
|
+
/**
|
|
7963
|
+
* set higher legendre gauss weight abscissae values
|
|
7964
|
+
* by more accurate weight/abscissae lookups
|
|
7965
|
+
* https://pomax.github.io/bezierinfo/legendre-gauss.html
|
|
7966
|
+
*/
|
|
7967
|
+
|
|
7968
|
+
let sum = 0;
|
|
7969
|
+
|
|
7970
|
+
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;
|
|
7971
|
+
|
|
7972
|
+
for (let i = 0, len = wa.length; i < len; i++) {
|
|
7973
|
+
// weight and abscissae
|
|
7974
|
+
let [w, a] = [wa[i][0], wa[i][1]];
|
|
7975
|
+
let ct1_t = t2 * a;
|
|
7976
|
+
let ct0 = -ct1_t + t2;
|
|
7977
|
+
|
|
7978
|
+
let xbase0 = base3(ct0, x0, cp1x, cp2x, px);
|
|
7979
|
+
let ybase0 = base3(ct0, y0, cp1y, cp2y, py);
|
|
7980
|
+
|
|
7981
|
+
let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
|
|
7982
|
+
|
|
7983
|
+
sum += w * Math.sqrt(comb0);
|
|
7984
|
+
|
|
7985
|
+
}
|
|
7986
|
+
return t2 * sum;
|
|
7987
|
+
};
|
|
7988
|
+
|
|
7989
|
+
const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
|
|
7990
|
+
if (t === 0) {
|
|
7991
|
+
return 0;
|
|
7992
|
+
}
|
|
7993
|
+
// is flat/linear – treat as line
|
|
7994
|
+
if (checkFlat) {
|
|
7995
|
+
let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
|
|
7996
|
+
let l2 = getDistance(p0, p);
|
|
7997
|
+
if (l1 === l2) {
|
|
7998
|
+
return l2;
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
|
|
8002
|
+
let a, b, c, d, e, e1, d1, v1x, v1y;
|
|
8003
|
+
v1x = cp1.x * 2;
|
|
8004
|
+
v1y = cp1.y * 2;
|
|
8005
|
+
d = p0.x - v1x + p.x;
|
|
8006
|
+
d1 = p0.y - v1y + p.y;
|
|
8007
|
+
e = v1x - 2 * p0.x;
|
|
8008
|
+
e1 = v1y - 2 * p0.y;
|
|
8009
|
+
a = 4 * (d * d + d1 * d1);
|
|
8010
|
+
b = 4 * (d * e + d1 * e1);
|
|
8011
|
+
c = e * e + e1 * e1;
|
|
8012
|
+
|
|
8013
|
+
const bt = b / (2 * a),
|
|
8014
|
+
ct = c / a,
|
|
8015
|
+
ut = t + bt,
|
|
8016
|
+
|
|
8017
|
+
k = ct - bt * bt;
|
|
8018
|
+
|
|
8019
|
+
return (
|
|
8020
|
+
(Math.sqrt(a) / 2) *
|
|
8021
|
+
(ut * Math.sqrt(ut * ut + k) -
|
|
8022
|
+
bt * Math.sqrt(bt * bt + k) +
|
|
8023
|
+
k *
|
|
8024
|
+
Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
|
|
8025
|
+
);
|
|
8026
|
+
};
|
|
8027
|
+
|
|
8028
|
+
let length;
|
|
8029
|
+
if (pts.length === 4) {
|
|
8030
|
+
length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr);
|
|
8031
|
+
|
|
8032
|
+
}
|
|
8033
|
+
else if (pts.length === 3) {
|
|
8034
|
+
length = quadraticBezierLength(pts[0], pts[1], pts[2], t);
|
|
8035
|
+
}
|
|
8036
|
+
else {
|
|
8037
|
+
length = getDistance(pts[0], pts[1]);
|
|
8038
|
+
}
|
|
8039
|
+
|
|
8040
|
+
return length;
|
|
8041
|
+
}
|
|
8042
|
+
|
|
8043
|
+
// LG weight/abscissae generator
|
|
8044
|
+
function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
|
|
8045
|
+
|
|
8046
|
+
let waArr = [];
|
|
8047
|
+
let z1, z, xm, xl, pp, p3, p2, p1;
|
|
8048
|
+
const m = (n + 1) >> 1;
|
|
8049
|
+
xm = 0.5 * (x2 + x1);
|
|
8050
|
+
xl = 0.5 * (x2 - x1);
|
|
8051
|
+
|
|
8052
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
8053
|
+
z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
|
|
8054
|
+
do {
|
|
8055
|
+
p1 = 1;
|
|
8056
|
+
p2 = 0;
|
|
8057
|
+
for (let j = 0; j < n; j++) {
|
|
8058
|
+
|
|
8059
|
+
p3 = p2;
|
|
8060
|
+
p2 = p1;
|
|
8061
|
+
p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
pp = (n * (z * p1 - p2)) / (z * z - 1);
|
|
8065
|
+
z1 = z;
|
|
8066
|
+
z = z1 - p1 / pp; //Newton’s method
|
|
8067
|
+
|
|
8068
|
+
} while (Math.abs(z - z1) > 1.0e-14);
|
|
8069
|
+
|
|
8070
|
+
let weight = (2 * xl) / ((1 - z * z) * pp * pp);
|
|
8071
|
+
let abscissa = xm + xl * z;
|
|
8072
|
+
|
|
8073
|
+
waArr.push(
|
|
8074
|
+
[weight, -abscissa],
|
|
8075
|
+
[weight, abscissa],
|
|
8076
|
+
);
|
|
8077
|
+
}
|
|
8078
|
+
|
|
8079
|
+
return waArr;
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
function base3(t, p1, p2, p3, p4) {
|
|
8083
|
+
let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
|
|
8084
|
+
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
|
|
8085
|
+
return t * t2 - 3 * p1 + 3 * p2;
|
|
8086
|
+
}
|
|
8087
|
+
|
|
8088
|
+
function getPolygonLength(pts=[], isPoly=false){
|
|
8089
|
+
|
|
8090
|
+
let len = 0;
|
|
8091
|
+
let l=pts.length;
|
|
8092
|
+
|
|
8093
|
+
for(let i=1; i<l; i++){
|
|
8094
|
+
let p1 = pts[i-1];
|
|
8095
|
+
let p2 = pts[i];
|
|
8096
|
+
len += getDistance(p1, p2);
|
|
8097
|
+
}
|
|
8098
|
+
if(isPoly){
|
|
8099
|
+
len += getDistance(pts[l-1], pts[0]);
|
|
8100
|
+
}
|
|
8101
|
+
return len
|
|
8102
|
+
}
|
|
8103
|
+
|
|
8104
|
+
/**
|
|
8105
|
+
* Ramanujan approximation
|
|
8106
|
+
* based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
|
|
8107
|
+
*/
|
|
8108
|
+
function getEllipseLength(rx=0, ry=0) {
|
|
8109
|
+
// is circle
|
|
8110
|
+
if (rx === ry) {
|
|
8111
|
+
|
|
8112
|
+
return 2 * Math.PI * rx;
|
|
8113
|
+
}
|
|
8114
|
+
|
|
8115
|
+
let c=rx+ry;
|
|
8116
|
+
let d = (rx - ry) / c;
|
|
8117
|
+
let h = d*d;
|
|
8118
|
+
|
|
8119
|
+
let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
|
|
8120
|
+
return totalLength;
|
|
8121
|
+
}
|
|
8122
|
+
|
|
8123
|
+
/**
|
|
8124
|
+
* ellipse helpers
|
|
8125
|
+
* approximate ellipse length
|
|
8126
|
+
* by Legendre-Gauss
|
|
8127
|
+
*/
|
|
8128
|
+
|
|
8129
|
+
function getCircleArcLength(r = 0, deltaAngle = 0) {
|
|
8130
|
+
if(r===0) {
|
|
8131
|
+
console.warn('Radius must be positive');
|
|
8132
|
+
return 0;
|
|
8133
|
+
}
|
|
8134
|
+
let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI));
|
|
8135
|
+
return len
|
|
8136
|
+
}
|
|
8137
|
+
|
|
8138
|
+
function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
|
|
8139
|
+
|
|
8140
|
+
// Transform [-1, 1] interval to [startAngle, endAngle]
|
|
8141
|
+
let halfInterval = (endAngle - startAngle) * 0.5;
|
|
8142
|
+
let midpoint = (endAngle + startAngle) * 0.5;
|
|
8143
|
+
|
|
8144
|
+
// Arc length integral approximation
|
|
8145
|
+
let arcLength = 0;
|
|
8146
|
+
for (let i = 0; i < wa.length; i++) {
|
|
8147
|
+
let [weight, abscissae] = wa[i];
|
|
8148
|
+
let theta = midpoint + halfInterval * abscissae;
|
|
8149
|
+
|
|
8150
|
+
let a = rx * Math.sin(theta);
|
|
8151
|
+
let b = ry * Math.cos(theta);
|
|
8152
|
+
let integrand = Math.sqrt(
|
|
8153
|
+
a * a + b * b
|
|
8154
|
+
);
|
|
8155
|
+
arcLength += weight * integrand;
|
|
8156
|
+
}
|
|
8157
|
+
|
|
8158
|
+
return Math.abs(halfInterval * arcLength)
|
|
8159
|
+
}
|
|
8160
|
+
|
|
8161
|
+
function getPathDataLength(pathData = []) {
|
|
8162
|
+
let len = 0;
|
|
8163
|
+
let pathDataArr = splitSubpaths(pathData);
|
|
8164
|
+
|
|
8165
|
+
for (let i = 0; i < pathDataArr.length; i++) {
|
|
8166
|
+
let pathData = pathDataArr[i];
|
|
8167
|
+
|
|
8168
|
+
// add verbose point data if not present
|
|
8169
|
+
if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
|
|
8170
|
+
|
|
8171
|
+
// Calculate Legendre Gauss weight and abscissa values
|
|
8172
|
+
if (!waArr_global.length) {
|
|
8173
|
+
|
|
8174
|
+
let waArr = getLegendreGaussValues(48);
|
|
8175
|
+
waArr.forEach(wa => {
|
|
8176
|
+
waArr_global.push(wa);
|
|
8177
|
+
});
|
|
8178
|
+
}
|
|
8179
|
+
|
|
8180
|
+
let waArr = waArr_global;
|
|
8181
|
+
|
|
8182
|
+
pathData.forEach(com => {
|
|
8183
|
+
let { type, values, p0, p, cp1 = null, cp2 = null } = com;
|
|
8184
|
+
let pts = [p0];
|
|
8185
|
+
if (type === 'C' || type === 'Q') pts.push(cp1);
|
|
8186
|
+
if (type === 'C') pts.push(cp2);
|
|
8187
|
+
pts.push(p);
|
|
8188
|
+
let comLen = 0;
|
|
8189
|
+
|
|
8190
|
+
if (type === 'A') {
|
|
8191
|
+
|
|
8192
|
+
// get parametrized arc properties
|
|
8193
|
+
let [largeArc, sweep] = [com.values[3], com.values[4]];
|
|
8194
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false);
|
|
8195
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData;
|
|
8196
|
+
|
|
8197
|
+
if (rx === ry) {
|
|
8198
|
+
comLen = getCircleArcLength(rx, Math.abs(deltaAngle));
|
|
8199
|
+
}
|
|
8200
|
+
|
|
8201
|
+
// is ellipse
|
|
8202
|
+
else {
|
|
8203
|
+
xAxisRotation = xAxisRotation * deg2rad;
|
|
8204
|
+
startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry);
|
|
8205
|
+
endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry);
|
|
8206
|
+
|
|
8207
|
+
// recalculate parametrized delta
|
|
8208
|
+
let deltaAngle_param = endAngle - startAngle;
|
|
8209
|
+
|
|
8210
|
+
let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
|
|
8211
|
+
|
|
8212
|
+
deltaAngle = signChange ? deltaAngle : deltaAngle_param;
|
|
8213
|
+
|
|
8214
|
+
// adjust end angle
|
|
8215
|
+
if (sweep && startAngle > endAngle) {
|
|
8216
|
+
endAngle += Math.PI * 2;
|
|
8217
|
+
}
|
|
8218
|
+
|
|
8219
|
+
if (!sweep && startAngle < endAngle) {
|
|
8220
|
+
endAngle -= Math.PI * 2;
|
|
8221
|
+
}
|
|
8222
|
+
comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr);
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
else {
|
|
8227
|
+
comLen = getLength(pts, {
|
|
8228
|
+
t: 1,
|
|
8229
|
+
waArr
|
|
8230
|
+
});
|
|
8231
|
+
}
|
|
8232
|
+
len += comLen;
|
|
8233
|
+
});
|
|
8234
|
+
}
|
|
8235
|
+
|
|
8236
|
+
return len;
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
function getElementLength(el, {
|
|
8240
|
+
props = {},
|
|
8241
|
+
pathLength = 0,
|
|
8242
|
+
} = {}) {
|
|
8243
|
+
|
|
8244
|
+
let nodeName = el.nodeName;
|
|
8245
|
+
let len = 0;
|
|
8246
|
+
|
|
8247
|
+
props = JSON.parse(JSON.stringify(props));
|
|
8248
|
+
|
|
8249
|
+
for (let prop in props) {
|
|
8250
|
+
if (props[prop] && props[prop].length && props[prop].length === 1) {
|
|
8251
|
+
props[prop] = props[prop][0];
|
|
8252
|
+
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
8256
|
+
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;
|
|
8257
|
+
|
|
8258
|
+
let pts = nodeName === 'polygon' || nodeName === 'polyline' ? el.getAttribute('points') : [];
|
|
8259
|
+
let isPolygon = nodeName === 'polygon';
|
|
8260
|
+
if (pts.length) {
|
|
8261
|
+
pts = normalizePoly(pts);
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
// we need to convert rects with corner rounding
|
|
8265
|
+
let pathData = [];
|
|
8266
|
+
if (nodeName === 'rect' && (rx || ry)) {
|
|
8267
|
+
pathData = rectToPathData(x, y, width, height, rx, ry);
|
|
8268
|
+
nodeName = 'path';
|
|
8269
|
+
}
|
|
8270
|
+
|
|
8271
|
+
switch (nodeName) {
|
|
8272
|
+
case 'line':
|
|
8273
|
+
len = getDistance({ x: x1, y: y1 }, { x: x2, y: y2 });
|
|
8274
|
+
break;
|
|
8275
|
+
case 'rect':
|
|
8276
|
+
len = width * 2 + height * 2;
|
|
8277
|
+
break;
|
|
8278
|
+
case 'circle':
|
|
8279
|
+
len = 2 * Math.PI * r;
|
|
8280
|
+
break;
|
|
8281
|
+
case 'ellipse':
|
|
8282
|
+
len = getEllipseLength(rx, ry);
|
|
8283
|
+
break;
|
|
8284
|
+
case 'polygon':
|
|
8285
|
+
case 'polyline':
|
|
8286
|
+
len = getPolygonLength(pts, isPolygon);
|
|
8287
|
+
break;
|
|
8288
|
+
case 'path':
|
|
8289
|
+
pathData = pathData.length ? pathData : parsePathDataNormalized(el.getAttribute('d'));
|
|
8290
|
+
len = getPathDataLength(pathData);
|
|
8291
|
+
break;
|
|
8292
|
+
}
|
|
8293
|
+
|
|
8294
|
+
return len
|
|
8295
|
+
}
|
|
8296
|
+
|
|
7554
8297
|
function removeHiddenSvgEls(svg) {
|
|
7555
8298
|
let els = svg.querySelectorAll('*');
|
|
7556
8299
|
els.forEach(el => {
|
|
@@ -7599,8 +8342,12 @@ function removeSvgEls(svg, {
|
|
|
7599
8342
|
*/
|
|
7600
8343
|
|
|
7601
8344
|
function removeSvgAtts(svg, remove = []) {
|
|
8345
|
+
removeAtts(svg, remove);
|
|
8346
|
+
}
|
|
8347
|
+
|
|
8348
|
+
function removeAtts(el, remove = []) {
|
|
7602
8349
|
remove.forEach(att => {
|
|
7603
|
-
|
|
8350
|
+
el.removeAttribute(att);
|
|
7604
8351
|
});
|
|
7605
8352
|
}
|
|
7606
8353
|
|
|
@@ -7698,22 +8445,108 @@ function cleanSvgPrologue(svgString) {
|
|
|
7698
8445
|
}
|
|
7699
8446
|
*/
|
|
7700
8447
|
|
|
8448
|
+
function setNormalizedTransformsToEl(el, {
|
|
8449
|
+
styleProps = {},
|
|
8450
|
+
} = {}) {
|
|
8451
|
+
let { remove, matrix, transComponents } = styleProps;
|
|
8452
|
+
let name = el.nodeName.toLowerCase();
|
|
8453
|
+
|
|
8454
|
+
if(!matrix) return styleProps;
|
|
8455
|
+
|
|
8456
|
+
let { rotate, scaleX, scaleY, skewX, translateX, translateY } = transComponents;
|
|
8457
|
+
|
|
8458
|
+
// scale attributes instead of transform
|
|
8459
|
+
let hasRot = rotate !== 0 || skewX !== 0;
|
|
8460
|
+
let unProportional = scaleX !== scaleY;
|
|
8461
|
+
let scalableByAtt = ['circle', 'ellipse', 'rect'];
|
|
8462
|
+
|
|
8463
|
+
let needsTrans = (hasRot) || unProportional;
|
|
8464
|
+
needsTrans = true;
|
|
8465
|
+
|
|
8466
|
+
if (!needsTrans && scalableByAtt.includes(name)) {
|
|
8467
|
+
|
|
8468
|
+
if (name === 'circle' || name === 'ellipse') {
|
|
8469
|
+
styleProps.cx[0] = [styleProps.cx[0] * scaleX + translateX];
|
|
8470
|
+
styleProps.cy[0] = [styleProps.cy[0] * scaleX + translateY];
|
|
8471
|
+
|
|
8472
|
+
if (styleProps.r) styleProps.r[0] = [styleProps.r[0] * scaleX];
|
|
8473
|
+
if (styleProps.rx) styleProps.rx[0] = [styleProps.rx[0] * scaleX];
|
|
8474
|
+
if (styleProps.ry) styleProps.ry[0] = [styleProps.ry[0] * scaleX];
|
|
8475
|
+
|
|
8476
|
+
}
|
|
8477
|
+
else if (name === 'rect') {
|
|
8478
|
+
let x = styleProps.x ? styleProps.x[0] + translateX : translateX;
|
|
8479
|
+
let y = styleProps.y ? styleProps.y[0] + translateY : translateY;
|
|
8480
|
+
|
|
8481
|
+
let rx = styleProps.rx ? styleProps.rx[0] * scaleX : 0;
|
|
8482
|
+
let ry = styleProps.ry ? styleProps.ry[0] * scaleY : 0;
|
|
8483
|
+
|
|
8484
|
+
styleProps.x = [x];
|
|
8485
|
+
styleProps.y = [y];
|
|
8486
|
+
|
|
8487
|
+
styleProps.rx = [rx];
|
|
8488
|
+
styleProps.ry = [ry];
|
|
8489
|
+
|
|
8490
|
+
styleProps.width = [styleProps.width[0] * scaleX];
|
|
8491
|
+
styleProps.height = [styleProps.height[0] * scaleX];
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8494
|
+
// remove now obsolete transform properties
|
|
8495
|
+
delete styleProps.matrix;
|
|
8496
|
+
delete styleProps.transformArr;
|
|
8497
|
+
delete styleProps.transComponents;
|
|
8498
|
+
|
|
8499
|
+
// mark transform attribute for removal
|
|
8500
|
+
styleProps.remove.push('transform');
|
|
8501
|
+
|
|
8502
|
+
// scale props like stroke width or dash-array
|
|
8503
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-width', 'stroke-dasharray'], scale: scaleX });
|
|
8504
|
+
|
|
8505
|
+
} else {
|
|
8506
|
+
el.setAttribute('transform', transComponents.matrixAtt);
|
|
8507
|
+
|
|
8508
|
+
}
|
|
8509
|
+
|
|
8510
|
+
return styleProps
|
|
8511
|
+
|
|
8512
|
+
}
|
|
8513
|
+
|
|
8514
|
+
function scaleProps(styleProps = {}, { props = [], scale = 1 } = {}, round = true) {
|
|
8515
|
+
if (scale === 1 || !props.length) return props;
|
|
8516
|
+
|
|
8517
|
+
for (let i = 0; i < props.length; i++) {
|
|
8518
|
+
let prop = props[i];
|
|
8519
|
+
|
|
8520
|
+
if (styleProps[prop] !== undefined) {
|
|
8521
|
+
styleProps[prop] = styleProps[prop].map(val => round ? roundTo(val * scale, 3) : val * scale);
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
return styleProps
|
|
8525
|
+
}
|
|
8526
|
+
|
|
7701
8527
|
function convertPathLengthAtt(el, {
|
|
7702
8528
|
styleProps = {}
|
|
7703
|
-
}={}) {
|
|
8529
|
+
} = {}) {
|
|
7704
8530
|
|
|
7705
|
-
let pathLength =
|
|
8531
|
+
let pathLength = styleProps['pathLength'];
|
|
7706
8532
|
|
|
7707
|
-
if (pathLength
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
8533
|
+
if (pathLength) {
|
|
8534
|
+
|
|
8535
|
+
if ((styleProps['stroke-dasharray'] || styleProps['stroke-dashoffset'])) {
|
|
8536
|
+
let elLength = getElementLength(el, {
|
|
8537
|
+
pathLength,
|
|
8538
|
+
props: styleProps
|
|
8539
|
+
});
|
|
8540
|
+
|
|
8541
|
+
let scale = elLength / pathLength;
|
|
7712
8542
|
|
|
7713
|
-
|
|
8543
|
+
styleProps = scaleProps(styleProps, { props: ['stroke-dasharray', 'stroke-dashoffset'], scale });
|
|
7714
8544
|
|
|
7715
|
-
|
|
7716
|
-
|
|
8545
|
+
// set absolute
|
|
8546
|
+
if (styleProps['stroke-dasharray']) el.setAttribute('stroke-dasharray', styleProps['stroke-dasharray'].join(' '));
|
|
8547
|
+
if (styleProps['stroke-dashoffset']) el.setAttribute('stroke-dashoffset', styleProps['stroke-dashoffset'][0]);
|
|
8548
|
+
|
|
8549
|
+
}
|
|
7717
8550
|
|
|
7718
8551
|
// tag for removal
|
|
7719
8552
|
delete styleProps['pathLength'];
|
|
@@ -8048,85 +8881,6 @@ function removeUnusedSelectors(parent=null, props={}){
|
|
|
8048
8881
|
return props
|
|
8049
8882
|
}
|
|
8050
8883
|
|
|
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
8884
|
function cleanUpSVG(svgMarkup, {
|
|
8131
8885
|
removeHidden = true,
|
|
8132
8886
|
|
|
@@ -8156,7 +8910,10 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8156
8910
|
cleanupSVGAtts = true,
|
|
8157
8911
|
removeNameSpaced = true,
|
|
8158
8912
|
removeNameSpacedAtts = true,
|
|
8913
|
+
|
|
8914
|
+
// unit conversions
|
|
8159
8915
|
convertPathLength = false,
|
|
8916
|
+
toAbsoluteUnits = false,
|
|
8160
8917
|
|
|
8161
8918
|
// meta
|
|
8162
8919
|
allowMeta = false,
|
|
@@ -8181,10 +8938,10 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8181
8938
|
} = {}) {
|
|
8182
8939
|
|
|
8183
8940
|
// resolve dependencies
|
|
8184
|
-
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8185
|
-
|
|
8941
|
+
if (unGroup || convertTransforms || minifyRgbColors || attributesToGroup)
|
|
8942
|
+
stylesToAttributes = true;
|
|
8186
8943
|
|
|
8187
|
-
if(stylesToAttributes) cleanUpStrokes = true;
|
|
8944
|
+
if (stylesToAttributes) cleanUpStrokes = true;
|
|
8188
8945
|
|
|
8189
8946
|
// replace namespaced refs
|
|
8190
8947
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
@@ -8229,7 +8986,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8229
8986
|
removeClassNames,
|
|
8230
8987
|
minifyRgbColors,
|
|
8231
8988
|
stylesheetProps: {},
|
|
8232
|
-
exclude:[]
|
|
8989
|
+
exclude: []
|
|
8233
8990
|
};
|
|
8234
8991
|
|
|
8235
8992
|
// root svg inline style properties
|
|
@@ -8311,9 +9068,13 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8311
9068
|
let stylePropsFiltered = {};
|
|
8312
9069
|
|
|
8313
9070
|
// convert pathLength before transforming
|
|
8314
|
-
if
|
|
9071
|
+
if(convertTransforms || attributesToGroup) convertPathLength=true;
|
|
9072
|
+
|
|
9073
|
+
if (convertPathLength ) {
|
|
9074
|
+
|
|
8315
9075
|
styleProps = convertPathLengthAtt(el, { styleProps });
|
|
8316
9076
|
remove = [...new Set([...remove, ...styleProps.remove])];
|
|
9077
|
+
|
|
8317
9078
|
}
|
|
8318
9079
|
|
|
8319
9080
|
// get parent styles
|
|
@@ -8346,9 +9107,14 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8346
9107
|
if (stylePropsSVG['class']) delete stylePropsSVG['class'];
|
|
8347
9108
|
if (stylePropsSVG['id']) delete stylePropsSVG['id'];
|
|
8348
9109
|
|
|
9110
|
+
// add svg props
|
|
9111
|
+
inheritedProps = {
|
|
9112
|
+
...stylePropsSVG,
|
|
9113
|
+
...inheritedProps,
|
|
9114
|
+
};
|
|
9115
|
+
|
|
8349
9116
|
// merge with svg props
|
|
8350
9117
|
styleProps = {
|
|
8351
|
-
...stylePropsSVG,
|
|
8352
9118
|
...inheritedProps,
|
|
8353
9119
|
...styleProps
|
|
8354
9120
|
};
|
|
@@ -8390,6 +9156,40 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8390
9156
|
// general cleanup
|
|
8391
9157
|
if (cleanupSVGAtts) cleanupSVGAttributes(svg, { removeIds, removeClassNames, removeDimensions, stylesToAttributes, allowMeta, allowAriaAtts, allowDataAtts });
|
|
8392
9158
|
|
|
9159
|
+
// all relative units to absolute
|
|
9160
|
+
if (toAbsoluteUnits) {
|
|
9161
|
+
normalizeTransforms = true;
|
|
9162
|
+
|
|
9163
|
+
/**
|
|
9164
|
+
* apply consolidated
|
|
9165
|
+
* element attributes
|
|
9166
|
+
* remove non-supported element props
|
|
9167
|
+
*/
|
|
9168
|
+
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
9169
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
9170
|
+
|
|
9171
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9172
|
+
let values = styleProps[prop];
|
|
9173
|
+
let val = values.length ? values.join(' ') : values[0];
|
|
9174
|
+
el.setAttribute(prop, val);
|
|
9175
|
+
}
|
|
9176
|
+
|
|
9177
|
+
let removeAttsEl = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
9178
|
+
|
|
9179
|
+
// check if same value is in inherited
|
|
9180
|
+
for (let prop in stylePropsFiltered.propsFiltered) {
|
|
9181
|
+
let valInh = inheritedProps[prop] || [];
|
|
9182
|
+
let val = stylePropsFiltered.propsFiltered[prop] || [];
|
|
9183
|
+
if (valInh.join() === val.join()) {
|
|
9184
|
+
removeAttsEl.push(prop);
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
|
|
9188
|
+
// remove obsolete/inherited
|
|
9189
|
+
removeAtts(el, removeAttsEl);
|
|
9190
|
+
|
|
9191
|
+
}
|
|
9192
|
+
|
|
8393
9193
|
if (stylesToAttributes) {
|
|
8394
9194
|
|
|
8395
9195
|
/**
|
|
@@ -8408,7 +9208,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8408
9208
|
* remove non-supported element props
|
|
8409
9209
|
*/
|
|
8410
9210
|
stylePropsFiltered = filterSvgElProps(name, styleProps,
|
|
8411
|
-
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds });
|
|
9211
|
+
{ removeDefaults: true, cleanUpStrokes, allowMeta, allowAriaAtts, allowDataAtts, removeIds, inheritedProps });
|
|
8412
9212
|
|
|
8413
9213
|
remove = [...new Set([...remove, ...stylePropsFiltered.remove])];
|
|
8414
9214
|
|
|
@@ -8422,12 +9222,14 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8422
9222
|
* remove obsolete
|
|
8423
9223
|
* attributes
|
|
8424
9224
|
*/
|
|
9225
|
+
removeAtts(el, remove);
|
|
8425
9226
|
|
|
9227
|
+
/*
|
|
8426
9228
|
for (let i = 0; i < remove.length; i++) {
|
|
8427
9229
|
let att = remove[i];
|
|
8428
|
-
|
|
8429
|
-
el.removeAttribute(att);
|
|
9230
|
+
el.removeAttribute(att)
|
|
8430
9231
|
}
|
|
9232
|
+
*/
|
|
8431
9233
|
|
|
8432
9234
|
} // endof style processing
|
|
8433
9235
|
|
|
@@ -8450,7 +9252,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8450
9252
|
|
|
8451
9253
|
// scale props like stroke width or dash-array before conversion
|
|
8452
9254
|
if (matrix && transComponents) {
|
|
8453
|
-
['stroke-width', 'stroke-dasharray'].forEach(att => {
|
|
9255
|
+
['stroke-width', 'stroke-dasharray', 'stroke-dashoffset'].forEach(att => {
|
|
8454
9256
|
let attVal = el.getAttribute(att);
|
|
8455
9257
|
let vals = attVal ? attVal.split(' ').filter(Boolean).map(Number).map(val => val * transComponents.scaleX) : [];
|
|
8456
9258
|
if (vals.length) el.setAttribute(att, vals.join(' '));
|
|
@@ -8512,15 +9314,15 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8512
9314
|
let values = stylePropsFiltered[prop];
|
|
8513
9315
|
let val = values.length ? values.join(' ') : values[0];
|
|
8514
9316
|
|
|
8515
|
-
if(prop!=='class' && prop!=='id'){
|
|
9317
|
+
if (prop !== 'class' && prop !== 'id') {
|
|
8516
9318
|
|
|
8517
9319
|
let propShort = toShortStr(prop);
|
|
8518
9320
|
let valShort = toShortStr(val);
|
|
8519
9321
|
let propStr = `${propShort}-${valShort}`;
|
|
8520
|
-
|
|
9322
|
+
|
|
8521
9323
|
// store in node property
|
|
8522
9324
|
if (!el.styleSet) el.styleSet = new Set();
|
|
8523
|
-
if(propStr) el.styleSet.add(propStr);
|
|
9325
|
+
if (propStr) el.styleSet.add(propStr);
|
|
8524
9326
|
}
|
|
8525
9327
|
}
|
|
8526
9328
|
|
|
@@ -8569,7 +9371,7 @@ function cleanUpSVG(svgMarkup, {
|
|
|
8569
9371
|
}
|
|
8570
9372
|
|
|
8571
9373
|
function removeEmptyClassAtts(svg) {
|
|
8572
|
-
let emptyClassEls = svg.querySelectorAll('[class=""');
|
|
9374
|
+
let emptyClassEls = svg.querySelectorAll('[class=""]');
|
|
8573
9375
|
emptyClassEls.forEach(el => {
|
|
8574
9376
|
el.removeAttribute('class');
|
|
8575
9377
|
});
|
|
@@ -8582,7 +9384,7 @@ function sharedAttributesToGroup(svg) {
|
|
|
8582
9384
|
|
|
8583
9385
|
let els = svg.querySelectorAll(renderedEls.join(', '));
|
|
8584
9386
|
let len = els.length;
|
|
8585
|
-
if(len===1) return;
|
|
9387
|
+
if (len === 1) return;
|
|
8586
9388
|
|
|
8587
9389
|
let el0 = els[0] || null;
|
|
8588
9390
|
let stylePrev = el0.styleSet !== undefined ? [...el0.styleSet].join('_') : '';
|
|
@@ -8658,7 +9460,7 @@ function sharedAttributesToGroup(svg) {
|
|
|
8658
9460
|
if (children.length === 1) continue
|
|
8659
9461
|
|
|
8660
9462
|
// create new group
|
|
8661
|
-
if (!groupEl || groups.length>1) {
|
|
9463
|
+
if (!groupEl || groups.length > 1) {
|
|
8662
9464
|
|
|
8663
9465
|
groupEl = document.createElementNS(svgNs, 'g');
|
|
8664
9466
|
child0.parentNode.insertBefore(groupEl, child0);
|
|
@@ -8740,7 +9542,9 @@ function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
|
8740
9542
|
bb0.bottom = y + height;
|
|
8741
9543
|
|
|
8742
9544
|
els.forEach(el => {
|
|
9545
|
+
|
|
8743
9546
|
let bb = getElBBox(el);
|
|
9547
|
+
|
|
8744
9548
|
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom;
|
|
8745
9549
|
if (outside) el.remove();
|
|
8746
9550
|
});
|
|
@@ -8849,8 +9653,130 @@ function hrefToXlink(svg) {
|
|
|
8849
9653
|
});
|
|
8850
9654
|
}
|
|
8851
9655
|
|
|
9656
|
+
function getArcFromPoly(pts, precise = false) {
|
|
9657
|
+
if (pts.length < 3) return false
|
|
9658
|
+
|
|
9659
|
+
// Pick 3 well-spaced points
|
|
9660
|
+
let len = pts.length;
|
|
9661
|
+
let idx1 = Math.floor(len * 0.333);
|
|
9662
|
+
let idx2 = Math.floor(len * 0.666);
|
|
9663
|
+
let idx3 = Math.floor(len * 0.5);
|
|
9664
|
+
|
|
9665
|
+
let p1 = pts[0];
|
|
9666
|
+
let p2 = pts[idx3];
|
|
9667
|
+
let p3 = pts[len - 1];
|
|
9668
|
+
|
|
9669
|
+
// Radius (use start point)
|
|
9670
|
+
let pts1 = [p1, p2, p3];
|
|
9671
|
+
let centroid = getPolyArcCentroid(pts1);
|
|
9672
|
+
|
|
9673
|
+
let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
|
|
9674
|
+
|
|
9675
|
+
// check if radii are consistent
|
|
9676
|
+
if (precise) {
|
|
9677
|
+
|
|
9678
|
+
/**
|
|
9679
|
+
* check multiple centroids
|
|
9680
|
+
* if the polyline can be expressed as
|
|
9681
|
+
* an arc - all centroids should be close
|
|
9682
|
+
*/
|
|
9683
|
+
|
|
9684
|
+
if (len > 3) {
|
|
9685
|
+
let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
|
|
9686
|
+
let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
|
|
9687
|
+
|
|
9688
|
+
if (!centroid1 || !centroid2) return false;
|
|
9689
|
+
|
|
9690
|
+
let dist0 = getDistManhattan(centroid, p2);
|
|
9691
|
+
let dist1 = getDistManhattan(centroid, centroid1);
|
|
9692
|
+
let dist2 = getDistManhattan(centroid, centroid2);
|
|
9693
|
+
let errorCentroid = (dist1 + dist2);
|
|
9694
|
+
|
|
9695
|
+
// centroids diverging too much
|
|
9696
|
+
if (errorCentroid > dist0 * 0.05) {
|
|
9697
|
+
|
|
9698
|
+
return false
|
|
9699
|
+
}
|
|
9700
|
+
|
|
9701
|
+
}
|
|
9702
|
+
|
|
9703
|
+
let rSqMid = getSquareDistance(centroid, p2);
|
|
9704
|
+
|
|
9705
|
+
for (let i = 0; i < len; i++) {
|
|
9706
|
+
let pt = pts[i];
|
|
9707
|
+
let rSq = getSquareDistance(centroid, pt);
|
|
9708
|
+
let error = Math.abs(rSqMid - rSq) / rSqMid;
|
|
9709
|
+
|
|
9710
|
+
if (error > 0.0025) {
|
|
9711
|
+
/*
|
|
9712
|
+
console.log('error', error, len, idx1, idx2, idx3);
|
|
9713
|
+
renderPoint(markers, centroid, 'orange')
|
|
9714
|
+
renderPoint(markers, p1, 'green')
|
|
9715
|
+
renderPoint(markers, p2)
|
|
9716
|
+
renderPoint(markers, p3, 'purple')
|
|
9717
|
+
*/
|
|
9718
|
+
return false;
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
|
|
9722
|
+
// calculate proper radius
|
|
9723
|
+
r = Math.sqrt(rSqMid);
|
|
9724
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9725
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9726
|
+
|
|
9727
|
+
} else {
|
|
9728
|
+
r = getDistance(centroid, p1);
|
|
9729
|
+
angleData = getDeltaAngle(centroid, p1, p3);
|
|
9730
|
+
({ deltaAngle, startAngle, endAngle } = angleData);
|
|
9731
|
+
}
|
|
9732
|
+
|
|
9733
|
+
return {
|
|
9734
|
+
centroid,
|
|
9735
|
+
r,
|
|
9736
|
+
startAngle,
|
|
9737
|
+
endAngle,
|
|
9738
|
+
deltaAngle
|
|
9739
|
+
};
|
|
9740
|
+
}
|
|
9741
|
+
|
|
9742
|
+
function getPolyArcCentroid(pts = []) {
|
|
9743
|
+
|
|
9744
|
+
pts = pts.filter(pt => pt !== undefined);
|
|
9745
|
+
if (pts.length < 3) return false
|
|
9746
|
+
|
|
9747
|
+
let p1 = pts[0];
|
|
9748
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9749
|
+
let p3 = pts[pts.length - 1];
|
|
9750
|
+
|
|
9751
|
+
let x1 = p1.x, y1 = p1.y;
|
|
9752
|
+
let x2 = p2.x, y2 = p2.y;
|
|
9753
|
+
let x3 = p3.x, y3 = p3.y;
|
|
9754
|
+
|
|
9755
|
+
let a = x1 - x2;
|
|
9756
|
+
let b = y1 - y2;
|
|
9757
|
+
let c = x1 - x3;
|
|
9758
|
+
let d = y1 - y3;
|
|
9759
|
+
|
|
9760
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
9761
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
9762
|
+
|
|
9763
|
+
let det = a * d - b * c;
|
|
9764
|
+
|
|
9765
|
+
// colinear points
|
|
9766
|
+
if (Math.abs(det) < 1e-10) {
|
|
9767
|
+
return false;
|
|
9768
|
+
}
|
|
9769
|
+
|
|
9770
|
+
// find center of arc
|
|
9771
|
+
let cx = (d * e - b * f) / det;
|
|
9772
|
+
let cy = (-c * e + a * f) / det;
|
|
9773
|
+
let centroid = { x: cx, y: cy };
|
|
9774
|
+
return centroid
|
|
9775
|
+
}
|
|
9776
|
+
|
|
8852
9777
|
function refineRoundedCorners(pathData, {
|
|
8853
9778
|
threshold = 0,
|
|
9779
|
+
simplifyQuadraticCorners = false,
|
|
8854
9780
|
tolerance = 1
|
|
8855
9781
|
} = {}) {
|
|
8856
9782
|
|
|
@@ -8875,6 +9801,9 @@ function refineRoundedCorners(pathData, {
|
|
|
8875
9801
|
let firstIsLine = pathData[1].type === 'L';
|
|
8876
9802
|
let firstIsBez = pathData[1].type === 'C';
|
|
8877
9803
|
|
|
9804
|
+
// in case we have simplified a corner connecting to the start
|
|
9805
|
+
let M_adj = null;
|
|
9806
|
+
|
|
8878
9807
|
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
8879
9808
|
|
|
8880
9809
|
// normalize closepath to lineto
|
|
@@ -8914,15 +9843,17 @@ function refineRoundedCorners(pathData, {
|
|
|
8914
9843
|
// closing corner to start
|
|
8915
9844
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
8916
9845
|
comL1 = pathData[1];
|
|
9846
|
+
|
|
8917
9847
|
comBez = [pathData[l - lastOff]];
|
|
8918
9848
|
|
|
8919
9849
|
}
|
|
8920
9850
|
|
|
9851
|
+
// collect enclosed bezier segments
|
|
8921
9852
|
for (let j = i + 1; j < l; j++) {
|
|
8922
9853
|
let comN = pathData[j] ? pathData[j] : null;
|
|
8923
9854
|
let comPrev = pathData[j - 1];
|
|
8924
9855
|
|
|
8925
|
-
if (comPrev.type === 'C') {
|
|
9856
|
+
if (comPrev.type === 'C' && j > 2) {
|
|
8926
9857
|
comBez.push(comPrev);
|
|
8927
9858
|
}
|
|
8928
9859
|
|
|
@@ -8953,39 +9884,67 @@ function refineRoundedCorners(pathData, {
|
|
|
8953
9884
|
let bezThresh = len3 * 0.5 * tolerance;
|
|
8954
9885
|
let isSmall = bezThresh < len1 && bezThresh < len2;
|
|
8955
9886
|
|
|
9887
|
+
/*
|
|
9888
|
+
*/
|
|
9889
|
+
|
|
8956
9890
|
if (comBez.length && !signChange && isSmall) {
|
|
8957
9891
|
|
|
8958
|
-
let
|
|
9892
|
+
let isSquare = false;
|
|
9893
|
+
|
|
9894
|
+
if (comBez.length === 1) {
|
|
9895
|
+
let dx = Math.abs(comBez[0].p.x - comBez[0].p0.x);
|
|
9896
|
+
let dy = Math.abs(comBez[0].p.y - comBez[0].p0.y);
|
|
9897
|
+
let diff = (dx - dy);
|
|
9898
|
+
let rat = Math.abs(diff / dx);
|
|
9899
|
+
isSquare = rat < 0.01;
|
|
9900
|
+
}
|
|
9901
|
+
|
|
9902
|
+
let preferArcs = true;
|
|
9903
|
+
preferArcs = false;
|
|
9904
|
+
|
|
9905
|
+
// if rectangular prefer arcs
|
|
9906
|
+
if (preferArcs && isSquare) {
|
|
9907
|
+
|
|
9908
|
+
let pM = pointAtT([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5);
|
|
9909
|
+
|
|
9910
|
+
let arcProps = getArcFromPoly([comBez[0].p0, pM, comBez[0].p]);
|
|
9911
|
+
let { r, centroid, deltaAngle } = arcProps;
|
|
9912
|
+
|
|
9913
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
9914
|
+
|
|
9915
|
+
let largeArc = 0;
|
|
9916
|
+
|
|
9917
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, comBez[0].p.x, comBez[0].p.y] };
|
|
9918
|
+
|
|
9919
|
+
pathDataN.push(comL0, comArc);
|
|
9920
|
+
i += offset;
|
|
9921
|
+
continue
|
|
9922
|
+
|
|
9923
|
+
}
|
|
9924
|
+
|
|
9925
|
+
let areaThresh = getSquareDistance(comBez[0].p0, comBez[0].p) * 0.005;
|
|
9926
|
+
let isFlatBezier = Math.abs(area2) < areaThresh;
|
|
9927
|
+
let isFlatBezier2 = Math.abs(area2) < areaThresh * 10;
|
|
9928
|
+
|
|
8959
9929
|
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p, comL1.p0, false, true) : null;
|
|
8960
9930
|
|
|
8961
|
-
|
|
9931
|
+
// exit: is rather flat or has no intersection
|
|
9932
|
+
|
|
9933
|
+
if (!ptQ || (isFlatBezier2 && comBez.length === 1)) {
|
|
8962
9934
|
pathDataN.push(com);
|
|
8963
9935
|
continue
|
|
8964
9936
|
}
|
|
8965
9937
|
|
|
8966
|
-
// check sign change
|
|
9938
|
+
// check sign change - exit if present
|
|
8967
9939
|
if (ptQ) {
|
|
8968
9940
|
let area0 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false);
|
|
8969
9941
|
let area0_abs = Math.abs(area0);
|
|
8970
9942
|
let area1 = getPolygonArea([comL0.p0, comL0.p, ptQ, comL1.p0, comL1.p], false);
|
|
8971
9943
|
let area1_abs = Math.abs(area1);
|
|
8972
9944
|
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
9945
|
let signChange = area0 < 0 && area1 > 0 || area0 > 0 && area1 < 0;
|
|
8986
9946
|
|
|
8987
9947
|
if (!ptQ || signChange || areaDiff > 0.5) {
|
|
8988
|
-
|
|
8989
9948
|
pathDataN.push(com);
|
|
8990
9949
|
continue
|
|
8991
9950
|
}
|
|
@@ -9000,24 +9959,67 @@ function refineRoundedCorners(pathData, {
|
|
|
9000
9959
|
|
|
9001
9960
|
// not in tolerance – return original command
|
|
9002
9961
|
if (bezThresh && dist1 > bezThresh && dist1 > len3 * 0.3) {
|
|
9003
|
-
|
|
9004
9962
|
pathDataN.push(com);
|
|
9005
9963
|
continue;
|
|
9006
9964
|
|
|
9007
|
-
}
|
|
9965
|
+
}
|
|
9008
9966
|
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
comQ.cp1 = ptQ;
|
|
9012
|
-
comQ.p = comL1.p0;
|
|
9967
|
+
// return simplified quadratic Bézier command
|
|
9968
|
+
let p_Q = comL1.p0;
|
|
9013
9969
|
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9970
|
+
// adjust previous end point to better fit the cubic curvature
|
|
9971
|
+
let adjustQ = !simplifyQuadraticCorners;
|
|
9972
|
+
|
|
9973
|
+
if (adjustQ) {
|
|
9974
|
+
|
|
9975
|
+
let t = 0.1666;
|
|
9976
|
+
let p0_adj = interpolate(ptQ, comL0.p, (1 + t));
|
|
9977
|
+
p_Q = interpolate(ptQ, comL1.p0, (1 + t));
|
|
9978
|
+
|
|
9979
|
+
// round for large enough segments
|
|
9980
|
+
let isH = ptQ.y===comL0.p.y;
|
|
9981
|
+
let isV = ptQ.x===comL0.p.x;
|
|
9982
|
+
let isH2 = ptQ.y===comL1.p0.y;
|
|
9983
|
+
let isV2 = ptQ.x===comL1.p0.x;
|
|
9984
|
+
|
|
9985
|
+
if(isSquare && com.dimA>3){
|
|
9986
|
+
let dec = 0.5;
|
|
9987
|
+
if(isH) p0_adj.x = roundTo(p0_adj.x, dec);
|
|
9988
|
+
if(isV) p0_adj.y = roundTo(p0_adj.y, dec);
|
|
9989
|
+
if(isH2) p_Q.x = roundTo(p_Q.x, dec);
|
|
9990
|
+
if(isV2) p_Q.y = roundTo(p_Q.y, dec);
|
|
9991
|
+
}
|
|
9992
|
+
|
|
9993
|
+
/*
|
|
9994
|
+
renderPoint(markers, p0_adj, 'orange')
|
|
9995
|
+
renderPoint(markers, p_Q, 'orange')
|
|
9996
|
+
renderPoint(markers, comL0.p, 'green')
|
|
9997
|
+
renderPoint(markers, comL1.p0, 'magenta')
|
|
9998
|
+
*/
|
|
9999
|
+
|
|
10000
|
+
// set new M starting point
|
|
10001
|
+
if (i === l - lastOff - 1) {
|
|
10002
|
+
|
|
10003
|
+
M_adj = p_Q;
|
|
10004
|
+
}
|
|
10005
|
+
|
|
10006
|
+
// adjust previous lineto end point
|
|
10007
|
+
comL0.values = [p0_adj.x, p0_adj.y];
|
|
10008
|
+
comL0.p = p0_adj;
|
|
9017
10009
|
|
|
9018
|
-
continue;
|
|
9019
10010
|
}
|
|
9020
10011
|
|
|
10012
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, p_Q.x, p_Q.y] };
|
|
10013
|
+
comQ.cp1 = ptQ;
|
|
10014
|
+
comQ.p0 = comL0.p;
|
|
10015
|
+
comQ.p = p_Q;
|
|
10016
|
+
|
|
10017
|
+
// add quadratic command
|
|
10018
|
+
pathDataN.push(comL0, comQ);
|
|
10019
|
+
|
|
10020
|
+
i += offset;
|
|
10021
|
+
continue;
|
|
10022
|
+
|
|
9021
10023
|
}
|
|
9022
10024
|
}
|
|
9023
10025
|
}
|
|
@@ -9031,6 +10033,12 @@ function refineRoundedCorners(pathData, {
|
|
|
9031
10033
|
|
|
9032
10034
|
}
|
|
9033
10035
|
|
|
10036
|
+
// correct starting point connecting with last corner rounding
|
|
10037
|
+
if (M_adj) {
|
|
10038
|
+
pathDataN[0].values = [M_adj.x, M_adj.y];
|
|
10039
|
+
pathDataN[0].p0 = M_adj;
|
|
10040
|
+
}
|
|
10041
|
+
|
|
9034
10042
|
// revert close path normalization
|
|
9035
10043
|
if (normalizeClose || (isClosed && pathDataN[pathDataN.length - 1].type !== 'Z')) {
|
|
9036
10044
|
pathDataN.push({ type: 'Z', values: [] });
|
|
@@ -9040,51 +10048,143 @@ function refineRoundedCorners(pathData, {
|
|
|
9040
10048
|
|
|
9041
10049
|
}
|
|
9042
10050
|
|
|
9043
|
-
function
|
|
9044
|
-
|
|
10051
|
+
function simplifyAdjacentRound(pathData, {
|
|
10052
|
+
threshold = 0,
|
|
10053
|
+
tolerance = 1,
|
|
10054
|
+
// take arcs or cubic beziers
|
|
10055
|
+
toCubic = false,
|
|
10056
|
+
debug = false
|
|
10057
|
+
} = {}) {
|
|
9045
10058
|
|
|
9046
|
-
//
|
|
9047
|
-
|
|
9048
|
-
let p2 = pts[Math.floor(pts.length / 2)];
|
|
9049
|
-
let p3 = pts[pts.length - 1];
|
|
10059
|
+
// fix small Arcs
|
|
10060
|
+
pathData = convertSmallArcsToLinetos(pathData);
|
|
9050
10061
|
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
let x3 = p3.x, y3 = p3.y;
|
|
10062
|
+
// min size threshold for corners
|
|
10063
|
+
threshold *= tolerance;
|
|
9054
10064
|
|
|
9055
|
-
let
|
|
9056
|
-
let b = y1 - y2;
|
|
9057
|
-
let c = x1 - x3;
|
|
9058
|
-
let d = y1 - y3;
|
|
10065
|
+
let l = pathData.length;
|
|
9059
10066
|
|
|
9060
|
-
|
|
9061
|
-
let
|
|
10067
|
+
// add fist command
|
|
10068
|
+
let pathDataN = [pathData[0]];
|
|
9062
10069
|
|
|
9063
|
-
|
|
10070
|
+
// find adjacent cubics between extremes
|
|
9064
10071
|
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
10072
|
+
for (let i = 1; i < l; i++) {
|
|
10073
|
+
pathData[i - 1];
|
|
10074
|
+
let com = pathData[i];
|
|
10075
|
+
let comN = pathData[i + 1] || null;
|
|
9069
10076
|
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
10077
|
+
if (!comN) {
|
|
10078
|
+
pathDataN.push(com);
|
|
10079
|
+
break
|
|
10080
|
+
}
|
|
9074
10081
|
|
|
9075
|
-
|
|
9076
|
-
|
|
10082
|
+
let { type, extreme = false, p0, p, dimA = 0 } = com;
|
|
10083
|
+
// for short segment detection
|
|
10084
|
+
let dimAN = comN.dimA;
|
|
10085
|
+
let dimA0 = dimA + dimAN;
|
|
10086
|
+
let thresh = 0.1;
|
|
9077
10087
|
|
|
9078
|
-
|
|
9079
|
-
|
|
10088
|
+
// ignore short linetos
|
|
10089
|
+
let isShortN = dimAN < dimA0 * thresh;
|
|
9080
10090
|
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
10091
|
+
// adjacent cubic commands - accept short in between linetos
|
|
10092
|
+
if ((type === 'C') && (comN.type === 'C' || isShortN)) {
|
|
10093
|
+
|
|
10094
|
+
let candidates = [];
|
|
10095
|
+
|
|
10096
|
+
for (let j = i + 1; j < l; j++) {
|
|
10097
|
+
let comN = pathData[j];
|
|
10098
|
+
let { type, extreme = false, corner = false, dimA = 0 } = comN;
|
|
10099
|
+
let isShort = dimA < dimA0 * thresh;
|
|
10100
|
+
|
|
10101
|
+
// skip for type change(unless very short), extremes or corners
|
|
10102
|
+
/*
|
|
10103
|
+
if ( (comN.extreme || comN.corner) ) {
|
|
10104
|
+
if(!extreme && !corner) candidates.push(comN)
|
|
10105
|
+
break;
|
|
10106
|
+
}
|
|
10107
|
+
*/
|
|
10108
|
+
|
|
10109
|
+
if (extreme || corner) {
|
|
10110
|
+
|
|
10111
|
+
if (isShort && comN.type !== 'C') ;
|
|
10112
|
+
|
|
10113
|
+
if ((extreme && !corner)) {
|
|
10114
|
+
|
|
10115
|
+
candidates.push(comN);
|
|
10116
|
+
}
|
|
10117
|
+
|
|
10118
|
+
break;
|
|
10119
|
+
}
|
|
10120
|
+
|
|
10121
|
+
candidates.push(comN);
|
|
10122
|
+
}
|
|
10123
|
+
|
|
10124
|
+
// try to create arc command
|
|
10125
|
+
if (candidates.length > 1) {
|
|
10126
|
+
|
|
10127
|
+
let clen = candidates.length;
|
|
10128
|
+
let pts = [com.p0, com.p,];
|
|
10129
|
+
|
|
10130
|
+
// add interpolated points to prevent wrong arc replacements
|
|
10131
|
+
candidates.forEach(c => {
|
|
10132
|
+
if (c.type === 'C') {
|
|
10133
|
+
let pt = pointAtT([c.p0, c.cp1, c.cp2, c.p], 0.5);
|
|
10134
|
+
pts.push(pt);
|
|
10135
|
+
}
|
|
10136
|
+
pts.push(c.p);
|
|
10137
|
+
});
|
|
10138
|
+
|
|
10139
|
+
let precise = true;
|
|
10140
|
+
let arcProps = getArcFromPoly(pts, precise);
|
|
10141
|
+
|
|
10142
|
+
// could be combined
|
|
10143
|
+
if (arcProps) {
|
|
10144
|
+
|
|
10145
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
10146
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
10147
|
+
|
|
10148
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
10149
|
+
largeArc = 0;
|
|
10150
|
+
let comLast = candidates[clen - 1];
|
|
10151
|
+
let p = comLast.p;
|
|
10152
|
+
|
|
10153
|
+
let comArc = { type: 'A', values: [r, r, 0, largeArc, sweep, p.x, p.y] };
|
|
10154
|
+
|
|
10155
|
+
comArc.dimA = getDistManhattan(p0, p);
|
|
10156
|
+
comArc.p0 = p0;
|
|
10157
|
+
comArc.p = p;
|
|
10158
|
+
comArc.error = 0;
|
|
10159
|
+
comArc.directionChange = comLast.directionChange;
|
|
10160
|
+
comArc.extreme = comLast.extreme;
|
|
10161
|
+
comArc.corner = comLast.corner;
|
|
10162
|
+
pathDataN.push(comArc);
|
|
10163
|
+
|
|
10164
|
+
i += candidates.length;
|
|
10165
|
+
continue
|
|
10166
|
+
|
|
10167
|
+
}
|
|
10168
|
+
|
|
10169
|
+
// arc radius calculation failed - return original
|
|
10170
|
+
else {
|
|
10171
|
+
pathDataN.push(com);
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
10174
|
+
|
|
10175
|
+
// could not be simplified – return original command
|
|
10176
|
+
else {
|
|
10177
|
+
pathDataN.push(com);
|
|
10178
|
+
}
|
|
10179
|
+
|
|
10180
|
+
}
|
|
10181
|
+
// all other commands
|
|
10182
|
+
else {
|
|
10183
|
+
pathDataN.push(com);
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
|
|
10187
|
+
return pathDataN
|
|
9088
10188
|
}
|
|
9089
10189
|
|
|
9090
10190
|
function refineRoundSegments(pathData, {
|
|
@@ -9103,9 +10203,6 @@ function refineRoundSegments(pathData, {
|
|
|
9103
10203
|
// add fist command
|
|
9104
10204
|
let pathDataN = [pathData[0]];
|
|
9105
10205
|
|
|
9106
|
-
// just for debugging
|
|
9107
|
-
let pathDataTest = [];
|
|
9108
|
-
|
|
9109
10206
|
for (let i = 1; i < l; i++) {
|
|
9110
10207
|
let com = pathData[i];
|
|
9111
10208
|
let { type } = com;
|
|
@@ -9132,11 +10229,12 @@ function refineRoundSegments(pathData, {
|
|
|
9132
10229
|
|
|
9133
10230
|
// 2. line-line-bezier-line-line
|
|
9134
10231
|
if (
|
|
10232
|
+
comN2 && comN3 &&
|
|
9135
10233
|
comP.type === 'L' &&
|
|
9136
10234
|
type === 'L' &&
|
|
9137
10235
|
comBez &&
|
|
9138
10236
|
comN2.type === 'L' &&
|
|
9139
|
-
|
|
10237
|
+
(comN3.type === 'L' || comN3.type === 'Z')
|
|
9140
10238
|
) {
|
|
9141
10239
|
|
|
9142
10240
|
L1 = [com.p0, com.p];
|
|
@@ -9163,10 +10261,10 @@ function refineRoundSegments(pathData, {
|
|
|
9163
10261
|
}
|
|
9164
10262
|
|
|
9165
10263
|
// 1. line-bezier-bezier-line
|
|
9166
|
-
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
10264
|
+
else if (comN && (type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
9167
10265
|
|
|
9168
10266
|
// 1.2 next is cubic next is lineto
|
|
9169
|
-
if (
|
|
10267
|
+
if (comN2 && comN2.type === 'L' && (comN.type === 'C' || comN.type === 'Q')) {
|
|
9170
10268
|
|
|
9171
10269
|
combine = true;
|
|
9172
10270
|
|
|
@@ -9225,16 +10323,19 @@ function refineRoundSegments(pathData, {
|
|
|
9225
10323
|
}
|
|
9226
10324
|
);
|
|
9227
10325
|
|
|
9228
|
-
if(bezierCommands.length === 1){
|
|
10326
|
+
if (bezierCommands.length === 1) {
|
|
9229
10327
|
|
|
9230
10328
|
// prefer more compact quadratic - otherwise arcs
|
|
9231
10329
|
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
9232
10330
|
|
|
9233
10331
|
if (comBezier.type === 'Q') {
|
|
9234
10332
|
toCubic = true;
|
|
10333
|
+
}else {
|
|
10334
|
+
comBezier = bezierCommands[0];
|
|
9235
10335
|
}
|
|
9236
10336
|
|
|
9237
10337
|
com = comBezier;
|
|
10338
|
+
|
|
9238
10339
|
}
|
|
9239
10340
|
|
|
9240
10341
|
// prefer arcs if 2 cubics are required
|
|
@@ -9254,25 +10355,28 @@ function refineRoundSegments(pathData, {
|
|
|
9254
10355
|
|
|
9255
10356
|
// test rendering
|
|
9256
10357
|
|
|
10358
|
+
/*
|
|
9257
10359
|
if (debug) {
|
|
9258
10360
|
// arcs
|
|
9259
10361
|
if (!toCubic) {
|
|
9260
10362
|
pathDataTest = [
|
|
9261
10363
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9262
10364
|
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
9263
|
-
]
|
|
10365
|
+
]
|
|
9264
10366
|
}
|
|
9265
10367
|
// cubics
|
|
9266
10368
|
else {
|
|
9267
10369
|
pathDataTest = [
|
|
9268
10370
|
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
9269
10371
|
...bezierCommands
|
|
9270
|
-
]
|
|
10372
|
+
]
|
|
10373
|
+
|
|
9271
10374
|
}
|
|
9272
10375
|
|
|
9273
10376
|
let d = pathDataToD(pathDataTest);
|
|
9274
|
-
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
10377
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5')
|
|
9275
10378
|
}
|
|
10379
|
+
*/
|
|
9276
10380
|
|
|
9277
10381
|
pathDataN.push(com);
|
|
9278
10382
|
i++;
|
|
@@ -9404,7 +10508,6 @@ function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
|
9404
10508
|
let com = pathData[c];
|
|
9405
10509
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
9406
10510
|
if (type === 'C') {
|
|
9407
|
-
|
|
9408
10511
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance);
|
|
9409
10512
|
if (comQ.type === 'Q') {
|
|
9410
10513
|
comQ.extreme = com.extreme;
|
|
@@ -11016,32 +12119,24 @@ function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
|
|
|
11016
12119
|
}
|
|
11017
12120
|
|
|
11018
12121
|
// reverse paths
|
|
11019
|
-
for (let i = 0; i < l; i++) {
|
|
12122
|
+
for (let i = 0; l && i < l; i++) {
|
|
11020
12123
|
|
|
11021
12124
|
let poly = polys[i];
|
|
11022
12125
|
let { cw, includedIn, includes } = poly;
|
|
11023
12126
|
|
|
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
|
-
}
|
|
12127
|
+
let len = includes.length;
|
|
11034
12128
|
|
|
11035
12129
|
// reverse inner sub paths
|
|
11036
|
-
for (let j = 0; j <
|
|
12130
|
+
for (let j = 0; len && j < len; j++) {
|
|
11037
12131
|
let ind = includes[j];
|
|
11038
12132
|
let child = polys[ind];
|
|
11039
12133
|
|
|
11040
|
-
|
|
12134
|
+
// nothing to do
|
|
12135
|
+
if (child.cw !== cw) continue
|
|
12136
|
+
|
|
12137
|
+
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
12138
|
+
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11041
12139
|
|
|
11042
|
-
pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
|
|
11043
|
-
polys[ind].cw = polys[ind].cw ? false : true;
|
|
11044
|
-
}
|
|
11045
12140
|
}
|
|
11046
12141
|
}
|
|
11047
12142
|
|
|
@@ -11081,6 +12176,7 @@ let settingsDefaults = {
|
|
|
11081
12176
|
allowAriaAtts: true,
|
|
11082
12177
|
|
|
11083
12178
|
convertPathLength: false,
|
|
12179
|
+
toAbsoluteUnits: false,
|
|
11084
12180
|
|
|
11085
12181
|
// custom removal
|
|
11086
12182
|
removeElements: [],
|
|
@@ -11109,6 +12205,7 @@ let settingsDefaults = {
|
|
|
11109
12205
|
revertToQuadratics: true,
|
|
11110
12206
|
refineExtremes: false,
|
|
11111
12207
|
simplifyCorners: false,
|
|
12208
|
+
simplifyQuadraticCorners: false,
|
|
11112
12209
|
keepExtremes: true,
|
|
11113
12210
|
keepCorners: true,
|
|
11114
12211
|
keepInflections: false,
|
|
@@ -11165,7 +12262,7 @@ for (let prop in settingsDefaults) {
|
|
|
11165
12262
|
let isArray = Array.isArray(val);
|
|
11166
12263
|
|
|
11167
12264
|
if (isBoolean) val = false;
|
|
11168
|
-
else if (!isArray && isNum) val = val===1 ? 1 : (prop==='decimals'? -1 : 0);
|
|
12265
|
+
else if (!isArray && isNum) val = val === 1 ? 1 : (prop === 'decimals' ? -1 : 0);
|
|
11169
12266
|
else if (isArray) val = [];
|
|
11170
12267
|
settingsNull[prop] = val;
|
|
11171
12268
|
}
|
|
@@ -11196,10 +12293,14 @@ const presetSettings = {
|
|
|
11196
12293
|
...settingsDefaults,
|
|
11197
12294
|
...{
|
|
11198
12295
|
keepSmaller: false,
|
|
12296
|
+
convertPathLength:true,
|
|
11199
12297
|
toRelative: true,
|
|
11200
12298
|
toMixed: true,
|
|
11201
12299
|
toShorthands: true,
|
|
11202
12300
|
|
|
12301
|
+
allowMeta:true,
|
|
12302
|
+
allowDataAtts:true,
|
|
12303
|
+
allowAriaAtts:true,
|
|
11203
12304
|
legacyHref: true,
|
|
11204
12305
|
addViewBox: true,
|
|
11205
12306
|
addDimensions: true,
|
|
@@ -11267,19 +12368,23 @@ const presetSettings = {
|
|
|
11267
12368
|
high: {
|
|
11268
12369
|
...settingsDefaults,
|
|
11269
12370
|
...{
|
|
11270
|
-
tolerance: 1.
|
|
12371
|
+
tolerance: 1.1,
|
|
11271
12372
|
toMixed: true,
|
|
11272
12373
|
refineExtremes: true,
|
|
11273
12374
|
simplifyCorners: true,
|
|
12375
|
+
simplifyQuadraticCorners: true,
|
|
12376
|
+
removeOrphanSubpaths: true,
|
|
11274
12377
|
simplifyRound: true,
|
|
11275
12378
|
removeClassNames: true,
|
|
11276
12379
|
cubicToArc: true,
|
|
12380
|
+
minifyD: 0,
|
|
11277
12381
|
removeComments: true,
|
|
11278
12382
|
removeHidden: true,
|
|
11279
|
-
removeOffCanvas: true,
|
|
11280
12383
|
addViewBox: true,
|
|
11281
12384
|
removeDimensions: true,
|
|
11282
|
-
|
|
12385
|
+
removeOffCanvas: true,
|
|
12386
|
+
/*
|
|
12387
|
+
*/
|
|
11283
12388
|
}
|
|
11284
12389
|
}
|
|
11285
12390
|
|
|
@@ -11437,18 +12542,44 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11437
12542
|
...settings
|
|
11438
12543
|
};
|
|
11439
12544
|
|
|
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,
|
|
12545
|
+
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
12546
|
|
|
11442
12547
|
// clamp tolerance and scale
|
|
11443
12548
|
tolerance = Math.max(0.1, tolerance);
|
|
11444
12549
|
scale = Math.max(0.001, scale);
|
|
11445
|
-
if(fixDirections) keepSmaller = false;
|
|
12550
|
+
if (fixDirections) keepSmaller = false;
|
|
11446
12551
|
if (scale !== 1 || scaleTo || crop || alignToOrigin) {
|
|
11447
12552
|
convertTransforms = true;
|
|
11448
12553
|
settings.convertTransforms = true;
|
|
11449
12554
|
}
|
|
11450
12555
|
|
|
11451
|
-
|
|
12556
|
+
/**
|
|
12557
|
+
* intercept
|
|
12558
|
+
* invalid inputs
|
|
12559
|
+
*/
|
|
12560
|
+
|
|
12561
|
+
let inputDetection = detectInputType(input);
|
|
12562
|
+
let { inputType, log } = inputDetection;
|
|
12563
|
+
|
|
12564
|
+
// invalid file
|
|
12565
|
+
if (inputType === 'invalid' || input === dummySVG) {
|
|
12566
|
+
// return dummy SVG to continue processing
|
|
12567
|
+
|
|
12568
|
+
let report = {
|
|
12569
|
+
original: 0,
|
|
12570
|
+
new: 0,
|
|
12571
|
+
saved: 0,
|
|
12572
|
+
svgSize: 0,
|
|
12573
|
+
svgSizeOpt: 0,
|
|
12574
|
+
compression: 0,
|
|
12575
|
+
decimals: 0,
|
|
12576
|
+
invalid: true
|
|
12577
|
+
};
|
|
12578
|
+
|
|
12579
|
+
return { svg: dummySVG, d: '', polys: [], report, pathDataPlusArr: [], pathDataPlusArr_global: [], inputType: 'invalid', dOriginal: '' };
|
|
12580
|
+
|
|
12581
|
+
}
|
|
12582
|
+
|
|
11452
12583
|
let svg = '';
|
|
11453
12584
|
let svgSize = 0;
|
|
11454
12585
|
let svgSizeOpt = 0;
|
|
@@ -11568,10 +12699,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11568
12699
|
// convert all shapes to paths
|
|
11569
12700
|
if (shapesToPaths) {
|
|
11570
12701
|
shapeConvert = 'toPaths';
|
|
11571
|
-
|
|
11572
|
-
convert_ellipses = true;
|
|
11573
|
-
convert_poly = true;
|
|
11574
|
-
convert_lines = true;
|
|
12702
|
+
convertShapes = ['rect', 'polygon', 'polyline', 'line', 'circle', 'ellipse'];
|
|
11575
12703
|
}
|
|
11576
12704
|
|
|
11577
12705
|
// sanitize SVG - clone/decouple settings
|
|
@@ -11605,6 +12733,9 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11605
12733
|
decimals,
|
|
11606
12734
|
};
|
|
11607
12735
|
|
|
12736
|
+
let comCount = 0;
|
|
12737
|
+
let comCountS = 0;
|
|
12738
|
+
|
|
11608
12739
|
for (let i = 0, l = paths.length; l && i < l; i++) {
|
|
11609
12740
|
|
|
11610
12741
|
let pathDataPlusArr = [];
|
|
@@ -11612,6 +12743,12 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11612
12743
|
let { d, el } = path;
|
|
11613
12744
|
let isPoly = false;
|
|
11614
12745
|
|
|
12746
|
+
// disable reordering for elements with stroke dash-array
|
|
12747
|
+
if (el && (el.hasAttribute('stroke-dasharray') || el.hasAttribute('stroke-dashoffset'))) {
|
|
12748
|
+
optimizeOrder = false;
|
|
12749
|
+
|
|
12750
|
+
}
|
|
12751
|
+
|
|
11615
12752
|
// if polygon we already heave absolute coordinates
|
|
11616
12753
|
|
|
11617
12754
|
let pathData = parsePathDataNormalized(d, { quadraticToCubic, arcToCubic });
|
|
@@ -11649,7 +12786,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11649
12786
|
}
|
|
11650
12787
|
|
|
11651
12788
|
// count commands for evaluation
|
|
11652
|
-
|
|
12789
|
+
comCount += pathData.length;
|
|
11653
12790
|
|
|
11654
12791
|
if (!isPoly && removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
11655
12792
|
|
|
@@ -11814,11 +12951,14 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11814
12951
|
if (simplifyCorners) {
|
|
11815
12952
|
|
|
11816
12953
|
let threshold = (bb.width + bb.height) * 0.1;
|
|
11817
|
-
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
12954
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance, simplifyQuadraticCorners });
|
|
11818
12955
|
}
|
|
11819
12956
|
|
|
11820
12957
|
// refine round segment sequences
|
|
11821
|
-
if (simplifyRound)
|
|
12958
|
+
if (simplifyRound) {
|
|
12959
|
+
pathData = refineRoundSegments(pathData);
|
|
12960
|
+
pathData = simplifyAdjacentRound(pathData);
|
|
12961
|
+
}
|
|
11822
12962
|
|
|
11823
12963
|
// simplify to quadratics
|
|
11824
12964
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
@@ -11848,7 +12988,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11848
12988
|
let yMax = Math.max(...yArr);
|
|
11849
12989
|
|
|
11850
12990
|
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin };
|
|
11851
|
-
|
|
12991
|
+
bb_global.height > bb_global.width;
|
|
11852
12992
|
|
|
11853
12993
|
// fix path directions - before reordering
|
|
11854
12994
|
if (fixDirections) {
|
|
@@ -11857,7 +12997,23 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11857
12997
|
|
|
11858
12998
|
// prefer top to bottom priority for portrait aspect ratios
|
|
11859
12999
|
if (optimizeOrder) {
|
|
11860
|
-
|
|
13000
|
+
/*
|
|
13001
|
+
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)
|
|
13002
|
+
*/
|
|
13003
|
+
|
|
13004
|
+
// add missin bbox
|
|
13005
|
+
pathDataPlusArr.forEach(p => {
|
|
13006
|
+
if (p.bb.x === undefined) {
|
|
13007
|
+
p.bb = getPolyBBox(getPathDataVertices(p.pathData));
|
|
13008
|
+
}
|
|
13009
|
+
});
|
|
13010
|
+
|
|
13011
|
+
try {
|
|
13012
|
+
pathDataPlusArr = pathDataPlusArr.sort((a, b) => +a.bb.x.toFixed(2) - (+b.bb.x.toFixed(2)) || a.bb.y - b.bb.y);
|
|
13013
|
+
|
|
13014
|
+
} catch {
|
|
13015
|
+
}
|
|
13016
|
+
|
|
11861
13017
|
}
|
|
11862
13018
|
|
|
11863
13019
|
// flatten compound paths
|
|
@@ -11920,7 +13076,7 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11920
13076
|
}
|
|
11921
13077
|
|
|
11922
13078
|
// compare command count
|
|
11923
|
-
|
|
13079
|
+
comCountS += pathData.length;
|
|
11924
13080
|
|
|
11925
13081
|
let dOpt = pathDataToD(pathData, minifyD);
|
|
11926
13082
|
|
|
@@ -11995,6 +13151,9 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
11995
13151
|
svgSizeOpt = +(svgSizeOpt / 1024).toFixed(3);
|
|
11996
13152
|
|
|
11997
13153
|
report = {
|
|
13154
|
+
original: comCount,
|
|
13155
|
+
new: comCountS,
|
|
13156
|
+
saved: comCount - comCountS,
|
|
11998
13157
|
svgSize,
|
|
11999
13158
|
svgSizeOpt,
|
|
12000
13159
|
compression,
|
|
@@ -12029,9 +13188,11 @@ function svgPathSimplify(input = '', settings = {}) {
|
|
|
12029
13188
|
if (typeof window !== 'undefined') {
|
|
12030
13189
|
window.svgPathSimplify = svgPathSimplify;
|
|
12031
13190
|
window.getElementTransform = getElementTransform;
|
|
13191
|
+
window.validateSVG = validateSVG;
|
|
13192
|
+
window.detectInputType = detectInputType;
|
|
12032
13193
|
|
|
12033
13194
|
window.getViewBox = getViewBox;
|
|
12034
13195
|
|
|
12035
13196
|
}
|
|
12036
13197
|
|
|
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 };
|
|
13198
|
+
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 };
|