import type { Artboard } from "@core/artboard"; import type { Asset } from "@core/asset"; import type { Layer } from "@core/layer"; export async function downloadArtboardPng(artboard: Artboard, assets: readonly Asset[]) { const width = Math.max(1, Math.round(artboard.bounds.w)); const height = Math.max(1, Math.round(artboard.bounds.h)); const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); if (!context) throw new Error("Canvas 2D is not available"); if (artboard.backgroundColor !== "transparent") { context.fillStyle = artboard.backgroundColor; context.fillRect(0, 0, width, height); } context.save(); context.translate(-artboard.bounds.x, -artboard.bounds.y); for (const layer of artboard.layers) await drawLayer(context, layer, assets); context.restore(); const url = canvas.toDataURL("image/png"); const link = document.createElement("a"); link.href = url; link.download = `${safeFilename(artboard.name)}.png`; link.click(); } async function drawLayer(context: CanvasRenderingContext2D, layer: Layer, assets: readonly Asset[]) { if (!layer.visible) return; context.save(); context.globalAlpha *= layer.opacity; if (layer.type === "group") { for (const child of layer.children) await drawLayer(context, child, assets); context.restore(); return; } const asset = assets.find((candidate) => candidate.id === layer.assetId); if (!asset) { context.restore(); return; } const image = await loadImage(asset.source); context.drawImage( image, layer.transform.position.x, layer.transform.position.y, asset.intrinsicSize.w * layer.transform.scale.x, asset.intrinsicSize.h * layer.transform.scale.y, ); context.restore(); } function loadImage(source: string) { return new Promise((resolve, reject) => { const image = new Image(); image.onload = () => resolve(image); image.onerror = () => reject(new Error("Failed to load image for export")); image.src = source; }); } function safeFilename(name: string) { return name.trim().replace(/[^a-z0-9-_]+/gi, "-").replace(/^-+|-+$/g, "") || "artboard"; }