Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/probe_typstfont.mjs

3.4 KiB, 1 run

created by r2519314175:93, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// probe_typstfont.mjs — does a font supplied at compile time actually get USED?
2//
3// `set_fonts` accepted a resolver in the first probe, but acceptance is not use:
4// the compiler renders an unknown family with a silent fallback, so a PDF comes
5// out either way and the byte count alone proves nothing. This asks the only
6// question that matters for correctness -- is the family named in the source the
7// family embedded in the PDF -- and whether `set_fonts` REPLACES the set it was
8// built with, which decides whether the bundled fonts must be re-added.
9import { readFileSync } from 'node:fs';
10import { open } from './harness.mjs';
11
12// The fixture's copy, not the author's tree: a probe that only runs on one machine
13// is a probe nobody reruns.
14const RADLEY = Array.from(readFileSync(new URL(
15 './fixtures/typstproj/books/assets/fonts/Radley-Regular.ttf', import.meta.url)));
16
17const s = await open({ name: 'typstfontprobe' });
18const p = s.page;
19await p.waitForTimeout(1200);
20
21const out = await p.evaluate(async (radley) => {
22 const VENDOR = new URL('/vendor/typst/', location.href);
23 const mod = await import(new URL('typst_ts_web_compiler.mjs', VENDOR).href);
24 await mod.default(new URL('typst_ts_web_compiler_bg.wasm', VENDOR));
25 const FONTS = ['LibertinusSerif-Regular.otf', 'LibertinusSerif-Bold.otf',
26 'LibertinusSerif-Italic.otf', 'LibertinusSerif-BoldItalic.otf', 'NewCMMath-Regular.otf'];
27 const bundled = [];
28 for (const n of FONTS) {
29 const r = await fetch(new URL('fonts/' + n, VENDOR));
30 bundled.push(new Uint8Array(await r.arrayBuffer()));
31 }
32 const b = new mod.TypstCompilerBuilder();
33 b.set_dummy_access_model();
34 for (const f of bundled) await b.add_raw_font(f);
35 const c = await b.build();
36
37 // A PDF names every embedded font in its descriptors, so grepping the bytes
38 // for the family is a direct question about what was actually drawn with.
39 const names = (pdf) => {
40 const t = new TextDecoder('latin1').decode(pdf);
41 const hits = new Set();
42 for (const m of t.matchAll(/\/BaseFont\s*\/([A-Za-z0-9+\-_]+)/g)) hits.add(m[1]);
43 return Array.from(hits);
44 };
45 const run = (main) => {
46 const r = c.compile(main, undefined, 'pdf', 3);
47 const pdf = r instanceof Uint8Array ? r : (r && (r.result || r.artifact));
48 if (pdf && pdf.length > 4) return { fonts: names(pdf) };
49 return { err: JSON.stringify(r && r.diagnostics ? r.diagnostics : r).slice(0, 300) };
50 };
51 const res = {};
52 c.reset_shadow();
53 c.add_source('/m.typ', '#set text(font: "Radley")\n= Hello there, Radley\n\nSome body text.\n');
54 res.beforeAdding = run('/m.typ');
55
56 const rad = new Uint8Array(radley);
57 // Project fonts only -- does the bundled set survive?
58 const fbOnly = new mod.TypstFontResolverBuilder();
59 fbOnly.add_raw_font(rad);
60 c.set_fonts(await fbOnly.build());
61 res.radleyOnly = run('/m.typ');
62 c.add_source('/l.typ', '#set text(font: "Libertinus Serif")\n= Hello\n\nBody.\n');
63 res.radleyOnlyLibertinus = run('/l.typ');
64
65 // Bundled plus project, which is what the driver will actually do.
66 const fbAll = new mod.TypstFontResolverBuilder();
67 for (const f of bundled) fbAll.add_raw_font(f);
68 fbAll.add_raw_font(rad);
69 c.set_fonts(await fbAll.build());
70 res.both = run('/m.typ');
71 res.bothLibertinus = run('/l.typ');
72 res.info = JSON.parse(JSON.stringify(new mod.TypstFontResolverBuilder().get_font_info(rad)))
73 .info.map(i => ({ family: i.family, variant: i.variant }));
74 return res;
75}, RADLEY);
76
77console.log(JSON.stringify(out, null, 2));
78await s.close();