Arcs as Bézier curves for drawing
64-bitTurns a run or an outline into move, line and curve commands.
k = 4 ÷ 3 × tan(φ ÷ 4); c₁ = p₀ + k·r·tangent₀; c₂ = p₃ − k·r·tangent₁
Each arc is split into pieces = max(1, ceil(|sweep| ÷ 90° − 1e-9)) equal parts with control points at k·r along the tangents, k = 4 ÷ 3 × tan(φ ÷ 4). The last piece ends exactly on the stored end point, not on a recomputed one. Used to draw only; lengths and areas never come from these curves.
/**
* A run or outline as drawing commands: ['M', p], ['L', p], ['C', c1, c2, p] (arcs as cubic
* Béziers of at most 90° each — what PDF content streams and the canvas both draw). Affine
* transforms keep Béziers exact, so callers may map the points to the screen afterwards.
*/
export function pathOps(pts, closed = false) {
const ops = [];
const n = pts.length;
if (!n) return ops;
ops.push(['M', { x: pts[0].x, y: pts[0].y }]);
const segs = closed ? n : n - 1;
for (let i = 0; i < segs; i++) {
const p = pts[i], q = pts[(i + 1) % n];
const a = arcOf(p, q, p.b);
if (!a) { ops.push(['L', { x: q.x, y: q.y }]); continue; }
const pieces = Math.max(1, Math.ceil(Math.abs(a.sweep) / (Math.PI / 2) - 1e-9));
const phi = a.sweep / pieces, k = 4 / 3 * Math.tan(phi / 4);
for (let j = 0; j < pieces; j++) {
const t0 = a.a0 + phi * j, t1 = t0 + phi;
const p0 = { x: a.cx + a.r * Math.cos(t0), y: a.cy + a.r * Math.sin(t0) };
const p3 = j === pieces - 1 ? { x: q.x, y: q.y } : { x: a.cx + a.r * Math.cos(t1), y: a.cy + a.r * Math.sin(t1) };
ops.push(['C', { x: p0.x - k * a.r * Math.sin(t0), y: p0.y + k * a.r * Math.cos(t0) }, { x: p3.x + k * a.r * Math.sin(t1), y: p3.y - k * a.r * Math.cos(t1) }, p3]);
}
}
if (closed) ops.push(['Z']);
return ops;
}