cranpose_text/
bidi_level.rs1use icu_properties::{CodePointMapData, props::BidiClass};
2use unicode_bidi::{Level, ParagraphBidiInfo};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct BidiLevel(u8);
8
9impl BidiLevel {
10 pub const LTR: Self = Self(0);
12 pub const RTL: Self = Self(1);
14
15 pub fn value(self) -> u8 {
17 self.0
18 }
19
20 pub fn is_rtl(self) -> bool {
22 self.0 & 1 == 1
23 }
24}
25
26pub(crate) fn level_runs(
31 paragraph: &str,
32 base: BidiLevel,
33 offset: usize,
34 runs: &mut Vec<(usize, BidiLevel)>,
35) {
36 let classes = CodePointMapData::<BidiClass>::new();
37 if base == BidiLevel::LTR
38 && (paragraph.is_ascii() || !paragraph.chars().any(|ch| turns(classes.get(ch))))
39 {
40 runs.push((offset + paragraph.len(), base));
41 return;
42 }
43 let base = if base.is_rtl() {
44 Level::rtl()
45 } else {
46 Level::ltr()
47 };
48 let info = ParagraphBidiInfo::new_with_data_source(&classes, paragraph, Some(base));
49 let mut current: Option<Level> = None;
50 for (at, &level) in info.levels.iter().enumerate() {
51 if let Some(previous) = current
52 && previous != level
53 {
54 runs.push((offset + at, BidiLevel(previous.number())));
55 }
56 current = Some(level);
57 }
58 if let Some(level) = current {
59 runs.push((offset + paragraph.len(), BidiLevel(level.number())));
60 }
61}
62
63pub(crate) fn reorder_visually<T>(runs: &mut [T], level: impl Fn(&T) -> BidiLevel) {
67 let Some(highest) = runs.iter().map(|run| level(run).0).max() else {
68 return;
69 };
70 let lowest = runs.iter().map(|run| level(run).0).min().unwrap_or(0) | 1;
71 for current in (lowest..=highest).rev() {
72 let mut start = 0;
73 while start < runs.len() {
74 if level(&runs[start]).0 < current {
75 start += 1;
76 continue;
77 }
78 let mut end = start;
79 while end < runs.len() && level(&runs[end]).0 >= current {
80 end += 1;
81 }
82 runs[start..end].reverse();
83 start = end;
84 }
85 }
86}
87
88fn turns(class: BidiClass) -> bool {
91 matches!(
92 class,
93 BidiClass::RightToLeft
94 | BidiClass::ArabicLetter
95 | BidiClass::ArabicNumber
96 | BidiClass::RightToLeftEmbedding
97 | BidiClass::RightToLeftOverride
98 | BidiClass::RightToLeftIsolate
99 | BidiClass::FirstStrongIsolate
100 | BidiClass::LeftToRightEmbedding
101 | BidiClass::LeftToRightOverride
102 | BidiClass::LeftToRightIsolate
103 | BidiClass::PopDirectionalFormat
104 | BidiClass::PopDirectionalIsolate
105 )
106}