Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion parley/src/shape/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
// <https://www.unicode.org/reports/tr37/>
let is_emoji_with_non_printing_variation_selector =
is_emoji_or_pictograph && info.is_variation_selector();

Comment thread
fundon marked this conversation as resolved.
let contributes_to_shaping =
info.contributes_to_shaping() && !is_emoji_with_non_printing_variation_selector;
if contributes_to_shaping {
map_len += 1;
}
Expand Down
29 changes: 29 additions & 0 deletions parley/src/tests/test_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>) -> 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<bool>) -> 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(
Expand Down Expand Up @@ -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]);
}
Binary file not shown.
Binary file not shown.
3 changes: 2 additions & 1 deletion parley_dev/assets/fonts/noto_color_emoji/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions parley_tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions parley_tests/tests/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}
5 changes: 5 additions & 0 deletions parley_tests/tests/util/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading