From 1a88f66f3e89db9e9b9c016c61f9fdd549b74228 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Thu, 24 Sep 2026 01:05:53 -0700 Subject: [PATCH] fix: keep digits in the paragraph font when its family is missing A paragraph whose family is not installed (Calibri on a Mac) fell back per script, and fontique resolved digits to Apple Color Emoji because digits carry emoji keycap presentations. The page date rendered as "Monday, August 1 0 , 2 0 2 6" with 10 pt emoji-width advances. OneNote draws the date in one face. The family list now ends in sans-serif, so a missing family resolves every character the generic face covers to one face; emoji and other scripts still fall back past it. Assisted-by: claude-opus-5 --- crates/canvas/src/layout.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/crates/canvas/src/layout.rs b/crates/canvas/src/layout.rs index 0eff9676a98272be2649d1705ef0ea5b6bddc46a..7c6c3387294827fe25d4f9fe8bff995c3952fc80 100644 --- a/crates/canvas/src/layout.rs +++ b/crates/canvas/src/layout.rs @@ -1,7 +1,8 @@ use onestore::page::text::Paragraph; use parley::{ - Affinity, Alignment, AlignmentOptions, BoundingBox, FontContext, FontFamily, FontStyle, - FontWeight, Layout, LayoutContext, OverflowWrap, PositionedLayoutItem, StyleProperty, + Affinity, Alignment, AlignmentOptions, BoundingBox, FontContext, FontFamily, FontFamilyName, + FontStyle, FontWeight, GenericFamily, Layout, LayoutContext, OverflowWrap, + PositionedLayoutItem, StyleProperty, editing::{Cursor, Selection}, }; use skrifa::{FontRef, MetadataProvider, raw::TableProvider, string::StringId}; @@ -139,8 +140,13 @@ impl TextEngine { return Err(LayoutError::InvalidFontSize); } let properties = [ - StyleProperty::FontFamily(FontFamily::named( - format.font.as_deref().unwrap_or("Arial"), + StyleProperty::FontFamily(FontFamily::List( + vec![ + FontFamilyName::named(format.font.as_deref().unwrap_or("Arial")), + // A missing family otherwise falls back per script and can put digits in an emoji font. + GenericFamily::SansSerif.into(), + ] + .into(), )), StyleProperty::FontSize(size), StyleProperty::FontWeight(if format.bold == Some(true) { @@ -522,6 +528,27 @@ mod tests { ); } + #[test] + fn missing_family_keeps_digits_in_the_letters_face() { + let paragraph = Paragraph::new( + "Monday, August 10, 2026".into(), + Format { + font: Some("Snowbound Missing Family".into()), + ..Format::default() + }, + ); + let layout = TextEngine::default().layout(¶graph, 468.0).unwrap(); + let fonts = layout + .lines() + .flat_map(|(line, _)| { + line.runs() + .map(|run| run.font().font.data.id()) + .collect::>() + }) + .collect::>(); + assert_eq!(fonts.len(), 1); + } + #[test] fn highlights_share_line_geometry_without_changing_wraps() { let paragraph = Paragraph::from_runs([ -- 2.54.0