svg-path-simplify 0.0.7 → 0.0.8

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.
@@ -2,11 +2,13 @@ import { detectInputType } from './detect_input';
2
2
  import { combineCubicPairs } from './pathData_simplify_cubic';
3
3
  import { getPathDataVertices, pointAtT } from './svgii/geometry';
4
4
  import { getPolyBBox } from './svgii/geometry_bbox';
5
- import { analyzePathData, analyzePathData2 } from './svgii/pathData_analyze';
5
+ import { analyzePathData } from './svgii/pathData_analyze';
6
6
  import { combineArcs, convertPathData, cubicCommandToArc, revertCubicQuadratic } from './svgii/pathData_convert';
7
7
  import { parsePathDataNormalized } from './svgii/pathData_parse';
8
+ import { shapeElToPath } from './svgii/pathData_parse_els';
8
9
  import { pathDataRemoveColinear } from './svgii/pathData_remove_collinear';
9
- import { removeZeroLengthLinetos, removeZeroLengthLinetos_post } from './svgii/pathData_remove_zerolength';
10
+ import { removeOrphanedM } from './svgii/pathData_remove_orphaned';
11
+ import { removeZeroLengthLinetos } from './svgii/pathData_remove_zerolength';
10
12
  import { optimizeClosePath, pathDataToTopLeft } from './svgii/pathData_reorder';
11
13
  import { reversePathData } from './svgii/pathData_reverse';
12
14
  import { addExtremePoints, splitSubpaths } from './svgii/pathData_split';
@@ -15,6 +17,7 @@ import { pathDataToD } from './svgii/pathData_stringify';
15
17
  import { analyzePoly } from './svgii/poly_analyze';
16
18
  import { getCurvePathData } from './svgii/poly_to_pathdata';
17
19
  import { detectAccuracy } from './svgii/rounding';
20
+ import { refineAdjacentExtremes } from './svgii/simplify_refineExtremes';
18
21
  import { cleanUpSVG, removeEmptySVGEls, stringifySVG } from './svgii/svg_cleanup';
19
22
  import { renderPoint } from './svgii/visualize';
20
23
 
@@ -42,11 +45,13 @@ export function svgPathSimplify(input = '', {
42
45
  flatBezierToLinetos = true,
43
46
  revertToQuadratics = true,
44
47
 
48
+ refineExtremes = true,
45
49
  keepExtremes = true,
46
50
  keepCorners = true,
47
51
  extrapolateDominant = true,
48
52
  keepInflections = false,
49
53
  addExtremes = false,
54
+ removeOrphanSubpaths = false,
50
55
 
51
56
 
52
57
  // svg path optimizations
@@ -61,6 +66,7 @@ export function svgPathSimplify(input = '', {
61
66
  mergePaths = false,
62
67
  removeHidden = true,
63
68
  removeUnused = true,
69
+ shapesToPaths = true,
64
70
 
65
71
 
66
72
  } = {}) {
@@ -105,6 +111,14 @@ export function svgPathSimplify(input = '', {
105
111
  svg = cleanUpSVG(input, { returnDom, removeHidden, removeUnused }
106
112
  );
107
113
 
114
+ if(shapesToPaths){
115
+ let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
116
+ shapes.forEach(shape=>{
117
+ let path = shapeElToPath(shape);
118
+ shape.replaceWith(path)
119
+ })
120
+ }
121
+
108
122
  // collect paths
109
123
  let pathEls = svg.querySelectorAll('path')
110
124
  pathEls.forEach(path => {
@@ -132,15 +146,23 @@ export function svgPathSimplify(input = '', {
132
146
  paths.forEach(path => {
133
147
  let { d, el } = path;
134
148
 
149
+ ///let t0 = performance.now()
135
150
  let pathDataO = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
136
- //console.log(pathDataO);
137
-
138
- // create clone for fallback
139
- let pathData = JSON.parse(JSON.stringify(pathDataO));
151
+ //console.log('pathDataO', pathDataO);
140
152
 
141
153
  // count commands for evaluation
142
154
  let comCount = pathDataO.length
143
155
 
156
+
157
+ // create clone for fallback
158
+ //let pathData = JSON.parse(JSON.stringify(pathDataO));
159
+ let pathData = pathDataO;
160
+ //let t1 = performance.now() - t0;
161
+ //console.log('t1', t1);
162
+
163
+
164
+ if(removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
165
+
144
166
  /**
145
167
  * get sub paths
146
168
  */
@@ -182,8 +204,17 @@ export function svgPathSimplify(input = '', {
182
204
  //let pathDataN = pathData;
183
205
 
184
206
  //console.log(pathDataPlus);
185
-
207
+ //let t0=performance.now()
186
208
  pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
209
+ //let t1=performance.now() - t0;
210
+ //console.log('t1', t1);
211
+
212
+
213
+ // refine extremes
214
+ if(refineExtremes){
215
+ let thresholdEx = (bb.width + bb.height) / 2 * 0.05
216
+ pathData = refineAdjacentExtremes(pathData, {threshold:thresholdEx, tolerance})
217
+ }
187
218
 
188
219
 
189
220
  // cubic to arcs
@@ -207,6 +238,9 @@ export function svgPathSimplify(input = '', {
207
238
  }
208
239
 
209
240
 
241
+
242
+
243
+
210
244
  // simplify to quadratics
211
245
  if (revertToQuadratics) {
212
246
  pathData.forEach((com, c) => {
@@ -232,6 +266,7 @@ export function svgPathSimplify(input = '', {
232
266
 
233
267
  //if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, tolerance, flatBezierToLinetos);
234
268
 
269
+
235
270
  // optimize close path
236
271
  if (optimizeOrder) pathData = optimizeClosePath(pathData)
237
272
 
@@ -260,13 +295,17 @@ export function svgPathSimplify(input = '', {
260
295
  */
261
296
  if (autoAccuracy) {
262
297
  decimals = detectAccuracy(pathData)
298
+ pathOptions.decimals = decimals
299
+ //console.log('!decimals', decimals);
263
300
  }
264
301
 
302
+ //console.log('autoAccuracy', autoAccuracy, decimals);
303
+
265
304
  // optimize path data
266
305
  pathData = convertPathData(pathData, pathOptions)
267
306
 
268
307
  // remove zero-length segments introduced by rounding
269
- pathData = removeZeroLengthLinetos_post(pathData);
308
+ pathData = removeZeroLengthLinetos(pathData);
270
309
 
271
310
  // compare command count
272
311
  let comCountS = pathData.length
@@ -301,7 +340,8 @@ export function svgPathSimplify(input = '', {
301
340
  let pathData = convertPathData(pathData_merged, pathOptions)
302
341
 
303
342
  // remove zero-length segments introduced by rounding
304
- pathData = removeZeroLengthLinetos_post(pathData);
343
+ //pathData = removeZeroLengthLinetos_post(pathData);
344
+ pathData = removeZeroLengthLinetos(pathData);
305
345
 
306
346
 
307
347
  let dOpt = pathDataToD(pathData, minifyD)
@@ -419,6 +459,7 @@ function simplifyPathDataCubic(pathData, {
419
459
  // add cumulative error to prevent distortions
420
460
  //console.log('combined', combined);
421
461
  error += combined[0].error * 0.5;
462
+ //error += combined[0].error * 1;
422
463
  offset++
423
464
  }
424
465
  com = combined[0]
@@ -23,19 +23,24 @@ export function getAngle(p1, p2, normalize = false) {
23
23
  * http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
24
24
  */
25
25
 
26
- export function checkLineIntersection(p1, p2, p3, p4, exact = true) {
26
+ export function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
27
27
  // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
28
28
  let denominator, a, b, numerator1, numerator2;
29
29
  let intersectionPoint = {}
30
30
 
31
+ if(!p1 || !p2 || !p3 || !p4){
32
+ if(debug) console.warn('points missing');
33
+ return false
34
+ }
35
+
31
36
  try {
32
37
  denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
33
38
  if (denominator == 0) {
34
39
  return false;
35
40
  }
36
-
37
41
  } catch {
38
- console.log('!catch', p1, p2, 'p3:', p3, p4);
42
+ if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
43
+ return false
39
44
  }
40
45
 
41
46
  a = p1.y - p3.y;
@@ -52,9 +57,6 @@ export function checkLineIntersection(p1, p2, p3, p4, exact = true) {
52
57
  y: p1.y + (a * (p2.y - p1.y))
53
58
  }
54
59
 
55
- // console.log('intersectionPoint', intersectionPoint, p1, p2);
56
-
57
-
58
60
 
59
61
  let intersection = false;
60
62
  // if line1 is a segment and line2 is infinite, they intersect if:
@@ -874,156 +876,8 @@ export function intersectLines(p1, p2, p3, p4) {
874
876
 
875
877
 
876
878
 
877
- /**
878
- * check polygon flatness helper
879
- * basically a reduced shoelace algorithm
880
- */
881
- export function commandIsFlat0(points, tolerance = 0.025) {
882
-
883
-
884
- let xArr = points.map(pt => { return pt.x })
885
- let yArr = points.map(pt => { return pt.y })
886
-
887
- let xMin = Math.min(...xArr)
888
- let xMax = Math.max(...xArr)
889
- let yMin = Math.min(...yArr)
890
- let yMax = Math.max(...yArr)
891
- let w = xMax - xMin
892
- let h = yMax - yMin
893
-
894
-
895
- if (points.length < 3 || (w === 0 || h === 0)) {
896
- return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
897
- }
898
-
899
- tolerance = 0.5;
900
- let thresh = (w + h) * 0.5 * tolerance;
901
-
902
-
903
- //let thresh = tolerance;
904
- //console.log('w,h', w, h, thresh);
905
-
906
- let area = 0;
907
- for (let i = 0; i < points.length; i++) {
908
- let addX = points[i].x;
909
- let addY = points[i === points.length - 1 ? 0 : i + 1].y;
910
- let subX = points[i === points.length - 1 ? 0 : i + 1].x;
911
- let subY = points[i].y;
912
- area += addX * addY * 0.5 - subX * subY * 0.5;
913
- }
914
-
915
- //console.log('flatArea', area, points);
916
- area = +Math.abs(area).toFixed(9);
917
-
918
- let ratio = area / thresh;
919
- let isFlat = area === 0 ? true : (ratio < 0.15 ? true : false);
920
- //isFlat= false
921
-
922
- return { area: area, flat: isFlat, thresh: thresh, ratio: ratio };
923
- }
924
-
925
-
926
- export function commandIsFlat(points, tolerance = 0.025) {
927
-
928
- let p0 = points[0];
929
- let p = points[points.length - 1];
930
-
931
- let xArr = points.map(pt => { return pt.x })
932
- let yArr = points.map(pt => { return pt.y })
933
-
934
- let xMin = Math.min(...xArr)
935
- let xMax = Math.max(...xArr)
936
- let yMin = Math.min(...yArr)
937
- let yMax = Math.max(...yArr)
938
- let w = xMax - xMin
939
- let h = yMax - yMin
940
-
941
-
942
- if (points.length < 3 || (w === 0 || h === 0)) {
943
- return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
944
- }
945
-
946
- let squareDist = getSquareDistance(p0, p)
947
- let squareDist1 = getSquareDistance(p0, points[0])
948
- let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
949
- let squareDistAvg = (squareDist1 + squareDist2) / 2
950
-
951
- tolerance = 0.5;
952
- let thresh = (w + h) * 0.5 * tolerance;
953
-
954
-
955
- //let thresh = tolerance;
956
- //console.log('w,h', w, h, thresh);
957
-
958
- let area = 0;
959
- for (let i = 0, l = points.length; i < l; i++) {
960
- let addX = points[i].x;
961
- let addY = points[i === points.length - 1 ? 0 : i + 1].y;
962
- let subX = points[i === points.length - 1 ? 0 : i + 1].x;
963
- let subY = points[i].y;
964
- area += addX * addY * 0.5 - subX * subY * 0.5;
965
- }
966
-
967
- //console.log('flatArea', area, points);
968
- area = +Math.abs(area).toFixed(9);
969
-
970
- let diff = Math.abs(area - squareDist);
971
- let areaDiff = Math.abs(100 - (100 / area * (area + diff)))
972
- let areaThresh = 1000
973
-
974
- //let ratio = area / (squareDistAvg/areaThresh);
975
- let ratio = area / (squareDistAvg);
976
-
977
-
978
- //let isFlat = area === 0 ? true : (ratio < 0.15 ? true : false);
979
- //let isFlat = area === 0 ? true : (area < squareDist/areaThresh ? true : false);
980
-
981
- let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
982
-
983
-
984
- return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
985
- }
986
-
987
-
988
- export function checkBezierFlatness(p0, cpts, p) {
989
-
990
- let isFlat = false;
991
-
992
- let isCubic = cpts.length===2;
993
-
994
- let cp1 = cpts[0]
995
- let cp2 = isCubic ? cpts[1] : cp1;
996
-
997
- if(p0.x===cp1.x && p0.y===cp1.y && p.x===cp2.x && p.y===cp2.y) return true;
998
-
999
- let dx1 = cp1.x - p0.x;
1000
- let dy1 = cp1.y - p0.y;
1001
-
1002
- let dx2 = p.x - cp2.x;
1003
- let dy2 = p.y - cp2.y;
1004
879
 
1005
- let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
1006
880
 
1007
- if(!cross1) return true
1008
-
1009
- let dx0 = p.x - p0.x;
1010
- let dy0 = p.y - p0.y;
1011
- let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
1012
-
1013
- if(!cross0) return true
1014
-
1015
- //let diff = Math.abs(cross0 - cross1)
1016
- //let rat0 = 1/cross0 * diff;
1017
- let rat = (cross0/cross1)
1018
-
1019
- if (rat<1.1 ) {
1020
- //console.log('cross', cross0, cross1, 'rat', rat, rat0, diff );
1021
- isFlat = true;
1022
- }
1023
-
1024
- return isFlat;
1025
-
1026
- }
1027
881
 
1028
882
  /**
1029
883
  * sloppy distance calculation
@@ -1041,7 +895,6 @@ export function getDistAv(pt1, pt2) {
1041
895
  let diff = Math.abs(diffX + diffY) / 2;
1042
896
  */
1043
897
 
1044
-
1045
898
  return diff;
1046
899
  }
1047
900
 
@@ -0,0 +1,101 @@
1
+ import { getSquareDistance } from "./geometry";
2
+ import { getPolygonArea } from "./geometry_area";
3
+
4
+ export function commandIsFlat(points, tolerance = 0.025) {
5
+
6
+ let p0 = points[0];
7
+ let p = points[points.length - 1];
8
+
9
+ let xArr = points.map(pt => { return pt.x })
10
+ let yArr = points.map(pt => { return pt.y })
11
+
12
+ let xMin = Math.min(...xArr)
13
+ let xMax = Math.max(...xArr)
14
+ let yMin = Math.min(...yArr)
15
+ let yMax = Math.max(...yArr)
16
+ let w = xMax - xMin
17
+ let h = yMax - yMin
18
+
19
+
20
+ if (points.length < 3 || (w === 0 || h === 0)) {
21
+ return { area: 0, flat: true, thresh: 0.0001, ratio: 0 };
22
+ }
23
+
24
+ let squareDist = getSquareDistance(p0, p)
25
+ let squareDist1 = getSquareDistance(p0, points[0])
26
+ let squareDist2 = points.length > 3 ? getSquareDistance(p, points[1]) : squareDist1;
27
+ let squareDistAvg = (squareDist1 + squareDist2) / 2
28
+
29
+ tolerance = 0.5;
30
+ let thresh = (w + h) * 0.5 * tolerance;
31
+
32
+ //let thresh = tolerance;
33
+ let area = getPolygonArea(points, true)
34
+
35
+
36
+ let diff = Math.abs(area - squareDist);
37
+ let areaDiff = Math.abs(100 - (100 / area * (area + diff)))
38
+ let areaThresh = 1000
39
+
40
+ //let ratio = area / (squareDistAvg/areaThresh);
41
+ let ratio = area / (squareDistAvg);
42
+
43
+
44
+ let isFlat = area === 0 ? true : area < squareDistAvg / areaThresh;
45
+
46
+
47
+ return { area: area, flat: isFlat, thresh: thresh, ratio: ratio, squareDist: squareDist, areaThresh: squareDist / areaThresh };
48
+ }
49
+
50
+
51
+ export function checkBezierFlatness(p0, cpts, p) {
52
+
53
+ let isFlat = false;
54
+
55
+ let isCubic = cpts.length === 2;
56
+
57
+ let cp1 = cpts[0]
58
+ let cp2 = isCubic ? cpts[1] : cp1;
59
+
60
+ if (p0.x === cp1.x && p0.y === cp1.y && p.x === cp2.x && p.y === cp2.y) return true;
61
+
62
+ let dx1 = cp1.x - p0.x;
63
+ let dy1 = cp1.y - p0.y;
64
+
65
+ let dx2 = p.x - cp2.x;
66
+ let dy2 = p.y - cp2.y;
67
+
68
+ let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
69
+
70
+ if (!cross1) return true
71
+
72
+ let dx0 = p.x - p0.x;
73
+ let dy0 = p.y - p0.y;
74
+ let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
75
+
76
+ if (!cross0) return true
77
+
78
+ let area = getPolygonArea([p0,...cpts, p], true)
79
+ let dist1 = getSquareDistance(p0, p)
80
+ let thresh = dist1/200;
81
+
82
+
83
+ // if(area<thresh) return true;
84
+ isFlat = area<thresh;
85
+ //console.log('area', area, thresh, isFlat);
86
+
87
+
88
+ /*
89
+ //let diff = Math.abs(cross0 - cross1)
90
+ //let rat0 = 1/cross0 * diff;
91
+ let rat = (cross0 / cross1)
92
+
93
+ if (rat < 1.1) {
94
+ console.log('cross', cross0, cross1, 'rat', rat );
95
+ isFlat = true;
96
+ }
97
+ */
98
+
99
+ return isFlat;
100
+
101
+ }