svg-path-simplify 0.4.4 → 0.4.6

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -1
  3. package/dist/svg-path-simplify.esm.js +1649 -1205
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1649 -1204
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +301 -637
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +8 -9
  10. package/drawing.svg +62 -0
  11. package/index.html +13 -8
  12. package/package.json +1 -1
  13. package/src/index.js +3 -1
  14. package/src/pathData_get_intersections.js +777 -0
  15. package/src/pathData_offset.js +201 -0
  16. package/src/pathData_simplify_cubic.js +19 -21
  17. package/src/pathData_simplify_cubic_extrapolate.js +147 -8
  18. package/src/pathData_simplify_cubicsToArcs.js +65 -21
  19. package/src/pathSimplify-main.js +267 -64
  20. package/src/pathSimplify-presets.js +1 -1
  21. package/src/poly-fit-curve-schneider.js +171 -132
  22. package/src/poly-fit-curve-schneider_check_bulge.js +131 -0
  23. package/src/poly-offset.js +133 -0
  24. package/src/simplify_poly_RC.js +29 -7
  25. package/src/svg-getAttributes.js +1 -1
  26. package/src/svgii/geometry.js +130 -27
  27. package/src/svgii/geometry_bbox.js +1 -1
  28. package/src/svgii/geometry_length.js +1 -1
  29. package/src/svgii/pathData_analyze.js +6 -28
  30. package/src/svgii/pathData_convert.js +63 -15
  31. package/src/svgii/pathData_remove_collinear.js +32 -36
  32. package/src/svgii/pathData_reorder.js +3 -2
  33. package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
  34. package/src/svgii/pathData_stringify.js +132 -6
  35. package/src/svgii/pathData_toPolygon.js +33 -23
  36. package/src/svgii/poly_analyze.js +272 -125
  37. package/src/svgii/poly_analyze_cleanup.js +311 -0
  38. package/src/svgii/poly_analyze_getTangents.js +125 -0
  39. package/src/svgii/poly_analyze_get_chunks.js +3 -1
  40. package/src/svgii/poly_normalize.js +21 -8
  41. package/src/svgii/poly_to_pathdata.js +477 -8
  42. package/src/svgii/rounding.js +35 -14
  43. package/src/svgii/svg_cleanup.js +44 -51
  44. package/src/svgii/svg_cleanup_convertPathLength.js +5 -0
  45. package/src/svgii/svg_cleanup_normalize_transforms.js +2 -2
  46. package/src/svgii/visualize.js +33 -2
  47. package/v/0.4.5/index.html +1040 -0
  48. package/v/0.4.5/svg-path-simplify.js +13227 -0
@@ -0,0 +1,311 @@
1
+ import { rad2Deg } from "../constants";
2
+ import { checkLineIntersection, getAngleFromDelta, getDistManhattan, interpolate } from "./geometry";
3
+ import { getPolygonArea } from "./geometry_area";
4
+ import { getPolyBBox } from "./geometry_bbox";
5
+ import { renderPoint, renderPoly } from "./visualize";
6
+
7
+ export function refineAdjacentPolyExtremes(pts = []) {
8
+
9
+ let l = pts.length;
10
+
11
+ let { x, y, width, height, top, bottom, left, right } = getPolyBBox(pts);
12
+ let threshShort = (width + height) * 0.05;
13
+ let thresh = (width + height) * 0.001;
14
+
15
+ let pt0 = pts[0];
16
+ let ptLast = pts[l - 1];
17
+
18
+
19
+ /**
20
+ * cleanup close path - almost vertical or horizontal
21
+ * average start and end extremes
22
+ */
23
+ let dx = Math.abs(ptLast.x - pt0.x)
24
+ let dy = Math.abs(ptLast.y - pt0.y)
25
+
26
+ if (dy < threshShort || dx < threshShort) {
27
+
28
+ if (pt0.isExtreme && !pt0.isCorner) {
29
+
30
+ let xAv = (pt0.x + ptLast.x) * 0.5
31
+ let yAv = (pt0.y + ptLast.y) * 0.5
32
+
33
+ pt0.x = xAv
34
+ pt0.y = yAv
35
+
36
+ ptLast.x = xAv
37
+ ptLast.y = yAv
38
+ ptLast.isExtreme = true;
39
+
40
+
41
+ if (dy < thresh) {
42
+ ptLast.tangentR.y = pt0.y
43
+ ptLast.tangentL.y = pt0.y
44
+
45
+ }
46
+ if (dx < thresh) {
47
+ ptLast.tangentR.x = pt0.x
48
+ ptLast.tangentL.x = pt0.x
49
+ }
50
+ }
51
+ }
52
+
53
+
54
+ for (let i = 1; i < l; i++) {
55
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
56
+ let p1 = pts[i];
57
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
58
+ let dist = getDistManhattan(p1, p2)
59
+
60
+
61
+ let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1
62
+ let offset = 0;
63
+
64
+ let extremes = []
65
+
66
+ /*
67
+ if(isExtreme && p0.isCorner && !isLong && !isCorner){
68
+ isExtreme= false
69
+ p1.isExtreme = false
70
+ p1.isHorizontal = false
71
+ p1.isVertical = false
72
+
73
+ continue;
74
+ }
75
+ */
76
+
77
+
78
+ /*
79
+ if(isExtreme && p2.isCorner && !isLong && !isCorner && dist<threshShort*0.5){
80
+ isExtreme= false
81
+ p1.isExtreme = false
82
+ p1.isHorizontal = false
83
+ p1.isVertical = false
84
+
85
+ if(isVertical){
86
+ p2.tangentL.x = p2.x
87
+ }
88
+ if(isHorizontal){
89
+ p2.tangentL.y = p2.y
90
+ }
91
+ continue;
92
+ }
93
+ */
94
+
95
+
96
+ if (isExtreme && !isCorner && p2.isExtreme) {
97
+ extremes.push(p1);
98
+
99
+ for (let j = i + 1; j < l; j++) {
100
+ let p2 = pts[j]
101
+ dist = getDistManhattan(p1, p2)
102
+
103
+ if (dist * 0.75 >= threshShort || p2.isCorner || p2.isDirChange) {
104
+ break
105
+ }
106
+ if (p2.isExtreme && !p2.isDirChange && !p2.isCorner) {
107
+ extremes.push(p2);
108
+ }
109
+ }
110
+
111
+ //extremes = []
112
+ if (extremes.length > 1) {
113
+
114
+ //let ptPrev = pts[i - 2]
115
+ //let ptNext = pts[i + extremes.length]
116
+
117
+ //renderPoint(markers, ptPrev, 'orange', '2%')
118
+ //renderPoint(markers, ptNext, 'orange', '2%')
119
+
120
+ //let lenEx = extremes.length
121
+ //let idxM = Math.ceil(lenEx * 0.5)
122
+ //let extremeMid = extremes[idxM]
123
+
124
+ // find best extreme according to angle
125
+ let angleDiffMin = Infinity;
126
+ //let bestMatch = extremes[Math.floor(extremes.length*0.5)];
127
+ let bestMatch = extremes[0];
128
+
129
+
130
+ extremes.forEach(pt => {
131
+ //renderPoint(markers, pt, 'red', '3%')
132
+ let angle = Math.abs(getAngleFromDelta(pt.dx2, pt.dy2, false)) * rad2Deg
133
+ let angleDiff = angle > 160 ? Math.abs(180 - angle) : (angle > 60 ? Math.abs(90 - angle) : angle)
134
+ pt.angle = angle;
135
+ pt.angleDiff = angleDiff;
136
+
137
+ if (angleDiff < angleDiffMin) {
138
+ bestMatch = pt;
139
+ angleDiffMin = angleDiff
140
+ //if(!angleDiff) matches.push(pt)
141
+
142
+ }
143
+ })
144
+
145
+ //renderPoint(markers, bestMatch, 'red', '4.5%', '0.5')
146
+ //renderPoint(markers, bestMatch, 'blue', '4.5%', '0.5')
147
+
148
+
149
+ //console.log('matches', bestMatch, extremes);
150
+
151
+ let extremes2 = []
152
+
153
+ extremes.forEach((pt, i) => {
154
+
155
+ //let isBestMatch = pt.x === bestMatch.x && pt.y === bestMatch.y;
156
+ let isBestMatch = pt === bestMatch;
157
+ //renderPoint(markers, bestMatch, 'blue', '4.5%', '0.5')
158
+
159
+ if (isBestMatch) {
160
+ //extremes2.push(pt)
161
+ //renderPoint(markers, pt, 'red', '4.5%', '0.5')
162
+
163
+ if (pt.isHorizontal) {
164
+ pt.tangentL.y = pt.y
165
+ pt.tangentR.y = pt.y
166
+ }
167
+ if (pt.isVertical) {
168
+ pt.tangentL.x = pt.x
169
+ pt.tangentR.x = pt.x
170
+ }
171
+ //renderPoint(markers, extremes[0], 'brown', '2.5%', '0.5')
172
+ //renderPoint(markers, extremes[extremes.length - 1], 'magenta', '2.5%', '0.5')
173
+ // renderPoint(markers, pt, 'green', '3%', '0.5')
174
+
175
+ }
176
+ else {
177
+
178
+
179
+ //console.log('bestMatch', bestMatch);
180
+ //renderPoint(markers, pt, 'blue', '4.5%', '0.5')
181
+
182
+
183
+ if (bestMatch) {
184
+
185
+ if (!isBestMatch && (pt.x === bestMatch.x || pt.y === bestMatch.y)) {
186
+ extremes2.push(pt)
187
+ }
188
+ pt.isExtreme = false
189
+ pt.isHorizontal = false
190
+ pt.isVertical = false
191
+ }
192
+
193
+
194
+ }
195
+
196
+ })
197
+
198
+ // average coordinates
199
+ if (extremes2.length) {
200
+ bestMatch.x = (extremes2[0].x + bestMatch.x) * 0.5
201
+ bestMatch.y = (extremes2[0].y + bestMatch.y) * 0.5
202
+ //pts.splice(extremes2[0].idx, 1)
203
+ }
204
+
205
+ //console.log('extremes2', extremes2);
206
+ i += extremes.length
207
+ continue;
208
+ }
209
+ }
210
+
211
+ }
212
+
213
+ }
214
+
215
+ export function cleanupPolyKeypoints(pts = []) {
216
+
217
+ let l = pts.length;
218
+
219
+ let { x, y, width, height, top, bottom, left, right } = getPolyBBox(pts);
220
+ //let thresh0 = (width + height) * 0.025;
221
+ let thresh = (width + height) * 0.001;
222
+
223
+ let pt0 = pts[0];
224
+ //let ptLast = pts[l - 1];
225
+ let ptsClean = [pt0];
226
+
227
+ //renderPoint(markers, ptLast)
228
+
229
+ for (let i = 1; i < l; i++) {
230
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
231
+ let p1 = pts[i];
232
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
233
+
234
+ let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1
235
+ let offset = 0;
236
+
237
+ if (!isSemiExtreme){
238
+ //ptsClean.push(p1, p2)
239
+ continue
240
+ }
241
+
242
+ if (isSemiExtreme || isExtreme) {
243
+ let semiExtremes = isSemiExtreme ? [p1] : [];
244
+ let extremes = isExtreme ? [p1] : [];
245
+
246
+ for (let j = i + 1; j < l; j++) {
247
+ let p2 = pts[j]
248
+
249
+ if (!p2.isSemiExtreme || p2.isExtreme || p2.isCorner){
250
+ break
251
+ }
252
+ semiExtremes.push(p2)
253
+ }
254
+
255
+ if (semiExtremes.length > 1) {
256
+
257
+ //interpolate
258
+ let semiExtremeMid = semiExtremes[Math.floor(semiExtremes.length*0.5)]
259
+ let p1_1 = semiExtremes[0]
260
+ let p2_1 = semiExtremes[semiExtremes.length - 1]
261
+ let ptI = checkLineIntersection(p1_1, p1_1.tangentR, p2_1, p2_1.tangentL, false, true)
262
+ //console.log(ptI);
263
+ //console.log('tangents', p1_1, p1_1.tangentR, p2_1, p2_1.tangentL);
264
+
265
+ //renderPoly(markers, [p1_1, p1_1.tangentR], 'red', '2%')
266
+ semiExtremes.forEach(pt=>{
267
+ pt.isSemiExtreme=false
268
+ })
269
+ semiExtremeMid.isSemiExtreme=true;
270
+
271
+ // interpolate mid point
272
+ if (ptI) {
273
+ let pI_1 = interpolate(p1_1, ptI, 0.5)
274
+ let pI_2 = interpolate(p2_1, ptI, 0.5)
275
+ let pI_3 = interpolate(pI_2, pI_1, 0.5)
276
+ //renderPoint(markers, pI_3, 'red', '2%')
277
+
278
+ semiExtremeMid.x = pI_3.x
279
+ semiExtremeMid.y = pI_3.y
280
+ semiExtremeMid.tangentL = pI_1
281
+ semiExtremeMid.tangentR = pI_2
282
+ //p1.idx=i
283
+ //offset += semiExtremes.length - 1
284
+ //ptsClean.push(p1)
285
+
286
+ i += offset
287
+ continue
288
+ }
289
+
290
+ //console.log('semiExtremes', semiExtremes);
291
+ }
292
+ }
293
+
294
+ //p1.idx=i
295
+ ptsClean.push(p1)
296
+ // find significant of same type
297
+
298
+ }
299
+
300
+
301
+ /*
302
+ // update index
303
+ ptsClean.forEach((pt, i) => {
304
+ pt.idx = i
305
+ })
306
+ */
307
+ //console.log('ptsClean', ptsClean);
308
+ //renderPoint(markers, ptsClean[0], 'green', '4%')
309
+ return pts;
310
+ //return ptsClean;
311
+ }
@@ -0,0 +1,125 @@
1
+ import { adjustTangentAngle } from "../poly-fit-curve-schneider_check_bulge";
2
+ import { getPolyBBox } from "./geometry_bbox";
3
+ import { renderPoint } from "./visualize";
4
+
5
+ export function getTangents(pts = [], {
6
+ x = 0,
7
+ y = 0,
8
+ width = 0,
9
+ height = 0,
10
+ debug = false,
11
+ closed=false,
12
+ } = {}) {
13
+
14
+ let l = pts.length;
15
+
16
+ // bounding box of this sub poly
17
+ if (!width || !height) {
18
+ ({ x, y, width, height } = getPolyBBox(pts));
19
+ }
20
+
21
+ //console.log(polyArea);
22
+ let thresh = (width + height) * 0.01;
23
+
24
+ // threshold for horizontal or vertical detection
25
+ //let thresh2 = thresh * 0.5
26
+
27
+
28
+ for (let i = 0; i < l; i++) {
29
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
30
+ let p1 = pts[i];
31
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
32
+ let p3 = i < l - 1 ? pts[i + 2] : pts[l - 1];
33
+
34
+ let { isHorizontal, isVertical, isCorner, isLong, isExtreme, isSemiExtreme, isDirChange } = p1
35
+
36
+
37
+
38
+ // default
39
+ let tangentL = { x: p1.x - p1.dx2 * 0.5, y: p1.y - p1.dy2 * 0.5 }
40
+ let tangentR = { x: p1.x + p1.dx2 * 0.5, y: p1.y + p1.dy2 * 0.5 }
41
+
42
+
43
+ // average first tangent
44
+ if(i===0){
45
+ tangentR = adjustTangentAngle(p2, p1, p2, p3)
46
+ }
47
+
48
+
49
+ /**
50
+ * add left and right tangents
51
+ * for later curve fitting
52
+ */
53
+
54
+ if (isHorizontal && !isCorner) {
55
+ tangentL = { x: p1.x - p1.dx2*0.5, y: p1.y }
56
+ tangentR = { x: p1.x + p1.dx2*0.5, y: p1.y }
57
+ }
58
+ else if (isVertical) {
59
+ tangentL = { x: p1.x , y: p1.y - p1.dy2*0.5 }
60
+ tangentR = { x: p1.x , y: p1.y + p1.dy2*0.5 }
61
+ }
62
+
63
+
64
+ if (!isExtreme && p1.isLong) {
65
+ tangentL = { x: p1.x - p1.dx*0.5, y: p1.y - p1.dy*0.5 }
66
+ tangentR = { x: p1.x + p1.dx*0.5, y: p1.y + p1.dy*0.5 }
67
+ }
68
+
69
+
70
+ /*
71
+ //isDirChange && !isCorner && !isHorizontal && !isVertical
72
+ if (isDirChange && !isCorner && !isExtreme) {
73
+ p1.tangentL = { x: p1.x-p1.dx2*0.5, y: p1.y-p1.dy2*0.5 }
74
+ p1.tangentR = { x: p1.x+p1.dx2*0.5, y: p1.y+p1.dy2*0.5 }
75
+ }
76
+ */
77
+
78
+ if (isCorner) {
79
+
80
+ tangentL = {x:p0.x, y:p0.y}
81
+ tangentR = {x:p2.x, y:p2.y}
82
+
83
+ //let p0_1 = pts[i - 2] ? pts[i - 2] : pts[l - 2]
84
+ let p0_1 = pts[i - 2] ? pts[i - 2] : pts[l - 1]
85
+ //let p2_1 = pts[i + 2] ? pts[i + 2] : pts[l - 2]
86
+ let p2_1 = pts[i + 2] ? pts[i + 2] : pts[1]
87
+
88
+ // adjust angle
89
+ if (!p0.isCorner) {
90
+ tangentL = adjustTangentAngle(p0, p1, p0, p0_1)
91
+ }
92
+
93
+ if (!p2.isCorner) {
94
+ tangentR = adjustTangentAngle(tangentR, p1, p2, p2_1)
95
+ }
96
+
97
+ /*
98
+ renderPoint(markers, p0, 'darkblue', '0.75%', '0.5')
99
+ // renderPoint(markers, p0_1, 'blue', '0.5%')
100
+ renderPoint(markers, tangentL, 'blue', '0.5%', '0.5')
101
+ renderPoint(markers, tangentR, 'blue', '0.5%', '0.5')
102
+ */
103
+
104
+ }
105
+
106
+ p1.tangentL = tangentL
107
+ p1.tangentR = tangentR
108
+
109
+
110
+ //console.log('t', p1.tangentL, p1.tangentR);
111
+
112
+ /*
113
+ debug = true
114
+ if(debug){
115
+ if (isCorner || isSemiExtreme || isDirChange || isExtreme) {
116
+ renderPoint(markers, p1.tangentL, 'darkred', '0.5%')
117
+ renderPoint(markers, p1.tangentR, 'darkblue', '0.5%')
118
+ }
119
+ }
120
+ */
121
+
122
+
123
+ }
124
+
125
+ }
@@ -27,7 +27,9 @@ export function getPolyChunks(pts,
27
27
 
28
28
  // start new chunk
29
29
  // keepInflections && p1.isDirChange
30
- if ((keepExtremes && p1.isExtreme || keepCorners && p1.isCorner )) {
30
+ if ((keepExtremes && p1.isExtreme || keepCorners && p1.isCorner ||
31
+ (keepInflections && p1.isDirChange && !p1.isExtreme && !p1.isCorner )
32
+ )) {
31
33
  idx++
32
34
  chunks.push([])
33
35
  }
@@ -4,20 +4,33 @@ export function normalizePoly(pts, {
4
4
  flatten = false
5
5
  } = {}) {
6
6
 
7
+
7
8
  // is stringified flat point attribute
8
- if(typeof pts === 'string' && !isNaN(pts[0])){
9
+ if (typeof pts === 'string' && !isNaN(pts[0])) {
9
10
  pts = toPointArray(pts.split(/,| /).filter(Boolean).map(Number));
10
11
  return pts
11
12
  }
12
13
 
14
+ if (pts.length && typeof pts[0] === 'string') {
15
+ pts = pts.map(pt => {
16
+ return toPointArray(pt.split(/,| /).filter(Boolean).map(Number))
17
+ });
18
+ pts = pts.flat(2)
19
+ //console.log(pts);
20
+ }
21
+
22
+
13
23
  if (flatten) pts = pts.flat(2);
24
+
25
+
14
26
  let poly = toArray ? polyPtsToArray(pts) : polyArrayToObject(pts)
15
27
  return poly
16
28
  }
17
29
 
18
30
 
19
- export function polyArrayToObject(pts) {
20
-
31
+ export function polyArrayToObject(pts = []) {
32
+ //console.log(pts);
33
+ if (!pts.length) return [];
21
34
  // is point object array
22
35
  if (pts[0].x !== undefined && pts[0].y !== undefined) return pts
23
36
 
@@ -35,7 +48,7 @@ export function polyArrayToObject(pts) {
35
48
  return poly
36
49
  }
37
50
 
38
- else if(pts.length>3){
51
+ else if (pts.length > 3) {
39
52
  pts = toPointArray(pts)
40
53
  return pts
41
54
  }
@@ -65,13 +78,13 @@ export function polyPtsToArray(pts) {
65
78
  export function toPointArray(pts) {
66
79
  let ptArr = [];
67
80
 
68
- if(pts[0].length===2){
69
- for (let i = 0, l = pts.length; i < l; i ++) {
81
+ if (pts[0].length === 2) {
82
+ for (let i = 0, l = pts.length; i < l; i++) {
70
83
  let pt = pts[i]
71
- ptArr.push({ x: pt[0], y:pt[1] });
84
+ ptArr.push({ x: pt[0], y: pt[1] });
72
85
  }
73
86
 
74
- }else{
87
+ } else {
75
88
  for (let i = 1, l = pts.length; i < l; i += 2) {
76
89
  ptArr.push({ x: pts[i - 1], y: pts[i] });
77
90
  }