UNPKG

9.04 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.arcToCubic = void 0;
4var rotate_vector_1 = require("../util/rotate-vector");
5/**
6 * Converts A (arc-to) segments to C (cubic-bezier-to).
7 *
8 * For more information of where this math came from visit:
9 * http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
10 */
11function arcToCubic(X1, Y1, RX, RY, angle, LAF, SF, X2, Y2, recursive) {
12 var x1 = X1;
13 var y1 = Y1;
14 var rx = RX;
15 var ry = RY;
16 var x2 = X2;
17 var y2 = Y2;
18 // for more information of where this Math came from visit:
19 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
20 var d120 = (Math.PI * 120) / 180;
21 var rad = (Math.PI / 180) * (+angle || 0);
22 /** @type {number[]} */
23 var res = [];
24 var xy;
25 var f1;
26 var f2;
27 var cx;
28 var cy;
29 if (!recursive) {
30 xy = (0, rotate_vector_1.rotateVector)(x1, y1, -rad);
31 x1 = xy.x;
32 y1 = xy.y;
33 xy = (0, rotate_vector_1.rotateVector)(x2, y2, -rad);
34 x2 = xy.x;
35 y2 = xy.y;
36 var x = (x1 - x2) / 2;
37 var y = (y1 - y2) / 2;
38 var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
39 if (h > 1) {
40 h = Math.sqrt(h);
41 rx *= h;
42 ry *= h;
43 }
44 var rx2 = rx * rx;
45 var ry2 = ry * ry;
46 var k = (LAF === SF ? -1 : 1) *
47 Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
48 cx = (k * rx * y) / ry + (x1 + x2) / 2;
49 cy = (k * -ry * x) / rx + (y1 + y2) / 2;
50 // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
51 f1 = Math.asin(((((y1 - cy) / ry) * Math.pow(10, 9)) >> 0) / Math.pow(10, 9));
52 // eslint-disable-next-line no-bitwise -- Impossible to satisfy no-bitwise
53 f2 = Math.asin(((((y2 - cy) / ry) * Math.pow(10, 9)) >> 0) / Math.pow(10, 9));
54 f1 = x1 < cx ? Math.PI - f1 : f1;
55 f2 = x2 < cx ? Math.PI - f2 : f2;
56 if (f1 < 0)
57 f1 = Math.PI * 2 + f1;
58 if (f2 < 0)
59 f2 = Math.PI * 2 + f2;
60 if (SF && f1 > f2) {
61 f1 -= Math.PI * 2;
62 }
63 if (!SF && f2 > f1) {
64 f2 -= Math.PI * 2;
65 }
66 }
67 else {
68 f1 = recursive[0], f2 = recursive[1], cx = recursive[2], cy = recursive[3];
69 }
70 var df = f2 - f1;
71 if (Math.abs(df) > d120) {
72 var f2old = f2;
73 var x2old = x2;
74 var y2old = y2;
75 f2 = f1 + d120 * (SF && f2 > f1 ? 1 : -1);
76 x2 = cx + rx * Math.cos(f2);
77 y2 = cy + ry * Math.sin(f2);
78 res = arcToCubic(x2, y2, rx, ry, angle, 0, SF, x2old, y2old, [f2, f2old, cx, cy]);
79 }
80 df = f2 - f1;
81 var c1 = Math.cos(f1);
82 var s1 = Math.sin(f1);
83 var c2 = Math.cos(f2);
84 var s2 = Math.sin(f2);
85 var t = Math.tan(df / 4);
86 var hx = (4 / 3) * rx * t;
87 var hy = (4 / 3) * ry * t;
88 var m1 = [x1, y1];
89 var m2 = [x1 + hx * s1, y1 - hy * c1];
90 var m3 = [x2 + hx * s2, y2 - hy * c2];
91 var m4 = [x2, y2];
92 m2[0] = 2 * m1[0] - m2[0];
93 m2[1] = 2 * m1[1] - m2[1];
94 if (recursive) {
95 return m2.concat(m3, m4, res);
96 // return [...m2, ...m3, ...m4, ...res];
97 }
98 res = m2.concat(m3, m4, res);
99 // res = [...m2, ...m3, ...m4, ...res];
100 var newres = [];
101 for (var i = 0, ii = res.length; i < ii; i += 1) {
102 newres[i] = i % 2 ? (0, rotate_vector_1.rotateVector)(res[i - 1], res[i], rad).y : (0, rotate_vector_1.rotateVector)(res[i], res[i + 1], rad).x;
103 }
104 return newres;
105}
106exports.arcToCubic = arcToCubic;
107// const TAU = Math.PI * 2;
108// const mapToEllipse = (
109// { x, y }: { x: number; y: number },
110// rx: number,
111// ry: number,
112// cosphi: number,
113// sinphi: number,
114// centerx: number,
115// centery: number,
116// ) => {
117// x *= rx;
118// y *= ry;
119// const xp = cosphi * x - sinphi * y;
120// const yp = sinphi * x + cosphi * y;
121// return {
122// x: xp + centerx,
123// y: yp + centery,
124// };
125// };
126// const approxUnitArc = (ang1: number, ang2: number) => {
127// // If 90 degree circular arc, use a constant
128// // as derived from http://spencermortensen.com/articles/bezier-circle
129// const a =
130// ang2 === 1.5707963267948966
131// ? 0.551915024494
132// : ang2 === -1.5707963267948966
133// ? -0.551915024494
134// : (4 / 3) * Math.tan(ang2 / 4);
135// const x1 = Math.cos(ang1);
136// const y1 = Math.sin(ang1);
137// const x2 = Math.cos(ang1 + ang2);
138// const y2 = Math.sin(ang1 + ang2);
139// return [
140// {
141// x: x1 - y1 * a,
142// y: y1 + x1 * a,
143// },
144// {
145// x: x2 + y2 * a,
146// y: y2 - x2 * a,
147// },
148// {
149// x: x2,
150// y: y2,
151// },
152// ];
153// };
154// const vectorAngle = (ux: number, uy: number, vx: number, vy: number) => {
155// const sign = ux * vy - uy * vx < 0 ? -1 : 1;
156// let dot = ux * vx + uy * vy;
157// if (dot > 1) {
158// dot = 1;
159// }
160// if (dot < -1) {
161// dot = -1;
162// }
163// return sign * Math.acos(dot);
164// };
165// const getArcCenter = (
166// px: any,
167// py: any,
168// cx: any,
169// cy: any,
170// rx: number,
171// ry: number,
172// largeArcFlag: number,
173// sweepFlag: number,
174// sinphi: number,
175// cosphi: number,
176// pxp: number,
177// pyp: number,
178// ) => {
179// const rxsq = Math.pow(rx, 2);
180// const rysq = Math.pow(ry, 2);
181// const pxpsq = Math.pow(pxp, 2);
182// const pypsq = Math.pow(pyp, 2);
183// let radicant = rxsq * rysq - rxsq * pypsq - rysq * pxpsq;
184// if (radicant < 0) {
185// radicant = 0;
186// }
187// radicant /= rxsq * pypsq + rysq * pxpsq;
188// radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1);
189// const centerxp = ((radicant * rx) / ry) * pyp;
190// const centeryp = ((radicant * -ry) / rx) * pxp;
191// const centerx = cosphi * centerxp - sinphi * centeryp + (px + cx) / 2;
192// const centery = sinphi * centerxp + cosphi * centeryp + (py + cy) / 2;
193// const vx1 = (pxp - centerxp) / rx;
194// const vy1 = (pyp - centeryp) / ry;
195// const vx2 = (-pxp - centerxp) / rx;
196// const vy2 = (-pyp - centeryp) / ry;
197// const ang1 = vectorAngle(1, 0, vx1, vy1);
198// let ang2 = vectorAngle(vx1, vy1, vx2, vy2);
199// if (sweepFlag === 0 && ang2 > 0) {
200// ang2 -= TAU;
201// }
202// if (sweepFlag === 1 && ang2 < 0) {
203// ang2 += TAU;
204// }
205// return [centerx, centery, ang1, ang2];
206// };
207// const arcToBezier = ({ px, py, cx, cy, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0 }) => {
208// const curves = [];
209// if (rx === 0 || ry === 0) {
210// return [{ x1: 0, y1: 0, x2: 0, y2: 0, x: cx, y: cy }];
211// }
212// const sinphi = Math.sin((xAxisRotation * TAU) / 360);
213// const cosphi = Math.cos((xAxisRotation * TAU) / 360);
214// const pxp = (cosphi * (px - cx)) / 2 + (sinphi * (py - cy)) / 2;
215// const pyp = (-sinphi * (px - cx)) / 2 + (cosphi * (py - cy)) / 2;
216// if (pxp === 0 && pyp === 0) {
217// return [{ x1: 0, y1: 0, x2: 0, y2: 0, x: cx, y: cy }];
218// }
219// rx = Math.abs(rx);
220// ry = Math.abs(ry);
221// const lambda = Math.pow(pxp, 2) / Math.pow(rx, 2) + Math.pow(pyp, 2) / Math.pow(ry, 2);
222// if (lambda > 1) {
223// rx *= Math.sqrt(lambda);
224// ry *= Math.sqrt(lambda);
225// }
226// let [centerx, centery, ang1, ang2] = getArcCenter(
227// px,
228// py,
229// cx,
230// cy,
231// rx,
232// ry,
233// largeArcFlag,
234// sweepFlag,
235// sinphi,
236// cosphi,
237// pxp,
238// pyp,
239// );
240// // If 'ang2' == 90.0000000001, then `ratio` will evaluate to
241// // 1.0000000001. This causes `segments` to be greater than one, which is an
242// // unecessary split, and adds extra points to the bezier curve. To alleviate
243// // this issue, we round to 1.0 when the ratio is close to 1.0.
244// let ratio = Math.abs(ang2) / (TAU / 4);
245// if (Math.abs(1.0 - ratio) < 0.0000001) {
246// ratio = 1.0;
247// }
248// const segments = Math.max(Math.ceil(ratio), 1);
249// ang2 /= segments;
250// for (let i = 0; i < segments; i++) {
251// curves.push(approxUnitArc(ang1, ang2));
252// ang1 += ang2;
253// }
254// return curves.map((curve) => {
255// const { x: x1, y: y1 } = mapToEllipse(curve[0], rx, ry, cosphi, sinphi, centerx, centery);
256// const { x: x2, y: y2 } = mapToEllipse(curve[1], rx, ry, cosphi, sinphi, centerx, centery);
257// const { x, y } = mapToEllipse(curve[2], rx, ry, cosphi, sinphi, centerx, centery);
258// return { x1, y1, x2, y2, x, y };
259// });
260// };
261// export function arcToCubic(
262// x1: number,
263// y1: number,
264// rx: number,
265// ry: number,
266// angle: number,
267// LAF: number,
268// SF: number,
269// x2: number,
270// y2: number,
271// ) {
272// const curves = arcToBezier({
273// px: x1,
274// py: y1,
275// cx: x2,
276// cy: y2,
277// rx,
278// ry,
279// xAxisRotation: angle,
280// largeArcFlag: LAF,
281// sweepFlag: SF,
282// });
283// return curves.reduce((prev, cur) => {
284// const { x1, y1, x2, y2, x, y } = cur;
285// prev.push(x1, y1, x2, y2, x, y);
286// return prev;
287// }, [] as number[]);
288// }
289//# sourceMappingURL=arc-2-cubic.js.map
\No newline at end of file