svg-path-simplify 0.3.5 → 0.4.0

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 (43) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +3 -76
  3. package/dist/svg-path-simplify.esm.js +5505 -4030
  4. package/dist/svg-path-simplify.esm.min.js +2 -8
  5. package/dist/svg-path-simplify.js +5506 -4029
  6. package/dist/svg-path-simplify.min.js +2 -8
  7. package/dist/svg-path-simplify.pathdata.esm.js +1154 -1042
  8. package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
  9. package/docs/api.md +127 -0
  10. package/index.html +279 -257
  11. package/package.json +1 -1
  12. package/src/constants.js +10 -1
  13. package/src/index.js +7 -1
  14. package/src/pathData_simplify_cubic.js +1 -18
  15. package/src/pathSimplify-main.js +87 -30
  16. package/src/pathSimplify-only-pathdata.js +2 -2
  17. package/src/svg-getAttributes.js +13 -0
  18. package/src/svg_flatten_transforms.js +1 -1
  19. package/src/svg_getViewbox.js +23 -11
  20. package/src/svg_rootSVG.js +9 -0
  21. package/src/svgii/convert_colors.js +145 -0
  22. package/src/svgii/convert_units.js +159 -0
  23. package/src/svgii/geometry.js +24 -4
  24. package/src/svgii/geometry_bbox.js +2 -1
  25. package/src/svgii/geometry_bbox_element.js +46 -0
  26. package/src/svgii/pathData_analyze.js +34 -14
  27. package/src/svgii/pathData_convert.js +204 -34
  28. package/src/svgii/pathData_parse.js +2 -99
  29. package/src/svgii/pathData_parse_els.js +217 -128
  30. package/src/svgii/pathData_simplify_refineCorners.js +72 -43
  31. package/src/svgii/pathData_stringify.js +6 -5
  32. package/src/svgii/pathData_toPolygon.js +3 -1
  33. package/src/svgii/pathData_transform.js +307 -0
  34. package/src/svgii/poly_normalize.js +21 -1
  35. package/src/svgii/rounding.js +36 -5
  36. package/src/svgii/svg-styles-getTransforms.js +119 -8
  37. package/src/svgii/svg-styles-to-attributes-const.js +26 -6
  38. package/src/svgii/svg_cleanup.js +540 -74
  39. package/src/svgii/svg_el_parse_style_props.js +561 -0
  40. package/src/svgii/transform_qr_decompose.js +74 -0
  41. package/src/svgii/pathData_scale.js +0 -42
  42. package/src/svgii/stringify.js +0 -103
  43. package/src/svgii/svg-styles-to-attributes.js +0 -217
@@ -0,0 +1,159 @@
1
+ import { deg2rad, inch2cm, inch2pt, rad2Deg, root2 } from "../constants";
2
+ import { hex2Rgb, hsl2Rgb, rgba2Hex } from "./convert_colors";
3
+ import { autoRound } from "./rounding";
4
+ import { horizontalProps, verticalProps } from "./svg-styles-to-attributes-const";
5
+
6
+
7
+ export function svgElUnitsToPixel(el, {
8
+ width = 0,
9
+ height = 0,
10
+ fontSize = 16,
11
+ dpi = 96,
12
+ autoRoundValues = false,
13
+ decimals = -1,
14
+ } = {}) {
15
+
16
+
17
+ let attributes = [...el.attributes];
18
+ let attNames = attributes.map(att => att.name)
19
+ let attValues = attributes.map(att => att.nodeValue)
20
+ //console.log('attributes', attributes);
21
+
22
+ let isSquare = width === height;
23
+
24
+ let atts = {}
25
+ attNames.forEach((att, i) => {
26
+ let isHorizontal = horizontalProps.includes(att)
27
+ let isVertical = verticalProps.includes(att)
28
+ let normalizedDiagonal = !isSquare && att === 'r' ? true : false
29
+ let attValue = attValues[i];
30
+ //console.log(att, isHorizontal, isVertical, autoRoundValues);
31
+ let val = normalizeUnits(attValue, { isHorizontal, isVertical, width, height, normalizedDiagonal, autoRoundValues })
32
+ atts[att] = val;
33
+
34
+ // apply
35
+ el.setAttribute(att, val)
36
+ })
37
+
38
+ return atts;
39
+ }
40
+
41
+
42
+ // convert real life units to pixels
43
+ export function normalizeUnits(value = null, {
44
+ unit = null,
45
+ width = 0,
46
+ height = 0,
47
+ decimals = -1,
48
+ isHorizontal = false,
49
+ isVertical = false,
50
+ autoRoundValues = false,
51
+ dpi = 96,
52
+ fontSize = 16,
53
+ normalizedDiagonal = false,
54
+ } = {}) {
55
+
56
+ // only required for circle r normalization when height!=width
57
+ normalizedDiagonal = width === height ? false : normalizedDiagonal;
58
+
59
+ let type = typeof value;
60
+ if (!value) return value;
61
+
62
+ // check if value is string
63
+ let isNum = type === 'number' ? true : isNumericValue(value)
64
+ let isArray = type === 'string' ? value.split(/,| /).length > 1 : false;
65
+ let isFunction = type === 'string' ? value.includes('(') : false;
66
+ //console.log(isArray);
67
+ if (!isNum || isArray || isFunction) return value
68
+
69
+ // check unit if not specified
70
+ unit = !unit ? getUnit(value) : unit;
71
+
72
+ let val = parseFloat(value);
73
+ let scale = 1;
74
+ let scaleRoot = Math.sqrt(width * width + height * height) / root2
75
+
76
+
77
+ // no unit - already pixes/user unit
78
+ if (!unit) {
79
+ return val;
80
+ }
81
+
82
+ switch (unit) {
83
+ case "%":
84
+ if (width && isHorizontal) {
85
+ scale = width / 100;
86
+ }
87
+ else if (height && isVertical) {
88
+ scale = height / 100;
89
+ }
90
+ else {
91
+ scale = normalizedDiagonal ? scaleRoot / 100 : 1;
92
+ }
93
+ break;
94
+
95
+ case "rad":
96
+ scale = rad2Deg;
97
+ break;
98
+ case "turn":
99
+ scale = 360;
100
+ break;
101
+
102
+ case "in":
103
+ scale = dpi;
104
+ break;
105
+
106
+ case "pt":
107
+ // 1/72
108
+ scale = dpi * inch2pt;
109
+ break;
110
+
111
+ case "pc":
112
+ // 1/6
113
+ scale = dpi * 0.16666667;
114
+ break;
115
+
116
+ case "cm":
117
+ // 1/2.54
118
+ scale = inch2cm * dpi;
119
+ break;
120
+ case "mm":
121
+ //scale = ((1 / 2.54) * dpi) * 0.1;
122
+ scale = inch2cm * dpi * 0.1
123
+ break;
124
+
125
+ // has anyone ever used it?
126
+ case "Q":
127
+ scale = inch2cm * dpi * 0.025;
128
+ break;
129
+
130
+ // just a default approximation
131
+ case "em":
132
+ case "rem":
133
+ scale = fontSize;
134
+ break;
135
+ default:
136
+ scale = 1;
137
+ }
138
+ let valuePx = val * scale;
139
+ if (autoRoundValues) valuePx = autoRound(valuePx);
140
+ else if (decimals > -1) valuePx = +valuePx.toFixed(decimals);
141
+
142
+ //console.log('valuePx', valuePx);
143
+ return valuePx;
144
+ };
145
+
146
+
147
+ export function getUnit(val) {
148
+ if (!val || !isNaN(val)) return '';
149
+ val = val.replace(/\+|\-/g, '');
150
+ let unit = val.match(/[^\d|.]+/g)[0];
151
+ return unit;
152
+ }
153
+
154
+ export function isNumericValue(val = '') {
155
+ // is number
156
+ if (!isNaN(val)) return true;
157
+ // parse with unit
158
+ return !isNaN(parseFloat(val))
159
+ }
@@ -3,9 +3,11 @@ import {abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
3
3
  log, max, min, pow, random, round, sin, sqrt, tan, PI} from '/.constants.js';
4
4
  */
5
5
 
6
- import { rad2Deg } from "../constants";
6
+ import { rad2Deg, root2 } from "../constants";
7
+ //import { getPolyBBox } from "./geometry_bbox";
7
8
  import { renderPoint } from "./visualize";
8
9
 
10
+
9
11
  export const {
10
12
  abs, acos, asin, atan, atan2, ceil, cos, exp, floor,
11
13
  log, max, min, pow, random, round, sin, sqrt, tan, PI
@@ -127,6 +129,15 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
127
129
  return false
128
130
  }
129
131
 
132
+ // coinciding line points
133
+ if (
134
+ (p1.x === p2.x && p1.y === p2.y) ||
135
+ (p3.x === p4.x && p3.y === p4.y)
136
+ ) {
137
+ return false
138
+ }
139
+
140
+
130
141
  try {
131
142
  denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
132
143
 
@@ -171,6 +182,8 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
171
182
  return false;
172
183
  }
173
184
 
185
+
186
+
174
187
  // if line1 and line2 are segments, they intersect if both of the above are true
175
188
  //console.log('inter', intersectionPoint)
176
189
  return intersectionPoint;
@@ -192,7 +205,7 @@ export function isPointInPolygon(pt, polygon, bb, skipBB = false) {
192
205
  }
193
206
  }
194
207
 
195
- let l=polygon.length;
208
+ let l = polygon.length;
196
209
  for (let i = l - 1, j = 0; j < l; i = j, j++) {
197
210
  const A = polygon[i];
198
211
  const B = polygon[j];
@@ -204,7 +217,7 @@ export function isPointInPolygon(pt, polygon, bb, skipBB = false) {
204
217
  /**
205
218
  * if pt inside the vertical range filter out "ray pass vertex" problem
206
219
  * by treating the line a little lower
207
- */
220
+ */
208
221
  if ((pt.y == A.y && B.y >= A.y) || (pt.y == B.y && A.y >= B.y)) continue;
209
222
  // calc cross product `ptA X ptB`, pt lays on left side of AB if c > 0
210
223
  const c = (A.x - pt.x) * (B.y - pt.y) - (B.x - pt.x) * (A.y - pt.y);
@@ -1273,7 +1286,7 @@ export function intersectLines(p1, p2, p3, p4) {
1273
1286
  */
1274
1287
  export function getDistance(p1, p2, isArray = false) {
1275
1288
  //if(Array.isArray(p1)) isArray = true;
1276
-
1289
+
1277
1290
  //console.log(p1, p2);
1278
1291
  let dx = isArray ? p2[0] - p1[0] : (p2.x - p1.x);
1279
1292
  let dy = isArray ? p2[1] - p1[1] : (p2.y - p1.y);
@@ -1388,6 +1401,13 @@ export function reducePoints(points, maxPoints = 48) {
1388
1401
  }
1389
1402
 
1390
1403
 
1404
+ export function getElementTransform(parent, el) {
1405
+ if (!parent || !el) return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }
1406
+ let matrix = parent.getScreenCTM().inverse().multiply(el.getScreenCTM());
1407
+ return matrix
1408
+ }
1409
+
1410
+
1391
1411
  export function mirrorCpts(cpt2_0, pt0, cpt2, pt1, outgoing = true, t = 0.666) {
1392
1412
 
1393
1413
  // hypotenuse angle
@@ -1,10 +1,11 @@
1
1
 
2
2
  //import { splitSubpaths } from "./convert_segments";
3
+ import { getElementAtts } from "../svg-getAttributes";
3
4
  import { pointAtT, svgArcToCenterParam, getBezierExtremeT, getArcExtemes, getDistance, interpolate, getPointOnEllipse } from "./geometry";
5
+ //import { parsePathDataNormalized } from "./pathData_convert";
4
6
  import { renderPoint } from "./visualize";
5
7
  //import {arcToBezier} from'./pathData_convert';
6
8
 
7
-
8
9
  /**
9
10
  * calculate polygon bbox
10
11
  */
@@ -0,0 +1,46 @@
1
+ import { getElementAtts } from "../svg-getAttributes";
2
+ import { getPathDataPoly, getPolyBBox } from "./geometry_bbox";
3
+ import { parsePathDataNormalized } from "./pathData_convert";
4
+ import { normalizePoly } from "./poly_normalize";
5
+
6
+ export function getElBBox(el){
7
+
8
+ let type=el.nodeName.toLowerCase()
9
+ let atts = getElementAtts(el);
10
+ let bb = {x:0, y:0, width:0, height:0}
11
+ let pts = [];
12
+
13
+ switch(type){
14
+ case 'path':
15
+ let pathData = parsePathDataNormalized(atts.d)
16
+ bb=getPolyBBox(getPathDataPoly(pathData))
17
+
18
+ break;
19
+ case 'rect':
20
+ bb = {x:atts.x||0, y:atts.y||0, width:atts.width, height:atts.height}
21
+ break;
22
+ case 'circle':
23
+ let diameter = atts.r*2
24
+ bb = {x:atts.cx-atts.r, y:atts.cy-atts.r, width:diameter, height:diameter}
25
+ break;
26
+ case 'ellipse':
27
+ bb = {x:atts.cx-atts.rx, y:atts.cy-atts.ry, width:atts.rx*2, height:atts.ry*2}
28
+ break;
29
+
30
+ case 'line':
31
+ pts = [{x:atts.x1, y:atts.y1}, {x:atts.x2, y:atts.y2}]
32
+ bb = getPolyBBox(pts)
33
+ break;
34
+
35
+ case 'polyline':
36
+ case 'polygon':
37
+ pts = normalizePoly(atts.points);
38
+ bb = getPolyBBox(pts)
39
+ break;
40
+
41
+ }
42
+ //console.log('bb', bb, type, el);
43
+
44
+ return bb;
45
+ }
46
+
@@ -55,10 +55,10 @@ export function analyzePathData(pathData = [], {
55
55
  let len = pathData.length;
56
56
 
57
57
  // threshold for corner angles: 10 deg
58
- let thresholdCorner = Math.PI * 2 / 360 * 10
58
+ //let thresholdCorner = Math.PI * 2 / 360 * 10
59
59
 
60
60
  // define angle threshold for semi extremes
61
- let thresholdAngle = detectSemiExtremes ? 0.01 : 0.05
61
+ //let thresholdAngle = detectSemiExtremes ? 0.01 : 0.05
62
62
 
63
63
 
64
64
  for (let c = 2; len && c <= len; c++) {
@@ -86,7 +86,7 @@ export function analyzePathData(pathData = [], {
86
86
  // check flatness of command
87
87
  let toleranceFlat = 0.01;
88
88
  let thresholdLength = dimA * 0.1
89
- let threshold = thresholdLength*0.01
89
+ let threshold = thresholdLength * 0.01
90
90
  let areaThresh = squareDist * toleranceFlat;
91
91
  let isFlat = Math.abs(cptArea) < areaThresh;
92
92
 
@@ -109,14 +109,29 @@ export function analyzePathData(pathData = [], {
109
109
  let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x)
110
110
  let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y)
111
111
 
112
- let horizontal = (dy === 0 || dy<threshold ) && dx > 0
113
- let vertical = (dx === 0 || dx<threshold ) && dy > 0
112
+ //let horizontal = (dy === 0 || dy<=threshold ) && dx > 0
113
+ //let vertical = (dx === 0 || dx<=threshold ) && dy > 0
114
+ let horizontal = (dy === 0 || dy <= threshold) && dx > 0
115
+ let vertical = (dx === 0 || dx <= threshold) && dy > 0
116
+
114
117
 
115
118
  if (horizontal || vertical) {
116
119
  hasExtremes = true;
117
120
  }
118
121
 
119
122
  // is extreme relative to bounding box
123
+
124
+ // (cp1.x===p0.x && cp1.y!==p0.y ) ||
125
+ if ((cp1.x === p0.x && cp1.y !== p0.y) || (cp1.y === p0.y && cp1.x !== p0.x)) {
126
+ //hasExtremes = true;
127
+ //renderPoint(markers, p0 )
128
+ //p0.extreme = true
129
+ //let comP = pathDataProps[pathDataProps.length-1]
130
+ pathDataProps[pathDataProps.length - 1].extreme = true
131
+ //console.log(comP);
132
+ }
133
+
134
+
120
135
  if ((p.x === left || p.y === top || p.x === right || p.y === bottom)) {
121
136
  hasExtremes = true;
122
137
  }
@@ -126,6 +141,7 @@ export function analyzePathData(pathData = [], {
126
141
  let couldHaveExtremes = bezierhasExtreme(null, commandPts)
127
142
  if (couldHaveExtremes) {
128
143
  let tArr = getTatAngles(commandPts)
144
+
129
145
  if (tArr.length && (tArr[0] > 0.2)) {
130
146
  hasExtremes = true;
131
147
  }
@@ -181,24 +197,28 @@ export function analyzePathData(pathData = [], {
181
197
 
182
198
  let signChange2 = (areaCpt < 0 && com.cptArea > 0) || (areaCpt > 0 && com.cptArea < 0) ? true : false;
183
199
 
184
- let isCorner=!isFlat && signChange2;
200
+ let isCorner = !isFlat && signChange2;
185
201
  if (isCorner) com.corner = true;
186
202
  }
187
203
  }
188
204
 
189
- //debug = true;
190
- if (debug) {
205
+ pathDataProps.push(com)
206
+
207
+ }
208
+
209
+
210
+ //debug = true;
211
+
212
+ if (debug) {
213
+ pathDataProps.forEach(com=>{
191
214
  //if (com.semiExtreme) renderPoint(markers, com.p, 'blue', '2%', '0.5')
192
215
  if (com.directionChange) renderPoint(markers, com.p, 'orange', '1.5%', '0.5')
193
216
  if (com.corner) renderPoint(markers, com.p, 'magenta', '1.5%', '0.5')
194
217
  if (com.extreme) renderPoint(markers, com.p, 'cyan', '1%', '0.5')
195
-
196
- }
197
-
198
- pathDataProps.push(com)
199
-
218
+ })
200
219
  }
201
220
 
221
+
202
222
  //pathDataProps.push(comLast)
203
223
 
204
224
 
@@ -316,7 +336,7 @@ export function getPathDataVerbose(pathData, {
316
336
  }
317
337
  }
318
338
 
319
- else if (type === 'A') {
339
+ else if (type === 'A' && addArcParams) {
320
340
  let { rx, ry, cx, cy, startAngle, endAngle, deltaAngle } = svgArcToCenterParam(p0.x, p0.y, ...values)
321
341
  com.cx = cx
322
342
  com.cy = cy