svg-path-simplify 0.2.6 → 0.3.0
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 +80 -5
- package/dist/svg-path-simplify.esm.js +465 -392
- package/dist/svg-path-simplify.esm.min.js +6 -6
- package/dist/svg-path-simplify.js +465 -392
- package/dist/svg-path-simplify.min.js +6 -6
- package/dist/svg-path-simplify.pathdata.esm.js +5094 -0
- package/dist/svg-path-simplify.pathdata.esm.min.js +9 -0
- package/dist/svg-path-simplify.poly.cjs +10 -1
- package/dist/svg-path-simplify.worker.js +27 -0
- package/dist/svg-path-simplify.worker.polyfills.js +32 -0
- package/index.html +90 -17
- package/package.json +3 -2
- package/src/index-pathdata.js +6 -0
- package/src/index-poly.js +9 -1
- package/src/index-worker.js +17 -0
- package/src/index.js +4 -1
- package/src/pathData_simplify_cubic_extrapolate.js +3 -2
- package/src/pathSimplify-main.js +54 -72
- package/src/pathSimplify-only-pathdata.js +274 -0
- package/src/svgii/geometry.js +48 -4
- package/src/svgii/pathData_analyze.js +8 -6
- package/src/svgii/pathData_convert.js +1 -1
- package/src/svgii/pathData_fix_directions.js +83 -0
- package/src/svgii/pathData_line_to_cubic.js +21 -0
- package/src/svgii/pathData_parse_els.js +7 -2
- package/src/svgii/pathData_remove_zerolength.js +0 -1
- package/src/svgii/pathData_reverse.js +0 -1
- package/src/svgii/pathData_toPolygon.js +61 -12
- package/src/svgii/rounding.js +2 -0
- package/src/svgii/svg-styles-to-attributes-const.js +2 -0
- package/src/svgii/svg_cleanup.js +92 -15
- package/tests/testSVG2.js +55 -0
- package/dist/svg-path-simplify.min.js.gz +0 -0
package/src/pathSimplify-main.js
CHANGED
|
@@ -34,6 +34,8 @@ import { pathDataFromPoly } from './svgii/pathData_fromPoly';
|
|
|
34
34
|
import { simplifyRDP } from './simplify_poly_RDP';
|
|
35
35
|
import { harmonizeCubicCpts } from './pathData_simplify_harmonize_cpts';
|
|
36
36
|
import { pathDataToPolygon } from './svgii/pathData_toPolygon';
|
|
37
|
+
import { pathDataLineToCubic } from './svgii/pathData_line_to_cubic';
|
|
38
|
+
import { fixPathDataDirections } from './svgii/pathData_fix_directions';
|
|
37
39
|
|
|
38
40
|
//import { installDOMPolyfills } from './dom-polyfill';
|
|
39
41
|
|
|
@@ -66,6 +68,12 @@ export function svgPathSimplify(input = '', {
|
|
|
66
68
|
|
|
67
69
|
refineExtremes = true,
|
|
68
70
|
simplifyCorners = false,
|
|
71
|
+
removeDimensions = false,
|
|
72
|
+
removeIds = false,
|
|
73
|
+
removeClassNames = false,
|
|
74
|
+
omitNamespace = false,
|
|
75
|
+
|
|
76
|
+
fixDirections = false,
|
|
69
77
|
|
|
70
78
|
keepExtremes = true,
|
|
71
79
|
keepCorners = true,
|
|
@@ -80,7 +88,6 @@ export function svgPathSimplify(input = '', {
|
|
|
80
88
|
|
|
81
89
|
|
|
82
90
|
removeOrphanSubpaths = false,
|
|
83
|
-
|
|
84
91
|
simplifyRound = false,
|
|
85
92
|
|
|
86
93
|
//svg scaling
|
|
@@ -90,32 +97,30 @@ export function svgPathSimplify(input = '', {
|
|
|
90
97
|
alignToOrigin = false,
|
|
91
98
|
|
|
92
99
|
|
|
93
|
-
//
|
|
100
|
+
//svg path optimizations
|
|
94
101
|
decimals = 3,
|
|
95
102
|
autoAccuracy = true,
|
|
96
103
|
|
|
97
104
|
minifyD = 0,
|
|
98
105
|
tolerance = 1,
|
|
99
|
-
|
|
106
|
+
reversePath = false,
|
|
100
107
|
|
|
101
|
-
//
|
|
102
|
-
cleanupSVGAtts=true,
|
|
108
|
+
//svg cleanup options
|
|
109
|
+
cleanupSVGAtts = true,
|
|
103
110
|
removePrologue = true,
|
|
104
111
|
stylesToAttributes = true,
|
|
105
112
|
fixHref = true,
|
|
106
|
-
removeNameSpaced=true,
|
|
107
|
-
attributesToGroup=false,
|
|
113
|
+
removeNameSpaced = true,
|
|
114
|
+
attributesToGroup = false,
|
|
108
115
|
mergePaths = false,
|
|
109
116
|
removeHidden = true,
|
|
110
117
|
removeUnused = true,
|
|
111
|
-
shapesToPaths =
|
|
118
|
+
shapesToPaths = false,
|
|
119
|
+
lineToCubic = false,
|
|
112
120
|
|
|
113
121
|
tMin = 0,
|
|
114
122
|
tMax = 1,
|
|
115
123
|
|
|
116
|
-
// redraw - for messed up paths
|
|
117
|
-
redraw = false,
|
|
118
|
-
|
|
119
124
|
|
|
120
125
|
} = {}) {
|
|
121
126
|
|
|
@@ -145,7 +150,6 @@ export function svgPathSimplify(input = '', {
|
|
|
145
150
|
svgSize = input.length;
|
|
146
151
|
|
|
147
152
|
|
|
148
|
-
|
|
149
153
|
/**
|
|
150
154
|
* global bbox and viewBox for
|
|
151
155
|
* path scaling
|
|
@@ -179,14 +183,18 @@ export function svgPathSimplify(input = '', {
|
|
|
179
183
|
else {
|
|
180
184
|
//sanitize
|
|
181
185
|
let returnDom = true
|
|
182
|
-
svg = cleanUpSVG(input, { cleanupSVGAtts, returnDom, removeHidden, removeUnused, removeNameSpaced,
|
|
186
|
+
svg = cleanUpSVG(input, { removeIds, removeClassNames, removeDimensions, cleanupSVGAtts, returnDom, removeHidden, removeUnused, removeNameSpaced, stylesToAttributes, removePrologue, fixHref, mergePaths, shapesToPaths }
|
|
183
187
|
);
|
|
184
188
|
|
|
185
189
|
if (shapesToPaths) {
|
|
186
190
|
let shapes = svg.querySelectorAll('polygon, polyline, line, rect, circle, ellipse');
|
|
187
191
|
shapes.forEach(shape => {
|
|
188
192
|
let path = shapeElToPath(shape);
|
|
193
|
+
//console.log('path', path.getAttribute('d'));
|
|
189
194
|
shape.replaceWith(path)
|
|
195
|
+
//shape.parentNode.insertBefore(path, shape)
|
|
196
|
+
//shape.remove()
|
|
197
|
+
//shape = path
|
|
190
198
|
})
|
|
191
199
|
}
|
|
192
200
|
|
|
@@ -194,8 +202,6 @@ export function svgPathSimplify(input = '', {
|
|
|
194
202
|
let pathEls = svg.querySelectorAll('path')
|
|
195
203
|
|
|
196
204
|
|
|
197
|
-
//if(mergePaths){}
|
|
198
|
-
|
|
199
205
|
pathEls.forEach(path => {
|
|
200
206
|
paths.push({ d: path.getAttribute('d'), el: path })
|
|
201
207
|
})
|
|
@@ -293,7 +299,6 @@ export function svgPathSimplify(input = '', {
|
|
|
293
299
|
let pathDataSub = subPathArr[i];
|
|
294
300
|
|
|
295
301
|
|
|
296
|
-
|
|
297
302
|
/**
|
|
298
303
|
* convert cureves to polygon
|
|
299
304
|
* flattening
|
|
@@ -361,23 +366,6 @@ export function svgPathSimplify(input = '', {
|
|
|
361
366
|
// remove zero length linetos
|
|
362
367
|
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
363
368
|
|
|
364
|
-
/**
|
|
365
|
-
* try to redraw messed up paths
|
|
366
|
-
* based on significant points suchas
|
|
367
|
-
* extremes, semi-extremes and corners
|
|
368
|
-
*/
|
|
369
|
-
if (redraw) {
|
|
370
|
-
addExtremes = true
|
|
371
|
-
addSemiExtremes = true
|
|
372
|
-
simplifyCorners = false
|
|
373
|
-
keepCorners = true
|
|
374
|
-
keepExtremes = true
|
|
375
|
-
optimizeOrder = true
|
|
376
|
-
//simplifyBezier = false
|
|
377
|
-
tMin = 0
|
|
378
|
-
tMax = 0
|
|
379
|
-
}
|
|
380
|
-
|
|
381
369
|
|
|
382
370
|
// sort to top left
|
|
383
371
|
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
@@ -391,6 +379,12 @@ export function svgPathSimplify(input = '', {
|
|
|
391
379
|
|
|
392
380
|
|
|
393
381
|
|
|
382
|
+
// reverse
|
|
383
|
+
if(reversePath) {
|
|
384
|
+
pathDataSub = reversePathData(pathDataSub)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
394
388
|
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
395
389
|
let pathDataPlus = analyzePathData(pathDataSub, {
|
|
396
390
|
detectSemiExtremes: addSemiExtremes,
|
|
@@ -408,7 +402,7 @@ export function svgPathSimplify(input = '', {
|
|
|
408
402
|
if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 })
|
|
409
403
|
|
|
410
404
|
|
|
411
|
-
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance
|
|
405
|
+
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance }) : pathData;
|
|
412
406
|
|
|
413
407
|
|
|
414
408
|
// refine extremes
|
|
@@ -419,34 +413,6 @@ export function svgPathSimplify(input = '', {
|
|
|
419
413
|
}
|
|
420
414
|
|
|
421
415
|
|
|
422
|
-
/**
|
|
423
|
-
* try redrawing
|
|
424
|
-
*/
|
|
425
|
-
|
|
426
|
-
if (redraw) {
|
|
427
|
-
|
|
428
|
-
//pathData = pathDataRemoveColinear(pathData, { tolerance, flatBezierToLinetos: false });
|
|
429
|
-
/*
|
|
430
|
-
pathData = addExtremePoints(pathData,
|
|
431
|
-
{ tMin: 0, tMax: 1, addExtremes: true, addSemiExtremes: true })
|
|
432
|
-
|
|
433
|
-
pathData = analyzePathData(pathDataSub, {
|
|
434
|
-
detectSemiExtremes: true,
|
|
435
|
-
detectExtremes: true,
|
|
436
|
-
}).pathData;
|
|
437
|
-
|
|
438
|
-
*/
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
let thresholdEx = (bb.width + bb.height) * 0.1
|
|
442
|
-
//pathData = refineAdjacentExtremes(pathData, { threshold: thresholdEx, tolerance })
|
|
443
|
-
pathData = redrawPathData(pathData, { tolerance, threshold: dimA * 0.001 })
|
|
444
|
-
|
|
445
|
-
//simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse })
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
416
|
// cubic to arcs
|
|
451
417
|
if (cubicToArc) pathData = pathDataCubicsToArc(pathData, { areaThreshold: 2.5 })
|
|
452
418
|
|
|
@@ -464,7 +430,7 @@ export function svgPathSimplify(input = '', {
|
|
|
464
430
|
let threshold = (bb.width + bb.height) * 0.1
|
|
465
431
|
pathData = refineRoundedCorners(pathData, { threshold, tolerance })
|
|
466
432
|
}
|
|
467
|
-
|
|
433
|
+
|
|
468
434
|
// refine round segment sequences
|
|
469
435
|
if (simplifyRound) pathData = refineRoundSegments(pathData);
|
|
470
436
|
|
|
@@ -472,12 +438,14 @@ export function svgPathSimplify(input = '', {
|
|
|
472
438
|
// simplify to quadratics
|
|
473
439
|
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
474
440
|
|
|
441
|
+
if (lineToCubic) pathData = pathDataLineToCubic(pathData);
|
|
442
|
+
|
|
443
|
+
|
|
475
444
|
// optimize close path
|
|
476
445
|
if (optimizeOrder) pathData = optimizeClosePath(pathData, { autoClose })
|
|
477
446
|
|
|
447
|
+
|
|
478
448
|
// update
|
|
479
|
-
//pathDataFlat.push(...pathData)
|
|
480
|
-
//subPathArr[i]=pathData
|
|
481
449
|
pathDataPlusArr.push({ pathData, bb })
|
|
482
450
|
|
|
483
451
|
} // end sup paths
|
|
@@ -500,25 +468,28 @@ export function svgPathSimplify(input = '', {
|
|
|
500
468
|
pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y)
|
|
501
469
|
}
|
|
502
470
|
|
|
471
|
+
|
|
472
|
+
// fix path directions
|
|
473
|
+
if (fixDirections) {
|
|
474
|
+
pathDataPlusArr = fixPathDataDirections(pathDataPlusArr);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
503
478
|
// flatten compound paths
|
|
504
479
|
pathData = [];
|
|
505
480
|
pathDataPlusArr.forEach(sub => {
|
|
506
481
|
pathData.push(...sub.pathData)
|
|
507
482
|
})
|
|
508
483
|
|
|
509
|
-
//pathData = pathDataFlat;
|
|
510
|
-
//pathData = subPathArr.flat();
|
|
511
|
-
//console.log('pathData', pathData);
|
|
512
484
|
|
|
513
485
|
if (autoAccuracy) {
|
|
514
486
|
decimals = detectAccuracy(pathData)
|
|
515
487
|
pathOptions.decimals = decimals
|
|
516
|
-
//console.log('!decimals', decimals);
|
|
517
488
|
}
|
|
518
489
|
|
|
519
490
|
|
|
520
491
|
// collect for merged svg paths
|
|
521
|
-
mergePaths= false
|
|
492
|
+
mergePaths = false
|
|
522
493
|
if (el && mergePaths) {
|
|
523
494
|
pathData_merged.push(...pathData)
|
|
524
495
|
}
|
|
@@ -574,6 +545,7 @@ export function svgPathSimplify(input = '', {
|
|
|
574
545
|
*/
|
|
575
546
|
if (mode) {
|
|
576
547
|
|
|
548
|
+
|
|
577
549
|
if (pathData_merged.length) {
|
|
578
550
|
|
|
579
551
|
// optimize path data
|
|
@@ -584,7 +556,6 @@ export function svgPathSimplify(input = '', {
|
|
|
584
556
|
|
|
585
557
|
let dOpt = pathDataToD(pathData, minifyD)
|
|
586
558
|
|
|
587
|
-
|
|
588
559
|
// apply new path for svgs
|
|
589
560
|
paths[0].el.setAttribute('d', dOpt)
|
|
590
561
|
|
|
@@ -625,8 +596,19 @@ export function svgPathSimplify(input = '', {
|
|
|
625
596
|
}
|
|
626
597
|
}
|
|
627
598
|
|
|
599
|
+
// remove fill rules
|
|
600
|
+
if (fixDirections) {
|
|
601
|
+
let elsFill = svg.querySelectorAll('path[fill-rule], path[clip-rule]');
|
|
602
|
+
elsFill.forEach(el => {
|
|
603
|
+
el.removeAttribute('fill-rule')
|
|
604
|
+
el.removeAttribute('clip-rule')
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
svg = stringifySVG(svg, omitNamespace);
|
|
610
|
+
|
|
628
611
|
|
|
629
|
-
svg = stringifySVG(svg);
|
|
630
612
|
//svgSizeOpt = new Blob([svg]).size
|
|
631
613
|
svgSizeOpt = svg.length;
|
|
632
614
|
//compression = +(100/svgSize * (svgSize-svgSizeOpt)).toFixed(2)
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { detectInputType } from './detect_input';
|
|
2
|
+
import { simplifyPathDataCubic } from './pathData_simplify_cubic';
|
|
3
|
+
import { analyzePathData } from './svgii/pathData_analyze';
|
|
4
|
+
import { convertPathData } from './svgii/pathData_convert';
|
|
5
|
+
import { parsePathDataNormalized } from './svgii/pathData_parse';
|
|
6
|
+
import { pathDataRemoveColinear } from './svgii/pathData_remove_collinear';
|
|
7
|
+
import { removeOrphanedM } from './svgii/pathData_remove_orphaned';
|
|
8
|
+
import { removeZeroLengthLinetos } from './svgii/pathData_remove_zerolength';
|
|
9
|
+
import { optimizeClosePath, pathDataToTopLeft } from './svgii/pathData_reorder';
|
|
10
|
+
import { addExtremePoints, splitSubpaths } from './svgii/pathData_split';
|
|
11
|
+
import { pathDataToD } from './svgii/pathData_stringify';
|
|
12
|
+
//import { pathDataToPolyPlus, pathDataToPolySingle } from './svgii/pathData_toPolygon';
|
|
13
|
+
import { detectAccuracy } from './svgii/rounding';
|
|
14
|
+
import { refineAdjacentExtremes } from './svgii/pathData_simplify_refineExtremes';
|
|
15
|
+
import { refineRoundedCorners } from './svgii/pathData_simplify_refineCorners';
|
|
16
|
+
import { refineRoundSegments } from './svgii/pathData_refine_round';
|
|
17
|
+
import { refineClosingCommand } from './svgii/pathData_remove_short';
|
|
18
|
+
import { pathDataRevertCubicToQuadratic } from './pathData_simplify_revertToquadratics';
|
|
19
|
+
import { pathDataLineToCubic } from './svgii/pathData_line_to_cubic';
|
|
20
|
+
|
|
21
|
+
//import { installDOMPolyfills } from './dom-polyfill';
|
|
22
|
+
|
|
23
|
+
export function simplifyPathData(input = '', {
|
|
24
|
+
|
|
25
|
+
toAbsolute = true,
|
|
26
|
+
toRelative = true,
|
|
27
|
+
toShorthands = true,
|
|
28
|
+
//optimize = 0,
|
|
29
|
+
|
|
30
|
+
// not necessary unless you need cubics only
|
|
31
|
+
quadraticToCubic = true,
|
|
32
|
+
|
|
33
|
+
// mostly a fallback if arc calculations fail
|
|
34
|
+
arcToCubic = false,
|
|
35
|
+
cubicToArc = false,
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
simplifyBezier = true,
|
|
39
|
+
optimizeOrder = true,
|
|
40
|
+
autoClose = true,
|
|
41
|
+
removeZeroLength = true,
|
|
42
|
+
refineClosing = true,
|
|
43
|
+
removeColinear = true,
|
|
44
|
+
flatBezierToLinetos = true,
|
|
45
|
+
revertToQuadratics = true,
|
|
46
|
+
|
|
47
|
+
refineExtremes = true,
|
|
48
|
+
simplifyCorners = false,
|
|
49
|
+
fixDirections = false,
|
|
50
|
+
|
|
51
|
+
keepExtremes = true,
|
|
52
|
+
keepCorners = true,
|
|
53
|
+
keepInflections = false,
|
|
54
|
+
addExtremes = false,
|
|
55
|
+
addSemiExtremes = false,
|
|
56
|
+
|
|
57
|
+
harmonizeCpts = false,
|
|
58
|
+
toPolygon = false,
|
|
59
|
+
removeOrphanSubpaths = false,
|
|
60
|
+
simplifyRound = false,
|
|
61
|
+
|
|
62
|
+
//svg path optimizations
|
|
63
|
+
decimals = 3,
|
|
64
|
+
autoAccuracy = true,
|
|
65
|
+
|
|
66
|
+
minifyD = 0,
|
|
67
|
+
tolerance = 1,
|
|
68
|
+
|
|
69
|
+
lineToCubic = false,
|
|
70
|
+
// return svg markup or object
|
|
71
|
+
getObject = false,
|
|
72
|
+
|
|
73
|
+
} = {}) {
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// clamp tolerance and scale
|
|
77
|
+
tolerance = Math.max(0.1, tolerance);
|
|
78
|
+
|
|
79
|
+
let compression = 0;
|
|
80
|
+
let report = {};
|
|
81
|
+
let d = '';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* normalize input
|
|
85
|
+
* switch mode
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* global bbox and viewBox for
|
|
91
|
+
* path scaling
|
|
92
|
+
* sorting and cropping
|
|
93
|
+
*/
|
|
94
|
+
let viewBox = { x: 0, y: 0, width: 0, height: 0 }
|
|
95
|
+
let bb_global = { x: 0, y: 0, width: 0, height: 0 }
|
|
96
|
+
let xArr = []
|
|
97
|
+
let yArr = []
|
|
98
|
+
|
|
99
|
+
// mode:0 – single path
|
|
100
|
+
let inputType = detectInputType(input)
|
|
101
|
+
if (inputType === 'pathDataString') {
|
|
102
|
+
d = input
|
|
103
|
+
} else if (inputType === 'polyString') {
|
|
104
|
+
d = 'M' + input
|
|
105
|
+
}
|
|
106
|
+
else if (inputType === 'pathData') {
|
|
107
|
+
d = input;
|
|
108
|
+
}else{
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* process all paths
|
|
115
|
+
* try simplifications and removals
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
// SVG optimization options
|
|
119
|
+
let pathOptions = {
|
|
120
|
+
toRelative,
|
|
121
|
+
toShorthands,
|
|
122
|
+
decimals,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
let pathData = parsePathDataNormalized(d, { quadraticToCubic, toAbsolute, arcToCubic });
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// count commands for evaluation
|
|
130
|
+
let comCount = pathData.length
|
|
131
|
+
|
|
132
|
+
if (removeOrphanSubpaths) pathData = removeOrphanedM(pathData);
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* get sub paths
|
|
137
|
+
*/
|
|
138
|
+
let subPathArr = splitSubpaths(pathData);
|
|
139
|
+
let lenSub = subPathArr.length;
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
// reset array
|
|
143
|
+
let pathDataPlusArr = []
|
|
144
|
+
|
|
145
|
+
// loop sub paths
|
|
146
|
+
for (let i = 0; i < lenSub; i++) {
|
|
147
|
+
|
|
148
|
+
//let { pathData, bb } = subPathArr[i];
|
|
149
|
+
let pathDataSub = subPathArr[i];
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
// remove zero length linetos
|
|
153
|
+
if (removeColinear || removeZeroLength) pathDataSub = removeZeroLengthLinetos(pathDataSub)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
// sort to top left
|
|
157
|
+
if (optimizeOrder) pathDataSub = pathDataToTopLeft(pathDataSub);
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
// Preprocessing: remove colinear - ignore flat beziers (removed later)
|
|
161
|
+
if (removeColinear) pathDataSub = pathDataRemoveColinear(pathDataSub, { tolerance, flatBezierToLinetos: false });
|
|
162
|
+
|
|
163
|
+
if (addExtremes || addSemiExtremes) pathDataSub = addExtremePoints(pathDataSub,
|
|
164
|
+
{ tMin, tMax, addExtremes, addSemiExtremes, angles: [30] })
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// analyze pathdata to add info about signicant properties such as extremes, corners
|
|
168
|
+
let pathDataPlus = analyzePathData(pathDataSub, {
|
|
169
|
+
detectSemiExtremes: addSemiExtremes,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
// simplify beziers
|
|
174
|
+
let { pathData, bb, dimA } = pathDataPlus;
|
|
175
|
+
xArr.push(bb.x, bb.x + bb.width)
|
|
176
|
+
yArr.push(bb.y, bb.y + bb.height)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 })
|
|
180
|
+
|
|
181
|
+
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, revertToQuadratics, tolerance }) : pathData;
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// refine extremes
|
|
185
|
+
if (refineExtremes) {
|
|
186
|
+
//let thresholdEx = (bb.width + bb.height) / 2 * 0.05
|
|
187
|
+
let thresholdEx = (bb.width + bb.height) * 0.05
|
|
188
|
+
pathData = refineAdjacentExtremes(pathData, { threshold: thresholdEx, tolerance })
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// post processing: remove flat beziers
|
|
194
|
+
if (removeColinear && flatBezierToLinetos) {
|
|
195
|
+
pathData = pathDataRemoveColinear(pathData, { tolerance, flatBezierToLinetos });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
// refine corners
|
|
200
|
+
if (simplifyCorners) {
|
|
201
|
+
|
|
202
|
+
let threshold = (bb.width + bb.height) * 0.1
|
|
203
|
+
pathData = refineRoundedCorners(pathData, { threshold, tolerance })
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// refine round segment sequences
|
|
207
|
+
if (simplifyRound) pathData = refineRoundSegments(pathData);
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
// simplify to quadratics
|
|
211
|
+
if (revertToQuadratics) pathData = pathDataRevertCubicToQuadratic(pathData, tolerance);
|
|
212
|
+
|
|
213
|
+
if (lineToCubic) pathData = pathDataLineToCubic(pathData);
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
// optimize close path
|
|
217
|
+
if (optimizeOrder) pathData = optimizeClosePath(pathData, { autoClose })
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
// update
|
|
221
|
+
pathDataPlusArr.push({ pathData, bb })
|
|
222
|
+
|
|
223
|
+
} // end sup paths
|
|
224
|
+
|
|
225
|
+
// sort subpaths to top left
|
|
226
|
+
let xMin = Math.min(...xArr)
|
|
227
|
+
let yMin = Math.min(...yArr)
|
|
228
|
+
let xMax = Math.max(...xArr)
|
|
229
|
+
let yMax = Math.max(...yArr)
|
|
230
|
+
|
|
231
|
+
bb_global = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin }
|
|
232
|
+
let isPortrait = bb_global.height > bb_global.width;
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
// prefer top to bottom priority for portrait aspect ratios
|
|
236
|
+
if (optimizeOrder) {
|
|
237
|
+
pathDataPlusArr = isPortrait ? pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x) : pathDataPlusArr.sort((a, b) => a.bb.x - b.bb.x || a.bb.y - b.bb.y)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
// flatten compound paths
|
|
242
|
+
pathData = [];
|
|
243
|
+
pathDataPlusArr.forEach(sub => {
|
|
244
|
+
pathData.push(...sub.pathData)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if (autoAccuracy) {
|
|
249
|
+
decimals = detectAccuracy(pathData)
|
|
250
|
+
pathOptions.decimals = decimals
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
// optimize path data
|
|
255
|
+
pathData = convertPathData(pathData, pathOptions)
|
|
256
|
+
|
|
257
|
+
// remove zero-length segments introduced by rounding
|
|
258
|
+
pathData = removeZeroLengthLinetos(pathData);
|
|
259
|
+
|
|
260
|
+
let dOpt = pathDataToD(pathData, minifyD)
|
|
261
|
+
|
|
262
|
+
// remove custom properties
|
|
263
|
+
if(getObject){
|
|
264
|
+
pathData = pathData.map(com=>{return {type:com.type, values:com.values}});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return !getObject ? dOpt : pathData;
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
package/src/svgii/geometry.js
CHANGED
|
@@ -117,7 +117,7 @@ export function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = fals
|
|
|
117
117
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
118
118
|
*/
|
|
119
119
|
|
|
120
|
-
export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null, exact = true, debug = false) {
|
|
120
|
+
export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null, exact = true, respectDirection = false, debug = false) {
|
|
121
121
|
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
|
|
122
122
|
let denominator, a, b, numerator1, numerator2;
|
|
123
123
|
let intersectionPoint = {}
|
|
@@ -129,7 +129,9 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
131
|
denominator = ((p4.y - p3.y) * (p2.x - p1.x)) - ((p4.x - p3.x) * (p2.y - p1.y));
|
|
132
|
-
|
|
132
|
+
|
|
133
|
+
// parallel or colinear
|
|
134
|
+
if (denominator === 0) {
|
|
133
135
|
return false;
|
|
134
136
|
}
|
|
135
137
|
} catch {
|
|
@@ -151,7 +153,6 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
151
153
|
y: p1.y + (a * (p2.y - p1.y))
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
|
|
155
156
|
let intersection = false;
|
|
156
157
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
|
157
158
|
if ((a > 0 && a < 1) && (b > 0 && b < 1)) {
|
|
@@ -159,8 +160,14 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
159
160
|
//console.log('line inters');
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
// direction
|
|
164
|
+
if (!exact && respectDirection && ((a > 0 && b < 0) || (a < 0 && b > 0))) {
|
|
165
|
+
intersection = false;
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
162
170
|
if (exact && !intersection) {
|
|
163
|
-
//console.log('no line inters');
|
|
164
171
|
return false;
|
|
165
172
|
}
|
|
166
173
|
|
|
@@ -170,6 +177,43 @@ export function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null
|
|
|
170
177
|
};
|
|
171
178
|
|
|
172
179
|
|
|
180
|
+
/** Get relationship between a point and a polygon using ray-casting algorithm
|
|
181
|
+
* based on timepp's answer
|
|
182
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon#63436180
|
|
183
|
+
*/
|
|
184
|
+
export function isPointInPolygon(pt, polygon, bb, skipBB = false) {
|
|
185
|
+
const between = (p, a, b) => (p >= a && p <= b) || (p <= a && p >= b);
|
|
186
|
+
let inside = false;
|
|
187
|
+
|
|
188
|
+
// not in bbox - quit || no bbox defined
|
|
189
|
+
if (!skipBB || !bb.bottom) {
|
|
190
|
+
if (bb.left > pt.x || bb.top > pt.y || bb.bottom < pt.y || bb.right < pt.x) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let l=polygon.length;
|
|
196
|
+
for (let i = l - 1, j = 0; j < l; i = j, j++) {
|
|
197
|
+
const A = polygon[i];
|
|
198
|
+
const B = polygon[j];
|
|
199
|
+
// corner cases
|
|
200
|
+
if ((pt.x == A.x && pt.y == A.y) || (pt.x == B.x && pt.y == B.y))
|
|
201
|
+
return true;
|
|
202
|
+
if (A.y == B.y && pt.y == A.y && between(pt.x, A.x, B.x)) return true;
|
|
203
|
+
if (between(pt.y, A.y, B.y)) {
|
|
204
|
+
/**
|
|
205
|
+
* if pt inside the vertical range filter out "ray pass vertex" problem
|
|
206
|
+
* by treating the line a little lower
|
|
207
|
+
*/
|
|
208
|
+
if ((pt.y == A.y && B.y >= A.y) || (pt.y == B.y && A.y >= B.y)) continue;
|
|
209
|
+
// calc cross product `ptA X ptB`, pt lays on left side of AB if c > 0
|
|
210
|
+
const c = (A.x - pt.x) * (B.y - pt.y) - (B.x - pt.x) * (A.y - pt.y);
|
|
211
|
+
if (c == 0) return true;
|
|
212
|
+
if (A.y < B.y == c > 0) inside = !inside;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return inside ? true : false;
|
|
216
|
+
}
|
|
173
217
|
|
|
174
218
|
|
|
175
219
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { splitSubpaths } from './pathData_split.js';
|
|
2
|
-
import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance, getDistManhattan, isMultipleOf45, pointAtT, getTatAngles } from "./geometry.js";
|
|
2
|
+
import { getAngle, bezierhasExtreme, getPathDataVertices, svgArcToCenterParam, getSquareDistance, getDistManhattan, isMultipleOf45, pointAtT, getTatAngles, checkLineIntersection } 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";
|
|
@@ -86,6 +86,7 @@ export function analyzePathData(pathData = [], {
|
|
|
86
86
|
// check flatness of command
|
|
87
87
|
let toleranceFlat = 0.01;
|
|
88
88
|
let thresholdLength = dimA * 0.1
|
|
89
|
+
let threshold = thresholdLength*0.01
|
|
89
90
|
let areaThresh = squareDist * toleranceFlat;
|
|
90
91
|
let isFlat = Math.abs(cptArea) < areaThresh;
|
|
91
92
|
|
|
@@ -108,10 +109,12 @@ export function analyzePathData(pathData = [], {
|
|
|
108
109
|
let dx = type === 'C' ? Math.abs(com.cp2.x - com.p.x) : Math.abs(com.cp1.x - com.p.x)
|
|
109
110
|
let dy = type === 'C' ? Math.abs(com.cp2.y - com.p.y) : Math.abs(com.cp1.y - com.p.y)
|
|
110
111
|
|
|
111
|
-
let horizontal = dy === 0 && dx > 0
|
|
112
|
-
let vertical = dx === 0 && dy > 0
|
|
112
|
+
let horizontal = (dy === 0 || dy<threshold ) && dx > 0
|
|
113
|
+
let vertical = (dx === 0 || dx<threshold ) && dy > 0
|
|
113
114
|
|
|
114
|
-
if (horizontal || vertical)
|
|
115
|
+
if (horizontal || vertical) {
|
|
116
|
+
hasExtremes = true;
|
|
117
|
+
}
|
|
115
118
|
|
|
116
119
|
// is extreme relative to bounding box
|
|
117
120
|
if ((p.x === left || p.y === top || p.x === right || p.y === bottom)) {
|
|
@@ -123,7 +126,7 @@ export function analyzePathData(pathData = [], {
|
|
|
123
126
|
let couldHaveExtremes = bezierhasExtreme(null, commandPts)
|
|
124
127
|
if (couldHaveExtremes) {
|
|
125
128
|
let tArr = getTatAngles(commandPts)
|
|
126
|
-
if (tArr.length && (tArr[0] > 0.
|
|
129
|
+
if (tArr.length && (tArr[0] > 0.2)) {
|
|
127
130
|
hasExtremes = true;
|
|
128
131
|
}
|
|
129
132
|
}
|
|
@@ -183,7 +186,6 @@ export function analyzePathData(pathData = [], {
|
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
|
|
187
189
|
//debug = true;
|
|
188
190
|
if (debug) {
|
|
189
191
|
//if (com.semiExtreme) renderPoint(markers, com.p, 'blue', '2%', '0.5')
|