diff --git a/parley/src/shape/mod.rs b/parley/src/shape/mod.rs index 3567d15fe..cd451001e 100644 --- a/parley/src/shape/mod.rs +++ b/parley/src/shape/mod.rs @@ -247,7 +247,23 @@ fn fill_cluster_in_place( is_emoji_or_pictograph |= info.is_emoji_or_pictograph(); *code_unit_offset_in_string += ch.len_utf8(); - let contributes_to_shaping = info.contributes_to_shaping(); + // TODO: Explore ignoring other modifiers in determining `contributes_to_shaping`: + // regional indicators, subdivision flag tag sequences, skin tone modifiers + // See also: https://github.com/google/emoji-segmenter + + // If the color emoji has a non-printing variation selector, ignore the variation selector. + // Its presentation depends on the platform and font. + // + // e.g. + // - `U+270C + U+FE0F`: `✌`, force basic presentation + // - `U+270C + U+FE0F`: `✌️`, force emoji presentation + // + // + let is_emoji_with_non_printing_variation_selector = + is_emoji_or_pictograph && info.is_variation_selector(); + + let contributes_to_shaping = + info.contributes_to_shaping() && !is_emoji_with_non_printing_variation_selector; if contributes_to_shaping { map_len += 1; } diff --git a/parley/src/tests/test_analysis.rs b/parley/src/tests/test_analysis.rs index 14146aecf..202e4f11a 100644 --- a/parley/src/tests/test_analysis.rs +++ b/parley/src/tests/test_analysis.rs @@ -85,6 +85,28 @@ impl TestContext { assert_eq!(actual, expected, "Force normalize list mismatch"); self } + + fn expect_is_emoji_or_pictograph_list(self, expected: Vec) -> Self { + let actual: Vec<_> = self + .layout_context + .info + .iter() + .map(|(info, _)| info.is_emoji_or_pictograph()) + .collect(); + assert_eq!(actual, expected, "Is emoji or pictograph list mismatch"); + self + } + + fn expect_is_variation_selector_list(self, expected: Vec) -> Self { + let actual: Vec<_> = self + .layout_context + .info + .iter() + .map(|(info, _)| info.is_variation_selector()) + .collect(); + assert_eq!(actual, expected, "Is variation selector list mismatch"); + self + } } fn verify_analysis( @@ -1156,3 +1178,10 @@ fn test_whitespace_contiguous_interspersed_in_latin_mixed() { Script::Latin, ]); } + +#[test] +fn test_color_emoji_with_non_printing_variation_selector() { + verify_analysis("\u{270c}\u{fe0f}", |_| {}) + .expect_is_emoji_or_pictograph_list(vec![true, false]) + .expect_is_variation_selector_list(vec![false, true]); +} diff --git a/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-CBTF-Subset.ttf b/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-CBTF-Subset.ttf index d0138dd26..b95563cd4 100644 Binary files a/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-CBTF-Subset.ttf and b/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-CBTF-Subset.ttf differ diff --git a/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-Subset.ttf b/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-Subset.ttf index 01733e080..f84e750bb 100644 Binary files a/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-Subset.ttf and b/parley_dev/assets/fonts/noto_color_emoji/NotoColorEmoji-Subset.ttf differ diff --git a/parley_dev/assets/fonts/noto_color_emoji/README.md b/parley_dev/assets/fonts/noto_color_emoji/README.md index 66e6a3968..f61acf8c7 100644 --- a/parley_dev/assets/fonts/noto_color_emoji/README.md +++ b/parley_dev/assets/fonts/noto_color_emoji/README.md @@ -7,7 +7,8 @@ Included emoji are: - ✅ Check Mark - \u{2705}/`:white_check_mark:` - 👀 Eyes - \u{1f440}/`:eyes:` - 🎉 Party Popper - \u{1f389}/`:party_popper:` -- 🤠 Face with Cowboy Hat - \u{1f920}/`cowboy_hat_face` +- 🤠 Face with Cowboy Hat - \u{1f920}/`:cowboy_hat_face:` +- ✌️ Victory hand - \u{270c}\u{fe0f}/`:victory_hand:` These are in the COLR format in `NotoColorEmoji-Subset` and in the CBTF format in `NotoColorEmoji-CBTF-Subset`. This covers all ways that Emoji are commonly packaged, and both are supported by Vello. diff --git a/parley_tests/Cargo.toml b/parley_tests/Cargo.toml index f621f7c50..a757c6192 100644 --- a/parley_tests/Cargo.toml +++ b/parley_tests/Cargo.toml @@ -14,6 +14,9 @@ autotests = false name = "tests" path = "tests/mod.rs" +[features] +system = ["parley/system"] + [dependencies] parley = { workspace = true, features = ["std"] } parley_dev = { workspace = true } diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint.png new file mode 100644 index 000000000..89307d853 Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint_skew.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint_skew.png new file mode 100644 index 000000000..f9f2da85b Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_hint_skew.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint.png new file mode 100644 index 000000000..89307d853 Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint_skew.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint_skew.png new file mode 100644 index 000000000..f9f2da85b Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-2x_nohint_skew.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint.png new file mode 100644 index 000000000..bdcdbb76f Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint_skew.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint_skew.png new file mode 100644 index 000000000..9ba1318ef Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-hint_skew.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint.png new file mode 100644 index 000000000..bdcdbb76f Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint.png differ diff --git a/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint_skew.png b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint_skew.png new file mode 100644 index 000000000..9ba1318ef Binary files /dev/null and b/parley_tests/snapshots/draw_colr_emoji_with_non_printing_variation_selector_16-nohint_skew.png differ diff --git a/parley_tests/tests/draw.rs b/parley_tests/tests/draw.rs index 5ed2661b6..b32e9891c 100644 --- a/parley_tests/tests/draw.rs +++ b/parley_tests/tests/draw.rs @@ -172,3 +172,34 @@ fn draw_colr_emoji() { layout }); } + +/// Test COLR emoji with non printing variation selector 16 rendering across different hinting, +/// per-glyph transform, and scale configurations. +#[cfg(all(target_os = "macos", feature = "system"))] +#[test] +fn draw_colr_emoji_with_non_printing_variation_selector_16() { + let mut env = TestEnv::new(test_name!(), None); + env.set_tolerance(5.0); + + let collection = &mut env.font_context().collection; + collection.load_system_fonts(); + + let text = "\u{270c}\u{fe0f}\u{2705}\u{270c}\u{fe0f}"; + + test_with_configs(&mut env, |env| { + let mut builder = env.ranged_builder(text); + builder.push_default(StyleProperty::FontSize(24.0)); + builder.push_default(StyleProperty::FontFamily(FontFamily::named( + "Apple Color Emoji", + ))); + builder.push( + StyleProperty::FontFamily(FontFamily::named("Noto Color Emoji")), + 0..9, + ); + + let mut layout = builder.build(text); + layout.break_all_lines(None); + layout.align(Alignment::Start, AlignmentOptions::default()); + layout + }); +} diff --git a/parley_tests/tests/util/env.rs b/parley_tests/tests/util/env.rs index cbeb8fb67..910e5f0a2 100644 --- a/parley_tests/tests/util/env.rs +++ b/parley_tests/tests/util/env.rs @@ -155,6 +155,11 @@ impl TestEnv { } } + #[cfg(all(target_os = "macos", feature = "system"))] + pub(crate) fn font_context(&mut self) -> &mut FontContext { + &mut self.font_cx + } + pub(crate) fn rendering_config(&mut self) -> &mut RenderingConfig { &mut self.rendering_config }