static BLOB: &[u8] = include_bytes!("../tables/unicode.bin");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BidiClass {
On,
L,
R,
An,
En,
Al,
Nsm,
Cs,
Es,
Et,
Bn,
S,
Ws,
B,
Rlo,
Rle,
Lro,
Lre,
Pdf,
}
impl BidiClass {
fn from_packed(value: u16) -> Self {
match value {
1 => Self::L,
2 => Self::R,
3 => Self::An,
4 => Self::En,
5 => Self::Al,
6 => Self::Nsm,
7 => Self::Cs,
8 => Self::Es,
9 => Self::Et,
10 => Self::Bn,
11 => Self::S,
12 => Self::Ws,
13 => Self::B,
14 => Self::Rlo,
15 => Self::Rle,
16 => Self::Lro,
17 => Self::Lre,
18 => Self::Pdf,
_ => Self::On,
}
}
}
fn section(tag: [u8; 4]) -> &'static [u8] {
let Some(count) = BLOB.get(0..8).filter(|head| head.starts_with(b"PDRT")) else {
return &[];
};
let Some(count) = count.get(4..8).and_then(|b| b.try_into().ok()) else {
return &[];
};
let count = u32::from_le_bytes(count) as usize;
let mut at = 8usize;
for _ in 0..count {
let Some(header) = BLOB.get(at..at + 8) else {
return &[];
};
let Some(len) = header.get(4..8).and_then(|b| b.try_into().ok()) else {
return &[];
};
let len = u32::from_le_bytes(len) as usize;
let body = at + 8;
if header.starts_with(&tag) {
return BLOB.get(body..body + len).unwrap_or(&[]);
}
at = body + len;
}
&[]
}
struct RleTable {
payload: &'static [u8],
expanded: std::sync::OnceLock<Box<[u16]>>,
}
fn word_at(bytes: &[u8], index: usize) -> Option<u16> {
let at = index.checked_mul(2)?;
bytes
.get(at..at + 2)
.and_then(|pair| pair.try_into().ok())
.map(u16::from_le_bytes)
}
impl RleTable {
const fn new(payload: &'static [u8]) -> Self {
Self {
payload,
expanded: std::sync::OnceLock::new(),
}
}
fn get(&self, index: usize) -> u16 {
let table = self.expanded.get_or_init(|| {
let mut out = Vec::with_capacity(0x1_0000);
for run in self.payload.as_chunks::<4>().0 {
let (Some(value), Some(len)) = (word_at(run, 0), word_at(run, 1)) else {
continue;
};
out.extend(std::iter::repeat_n(value, usize::from(len)));
}
out.into_boxed_slice()
});
table.get(index).copied().unwrap_or(0)
}
}
fn properties(code: u32) -> u16 {
if code > 0xFFFF {
return 0;
}
ucd_table().get(code as usize)
}
fn ucd_table() -> &'static RleTable {
static TABLE: std::sync::OnceLock<RleTable> = std::sync::OnceLock::new();
TABLE.get_or_init(|| RleTable::new(section(*b"UCDR")))
}
fn normalization_table() -> &'static RleTable {
static TABLE: std::sync::OnceLock<RleTable> = std::sync::OnceLock::new();
TABLE.get_or_init(|| RleTable::new(section(*b"NRMR")))
}
#[must_use]
pub fn bidi_class(code: u32) -> BidiClass {
BidiClass::from_packed(properties(code) & 0x1F)
}
#[must_use]
pub fn mirror_char(code: u32) -> u32 {
if code > 0xFFFF {
return code;
}
let index = usize::from(properties(code) >> 5);
if index == 0x1FF {
return code;
}
let pairs = section(*b"MIRR");
let at = index * 2;
pairs
.get(at..at + 2)
.and_then(|b| b.try_into().ok())
.map_or(code, |b| u32::from(u16::from_le_bytes(b)))
}
#[must_use]
pub const fn normalize_space(code: u32) -> u32 {
match code {
0x00A0 | 0x2000..=0x200A | 0x202F => 0x0020,
other => other,
}
}
#[must_use]
pub fn normalize(code: u32) -> Vec<u32> {
let code = code & 0xFFFF;
let found = normalization_table().get(code as usize);
if found == 0 {
return vec![code];
}
if found >= 0x8000 {
let index = usize::from(found - 0x8000);
return match word_at(section(*b"NRM1"), index) {
Some(value) => vec![u32::from(value)],
None => vec![code],
};
}
let index = usize::from(found & 0x0FFF);
let table = found >> 12;
let payload = match table {
2 => section(*b"NRM2"),
3 => section(*b"NRM3"),
4 => section(*b"NRM4"),
_ => return vec![code],
};
let (start, len) = if table == 4 {
let Some(len) = word_at(payload, index) else {
return vec![code];
};
(index + 1, usize::from(len))
} else {
(index, usize::from(table))
};
let mut out = Vec::with_capacity(len);
for offset in 0..len {
match word_at(payload, start + offset) {
Some(value) => out.push(u32::from(value)),
None => return vec![code],
}
}
if out.is_empty() { vec![code] } else { out }
}
fn in_ranges(payload: &[u8], code: u32) -> bool {
let rows = payload.len() / 8;
let bound = |i: usize, half: usize| -> u32 {
let at = i * 8 + half * 4;
payload
.get(at..at + 4)
.and_then(|b| b.try_into().ok())
.map_or(u32::MAX, u32::from_le_bytes)
};
let (mut lo, mut hi) = (0usize, rows);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if code < bound(mid, 0) {
hi = mid;
} else if code > bound(mid, 1) {
lo = mid + 1;
} else {
return true;
}
}
false
}
#[must_use]
pub fn is_alpha(code: u32) -> bool {
in_ranges(section(*b"ALPH"), code)
}
#[must_use]
pub fn is_alnum(code: u32) -> bool {
in_ranges(section(*b"ALNM"), code)
}
#[must_use]
pub fn to_lower(code: u32) -> u32 {
let payload = section(*b"LOWR");
let rows = payload.len() / 12;
let field = |i: usize, which: usize| -> u32 {
let at = i * 12 + which * 4;
payload
.get(at..at + 4)
.and_then(|b| b.try_into().ok())
.map_or(0, u32::from_le_bytes)
};
let (mut lo, mut hi) = (0usize, rows);
while lo < hi {
let mid = lo + (hi - lo) / 2;
if code < field(mid, 0) {
hi = mid;
} else if code > field(mid, 1) {
lo = mid + 1;
} else {
#[expect(
clippy::cast_possible_wrap,
reason = "the delta was written from an i32"
)]
let delta = field(mid, 2) as i32;
return u32::try_from(i64::from(code) + i64::from(delta)).unwrap_or(code);
}
}
code
}
#[must_use]
pub fn is_decimal_digit(code: u32) -> bool {
(u32::from(b'0')..=u32::from(b'9')).contains(&code)
}
#[must_use]
pub fn is_print(code: u32) -> bool {
(0x20..=0x7E).contains(&code)
}
#[must_use]
pub fn lower_string(text: &str) -> String {
text.chars()
.map(|ch| char::from_u32(to_lower(u32::from(ch))).unwrap_or(ch))
.collect()
}
#[cfg(test)]
mod tests {
#![allow(
clippy::float_cmp,
clippy::indexing_slicing,
clippy::unreadable_literal,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "test fixtures quote oracle vectors verbatim and compare exactly"
)]
use super::*;
#[test]
fn the_blob_parses_and_every_section_is_present() {
for tag in [
*b"UCDR", *b"MIRR", *b"NRMR", *b"NRM1", *b"NRM2", *b"NRM3", *b"NRM4", *b"ALPH",
*b"ALNM", *b"LOWR",
] {
assert!(
!section(tag).is_empty(),
"section {} is missing",
String::from_utf8_lossy(&tag)
);
}
}
#[test]
fn the_property_table_covers_the_whole_bmp() {
let payload = section(*b"UCDR");
let total: usize = payload
.as_chunks::<4>()
.0
.iter()
.map(|&[_, _, lo, hi]| usize::from(u16::from_le_bytes([lo, hi])))
.sum();
assert_eq!(total, 65536);
let norm: usize = section(*b"NRMR")
.as_chunks::<4>()
.0
.iter()
.map(|&[_, _, lo, hi]| usize::from(u16::from_le_bytes([lo, hi])))
.sum();
assert_eq!(norm, 65536);
}
#[test]
fn bidi_classes_match_the_oracle_table() {
assert_eq!(bidi_class(0x0000), BidiClass::Bn);
assert_eq!(bidi_class(0x0009), BidiClass::S);
assert_eq!(bidi_class(0x000A), BidiClass::B);
assert_eq!(bidi_class(0x0020), BidiClass::Ws);
assert_eq!(bidi_class(0x0030), BidiClass::En);
assert_eq!(bidi_class(0x002C), BidiClass::Cs);
assert_eq!(bidi_class(0x0041), BidiClass::L);
assert_eq!(bidi_class(0x05D0), BidiClass::R);
assert_eq!(bidi_class(0x0627), BidiClass::Al);
assert_eq!(bidi_class(0x0660), BidiClass::An);
assert_eq!(bidi_class(0x0300), BidiClass::Nsm);
assert_eq!(bidi_class(0x202B), BidiClass::Rle);
assert_eq!(bidi_class(0x202C), BidiClass::Pdf);
}
#[test]
fn mirroring_is_symmetric_where_the_table_says_so() {
for (a, b) in [
(0x0028, 0x0029),
(0x003C, 0x003E),
(0x005B, 0x005D),
(0x007B, 0x007D),
(0x00AB, 0x00BB),
(0x2018, 0x2019),
(0x3008, 0x3009),
] {
assert_eq!(mirror_char(a), b, "{a:#X}");
assert_eq!(mirror_char(b), a, "{b:#X}");
}
assert_eq!(mirror_char(0x0041), 0x0041);
assert_eq!(mirror_char(0x1_0000), 0x1_0000);
assert_eq!(mirror_char(0x1_0330), 0x1_0330);
}
#[test]
fn normalization_covers_all_four_map_tables() {
assert_eq!(normalize(0x00A0), vec![0x0020]);
assert_eq!(normalize(0xFB01), vec![0x0066, 0x0069]);
assert_eq!(normalize(0xFB03), vec![0x0066, 0x0066, 0x0069]);
assert_eq!(normalize(0x0041), vec![0x0041]);
assert_eq!(normalize(0x1_FB01), normalize(0xFB01));
}
#[test]
fn normalization_never_returns_an_empty_vector() {
for code in 0u32..=0xFFFF {
assert!(!normalize(code).is_empty(), "{code:#X}");
}
}
#[test]
fn character_classes_match_icu() {
assert!(is_alpha(u32::from(b'A')) && is_alpha(u32::from(b'z')));
assert!(!is_alpha(u32::from(b'0')) && !is_alpha(u32::from(b'-')));
assert!(is_alnum(u32::from(b'0')) && is_alnum(u32::from(b'A')));
assert!(!is_alnum(u32::from(b'.')) && !is_alnum(u32::from(b'@')));
assert!(is_alnum(0x0660) && !is_alpha(0x0660));
for code in [0x4E00, 0x05D0, 0x0905, 0x3042, 0xAC00] {
assert!(is_alpha(code), "{code:#X}");
}
assert!(is_alpha(0x1_0400) && is_alpha(0x2_0000));
for code in [0x0000, 0x0002, 0x0020, 0x2010, 0xFFFD] {
assert!(!is_alpha(code) && !is_alnum(code), "{code:#X}");
}
}
#[test]
fn lowercase_is_the_simple_unicode_mapping() {
assert_eq!(to_lower(u32::from(b'A')), u32::from(b'a'));
assert_eq!(to_lower(u32::from(b'a')), u32::from(b'a'));
assert_eq!(to_lower(0x0102), 0x0103);
assert_eq!(to_lower(0x0103), 0x0103);
assert_eq!(to_lower(0x0391), 0x03B1);
assert_eq!(to_lower(0x0410), 0x0430);
assert_eq!(to_lower(0x1_0400), 0x1_0428);
for code in [u32::from(b'!'), 0x4E00, 0x05D0, 0x0000] {
assert_eq!(to_lower(code), code, "{code:#X}");
}
assert_eq!(lower_string("Hello, WORLD!"), "hello, world!");
}
#[test]
fn decimal_digits_are_ascii_only() {
assert!(is_decimal_digit(u32::from(b'0')));
assert!(is_decimal_digit(u32::from(b'9')));
assert!(!is_decimal_digit(u32::from(b'/')));
assert!(!is_decimal_digit(u32::from(b':')));
assert!(!is_decimal_digit(0x0660));
assert!(!is_decimal_digit(0xFF10));
}
#[test]
fn printability_is_the_c_locale_band() {
assert!(!is_print(0x1F));
assert!(is_print(0x20));
assert!(is_print(0x7E));
assert!(!is_print(0x7F));
assert!(!is_print(0x80));
}
#[test]
fn the_expanded_tables_agree_with_a_linear_decode() {
let linear = |tag: [u8; 4]| {
let mut out = Vec::with_capacity(65536);
for &[v0, v1, l0, l1] in section(tag).as_chunks::<4>().0 {
let value = u16::from_le_bytes([v0, v1]);
let len = usize::from(u16::from_le_bytes([l0, l1]));
out.extend(std::iter::repeat_n(value, len));
}
out
};
for (table, expected) in [
(ucd_table(), linear(*b"UCDR")),
(normalization_table(), linear(*b"NRMR")),
] {
assert_eq!(expected.len(), 65536);
for (index, want) in expected.iter().enumerate() {
assert_eq!(table.get(index), *want, "{index:#X}");
}
assert_eq!(table.get(65536), 0);
assert_eq!(table.get(usize::MAX), 0);
}
}
#[test]
fn the_bidi_class_table_answers_for_each_script() {
assert_eq!(bidi_class('A' as u32), BidiClass::L);
assert_eq!(bidi_class(0x05D0), BidiClass::R); assert_eq!(bidi_class(0x0627), BidiClass::Al); assert_eq!(bidi_class('(' as u32), BidiClass::On);
assert_eq!(bidi_class(0x1_0000), BidiClass::On);
}
#[test]
fn mirroring_swaps_brackets_and_leaves_everything_else() {
assert_eq!(mirror_char('(' as u32), ')' as u32);
assert_eq!(mirror_char('[' as u32), ']' as u32);
assert_eq!(mirror_char('a' as u32), 'a' as u32);
assert_eq!(mirror_char(0x10800), 0x10800);
}
#[test]
fn space_normalization_touches_only_the_spaces() {
assert_eq!(normalize_space(0x00A0), 0x0020);
assert_eq!(normalize_space(0x2003), 0x0020);
assert_eq!(normalize_space(0x202F), 0x0020);
assert_eq!(normalize_space(0x00C0), 0x00C0);
assert_eq!(normalize_space(0x0020), 0x0020);
}
#[test]
fn the_normalization_table_decomposes_ligatures_and_passes_the_rest() {
assert_eq!(normalize(0xFB01), vec![u32::from(b'f'), u32::from(b'i')]);
assert_eq!(normalize(u32::from(b'a')), vec![u32::from(b'a')]);
}
#[test]
fn the_alpha_test_accepts_letters_and_ideographs() {
assert!(is_alpha(u32::from(b'a')));
assert!(is_alpha(0x4E00)); assert!(!is_alpha(u32::from(b'0')));
assert!(!is_alpha(u32::from(b'-')));
}
#[test]
fn the_alnum_test_accepts_letters_and_digits_of_any_script() {
assert!(is_alnum(u32::from(b'z')));
assert!(is_alnum(u32::from(b'7')));
assert!(is_alnum(0x0660)); assert!(!is_alnum(u32::from(b'@')));
}
#[test]
fn lowering_reaches_above_the_basic_plane() {
assert_eq!(to_lower(u32::from(b'A')), u32::from(b'a'));
assert_eq!(to_lower(0x0102), 0x0103); assert_eq!(to_lower(0x1_0400), 0x1_0428); assert_eq!(to_lower(u32::from(b'!')), u32::from(b'!'));
}
}