source: imaps-frontend/node_modules/svg-pathdata/src/SVGPathDataTransformer.ts@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 22.6 KB
Line 
1// Transform SVG PathData
2// http://www.w3.org/TR/SVG/paths.html#PathDataBNF
3
4import { a2c, annotateArcCommand, arcAt, assertNumbers, bezierAt, bezierRoot,
5 intersectionUnitCircleLine } from "./mathUtils";
6import { SVGPathData } from "./SVGPathData";
7import { SVGCommand, TransformFunction } from "./types";
8
9export namespace SVGPathDataTransformer {
10 // Predefined transforming functions
11 // Rounds commands values
12 export function ROUND(roundVal = 1e13) {
13 assertNumbers(roundVal);
14 function rf(val: number) { return Math.round(val * roundVal) / roundVal; }
15 return function round(command: any) {
16 if ("undefined" !== typeof command.x1) {
17 command.x1 = rf(command.x1);
18 }
19 if ("undefined" !== typeof command.y1) {
20 command.y1 = rf(command.y1);
21 }
22
23 if ("undefined" !== typeof command.x2) {
24 command.x2 = rf(command.x2);
25 }
26 if ("undefined" !== typeof command.y2) {
27 command.y2 = rf(command.y2);
28 }
29
30 if ("undefined" !== typeof command.x) {
31 command.x = rf(command.x);
32 }
33 if ("undefined" !== typeof command.y) {
34 command.y = rf(command.y);
35 }
36
37 if ("undefined" !== typeof command.rX) {
38 command.rX = rf(command.rX);
39 }
40 if ("undefined" !== typeof command.rY) {
41 command.rY = rf(command.rY);
42 }
43
44 return command;
45 };
46 }
47 // Relative to absolute commands
48 export function TO_ABS() {
49 return INFO((command, prevX, prevY) => {
50 if (command.relative) {
51 // x1/y1 values
52 if ("undefined" !== typeof command.x1) {
53 command.x1 += prevX;
54 }
55 if ("undefined" !== typeof command.y1) {
56 command.y1 += prevY;
57 }
58 // x2/y2 values
59 if ("undefined" !== typeof command.x2) {
60 command.x2 += prevX;
61 }
62 if ("undefined" !== typeof command.y2) {
63 command.y2 += prevY;
64 }
65 // Finally x/y values
66 if ("undefined" !== typeof command.x) {
67 command.x += prevX;
68 }
69 if ("undefined" !== typeof command.y) {
70 command.y += prevY;
71 }
72 command.relative = false;
73 }
74 return command;
75 });
76 }
77 // Absolute to relative commands
78 export function TO_REL() {
79 return INFO((command, prevX, prevY) => {
80 if (!command.relative) {
81 // x1/y1 values
82 if ("undefined" !== typeof command.x1) {
83 command.x1 -= prevX;
84 }
85 if ("undefined" !== typeof command.y1) {
86 command.y1 -= prevY;
87 }
88 // x2/y2 values
89 if ("undefined" !== typeof command.x2) {
90 command.x2 -= prevX;
91 }
92 if ("undefined" !== typeof command.y2) {
93 command.y2 -= prevY;
94 }
95 // Finally x/y values
96 if ("undefined" !== typeof command.x) {
97 command.x -= prevX;
98 }
99 if ("undefined" !== typeof command.y) {
100 command.y -= prevY;
101 }
102 command.relative = true;
103 }
104 return command;
105 });
106 }
107 // Convert H, V, Z and A with rX = 0 to L
108 export function NORMALIZE_HVZ(normalizeZ = true, normalizeH = true, normalizeV = true) {
109 return INFO((command, prevX, prevY, pathStartX, pathStartY) => {
110 if (isNaN(pathStartX) && !(command.type & SVGPathData.MOVE_TO)) {
111 throw new Error("path must start with moveto");
112 }
113 if (normalizeH && command.type & SVGPathData.HORIZ_LINE_TO) {
114 command.type = SVGPathData.LINE_TO;
115 command.y = command.relative ? 0 : prevY;
116 }
117 if (normalizeV && command.type & SVGPathData.VERT_LINE_TO) {
118 command.type = SVGPathData.LINE_TO;
119 command.x = command.relative ? 0 : prevX;
120 }
121 if (normalizeZ && command.type & SVGPathData.CLOSE_PATH) {
122 command.type = SVGPathData.LINE_TO;
123 command.x = command.relative ? pathStartX - prevX : pathStartX;
124 command.y = command.relative ? pathStartY - prevY : pathStartY;
125 }
126 if (command.type & SVGPathData.ARC && (0 === command.rX || 0 === command.rY)) {
127 command.type = SVGPathData.LINE_TO;
128 delete command.rX;
129 delete command.rY;
130 delete command.xRot;
131 delete command.lArcFlag;
132 delete command.sweepFlag;
133 }
134 return command;
135 });
136 }
137 /*
138 * Transforms smooth curves and quads to normal curves and quads (SsTt to CcQq)
139 */
140 export function NORMALIZE_ST() {
141 let prevCurveC2X = NaN;
142 let prevCurveC2Y = NaN;
143 let prevQuadCX = NaN;
144 let prevQuadCY = NaN;
145
146 return INFO((command, prevX, prevY) => {
147 if (command.type & SVGPathData.SMOOTH_CURVE_TO) {
148 command.type = SVGPathData.CURVE_TO;
149 prevCurveC2X = isNaN(prevCurveC2X) ? prevX : prevCurveC2X;
150 prevCurveC2Y = isNaN(prevCurveC2Y) ? prevY : prevCurveC2Y;
151 command.x1 = command.relative ? prevX - prevCurveC2X : 2 * prevX - prevCurveC2X;
152 command.y1 = command.relative ? prevY - prevCurveC2Y : 2 * prevY - prevCurveC2Y;
153 }
154 if (command.type & SVGPathData.CURVE_TO) {
155 prevCurveC2X = command.relative ? prevX + command.x2 : command.x2;
156 prevCurveC2Y = command.relative ? prevY + command.y2 : command.y2;
157 } else {
158 prevCurveC2X = NaN;
159 prevCurveC2Y = NaN;
160 }
161 if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
162 command.type = SVGPathData.QUAD_TO;
163 prevQuadCX = isNaN(prevQuadCX) ? prevX : prevQuadCX;
164 prevQuadCY = isNaN(prevQuadCY) ? prevY : prevQuadCY;
165 command.x1 = command.relative ? prevX - prevQuadCX : 2 * prevX - prevQuadCX;
166 command.y1 = command.relative ? prevY - prevQuadCY : 2 * prevY - prevQuadCY;
167 }
168 if (command.type & SVGPathData.QUAD_TO) {
169 prevQuadCX = command.relative ? prevX + command.x1 : command.x1;
170 prevQuadCY = command.relative ? prevY + command.y1 : command.y1;
171 } else {
172 prevQuadCX = NaN;
173 prevQuadCY = NaN;
174 }
175
176 return command;
177 });
178 }
179 /*
180 * A quadratic bézier curve can be represented by a cubic bézier curve which has
181 * the same end points as the quadratic and both control points in place of the
182 * quadratic"s one.
183 *
184 * This transformer replaces QqTt commands with Cc commands respectively.
185 * This is useful for reading path data into a system which only has a
186 * representation for cubic curves.
187 */
188 export function QT_TO_C() {
189 let prevQuadX1 = NaN;
190 let prevQuadY1 = NaN;
191
192 return INFO((command, prevX, prevY) => {
193 if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
194 command.type = SVGPathData.QUAD_TO;
195 prevQuadX1 = isNaN(prevQuadX1) ? prevX : prevQuadX1;
196 prevQuadY1 = isNaN(prevQuadY1) ? prevY : prevQuadY1;
197 command.x1 = command.relative ? prevX - prevQuadX1 : 2 * prevX - prevQuadX1;
198 command.y1 = command.relative ? prevY - prevQuadY1 : 2 * prevY - prevQuadY1;
199 }
200 if (command.type & SVGPathData.QUAD_TO) {
201 prevQuadX1 = command.relative ? prevX + command.x1 : command.x1;
202 prevQuadY1 = command.relative ? prevY + command.y1 : command.y1;
203 const x1 = command.x1;
204 const y1 = command.y1;
205
206 command.type = SVGPathData.CURVE_TO;
207 command.x1 = ((command.relative ? 0 : prevX) + x1 * 2) / 3;
208 command.y1 = ((command.relative ? 0 : prevY) + y1 * 2) / 3;
209 command.x2 = (command.x + x1 * 2) / 3;
210 command.y2 = (command.y + y1 * 2) / 3;
211 } else {
212 prevQuadX1 = NaN;
213 prevQuadY1 = NaN;
214 }
215
216 return command;
217 });
218 }
219 export function INFO(
220 f: (command: any, prevXAbs: number, prevYAbs: number,
221 pathStartXAbs: number, pathStartYAbs: number) => any | any[]) {
222 let prevXAbs = 0;
223 let prevYAbs = 0;
224 let pathStartXAbs = NaN;
225 let pathStartYAbs = NaN;
226
227 return function transform(command: any) {
228 if (isNaN(pathStartXAbs) && !(command.type & SVGPathData.MOVE_TO)) {
229 throw new Error("path must start with moveto");
230 }
231
232 const result = f(command, prevXAbs, prevYAbs, pathStartXAbs, pathStartYAbs);
233
234 if (command.type & SVGPathData.CLOSE_PATH) {
235 prevXAbs = pathStartXAbs;
236 prevYAbs = pathStartYAbs;
237 }
238
239 if ("undefined" !== typeof command.x) {
240 prevXAbs = (command.relative ? prevXAbs + command.x : command.x);
241 }
242 if ("undefined" !== typeof command.y) {
243 prevYAbs = (command.relative ? prevYAbs + command.y : command.y);
244 }
245
246 if (command.type & SVGPathData.MOVE_TO) {
247 pathStartXAbs = prevXAbs;
248 pathStartYAbs = prevYAbs;
249 }
250
251 return result;
252 };
253 }
254 /*
255 * remove 0-length segments
256 */
257 export function SANITIZE(EPS = 0) {
258 assertNumbers(EPS);
259 let prevCurveC2X = NaN;
260 let prevCurveC2Y = NaN;
261 let prevQuadCX = NaN;
262 let prevQuadCY = NaN;
263
264 return INFO((command, prevX, prevY, pathStartX, pathStartY) => {
265 const abs = Math.abs;
266 let skip = false;
267 let x1Rel = 0;
268 let y1Rel = 0;
269
270 if (command.type & SVGPathData.SMOOTH_CURVE_TO) {
271 x1Rel = isNaN(prevCurveC2X) ? 0 : prevX - prevCurveC2X;
272 y1Rel = isNaN(prevCurveC2Y) ? 0 : prevY - prevCurveC2Y;
273 }
274 if (command.type & (SVGPathData.CURVE_TO | SVGPathData.SMOOTH_CURVE_TO)) {
275 prevCurveC2X = command.relative ? prevX + command.x2 : command.x2;
276 prevCurveC2Y = command.relative ? prevY + command.y2 : command.y2;
277 } else {
278 prevCurveC2X = NaN;
279 prevCurveC2Y = NaN;
280 }
281 if (command.type & SVGPathData.SMOOTH_QUAD_TO) {
282 prevQuadCX = isNaN(prevQuadCX) ? prevX : 2 * prevX - prevQuadCX;
283 prevQuadCY = isNaN(prevQuadCY) ? prevY : 2 * prevY - prevQuadCY;
284 } else if (command.type & SVGPathData.QUAD_TO) {
285 prevQuadCX = command.relative ? prevX + command.x1 : command.x1;
286 prevQuadCY = command.relative ? prevY + command.y1 : command.y2;
287 } else {
288 prevQuadCX = NaN;
289 prevQuadCY = NaN;
290 }
291
292 if (command.type & SVGPathData.LINE_COMMANDS ||
293 command.type & SVGPathData.ARC && (0 === command.rX || 0 === command.rY || !command.lArcFlag) ||
294 command.type & SVGPathData.CURVE_TO || command.type & SVGPathData.SMOOTH_CURVE_TO ||
295 command.type & SVGPathData.QUAD_TO || command.type & SVGPathData.SMOOTH_QUAD_TO) {
296 const xRel = "undefined" === typeof command.x ? 0 :
297 (command.relative ? command.x : command.x - prevX);
298 const yRel = "undefined" === typeof command.y ? 0 :
299 (command.relative ? command.y : command.y - prevY);
300
301 x1Rel = !isNaN(prevQuadCX) ? prevQuadCX - prevX :
302 "undefined" === typeof command.x1 ? x1Rel :
303 command.relative ? command.x :
304 command.x1 - prevX;
305 y1Rel = !isNaN(prevQuadCY) ? prevQuadCY - prevY :
306 "undefined" === typeof command.y1 ? y1Rel :
307 command.relative ? command.y :
308 command.y1 - prevY;
309
310 const x2Rel = "undefined" === typeof command.x2 ? 0 :
311 (command.relative ? command.x : command.x2 - prevX);
312 const y2Rel = "undefined" === typeof command.y2 ? 0 :
313 (command.relative ? command.y : command.y2 - prevY);
314
315 if (abs(xRel) <= EPS && abs(yRel) <= EPS &&
316 abs(x1Rel) <= EPS && abs(y1Rel) <= EPS &&
317 abs(x2Rel) <= EPS && abs(y2Rel) <= EPS) {
318 skip = true;
319 }
320 }
321
322 if (command.type & SVGPathData.CLOSE_PATH) {
323 if (abs(prevX - pathStartX) <= EPS && abs(prevY - pathStartY) <= EPS) {
324 skip = true;
325 }
326 }
327
328 return skip ? [] : command;
329 });
330 }
331 // SVG Transforms : http://www.w3.org/TR/SVGTiny12/coords.html#TransformList
332 // Matrix : http://apike.ca/prog_svg_transform.html
333 // a c e
334 // b d f
335 export function MATRIX(a: number, b: number, c: number, d: number, e: number, f: number) {
336 assertNumbers(a, b, c, d, e, f);
337
338 return INFO((command, prevX, prevY, pathStartX) => {
339 const origX1 = command.x1;
340 const origX2 = command.x2;
341 // if isNaN(pathStartX), then this is the first command, which is ALWAYS an
342 // absolute MOVE_TO, regardless what the relative flag says
343 const comRel = command.relative && !isNaN(pathStartX);
344 const x = "undefined" !== typeof command.x ? command.x : (comRel ? 0 : prevX);
345 const y = "undefined" !== typeof command.y ? command.y : (comRel ? 0 : prevY);
346
347 if (command.type & SVGPathData.HORIZ_LINE_TO && 0 !== b) {
348 command.type = SVGPathData.LINE_TO;
349 command.y = command.relative ? 0 : prevY;
350 }
351 if (command.type & SVGPathData.VERT_LINE_TO && 0 !== c) {
352 command.type = SVGPathData.LINE_TO;
353 command.x = command.relative ? 0 : prevX;
354 }
355
356 if ("undefined" !== typeof command.x) {
357 command.x = (command.x * a) + (y * c) + (comRel ? 0 : e);
358 }
359 if ("undefined" !== typeof command.y) {
360 command.y = (x * b) + command.y * d + (comRel ? 0 : f);
361 }
362 if ("undefined" !== typeof command.x1) {
363 command.x1 = command.x1 * a + command.y1 * c + (comRel ? 0 : e);
364 }
365 if ("undefined" !== typeof command.y1) {
366 command.y1 = origX1 * b + command.y1 * d + (comRel ? 0 : f);
367 }
368 if ("undefined" !== typeof command.x2) {
369 command.x2 = command.x2 * a + command.y2 * c + (comRel ? 0 : e);
370 }
371 if ("undefined" !== typeof command.y2) {
372 command.y2 = origX2 * b + command.y2 * d + (comRel ? 0 : f);
373 }
374 function sqr(x: number) { return x * x; }
375 const det = a * d - b * c;
376
377 if ("undefined" !== typeof command.xRot) {
378 // Skip if this is a pure translation
379 if (1 !== a || 0 !== b || 0 !== c || 1 !== d) {
380 // Special case for singular matrix
381 if (0 === det) {
382 // In the singular case, the arc is compressed to a line. The actual geometric image of the original
383 // curve under this transform possibly extends beyond the starting and/or ending points of the segment, but
384 // for simplicity we ignore this detail and just replace this command with a single line segment.
385 delete command.rX;
386 delete command.rY;
387 delete command.xRot;
388 delete command.lArcFlag;
389 delete command.sweepFlag;
390 command.type = SVGPathData.LINE_TO;
391 } else {
392 // Convert to radians
393 const xRot = command.xRot * Math.PI / 180;
394
395 // Convert rotated ellipse to general conic form
396 // x0^2/rX^2 + y0^2/rY^2 - 1 = 0
397 // x0 = x*cos(xRot) + y*sin(xRot)
398 // y0 = -x*sin(xRot) + y*cos(xRot)
399 // --> A*x^2 + B*x*y + C*y^2 - 1 = 0, where
400 const sinRot = Math.sin(xRot);
401 const cosRot = Math.cos(xRot);
402 const xCurve = 1 / sqr(command.rX);
403 const yCurve = 1 / sqr(command.rY);
404 const A = sqr(cosRot) * xCurve + sqr(sinRot) * yCurve;
405 const B = 2 * sinRot * cosRot * (xCurve - yCurve);
406 const C = sqr(sinRot) * xCurve + sqr(cosRot) * yCurve;
407
408 // Apply matrix to A*x^2 + B*x*y + C*y^2 - 1 = 0
409 // x1 = a*x + c*y
410 // y1 = b*x + d*y
411 // (we can ignore e and f, since pure translations don"t affect the shape of the ellipse)
412 // --> A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 = 0, where
413 const A1 = A * d * d - B * b * d + C * b * b;
414 const B1 = B * (a * d + b * c) - 2 * (A * c * d + C * a * b);
415 const C1 = A * c * c - B * a * c + C * a * a;
416
417 // Unapply newXRot to get back to axis-aligned ellipse equation
418 // x1 = x2*cos(newXRot) - y2*sin(newXRot)
419 // y1 = x2*sin(newXRot) + y2*cos(newXRot)
420 // A1*x1^2 + B1*x1*y1 + C1*y1^2 - det^2 =
421 // x2^2*(A1*cos(newXRot)^2 + B1*sin(newXRot)*cos(newXRot) + C1*sin(newXRot)^2)
422 // + x2*y2*(2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2))
423 // + y2^2*(A1*sin(newXRot)^2 - B1*sin(newXRot)*cos(newXRot) + C1*cos(newXRot)^2)
424 // (which must have the same zeroes as)
425 // x2^2/newRX^2 + y2^2/newRY^2 - 1
426 // (so we have)
427 // 2*(C1 - A1)*sin(newXRot)*cos(newXRot) + B1*(cos(newXRot)^2 - sin(newXRot)^2) = 0
428 // (A1 - C1)*sin(2*newXRot) = B1*cos(2*newXRot)
429 // 2*newXRot = atan2(B1, A1 - C1)
430 const newXRot = ((Math.atan2(B1, A1 - C1) + Math.PI) % Math.PI) / 2;
431 // For any integer n, (atan2(B1, A1 - C1) + n*pi)/2 is a solution to the above; incrementing n just swaps
432 // the x and y radii computed below (since that"s what rotating an ellipse by pi/2 does). Choosing the
433 // rotation between 0 and pi/2 eliminates the ambiguity and leads to more predictable output.
434
435 // Finally, we get newRX and newRY from the same-zeroes relationship that gave us newXRot
436 const newSinRot = Math.sin(newXRot);
437 const newCosRot = Math.cos(newXRot);
438
439 command.rX = Math.abs(det) /
440 Math.sqrt(A1 * sqr(newCosRot) + B1 * newSinRot * newCosRot + C1 * sqr(newSinRot));
441 command.rY = Math.abs(det) /
442 Math.sqrt(A1 * sqr(newSinRot) - B1 * newSinRot * newCosRot + C1 * sqr(newCosRot));
443 command.xRot = newXRot * 180 / Math.PI;
444 }
445 }
446 }
447 // sweepFlag needs to be inverted when mirroring shapes
448 // see http://www.itk.ilstu.edu/faculty/javila/SVG/SVG_drawing1/elliptical_curve.htm
449 // m 65,10 a 50,25 0 1 0 50,25
450 // M 65,60 A 50,25 0 1 1 115,35
451 if ("undefined" !== typeof command.sweepFlag && 0 > det) {
452 command.sweepFlag = +!command.sweepFlag;
453 }
454 return command;
455 });
456 }
457 export function ROTATE(a: number, x = 0, y = 0) {
458 assertNumbers(a, x, y);
459 const sin = Math.sin(a);
460 const cos = Math.cos(a);
461
462 return MATRIX(cos, sin, -sin, cos, x - x * cos + y * sin, y - x * sin - y * cos);
463 }
464 export function TRANSLATE(dX: number, dY = 0) {
465 assertNumbers(dX, dY);
466 return MATRIX(1, 0, 0, 1, dX, dY);
467 }
468 export function SCALE(dX: number, dY = dX) {
469 assertNumbers(dX, dY);
470 return MATRIX(dX, 0, 0, dY, 0, 0);
471 }
472 export function SKEW_X(a: number) {
473 assertNumbers(a);
474 return MATRIX(1, 0, Math.atan(a), 1, 0, 0);
475 }
476 export function SKEW_Y(a: number) {
477 assertNumbers(a);
478 return MATRIX(1, Math.atan(a), 0, 1, 0, 0);
479 }
480 export function X_AXIS_SYMMETRY(xOffset = 0) {
481 assertNumbers(xOffset);
482 return MATRIX(-1, 0, 0, 1, xOffset, 0);
483 }
484 export function Y_AXIS_SYMMETRY(yOffset = 0) {
485 assertNumbers(yOffset);
486 return MATRIX(1, 0, 0, -1, 0, yOffset);
487 }
488 // Convert arc commands to curve commands
489 export function A_TO_C() {
490 return INFO((command, prevX, prevY) => {
491 if (SVGPathData.ARC === command.type) {
492 return a2c(command, command.relative ? 0 : prevX, command.relative ? 0 : prevY);
493 }
494 return command;
495 });
496 }
497 // @see annotateArcCommand
498 export function ANNOTATE_ARCS() {
499 return INFO((c, x1, y1) => {
500 if (c.relative) {
501 x1 = 0;
502 y1 = 0;
503 }
504 if (SVGPathData.ARC === c.type) {
505 annotateArcCommand(c, x1, y1);
506 }
507 return c;
508 });
509 }
510 export function CLONE() {
511 return (c: SVGCommand) => {
512 const result = {} as SVGCommand;
513 // tslint:disable-next-line
514 for (const key in c) {
515 result[key as keyof SVGCommand] = c[key as keyof SVGCommand];
516 }
517 return result;
518 };
519 }
520 // @see annotateArcCommand
521 export function CALCULATE_BOUNDS() {
522 const clone = CLONE();
523 const toAbs = TO_ABS();
524 const qtToC = QT_TO_C();
525 const normST = NORMALIZE_ST();
526 const f: TransformFunction & {minX: number, maxX: number, minY: number, maxY: number} =
527 INFO((command, prevXAbs, prevYAbs) => {
528 const c = normST(qtToC(toAbs(clone(command))));
529 function fixX(absX: number) {
530 if (absX > f.maxX) { f.maxX = absX; }
531 if (absX < f.minX) { f.minX = absX; }
532 }
533 function fixY(absY: number) {
534 if (absY > f.maxY) { f.maxY = absY; }
535 if (absY < f.minY) { f.minY = absY; }
536 }
537 if (c.type & SVGPathData.DRAWING_COMMANDS) {
538 fixX(prevXAbs);
539 fixY(prevYAbs);
540 }
541 if (c.type & SVGPathData.HORIZ_LINE_TO) {
542 fixX(c.x);
543 }
544 if (c.type & SVGPathData.VERT_LINE_TO) {
545 fixY(c.y);
546 }
547 if (c.type & SVGPathData.LINE_TO) {
548 fixX(c.x);
549 fixY(c.y);
550 }
551 if (c.type & SVGPathData.CURVE_TO) {
552 // add start and end points
553 fixX(c.x);
554 fixY(c.y);
555 const xDerivRoots = bezierRoot(prevXAbs, c.x1, c.x2, c.x);
556
557 for (const derivRoot of xDerivRoots) {
558 if (0 < derivRoot && 1 > derivRoot) {
559 fixX(bezierAt(prevXAbs, c.x1, c.x2, c.x, derivRoot));
560 }
561 }
562 const yDerivRoots = bezierRoot(prevYAbs, c.y1, c.y2, c.y);
563
564 for (const derivRoot of yDerivRoots) {
565 if (0 < derivRoot && 1 > derivRoot) {
566 fixY(bezierAt(prevYAbs, c.y1, c.y2, c.y, derivRoot));
567 }
568 }
569 }
570 if (c.type & SVGPathData.ARC) {
571 // add start and end points
572 fixX(c.x);
573 fixY(c.y);
574 annotateArcCommand(c, prevXAbs, prevYAbs);
575 // p = cos(phi) * xv + sin(phi) * yv
576 // dp = -sin(phi) * xv + cos(phi) * yv = 0
577 const xRotRad = c.xRot / 180 * Math.PI;
578 // points on ellipse for phi = 0° and phi = 90°
579 const x0 = Math.cos(xRotRad) * c.rX;
580 const y0 = Math.sin(xRotRad) * c.rX;
581 const x90 = -Math.sin(xRotRad) * c.rY;
582 const y90 = Math.cos(xRotRad) * c.rY;
583
584 // annotateArcCommand returns phi1 and phi2 such that -180° < phi1 < 180° and phi2 is smaller or greater
585 // depending on the sweep flag. Calculate phiMin, phiMax such that -180° < phiMin < 180° and phiMin < phiMax
586 const [phiMin, phiMax] = c.phi1 < c.phi2 ?
587 [c.phi1, c.phi2] :
588 (-180 > c.phi2 ? [c.phi2 + 360, c.phi1 + 360] : [c.phi2, c.phi1]);
589 const normalizeXiEta = ([xi, eta]: [number, number]) => {
590 const phiRad = Math.atan2(eta, xi);
591 const phi = phiRad * 180 / Math.PI;
592
593 return phi < phiMin ? phi + 360 : phi;
594 };
595 // xi = cos(phi), eta = sin(phi)
596
597 const xDerivRoots = intersectionUnitCircleLine(x90, -x0, 0).map(normalizeXiEta);
598 for (const derivRoot of xDerivRoots) {
599 if (derivRoot > phiMin && derivRoot < phiMax) {
600 fixX(arcAt(c.cX, x0, x90, derivRoot));
601 }
602 }
603
604 const yDerivRoots = intersectionUnitCircleLine(y90, -y0, 0).map(normalizeXiEta);
605 for (const derivRoot of yDerivRoots) {
606 if (derivRoot > phiMin && derivRoot < phiMax) {
607 fixY(arcAt(c.cY, y0, y90, derivRoot));
608 }
609 }
610 }
611 return command;
612 }) as any;
613
614 f.minX = Infinity;
615 f.maxX = -Infinity;
616 f.minY = Infinity;
617 f.maxY = -Infinity;
618 return f;
619 }
620}
Note: See TracBrowser for help on using the repository browser.