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
}