import { CLEAR, board } from "./frame.js"; const TAU = 2 * Math.PI; // PAINT const hex = (c) => "#" + [c[0], c[1], c[2]].map((v) => v.toString(16).padStart(2, "0")).join(""); const alpha = (key, c) => (c[3] < 255 ? ` ${key}-opacity="${c[3] / 255}"` : ""); const fill = (c) => ` fill="${hex(c)}"${alpha("fill", c)}`; const stroke = (c, thick) => ` fill="none" stroke="${hex(c)}"${alpha("stroke", c)} stroke-width="${thick}"`; const points = (pts) => pts.map((p) => `${p[0]},${p[1]}`).join(" "); const ROUND = ` stroke-linecap="round" stroke-linejoin="round"`; function base64(bytes) { let text = ""; for (let i = 0; i < bytes.length; i += 0x8000) text += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); return btoa(text); } async function href(pixels, png) { if (!png) throw new Error("svg: an image needs a png encoder, text({ png })"); const out = await png(pixels); if (typeof out === "string") return out; return `data:image/png;base64,${base64(out instanceof Uint8Array ? out : new Uint8Array(out))}`; } // SVG export function svg(width, height, ground = CLEAR) { const parts = []; if (ground[3] > 0) parts.push(``); function disc(cx, cy, r, c) { if (r > 0) parts.push(``); } function polygon(pts, c) { if (pts.length >= 3) parts.push(``); } function ring(cx, cy, r, thick, c) { if (r <= 0) return disc(cx, cy, r + thick / 2, c); parts.push(``); } return { ...board(width, height), async text({ png } = {}) { const body = []; for (const part of parts) { if (typeof part === "string") body.push(part); else body.push(``); } return `\n${body.join("\n")}\n\n`; }, rect(x, y, w, h, c) { parts.push(``); }, round_rect(x, y, w, h, r, c) { const rr = Math.max(Math.min(r, w / 2, h / 2), 0); parts.push(``); }, disc, ring, segment(a, b, thick, c) { if (a[0] === b[0] && a[1] === b[1]) return disc(a[0], a[1], thick / 2, c); parts.push(``); }, polyline(pts, thick, c) { if (pts.length >= 2) parts.push(``); }, triangle(a, b, c, color) { polygon([a, b, c], color); }, polygon, arc(center, r, angles, thick, c) { const [cx, cy] = center; const lo = Math.min(angles[0], angles[1]); const hi = Math.max(angles[0], angles[1]); const span = hi - lo; const at = (t) => [cx + r * Math.cos(t), cy + r * Math.sin(t)]; if (span >= TAU) return ring(cx, cy, r, thick, c); if (span === 0) return disc(...at(lo), thick / 2, c); const arm = `A${r},${r} 0 0 1`; parts.push(``); }, image(x, y, w, h, pixels) { parts.push({ x, y, w, h, pixels: { shape: [...pixels.shape], colors: Uint8Array.from(pixels.colors) } }); }, }; }