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.
- package/dist/svg-path-simplify.esm.js +775 -169
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +4674 -4068
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.node.js +775 -169
- package/dist/svg-path-simplify.node.min.js +1 -1
- package/index.html +11 -8
- package/package.json +5 -3
- package/src/dom-polyfill.js +29 -0
- package/src/dom-polyfill_back.js +22 -0
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -37
- package/src/pathData_simplify_cubic_extrapolate.js +43 -28
- package/src/pathSimplify-main.js +50 -9
- package/src/svgii/geometry.js +8 -155
- package/src/svgii/geometry_flatness.js +101 -0
- package/src/svgii/pathData_analyze.js +11 -596
- package/src/svgii/pathData_parse_els.js +239 -0
- package/src/svgii/pathData_remove_collinear.js +3 -1
- package/src/svgii/pathData_remove_orphaned.js +20 -0
- package/src/svgii/pathData_remove_zerolength.js +12 -2
- package/src/svgii/pathData_reorder.js +3 -1
- package/src/svgii/pathData_split.js +1 -0
- package/src/svgii/rounding.js +25 -19
- package/src/svgii/simplify_refineExtremes.js +173 -0
- package/src/svgii/svg_cleanup.js +4 -1
- package/testSVG.js +39 -0
|
@@ -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
|
|
|
@@ -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
|
+
}
|