svg-path-simplify 0.4.5 → 0.4.7

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 (42) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +24 -14
  3. package/dist/svg-path-simplify.esm.js +1620 -1216
  4. package/dist/svg-path-simplify.esm.min.js +2 -2
  5. package/dist/svg-path-simplify.js +1620 -1215
  6. package/dist/svg-path-simplify.min.js +2 -2
  7. package/dist/svg-path-simplify.pathdata.esm.js +298 -665
  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 +11 -5
  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 +14 -34
  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 +259 -60
  20. package/src/pathSimplify-presets.js +1 -0
  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/svgii/geometry.js +130 -27
  26. package/src/svgii/geometry_bbox.js +1 -1
  27. package/src/svgii/pathData_analyze.js +6 -28
  28. package/src/svgii/pathData_convert.js +70 -15
  29. package/src/svgii/pathData_remove_collinear.js +32 -36
  30. package/src/svgii/pathData_simplify_refineExtremes.js +1 -3
  31. package/src/svgii/pathData_stringify.js +132 -6
  32. package/src/svgii/pathData_toPolygon.js +33 -23
  33. package/src/svgii/poly_analyze.js +272 -125
  34. package/src/svgii/poly_analyze_cleanup.js +311 -0
  35. package/src/svgii/poly_analyze_getTangents.js +125 -0
  36. package/src/svgii/poly_analyze_get_chunks.js +3 -1
  37. package/src/svgii/poly_normalize.js +12 -0
  38. package/src/svgii/poly_to_pathdata.js +477 -8
  39. package/src/svgii/rounding.js +29 -39
  40. package/src/svgii/svg_cleanup.js +42 -54
  41. package/src/svgii/svg_cleanup_normalize_transforms.js +1 -1
  42. package/src/svgii/visualize.js +33 -2
@@ -18,66 +18,59 @@ export function pathDataRemoveColinear(pathData, {
18
18
  let isClosed = pathData[pathData.length - 1].type.toLowerCase() === 'z'
19
19
 
20
20
  for (let c = 1, l = pathData.length; c < l; c++) {
21
- //let comPrev = pathData[c - 1];
22
21
  let com = pathData[c];
22
+ let { type, values } = com;
23
23
  let comN = pathData[c + 1] || pathData[l - 1];
24
- //let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
24
+ let valuesN = comN.values;
25
25
  let p1 = comN.type.toLowerCase() === 'z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
26
26
 
27
27
 
28
- let { type, values } = com;
29
28
  let valsL = values.slice(-2)
30
29
  p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
31
30
 
31
+ let nextBezier = type!==comN.type && (comN.type==='C' || comN.type==='Q');
32
+
32
33
 
33
34
  let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity
34
35
  let distSquare = getSquareDistance(p0, p1)
35
36
  let distMax = distSquare ? distSquare / 333 * tolerance : 0
36
37
 
37
38
  let isFlat = area < distMax;
38
- //let isFlat = false;
39
39
  let isFlatBez = false;
40
+ let cpts = [];
40
41
 
41
42
 
42
- /*
43
- // flatness by cross product
44
- let dx0 = Math.abs(p1.x - p0.x)
45
- let dy0 = Math.abs(p1.y - p0.y)
46
-
47
- let dx1 = Math.abs(p.x - p0.x)
48
- let dy1 = Math.abs(p.y - p0.y)
49
-
50
- let dx2 = Math.abs(p1.x - p.x)
51
- let dy2 = Math.abs(p1.y - p.y)
43
+ //if (comN.type === 'C') isFlat = false;
44
+ //if (!flatBezierToLinetos && type === 'C') isFlat = false;
52
45
 
53
- // zero length segments are flat
54
- let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
55
- if (isZeroLength) isFlat = true;
46
+ /**
47
+ * type change
48
+ * check flatness
49
+ */
50
+ if (nextBezier) {
51
+ cpts = comN.type === 'C' ?
52
+ [{ x: valuesN[0], y: valuesN[1] }, { x: valuesN[2], y: valuesN[3] }] :
53
+ (comN.type === 'Q' ? [{ x: valuesN[0], y: valuesN[1] }] : []);
56
54
 
57
- // check cross products for colinearity
58
- if (!isFlat) {
55
+ //isFlatBez = commandIsFlat([p0, ...cpts, p], { tolerance });
59
56
 
60
- let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
61
- //let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
62
- //let crossDiff = Math.abs(cross0-cross1)
63
- //let cross = Math.max(cross0, cross1)
64
- let thresh = (dx0 + dy0) * 0.1
57
+ isFlat = commandIsFlat([p0, ...cpts, p], { tolerance });
58
+ //if(isFlat) continue;
65
59
 
66
- //!cross0 ||
67
- if ( cross0 < thresh) {
68
- //renderPoint(markers, p)
69
- isFlat = true
60
+ /*
61
+ //console.log('isFlatBez', isFlatBez);
62
+ if(!isFlatBez){
63
+ pathDataN.push(com)
64
+ continue
70
65
  }
71
- }
72
- */
73
-
66
+ */
74
67
 
75
- if (!flatBezierToLinetos && type === 'C') isFlat = false;
68
+ }
76
69
 
77
70
  // convert flat beziers to linetos
78
71
  if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
79
72
 
80
- let cpts = type === 'C' ?
73
+ cpts = type === 'C' ?
81
74
  [{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
82
75
  (type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
83
76
 
@@ -92,10 +85,13 @@ export function pathDataRemoveColinear(pathData, {
92
85
  }
93
86
 
94
87
 
95
- // colinear – exclude arcs (as always =) as semicircles won't have an area
96
- //&& comN.type==='L'
97
- if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
88
+ /**
89
+ * colinear = simplification success
90
+ * exclude arcs (as always =)
91
+ * as semicircles won't have an area
92
+ */
98
93
 
94
+ if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
99
95
  continue;
100
96
  }
101
97
 
@@ -1,5 +1,5 @@
1
1
  import { findSplitT, getExtrapolatedCommand } from "../pathData_simplify_cubic";
2
- import { getCombinedByDominant } from "../pathData_simplify_cubic_extrapolate";
2
+ import { getCombinedByDominant, getCombinedByDominant_back } from "../pathData_simplify_cubic_extrapolate";
3
3
  import { bezierhasExtreme, checkLineIntersection, getDistAv, getDistManhattan, getSquareDistance, interpolate } from "./geometry";
4
4
  import { getPathArea, getPolygonArea } from "./geometry_area";
5
5
  import { getPathDataBBox } from "./geometry_bbox";
@@ -58,9 +58,7 @@ export function refineAdjacentExtremes(pathData, {
58
58
  if (comEx.length === 1) {
59
59
 
60
60
  comEx = comEx[0]
61
-
62
61
  pathData[i + 1] = null;
63
-
64
62
  pathData[i + 2].values = [comEx.cp1.x, comEx.cp1.y, comEx.cp2.x, comEx.cp2.y, comEx.p.x, comEx.p.y]
65
63
  pathData[i + 2].cp1 = comEx.cp1
66
64
  pathData[i + 2].cp2 = comEx.cp2
@@ -4,7 +4,129 @@
4
4
  * d attribute string
5
5
  */
6
6
 
7
- export function pathDataToD(pathData, mode = 0) {
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
+ */
16
+
17
+ let len = pathData.length;
18
+ let d = ''
19
+
20
+ // group same types
21
+ let pathDataGrouped = mode >0.5 ? JSON.parse(JSON.stringify(pathData)) : [];
22
+ let typePrev = 'M'
23
+
24
+ if (mode < 1) {
25
+ pathDataGrouped = [pathData[0]];
26
+ //pathDataGrouped = [{type:pathData[0].type, values:[...pathData[0].values]}];
27
+ let idx = 0;
28
+
29
+ for (let i = 1; i < len; i++) {
30
+ let com = pathData[i];
31
+ let { type } = com;
32
+ // decouple from object
33
+ let values = [...com.values]
34
+
35
+ // new type
36
+ if (type !== typePrev) {
37
+ pathDataGrouped.push({type, values})
38
+ idx++
39
+ } else {
40
+ pathDataGrouped[idx].values.push(...values)
41
+ }
42
+
43
+ // update type
44
+ typePrev = type
45
+ }
46
+ }
47
+
48
+ // stringify grouped
49
+ len = pathDataGrouped.length;
50
+ let separator_type = mode < 1 ? '' : ' ';
51
+ let separator_command = mode < 1 ? '' : (mode === 1 ? ' ' : `\n`);
52
+
53
+ typePrev = 'M'
54
+
55
+ for (let i = 0; i < len; i++) {
56
+ let com = pathDataGrouped[i];
57
+ let { type, values } = com;
58
+
59
+ // we're always starting a path with absolute M!
60
+ let omitType = mode < 1 && ((typePrev === 'M' && type === 'L') || (typePrev === 'm' && type === 'l'))
61
+
62
+ // add type
63
+ if (!omitType) d += type + separator_type;
64
+
65
+ // add values
66
+ let wasSmallFloat = false;
67
+ let separatorVal = ' ';
68
+
69
+ for (let v = 0, vlen = values.length; vlen && v < vlen; v++) {
70
+ let val = values[v];
71
+ let valAbs = Math.abs(val);
72
+ let valStr = val.toString();
73
+ let isNegative = val < 0;
74
+ let sign = isNegative ? '-' : ''
75
+ let isSmallFloat = mode > 0.5 ? false : (val && valAbs < 1);
76
+ let idxSub = isSmallFloat ? (isNegative ? 2 : 1) : 0
77
+
78
+ // we don't need whitespace for first value
79
+ separatorVal = v === 0 || isNegative ? '' : ' '
80
+
81
+ if (mode < 1) {
82
+ // omit leading zero
83
+ if (isSmallFloat) valStr = sign + valStr.substring(idxSub)
84
+
85
+ // omit whitespace for subsequent small floats
86
+ separatorVal = (v === 0 && !omitType) || (wasSmallFloat && isSmallFloat) ?
87
+ (!mode ? '' : (isNegative ? '' : ' '))
88
+ : (isNegative ? '' : ' ');
89
+
90
+ }
91
+
92
+ // omit separator between large Arc sweep and final x in minify mode
93
+ if (!mode && (type === 'a' || type === 'A')) {
94
+ let pos = (v % 7)
95
+ if (pos > 3 && pos < 6) separatorVal = ''
96
+ }
97
+
98
+ d += `${separatorVal}${valStr}`
99
+ wasSmallFloat = isSmallFloat;
100
+
101
+ }
102
+
103
+ // add command separator
104
+ if (mode) d += separator_command;
105
+
106
+ // update previous type
107
+ typePrev = type
108
+
109
+ }
110
+
111
+ //console.log('d', d, mode);
112
+ //console.log('pathDataGrouped', pathDataGrouped);
113
+ //console.log(pathData);
114
+
115
+ return d;
116
+ }
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+ export function pathDataToD__(pathData, mode = 0) {
8
130
 
9
131
  mode = parseFloat(mode)
10
132
  /*
@@ -79,7 +201,7 @@ export function pathDataToD(pathData, mode = 0) {
79
201
  valsString = values.join(' ')
80
202
  }
81
203
 
82
- if(i===len-1) separator_command=''
204
+ if (i === len - 1) separator_command = ''
83
205
  d += `${type}${separator_type}${valsString}${separator_command}`;
84
206
  }
85
207
 
@@ -87,10 +209,14 @@ export function pathDataToD(pathData, mode = 0) {
87
209
  if (mode < 1) {
88
210
  d = d
89
211
  .replace(/[A-Za-z]0(?=\.)/g, m => m[0])
90
- .replace(/ 0\./g, " .") // Space before small decimals
91
- .replace(/ -/g, "-") // Remove space before negatives
92
- .replace(/-0\./g, "-.") // Remove leading zero from negative decimals
93
- .replace(/Z/g, "z") // Convert uppercase 'Z' to lowercase
212
+ //Space before small decimals
213
+ .replace(/ 0\./g, " .")
214
+ // Remove space before negatives
215
+ .replace(/ -/g, "-")
216
+ // Remove leading zero from negative decimals
217
+ .replace(/-0\./g, "-.")
218
+ // Convert uppercase 'Z' to lowercase
219
+ .replace(/Z/g, "z")
94
220
  }
95
221
 
96
222
  //console.log(`"${d}"`);
@@ -4,8 +4,8 @@ import { simplifyPolyRD } from "../simplify_poly_radial_distance";
4
4
  import { getDistAv, getDistManhattan, getSquareDistance, getTatAngles, interpolate, pointAtT } from "./geometry";
5
5
  import { getBezierArea, getPolygonArea } from "./geometry_area";
6
6
  import { getPolyBBox } from "./geometry_bbox";
7
- import { addDimensionData, analyzePathData } from "./pathData_analyze";
8
- import { arcToBezier } from "./pathData_convert";
7
+ import { addDimensionData, analyzePathData, getPathDataVerbose } from "./pathData_analyze";
8
+ import { arcToBezier, convertPathData, pathDataToVerbose } from "./pathData_convert";
9
9
  import { pathDataFromPoly } from "./pathData_fromPoly";
10
10
  import { addExtremePoints } from "./pathData_split";
11
11
  import { pathDataToD } from "./pathData_stringify";
@@ -23,15 +23,24 @@ import { renderPoint } from "./visualize";
23
23
  */
24
24
  export function pathDataToPolygonOpt(pathData, {
25
25
  precisionPoly = 1,
26
- autoAccuracy=false,
27
- polyFormat='points',
28
- decimals=-1,
29
- simplifyRD=1,
30
- simplifyRDP=1,
26
+ autoAccuracy = false,
27
+ polyFormat = 'object',
28
+ decimals = -1,
29
+ simplifyRD = 1,
30
+ simplifyRDP = 1,
31
31
  } = {}) {
32
32
 
33
33
  //console.log(pathData);
34
34
 
35
+ pathData = convertPathData(pathData, {toAbsolute:true, toLonghands:true, arcToCubic:true});
36
+ pathData = addExtremePoints(pathData);
37
+
38
+ //let d2 = pathDataToD(pathData)
39
+ //console.log(d2);
40
+
41
+
42
+ pathData = getPathDataVerbose(pathData);
43
+
35
44
  let l = pathData.length;
36
45
  let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
37
46
  let p0 = M
@@ -59,11 +68,10 @@ simplifyRDP=1,
59
68
  pts.push(p)
60
69
  }
61
70
 
62
-
63
71
  let pts2 = [pts[0]]
64
72
 
65
73
  // adjustments for very small or large paths
66
- dims = dims.filter(Boolean).sort()
74
+ dims = dims.filter(Boolean).sort((a,b)=>a-b)
67
75
  let dimMax = dims[dims.length - 1]
68
76
 
69
77
  let scale = dimMax > 2 && dimMax < 25 ? 1 : (20 / dimMax);
@@ -104,42 +112,44 @@ simplifyRDP=1,
104
112
 
105
113
 
106
114
  // simplify polygon
107
- if(simplifyRD>0){
108
- pts2 = simplifyPolyRD(pts2, {quality:simplifyRD})
115
+ if (simplifyRD > 0) {
116
+ pts2 = simplifyPolyRD(pts2, { quality: simplifyRD })
109
117
  }
110
118
 
111
119
 
112
- if(simplifyRDP>0){
113
- pts2 = simplifyPolyRDP(pts2, {quality:simplifyRDP})
120
+ if (simplifyRDP > 0) {
121
+ pts2 = simplifyPolyRDP(pts2, { quality: simplifyRDP })
114
122
  }
115
123
 
116
124
 
117
125
 
118
- pathDataPoly = pathDataFromPoly(pts2)
119
- pathData = pathDataPoly
120
-
121
- if(autoAccuracy){
126
+
127
+ if (autoAccuracy) {
122
128
  decimals = detectAccuracyPoly(pts)
123
129
  }
124
130
 
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 } })
131
+ 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 } })
126
132
 
127
- if(polyFormat==='array'){
133
+ pathDataPoly = pathDataFromPoly(poly)
134
+ pathData = pathDataPoly
135
+
136
+
137
+
138
+ if (polyFormat === 'array') {
128
139
  poly = poly.map(pt => { return [pt.x, pt.y] })
129
140
  }
130
- else if(polyFormat==='string'){
141
+ else if (polyFormat === 'string') {
131
142
  poly = poly.map(pt => { return [pt.x, pt.y].join(',') }).flat().join(' ')
132
143
  }
133
144
 
134
- //console.log(pathData);
145
+ let d= pathDataToD(pathData)
135
146
 
136
- return { pathData, poly }
147
+ return { pathData, poly, d }
137
148
 
138
149
  }
139
150
 
140
151
 
141
152
 
142
-
143
153
  /**
144
154
  * creates precise polygon
145
155
  * from command end points