mod internal;
use alloc::vec::Vec;
pub use internal::*;
struct Line {
pub start: usize,
pub end: usize,
}
#[derive(Copy, Clone)]
struct Chunk {
pub start: usize,
pub end: usize,
pub r#type: Type,
}
impl Chunk {
pub fn new(start: usize, end: usize, r#type: Type) -> Chunk {
Chunk { start, end, r#type }
}
pub fn is_type(&self, t: Type) -> bool {
self.r#type == t
}
pub fn set_type(&mut self, t: Type) {
self.r#type = t;
}
pub fn set_chunk_type(&mut self, c: &Chunk) {
self.r#type = c.r#type;
}
}
pub fn process_bidi_text(input: &[u16]) -> Vec<u16> {
let mut result = Vec::<u16>::new();
let mut lines = Vec::<Line>::new();
let mut start: usize = 0;
let mut end: usize = 0;
for (idx, c) in input.iter().enumerate() {
if *c == 0x000A || *c == 0x000D {
if start == end {
start = idx + 1;
} else {
lines.push(Line { start, end: idx });
start = idx + 1;
}
}
end = idx + 1;
}
if start < input.len() {
lines.push(Line { start, end: input.len() });
}
for (line_idx, line) in lines.iter().enumerate() {
let line_str = &input[line.start..line.end];
let mut word_chunks = Vec::<Chunk>::new();
let mut cur_type: Type = get_type(&line_str[0]);
let dominant_rtl: bool = if cur_type != Type::Neutral && cur_type != Type::Weak {
cur_type == Type::Rtl
} else {
find_dominant_type(line_str) == Type::Rtl
};
if cur_type == Type::Neutral || cur_type == Type::Weak {
if dominant_rtl {
cur_type = Type::Rtl
} else {
cur_type = Type::Ltr;
}
}
start = 0;
for (idx, u_code) in line_str.iter().enumerate() {
let u_type = get_type(u_code);
if u_type != cur_type {
word_chunks.push(Chunk::new(start, idx, cur_type));
start = idx;
cur_type = u_type;
}
}
if start != line_str.len() {
word_chunks.push(Chunk::new(start, line_str.len(), cur_type));
}
let word_chunks_len = word_chunks.len();
for idx in 0..word_chunks_len {
let prev: Option<Chunk> =
if idx == 0 { None } else { word_chunks.get(idx - 1).cloned() };
let next: Option<Chunk> =
if idx == word_chunks_len - 1 { None } else { word_chunks.get(idx + 1).cloned() };
let chunk = word_chunks.get_mut(idx).unwrap();
if chunk.is_type(Type::Neutral) {
if idx == 0 {
chunk.set_chunk_type(&next.unwrap());
} else if idx == word_chunks_len - 1 {
continue;
} else if prev.unwrap().r#type == next.unwrap().r#type {
chunk.set_chunk_type(&prev.unwrap());
} else {
chunk.set_type(if dominant_rtl { Type::Rtl } else { Type::Ltr });
}
} else if chunk.is_type(Type::Weak)
&& idx != 0
&& prev.unwrap().is_type(Type::Rtl)
&& !dominant_rtl
{
word_chunks.swap(idx, idx - 1);
}
}
let mut i: isize = 0;
while i < (word_chunks.len() as isize - 1) {
let idx: usize = i.try_into().unwrap();
if word_chunks[idx].r#type == word_chunks[idx + 1].r#type {
word_chunks[idx].end = word_chunks[idx + 1].end;
word_chunks.remove(idx + 1);
i -= 1;
}
i += 1
}
if dominant_rtl {
word_chunks.reverse();
}
for chunk in word_chunks {
let mut chunk_vec = line_str[chunk.start..chunk.end].to_vec();
let chunk_str = chunk_vec.as_mut_slice();
if chunk.is_type(Type::Rtl) {
chunk_str.reverse();
mirror_adjust_string(chunk_str);
}
result.extend_from_slice(chunk_str)
}
if line_idx != lines.len() - 1 {
result.push(0x000A);
} }
result.to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_string() {
let my_string = "Hello, 你好";
let utf16_slice: Vec<u16> = my_string.encode_utf16().collect();
let utf16_ref: &[u16] = &utf16_slice;
let result: &[u16] = &process_bidi_text(utf16_ref);
assert_eq!(result, utf16_ref);
}
#[test]
fn arabic_string() {
let input_utf16_ref: &[u16] = &[65203, 65276, 65249, 1779, 1785];
let expected_utf16_ref: &[u16] = &[1779, 1785, 65249, 65276, 65203];
let result: &[u16] = &process_bidi_text(input_utf16_ref);
assert_ne!(result, input_utf16_ref);
assert_eq!(result, expected_utf16_ref);
}
#[test]
fn hebrew_string() {
let input_utf16_ref: &[u16] = &[1468, 1489];
let expected_utf16_ref: &[u16] = &[1489, 1468];
let result: &[u16] = &process_bidi_text(input_utf16_ref);
assert_ne!(result, input_utf16_ref);
assert_eq!(result, expected_utf16_ref);
}
}