use std::cmp::max;
use std::ops::Range;
use super::char_data::BidiClass;
use super::level::Level;
use BidiClass::*;
pub type LevelRun = Range<usize>;
pub struct IsolatingRunSequence {
pub runs: Vec<LevelRun>,
pub sos: BidiClass, pub eos: BidiClass, }
pub fn isolating_run_sequences(
para_level: Level,
initial_classes: &[BidiClass],
levels: &[Level],
) -> Vec<IsolatingRunSequence> {
let runs = level_runs(levels, initial_classes);
let mut sequences = Vec::with_capacity(runs.len());
let mut stack = vec![Vec::new()];
for run in runs {
assert!(run.len() > 0);
assert!(stack.len() > 0);
let start_class = initial_classes[run.start];
let end_class = initial_classes[run.end - 1];
let mut sequence = if start_class == PDI && stack.len() > 1 {
stack.pop().unwrap()
} else {
Vec::new()
};
sequence.push(run);
if matches!(end_class, RLI | LRI | FSI) {
stack.push(sequence);
} else {
sequences.push(sequence);
}
}
sequences.extend(stack.into_iter().rev().filter(|seq| seq.len() > 0));
return sequences
.into_iter()
.map(
|sequence| {
assert!(!sequence.len() > 0);
let start = sequence[0].start;
let end = sequence[sequence.len() - 1].end;
let level = levels[start];
let pred_level = match initial_classes[..start]
.iter()
.rposition(not_removed_by_x9) {
Some(idx) => levels[idx],
None => para_level,
};
let succ_level = if matches!(initial_classes[end - 1], RLI | LRI | FSI) {
para_level
} else {
match initial_classes[end..].iter().position(not_removed_by_x9) {
Some(idx) => levels[idx],
None => para_level,
}
};
IsolatingRunSequence {
runs: sequence,
sos: max(level, pred_level).bidi_class(),
eos: max(level, succ_level).bidi_class(),
}
},
)
.collect();
}
fn level_runs(levels: &[Level], original_classes: &[BidiClass]) -> Vec<LevelRun> {
assert!(levels.len() == original_classes.len());
let mut runs = Vec::new();
if levels.len() == 0 {
return runs;
}
let mut current_run_level = levels[0];
let mut current_run_start = 0;
for i in 1..levels.len() {
if !removed_by_x9(original_classes[i]) {
if levels[i] != current_run_level {
runs.push(current_run_start..i);
current_run_level = levels[i];
current_run_start = i;
}
}
}
runs.push(current_run_start..levels.len());
runs
}
pub fn removed_by_x9(class: BidiClass) -> bool {
matches!(class, RLE | LRE | RLO | LRO | PDF | BN)
}
pub fn not_removed_by_x9(class: &BidiClass) -> bool {
!removed_by_x9(*class)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_runs() {
let levels = &[0, 0, 0, 1, 1, 2, 0, 0];
assert_eq!(
level_runs(&Level::vec(levels), &[L; 8]),
&[0..3, 3..5, 5..6, 6..8]
);
}
#[cfg_attr(rustfmt, rustfmt_skip)]
#[test]
fn test_isolating_run_sequences() {
let classes = &[L, RLI, AL, LRI, L, R, L, PDI, AL, PDI, L];
let levels = &[0, 0, 1, 1, 2, 3, 2, 1, 1, 0, 0];
let para_level = Level::ltr();
let sequences = isolating_run_sequences(para_level, classes, &Level::vec(levels));
let runs: Vec<Vec<LevelRun>> = sequences.iter().map(|s| s.runs.clone()).collect();
assert_eq!(
runs,
vec![vec![4..5], vec![5..6], vec![6..7], vec![2..4, 7..9], vec![0..2, 9..11]]
);
}
#[test]
fn test_removed_by_x9() {
let rem_classes = &[RLE, LRE, RLO, LRO, PDF, BN];
let not_classes = &[L, RLI, AL, LRI, PDI];
for x in rem_classes {
assert_eq!(removed_by_x9(*x), true);
}
for x in not_classes {
assert_eq!(removed_by_x9(*x), false);
}
}
#[test]
fn test_not_removed_by_x9() {
let non_x9_classes = &[L, R, AL, EN, ES, ET, AN, CS, NSM, B, S, WS, ON, LRI, RLI, FSI, PDI];
for x in non_x9_classes {
assert_eq!(not_removed_by_x9(&x), true);
}
}
}