Skip to content

packages/core/src/lib/geometry/path-conversion.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 import { arcPoint, ellipseParameters, singularArcPoints, type ArcSegment } from './path-arc.js';
2 import { PathGeometry, visitPathGeometry } from './path-geometry.js';
3 
4 /**
5  * Creates a fresh caller-owned native snapshot; never returns a shared cache.
6  *
7  * @param geometry - Pibbl geometry to convert. See {@link PathGeometry}.
8  * @returns A native Canvas path representing the geometry.
9  *
10  * @see {@link PathGeometry}
11  */
12 export function toPath2D(geometry: PathGeometry): Path2D {
13   const path = new Path2D();
14   visitPathGeometry(geometry, (type, data, i) => {
15     switch (type) {
16       case 0: path.moveTo(data[i], data[i + 1]); break;
17       case 1: path.lineTo(data[i], data[i + 1]); break;
18       case 2: path.quadraticCurveTo(data[i], data[i + 1], data[i + 2], data[i + 3]); break;
19       case 3: path.bezierCurveTo(data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], data[i + 5]); break;
20       case 4: path.closePath(); break;
21       case 5: {
22         const arc = readArc(data, i);
23         const ellipse = ellipseParameters(arc);
24         if (ellipse) {
25           path.ellipse(arc.cx, arc.cy, ellipse.radiusX, ellipse.radiusY, ellipse.rotation,
26             ellipse.start, ellipse.start + ellipse.sweep, ellipse.sweep < 0);
27         } else {
28           const start = arcPoint(arc, arc.startAngle);
29           path.lineTo(start[0], start[1]);
30           for (const [x, y] of singularArcPoints(arc)) path.lineTo(x, y);
31         }
32         break;
33       }
34     }
35   });
36   return path;
37 }
38 
39 /**
40  * Serializes independent absolute SVG path data without implicit rounding.
41  *
42  * @param geometry - Pibbl geometry to serialize. See {@link PathGeometry}.
43  * @returns SVG path data representing the geometry.
44  *
45  * @see {@link PathGeometry}
46  */
47 export function toSvgPathData(geometry: PathGeometry): string {
48   const parts: string[] = [];
49   const commands = ['M', 'L', 'Q', 'C', 'Z'];
50   const widths = [2, 2, 4, 6, 0];
51   let currentX = 0, currentY = 0, startX = 0, startY = 0;
52   visitPathGeometry(geometry, (type, data, i) => {
53     if (type === 5) {
54       const arc = readArc(data, i);
55       const arcStart = arcPoint(arc, arc.startAngle);
56       if (currentX !== arcStart[0] || currentY !== arcStart[1]) {
57         parts.push('L', String(arcStart[0]), String(arcStart[1]));
58       }
59       [currentX, currentY] = arcPoint(arc, arc.startAngle + arc.sweep);
60       const ellipse = ellipseParameters(arc);
61       if (!ellipse) {
62         for (const [x, y] of singularArcPoints(arc)) parts.push('L', String(x), String(y));
63       } else if (arc.sweep !== 0) {
64         // Splitting into at most half-turns handles full circles and avoids SVG's
65         // ambiguous coincident endpoints. No flattening is involved.
66         const count = Math.ceil(Math.abs(arc.sweep) / Math.PI);
67         for (let step = 1; step <= count; step++) {
68           const [x, y] = arcPoint(arc, arc.startAngle + arc.sweep * step / count);
69           parts.push('A', String(ellipse.radiusX), String(ellipse.radiusY),
70             String(ellipse.rotation * 180 / Math.PI), '0', ellipse.sweep < 0 ? '0' : '1', String(x), String(y));
71         }
72       }
73       return;
74     }
75     parts.push(commands[type]);
76     for (let j = 0; j < widths[type]; j++) parts.push(String(data[i + j]));
77     if (type === 4) { currentX = startX; currentY = startY; }
78     else {
79       currentX = data[i + widths[type] - 2]; currentY = data[i + widths[type] - 1];
80       if (type === 0) { startX = currentX; startY = currentY; }
81     }
82   });
83   return parts.join(' ');
84 }
85 
86 function readArc(data: Float64Array, i: number): ArcSegment {
87   return { type: 'arc', cx: data[i], cy: data[i + 1], ux: data[i + 2], uy: data[i + 3],
88     vx: data[i + 4], vy: data[i + 5], startAngle: data[i + 6], sweep: data[i + 7] };
89 }
90 

Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.