import type { BaseLayer } from "./base-layer"; import type { Size } from "./geometry"; export const builtInTextFonts = ["Arial", "Georgia", "Courier New", "Trebuchet MS"] as const; export type TextFontFamily = typeof builtInTextFonts[number]; export type TextAlignment = "left" | "center" | "right"; export type TextFontStyle = "normal" | "italic"; export type TextFontWeight = 400 | 700; export type TextStyle = { fontFamily: TextFontFamily; fontSize: number; fontWeight: TextFontWeight; fontStyle: TextFontStyle; color: string; alignment: TextAlignment; lineHeight: number; }; export type TextLayer = BaseLayer & { type: "text"; content: string; style: TextStyle; }; /** Deterministic layout box shared by selection, renderer and export. */ export function measureTextLayer(layer: Pick): Size { const lines = layer.content.split("\n"); const weightFactor = layer.style.fontWeight === 700 ? 1.04 : 1; const italicFactor = layer.style.fontStyle === "italic" ? 1.03 : 1; const familyFactor = layer.style.fontFamily === "Courier New" ? 0.62 : layer.style.fontFamily === "Georgia" ? 0.56 : 0.54; const longest = Math.max(1, ...lines.map((line) => [...line].length)); return { w: Math.max(1, longest * layer.style.fontSize * familyFactor * weightFactor * italicFactor), h: Math.max(1, lines.length * layer.style.fontSize * layer.style.lineHeight), }; } export function isValidTextStyle(style: TextStyle): boolean { return builtInTextFonts.includes(style.fontFamily) && Number.isFinite(style.fontSize) && style.fontSize >= 1 && style.fontSize <= 1000 && (style.fontWeight === 400 || style.fontWeight === 700) && (style.fontStyle === "normal" || style.fontStyle === "italic") && /^#[0-9a-f]{6}$/i.test(style.color) && (style.alignment === "left" || style.alignment === "center" || style.alignment === "right") && Number.isFinite(style.lineHeight) && style.lineHeight >= 0.5 && style.lineHeight <= 5; }