emoji_sanitize/lib.rs
1//! # emoji-sanitize
2//!
3//! Strip Unicode characters that emoji rendering uses but plain-text
4//! analysis doesn't need: variation selectors (U+FE00–U+FE0F),
5//! the zero-width joiner (U+200D), and skin-tone modifiers
6//! (U+1F3FB–U+1F3FF).
7//!
8//! Two modes:
9//! - [`strip_modifiers`] — keep the base emoji codepoint, drop
10//! modifiers. `👨👩👧` becomes `👨👩👧`.
11//! - [`strip_all_emoji`] — drop everything in the emoji blocks too.
12//!
13//! ## Example
14//!
15//! ```
16//! use emoji_sanitize::{strip_modifiers, strip_all_emoji};
17//! let s = "Hi \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}!";
18//! assert_eq!(strip_modifiers(s), "Hi \u{1F468}\u{1F469}\u{1F467}!");
19//! ```
20
21#![deny(missing_docs)]
22
23/// Strip variation selectors, ZWJ, and skin-tone modifiers — keeping
24/// the base emoji codepoints.
25pub fn strip_modifiers(s: &str) -> String {
26 s.chars().filter(|c| !is_modifier(*c)).collect()
27}
28
29/// Strip everything we recognize as emoji or emoji-modifier.
30pub fn strip_all_emoji(s: &str) -> String {
31 s.chars()
32 .filter(|c| !is_modifier(*c) && !is_emoji(*c))
33 .collect()
34}
35
36/// True for emoji codepoints in the common ranges (BMP misc symbols,
37/// SMP misc, SMP supplemental, emoticons, transport/map symbols).
38pub fn is_emoji(c: char) -> bool {
39 matches!(c as u32,
40 0x1F300..=0x1F5FF // misc symbols / pictographs
41 | 0x1F600..=0x1F64F // emoticons
42 | 0x1F680..=0x1F6FF // transport / map
43 | 0x1F700..=0x1F77F // alchemy
44 | 0x1F780..=0x1F7FF // geometric
45 | 0x1F800..=0x1F8FF // supplemental arrows-c
46 | 0x1F900..=0x1F9FF // supplemental symbols/pictographs
47 | 0x1FA00..=0x1FA6F // chess + extended
48 | 0x1FA70..=0x1FAFF // symbols and pictographs extended-A
49 | 0x2600..=0x26FF // misc symbols
50 | 0x2700..=0x27BF // dingbats
51 )
52}
53
54fn is_modifier(c: char) -> bool {
55 matches!(c as u32,
56 0x200D // ZWJ
57 | 0xFE00..=0xFE0F // variation selectors 1-16 (incl. VS16)
58 | 0x1F3FB..=0x1F3FF // skin tones
59 )
60}