svg-path-simplify 0.4.1 → 0.4.3

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 +6 -4
  3. package/dist/svg-path-simplify.esm.js +2450 -888
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +2450 -888
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +167 -85
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
  9. package/docs/privacy-webapp.md +24 -0
  10. package/index.html +333 -132
  11. package/package.json +5 -2
  12. package/src/css_parse.js +317 -0
  13. package/src/detect_input.js +34 -4
  14. package/src/pathData_simplify_harmonize_cpts.js +77 -1
  15. package/src/pathSimplify-main.js +246 -262
  16. package/src/pathSimplify-presets.js +243 -0
  17. package/src/poly-fit-curve-schneider.js +14 -7
  18. package/src/simplify_poly_RC.js +102 -0
  19. package/src/simplify_poly_RDP.js +109 -1
  20. package/src/simplify_poly_radial_distance.js +3 -3
  21. package/src/string_helpers.js +144 -0
  22. package/src/svgii/convert_units.js +8 -2
  23. package/src/svgii/geometry.js +182 -3
  24. package/src/svgii/geometry_length.js +237 -0
  25. package/src/svgii/pathData_convert.js +43 -1
  26. package/src/svgii/pathData_fix_directions.js +6 -0
  27. package/src/svgii/pathData_fromPoly.js +3 -3
  28. package/src/svgii/pathData_getLength.js +86 -0
  29. package/src/svgii/pathData_parse.js +2 -0
  30. package/src/svgii/pathData_parse_els.js +189 -189
  31. package/src/svgii/pathData_split_to_groups.js +168 -0
  32. package/src/svgii/pathData_stringify.js +26 -64
  33. package/src/svgii/pathData_toPolygon.js +3 -4
  34. package/src/svgii/poly_analyze.js +61 -0
  35. package/src/svgii/poly_normalize.js +11 -2
  36. package/src/svgii/poly_to_pathdata.js +85 -24
  37. package/src/svgii/rounding.js +8 -7
  38. package/src/svgii/svg-styles-to-attributes-const.js +1 -0
  39. package/src/svgii/svg_cleanup.js +467 -421
  40. package/src/svgii/svg_cleanup_convertPathLength.js +32 -0
  41. package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
  42. package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
  43. package/src/svgii/svg_cleanup_remove_els_and_atts.js +72 -0
  44. package/src/svgii/svg_cleanup_ungroup.js +36 -0
  45. package/src/svgii/svg_el_parse_style_props.js +76 -28
  46. package/src/svgii/svg_getElementLength.js +67 -0
  47. package/tests/testSVG_shape.js +59 -0
  48. package/tests/testSVG_transform.js +61 -0
@@ -4,22 +4,24 @@
4
4
  * d attribute string
5
5
  */
6
6
 
7
- export function pathDataToD(pathData, optimize = 0) {
8
-
9
- optimize = parseFloat(optimize)
10
-
11
-
7
+ export function pathDataToD(pathData, mode = 0) {
8
+
9
+ mode = parseFloat(mode)
10
+ /*
11
+ 0 = max minification
12
+ 0.5 = safe
13
+ 1 = verbose
14
+ 2 = beautify
15
+ */
12
16
  let len = pathData.length;
13
- let beautify = optimize > 1;
14
- let minify = beautify || optimize ? false : true;
15
17
 
16
-
17
- let d = '';
18
18
  let valsString = pathData[0].values.join(" ");
19
- let separator_command = beautify ? `\n` : (minify ? '' : ' ');
20
- let separator_type = !minify ? ' ' : '';
19
+ let separator_command = mode > 1 ? `\n` :
20
+ ((mode < 1) ? '' : ' ');
21
+ let separator_type = mode > 0.5 ? ' ' : '';
21
22
 
22
- d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
23
+ // 1st command
24
+ let d = `${pathData[0].type}${separator_type}${valsString}${separator_command}`;
23
25
 
24
26
 
25
27
  for (let i = 1; i < len; i++) {
@@ -29,7 +31,7 @@ export function pathDataToD(pathData, optimize = 0) {
29
31
  valsString = '';
30
32
 
31
33
  // Minify Arc commands (A/a) – actually sucks!
32
- if (minify && (type === 'A' || type === 'a')) {
34
+ if (!mode && (type === 'A' || type === 'a')) {
33
35
  values = [
34
36
  values[0], values[1], values[2],
35
37
  `${values[3]}${values[4]}${values[5]}`,
@@ -38,16 +40,15 @@ export function pathDataToD(pathData, optimize = 0) {
38
40
  }
39
41
 
40
42
  // Omit type for repeated commands
41
- type = (minify && com0.type === com.type && com.type.toLowerCase() !== 'm')
43
+ type = ((mode < 1) && com0.type === com.type && com.type.toLowerCase() !== 'm')
42
44
  ? " "
43
- : (minify && com0.type === "M" && com.type === "L"
45
+ : ((mode < 1) && com0.type === "M" && com.type === "L"
44
46
  ? " "
45
47
  : com.type);
46
48
 
47
49
 
48
50
  // concatenate subsequent floating point values
49
- if (minify) {
50
-
51
+ if (!mode) {
51
52
 
52
53
  let prevWasFloat = false;
53
54
 
@@ -67,71 +68,32 @@ export function pathDataToD(pathData, optimize = 0) {
67
68
  if (v > 0 && !(prevWasFloat && isSmallFloat)) {
68
69
  valsString += ' ';
69
70
  }
70
- //console.log(isSmallFloat, prevWasFloat, valStr);
71
71
 
72
72
  valsString += valStr
73
73
  prevWasFloat = isSmallFloat;
74
74
  }
75
75
 
76
- //console.log('minify', valsString);
77
- d += `${type}${separator_type}${valsString}${separator_command}`;
78
-
79
76
  }
80
77
  // regular non-minified output
81
78
  else {
82
- d += `${type}${separator_type}${values.join(' ')}${separator_command}`;
79
+ valsString = values.join(' ')
83
80
  }
81
+
82
+ if(i===len-1) separator_command=''
83
+ d += `${type}${separator_type}${valsString}${separator_command}`;
84
84
  }
85
85
 
86
- if (minify) {
86
+
87
+ if (mode < 1) {
87
88
  d = d
88
89
  .replace(/[A-Za-z]0(?=\.)/g, m => m[0])
89
90
  .replace(/ 0\./g, " .") // Space before small decimals
90
91
  .replace(/ -/g, "-") // Remove space before negatives
91
92
  .replace(/-0\./g, "-.") // Remove leading zero from negative decimals
92
- .replace(/Z/g, "z"); // Convert uppercase 'Z' to lowercase
93
- }
94
-
95
- return d;
96
- }
97
-
98
-
99
- export function pathDataToD_0(pathData, decimals = -1, minify = false) {
100
- // implicit l command
101
- if (pathData[1].type === "l" && minify) {
102
- pathData[0].type = "m";
103
- }
104
- let d = `${pathData[0].type}${pathData[0].values.join(" ")}`;
105
-
106
- for (let i = 1; i < pathData.length; i++) {
107
- let com0 = pathData[i - 1];
108
- let com = pathData[i];
109
-
110
- let type = (com0.type === com.type && minify) ?
111
- " " : (
112
- (com0.type === "m" && com.type === "l") ||
113
- (com0.type === "M" && com.type === "l") ||
114
- (com0.type === "M" && com.type === "L")
115
- ) && minify ?
116
- " " : com.type;
117
-
118
- // round
119
- if (com.values.length && decimals > -1) {
120
- com.values = com.values.map(val => { return +val.toFixed(decimals) })
121
- }
122
- d += `${type}${com.values.join(" ")}`;
93
+ .replace(/Z/g, "z") // Convert uppercase 'Z' to lowercase
123
94
  }
124
95
 
125
-
126
- if (minify) {
127
- d = d
128
- .replaceAll(" 0.", " .")
129
- .replaceAll(" -", "-")
130
- .replaceAll("-0.", "-.")
131
- .replace(/\s+([mlcsqtahvz])/gi, "$1")
132
- .replaceAll("Z", "z");
133
- }
96
+ //console.log(`"${d}"`);
134
97
 
135
98
  return d;
136
99
  }
137
-
@@ -21,7 +21,7 @@ import { renderPoint } from "./visualize";
21
21
  * creates precise polygon approximation from pathdata
22
22
  * converts arc to cubis
23
23
  */
24
- export function pathDataToPolygon(pathData, {
24
+ export function pathDataToPolygonOpt(pathData, {
25
25
  precisionPoly = 1,
26
26
  autoAccuracy=false,
27
27
  polyFormat='points',
@@ -105,12 +105,12 @@ simplifyRDP=1,
105
105
 
106
106
  // simplify polygon
107
107
  if(simplifyRD>0){
108
- pts2 = simplifyPolyRD(pts2, {quality:simplifyRD+'px'})
108
+ pts2 = simplifyPolyRD(pts2, {quality:simplifyRD})
109
109
  }
110
110
 
111
111
 
112
112
  if(simplifyRDP>0){
113
- pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP+'px'})
113
+ pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP})
114
114
  }
115
115
 
116
116
 
@@ -120,7 +120,6 @@ simplifyRDP=1,
120
120
 
121
121
  if(autoAccuracy){
122
122
  decimals = detectAccuracyPoly(pts)
123
- //console.log('decimals', decimals);
124
123
  }
125
124
 
126
125
  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 } })
@@ -9,6 +9,67 @@ import { pathDataToD } from "./pathData_stringify";
9
9
  import { renderPath, renderPoint, renderPoly } from "./visualize";
10
10
 
11
11
 
12
+ export function getPolyCentroid(pts) {
13
+
14
+ let l = pts.length;
15
+ let x = 0, y = 0;
16
+ for (let i = 0; l && i < l; i++) {
17
+ let pt = pts[i];
18
+ x += pt.x
19
+ y += pt.y
20
+ }
21
+
22
+ let centroid = {x: x/l, y:y/l}
23
+ return centroid
24
+ //console.log(centroid);
25
+
26
+ }
27
+
28
+ export function getPolyCentroidWeighted(points) {
29
+ if (!points || points.length === 0) return null;
30
+
31
+ let totalWeight = 0;
32
+ let sumX = 0;
33
+ let sumY = 0;
34
+
35
+ for (const point of points) {
36
+ let weight = point.weight || 1; // default weight = 1
37
+ sumX += point.x * weight;
38
+ sumY += point.y * weight;
39
+ totalWeight += weight;
40
+ }
41
+
42
+ if (totalWeight === 0) return null;
43
+
44
+ return {
45
+ x: sumX / totalWeight,
46
+ y: sumY / totalWeight
47
+ };
48
+ }
49
+
50
+
51
+
52
+ export function detectRegularPolygon(pts, centroid={x:0, y:0}) {
53
+ let rSq = getSquareDistance(pts[0], centroid);
54
+ let isRegular = true;
55
+
56
+ for (let i = 1, l = pts.length; i < l; i++) {
57
+ let pt1 = pts[i];
58
+ let dist = getSquareDistance(pt1, centroid);
59
+
60
+ let diff = Math.abs(rSq-dist);
61
+ let diffRel = diff/rSq
62
+ //console.log('diffRel', diffRel);
63
+
64
+ if (diffRel > 0.05) {
65
+ return false;
66
+ }
67
+
68
+
69
+ }
70
+ return isRegular;
71
+ }
72
+
12
73
 
13
74
  export function analyzePoly(pts, {
14
75
  x = 0,
@@ -64,8 +64,17 @@ export function polyPtsToArray(pts) {
64
64
  // convert flat point value array to point object array
65
65
  export function toPointArray(pts) {
66
66
  let ptArr = [];
67
- for (let i = 1, l = pts.length; i < l; i += 2) {
68
- ptArr.push({ x: pts[i - 1], y: pts[i] });
67
+
68
+ if(pts[0].length===2){
69
+ for (let i = 0, l = pts.length; i < l; i ++) {
70
+ let pt = pts[i]
71
+ ptArr.push({ x: pt[0], y:pt[1] });
72
+ }
73
+
74
+ }else{
75
+ for (let i = 1, l = pts.length; i < l; i += 2) {
76
+ ptArr.push({ x: pts[i - 1], y: pts[i] });
77
+ }
69
78
  }
70
79
  return ptArr;
71
80
  };
@@ -5,15 +5,19 @@
5
5
  * https://francoisromain.medium.com/smooth-a-svg-path-with-cubic-bezier-curves-e37b49d46c74
6
6
  */
7
7
 
8
- import { checkLineIntersection, getDistManhattan, interpolate, mirrorCpts } from "./geometry";
8
+ import { checkLineIntersection, getAngle, getDistance, getDistManhattan, getPathDataVertices, getPointOnEllipse, getSquareDistance, interpolate, mirrorCpts, reducePoints, rotatePoint } from "./geometry";
9
9
  import { getPolyBBox } from "./geometry_bbox";
10
10
  import { renderPath, renderPoint, renderPoly } from "./visualize";
11
11
  import { simplifyPolyRDP } from "../simplify_poly_RDP";
12
12
  import { pathDataFromPoly } from "./pathData_fromPoly";
13
13
  import { getPolyChunks } from "./poly_analyze_get_chunks";
14
- import { analyzePoly, isClosedPolygon } from "./poly_analyze";
14
+ import { analyzePoly, detectRegularPolygon, getPolyCentroid, getPolyCentroidWeighted, isClosedPolygon } from "./poly_analyze";
15
15
  import { fitCurveSchneider } from "../poly-fit-curve-schneider";
16
16
  import { simplifyPolyRD } from "../simplify_poly_radial_distance";
17
+ import { simplifyRC } from "../simplify_poly_RC";
18
+ import { getPolygonArea } from "./geometry_area";
19
+ import { pathDataToD } from "./pathData_stringify";
20
+ import { fixIntersectingCpts } from "../pathData_simplify_harmonize_cpts";
17
21
 
18
22
 
19
23
 
@@ -34,34 +38,89 @@ export function simplifyPolygonToPathData(pts, {
34
38
  } = {}) {
35
39
 
36
40
 
41
+ let polyPath = [];
42
+ let l = pts.length;
43
+ let M = pts[0]
44
+ let Z = pts[l - 1]
45
+
46
+
47
+ // triangle
48
+ if (pts.length === 3) {
49
+
50
+ let pM1 = interpolate(M, pts[1], 0.5)
51
+ let pM2 = interpolate(pts[1], Z, 0.5)
52
+ let pM3 = interpolate(Z, pts[0], 0.5)
53
+
54
+ /*
55
+ console.log('triangle');
56
+ renderPoint(markers, M)
57
+ renderPoint(markers, pM1)
58
+ renderPoint(markers, pM2)
59
+ renderPoint(markers, pM3)
60
+ */
61
+
62
+ if (closed) {
63
+ let t = 0.6666
64
+ let cp1_1 = interpolate(pM1, pts[1], t)
65
+ let cp2_1 = interpolate(pM2, pts[1], t)
66
+ let cp1_2 = interpolate(pM2, Z, t)
67
+ let cp2_2 = interpolate(pM3, Z, t)
68
+ let cp1_3 = interpolate(pM3, M, t)
69
+ let cp2_3 = interpolate(pM1, M, t)
70
+
71
+ polyPath = [
72
+ { type: 'M', values: [pM1.x, pM1.y] },
73
+ { type: 'C', values: [cp1_1.x, cp1_1.y, cp2_1.x, cp2_1.y, pM2.x, pM2.y] },
74
+ { type: 'C', values: [cp1_2.x, cp1_2.y, cp2_2.x, cp2_2.y, pM3.x, pM3.y] },
75
+ { type: 'C', values: [cp1_3.x, cp1_3.y, cp2_3.x, cp2_3.y, pM1.x, pM1.y] },
76
+ { type: 'Z', values: [] },
77
+ ]
37
78
 
38
- /*
39
- // denoise via RDP
40
- if (denoise && denoise !== 1) {
41
- pts = simplifyPolyRDP(pts, {
42
- width,
43
- height,
44
- quality: denoise,
45
- manhattan,
46
- absolute
47
- })
79
+ } else {
80
+ polyPath = [
81
+ //{ type: 'M', values: [pM1.x, pM1.y] },
82
+ { type: 'M', values: [M.x, M.y] },
83
+ { type: 'C', values: [pts[1].x, pts[1].y, pts[1].x, pts[1].y, Z.x, Z.y] },
84
+ ]
85
+ }
86
+ return polyPath;
48
87
  }
49
- */
50
88
 
51
89
 
52
- /*
53
- // simplify polygon
54
- if (simplifyRD != 1) {
55
- pts = simplifyPolyRD(pts, { quality: simplifyRD+'px' })
56
- }
90
+
91
+ // remove colinear
92
+ //pts = simplifyRC(pts)
93
+
94
+ /**
95
+ * detect regular polygon
96
+ * curved path is a circle
97
+ */
98
+ let centroid = getPolyCentroid(simplifyRC(pts))
99
+ let isRegularPolygon = detectRegularPolygon(pts, centroid)
57
100
 
58
101
 
59
- if (simplifyRDP != 1) {
60
- pts = simplifyPolyRDP(pts, { quality: simplifyRDP+'px' })
102
+ if (isRegularPolygon) {
103
+ //renderPoint(markers, centroid)
104
+ //let r = getDistance(centroid, pts[0])
105
+ let ptAd = rotatePoint(pts[0], centroid.x, centroid.y, Math.PI)
106
+ let sweep = getPolygonArea(pts) > 0 ? 1 : 0;
107
+
108
+ polyPath = [
109
+ { type: 'M', values: [pts[0].x, pts[0].y] },
110
+ { type: 'A', values: [1, 1, 0, 0, sweep, ptAd.x, ptAd.y] },
111
+ { type: 'A', values: [1, 1, 0, 0, sweep, pts[0].x, pts[0].y] }
112
+ ]
113
+
114
+ if (closed) {
115
+ polyPath.push({ type: 'Z', values: [] })
116
+ }
117
+ return polyPath;
61
118
  }
62
- */
63
119
 
64
120
 
121
+ // remove colinear
122
+ //pts = simplifyRC(pts)
123
+
65
124
  // get topology of poly
66
125
  let polyAnalyzed = !keepExtremes && !keepCorners ? pts : analyzePoly(pts, {
67
126
  debug: false
@@ -70,7 +129,6 @@ export function simplifyPolygonToPathData(pts, {
70
129
  })
71
130
 
72
131
  //console.log(polyAnalyzed, polyAnalyzed2);
73
- //return
74
132
 
75
133
  // split into segment chunks
76
134
  let chunks = !keepExtremes && !keepCorners ? [polyAnalyzed] : getPolyChunks(polyAnalyzed, { keepCorners, keepExtremes, keepInflections });
@@ -81,12 +139,15 @@ export function simplifyPolygonToPathData(pts, {
81
139
 
82
140
  //threshold = 2
83
141
 
84
- let polyPath = simplifyPolyChunks(chunks, {
142
+ polyPath = simplifyPolyChunks(chunks, {
85
143
  closed,
86
144
  tolerance: threshold,
87
- keepCorners
145
+ keepCorners,
146
+ keepExtremes: true,
88
147
  });
89
148
 
149
+ polyPath = fixIntersectingCpts(polyPath);
150
+
90
151
  return polyPath;
91
152
  }
92
153
 
@@ -72,7 +72,7 @@ export function detectAccuracy(pathData) {
72
72
  let dim_min = dims.sort()
73
73
  //console.log('dim_min', dim_min);
74
74
 
75
- let sliceIdx = Math.ceil(dim_min.length / 8);
75
+ let sliceIdx = Math.ceil(dim_min.length / 6);
76
76
  dim_min = dim_min.slice(0, sliceIdx);
77
77
  let minVal = dim_min.reduce((a, b) => a + b, 0) / sliceIdx;
78
78
 
@@ -88,6 +88,7 @@ export function detectAccuracy(pathData) {
88
88
 
89
89
 
90
90
  export function roundTo(num = 0, decimals = 3) {
91
+ if(decimals<=-1) return num;
91
92
  if (!decimals) return Math.round(num);
92
93
  let factor = 10 ** decimals;
93
94
  return Math.round(num * factor) / factor;
@@ -98,20 +99,20 @@ export function roundTo(num = 0, decimals = 3) {
98
99
  * floating point accuracy
99
100
  * based on numeric value
100
101
  */
101
- export function autoRound(val, integerThresh = 10){
102
+ export function autoRound(val, integerThresh = 50){
102
103
  let decimals=8;
103
-
104
- //console.log('val', val);
105
104
 
106
- if(val>integerThresh){
105
+ if(val>integerThresh*2){
107
106
  decimals=0
108
107
  }
109
- else if(val>5){
108
+ else if(val>integerThresh){
110
109
  decimals=1
111
110
  }else{
112
- decimals=Math.ceil(integerThresh/val).toString().length
111
+ decimals=Math.ceil(500/val).toString().length
112
+ //console.log('decimals small', val, decimals);
113
113
  }
114
114
 
115
+ //console.log(val, decimals);
115
116
  let factor = 10 ** decimals;
116
117
  return Math.round(val * factor) / factor;
117
118
  }
@@ -19,6 +19,7 @@ export const transHorizontal = ['scaleX', 'translateX', 'skewX'];
19
19
  export const transVertical = ['scaleY', 'translateY', 'skewY'];
20
20
 
21
21
  export const colorProps = ['fill', 'stroke', 'stop-color'];
22
+ export const geometryProps = ['d', 'points', 'cx', 'cy', 'x1', 'x2', 'y1', 'y2', 'width', 'height', 'r', 'rx', 'ry', 'x', 'y'];
22
23
 
23
24
 
24
25
  export const geometryEls = [