svg-path-simplify 0.0.1 → 0.0.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 +28 -1
- package/dist/svg-path-simplify.esm.js +4040 -0
- package/dist/svg-path-simplify.esm.min.js +1 -0
- package/dist/svg-path-simplify.js +4065 -0
- package/dist/svg-path-simplify.min.js +1 -0
- package/dist/svg-path-simplify.node.js +4062 -0
- package/dist/svg-path-simplify.node.min.js +1 -0
- package/index.html +222 -0
- package/package.json +2 -2
- package/src/constants.js +4 -0
- package/src/index.js +18 -3
- package/src/pathData_simplify_cubic.js +324 -0
- package/src/pathData_simplify_cubic_arr.js +50 -0
- package/src/pathData_simplify_cubic_extrapolate.js +220 -0
- package/src/pathSimplify-main.js +294 -0
- package/src/svgii/...parse.js +402 -0
- package/src/svgii/geometry.js +1096 -0
- package/src/svgii/geometry_area.js +265 -0
- package/src/svgii/geometry_bbox.js +223 -0
- package/src/svgii/pathData_analyze.js +896 -0
- package/src/svgii/pathData_convert.js +1180 -0
- package/src/svgii/pathData_parse.js +487 -0
- package/src/svgii/pathData_remove_collinear.js +85 -0
- package/src/svgii/pathData_remove_zerolength.js +28 -0
- package/src/svgii/pathData_reorder.js +204 -0
- package/src/svgii/pathData_reverse.js +124 -0
- package/src/svgii/pathData_scale.js +42 -0
- package/src/svgii/pathData_split.js +449 -0
- package/src/svgii/pathData_stringify.js +146 -0
- package/src/svgii/pathData_toPolygon.js +92 -0
- package/src/svgii/pathdata_cleanup.js +363 -0
- package/src/svgii/poly_analyze.js +172 -0
- package/src/svgii/poly_to_pathdata.js +185 -0
- package/src/svgii/rounding.js +154 -0
- package/src/svgii/simplify.js +248 -0
- package/src/svgii/simplify_bezier.js +470 -0
- package/src/svgii/simplify_linetos.js +93 -0
- package/src/svgii/simplify_polygon.js +135 -0
- package/src/svgii/stringify.js +103 -0
- package/src/svgii/svg_cleanup.js +80 -0
- package/src/svgii/visualize.js +317 -0
- package/LICENSE +0 -21
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
|
|
2
|
+
//import { splitSubpaths } from "./convert_segments";
|
|
3
|
+
import { splitSubpaths } from './pathData_split.js';
|
|
4
|
+
|
|
5
|
+
import { pointAtT, svgArcToCenterParam, getAngle, checkLineIntersection } from "./geometry";
|
|
6
|
+
import { getSubPathBBoxes, checkBBoxIntersections } from "./geometry_bbox";
|
|
7
|
+
import { renderPoint } from './visualize.js';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* get pathdata area
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export function getPathArea(pathData, decimals = 9) {
|
|
15
|
+
let totalArea = 0;
|
|
16
|
+
let polyPoints = [];
|
|
17
|
+
|
|
18
|
+
//check subpaths
|
|
19
|
+
let subPathsData = splitSubpaths(pathData);
|
|
20
|
+
let isCompoundPath = subPathsData.length > 1 ? true : false;
|
|
21
|
+
let counterShapes = [];
|
|
22
|
+
|
|
23
|
+
// check intersections for compund paths
|
|
24
|
+
if (isCompoundPath) {
|
|
25
|
+
let bboxArr = getSubPathBBoxes(subPathsData);
|
|
26
|
+
|
|
27
|
+
bboxArr.forEach(function (bb, b) {
|
|
28
|
+
//let path1 = path;
|
|
29
|
+
for (let i = 0; i < bboxArr.length; i++) {
|
|
30
|
+
let bb2 = bboxArr[i];
|
|
31
|
+
if (bb != bb2) {
|
|
32
|
+
let intersects = checkBBoxIntersections(bb, bb2);
|
|
33
|
+
if (intersects) {
|
|
34
|
+
counterShapes.push(i);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
subPathsData.forEach((pathData, d) => {
|
|
42
|
+
//reset polygon points for each segment
|
|
43
|
+
polyPoints = [];
|
|
44
|
+
let comArea = 0;
|
|
45
|
+
let pathArea = 0;
|
|
46
|
+
let multiplier = 1;
|
|
47
|
+
let pts = [];
|
|
48
|
+
|
|
49
|
+
pathData.forEach(function (com, i) {
|
|
50
|
+
let [type, values] = [com.type, com.values];
|
|
51
|
+
let valuesL = values.length;
|
|
52
|
+
|
|
53
|
+
if (values.length) {
|
|
54
|
+
let prevC = i > 0 ? pathData[i - 1] : pathData[0];
|
|
55
|
+
let prevCVals = prevC.values;
|
|
56
|
+
let prevCValsL = prevCVals.length;
|
|
57
|
+
let p0 = { x: prevCVals[prevCValsL - 2], y: prevCVals[prevCValsL - 1] };
|
|
58
|
+
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
59
|
+
|
|
60
|
+
// C commands
|
|
61
|
+
if (type === 'C' || type === 'Q') {
|
|
62
|
+
let cp1 = { x: values[0], y: values[1] };
|
|
63
|
+
pts = type === 'C' ? [p0, cp1, { x: values[2], y: values[3] }, p] : [p0, cp1, p];
|
|
64
|
+
let areaBez = Math.abs(getBezierArea(pts))
|
|
65
|
+
comArea += areaBez;
|
|
66
|
+
|
|
67
|
+
//push points to calculate inner/remaining polygon area
|
|
68
|
+
polyPoints.push(p0, p);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// A commands
|
|
73
|
+
else if (type === 'A') {
|
|
74
|
+
let arcData = svgArcToCenterParam(p0.x, p0.y, com.values[0], com.values[1], com.values[2], com.values[3], com.values[4], p.x, p.y)
|
|
75
|
+
let { cx, cy, rx, ry, startAngle, endAngle, deltaAngle } = arcData
|
|
76
|
+
|
|
77
|
+
let arcArea = Math.abs(getEllipseArea(rx, ry, startAngle, endAngle));
|
|
78
|
+
|
|
79
|
+
// subtract remaining polygon between p0, center and p
|
|
80
|
+
let polyArea = Math.abs(getPolygonArea([p0, { x: cx, y: cy }, p]));
|
|
81
|
+
arcArea -= polyArea;
|
|
82
|
+
|
|
83
|
+
//push points to calculate inner/remaining polygon area
|
|
84
|
+
polyPoints.push(p0, p);
|
|
85
|
+
comArea += arcArea;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// L commands
|
|
89
|
+
else {
|
|
90
|
+
polyPoints.push(p0, p);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
let areaPoly = getPolygonArea(polyPoints);
|
|
97
|
+
|
|
98
|
+
//subtract area by negative multiplier
|
|
99
|
+
if (counterShapes.indexOf(d) !== -1) {
|
|
100
|
+
multiplier = -1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//values have the same sign - subtract polygon area
|
|
104
|
+
if (
|
|
105
|
+
(areaPoly < 0 && comArea < 0)
|
|
106
|
+
) {
|
|
107
|
+
// are negative
|
|
108
|
+
pathArea = (Math.abs(comArea) - Math.abs(areaPoly)) * multiplier;
|
|
109
|
+
//console.log('negative area', pathArea );
|
|
110
|
+
} else {
|
|
111
|
+
pathArea = (Math.abs(comArea) + Math.abs(areaPoly)) * multiplier;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
totalArea += pathArea;
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
//if(decimals>-1) totalArea = +totalArea.toFixed(decimals)
|
|
118
|
+
//console.log('negative area', totalArea );
|
|
119
|
+
|
|
120
|
+
return totalArea;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* compare bezier area diffs
|
|
126
|
+
*/
|
|
127
|
+
export function getBezierAreaAccuracy(cpts = [], areaPath = 0, areaPoly = 0, tolerance = 0.75) {
|
|
128
|
+
|
|
129
|
+
let type = cpts.length === 4 ? 'C' : 'Q';
|
|
130
|
+
let p0 = cpts.shift();
|
|
131
|
+
let cp1 = cpts[0];
|
|
132
|
+
let p = cpts[cpts.length - 1];
|
|
133
|
+
let cp2 = type === 'C' ? cpts[1] : cp1;
|
|
134
|
+
|
|
135
|
+
let res = { accurate: false, areaDiff: Infinity, comArea: null, cpArea: null, signChange: null }
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* check self intersections
|
|
139
|
+
* won't work for simplifications
|
|
140
|
+
*/
|
|
141
|
+
let selfIntersecting = checkLineIntersection(p0, p, cp1, cp2, true);
|
|
142
|
+
let selfIntersecting2 = checkLineIntersection(p0, cp1, p, cp2, true);
|
|
143
|
+
if (selfIntersecting || selfIntersecting2) {
|
|
144
|
+
//renderPoint(svg1, p, 'yellow')
|
|
145
|
+
return res
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* check sign changes
|
|
152
|
+
* from cpts poly
|
|
153
|
+
* they indicate a wrong approximation
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
//cpArea = getPolygonArea([p0, cp1, p]);
|
|
157
|
+
res.cpArea = getPolygonArea([p0, ...cpts]);
|
|
158
|
+
res.signChange = (res.cpArea < 0 && areaPoly > 0) || (res.cpArea > 0 && areaPoly < 0);
|
|
159
|
+
|
|
160
|
+
//console.log('signChange', areaDiff, signChange, cpArea, areaPoly);
|
|
161
|
+
|
|
162
|
+
if (res.signChange) {
|
|
163
|
+
//areaDiff = Infinity
|
|
164
|
+
return res
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* check bézier area
|
|
169
|
+
*/
|
|
170
|
+
let com = [
|
|
171
|
+
{ type: 'M', values: [p0.x, p0.y] },
|
|
172
|
+
{ type: type, values: [...cpts.map(pt => [pt.x, pt.y]).flat()] }
|
|
173
|
+
];
|
|
174
|
+
res.comArea = getPathArea(com);
|
|
175
|
+
res.areaDiff = getRelativeAreaDiff(areaPath, res.comArea);
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
// very accurate - used to skip alternative calculations
|
|
179
|
+
res.accurate = res.areaDiff < tolerance * 0.3;
|
|
180
|
+
|
|
181
|
+
return res
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* get ellipse area
|
|
187
|
+
* skips to circle calculation if rx===ry
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
export function getEllipseArea(rx, ry, startAngle, endAngle) {
|
|
191
|
+
const totalArea = Math.PI * rx * ry;
|
|
192
|
+
let angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
193
|
+
// If circle, use simple circular formula
|
|
194
|
+
if (rx === ry) return totalArea * (angleDiff / (2 * Math.PI));
|
|
195
|
+
|
|
196
|
+
// Convert absolute angles to parametric angles
|
|
197
|
+
const absoluteToParametric = (phi)=>{
|
|
198
|
+
return Math.atan2(rx * Math.sin(phi), ry * Math.cos(phi));
|
|
199
|
+
}
|
|
200
|
+
startAngle = absoluteToParametric(startAngle);
|
|
201
|
+
endAngle = absoluteToParametric(endAngle);
|
|
202
|
+
angleDiff = (endAngle - startAngle + 2 * Math.PI) % (2 * Math.PI);
|
|
203
|
+
return totalArea * (angleDiff / (2 * Math.PI));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* compare areas
|
|
210
|
+
* for thresholds
|
|
211
|
+
* returns a percentage value
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
export function getRelativeAreaDiff(area0, area1) {
|
|
215
|
+
let diff = Math.abs(area0 - area1);
|
|
216
|
+
return Math.abs(100 - (100 / area0 * (area0 + diff)))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* get bezier area
|
|
224
|
+
*/
|
|
225
|
+
export function getBezierArea(pts, absolute=false) {
|
|
226
|
+
|
|
227
|
+
let [p0, cp1, cp2, p] = [pts[0], pts[1], pts[2], pts[pts.length - 1]]
|
|
228
|
+
let area;
|
|
229
|
+
|
|
230
|
+
if (pts.length < 3) return 0;
|
|
231
|
+
|
|
232
|
+
// quadratic beziers
|
|
233
|
+
if (pts.length === 3) {
|
|
234
|
+
cp1 = {
|
|
235
|
+
x: pts[0].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
236
|
+
y: pts[0].y * 1 / 3 + pts[1].y * 2 / 3
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
cp2 = {
|
|
240
|
+
x: pts[2].x * 1 / 3 + pts[1].x * 2 / 3,
|
|
241
|
+
y: pts[2].y * 1 / 3 + pts[1].y * 2 / 3
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
area = ((p0.x * (-2 * cp1.y - cp2.y + 3 * p.y) +
|
|
246
|
+
cp1.x * (2 * p0.y - cp2.y - p.y) +
|
|
247
|
+
cp2.x * (p0.y + cp1.y - 2 * p.y) +
|
|
248
|
+
p.x * (-3 * p0.y + cp1.y + 2 * cp2.y)) *
|
|
249
|
+
3) / 20;
|
|
250
|
+
|
|
251
|
+
return absolute ? Math.abs(area) : area;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getPolygonArea(points, absolute=false) {
|
|
255
|
+
let area = 0;
|
|
256
|
+
for (let i = 0, len = points.length; len && i < len; i++) {
|
|
257
|
+
let addX = points[i].x;
|
|
258
|
+
let addY = points[i === points.length - 1 ? 0 : i + 1].y;
|
|
259
|
+
let subX = points[i === points.length - 1 ? 0 : i + 1].x;
|
|
260
|
+
let subY = points[i].y;
|
|
261
|
+
area += addX * addY * 0.5 - subX * subY * 0.5;
|
|
262
|
+
}
|
|
263
|
+
if(absolute) area=Math.abs(area);
|
|
264
|
+
return area;
|
|
265
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
|
|
2
|
+
//import { splitSubpaths } from "./convert_segments";
|
|
3
|
+
import { pointAtT, svgArcToCenterParam, getBezierExtremeT, getArcExtemes, getDistance, interpolate, getPointOnEllipse } from "./geometry";
|
|
4
|
+
import { renderPoint } from "./visualize";
|
|
5
|
+
//import {arcToBezier} from'./pathData_convert';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* calculate polygon bbox
|
|
10
|
+
*/
|
|
11
|
+
export function getPolyBBox(vertices, decimals = -1) {
|
|
12
|
+
let xArr = vertices.map(pt => pt.x);
|
|
13
|
+
let yArr = vertices.map(pt => pt.y);
|
|
14
|
+
let left = Math.min(...xArr)
|
|
15
|
+
let right = Math.max(...xArr)
|
|
16
|
+
let top = Math.min(...yArr)
|
|
17
|
+
let bottom = Math.max(...yArr)
|
|
18
|
+
let bb = {
|
|
19
|
+
x: left,
|
|
20
|
+
left: left,
|
|
21
|
+
right: right,
|
|
22
|
+
y: top,
|
|
23
|
+
top: top,
|
|
24
|
+
bottom: bottom,
|
|
25
|
+
width: right - left,
|
|
26
|
+
height: bottom - top
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// round
|
|
30
|
+
|
|
31
|
+
if (decimals > -1) {
|
|
32
|
+
for (let prop in bb) {
|
|
33
|
+
bb[prop] = +bb[prop].toFixed(decimals)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//console.log(bb);
|
|
37
|
+
return bb;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getSubPathBBoxes(subPaths) {
|
|
41
|
+
let bboxArr = [];
|
|
42
|
+
subPaths.forEach((pathData) => {
|
|
43
|
+
//let bb = getPathDataBBox(pathData)
|
|
44
|
+
let bb = getPathDataBBox_sloppy(pathData);
|
|
45
|
+
bboxArr.push(bb);
|
|
46
|
+
});
|
|
47
|
+
//console.log('bboxArr', bboxArr);
|
|
48
|
+
return bboxArr;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function checkBBoxIntersections(bb, bb1) {
|
|
52
|
+
let [x, y, width, height, right, bottom] = [
|
|
53
|
+
bb.x,
|
|
54
|
+
bb.y,
|
|
55
|
+
bb.width,
|
|
56
|
+
bb.height,
|
|
57
|
+
bb.x + bb.width,
|
|
58
|
+
bb.y + bb.height
|
|
59
|
+
];
|
|
60
|
+
let [x1, y1, width1, height1, right1, bottom1] = [
|
|
61
|
+
bb1.x,
|
|
62
|
+
bb1.y,
|
|
63
|
+
bb1.width,
|
|
64
|
+
bb1.height,
|
|
65
|
+
bb1.x + bb1.width,
|
|
66
|
+
bb1.y + bb1.height
|
|
67
|
+
];
|
|
68
|
+
let intersects = false;
|
|
69
|
+
if (width * height != width1 * height1) {
|
|
70
|
+
if (width * height > width1 * height1) {
|
|
71
|
+
if (x < x1 && right > right1 && y < y1 && bottom > bottom1) {
|
|
72
|
+
intersects = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return intersects;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* sloppy path bbox aaproximation
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
export function getPathDataBBox_sloppy(pathData) {
|
|
85
|
+
let pts = getPathDataPoly(pathData);
|
|
86
|
+
let bb = getPolyBBox(pts);
|
|
87
|
+
return bb;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* get path data poly
|
|
93
|
+
* including command points
|
|
94
|
+
* handy for faster/sloppy bbox approximations
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
export function getPathDataPoly(pathData) {
|
|
98
|
+
|
|
99
|
+
let poly = [];
|
|
100
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
101
|
+
let com = pathData[i]
|
|
102
|
+
let prev = i > 0 ? pathData[i - 1] : pathData[i];
|
|
103
|
+
let { type, values } = com;
|
|
104
|
+
let p0 = { x: prev.values[prev.values.length - 2], y: prev.values[prev.values.length - 1] };
|
|
105
|
+
let p = values.length ? { x: values[values.length - 2], y: values[values.length - 1] } : ''
|
|
106
|
+
let cp1 = values.length ? { x: values[0], y: values[1] } : ''
|
|
107
|
+
|
|
108
|
+
switch (type) {
|
|
109
|
+
|
|
110
|
+
// convert to cubic to get polygon
|
|
111
|
+
case 'A':
|
|
112
|
+
|
|
113
|
+
let [, , xAxisRotation, largeArc, sweep, x1, y1] = com;
|
|
114
|
+
|
|
115
|
+
if (typeof arcToBezier !== 'function') {
|
|
116
|
+
|
|
117
|
+
// get real radii
|
|
118
|
+
let rx = getDistance(p0, p) / 2;
|
|
119
|
+
let ptMid = interpolate(p0, p, 0.5);
|
|
120
|
+
|
|
121
|
+
let pt1 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, 0)
|
|
122
|
+
let pt2 = getPointOnEllipse(ptMid.x, ptMid.y, rx, rx, Math.PI)
|
|
123
|
+
poly.push(pt1, pt2, p)
|
|
124
|
+
|
|
125
|
+
//console.log('has no arc to cubic conversion');
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
let cubic = arcToBezier(p0, values)
|
|
129
|
+
cubic.forEach(com => {
|
|
130
|
+
let vals = com.values
|
|
131
|
+
let cp1 = { x: vals[0], y: vals[1] }
|
|
132
|
+
let cp2 = { x: vals[2], y: vals[3] }
|
|
133
|
+
let p = { x: vals[4], y: vals[5] }
|
|
134
|
+
poly.push(cp1, cp2, p)
|
|
135
|
+
})
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case 'C':
|
|
139
|
+
let cp2 = { x: values[2], y: values[3] }
|
|
140
|
+
poly.push(cp1, cp2)
|
|
141
|
+
break;
|
|
142
|
+
case 'Q':
|
|
143
|
+
poly.push(cp1)
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// M and L commands
|
|
148
|
+
if (type.toLowerCase() !== 'z') {
|
|
149
|
+
poly.push(p)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return poly;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* get exact path BBox
|
|
159
|
+
* calculating extremes for all command types
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
export function getPathDataBBox(pathData) {
|
|
163
|
+
|
|
164
|
+
// save extreme values
|
|
165
|
+
let xMin = Infinity;
|
|
166
|
+
let xMax = -Infinity;
|
|
167
|
+
let yMin = Infinity;
|
|
168
|
+
let yMax = -Infinity;
|
|
169
|
+
|
|
170
|
+
const setXYmaxMin = (pt) => {
|
|
171
|
+
if (pt.x < xMin) {
|
|
172
|
+
xMin = pt.x
|
|
173
|
+
}
|
|
174
|
+
if (pt.x > xMax) {
|
|
175
|
+
xMax = pt.x
|
|
176
|
+
}
|
|
177
|
+
if (pt.y < yMin) {
|
|
178
|
+
yMin = pt.y
|
|
179
|
+
}
|
|
180
|
+
if (pt.y > yMax) {
|
|
181
|
+
yMax = pt.y
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < pathData.length; i++) {
|
|
186
|
+
let com = pathData[i]
|
|
187
|
+
let { type, values } = com;
|
|
188
|
+
let valuesL = values.length;
|
|
189
|
+
let comPrev = pathData[i - 1] ? pathData[i - 1] : pathData[i];
|
|
190
|
+
let valuesPrev = comPrev.values;
|
|
191
|
+
let valuesPrevL = valuesPrev.length;
|
|
192
|
+
|
|
193
|
+
if (valuesL) {
|
|
194
|
+
let p0 = { x: valuesPrev[valuesPrevL - 2], y: valuesPrev[valuesPrevL - 1] };
|
|
195
|
+
let p = { x: values[valuesL - 2], y: values[valuesL - 1] };
|
|
196
|
+
// add final on path point
|
|
197
|
+
setXYmaxMin(p)
|
|
198
|
+
|
|
199
|
+
if (type === 'C' || type === 'Q') {
|
|
200
|
+
let cp1 = { x: values[0], y: values[1] };
|
|
201
|
+
let cp2 = type === 'C' ? { x: values[2], y: values[3] } : cp1;
|
|
202
|
+
let pts = type === 'C' ? [p0, cp1, cp2, p] : [p0, cp1, p];
|
|
203
|
+
|
|
204
|
+
let bezierExtremesT = getBezierExtremeT(pts)
|
|
205
|
+
bezierExtremesT.forEach(t => {
|
|
206
|
+
let pt = pointAtT(pts, t);
|
|
207
|
+
setXYmaxMin(pt)
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
else if (type === 'A') {
|
|
212
|
+
let arcExtremes = getArcExtemes(p0, values)
|
|
213
|
+
arcExtremes.forEach(pt => {
|
|
214
|
+
setXYmaxMin(pt)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let bbox = { x: xMin, y: yMin, width: xMax - xMin, height: yMax - yMin }
|
|
221
|
+
return bbox
|
|
222
|
+
}
|
|
223
|
+
|