svg-path-simplify 0.0.8 → 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 +576 -494
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +576 -494
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +576 -494
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +86 -29
- package/package.json +1 -1
- package/src/detect_input.js +17 -10
- package/src/index.js +3 -0
- package/src/pathData_simplify_cubic.js +113 -106
- package/src/pathData_simplify_cubic_extrapolate.js +25 -11
- package/src/pathSimplify-main.js +89 -182
- package/src/svgii/geometry_flatness.js +29 -36
- package/src/svgii/pathData_analyze.js +4 -0
- 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 +18 -12
- package/src/svgii/pathData_remove_collinear.js +31 -28
- package/src/svgii/pathData_remove_orphaned.js +5 -4
- package/src/svgii/pathData_remove_zerolength.js +8 -4
- package/src/svgii/pathData_reorder.js +6 -2
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/{simplify_refineExtremes.js → pathData_simplify_refineExtremes.js} +78 -43
- package/src/svgii/pathData_split.js +42 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +16 -14
- package/src/svgii/svg_cleanup.js +1 -1
- 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
|
@@ -5,88 +5,134 @@ import { renderPoint } from "./svgii/visualize";
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
8
|
+
export function simplifyPathDataCubic(pathData, {
|
|
9
|
+
keepExtremes = true,
|
|
10
|
+
keepInflections = true,
|
|
11
|
+
keepCorners = true,
|
|
12
|
+
extrapolateDominant = true,
|
|
13
|
+
tolerance = 1,
|
|
14
|
+
} = {}) {
|
|
15
|
+
|
|
16
|
+
let pathDataN = [pathData[0]];
|
|
17
|
+
let l = pathData.length;
|
|
18
|
+
|
|
19
|
+
for (let i = 2; l && i <= l; i++) {
|
|
20
|
+
let com = pathData[i - 1];
|
|
21
|
+
let comN = i < l ? pathData[i] : null;
|
|
22
|
+
let typeN = comN?.type || null;
|
|
23
|
+
//let isCornerN = comN?.corner || null;
|
|
24
|
+
//let isExtremeN = comN?.extreme || null;
|
|
25
|
+
let isDirChange = com?.directionChange || null;
|
|
26
|
+
let isDirChangeN = comN?.directionChange || null;
|
|
27
|
+
|
|
28
|
+
let { type, values, p0, p, cp1 = null, cp2 = null, extreme = false, corner = false, dimA = 0 } = com;
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// next is also cubic
|
|
32
|
+
if (type === 'C' && typeN === 'C') {
|
|
33
|
+
|
|
34
|
+
// cannot be combined as crossing extremes or corners
|
|
35
|
+
if (
|
|
36
|
+
(keepInflections && isDirChangeN) ||
|
|
37
|
+
(keepCorners && corner) ||
|
|
38
|
+
(!isDirChange && keepExtremes && extreme)
|
|
39
|
+
) {
|
|
40
|
+
//renderPoint(markers, p, 'red', '1%')
|
|
41
|
+
pathDataN.push(com)
|
|
42
|
+
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
// try simplification
|
|
45
|
+
else {
|
|
46
|
+
//renderPoint(markers, p, 'magenta', '1%')
|
|
47
|
+
let combined = combineCubicPairs(com, comN, {tolerance})
|
|
48
|
+
let error = 0;
|
|
49
|
+
|
|
50
|
+
// combining successful! try next segment
|
|
51
|
+
if (combined.length === 1) {
|
|
52
|
+
com = combined[0]
|
|
53
|
+
let offset = 1;
|
|
54
|
+
|
|
55
|
+
// add cumulative error to prevent distortions
|
|
56
|
+
error += com.error;
|
|
57
|
+
//console.log('!error', error);
|
|
58
|
+
|
|
59
|
+
// find next candidates
|
|
60
|
+
//offset<2 &&
|
|
61
|
+
for (let n = i + 1; error < tolerance && n < l; n++) {
|
|
62
|
+
let comN = pathData[n]
|
|
63
|
+
if (comN.type !== 'C' ||
|
|
64
|
+
(
|
|
65
|
+
(keepInflections && comN.directionChange) ||
|
|
66
|
+
(keepCorners && com.corner) ||
|
|
67
|
+
(keepExtremes && com.extreme)
|
|
68
|
+
)
|
|
69
|
+
) {
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let combined = combineCubicPairs(com, comN, {tolerance})
|
|
74
|
+
if (combined.length === 1) {
|
|
75
|
+
// add cumulative error to prevent distortions
|
|
76
|
+
//console.log('combined', combined);
|
|
77
|
+
error += combined[0].error * 0.5;
|
|
78
|
+
//error += combined[0].error * 1;
|
|
79
|
+
offset++
|
|
80
|
+
}
|
|
81
|
+
com = combined[0]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//com.opt = true
|
|
85
|
+
pathDataN.push(com)
|
|
86
|
+
|
|
87
|
+
if (i < l) {
|
|
88
|
+
i += offset
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} else {
|
|
92
|
+
pathDataN.push(com)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
43
95
|
|
|
44
|
-
|
|
45
|
-
const R1_calc = interpolate(P1P2_mid, P2P3_mid, t);
|
|
96
|
+
} // end of bezier command
|
|
46
97
|
|
|
47
|
-
// Calculate errors
|
|
48
|
-
const errorQ2 = getDistance(Q2_calc, com1.cp2);
|
|
49
|
-
const errorR1 = getDistance(R1_calc, com2.cp1);
|
|
50
|
-
const totalError = errorQ2 + errorR1;
|
|
51
98
|
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
// other commands
|
|
100
|
+
else {
|
|
101
|
+
pathDataN.push(com)
|
|
54
102
|
}
|
|
55
103
|
|
|
56
|
-
|
|
57
|
-
t = t * 0.5 + 0.5 * (errorQ2 < errorR1 ? t + 0.01 : t - 0.01);
|
|
58
|
-
t = Math.max(0.001, Math.min(0.999, t));
|
|
59
|
-
}
|
|
104
|
+
} // end command loop
|
|
60
105
|
|
|
61
|
-
return
|
|
106
|
+
return pathDataN
|
|
62
107
|
}
|
|
63
108
|
|
|
64
109
|
|
|
65
110
|
|
|
66
|
-
|
|
111
|
+
|
|
112
|
+
export function combineCubicPairs(com1, com2, {
|
|
113
|
+
tolerance = 1
|
|
114
|
+
} = {}) {
|
|
67
115
|
|
|
68
116
|
let commands = [com1, com2];
|
|
69
|
-
let t = findSplitT(com1, com2);
|
|
70
117
|
|
|
71
|
-
//
|
|
118
|
+
// assume 2 segments are result of a segment split
|
|
119
|
+
let t = findSplitT(com1, com2);
|
|
72
120
|
|
|
73
121
|
let distAv1 = getDistAv(com1.p0, com1.p);
|
|
74
122
|
let distAv2 = getDistAv(com2.p0, com2.p);
|
|
75
|
-
let distMin = Math.min(distAv1, distAv2)
|
|
76
|
-
|
|
123
|
+
let distMin = Math.max(0, Math.min(distAv1, distAv2))
|
|
124
|
+
|
|
77
125
|
|
|
78
|
-
let distScale = 0.
|
|
126
|
+
let distScale = 0.06
|
|
79
127
|
let maxDist = distMin * distScale * tolerance
|
|
80
|
-
//tolerance = distMax * threshold
|
|
81
128
|
|
|
82
|
-
|
|
129
|
+
// get hypothetical combined command
|
|
130
|
+
let comS = getExtrapolatedCommand(com1, com2, t)
|
|
83
131
|
|
|
84
|
-
// test
|
|
132
|
+
// test new point-at-t against original mid segment starting point
|
|
85
133
|
let pt = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
|
|
86
134
|
|
|
87
135
|
|
|
88
|
-
//let tolerance = Math.min(Math.max(com1.dimA, com2.dimA), Math.min(com1.dimA, com2.dimA)) * 0.05
|
|
89
|
-
|
|
90
136
|
let dist0 = getDistAv(com1.p, pt)
|
|
91
137
|
let dist1 = 0, dist2 = 0;
|
|
92
138
|
let close = dist0 < maxDist;
|
|
@@ -97,14 +143,6 @@ export function combineCubicPairs(com1, com2, extrapolateDominant = false, toler
|
|
|
97
143
|
|
|
98
144
|
|
|
99
145
|
|
|
100
|
-
/*
|
|
101
|
-
if (com2.directionChange) {
|
|
102
|
-
//renderPoint(markers, com2.p0)
|
|
103
|
-
}
|
|
104
|
-
*/
|
|
105
|
-
|
|
106
|
-
//console.log('tolerance', tolerance, close, dist0);
|
|
107
|
-
|
|
108
146
|
if (close) {
|
|
109
147
|
|
|
110
148
|
/**
|
|
@@ -159,34 +197,10 @@ export function combineCubicPairs(com1, com2, extrapolateDominant = false, toler
|
|
|
159
197
|
} // end 1st try
|
|
160
198
|
|
|
161
199
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// try extrapolated dominant curve
|
|
165
|
-
//&& !com2.extreme
|
|
166
|
-
// && !com1.extreme
|
|
167
|
-
if (extrapolateDominant && !success ) {
|
|
168
|
-
|
|
169
|
-
let combinedEx = getCombinedByDominant(com1, com2, maxDist, tolerance);
|
|
170
|
-
|
|
171
|
-
//console.log('???combinedEx', combinedEx);
|
|
172
|
-
|
|
173
|
-
if(combinedEx.length===1){
|
|
174
|
-
success = true
|
|
175
|
-
comS = combinedEx[0]
|
|
176
|
-
error = comS.error
|
|
177
|
-
|
|
178
|
-
//console.log('!!!combinedEx', combinedEx);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
200
|
|
|
184
201
|
// add meta
|
|
185
202
|
if (success) {
|
|
186
203
|
|
|
187
|
-
//comS.dimA = (Math.abs(comS.p0.x - comS.p.x) + Math.abs(comS.p0.y - comS.p.y)) / 2
|
|
188
|
-
|
|
189
|
-
|
|
190
204
|
// correct to exact start and end points
|
|
191
205
|
comS.p0 = com1.p0
|
|
192
206
|
comS.p = com2.p
|
|
@@ -205,8 +219,6 @@ export function combineCubicPairs(com1, com2, extrapolateDominant = false, toler
|
|
|
205
219
|
//comS.p0 = com1.p0;
|
|
206
220
|
commands = [comS];
|
|
207
221
|
|
|
208
|
-
//console.log('commands combined', commands);
|
|
209
|
-
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
|
|
@@ -229,28 +241,23 @@ export function getError(com, area = 0, threshold = 0) {
|
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
|
|
232
|
-
export function getExtrapolatedCommand(com1, com2,
|
|
244
|
+
export function getExtrapolatedCommand(com1, com2, t = 0) {
|
|
233
245
|
|
|
234
246
|
let { p0, cp1 } = com1;
|
|
235
247
|
let { p, cp2 } = com2;
|
|
236
248
|
|
|
237
249
|
// extrapolate control points
|
|
238
|
-
|
|
239
|
-
x: (cp1.x - (1 -
|
|
240
|
-
y: (cp1.y - (1 -
|
|
250
|
+
cp1 = {
|
|
251
|
+
x: (cp1.x - (1 - t) * p0.x) / t,
|
|
252
|
+
y: (cp1.y - (1 - t) * p0.y) / t
|
|
241
253
|
};
|
|
242
254
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
y: (cp2.y - t2 * p.y) / (1 - t2)
|
|
255
|
+
cp2 = {
|
|
256
|
+
x: (cp2.x - t * p.x) / (1 - t),
|
|
257
|
+
y: (cp2.y - t * p.y) / (1 - t)
|
|
247
258
|
};
|
|
248
259
|
|
|
249
|
-
|
|
250
|
-
//let pt = pointAtT([p0, cp1_S, cp2_S, p], (t1 + t2) / 2);
|
|
251
|
-
|
|
252
|
-
return comS
|
|
253
|
-
|
|
260
|
+
return { p0, cp1, cp2, p };
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
|
|
@@ -271,7 +278,7 @@ export function getBezierCommandArea(commands = [com1, com2], absolute = true) {
|
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
|
|
274
|
-
function findSplitT(com1, com2) {
|
|
281
|
+
export function findSplitT(com1, com2) {
|
|
275
282
|
|
|
276
283
|
let len3 = getDistance(com1.cp2, com1.p)
|
|
277
284
|
let len4 = getDistance(com1.cp2, com2.cp1)
|
|
@@ -21,8 +21,6 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
24
|
// if combining fails return original commands
|
|
27
25
|
let commands = [com1, com2]
|
|
28
26
|
|
|
@@ -30,12 +28,8 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
30
28
|
let dist1 = getDistAv(com1.p0, com1.p)
|
|
31
29
|
let dist2 = getDistAv(com2.p0, com2.p)
|
|
32
30
|
|
|
33
|
-
|
|
34
31
|
let reverse = dist1 > dist2;
|
|
35
32
|
|
|
36
|
-
//let ang1 = getAngle(com1.p0, com1.cp1)
|
|
37
|
-
//let ang2 = getAngle(com2.p, com1.cp2)
|
|
38
|
-
|
|
39
33
|
// backup original commands
|
|
40
34
|
let com1_o = JSON.parse(JSON.stringify(com1))
|
|
41
35
|
let com2_o = JSON.parse(JSON.stringify(com2))
|
|
@@ -118,6 +112,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
118
112
|
cp1: Q1,
|
|
119
113
|
cp2: Q2,
|
|
120
114
|
p: Q3,
|
|
115
|
+
t0
|
|
121
116
|
};
|
|
122
117
|
|
|
123
118
|
|
|
@@ -127,14 +122,18 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
127
122
|
cp1: Q2,
|
|
128
123
|
cp2: Q1,
|
|
129
124
|
p: Q0,
|
|
125
|
+
t0
|
|
130
126
|
}
|
|
131
127
|
}
|
|
132
128
|
|
|
133
129
|
|
|
134
130
|
let tMid = (1 - t0) * 0.5;
|
|
131
|
+
//tMid = (1 +t0) * 0.5;
|
|
135
132
|
let tSplit = t0 - 1;
|
|
136
133
|
//tMid = 0.5;
|
|
137
134
|
|
|
135
|
+
//console.log(1 - t0);
|
|
136
|
+
|
|
138
137
|
|
|
139
138
|
let ptM = pointAtT([result.p0, result.cp1, result.cp2, result.p], tMid, false, true)
|
|
140
139
|
let seg1_cp2 = ptM.cpts[2]
|
|
@@ -144,10 +143,10 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
144
143
|
let ptI_2 = checkLineIntersection(ptM, seg1_cp2, result.p, ptI, false)
|
|
145
144
|
|
|
146
145
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let cp1_2 = interpolate(result.p0, ptI_1, 1.333)
|
|
150
|
-
let cp2_2 = interpolate(result.p, ptI_2, 1.333)
|
|
146
|
+
//let tscale =(1 + t0)
|
|
147
|
+
//console.log('tscale', tscale);
|
|
148
|
+
let cp1_2 = interpolate(result.p0, ptI_1, 1.333 )
|
|
149
|
+
let cp2_2 = interpolate(result.p, ptI_2, 1.333 )
|
|
151
150
|
|
|
152
151
|
// test self intersections and exit
|
|
153
152
|
let cp_intersection = checkLineIntersection(com1_o.p0, cp1_2, com2_o.p, cp2_2, true)
|
|
@@ -181,8 +180,22 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
181
180
|
// extrapolated starting point is not completely off
|
|
182
181
|
if (dist5 < maxDist) {
|
|
183
182
|
|
|
183
|
+
/*
|
|
184
|
+
let tTotal = 1 + Math.abs(t0);
|
|
185
|
+
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
186
|
+
//tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
|
|
187
|
+
//console.log('t0', t0, tMid, 'tSplit', tSplit);
|
|
188
|
+
|
|
189
|
+
let pO = pointAtT([com2_o.p0, com2_o.cp1, com2_o.cp2, com2_o.p], t0);
|
|
190
|
+
*/
|
|
191
|
+
|
|
184
192
|
// split t to meet original mid segment start point
|
|
185
193
|
let tSplit = reverse ? 1 + t0 : Math.abs(t0);
|
|
194
|
+
|
|
195
|
+
let tTotal = 1 + Math.abs(t0);
|
|
196
|
+
tSplit = reverse ? 1 + t0 : Math.abs(t0) / tTotal;
|
|
197
|
+
|
|
198
|
+
|
|
186
199
|
//console.log('t0', t0, tMid, 'tSplit', tSplit);
|
|
187
200
|
|
|
188
201
|
let ptSplit = pointAtT([result.p0, result.cp1, result.cp2, result.p], tSplit);
|
|
@@ -190,7 +203,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
190
203
|
//console.log('distS', distS, maxDist );
|
|
191
204
|
|
|
192
205
|
// not close enough - exit
|
|
193
|
-
if (distSplit > maxDist * tolerance) {
|
|
206
|
+
if (distSplit > maxDist * tolerance ) {
|
|
194
207
|
//renderPoint(markers, ptSplit, 'cyan', '1%')
|
|
195
208
|
//renderPoint(markers, com1.p, 'red', '0.5%')
|
|
196
209
|
return commands;
|
|
@@ -216,6 +229,7 @@ export function getCombinedByDominant(com1, com2, maxDist = 0, tolerance = 1, de
|
|
|
216
229
|
result.error = areaDiff * 5 * tolerance;
|
|
217
230
|
//result.error = areaDiff + dist5;
|
|
218
231
|
|
|
232
|
+
//debug=true;
|
|
219
233
|
|
|
220
234
|
if (debug) {
|
|
221
235
|
let d = pathDataToD(pathDataN)
|