diff --git a/crates/layout/src/display/lines.rs b/crates/layout/src/display/lines.rs index 0d253f9205..6b1fbd6098 100644 --- a/crates/layout/src/display/lines.rs +++ b/crates/layout/src/display/lines.rs @@ -638,6 +638,8 @@ pub fn section_break_label(start: SectionStart) -> &'static str { /// Width of a [`Draw::MarkText`] label. pub fn mark_text_width(text: &str, size: f32) -> f32 { + #[cfg(test)] + crate::test_fonts(); let face = wordcraft_fonts::word::resolve("Source Sans 3", false, false).face; let k = size / face.upem.max(1.0) as f32; text.chars().map(|c| face.advance(face.glyph_for(c)) as f32 * k).sum() diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index 6acab3d28c..504c178fa9 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -8,8 +8,13 @@ //! - [`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`, `display::mark_text_width`) call it first in test +/// builds, so the shared `lay` helper and every test that goes through them are covered; a test +/// that looks up fonts itself before any such 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 +319,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/para/paragraph.rs b/crates/layout/src/para/paragraph.rs index 2c0c4e8c1e..1cd053e134 100644 --- a/crates/layout/src/para/paragraph.rs +++ b/crates/layout/src/para/paragraph.rs @@ -10,6 +10,8 @@ use super::*; /// Lay out one paragraph. pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout { + #[cfg(test)] + crate::test_fonts(); let doc = env.doc; let mut rp = doc.styles.resolve_para_in(&p.props, env.table.map(|t| &t.para)); if let Some(spacing) = env.read_line_spacing.filter(|s| s.is_finite() && (1.0..=2.0).contains(s)) { diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 7275046772..2e22d43630 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. 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..64144585bd 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(); @@ -54,6 +55,7 @@ fn line_width(text: &str) -> f32 { #[test] fn a_no_break_space_is_as_wide_as_a_space() { + crate::test_fonts(); let face = wordcraft_fonts::word::resolve(wide_nbsp_family(), false, false).face; assert_eq!(face.family, wide_nbsp_family(), "the test font is installed under its own name"); assert!(