svg-path-simplify 0.0.7 → 0.0.9
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/README.md +25 -5
- package/dist/svg-path-simplify.esm.js +1250 -562
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4756 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +1250 -562
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +89 -29
- package/package.json +5 -3
- package/src/detect_input.js +17 -10
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +10 -1
- package/src/pathData_simplify_cubic.js +114 -143
- package/src/pathData_simplify_cubic_extrapolate.js +64 -35
- package/src/pathSimplify-main.js +113 -165
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +94 -0
- package/src/svgii/pathData_analyze.js +15 -596
- package/src/svgii/pathData_convert.js +26 -17
- package/src/svgii/pathData_interpolate.js +65 -0
- package/src/svgii/pathData_parse.js +25 -9
- package/src/svgii/pathData_parse_els.js +245 -0
- package/src/svgii/pathData_remove_collinear.js +33 -28
- package/src/svgii/pathData_remove_orphaned.js +21 -0
- package/src/svgii/pathData_remove_zerolength.js +17 -3
- package/src/svgii/pathData_reorder.js +9 -3
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/pathData_simplify_refineExtremes.js +208 -0
- package/src/svgii/pathData_split.js +43 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +35 -27
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
- package/src/pathData_simplify_cubic_arr.js +0 -50
- package/src/svgii/simplify.js +0 -248
- package/src/svgii/simplify_bezier.js +0 -470
- package/src/svgii/simplify_linetos.js +0 -93
|
@@ -32,7 +32,7 @@ export function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
|
|
|
32
32
|
let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
33
33
|
let comN = {type, values}
|
|
34
34
|
|
|
35
|
-
if (dist1 < threshold) {
|
|
35
|
+
if (dist1 && threshold && dist1 < threshold) {
|
|
36
36
|
cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
37
37
|
if (cp1_Q) {
|
|
38
38
|
//renderPoint(markers, cp1_Q )
|
|
@@ -55,13 +55,19 @@ export function convertPathData(pathData, {
|
|
|
55
55
|
decimals = 3
|
|
56
56
|
} = {}) {
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
//console.log(toShorthands, toRelative, decimals);
|
|
60
|
+
|
|
58
61
|
//if(decimals>-1 && decimals<2) pathData = roundPathData(pathData, decimals);
|
|
59
62
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
60
63
|
|
|
61
64
|
// pre round - before relative conversion to minimize distortions
|
|
62
|
-
pathData = roundPathData(pathData, decimals);
|
|
65
|
+
if(decimals>-1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
63
66
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
64
67
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
65
71
|
return pathData
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -374,7 +380,7 @@ export function pathDataToLonghands(pathData, decimals = -1, test = true) {
|
|
|
374
380
|
* L, L, C, Q => H, V, S, T
|
|
375
381
|
* reversed method: pathDataToLonghands()
|
|
376
382
|
*/
|
|
377
|
-
export function pathDataToShorthands(pathData, decimals = -1, test =
|
|
383
|
+
export function pathDataToShorthands(pathData, decimals = -1, test = false) {
|
|
378
384
|
|
|
379
385
|
//pathData = JSON.parse(JSON.stringify(pathData))
|
|
380
386
|
//console.log('has dec', pathData);
|
|
@@ -388,29 +394,28 @@ export function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
388
394
|
hasRel = /[astvqmhlc]/g.test(commandTokens);
|
|
389
395
|
}
|
|
390
396
|
|
|
391
|
-
pathData = test && hasRel ?
|
|
397
|
+
pathData = test && hasRel ? pathDataToAbsoluteOrRelative(pathData) : pathData;
|
|
398
|
+
|
|
399
|
+
let len = pathData.length
|
|
400
|
+
let pathDataShorts = new Array(len);
|
|
392
401
|
|
|
393
402
|
let comShort = {
|
|
394
403
|
type: "M",
|
|
395
404
|
values: pathData[0].values
|
|
396
405
|
};
|
|
397
406
|
|
|
398
|
-
|
|
399
|
-
//console.log('has dec');
|
|
400
|
-
comShort.decimals = pathData[0].decimals
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
let pathDataShorts = [comShort];
|
|
407
|
+
pathDataShorts[0] = comShort;
|
|
404
408
|
|
|
405
409
|
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
406
410
|
let p;
|
|
407
411
|
let tolerance = 0.01
|
|
408
412
|
|
|
409
|
-
for (let i = 1
|
|
413
|
+
for (let i = 1; i < len; i++) {
|
|
410
414
|
|
|
411
415
|
let com = pathData[i];
|
|
412
416
|
let { type, values } = com;
|
|
413
|
-
let
|
|
417
|
+
let valuesLen = values.length;
|
|
418
|
+
let valuesLast = [values[valuesLen-2], values[valuesLen-1]];
|
|
414
419
|
|
|
415
420
|
// previoius command
|
|
416
421
|
let comPrev = pathData[i - 1];
|
|
@@ -462,7 +467,8 @@ export function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
462
467
|
if (typePrev !== 'Q') {
|
|
463
468
|
//console.log('skip T:', type, typePrev);
|
|
464
469
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
465
|
-
pathDataShorts.push(com);
|
|
470
|
+
//pathDataShorts.push(com);
|
|
471
|
+
pathDataShorts[i] = com;
|
|
466
472
|
continue;
|
|
467
473
|
}
|
|
468
474
|
|
|
@@ -492,7 +498,9 @@ export function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
492
498
|
|
|
493
499
|
if (typePrev !== 'C') {
|
|
494
500
|
//console.log('skip S', typePrev);
|
|
495
|
-
pathDataShorts.push(com);
|
|
501
|
+
//pathDataShorts.push(com);
|
|
502
|
+
pathDataShorts[i] = com;
|
|
503
|
+
|
|
496
504
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
497
505
|
continue;
|
|
498
506
|
}
|
|
@@ -525,21 +533,22 @@ export function pathDataToShorthands(pathData, decimals = -1, test = true) {
|
|
|
525
533
|
};
|
|
526
534
|
}
|
|
527
535
|
|
|
528
|
-
|
|
529
536
|
// add decimal info
|
|
530
537
|
if (com.decimals || com.decimals === 0) {
|
|
531
538
|
comShort.decimals = com.decimals
|
|
532
539
|
}
|
|
533
540
|
|
|
534
|
-
|
|
535
541
|
// round final values
|
|
536
542
|
if (decimals > -1) {
|
|
537
543
|
comShort.values = comShort.values.map(val => { return +val.toFixed(decimals) })
|
|
538
544
|
}
|
|
539
545
|
|
|
540
546
|
p0 = { x: valuesLast[0], y: valuesLast[1] };
|
|
541
|
-
pathDataShorts
|
|
547
|
+
pathDataShorts[i] = comShort;
|
|
548
|
+
//pathDataShorts.push(comShort);
|
|
542
549
|
}
|
|
550
|
+
|
|
551
|
+
//console.log('pathDataShorts', pathDataShorts);
|
|
543
552
|
return pathDataShorts;
|
|
544
553
|
}
|
|
545
554
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export function interpolatedPathData(pathData1, pathData2, t = 0.5) {
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* linear interpolation helper (LERP)
|
|
5
|
+
* respecting non-interpolatable
|
|
6
|
+
* Boolean values – required by arc commands
|
|
7
|
+
*/
|
|
8
|
+
const interpolateValues = (values1 = [], values2 = [], t = 0) => {
|
|
9
|
+
|
|
10
|
+
// return start or end for t=0 or 1
|
|
11
|
+
if (t === 0) return values1;
|
|
12
|
+
if (t === 1) return values2;
|
|
13
|
+
|
|
14
|
+
let len = values1.length;
|
|
15
|
+
let isArc = len === 7;
|
|
16
|
+
|
|
17
|
+
// interpolated values: copy values1
|
|
18
|
+
let valuesI = values1.slice();
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < len; i++) {
|
|
21
|
+
let valI
|
|
22
|
+
|
|
23
|
+
// skip Boolean arc command values (largeArc and sweep)
|
|
24
|
+
if (isArc && (i === 3 || i === 4)) {
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// interpolate
|
|
29
|
+
valuesI[i] = (values2[i] - values1[i]) * t + values1[i];
|
|
30
|
+
}
|
|
31
|
+
return valuesI;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// interpolation impossible
|
|
36
|
+
if (pathData1.length !== pathData2.length) {
|
|
37
|
+
throw new Error("Paths are not compatible");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
// interpolate command coordinates
|
|
42
|
+
let pathDataI = [];
|
|
43
|
+
pathData1.forEach((com, c) => {
|
|
44
|
+
let {
|
|
45
|
+
type,
|
|
46
|
+
values
|
|
47
|
+
} = com;
|
|
48
|
+
let [type2, values2] = [pathData2[c].type, pathData2[c].values];
|
|
49
|
+
|
|
50
|
+
// interpolate command values
|
|
51
|
+
let valuesInter = interpolateValues(values, values2, t);
|
|
52
|
+
|
|
53
|
+
pathDataI.push({
|
|
54
|
+
type: type,
|
|
55
|
+
values: valuesInter,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// serialize to "d" attribute string
|
|
60
|
+
let dInter = pathDataI.map(com => `${com.type} ${com.values.join(' ')}`).join(' ');
|
|
61
|
+
return {
|
|
62
|
+
pathData: pathDataI,
|
|
63
|
+
d: dInter
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//import { arcToBezier, quadratic2Cubic } from './convert.js';
|
|
2
2
|
//import { getAngle, bezierhasExtreme, getDistance } from "./geometry";
|
|
3
3
|
import { pathDataToAbsoluteOrRelative, pathDataToLonghands, pathDataArcsToCubics, pathDataQuadraticToCubic } from './pathData_convert.js';
|
|
4
|
+
import { pathDataToD } from './pathData_stringify.js';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
|
|
@@ -15,10 +16,10 @@ export function normalizePathData(pathData = [],
|
|
|
15
16
|
quadraticToCubic = false,
|
|
16
17
|
arcToCubic = false,
|
|
17
18
|
arcAccuracy = 2,
|
|
18
|
-
} = {},
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// assume we need full normalization
|
|
21
21
|
hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true, testTypes = false
|
|
22
|
+
|
|
22
23
|
} = {}
|
|
23
24
|
) {
|
|
24
25
|
|
|
@@ -71,15 +72,29 @@ export function parsePathDataNormalized(d,
|
|
|
71
72
|
) {
|
|
72
73
|
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
let
|
|
76
|
-
|
|
75
|
+
// is already array
|
|
76
|
+
let isArray = Array.isArray(d);
|
|
77
|
+
|
|
78
|
+
// normalize native pathData to regular array
|
|
79
|
+
let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function'
|
|
80
|
+
/*
|
|
81
|
+
if (hasConstructor) {
|
|
82
|
+
d = d.map(com => { return { type: com.type, values: com.values } })
|
|
83
|
+
console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
|
|
84
|
+
}
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
let pathDataObj = isArray ? d : parsePathDataString(d);
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
|
|
91
|
+
let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
|
|
77
92
|
|
|
78
93
|
// normalize
|
|
79
94
|
pathData = normalizePathData(pathData,
|
|
80
|
-
{ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
{ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
|
|
96
|
+
hasRelatives, hasShorthands, hasQuadratics, hasArcs
|
|
97
|
+
},
|
|
83
98
|
)
|
|
84
99
|
|
|
85
100
|
return pathData;
|
|
@@ -450,7 +465,8 @@ export function parsePathDataString(d, debug = true) {
|
|
|
450
465
|
if (debug === 'log') {
|
|
451
466
|
console.log(feedback);
|
|
452
467
|
} else {
|
|
453
|
-
throw new Error(feedback)
|
|
468
|
+
//throw new Error(feedback)
|
|
469
|
+
console.warn(feedback)
|
|
454
470
|
}
|
|
455
471
|
}
|
|
456
472
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
//import { pathDataToAbsoluteOrRelative, pathDataToLonghands, cubicToArc } from './pathData_convert.js';
|
|
2
|
+
import { parsePathDataString, parsePathDataNormalized, stringifyPathData } from './pathData_parse.js';
|
|
3
|
+
|
|
4
|
+
export function shapeElToPath(el) {
|
|
5
|
+
|
|
6
|
+
let nodeName = el.nodeName.toLowerCase();
|
|
7
|
+
if (nodeName === 'path') return el;
|
|
8
|
+
|
|
9
|
+
let pathData = getPathDataFromEl(el);
|
|
10
|
+
let d = pathData.map(com => { return `${com.type} ${com.values} ` }).join(' ')
|
|
11
|
+
let attributes = [...el.attributes].map(att => att.name);
|
|
12
|
+
|
|
13
|
+
let pathN = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
14
|
+
pathN.setAttribute('d', d);
|
|
15
|
+
|
|
16
|
+
let exclude = ['x', 'y', 'cx', 'cy', 'dx', 'dy', 'r', 'rx', 'ry', 'width', 'height', 'points']
|
|
17
|
+
|
|
18
|
+
attributes.forEach(att => {
|
|
19
|
+
if (!exclude.includes(att)) {
|
|
20
|
+
let val = el.getAttribute(att);
|
|
21
|
+
pathN.setAttribute(att, val)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
//el.replaceWith(pathN)
|
|
26
|
+
return pathN
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// retrieve pathdata from svg geometry elements
|
|
32
|
+
export function getPathDataFromEl(el, stringify = false) {
|
|
33
|
+
|
|
34
|
+
let pathData = [];
|
|
35
|
+
let type = el.nodeName;
|
|
36
|
+
let atts, attNames, d, x, y, width, height, r, rx, ry, cx, cy, x1, x2, y1, y2;
|
|
37
|
+
|
|
38
|
+
// convert relative or absolute units
|
|
39
|
+
const svgElUnitsToPixel = (el, decimals = 9) => {
|
|
40
|
+
//console.log(this);
|
|
41
|
+
const svg = el.nodeName !== "svg" ? el.closest("svg") : el;
|
|
42
|
+
|
|
43
|
+
// convert real life units to pixels
|
|
44
|
+
const translateUnitToPixel = (value) => {
|
|
45
|
+
|
|
46
|
+
if (value === null) {
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
//default dpi = 96
|
|
50
|
+
let dpi = 96;
|
|
51
|
+
let unit = value.match(/([a-z]+)/gi);
|
|
52
|
+
unit = unit ? unit[0] : "";
|
|
53
|
+
let val = parseFloat(value);
|
|
54
|
+
let rat;
|
|
55
|
+
|
|
56
|
+
// no unit - already pixes/user unit
|
|
57
|
+
if (!unit) {
|
|
58
|
+
return val;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (unit) {
|
|
62
|
+
case "in":
|
|
63
|
+
rat = dpi;
|
|
64
|
+
break;
|
|
65
|
+
case "pt":
|
|
66
|
+
rat = (1 / 72) * 96;
|
|
67
|
+
break;
|
|
68
|
+
case "cm":
|
|
69
|
+
rat = (1 / 2.54) * 96;
|
|
70
|
+
break;
|
|
71
|
+
case "mm":
|
|
72
|
+
rat = ((1 / 2.54) * 96) / 10;
|
|
73
|
+
break;
|
|
74
|
+
// just a default approximation
|
|
75
|
+
case "em":
|
|
76
|
+
case "rem":
|
|
77
|
+
rat = 16;
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
rat = 1;
|
|
81
|
+
}
|
|
82
|
+
let valuePx = val * rat;
|
|
83
|
+
return +valuePx.toFixed(decimals);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// svg width and height attributes
|
|
87
|
+
let width = svg.getAttribute("width");
|
|
88
|
+
width = width ? translateUnitToPixel(width) : 300;
|
|
89
|
+
let height = svg.getAttribute("height");
|
|
90
|
+
height = width ? translateUnitToPixel(height) : 150;
|
|
91
|
+
|
|
92
|
+
//prefer viewBox values
|
|
93
|
+
let vB = svg.getAttribute("viewBox");
|
|
94
|
+
vB = vB
|
|
95
|
+
? vB
|
|
96
|
+
.replace(/,/g, " ")
|
|
97
|
+
.split(" ")
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.map((val) => {
|
|
100
|
+
return +val;
|
|
101
|
+
})
|
|
102
|
+
: [];
|
|
103
|
+
|
|
104
|
+
let w = vB.length ? vB[2] : width;
|
|
105
|
+
let h = vB.length ? vB[3] : height;
|
|
106
|
+
let scaleX = w / 100;
|
|
107
|
+
let scaleY = h / 100;
|
|
108
|
+
let scalRoot = Math.sqrt((Math.pow(scaleX, 2) + Math.pow(scaleY, 2)) / 2);
|
|
109
|
+
|
|
110
|
+
let attsH = ["x", "width", "x1", "x2", "rx", "cx", "r"];
|
|
111
|
+
let attsV = ["y", "height", "y1", "y2", "ry", "cy"];
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
let atts = el.getAttributeNames();
|
|
115
|
+
atts.forEach((att) => {
|
|
116
|
+
let val = el.getAttribute(att);
|
|
117
|
+
let valAbs = val;
|
|
118
|
+
if (attsH.includes(att) || attsV.includes(att)) {
|
|
119
|
+
let scale = attsH.includes(att) ? scaleX : scaleY;
|
|
120
|
+
scale = att === "r" && w != h ? scalRoot : scale;
|
|
121
|
+
let unit = val.match(/([a-z|%]+)/gi);
|
|
122
|
+
unit = unit ? unit[0] : "";
|
|
123
|
+
if (val.includes("%")) {
|
|
124
|
+
valAbs = parseFloat(val) * scale;
|
|
125
|
+
}
|
|
126
|
+
//absolute units
|
|
127
|
+
else {
|
|
128
|
+
valAbs = translateUnitToPixel(val);
|
|
129
|
+
}
|
|
130
|
+
el.setAttribute(att, +valAbs);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
svgElUnitsToPixel(el)
|
|
136
|
+
|
|
137
|
+
const getAtts = (attNames) => {
|
|
138
|
+
atts = {}
|
|
139
|
+
attNames.forEach(att => {
|
|
140
|
+
atts[att] = +el.getAttribute(att)
|
|
141
|
+
})
|
|
142
|
+
return atts
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
switch (type) {
|
|
146
|
+
case 'path':
|
|
147
|
+
d = el.getAttribute("d");
|
|
148
|
+
pathData = parsePathDataNormalized(d);
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'rect':
|
|
152
|
+
attNames = ['x', 'y', 'width', 'height', 'rx', 'ry'];
|
|
153
|
+
({ x, y, width, height, rx, ry } = getAtts(attNames));
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if (!rx && !ry) {
|
|
157
|
+
pathData = [
|
|
158
|
+
{ type: "M", values: [x, y] },
|
|
159
|
+
{ type: "L", values: [x + width, y] },
|
|
160
|
+
{ type: "L", values: [x + width, y + height] },
|
|
161
|
+
{ type: "L", values: [x, y + height] },
|
|
162
|
+
{ type: "Z", values: [] }
|
|
163
|
+
];
|
|
164
|
+
} else {
|
|
165
|
+
|
|
166
|
+
if (rx > width / 2) {
|
|
167
|
+
rx = width / 2;
|
|
168
|
+
}
|
|
169
|
+
if (ry > height / 2) {
|
|
170
|
+
ry = height / 2;
|
|
171
|
+
}
|
|
172
|
+
pathData = [
|
|
173
|
+
{ type: "M", values: [x + rx, y] },
|
|
174
|
+
{ type: "L", values: [x + width - rx, y] },
|
|
175
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width, y + ry] },
|
|
176
|
+
{ type: "L", values: [x + width, y + height - ry] },
|
|
177
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + width - rx, y + height] },
|
|
178
|
+
{ type: "L", values: [x + rx, y + height] },
|
|
179
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x, y + height - ry] },
|
|
180
|
+
{ type: "L", values: [x, y + ry] },
|
|
181
|
+
{ type: "A", values: [rx, ry, 0, 0, 1, x + rx, y] },
|
|
182
|
+
{ type: "Z", values: [] }
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case 'circle':
|
|
188
|
+
case 'ellipse':
|
|
189
|
+
|
|
190
|
+
attNames = ['cx', 'cy', 'rx', 'ry', 'r'];
|
|
191
|
+
({ cx, cy, r, rx, ry } = getAtts(attNames));
|
|
192
|
+
|
|
193
|
+
let isCircle = type === 'circle';
|
|
194
|
+
|
|
195
|
+
if (isCircle) {
|
|
196
|
+
r = r;
|
|
197
|
+
rx = r
|
|
198
|
+
ry = r
|
|
199
|
+
} else {
|
|
200
|
+
rx = rx ? rx : r;
|
|
201
|
+
ry = ry ? ry : r;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// simplified radii for cirecles
|
|
205
|
+
let rxS = isCircle && r>=1 ? 1 : rx;
|
|
206
|
+
let ryS = isCircle && r>=1 ? 1 : rx;
|
|
207
|
+
|
|
208
|
+
pathData = [
|
|
209
|
+
{ type: "M", values: [cx + rx, cy] },
|
|
210
|
+
{ type: "A", values: [rxS, ryS, 0, 1, 1, cx - rx, cy] },
|
|
211
|
+
{ type: "A", values: [rxS, ryS, 0, 1, 1, cx + rx, cy] },
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
break;
|
|
215
|
+
case 'line':
|
|
216
|
+
attNames = ['x1', 'y1', 'x2', 'y2'];
|
|
217
|
+
({ x1, y1, x2, y2 } = getAtts(attNames));
|
|
218
|
+
pathData = [
|
|
219
|
+
{ type: "M", values: [x1, y1] },
|
|
220
|
+
{ type: "L", values: [x2, y2] }
|
|
221
|
+
];
|
|
222
|
+
break;
|
|
223
|
+
case 'polygon':
|
|
224
|
+
case 'polyline':
|
|
225
|
+
|
|
226
|
+
let points = el.getAttribute('points').replaceAll(',', ' ').split(' ').filter(Boolean)
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
229
|
+
pathData.push({
|
|
230
|
+
type: (i === 0 ? "M" : "L"),
|
|
231
|
+
values: [+points[i], +points[i + 1]]
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (type === 'polygon') {
|
|
235
|
+
pathData.push({
|
|
236
|
+
type: "Z",
|
|
237
|
+
values: []
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return stringify ? stringifyPathData(pathData) : pathData;
|
|
244
|
+
|
|
245
|
+
};
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getDistAv, getSquareDistance } from "./geometry.js";
|
|
2
2
|
import { getPolygonArea } from "./geometry_area.js";
|
|
3
|
+
import { checkBezierFlatness, commandIsFlat } from "./geometry_flatness.js";
|
|
3
4
|
import { renderPoint } from "./visualize.js";
|
|
4
5
|
|
|
5
|
-
export function pathDataRemoveColinear(pathData,
|
|
6
|
+
export function pathDataRemoveColinear(pathData, {
|
|
7
|
+
tolerance = 1,
|
|
8
|
+
//toleranceCubics = null,
|
|
9
|
+
flatBezierToLinetos = true
|
|
10
|
+
}={}) {
|
|
6
11
|
|
|
12
|
+
//toleranceCubics = !toleranceCubics ? tolerance : toleranceCubics;
|
|
7
13
|
let pathDataN = [pathData[0]];
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
let lastType = 'L';
|
|
11
15
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
12
16
|
let p0 = M;
|
|
13
17
|
let p = M
|
|
14
18
|
let isClosed = pathData[pathData.length - 1].type.toLowerCase() === 'z'
|
|
15
19
|
|
|
16
20
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
17
|
-
let comPrev = pathData[c - 1];
|
|
21
|
+
//let comPrev = pathData[c - 1];
|
|
18
22
|
let com = pathData[c];
|
|
19
23
|
let comN = pathData[c + 1] || pathData[l - 1];
|
|
24
|
+
//let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
|
|
20
25
|
let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
|
|
21
26
|
|
|
22
27
|
let { type, values } = com;
|
|
@@ -25,19 +30,18 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
25
30
|
|
|
26
31
|
let area = getPolygonArea([p0, p, p1], true)
|
|
27
32
|
|
|
28
|
-
let distSquare0 = getSquareDistance(p0, p)
|
|
29
|
-
let distSquare1 = getSquareDistance(p, p1)
|
|
33
|
+
//let distSquare0 = getSquareDistance(p0, p)
|
|
34
|
+
//let distSquare1 = getSquareDistance(p, p1)
|
|
30
35
|
let distSquare = getSquareDistance(p0, p1)
|
|
31
|
-
//distSquare = (distSquare0+distSquare1)
|
|
36
|
+
//distSquare = (distSquare0+distSquare1) * 0.5
|
|
32
37
|
|
|
33
|
-
let distMax = distSquare /
|
|
38
|
+
let distMax = distSquare ? distSquare / 333 * tolerance : 0
|
|
34
39
|
|
|
35
40
|
let isFlat = area < distMax;
|
|
36
41
|
let isFlatBez = false;
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
if (!flatBezierToLinetos && type === 'C') isFlat = false;
|
|
40
|
-
//let isFlat = flatBezierToLinetos && type === 'C' ? area < distMax : false
|
|
41
45
|
|
|
42
46
|
// convert flat beziers to linetos
|
|
43
47
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
@@ -46,40 +50,41 @@ export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLine
|
|
|
46
50
|
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
47
51
|
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
48
52
|
|
|
53
|
+
isFlatBez = commandIsFlat([p0, ...cpts, p],{tolerance});
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
isFlatBez = checkBezierFlatness(p0, cpts, p)
|
|
53
|
-
// console.log();
|
|
54
|
-
|
|
55
|
-
//isFlatBez = areaBez < distMax * 0.25
|
|
56
|
-
//console.log('isFlatBez', isFlatBez);
|
|
57
|
-
//isFlatBez = false
|
|
58
|
-
|
|
59
|
-
//&& comPrev.type !== 'C'
|
|
60
|
-
if (isFlatBez && c < l - 1 && comPrev.type !== 'C') {
|
|
55
|
+
if (isFlatBez && c < l - 1 ) {
|
|
61
56
|
type = "L"
|
|
62
57
|
com.type = "L"
|
|
63
58
|
com.values = valsL
|
|
64
|
-
|
|
65
|
-
//renderPoint(markers, p)
|
|
59
|
+
//renderPoint(markers, p, 'cyan', '1%', '0.5')
|
|
66
60
|
}
|
|
67
|
-
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
// update end point
|
|
71
|
-
p0 = p;
|
|
72
63
|
|
|
73
64
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
74
65
|
//&& comN.type==='L'
|
|
75
66
|
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
76
|
-
//console.log(area,distMax );
|
|
77
|
-
//renderPoint(markers, p)
|
|
78
67
|
|
|
68
|
+
/*
|
|
69
|
+
console.log(area, distMax );
|
|
79
70
|
//if(comN.type!=='L' ){}
|
|
71
|
+
|
|
72
|
+
if(p0.x === p.x && p0.y === p.y){
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
renderPoint(markers, p0, 'blue', '1.5%', '1')
|
|
77
|
+
renderPoint(markers, p, 'red', '1%', '1')
|
|
78
|
+
renderPoint(markers, p1, 'cyan', '0.5%', '1')
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
|
|
80
82
|
continue;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
// update end point
|
|
86
|
+
p0 = p;
|
|
87
|
+
|
|
83
88
|
|
|
84
89
|
if (type === 'M') {
|
|
85
90
|
M = p
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function removeOrphanedM(pathData) {
|
|
2
|
+
|
|
3
|
+
let pathDataN = []
|
|
4
|
+
for (let i = 0, l = pathData.length; i < l; i++) {
|
|
5
|
+
let com = pathData[i];
|
|
6
|
+
if (!com) continue;
|
|
7
|
+
let { type = null, values = [] } = com;
|
|
8
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
9
|
+
if ((type === 'M' || type === 'm')) {
|
|
10
|
+
|
|
11
|
+
if (!comN || (comN && (comN.type === 'Z' || comN.type === 'z'))) {
|
|
12
|
+
if(comN) i++
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
pathDataN.push(com)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return pathDataN;
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
|
|
2
|
+
/*
|
|
2
3
|
// remove zero-length segments introduced by rounding
|
|
3
4
|
export function removeZeroLengthLinetos_post(pathData) {
|
|
4
5
|
let pathDataOpt = []
|
|
@@ -13,6 +14,7 @@ export function removeZeroLengthLinetos_post(pathData) {
|
|
|
13
14
|
})
|
|
14
15
|
return pathDataOpt
|
|
15
16
|
}
|
|
17
|
+
*/
|
|
16
18
|
|
|
17
19
|
export function removeZeroLengthLinetos(pathData) {
|
|
18
20
|
|
|
@@ -24,16 +26,28 @@ export function removeZeroLengthLinetos(pathData) {
|
|
|
24
26
|
|
|
25
27
|
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
26
28
|
let com = pathData[c];
|
|
29
|
+
let comPrev = pathData[c-1]
|
|
30
|
+
let comNext = pathData[c+1] || null
|
|
27
31
|
let { type, values } = com;
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// zero length segments are simetimes used in icons for dots
|
|
34
|
+
let isDot = comPrev.type.toLowerCase() ==='m' && !comNext;
|
|
35
|
+
|
|
36
|
+
let valsLen = values.length;
|
|
37
|
+
p = { x: values[valsLen-2], y: values[valsLen-1] };
|
|
31
38
|
|
|
32
39
|
// skip lineto
|
|
33
|
-
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
40
|
+
if (!isDot && type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
34
41
|
continue
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
|
|
45
|
+
// skip minified zero length
|
|
46
|
+
if (!isDot && (type === 'l' || type === 'v' || type === 'h')) {
|
|
47
|
+
let noLength = type === 'l' ? (values.join('') === '00') : values[0] === 0;
|
|
48
|
+
if(noLength) continue
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
pathDataN.push(com)
|
|
38
52
|
p0 = p;
|
|
39
53
|
}
|