svg-path-simplify 0.0.7 → 0.0.8

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.
@@ -1,8 +1,9 @@
1
1
  import { splitSubpaths } from './pathData_split.js';
2
- import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance, commandIsFlat, getDistAv } from "./geometry.js";
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
 
@@ -179,6 +180,7 @@ export function analyzePathData(pathData = []) {
179
180
  if (type === 'C') commandPts.push(cp2);
180
181
  commandPts.push(p);
181
182
 
183
+ /*
182
184
  let commandFlatness = commandIsFlat(commandPts);
183
185
  isFlat = commandFlatness.flat;
184
186
  com.flat = isFlat;
@@ -186,6 +188,7 @@ export function analyzePathData(pathData = []) {
186
188
  if (isFlat) {
187
189
  com.extreme = false;
188
190
  }
191
+ */
189
192
  }
190
193
 
191
194
  /**
@@ -247,14 +250,17 @@ export function analyzePathData(pathData = []) {
247
250
  let thresh = (w + h) / 2 * 0.1;
248
251
  let pts1 = type === 'C' ? [p, cp1N, cp2N, pN] : [p, cp1N, pN];
249
252
 
250
- let flatness2 = commandIsFlat(pts1, thresh)
251
- let isFlat2 = flatness2.flat;
253
+ //let flatness2 = commandIsFlat(pts1, thresh)
254
+ //let isFlat2 = flatness2.flat;
252
255
 
253
256
  /**
254
257
  * if current and next cubic are flat
255
258
  * we don't flag them as extremes to allow simplification
256
259
  */
257
- let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
260
+ //let hasExtremes = (isFlat && isFlat2) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
261
+
262
+ let hasExtremes = (isFlat) ? false : (!com.extreme ? bezierhasExtreme(p0, cpts, angleThreshold) : true);
263
+
258
264
 
259
265
  //let bezierExtreme = bezierhasExtreme(p0, cpts, angleThreshold);
260
266
 
@@ -302,595 +308,4 @@ export function analyzePathData(pathData = []) {
302
308
  //console.log('pathDataPlus', pathDataPlus);
303
309
  return pathDataPlus
304
310
 
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
- }
311
+ }