svg-path-simplify 0.1.3 → 0.2.2

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 (43) hide show
  1. package/README.md +10 -0
  2. package/dist/svg-path-simplify.esm.js +3905 -1533
  3. package/dist/svg-path-simplify.esm.min.js +13 -1
  4. package/dist/svg-path-simplify.js +3923 -1551
  5. package/dist/svg-path-simplify.min.js +13 -1
  6. package/dist/svg-path-simplify.min.js.gz +0 -0
  7. package/index.html +61 -31
  8. package/package.json +3 -5
  9. package/src/constants.js +3 -0
  10. package/src/index-node.js +0 -1
  11. package/src/index.js +26 -0
  12. package/src/pathData_simplify_cubic.js +74 -31
  13. package/src/pathData_simplify_cubicsToArcs.js +566 -0
  14. package/src/pathData_simplify_harmonize_cpts.js +170 -0
  15. package/src/pathData_simplify_revertToquadratics.js +21 -0
  16. package/src/pathSimplify-main.js +253 -86
  17. package/src/poly-fit-curve-schneider.js +570 -0
  18. package/src/simplify_poly_RDP.js +146 -0
  19. package/src/simplify_poly_radial_distance.js +100 -0
  20. package/src/svg_getViewbox.js +1 -1
  21. package/src/svgii/geometry.js +389 -63
  22. package/src/svgii/geometry_area.js +2 -1
  23. package/src/svgii/pathData_analyze.js +259 -212
  24. package/src/svgii/pathData_convert.js +91 -663
  25. package/src/svgii/pathData_fromPoly.js +12 -0
  26. package/src/svgii/pathData_parse.js +90 -89
  27. package/src/svgii/pathData_parse_els.js +3 -0
  28. package/src/svgii/pathData_parse_fontello.js +449 -0
  29. package/src/svgii/pathData_remove_collinear.js +44 -37
  30. package/src/svgii/pathData_reorder.js +2 -1
  31. package/src/svgii/pathData_simplify_redraw.js +343 -0
  32. package/src/svgii/pathData_simplify_refineCorners.js +18 -9
  33. package/src/svgii/pathData_simplify_refineExtremes.js +19 -78
  34. package/src/svgii/pathData_split.js +42 -45
  35. package/src/svgii/pathData_toPolygon.js +130 -4
  36. package/src/svgii/poly_analyze.js +470 -14
  37. package/src/svgii/poly_to_pathdata.js +224 -19
  38. package/src/svgii/rounding.js +55 -112
  39. package/src/svgii/svg_cleanup.js +13 -1
  40. package/src/svgii/visualize.js +8 -3
  41. package/{debug.cjs → tests/debug.cjs} +3 -0
  42. /package/{test.js → tests/test.js} +0 -0
  43. /package/{testSVG.js → tests/testSVG.js} +0 -0
@@ -1,20 +1,453 @@
1
- import { checkLineIntersection, getAngle, getPointOnEllipse, getSquareDistance, pointAtT, reducePoints } from "./geometry";
1
+ import { rad2Deg } from "../constants";
2
+ import { simplifyRDP } from "../simplify_poly_RDP";
3
+ import { simplifyRD } from "../simplify_poly_radial_distance";
4
+ import { checkLineIntersection, getAngle, getDeltaAngle, getDeltaAngle2, getDistManhattan, getPointOnEllipse, getSquareDistance, pointAtT, reducePoints, rotatePoint } from "./geometry";
2
5
  import { getPolygonArea } from "./geometry_area";
3
6
  import { getPolyBBox } from "./geometry_bbox";
4
- import { renderPoint } from "./visualize";
7
+ import { pathDataFromPoly } from "./pathData_fromPoly";
8
+ import { pathDataToD } from "./pathData_stringify";
9
+ import { renderPath, renderPoint, renderPoly } from "./visualize";
5
10
 
6
- export function analyzePoly(pts, debug=false) {
11
+
12
+ export function analyzePoly(pts, {
13
+ x = 0,
14
+ y = 0,
15
+ width = 0,
16
+ height = 0,
17
+ debug = false
18
+ } = {}) {
19
+
20
+ let l = pts.length;
21
+ //let polyArea = getPolygonArea(pts, true)
22
+
23
+ //let bb0 = {x:0, y:0, width:0, height:0}
24
+
25
+ // bounding box of this sub poly
26
+ let bb0 = getPolyBBox(pts);
27
+
28
+
29
+ if (!width || !height) {
30
+ ({ x, y, width, height } = bb0);
31
+ }
32
+
33
+ //console.log(polyArea);
34
+ let thresh = (width + height) * 0.01
35
+
36
+ //console.log(thresh);
37
+
38
+ // get areas
39
+ for (let i = 0; i < l; i++) {
40
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
41
+ let p1 = pts[i];
42
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
43
+
44
+ let area = getPolygonArea([p0, p1, p2], false);
45
+ p1.area = area;
46
+ p1.dist = getDistManhattan(p0, p1)
47
+ //pts[i] = p1
48
+ }
49
+
50
+ // pts= pts.reverse();
51
+
52
+ let remove = []
53
+
54
+ for (let i = 0; i < l; i++) {
55
+ let p02 = i > 1 ? pts[i - 2] : pts[l - 1];
56
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
57
+ let p1 = pts[i];
58
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
59
+
60
+
61
+ let bb = getPolyBBox([p0, p2]);
62
+ let max = getSquareDistance(p0, p2) * 0.01
63
+
64
+ let area0 = Math.abs(p0.area)
65
+ let area1 = Math.abs(p1.area)
66
+ let area2 = Math.abs(p2.area)
67
+ let isCloseExtreme = false
68
+ let isCorner = false;
69
+
70
+
71
+ let flat = !p1.area || area1 < thresh
72
+
73
+
74
+ //console.log(bb);
75
+ let { left, right, top, bottom } = bb;
76
+
77
+ // is local or absolute extreme
78
+ let extremeLocal = (p1.x <= left || p1.x >= right || p1.y <= top || p1.y >= bottom)
79
+
80
+
81
+ let isExtreme = (
82
+ // extremeLocal ||
83
+ (p1.x === bb0.left || p1.x === bb0.right || p1.y === bb0.top || p1.y === bb0.bottom)
84
+ );
85
+
86
+
87
+ let dist = getDistManhattan(p1, p0)
88
+ let isNear = dist < thresh * 5
89
+
90
+
91
+
92
+ //let isHorizontal = !pt0.isHorizontal ? pt1.y === pt0.y && pt1.x!== pt0.x : false;
93
+ let isHorizontal = p1.y === p0.y && p1.x !== p0.x;
94
+ let isVertical = p1.x === p0.x && p1.y !== p0.y
95
+
96
+
97
+ //!isCloseExtreme &&
98
+ if ((isHorizontal || isVertical)) {
99
+ isExtreme = true
100
+ }
101
+
102
+ //let area0 = p02.area + p0.area
103
+ //let area1 = p1.area + p2.area
104
+
105
+ let signChange = (p0.area < 0 && p1.area > 0) || (p0.area > 0 && p1.area < 0)
106
+ let isDirChange = signChange && !flat
107
+ isCorner = isDirChange
108
+
109
+ // isDirChange = signChange && signChange3
110
+
111
+ if (extremeLocal && !p0.isDirChange) {
112
+ //renderPoint(markers, p0, 'red', '0.75%', '1')
113
+ //renderPoint(markers, p1, 'blue', '0.75%', '1')
114
+ //p1.isDirChange=false
115
+ isExtreme = true;
116
+ }
117
+
118
+
119
+
120
+ //|| (p0.isVertical || p1.isHorizontal )
121
+ //|| (p0.isVertical || isHorizontal )
122
+ if ((isVertical && p0.isHorizontal)) {
123
+ //renderPoint(markers, p0, 'red', '2%', '1')
124
+ p0.isCorner = true
125
+ p0.isExtreme = false
126
+ isExtreme = false
127
+ }
128
+
129
+
130
+ /*
131
+ if (isExtreme && p0.isExtreme && signChange && isNear) {
132
+ //console.log(p0);
133
+ //renderPoint(markers, p1, 'red', '2%', '1')
134
+ isCorner = true
135
+ }
136
+ */
137
+
138
+ /*
139
+ //nearby extremes
140
+ if (isExtreme && p0.isExtreme) {
141
+ let dist = getDistManhattan(p1, p0)
142
+ if (dist < thresh * 5) {
143
+ //p0.isExtreme = true
144
+ p1.isExtreme = false
145
+ isExtreme=false
146
+ isCloseExtreme = true
147
+ remove.push(i)
148
+ }
149
+
150
+ }
151
+ */
152
+
153
+
154
+ // reset if 2 in sequence
155
+ if (isDirChange && (p0.isDirChange || p0.isExtreme)) {
156
+ isDirChange = false
157
+ p1.isDirChange = false
158
+ }
159
+
160
+
161
+
162
+
163
+ /*
164
+ let areaChange = area2*1.5<area1 && area0*1.5<area1;
165
+ if(areaChange){
166
+ //renderPoint(markers, p1, 'red', '1.75%', '1')
167
+ }
168
+ */
169
+
170
+
171
+
172
+ /*
173
+ let thresh2 = (width + height) * 0.01
174
+ let isLong = p1.dist > thresh2
175
+ console.log('thresh2', p1.dist, thresh2, thresh);
176
+
177
+ if( isLong && isDirChange){
178
+ renderPoint(markers, p0, 'red', '1.75%', '1')
179
+ renderPoint(markers, p1, 'green', '1.75%', '1')
180
+ }
181
+ */
182
+
183
+ // corner after extreme
184
+ if (p0.isExtreme && area1 > area2 && !isDirChange && !p1.isHorizontal) {
185
+ }
186
+
187
+
188
+ // refine corner check
189
+ /*
190
+ if (isCorner) {
191
+ let delta = getDeltaAngle(p1, p0, p2)
192
+ delta = Math.abs(delta.deltaAngleDeg)
193
+ if (delta > 160) {
194
+ //renderPoint(markers, p1, 'blue', '2.75%', '1')
195
+ isCorner = false;
196
+ } else {
197
+ isCorner = true;
198
+ }
199
+ console.log(delta);
200
+ }
201
+ */
202
+
203
+
204
+ if ((isExtreme || signChange || area1 > area0)) {
205
+
206
+ let delta = getDeltaAngle(p1, p2, p0)
207
+ let { deltaAngleDeg } = delta
208
+ deltaAngleDeg = Math.abs(deltaAngleDeg)
209
+
210
+
211
+ // not a corner
212
+ if (deltaAngleDeg < 3 || deltaAngleDeg > 160) {
213
+ //renderPoint(markers, p1, 'blue', '2.75%', '1')
214
+ isCorner = false;
215
+ }
216
+ else {
217
+ //console.log(deltaAngleDeg, delta);
218
+ isCorner = true;
219
+ }
220
+ }
221
+
222
+ if (isHorizontal) {
223
+ //renderPoint(markers, p1, 'blue', '2.75%', '1')
224
+
225
+ }
226
+
227
+
228
+ p1.isCorner = isCorner;
229
+ p1.isExtreme = isExtreme;
230
+ p1.isHorizontal = isHorizontal;
231
+ p1.isVertical = isVertical;
232
+ p1.isDirChange = isDirChange;
233
+
234
+ }
235
+
236
+
237
+
238
+ // filter adjacent extremes
239
+ let pts1 = []
240
+ let exclude = []
241
+
242
+ for (let i = 0; i < pts.length; i++) {
243
+ let p = pts[i]
244
+ let p1 = pts[i + 1] || null
245
+ let p2 = pts[i + 2] || null
246
+
247
+ let extremes = []
248
+
249
+
250
+ if (p1 && p1.isExtreme && p.isExtreme && !p.isCorner) {
251
+ let has2nd = p1.dist < thresh * 2 && !p1.isCorner
252
+ let has3rd = p2 && p2.isExtreme && p2.dist < thresh * 2 && !p2.isCorner
253
+ let lastExt = p1
254
+
255
+ if (has2nd && !has3rd) {
256
+ extremes.push(p, p1)
257
+ //renderPoint(markers, p1, 'magenta', '1%', '0.5')
258
+ } else if (has3rd) {
259
+ extremes.push(p, p1, p2)
260
+ /*
261
+ renderPoint(markers, p, 'green', '1%', '0.5')
262
+ renderPoint(markers, p1, 'red', '1%', '0.5')
263
+ renderPoint(markers, p2, 'blue', '1%', '0.5')
264
+ */
265
+ }
266
+
267
+ if (extremes.length) {
268
+ // average extreme
269
+ //console.log(extremes);
270
+ let x = extremes.reduce((a, b) => a + b.x, 0) / extremes.length
271
+ let y = extremes.reduce((a, b) => a + b.y, 0) / extremes.length
272
+
273
+ ///extremes.length
274
+ p.x = x
275
+ p.y = y
276
+ //console.log(x);
277
+ i += extremes.length - 1
278
+ }
279
+ }
280
+
281
+ if (p.isExtreme || p.isCorner || p.isDirChange) {
282
+ exclude.push(pts1.length)
283
+ }
284
+
285
+
286
+ pts1.push(p)
287
+ }
288
+
289
+ // remove last nearby extreme
290
+ let l2 = pts1.length;
291
+ let p0 = pts1[0]
292
+ let pL = pts1[l2 - 1]
293
+ let near0 = getDistManhattan(p0, pL) < thresh * 2
294
+ if (p0.isExtreme && pL.isExtreme && near0) {
295
+ pL.x = p0.x
296
+ pL.y = p0.y
297
+ }
298
+
299
+
300
+ //console.log(exclude);
301
+ //pts1 = simplifyRD(pts1, {quality:0.7, exclude})
302
+
303
+ // simplify via RDP - exclude extremes
304
+ //pts1 = simplifyRDP(pts1, {quality:0.85, exclude})
305
+
306
+ /*
307
+ pts1.forEach(pt=>{
308
+ renderPoint(markers, pt, 'green', '1%', '0.5')
309
+ })
310
+ */
311
+
312
+ pts = pts1
313
+ //console.log(pts1);
314
+
315
+ // test render
316
+ //renderPolyTopology(pts)
317
+ //return pathDataFromPoly(pts)
318
+
319
+ return pts
320
+ }
321
+
322
+
323
+ // split in chunks based on significant points
324
+
325
+ export function getPolyChunks(pts,
326
+ { closed = true,
327
+ keepCorners = true,
328
+ keepExtremes = true,
329
+ keepInflections = false
330
+ } = {}
331
+ ) {
332
+ let chunks = [];
333
+ let chunk = [pts[0]];
334
+
335
+ let l = pts.length
336
+
337
+ // render
338
+ for (let i = 1; i < l; i++) {
339
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
340
+ let p1 = pts[i];
341
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
342
+
343
+ chunk.push(p1)
344
+
345
+ // start new chunk
346
+ if (i > 0 &&
347
+ (keepExtremes && p2.isExtreme || keepCorners && p2.isCorner)
348
+ ) {
349
+ chunks.push(chunk)
350
+ chunk = []
351
+ }
352
+
353
+ //else if(!keepExtremes && !keepExtremes){}
354
+ }
355
+
356
+ // chunk is empty - extremes or corners
357
+ if(!chunks.length && pts.length>1){
358
+ chunks = [pts]
359
+ }
360
+
361
+ //console.log(keepExtremes, keepCorners, chunks, pts);
362
+
363
+ // test render
364
+ //renderchunks(chunks)
365
+
366
+ return chunks;
367
+ }
368
+
369
+ function renderchunks(chunks) {
370
+
371
+ //console.log('renderchunks', chunks);
372
+
373
+ chunks.forEach((chunk, i) => {
374
+
375
+ let stroke = i % 2 === 0 ? 'orange' : 'blue';
376
+ let pathData = [{ type: 'M', values: [chunk[0].x, chunk[0].y] }]
377
+ let d = `M`
378
+
379
+ chunk.forEach(pt => {
380
+
381
+ pathData.push({ type: 'L', values: [pt.x, pt.y] })
382
+ d += ` ${[pt.x, pt.y].join(' ')}`
383
+
384
+ })
385
+
386
+ d = pathDataToD(pathData)
387
+ //console.log(d);
388
+
389
+ renderPath(markers, d, stroke, '2%', '0.5')
390
+ })
391
+
392
+ }
393
+
394
+ // just for visualization
395
+ function renderPolyTopology(pts) {
396
+
397
+ let l = pts.length
398
+ // render
399
+ for (let i = 0; i < l; i++) {
400
+ let p0 = i > 0 ? pts[i - 1] : pts[l - 1];
401
+ let p1 = pts[i];
402
+ let p2 = i < l - 1 ? pts[i + 1] : pts[l - 1];
403
+
404
+
405
+ renderPoly(markers, [p0, p1, p2], '0.5%', 'none', (p1.area > 0 ? 'green' : 'blue'), true)
406
+
407
+
408
+ if (p1.isDirChange) {
409
+ renderPoint(markers, p1, 'orange', '1%', '0.75')
410
+ }
411
+
412
+
413
+ if (p1.isExtreme) {
414
+ renderPoint(markers, p1, 'cyan', '1%', '0.5')
415
+ }
416
+
417
+ if (p1.isHorizontal) {
418
+ //renderPoint(markers, p1, 'blue', '0.5%', '0.75')
419
+ }
420
+
421
+ if (p1.isVertical) {
422
+ //renderPoint(markers, p1, 'purple', '0.5%', '0.5')
423
+ }
424
+
425
+
426
+
427
+ if (p1.isCorner) {
428
+ renderPoint(markers, p1, 'magenta', '1%', '1')
429
+ }
430
+
431
+ //pts[i] = p1
432
+ }
433
+
434
+ }
435
+
436
+ export function analyzePoly2(pts, debug = false) {
7
437
 
8
438
  let l = pts.length;
9
439
  let polyArea = getPolygonArea(pts, true)
440
+ let bb0 = getPolyBBox(pts)
10
441
  //console.log(polyArea);
11
442
 
12
443
 
13
444
  // get areas
14
445
  for (let i = 0; i < l; i++) {
15
- let pt0 = i > 0 ? pts[i - 1] : pts[l - 1];
446
+ let pt0 = i > 0 ? pts[i - 1] : null;
16
447
  let pt1 = pts[i];
17
- let pt2 = i < l - 1 ? pts[i + 1] : pts[0];
448
+ let pt2 = i < l - 1 ? pts[i + 1] : null;
449
+
450
+ if (!pt0 || !pt2) continue
18
451
 
19
452
  let area = getPolygonArea([pt0, pt1, pt2], false);
20
453
  let ang1 = getAngle(pt1, pt0, true);
@@ -22,7 +455,7 @@ export function analyzePoly(pts, debug=false) {
22
455
  let delta = Math.abs(ang1 - ang2);
23
456
  let deltaDeg = delta * 180 / Math.PI;
24
457
 
25
-
458
+ //console.log(bb0);
26
459
 
27
460
  /**
28
461
  * get local extremes
@@ -30,7 +463,17 @@ export function analyzePoly(pts, debug=false) {
30
463
  * direction changes
31
464
  */
32
465
  let { left, right, top, bottom } = getPolyBBox([pt0, pt2]);
33
- let isExtreme = (pt1.x < left || pt1.x > right || pt1.y < top || pt1.y > bottom);
466
+ let isExtreme = ((pt1.x < left || pt1.x > right || pt1.y < top || pt1.y > bottom) ||
467
+ (pt1.x === bb0.left || pt1.x === bb0.right || pt1.y === bb0.top || pt1.y === bb0.bottom)
468
+ );
469
+ //let isHorizontal = !pt0.isHorizontal ? pt1.y === pt0.y && pt1.x!== pt0.x : false;
470
+ let isHorizontal = pt1.y === pt0.y && pt1.x !== pt0.x;
471
+ let isVertical = pt1.x === pt0.x && pt1.y !== pt0.y
472
+
473
+ if (pt0.isHorizontal) {
474
+ //isHorizontal=false
475
+ //renderPoint(markers, pt0)
476
+ }
34
477
 
35
478
 
36
479
  /**
@@ -75,24 +518,35 @@ export function analyzePoly(pts, debug=false) {
75
518
  /*
76
519
  */
77
520
 
78
- if(debug){
521
+ if (debug) {
79
522
 
80
523
  if ((isExtreme && isCorner)) {
81
- isExtreme = false;
524
+ //isExtreme = false;
82
525
  directionChange = false;
83
526
  //isCorner = false;
84
527
  }
85
-
528
+
529
+ if (isHorizontal) {
530
+ renderPoint(markers, pt0, 'blue', '1%', '0.5');
531
+ renderPoint(markers, pt1, 'red', '1%', '0.5');
532
+ }
533
+
534
+
535
+ if (isVertical) {
536
+ //renderPoint(markers, pt1, 'blue', '2%', '0.5');
537
+ }
538
+
539
+
86
540
  if (isExtreme) {
87
- renderPoint(markers, pt1, 'cyan', '1%');
541
+ renderPoint(markers, pt1, 'cyan', '2%');
88
542
  }
89
-
543
+
90
544
  if (isCorner) {
91
545
  renderPoint(markers, pt1, 'purple', '0.5%');
92
546
  }
93
-
547
+
94
548
  if (directionChange) {
95
- renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
549
+ //renderPoint(markers, pt1, 'orange', '1.5%', '0.5');
96
550
  }
97
551
 
98
552
  }
@@ -102,6 +556,8 @@ export function analyzePoly(pts, debug=false) {
102
556
  * save point analysis properties
103
557
  * to point objects
104
558
  */
559
+ pt1.isHorizontal = isHorizontal;
560
+ pt1.isVertical = isVertical;
105
561
  pt1.isExtreme = isExtreme;
106
562
  pt1.isCorner = isCorner;
107
563
  pt1.directionChange = directionChange;