1use std::borrow::Cow;
13use std::cmp::{max, min};
14use std::fmt;
15use std::iter::repeat;
16use std::ops::Range;
17
18use unic_ucd_bidi::bidi_class::abbr_names::*;
19use unic_ucd_bidi::BidiClass;
20
21use crate::explicit;
22use crate::format_chars;
23use crate::implicit;
24use crate::level;
25use crate::prepare;
26
27use crate::level::{Level, LTR_LEVEL, RTL_LEVEL};
28use crate::prepare::LevelRun;
29
30#[derive(Clone, Debug, Eq, PartialEq, Hash)]
32pub struct ParagraphInfo {
33 pub range: Range<usize>,
37
38 pub level: Level,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq, Hash)]
48pub struct InitialInfo<'text> {
49 pub text: &'text str,
51
52 pub original_classes: Vec<BidiClass>,
55
56 pub paragraphs: Vec<ParagraphInfo>,
58}
59
60impl<'text> InitialInfo<'text> {
61 pub fn new(text: &str, default_para_level: Option<Level>) -> InitialInfo<'_> {
69 let mut original_classes = Vec::with_capacity(text.len());
70
71 let mut isolate_stack = Vec::new();
73 let mut paragraphs = Vec::new();
74
75 let mut para_start = 0;
76 let mut para_level = default_para_level;
77
78 for (i, c) in text.char_indices() {
79 let class = BidiClass::of(c);
80 original_classes.extend(repeat(class).take(c.len_utf8()));
81 match class {
82 B => {
83 let para_end = i + c.len_utf8();
86 paragraphs.push(ParagraphInfo {
87 range: para_start..para_end,
88 level: para_level.unwrap_or(LTR_LEVEL),
90 });
91 para_start = para_end;
93 para_level = default_para_level;
97 isolate_stack.clear();
98 }
99 L | R | AL => {
100 match isolate_stack.last() {
101 Some(&start) => {
102 if original_classes[start] == FSI {
103 for j in 0..format_chars::FSI.len_utf8() {
106 original_classes[start + j] =
107 if class == L { LRI } else { RLI };
108 }
109 }
110 }
111 None => {
112 if para_level.is_none() {
113 para_level = Some(if class != L { RTL_LEVEL } else { LTR_LEVEL });
117 }
118 }
119 }
120 }
121 RLI | LRI | FSI => {
122 isolate_stack.push(i);
123 }
124 PDI => {
125 isolate_stack.pop();
126 }
127 _ => {}
128 }
129 }
130 if para_start < text.len() {
131 paragraphs.push(ParagraphInfo {
132 range: para_start..text.len(),
133 level: para_level.unwrap_or(LTR_LEVEL),
134 });
135 }
136 assert_eq!(original_classes.len(), text.len());
137
138 InitialInfo {
139 text,
140 original_classes,
141 paragraphs,
142 }
143 }
144}
145
146#[derive(Debug, Eq, PartialEq, Hash)]
153pub struct BidiInfo<'text> {
154 pub text: &'text str,
156
157 pub original_classes: Vec<BidiClass>,
159
160 pub levels: Vec<Level>,
162
163 pub paragraphs: Vec<ParagraphInfo>,
168}
169
170impl<'text> BidiInfo<'text> {
171 pub fn new(text: &str, default_para_level: Option<Level>) -> BidiInfo<'_> {
178 let InitialInfo {
179 original_classes,
180 paragraphs,
181 ..
182 } = InitialInfo::new(text, default_para_level);
183
184 let mut levels = Vec::<Level>::with_capacity(text.len());
185 let mut processing_classes = original_classes.clone();
186
187 for para in ¶graphs {
188 let text = &text[para.range.clone()];
189 let original_classes = &original_classes[para.range.clone()];
190 let processing_classes = &mut processing_classes[para.range.clone()];
191
192 let new_len = levels.len() + para.range.len();
193 levels.resize(new_len, para.level);
194 let levels = &mut levels[para.range.clone()];
195
196 explicit::compute(
197 text,
198 para.level,
199 original_classes,
200 levels,
201 processing_classes,
202 );
203
204 let sequences = prepare::isolating_run_sequences(para.level, original_classes, levels);
205 for sequence in &sequences {
206 implicit::resolve_weak(sequence, processing_classes);
207 implicit::resolve_neutral(sequence, levels, processing_classes);
208 }
209 implicit::resolve_levels(processing_classes, levels);
210
211 Self::assign_levels_to_removed_chars(para.level, original_classes, levels);
212 }
213
214 BidiInfo {
215 text,
216 original_classes,
217 paragraphs,
218 levels,
219 }
220 }
221
222 fn assign_levels_to_removed_chars(
227 para_level: Level,
228 classes: &[BidiClass],
229 levels: &mut [Level],
230 ) {
231 for i in 0..levels.len() {
232 if prepare::removed_by_x9(classes[i]) {
233 levels[i] = if i > 0 { levels[i - 1] } else { para_level };
234 }
235 }
236 }
237
238 pub fn reordered_levels(&self, para: &ParagraphInfo, line: Range<usize>) -> Vec<Level> {
241 let (levels, _) = self.visual_runs(para, line.clone());
242 levels
243 }
244
245 pub fn reordered_levels_per_char(
248 &self,
249 para: &ParagraphInfo,
250 line: Range<usize>,
251 ) -> Vec<Level> {
252 let levels = self.reordered_levels(para, line);
253 self.text.char_indices().map(|(i, _)| levels[i]).collect()
254 }
255
256 pub fn reorder_line(&self, para: &ParagraphInfo, line: Range<usize>) -> Cow<'text, str> {
258 let (levels, runs) = self.visual_runs(para, line.clone());
259
260 if runs.iter().all(|run| levels[run.start].is_ltr()) {
262 return self.text[line.clone()].into();
263 }
264
265 let mut result = String::with_capacity(line.len());
266 for run in runs {
267 if levels[run.start].is_rtl() {
268 result.extend(self.text[run].chars().rev());
269 } else {
270 result.push_str(&self.text[run]);
271 }
272 }
273 result.into()
274 }
275
276 #[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
282 pub fn visual_runs(
283 &self,
284 para: &ParagraphInfo,
285 line: Range<usize>,
286 ) -> (Vec<Level>, Vec<LevelRun>) {
287 assert!(line.start <= self.levels.len());
288 assert!(line.end <= self.levels.len());
289
290 let mut levels = self.levels.clone();
291
292 let line_str: &str = &self.text[line.clone()];
295 let mut reset_from: Option<usize> = Some(0);
296 let mut reset_to: Option<usize> = None;
297 for (i, c) in line_str.char_indices() {
298 match self.original_classes[i] {
299 RLE | LRE | RLO | LRO | PDF | BN => {}
301 B | S => {
303 assert_eq!(reset_to, None);
304 reset_to = Some(i + c.len_utf8());
305 if reset_from == None {
306 reset_from = Some(i);
307 }
308 }
309 WS | FSI | LRI | RLI | PDI => {
311 if reset_from == None {
312 reset_from = Some(i);
313 }
314 }
315 _ => {
316 reset_from = None;
317 }
318 }
319 if let (Some(from), Some(to)) = (reset_from, reset_to) {
320 for j in from..to {
321 levels[j] = para.level;
322 }
323 reset_from = None;
324 reset_to = None;
325 }
326 }
327 if let Some(from) = reset_from {
328 for j in from..line_str.len() {
329 levels[j] = para.level;
330 }
331 }
332
333 let mut runs = Vec::new();
335 let mut start = line.start;
336 let mut level = levels[start];
337 let mut min_level = level;
338 let mut max_level = level;
339
340 for i in (start + 1)..line.end {
341 let new_level = levels[i];
342 if new_level != level {
343 runs.push(start..i);
345 start = i;
346 level = new_level;
347
348 min_level = min(level, min_level);
349 max_level = max(level, max_level);
350 }
351 }
352 runs.push(start..line.end);
353
354 let run_count = runs.len();
355
356 min_level = min_level.new_lowest_ge_rtl().expect("Level error");
361
362 while max_level >= min_level {
363 let mut seq_start = 0;
365 while seq_start < run_count {
366 if self.levels[runs[seq_start].start] < max_level {
367 seq_start += 1;
368 continue;
369 }
370
371 let mut seq_end = seq_start + 1;
373 while seq_end < run_count {
374 if self.levels[runs[seq_end].start] < max_level {
375 break;
376 }
377 seq_end += 1;
378 }
379
380 runs[seq_start..seq_end].reverse();
382
383 seq_start = seq_end;
384 }
385 max_level
386 .lower(1)
387 .expect("Lowering embedding level below zero");
388 }
389
390 (levels, runs)
391 }
392
393 #[inline]
397 pub fn has_rtl(&self) -> bool {
398 level::has_rtl(&self.levels)
399 }
400}
401
402impl<'text> fmt::Display for BidiInfo<'text> {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 write!(
405 f,
406 "{} paragraphs with a maximum bidirectional level of {}",
407 self.paragraphs.len(),
408 self.levels.iter().max().unwrap_or(&Level::ltr()),
409 )
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn test_initial_text_info() {
419 let text = "a1";
420 assert_eq!(
421 InitialInfo::new(text, None),
422 InitialInfo {
423 text: &text,
424 original_classes: vec![L, EN],
425 paragraphs: vec![ParagraphInfo {
426 range: 0..2,
427 level: LTR_LEVEL,
428 },],
429 }
430 );
431
432 let text = "غ א";
433 assert_eq!(
434 InitialInfo::new(text, None),
435 InitialInfo {
436 text: &text,
437 original_classes: vec![AL, AL, WS, R, R],
438 paragraphs: vec![ParagraphInfo {
439 range: 0..5,
440 level: RTL_LEVEL,
441 },],
442 }
443 );
444
445 let text = "a\u{2029}b";
446 assert_eq!(
447 InitialInfo::new(text, None),
448 InitialInfo {
449 text: &text,
450 original_classes: vec![L, B, B, B, L],
451 paragraphs: vec![
452 ParagraphInfo {
453 range: 0..4,
454 level: LTR_LEVEL,
455 },
456 ParagraphInfo {
457 range: 4..5,
458 level: LTR_LEVEL,
459 },
460 ],
461 }
462 );
463
464 let text = format!("{}א{}a", format_chars::FSI, format_chars::PDI);
465 assert_eq!(
466 InitialInfo::new(&text, None),
467 InitialInfo {
468 text: &text,
469 original_classes: vec![RLI, RLI, RLI, R, R, PDI, PDI, PDI, L],
470 paragraphs: vec![ParagraphInfo {
471 range: 0..9,
472 level: LTR_LEVEL,
473 },],
474 }
475 );
476 }
477
478 #[test]
479 fn test_bidi_info() {
480 let text = "abc123";
481 assert_eq!(
482 BidiInfo::new(text, Some(LTR_LEVEL)),
483 BidiInfo {
484 text: &text,
485 levels: Level::vec(&[0, 0, 0, 0, 0, 0]),
486 original_classes: vec![L, L, L, EN, EN, EN],
487 paragraphs: vec![ParagraphInfo {
488 range: 0..6,
489 level: LTR_LEVEL,
490 },],
491 }
492 );
493
494 let text = "abc אבג";
495 assert_eq!(
496 BidiInfo::new(text, Some(LTR_LEVEL)),
497 BidiInfo {
498 text: &text,
499 levels: Level::vec(&[0, 0, 0, 0, 1, 1, 1, 1, 1, 1]),
500 original_classes: vec![L, L, L, WS, R, R, R, R, R, R],
501 paragraphs: vec![ParagraphInfo {
502 range: 0..10,
503 level: LTR_LEVEL,
504 },],
505 }
506 );
507 assert_eq!(
508 BidiInfo::new(text, Some(RTL_LEVEL)),
509 BidiInfo {
510 text: &text,
511 levels: Level::vec(&[2, 2, 2, 1, 1, 1, 1, 1, 1, 1]),
512 original_classes: vec![L, L, L, WS, R, R, R, R, R, R],
513 paragraphs: vec![ParagraphInfo {
514 range: 0..10,
515 level: RTL_LEVEL,
516 },],
517 }
518 );
519
520 let text = "אבג abc";
521 assert_eq!(
522 BidiInfo::new(text, Some(LTR_LEVEL)),
523 BidiInfo {
524 text: &text,
525 levels: Level::vec(&[1, 1, 1, 1, 1, 1, 0, 0, 0, 0]),
526 original_classes: vec![R, R, R, R, R, R, WS, L, L, L],
527 paragraphs: vec![ParagraphInfo {
528 range: 0..10,
529 level: LTR_LEVEL,
530 },],
531 }
532 );
533 assert_eq!(
534 BidiInfo::new(text, None),
535 BidiInfo {
536 text: &text,
537 levels: Level::vec(&[1, 1, 1, 1, 1, 1, 1, 2, 2, 2]),
538 original_classes: vec![R, R, R, R, R, R, WS, L, L, L],
539 paragraphs: vec![ParagraphInfo {
540 range: 0..10,
541 level: RTL_LEVEL,
542 },],
543 }
544 );
545
546 let text = "غ2ظ א2ג";
547 assert_eq!(
548 BidiInfo::new(text, Some(LTR_LEVEL)),
549 BidiInfo {
550 text: &text,
551 levels: Level::vec(&[1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1]),
552 original_classes: vec![AL, AL, EN, AL, AL, WS, R, R, EN, R, R],
553 paragraphs: vec![ParagraphInfo {
554 range: 0..11,
555 level: LTR_LEVEL,
556 },],
557 }
558 );
559
560 let text = "a א.\nג";
561 assert_eq!(
562 BidiInfo::new(text, None),
563 BidiInfo {
564 text: &text,
565 original_classes: vec![L, WS, R, R, CS, B, R, R],
566 levels: Level::vec(&[0, 0, 1, 1, 0, 0, 1, 1]),
567 paragraphs: vec![
568 ParagraphInfo {
569 range: 0..6,
570 level: LTR_LEVEL,
571 },
572 ParagraphInfo {
573 range: 6..8,
574 level: RTL_LEVEL,
575 },
576 ],
577 }
578 );
579
580 let bidi_info = BidiInfo::new("\u{060B}\u{20CF}\u{06F9}", None);
582 assert_eq!(bidi_info.original_classes, vec![AL, AL, ET, ET, ET, EN, EN]);
583 }
584
585 #[test]
586 fn test_bidi_info_has_rtl() {
587 assert_eq!(BidiInfo::new("123", None).has_rtl(), false);
589 assert_eq!(BidiInfo::new("123", Some(LTR_LEVEL)).has_rtl(), false);
590 assert_eq!(BidiInfo::new("123", Some(RTL_LEVEL)).has_rtl(), false);
591 assert_eq!(BidiInfo::new("abc", None).has_rtl(), false);
592 assert_eq!(BidiInfo::new("abc", Some(LTR_LEVEL)).has_rtl(), false);
593 assert_eq!(BidiInfo::new("abc", Some(RTL_LEVEL)).has_rtl(), false);
594 assert_eq!(BidiInfo::new("abc 123", None).has_rtl(), false);
595 assert_eq!(BidiInfo::new("abc\n123", None).has_rtl(), false);
596
597 assert_eq!(BidiInfo::new("אבּג", None).has_rtl(), true);
599 assert_eq!(BidiInfo::new("אבּג", Some(LTR_LEVEL)).has_rtl(), true);
600 assert_eq!(BidiInfo::new("אבּג", Some(RTL_LEVEL)).has_rtl(), true);
601 assert_eq!(BidiInfo::new("abc אבּג", None).has_rtl(), true);
602 assert_eq!(BidiInfo::new("abc\nאבּג", None).has_rtl(), true);
603 assert_eq!(BidiInfo::new("אבּג abc", None).has_rtl(), true);
604 assert_eq!(BidiInfo::new("אבּג\nabc", None).has_rtl(), true);
605 assert_eq!(BidiInfo::new("אבּג 123", None).has_rtl(), true);
606 assert_eq!(BidiInfo::new("אבּג\n123", None).has_rtl(), true);
607 }
608
609 fn reorder_paras(text: &str) -> Vec<Cow<'_, str>> {
610 let bidi_info = BidiInfo::new(text, None);
611 bidi_info
612 .paragraphs
613 .iter()
614 .map(|para| bidi_info.reorder_line(para, para.range.clone()))
615 .collect()
616 }
617
618 #[test]
619 fn test_reorder_line() {
620 assert_eq!(
622 reorder_paras("abc\ndef\nghi"),
623 vec!["abc\n", "def\n", "ghi"]
624 );
625
626 assert_eq!(
628 reorder_paras("ab1\nde2\ngh3"),
629 vec!["ab1\n", "de2\n", "gh3"]
630 );
631
632 assert_eq!(reorder_paras("abc\nابج"), vec!["abc\n", "جبا"]);
634
635 assert_eq!(reorder_paras("ابج\nabc"), vec!["\nجبا", "abc"]);
637
638 assert_eq!(reorder_paras("1.-2"), vec!["1.-2"]);
639 assert_eq!(reorder_paras("1-.2"), vec!["1-.2"]);
640 assert_eq!(reorder_paras("abc אבג"), vec!["abc גבא"]);
641
642 assert_eq!(reorder_paras("123 אבג"), vec!["גבא 123"]);
644
645 assert_eq!(reorder_paras("abc\u{202A}def"), vec!["abc\u{202A}def"]);
646
647 assert_eq!(
648 reorder_paras("abc\u{202A}def\u{202C}ghi"),
649 vec!["abc\u{202A}def\u{202C}ghi"]
650 );
651
652 assert_eq!(
653 reorder_paras("abc\u{2066}def\u{2069}ghi"),
654 vec!["abc\u{2066}def\u{2069}ghi"]
655 );
656
657 assert_eq!(
659 reorder_paras("\u{202B}abc אבג\u{202C}"),
660 vec!["\u{202B}\u{202C}גבא abc"]
661 );
662
663 assert_eq!(reorder_paras("אבג? אבג"), vec!["גבא ?גבא"]);
665
666 assert_eq!(reorder_paras("A אבג?"), vec!["A גבא?"]);
668
669 assert_eq!(
671 reorder_paras("A אבג?\u{200F}"),
672 vec!["A \u{200F}?גבא"]
673 );
674 assert_eq!(reorder_paras("אבג abc"), vec!["abc גבא"]);
675 assert_eq!(
676 reorder_paras("abc\u{2067}.-\u{2069}ghi"),
677 vec!["abc\u{2067}-.\u{2069}ghi"]
678 );
679
680 assert_eq!(
681 reorder_paras("Hello, \u{2068}\u{202E}world\u{202C}\u{2069}!"),
682 vec!["Hello, \u{2068}\u{202E}\u{202C}dlrow\u{2069}!"]
683 );
684
685 assert_eq!(reorder_paras("א(ב)ג."), vec![".ג)ב(א"]);
687
688 assert_eq!(
690 reorder_paras("אב(גד[&ef].)gh"),
691 vec!["ef].)gh&[דג(בא"]
692 );
693 }
694
695 fn reordered_levels_for_paras(text: &str) -> Vec<Vec<Level>> {
696 let bidi_info = BidiInfo::new(text, None);
697 bidi_info
698 .paragraphs
699 .iter()
700 .map(|para| bidi_info.reordered_levels(para, para.range.clone()))
701 .collect()
702 }
703
704 fn reordered_levels_per_char_for_paras(text: &str) -> Vec<Vec<Level>> {
705 let bidi_info = BidiInfo::new(text, None);
706 bidi_info
707 .paragraphs
708 .iter()
709 .map(|para| bidi_info.reordered_levels_per_char(para, para.range.clone()))
710 .collect()
711 }
712
713 #[test]
714 fn test_reordered_levels() {
715 let text = "\u{2067}\u{2069}";
717 assert_eq!(
718 reordered_levels_for_paras(text),
719 vec![Level::vec(&[0, 0, 0, 0, 0, 0])]
720 );
721 assert_eq!(
722 reordered_levels_per_char_for_paras(text),
723 vec![Level::vec(&[0, 0])]
724 );
725
726 }
747
748 #[test]
749 fn test_display() {
750 assert_eq!(
751 format!("{}", BidiInfo::new("", None)),
752 "0 paragraphs with a maximum bidirectional level of 0"
753 );
754
755 assert_eq!(
756 format!("{}", BidiInfo::new("abc\nאבּג", None)),
757 "2 paragraphs with a maximum bidirectional level of 1"
758 );
759 }
760}
761
762#[cfg(all(feature = "serde", test))]
763mod serde_tests {
764 use super::*;
765 use serde_test::{assert_tokens, Token};
766
767 #[test]
768 fn test_levels() {
769 let text = "abc אבג";
770 let bidi_info = BidiInfo::new(text, None);
771 let levels = bidi_info.levels;
772 assert_eq!(text.as_bytes().len(), 10);
773 assert_eq!(levels.len(), 10);
774 assert_tokens(
775 &levels,
776 &[
777 Token::Seq { len: Some(10) },
778 Token::NewtypeStruct { name: "Level" },
779 Token::U8(0),
780 Token::NewtypeStruct { name: "Level" },
781 Token::U8(0),
782 Token::NewtypeStruct { name: "Level" },
783 Token::U8(0),
784 Token::NewtypeStruct { name: "Level" },
785 Token::U8(0),
786 Token::NewtypeStruct { name: "Level" },
787 Token::U8(1),
788 Token::NewtypeStruct { name: "Level" },
789 Token::U8(1),
790 Token::NewtypeStruct { name: "Level" },
791 Token::U8(1),
792 Token::NewtypeStruct { name: "Level" },
793 Token::U8(1),
794 Token::NewtypeStruct { name: "Level" },
795 Token::U8(1),
796 Token::NewtypeStruct { name: "Level" },
797 Token::U8(1),
798 Token::SeqEnd,
799 ],
800 );
801 }
802}