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).
65 lines
2.2 KiB
TypeScript
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;
|
|
}
|