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