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
@@ -17,54 +17,10 @@ import { pathDataToD } from './pathData_stringify';
17
17
  import { roundPathData } from './rounding';
18
18
  import { renderPoint } from './visualize';
19
19
 
20
-
21
- export function parsePathDataNormalized(d,
22
- {
23
- // necessary for most calculations
24
- toAbsolute = true,
25
- toLonghands = true,
26
-
27
- // not necessary unless you need cubics only
28
- quadraticToCubic = false,
29
-
30
- // mostly a fallback if arc calculations fail
31
- arcToCubic = false,
32
- // arc to cubic precision - adds more segments for better precision
33
- arcAccuracy = 4,
34
- } = {}
35
- ) {
36
-
37
- // is already array
38
- let isArray = Array.isArray(d);
39
-
40
- // normalize native pathData to regular array
41
- let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function'
42
- /*
43
- if (hasConstructor) {
44
- d = d.map(com => { return { type: com.type, values: com.values } })
45
- console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
46
- }
47
- */
48
-
49
- let pathDataObj = isArray ? d : parsePathDataString(d);
50
-
51
- let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
52
- let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
53
-
54
- //console.log('???quadraticToCubic', quadraticToCubic);
55
-
56
- // normalize
57
- pathData = normalizePathData(pathData,
58
- {
59
- toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
60
- hasRelatives, hasShorthands, hasQuadratics, hasArcs
61
- },
62
- )
63
-
64
- return pathData;
65
- }
66
-
67
-
20
+ /**
21
+ * wrapper function for
22
+ * all path data conversion
23
+ */
68
24
  export function convertPathData(pathData, {
69
25
  toShorthands = true,
70
26
  toLonghands = false,
@@ -80,6 +36,7 @@ export function convertPathData(pathData, {
80
36
  hasShorthands = true,
81
37
  hasQuadratics = true,
82
38
  hasArcs = true,
39
+ isPoly = false,
83
40
  optimizeArcs = true,
84
41
  testTypes = false
85
42
 
@@ -103,7 +60,9 @@ export function convertPathData(pathData, {
103
60
 
104
61
  // some params exclude each other
105
62
  toRelative = toAbsolute ? false : toRelative;
106
- toShorthands = toLonghands ? false : toShorthands
63
+ //toAbsolute = !toRelative ? true : toAbsolute;
64
+ toShorthands = toLonghands ? false : toShorthands;
65
+ //toLonghands = !toShorthands ? true : toLonghands;
107
66
 
108
67
 
109
68
  if (toAbsolute) pathData = pathDataToAbsolute(pathData);
@@ -117,28 +76,31 @@ export function convertPathData(pathData, {
117
76
  //if(decimals>-1 && decimals<2) pathData = roundPathData(pathData, decimals);
118
77
  if (toShorthands) pathData = pathDataToShorthands(pathData);
119
78
 
79
+ //console.log('hasArcs', hasArcs, arcToCubic, pathData);
120
80
  if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData)
121
81
 
122
82
  //console.log(toShorthands, toRelative, decimals);
123
83
  if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
124
84
 
125
- if(toMixed) toRelative = true
85
+ if (toMixed) toRelative = true
126
86
 
127
87
  // pre round - before relative conversion to minimize distortions
128
88
  if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
129
89
 
130
90
  // clone absolute pathdata
131
- if(toMixed){
91
+ if (toMixed) {
132
92
  pathDataAbs = JSON.parse(JSON.stringify(pathData))
133
93
  }
134
94
 
135
95
  if (toRelative) pathData = pathDataToRelative(pathData);
96
+
97
+ // final rounding
136
98
  if (decimals > -1) pathData = roundPathData(pathData, decimals);
137
99
 
138
100
 
139
101
  // choose most compact commands: relative or absolute
140
- if(toMixed){
141
- for(let i=0; i<pathData.length; i++){
102
+ if (toMixed) {
103
+ for (let i = 0; i < pathData.length; i++) {
142
104
  let com = pathData[i]
143
105
  let comA = pathDataAbs[i]
144
106
  // compare Lengths
@@ -148,7 +110,7 @@ export function convertPathData(pathData, {
148
110
  let lenR = comStr.length;
149
111
  let lenA = comStrA.length;
150
112
 
151
- if(lenA<lenR){
113
+ if (lenA < lenR) {
152
114
  //console.log('absolute is shorter', comStrA, comStr);
153
115
  pathData[i] = pathDataAbs[i]
154
116
  }
@@ -158,6 +120,56 @@ export function convertPathData(pathData, {
158
120
  return pathData
159
121
  }
160
122
 
123
+
124
+
125
+
126
+ export function parsePathDataNormalized(d,
127
+ {
128
+ // necessary for most calculations
129
+ toAbsolute = true,
130
+ toLonghands = true,
131
+
132
+ // not necessary unless you need cubics only
133
+ quadraticToCubic = false,
134
+
135
+ // mostly a fallback if arc calculations fail
136
+ arcToCubic = false,
137
+ // arc to cubic precision - adds more segments for better precision
138
+ arcAccuracy = 4,
139
+ } = {}
140
+ ) {
141
+
142
+ // is already array
143
+ let isArray = Array.isArray(d);
144
+
145
+ // normalize native pathData to regular array
146
+ let hasConstructor = isArray && typeof d[0] === 'object' && typeof d[0].constructor === 'function'
147
+ /*
148
+ if (hasConstructor) {
149
+ d = d.map(com => { return { type: com.type, values: com.values } })
150
+ console.log('hasConstructor', hasConstructor, (typeof d[0].constructor), d);
151
+ }
152
+ */
153
+
154
+ let pathDataObj = isArray ? d : parsePathDataString(d);
155
+
156
+ let { hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true } = pathDataObj;
157
+ let pathData = hasConstructor ? pathDataObj : pathDataObj.pathData;
158
+
159
+ //console.log('???quadraticToCubic', quadraticToCubic);
160
+
161
+ // normalize
162
+ pathData = normalizePathData(pathData,
163
+ {
164
+ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy,
165
+ hasRelatives, hasShorthands, hasQuadratics, hasArcs
166
+ },
167
+ )
168
+
169
+ return pathData;
170
+ }
171
+
172
+
161
173
  /**
162
174
  *
163
175
  * @param {*} pathData
@@ -166,50 +178,100 @@ export function convertPathData(pathData, {
166
178
 
167
179
  export function optimizeArcPathData(pathData = []) {
168
180
 
169
- let remove =[]
181
+ //return pathData
170
182
 
171
- pathData.forEach((com, i) => {
183
+ let remove = []
184
+ let l = pathData.length;
185
+ let pathDataN = [];
186
+
187
+ for (let i = 0; i < l; i++) {
188
+ let com = pathData[i];
172
189
  let { type, values } = com;
173
- if (type === 'A') {
174
- let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
175
- let comPrev = pathData[i - 1]
176
- let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
177
- let M = { x: x0, y: y0 };
178
- let p = { x, y };
179
- //largeArc=true
180
- //let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
181
-
182
- if(rx===0 || ry===0){
183
- pathData[i]= null
184
- remove.push(i)
185
- //console.log('!!!');
186
- }
187
190
 
188
- // rx and ry are large enough
189
- if (rx >= 1 && (x === x0 || y === y0)) {
190
- let diff = Math.abs(rx - ry) / rx;
191
+ if (type !== 'A') {
192
+ pathDataN.push(com)
193
+ continue
194
+ }
191
195
 
192
- // rx~==ry
193
- if (diff < 0.01) {
194
196
 
195
- // test radius against mid point
196
- let pMid = interpolate(M, p, 0.5)
197
- let distM = getDistance(pMid, M)
198
- let rDiff = Math.abs(distM - rx) / rx
197
+ let [rx, ry, largeArc, x, y] = [values[0], values[1], values[3], values[5], values[6]];
198
+ let comPrev = pathData[i - 1]
199
+ let [x0, y0] = [comPrev.values[comPrev.values.length - 2], comPrev.values[comPrev.values.length - 1]];
200
+ let M = { x: x0, y: y0 };
201
+ let p = { x, y };
202
+ //largeArc=true
203
+ //let pMid = {x: Math.abs(x-x0), y:Math.abs(y-y0)}
199
204
 
200
- // half distance between mid and start point should be ~ equal
201
- if(rDiff<0.01){
202
- pathData[i].values[0] = 1;
203
- pathData[i].values[1] = 1;
204
- pathData[i].values[2] = 0;
205
- }
206
- }
205
+ if (rx === 0 || ry === 0) {
206
+ pathData[i] = null
207
+ remove.push(i)
208
+ }
209
+
210
+ // test for elliptic
211
+ let rat = rx / ry
212
+ let error = rx !== ry ? Math.abs(1 - rat) : 0
213
+
214
+ if (error > 0.01) {
215
+ //console.log('is elliptic');
216
+ pathDataN.push(com)
217
+ continue
218
+
219
+ }
220
+
221
+ // xAxis rotation is futile for circular arcs - reset
222
+ com.values[2] = 0
223
+
224
+ /**
225
+ * test semi circles
226
+ * rx and ry are large enough
227
+ */
228
+
229
+
230
+ // 1. horizontal or vertical
231
+ let thresh = getDistManhattan(M, p) * 0.001;
232
+ let diffX = Math.abs(x - x0)
233
+ let diffY = Math.abs(y - y0)
234
+
235
+ let isHorizontal = diffY < thresh
236
+ let isVertical = diffX < thresh
237
+
238
+
239
+ // minify rx and ry
240
+ if (isHorizontal || isVertical) {
241
+
242
+ // check if semi circle
243
+ let needsTrueR = isHorizontal ? rx*1.9 > diffX : ry*1.9 > diffY;
244
+
245
+ // is semicircle we can simplify rx
246
+ if (!needsTrueR) {
247
+ //console.log('needsTrueR', needsTrueR, diffX, rx, diffY, ry);
248
+ rx = rx >= 1 ? 1 : (rx > 0.5 ? 0.5 : rx);
207
249
  }
250
+
251
+ com.values[0] = rx
252
+ com.values[1] = rx
253
+ pathDataN.push(com)
254
+ continue
255
+
256
+
208
257
  }
209
- })
210
258
 
211
- if(remove.length) pathData = pathData.filter(Boolean)
212
- return pathData;
259
+ // 2. get true radius - if rx ~= diameter/distance we have a semicircle
260
+ let r = getDistance(M, p) * 0.5
261
+ error = rx / r
262
+ //console.log('rx', rx, r, error);
263
+
264
+ if (error < 0.5) {
265
+ rx = r >= 1 ? 1 : (r > 0.5 ? 0.5 : r);
266
+ }
267
+
268
+ com.values[0] = rx;
269
+ com.values[1] = rx;
270
+ pathDataN.push(com)
271
+
272
+ }
273
+
274
+ return pathDataN;
213
275
  }
214
276
 
215
277
 
@@ -277,6 +339,48 @@ export function normalizePathData(pathData = [],
277
339
  }
278
340
  */
279
341
 
342
+ export function convertSmallArcsToLinetos(pathData) {
343
+
344
+ let l = pathData.length;
345
+
346
+ // add fist command
347
+ let pathDataN = [pathData[0]]
348
+
349
+ for (let i = 1; i < l; i++) {
350
+ let com = pathData[i];
351
+ let comPrev = pathData[i - 1];
352
+ let comN = pathData[i + 1] || null;
353
+
354
+ if (!comN) {
355
+ pathDataN.push(com);
356
+ break
357
+ }
358
+
359
+ let { type, values, extreme = false, p0, p, dimA = 0 } = com;
360
+ // for short segment detection
361
+ let dimAN = comN.dimA;
362
+ let dimA0 = comPrev.dimA + dimA + dimAN;
363
+ let thresh = 0.05
364
+ let isShort = dimA < dimA0 * thresh;
365
+ //let isShortN = dimAN < dimA0 * thresh;
366
+
367
+ if (type === 'A' && isShort && values[0] < 1 && values[1] < 1) {
368
+
369
+ //renderPoint(markers, p0, 'green', '0.1%')
370
+ //renderPoint(markers, p, 'magenta', '0.1%')
371
+ com.type = 'L';
372
+ com.values = [p.x, p.y];
373
+ }
374
+
375
+ pathDataN.push(com)
376
+
377
+ }
378
+
379
+
380
+ return pathDataN;
381
+
382
+
383
+ }
280
384
 
281
385
 
282
386
 
@@ -10,6 +10,7 @@ import { getPolygonArea } from "./geometry_area";
10
10
  import { getPolyBBox } from "./geometry_bbox";
11
11
  import { reversePathData } from "./pathData_reverse";
12
12
  import { getPathDataPolyPrecise } from "./pathData_toPolygon";
13
+ import { renderPoint, renderPoly } from "./visualize";
13
14
 
14
15
  export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
15
16
 
@@ -42,6 +43,7 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
42
43
  let ptMid = { x: bb.left + bb.width / 2, y: bb.top + bb.height / 2 }
43
44
  let inPoly = isPointInPolygon(ptMid, prev.pts, bb0)
44
45
 
46
+
45
47
  if (inPoly) {
46
48
  polys[j].inter += 1
47
49
  poly.includedIn.push(i)
@@ -52,29 +54,25 @@ export function fixPathDataDirections(pathDataArr = [], toClockwise = false) {
52
54
 
53
55
 
54
56
  // reverse paths
55
- for (let i = 0; i < l; i++) {
57
+ for (let i = 0; l && i < l; i++) {
56
58
 
57
59
  let poly = polys[i]
58
60
  let { cw, includedIn, includes } = poly
59
61
 
60
- // outer path direction to counter clockwise
61
- if (!includedIn.length && cw && !toClockwise
62
- || !includedIn.length && !cw && toClockwise
63
- ) {
64
- pathDataArr[i].pathData = reversePathData(pathDataArr[i].pathData);
65
- polys[i].cw = polys[i].cw ? false : true
66
- cw = polys[i].cw
67
- }
62
+ let len = includes.length;
63
+ //console.log('try reverse', includes);
68
64
 
69
65
  // reverse inner sub paths
70
- for (let j = 0; j < includes.length; j++) {
66
+ for (let j = 0; len && j < len; j++) {
71
67
  let ind = includes[j];
72
68
  let child = polys[ind];
73
69
 
74
- if (child.cw === cw) {
75
- pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
76
- polys[ind].cw = polys[ind].cw ? false : true
77
- }
70
+ // nothing to do
71
+ if (child.cw !== cw) continue
72
+
73
+ pathDataArr[ind].pathData = reversePathData(pathDataArr[ind].pathData);
74
+ polys[ind].cw = polys[ind].cw ? false : true
75
+
78
76
  }
79
77
  }
80
78
 
@@ -3,17 +3,17 @@ export function pathDataFromPoly(pts, closed = true) {
3
3
  let pathData = []
4
4
  let subPath = []
5
5
 
6
-
7
6
  // complex polygon
8
7
  if (Array.isArray(pts[0])) {
9
- pts.forEach(sub => {
8
+ pts.forEach(sub => {
10
9
  subPath = [
11
10
  { type: 'M', values: [sub[0].x, sub[0].y] },
12
11
  ...sub.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
13
12
  ];
13
+ if (closed) subPath.push({ type: 'Z', values: [] })
14
14
  pathData.push(...subPath)
15
15
  })
16
- }else{
16
+ } else {
17
17
  pathData = [
18
18
  { type: 'M', values: [pts[0].x, pts[0].y] },
19
19
  ...pts.slice(1).map(pt => { return { type: 'L', values: [pt.x, pt.y] } })
@@ -0,0 +1,86 @@
1
+ import { deg2rad } from "../constants";
2
+ import { svgArcToCenterParam, toParametricAngle } from "./geometry";
3
+ import { getCircleArcLength, getEllipseLengthLG, getLegendreGaussValues, getLength, waArr_global } from "./geometry_length";
4
+ import { getPathDataVerbose } from "./pathData_analyze";
5
+ import { splitSubpaths } from "./pathData_split";
6
+
7
+
8
+ export function getPathDataLength(pathData = []) {
9
+ let len = 0
10
+ let pathDataArr = splitSubpaths(pathData);
11
+
12
+ for (let i = 0; i < pathDataArr.length; i++) {
13
+ let pathData = pathDataArr[i]
14
+
15
+ // add verbose point data if not present
16
+ if (pathData[0].p === undefined) pathData = getPathDataVerbose(pathData);
17
+
18
+ // Calculate Legendre Gauss weight and abscissa values
19
+ if (!waArr_global.length) {
20
+ //console.log('no LG');
21
+ let waArr = getLegendreGaussValues(48)
22
+ waArr.forEach(wa => {
23
+ waArr_global.push(wa)
24
+ })
25
+ }
26
+
27
+ let waArr = waArr_global;
28
+
29
+ pathData.forEach(com => {
30
+ let { type, values, p0, p, cp1 = null, cp2 = null } = com;
31
+ let pts = [p0]
32
+ if (type === 'C' || type === 'Q') pts.push(cp1)
33
+ if (type === 'C') pts.push(cp2)
34
+ pts.push(p)
35
+ let comLen = 0
36
+
37
+ if (type === 'A') {
38
+
39
+ // get parametrized arc properties
40
+ let [largeArc, sweep] = [com.values[3], com.values[4]];
41
+ let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], largeArc, sweep, p.x, p.y, false)
42
+ let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle, xAxisRotation } = arcData
43
+
44
+ //is circle
45
+ if (rx === ry) {
46
+ comLen = getCircleArcLength(rx, Math.abs(deltaAngle))
47
+ }
48
+
49
+ // is ellipse
50
+ else {
51
+ xAxisRotation = xAxisRotation * deg2rad;
52
+ startAngle = toParametricAngle((startAngle - xAxisRotation), rx, ry)
53
+ endAngle = toParametricAngle((endAngle - xAxisRotation), rx, ry)
54
+
55
+ // recalculate parametrized delta
56
+ let deltaAngle_param = endAngle - startAngle;
57
+
58
+ let signChange = deltaAngle > 0 && deltaAngle_param < 0 || deltaAngle < 0 && deltaAngle_param > 0;
59
+
60
+ //deltaAngle = xAxisRotation>0 ? endAngle- startAngle: deltaAngle;
61
+ deltaAngle = signChange ? deltaAngle : deltaAngle_param;
62
+
63
+ // adjust end angle
64
+ if (sweep && startAngle > endAngle) {
65
+ endAngle += Math.PI * 2
66
+ }
67
+
68
+ if (!sweep && startAngle < endAngle) {
69
+ endAngle -= Math.PI * 2
70
+ }
71
+ comLen = getEllipseLengthLG(rx, ry, startAngle, endAngle, waArr)
72
+ }
73
+ }
74
+
75
+ else {
76
+ comLen = getLength(pts, {
77
+ t: 1,
78
+ waArr
79
+ })
80
+ }
81
+ len += comLen;
82
+ })
83
+ }
84
+
85
+ return len;
86
+ }
@@ -103,8 +103,10 @@ const sanitizeArc = (val='', valueIndex=0) => {
103
103
 
104
104
 
105
105
  export function parsePathDataString(d, debug = true, limit=0) {
106
+ if(!d) return []
106
107
  d = d.trim();
107
108
 
109
+
108
110
  if(limit) console.log('!!!limit', limit);
109
111
 
110
112
  let pathDataObj = {