diff --git a/crates/text/src/color_tests.rs b/crates/text/src/color_tests.rs index 929d3112..fa92298b 100644 --- a/crates/text/src/color_tests.rs +++ b/crates/text/src/color_tests.rs @@ -183,6 +183,17 @@ fn colour_emoji_carry_their_own_pixels() { } } +#[test] +fn emoji_in_plain_text_draw_from_a_colour_face() { + // A symbol font among the fallbacks (Windows has one) draws U+1F600 in one colour: the emoji + // face comes first for it. + if emoji_face().is_none() { + return; + } + let l = layout(&TextData::new("Hi \u{1F600}"), None, None); + assert!(l.glyphs.iter().any(|g| g.color(1.0).is_some()), "the emoji has colour ink"); +} + #[test] fn glyphs_place_their_ink_at_a_scale() { let Some(face) = emoji_face() else { return }; diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index 9e4df34c..dcd1eed2 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -329,11 +329,10 @@ pub fn layout(text: &TextData, box_width: Option, box_height: Option) faces.push(dbw.find(&s.font_family, &s.font_style).or_else(|| dbw.default_face())); } // Colour emoji are their own, large faces (Apple Color Emoji is ~180 MB): they are read only - // for a text whose characters nothing above covers. + // for a text with emoji, or with characters nothing above covers. let covered = |c: char| c == '\n' || faces.iter().flatten().any(|f| f.has_char(c)) || fallbacks.iter().any(|f| f.has_char(c)); - if chars.iter().any(|c| !covered(*c)) { - fallbacks.extend(dbw.emoji_fallbacks()); - } + let emoji = if chars.iter().any(|c| is_emoji(*c) || !covered(*c)) { dbw.emoji_fallbacks() } else { Vec::new() }; + fallbacks.extend(emoji.iter().cloned()); drop(dbw); // OpenType features the user toggled (global for the node). @@ -364,7 +363,9 @@ pub fn layout(text: &TextData, box_width: Option, box_height: Option) if c == '\n' || primary.has_char(c) { return primary.clone(); } - fallbacks.iter().find(|f| f.has_char(c)).cloned().unwrap_or_else(|| primary.clone()) + // Emoji take a colour face before the symbol fonts, which draw many of them in one colour. + let colour = if is_emoji(c) { emoji.iter().find(|f| f.has_char(c)) } else { None }; + colour.or_else(|| fallbacks.iter().find(|f| f.has_char(c))).cloned().unwrap_or_else(|| primary.clone()) }; let first = chars.get(seg_start).copied().unwrap_or(' '); let face = face_for(first); @@ -988,6 +989,13 @@ fn sanitize_size(s: f64) -> f64 { if s.is_finite() { s.clamp(0.5, 5000.0) } else { 12.0 } } +/// A character drawn as a colour emoji by default: flags and the pictograph blocks. +/// shortcut: the older symbols (U+2600–U+27BF) keep their text look even before U+FE0F; read the +/// emoji presentation data if they should turn colour too. +fn is_emoji(c: char) -> bool { + matches!(c, '\u{1F1E6}'..='\u{1F1FF}' | '\u{1F300}'..='\u{1FAFF}') +} + /// One run of chars shaped with a single face and style. struct Segment<'a> { start: usize,