use ttf_parser::{Face, Tag};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(u8)]
pub enum WritingSystem {
Latin = 0,
Greek,
Cyrillic,
Armenian,
Hebrew,
Arabic,
Syriac,
Thaana,
Devanagari,
Bengali,
Gurmukhi,
Gujarati,
Oriya,
Tamil,
Telugu,
Kannada,
Malayalam,
Sinhala,
Thai,
Lao,
Tibetan,
Myanmar,
Georgian,
Khmer,
SimplifiedChinese,
TraditionalChinese,
Japanese,
Korean,
Vietnamese,
Symbol,
Ogham,
Runic,
Nko,
}
impl WritingSystem {
pub const ALL: [WritingSystem; 33] = [
WritingSystem::Latin,
WritingSystem::Greek,
WritingSystem::Cyrillic,
WritingSystem::Armenian,
WritingSystem::Hebrew,
WritingSystem::Arabic,
WritingSystem::Syriac,
WritingSystem::Thaana,
WritingSystem::Devanagari,
WritingSystem::Bengali,
WritingSystem::Gurmukhi,
WritingSystem::Gujarati,
WritingSystem::Oriya,
WritingSystem::Tamil,
WritingSystem::Telugu,
WritingSystem::Kannada,
WritingSystem::Malayalam,
WritingSystem::Sinhala,
WritingSystem::Thai,
WritingSystem::Lao,
WritingSystem::Tibetan,
WritingSystem::Myanmar,
WritingSystem::Georgian,
WritingSystem::Khmer,
WritingSystem::SimplifiedChinese,
WritingSystem::TraditionalChinese,
WritingSystem::Japanese,
WritingSystem::Korean,
WritingSystem::Vietnamese,
WritingSystem::Symbol,
WritingSystem::Ogham,
WritingSystem::Runic,
WritingSystem::Nko,
];
pub const fn id(self) -> &'static str {
match self {
WritingSystem::Latin => "latin",
WritingSystem::Greek => "greek",
WritingSystem::Cyrillic => "cyrillic",
WritingSystem::Armenian => "armenian",
WritingSystem::Hebrew => "hebrew",
WritingSystem::Arabic => "arabic",
WritingSystem::Syriac => "syriac",
WritingSystem::Thaana => "thaana",
WritingSystem::Devanagari => "devanagari",
WritingSystem::Bengali => "bengali",
WritingSystem::Gurmukhi => "gurmukhi",
WritingSystem::Gujarati => "gujarati",
WritingSystem::Oriya => "oriya",
WritingSystem::Tamil => "tamil",
WritingSystem::Telugu => "telugu",
WritingSystem::Kannada => "kannada",
WritingSystem::Malayalam => "malayalam",
WritingSystem::Sinhala => "sinhala",
WritingSystem::Thai => "thai",
WritingSystem::Lao => "lao",
WritingSystem::Tibetan => "tibetan",
WritingSystem::Myanmar => "myanmar",
WritingSystem::Georgian => "georgian",
WritingSystem::Khmer => "khmer",
WritingSystem::SimplifiedChinese => "simplified-chinese",
WritingSystem::TraditionalChinese => "traditional-chinese",
WritingSystem::Japanese => "japanese",
WritingSystem::Korean => "korean",
WritingSystem::Vietnamese => "vietnamese",
WritingSystem::Symbol => "symbol",
WritingSystem::Ogham => "ogham",
WritingSystem::Runic => "runic",
WritingSystem::Nko => "nko",
}
}
pub const fn english_name(self) -> &'static str {
match self {
WritingSystem::Latin => "Latin",
WritingSystem::Greek => "Greek",
WritingSystem::Cyrillic => "Cyrillic",
WritingSystem::Armenian => "Armenian",
WritingSystem::Hebrew => "Hebrew",
WritingSystem::Arabic => "Arabic",
WritingSystem::Syriac => "Syriac",
WritingSystem::Thaana => "Thaana",
WritingSystem::Devanagari => "Devanagari",
WritingSystem::Bengali => "Bengali",
WritingSystem::Gurmukhi => "Gurmukhi",
WritingSystem::Gujarati => "Gujarati",
WritingSystem::Oriya => "Oriya",
WritingSystem::Tamil => "Tamil",
WritingSystem::Telugu => "Telugu",
WritingSystem::Kannada => "Kannada",
WritingSystem::Malayalam => "Malayalam",
WritingSystem::Sinhala => "Sinhala",
WritingSystem::Thai => "Thai",
WritingSystem::Lao => "Lao",
WritingSystem::Tibetan => "Tibetan",
WritingSystem::Myanmar => "Myanmar",
WritingSystem::Georgian => "Georgian",
WritingSystem::Khmer => "Khmer",
WritingSystem::SimplifiedChinese => "Simplified Chinese",
WritingSystem::TraditionalChinese => "Traditional Chinese",
WritingSystem::Japanese => "Japanese",
WritingSystem::Korean => "Korean",
WritingSystem::Vietnamese => "Vietnamese",
WritingSystem::Symbol => "Symbol",
WritingSystem::Ogham => "Ogham",
WritingSystem::Runic => "Runic",
WritingSystem::Nko => "N'Ko",
}
}
pub const fn sample_text(self) -> &'static str {
match self {
WritingSystem::Latin => "Aa Bb Yy Zz",
WritingSystem::Greek => "Αα Ββ Γγ",
WritingSystem::Cyrillic => "Аа Бб Вв",
WritingSystem::Armenian => "Աա Բբ Գգ",
WritingSystem::Hebrew => "אבּג",
WritingSystem::Arabic => "أبجد",
WritingSystem::Syriac => "ܐܒܓ",
WritingSystem::Thaana => "ހށނ",
WritingSystem::Devanagari => "अआइ",
WritingSystem::Bengali => "অআই",
WritingSystem::Gurmukhi => "ਅਆਇ",
WritingSystem::Gujarati => "અઆઇ",
WritingSystem::Oriya => "ଅଆଇ",
WritingSystem::Tamil => "அஆஇ",
WritingSystem::Telugu => "అఆఇ",
WritingSystem::Kannada => "ಅಆಇ",
WritingSystem::Malayalam => "അആഇ",
WritingSystem::Sinhala => "අආඇ",
WritingSystem::Thai => "กขค",
WritingSystem::Lao => "ກຂຄ",
WritingSystem::Tibetan => "ཀཁག",
WritingSystem::Myanmar => "ကခဂ",
WritingSystem::Georgian => "აბგ",
WritingSystem::Khmer => "កខគ",
WritingSystem::SimplifiedChinese => "汉字样本",
WritingSystem::TraditionalChinese => "漢字樣本",
WritingSystem::Japanese => "あア漢字",
WritingSystem::Korean => "한국어",
WritingSystem::Vietnamese => "Tiếng Việt",
WritingSystem::Symbol => "★ ♦ ♣ ♠",
WritingSystem::Ogham => "ᚁᚂᚃ",
WritingSystem::Runic => "ᚠᚢᚦ",
WritingSystem::Nko => "ߊߋߌ",
}
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct WritingSystemSet(u128);
impl WritingSystemSet {
pub const fn new() -> Self {
Self(0)
}
pub fn insert(&mut self, ws: WritingSystem) {
self.0 |= 1u128 << (ws as u8);
}
pub const fn contains(self, ws: WritingSystem) -> bool {
self.0 & (1u128 << (ws as u8)) != 0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn len(self) -> u32 {
self.0.count_ones()
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub fn iter(self) -> impl Iterator<Item = WritingSystem> {
WritingSystem::ALL
.into_iter()
.filter(move |&ws| self.contains(ws))
}
}
const UNICODE_RANGE_BITS: &[(u8, WritingSystem)] = &[
(0, WritingSystem::Latin), (7, WritingSystem::Greek), (9, WritingSystem::Cyrillic), (10, WritingSystem::Armenian), (11, WritingSystem::Hebrew), (13, WritingSystem::Arabic), (14, WritingSystem::Nko), (15, WritingSystem::Devanagari), (16, WritingSystem::Bengali), (17, WritingSystem::Gurmukhi), (18, WritingSystem::Gujarati), (19, WritingSystem::Oriya), (20, WritingSystem::Tamil), (21, WritingSystem::Telugu), (22, WritingSystem::Kannada), (23, WritingSystem::Malayalam), (24, WritingSystem::Thai), (25, WritingSystem::Lao), (26, WritingSystem::Georgian), (28, WritingSystem::Korean), (49, WritingSystem::Japanese), (50, WritingSystem::Japanese), (52, WritingSystem::Korean), (56, WritingSystem::Korean), (70, WritingSystem::Tibetan), (71, WritingSystem::Syriac), (72, WritingSystem::Thaana), (73, WritingSystem::Sinhala), (74, WritingSystem::Myanmar), (78, WritingSystem::Ogham), (79, WritingSystem::Runic), (80, WritingSystem::Khmer), ];
const CODEPAGE_RANGE1_BITS: &[(u8, WritingSystem)] = &[
(8, WritingSystem::Vietnamese), (16, WritingSystem::Thai), (17, WritingSystem::Japanese), (18, WritingSystem::SimplifiedChinese), (19, WritingSystem::Korean), (20, WritingSystem::TraditionalChinese), (21, WritingSystem::Korean), ];
const CMAP_PROBES: &[(WritingSystem, &[char])] = &[
(WritingSystem::Latin, &['A', 'z']),
(WritingSystem::Greek, &['Α', 'ω']),
(WritingSystem::Cyrillic, &['А', 'я']),
(WritingSystem::Armenian, &['Ա', 'ֆ']),
(WritingSystem::Hebrew, &['א', 'ת']),
(WritingSystem::Arabic, &['ا', 'ي']),
(WritingSystem::Syriac, &['ܐ']),
(WritingSystem::Thaana, &['ހ']),
(WritingSystem::Devanagari, &['अ', 'ह']),
(WritingSystem::Bengali, &['অ']),
(WritingSystem::Gurmukhi, &['ਅ']),
(WritingSystem::Gujarati, &['અ']),
(WritingSystem::Oriya, &['ଅ']),
(WritingSystem::Tamil, &['அ']),
(WritingSystem::Telugu, &['అ']),
(WritingSystem::Kannada, &['ಅ']),
(WritingSystem::Malayalam, &['അ']),
(WritingSystem::Sinhala, &['අ']),
(WritingSystem::Thai, &['ก', 'ฮ']),
(WritingSystem::Lao, &['ກ']),
(WritingSystem::Tibetan, &['ཀ']),
(WritingSystem::Myanmar, &['က']),
(WritingSystem::Georgian, &['ა', 'ჰ']),
(WritingSystem::Khmer, &['ក']),
(WritingSystem::Japanese, &['あ', 'ア']),
(WritingSystem::Korean, &['가', '한']),
(WritingSystem::Vietnamese, &['ế', 'ệ']),
(WritingSystem::Ogham, &['ᚁ']),
(WritingSystem::Runic, &['ᚠ']),
(WritingSystem::Nko, &['ߊ']),
];
const OS2_HAN_BIT: u8 = 59;
pub fn writing_systems_for_face(bytes: &[u8], index: u32) -> WritingSystemSet {
let mut set = WritingSystemSet::new();
let Ok(face) = Face::parse(bytes, index) else {
return set;
};
let unicode_bits = face.tables().os2.map(|t| t.unicode_ranges().0).unwrap_or(0);
for &(bit, ws) in UNICODE_RANGE_BITS {
if unicode_bits & (1u128 << bit) != 0 {
set.insert(ws);
}
}
let has_han = unicode_bits & (1u128 << OS2_HAN_BIT) != 0;
if let Some(cp1) = raw_code_page_range1(&face) {
for &(bit, ws) in CODEPAGE_RANGE1_BITS {
if cp1 & (1u32 << bit) != 0 {
set.insert(ws);
}
}
}
if has_han && !has_any_cjk_language(set) {
set.insert(WritingSystem::SimplifiedChinese);
set.insert(WritingSystem::TraditionalChinese);
}
for &(ws, probes) in CMAP_PROBES {
if !set.contains(ws) && probes.iter().all(|&c| face.glyph_index(c).is_some()) {
set.insert(ws);
}
}
if !has_any_cjk_language(set) && ['中', '文'].iter().all(|&c| face.glyph_index(c).is_some()) {
set.insert(WritingSystem::SimplifiedChinese);
set.insert(WritingSystem::TraditionalChinese);
}
if set.is_empty() {
set.insert(WritingSystem::Symbol);
}
set
}
const fn has_any_cjk_language(set: WritingSystemSet) -> bool {
set.contains(WritingSystem::SimplifiedChinese)
|| set.contains(WritingSystem::TraditionalChinese)
|| set.contains(WritingSystem::Japanese)
|| set.contains(WritingSystem::Korean)
}
fn raw_code_page_range1(face: &Face) -> Option<u32> {
let data = face.raw_face().table(Tag::from_bytes(b"OS/2"))?;
if data.len() < 82 {
return None;
}
let version = u16::from_be_bytes([data[0], data[1]]);
if version < 1 {
return None; }
Some(u32::from_be_bytes([data[78], data[79], data[80], data[81]]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_insert_contains_iter() {
let mut set = WritingSystemSet::new();
assert!(set.is_empty());
set.insert(WritingSystem::Latin);
set.insert(WritingSystem::Arabic);
assert!(set.contains(WritingSystem::Latin));
assert!(set.contains(WritingSystem::Arabic));
assert!(!set.contains(WritingSystem::Greek));
assert_eq!(set.len(), 2);
let collected: Vec<_> = set.iter().collect();
assert_eq!(collected, vec![WritingSystem::Latin, WritingSystem::Arabic]);
}
#[test]
fn all_ids_unique_and_stable() {
let mut ids: Vec<&str> = WritingSystem::ALL.iter().map(|w| w.id()).collect();
let n = ids.len();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), n, "writing-system ids must be unique");
assert_eq!(n, 33);
}
#[test]
fn every_system_has_a_sample() {
for ws in WritingSystem::ALL {
assert!(!ws.sample_text().is_empty(), "{ws:?} has no sample text");
assert!(!ws.english_name().is_empty());
}
}
}