Files
Voiced/src/pdf/fonts/register.ts
T
xavierk 40aa3e05d9 Add typography roles, text primitives, fontkit measurer and layout lint
Txt always sets an absolute line height; RenderTxt never does. Row pins
every cell and stacks when the fill column would be too narrow. IdText and
FitText shrink then chunk or truncate. The fontkit measurer matches react-pdf
text widths exactly. A TypeScript-AST lint test enforces the layout rules
(no lineHeight outside Txt, no non-literal fixed/wrap/minPresenceAhead,
flexGrow needs flexBasis, no stray absolute positioning).
2026-10-04 04:48:19 +05:30

65 lines
2.2 KiB
TypeScript

import { Font } from "@react-pdf/renderer";
import { FONT_MANIFEST } from "./manifest";
import { familySources } from "./stack";
export { FONT_STACK, familySources, stack, type FamilySource } from "./stack";
let registered = false;
/**
* Registers every bundled family from the manifest. Idempotent; never call Font.reset(), the fonts
* would not load again.
*
* `base` is a URL prefix in the app ("/fonts") and a directory under Node (tests). Only static TTF is
* used: WOFF2 embeds glyphs without outlines (invisible text) and variable fonts embed only their
* default instance.
*/
export function registerBundledFonts(base: string): void {
if (registered) return;
registered = true;
for (const fam of FONT_MANIFEST.families) {
const sources = familySources(fam).map((s) => ({
src: `${base}/${fam.folder}/${s.file}`,
fontWeight: s.fontWeight,
fontStyle: s.fontStyle,
}));
Font.register({ family: fam.family, fonts: sources });
}
// The default hyphenation inserts "-" into e-mail addresses, URLs and identifiers.
Font.registerHyphenationCallback((word) => [word]);
}
const MIN_FONT_BYTES = 10 * 1024;
function hasTrueTypeMagic(b: Uint8Array): boolean {
if (b.length < 4) return false;
if (b[0] === 0 && b[1] === 1 && b[2] === 0 && b[3] === 0) return true;
return String.fromCharCode(b[0], b[1], b[2], b[3]) === "true";
}
/**
* Fetches every bundled font and returns a list of problems (empty when all are fine). Tauri answers
* an unknown path with index.html and HTTP 200, so the response bytes must be checked, not the status.
*/
export async function preflightFonts(
fetchBytes: (url: string) => Promise<Uint8Array>,
base: string,
): Promise<string[]> {
const problems: string[] = [];
for (const fam of FONT_MANIFEST.families) {
for (const font of fam.fonts) {
const url = `${base}/${fam.folder}/${font.file}`;
try {
const bytes = await fetchBytes(url);
if (!hasTrueTypeMagic(bytes)) problems.push(`${url}: not a TrueType font`);
else if (bytes.length <= MIN_FONT_BYTES) problems.push(`${url}: only ${bytes.length} bytes`);
} catch (err) {
problems.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
}
}
}
return problems;
}