svg-path-simplify 0.0.9 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -46
- package/debug.cjs +11 -0
- package/dist/svg-path-simplify.esm.js +613 -98
- package/dist/svg-path-simplify.esm.min.js +1 -1
- package/dist/svg-path-simplify.js +613 -98
- package/dist/svg-path-simplify.min.js +1 -1
- package/dist/svg-path-simplify.poly.cjs +29 -0
- package/index.html +5 -1
- package/package.json +36 -16
- package/src/index-node.js +15 -0
- package/src/index-poly.js +27 -0
- package/src/index.js +1 -6
- package/src/pathSimplify-main.js +47 -14
- package/src/svgii/geometry.js +54 -4
- package/src/svgii/geometry_deduceRadius.js +50 -0
- package/src/svgii/pathData_convert.js +339 -5
- package/src/svgii/pathData_refine_round.js +222 -0
- package/src/svgii/pathData_remove_collinear.js +8 -3
- package/src/svgii/pathData_remove_short.js +66 -0
- package/src/svgii/pathData_reorder.js +27 -11
- package/src/svgii/pathData_simplify_refineCorners.js +57 -37
- package/src/svgii/svg_cleanup.js +28 -29
- package/src/svgii/visualize.js +2 -2
- package/testSVG.js +27 -20
- package/dist/svg-path-simplify.node.js +0 -5129
- package/dist/svg-path-simplify.node.min.js +0 -1
- package/src/dom-polyfill.js +0 -29
- package/src/dom-polyfill_back.js +0 -22
|
@@ -25,9 +25,9 @@ function renderPoint(
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', render = true) {
|
|
28
|
+
function renderPath(svg, d = '', stroke = 'green', strokeWidth = '1%', opacity="1", render = true) {
|
|
29
29
|
|
|
30
|
-
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" /> `;
|
|
30
|
+
let path = `<path d="${d}" fill="none" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${opacity}" /> `;
|
|
31
31
|
|
|
32
32
|
if (render) {
|
|
33
33
|
svg.insertAdjacentHTML("beforeend", path);
|
|
@@ -105,18 +105,62 @@ function getAngle(p1, p2, normalize = false) {
|
|
|
105
105
|
return angle
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
function getDeltaAngle(centerPoint, startPoint, endPoint, largeArc = false) {
|
|
109
|
+
|
|
110
|
+
const normalizeAngle = (angle) => {
|
|
111
|
+
let normalized = angle % (2 * Math.PI);
|
|
112
|
+
|
|
113
|
+
if (normalized > Math.PI) {
|
|
114
|
+
normalized -= 2 * Math.PI;
|
|
115
|
+
} else if (normalized <= -Math.PI) {
|
|
116
|
+
normalized += 2 * Math.PI;
|
|
117
|
+
}
|
|
118
|
+
return normalized;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
let startAngle = Math.atan2(
|
|
122
|
+
startPoint.y - centerPoint.y,
|
|
123
|
+
startPoint.x - centerPoint.x
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let endAngle = Math.atan2(
|
|
127
|
+
endPoint.y - centerPoint.y,
|
|
128
|
+
endPoint.x - centerPoint.x
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Calculate raw delta angle (difference)
|
|
132
|
+
let deltaAngle = endAngle - startAngle;
|
|
133
|
+
|
|
134
|
+
// Normalize the delta angle to range (-π, π]
|
|
135
|
+
deltaAngle = normalizeAngle(deltaAngle);
|
|
136
|
+
|
|
137
|
+
if (largeArc) deltaAngle = Math.PI*2 - Math.abs(deltaAngle);
|
|
138
|
+
|
|
139
|
+
let phi = 180 / Math.PI;
|
|
140
|
+
let startAngleDeg = startAngle * phi;
|
|
141
|
+
let endAngleDeg = endAngle * phi;
|
|
142
|
+
let deltaAngleDeg = deltaAngle * phi;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
startAngle, endAngle, deltaAngle, startAngleDeg,
|
|
146
|
+
endAngleDeg,
|
|
147
|
+
deltaAngleDeg
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
108
152
|
/**
|
|
109
153
|
* based on: Justin C. Round's
|
|
110
154
|
* http://jsfiddle.net/justin_c_rounds/Gd2S2/light/
|
|
111
155
|
*/
|
|
112
156
|
|
|
113
|
-
function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true, debug=false) {
|
|
157
|
+
function checkLineIntersection(p1 = null, p2 = null, p3 = null, p4 = null, exact = true, debug = false) {
|
|
114
158
|
// 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
|
|
115
159
|
let denominator, a, b, numerator1, numerator2;
|
|
116
160
|
let intersectionPoint = {};
|
|
117
161
|
|
|
118
|
-
if(!p1 || !p2 || !p3 || !p4){
|
|
119
|
-
if(debug) console.warn('points missing');
|
|
162
|
+
if (!p1 || !p2 || !p3 || !p4) {
|
|
163
|
+
if (debug) console.warn('points missing');
|
|
120
164
|
return false
|
|
121
165
|
}
|
|
122
166
|
|
|
@@ -126,7 +170,7 @@ function checkLineIntersection(p1=null, p2=null, p3=null, p4=null, exact = true,
|
|
|
126
170
|
return false;
|
|
127
171
|
}
|
|
128
172
|
} catch {
|
|
129
|
-
if(debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
173
|
+
if (debug) console.warn('!catch', p1, p2, 'p3:', p3, 'p4:', p4);
|
|
130
174
|
return false
|
|
131
175
|
}
|
|
132
176
|
|
|
@@ -483,6 +527,17 @@ function svgArcToCenterParam(x1, y1, rx, ry, xAxisRotation, largeArc, sweep, x2,
|
|
|
483
527
|
return arcData;
|
|
484
528
|
}
|
|
485
529
|
|
|
530
|
+
function rotatePoint(pt, cx, cy, rotation = 0, convertToRadians = false) {
|
|
531
|
+
if (!rotation) return pt;
|
|
532
|
+
|
|
533
|
+
rotation = convertToRadians ? (rotation / 180) * Math.PI : rotation;
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
x: cx + (pt.x - cx) * Math.cos(rotation) - (pt.y - cy) * Math.sin(rotation),
|
|
537
|
+
y: cy + (pt.x - cx) * Math.sin(rotation) + (pt.y - cy) * Math.cos(rotation)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
486
541
|
function getPointOnEllipse(cx, cy, rx, ry, angle, ellipseRotation = 0, parametricAngle = true, degrees = false) {
|
|
487
542
|
|
|
488
543
|
// Convert degrees to radians
|
|
@@ -516,6 +571,18 @@ function getPointOnEllipse(cx, cy, rx, ry, angle, ellipseRotation = 0, parametri
|
|
|
516
571
|
return pt
|
|
517
572
|
}
|
|
518
573
|
|
|
574
|
+
// to parametric angle helper
|
|
575
|
+
function toParametricAngle(angle, rx, ry) {
|
|
576
|
+
|
|
577
|
+
if (rx === ry || (angle % PI$1 * 0.5 === 0)) return angle;
|
|
578
|
+
let angleP = atan$1(tan$1(angle) * (rx / ry));
|
|
579
|
+
|
|
580
|
+
// Ensure the parametric angle is in the correct quadrant
|
|
581
|
+
angleP = cos$1(angle) < 0 ? angleP + PI$1 : angleP;
|
|
582
|
+
|
|
583
|
+
return angleP
|
|
584
|
+
}
|
|
585
|
+
|
|
519
586
|
function bezierhasExtreme(p0, cpts = [], angleThreshold = 0.05) {
|
|
520
587
|
let isCubic = cpts.length === 3 ? true : false;
|
|
521
588
|
let cp1 = cpts[0] || null;
|
|
@@ -2331,13 +2398,13 @@ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
|
|
|
2331
2398
|
let cp2X = interpolate(p, cp2, 1.5);
|
|
2332
2399
|
|
|
2333
2400
|
let dist0 = getDistAv(p0, p);
|
|
2334
|
-
let threshold = dist0 * 0.
|
|
2401
|
+
let threshold = dist0 * 0.03;
|
|
2335
2402
|
let dist1 = getDistAv(cp1X, cp2X);
|
|
2336
2403
|
|
|
2337
2404
|
let cp1_Q = null;
|
|
2338
2405
|
let type = 'C';
|
|
2339
2406
|
let values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y];
|
|
2340
|
-
let comN = {type, values};
|
|
2407
|
+
let comN = { type, values };
|
|
2341
2408
|
|
|
2342
2409
|
if (dist1 && threshold && dist1 < threshold) {
|
|
2343
2410
|
cp1_Q = checkLineIntersection(p0, cp1, p, cp2, false);
|
|
@@ -2347,6 +2414,7 @@ function revertCubicQuadratic(p0 = {}, cp1 = {}, cp2 = {}, p = {}) {
|
|
|
2347
2414
|
comN.values = [cp1_Q.x, cp1_Q.y, p.x, p.y];
|
|
2348
2415
|
comN.p0 = p0;
|
|
2349
2416
|
comN.cp1 = cp1_Q;
|
|
2417
|
+
comN.cp2 = null;
|
|
2350
2418
|
comN.p = p;
|
|
2351
2419
|
}
|
|
2352
2420
|
}
|
|
@@ -2364,7 +2432,7 @@ function convertPathData(pathData, {
|
|
|
2364
2432
|
if (toShorthands) pathData = pathDataToShorthands(pathData);
|
|
2365
2433
|
|
|
2366
2434
|
// pre round - before relative conversion to minimize distortions
|
|
2367
|
-
if(decimals
|
|
2435
|
+
if (decimals > -1 && toRelative) pathData = roundPathData(pathData, decimals);
|
|
2368
2436
|
if (toRelative) pathData = pathDataToRelative(pathData);
|
|
2369
2437
|
if (decimals > -1) pathData = roundPathData(pathData, decimals);
|
|
2370
2438
|
|
|
@@ -2701,7 +2769,7 @@ function pathDataToShorthands(pathData, decimals = -1, test = false) {
|
|
|
2701
2769
|
let com = pathData[i];
|
|
2702
2770
|
let { type, values } = com;
|
|
2703
2771
|
let valuesLen = values.length;
|
|
2704
|
-
let valuesLast = [values[valuesLen-2], values[valuesLen-1]];
|
|
2772
|
+
let valuesLast = [values[valuesLen - 2], values[valuesLen - 1]];
|
|
2705
2773
|
|
|
2706
2774
|
// previoius command
|
|
2707
2775
|
let comPrev = pathData[i - 1];
|
|
@@ -2829,6 +2897,108 @@ function pathDataToShorthands(pathData, decimals = -1, test = false) {
|
|
|
2829
2897
|
return pathDataShorts;
|
|
2830
2898
|
}
|
|
2831
2899
|
|
|
2900
|
+
/**
|
|
2901
|
+
* Convert a parametrized SVG arc to cubic Beziers
|
|
2902
|
+
* Assumes arc parameters are already resolved
|
|
2903
|
+
*/
|
|
2904
|
+
function arcToBezierResolved({
|
|
2905
|
+
|
|
2906
|
+
// start / end points
|
|
2907
|
+
p0 = { x: 0, y: 0 },
|
|
2908
|
+
p = { x: 0, y: 0 },
|
|
2909
|
+
|
|
2910
|
+
// center
|
|
2911
|
+
centroid = { x: 0, y: 0 },
|
|
2912
|
+
|
|
2913
|
+
// radii
|
|
2914
|
+
rx = 0,
|
|
2915
|
+
ry = 0,
|
|
2916
|
+
|
|
2917
|
+
// SVG-style rotation
|
|
2918
|
+
xAxisRotation = 0,
|
|
2919
|
+
radToDegree = false,
|
|
2920
|
+
|
|
2921
|
+
// optional
|
|
2922
|
+
startAngle = null,
|
|
2923
|
+
endAngle = null,
|
|
2924
|
+
deltaAngle = null
|
|
2925
|
+
|
|
2926
|
+
} = {}) {
|
|
2927
|
+
|
|
2928
|
+
if (!rx || !ry) return [];
|
|
2929
|
+
|
|
2930
|
+
// new pathData
|
|
2931
|
+
let pathData = [];
|
|
2932
|
+
|
|
2933
|
+
// maximum delta for cubic approximations: Math.PI / 2 (90deg)
|
|
2934
|
+
const maxSegAngle = 1.5707963267948966;
|
|
2935
|
+
|
|
2936
|
+
// Pomax cubic constant
|
|
2937
|
+
const k = 0.551785;
|
|
2938
|
+
|
|
2939
|
+
// rotation
|
|
2940
|
+
let phi = radToDegree
|
|
2941
|
+
? xAxisRotation
|
|
2942
|
+
: xAxisRotation * Math.PI / 180;
|
|
2943
|
+
|
|
2944
|
+
let cosphi = Math.cos(phi);
|
|
2945
|
+
let sinphi = Math.sin(phi);
|
|
2946
|
+
|
|
2947
|
+
// derive angles if not provided
|
|
2948
|
+
if (startAngle === null || endAngle === null || deltaAngle === null) {
|
|
2949
|
+
({ startAngle, endAngle, deltaAngle } = getDeltaAngle(centroid, p0, p));
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// parametrize for elliptic arcs
|
|
2953
|
+
let startAngleParam = rx !== ry ? toParametricAngle(startAngle, rx, ry) : startAngle;
|
|
2954
|
+
|
|
2955
|
+
let deltaAngleParam = rx !== ry ? toParametricAngle(deltaAngle, rx, ry) : deltaAngle;
|
|
2956
|
+
|
|
2957
|
+
let segments = Math.max(1, Math.ceil(Math.abs(deltaAngleParam) / maxSegAngle));
|
|
2958
|
+
let angStep = deltaAngleParam / segments;
|
|
2959
|
+
|
|
2960
|
+
for (let i = 0; i < segments; i++) {
|
|
2961
|
+
|
|
2962
|
+
const a = Math.abs(angStep) === maxSegAngle ?
|
|
2963
|
+
Math.sign(angStep) * k :
|
|
2964
|
+
(4 / 3) * Math.tan(angStep / 4);
|
|
2965
|
+
|
|
2966
|
+
let cos0 = Math.cos(startAngleParam);
|
|
2967
|
+
let sin0 = Math.sin(startAngleParam);
|
|
2968
|
+
let cos1 = Math.cos(startAngleParam + angStep);
|
|
2969
|
+
let sin1 = Math.sin(startAngleParam + angStep);
|
|
2970
|
+
|
|
2971
|
+
// unit arc → cubic
|
|
2972
|
+
let c1 = { x: cos0 - sin0 * a, y: sin0 + cos0 * a };
|
|
2973
|
+
let c2 = { x: cos1 + sin1 * a, y: sin1 - cos1 * a };
|
|
2974
|
+
let e = { x: cos1, y: sin1 };
|
|
2975
|
+
|
|
2976
|
+
let values = [];
|
|
2977
|
+
|
|
2978
|
+
[c1, c2, e].forEach(pt => {
|
|
2979
|
+
let x = pt.x * rx;
|
|
2980
|
+
let y = pt.y * ry;
|
|
2981
|
+
|
|
2982
|
+
values.push(
|
|
2983
|
+
cosphi * x - sinphi * y + centroid.x,
|
|
2984
|
+
sinphi * x + cosphi * y + centroid.y
|
|
2985
|
+
);
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
pathData.push({
|
|
2989
|
+
type: 'C',
|
|
2990
|
+
values,
|
|
2991
|
+
cp1: { x: values[0], y: values[1] },
|
|
2992
|
+
cp2: { x: values[2], y: values[3] },
|
|
2993
|
+
p: { x: values[4], y: values[5] },
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
startAngleParam += angStep;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
return pathData;
|
|
3000
|
+
}
|
|
3001
|
+
|
|
2832
3002
|
/**
|
|
2833
3003
|
* convert arctocommands to cubic bezier
|
|
2834
3004
|
* based on puzrin's a2c.js
|
|
@@ -2963,10 +3133,6 @@ function arcToBezier$1(p0, values, splitSegments = 1) {
|
|
|
2963
3133
|
return pathDataArc;
|
|
2964
3134
|
}
|
|
2965
3135
|
|
|
2966
|
-
/**
|
|
2967
|
-
* cubics to arcs
|
|
2968
|
-
*/
|
|
2969
|
-
|
|
2970
3136
|
function cubicCommandToArc(p0, cp1, cp2, p, tolerance = 7.5) {
|
|
2971
3137
|
|
|
2972
3138
|
let com = { type: 'C', values: [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y] };
|
|
@@ -3992,7 +4158,7 @@ function pathDataRemoveColinear(pathData, {
|
|
|
3992
4158
|
let valsL = values.slice(-2);
|
|
3993
4159
|
p = type !== 'Z' ? { x: valsL[0], y: valsL[1] } : M;
|
|
3994
4160
|
|
|
3995
|
-
let area = getPolygonArea([p0, p, p1], true);
|
|
4161
|
+
let area = p1 ? getPolygonArea([p0, p, p1], true) : Infinity;
|
|
3996
4162
|
|
|
3997
4163
|
let distSquare = getSquareDistance(p0, p1);
|
|
3998
4164
|
|
|
@@ -4022,7 +4188,7 @@ function pathDataRemoveColinear(pathData, {
|
|
|
4022
4188
|
|
|
4023
4189
|
// colinear – exclude arcs (as always =) as semicircles won't have an area
|
|
4024
4190
|
|
|
4025
|
-
if ( isFlat && c < l - 1 && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
4191
|
+
if ( isFlat && c < l - 1 && comN.type!=='A' && (type === 'L' || (flatBezierToLinetos && isFlatBez)) ) {
|
|
4026
4192
|
|
|
4027
4193
|
/*
|
|
4028
4194
|
console.log(area, distMax );
|
|
@@ -4170,25 +4336,43 @@ function pathDataToTopLeft(pathData) {
|
|
|
4170
4336
|
return newIndex ? shiftSvgStartingPoint(pathData, newIndex) : pathData;
|
|
4171
4337
|
}
|
|
4172
4338
|
|
|
4173
|
-
function optimizeClosePath(pathData, removeFinalLineto = true,
|
|
4339
|
+
function optimizeClosePath(pathData, {removeFinalLineto = true, autoClose = true}={}) {
|
|
4174
4340
|
|
|
4175
4341
|
let pathDataNew = [];
|
|
4176
|
-
let
|
|
4342
|
+
let l = pathData.length;
|
|
4177
4343
|
let M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
4178
|
-
let isClosed = pathData[
|
|
4344
|
+
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4179
4345
|
|
|
4180
4346
|
let linetos = pathData.filter(com => com.type === 'L');
|
|
4181
4347
|
|
|
4182
4348
|
// check if order is ideal
|
|
4183
|
-
let
|
|
4349
|
+
let idxPenultimate = isClosed ? l-2 : l-1;
|
|
4350
|
+
|
|
4351
|
+
let penultimateCom = pathData[idxPenultimate];
|
|
4184
4352
|
let penultimateType = penultimateCom.type;
|
|
4185
4353
|
let penultimateComCoords = penultimateCom.values.slice(-2).map(val => +val.toFixed(8));
|
|
4186
4354
|
|
|
4187
4355
|
// last L command ends at M
|
|
4188
4356
|
let isClosingCommand = penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4189
4357
|
|
|
4358
|
+
// add closepath Z to enable order optimizations
|
|
4359
|
+
if(!isClosed && autoClose && isClosingCommand){
|
|
4360
|
+
|
|
4361
|
+
/*
|
|
4362
|
+
// adjust final coords
|
|
4363
|
+
let valsLast = pathData[idxPenultimate].values
|
|
4364
|
+
let valsLastLen = valsLast.length;
|
|
4365
|
+
pathData[idxPenultimate].values[valsLastLen-2] = M.x
|
|
4366
|
+
pathData[idxPenultimate].values[valsLastLen-1] = M.y
|
|
4367
|
+
*/
|
|
4368
|
+
|
|
4369
|
+
pathData.push({type:'Z', values:[]});
|
|
4370
|
+
isClosed = true;
|
|
4371
|
+
l++;
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4190
4374
|
// if last segment is not closing or a lineto
|
|
4191
|
-
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand ||
|
|
4375
|
+
let skipReorder = pathData[1].type !== 'L' && (!isClosingCommand || penultimateCom.type === 'L');
|
|
4192
4376
|
skipReorder = false;
|
|
4193
4377
|
|
|
4194
4378
|
// we can't change starting point for non closed paths
|
|
@@ -4201,7 +4385,7 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
|
|
|
4201
4385
|
if (!skipReorder) {
|
|
4202
4386
|
|
|
4203
4387
|
let indices = [];
|
|
4204
|
-
for (let i = 0
|
|
4388
|
+
for (let i = 0; i < l; i++) {
|
|
4205
4389
|
let com = pathData[i];
|
|
4206
4390
|
let { type, values } = com;
|
|
4207
4391
|
if (values.length) {
|
|
@@ -4237,17 +4421,17 @@ function optimizeClosePath(pathData, removeFinalLineto = true, reorder = true) {
|
|
|
4237
4421
|
|
|
4238
4422
|
M = { x: +pathData[0].values[0].toFixed(8), y: +pathData[0].values[1].toFixed(8) };
|
|
4239
4423
|
|
|
4240
|
-
|
|
4424
|
+
l = pathData.length;
|
|
4241
4425
|
|
|
4242
4426
|
// remove last lineto
|
|
4243
|
-
penultimateCom = pathData[
|
|
4427
|
+
penultimateCom = pathData[l - 2];
|
|
4244
4428
|
penultimateType = penultimateCom.type;
|
|
4245
4429
|
penultimateComCoords = penultimateCom.values.slice(-2).map(val=>+val.toFixed(8));
|
|
4246
4430
|
|
|
4247
4431
|
isClosingCommand = penultimateType === 'L' && penultimateComCoords[0] === M.x && penultimateComCoords[1] === M.y;
|
|
4248
4432
|
|
|
4249
4433
|
if (removeFinalLineto && isClosingCommand) {
|
|
4250
|
-
pathData.splice(
|
|
4434
|
+
pathData.splice(l - 2, 1);
|
|
4251
4435
|
}
|
|
4252
4436
|
|
|
4253
4437
|
pathDataNew.push(...pathData);
|
|
@@ -4509,49 +4693,47 @@ function refineAdjacentExtremes(pathData, {
|
|
|
4509
4693
|
function removeEmptySVGEls(svg) {
|
|
4510
4694
|
let els = svg.querySelectorAll('g, defs');
|
|
4511
4695
|
els.forEach(el => {
|
|
4512
|
-
|
|
4696
|
+
if (!el.children.length) el.remove();
|
|
4513
4697
|
});
|
|
4514
4698
|
}
|
|
4515
4699
|
|
|
4516
4700
|
function cleanUpSVG(svgMarkup, {
|
|
4517
|
-
returnDom=false,
|
|
4518
|
-
removeHidden=true,
|
|
4519
|
-
removeUnused=true,
|
|
4520
|
-
}={}) {
|
|
4701
|
+
returnDom = false,
|
|
4702
|
+
removeHidden = true,
|
|
4703
|
+
removeUnused = true,
|
|
4704
|
+
} = {}) {
|
|
4521
4705
|
svgMarkup = cleanSvgPrologue(svgMarkup);
|
|
4522
|
-
|
|
4706
|
+
|
|
4523
4707
|
// replace namespaced refs
|
|
4524
4708
|
svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
4525
|
-
|
|
4709
|
+
|
|
4526
4710
|
let svg = new DOMParser()
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
let allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
|
|
4711
|
+
.parseFromString(svgMarkup, "text/html")
|
|
4712
|
+
.querySelector("svg");
|
|
4713
|
+
|
|
4714
|
+
let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
|
|
4532
4715
|
removeExcludedAttribues(svg, allowed);
|
|
4533
|
-
|
|
4716
|
+
|
|
4534
4717
|
let removeEls = ['metadata', 'script'];
|
|
4535
|
-
|
|
4718
|
+
|
|
4536
4719
|
let els = svg.querySelectorAll('*');
|
|
4537
|
-
els.forEach(el=>{
|
|
4538
|
-
let name = el.nodeName;
|
|
4720
|
+
els.forEach(el => {
|
|
4721
|
+
let name = el.nodeName;
|
|
4539
4722
|
// remove hidden elements
|
|
4540
4723
|
let style = el.getAttribute('style') || '';
|
|
4541
4724
|
let isHiddenByStyle = style ? style.trim().includes('display:none') : false;
|
|
4542
4725
|
let isHidden = (el.getAttribute('display') && el.getAttribute('display') === 'none') || isHiddenByStyle;
|
|
4543
|
-
if(name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden
|
|
4726
|
+
if (name.includes(':') || removeEls.includes(name) || (removeHidden && isHidden)) {
|
|
4544
4727
|
el.remove();
|
|
4545
|
-
}else {
|
|
4728
|
+
} else {
|
|
4546
4729
|
// remove BS elements
|
|
4547
4730
|
removeNameSpaceAtts(el);
|
|
4548
4731
|
}
|
|
4549
4732
|
});
|
|
4550
4733
|
|
|
4551
|
-
if(returnDom) return svg
|
|
4734
|
+
if (returnDom) return svg
|
|
4552
4735
|
|
|
4553
4736
|
let markup = stringifySVG(svg);
|
|
4554
|
-
console.log(markup);
|
|
4555
4737
|
|
|
4556
4738
|
return markup;
|
|
4557
4739
|
}
|
|
@@ -4570,7 +4752,7 @@ function cleanSvgPrologue(svgString) {
|
|
|
4570
4752
|
);
|
|
4571
4753
|
}
|
|
4572
4754
|
|
|
4573
|
-
function removeExcludedAttribues(el, allowed=['viewBox', 'xmlns', 'width', 'height', 'id', 'class']){
|
|
4755
|
+
function removeExcludedAttribues(el, allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class']) {
|
|
4574
4756
|
let atts = [...el.attributes].map((att) => att.name);
|
|
4575
4757
|
atts.forEach((att) => {
|
|
4576
4758
|
if (!allowed.includes(att)) {
|
|
@@ -4588,13 +4770,13 @@ function removeNameSpaceAtts(el) {
|
|
|
4588
4770
|
});
|
|
4589
4771
|
}
|
|
4590
4772
|
|
|
4591
|
-
function stringifySVG(svg){
|
|
4592
|
-
|
|
4773
|
+
function stringifySVG(svg) {
|
|
4774
|
+
let markup = new XMLSerializer().serializeToString(svg);
|
|
4593
4775
|
markup = markup
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4776
|
+
.replace(/\t/g, "")
|
|
4777
|
+
.replace(/[\n\r|]/g, "\n")
|
|
4778
|
+
.replace(/\n\s*\n/g, '\n')
|
|
4779
|
+
.replace(/ +/g, ' ');
|
|
4598
4780
|
|
|
4599
4781
|
return markup
|
|
4600
4782
|
}
|
|
@@ -4604,12 +4786,19 @@ function refineRoundedCorners(pathData, {
|
|
|
4604
4786
|
tolerance = 1
|
|
4605
4787
|
} = {}) {
|
|
4606
4788
|
|
|
4789
|
+
// min size threshold for corners
|
|
4790
|
+
threshold *= tolerance;
|
|
4791
|
+
|
|
4607
4792
|
let l = pathData.length;
|
|
4608
4793
|
|
|
4609
4794
|
// add fist command
|
|
4610
4795
|
let pathDataN = [pathData[0]];
|
|
4611
4796
|
|
|
4612
4797
|
let isClosed = pathData[l - 1].type.toLowerCase() === 'z';
|
|
4798
|
+
let zIsLineto = isClosed ?
|
|
4799
|
+
(pathData[l-1].p.x === pathData[0].p0.x && pathData[l-1].p.y === pathData[0].p0.y)
|
|
4800
|
+
: false ;
|
|
4801
|
+
|
|
4613
4802
|
let lastOff = isClosed ? 2 : 1;
|
|
4614
4803
|
|
|
4615
4804
|
let comLast = pathData[l - lastOff];
|
|
@@ -4618,7 +4807,7 @@ function refineRoundedCorners(pathData, {
|
|
|
4618
4807
|
let firstIsLine = pathData[1].type === 'L';
|
|
4619
4808
|
let firstIsBez = pathData[1].type === 'C';
|
|
4620
4809
|
|
|
4621
|
-
let normalizeClose = isClosed && firstIsBez;
|
|
4810
|
+
let normalizeClose = isClosed && firstIsBez && (lastIsLine || zIsLineto);
|
|
4622
4811
|
|
|
4623
4812
|
// normalize closepath to lineto
|
|
4624
4813
|
if (normalizeClose) {
|
|
@@ -4635,9 +4824,8 @@ function refineRoundedCorners(pathData, {
|
|
|
4635
4824
|
// search small cubic segments enclosed by linetos
|
|
4636
4825
|
if ((type === 'L' && comN && comN.type === 'C') ||
|
|
4637
4826
|
(type === 'C' && comN && comN.type === 'L')
|
|
4638
|
-
|
|
4639
4827
|
) {
|
|
4640
|
-
let comL0 = com;
|
|
4828
|
+
let comL0 = type==='L' ? com : null;
|
|
4641
4829
|
let comL1 = null;
|
|
4642
4830
|
let comBez = [];
|
|
4643
4831
|
let offset = 0;
|
|
@@ -4650,6 +4838,11 @@ function refineRoundedCorners(pathData, {
|
|
|
4650
4838
|
|
|
4651
4839
|
}
|
|
4652
4840
|
|
|
4841
|
+
if(!comL0) {
|
|
4842
|
+
pathDataN.push(com);
|
|
4843
|
+
continue
|
|
4844
|
+
}
|
|
4845
|
+
|
|
4653
4846
|
// closing corner to start
|
|
4654
4847
|
if (isClosed && lastIsBez && firstIsLine && i === l - lastOff - 1) {
|
|
4655
4848
|
comL1 = pathData[1];
|
|
@@ -4689,43 +4882,41 @@ function refineRoundedCorners(pathData, {
|
|
|
4689
4882
|
|
|
4690
4883
|
let signChange = (area1 < 0 && area2 > 0) || (area1 > 0 && area2 < 0);
|
|
4691
4884
|
|
|
4692
|
-
|
|
4885
|
+
// exclude mid bezier segments that are larger than surrounding linetos
|
|
4886
|
+
let bezThresh = len3*0.5 * tolerance;
|
|
4887
|
+
let isSmall = bezThresh < len1 && bezThresh < len2 ;
|
|
4693
4888
|
|
|
4694
|
-
|
|
4695
|
-
if (ptQ) {
|
|
4889
|
+
if (comBez.length && !signChange && isSmall ) {
|
|
4696
4890
|
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
let dist2 = getDistAv(ptQ, comL1.p0)
|
|
4700
|
-
let diff = Math.abs(dist1-dist2)
|
|
4701
|
-
let rat = diff/Math.max(dist1, dist2)
|
|
4702
|
-
console.log('rat', rat);
|
|
4703
|
-
*/
|
|
4891
|
+
let isFlatBezier = Math.abs(area2) <= getSquareDistance(comBez[0].p0, comBez[0].p)*0.005;
|
|
4892
|
+
let ptQ = !isFlatBezier ? checkLineIntersection(comL0.p0, comL0.p, comL1.p0, comL1.p, false) : null;
|
|
4704
4893
|
|
|
4705
|
-
|
|
4706
|
-
// adjust curve start and end to meet original
|
|
4707
|
-
let t = 1
|
|
4894
|
+
if (!isFlatBezier && ptQ) {
|
|
4708
4895
|
|
|
4709
|
-
|
|
4896
|
+
// final check: mid point proximity
|
|
4897
|
+
let ptM = pointAtT([comL0.p, ptQ, comL1.p0], 0.5);
|
|
4710
4898
|
|
|
4711
|
-
|
|
4712
|
-
comL0.values = [p0_2.x, p0_2.y]
|
|
4899
|
+
let ptM_bez = comBez.length===1 ? pointAtT( [comBez[0].p0, comBez[0].cp1, comBez[0].cp2, comBez[0].p], 0.5 ) : comBez[0].p ;
|
|
4713
4900
|
|
|
4714
|
-
let
|
|
4901
|
+
let dist1 = getDistAv(ptM, ptM_bez);
|
|
4715
4902
|
|
|
4716
|
-
|
|
4903
|
+
// not in tolerance – rturn original command
|
|
4904
|
+
if(dist1>len3){
|
|
4717
4905
|
|
|
4718
|
-
|
|
4906
|
+
pathDataN.push(com);
|
|
4907
|
+
} else {
|
|
4719
4908
|
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4909
|
+
let comQ = { type: 'Q', values: [ptQ.x, ptQ.y, comL1.p0.x, comL1.p0.y] };
|
|
4910
|
+
comQ.p0 = comL0.p;
|
|
4911
|
+
comQ.cp1 = ptQ;
|
|
4912
|
+
comQ.p = comL1.p0;
|
|
4913
|
+
|
|
4914
|
+
// add quadratic command
|
|
4915
|
+
pathDataN.push(comL0, comQ);
|
|
4916
|
+
i += offset;
|
|
4917
|
+
continue;
|
|
4918
|
+
}
|
|
4724
4919
|
|
|
4725
|
-
// add quadratic command
|
|
4726
|
-
pathDataN.push(comL0, comQ);
|
|
4727
|
-
i += offset;
|
|
4728
|
-
continue;
|
|
4729
4920
|
}
|
|
4730
4921
|
}
|
|
4731
4922
|
}
|
|
@@ -4741,7 +4932,7 @@ function refineRoundedCorners(pathData, {
|
|
|
4741
4932
|
}
|
|
4742
4933
|
|
|
4743
4934
|
// revert close path normalization
|
|
4744
|
-
if (normalizeClose) {
|
|
4935
|
+
if (normalizeClose || (isClosed && pathDataN[pathDataN.length-1].type!=='Z') ) {
|
|
4745
4936
|
pathDataN.push({ type: 'Z', values: [] });
|
|
4746
4937
|
}
|
|
4747
4938
|
|
|
@@ -4749,6 +4940,313 @@ function refineRoundedCorners(pathData, {
|
|
|
4749
4940
|
|
|
4750
4941
|
}
|
|
4751
4942
|
|
|
4943
|
+
function getArcFromPoly(pts) {
|
|
4944
|
+
if (pts.length < 3) return false
|
|
4945
|
+
|
|
4946
|
+
// Pick 3 well-spaced points
|
|
4947
|
+
let p1 = pts[0];
|
|
4948
|
+
let p2 = pts[Math.floor(pts.length / 2)];
|
|
4949
|
+
let p3 = pts[pts.length - 1];
|
|
4950
|
+
|
|
4951
|
+
let x1 = p1.x, y1 = p1.y;
|
|
4952
|
+
let x2 = p2.x, y2 = p2.y;
|
|
4953
|
+
let x3 = p3.x, y3 = p3.y;
|
|
4954
|
+
|
|
4955
|
+
let a = x1 - x2;
|
|
4956
|
+
let b = y1 - y2;
|
|
4957
|
+
let c = x1 - x3;
|
|
4958
|
+
let d = y1 - y3;
|
|
4959
|
+
|
|
4960
|
+
let e = ((x1 * x1 - x2 * x2) + (y1 * y1 - y2 * y2)) / 2;
|
|
4961
|
+
let f = ((x1 * x1 - x3 * x3) + (y1 * y1 - y3 * y3)) / 2;
|
|
4962
|
+
|
|
4963
|
+
let det = a * d - b * c;
|
|
4964
|
+
|
|
4965
|
+
if (Math.abs(det) < 1e-10) {
|
|
4966
|
+
console.warn("Points are collinear or numerically unstable");
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
|
|
4970
|
+
// find center of arc
|
|
4971
|
+
let cx = (d * e - b * f) / det;
|
|
4972
|
+
let cy = (-c * e + a * f) / det;
|
|
4973
|
+
let centroid = { x: cx, y: cy };
|
|
4974
|
+
|
|
4975
|
+
// Radius (use start point)
|
|
4976
|
+
let r = getDistance(centroid, p1);
|
|
4977
|
+
|
|
4978
|
+
let angleData = getDeltaAngle(centroid, p1, p3);
|
|
4979
|
+
let {deltaAngle, startAngle, endAngle} = angleData;
|
|
4980
|
+
|
|
4981
|
+
return {
|
|
4982
|
+
centroid,
|
|
4983
|
+
r,
|
|
4984
|
+
startAngle,
|
|
4985
|
+
endAngle,
|
|
4986
|
+
deltaAngle
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
|
|
4990
|
+
function refineRoundSegments(pathData, {
|
|
4991
|
+
threshold = 0,
|
|
4992
|
+
tolerance = 1,
|
|
4993
|
+
// take arcs or cubic beziers
|
|
4994
|
+
toCubic = false,
|
|
4995
|
+
debug = false
|
|
4996
|
+
} = {}) {
|
|
4997
|
+
|
|
4998
|
+
// min size threshold for corners
|
|
4999
|
+
threshold *= tolerance;
|
|
5000
|
+
|
|
5001
|
+
let l = pathData.length;
|
|
5002
|
+
|
|
5003
|
+
// add fist command
|
|
5004
|
+
let pathDataN = [pathData[0]];
|
|
5005
|
+
|
|
5006
|
+
// just for debugging
|
|
5007
|
+
let pathDataTest = [];
|
|
5008
|
+
|
|
5009
|
+
for (let i = 1; i < l; i++) {
|
|
5010
|
+
let com = pathData[i];
|
|
5011
|
+
let { type } = com;
|
|
5012
|
+
let comP = pathData[i - 1];
|
|
5013
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
5014
|
+
let comN2 = pathData[i + 2] ? pathData[i + 2] : null;
|
|
5015
|
+
let comN3 = pathData[i + 3] ? pathData[i + 3] : null;
|
|
5016
|
+
let comBez = null;
|
|
5017
|
+
|
|
5018
|
+
if ((com.type === 'C' || com.type === 'Q')) comBez = com;
|
|
5019
|
+
else if (comN && (comN.type === 'C' || comN.type === 'Q')) comBez = comN;
|
|
5020
|
+
|
|
5021
|
+
let cpts = comBez ? (comBez.type === 'C' ? [comBez.p0, comBez.cp1, comBez.cp2, comBez.p] : [comBez.p0, comBez.cp1, comBez.p]) : [];
|
|
5022
|
+
|
|
5023
|
+
let areaBez = 0;
|
|
5024
|
+
let areaLines = 0;
|
|
5025
|
+
let signChange = false;
|
|
5026
|
+
let L1, L2;
|
|
5027
|
+
let combine = false;
|
|
5028
|
+
|
|
5029
|
+
let p0_S, p_S;
|
|
5030
|
+
let poly = [];
|
|
5031
|
+
let pMid;
|
|
5032
|
+
|
|
5033
|
+
// 2. line-line-bezier-line-line
|
|
5034
|
+
if (
|
|
5035
|
+
comP.type === 'L' &&
|
|
5036
|
+
type === 'L' &&
|
|
5037
|
+
comBez &&
|
|
5038
|
+
comN2.type === 'L' &&
|
|
5039
|
+
comN3 && (comN3.type === 'L' || comN3.type === 'Z')
|
|
5040
|
+
) {
|
|
5041
|
+
|
|
5042
|
+
L1 = [com.p0, com.p];
|
|
5043
|
+
L2 = [comN2.p0, comN2.p];
|
|
5044
|
+
p0_S = com.p0;
|
|
5045
|
+
p_S = comN2.p;
|
|
5046
|
+
|
|
5047
|
+
// don't allow sign changes
|
|
5048
|
+
areaBez = getPolygonArea(cpts, false);
|
|
5049
|
+
areaLines = getPolygonArea([...L1, ...L2], false);
|
|
5050
|
+
signChange = (areaBez < 0 && areaLines > 0) || (areaBez > 0 && areaLines < 0);
|
|
5051
|
+
|
|
5052
|
+
if (!signChange) {
|
|
5053
|
+
|
|
5054
|
+
// mid point of mid bezier
|
|
5055
|
+
pMid = pointAtT(cpts, 0.5);
|
|
5056
|
+
|
|
5057
|
+
// add to poly
|
|
5058
|
+
poly = [p0_S, pMid, p_S];
|
|
5059
|
+
|
|
5060
|
+
combine = true;
|
|
5061
|
+
}
|
|
5062
|
+
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
// 1. line-bezier-bezier-line
|
|
5066
|
+
else if ((type === 'C' || type === 'Q') && comP.type === 'L') {
|
|
5067
|
+
|
|
5068
|
+
// 1.2 next is cubic next is lineto
|
|
5069
|
+
if ((comN.type === 'C' || comN.type === 'Q') && comN2.type === 'L') {
|
|
5070
|
+
|
|
5071
|
+
combine = true;
|
|
5072
|
+
|
|
5073
|
+
L1 = [comP.p0, comP.p];
|
|
5074
|
+
L2 = [comN2.p0, comN2.p];
|
|
5075
|
+
p0_S = comP.p;
|
|
5076
|
+
p_S = comN2.p0;
|
|
5077
|
+
|
|
5078
|
+
// mid point of mid bezier
|
|
5079
|
+
pMid = comBez.p;
|
|
5080
|
+
|
|
5081
|
+
// add to poly
|
|
5082
|
+
poly = [p0_S, comBez.p, p_S];
|
|
5083
|
+
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
/**
|
|
5088
|
+
* calculate either combined
|
|
5089
|
+
* cubic or arc commands
|
|
5090
|
+
*/
|
|
5091
|
+
if (combine) {
|
|
5092
|
+
|
|
5093
|
+
// try to find center of arc
|
|
5094
|
+
let arcProps = getArcFromPoly(poly);
|
|
5095
|
+
if (arcProps) {
|
|
5096
|
+
|
|
5097
|
+
let { centroid, r, deltaAngle, startAngle, endAngle } = arcProps;
|
|
5098
|
+
|
|
5099
|
+
let xAxisRotation = 0;
|
|
5100
|
+
let sweep = deltaAngle > 0 ? 1 : 0;
|
|
5101
|
+
let largeArc = Math.abs(deltaAngle) > Math.PI ? 1 : 0;
|
|
5102
|
+
|
|
5103
|
+
let pCM = rotatePoint(p0_S, centroid.x, centroid.y, deltaAngle * 0.5);
|
|
5104
|
+
|
|
5105
|
+
let dist2 = getDistAv(pCM, pMid);
|
|
5106
|
+
let thresh = getDistAv(p0_S, p_S) * 0.05;
|
|
5107
|
+
let bezierCommands;
|
|
5108
|
+
|
|
5109
|
+
// point is close enough
|
|
5110
|
+
if (dist2 < thresh) {
|
|
5111
|
+
|
|
5112
|
+
bezierCommands = arcToBezierResolved(
|
|
5113
|
+
{
|
|
5114
|
+
p0: p0_S,
|
|
5115
|
+
p: p_S,
|
|
5116
|
+
centroid,
|
|
5117
|
+
rx: r,
|
|
5118
|
+
ry: r,
|
|
5119
|
+
xAxisRotation,
|
|
5120
|
+
sweep,
|
|
5121
|
+
largeArc,
|
|
5122
|
+
deltaAngle,
|
|
5123
|
+
startAngle,
|
|
5124
|
+
endAngle
|
|
5125
|
+
}
|
|
5126
|
+
);
|
|
5127
|
+
|
|
5128
|
+
if(bezierCommands.length === 1){
|
|
5129
|
+
|
|
5130
|
+
// prefer more compact quadratic - otherwise arcs
|
|
5131
|
+
let comBezier = revertCubicQuadratic(p0_S, bezierCommands[0].cp1, bezierCommands[0].cp2, p_S);
|
|
5132
|
+
|
|
5133
|
+
if (comBezier.type === 'Q') {
|
|
5134
|
+
toCubic = true;
|
|
5135
|
+
}
|
|
5136
|
+
|
|
5137
|
+
com = comBezier;
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
// prefer arcs if 2 cubics are required
|
|
5141
|
+
if (bezierCommands.length > 1) toCubic = false;
|
|
5142
|
+
|
|
5143
|
+
// return elliptic arc commands
|
|
5144
|
+
if (!toCubic) {
|
|
5145
|
+
// rewrite simplified command
|
|
5146
|
+
com.type = 'A';
|
|
5147
|
+
com.values = [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y];
|
|
5148
|
+
}
|
|
5149
|
+
|
|
5150
|
+
com.p0 = p0_S;
|
|
5151
|
+
com.p = p_S;
|
|
5152
|
+
com.extreme = false;
|
|
5153
|
+
com.corner = false;
|
|
5154
|
+
|
|
5155
|
+
// test rendering
|
|
5156
|
+
|
|
5157
|
+
if (debug) {
|
|
5158
|
+
// arcs
|
|
5159
|
+
if (!toCubic) {
|
|
5160
|
+
pathDataTest = [
|
|
5161
|
+
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
5162
|
+
{ type: 'A', values: [r, r, xAxisRotation, largeArc, sweep, p_S.x, p_S.y] },
|
|
5163
|
+
];
|
|
5164
|
+
}
|
|
5165
|
+
// cubics
|
|
5166
|
+
else {
|
|
5167
|
+
pathDataTest = [
|
|
5168
|
+
{ type: 'M', values: [p0_S.x, p0_S.y] },
|
|
5169
|
+
...bezierCommands
|
|
5170
|
+
];
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
let d = pathDataToD(pathDataTest);
|
|
5174
|
+
renderPath(markers, d, 'orange', '0.5%', '0.5');
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
pathDataN.push(com);
|
|
5178
|
+
i++;
|
|
5179
|
+
continue
|
|
5180
|
+
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
// pass through
|
|
5186
|
+
pathDataN.push(com);
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
return pathDataN;
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
function refineClosingCommand(pathData = [], {
|
|
5193
|
+
threshold = 0,
|
|
5194
|
+
} = {}) {
|
|
5195
|
+
|
|
5196
|
+
let l = pathData.length;
|
|
5197
|
+
let comLast = pathData[l - 1];
|
|
5198
|
+
let isClosed = comLast.type.toLowerCase() === 'z';
|
|
5199
|
+
let idxPenultimate = isClosed ? l - 2 : l - 1;
|
|
5200
|
+
let comPenultimate = isClosed ? pathData[idxPenultimate] : pathData[idxPenultimate];
|
|
5201
|
+
let valsPen = comPenultimate.values.slice(-2);
|
|
5202
|
+
|
|
5203
|
+
let M = { x: pathData[0].values[0], y: pathData[0].values[1] };
|
|
5204
|
+
let pPen = { x: valsPen[0], y: valsPen[1] };
|
|
5205
|
+
let dist = getDistAv(M, pPen);
|
|
5206
|
+
|
|
5207
|
+
// adjust last coordinates for better reordering
|
|
5208
|
+
if (dist && dist < threshold) {
|
|
5209
|
+
|
|
5210
|
+
let valsLast = pathData[idxPenultimate].values;
|
|
5211
|
+
let valsLastLen = valsLast.length;
|
|
5212
|
+
pathData[idxPenultimate].values[valsLastLen - 2] = M.x;
|
|
5213
|
+
pathData[idxPenultimate].values[valsLastLen - 1] = M.y;
|
|
5214
|
+
|
|
5215
|
+
// adjust cpts
|
|
5216
|
+
let comFirst = pathData[1];
|
|
5217
|
+
|
|
5218
|
+
if (comFirst.type === 'C' && comPenultimate.type === 'C') {
|
|
5219
|
+
let dx1 = Math.abs(comFirst.values[0] - comPenultimate.values[2]);
|
|
5220
|
+
let dy1 = Math.abs(comFirst.values[1] - comPenultimate.values[3]);
|
|
5221
|
+
|
|
5222
|
+
let dx2 = Math.abs(pathData[1].values[0] - comFirst.values[0]);
|
|
5223
|
+
let dy2 = Math.abs(pathData[1].values[1] - comFirst.values[1]);
|
|
5224
|
+
|
|
5225
|
+
let dx3 = Math.abs(pathData[1].values[0] - comPenultimate.values[2]);
|
|
5226
|
+
let dy3 = Math.abs(pathData[1].values[1] - comPenultimate.values[3]);
|
|
5227
|
+
|
|
5228
|
+
let ver = dx2 < threshold && dx3 < threshold && dy1;
|
|
5229
|
+
let hor = (dy2 < threshold && dy3 < threshold) && dx1;
|
|
5230
|
+
|
|
5231
|
+
if (dx1 && dx1 < threshold && ver) {
|
|
5232
|
+
|
|
5233
|
+
pathData[1].values[0] = M.x;
|
|
5234
|
+
pathData[idxPenultimate].values[2] = M.x;
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
if (dy1 && dy1 < threshold && hor) {
|
|
5238
|
+
|
|
5239
|
+
pathData[1].values[1] = M.y;
|
|
5240
|
+
pathData[idxPenultimate].values[3] = M.y;
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
|
|
5246
|
+
return pathData;
|
|
5247
|
+
|
|
5248
|
+
}
|
|
5249
|
+
|
|
4752
5250
|
function svgPathSimplify(input = '', {
|
|
4753
5251
|
|
|
4754
5252
|
// return svg markup or object
|
|
@@ -4767,13 +5265,15 @@ function svgPathSimplify(input = '', {
|
|
|
4767
5265
|
|
|
4768
5266
|
simplifyBezier = true,
|
|
4769
5267
|
optimizeOrder = true,
|
|
5268
|
+
autoClose = true,
|
|
4770
5269
|
removeZeroLength = true,
|
|
5270
|
+
refineClosing = true,
|
|
4771
5271
|
removeColinear = true,
|
|
4772
5272
|
flatBezierToLinetos = true,
|
|
4773
5273
|
revertToQuadratics = true,
|
|
4774
5274
|
|
|
4775
5275
|
refineExtremes = true,
|
|
4776
|
-
|
|
5276
|
+
simplifyCorners = false,
|
|
4777
5277
|
|
|
4778
5278
|
keepExtremes = true,
|
|
4779
5279
|
keepCorners = true,
|
|
@@ -4782,6 +5282,8 @@ function svgPathSimplify(input = '', {
|
|
|
4782
5282
|
addExtremes = false,
|
|
4783
5283
|
removeOrphanSubpaths = false,
|
|
4784
5284
|
|
|
5285
|
+
simplifyRound = false,
|
|
5286
|
+
|
|
4785
5287
|
// svg path optimizations
|
|
4786
5288
|
decimals = 3,
|
|
4787
5289
|
autoAccuracy = true,
|
|
@@ -4834,7 +5336,7 @@ function svgPathSimplify(input = '', {
|
|
|
4834
5336
|
|
|
4835
5337
|
// stringify to compare lengths
|
|
4836
5338
|
|
|
4837
|
-
let dStr = d.map(com=>{return `${com.type} ${com.values.join(' ')}`}).join(' ')
|
|
5339
|
+
let dStr = d.map(com => { return `${com.type} ${com.values.join(' ')}` }).join(' ');
|
|
4838
5340
|
svgSize = dStr.length;
|
|
4839
5341
|
|
|
4840
5342
|
}
|
|
@@ -4899,8 +5401,9 @@ function svgPathSimplify(input = '', {
|
|
|
4899
5401
|
// cleaned up pathData
|
|
4900
5402
|
|
|
4901
5403
|
// reset array
|
|
4902
|
-
let
|
|
5404
|
+
let pathDataPlusArr = [];
|
|
4903
5405
|
|
|
5406
|
+
// loop sub paths
|
|
4904
5407
|
for (let i = 0; i < lenSub; i++) {
|
|
4905
5408
|
|
|
4906
5409
|
let pathDataSub = subPathArr[i];
|
|
@@ -4924,6 +5427,9 @@ function svgPathSimplify(input = '', {
|
|
|
4924
5427
|
|
|
4925
5428
|
// simplify beziers
|
|
4926
5429
|
let { pathData, bb, dimA } = pathDataPlus;
|
|
5430
|
+
|
|
5431
|
+
if (refineClosing) pathData = refineClosingCommand(pathData, { threshold: dimA * 0.001 });
|
|
5432
|
+
|
|
4927
5433
|
pathData = simplifyBezier ? simplifyPathDataCubic(pathData, { simplifyBezier, keepInflections, keepExtremes, keepCorners, extrapolateDominant, revertToQuadratics, tolerance, reverse }) : pathData;
|
|
4928
5434
|
|
|
4929
5435
|
// refine extremes
|
|
@@ -4937,7 +5443,7 @@ function svgPathSimplify(input = '', {
|
|
|
4937
5443
|
|
|
4938
5444
|
let thresh = 1;
|
|
4939
5445
|
|
|
4940
|
-
for(let c=0, l=pathData.length; c<l; c++){
|
|
5446
|
+
for (let c = 0, l = pathData.length; c < l; c++) {
|
|
4941
5447
|
let com = pathData[c];
|
|
4942
5448
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4943
5449
|
if (type === 'C') {
|
|
@@ -4950,6 +5456,9 @@ function svgPathSimplify(input = '', {
|
|
|
4950
5456
|
// combine adjacent cubics
|
|
4951
5457
|
pathData = combineArcs(pathData);
|
|
4952
5458
|
|
|
5459
|
+
/*
|
|
5460
|
+
*/
|
|
5461
|
+
|
|
4953
5462
|
}
|
|
4954
5463
|
|
|
4955
5464
|
// post processing: remove flat beziers
|
|
@@ -4958,15 +5467,18 @@ function svgPathSimplify(input = '', {
|
|
|
4958
5467
|
}
|
|
4959
5468
|
|
|
4960
5469
|
// refine corners
|
|
4961
|
-
if(
|
|
5470
|
+
if (simplifyCorners) {
|
|
5471
|
+
|
|
4962
5472
|
let threshold = (bb.width + bb.height) / 2 * 0.1;
|
|
4963
5473
|
pathData = refineRoundedCorners(pathData, { threshold, tolerance });
|
|
4964
|
-
|
|
4965
5474
|
}
|
|
4966
5475
|
|
|
5476
|
+
// refine round segment sequences
|
|
5477
|
+
if (simplifyRound) pathData = refineRoundSegments(pathData);
|
|
5478
|
+
|
|
4967
5479
|
// simplify to quadratics
|
|
4968
5480
|
if (revertToQuadratics) {
|
|
4969
|
-
for(let c=0, l=pathData.length; c<l; c++){
|
|
5481
|
+
for (let c = 0, l = pathData.length; c < l; c++) {
|
|
4970
5482
|
let com = pathData[c];
|
|
4971
5483
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
4972
5484
|
if (type === 'C') {
|
|
@@ -4984,15 +5496,22 @@ function svgPathSimplify(input = '', {
|
|
|
4984
5496
|
}
|
|
4985
5497
|
|
|
4986
5498
|
// optimize close path
|
|
4987
|
-
if (optimizeOrder) pathData = optimizeClosePath(pathData);
|
|
5499
|
+
if (optimizeOrder) pathData = optimizeClosePath(pathData, { autoClose });
|
|
4988
5500
|
|
|
4989
5501
|
// update
|
|
4990
|
-
|
|
5502
|
+
|
|
5503
|
+
pathDataPlusArr.push({ pathData, bb });
|
|
4991
5504
|
|
|
4992
5505
|
}
|
|
4993
5506
|
|
|
5507
|
+
// sort to top left
|
|
5508
|
+
if (optimizeOrder) pathDataPlusArr = pathDataPlusArr.sort((a, b) => a.bb.y - b.bb.y || a.bb.x - b.bb.x);
|
|
5509
|
+
|
|
4994
5510
|
// flatten compound paths
|
|
4995
|
-
pathData =
|
|
5511
|
+
pathData = [];
|
|
5512
|
+
pathDataPlusArr.forEach(sub => {
|
|
5513
|
+
pathData.push(...sub.pathData);
|
|
5514
|
+
});
|
|
4996
5515
|
|
|
4997
5516
|
if (autoAccuracy) {
|
|
4998
5517
|
decimals = detectAccuracy(pathData);
|
|
@@ -5092,11 +5611,7 @@ const {
|
|
|
5092
5611
|
log, hypot, max, min, pow, random, round, sin, sqrt, tan, PI
|
|
5093
5612
|
} = Math;
|
|
5094
5613
|
|
|
5095
|
-
|
|
5096
|
-
import {XMLSerializerPoly, DOMParserPoly} from './dom_polyfills';
|
|
5097
|
-
export {XMLSerializerPoly as XMLSerializerPoly};
|
|
5098
|
-
export {DOMParserPoly as DOMParserPoly};
|
|
5099
|
-
*/
|
|
5614
|
+
// just for visual debugging
|
|
5100
5615
|
|
|
5101
5616
|
// IIFE
|
|
5102
5617
|
if (typeof window !== 'undefined') {
|