svg-path-simplify 0.0.7 → 0.0.9
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 +25 -5
- package/dist/svg-path-simplify.esm.js +1250 -562
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4756 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +1250 -562
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +89 -29
- package/package.json +5 -3
- package/src/detect_input.js +17 -10
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +10 -1
- package/src/pathData_simplify_cubic.js +114 -143
- package/src/pathData_simplify_cubic_extrapolate.js +64 -35
- package/src/pathSimplify-main.js +113 -165
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +94 -0
- package/src/svgii/pathData_analyze.js +15 -596
- package/src/svgii/pathData_convert.js +26 -17
- package/src/svgii/pathData_interpolate.js +65 -0
- package/src/svgii/pathData_parse.js +25 -9
- package/src/svgii/pathData_parse_els.js +245 -0
- package/src/svgii/pathData_remove_collinear.js +33 -28
- package/src/svgii/pathData_remove_orphaned.js +21 -0
- package/src/svgii/pathData_remove_zerolength.js +17 -3
- package/src/svgii/pathData_reorder.js +9 -3
- package/src/svgii/pathData_simplify_refineCorners.js +160 -0
- package/src/svgii/pathData_simplify_refineExtremes.js +208 -0
- package/src/svgii/pathData_split.js +43 -15
- package/src/svgii/pathData_stringify.js +3 -12
- package/src/svgii/rounding.js +35 -27
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
- package/src/pathData_simplify_cubic_arr.js +0 -50
- package/src/svgii/simplify.js +0 -248
- package/src/svgii/simplify_bezier.js +0 -470
- package/src/svgii/simplify_linetos.js +0 -93
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { splitSubpaths } from './pathData_split.js';
|
|
2
|
-
import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance,
|
|
2
|
+
import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance, getDistAv } from "./geometry.js";
|
|
3
3
|
import { getPolygonArea, getPathArea } from './geometry_area.js';
|
|
4
4
|
import { getPolyBBox } from './geometry_bbox.js';
|
|
5
5
|
import { renderPoint, renderPath } from "./visualize.js";
|
|
6
|
+
import { commandIsFlat } from './geometry_flatness.js';
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
|
|
@@ -111,6 +112,7 @@ export function analyzePathData(pathData = []) {
|
|
|
111
112
|
com.directionChange = false;
|
|
112
113
|
com.closePath = false;
|
|
113
114
|
com.dimA = 0;
|
|
115
|
+
//com.flat = false;
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
/**
|
|
@@ -179,13 +181,18 @@ export function analyzePathData(pathData = []) {
|
|
|
179
181
|
if (type === 'C') commandPts.push(cp2);
|
|
180
182
|
commandPts.push(p);
|
|
181
183
|
|
|
184
|
+
/*
|
|
185
|
+
//let commandFlatness = commandIsFlat(commandPts);
|
|
182
186
|
let commandFlatness = commandIsFlat(commandPts);
|
|
183
187
|
isFlat = commandFlatness.flat;
|
|
184
188
|
com.flat = isFlat;
|
|
185
189
|
|
|
186
190
|
if (isFlat) {
|
|
187
191
|
com.extreme = false;
|
|
192
|
+
//renderPoint(markers, p, 'red', '1%', '0.5')
|
|
188
193
|
}
|
|
194
|
+
*/
|
|
195
|
+
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
/**
|
|
@@ -247,14 +254,17 @@ export function analyzePathData(pathData = []) {
|
|
|
247
254
|
let thresh = (w + h) / 2 * 0.1;
|
|
248
255
|
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
249
256
|
|
|
250
|
-
let flatness2 = commandIsFlat(pts1, thresh)
|
|
251
|
-
let isFlat2 = flatness2.flat;
|
|
257
|
+
//let flatness2 = commandIsFlat(pts1, thresh)
|
|
258
|
+
//let isFlat2 = flatness2.flat;
|
|
252
259
|
|
|
253
260
|
/**
|
|
254
261
|
* if current and next cubic are flat
|
|
255
262
|
* we don't flag them as extremes to allow simplification
|
|
256
263
|
*/
|
|
257
|
-
let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
264
|
+
//let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
265
|
+
|
|
266
|
+
let hasExtremes = (isFlat) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
267
|
+
|
|
258
268
|
|
|
259
269
|
//let bezierExtreme = bezierhasExtreme(p0, cpts, angleThreshold);
|
|
260
270
|
|
|
@@ -302,595 +312,4 @@ export function analyzePathData(pathData = []) {
|
|
|
302
312
|
//console.log('pathDataPlus', pathDataPlus);
|
|
303
313
|
return pathDataPlus
|
|
304
314
|
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
export function analyzePathData2(pathData = [], debug = true) {
|
|
311
|
-
|
|
312
|
-
// clone
|
|
313
|
-
pathData = JSON.parse(JSON.stringify(pathData));
|
|
314
|
-
|
|
315
|
-
// split to sub paths
|
|
316
|
-
let pathDataSubArr = splitSubpaths(pathData)
|
|
317
|
-
|
|
318
|
-
// collect more verbose data
|
|
319
|
-
let pathDataPlus = [];
|
|
320
|
-
|
|
321
|
-
// log
|
|
322
|
-
let simplyfy_debug_log = [];
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* analyze sub paths
|
|
326
|
-
* add simplified bbox (based on on-path-points)
|
|
327
|
-
* get area
|
|
328
|
-
*/
|
|
329
|
-
pathDataSubArr.forEach(pathData => {
|
|
330
|
-
|
|
331
|
-
let pathPoly = getPathDataVertices(pathData);
|
|
332
|
-
//let pathDataArea = getPathArea(pathData);
|
|
333
|
-
let bb = getPolyBBox(pathPoly)
|
|
334
|
-
let { left, right, top, bottom, width, height } = bb;
|
|
335
|
-
|
|
336
|
-
// initial starting point coordinates
|
|
337
|
-
let M0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
338
|
-
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
339
|
-
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
340
|
-
let p;
|
|
341
|
-
|
|
342
|
-
// init starting point data
|
|
343
|
-
pathData[0].p0 = M;
|
|
344
|
-
pathData[0].p = M;
|
|
345
|
-
pathData[0].lineto = false;
|
|
346
|
-
pathData[0].corner = false;
|
|
347
|
-
pathData[0].extreme = false;
|
|
348
|
-
pathData[0].directionChange = false;
|
|
349
|
-
pathData[0].closePath = false;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// add first M command
|
|
353
|
-
let pathDataProps = [pathData[0]];
|
|
354
|
-
let area0 = 0;
|
|
355
|
-
let len = pathData.length;
|
|
356
|
-
|
|
357
|
-
for (let c = 2; len && c <= len; c++) {
|
|
358
|
-
|
|
359
|
-
let com = pathData[c - 1];
|
|
360
|
-
let { type, values } = com;
|
|
361
|
-
let valsL = values.slice(-2);
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* get command points for
|
|
365
|
-
* flatness checks:
|
|
366
|
-
* this way we can skip certain tests
|
|
367
|
-
*/
|
|
368
|
-
let commandPts = [p0];
|
|
369
|
-
let isFlat = false;
|
|
370
|
-
|
|
371
|
-
// init properties
|
|
372
|
-
com.lineto = false;
|
|
373
|
-
com.corner = false;
|
|
374
|
-
com.extreme = false;
|
|
375
|
-
com.directionChange = false;
|
|
376
|
-
com.closePath = false;
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* define angle threshold for
|
|
380
|
-
* corner detection
|
|
381
|
-
*/
|
|
382
|
-
let angleThreshold = 0.05
|
|
383
|
-
p = valsL.length ? { x: valsL[0], y: valsL[1] } : M;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// update M for Z starting points
|
|
387
|
-
if (type === 'M') {
|
|
388
|
-
M = p;
|
|
389
|
-
p0 = p
|
|
390
|
-
//p0 = p
|
|
391
|
-
}
|
|
392
|
-
else if (type.toLowerCase() === 'z') {
|
|
393
|
-
//p0 = M;
|
|
394
|
-
p = M;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// add on-path points
|
|
398
|
-
com.p0 = p0;
|
|
399
|
-
com.p = p;
|
|
400
|
-
|
|
401
|
-
let cp1, cp2, cp1N, cp2N, pN, typeN, area1;
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* explicit and implicit linetos
|
|
405
|
-
* - introduced by Z
|
|
406
|
-
*/
|
|
407
|
-
if (type === 'L') com.lineto = true;
|
|
408
|
-
|
|
409
|
-
if (type === 'Z') {
|
|
410
|
-
com.closePath = true;
|
|
411
|
-
// if Z introduces an implicit lineto with a length
|
|
412
|
-
if (M.x !== M0.x && M.y !== M0.y) {
|
|
413
|
-
com.lineto = true;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// if bezier
|
|
418
|
-
if (type === 'Q' || type === 'C') {
|
|
419
|
-
cp1 = { x: values[0], y: values[1] }
|
|
420
|
-
cp2 = type === 'C' ? { x: values[2], y: values[3] } : null;
|
|
421
|
-
com.cp1 = cp1;
|
|
422
|
-
if (cp2) com.cp2 = cp2;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* check command flatness
|
|
427
|
-
* we leave it to the bezier simplifier
|
|
428
|
-
* to convert flat beziers to linetos
|
|
429
|
-
* otherwise we may strip rather flat starting segments
|
|
430
|
-
* preventing a better simplification
|
|
431
|
-
*/
|
|
432
|
-
|
|
433
|
-
if (values.length > 2) {
|
|
434
|
-
if (type === 'Q' || type === 'C') commandPts.push(cp1);
|
|
435
|
-
if (type === 'C') commandPts.push(cp2);
|
|
436
|
-
commandPts.push(p);
|
|
437
|
-
|
|
438
|
-
let commandFlatness = commandIsFlat(commandPts);
|
|
439
|
-
isFlat = commandFlatness.flat;
|
|
440
|
-
com.flat = isFlat;
|
|
441
|
-
|
|
442
|
-
if (isFlat) {
|
|
443
|
-
com.extreme = false;
|
|
444
|
-
/*
|
|
445
|
-
pathDataProps.push(com)
|
|
446
|
-
p0 = p;
|
|
447
|
-
continue;
|
|
448
|
-
*/
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* is extreme relative to bounding box
|
|
455
|
-
* in case elements are rotated we can't rely on 90degree angles
|
|
456
|
-
* so we interpret maximum x/y on-path points as well as extremes
|
|
457
|
-
* but we ignore linetos to allow chunk compilation
|
|
458
|
-
*/
|
|
459
|
-
if (!isFlat && type !== 'L' && (p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
460
|
-
com.extreme = true;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// add to average
|
|
465
|
-
//let squareDist = getSquareDistance(p0, p)
|
|
466
|
-
//com.size = squareDist;
|
|
467
|
-
|
|
468
|
-
let dimA = (width + height) / 2;
|
|
469
|
-
com.dimA = dimA;
|
|
470
|
-
//console.log('decimals', decimals, size);
|
|
471
|
-
|
|
472
|
-
//next command
|
|
473
|
-
let comN = pathData[c] ? pathData[c] : null;
|
|
474
|
-
let comNValsL = comN ? comN.values.slice(-2) : null;
|
|
475
|
-
typeN = comN ? comN.type : null;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
// get bezier control points
|
|
479
|
-
if (comN) {
|
|
480
|
-
pN = comN ? { x: comNValsL[0], y: comNValsL[1] } : null;
|
|
481
|
-
|
|
482
|
-
if (comN.type === 'Q' || comN.type === 'C') {
|
|
483
|
-
cp1N = { x: comN.values[0], y: comN.values[1] }
|
|
484
|
-
cp2N = comN.type === 'C' ? { x: comN.values[2], y: comN.values[3] } : null;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Detect direction change points
|
|
491
|
-
* this will prevent distortions when simplifying
|
|
492
|
-
* e.g in the "spine" of an "S" glyph
|
|
493
|
-
*/
|
|
494
|
-
area1 = getPolygonArea(commandPts)
|
|
495
|
-
let signChange = (area0 < 0 && area1 > 0) || (area0 > 0 && area1 < 0) ? true : false;
|
|
496
|
-
// update area
|
|
497
|
-
area0 = area1
|
|
498
|
-
|
|
499
|
-
if (signChange) {
|
|
500
|
-
//renderPoint(svg1, p0, 'orange', '1%', '0.75')
|
|
501
|
-
com.directionChange = true;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* check extremes or corners for adjacent curves by control point angles
|
|
507
|
-
*/
|
|
508
|
-
if ((type === 'Q' || type === 'C')) {
|
|
509
|
-
|
|
510
|
-
if ((type === 'Q' && typeN === 'Q') || (type === 'C' && typeN === 'C')) {
|
|
511
|
-
|
|
512
|
-
// check extremes
|
|
513
|
-
let cpts = commandPts.slice(1);
|
|
514
|
-
|
|
515
|
-
let w = Math.abs(pN.x - p0.x)
|
|
516
|
-
let h = Math.abs(pN.y - p0.y)
|
|
517
|
-
let thresh = (w + h) / 2 * 0.1;
|
|
518
|
-
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
519
|
-
|
|
520
|
-
let flatness2 = commandIsFlat(pts1, thresh)
|
|
521
|
-
let isFlat2 = flatness2.flat;
|
|
522
|
-
|
|
523
|
-
//console.log('isFlat2', isFlat2, isFlat);
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* if current and next cubic are flat
|
|
527
|
-
* we don't flag them as extremes to allow simplification
|
|
528
|
-
*/
|
|
529
|
-
let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
530
|
-
|
|
531
|
-
if (hasExtremes) {
|
|
532
|
-
com.extreme = true
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// check corners
|
|
536
|
-
else {
|
|
537
|
-
|
|
538
|
-
let cpts1 = cp2 ? [cp2, p] : [cp1, p];
|
|
539
|
-
let cpts2 = cp2 ? [p, cp1N] : [p, cp1N];
|
|
540
|
-
|
|
541
|
-
let angCom1 = getAngle(...cpts1, true)
|
|
542
|
-
let angCom2 = getAngle(...cpts2, true)
|
|
543
|
-
let angDiff = Math.abs(angCom1 - angCom2) * 180 / Math.PI
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
let cpDist1 = getSquareDistance(...cpts1)
|
|
547
|
-
let cpDist2 = getSquareDistance(...cpts2)
|
|
548
|
-
|
|
549
|
-
let cornerThreshold = 10
|
|
550
|
-
let isCorner = angDiff > cornerThreshold && cpDist1 && cpDist2
|
|
551
|
-
|
|
552
|
-
if (isCorner) {
|
|
553
|
-
com.corner = true;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
//let debug = false
|
|
562
|
-
//debug = true
|
|
563
|
-
if (debug) {
|
|
564
|
-
if (com.signChange) {
|
|
565
|
-
renderPoint(markers, p0, 'orange', '1.5%', '0.75')
|
|
566
|
-
}
|
|
567
|
-
if (com.extreme) {
|
|
568
|
-
renderPoint(markers, p, 'cyan', '1%', '0.75')
|
|
569
|
-
}
|
|
570
|
-
if (com.corner) {
|
|
571
|
-
renderPoint(markers, p, 'magenta')
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
pathDataProps.push(com)
|
|
576
|
-
p0 = p;
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
//decimalsAV = Array.from(decimalsAV)
|
|
582
|
-
//decimalsAV = Math.ceil(decimalsAV.reduce((a, b) => a + b) / decimalsAV.length);
|
|
583
|
-
//console.log('decimalsAV', decimalsAV);
|
|
584
|
-
//pathDataProps[0].decimals = decimalsAV
|
|
585
|
-
|
|
586
|
-
//decimalsAV = Math.floor(decimalsAV/decimalsAV.length);
|
|
587
|
-
let dimA = (width + height) / 2
|
|
588
|
-
pathDataPlus.push({ pathData: pathDataProps, bb: bb, dimA: dimA })
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (simplyfy_debug_log.length) {
|
|
592
|
-
console.log(simplyfy_debug_log);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
})
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
return pathDataPlus
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
export function analyzePathData__(pathData = [], debug = true) {
|
|
606
|
-
|
|
607
|
-
// clone
|
|
608
|
-
pathData = JSON.parse(JSON.stringify(pathData));
|
|
609
|
-
|
|
610
|
-
// split to sub paths
|
|
611
|
-
let pathDataSubArr = splitSubpaths(pathData)
|
|
612
|
-
|
|
613
|
-
// collect more verbose data
|
|
614
|
-
let pathDataPlus = [];
|
|
615
|
-
|
|
616
|
-
// log
|
|
617
|
-
let simplyfy_debug_log = [];
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* analyze sub paths
|
|
621
|
-
* add simplified bbox (based on on-path-points)
|
|
622
|
-
* get area
|
|
623
|
-
*/
|
|
624
|
-
pathDataSubArr.forEach(pathData => {
|
|
625
|
-
|
|
626
|
-
let pathDataArea = getPathArea(pathData);
|
|
627
|
-
let pathPoly = getPathDataVertices(pathData);
|
|
628
|
-
let bb = getPolyBBox(pathPoly)
|
|
629
|
-
let { left, right, top, bottom, width, height } = bb;
|
|
630
|
-
|
|
631
|
-
// initial starting point coordinates
|
|
632
|
-
let M0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
633
|
-
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
634
|
-
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
635
|
-
let p;
|
|
636
|
-
|
|
637
|
-
// init starting point data
|
|
638
|
-
pathData[0].p0 = M;
|
|
639
|
-
pathData[0].p = M;
|
|
640
|
-
pathData[0].lineto = false;
|
|
641
|
-
pathData[0].corner = false;
|
|
642
|
-
pathData[0].extreme = false;
|
|
643
|
-
pathData[0].directionChange = false;
|
|
644
|
-
pathData[0].closePath = false;
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
// add first M command
|
|
648
|
-
let pathDataProps = [pathData[0]];
|
|
649
|
-
let area0 = 0;
|
|
650
|
-
let len = pathData.length;
|
|
651
|
-
|
|
652
|
-
for (let c = 2; len && c <= len; c++) {
|
|
653
|
-
|
|
654
|
-
let com = pathData[c - 1];
|
|
655
|
-
let { type, values } = com;
|
|
656
|
-
let valsL = values.slice(-2);
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* get command points for
|
|
660
|
-
* flatness checks:
|
|
661
|
-
* this way we can skip certain tests
|
|
662
|
-
*/
|
|
663
|
-
let commandPts = [p0];
|
|
664
|
-
let isFlat = false;
|
|
665
|
-
|
|
666
|
-
// init properties
|
|
667
|
-
com.lineto = false;
|
|
668
|
-
com.corner = false;
|
|
669
|
-
com.extreme = false;
|
|
670
|
-
com.directionChange = false;
|
|
671
|
-
com.closePath = false;
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* define angle threshold for
|
|
675
|
-
* corner detection
|
|
676
|
-
*/
|
|
677
|
-
let angleThreshold = 0.05
|
|
678
|
-
p = valsL.length ? { x: valsL[0], y: valsL[1] } : M;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
// update M for Z starting points
|
|
682
|
-
if (type === 'M') {
|
|
683
|
-
M = p;
|
|
684
|
-
p0 = p
|
|
685
|
-
//p0 = p
|
|
686
|
-
}
|
|
687
|
-
else if (type.toLowerCase() === 'z') {
|
|
688
|
-
//p0 = M;
|
|
689
|
-
p = M;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// add on-path points
|
|
693
|
-
com.p0 = p0;
|
|
694
|
-
com.p = p;
|
|
695
|
-
|
|
696
|
-
let cp1, cp2, cp1N, cp2N, pN, typeN, area1;
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* explicit and implicit linetos
|
|
700
|
-
* - introduced by Z
|
|
701
|
-
*/
|
|
702
|
-
if (type === 'L') com.lineto = true;
|
|
703
|
-
|
|
704
|
-
if (type === 'Z') {
|
|
705
|
-
com.closePath = true;
|
|
706
|
-
// if Z introduces an implicit lineto with a length
|
|
707
|
-
if (M.x !== M0.x && M.y !== M0.y) {
|
|
708
|
-
com.lineto = true;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// if bezier
|
|
713
|
-
if (type === 'Q' || type === 'C') {
|
|
714
|
-
cp1 = { x: values[0], y: values[1] }
|
|
715
|
-
cp2 = type === 'C' ? { x: values[2], y: values[3] } : null;
|
|
716
|
-
com.cp1 = cp1;
|
|
717
|
-
if (cp2) com.cp2 = cp2;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* check command flatness
|
|
722
|
-
* we leave it to the bezier simplifier
|
|
723
|
-
* to convert flat beziers to linetos
|
|
724
|
-
* otherwise we may strip rather flat starting segments
|
|
725
|
-
* preventing a better simplification
|
|
726
|
-
*/
|
|
727
|
-
|
|
728
|
-
if (values.length > 2) {
|
|
729
|
-
if (type === 'Q' || type === 'C') commandPts.push(cp1);
|
|
730
|
-
if (type === 'C') commandPts.push(cp2);
|
|
731
|
-
commandPts.push(p);
|
|
732
|
-
|
|
733
|
-
let commandFlatness = commandIsFlat(commandPts);
|
|
734
|
-
isFlat = commandFlatness.flat;
|
|
735
|
-
com.flat = isFlat;
|
|
736
|
-
|
|
737
|
-
if (isFlat) {
|
|
738
|
-
com.extreme = false;
|
|
739
|
-
/*
|
|
740
|
-
pathDataProps.push(com)
|
|
741
|
-
p0 = p;
|
|
742
|
-
continue;
|
|
743
|
-
*/
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* is extreme relative to bounding box
|
|
750
|
-
* in case elements are rotated we can't rely on 90degree angles
|
|
751
|
-
* so we interpret maximum x/y on-path points as well as extremes
|
|
752
|
-
* but we ignore linetos to allow chunk compilation
|
|
753
|
-
*/
|
|
754
|
-
if (!isFlat && type !== 'L' && (p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
755
|
-
com.extreme = true;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
// add to average
|
|
760
|
-
//let squareDist = getSquareDistance(p0, p)
|
|
761
|
-
//com.size = squareDist;
|
|
762
|
-
|
|
763
|
-
let dimA = (width + height) / 2;
|
|
764
|
-
com.dimA = dimA;
|
|
765
|
-
//console.log('decimals', decimals, size);
|
|
766
|
-
|
|
767
|
-
//next command
|
|
768
|
-
let comN = pathData[c] ? pathData[c] : null;
|
|
769
|
-
let comNValsL = comN ? comN.values.slice(-2) : null;
|
|
770
|
-
typeN = comN ? comN.type : null;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// get bezier control points
|
|
774
|
-
if (comN) {
|
|
775
|
-
pN = comN ? { x: comNValsL[0], y: comNValsL[1] } : null;
|
|
776
|
-
|
|
777
|
-
if (comN.type === 'Q' || comN.type === 'C') {
|
|
778
|
-
cp1N = { x: comN.values[0], y: comN.values[1] }
|
|
779
|
-
cp2N = comN.type === 'C' ? { x: comN.values[2], y: comN.values[3] } : null;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Detect direction change points
|
|
786
|
-
* this will prevent distortions when simplifying
|
|
787
|
-
* e.g in the "spine" of an "S" glyph
|
|
788
|
-
*/
|
|
789
|
-
area1 = getPolygonArea(commandPts)
|
|
790
|
-
let signChange = (area0 < 0 && area1 > 0) || (area0 > 0 && area1 < 0) ? true : false;
|
|
791
|
-
// update area
|
|
792
|
-
area0 = area1
|
|
793
|
-
|
|
794
|
-
if (signChange) {
|
|
795
|
-
//renderPoint(svg1, p0, 'orange', '1%', '0.75')
|
|
796
|
-
com.directionChange = true;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* check extremes or corners for adjacent curves by control point angles
|
|
802
|
-
*/
|
|
803
|
-
if ((type === 'Q' || type === 'C')) {
|
|
804
|
-
|
|
805
|
-
if ((type === 'Q' && typeN === 'Q') || (type === 'C' && typeN === 'C')) {
|
|
806
|
-
|
|
807
|
-
// check extremes
|
|
808
|
-
let cpts = commandPts.slice(1);
|
|
809
|
-
|
|
810
|
-
let w = Math.abs(pN.x - p0.x)
|
|
811
|
-
let h = Math.abs(pN.y - p0.y)
|
|
812
|
-
let thresh = (w + h) / 2 * 0.1;
|
|
813
|
-
let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
|
|
814
|
-
|
|
815
|
-
let flatness2 = commandIsFlat(pts1, thresh)
|
|
816
|
-
let isFlat2 = flatness2.flat;
|
|
817
|
-
|
|
818
|
-
//console.log('isFlat2', isFlat2, isFlat);
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* if current and next cubic are flat
|
|
822
|
-
* we don't flag them as extremes to allow simplification
|
|
823
|
-
*/
|
|
824
|
-
let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
|
|
825
|
-
|
|
826
|
-
if (hasExtremes) {
|
|
827
|
-
com.extreme = true
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// check corners
|
|
831
|
-
else {
|
|
832
|
-
|
|
833
|
-
let cpts1 = cp2 ? [cp2, p] : [cp1, p];
|
|
834
|
-
let cpts2 = cp2 ? [p, cp1N] : [p, cp1N];
|
|
835
|
-
|
|
836
|
-
let angCom1 = getAngle(...cpts1, true)
|
|
837
|
-
let angCom2 = getAngle(...cpts2, true)
|
|
838
|
-
let angDiff = Math.abs(angCom1 - angCom2) * 180 / Math.PI
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
let cpDist1 = getSquareDistance(...cpts1)
|
|
842
|
-
let cpDist2 = getSquareDistance(...cpts2)
|
|
843
|
-
|
|
844
|
-
let cornerThreshold = 10
|
|
845
|
-
let isCorner = angDiff > cornerThreshold && cpDist1 && cpDist2
|
|
846
|
-
|
|
847
|
-
if (isCorner) {
|
|
848
|
-
com.corner = true;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
let debug = false
|
|
857
|
-
//debug = true
|
|
858
|
-
if (debug) {
|
|
859
|
-
if (com.signChange) {
|
|
860
|
-
renderPoint(svg1, p0, 'orange', '1.5%', '0.75')
|
|
861
|
-
}
|
|
862
|
-
if (com.extreme) {
|
|
863
|
-
renderPoint(svg1, p, 'cyan', '1%', '0.75')
|
|
864
|
-
}
|
|
865
|
-
if (com.corner) {
|
|
866
|
-
renderPoint(svg1, p, 'magenta')
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
pathDataProps.push(com)
|
|
871
|
-
p0 = p;
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
//decimalsAV = Array.from(decimalsAV)
|
|
877
|
-
//decimalsAV = Math.ceil(decimalsAV.reduce((a, b) => a + b) / decimalsAV.length);
|
|
878
|
-
//console.log('decimalsAV', decimalsAV);
|
|
879
|
-
//pathDataProps[0].decimals = decimalsAV
|
|
880
|
-
|
|
881
|
-
//decimalsAV = Math.floor(decimalsAV/decimalsAV.length);
|
|
882
|
-
let dimA = (width + height) / 2
|
|
883
|
-
pathDataPlus.push({ pathData: pathDataProps, bb: bb, area: pathDataArea, dimA: dimA })
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (simplyfy_debug_log.length) {
|
|
887
|
-
console.log(simplyfy_debug_log);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
})
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
return pathDataPlus
|
|
895
|
-
|
|
896
|
-
}
|
|
315
|
+
}
|