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 | |
| 12 | use oxedyne_fe2o3_austenite::book; |
| 13 | use oxedyne_fe2o3_austenite::compile; |
| 14 | use oxedyne_fe2o3_austenite::fonts; |
| 15 | use oxedyne_fe2o3_austenite::vfs; |
| 16 | |
| 17 | use oxedyne_fe2o3_core::prelude::*; |
| 18 | |
| 19 | use std::collections::HashMap; |
| 20 | use std::path::PathBuf; |
| 21 | use 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. |
| 25 | const 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). |
| 30 | fn 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. |
| 41 | fn 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] |
| 69 | fn 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 | } |