Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/tests/font_inject.rs

5.6 KiB, 21 runs

created by r1870400018:58791, 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//! Do fonts injected through a project's `fonts` field actually reach the document as usable faces?
2//!
3//! The wasm surface installs every `project.fonts` pair into the source map ([`crate::vfs`]) and then
4//! assembles the document. Before the fix in this lane a font was installed only at the path the consumer
5//! named, which the lone-file face resolver -- reading its own `<root>/assets/fonts` directory alone --
6//! never saw unless the consumer happened to inject it there. The wasm surface now also places each font at
7//! [`book::project_font_path`], the resolver's own location, so a face the document names resolves whatever
8//! path the file was injected under. This test drives the same [`compile::assemble`] path natively, with a
9//! source map installed as the wasm surface installs one, and exercises the real [`book::project_font_path`]
10//! the surface routes through -- so it is the native proof of the browser behaviour.
11
12use oxedyne_fe2o3_austenite::book;
13use oxedyne_fe2o3_austenite::compile;
14use oxedyne_fe2o3_austenite::fonts;
15use oxedyne_fe2o3_austenite::vfs;
16
17use oxedyne_fe2o3_core::prelude::*;
18
19use std::collections::HashMap;
20use std::path::PathBuf;
21use std::sync::Arc;
22
23/// A lone document that names `Testface` as its heading display face through the `doc.with` idiom the
24/// documentation trees use, so the theme's top heading levels resolve against it.
25const SRC: &str = "#import \"template.typ\": *\n#show: doc.with(heading-font: \"Testface\")\n\n= A Heading\n\nBody text that sets in the reading face.\n";
26
27/// The bytes of a real, parseable face, borrowed from the crate's own Libertinus set so the resolver has
28/// something that actually loads (a face that will not parse is dropped by the resolver, which would make
29/// a negative result ambiguous).
30fn face_bytes() -> Vec<u8> {
31 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fonts").join("LibertinusSerif-Regular.otf");
32 std::fs::read(&path).expect("the crate's embedded Libertinus face must be present for this test")
33}
34
35/// Assembles the lone document with the given font pairs installed into the source map, returning whether
36/// the assembled document's face resolver holds `Testface` -- or the assembly error, which a heading face
37/// the document names but no injected font supplies now is (the missing-family precheck). When `route` is set, each font is additionally
38/// installed at [`book::project_font_path`] -- exactly what the wasm surface's `read_font_pairs` now does --
39/// so the test exercises the real routing rather than a copy of it. Mirrors the wasm `assemble_and_run`
40/// otherwise: the embedded reading set is the lone-file provider, and the map is cleared afterwards.
41fn resolves_injected(font_pairs: &[(&str, Vec<u8>)], route: bool) -> Outcome<Result<bool, String>> {
42 let main = PathBuf::from("/main.typ");
43 let mut files: HashMap<PathBuf, Vec<u8>> = HashMap::new();
44 files.insert(main.clone(), SRC.as_bytes().to_vec());
45 for (path, bytes) in font_pairs {
46 let given = PathBuf::from(path);
47 if route {
48 if let Some(routed) = book::project_font_path(&main, &given) {
49 files.insert(routed, bytes.clone());
50 }
51 }
52 files.insert(given, bytes.clone());
53 }
54 res!(vfs::install(files));
55 let fonts = Arc::new(res!(fonts::libertinus()));
56 let outcome = compile::assemble(&main, || Ok(fonts.clone()));
57 let _ = vfs::clear();
58 Ok(match outcome {
59 Ok((assembled, _refusals, _skip)) => Ok(assembled.faces.resolves("Testface")),
60 Err(e) => Err(fmt!("{}", e)),
61 })
62}
63
64/// Pins the whole story in one test (the shared source-map global forbids two at once): the resolver reads
65/// only its own directory, so an unrouted font is invisible unless injected exactly there; routing each
66/// injected font to [`book::project_font_path`] -- as the wasm surface now does -- makes a face named in the
67/// document resolve whatever path the consumer chose to inject it under.
68#[test]
69fn injected_fonts_resolve_once_routed_to_the_resolver_path() -> Outcome<()> {
70 // The bare gap: the same bytes under a naive `/fonts` directory are invisible without routing.
71 let unrouted_bare = res!(resolves_injected(&[("/fonts/Testface-Regular.otf", face_bytes())], false));
72 // The pre-existing narrow path that worked: injected exactly where the resolver reads.
73 let unrouted_at_assets = res!(resolves_injected(&[("/assets/fonts/Testface-Regular.otf", face_bytes())], false));
74 // The fix: routing an arbitrarily-pathed injected font makes it resolve.
75 let routed_bare = res!(resolves_injected(&[("/fonts/Testface-Regular.otf", face_bytes())], true));
76 // And a bare basename with no directory at all also resolves once routed.
77 let routed_basename = res!(resolves_injected(&[("Testface-Regular.otf", face_bytes())], true));
78
79 eprintln!(
80 "[font-inject] unrouted /fonts -> {:?}, unrouted /assets/fonts -> {:?}, routed /fonts -> {:?}, routed basename -> {:?}",
81 unrouted_bare, unrouted_at_assets, routed_bare, routed_basename);
82
83 // Without routing, a font outside /assets/fonts is invisible; the heading face the document names is
84 // then missing, which is a hard error naming it rather than a silent fall-back to the body role.
85 match &unrouted_bare {
86 Err(msg) => assert!(msg.contains("Testface"), "the error must name the missing family: {}", msg),
87 Ok(r) => return Err(err!(
88 "An unrouted, invisible font must fail the precheck, but assembly gave {}.", r; Test, Mismatch)),
89 }
90 assert_eq!(unrouted_at_assets, Ok(true), "a font injected exactly at /assets/fonts/<Name>-Regular.otf always resolved");
91 assert_eq!(routed_bare, Ok(true), "routing makes an arbitrarily-pathed injected font resolve -- the fix");
92 assert_eq!(routed_basename, Ok(true), "routing makes a bare-basename injected font resolve too");
93 Ok(())
94}