import type { LineNote } from "@/lib/line"; import { chartOuterWidth, chartPixelHeight } from "@/lib/notation"; export const PNG_CHART_WIDTH = 88; const GAP = 18; const PAD = 28; export { chartPixelHeight }; /** A quarter space is about one chart wide. Longer spaces take more room. */ export function spacePixelWidth(quarters: number, chartWidth: number): number { return Math.max(Math.round(chartWidth * 0.45), Math.round(chartWidth * Math.max(quarters, 0))); } export type PngItem = { kind: LineNote["kind"]; caption: string; glyphWidth: number; glyphHeight: number; }; export function pngItemFromNote(note: Pick, chartWidth = PNG_CHART_WIDTH): PngItem { const drawn = chartOuterWidth(chartWidth); const glyphHeight = chartPixelHeight(chartWidth); const caption = note.kind === "note" ? note.pitch : note.kind === "rest" ? "Rest" : "Space"; return { kind: note.kind, caption, glyphWidth: note.kind === "space" ? spacePixelWidth(note.quarters, drawn) : drawn, glyphHeight, }; } export function pngSlotWidth(item: PngItem): number { const text = item.caption.length * 8; return Math.max(item.glyphWidth, text, 48); } export const PNG_ROW_GAP = 36; /** One header, then every fingering line stacked underneath. */ export function pngStackLayout(rows: PngItem[][], title: string) { const laid = rows.map((items) => pngLayout(items, title)); const width = Math.max(1, ...laid.map((row) => row.width)); const blocks = laid.reduce((sum, row) => sum + row.glyphHeight + 28, 0); const height = PAD + 44 + blocks + PNG_ROW_GAP * Math.max(0, laid.length - 1) + PAD; return { width, height, rows: laid, pad: PAD, rowGap: PNG_ROW_GAP }; } export function pngLayout(items: PngItem[], title: string) { const slots = items.map((item) => ({ item, width: pngSlotWidth(item) })); const content = slots.reduce((sum, slot) => sum + slot.width, 0) + GAP * Math.max(0, slots.length - 1); const titleWidth = Math.max(220, title.length * 13 + 8); const width = Math.max(titleWidth, content, 1) + PAD * 2; const glyphHeight = chartPixelHeight(PNG_CHART_WIDTH); const height = PAD + 44 + glyphHeight + 28 + PAD; return { width, height, slots, glyphHeight, pad: PAD, gap: GAP }; } export function pngFilename(title: string): string { const slug = title .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-|-$/g, ""); return `${slug || "line"}.png`; }