| 2026 | } |
| 2027 | |
| 2028 | function getPreviewTftFontStyle(tftFontValue, textSize) { |
| 2029 | const v = String(tftFontValue || DEFAULT_TFT_FONT); |
| 2030 | const ts = Math.max(1, Number(textSize || 1)); |
| 2031 | |
| 2032 | // Built-in bitmap fonts (1..8) — approximate sizes for browser preview. |
| 2033 | if (/^\d+$/.test(v)) { |
| 2034 | const n = Math.max(1, Math.min(8, parseInt(v, 10))); |
| 2035 | const basePxByFont = { 1: 12, 2: 14, 3: 16, 4: 18, 5: 20, 6: 24, 7: 28, 8: 32 }; |
| 2036 | return { |
| 2037 | family: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', |
| 2038 | sizePx: (basePxByFont[n] || 12) * (ts * 0.9), |
| 2039 | weight: "500", |
| 2040 | }; |
| 2041 | } |
| 2042 | |
| 2043 | // FreeFonts (GFXFF) — infer family/weight/size from the name. |
| 2044 | const lower = v.toLowerCase(); |
| 2045 | const ptMatch = lower.match(/(\d+)pt/); |
| 2046 | const pt = ptMatch ? Math.max(6, Math.min(48, parseInt(ptMatch[1], 10))) : 12; |
| 2047 | const isMono = lower.includes("mono"); |
| 2048 | const isSerif = lower.includes("serif"); |
| 2049 | const isBold = lower.includes("bold"); |
| 2050 | |
| 2051 | const family = isMono |
| 2052 | ? 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' |
| 2053 | : (isSerif ? 'Georgia, "Times New Roman", Times, serif' : 'system-ui, -apple-system, "Segoe UI", Arial, sans-serif'); |
| 2054 | |
| 2055 | // 1pt ≈ 1.333px; apply textSize as a mild multiplier for preview only. |
| 2056 | return { family, sizePx: pt * 1.333 * (ts * 0.85), weight: isBold ? "700" : "500" }; |
| 2057 | } |
| 2058 | |
| 2059 | function safeFnNameFromTitle(title) { |
| 2060 | const base = String(title || "Screen").replace(/[^a-zA-Z0-9]+/g, " ").trim(); |