use unicode_segmentation::UnicodeSegmentation;
pub(crate) fn grapheme_extends(prev_cluster: &str, c: char) -> bool {
if prev_cluster.is_empty() {
return false;
}
let before = prev_cluster.graphemes(true).count();
let mut extended = String::with_capacity(prev_cluster.len() + c.len_utf8());
extended.push_str(prev_cluster);
extended.push(c);
extended.graphemes(true).count() == before
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combining_mark_extends() {
assert!(grapheme_extends("e", '\u{0301}'));
}
#[test]
fn zwj_extends_then_joined_emoji_extends() {
assert!(grapheme_extends("\u{1F468}", '\u{200D}'), "👨 + ZWJ");
assert!(
grapheme_extends("\u{1F468}\u{200D}", '\u{1F469}'),
"👨 + 👩"
);
}
#[test]
fn skin_tone_modifier_extends() {
assert!(grapheme_extends("\u{1F44D}", '\u{1F3FB}'));
}
#[test]
fn regional_indicator_pair_extends_but_the_third_breaks() {
assert!(grapheme_extends("\u{1F1F0}", '\u{1F1F7}'), "🇰 + 🇷 = 🇰🇷");
assert!(
!grapheme_extends("\u{1F1F0}\u{1F1F7}", '\u{1F1FA}'),
"🇰🇷 + 🇺 breaks"
);
}
#[test]
fn vs16_and_vs15_selectors_extend() {
assert!(grapheme_extends("\u{25B6}", '\u{FE0F}'), "▶ + VS16");
assert!(grapheme_extends("\u{25B6}", '\u{FE0E}'), "▶ + VS15");
}
#[test]
fn a_new_base_scalar_breaks() {
assert!(!grapheme_extends("\u{1F468}", 'A'), "👨 + A breaks");
assert!(!grapheme_extends("A", 'B'), "A + B breaks");
assert!(
!grapheme_extends("\u{4E2D}", '\u{6587}'),
"中 + 文 breaks (CJK)"
);
}
#[test]
fn empty_prefix_never_extends() {
assert!(!grapheme_extends("", 'A'));
assert!(!grapheme_extends("", '\u{1F468}'));
}
}