use crate::rasterizer::{GlyphFormat, GlyphRasterizer, RasterizedGlyph};
use crate::registry::{FontRegistry, GenericFont};
use crate::shaper::TextShaper;
use crate::{Result, TextError};
use std::sync::{Arc, Mutex};
pub fn is_emoji(c: char) -> bool {
let cp = c as u32;
matches!(
cp,
0x1F600..=0x1F64F |
0x1F300..=0x1F5FF |
0x1F680..=0x1F6FF |
0x1F900..=0x1F9FF |
0x1FA00..=0x1FA6F |
0x1FA70..=0x1FAFF |
0x1F1E0..=0x1F1FF |
0x1F000..=0x1F0FF |
0x1F100..=0x1F1FF |
0x1F780..=0x1F7FF |
0x2600..=0x26FF |
0x2702 | 0x2705 | 0x2708..=0x270D | 0x270F | 0x2712 | 0x2714 | 0x2716 | 0x271D | 0x2721 | 0x2728 | 0x2733..=0x2734 | 0x2744 | 0x2747 | 0x274C | 0x274E | 0x2753..=0x2755 | 0x2757 | 0x2763..=0x2764 | 0x2795..=0x2797 | 0x27A1 | 0x27B0 | 0x27BF |
0x231A..=0x231B | 0x2328 | 0x23CF | 0x23E9..=0x23F3 | 0x23F8..=0x23FA |
0x24C2 |
0x25AA..=0x25AB | 0x25B6 | 0x25C0 | 0x25FB..=0x25FE |
0x2B05..=0x2B07 | 0x2B1B..=0x2B1C | 0x2B50 | 0x2B55 |
0x203C | 0x2049 | 0x2139 |
0x3030 | 0x303D | 0x3297 | 0x3299
)
}
pub fn contains_emoji(s: &str) -> bool {
s.chars().any(is_emoji)
}
pub fn count_emoji(s: &str) -> usize {
s.chars().filter(|&c| is_emoji(c)).count()
}
pub fn is_skin_tone_modifier(c: char) -> bool {
let cp = c as u32;
matches!(cp, 0x1F3FB..=0x1F3FF)
}
pub fn is_zwj(c: char) -> bool {
c == '\u{200D}'
}
pub fn is_variation_selector(c: char) -> bool {
let cp = c as u32;
matches!(cp, 0xFE00..=0xFE0F)
}
#[derive(Debug, Clone)]
pub struct EmojiSprite {
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
}
pub struct EmojiRenderer {
font_registry: Arc<Mutex<FontRegistry>>,
rasterizer: GlyphRasterizer,
shaper: TextShaper,
}
impl EmojiRenderer {
pub fn with_registry(font_registry: Arc<Mutex<FontRegistry>>) -> Self {
Self {
font_registry,
rasterizer: GlyphRasterizer::new(),
shaper: TextShaper::new(),
}
}
pub fn new() -> Self {
Self {
font_registry: crate::global_font_registry(),
rasterizer: GlyphRasterizer::new(),
shaper: TextShaper::new(),
}
}
pub fn render(&mut self, emoji: char, size: f32) -> Result<EmojiSprite> {
let emoji_font = {
let mut registry = self.font_registry.lock().unwrap();
registry.load_generic(GenericFont::Emoji)?
};
let glyph_id = emoji_font
.glyph_id(emoji)
.ok_or(TextError::GlyphNotFound(emoji))?;
if glyph_id == 0 {
return Err(TextError::GlyphNotFound(emoji));
}
let rasterized = self
.rasterizer
.rasterize_color(&emoji_font, glyph_id, size)?;
if rasterized.width == 0 || rasterized.height == 0 {
return Err(TextError::GlyphNotFound(emoji));
}
Ok(EmojiSprite {
data: rasterized.bitmap,
width: rasterized.width,
height: rasterized.height,
})
}
pub fn render_string(&mut self, emoji_str: &str, size: f32) -> Result<EmojiSprite> {
let emoji_font = {
let mut registry = self.font_registry.lock().unwrap();
registry.load_generic(GenericFont::Emoji)?
};
let shaped = self.shaper.shape(emoji_str, &emoji_font, size);
if shaped.glyphs.is_empty() {
return Err(TextError::GlyphNotFound(
emoji_str.chars().next().unwrap_or(' '),
));
}
let glyph = &shaped.glyphs[0];
let rasterized = self
.rasterizer
.rasterize_color(&emoji_font, glyph.glyph_id, size)?;
if rasterized.width == 0 || rasterized.height == 0 {
return Err(TextError::GlyphNotFound(
emoji_str.chars().next().unwrap_or(' '),
));
}
Ok(EmojiSprite {
data: rasterized.bitmap,
width: rasterized.width,
height: rasterized.height,
})
}
}
impl Default for EmojiRenderer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_emoji_detection() {
assert!(is_emoji('😀'));
assert!(is_emoji('🎉'));
assert!(is_emoji('❤'));
assert!(is_emoji('✅')); assert!(is_emoji('🚀'));
assert!(is_emoji('🌍'));
assert!(!is_emoji('A'));
assert!(!is_emoji('中'));
assert!(!is_emoji(' '));
assert!(!is_emoji('1'));
assert!(!is_emoji('@'));
assert!(!is_emoji('✓')); assert!(!is_emoji('✗')); }
#[test]
fn test_contains_emoji() {
assert!(contains_emoji("Hello 😀 World"));
assert!(contains_emoji("🎉"));
assert!(contains_emoji("Start 🚀 End"));
assert!(!contains_emoji("Hello World"));
assert!(!contains_emoji(""));
assert!(!contains_emoji("Plain text only"));
}
#[test]
fn test_count_emoji() {
assert_eq!(count_emoji("😀😀😀"), 3);
assert_eq!(count_emoji("Hello 😀 World 🎉"), 2);
assert_eq!(count_emoji("No emoji here"), 0);
assert_eq!(count_emoji(""), 0);
}
#[test]
fn test_skin_tone_modifier() {
assert!(is_skin_tone_modifier('\u{1F3FB}')); assert!(is_skin_tone_modifier('\u{1F3FF}')); assert!(!is_skin_tone_modifier('😀'));
assert!(!is_skin_tone_modifier('A'));
}
#[test]
fn test_zwj() {
assert!(is_zwj('\u{200D}'));
assert!(!is_zwj('😀'));
assert!(!is_zwj(' '));
}
#[test]
fn test_variation_selector() {
assert!(is_variation_selector('\u{FE0F}')); assert!(is_variation_selector('\u{FE0E}')); assert!(!is_variation_selector('😀'));
}
#[test]
fn test_flag_emoji() {
assert!(is_emoji('\u{1F1FA}')); assert!(is_emoji('\u{1F1F8}')); }
#[test]
fn test_dingbats() {
assert!(is_emoji('✂')); assert!(is_emoji('✈')); assert!(is_emoji('✉')); }
#[test]
fn test_miscellaneous_symbols() {
assert!(is_emoji('☀')); assert!(is_emoji('☁')); assert!(is_emoji('☂')); }
}