1use super::codepoint::Codepoint;
2use super::codepoint_enumerator::CodepointEnumerator;
3use super::general_category::GeneralCategory;
4use super::grapheme_break_class::GraphemeBreakClass;
5use super::line_break::LineBreak;
6use super::line_break_class::LineBreakClass;
7
8const DOT_CIRCLE: u32 = 0x25CC;
9
10#[derive(Clone, Debug)]
12pub struct LineBreakEnumerator<'a> {
13 text: &'a [u16],
14 state: LineBreakState,
15}
16
17impl<'a> LineBreakEnumerator<'a> {
18 #[inline]
19 pub fn new(text: &'a [u16]) -> Self {
20 Self { text, state: LineBreakState::new() }
21 }
22
23 #[inline]
25 pub const fn text(&self) -> &'a [u16] {
26 self.text
27 }
28
29 pub fn move_next(&mut self) -> Option<LineBreak> {
31 if self.text.is_empty() {
32 return None;
33 }
34
35 if self.state.current.end_of_text {
36 return None;
37 }
38
39 loop {
40 self.state.read(self.text);
41
42 if let Some(result) = execute_rules(self.text, &mut self.state) {
43 return Some(result);
44 }
45 }
46 }
47}
48
49impl Iterator for LineBreakEnumerator<'_> {
50 type Item = LineBreak;
51
52 #[inline]
53 fn next(&mut self) -> Option<LineBreak> {
54 self.move_next()
55 }
56}
57
58#[inline(always)]
59fn is_break_class(cls: LineBreakClass) -> bool {
60 const MASK: u64 = (1u64 << LineBreakClass::MandatoryBreak as i32)
61 | (1u64 << LineBreakClass::LineFeed as i32)
62 | (1u64 << LineBreakClass::CarriageReturn as i32)
63 | (1u64 << LineBreakClass::NextLine as i32);
64 ((1u64 << cls as i32) & MASK) != 0
65}
66
67#[inline(always)]
71fn read_at(text: &[u16], index: i32) -> (Codepoint, i32) {
72 if index < 0 {
73 return (Codepoint::REPLACEMENT_CODEPOINT, 1);
74 }
75
76 let (codepoint, count) = Codepoint::read_at(text, index as usize);
77 (codepoint, count as i32)
78}
79
80fn get_line_break(text: &[u16], state: &mut LineBreakState, is_required: bool) -> LineBreak {
81 let mut position_measure = state.current.start + state.current.length;
82 let position_wrap = position_measure;
83
84 match state.current.line_break_class {
85 LineBreakClass::Space | LineBreakClass::CarriageReturn | LineBreakClass::LineFeed => {
86 if state.previous_class() == LineBreakClass::CarriageReturn {
87 position_measure = find_prior_non_whitespace(text, state.previous().start);
88 } else {
89 position_measure = find_prior_non_whitespace(text, position_measure);
90 }
91 }
92 _ => {}
93 }
94
95 LineBreak::new(position_measure.max(0) as usize, position_wrap.max(0) as usize, is_required)
96}
97
98fn find_prior_non_whitespace(text: &[u16], from: i32) -> i32 {
99 let mut from = from;
100
101 if from > 0 {
102 let (cp, count) = read_at(text, from - 1);
103
104 let cls = cp.line_break_class();
105
106 if is_break_class(cls) {
107 from -= count;
108 }
109 }
110
111 while from > 0 {
112 let (cp, count) = read_at(text, from - 1);
113
114 let cls = cp.line_break_class();
115
116 if cls == LineBreakClass::Space {
117 from -= count;
118 } else {
119 break;
120 }
121 }
122
123 from
124}
125
126fn execute_rules(text: &[u16], state: &mut LineBreakState) -> Option<LineBreak> {
127 macro_rules! rule {
131 ($done:lifetime, $rule:ident) => {
132 let res = $rule(text, state);
133 if res != RuleResult::Pass {
134 break $done res;
135 }
136 };
137 }
138
139 let res = 'done: {
140 rule!('done, regional_indicator);
141 rule!('done, lb03);
142 rule!('done, lb04);
143 rule!('done, lb05);
144 rule!('done, lb06);
145 rule!('done, lb07);
146 rule!('done, lb08);
147 rule!('done, lb08a);
148 rule!('done, lb09);
149 rule!('done, lb10);
150 rule!('done, lb11);
151 rule!('done, lb12);
152 rule!('done, lb12a);
153 rule!('done, lb13);
154 rule!('done, lb14);
155 rule!('done, lb15a);
156 rule!('done, lb15b);
157 rule!('done, lb15c);
158 rule!('done, lb15d);
159 rule!('done, lb16);
160 rule!('done, lb17);
161 rule!('done, lb18);
162 rule!('done, lb19);
163 rule!('done, lb20);
164 rule!('done, lb20a);
165 rule!('done, lb21a);
166 rule!('done, lb21);
167 rule!('done, lb21b);
168 rule!('done, lb22);
169 rule!('done, lb23);
170 rule!('done, lb23a);
171 rule!('done, lb24);
172 rule!('done, lb25);
173 rule!('done, lb26);
174 rule!('done, lb27);
175 rule!('done, lb28);
176 rule!('done, lb28a);
177 rule!('done, lb29);
178 rule!('done, lb30);
179 rule!('done, lb30a);
180 rule!('done, lb30b);
181 lb31(text, state) };
183
184 match res {
185 RuleResult::NoBreak => None,
186 RuleResult::MayBreak | RuleResult::MustBreak => {
187 let is_required = is_break_class(state.current.line_break_class);
188 Some(get_line_break(text, state, is_required))
189 }
190 RuleResult::Pass => None,
191 }
192}
193
194fn regional_indicator(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
195 if state.current.inherited {
196 return RuleResult::Pass;
197 }
198
199 if state.current.line_break_class == LineBreakClass::RegionalIndicator {
200 state.regional_indicator += 1;
201 if state.regional_indicator % 2 == 0 {
202 state.regional_indicator = 0;
203 }
204 }
205
206 RuleResult::Pass
207}
208
209#[inline(always)]
211fn lb03(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
212 if state.current.end_of_text {
213 return RuleResult::MustBreak;
214 }
215
216 RuleResult::Pass
217}
218
219#[inline(always)]
221fn lb04(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
222 if state.current.line_break_class == LineBreakClass::MandatoryBreak {
224 return RuleResult::MustBreak;
225 }
226
227 RuleResult::Pass
228}
229
230fn lb05(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
232 match state.current.line_break_class {
233 LineBreakClass::CarriageReturn => {
234 if state.next_class() == LineBreakClass::LineFeed {
235 return RuleResult::NoBreak; }
237
238 RuleResult::MustBreak }
240 LineBreakClass::LineFeed | LineBreakClass::NextLine => RuleResult::MustBreak,
243 _ => RuleResult::Pass,
244 }
245}
246
247#[inline(always)]
249fn lb06(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
250 if is_break_class(state.next_class()) {
252 return RuleResult::NoBreak;
253 }
254
255 RuleResult::Pass
256}
257
258#[inline(always)]
260fn lb07(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
261 match state.next_class() {
264 LineBreakClass::Space | LineBreakClass::ZWSpace => RuleResult::NoBreak,
265 _ => RuleResult::Pass,
266 }
267}
268
269fn lb08(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
271 if state.last_before_space.line_break_class == LineBreakClass::ZWSpace && state.next_class() != LineBreakClass::Space {
272 return RuleResult::MayBreak;
273 }
274
275 RuleResult::Pass
276}
277
278#[inline(always)]
280fn lb08a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
281 if state.current.line_break_class == LineBreakClass::ZWJ {
283 return RuleResult::NoBreak;
284 }
285
286 RuleResult::Pass
287}
288
289fn lb09(text: &[u16], state: &mut LineBreakState) -> RuleResult {
293 let cls = state.current.line_break_class;
296 if is_break_class(cls) || cls == LineBreakClass::Space || cls == LineBreakClass::ZWSpace {
297 return RuleResult::Pass;
298 }
299
300 match state.next_class() {
301 LineBreakClass::CombiningMark | LineBreakClass::ZWJ => {
302 state.ignore_next(text);
303 RuleResult::NoBreak
304 }
305 _ => RuleResult::Pass,
306 }
307}
308
309fn lb10(text: &[u16], state: &mut LineBreakState) -> RuleResult {
311 if state.current.line_break_class == LineBreakClass::CombiningMark {
312 state.current = BreakUnit { line_break_class: LineBreakClass::Alphabetic, inherited: true, ..state.current };
313 }
314
315 let next = state.next(text);
316
317 if next.line_break_class == LineBreakClass::CombiningMark {
318 state.replace_next(BreakUnit { line_break_class: LineBreakClass::Alphabetic, inherited: true, ..next });
319 }
320
321 RuleResult::Pass
322}
323
324#[inline(always)]
326fn lb11(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
327 if state.next_class() == LineBreakClass::WordJoiner || state.current.line_break_class == LineBreakClass::WordJoiner
329 {
331 return RuleResult::NoBreak;
332 }
333
334 RuleResult::Pass
335}
336
337#[inline(always)]
339fn lb12(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
340 if state.current.line_break_class == LineBreakClass::Glue {
342 return RuleResult::NoBreak;
343 }
344
345 RuleResult::Pass
346}
347
348fn lb12a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
350 if state.next_class() == LineBreakClass::Glue {
352 return match state.current.line_break_class {
353 LineBreakClass::Space
354 | LineBreakClass::BreakAfter
355 | LineBreakClass::Hyphen
356 | LineBreakClass::UnambiguousHyphen => RuleResult::Pass,
357 _ => RuleResult::NoBreak,
358 };
359 }
360
361 RuleResult::Pass
362}
363
364#[inline(always)]
366fn lb13(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
367 match state.next_class() {
372 LineBreakClass::ClosePunctuation
373 | LineBreakClass::CloseParenthesis
374 | LineBreakClass::Exclamation
375 | LineBreakClass::BreakSymbols => RuleResult::NoBreak,
376 _ => RuleResult::Pass,
377 }
378}
379
380#[inline(always)]
382fn lb14(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
383 if state.last_before_whitespace.line_break_class == LineBreakClass::OpenPunctuation {
385 return RuleResult::NoBreak;
386 }
387
388 RuleResult::Pass
389}
390
391fn lb15a(text: &[u16], state: &mut LineBreakState) -> RuleResult {
394 fn is_start_like(unit: BreakUnit) -> bool {
395 if (unit.start_of_text && unit.length == 0) || is_break_class(unit.line_break_class) {
396 return true;
397 }
398
399 matches!(
400 unit.line_break_class,
401 LineBreakClass::OpenPunctuation
402 | LineBreakClass::Quotation
403 | LineBreakClass::Glue
404 | LineBreakClass::Space
405 | LineBreakClass::ZWSpace
406 )
407 }
408
409 if state.last_before_whitespace.codepoint.general_category() == GeneralCategory::InitialPunctuation
411 && state.last_before_whitespace.line_break_class == LineBreakClass::Quotation
412 && is_start_like(LineBreakState::before(text, state.last_before_whitespace))
413 {
414 return RuleResult::NoBreak;
415 }
416
417 RuleResult::Pass
418}
419
420fn lb15b(text: &[u16], state: &mut LineBreakState) -> RuleResult {
423 if state.next(text).codepoint.general_category() == GeneralCategory::FinalPunctuation
425 && (state.next_class() == LineBreakClass::Quotation)
426 {
427 let after = LineBreakState::after(text, state.next(text));
428
429 if after.end_of_text {
430 return RuleResult::NoBreak;
432 }
433
434 if is_break_class(after.line_break_class) {
435 return RuleResult::NoBreak;
436 }
437
438 if matches!(
439 after.line_break_class,
440 LineBreakClass::Space
441 | LineBreakClass::Glue
442 | LineBreakClass::WordJoiner
443 | LineBreakClass::ClosePunctuation
444 | LineBreakClass::Quotation
445 | LineBreakClass::CloseParenthesis
446 | LineBreakClass::Exclamation
447 | LineBreakClass::InfixNumeric
448 | LineBreakClass::BreakSymbols
449 | LineBreakClass::ZWSpace
450 ) {
451 return RuleResult::NoBreak;
452 }
453 }
454
455 RuleResult::Pass
456}
457
458fn lb15c(text: &[u16], state: &mut LineBreakState) -> RuleResult {
460 if state.current.line_break_class == LineBreakClass::Space
462 && state.next_class() == LineBreakClass::InfixNumeric
463 && LineBreakState::after(text, state.next(text)).line_break_class == LineBreakClass::Numeric
464 {
465 return RuleResult::MayBreak;
466 }
467
468 RuleResult::Pass
469}
470
471#[inline(always)]
473fn lb15d(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
474 if state.next_class() == LineBreakClass::InfixNumeric {
476 return RuleResult::NoBreak;
477 }
478
479 RuleResult::Pass
480}
481
482fn lb16(text: &[u16], state: &mut LineBreakState) -> RuleResult {
485 if matches!(
486 state.last_before_whitespace.line_break_class,
487 LineBreakClass::ClosePunctuation | LineBreakClass::CloseParenthesis
488 ) {
489 let class_after_spaces = LineBreakState::class_after_spaces(text, state.current);
490
491 if matches!(class_after_spaces, LineBreakClass::ConditionalJapaneseStarter | LineBreakClass::Nonstarter) {
492 return RuleResult::NoBreak;
493 }
494 }
495
496 RuleResult::Pass
497}
498
499fn lb17(text: &[u16], state: &mut LineBreakState) -> RuleResult {
501 if state.last_before_whitespace.line_break_class == LineBreakClass::BreakBoth
503 && LineBreakState::class_after_spaces(text, state.current) == LineBreakClass::BreakBoth
504 {
505 return RuleResult::NoBreak;
506 }
507
508 RuleResult::Pass
509}
510
511#[inline(always)]
513fn lb18(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
514 if state.current.line_break_class == LineBreakClass::Space {
516 return RuleResult::MayBreak;
517 }
518
519 RuleResult::Pass
520}
521
522fn lb19(text: &[u16], state: &mut LineBreakState) -> RuleResult {
524 let next = state.next(text);
525
526 if next.line_break_class == LineBreakClass::Quotation
527 && next.codepoint.general_category() != GeneralCategory::InitialPunctuation
528 {
529 return RuleResult::NoBreak;
530 }
531
532 if state.current.line_break_class == LineBreakClass::Quotation
533 && state.current.codepoint.general_category() != GeneralCategory::FinalPunctuation
534 {
535 return RuleResult::NoBreak;
536 }
537
538 if !state.current.codepoint.is_east_asian() && next.line_break_class == LineBreakClass::Quotation {
539 return RuleResult::NoBreak;
540 }
541
542 if next.line_break_class == LineBreakClass::Quotation {
543 let after = LineBreakState::after(text, next);
544
545 if after.end_of_text || !after.codepoint.is_east_asian() {
546 return RuleResult::NoBreak;
547 }
548 }
549
550 if state.current.line_break_class == LineBreakClass::Quotation && !next.codepoint.is_east_asian() {
551 return RuleResult::NoBreak;
552 }
553
554 let previous = state.previous();
555
556 if ((previous.start_of_text && previous.length == 0) || !previous.codepoint.is_east_asian())
557 && state.current.line_break_class == LineBreakClass::Quotation
558 {
559 return RuleResult::NoBreak;
560 }
561
562 RuleResult::Pass
563}
564
565#[inline(always)]
567fn lb20(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
568 if (state.current.line_break_class == LineBreakClass::ContingentBreak)
571 || (state.next_class() == LineBreakClass::ContingentBreak)
572 {
573 return RuleResult::MayBreak;
574 }
575
576 RuleResult::Pass
577}
578
579fn lb20a(text: &[u16], state: &mut LineBreakState) -> RuleResult {
581 fn is_match(unit: BreakUnit) -> bool {
582 if unit.start_of_text && unit.length == 0 {
583 return true;
584 }
585
586 if is_break_class(unit.line_break_class) {
587 return true;
588 }
589
590 matches!(
591 unit.line_break_class,
592 LineBreakClass::Space | LineBreakClass::ZWSpace | LineBreakClass::ContingentBreak | LineBreakClass::Glue
593 )
594 }
595
596 let current = if state.current.inherited { state.last_before_whitespace } else { state.current };
597 let previous = if state.current.inherited { LineBreakState::before(text, current) } else { state.previous() };
598
599 if is_match(previous)
601 && matches!(state.next_class(), LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter)
602 && matches!(current.line_break_class, LineBreakClass::Hyphen | LineBreakClass::UnambiguousHyphen)
603 {
604 return RuleResult::NoBreak;
605 }
606
607 RuleResult::Pass
608}
609
610fn lb21(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
612 if matches!(
614 state.next_class(),
615 LineBreakClass::BreakAfter
617 | LineBreakClass::UnambiguousHyphen
619 | LineBreakClass::Hyphen
621 | LineBreakClass::Nonstarter
623 ) {
624 return RuleResult::NoBreak;
625 }
626
627 if state.current.line_break_class == LineBreakClass::BreakBefore {
629 return RuleResult::NoBreak;
630 }
631
632 RuleResult::Pass
633}
634
635fn lb21a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
637 if state.next_class() != LineBreakClass::HebrewLetter {
638 if state.previous_class() == LineBreakClass::HebrewLetter
640 && matches!(state.current.line_break_class, LineBreakClass::Hyphen | LineBreakClass::UnambiguousHyphen)
641 {
642 return RuleResult::NoBreak;
643 }
644 }
645
646 RuleResult::Pass
647}
648
649#[inline(always)]
651fn lb21b(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
652 if (state.current.line_break_class == LineBreakClass::BreakSymbols) && (state.next_class() == LineBreakClass::HebrewLetter)
654 {
655 return RuleResult::NoBreak;
656 }
657
658 RuleResult::Pass
659}
660
661#[inline(always)]
663fn lb22(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
664 if state.next_class() == LineBreakClass::Inseparable {
666 return RuleResult::NoBreak;
667 }
668
669 RuleResult::Pass
670}
671
672fn lb23(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
674 match state.current.line_break_class {
675 LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter => {
676 if state.next_class() == LineBreakClass::Numeric {
678 return RuleResult::NoBreak;
679 }
680 }
681 LineBreakClass::Numeric => {
682 if matches!(state.next_class(), LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter) {
684 return RuleResult::NoBreak;
685 }
686 }
687 _ => {}
688 }
689
690 RuleResult::Pass
691}
692
693#[inline(always)]
694fn is_id_eb_em(cls: LineBreakClass) -> bool {
695 cls == LineBreakClass::Ideographic || cls == LineBreakClass::EBase || cls == LineBreakClass::EModifier
696}
697
698#[inline(always)]
699fn is_pr_po(cls: LineBreakClass) -> bool {
700 cls == LineBreakClass::PrefixNumeric || cls == LineBreakClass::PostfixNumeric
701}
702
703#[inline(always)]
706fn lb23a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
707 let current_class = state.current.line_break_class;
708 let next_class = state.next_class();
709
710 if current_class == LineBreakClass::PrefixNumeric && is_id_eb_em(next_class) {
712 return RuleResult::NoBreak;
713 }
714
715 if next_class == LineBreakClass::PostfixNumeric && is_id_eb_em(current_class) {
717 return RuleResult::NoBreak;
718 }
719
720 RuleResult::Pass
721}
722
723#[inline(always)]
726fn lb24(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
727 let current_class = state.current.line_break_class;
728 let next_class = state.next_class();
729
730 if is_pr_po(current_class) && (next_class == LineBreakClass::Alphabetic || next_class == LineBreakClass::HebrewLetter) {
732 return RuleResult::NoBreak;
733 }
734
735 if (current_class == LineBreakClass::Alphabetic || current_class == LineBreakClass::HebrewLetter) && is_pr_po(next_class)
737 {
738 return RuleResult::NoBreak;
739 }
740
741 RuleResult::Pass
742}
743
744fn lb25(text: &[u16], state: &mut LineBreakState) -> RuleResult {
746 match state.next_class() {
747 LineBreakClass::PrefixNumeric => {
749 match state.current.line_break_class {
750 LineBreakClass::CloseParenthesis => match state.previous_class() {
752 LineBreakClass::Numeric => {
753 return RuleResult::NoBreak;
754 }
755 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
756 let previous = state.previous();
757 if LineBreakState::before(text, previous).line_break_class == LineBreakClass::Numeric {
758 return RuleResult::NoBreak;
759 }
760 }
761 _ => {}
762 },
763 LineBreakClass::Numeric => {
764 return RuleResult::NoBreak;
765 }
766 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
767 if state.previous_class() == LineBreakClass::Numeric {
768 return RuleResult::NoBreak;
769 }
770 }
771 LineBreakClass::ClosePunctuation => match state.previous_class() {
773 LineBreakClass::Numeric => {
774 return RuleResult::NoBreak;
775 }
776 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
777 if state.previous_class() == LineBreakClass::Numeric {
780 return RuleResult::NoBreak;
781 }
782 }
783 _ => {}
784 },
785 _ => {}
786 }
787 }
788 LineBreakClass::Numeric => match state.current.line_break_class {
790 LineBreakClass::Numeric => {
791 return RuleResult::NoBreak;
792 }
793 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
794 if state.previous_class() == LineBreakClass::Numeric {
795 return RuleResult::NoBreak;
796 }
797 }
798 _ => {}
799 },
800 LineBreakClass::PostfixNumeric => {
801 match state.current.line_break_class {
802 LineBreakClass::ClosePunctuation => match state.previous_class() {
804 LineBreakClass::Numeric => {
805 return RuleResult::NoBreak;
806 }
807 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
808 if state.previous_class() == LineBreakClass::Numeric {
811 return RuleResult::NoBreak;
812 }
813 }
814 _ => {}
815 },
816 LineBreakClass::Numeric => {
818 return RuleResult::NoBreak;
819 }
820 LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
821 if state.previous_class() == LineBreakClass::Numeric {
822 return RuleResult::NoBreak;
823 }
824 }
825 _ => {}
826 }
827 }
828 _ => {}
829 }
830
831 if state.current.line_break_class == LineBreakClass::PrefixNumeric {
832 match state.next_class() {
833 LineBreakClass::OpenPunctuation => {
834 let after_next = LineBreakState::after(text, state.next(text));
835
836 if after_next.line_break_class == LineBreakClass::Numeric {
838 return RuleResult::NoBreak;
839 }
840
841 if after_next.line_break_class == LineBreakClass::InfixNumeric
843 && LineBreakState::after(text, after_next).line_break_class == LineBreakClass::Numeric
844 {
845 return RuleResult::NoBreak;
846 }
847 }
848 LineBreakClass::Numeric => {
850 return RuleResult::NoBreak;
851 }
852 _ => {}
853 }
854 }
855
856 if state.current.line_break_class == LineBreakClass::PostfixNumeric {
857 match state.next_class() {
858 LineBreakClass::OpenPunctuation => {
859 let after_next = LineBreakState::after(text, state.next(text));
860
861 if after_next.line_break_class == LineBreakClass::Numeric {
863 return RuleResult::NoBreak;
864 }
865
866 if after_next.line_break_class == LineBreakClass::InfixNumeric
868 && LineBreakState::after(text, after_next).line_break_class == LineBreakClass::Numeric
869 {
870 return RuleResult::NoBreak;
871 }
872 }
873 LineBreakClass::Numeric => {
875 return RuleResult::NoBreak;
876 }
877 _ => {}
878 }
879 }
880
881 if matches!(state.current.line_break_class, LineBreakClass::Hyphen | LineBreakClass::InfixNumeric)
884 && state.next_class() == LineBreakClass::Numeric
885 {
886 return RuleResult::NoBreak;
887 }
888
889 RuleResult::Pass
890}
891
892fn lb26(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
894 match state.current.line_break_class {
895 LineBreakClass::JL => {
896 if matches!(
898 state.next_class(),
899 LineBreakClass::JL | LineBreakClass::JV | LineBreakClass::H2 | LineBreakClass::H3
900 ) {
901 return RuleResult::NoBreak;
902 }
903 }
904 LineBreakClass::JV | LineBreakClass::H2 => {
905 if matches!(state.next_class(), LineBreakClass::JV | LineBreakClass::JT) {
907 return RuleResult::NoBreak;
908 }
909 }
910 LineBreakClass::JT | LineBreakClass::H3 => {
911 if state.next_class() == LineBreakClass::JT {
913 return RuleResult::NoBreak;
914 }
915 }
916 _ => {}
917 }
918
919 RuleResult::Pass
920}
921
922fn lb27(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
924 match state.current.line_break_class {
925 LineBreakClass::JL | LineBreakClass::JV | LineBreakClass::JT | LineBreakClass::H2 | LineBreakClass::H3 => {
926 if state.next_class() == LineBreakClass::PostfixNumeric {
928 return RuleResult::NoBreak;
929 }
930 }
931 LineBreakClass::PrefixNumeric => {
932 if matches!(
934 state.next_class(),
935 LineBreakClass::JL | LineBreakClass::JV | LineBreakClass::JT | LineBreakClass::H2 | LineBreakClass::H3
936 ) {
937 return RuleResult::NoBreak;
938 }
939 }
940 _ => {}
941 }
942
943 RuleResult::Pass
944}
945
946#[inline(always)]
948fn lb28(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
949 if matches!(state.current.line_break_class, LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter)
951 && matches!(state.next_class(), LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter)
952 {
953 return RuleResult::NoBreak;
954 }
955
956 RuleResult::Pass
957}
958
959fn lb28a(text: &[u16], state: &mut LineBreakState) -> RuleResult {
961 fn is_match(chr: BreakUnit) -> bool {
963 (chr.line_break_class == LineBreakClass::Aksara)
964 || (chr.codepoint.value() == DOT_CIRCLE)
965 || (chr.line_break_class == LineBreakClass::AksaraStart)
966 }
967
968 let current = if state.current.inherited { state.last_before_whitespace } else { state.current };
969 let previous = if state.current.inherited { LineBreakState::before(text, current) } else { state.previous() };
970
971 if (current.line_break_class == LineBreakClass::AksaraPrebase) && is_match(state.next(text)) {
973 return RuleResult::NoBreak;
974 }
975
976 if is_match(current)
978 && ((state.next_class() == LineBreakClass::ViramaFinal) || (state.next_class() == LineBreakClass::Virama))
979 {
980 return RuleResult::NoBreak;
981 }
982
983 if is_match(previous)
985 && current.line_break_class == LineBreakClass::Virama
986 && ((state.next_class() == LineBreakClass::Aksara) || (state.next(text).codepoint.value() == DOT_CIRCLE))
987 {
988 return RuleResult::NoBreak;
989 }
990
991 if is_match(current)
993 && is_match(state.next(text))
994 && (LineBreakState::after(text, state.next(text)).line_break_class == LineBreakClass::ViramaFinal)
995 {
996 return RuleResult::NoBreak;
997 }
998
999 RuleResult::Pass
1000}
1001
1002#[inline(always)]
1004fn lb29(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
1005 if (state.current.line_break_class == LineBreakClass::InfixNumeric)
1007 && ((state.next_class() == LineBreakClass::Alphabetic) || (state.next_class() == LineBreakClass::HebrewLetter))
1008 {
1009 return RuleResult::NoBreak;
1010 }
1011
1012 RuleResult::Pass
1013}
1014
1015fn lb30(text: &[u16], state: &mut LineBreakState) -> RuleResult {
1017 match state.current.line_break_class {
1018 LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter | LineBreakClass::Numeric => {
1019 let next = state.next(text);
1020
1021 if (next.line_break_class == LineBreakClass::OpenPunctuation) && !next.codepoint.is_east_asian() {
1023 return RuleResult::NoBreak;
1024 }
1025 }
1026 LineBreakClass::CloseParenthesis => {
1027 if !state.current.codepoint.is_east_asian()
1029 && matches!(
1030 state.next_class(),
1031 LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter | LineBreakClass::Numeric
1032 )
1033 {
1034 return RuleResult::NoBreak;
1035 }
1036 }
1037 _ => {}
1038 }
1039
1040 RuleResult::Pass
1041}
1042
1043fn lb30a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
1047 if state.regional_indicator > 0
1048 && state.next_class() == LineBreakClass::RegionalIndicator
1049 && state.regional_indicator + 1 == 2
1050 {
1051 return RuleResult::NoBreak;
1052 }
1053
1054 RuleResult::Pass
1055}
1056
1057fn lb30b(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
1059 let current = if state.current.inherited { state.previous() } else { state.current };
1060
1061 if (current.line_break_class == LineBreakClass::EBase) && (state.next_class() == LineBreakClass::EModifier) {
1063 return RuleResult::NoBreak;
1064 }
1065
1066 if state.next_class() == LineBreakClass::EModifier
1075 && current.codepoint.grapheme_break_class() == GraphemeBreakClass::ExtendedPictographic
1076 && current.codepoint.general_category() == GeneralCategory::Unassigned
1077 {
1078 return RuleResult::NoBreak;
1079 }
1080
1081 RuleResult::Pass
1082}
1083
1084#[inline(always)]
1086fn lb31(_text: &[u16], _state: &mut LineBreakState) -> RuleResult {
1087 RuleResult::MayBreak
1088}
1089
1090#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1091enum RuleResult {
1092 Pass,
1093 NoBreak,
1094 MayBreak,
1095 MustBreak,
1096}
1097
1098#[derive(Clone, Copy, Debug)]
1103struct BreakUnit {
1104 start: i32,
1105 length: i32,
1106 codepoint: Codepoint,
1107 end_of_text: bool,
1108 start_of_text: bool,
1109 line_break_class: LineBreakClass,
1110 ignored: bool,
1111 inherited: bool,
1112}
1113
1114impl BreakUnit {
1115 const fn empty() -> Self {
1117 Self {
1118 start: 0,
1119 length: 0,
1120 codepoint: Codepoint::new(0),
1121 end_of_text: false,
1122 start_of_text: false,
1123 line_break_class: LineBreakClass::Unknown,
1124 ignored: false,
1125 inherited: false,
1126 }
1127 }
1128
1129 const DEFAULT: BreakUnit = BreakUnit { line_break_class: LineBreakClass::OpenPunctuation, ..Self::empty() };
1131
1132 const SOT: BreakUnit = BreakUnit { start_of_text: true, ..Self::empty() };
1133 const EOT: BreakUnit = BreakUnit { end_of_text: true, ..Self::empty() };
1134
1135 #[inline]
1136 fn new(codepoint: Codepoint, start: i32, length: i32) -> Self {
1137 Self { codepoint, start, length, line_break_class: Self::map_class(codepoint), ..Self::empty() }
1138 }
1139
1140 fn map_class(cp: Codepoint) -> LineBreakClass {
1141 if cp.value() == 327685 {
1142 return LineBreakClass::Alphabetic;
1143 }
1144
1145 let cls = cp.line_break_class();
1154
1155 const SPECIAL_MASK: u64 = (1u64 << LineBreakClass::Ambiguous as i32)
1156 | (1u64 << LineBreakClass::Surrogate as i32)
1157 | (1u64 << LineBreakClass::Unknown as i32)
1158 | (1u64 << LineBreakClass::ComplexContext as i32)
1159 | (1u64 << LineBreakClass::ConditionalJapaneseStarter as i32);
1160
1161 if ((1u64 << cls as i32) & SPECIAL_MASK) != 0 {
1162 match cls {
1163 LineBreakClass::Ambiguous | LineBreakClass::Surrogate | LineBreakClass::Unknown => {
1164 return LineBreakClass::Alphabetic;
1165 }
1166 LineBreakClass::ComplexContext => {
1167 return if matches!(
1168 cp.general_category(),
1169 GeneralCategory::NonspacingMark | GeneralCategory::SpacingMark
1170 ) {
1171 LineBreakClass::CombiningMark
1172 } else {
1173 LineBreakClass::Alphabetic
1174 };
1175 }
1176 LineBreakClass::ConditionalJapaneseStarter => {
1177 return LineBreakClass::Nonstarter;
1178 }
1179 _ => {}
1180 }
1181 }
1182
1183 cls
1184 }
1185}
1186
1187#[derive(Clone, Debug)]
1188struct LineBreakState {
1189 next: BreakUnit,
1194 has_next: bool,
1195 next_class: LineBreakClass,
1196 previous: BreakUnit,
1197 previous_class: LineBreakClass,
1198
1199 current: BreakUnit,
1200 position: i32,
1201 regional_indicator: i32,
1202 last_before_whitespace: BreakUnit,
1203 last_before_space: BreakUnit,
1204}
1205
1206impl LineBreakState {
1207 fn new() -> Self {
1208 Self {
1209 has_next: false,
1210 next: BreakUnit::DEFAULT,
1211 next_class: LineBreakClass::Unknown,
1212 previous: BreakUnit::SOT,
1213 previous_class: BreakUnit::SOT.line_break_class,
1214 current: BreakUnit::SOT,
1215 position: 0,
1216 regional_indicator: 0,
1217 last_before_space: BreakUnit::SOT,
1218 last_before_whitespace: BreakUnit::SOT,
1219 }
1220 }
1221
1222 #[inline]
1226 fn previous_class(&mut self) -> LineBreakClass {
1227 if self.previous.ignored || self.previous.inherited {
1228 self.previous = self.last_before_whitespace;
1229 self.previous_class = self.last_before_whitespace.line_break_class;
1230 }
1231 self.previous_class
1232 }
1233
1234 #[inline]
1235 fn previous(&mut self) -> BreakUnit {
1236 if self.previous.ignored || self.previous.inherited {
1237 self.previous = self.last_before_whitespace;
1238 self.previous_class = self.last_before_whitespace.line_break_class;
1239 }
1240 self.previous
1241 }
1242
1243 #[inline]
1244 fn next(&mut self, text: &[u16]) -> BreakUnit {
1245 if !self.has_next {
1246 self.next = self.peek(text);
1247 self.next_class = self.next.line_break_class;
1248 self.has_next = true;
1249 }
1250
1251 self.next
1252 }
1253
1254 #[inline(always)]
1259 fn next_class(&self) -> LineBreakClass {
1260 self.next_class
1261 }
1262
1263 fn after(text: &[u16], current: BreakUnit) -> BreakUnit {
1264 if current.end_of_text {
1265 return BreakUnit::EOT;
1266 }
1267
1268 Self::peek_at(text, current.start + current.length)
1269 }
1270
1271 fn before(text: &[u16], current: BreakUnit) -> BreakUnit {
1272 if current.start_of_text {
1273 return BreakUnit::SOT;
1274 }
1275
1276 let position = current.start - 1;
1277
1278 Self::peek_at(text, position)
1279 }
1280
1281 fn ignore_next(&mut self, text: &[u16]) {
1282 let n = BreakUnit { ignored: true, ..self.next(text) };
1283 self.next = n;
1284 self.next_class = n.line_break_class;
1285 self.has_next = true;
1286 }
1287
1288 fn replace_next(&mut self, next: BreakUnit) {
1289 self.next = next;
1290 self.next_class = next.line_break_class;
1291 self.has_next = true;
1292 }
1293
1294 fn peek_at(text: &[u16], index: i32) -> BreakUnit {
1295 if text.is_empty() {
1296 return BreakUnit { start_of_text: true, ..BreakUnit::new(Codepoint::REPLACEMENT_CODEPOINT, index, 0) };
1297 }
1298
1299 if index >= text.len() as i32 {
1300 return BreakUnit::EOT;
1301 }
1302
1303 let (codepoint, count) = read_at(text, index);
1304
1305 BreakUnit {
1306 end_of_text: index + count == text.len() as i32,
1307 start_of_text: index == 0,
1308 ..BreakUnit::new(codepoint, index, count)
1309 }
1310 }
1311
1312 #[inline]
1313 fn peek(&self, text: &[u16]) -> BreakUnit {
1314 Self::peek_at(text, self.position)
1315 }
1316
1317 fn read(&mut self, text: &[u16]) -> BreakUnit {
1318 self.previous = self.current;
1319 self.previous_class = self.current.line_break_class;
1320
1321 let next = self.next(text);
1322
1323 self.current = next;
1324
1325 self.position += next.length;
1326
1327 self.next = self.peek(text);
1330 self.next_class = self.next.line_break_class;
1331 self.has_next = true;
1332
1333 if self.previous.ignored || self.previous.inherited {
1335 self.previous = self.last_before_whitespace;
1336 self.previous_class = self.last_before_whitespace.line_break_class;
1337 }
1338
1339 if self.current.ignored {
1340 let line_break_class = self.previous().line_break_class;
1341 self.current = BreakUnit { line_break_class, inherited: true, ..self.current };
1342 }
1343
1344 let current = if self.current.inherited { self.previous() } else { self.current };
1345
1346 if !self.current.codepoint.is_white_space() {
1347 self.last_before_whitespace = current;
1348 }
1349
1350 if self.current.line_break_class != LineBreakClass::Space {
1351 self.last_before_space = current;
1352 }
1353
1354 next
1355 }
1356
1357 fn class_after_spaces(text: &[u16], current: BreakUnit) -> LineBreakClass {
1358 let position = current.start + current.length;
1359
1360 if position >= text.len() as i32 {
1361 return current.line_break_class;
1362 }
1363
1364 let mut enumerator = CodepointEnumerator::new(&text[position as usize..]);
1366
1367 loop {
1368 match enumerator.move_next() {
1369 Some(cp) => {
1370 if cp.line_break_class() != LineBreakClass::Space {
1371 return cp.line_break_class();
1372 }
1373 }
1374 None => return Codepoint::REPLACEMENT_CODEPOINT.line_break_class(),
1376 }
1377 }
1378 }
1379}