svg-path-simplify 0.4.2 → 0.4.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -4
  3. package/dist/svg-path-simplify.esm.js +3593 -1279
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +3594 -1278
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/dist/svg-path-simplify.poly.cjs +9 -8
  10. package/docs/privacy-webapp.md +24 -0
  11. package/index.html +331 -152
  12. package/package.json +1 -1
  13. package/src/constants.js +4 -0
  14. package/src/css_parse.js +317 -0
  15. package/src/detect_input.js +76 -28
  16. package/src/index.js +8 -0
  17. package/src/pathData_simplify_cubic.js +26 -16
  18. package/src/pathData_simplify_harmonize_cpts.js +77 -1
  19. package/src/pathData_simplify_revertToquadratics.js +0 -1
  20. package/src/pathSimplify-main.js +304 -276
  21. package/src/pathSimplify-only-pathdata.js +7 -2
  22. package/src/pathSimplify-presets.js +254 -0
  23. package/src/poly-fit-curve-schneider.js +14 -7
  24. package/src/simplify_poly_RC.js +102 -0
  25. package/src/simplify_poly_RDP.js +109 -1
  26. package/src/simplify_poly_radial_distance.js +3 -3
  27. package/src/string_helpers.js +130 -4
  28. package/src/svg-getAttributes.js +4 -2
  29. package/src/svgii/convert_units.js +1 -1
  30. package/src/svgii/geometry.js +322 -5
  31. package/src/svgii/geometry_bbox_element.js +1 -1
  32. package/src/svgii/geometry_deduceRadius.js +116 -27
  33. package/src/svgii/geometry_length.js +253 -0
  34. package/src/svgii/pathData_analyze.js +18 -0
  35. package/src/svgii/pathData_convert.js +193 -89
  36. package/src/svgii/pathData_fix_directions.js +12 -14
  37. package/src/svgii/pathData_fromPoly.js +3 -3
  38. package/src/svgii/pathData_getLength.js +86 -0
  39. package/src/svgii/pathData_parse.js +2 -0
  40. package/src/svgii/pathData_parse_els.js +66 -68
  41. package/src/svgii/pathData_reorder.js +122 -16
  42. package/src/svgii/pathData_simplify_refineCorners.js +130 -35
  43. package/src/svgii/pathData_simplify_refine_round.js +420 -0
  44. package/src/svgii/pathData_split_to_groups.js +168 -0
  45. package/src/svgii/pathData_stringify.js +26 -64
  46. package/src/svgii/pathData_toPolygon.js +3 -4
  47. package/src/svgii/poly_analyze.js +61 -0
  48. package/src/svgii/poly_normalize.js +11 -2
  49. package/src/svgii/poly_to_pathdata.js +85 -24
  50. package/src/svgii/rounding.js +80 -78
  51. package/src/svgii/svg_cleanup.js +421 -619
  52. package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
  53. package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
  54. package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
  55. package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
  56. package/src/svgii/svg_cleanup_ungroup.js +36 -0
  57. package/src/svgii/svg_el_parse_style_props.js +72 -47
  58. package/src/svgii/svg_getElementLength.js +67 -0
  59. package/src/svgii/svg_validate.js +220 -0
  60. package/tests/testSVG.js +14 -1
  61. package/src/svgii/pathData_refine_round.js +0 -222
@@ -1,42 +1,92 @@
1
- import { getDeltaAngle, getDistance } from "./geometry";
1
+ import { checkLineIntersection, getDeltaAngle, getDistance, getDistManhattan, getSquareDistance } from "./geometry";
2
+ import { renderPoint } from "./visualize";
3
+ //import { arcToBezierResolved } from "./pathData_convert";
4
+ //import { renderPoint, renderPoly } from "./visualize";
2
5
 
3
- export function getArcFromPoly(pts) {
6
+ export function getArcFromPoly(pts, precise = false) {
4
7
  if (pts.length < 3) return false
5
8
 
6
9
  // Pick 3 well-spaced points
7
- let p1 = pts[0];
8
- let p2 = pts[Math.floor(pts.length / 2)];
9
- let p3 = pts[pts.length - 1];
10
+ let len = pts.length
11
+ let idx1 = Math.floor(len * 0.333)
12
+ let idx2 = Math.floor(len * 0.666)
13
+ let idx3 = Math.floor(len * 0.5)
10
14
 
11
- let x1 = p1.x, y1 = p1.y;
12
- let x2 = p2.x, y2 = p2.y;
13
- let x3 = p3.x, y3 = p3.y;
14
15
 
15
- let a = x1 - x2;
16
- let b = y1 - y2;
17
- let c = x1 - x3;
18
- let d = y1 - y3;
16
+ let p1 = pts[0];
17
+ let p2 = pts[idx3];
18
+ let p3 = pts[len - 1];
19
19
 
20
- let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
21
- let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
20
+ // Radius (use start point)
21
+ let pts1 = [p1, p2, p3];
22
+ let centroid = getPolyArcCentroid(pts1);
22
23
 
23
- let det = a * d - b * c;
24
+ let r = 0, deltaAngle = 0, startAngle = 0, endAngle = 0, angleData = {};
24
25
 
25
- if (Math.abs(det) < 1e-10) {
26
- console.warn("Points are collinear or numerically unstable");
27
- return false;
28
- }
29
26
 
30
- // find center of arc
31
- let cx = (d * e - b * f) / det;
32
- let cy = (-c * e + a * f) / det;
33
- let centroid = { x: cx, y: cy };
27
+ // check if radii are consistent
28
+ if (precise) {
29
+
30
+
31
+ /**
32
+ * check multiple centroids
33
+ * if the polyline can be expressed as
34
+ * an arc - all centroids should be close
35
+ */
36
+
37
+ if (len > 3) {
38
+ let centroid1 = getPolyArcCentroid([p1, pts[idx1], p3]);
39
+ let centroid2 = getPolyArcCentroid([p1, pts[idx2], p3]);
40
+
41
+ if (!centroid1 || !centroid2) return false;
42
+
43
+ //let dist0 = getDistManhattan(p1, p3)
44
+ let dist0 = getDistManhattan(centroid, p2)
45
+ let dist1 = getDistManhattan(centroid, centroid1)
46
+ let dist2 = getDistManhattan(centroid, centroid2)
47
+ let errorCentroid = (dist1 + dist2)
48
+
49
+ // centroids diverging too much
50
+ if (errorCentroid > dist0 * 0.05) {
51
+ //renderPoint(markers, centroid, 'magenta')
52
+ return false
53
+ }
54
+
55
+ }
56
+
57
+ let rSqMid = getSquareDistance(centroid, p2);
58
+
59
+ //check if radii are close enough
60
+ for (let i = 0; i < len; i++) {
61
+ let pt = pts[i]
62
+ let rSq = getSquareDistance(centroid, pt);
63
+ let error = Math.abs(rSqMid - rSq) / rSqMid
64
+
65
+ if (error > 0.0025) {
66
+ /*
67
+ console.log('error', error, len, idx1, idx2, idx3);
68
+ renderPoint(markers, centroid, 'orange')
69
+ renderPoint(markers, p1, 'green')
70
+ renderPoint(markers, p2)
71
+ renderPoint(markers, p3, 'purple')
72
+ */
73
+ return false;
74
+ }
75
+ }
76
+
77
+ // calculate proper radius
78
+ r = Math.sqrt(rSqMid);
79
+ angleData = getDeltaAngle(centroid, p1, p3);
80
+ ({ deltaAngle, startAngle, endAngle } = angleData);
81
+
82
+
83
+ } else {
84
+ r = getDistance(centroid, p1);
85
+ angleData = getDeltaAngle(centroid, p1, p3);
86
+ ({ deltaAngle, startAngle, endAngle } = angleData);
87
+ }
34
88
 
35
- // Radius (use start point)
36
- let r = getDistance(centroid, p1);
37
89
 
38
- let angleData = getDeltaAngle(centroid, p1, p3)
39
- let {deltaAngle, startAngle, endAngle} = angleData;
40
90
 
41
91
  return {
42
92
  centroid,
@@ -48,3 +98,42 @@ export function getArcFromPoly(pts) {
48
98
  }
49
99
 
50
100
 
101
+
102
+ export function getPolyArcCentroid(pts = []) {
103
+
104
+ pts = pts.filter(pt => pt !== undefined);
105
+ if (pts.length < 3) return false
106
+ //console.log(pts);
107
+
108
+ let p1 = pts[0];
109
+ let p2 = pts[Math.floor(pts.length / 2)];
110
+ let p3 = pts[pts.length - 1];
111
+
112
+ let x1 = p1.x, y1 = p1.y;
113
+ let x2 = p2.x, y2 = p2.y;
114
+ let x3 = p3.x, y3 = p3.y;
115
+
116
+ let a = x1 - x2;
117
+ let b = y1 - y2;
118
+ let c = x1 - x3;
119
+ let d = y1 - y3;
120
+
121
+ let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
122
+ let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
123
+
124
+ let det = a * d - b * c;
125
+
126
+ // colinear points
127
+ if (Math.abs(det) < 1e-10) {
128
+ return false;
129
+ }
130
+
131
+ // find center of arc
132
+ let cx = (d * e - b * f) / det;
133
+ let cy = (-c * e + a * f) / det;
134
+ let centroid = { x: cx, y: cy };
135
+ return centroid
136
+ }
137
+
138
+
139
+
@@ -0,0 +1,253 @@
1
+ import { getDistance, getDistManhattan } from "./geometry";
2
+
3
+ // Legendre Gauss weight and abscissa values
4
+ export const waArr_global = [];
5
+
6
+
7
+
8
+ export function getLength(pts, {
9
+ t = 1,
10
+ waArr = []
11
+ } = {}) {
12
+
13
+
14
+ const cubicBezierLength = (p0, cp1, cp2, p, t = 0, wa = []) => {
15
+ if (t === 0) {
16
+ return 0;
17
+ }
18
+
19
+ t = t > 1 ? 1 : t < 0 ? 0 : t;
20
+ let t2 = t / 2;
21
+
22
+ /**
23
+ * set higher legendre gauss weight abscissae values
24
+ * by more accurate weight/abscissae lookups
25
+ * https://pomax.github.io/bezierinfo/legendre-gauss.html
26
+ */
27
+
28
+
29
+ let sum = 0;
30
+
31
+ let x0 = p0.x, y0 = p0.y, cp1x = cp1.x, cp1y = cp1.y, cp2x = cp2.x, cp2y = cp2.y, px = p.x, py = p.y;
32
+
33
+
34
+ for (let i = 0, len = wa.length; i < len; i++) {
35
+ // weight and abscissae
36
+ let [w, a] = [wa[i][0], wa[i][1]];
37
+ let ct1_t = t2 * a;
38
+ let ct0 = -ct1_t + t2;
39
+
40
+ let xbase0 = base3(ct0, x0, cp1x, cp2x, px)
41
+ let ybase0 = base3(ct0, y0, cp1y, cp2y, py)
42
+
43
+ let comb0 = xbase0 * xbase0 + ybase0 * ybase0;
44
+
45
+ sum += w * Math.sqrt(comb0)
46
+
47
+ }
48
+ return t2 * sum;
49
+ }
50
+
51
+
52
+ const quadraticBezierLength = (p0, cp1, p, t, checkFlat = false) => {
53
+ if (t === 0) {
54
+ return 0;
55
+ }
56
+ // is flat/linear – treat as line
57
+ if (checkFlat) {
58
+ let l1 = getDistance(p0, cp1) + getDistance(cp1, p);
59
+ let l2 = getDistance(p0, p);
60
+ if (l1 === l2) {
61
+ return l2;
62
+ }
63
+ }
64
+
65
+ let a, b, c, d, e, e1, d1, v1x, v1y;
66
+ v1x = cp1.x * 2;
67
+ v1y = cp1.y * 2;
68
+ d = p0.x - v1x + p.x;
69
+ d1 = p0.y - v1y + p.y;
70
+ e = v1x - 2 * p0.x;
71
+ e1 = v1y - 2 * p0.y;
72
+ a = 4 * (d * d + d1 * d1);
73
+ b = 4 * (d * e + d1 * e1);
74
+ c = e * e + e1 * e1;
75
+
76
+ const bt = b / (2 * a),
77
+ ct = c / a,
78
+ ut = t + bt,
79
+ //k = ct - bt ** 2;
80
+ k = ct - bt * bt;
81
+
82
+ return (
83
+ (Math.sqrt(a) / 2) *
84
+ (ut * Math.sqrt(ut * ut + k) -
85
+ bt * Math.sqrt(bt * bt + k) +
86
+ k *
87
+ Math.log((ut + Math.sqrt(ut * ut + k)) / (bt + Math.sqrt(bt * bt + k))))
88
+ );
89
+ }
90
+
91
+
92
+ let length
93
+ if (pts.length === 4) {
94
+ length = cubicBezierLength(pts[0], pts[1], pts[2], pts[3], t, waArr)
95
+
96
+ }
97
+ else if (pts.length === 3) {
98
+ length = quadraticBezierLength(pts[0], pts[1], pts[2], t)
99
+ }
100
+ else {
101
+ length = getDistance(pts[0], pts[1])
102
+ }
103
+
104
+ return length;
105
+ }
106
+
107
+
108
+
109
+
110
+
111
+
112
+ // LG weight/abscissae generator
113
+ export function getLegendreGaussValues(n, x1 = -1, x2 = 1) {
114
+ console.log('add new LG', n);
115
+
116
+ let waArr = []
117
+ let z1, z, xm, xl, pp, p3, p2, p1;
118
+ const m = (n + 1) >> 1;
119
+ xm = 0.5 * (x2 + x1);
120
+ xl = 0.5 * (x2 - x1);
121
+
122
+ for (let i = m - 1; i >= 0; i--) {
123
+ z = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));
124
+ do {
125
+ p1 = 1;
126
+ p2 = 0;
127
+ for (let j = 0; j < n; j++) {
128
+ //Loop up the recurrence relation to get the Legendre polynomial evaluated at z.
129
+ p3 = p2;
130
+ p2 = p1;
131
+ p1 = ((2 * j + 1) * z * p2 - j * p3) / (j + 1);
132
+ }
133
+
134
+ pp = (n * (z * p1 - p2)) / (z * z - 1);
135
+ z1 = z;
136
+ z = z1 - p1 / pp; //Newton’s method
137
+
138
+ } while (Math.abs(z - z1) > 1.0e-14);
139
+
140
+ let weight = (2 * xl) / ((1 - z * z) * pp * pp);
141
+ let abscissa = xm + xl * z;
142
+
143
+ waArr.push(
144
+ [weight, -abscissa],
145
+ [weight, abscissa],
146
+ )
147
+ }
148
+
149
+ return waArr;
150
+ }
151
+
152
+
153
+
154
+
155
+
156
+ export function base3(t, p1, p2, p3, p4) {
157
+ let t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
158
+ t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
159
+ return t * t2 - 3 * p1 + 3 * p2;
160
+ };
161
+
162
+
163
+ export function getPolygonLength(pts=[], isPoly=false){
164
+
165
+ let len = 0;
166
+ let l=pts.length;
167
+
168
+ for(let i=1; i<l; i++){
169
+ let p1 = pts[i-1]
170
+ let p2 = pts[i]
171
+ len += getDistance(p1, p2)
172
+ }
173
+ if(isPoly){
174
+ len += getDistance(pts[l-1], pts[0])
175
+ }
176
+ return len
177
+ }
178
+
179
+ export function getPolygonLengthManhattan(pts=[], isPoly=false){
180
+
181
+ let len = 0;
182
+ let l=pts.length;
183
+
184
+ for(let i=1; i<l; i++){
185
+ let p1 = pts[i-1]
186
+ let p2 = pts[i]
187
+ len += getDistManhattan(p1, p2)
188
+ }
189
+ if(isPoly){
190
+ len += getDistManhattan(pts[l-1], pts[0])
191
+ }
192
+ return len
193
+ }
194
+
195
+
196
+ /**
197
+ * Ramanujan approximation
198
+ * based on: https://www.mathsisfun.com/geometry/ellipse-perimeter.html#tool
199
+ */
200
+ export function getEllipseLength(rx=0, ry=0) {
201
+ // is circle
202
+ if (rx === ry) {
203
+ //console.log('is circle')
204
+ return 2 * Math.PI * rx;
205
+ }
206
+
207
+ let c=rx+ry
208
+ let d = (rx - ry) / c;
209
+ let h = d*d
210
+
211
+ let totalLength = Math.PI * c * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h) ));
212
+ return totalLength;
213
+ };
214
+
215
+
216
+
217
+ /**
218
+ * ellipse helpers
219
+ * approximate ellipse length
220
+ * by Legendre-Gauss
221
+ */
222
+
223
+ export function getCircleArcLength(r = 0, deltaAngle = 0) {
224
+ if(r===0) {
225
+ console.warn('Radius must be positive');
226
+ return 0;
227
+ }
228
+ let len = 2 * Math.PI * r * (1 / 360 * Math.abs(deltaAngle * 180 / Math.PI))
229
+ return len
230
+ }
231
+
232
+ export function getEllipseLengthLG(rx, ry, startAngle, endAngle, wa = []) {
233
+
234
+ // Transform [-1, 1] interval to [startAngle, endAngle]
235
+ let halfInterval = (endAngle - startAngle) * 0.5;
236
+ let midpoint = (endAngle + startAngle) * 0.5;
237
+
238
+ // Arc length integral approximation
239
+ let arcLength = 0;
240
+ for (let i = 0; i < wa.length; i++) {
241
+ let [weight, abscissae] = wa[i];
242
+ let theta = midpoint + halfInterval * abscissae;
243
+
244
+ let a = rx * Math.sin(theta);
245
+ let b = ry * Math.cos(theta);
246
+ let integrand = Math.sqrt(
247
+ a * a + b * b
248
+ );
249
+ arcLength += weight * integrand;
250
+ }
251
+
252
+ return Math.abs(halfInterval * arcLength)
253
+ }
@@ -67,6 +67,7 @@ export function analyzePathData(pathData = [], {
67
67
  let { type, values, p0, p, cp1 = null, cp2 = null, squareDist = 0, cptArea = 0, dimA = 0 } = com;
68
68
 
69
69
  //next command
70
+ let comPrev = pathData[c-2];
70
71
  let comN = pathData[c] || null;
71
72
 
72
73
 
@@ -93,6 +94,7 @@ export function analyzePathData(pathData = [], {
93
94
 
94
95
  // bezier types
95
96
  let isBezier = type === 'Q' || type === 'C';
97
+ let isArc = type === 'A';
96
98
  let isBezierN = comN && (comN.type === 'Q' || comN.type === 'C');
97
99
 
98
100
 
@@ -149,6 +151,22 @@ export function analyzePathData(pathData = [], {
149
151
  }
150
152
  }
151
153
 
154
+ // check extremes introduce by small arcs
155
+ else if(isArc && comN && ((comPrev.type==='C' || comPrev.type==='Q') || (comN.type==='C' || comN.type==='Q')) ){
156
+ let distN = comN ? comN.dimA : 0
157
+ let isShort = com.dimA < (comPrev.dimA + distN) * 0.1;
158
+ let smallRadius = com.values[0] === com.values[1] && (com.values[0] < 1)
159
+
160
+ if(isShort && smallRadius){
161
+ let bb = getPolyBBox([comPrev.p0, comN.p])
162
+ if(p.x>bb.right || p.x<bb.x || p.y<bb.y || p.y>bb.bottom){
163
+ hasExtremes = true;
164
+ //renderPoint(markers, p)
165
+ }
166
+ }
167
+
168
+ }
169
+
152
170
  if (hasExtremes) com.extreme = true
153
171
 
154
172
  // Corners and semi extremes