use icu_properties::{
CodePointMapData, PropertyNamesShortBorrowed,
props::{BidiMirroringGlyph, BidiPairedBracketType, Script as UnicodeScript},
script::ScriptWithExtensions,
};
use smallvec::SmallVec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Script([u8; 4]);
impl Script {
pub const COMMON: Self = Self(*b"Zyyy");
pub const LATIN: Self = Self(*b"Latn");
pub fn tag(self) -> [u8; 4] {
self.0
}
pub(crate) fn takes_letter_spacing(self) -> bool {
const JOINING: [u32; 14] = [
u32::from_ne_bytes(*b"Arab"),
u32::from_ne_bytes(*b"Nkoo"),
u32::from_ne_bytes(*b"Phlp"),
u32::from_ne_bytes(*b"Mand"),
u32::from_ne_bytes(*b"Mong"),
u32::from_ne_bytes(*b"Phag"),
u32::from_ne_bytes(*b"Deva"),
u32::from_ne_bytes(*b"Beng"),
u32::from_ne_bytes(*b"Guru"),
u32::from_ne_bytes(*b"Modi"),
u32::from_ne_bytes(*b"Shrd"),
u32::from_ne_bytes(*b"Sylo"),
u32::from_ne_bytes(*b"Tirh"),
u32::from_ne_bytes(*b"Ogam"),
];
!JOINING.contains(&u32::from_ne_bytes(self.0))
}
fn of(script: UnicodeScript) -> Self {
PropertyNamesShortBorrowed::<UnicodeScript>::new()
.get(script)
.and_then(|name| name.as_bytes().try_into().ok())
.map_or(Self::COMMON, Self)
}
}
pub(crate) fn ascii_script(text: &str) -> Script {
if text.bytes().any(|byte| byte.is_ascii_alphabetic()) {
Script::LATIN
} else {
Script::COMMON
}
}
pub(crate) fn script_runs(text: &str, offset: usize, runs: &mut Vec<(usize, Script)>) {
if text.is_ascii() {
runs.push((offset + text.len(), ascii_script(text)));
return;
}
let scripts = ScriptWithExtensions::new();
let mirroring = CodePointMapData::<BidiMirroringGlyph>::new();
let mut run = UnicodeScript::Common;
let mut open: SmallVec<[(char, UnicodeScript); 8]> = SmallVec::new();
for (at, ch) in text.char_indices() {
let mut script = scripts.get_script_val(ch);
if matches!(script, UnicodeScript::Common | UnicodeScript::Inherited) {
let bracket = mirroring.get(ch);
match bracket.paired_bracket_type {
BidiPairedBracketType::Open => {
open.push((bracket.mirroring_glyph.unwrap_or(ch), run));
}
BidiPairedBracketType::Close => {
if let Some(at) = open.iter().rposition(|(closing, _)| *closing == ch) {
script = open[at].1;
open.truncate(at);
}
}
_ => {}
}
}
if matches!(
script,
UnicodeScript::Common | UnicodeScript::Inherited | UnicodeScript::Unknown
) || script == run
|| (run != UnicodeScript::Common && scripts.has_script(ch, run))
{
continue;
}
if run == UnicodeScript::Common {
for (_, opening) in &mut open {
if *opening == UnicodeScript::Common {
*opening = script;
}
}
} else {
runs.push((offset + at, Script::of(run)));
}
run = script;
}
runs.push((offset + text.len(), Script::of(run)));
}