import { splitWordmark } from "./brand"; import { WORDMARK_METRICS } from "./wordmarkMetrics"; // A wordmark's width as the DISPLAY FACE sets it, in em of its font size — // so a layout can reserve it before the web font has loaded. The export // header's wordmark drops its text when the text does not fit beside the // icons (export/app/components/Header.tsx); measured in the fallback face, // which is narrower, a title could fit, show, and then vanish when Archivo // arrived. Reserving this width makes the decision the same either way. // // The lead is weight 720 and the suffix 380 (common/components/Wordmark.tsx), // both at wdth 118; each character is its advance from the vendored font // (lib/wordmarkMetrics.ts, generated) plus `letterSpacingEm`, kerning left // out, and MARGIN more: the web font Google serves renders 0.2–2.4 % wider // than the vendored file's advances sum to (measured in Chromium). The header // sets this as the wordmark's MIN-width, not its width: where a platform // renders the text wider still, the box grows with it (and the text drops a // little sooner) rather than clipping its last letter. A character the table // does not hold counts as UNMAPPED_EM, wider than any it does. const UNMAPPED_EM = 1.25; const MARGIN = 1.03; function advanceEm(cp: number, wght: 720 | 380): number { const { ranges, advances, unitsPerEm } = WORDMARK_METRICS; let offset = 0; for (const [first, last] of ranges) { if (cp >= first && cp <= last) return advances[wght][offset + cp - first] / unitsPerEm; offset += last - first + 1; } return UNMAPPED_EM; } export function wordmarkWidthEm(title: string, lead: string | undefined, letterSpacingEm = 0): number { const parts = splitWordmark(title, lead); let em = 0; for (const [text, wght] of [ [parts.lead, 720], [parts.suffix, 380], ] as const) { for (const ch of text) em += advanceEm(ch.codePointAt(0)!, wght) + letterSpacingEm; } return Math.round(em * MARGIN * 1000) / 1000; }