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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { splitSubpaths, addExtemesToCommand } from './pathData_split.js';
|
|
2
|
-
import { getComThresh,
|
|
2
|
+
import { getComThresh, getPathDataVertices, getSquareDistance } from './geometry.js';
|
|
3
3
|
import { getPolyBBox } from './geometry_bbox.js';
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
import { renderPoint, renderPath } from './visualize.js';
|
|
7
7
|
import { getPolygonArea } from './geometry_area.js';
|
|
8
|
+
import { checkBezierFlatness, commandIsFlat } from "./geometry_flatness.js";
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
export function pathDataToTopLeft(pathData) {
|
|
@@ -12,6 +14,8 @@ export function pathDataToTopLeft(pathData) {
|
|
|
12
14
|
let len = pathData.length;
|
|
13
15
|
let isClosed = pathData[len - 1].type.toLowerCase() === 'z'
|
|
14
16
|
|
|
17
|
+
//return pathData;
|
|
18
|
+
|
|
15
19
|
// we can't change starting point for non closed paths
|
|
16
20
|
if (!isClosed) {
|
|
17
21
|
return pathData
|
|
@@ -52,6 +56,8 @@ export function optimizeClosePath(pathData, removeFinalLineto = true, reorder =
|
|
|
52
56
|
|
|
53
57
|
let linetos = pathData.filter(com => com.type === 'L')
|
|
54
58
|
|
|
59
|
+
//return pathData;
|
|
60
|
+
|
|
55
61
|
|
|
56
62
|
// check if order is ideal
|
|
57
63
|
let penultimateCom = pathData[len - 2];
|
|
@@ -105,7 +111,7 @@ export function optimizeClosePath(pathData, removeFinalLineto = true, reorder =
|
|
|
105
111
|
}
|
|
106
112
|
// use top most command
|
|
107
113
|
else {
|
|
108
|
-
indices = indices.sort((a, b) => +a.y.toFixed(
|
|
114
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x - b.x);
|
|
109
115
|
newIndex = indices[0].index
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -114,7 +120,7 @@ export function optimizeClosePath(pathData, removeFinalLineto = true, reorder =
|
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
|
|
117
|
-
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(
|
|
123
|
+
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) }
|
|
118
124
|
|
|
119
125
|
len = pathData.length
|
|
120
126
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { checkLineIntersection, getDistAv, interpolate, pointAtT } from "./geometry";
|
|
2
|
+
import { getPolygonArea } from "./geometry_area";
|
|
3
|
+
import { commandIsFlat } from "./geometry_flatness";
|
|
4
|
+
import { renderPoint } from "./visualize";
|
|
5
|
+
|
|
6
|
+
export function refineRoundedCorners(pathData, {
|
|
7
|
+
threshold = 0,
|
|
8
|
+
tolerance = 1
|
|
9
|
+
} = {}) {
|
|
10
|
+
|
|
11
|
+
let l = pathData.length;
|
|
12
|
+
|
|
13
|
+
// add fist command
|
|
14
|
+
let pathDataN = [pathData[0]]
|
|
15
|
+
|
|
16
|
+
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
17
|
+
let lastOff = isClosed ? 2 : 1;
|
|
18
|
+
|
|
19
|
+
let comLast = pathData[l - lastOff];
|
|
20
|
+
let lastIsLine = comLast.type === 'L'
|
|
21
|
+
let lastIsBez = comLast.type === 'C'
|
|
22
|
+
let firstIsLine = pathData[1].type === 'L';
|
|
23
|
+
let firstIsBez = pathData[1].type === 'C';
|
|
24
|
+
|
|
25
|
+
//console.log('lastIsLine', lastIsLine, 'firstIsLine', firstIsLine, 'lastIsBez', lastIsBez, 'firstIsBez', firstIsBez, 'isClosed', isClosed, 'comLast1', comLast1);
|
|
26
|
+
|
|
27
|
+
let normalizeClose = isClosed && firstIsBez;
|
|
28
|
+
//console.log('normalizeClose', normalizeClose);
|
|
29
|
+
|
|
30
|
+
// normalize closepath to lineto
|
|
31
|
+
if (normalizeClose) {
|
|
32
|
+
pathData[l - 1].values = pathData[0].values
|
|
33
|
+
pathData[l - 1].type = 'L'
|
|
34
|
+
lastIsLine = true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 1; i < l; i++) {
|
|
38
|
+
let com = pathData[i];
|
|
39
|
+
let { type } = com;
|
|
40
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
41
|
+
|
|
42
|
+
// search small cubic segments enclosed by linetos
|
|
43
|
+
if ((type === 'L' && comN && comN.type === 'C') ||
|
|
44
|
+
(type === 'C' && comN && comN.type === 'L')
|
|
45
|
+
|
|
46
|
+
) {
|
|
47
|
+
let comL0 = com;
|
|
48
|
+
let comL1 = null;
|
|
49
|
+
let comBez = [];
|
|
50
|
+
let offset = 0;
|
|
51
|
+
|
|
52
|
+
// start to end
|
|
53
|
+
if (i === 1 && firstIsBez && lastIsLine) {
|
|
54
|
+
comBez = [pathData[1]]
|
|
55
|
+
comL0 = pathData[l - 1]
|
|
56
|
+
comL1 = comN
|
|
57
|
+
//renderPoint(markers, com.p, 'orange')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// closing corner to start
|
|
61
|
+
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
62
|
+
comL1 = pathData[1]
|
|
63
|
+
comBez = [pathData[l - lastOff]]
|
|
64
|
+
//renderPoint(markers, com.p)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (let j = i + 1; j < l; j++) {
|
|
68
|
+
let comN = pathData[j] ? pathData[j] : null;
|
|
69
|
+
let comPrev = pathData[j - 1];
|
|
70
|
+
|
|
71
|
+
if (comPrev.type === 'C') {
|
|
72
|
+
comBez.push(comPrev)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (comN.type === 'L' && comPrev.type === 'C') {
|
|
76
|
+
comL1 = comN
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
offset++
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (comL1) {
|
|
83
|
+
|
|
84
|
+
// linetos
|
|
85
|
+
let len1 = getDistAv(comL0.p0, comL0.p)
|
|
86
|
+
let len2 = getDistAv(comL1.p0, comL1.p)
|
|
87
|
+
|
|
88
|
+
// bezier
|
|
89
|
+
//comBez = comBez[0];
|
|
90
|
+
let comBezLen = comBez.length;
|
|
91
|
+
let len3 = getDistAv(comBez[0].p0, comBez[comBezLen - 1].p)
|
|
92
|
+
|
|
93
|
+
// check concaveness by area sign change
|
|
94
|
+
let area1 = getPolygonArea([comL0.p0, comL0.p, comL1.p0, comL1.p], false)
|
|
95
|
+
let area2 = getPolygonArea([comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], false)
|
|
96
|
+
|
|
97
|
+
let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0)
|
|
98
|
+
|
|
99
|
+
if (comBez && !signChange && len3 < threshold && len1 > len3 && len2 > len3) {
|
|
100
|
+
|
|
101
|
+
let ptQ = checkLineIntersection(comL0.p0, comL0.p, comL1.p0, comL1.p, false)
|
|
102
|
+
if (ptQ) {
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
let dist1 = getDistAv(ptQ, comL0.p)
|
|
106
|
+
let dist2 = getDistAv(ptQ, comL1.p0)
|
|
107
|
+
let diff = Math.abs(dist1-dist2)
|
|
108
|
+
let rat = diff/Math.max(dist1, dist2)
|
|
109
|
+
console.log('rat', rat);
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
// adjust curve start and end to meet original
|
|
114
|
+
let t = 1
|
|
115
|
+
|
|
116
|
+
let p0_2 = pointAtT([ptQ, comL0.p], t)
|
|
117
|
+
//renderPoint(markers, p0_2, 'cyan', '1%', '0.5')
|
|
118
|
+
comL0.p = p0_2
|
|
119
|
+
comL0.values = [p0_2.x, p0_2.y]
|
|
120
|
+
|
|
121
|
+
let p_2 = pointAtT([ptQ, comL1.p0], t)
|
|
122
|
+
//renderPoint(markers, p_2, 'orange', '1%', '0.5')
|
|
123
|
+
comL1.p0 = p_2
|
|
124
|
+
|
|
125
|
+
//renderPoint(markers, comL0.p, 'red', '1%', '0.5')
|
|
126
|
+
//renderPoint(markers, ptQ, 'magenta')
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] }
|
|
131
|
+
comQ.p0 = comL0.p;
|
|
132
|
+
comQ.cp1 = ptQ;
|
|
133
|
+
comQ.p = comL1.p0;
|
|
134
|
+
|
|
135
|
+
// add quadratic command
|
|
136
|
+
pathDataN.push(comL0, comQ);
|
|
137
|
+
i += offset;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// skip last lineto
|
|
145
|
+
if (normalizeClose && i === l - 1 && type === 'L') {
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pathDataN.push(com)
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// revert close path normalization
|
|
154
|
+
if (normalizeClose) {
|
|
155
|
+
pathDataN.push({ type: 'Z', values: [] })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return pathDataN;
|
|
159
|
+
|
|
160
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { findSplitT, getExtrapolatedCommand } from "../pathData_simplify_cubic";
|
|
2
|
+
import { getCombinedByDominant } from "../pathData_simplify_cubic_extrapolate";
|
|
3
|
+
import { getDistAv, interpolate } from "./geometry";
|
|
4
|
+
import { getPathArea, getPolygonArea } from "./geometry_area";
|
|
5
|
+
import { getPathDataBBox } from "./geometry_bbox";
|
|
6
|
+
import { interpolatedPathData } from "./pathData_interpolate";
|
|
7
|
+
import { pathDataToD } from "./pathData_stringify";
|
|
8
|
+
import { renderPath, renderPoint } from "./visualize";
|
|
9
|
+
|
|
10
|
+
export function refineAdjacentExtremes(pathData, {
|
|
11
|
+
threshold = null, tolerance = 1
|
|
12
|
+
} = {}) {
|
|
13
|
+
|
|
14
|
+
//dimA = dimA ? dimA :
|
|
15
|
+
if (!threshold) {
|
|
16
|
+
let bb = getPathDataBBox(pathData);
|
|
17
|
+
threshold = (bb.width + bb.height) / 2 * 0.05
|
|
18
|
+
//console.log('new threshold', threshold);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//let bb = getPathDataBBox(pathData);
|
|
22
|
+
//threshold = (bb.width + bb.height) / 2 * 0.1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
let l = pathData.length
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < l; i++) {
|
|
28
|
+
let com = pathData[i];
|
|
29
|
+
let { type, values, extreme, corner = false, dimA, p0, p } = com;
|
|
30
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
31
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// check dist
|
|
35
|
+
let diff = comN ? getDistAv(p, comN.p) : Infinity;
|
|
36
|
+
let isCose = diff < threshold;
|
|
37
|
+
|
|
38
|
+
let diff2 = comN2 ? getDistAv(comN2.p, comN.p) : Infinity
|
|
39
|
+
let isCose2 = diff2 < threshold;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// next is extreme
|
|
43
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme && comN2 && comN2.extreme) {
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if (isCose2 || isCose) {
|
|
47
|
+
|
|
48
|
+
// extrapolate
|
|
49
|
+
let comEx = getCombinedByDominant(comN, comN2, threshold, tolerance, false)
|
|
50
|
+
//console.log('comEx', comEx);
|
|
51
|
+
//renderPoint(markers, comN.p)
|
|
52
|
+
|
|
53
|
+
if (comEx.length === 1) {
|
|
54
|
+
|
|
55
|
+
pathData[i + 1] = null;
|
|
56
|
+
comEx = comEx[0]
|
|
57
|
+
|
|
58
|
+
pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y]
|
|
59
|
+
pathData[i + 2].cp1 = comEx.cp1
|
|
60
|
+
pathData[i + 2].cp2 = comEx.cp2
|
|
61
|
+
pathData[i + 2].p0 = comEx.p0
|
|
62
|
+
pathData[i + 2].p = comEx.p
|
|
63
|
+
pathData[i + 2].extreme = comEx.extreme
|
|
64
|
+
|
|
65
|
+
i++
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
// short after extreme
|
|
74
|
+
|
|
75
|
+
if (comN && type === 'C' && comN.type === 'C' && extreme ) {
|
|
76
|
+
|
|
77
|
+
if (isCose) {
|
|
78
|
+
|
|
79
|
+
//renderPoint(markers, com.p, 'cyan', '1%', '0.5')
|
|
80
|
+
//renderPoint(markers, comN.p, 'cyan', '1%', '0.5')
|
|
81
|
+
//console.log(comN);
|
|
82
|
+
//console.log(diff, threshold);
|
|
83
|
+
|
|
84
|
+
let dx1 = (com.cp1.x - comN.p0.x)
|
|
85
|
+
let dy1 = (com.cp1.y - comN.p0.y)
|
|
86
|
+
|
|
87
|
+
let horizontal = Math.abs(dy1) < Math.abs(dx1);
|
|
88
|
+
|
|
89
|
+
let pN = comN.p;
|
|
90
|
+
let ptI;
|
|
91
|
+
let t = 1;
|
|
92
|
+
|
|
93
|
+
let area0 = getPolygonArea([com.p0, com.p , comN.p])
|
|
94
|
+
// cpts area
|
|
95
|
+
let area1 = getPolygonArea([com.p0, com.cp1, com.cp2, com.p])
|
|
96
|
+
|
|
97
|
+
// sign change: is corner => skip
|
|
98
|
+
if ( (area0<0 && area1>0) || (area0>0 && area1<0)) {
|
|
99
|
+
//renderPoint(markers, com.p, 'orange', '1%', '0.5')
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if (comN.extreme) {
|
|
105
|
+
|
|
106
|
+
// extend cp2
|
|
107
|
+
if (horizontal) {
|
|
108
|
+
t = Math.abs(Math.abs(comN.cp2.x - comN.p.x) / Math.abs(com.cp2.x - com.p.x))
|
|
109
|
+
t = Math.min(1, t)
|
|
110
|
+
//console.log('t', t);
|
|
111
|
+
|
|
112
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t)
|
|
113
|
+
com.cp2.x = ptI.x
|
|
114
|
+
//renderPoint(markers, com.cp2, 'cyan', '1%', '0.5')
|
|
115
|
+
//renderPoint(markers, ptI, 'orange', '1%', '0.5')
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
//renderPoint(markers, comN.p0, 'cyan', '1%', '0.5')
|
|
119
|
+
t = Math.abs(Math.abs(comN.cp2.y - comN.p.y) / Math.abs(com.cp2.y - com.p.y))
|
|
120
|
+
t = Math.min(1, t)
|
|
121
|
+
//console.log('t v', t);
|
|
122
|
+
|
|
123
|
+
ptI = interpolate(comN.p, com.cp2, 1 + t)
|
|
124
|
+
com.cp2.y = ptI.y
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//merge commands
|
|
128
|
+
pathData[i + 1].values = [com.cp1.x, com.cp1.y, com.cp2.x, com.cp2.y, pN.x, pN.y]
|
|
129
|
+
pathData[i + 1].cp1 = com.cp1
|
|
130
|
+
pathData[i + 1].cp2 = com.cp2
|
|
131
|
+
pathData[i + 1].p0 = com.p0
|
|
132
|
+
pathData[i + 1].p = pN
|
|
133
|
+
pathData[i + 1].extreme = true
|
|
134
|
+
|
|
135
|
+
// nullify 1st
|
|
136
|
+
pathData[i] = null;
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// remove commands
|
|
151
|
+
pathData = pathData.filter(Boolean)
|
|
152
|
+
l = pathData.length
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* refine closing commands
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
let closed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
161
|
+
let lastIdx = closed ? l - 2 : l - 1;
|
|
162
|
+
let lastCom = pathData[lastIdx];
|
|
163
|
+
let penultimateCom = pathData[lastIdx - 1] || null;
|
|
164
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
165
|
+
|
|
166
|
+
let dec = 8
|
|
167
|
+
let lastVals = lastCom.values.slice(-2);
|
|
168
|
+
let isClosingTo = +lastVals[0].toFixed(dec) === +M.x.toFixed(dec) && +lastVals[1].toFixed(dec) === +M.y.toFixed(dec)
|
|
169
|
+
let fistExt = pathData[1].type === 'C' && pathData[1].extreme ? pathData[1] : null;
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
//renderPoint(markers, M, 'blue')
|
|
173
|
+
//renderPoint(markers, fistExt.cp1, 'blue')
|
|
174
|
+
//renderPoint(markers, fistExt.p0, 'blue')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
let diff = getDistAv(lastCom.p0, lastCom.p)
|
|
179
|
+
let isCose = diff < threshold;
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if (penultimateCom && penultimateCom.type === 'C' && isCose && isClosingTo && fistExt) {
|
|
183
|
+
|
|
184
|
+
//let dx1 = Math.abs(fistExt.cp1.x - M.x)
|
|
185
|
+
//let dy1 = Math.abs(fistExt.cp1.y - M.y)
|
|
186
|
+
|
|
187
|
+
//let horizontal = dy1 < dx1;
|
|
188
|
+
//console.log(dx1, dx2);
|
|
189
|
+
//console.log('isCose', isCose, diff, dimA);
|
|
190
|
+
|
|
191
|
+
let comEx = getCombinedByDominant(penultimateCom, lastCom, threshold, tolerance, false)
|
|
192
|
+
//console.log('comEx', comEx);
|
|
193
|
+
|
|
194
|
+
if (comEx.length === 1) {
|
|
195
|
+
pathData[lastIdx - 1] = comEx[0];
|
|
196
|
+
pathData[lastIdx] = null;
|
|
197
|
+
pathData = pathData.filter(Boolean)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
//console.log(pathData);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
//console.log('pathData ex', pathData);
|
|
205
|
+
|
|
206
|
+
return pathData
|
|
207
|
+
|
|
208
|
+
}
|
|
@@ -2,6 +2,7 @@ import { pointAtT, svgArcToCenterParam, getBezierExtremeT } from "./geometry";
|
|
|
2
2
|
import { renderPoint, renderPath } from "./visualize";
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
/**
|
|
6
7
|
* split segments into chunks to
|
|
7
8
|
* prevent simplification across
|
|
@@ -124,31 +125,58 @@ export function getPathDataPlusChunks(pathDataPlus = [], debug = false) {
|
|
|
124
125
|
* split compound paths into
|
|
125
126
|
* sub path data array
|
|
126
127
|
*/
|
|
127
|
-
export function splitSubpaths(pathData) {
|
|
128
128
|
|
|
129
|
+
export function splitSubpaths(pathData) {
|
|
129
130
|
let subPathArr = [];
|
|
131
|
+
let current = [pathData[0]];
|
|
132
|
+
let l = pathData.length;
|
|
133
|
+
|
|
134
|
+
for (let i = 1; i < l; i++) {
|
|
135
|
+
let com = pathData[i];
|
|
136
|
+
|
|
137
|
+
if (com.type === 'M' || com.type === 'm') {
|
|
138
|
+
subPathArr.push(current);
|
|
139
|
+
current = [];
|
|
140
|
+
}
|
|
141
|
+
current.push(com);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (current.length) subPathArr.push(current);
|
|
145
|
+
|
|
146
|
+
//console.log(subPathArr);
|
|
147
|
+
return subPathArr;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
export function splitSubpaths0(pathData) {
|
|
153
|
+
|
|
154
|
+
let indices = [0];
|
|
155
|
+
let l = pathData.length;
|
|
156
|
+
//let com
|
|
157
|
+
//console.log(pathData);
|
|
130
158
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
try{
|
|
134
|
-
let subPathIndices = pathData.map((com, i) => (com.type.toLowerCase() === 'm' ? i : -1)).filter(i => i !== -1);
|
|
159
|
+
if(!l) return [];
|
|
135
160
|
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
//find split segments indices introduced by M commands
|
|
162
|
+
for(let i=1; i<l; i++){
|
|
163
|
+
let type= pathData[i].type.toLowerCase();
|
|
164
|
+
if(type==='m') indices.push(i)
|
|
138
165
|
}
|
|
166
|
+
//console.log(indices);
|
|
139
167
|
|
|
168
|
+
// only one sub path
|
|
169
|
+
let len = indices.length;
|
|
170
|
+
if(len===1) return [pathData];
|
|
140
171
|
|
|
141
|
-
let
|
|
142
|
-
//let subPathIndices = pathData.map((com, i) => (com.type === 'M' ? i : -1)).filter(i => i !== -1);
|
|
172
|
+
let subPathArr = new Array(len);
|
|
143
173
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
174
|
+
for(let i=0; i<len; i++){
|
|
175
|
+
let idx = indices[i]
|
|
176
|
+
subPathArr[i] = pathData.slice(idx, indices[i + 1]);
|
|
147
177
|
}
|
|
148
|
-
subPathIndices.forEach((index, i) => {
|
|
149
|
-
subPathArr.push(pathData.slice(index, subPathIndices[i + 1]));
|
|
150
|
-
});
|
|
151
178
|
|
|
179
|
+
//console.log(subPathArr);
|
|
152
180
|
return subPathArr;
|
|
153
181
|
}
|
|
154
182
|
|
|
@@ -9,17 +9,10 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
9
9
|
optimize = parseFloat(optimize)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
let len = pathData.length;
|
|
14
13
|
let beautify = optimize > 1;
|
|
15
14
|
let minify = beautify || optimize ? false : true;
|
|
16
15
|
|
|
17
|
-
// Convert first "M" to "m" if followed by "l" (when minified)
|
|
18
|
-
/*
|
|
19
|
-
if (pathData[1].type === "l" && minify) {
|
|
20
|
-
pathData[0].type = "m";
|
|
21
|
-
}
|
|
22
|
-
*/
|
|
23
16
|
|
|
24
17
|
let d = '';
|
|
25
18
|
let separator_command = beautify ? `\n` : (minify ? '' : ' ');
|
|
@@ -43,13 +36,11 @@ export function pathDataToD(pathData, optimize = 0) {
|
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
// Omit type for repeated commands
|
|
46
|
-
type = (com0.type === com.type && com.type.toLowerCase() !== 'm'
|
|
39
|
+
type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm' )
|
|
47
40
|
? " "
|
|
48
|
-
: (
|
|
49
|
-
(com0.type === "M" && com.type === "L")
|
|
50
|
-
) && minify
|
|
41
|
+
: (minify && com0.type === "M" && com.type === "L"
|
|
51
42
|
? " "
|
|
52
|
-
: com.type;
|
|
43
|
+
: com.type);
|
|
53
44
|
|
|
54
45
|
|
|
55
46
|
// concatenate subsequent floating point values
|
package/src/svgii/rounding.js
CHANGED
|
@@ -11,7 +11,7 @@ export function detectAccuracy(pathData) {
|
|
|
11
11
|
|
|
12
12
|
// Reference first MoveTo command (M)
|
|
13
13
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
14
|
-
let p0 =
|
|
14
|
+
let p0 = M
|
|
15
15
|
let p = M
|
|
16
16
|
pathData[0].decimals = 0
|
|
17
17
|
let lastDec = 0;
|
|
@@ -21,7 +21,8 @@ export function detectAccuracy(pathData) {
|
|
|
21
21
|
|
|
22
22
|
//console.log('detectAccuracy');
|
|
23
23
|
|
|
24
|
-
let dims = new Set();
|
|
24
|
+
//let dims = new Set();
|
|
25
|
+
let dims = [];
|
|
25
26
|
|
|
26
27
|
// add average distances
|
|
27
28
|
for (let i = 0, len = pathData.length; i < len; i++) {
|
|
@@ -29,34 +30,40 @@ export function detectAccuracy(pathData) {
|
|
|
29
30
|
let { type, values } = com;
|
|
30
31
|
|
|
31
32
|
let lastVals = values.length ? values.slice(-2) : [M.x, M.y];
|
|
32
|
-
p={x:lastVals[0], y:lastVals[1]}
|
|
33
|
+
p = { x: lastVals[0], y: lastVals[1] }
|
|
33
34
|
|
|
34
35
|
// use existing averave dimension value or calculate
|
|
35
|
-
let dimA = com.dimA ? +com.dimA.toFixed(8) : type!=='M' ? +getDistAv(p0, p).toFixed(8) : 0
|
|
36
|
+
let dimA = com.dimA ? +com.dimA.toFixed(8) : type !== 'M' ? +getDistAv(p0, p).toFixed(8) : 0
|
|
36
37
|
//let dimA = +getDistAv(p0, p).toFixed(8)
|
|
37
38
|
//console.log('dimA', dimA, com.dimA, type);
|
|
38
39
|
|
|
39
|
-
if(dimA) dims.
|
|
40
|
+
if (dimA) dims.push(dimA);
|
|
41
|
+
|
|
42
|
+
if (dimA && dimA < minDim) minDim = dimA;
|
|
43
|
+
if (dimA && dimA > maxDim) maxDim = dimA;
|
|
40
44
|
|
|
41
|
-
if(dimA && dimA<minDim) minDim = dimA;
|
|
42
|
-
if(dimA && dimA>maxDim) maxDim = dimA;
|
|
43
|
-
|
|
44
45
|
|
|
45
|
-
if(type==='M'){
|
|
46
|
-
M=p;
|
|
46
|
+
if (type === 'M') {
|
|
47
|
+
M = p;
|
|
47
48
|
}
|
|
48
49
|
p0 = p;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
let dim_min =
|
|
53
|
-
let sliceIdx = Math.ceil(dim_min.length/8);
|
|
54
|
-
dim_min = dim_min.slice(0, sliceIdx );
|
|
53
|
+
let dim_min = dims.sort()
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
/*
|
|
56
|
+
let minVal = dim_min.length > 15 ?
|
|
57
|
+
(dim_min[0] + dim_min[2]) / 2 :
|
|
58
|
+
dim_min[0];
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
let sliceIdx = Math.ceil(dim_min.length / 10);
|
|
62
|
+
dim_min = dim_min.slice(0, sliceIdx);
|
|
63
|
+
let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
|
|
57
64
|
|
|
58
|
-
let threshold =
|
|
59
|
-
let decimalsAuto =
|
|
65
|
+
let threshold = 40
|
|
66
|
+
let decimalsAuto = minVal > threshold*1.5 ? 0 : Math.floor(threshold / minVal).toString().length
|
|
60
67
|
|
|
61
68
|
// clamp
|
|
62
69
|
return Math.min(Math.max(0, decimalsAuto), 8)
|
|
@@ -141,22 +148,23 @@ export function detectAccuracy_back(pathData) {
|
|
|
141
148
|
* based on suggested accuracy in path data
|
|
142
149
|
*/
|
|
143
150
|
export function roundPathData(pathData, decimals = -1) {
|
|
144
|
-
// has recommended decimals
|
|
145
|
-
let hasDecimal = decimals == 'auto' && pathData[0].hasOwnProperty('decimals') ? true : false;
|
|
146
|
-
//console.log('decimals', decimals, hasDecimal);
|
|
147
|
-
|
|
148
|
-
for(let c=0, len=pathData.length; c<len; c++){
|
|
149
|
-
let com=pathData[c];
|
|
150
|
-
let {type, values} = com
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
decimals = hasDecimal ? com.decimals : decimals;
|
|
152
|
+
let len = pathData.length;
|
|
154
153
|
|
|
154
|
+
for (let c = 0; c < len; c++) {
|
|
155
|
+
//let com = pathData[c];
|
|
156
|
+
let values = pathData[c].values
|
|
157
|
+
let valLen = values.length;
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
pathData[c].values = com.values.map(val=>{return val ? +val.toFixed(decimals) : val });
|
|
159
|
+
if (valLen && (decimals > -1) ) {
|
|
158
160
|
|
|
161
|
+
for(let v=0; v<valLen; v++){
|
|
162
|
+
//pathData[c].values[v] = values[v] ? +values[v].toFixed(decimals) : values[v];
|
|
163
|
+
pathData[c].values[v] = +values[v].toFixed(decimals);
|
|
164
|
+
}
|
|
159
165
|
}
|
|
160
166
|
};
|
|
167
|
+
|
|
168
|
+
//console.log(pathData);
|
|
161
169
|
return pathData;
|
|
162
170
|
}
|
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
export function removeEmptySVGEls(svg) {
|
|
2
5
|
let els = svg.querySelectorAll('g, defs');
|
|
3
6
|
els.forEach(el => {
|
|
@@ -22,7 +25,7 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
22
25
|
.querySelector("svg");
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class'];
|
|
28
|
+
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
|
|
26
29
|
removeExcludedAttribues(svg, allowed)
|
|
27
30
|
|
|
28
31
|
let removeEls = ['metadata', 'script']
|