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,487 @@
|
|
|
1
|
+
//import { arcToBezier, quadratic2Cubic } from './convert.js';
|
|
2
|
+
//import { getAngle, bezierhasExtreme, getDistance } from "./geometry";
|
|
3
|
+
import { pathDataToAbsoluteOrRelative, pathDataToLonghands, pathDataArcsToCubics, pathDataQuadraticToCubic } from './pathData_convert.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* parse normalized
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function normalizePathData(pathData = [],
|
|
12
|
+
{
|
|
13
|
+
toAbsolute = true,
|
|
14
|
+
toLonghands = true,
|
|
15
|
+
quadraticToCubic = false,
|
|
16
|
+
arcToCubic = false,
|
|
17
|
+
arcAccuracy = 2,
|
|
18
|
+
} = {},
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
hasRelatives = true, hasShorthands = true, hasQuadratics = true, hasArcs = true, testTypes = false
|
|
22
|
+
} = {}
|
|
23
|
+
) {
|
|
24
|
+
|
|
25
|
+
// pathdata properties - test= true adds a manual test
|
|
26
|
+
if (testTypes) {
|
|
27
|
+
//console.log('test for conversions');
|
|
28
|
+
let commands = Array.from(new Set(pathData.map(com => com.type))).join('');
|
|
29
|
+
hasRelatives = /[lcqamts]/gi.test(commands);
|
|
30
|
+
hasQuadratics = /[qt]/gi.test(commands);
|
|
31
|
+
hasArcs = /[a]/gi.test(commands);
|
|
32
|
+
hasShorthands = /[vhst]/gi.test(commands);
|
|
33
|
+
isPoly = /[mlz]/gi.test(commands);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* normalize:
|
|
39
|
+
* convert to all absolute
|
|
40
|
+
* all longhands
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
if ((hasQuadratics && quadraticToCubic) || (hasArcs && arcToCubic)) {
|
|
44
|
+
toLonghands = true
|
|
45
|
+
toAbsolute = true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (hasRelatives && toAbsolute) pathData = pathDataToAbsoluteOrRelative(pathData, false);
|
|
49
|
+
if (hasShorthands && toLonghands) pathData = pathDataToLonghands(pathData, -1, false);
|
|
50
|
+
if (hasArcs && arcToCubic) pathData = pathDataArcsToCubics(pathData, arcAccuracy);
|
|
51
|
+
if (hasQuadratics && quadraticToCubic) pathData = pathDataQuadraticToCubic(pathData);
|
|
52
|
+
|
|
53
|
+
return pathData;
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parsePathDataNormalized(d,
|
|
58
|
+
{
|
|
59
|
+
// necessary for most calculations
|
|
60
|
+
toAbsolute = true,
|
|
61
|
+
toLonghands = true,
|
|
62
|
+
|
|
63
|
+
// not necessary unless you need cubics only
|
|
64
|
+
quadraticToCubic = false,
|
|
65
|
+
|
|
66
|
+
// mostly a fallback if arc calculations fail
|
|
67
|
+
arcToCubic = false,
|
|
68
|
+
// arc to cubic precision - adds more segments for better precision
|
|
69
|
+
arcAccuracy = 4,
|
|
70
|
+
} = {}
|
|
71
|
+
) {
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
let pathDataObj = parsePathDataString(d);
|
|
75
|
+
let { hasRelatives, hasShorthands, hasQuadratics, hasArcs } = pathDataObj;
|
|
76
|
+
let pathData = pathDataObj.pathData;
|
|
77
|
+
|
|
78
|
+
// normalize
|
|
79
|
+
pathData = normalizePathData(pathData,
|
|
80
|
+
{ toAbsolute, toLonghands, quadraticToCubic, arcToCubic, arcAccuracy },
|
|
81
|
+
//{test:true}
|
|
82
|
+
{ hasRelatives, hasShorthands, hasQuadratics, hasArcs }
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return pathData;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const commandSet = new Set([
|
|
90
|
+
0x4D, 0x6D, 0x41, 0x61, 0x43, 0x63,
|
|
91
|
+
0x4C, 0x6C, 0x51, 0x71, 0x53, 0x73,
|
|
92
|
+
0x54, 0x74, 0x48, 0x68, 0x56, 0x76,
|
|
93
|
+
0x5A, 0x7A
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const paramCountsArr = new Uint8Array(128);
|
|
97
|
+
// M starting point
|
|
98
|
+
paramCountsArr[0x4D] = 2;
|
|
99
|
+
paramCountsArr[0x6D] = 2;
|
|
100
|
+
|
|
101
|
+
// A Arc
|
|
102
|
+
paramCountsArr[0x41] = 7
|
|
103
|
+
paramCountsArr[0x61] = 7
|
|
104
|
+
|
|
105
|
+
// C Cubic Bézier
|
|
106
|
+
paramCountsArr[0x43] = 6
|
|
107
|
+
paramCountsArr[0x63] = 6
|
|
108
|
+
|
|
109
|
+
// L Line To
|
|
110
|
+
paramCountsArr[0x4C] = 2
|
|
111
|
+
paramCountsArr[0x6C] = 2
|
|
112
|
+
|
|
113
|
+
// Q Quadratic Bézier
|
|
114
|
+
paramCountsArr[0x51] = 4
|
|
115
|
+
paramCountsArr[0x71] = 4
|
|
116
|
+
|
|
117
|
+
// S Smooth Cubic Bézier
|
|
118
|
+
paramCountsArr[0x53] = 4
|
|
119
|
+
paramCountsArr[0x73] = 4
|
|
120
|
+
|
|
121
|
+
// T Smooth Quadratic Bézier
|
|
122
|
+
paramCountsArr[0x54] = 2
|
|
123
|
+
paramCountsArr[0x74] = 2
|
|
124
|
+
|
|
125
|
+
// H Horizontal Line
|
|
126
|
+
paramCountsArr[0x48] = 1
|
|
127
|
+
paramCountsArr[0x68] = 1
|
|
128
|
+
|
|
129
|
+
// V Vertical Line
|
|
130
|
+
paramCountsArr[0x56] = 1
|
|
131
|
+
paramCountsArr[0x76] = 1
|
|
132
|
+
|
|
133
|
+
// Z Close Path
|
|
134
|
+
paramCountsArr[0x5A] = 0
|
|
135
|
+
paramCountsArr[0x7A] = 0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
export function parsePathDataString(d, debug = true) {
|
|
140
|
+
d = d.trim();
|
|
141
|
+
|
|
142
|
+
if (d === '') {
|
|
143
|
+
return {
|
|
144
|
+
pathData: [],
|
|
145
|
+
hasRelatives: false,
|
|
146
|
+
hasShorthands: false,
|
|
147
|
+
hasQuadratics: false,
|
|
148
|
+
hasArcs: false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const SPECIAL_SPACES = new Set([
|
|
153
|
+
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
|
|
154
|
+
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const isSpace = (ch) => {
|
|
159
|
+
return (ch === 0x20) || (ch === 0x002C) || // White spaces or comma
|
|
160
|
+
(ch === 0x0A) || (ch === 0x0D) || // nl cr
|
|
161
|
+
(ch === 0x2028) || (ch === 0x2029) || // Line terminators
|
|
162
|
+
(ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
|
|
163
|
+
(ch >= 0x1680 && SPECIAL_SPACES.has(ch));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
let i = 0, len = d.length;
|
|
168
|
+
let lastCommand = "";
|
|
169
|
+
let pathData = [];
|
|
170
|
+
let itemCount = -1;
|
|
171
|
+
let val = '';
|
|
172
|
+
let wasE = false;
|
|
173
|
+
let floatCount = 0;
|
|
174
|
+
let valueIndex = 0;
|
|
175
|
+
let maxParams = 0;
|
|
176
|
+
let needsNewSegment = false;
|
|
177
|
+
let foundCommands = new Set([]);
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// collect errors
|
|
181
|
+
let log = [];
|
|
182
|
+
let feedback;
|
|
183
|
+
|
|
184
|
+
const addSeg = () => {
|
|
185
|
+
// Create new segment if needed before adding the minus sign
|
|
186
|
+
if (needsNewSegment) {
|
|
187
|
+
|
|
188
|
+
// sanitize implicit linetos
|
|
189
|
+
if (lastCommand === 'M') lastCommand = 'L';
|
|
190
|
+
else if (lastCommand === 'm') lastCommand = 'l';
|
|
191
|
+
|
|
192
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
193
|
+
|
|
194
|
+
itemCount++;
|
|
195
|
+
valueIndex = 0;
|
|
196
|
+
needsNewSegment = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const pushVal = (checkFloats = false) => {
|
|
201
|
+
|
|
202
|
+
// regular value or float
|
|
203
|
+
if (!checkFloats ? val !== '' : floatCount > 0) {
|
|
204
|
+
|
|
205
|
+
// error: no first command
|
|
206
|
+
if (debug && itemCount === -1) {
|
|
207
|
+
|
|
208
|
+
feedback = 'Pathdata must start with M command'
|
|
209
|
+
log.push(feedback)
|
|
210
|
+
|
|
211
|
+
// add M command to collect subsequent errors
|
|
212
|
+
lastCommand = 'M'
|
|
213
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
214
|
+
maxParams = 2;
|
|
215
|
+
valueIndex = 0
|
|
216
|
+
itemCount++
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (lastCommand === 'A' || lastCommand === 'a') {
|
|
221
|
+
val = sanitizeArc()
|
|
222
|
+
//console.log('arc', val);
|
|
223
|
+
pathData[itemCount].values.push(...val);
|
|
224
|
+
|
|
225
|
+
} else {
|
|
226
|
+
// error: leading zeroes
|
|
227
|
+
if (debug && val[1] && val[1] !== '.' && val[0] === '0') {
|
|
228
|
+
feedback = `${itemCount}. command: Leading zeros not valid: ${val}`
|
|
229
|
+
log.push(feedback)
|
|
230
|
+
}
|
|
231
|
+
pathData[itemCount].values.push(+val);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
valueIndex++;
|
|
235
|
+
val = '';
|
|
236
|
+
floatCount = 0;
|
|
237
|
+
|
|
238
|
+
// Mark that a new segment is needed if maxParams is reached
|
|
239
|
+
needsNewSegment = valueIndex >= maxParams;
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const sanitizeArc = () => {
|
|
245
|
+
|
|
246
|
+
let valLen = val.length;
|
|
247
|
+
let arcSucks = false;
|
|
248
|
+
|
|
249
|
+
// large arc and sweep
|
|
250
|
+
if (valueIndex === 3 && valLen === 2) {
|
|
251
|
+
//console.log('large arc sweep combined', val, +val[0], +val[1]);
|
|
252
|
+
val = [+val[0], +val[1]];
|
|
253
|
+
arcSucks = true
|
|
254
|
+
valueIndex++
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// sweep and final
|
|
258
|
+
else if (valueIndex === 4 && valLen > 1) {
|
|
259
|
+
//console.log('sweep and final', val, val[0], val[1]);
|
|
260
|
+
val = [+val[0], +val[1]];
|
|
261
|
+
arcSucks = true
|
|
262
|
+
valueIndex++
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// large arc, sweep and final pt combined
|
|
266
|
+
else if (valueIndex === 3 && valLen >= 3) {
|
|
267
|
+
//console.log('large arc, sweep and final pt combined', val);
|
|
268
|
+
val = [+val[0], +val[1], +val.substring(2)];
|
|
269
|
+
arcSucks = true
|
|
270
|
+
valueIndex += 2
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//console.log('val arc', val);
|
|
274
|
+
return !arcSucks ? [+val] : val;
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const validateCommand = () => {
|
|
279
|
+
|
|
280
|
+
if (itemCount > 0) {
|
|
281
|
+
let lastCom = pathData[itemCount];
|
|
282
|
+
let valLen = lastCom.values.length;
|
|
283
|
+
|
|
284
|
+
if ((valLen && valLen < maxParams) || (valLen && valLen > maxParams) || ((lastCommand === 'z' || lastCommand === 'Z') && valLen > 0)) {
|
|
285
|
+
let diff = maxParams - valLen;
|
|
286
|
+
feedback = `${itemCount}. command of type "${lastCommand}": ${diff} values too few - ${maxParams} expected`;
|
|
287
|
+
|
|
288
|
+
let prevFeedback = log[log.length - 1];
|
|
289
|
+
|
|
290
|
+
if (prevFeedback !== feedback) {
|
|
291
|
+
log.push(feedback)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
let isE = false;
|
|
299
|
+
let isMinusorPlus = false;
|
|
300
|
+
let isDot = false;
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
while (i < len) {
|
|
304
|
+
|
|
305
|
+
let charCode = d.charCodeAt(i);
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
let isDigit = (charCode > 47 && charCode < 58);
|
|
309
|
+
if (!isDigit) {
|
|
310
|
+
isE = (charCode === 101 || charCode === 69);
|
|
311
|
+
isMinusorPlus = (charCode === 45 || charCode === 43);
|
|
312
|
+
isDot = charCode === 46;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* number related:
|
|
317
|
+
* digit, e-notation, dot or -/+ operator
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
if (
|
|
321
|
+
isDigit ||
|
|
322
|
+
isMinusorPlus ||
|
|
323
|
+
isDot ||
|
|
324
|
+
isE
|
|
325
|
+
) {
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
// minus or float/dot separated: 0x2D=hyphen; 0x2E=dot
|
|
329
|
+
if (!wasE && (charCode === 0x2D || charCode === 0x2E)) {
|
|
330
|
+
|
|
331
|
+
// checkFloats changes condition for value adding
|
|
332
|
+
let checkFloats = charCode === 0x2E;
|
|
333
|
+
|
|
334
|
+
// new val
|
|
335
|
+
pushVal(checkFloats);
|
|
336
|
+
|
|
337
|
+
// new segment
|
|
338
|
+
addSeg()
|
|
339
|
+
|
|
340
|
+
// concatenated floats
|
|
341
|
+
if (checkFloats) {
|
|
342
|
+
floatCount++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
// regular splitting
|
|
348
|
+
else {
|
|
349
|
+
//console.log('reg', d[i]);
|
|
350
|
+
addSeg()
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
//isNumber
|
|
354
|
+
val += d[i];
|
|
355
|
+
|
|
356
|
+
// e/scientific notation in value
|
|
357
|
+
wasE = isE;
|
|
358
|
+
i++;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Separated by white space
|
|
365
|
+
*/
|
|
366
|
+
if ((charCode < 48 || charCode > 5759) && isSpace(charCode)) {
|
|
367
|
+
|
|
368
|
+
// push value
|
|
369
|
+
pushVal()
|
|
370
|
+
|
|
371
|
+
i++;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* New command introduced by
|
|
378
|
+
* alphabetic A-Z character
|
|
379
|
+
*/
|
|
380
|
+
if (charCode > 64) {
|
|
381
|
+
|
|
382
|
+
// is valid command
|
|
383
|
+
let isValid = commandSet.has(charCode);
|
|
384
|
+
|
|
385
|
+
if (!isValid) {
|
|
386
|
+
feedback = `${itemCount}. command "${d[i]}" is not a valid type`;
|
|
387
|
+
log.push(feedback);
|
|
388
|
+
i++
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
// command is concatenated without whitespace
|
|
394
|
+
if (val !== '') {
|
|
395
|
+
pathData[itemCount].values.push(+val);
|
|
396
|
+
valueIndex++;
|
|
397
|
+
val = '';
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// check if previous command was correctly closed
|
|
401
|
+
if (debug) validateCommand()
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
lastCommand = d[i];
|
|
405
|
+
maxParams = paramCountsArr[charCode];
|
|
406
|
+
let isM = lastCommand === 'M' || lastCommand === 'm'
|
|
407
|
+
let wasClosePath = itemCount > 0 && (pathData[itemCount].type === 'z' || pathData[itemCount].type === 'Z')
|
|
408
|
+
|
|
409
|
+
foundCommands.add(lastCommand);
|
|
410
|
+
|
|
411
|
+
// add omitted M command after Z
|
|
412
|
+
if (wasClosePath && !isM) {
|
|
413
|
+
pathData.push({ type: 'm', values: [0, 0] });
|
|
414
|
+
itemCount++;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// init new command
|
|
418
|
+
pathData.push({ type: lastCommand, values: [] });
|
|
419
|
+
itemCount++;
|
|
420
|
+
|
|
421
|
+
// reset counters
|
|
422
|
+
floatCount = 0;
|
|
423
|
+
valueIndex = 0;
|
|
424
|
+
needsNewSegment = false;
|
|
425
|
+
|
|
426
|
+
i++;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
// exceptions - prevent infinite loop
|
|
432
|
+
if (!isDigit) {
|
|
433
|
+
feedback = `${itemCount}. ${d[i]} is not a valid separarator or token`;
|
|
434
|
+
log.push(feedback);
|
|
435
|
+
val = '';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
i++;
|
|
439
|
+
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// final value
|
|
443
|
+
pushVal()
|
|
444
|
+
if (debug) validateCommand()
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
// return error log
|
|
448
|
+
if (debug && log.length) {
|
|
449
|
+
feedback = 'Invalid path data:\n' + log.join('\n')
|
|
450
|
+
if (debug === 'log') {
|
|
451
|
+
console.log(feedback);
|
|
452
|
+
} else {
|
|
453
|
+
throw new Error(feedback)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
pathData[0].type = 'M'
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* check if absolute/relative or
|
|
461
|
+
* shorthands are present
|
|
462
|
+
* to specify if normalization is required
|
|
463
|
+
*/
|
|
464
|
+
//check types relative arcs or quadratics
|
|
465
|
+
let commands = Array.from(foundCommands).join('');
|
|
466
|
+
let hasRelatives = /[lcqamts]/g.test(commands);
|
|
467
|
+
let hasShorthands = /[vhst]/gi.test(commands);
|
|
468
|
+
let hasArcs = /[a]/gi.test(commands);
|
|
469
|
+
let hasQuadratics = /[qt]/gi.test(commands);
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
pathData,
|
|
474
|
+
hasRelatives,
|
|
475
|
+
hasShorthands,
|
|
476
|
+
hasQuadratics,
|
|
477
|
+
hasArcs
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
export function stringifyPathData(pathData) {
|
|
485
|
+
return pathData.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
486
|
+
}
|
|
487
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getSquareDistance } from "./geometry.js";
|
|
2
|
+
import { getPolygonArea } from "./geometry_area.js";
|
|
3
|
+
import { renderPoint } from "./visualize.js";
|
|
4
|
+
|
|
5
|
+
export function pathDataRemoveColinear(pathData, tolerance = 1, flatBezierToLinetos = true) {
|
|
6
|
+
|
|
7
|
+
let pathDataN = [pathData[0]];
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let lastType = 'L';
|
|
11
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
12
|
+
let p0 = M;
|
|
13
|
+
let p = M
|
|
14
|
+
let isClosed = pathData[pathData.length - 1].type.toLowerCase() === 'z'
|
|
15
|
+
|
|
16
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
17
|
+
let comPrev = pathData[c - 1];
|
|
18
|
+
let com = pathData[c];
|
|
19
|
+
let comN = pathData[c + 1] || pathData[l - 1];
|
|
20
|
+
let p1 = comN.type === 'Z' ? M : { x: comN.values[comN.values.length - 2], y: comN.values[comN.values.length - 1] }
|
|
21
|
+
|
|
22
|
+
let { type, values } = com;
|
|
23
|
+
let valsL = values.slice(-2)
|
|
24
|
+
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
25
|
+
|
|
26
|
+
let cpts = type === 'C' ?
|
|
27
|
+
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
28
|
+
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
let area = getPolygonArea([p0, ...cpts, p, p1], true)
|
|
32
|
+
let distSquare = getSquareDistance(p0, p)
|
|
33
|
+
let distMax = distSquare / 500 * tolerance
|
|
34
|
+
|
|
35
|
+
let isFlat = area < distMax
|
|
36
|
+
|
|
37
|
+
if(!flatBezierToLinetos && type==='C') isFlat = false;
|
|
38
|
+
//let isFlat = flatBezierToLinetos && type === 'C' ? area < distMax : false
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
// convert flat beziers to linetos
|
|
42
|
+
if (flatBezierToLinetos && type === 'C') {
|
|
43
|
+
|
|
44
|
+
let areaBez = getPolygonArea([p0, ...cpts, p], true)
|
|
45
|
+
let isFlatBez = areaBez < distSquare / 1000
|
|
46
|
+
|
|
47
|
+
if (isFlatBez && comPrev.type !== 'C') {
|
|
48
|
+
com.type = "L"
|
|
49
|
+
com.values = valsL
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// update end point
|
|
56
|
+
p0 = p;
|
|
57
|
+
|
|
58
|
+
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
59
|
+
if (type !== 'A' && isFlat && c < l - 1) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (type === 'M') {
|
|
64
|
+
M = p
|
|
65
|
+
p0 = M
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
else if (type === 'Z') {
|
|
69
|
+
p0 = M;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// proceed and add command
|
|
73
|
+
pathDataN.push(com)
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// add close path
|
|
78
|
+
if (isClosed) {
|
|
79
|
+
//pathDataN.push({ type: 'Z', values: [] })
|
|
80
|
+
}
|
|
81
|
+
//console.log('pathDataN', pathDataN);
|
|
82
|
+
|
|
83
|
+
return pathDataN;
|
|
84
|
+
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function removeZeroLengthLinetos(pathData) {
|
|
2
|
+
|
|
3
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
4
|
+
let p0 = M
|
|
5
|
+
let p = p0
|
|
6
|
+
|
|
7
|
+
let pathDataN = [pathData[0]]
|
|
8
|
+
|
|
9
|
+
for (let c = 1, l = pathData.length; c < l; c++) {
|
|
10
|
+
let com = pathData[c];
|
|
11
|
+
let { type, values } = com;
|
|
12
|
+
|
|
13
|
+
let valsL = values.slice(-2);
|
|
14
|
+
p = { x: valsL[0], y: valsL[1] };
|
|
15
|
+
|
|
16
|
+
// skip lineto
|
|
17
|
+
if (type === 'L' && p.x === p0.x && p.y === p0.y) {
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pathDataN.push(com)
|
|
22
|
+
p0 = p;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
return pathDataN
|
|
27
|
+
|
|
28
|
+
}
|