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,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
+