use crate::unicode_constants::{
emoji_ranges, emoji_with_vs16_ranges, keycap, regional_indicators, variation_selectors,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Codepoint(u32);
impl From<char> for Codepoint {
fn from(ch: char) -> Self {
Self(ch as u32)
}
}
impl Codepoint {
fn value(self) -> u32 {
self.0
}
fn is_in_range(self, range: &std::ops::RangeInclusive<u32>) -> bool {
range.contains(&self.0)
}
}
pub fn is_rgi_emoji(segment: &str) -> bool {
if segment.is_empty() {
return false;
}
let chars: Vec<char> = segment.chars().collect();
if let Some(result) = handle_variation_selector_sequences(&chars) {
return result;
}
is_keycap_emoji_sequence(&chars)
|| is_regional_indicator_pair(&chars)
|| has_default_emoji_presentation_any(&chars)
}
fn handle_variation_selector_sequences(chars: &[char]) -> Option<bool> {
match chars {
[_base, vs, ..] => {
let vs_code = Codepoint::from(*vs);
match vs_code.value() {
variation_selectors::TEXT_PRESENTATION => Some(false),
variation_selectors::EMOJI_PRESENTATION => {
Some(handle_emoji_presentation_sequence(chars))
}
_ => None,
}
}
_ => None,
}
}
fn handle_emoji_presentation_sequence(chars: &[char]) -> bool {
if is_keycap_sequence_with_vs16(chars) {
return true;
}
if is_keycap_base_character(chars[0]) {
return false;
}
if chars.len() >= 3 && Codepoint::from(chars[2]).value() == keycap::COMBINING_ENCLOSING {
return false;
}
is_emoji_with_vs16(chars[0])
}
fn is_keycap_emoji_sequence(chars: &[char]) -> bool {
is_keycap_sequence_with_vs16(chars) || is_keycap_sequence_without_vs16(chars)
}
fn is_keycap_sequence_without_vs16(chars: &[char]) -> bool {
matches!(chars, [base, keycap, ..]
if Codepoint::from(*keycap).value() == keycap::COMBINING_ENCLOSING
&& is_keycap_base_character(*base))
}
fn is_keycap_sequence_with_vs16(chars: &[char]) -> bool {
matches!(chars, [base, vs, keycap, ..]
if Codepoint::from(*vs).value() == variation_selectors::EMOJI_PRESENTATION
&& Codepoint::from(*keycap).value() == keycap::COMBINING_ENCLOSING
&& is_keycap_base_character(*base))
}
fn is_keycap_base_character(ch: char) -> bool {
let code = Codepoint::from(ch);
matches!(
code.value(),
keycap::DIGIT_ZERO..=keycap::DIGIT_NINE | keycap::ASTERISK | keycap::NUMBER_SIGN
)
}
fn is_regional_indicator_pair(chars: &[char]) -> bool {
match chars {
[first, second, ..] => {
let first_code = Codepoint::from(*first);
let second_code = Codepoint::from(*second);
first_code.is_in_range(&(regional_indicators::START..=regional_indicators::END))
&& second_code.is_in_range(&(regional_indicators::START..=regional_indicators::END))
}
_ => false,
}
}
fn has_default_emoji_presentation_any(chars: &[char]) -> bool {
chars.iter().any(|&ch| has_default_emoji_presentation(ch))
}
pub fn has_default_emoji_presentation(ch: char) -> bool {
let code = ch as u32;
emoji_ranges::EMOTICONS.contains(&code)
|| emoji_ranges::MISC_SYMBOLS_PICTOGRAPHS.contains(&code)
|| emoji_ranges::TRANSPORT_MAP.contains(&code)
|| emoji_ranges::ALCHEMICAL.contains(&code)
|| emoji_ranges::GEOMETRIC_EXTENDED.contains(&code)
|| emoji_ranges::SUPPLEMENTAL_ARROWS_C.contains(&code)
|| emoji_ranges::SUPPLEMENTAL_SYMBOLS.contains(&code)
|| emoji_ranges::CHESS.contains(&code)
|| emoji_ranges::SYMBOLS_EXTENDED_A.contains(&code)
}
pub fn is_emoji_with_vs16(ch: char) -> bool {
let code = Codepoint::from(ch);
code.is_in_range(&emoji_with_vs16_ranges::COPYRIGHT_REGISTERED)
|| code.is_in_range(&emoji_with_vs16_ranges::EXCLAMATION_MARKS)
|| code.is_in_range(&emoji_with_vs16_ranges::ARROWS)
|| code.is_in_range(&emoji_with_vs16_ranges::RETURN_ARROWS)
|| code.is_in_range(&emoji_with_vs16_ranges::WATCH_HOURGLASS)
|| code.is_in_range(&emoji_with_vs16_ranges::MEDIA_CONTROLS)
|| code.is_in_range(&emoji_with_vs16_ranges::SMALL_SQUARES)
|| code.is_in_range(&emoji_with_vs16_ranges::WEATHER_BASIC)
|| code.is_in_range(&emoji_with_vs16_ranges::UMBRELLA_COFFEE)
|| code.is_in_range(&emoji_with_vs16_ranges::HAZARD_SYMBOLS)
|| code.is_in_range(&emoji_with_vs16_ranges::PEACE_YIN_YANG)
|| code.is_in_range(&emoji_with_vs16_ranges::DHARMA_SMILE)
|| code.is_in_range(&emoji_with_vs16_ranges::ZODIAC)
|| code.is_in_range(&emoji_with_vs16_ranges::CARD_SUITS_1)
|| code.is_in_range(&emoji_with_vs16_ranges::TOOLS_SCIENCE)
|| code.is_in_range(&emoji_with_vs16_ranges::ATOM_FLEUR)
|| code.is_in_range(&emoji_with_vs16_ranges::WARNING_ZAP)
|| code.is_in_range(&emoji_with_vs16_ranges::CIRCLES)
|| code.is_in_range(&emoji_with_vs16_ranges::FUNERAL)
|| code.is_in_range(&emoji_with_vs16_ranges::SPORTS_BALLS)
|| code.is_in_range(&emoji_with_vs16_ranges::WEATHER_EXTENDED)
|| code.is_in_range(&emoji_with_vs16_ranges::OPHIUCHUS_PICK)
|| code.is_in_range(&emoji_with_vs16_ranges::CHAINS_NO_ENTRY)
|| code.is_in_range(&emoji_with_vs16_ranges::RELIGIOUS_BUILDINGS)
|| code.is_in_range(&emoji_with_vs16_ranges::MOUNTAIN_SAILBOAT)
|| code.is_in_range(&emoji_with_vs16_ranges::SKIER_TENT)
|| code.is_in_range(&emoji_with_vs16_ranges::AIRPLANE_PENCIL)
|| code.is_in_range(&emoji_with_vs16_ranges::ASTERISK_VARIANTS)
|| code.is_in_range(&emoji_with_vs16_ranges::QUESTION_MARKS)
|| code.is_in_range(&emoji_with_vs16_ranges::HEARTS)
|| code.is_in_range(&emoji_with_vs16_ranges::PLUS_MINUS)
|| code.is_in_range(&emoji_with_vs16_ranges::CURVED_ARROWS)
|| code.is_in_range(&emoji_with_vs16_ranges::BASIC_ARROWS)
|| code.is_in_range(&emoji_with_vs16_ranges::LARGE_SQUARES)
|| emoji_with_vs16_ranges::INDIVIDUAL_CHARS.contains(&code.value())
}