use unicode_bidi::{BidiInfo, Level};
#[derive(Debug, Clone)]
pub struct BidiRun {
pub start: usize,
pub end: usize,
pub level: u8,
}
pub struct BidiParagraph {
runs: Vec<BidiRun>,
base_level: u8,
levels: Vec<Level>,
}
impl BidiParagraph {
pub fn new(text: &str, base_rtl: Option<bool>) -> Self {
let hint = match base_rtl {
Some(true) => Some(Level::rtl()),
Some(false) => Some(Level::ltr()),
None => None,
};
let bidi = BidiInfo::new(text, hint);
let levels = bidi.levels.clone();
let mut runs: Vec<BidiRun> = Vec::new();
for para in &bidi.paragraphs {
let para_range = para.range.start..para.range.end;
let (_run_levels, run_ranges) = bidi.visual_runs(para, para_range);
for run_range in run_ranges {
let level = if run_range.start < bidi.levels.len() {
bidi.levels[run_range.start].number()
} else {
para.level.number()
};
runs.push(BidiRun {
start: run_range.start,
end: run_range.end,
level,
});
}
}
let base_level = bidi
.paragraphs
.first()
.map(|p| p.level.number())
.unwrap_or(0);
BidiParagraph {
runs,
base_level,
levels,
}
}
pub fn runs(&self) -> &[BidiRun] {
&self.runs
}
pub fn base_level(&self) -> u8 {
self.base_level
}
pub fn is_rtl(&self) -> bool {
self.base_level % 2 == 1
}
pub fn levels(&self) -> &[Level] {
&self.levels
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ltr_paragraph_base_level_is_even() {
let para = BidiParagraph::new("hello", Some(false));
assert!(!para.is_rtl(), "LTR forced base should not be RTL");
}
#[test]
fn rtl_forced_base_level_is_odd() {
let para = BidiParagraph::new("hello", Some(true));
assert!(para.is_rtl(), "RTL forced base should be RTL");
}
}