diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index e563d4d232..6b39a7b4c2 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -8,8 +8,12 @@ //! - [`hit`]: point ↔ position, caret geometry, line navigation, selection rectangles. #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] -/// Every unit test calls this before any font lookup (including its fixture construction). -/// A process-wide immutable choice keeps font and math caches consistent across test threads. +/// Installs the pinned test fonts as the process-wide font database. The layout entry points +/// (`layout`, `layout_para`, `layout_equation`) call it first in test builds; a test that looks +/// up fonts itself before any layout call (fixtures, `word::resolve`, `FontDb::global`) calls it +/// at its start. It is idempotent and never calls back into layout, so it cannot recurse. The +/// choice is process-wide and immutable, which keeps font and math caches consistent across +/// test threads. #[cfg(test)] fn test_fonts() { assert!(wordcraft_fonts::FontDb::init_test_fonts(), "layout tests must initialize fixed fonts before any global font lookup"); @@ -314,6 +318,8 @@ pub struct DocLayout { /// Lay out the whole document. pub fn layout(doc: &Document, cache: &mut LayoutCache, opts: &LayoutOptions) -> DocLayout { + #[cfg(test)] + crate::test_fonts(); let t0 = now_ms(); // Page totals shown in the body (`NUMPAGES`, `SECTIONPAGES`) are known only after // paginating: lay out again with the totals found until they hold (bounded). Footnotes diff --git a/crates/layout/src/math.rs b/crates/layout/src/math.rs index 95070ecd2f..b5ba3d8897 100644 --- a/crates/layout/src/math.rs +++ b/crates/layout/src/math.rs @@ -207,6 +207,8 @@ pub fn layout_equation( props: &MathProps, counter: &mut u32, ) -> MathLayout { + #[cfg(test)] + crate::test_fonts(); let parsed; let nodes: &Arg = if math.nodes.is_empty() && !math.linear { parsed = parse_linear(linear); diff --git a/crates/layout/src/math/tests.rs b/crates/layout/src/math/tests.rs index cf5a2f2ea9..4900af15e4 100644 --- a/crates/layout/src/math/tests.rs +++ b/crates/layout/src/math/tests.rs @@ -35,6 +35,7 @@ fn lay_in(math: &Math, lin: &str, display: bool, avail: f32, mp: &MathProps) -> #[test] fn eq_array_rows_without_ampersand_are_centred() { + crate::test_fonts(); // Word centres a row with no `&` in the array (#2617: a case row "a, n odd" above // "|a|, &n even"), whether or not the other rows have one. use wordcraft_doc::math::*; diff --git a/crates/layout/src/para/paragraph.rs b/crates/layout/src/para/paragraph.rs index f5fdb3c670..3cd1dbdb86 100644 --- a/crates/layout/src/para/paragraph.rs +++ b/crates/layout/src/para/paragraph.rs @@ -553,6 +553,8 @@ fn joined_cluster_starts(p: &Paragraph, clusters: &[Cluster]) -> Vec { /// Lay out one paragraph. pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout { + #[cfg(test)] + crate::test_fonts(); let doc = env.doc; let rp = paragraph_props(p, env); let res = Resolver { doc, style: p.props.style.as_deref(), table: env.table.map(|t| &t.chr) }; diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 29f0b4962e..779232f6ea 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -1345,6 +1345,7 @@ fn a_missing_roman_font_is_drawn_as_word_draws_it() { #[test] fn a_missing_roman_font_with_its_own_substitute_keeps_it() { + crate::test_fonts(); // Times New Roman is roman in every font table. Where it isn't installed it keeps its metric // clone (Liberation Serif), not Cambria: Word, which always has it, never draws it missing. let mut d = Document::from_text("Serif"); @@ -1970,6 +1971,7 @@ fn bullet_first_line(label_font: Option<&str>) -> (f32, f32, f32, f32) { #[test] fn a_symbol_bullet_sizes_its_line_by_the_symbol_font() { + crate::test_fonts(); // Measured in Word: a Symbol bullet makes its line taller than the text alone (Calibri 11 pt // at 1.08 spacing: 15.1 pt, not 14.5 pt), the extra ascent added unscaled. Symbol isn't an open // font; without it the bullet is drawn from another face but its line keeps Symbol's height. @@ -6396,6 +6398,7 @@ fn graphic_text_in_a_degenerate_rectangle_is_not_drawn() { #[test] fn chart_text_the_font_lacks_is_drawn_in_a_fallback_face() { + crate::test_fonts(); // #4720: a Chinese legend in a chart drawn in the body font (which has no Chinese) showed boxes. use wordcraft_doc::graphic::{Graphic, GraphicItem, GraphicKind, TextAlign}; use wordcraft_doc::props::Rgb; diff --git a/crates/layout/src/tests/bidi.rs b/crates/layout/src/tests/bidi.rs index 002d0ad89f..5eae020ec5 100644 --- a/crates/layout/src/tests/bidi.rs +++ b/crates/layout/src/tests/bidi.rs @@ -314,6 +314,7 @@ fn hostile_bidi_text_never_panics() { /// A face that has Arabic letters (installed system fonts), or `None` to skip. fn arabic_face() -> Option { + crate::test_fonts(); wordcraft_fonts::FontDb::global().fallback_for('ب', 0).map(|f| wordcraft_fonts::FaceRef::of(&f)).filter(|f| f.covers('پ')) } diff --git a/crates/layout/src/tests/complex.rs b/crates/layout/src/tests/complex.rs index 01551b1a31..206f48875a 100644 --- a/crates/layout/src/tests/complex.rs +++ b/crates/layout/src/tests/complex.rs @@ -68,6 +68,7 @@ fn myanmar_without_spaces_wraps_between_syllables() { /// A face with Myanmar letters (an installed system font), or `None` to skip. fn myanmar_face() -> Option { + crate::test_fonts(); wordcraft_fonts::FontDb::global().fallback_for('\u{1000}', 0).map(|f| wordcraft_fonts::FaceRef::of(&f)).filter(|f| f.has_layout_tables()) } @@ -119,12 +120,14 @@ const THAI_RUN: &str = "[D] น้ำ ค่ำ ปั้น ฝั่ง ปู /// Is a font with Thai letters installed? `false` skips the test. fn thai_font_installed() -> bool { + crate::test_fonts(); wordcraft_fonts::FontDb::global().fallback_for('\u{0E19}', 0).is_some() } /// Is a Thai font with Latin letters and digits installed (Tahoma, Leelawadee UI, Thonburi, the /// TLWG fonts…)? Noto Sans Thai alone has none, so no face covers [`THAI_RUN`]: `false` skips. fn thai_latin_font_installed() -> bool { + crate::test_fonts(); let face = wordcraft_fonts::word::resolve("TH Sarabun New", false, false).face; THAI_RUN.chars().filter(|c| !c.is_whitespace()).all(|c| face.covers(c)) } diff --git a/crates/layout/src/tests/nbsp.rs b/crates/layout/src/tests/nbsp.rs index 36b9ccb9b1..8303db2733 100644 --- a/crates/layout/src/tests/nbsp.rs +++ b/crates/layout/src/tests/nbsp.rs @@ -6,6 +6,7 @@ use crate::{Document, LayoutCache, LayoutOptions, Placed, layout}; /// Inter with its U+00A0 glyph twice as wide as its space, under the family name "Wnbsp". /// Built from the bundled font so the test needs no installed font. fn wide_nbsp_family() -> &'static str { + crate::test_fonts(); const FAMILY: &str = "Wnbsp"; let inter = wordcraft_fonts::word::resolve("Inter", false, false).face; let mut data = inter.data().to_vec();