svg-path-simplify 0.1.3 → 0.2.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 +10 -0
- package/dist/svg-path-simplify.esm.js +3905 -1533
- package/dist/svg-path-simplify.esm.min.js +13 -1
- package/dist/svg-path-simplify.js +3923 -1551
- package/dist/svg-path-simplify.min.js +13 -1
- package/dist/svg-path-simplify.min.js.gz +0 -0
- package/index.html +61 -31
- package/package.json +3 -5
- package/src/constants.js +3 -0
- package/src/index-node.js +0 -1
- package/src/index.js +26 -0
- package/src/pathData_simplify_cubic.js +74 -31
- package/src/pathData_simplify_cubicsToArcs.js +566 -0
- package/src/pathData_simplify_harmonize_cpts.js +170 -0
- package/src/pathData_simplify_revertToquadratics.js +21 -0
- package/src/pathSimplify-main.js +253 -86
- package/src/poly-fit-curve-schneider.js +570 -0
- package/src/simplify_poly_RDP.js +146 -0
- package/src/simplify_poly_radial_distance.js +100 -0
- package/src/svg_getViewbox.js +1 -1
- package/src/svgii/geometry.js +389 -63
- package/src/svgii/geometry_area.js +2 -1
- package/src/svgii/pathData_analyze.js +259 -212
- package/src/svgii/pathData_convert.js +91 -663
- package/src/svgii/pathData_fromPoly.js +12 -0
- package/src/svgii/pathData_parse.js +90 -89
- package/src/svgii/pathData_parse_els.js +3 -0
- package/src/svgii/pathData_parse_fontello.js +449 -0
- package/src/svgii/pathData_remove_collinear.js +44 -37
- package/src/svgii/pathData_reorder.js +2 -1
- package/src/svgii/pathData_simplify_redraw.js +343 -0
- package/src/svgii/pathData_simplify_refineCorners.js +18 -9
- package/src/svgii/pathData_simplify_refineExtremes.js +19 -78
- package/src/svgii/pathData_split.js +42 -45
- package/src/svgii/pathData_toPolygon.js +130 -4
- package/src/svgii/poly_analyze.js +470 -14
- package/src/svgii/poly_to_pathdata.js +224 -19
- package/src/svgii/rounding.js +55 -112
- package/src/svgii/svg_cleanup.js +13 -1
- package/src/svgii/visualize.js +8 -3
- package/{debug.cjs → tests/debug.cjs} +3 -0
- /package/{test.js → tests/test.js} +0 -0
- /package/{testSVG.js → tests/testSVG.js} +0 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
|
|
2
|
+
const paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 };
|
|
3
|
+
|
|
4
|
+
// report errors for debugging
|
|
5
|
+
const addError = (state, errorCode) => {
|
|
6
|
+
let value = state.path[state.index]
|
|
7
|
+
let com = errorCode !== 6 ? state.path[state.segmentStart] : value;
|
|
8
|
+
let typeInfo = errorCode != 0 ? `type: »${com}«` : ``
|
|
9
|
+
|
|
10
|
+
console.log(state);
|
|
11
|
+
|
|
12
|
+
if (state.debug) {
|
|
13
|
+
|
|
14
|
+
state.log.push(`Command #${state.lastIndex} | ${state.index}) ${typeInfo} value: »${value}«: ${errors[errorCode]}`);
|
|
15
|
+
|
|
16
|
+
if (errorCode === 0) {
|
|
17
|
+
state.path = 'M' + state.path
|
|
18
|
+
state.max++
|
|
19
|
+
state.index--
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
else if (errorCode === 5) {
|
|
23
|
+
/*
|
|
24
|
+
let idx = state.index;
|
|
25
|
+
state.path = state.path.slice(0, idx + 2) + state.path.slice(idx + 3)
|
|
26
|
+
state.max--
|
|
27
|
+
console.log(state.path);
|
|
28
|
+
//state.index-=1
|
|
29
|
+
//state.index+=1
|
|
30
|
+
|
|
31
|
+
//state.index++;
|
|
32
|
+
//finalizeSegment(state)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// skip to next segment
|
|
36
|
+
state.index = getNextCommandIndex(state)
|
|
37
|
+
//state.lastIndex++
|
|
38
|
+
state.skipped++
|
|
39
|
+
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// missing param
|
|
45
|
+
else if (errorCode === 2) {
|
|
46
|
+
|
|
47
|
+
// skip to next segment
|
|
48
|
+
state.index = getNextCommandIndex(state)
|
|
49
|
+
state.lastIndex++
|
|
50
|
+
//state.skipped++
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
state.index++;
|
|
56
|
+
|
|
57
|
+
} else {
|
|
58
|
+
state.err.push(`Command #${state.result.length} | ${state.index}) ${typeInfo} value: »${value}«: ${errors[errorCode]}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const errors = [
|
|
64
|
+
'Paths must start with `M` or `m` command',
|
|
65
|
+
'Arc largeArc or sweep flag can only be 1 or 0',
|
|
66
|
+
'Missing param',
|
|
67
|
+
'Not a number – param should start with 0..9 or `.`',
|
|
68
|
+
'Trailing zeroes are not permitted',
|
|
69
|
+
'Invalid float exponent',
|
|
70
|
+
'Invalid command'
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const SPECIAL_SPACES = [
|
|
74
|
+
0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
|
|
75
|
+
0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
function isSpaceOrComma(ch) {
|
|
80
|
+
return (ch === 0x20) || (ch === 0x002C) || // White spaces or comma
|
|
81
|
+
(ch === 0x0A) || (ch === 0x0D) || // nl cr
|
|
82
|
+
(ch === 0x2028) || (ch === 0x2029) || // Line terminators
|
|
83
|
+
(ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
|
|
84
|
+
(ch > 5759 && SPECIAL_SPACES.indexOf(ch) >= 0);
|
|
85
|
+
//(ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const COMMAND_LOOKUP = new Uint8Array(128);
|
|
90
|
+
'achlmqstvz'.split('').forEach(ch => {
|
|
91
|
+
const code = ch.charCodeAt(0);
|
|
92
|
+
COMMAND_LOOKUP[code] = 1;
|
|
93
|
+
COMMAND_LOOKUP[code - 32] = 1; // uppercase
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
function isCommand(code) {
|
|
98
|
+
//return code < 128 && COMMAND_LOOKUP[code] === 1;
|
|
99
|
+
return code > 64 && COMMAND_LOOKUP[code];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function isDigit(code) {
|
|
103
|
+
// 0..9
|
|
104
|
+
return (code > 47 && code < 58);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isDigitStart(code) {
|
|
108
|
+
return (code > 47 && code < 58) || /* 0..9 */
|
|
109
|
+
code === 0x2E || /* . */
|
|
110
|
+
code === 0x2D || /* - */
|
|
111
|
+
code === 0x2B /* + */
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
function skipSpaces(state) {
|
|
116
|
+
while (state.index < state.max && isSpaceOrComma(state.path.charCodeAt(state.index))) {
|
|
117
|
+
state.index++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function scanArcFlag(state) {
|
|
123
|
+
let ch = state.path.charCodeAt(state.index);
|
|
124
|
+
|
|
125
|
+
// zero
|
|
126
|
+
if (ch === 0x30) {
|
|
127
|
+
state.param = 0;
|
|
128
|
+
state.index++;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// one
|
|
133
|
+
if (ch === 0x31) {
|
|
134
|
+
state.param = 1;
|
|
135
|
+
state.index++;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
addError(state, 1)
|
|
140
|
+
//state.err.push(`${state.index}: ${errors[1]}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// collect command data
|
|
144
|
+
function State(path, debug = true) {
|
|
145
|
+
this.index = 0;
|
|
146
|
+
this.path = path;
|
|
147
|
+
this.max = path.length;
|
|
148
|
+
this.result = [];
|
|
149
|
+
this.param = 0;
|
|
150
|
+
this.err = [];
|
|
151
|
+
this.log = [];
|
|
152
|
+
this.segmentStart = 0;
|
|
153
|
+
this.data = [];
|
|
154
|
+
this.lastType = '';
|
|
155
|
+
this.allTypes = new Set([])
|
|
156
|
+
this.debug = debug
|
|
157
|
+
this.skipped=0
|
|
158
|
+
this.lastIndex=0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Scan command
|
|
164
|
+
*/
|
|
165
|
+
function scanSegment(state) {
|
|
166
|
+
let max = state.max;
|
|
167
|
+
let cmdCode = state.path.charCodeAt(state.index);
|
|
168
|
+
let is_arc = (cmdCode === 0x61 || cmdCode === 0x41);
|
|
169
|
+
|
|
170
|
+
// Error 6:not valid command
|
|
171
|
+
if (!isCommand(cmdCode)) {
|
|
172
|
+
addError(state, 6)
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let cmd = state.path[state.index];
|
|
177
|
+
let cmdLC = cmd.toLowerCase();
|
|
178
|
+
let needParams = paramCounts[cmdLC];
|
|
179
|
+
|
|
180
|
+
state.segmentStart = state.index;
|
|
181
|
+
state.data = [];
|
|
182
|
+
|
|
183
|
+
state.index++;
|
|
184
|
+
skipSpaces(state);
|
|
185
|
+
|
|
186
|
+
// Z : close path
|
|
187
|
+
if (!needParams) {
|
|
188
|
+
finalizeSegment(state);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
while (state.index < max) {
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < needParams; i++) {
|
|
196
|
+
|
|
197
|
+
// Error 2: missing param
|
|
198
|
+
if (state.index < max && isCommand(state.path.charCodeAt(state.index))) {
|
|
199
|
+
addError(state, 2)
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// is Arc command
|
|
204
|
+
if (is_arc && (i === 3 || i === 4)) scanArcFlag(state);
|
|
205
|
+
else scanParam(state);
|
|
206
|
+
|
|
207
|
+
if (state.err.length) {
|
|
208
|
+
//state.index++
|
|
209
|
+
//continue
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
state.data.push(state.param);
|
|
214
|
+
skipSpaces(state);
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (state.index >= max) break;
|
|
219
|
+
|
|
220
|
+
// If next is not number start → segment done
|
|
221
|
+
if (!isDigitStart(state.path.charCodeAt(state.index))) break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
finalizeSegment(state);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Scan parameters in command
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
function scanParam(state) {
|
|
235
|
+
let start = state.index,
|
|
236
|
+
index = start,
|
|
237
|
+
max = state.max,
|
|
238
|
+
hasCeiling = false,
|
|
239
|
+
hasDecimal = false,
|
|
240
|
+
hasDot = false
|
|
241
|
+
|
|
242
|
+
let ch = state.path.charCodeAt(index);
|
|
243
|
+
|
|
244
|
+
// Error 2: Missing param
|
|
245
|
+
if (state.index >= max) {
|
|
246
|
+
addError(state, 2)
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Plus/Minus
|
|
251
|
+
if (ch === 0x2B || ch === 0x2D) {
|
|
252
|
+
index++;
|
|
253
|
+
ch = (index < max) ? state.path.charCodeAt(index) : 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
// not number or '.' dot separator: 3. Not a number – param should start with 0..9 or `.`
|
|
258
|
+
if (!isDigit(ch) && ch !== 0x2E) {
|
|
259
|
+
addError(state, 3)
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// not '.' floating point separator
|
|
264
|
+
if (ch !== 0x2E) {
|
|
265
|
+
|
|
266
|
+
// is zero
|
|
267
|
+
let zeroFirst = (ch === 0x30);
|
|
268
|
+
index++;
|
|
269
|
+
|
|
270
|
+
ch = (index < max) ? state.path.charCodeAt(index) : 0;
|
|
271
|
+
|
|
272
|
+
if (zeroFirst && index < max) {
|
|
273
|
+
// decimal number starts with '0' such as '09' is illegal.
|
|
274
|
+
if (ch && isDigit(ch)) {
|
|
275
|
+
addError(state, 4)
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
while (index < max && isDigit(state.path.charCodeAt(index))) {
|
|
281
|
+
index++;
|
|
282
|
+
hasCeiling = true;
|
|
283
|
+
}
|
|
284
|
+
ch = (index < max) ? state.path.charCodeAt(index) : 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// '.' separator
|
|
288
|
+
if (ch === 0x2E) {
|
|
289
|
+
hasDot = true;
|
|
290
|
+
index++;
|
|
291
|
+
while (isDigit(state.path.charCodeAt(index))) {
|
|
292
|
+
index++;
|
|
293
|
+
hasDecimal = true;
|
|
294
|
+
}
|
|
295
|
+
ch = (index < max) ? state.path.charCodeAt(index) : 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// scientific notation 'e/E'
|
|
299
|
+
if (ch === 0x65 || ch === 0x45) {
|
|
300
|
+
|
|
301
|
+
// 5. Invalid float exponent
|
|
302
|
+
if (hasDot && !hasCeiling && !hasDecimal) {
|
|
303
|
+
addError(state, 5)
|
|
304
|
+
|
|
305
|
+
//finalizeSegment(state)
|
|
306
|
+
// skip to next segment
|
|
307
|
+
//state.index = getNextCommandIndex(state)-1
|
|
308
|
+
|
|
309
|
+
return
|
|
310
|
+
//if(!debug) return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
index++;
|
|
314
|
+
|
|
315
|
+
ch = (index < max) ? state.path.charCodeAt(index) : 0;
|
|
316
|
+
// plus or minus
|
|
317
|
+
if (ch === 0x2B || ch === 0x2D) {
|
|
318
|
+
index++;
|
|
319
|
+
}
|
|
320
|
+
if (index < max && isDigit(state.path.charCodeAt(index))) {
|
|
321
|
+
while (index < max && isDigit(state.path.charCodeAt(index))) {
|
|
322
|
+
index++;
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
// 5. Invalid float exponent
|
|
326
|
+
addError(state, 5)
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
state.index = index;
|
|
332
|
+
state.param = +state.path.slice(start, index);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Process duplicated commands (without comand name)
|
|
340
|
+
* This logic is shamelessly borrowed from Raphael
|
|
341
|
+
* https://github.com/DmitryBaranovskiy/raphael/
|
|
342
|
+
*
|
|
343
|
+
* !!! removed ROM Catmull command
|
|
344
|
+
*/
|
|
345
|
+
function finalizeSegment(state) {
|
|
346
|
+
|
|
347
|
+
let cmd = state.path[state.segmentStart];
|
|
348
|
+
let cmdLC = cmd.toLowerCase();
|
|
349
|
+
let params = state.data;
|
|
350
|
+
let lastType = state.lastType
|
|
351
|
+
state.allTypes.add(cmd)
|
|
352
|
+
|
|
353
|
+
// Z close path
|
|
354
|
+
if (cmdLC === 'z') {
|
|
355
|
+
//console.log('!!!is Z');
|
|
356
|
+
state.result.push({ type: 'Z', values: [] });
|
|
357
|
+
state.lastType = 'z'
|
|
358
|
+
state.lastIndex++
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// implicit linetos introduced by M/m commands
|
|
363
|
+
if (cmdLC === 'm' && params.length > 2) {
|
|
364
|
+
state.result.push({ type: cmd, values: [params[0], params[1]] });
|
|
365
|
+
state.lastIndex++
|
|
366
|
+
params = params.slice(2);
|
|
367
|
+
cmdLC = 'l';
|
|
368
|
+
cmd = (cmd === 'm') ? 'l' : 'L';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let maxParams = paramCounts[cmdLC]
|
|
372
|
+
|
|
373
|
+
// prepend implicit m after Z for better subpath detection
|
|
374
|
+
if (lastType === 'z' && cmdLC !== 'z') {
|
|
375
|
+
state.result.push({ type: 'm', values: [0, 0] });
|
|
376
|
+
state.lastIndex++
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
state.lastType = cmdLC
|
|
380
|
+
|
|
381
|
+
// create new commands of same type
|
|
382
|
+
while (params.length >= maxParams) {
|
|
383
|
+
state.result.push({ type: cmd, values: params.splice(0, maxParams) });
|
|
384
|
+
state.lastIndex++
|
|
385
|
+
|
|
386
|
+
if (!maxParams) {
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getNextCommandIndex(state) {
|
|
394
|
+
let i = state.index;
|
|
395
|
+
while (i < state.max) {
|
|
396
|
+
let ch = state.path.charCodeAt(i);
|
|
397
|
+
if (isCommand(ch)) break
|
|
398
|
+
i++
|
|
399
|
+
}
|
|
400
|
+
return i
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
export function parsePathDataFontello(pathDataString, debug = true) {
|
|
405
|
+
|
|
406
|
+
pathDataString = pathDataString.trim()
|
|
407
|
+
let state = new State(pathDataString, debug);
|
|
408
|
+
let max = state.max;
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
// Error 0: missing M command: 0. Paths must start with `M` or `m` command
|
|
412
|
+
if (pathDataString[0] !== 'M' && pathDataString[0] !== 'm') {
|
|
413
|
+
addError(state, 0)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
while (state.index < max && (debug || !state.err.length)) {
|
|
417
|
+
scanSegment(state);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// force absolute M for 1st sub path - facilitates concatenation
|
|
421
|
+
if (state.result.length) {
|
|
422
|
+
state.result[0].type = 'M';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* check if absolute/relative or
|
|
427
|
+
* shorthands are present
|
|
428
|
+
* to specify if normalization is required
|
|
429
|
+
*/
|
|
430
|
+
//check types relative arcs or quadratics
|
|
431
|
+
let allTypestypes = Array.from(state.allTypes).join('');
|
|
432
|
+
|
|
433
|
+
let pathDataObj = {
|
|
434
|
+
errors: state.err,
|
|
435
|
+
log: state.log,
|
|
436
|
+
pathData: state.result,
|
|
437
|
+
hasRelatives: /[lcqamts]/g.test(allTypestypes),
|
|
438
|
+
hasShorthands: /[vhst]/gi.test(allTypestypes),
|
|
439
|
+
hasArcs: /[a]/gi.test(allTypestypes),
|
|
440
|
+
hasQuadratics: /[qt]/gi.test(allTypestypes),
|
|
441
|
+
isPolygon: /[cqats]/gi.test(allTypestypes) ? false : true,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
console.log(pathDataObj.log);
|
|
446
|
+
console.log(pathDataObj.errors);
|
|
447
|
+
console.log(pathDataObj.pathData);
|
|
448
|
+
return pathDataObj
|
|
449
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { getDistAv, getSquareDistance } from "./geometry.js";
|
|
1
|
+
import { getDeltaAngle, getDistAv, getDistManhattan, getSquareDistance } from "./geometry.js";
|
|
2
2
|
import { getPolygonArea } from "./geometry_area.js";
|
|
3
3
|
import { checkBezierFlatness, commandIsFlat } from "./geometry_flatness.js";
|
|
4
|
-
import { renderPoint } from "./visualize.js";
|
|
4
|
+
import { renderPoint, renderPoly } from "./visualize.js";
|
|
5
5
|
|
|
6
6
|
export function pathDataRemoveColinear(pathData, {
|
|
7
|
-
tolerance = 1,
|
|
7
|
+
tolerance = 1,
|
|
8
8
|
//toleranceCubics = null,
|
|
9
9
|
flatBezierToLinetos = true
|
|
10
|
-
}={}) {
|
|
10
|
+
} = {}) {
|
|
11
11
|
|
|
12
12
|
//toleranceCubics = !toleranceCubics ? tolerance : toleranceCubics;
|
|
13
13
|
let pathDataN = [pathData[0]];
|
|
@@ -30,32 +30,60 @@ export function pathDataRemoveColinear(pathData, {
|
|
|
30
30
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
/*
|
|
33
34
|
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//let distSquare0 = getSquareDistance(p0, p)
|
|
37
|
-
//let distSquare1 = getSquareDistance(p, p1)
|
|
38
35
|
let distSquare = getSquareDistance(p0, p1)
|
|
39
|
-
//distSquare = (distSquare0+distSquare1) * 0.5
|
|
40
|
-
|
|
41
36
|
let distMax = distSquare ? distSquare / 333 * tolerance : 0
|
|
37
|
+
*/
|
|
42
38
|
|
|
43
|
-
let isFlat = area < distMax;
|
|
39
|
+
//let isFlat = area < distMax;
|
|
40
|
+
let isFlat = false;
|
|
44
41
|
let isFlatBez = false;
|
|
45
42
|
|
|
46
43
|
|
|
44
|
+
// flatness by cross product
|
|
45
|
+
let dx0 = Math.abs(p1.x - p0.x)
|
|
46
|
+
let dy0 = Math.abs(p1.y - p0.y)
|
|
47
|
+
|
|
48
|
+
let dx1 = Math.abs(p.x - p0.x)
|
|
49
|
+
let dy1 = Math.abs(p.y - p0.y)
|
|
50
|
+
|
|
51
|
+
let dx2 = Math.abs(p1.x - p.x)
|
|
52
|
+
let dy2 = Math.abs(p1.y - p.y)
|
|
53
|
+
|
|
54
|
+
// zero length segments are flat
|
|
55
|
+
let isZeroLength = (!dy1 && !dx1) || (!dy2 && !dx2)
|
|
56
|
+
if (isZeroLength) isFlat = true;
|
|
57
|
+
|
|
58
|
+
// check cross products for colinearity
|
|
59
|
+
if (!isFlat) {
|
|
60
|
+
|
|
61
|
+
let cross0 = Math.abs(dx0 * dy1 - dy0 * dx1);
|
|
62
|
+
//let cross1 = Math.abs(dx1 * dy2 - dy1 * dx2);
|
|
63
|
+
//let crossDiff = Math.abs(cross0-cross1)
|
|
64
|
+
//let cross = Math.max(cross0, cross1)
|
|
65
|
+
let thresh = (dx0 + dy0) * 0.1
|
|
66
|
+
|
|
67
|
+
//!cross0 ||
|
|
68
|
+
if ( cross0 < thresh) {
|
|
69
|
+
//renderPoint(markers, p)
|
|
70
|
+
isFlat = true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
47
75
|
if (!flatBezierToLinetos && type === 'C') isFlat = false;
|
|
48
76
|
|
|
49
77
|
// convert flat beziers to linetos
|
|
50
78
|
if (flatBezierToLinetos && (type === 'C' || type === 'Q')) {
|
|
51
79
|
|
|
52
80
|
let cpts = type === 'C' ?
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
[{ x: values[0], y: values[1] }, { x: values[2], y: values[3] }] :
|
|
82
|
+
(type === 'Q' ? [{ x: values[0], y: values[1] }] : []);
|
|
55
83
|
|
|
56
|
-
isFlatBez = commandIsFlat([p0, ...cpts, p],{tolerance});
|
|
84
|
+
isFlatBez = commandIsFlat([p0, ...cpts, p], { tolerance });
|
|
57
85
|
|
|
58
|
-
if (isFlatBez
|
|
86
|
+
if (isFlatBez && c < l - 1) {
|
|
59
87
|
type = "L"
|
|
60
88
|
com.type = "L"
|
|
61
89
|
com.values = valsL
|
|
@@ -66,23 +94,7 @@ export function pathDataRemoveColinear(pathData, {
|
|
|
66
94
|
|
|
67
95
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
68
96
|
//&& comN.type==='L'
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
/*
|
|
72
|
-
console.log(area, distMax );
|
|
73
|
-
//if(comN.type!=='L' ){}
|
|
74
|
-
|
|
75
|
-
if(p0.x === p.x && p0.y === p.y){
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
renderPoint(markers, p0, 'blue', '1.5%', '1')
|
|
80
|
-
renderPoint(markers, p, 'red', '1%', '1')
|
|
81
|
-
renderPoint(markers, p1, 'cyan', '0.5%', '1')
|
|
82
|
-
*/
|
|
83
|
-
|
|
84
|
-
//renderPoint(markers, p, 'blue', '1%', '1')
|
|
85
|
-
|
|
97
|
+
if (isFlat && c < l - 1 && comN.type !== 'A' && (type === 'L' || (flatBezierToLinetos && isFlatBez))) {
|
|
86
98
|
|
|
87
99
|
continue;
|
|
88
100
|
}
|
|
@@ -93,11 +105,6 @@ export function pathDataRemoveColinear(pathData, {
|
|
|
93
105
|
|
|
94
106
|
if (type === 'M') {
|
|
95
107
|
M = p
|
|
96
|
-
p0 = M
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
else if (type === 'Z') {
|
|
100
|
-
p0 = M;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
// proceed and add command
|
|
@@ -38,7 +38,7 @@ export function pathDataToTopLeft(pathData) {
|
|
|
38
38
|
|
|
39
39
|
// reorder to top left most
|
|
40
40
|
//|| a.x - b.x
|
|
41
|
-
indices = indices.sort((a, b) => +a.y.toFixed(
|
|
41
|
+
indices = indices.sort((a, b) => +a.y.toFixed(8) - +b.y.toFixed(8) || a.x-b.x );
|
|
42
42
|
newIndex = indices[0].index
|
|
43
43
|
|
|
44
44
|
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
@@ -233,6 +233,7 @@ export function shiftSvgStartingPoint(pathData, offset) {
|
|
|
233
233
|
*/
|
|
234
234
|
|
|
235
235
|
export function addClosePathLineto(pathData) {
|
|
236
|
+
|
|
236
237
|
let pathDataL = pathData.length;
|
|
237
238
|
let closed = pathData[pathDataL - 1].type.toLowerCase() === "z" ? true : false;
|
|
238
239
|
|