svg-path-simplify 0.0.1 → 0.0.2

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/README.md +28 -1
  2. package/dist/svg-path-simplify.esm.js +4040 -0
  3. package/dist/svg-path-simplify.esm.min.js +1 -0
  4. package/dist/svg-path-simplify.js +4065 -0
  5. package/dist/svg-path-simplify.min.js +1 -0
  6. package/dist/svg-path-simplify.node.js +4062 -0
  7. package/dist/svg-path-simplify.node.min.js +1 -0
  8. package/index.html +222 -0
  9. package/package.json +2 -2
  10. package/src/constants.js +4 -0
  11. package/src/index.js +18 -3
  12. package/src/pathData_simplify_cubic.js +324 -0
  13. package/src/pathData_simplify_cubic_arr.js +50 -0
  14. package/src/pathData_simplify_cubic_extrapolate.js +220 -0
  15. package/src/pathSimplify-main.js +294 -0
  16. package/src/svgii/...parse.js +402 -0
  17. package/src/svgii/geometry.js +1096 -0
  18. package/src/svgii/geometry_area.js +265 -0
  19. package/src/svgii/geometry_bbox.js +223 -0
  20. package/src/svgii/pathData_analyze.js +896 -0
  21. package/src/svgii/pathData_convert.js +1180 -0
  22. package/src/svgii/pathData_parse.js +487 -0
  23. package/src/svgii/pathData_remove_collinear.js +85 -0
  24. package/src/svgii/pathData_remove_zerolength.js +28 -0
  25. package/src/svgii/pathData_reorder.js +204 -0
  26. package/src/svgii/pathData_reverse.js +124 -0
  27. package/src/svgii/pathData_scale.js +42 -0
  28. package/src/svgii/pathData_split.js +449 -0
  29. package/src/svgii/pathData_stringify.js +146 -0
  30. package/src/svgii/pathData_toPolygon.js +92 -0
  31. package/src/svgii/pathdata_cleanup.js +363 -0
  32. package/src/svgii/poly_analyze.js +172 -0
  33. package/src/svgii/poly_to_pathdata.js +185 -0
  34. package/src/svgii/rounding.js +154 -0
  35. package/src/svgii/simplify.js +248 -0
  36. package/src/svgii/simplify_bezier.js +470 -0
  37. package/src/svgii/simplify_linetos.js +93 -0
  38. package/src/svgii/simplify_polygon.js +135 -0
  39. package/src/svgii/stringify.js +103 -0
  40. package/src/svgii/svg_cleanup.js +80 -0
  41. package/src/svgii/visualize.js +317 -0
  42. package/LICENSE +0 -21
@@ -0,0 +1,204 @@
1
+ import { splitSubpaths, addExtemesToCommand } from './pathData_split.js';
2
+ import { getComThresh, commandIsFlat, getPathDataVertices, getSquareDistance } from './geometry.js';
3
+ import { getPolyBBox } from './geometry_bbox.js';
4
+
5
+
6
+ import { renderPoint, renderPath } from './visualize.js';
7
+ import { getPolygonArea } from './geometry_area.js';
8
+
9
+
10
+
11
+ export function pathDataToTopLeft(pathData, removeFinalLineto = false, reorder = true) {
12
+
13
+ let pathDataNew = [];
14
+ let len = pathData.length;
15
+ let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
16
+ let isClosed = pathData[len - 1].type.toLowerCase() === 'z'
17
+
18
+ let linetos = pathData.filter(com => com.type === 'L')
19
+
20
+
21
+ // check if order is ideal
22
+ let penultimateCom = pathData[len - 2];
23
+ let penultimateType = penultimateCom.type;
24
+ let penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8))
25
+
26
+ // last L command ends at M
27
+ let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
28
+
29
+ // if last segment is not closing or a lineto
30
+ let skipReorder = pathData[1].type!=='L' && (!isClosingCommand || penultimateType==='L' )
31
+ skipReorder=false
32
+
33
+
34
+ // we can't change starting point for non closed paths
35
+ if (!isClosed ) {
36
+ return pathData
37
+ }
38
+
39
+ let newIndex = 0;
40
+
41
+ if (!skipReorder) {
42
+ //get top most index
43
+ let indices = [];
44
+ for (let i = 0, len = pathData.length; i < len; i++) {
45
+ let com = pathData[i];
46
+ let { type, values } = com;
47
+ if (values.length) {
48
+ let valsL = values.slice(-2)
49
+ let prevL = pathData[i - 1] && pathData[i - 1].type === 'L';
50
+ let nextL = pathData[i + 1] && pathData[i + 1].type === 'L';
51
+ let prevCom = pathData[i - 1] ? pathData[i - 1].type.toUpperCase() : null;
52
+ let nextCom = pathData[i + 1] ? pathData[i + 1].type.toUpperCase() : null;
53
+ let p = { type: type, x: valsL[0], y: valsL[1], dist: 0, index: 0, prevL, nextL, prevCom, nextCom }
54
+ p.index = i
55
+ indices.push(p)
56
+ }
57
+ }
58
+
59
+ //console.log('indices', indices);
60
+
61
+
62
+ // find top most lineto
63
+
64
+ if (linetos.length) {
65
+ let curveAfterLine = indices.filter(com => (com.type !== 'L' && com.type !== 'M') && com.prevCom &&
66
+ com.prevCom === 'L' || com.prevCom==='M' && penultimateType==='L' ).sort((a, b) => a.y - b.y || a.x-b.x)[0]
67
+
68
+ newIndex = curveAfterLine ? curveAfterLine.index - 1 : 0
69
+
70
+ }
71
+ // use top most command
72
+ else {
73
+ indices = indices.sort((a, b) => +a.y.toFixed(1) - +b.y.toFixed(1) || a.x - b.x );
74
+ newIndex = indices[0].index
75
+ }
76
+
77
+ // reorder
78
+ pathData = newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData
79
+ }
80
+
81
+
82
+ len = pathData.length
83
+
84
+ // remove last lineto
85
+ penultimateCom = pathData[len - 2];
86
+ penultimateType = penultimateCom.type;
87
+ penultimateComCoords = penultimateCom.values.slice(-2)
88
+
89
+ isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y
90
+
91
+ if (removeFinalLineto && isClosingCommand) {
92
+ pathData.splice(len - 2, 1)
93
+ }
94
+
95
+ pathDataNew.push(...pathData);
96
+
97
+ return pathDataNew
98
+ }
99
+
100
+
101
+
102
+ /**
103
+ * shift starting point
104
+ */
105
+ export function shiftSvgStartingPoint(pathData, offset) {
106
+ let pathDataL = pathData.length;
107
+ let newStartIndex = 0;
108
+ let lastCommand = pathData[pathDataL - 1]["type"];
109
+ let isClosed = lastCommand.toLowerCase() === "z";
110
+
111
+ if (!isClosed || offset < 1 || pathData.length < 3) {
112
+ return pathData;
113
+ }
114
+
115
+ //exclude Z/z (closepath) command if present
116
+ let trimRight = isClosed ? 1 : 0;
117
+
118
+
119
+ // add explicit lineto
120
+ addClosePathLineto(pathData)
121
+
122
+
123
+ // M start offset
124
+ newStartIndex =
125
+ offset + 1 < pathData.length - 1
126
+ ? offset + 1
127
+ : pathData.length - 1 - trimRight;
128
+
129
+ // slice array to reorder
130
+ let pathDataStart = pathData.slice(newStartIndex);
131
+ let pathDataEnd = pathData.slice(0, newStartIndex);
132
+
133
+ // remove original M
134
+ pathDataEnd.shift();
135
+ let pathDataEndL = pathDataEnd.length;
136
+
137
+ let pathDataEndLastValues, pathDataEndLastXY;
138
+ pathDataEndLastValues = pathDataEnd[pathDataEndL - 1].values || [];
139
+ pathDataEndLastXY = [
140
+ pathDataEndLastValues[pathDataEndLastValues.length - 2],
141
+ pathDataEndLastValues[pathDataEndLastValues.length - 1]
142
+ ];
143
+
144
+
145
+ //remove z(close path) from original pathdata array
146
+ if (trimRight) {
147
+ pathDataStart.pop();
148
+ pathDataEnd.push({
149
+ type: "Z",
150
+ values: []
151
+ });
152
+ }
153
+ // prepend new M command and concatenate array chunks
154
+ pathData = [
155
+ {
156
+ type: "M",
157
+ values: pathDataEndLastXY
158
+ },
159
+ ...pathDataStart,
160
+ ...pathDataEnd,
161
+ ]
162
+
163
+
164
+ return pathData;
165
+ }
166
+
167
+
168
+
169
+ /**
170
+ * Add closing lineto:
171
+ * needed for path reversing or adding points
172
+ */
173
+
174
+ export function addClosePathLineto(pathData) {
175
+ let pathDataL = pathData.length;
176
+ let closed = pathData[pathDataL - 1].type.toLowerCase() === "z" ? true : false;
177
+
178
+ let M = pathData[0];
179
+ let [x0, y0] = [M.values[0], M.values[1]].map(val => { return +val.toFixed(8) });
180
+ let comLast = closed ? pathData[pathDataL - 2] : pathData[pathDataL - 1];
181
+ let comLastL = comLast.values.length;
182
+
183
+ // last explicit on-path coordinates
184
+ let [xL, yL] = [comLast.values[comLastL - 2], comLast.values[comLastL - 1]].map(val => { return +val.toFixed(8) });
185
+
186
+ if (closed && (x0 != xL || y0 != yL)) {
187
+
188
+ pathData.pop();
189
+ pathData.push(
190
+ {
191
+ type: "L",
192
+ values: [x0, y0]
193
+ },
194
+ {
195
+ type: "Z",
196
+ values: []
197
+ }
198
+ );
199
+ }
200
+
201
+ return pathData;
202
+ }
203
+
204
+
@@ -0,0 +1,124 @@
1
+ import { addClosePathLineto } from "./pathData_reorder";
2
+
3
+ /**
4
+ * reverse pathdata
5
+ * make sure all command coordinates are absolute and
6
+ * shorthands are converted to long notation
7
+ */
8
+ export function reversePathData(pathData, {
9
+ arcToCubic = false,
10
+ quadraticToCubic = false,
11
+ toClockwise = false,
12
+ returnD = false
13
+ } = {}) {
14
+
15
+
16
+ /**
17
+ * Add closing lineto:
18
+ * needed for path reversing or adding points
19
+ */
20
+ const addClosePathLineto = (pathData) => {
21
+ let closed = pathData[pathData.length - 1].type.toLowerCase() === "z";
22
+ let M = pathData[0];
23
+ let [x0, y0] = [M.values[0], M.values[1]];
24
+ let lastCom = closed ? pathData[pathData.length - 2] : pathData[pathData.length - 1];
25
+ let [xE, yE] = [lastCom.values[lastCom.values.length - 2], lastCom.values[lastCom.values.length - 1]];
26
+
27
+ if (closed && (x0 != xE || y0 != yE)) {
28
+
29
+ pathData.pop();
30
+ pathData.push(
31
+ {
32
+ type: "L",
33
+ values: [x0, y0]
34
+ },
35
+ {
36
+ type: "Z",
37
+ values: []
38
+ }
39
+ );
40
+ }
41
+ return pathData;
42
+ }
43
+
44
+ // helper to rearrange control points for all command types
45
+ const reverseControlPoints = (type, values) => {
46
+ let controlPoints = [];
47
+ let endPoints = [];
48
+ if (type !== "A") {
49
+ for (let p = 0; p < values.length; p += 2) {
50
+ controlPoints.push([values[p], values[p + 1]]);
51
+ }
52
+ endPoints = controlPoints.pop();
53
+ controlPoints.reverse();
54
+ }
55
+ // is arc
56
+ else {
57
+ //reverse sweep;
58
+ let sweep = values[4] == 0 ? 1 : 0;
59
+ controlPoints = [values[0], values[1], values[2], values[3], sweep];
60
+ endPoints = [values[5], values[6]];
61
+ }
62
+ return { controlPoints, endPoints };
63
+ };
64
+
65
+
66
+ // start compiling new path data
67
+ let pathDataNew = [];
68
+
69
+
70
+ let closed =
71
+ pathData[pathData.length - 1].type.toLowerCase() === "z" ? true : false;
72
+ if (closed) {
73
+ // add lineto closing space between Z and M
74
+ pathData = addClosePathLineto(pathData);
75
+ // remove Z closepath
76
+ pathData.pop();
77
+ }
78
+
79
+ // define last point as new M if path isn't closed
80
+ let valuesLast = pathData[pathData.length - 1].values;
81
+ let valuesLastL = valuesLast.length;
82
+ let M = closed
83
+ ? pathData[0]
84
+ : {
85
+ type: "M",
86
+ values: [valuesLast[valuesLastL - 2], valuesLast[valuesLastL - 1]]
87
+ };
88
+ // starting M stays the same – unless the path is not closed
89
+ pathDataNew.push(M);
90
+
91
+ // reverse path data command order for processing
92
+ pathData.reverse();
93
+ for (let i = 1; i < pathData.length; i++) {
94
+ let com = pathData[i];
95
+ let type = com.type;
96
+ let values = com.values;
97
+ let comPrev = pathData[i - 1];
98
+ let typePrev = comPrev.type;
99
+ let valuesPrev = comPrev.values;
100
+
101
+ // get reversed control points and new end coordinates
102
+ let controlPointsPrev = reverseControlPoints(typePrev, valuesPrev).controlPoints;
103
+ let endPoints = reverseControlPoints(type, values).endPoints;
104
+
105
+ // create new path data
106
+ let newValues = [];
107
+ newValues = [controlPointsPrev, endPoints].flat();
108
+ pathDataNew.push({
109
+ type: typePrev,
110
+ values: newValues.flat()
111
+ });
112
+ }
113
+
114
+ // add previously removed Z close path
115
+ if (closed) {
116
+ pathDataNew.push({
117
+ type: "z",
118
+ values: []
119
+ });
120
+ }
121
+
122
+
123
+ return pathDataNew;
124
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * scale pathData
3
+ */
4
+
5
+ export function scalePathData(pathData, scaleX, scaleY) {
6
+ pathData.forEach((com, i) => {
7
+ let { type, values } = com;
8
+ let typeRel = type.toLowerCase();
9
+
10
+ switch (typeRel) {
11
+ case "a":
12
+ com.values = [
13
+ values[0] * scaleX,
14
+ values[1] * scaleY,
15
+ values[2],
16
+ values[3],
17
+ values[4],
18
+ values[5] * scaleX,
19
+ values[6] * scaleY
20
+ ];
21
+ break;
22
+
23
+ case "h":
24
+ com.values = [values[0] * scaleX];
25
+ break;
26
+
27
+ case "v":
28
+ com.values = [values[0] * scaleY];
29
+ break;
30
+
31
+ default:
32
+ if (values.length) {
33
+ for (let i = 0; i < values.length; i += 2) {
34
+ com.values[i] *= scaleX;
35
+ com.values[i + 1] *= scaleY;
36
+ }
37
+ }
38
+ }
39
+
40
+ });
41
+ return pathData;
42
+ }