svg-path-simplify 0.2.7 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -5
- package/dist/svg-path-simplify.esm.js +2787 -2767
- package/dist/svg-path-simplify.esm.min.js +5 -7
- package/dist/svg-path-simplify.js +2787 -2767
- package/dist/svg-path-simplify.min.js +5 -7
- package/dist/svg-path-simplify.pathdata.esm.js +5123 -0
- package/dist/svg-path-simplify.pathdata.esm.min.js +9 -0
- package/dist/svg-path-simplify.worker.js +1 -36
- package/index.html +209 -158
- package/package.json +1 -1
- package/src/detect_input.js +30 -3
- package/src/index-pathdata.js +6 -0
- package/src/pathData_simplify_cubic_extrapolate.js +3 -2
- package/src/pathSimplify-main.js +131 -66
- package/src/pathSimplify-only-pathdata.js +274 -0
- package/src/poly-fit-curve-schneider.js +266 -175
- package/src/simplify_poly_RDP.js +101 -1
- package/src/simplify_poly_radial_distance.js +85 -2
- package/src/svgii/geometry.js +1 -0
- package/src/svgii/pathData_fromPoly.js +22 -7
- package/src/svgii/pathData_toPolygon.js +122 -230
- package/src/svgii/poly_analyze.js +114 -435
- package/src/svgii/poly_analyze_get_chunks.js +67 -0
- package/src/svgii/poly_normalize.js +51 -0
- package/src/svgii/poly_to_pathdata.js +51 -24
- package/src/svgii/rounding.js +36 -0
- package/src/svgii/svg_cleanup.js +10 -0
- package/dist/svg-path-simplify.min.js.gz +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { deg2rad, rad2Deg } from "../constants";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getDistAv, getTatAngles, pointAtT } from "./geometry";
|
|
2
|
+
import { simplifyPolyRDP } from "../simplify_poly_RDP";
|
|
3
|
+
import { simplifyPolyRD } from "../simplify_poly_radial_distance";
|
|
4
|
+
import { getDistAv, getDistManhattan, getSquareDistance, getTatAngles, interpolate, pointAtT } from "./geometry";
|
|
5
|
+
import { getBezierArea, getPolygonArea } from "./geometry_area";
|
|
5
6
|
import { getPolyBBox } from "./geometry_bbox";
|
|
6
7
|
import { addDimensionData, analyzePathData } from "./pathData_analyze";
|
|
7
8
|
import { arcToBezier } from "./pathData_convert";
|
|
@@ -10,309 +11,200 @@ import { addExtremePoints } from "./pathData_split";
|
|
|
10
11
|
import { pathDataToD } from "./pathData_stringify";
|
|
11
12
|
import { analyzePoly } from "./poly_analyze";
|
|
12
13
|
import { getCurvePathData } from "./poly_to_pathdata";
|
|
14
|
+
import { detectAccuracyPoly, roundTo } from "./rounding";
|
|
13
15
|
import { renderPoint } from "./visualize";
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
export function getPathDataPolyPrecise(pathData = []) {
|
|
17
|
-
|
|
18
|
-
let poly = [];
|
|
19
|
-
for (let i = 0; i < pathData.length; i++) {
|
|
20
|
-
let com = pathData[i]
|
|
21
|
-
let prev = i > 0 ? pathData[i - 1] : pathData[i];
|
|
22
|
-
let { type, values } = com;
|
|
23
|
-
let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
|
|
24
|
-
let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : ''
|
|
25
|
-
let cp1 = values.length ? { x: values[0], y: values[1] } : ''
|
|
26
|
-
|
|
27
|
-
switch (type) {
|
|
28
|
-
|
|
29
|
-
// convert to cubic to get polygon
|
|
30
|
-
case 'A':
|
|
31
|
-
if (typeof arcToBezier !== 'function') {
|
|
32
|
-
//console.log('has no arc to cubic conversion');
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
let cubic = arcToBezier(p0, values)
|
|
36
|
-
cubic.forEach(com => {
|
|
37
|
-
let vals = com.values
|
|
38
|
-
let cp1 = { x: vals[0], y: vals[1] }
|
|
39
|
-
let cp2 = { x: vals[2], y: vals[3] }
|
|
40
|
-
let p = { x: vals[4], y: vals[5] }
|
|
41
|
-
poly.push(cp1, cp2, p)
|
|
42
|
-
})
|
|
43
|
-
break;
|
|
44
|
-
|
|
45
|
-
case 'C':
|
|
46
|
-
let cp2 = { x: values[2], y: values[3] }
|
|
47
|
-
poly.push(cp1, cp2)
|
|
48
|
-
break;
|
|
49
|
-
case 'Q':
|
|
50
|
-
poly.push(cp1)
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// M and L commands
|
|
55
|
-
if (type.toLowerCase() !== 'z') {
|
|
56
|
-
poly.push(p)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return poly;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
18
|
|
|
64
19
|
|
|
20
|
+
/**
|
|
21
|
+
* creates precise polygon approximation from pathdata
|
|
22
|
+
* converts arc to cubis
|
|
23
|
+
*/
|
|
65
24
|
export function pathDataToPolygon(pathData, {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
25
|
+
precisionPoly = 1,
|
|
26
|
+
autoAccuracy=false,
|
|
27
|
+
polyFormat='points',
|
|
28
|
+
decimals=-1,
|
|
29
|
+
simplifyRD=1,
|
|
30
|
+
simplifyRDP=1,
|
|
71
31
|
} = {}) {
|
|
72
32
|
|
|
33
|
+
//console.log(pathData);
|
|
73
34
|
|
|
74
35
|
let l = pathData.length;
|
|
75
36
|
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
76
37
|
let p0 = M
|
|
77
|
-
|
|
78
|
-
let minT = 1 / split * 0.5;
|
|
79
|
-
let maxT = 1 - minT
|
|
38
|
+
let p = M
|
|
80
39
|
|
|
81
40
|
|
|
82
41
|
// collect polygon vertices
|
|
83
42
|
let pathDataPoly = []
|
|
84
|
-
let pts = [p0]
|
|
85
|
-
|
|
86
|
-
let ptsEnd = [0]
|
|
87
|
-
let ptCount = 1
|
|
88
|
-
|
|
89
|
-
// get max length
|
|
90
|
-
let minLength = 0;
|
|
91
43
|
|
|
44
|
+
// end point vertices
|
|
45
|
+
let pts = [p0]
|
|
92
46
|
|
|
93
|
-
split = !split ? 1 : split;
|
|
94
|
-
|
|
95
|
-
if (width && height) {
|
|
96
|
-
minLength = (width + height) * 0.025 / split
|
|
97
|
-
} else {
|
|
98
|
-
//let areas = pathData.map(com => com.cptArea || 0).filter(Boolean).sort()
|
|
99
|
-
let lengths = pathData.map(com => com.dimA || 0).filter(Boolean).sort()
|
|
100
|
-
minLength = lengths[0]
|
|
101
|
-
//console.log('areas', areas, 'lengths', lengths);
|
|
102
|
-
}
|
|
103
47
|
|
|
104
|
-
|
|
48
|
+
let dims = []
|
|
49
|
+
let areas = []
|
|
105
50
|
|
|
51
|
+
// minimum dimension
|
|
106
52
|
for (let i = 1; i < l; i++) {
|
|
107
53
|
let com = pathData[i];
|
|
108
|
-
|
|
109
|
-
let comNext = pathData[i + 1] || null;
|
|
110
|
-
let { type, values } = com;
|
|
111
|
-
let valuesL = values.length;
|
|
112
|
-
let p = valuesL ? { x: values[valuesL - 2], y: values[valuesL - 1] } : M;
|
|
113
|
-
|
|
114
|
-
if (type === 'C' || type === 'Q') {
|
|
115
|
-
|
|
116
|
-
let cp1 = { x: values[0], y: values[1] }
|
|
117
|
-
let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1
|
|
118
|
-
let cpts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// calculate split according to length
|
|
122
|
-
let length = com.dimA
|
|
123
|
-
let rat = Math.floor(length / (minLength))
|
|
124
|
-
split = Math.ceil(length / minLength)
|
|
125
|
-
|
|
126
|
-
let tArr = []
|
|
127
|
-
for (let i = 1; i < split; i++) {
|
|
128
|
-
tArr.push(1 / split * i)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
tArr.forEach(t => {
|
|
132
|
-
let pt = pointAtT(cpts, t)
|
|
133
|
-
pts.push(pt)
|
|
134
|
-
ptCount++
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (type === 'M') {
|
|
140
|
-
M = p
|
|
141
|
-
}
|
|
142
|
-
|
|
54
|
+
let { type, values, p0, p, dimA = 0 } = com;
|
|
143
55
|
|
|
144
|
-
|
|
145
|
-
p.isExtreme = com.extreme || false
|
|
146
|
-
p.isCorner = com.corner || false
|
|
147
|
-
p.isDirChange = com.directionChange || false;
|
|
56
|
+
dims.push(+dimA.toFixed(8))
|
|
148
57
|
|
|
149
58
|
// segment end point
|
|
150
59
|
pts.push(p)
|
|
151
|
-
|
|
152
|
-
// exclude for polygon simplification
|
|
153
|
-
if (com.extreme || com.corner || (comNext && comNext.type !== type) || type === 'L') {
|
|
154
|
-
//console.log('is ext' , com);
|
|
155
|
-
ptsEnd.push(ptCount)
|
|
156
|
-
//renderPoint(markers, p, 'magenta', '2%', '0.5')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
ptCount++
|
|
160
|
-
|
|
161
|
-
p0 = p
|
|
162
|
-
|
|
163
60
|
}
|
|
164
61
|
|
|
165
|
-
// reduce poly vertices
|
|
166
|
-
//pts = simplifyRD(pts, { quality: 0.5, exclude: ptsEnd, width, height })
|
|
167
|
-
pts = simplifyRD(pts, { quality: 0.5, width, height })
|
|
168
|
-
//pts = simplifyRDP(pts, { quality: 0.8, width, height })
|
|
169
|
-
//console.log(ptsEnd);
|
|
170
62
|
|
|
171
|
-
|
|
172
|
-
pts.forEach(pt => {
|
|
173
|
-
//renderPoint(markers, pt, 'cyan', '1%', '0.5')
|
|
174
|
-
})
|
|
175
|
-
//console.log(pts);
|
|
176
|
-
*/
|
|
63
|
+
let pts2 = [pts[0]]
|
|
177
64
|
|
|
65
|
+
// adjustments for very small or large paths
|
|
66
|
+
dims = dims.filter(Boolean).sort()
|
|
67
|
+
let dimMax = dims[dims.length - 1]
|
|
178
68
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
69
|
+
let scale = dimMax > 2 && dimMax < 25 ? 1 : (20 / dimMax);
|
|
70
|
+
precisionPoly = precisionPoly * scale
|
|
182
71
|
|
|
72
|
+
// check how much segments contribute to total area
|
|
73
|
+
for (let i = 1; i < l; i++) {
|
|
74
|
+
let com = pathData[i];
|
|
75
|
+
let { type, values, p0, p, cp1 = null, cp2 = null, dimA } = com;
|
|
183
76
|
|
|
77
|
+
let distAv = (dimA)
|
|
184
78
|
|
|
185
|
-
|
|
186
|
-
export function pathDataToPolySingle(pathData, addExtremes = true) {
|
|
79
|
+
let cpts = cp1 && cp2 ? [p0, cp1, cp2, p] : (cp1 ? [p0, cp1, p] : []);
|
|
187
80
|
|
|
81
|
+
if (cpts.length) {
|
|
82
|
+
let ptM = cp2 ? interpolate(cp1, cp2, 0.5) : cp1
|
|
83
|
+
let distCpt1 = getDistManhattan(p0, ptM)
|
|
84
|
+
let distCpt2 = getDistManhattan(p, ptM)
|
|
85
|
+
let dist4 = (distCpt1 + distCpt2) * 0.2
|
|
86
|
+
distAv = (dist4 + dimA)
|
|
87
|
+
}
|
|
188
88
|
|
|
189
|
-
|
|
190
|
-
|
|
89
|
+
// calculate split value according to manhattan distance of segment
|
|
90
|
+
let rat = Math.ceil(distAv * 0.2 * precisionPoly)
|
|
91
|
+
let split = Math.ceil(rat)
|
|
191
92
|
|
|
192
93
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
94
|
+
if (split && cpts.length) {
|
|
95
|
+
let step = split ? 1 / (split + 1) : 0
|
|
96
|
+
for (let j = 1; j <= split; j++) {
|
|
97
|
+
let t = step * j
|
|
98
|
+
let pt = pointAtT(cpts, t)
|
|
99
|
+
pts2.push(pt)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
pts2.push(p)
|
|
199
103
|
}
|
|
200
104
|
|
|
201
|
-
//console.log(pathData);
|
|
202
105
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
106
|
+
// simplify polygon
|
|
107
|
+
if(simplifyRD>0){
|
|
108
|
+
pts2 = simplifyPolyRD(pts2, {quality:simplifyRD+'px'})
|
|
109
|
+
}
|
|
206
110
|
|
|
207
|
-
pathData = pathDataPlus.pathData
|
|
208
111
|
|
|
112
|
+
if(simplifyRDP>0){
|
|
113
|
+
pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP+'px'})
|
|
114
|
+
}
|
|
209
115
|
|
|
210
|
-
/**
|
|
211
|
-
* approximate min and max segment sizes
|
|
212
|
-
* for segment splitting
|
|
213
|
-
*/
|
|
214
|
-
let dimArr = pathData.filter(com => com.dimA).sort((a, b) => a.dimA - b.DimA)
|
|
215
|
-
let dimMinL = dimArr[0].dimA
|
|
216
|
-
let dimMaxL = dimArr[dimArr.length - 1].dimA
|
|
217
|
-
//console.log('dimArr', dimArr, dimMaxL);
|
|
218
|
-
if (dimMinL && dimMinL < dimMin) dimMin = dimMinL;
|
|
219
|
-
if (dimMaxL && dimMaxL > dimMax) dimMax = dimMaxL;
|
|
220
116
|
|
|
221
|
-
//console.log(dimMin, dimMax);
|
|
222
117
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
118
|
+
pathDataPoly = pathDataFromPoly(pts2)
|
|
119
|
+
pathData = pathDataPoly
|
|
120
|
+
|
|
121
|
+
if(autoAccuracy){
|
|
122
|
+
decimals = detectAccuracyPoly(pts)
|
|
123
|
+
//console.log('decimals', decimals);
|
|
124
|
+
}
|
|
226
125
|
|
|
227
|
-
|
|
228
|
-
let polyArr = [];
|
|
126
|
+
let poly = decimals>-1 ? pts2.map(pt => { return { x: roundTo(pt.x, decimals), y: roundTo(pt.y, decimals) } }) : pts2.map(pt => { return { x: pt.x, y: pt.y } })
|
|
229
127
|
|
|
230
|
-
|
|
231
|
-
|
|
128
|
+
if(polyFormat==='array'){
|
|
129
|
+
poly = poly.map(pt => { return [pt.x, pt.y] })
|
|
130
|
+
}
|
|
131
|
+
else if(polyFormat==='string'){
|
|
132
|
+
poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).join(' ')
|
|
133
|
+
}
|
|
232
134
|
|
|
233
|
-
|
|
135
|
+
return { pathData, poly }
|
|
234
136
|
|
|
235
|
-
|
|
236
|
-
let { type, values, extreme = false, corner = false, dimA = null, p0, p, cp1 = null, cp2 = null } = com;
|
|
137
|
+
}
|
|
237
138
|
|
|
238
|
-
dimA = getDistAv(p0, p);
|
|
239
139
|
|
|
240
140
|
|
|
241
|
-
if (extreme) {
|
|
242
|
-
//renderPoint(markers, p, 'cyan')
|
|
243
|
-
}
|
|
244
141
|
|
|
245
|
-
|
|
142
|
+
/**
|
|
143
|
+
* creates precise polygon
|
|
144
|
+
* from command end points
|
|
145
|
+
* converts arc to cubis
|
|
146
|
+
*/
|
|
147
|
+
export function getPathDataPolyPrecise(pathData = [], {
|
|
148
|
+
precision = 1
|
|
149
|
+
} = {}) {
|
|
246
150
|
|
|
151
|
+
let poly = [];
|
|
152
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
153
|
+
let com = pathData[i]
|
|
154
|
+
let prev = i > 0 ? pathData[i - 1] : pathData[i];
|
|
155
|
+
let { type, values } = com;
|
|
156
|
+
let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
|
|
157
|
+
let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : ''
|
|
158
|
+
let cp1 = values.length ? { x: values[0], y: values[1] } : ''
|
|
247
159
|
|
|
248
|
-
|
|
249
|
-
p.extreme = extreme
|
|
250
|
-
p.corner = corner
|
|
160
|
+
switch (type) {
|
|
251
161
|
|
|
252
|
-
|
|
162
|
+
// convert to cubic to get polygon
|
|
163
|
+
case 'A':
|
|
164
|
+
if (typeof arcToBezier !== 'function') {
|
|
165
|
+
//console.log('has no arc to cubic conversion');
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
let cubic = arcToBezier(p0, values)
|
|
169
|
+
cubic.forEach(com => {
|
|
170
|
+
let vals = com.values
|
|
171
|
+
let cp1 = { x: vals[0], y: vals[1] }
|
|
172
|
+
let cp2 = { x: vals[2], y: vals[3] }
|
|
173
|
+
let p = { x: vals[4], y: vals[5] }
|
|
174
|
+
poly.push(cp1, cp2, p)
|
|
175
|
+
})
|
|
176
|
+
break;
|
|
253
177
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
178
|
+
case 'C':
|
|
179
|
+
let cp2 = { x: values[2], y: values[3] }
|
|
180
|
+
poly.push(cp1, cp2)
|
|
181
|
+
break;
|
|
182
|
+
case 'Q':
|
|
183
|
+
poly.push(cp1)
|
|
184
|
+
break;
|
|
262
185
|
}
|
|
263
|
-
poly.push(p)
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
186
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
let p = poly[i - 1]
|
|
272
|
-
let pN = poly[i]
|
|
273
|
-
|
|
274
|
-
let dist1 = getDistAv(p, pN)
|
|
275
|
-
if (dist1 < thresh && pN.extreme) {
|
|
276
|
-
let pR = p.extreme ? pN : p
|
|
277
|
-
let idx = p.extreme ? i : i - 1
|
|
278
|
-
//console.log('remove', idx);
|
|
279
|
-
remove.add(idx)
|
|
187
|
+
// M and L commands
|
|
188
|
+
if (type.toLowerCase() !== 'z') {
|
|
189
|
+
poly.push(p)
|
|
280
190
|
}
|
|
281
191
|
}
|
|
282
192
|
|
|
193
|
+
return poly;
|
|
194
|
+
}
|
|
283
195
|
|
|
284
|
-
remove = Array.from(remove).reverse();
|
|
285
|
-
//console.log(remove);
|
|
286
|
-
|
|
287
|
-
for (let i = 0; i < remove.length; i++) {
|
|
288
|
-
let idx = remove[i];
|
|
289
|
-
//console.log('idx', idx);
|
|
290
|
-
poly.splice(idx, 1)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
poly.splice(poly.length - 1, poly.length)
|
|
294
196
|
|
|
295
197
|
|
|
296
|
-
let polyAtt = poly.map(pt => `${pt.x} ${pt.y} `).join(' ')
|
|
297
|
-
//console.log('polyAtt', polyAtt);
|
|
298
198
|
|
|
299
|
-
//markers.insertAdjacentHTML('beforeend', `<polygon points="${polyAtt}" stroke="red" fill="none"/>`)
|
|
300
199
|
|
|
301
200
|
|
|
302
|
-
poly = analyzePoly(poly, false)
|
|
303
|
-
let pathDataP = getCurvePathData(poly, 0.666, true)
|
|
304
|
-
let d = pathDataToD(pathDataP)
|
|
305
201
|
|
|
306
|
-
console.log(d);
|
|
307
|
-
//markers.insertAdjacentHTML('beforeend', `<path d="${d}" stroke="green" fill="none" stroke-width="1%"/>`)
|
|
308
202
|
|
|
309
203
|
|
|
310
204
|
|
|
311
205
|
|
|
312
206
|
|
|
313
|
-
return poly
|
|
314
207
|
|
|
315
|
-
}
|
|
316
208
|
|
|
317
209
|
|
|
318
210
|
|