From 8ec4f29059de67706a72e1066b06cc592828aa80 Mon Sep 17 00:00:00 2001 From: Madhasoeki Date: Sun, 1 Nov 2026 07:19:41 +0700 Subject: [PATCH] Fonts: refine monospace heuristic and preserve office_twin order (#1598) - Avoid false-positive monospace match on 'code' in split_whitespace for proportional families like Code Pro. - Check office_twin before is_monospace_family in document_face so alias families like Courier New CE resolve to Courier New when installed. - Add negative tests for Code Pro and test Courier New CE mapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/fonts/src/fontdb.rs | 12 +++++------- crates/fonts/src/tests.rs | 12 ++++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index cc2bd417..1cffbe00 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -105,7 +105,7 @@ pub fn is_monospace_family(family: &str) -> bool { || s.ends_with(" console") || s.ends_with(" typewriter") || s.ends_with(" monospace") - || s.split_whitespace().any(|w| w == "mono" || w == "code" || w == "terminal" || w == "monospace") + || s.split_whitespace().any(|w| w == "mono" || w == "terminal" || w == "monospace") } /// Families whose open substitutes ([`substitutes`]) keep their metrics, so text drawn with @@ -1149,17 +1149,15 @@ impl FontDb { /// Monospaced families keep a monospaced substitute instead of falling back to Calibri. /// DeckCraft's own default family keeps [`FontDb::face`]'s fallbacks. pub fn document_face(&self, family: &str, style: &str) -> Arc { - if family.eq_ignore_ascii_case(FALLBACK_FAMILY) - || has_metric_twin(family) - || is_monospace_family(family) - || is_office_cloud_font(family) - || self.has_family(family) - { + if family.eq_ignore_ascii_case(FALLBACK_FAMILY) || has_metric_twin(family) || is_office_cloud_font(family) || self.has_family(family) { return self.face(family, style); } if let Some(twin) = office_twin(family) { return self.face(twin, style); } + if is_monospace_family(family) { + return self.face(family, style); + } self.face(MISSING_FONT_FAMILY, style) } diff --git a/crates/fonts/src/tests.rs b/crates/fonts/src/tests.rs index 69f54227..f912f998 100644 --- a/crates/fonts/src/tests.rs +++ b/crates/fonts/src/tests.rs @@ -216,9 +216,13 @@ fn missing_document_fonts_are_drawn_in_calibri() { assert_eq!(f.family, db.face(MISSING_FONT_FAMILY, "Bold").family, "{missing:?}"); } // PowerPoint's own twins and Windows' substitutes, when the font itself is missing. - for (missing, twin) in - [("Liberation Sans", "Arial"), ("Liberation Serif", "Times New Roman"), ("Arial CE", "Arial"), ("Times New Roman CYR", "Times New Roman")] - { + for (missing, twin) in [ + ("Liberation Sans", "Arial"), + ("Liberation Serif", "Times New Roman"), + ("Arial CE", "Arial"), + ("Times New Roman CYR", "Times New Roman"), + ("Courier New CE", "Courier New"), + ] { if !db.has_family(missing) { assert_eq!(db.document_face(missing, "Regular").family, db.face(twin, "Regular").family, "{missing}"); } @@ -261,7 +265,7 @@ fn office_fonts_have_substitutes() { assert!(is_monospace_family(mono), "{mono} should be recognized as monospace"); } - for prop in ["Calibri", "Arial", "Times New Roman", "Inter", "Monotype Corsiva", "Monotype Baskerville", "zzz"] { + for prop in ["Calibri", "Arial", "Times New Roman", "Inter", "Monotype Corsiva", "Monotype Baskerville", "Code Pro", "zzz"] { assert!(!is_monospace_family(prop), "{prop} should not be recognized as monospace"); } }