1use icu_properties::{
2 CodePointMapData, PropertyNamesShortBorrowed,
3 props::{BidiMirroringGlyph, BidiPairedBracketType, Script as UnicodeScript},
4 script::ScriptWithExtensions,
5};
6use smallvec::SmallVec;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct Script([u8; 4]);
11
12impl Script {
13 pub const COMMON: Self = Self(*b"Zyyy");
16 pub const LATIN: Self = Self(*b"Latn");
18
19 pub fn tag(self) -> [u8; 4] {
21 self.0
22 }
23
24 pub(crate) fn takes_letter_spacing(self) -> bool {
28 const JOINING: [u32; 14] = [
31 u32::from_ne_bytes(*b"Arab"),
32 u32::from_ne_bytes(*b"Nkoo"),
33 u32::from_ne_bytes(*b"Phlp"),
34 u32::from_ne_bytes(*b"Mand"),
35 u32::from_ne_bytes(*b"Mong"),
36 u32::from_ne_bytes(*b"Phag"),
37 u32::from_ne_bytes(*b"Deva"),
38 u32::from_ne_bytes(*b"Beng"),
39 u32::from_ne_bytes(*b"Guru"),
40 u32::from_ne_bytes(*b"Modi"),
41 u32::from_ne_bytes(*b"Shrd"),
42 u32::from_ne_bytes(*b"Sylo"),
43 u32::from_ne_bytes(*b"Tirh"),
44 u32::from_ne_bytes(*b"Ogam"),
45 ];
46 !JOINING.contains(&u32::from_ne_bytes(self.0))
47 }
48
49 fn of(script: UnicodeScript) -> Self {
50 PropertyNamesShortBorrowed::<UnicodeScript>::new()
51 .get(script)
52 .and_then(|name| name.as_bytes().try_into().ok())
53 .map_or(Self::COMMON, Self)
54 }
55}
56
57pub(crate) fn ascii_script(text: &str) -> Script {
59 if text.bytes().any(|byte| byte.is_ascii_alphabetic()) {
60 Script::LATIN
61 } else {
62 Script::COMMON
63 }
64}
65
66pub(crate) fn script_runs(text: &str, offset: usize, runs: &mut Vec<(usize, Script)>) {
73 if text.is_ascii() {
74 runs.push((offset + text.len(), ascii_script(text)));
75 return;
76 }
77 let scripts = ScriptWithExtensions::new();
78 let mirroring = CodePointMapData::<BidiMirroringGlyph>::new();
79 let mut run = UnicodeScript::Common;
80 let mut open: SmallVec<[(char, UnicodeScript); 8]> = SmallVec::new();
82 for (at, ch) in text.char_indices() {
83 let mut script = scripts.get_script_val(ch);
84 if matches!(script, UnicodeScript::Common | UnicodeScript::Inherited) {
85 let bracket = mirroring.get(ch);
86 match bracket.paired_bracket_type {
87 BidiPairedBracketType::Open => {
88 open.push((bracket.mirroring_glyph.unwrap_or(ch), run));
89 }
90 BidiPairedBracketType::Close => {
91 if let Some(at) = open.iter().rposition(|(closing, _)| *closing == ch) {
92 script = open[at].1;
93 open.truncate(at);
94 }
95 }
96 _ => {}
97 }
98 }
99 if matches!(
100 script,
101 UnicodeScript::Common | UnicodeScript::Inherited | UnicodeScript::Unknown
102 ) || script == run
103 || (run != UnicodeScript::Common && scripts.has_script(ch, run))
104 {
105 continue;
106 }
107 if run == UnicodeScript::Common {
108 for (_, opening) in &mut open {
109 if *opening == UnicodeScript::Common {
110 *opening = script;
111 }
112 }
113 } else {
114 runs.push((offset + at, Script::of(run)));
115 }
116 run = script;
117 }
118 runs.push((offset + text.len(), Script::of(run)));
119}