use unicode_bidi::Level;
pub fn needs_bidi(text: &str) -> bool {
text.chars().any(|c| {
let cp = c as u32;
(0x0590..=0x05FF).contains(&cp) || (0x0600..=0x06FF).contains(&cp) || (0x0750..=0x077F).contains(&cp) || (0xFB1D..=0xFB4F).contains(&cp) || (0xFB50..=0xFDFF).contains(&cp) || (0xFE70..=0xFEFF).contains(&cp) || cp == 0x200F || cp == 0x202B || cp == 0x202E })
}
pub fn line_visual_order(levels: &[Level]) -> Vec<usize> {
let n = levels.len();
let mut indices: Vec<usize> = (0..n).collect();
if n == 0 {
return indices;
}
let max_level = levels.iter().map(|l| l.number()).max().unwrap_or(0);
let min_odd = match levels
.iter()
.map(|l| l.number())
.filter(|&l| l % 2 == 1)
.min()
{
Some(l) => l,
None => return indices,
};
let mut l = max_level;
loop {
let mut run_start: Option<usize> = None;
for i in 0..=n {
let at_or_above = i < n && levels[i].number() >= l;
match (run_start, at_or_above) {
(None, true) => run_start = Some(i),
(Some(s), false) => {
indices[s..i].reverse();
run_start = None;
}
_ => {}
}
}
if l <= min_odd {
break;
}
l -= 1;
}
indices
}
#[cfg(test)]
mod tests {
use super::*;
fn levels_from_u8(v: &[u8]) -> Vec<Level> {
v.iter()
.map(|&n| Level::new(n).expect("valid level"))
.collect()
}
#[test]
fn all_ltr_is_identity() {
let levels = levels_from_u8(&[0, 0, 0, 0]);
let order = line_visual_order(&levels);
assert_eq!(
order,
vec![0, 1, 2, 3],
"all-LTR must be identity permutation"
);
}
#[test]
fn all_rtl_reverses_completely() {
let levels = levels_from_u8(&[1, 1, 1, 1]);
let order = line_visual_order(&levels);
assert_eq!(order, vec![3, 2, 1, 0], "all-RTL must fully reverse");
}
#[test]
fn mixed_ltr_rtl_partial_reverse() {
let levels = levels_from_u8(&[0, 0, 1, 1]);
let order = line_visual_order(&levels);
assert_eq!(order, vec![0, 1, 3, 2]);
}
#[test]
fn empty_slice_returns_empty() {
let order = line_visual_order(&[]);
assert!(order.is_empty(), "empty input yields empty output");
}
#[test]
fn needs_bidi_ascii_false() {
assert!(!needs_bidi("hello"), "ASCII text needs no bidi processing");
}
#[test]
fn needs_bidi_arabic_true() {
assert!(needs_bidi("مرحبا"), "Arabic text must trigger bidi");
}
#[test]
fn needs_bidi_hebrew_true() {
assert!(needs_bidi("שלום"), "Hebrew text must trigger bidi");
}
}