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.
- package/README.md +28 -1
- package/dist/svg-path-simplify.esm.js +4040 -0
- package/dist/svg-path-simplify.esm.min.js +1 -0
- package/dist/svg-path-simplify.js +4065 -0
- package/dist/svg-path-simplify.min.js +1 -0
- package/dist/svg-path-simplify.node.js +4062 -0
- package/dist/svg-path-simplify.node.min.js +1 -0
- package/index.html +222 -0
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/index.js +18 -3
- package/src/pathData_simplify_cubic.js +324 -0
- package/src/pathData_simplify_cubic_arr.js +50 -0
- package/src/pathData_simplify_cubic_extrapolate.js +220 -0
- package/src/pathSimplify-main.js +294 -0
- package/src/svgii/...parse.js +402 -0
- package/src/svgii/geometry.js +1096 -0
- package/src/svgii/geometry_area.js +265 -0
- package/src/svgii/geometry_bbox.js +223 -0
- package/src/svgii/pathData_analyze.js +896 -0
- package/src/svgii/pathData_convert.js +1180 -0
- package/src/svgii/pathData_parse.js +487 -0
- package/src/svgii/pathData_remove_collinear.js +85 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -0
- package/src/svgii/pathData_reorder.js +204 -0
- package/src/svgii/pathData_reverse.js +124 -0
- package/src/svgii/pathData_scale.js +42 -0
- package/src/svgii/pathData_split.js +449 -0
- package/src/svgii/pathData_stringify.js +146 -0
- package/src/svgii/pathData_toPolygon.js +92 -0
- package/src/svgii/pathdata_cleanup.js +363 -0
- package/src/svgii/poly_analyze.js +172 -0
- package/src/svgii/poly_to_pathdata.js +185 -0
- package/src/svgii/rounding.js +154 -0
- package/src/svgii/simplify.js +248 -0
- package/src/svgii/simplify_bezier.js +470 -0
- package/src/svgii/simplify_linetos.js +93 -0
- package/src/svgii/simplify_polygon.js +135 -0
- package/src/svgii/stringify.js +103 -0
- package/src/svgii/svg_cleanup.js +80 -0
- package/src/svgii/visualize.js +317 -0
- 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
|
+
}
|