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,402 @@
|
|
|
1
|
+
//import { arcToBezier, quadratic2Cubic } from './convert.js';
|
|
2
|
+
//import { getAngle, bezierhasExtreme, getDistance } from "./geometry";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export function parse(path, debug = true) {
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
debug = 'log'
|
|
10
|
+
|
|
11
|
+
const paramCounts = {
|
|
12
|
+
// Move (absolute & relative)
|
|
13
|
+
0x4D: 2, // 'M'
|
|
14
|
+
0x6D: 2, // 'm'
|
|
15
|
+
|
|
16
|
+
// Arc
|
|
17
|
+
0x41: 7, // 'A'
|
|
18
|
+
0x61: 7, // 'a'
|
|
19
|
+
|
|
20
|
+
// Cubic Bézier
|
|
21
|
+
0x43: 6, // 'C'
|
|
22
|
+
0x63: 6, // 'c'
|
|
23
|
+
|
|
24
|
+
// Horizontal Line
|
|
25
|
+
0x48: 1, // 'H'
|
|
26
|
+
0x68: 1, // 'h'
|
|
27
|
+
|
|
28
|
+
// Line To
|
|
29
|
+
0x4C: 2, // 'L'
|
|
30
|
+
0x6C: 2, // 'l'
|
|
31
|
+
|
|
32
|
+
// Quadratic Bézier
|
|
33
|
+
0x51: 4, // 'Q'
|
|
34
|
+
0x71: 4, // 'q'
|
|
35
|
+
|
|
36
|
+
// Smooth Cubic Bézier
|
|
37
|
+
0x53: 4, // 'S'
|
|
38
|
+
0x73: 4, // 's'
|
|
39
|
+
|
|
40
|
+
// Smooth Quadratic Bézier
|
|
41
|
+
0x54: 2, // 'T'
|
|
42
|
+
0x74: 2, // 't'
|
|
43
|
+
|
|
44
|
+
// Vertical Line
|
|
45
|
+
0x56: 1, // 'V'
|
|
46
|
+
0x76: 1, // 'v'
|
|
47
|
+
|
|
48
|
+
// Close Path
|
|
49
|
+
0x5A: 0, // 'Z'
|
|
50
|
+
0x7A: 0 // 'z'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
const commandSet = new Set([
|
|
56
|
+
|
|
57
|
+
//M
|
|
58
|
+
0x4D,
|
|
59
|
+
0x6D,
|
|
60
|
+
|
|
61
|
+
// Arc
|
|
62
|
+
0x41,
|
|
63
|
+
0x61,
|
|
64
|
+
|
|
65
|
+
// Cubic Bézier
|
|
66
|
+
0x43,
|
|
67
|
+
0x63,
|
|
68
|
+
|
|
69
|
+
// Horizontal Line
|
|
70
|
+
0x48,
|
|
71
|
+
0x68,
|
|
72
|
+
|
|
73
|
+
// Line To
|
|
74
|
+
0x4C,
|
|
75
|
+
0x6C,
|
|
76
|
+
|
|
77
|
+
// Quadratic Bézier
|
|
78
|
+
0x51,
|
|
79
|
+
0x71,
|
|
80
|
+
|
|
81
|
+
// Smooth Cubic Bézier
|
|
82
|
+
0x53,
|
|
83
|
+
0x73,
|
|
84
|
+
|
|
85
|
+
// Smooth Quadratic Bézier
|
|
86
|
+
0x54,
|
|
87
|
+
0x74,
|
|
88
|
+
|
|
89
|
+
// Vertical Line
|
|
90
|
+
0x56,
|
|
91
|
+
0x76,
|
|
92
|
+
|
|
93
|
+
// Close Path
|
|
94
|
+
0x5A,
|
|
95
|
+
0x7A,
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
const SPECIAL_SPACES = new Set([
|
|
100
|
+
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
|
|
101
|
+
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
function isSpace(ch) {
|
|
106
|
+
return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators
|
|
107
|
+
// White spaces or comma
|
|
108
|
+
(ch === 0x002C) || (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
|
|
109
|
+
(ch >= 0x1680 && SPECIAL_SPACES.has(ch) >= 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
function isCommandType(code) {
|
|
114
|
+
//return paramCounts.hasOwnProperty(code);
|
|
115
|
+
return commandSet.has(code);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
let i = 0, len = path.length;
|
|
120
|
+
let lastCommand = "";
|
|
121
|
+
let pathData = [];
|
|
122
|
+
let itemCount = -1;
|
|
123
|
+
let val = '';
|
|
124
|
+
let wasE = false;
|
|
125
|
+
let wasSpace = false;
|
|
126
|
+
let floatCount = 0;
|
|
127
|
+
let valueIndex = 0;
|
|
128
|
+
let maxParams = 0;
|
|
129
|
+
let needsNewSegment = false;
|
|
130
|
+
|
|
131
|
+
//absolute/relative or shorthands
|
|
132
|
+
let hasRelatives = false;
|
|
133
|
+
let hasArcs = false;
|
|
134
|
+
let hasShorthands = false;
|
|
135
|
+
let hasQuadratics = false;
|
|
136
|
+
|
|
137
|
+
let relatives = new Set(['m', 'c', 'q', 'l', 'a', 't', 's', 'v', 'h']);
|
|
138
|
+
let shorthands = new Set(['t', 's', 'v', 'h', 'T', 'S', 'V', 'H']);
|
|
139
|
+
let quadratics = new Set(['t', 'q', 'T', 'Q']);
|
|
140
|
+
|
|
141
|
+
//collect errors
|
|
142
|
+
let log = [];
|
|
143
|
+
let feedback;
|
|
144
|
+
|
|
145
|
+
function addSeg() {
|
|
146
|
+
// Create new segment if needed before adding the minus sign
|
|
147
|
+
if (needsNewSegment) {
|
|
148
|
+
|
|
149
|
+
// sanitize implicit linetos
|
|
150
|
+
if (lastCommand === 'M') lastCommand = 'L';
|
|
151
|
+
else if (lastCommand === 'm') lastCommand = 'l';
|
|
152
|
+
|
|
153
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
154
|
+
itemCount++;
|
|
155
|
+
valueIndex = 0;
|
|
156
|
+
needsNewSegment = false;
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function pushVal(checkFloats = false) {
|
|
163
|
+
|
|
164
|
+
// regular value or float
|
|
165
|
+
if (!checkFloats ? val !== '' : floatCount > 0) {
|
|
166
|
+
|
|
167
|
+
// error: no first command
|
|
168
|
+
if (debug && itemCount === -1) {
|
|
169
|
+
|
|
170
|
+
feedback = 'Pathdata must start with M command'
|
|
171
|
+
log.push(feedback)
|
|
172
|
+
|
|
173
|
+
// add M command to collect subsequent errors
|
|
174
|
+
lastCommand = 'M'
|
|
175
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
176
|
+
maxParams = 2;
|
|
177
|
+
valueIndex = 0
|
|
178
|
+
itemCount++
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (lastCommand === 'A' || lastCommand === 'a') {
|
|
183
|
+
val = sanitizeArc()
|
|
184
|
+
pathData[itemCount].values.push(...val);
|
|
185
|
+
|
|
186
|
+
} else {
|
|
187
|
+
// error: leading zeroes
|
|
188
|
+
if (debug && val[1] && val[1] !== '.' && val[0] === '0') {
|
|
189
|
+
feedback = 'Leading zeros not valid: ' + val
|
|
190
|
+
log.push(feedback)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pathData[itemCount].values.push(+val);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
valueIndex++;
|
|
197
|
+
val = '';
|
|
198
|
+
floatCount = 0;
|
|
199
|
+
|
|
200
|
+
// Mark that a new segment is needed if maxParams is reached
|
|
201
|
+
needsNewSegment = valueIndex >= maxParams;
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
function sanitizeArc() {
|
|
209
|
+
|
|
210
|
+
let valLen = val.length;
|
|
211
|
+
let arcSucks = false;
|
|
212
|
+
|
|
213
|
+
// large arc and sweep
|
|
214
|
+
if (valueIndex === 3 && valLen === 2) {
|
|
215
|
+
//console.log('large arc sweep combined', val, +val[0], +val[1]);
|
|
216
|
+
val = [+val[0], +val[1]];
|
|
217
|
+
arcSucks = true
|
|
218
|
+
valueIndex++
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// sweep and final
|
|
222
|
+
else if (valueIndex === 4 && valLen > 1) {
|
|
223
|
+
//console.log('sweep and final', val, val[0], val[1]);
|
|
224
|
+
val = [+val[0], +val[1]];
|
|
225
|
+
arcSucks = true
|
|
226
|
+
valueIndex++
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// large arc, sweep and final pt combined
|
|
230
|
+
else if (valueIndex === 3 && valLen >= 3) {
|
|
231
|
+
//console.log('large arc, sweep and final pt combined', val);
|
|
232
|
+
val = [+val[0], +val[1], +val.substring(2)];
|
|
233
|
+
arcSucks = true
|
|
234
|
+
valueIndex += 2
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//console.log('val arc', val);
|
|
238
|
+
return !arcSucks ? [+val] : val;
|
|
239
|
+
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validateCommand() {
|
|
243
|
+
if (debug) {
|
|
244
|
+
let lastCom = itemCount > 0 ? pathData[itemCount] : 0
|
|
245
|
+
let valLen = lastCom ? lastCom.values.length : 0;
|
|
246
|
+
|
|
247
|
+
//console.log(lastCom, valLen, maxParams, (valLen && valLen < maxParams ) );
|
|
248
|
+
|
|
249
|
+
if ((valLen && valLen < maxParams) || (valLen && valLen > maxParams) || ((lastCommand === 'z' || lastCommand === 'Z') && valLen > 0)) {
|
|
250
|
+
let diff = maxParams - valLen
|
|
251
|
+
feedback = `Pathdata commands in "${lastCommand}" (segment index: ${itemCount}) don't match allowed number of values: ${diff}/${maxParams}`;
|
|
252
|
+
log.push(feedback)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
while (i < len) {
|
|
260
|
+
let char = path[i];
|
|
261
|
+
let charCode = path.charCodeAt(i);
|
|
262
|
+
|
|
263
|
+
// New command
|
|
264
|
+
if (isCommandType(charCode)) {
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
// command is concatenated without whitespace
|
|
268
|
+
if (val !== '') {
|
|
269
|
+
pathData[itemCount].values.push(+val);
|
|
270
|
+
valueIndex++;
|
|
271
|
+
val = '';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// check if previous command was correctly closed
|
|
275
|
+
validateCommand()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
// new command type
|
|
279
|
+
lastCommand = char;
|
|
280
|
+
maxParams = paramCounts[charCode];
|
|
281
|
+
let isM = lastCommand === 'M' || lastCommand === 'm'
|
|
282
|
+
let wasClosePath = itemCount>0 && (pathData[itemCount].type === 'z' || pathData[itemCount].type === 'Z')
|
|
283
|
+
|
|
284
|
+
// add omitted M command after Z
|
|
285
|
+
if ( wasClosePath && !isM ) {
|
|
286
|
+
pathData.push({ type: 'm', values: [0, 0]});
|
|
287
|
+
itemCount++;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
293
|
+
itemCount++;
|
|
294
|
+
|
|
295
|
+
//check types relative arcs or quadratics
|
|
296
|
+
if (!hasRelatives) hasRelatives = relatives.has(lastCommand);
|
|
297
|
+
if (!hasShorthands) hasShorthands = shorthands.has(lastCommand);
|
|
298
|
+
if (!hasQuadratics) hasQuadratics = quadratics.has(lastCommand);
|
|
299
|
+
if (!hasArcs) hasArcs = lastCommand === 'a' || lastCommand === 'A';
|
|
300
|
+
|
|
301
|
+
// reset counters
|
|
302
|
+
wasSpace = false;
|
|
303
|
+
floatCount = 0;
|
|
304
|
+
valueIndex = 0;
|
|
305
|
+
needsNewSegment = false;
|
|
306
|
+
|
|
307
|
+
i++;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Separated by White space
|
|
312
|
+
if (isSpace(charCode)) {
|
|
313
|
+
|
|
314
|
+
// push value
|
|
315
|
+
pushVal()
|
|
316
|
+
|
|
317
|
+
wasSpace = true;
|
|
318
|
+
wasE = false;
|
|
319
|
+
i++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
// if last
|
|
325
|
+
else if (i === len - 1) {
|
|
326
|
+
|
|
327
|
+
val += char;
|
|
328
|
+
//console.log('last', val, char);
|
|
329
|
+
|
|
330
|
+
// push value
|
|
331
|
+
pushVal()
|
|
332
|
+
wasSpace = false;
|
|
333
|
+
wasE = false;
|
|
334
|
+
|
|
335
|
+
validateCommand()
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
// minus or float separated
|
|
341
|
+
if ((!wasE && !wasSpace && charCode === 0x2D) ||
|
|
342
|
+
(!wasE && charCode === 0x2E)
|
|
343
|
+
) {
|
|
344
|
+
|
|
345
|
+
// checkFloats changes condition for value adding
|
|
346
|
+
let checkFloats = charCode === 0x2E;
|
|
347
|
+
|
|
348
|
+
// new val
|
|
349
|
+
pushVal(checkFloats);
|
|
350
|
+
|
|
351
|
+
// new segment
|
|
352
|
+
addSeg()
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
// concatenated floats
|
|
356
|
+
if (checkFloats) {
|
|
357
|
+
floatCount++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
// regular splitting
|
|
363
|
+
else {
|
|
364
|
+
addSeg()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
val += char;
|
|
368
|
+
|
|
369
|
+
// e/scientific notation in value
|
|
370
|
+
wasE = (charCode === 0x45 || charCode === 0x65);
|
|
371
|
+
wasSpace = false;
|
|
372
|
+
i++;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
//validate final
|
|
376
|
+
validateCommand()
|
|
377
|
+
|
|
378
|
+
// return error log
|
|
379
|
+
if (debug && log.length) {
|
|
380
|
+
feedback = 'Invalid path data:\n' + log.join('\n')
|
|
381
|
+
if (debug === 'log') {
|
|
382
|
+
console.log(feedback);
|
|
383
|
+
} else {
|
|
384
|
+
throw new Error(feedback)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
pathData[0].type = 'M'
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
pathData: pathData,
|
|
395
|
+
hasRelatives: hasRelatives,
|
|
396
|
+
hasShorthands: hasShorthands,
|
|
397
|
+
hasQuadratics: hasQuadratics,
|
|
398
|
+
hasArcs: hasArcs
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
}
|
|
402
|
+
|