1#![no_std]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4
5use core::iter::FusedIterator;
8use core::str::CharIndices;
9
10mod unicode_tables;
11
12pub const UNICODE_VERSION: (u8, u8, u8) = (17, 0, 0);
14
15#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
17#[non_exhaustive]
18#[repr(u8)]
19pub enum BreakClass {
20 Ambiguous,
22 Aksara,
24 Alphabetic,
26 AksaraPrebase,
28 AksaraStart,
30 BreakBoth,
32 BreakAfter,
34 BreakBefore,
36 MandatoryBreak,
38 ContingentBreak,
40 ConditionalJapaneseStarter,
42 ClosePunctuation,
44 CombiningMark,
46 CloseParenthesis,
48 CarriageReturn,
50 EBase,
52 EModifier,
54 Exclamation,
56 Glue,
58 H2,
60 H3,
62 UnambiguousHyphen,
64 HebrewLetter,
66 Hyphen,
68 Ideographic,
70 Inseparable,
72 InfixNumeric,
74 JL,
76 JT,
78 JV,
80 LineFeed,
82 NextLine,
84 Nonstarter,
86 Numeric,
88 OpenPunctuation,
90 PostfixNumeric,
92 PrefixNumeric,
94 Quotation,
96 RegionalIndicator,
98 ComplexContext,
100 Surrogate,
102 Space,
104 BreakSymbols,
106 ViramaFinal,
108 Virama,
110 WordJoiner,
112 Unknown,
114 ZWSpace,
116 ZWJ,
118}
119
120#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
122pub enum BreakOpportunity {
123 Mandatory,
125 Allowed,
127}
128
129pub fn break_property(codepoint: u32) -> BreakClass {
133 property(codepoint).class.public()
134}
135
136#[derive(Clone, Debug)]
138pub struct Linebreaks<'a> {
139 inner: LinebreaksIter<usize, CharIndices<'a>>,
140}
141
142impl Iterator for Linebreaks<'_> {
143 type Item = (usize, BreakOpportunity);
144
145 fn next(&mut self) -> Option<Self::Item> {
146 self.inner.next()
147 }
148
149 fn size_hint(&self) -> (usize, Option<usize>) {
150 self.inner.size_hint()
151 }
152}
153
154impl FusedIterator for Linebreaks<'_> {}
155
156pub fn linebreaks(text: &str) -> Linebreaks<'_> {
158 Linebreaks {
159 inner: linebreaks_iter(text.char_indices(), text.len()),
160 }
161}
162
163#[derive(Clone, Debug)]
168pub struct LinebreaksIter<I, It>
169where
170 It: Iterator<Item = (I, char)> + Clone,
171 I: Clone,
172{
173 chars: It,
174 eot: I,
175 state: Option<State>,
176 finished: bool,
177}
178
179pub fn linebreaks_iter<I, It>(chars: It, eot: I) -> LinebreaksIter<I, It>
181where
182 It: Iterator<Item = (I, char)> + Clone,
183 I: Clone,
184{
185 LinebreaksIter {
186 chars,
187 eot,
188 state: None,
189 finished: false,
190 }
191}
192
193impl<I, It> Iterator for LinebreaksIter<I, It>
194where
195 It: Iterator<Item = (I, char)> + Clone,
196 I: Clone,
197{
198 type Item = (I, BreakOpportunity);
199
200 fn next(&mut self) -> Option<Self::Item> {
201 if self.finished {
202 return None;
203 }
204
205 if self.state.is_none() {
206 let (_, first) = match self.chars.next() {
207 Some(first) => first,
208 None => {
209 self.finished = true;
210 return None;
211 }
212 };
213 let raw = resolved_property(first as u32);
214 let significant = normalize(raw, None).expect("initial property is significant");
215 self.state = Some(State::new(significant, raw.class == Class::Zwj));
216 }
217
218 loop {
219 let (index, character) = match self.chars.next() {
220 Some(value) => value,
221 None => {
222 self.finished = true;
223 return Some((self.eot.clone(), BreakOpportunity::Mandatory));
224 }
225 };
226 let raw = resolved_property(character as u32);
227 let state = self.state.as_mut().expect("state was initialized");
228 let right = match normalize(raw, Some(state.last)) {
229 Some(right) => right,
230 None => {
231 state.last_was_zwj = raw.class == Class::Zwj;
232 continue;
233 }
234 };
235 let raw_zwj = raw.class == Class::Zwj;
236 let opportunity = match decide_pair(state, right, raw_zwj) {
237 Ok(opportunity) => opportunity,
238 Err(()) => {
239 let lookahead = if needs_lookahead(state, right) {
240 significant_lookahead(self.chars.clone(), right)
241 } else {
242 [None, None]
243 };
244 let opportunity = decide(state, right, lookahead);
245 state.push(right, raw_zwj);
246 opportunity
247 }
248 };
249 if let Some(opportunity) = opportunity {
250 return Some((index, opportunity));
251 }
252 }
253 }
254
255 fn size_hint(&self) -> (usize, Option<usize>) {
256 if self.finished {
257 (0, Some(0))
258 } else {
259 (
260 0,
261 self.chars
262 .size_hint()
263 .1
264 .and_then(|count| count.checked_add(1)),
265 )
266 }
267 }
268}
269
270impl<I, It> FusedIterator for LinebreaksIter<I, It>
271where
272 It: Iterator<Item = (I, char)> + Clone + FusedIterator,
273 I: Clone,
274{
275}
276
277pub fn split_at_safe(text: &str) -> (&str, &str) {
282 let split = text
283 .char_indices()
284 .rev()
285 .find(|&(index, character)| {
286 index < text.len() && is_safe_restart(property(character as u32))
287 })
288 .map(|(index, _)| index)
289 .unwrap_or(0);
290 text.split_at(split)
291}
292
293#[derive(Copy, Clone, Debug, Eq, PartialEq)]
294struct Property {
295 class: Class,
296 flags: u8,
297}
298
299impl Property {
300 const EAST_ASIAN: u8 = 1;
301 const PI: u8 = 2;
302 const PF: u8 = 4;
303 const EXTENDED_PICTOGRAPHIC_CN: u8 = 8;
304 const DOTTED_CIRCLE: u8 = 16;
305 const COMBINING_CATEGORY: u8 = 32;
306
307 fn has(self, flag: u8) -> bool {
308 self.flags & flag != 0
309 }
310
311 fn is_aksara_or_dotted(self) -> bool {
312 matches!(self.class, Class::Ak | Class::As) || self.has(Self::DOTTED_CIRCLE)
313 }
314}
315
316#[derive(Copy, Clone, Debug, Eq, PartialEq)]
317#[repr(u8)]
318enum Class {
319 Ambiguous,
320 Ak,
321 Al,
322 Ap,
323 As,
324 B2,
325 Ba,
326 Bb,
327 Bk,
328 Cb,
329 Cj,
330 Cl,
331 Cm,
332 Cp,
333 Cr,
334 Eb,
335 Em,
336 Ex,
337 Gl,
338 H2,
339 H3,
340 Hh,
341 Hl,
342 Hy,
343 Id,
344 In,
345 Is,
346 Jl,
347 Jt,
348 Jv,
349 Lf,
350 Nl,
351 Ns,
352 Nu,
353 Op,
354 Po,
355 Pr,
356 Qu,
357 Ri,
358 Sa,
359 Sg,
360 Sp,
361 Sy,
362 Vf,
363 Vi,
364 Wj,
365 Xx,
366 Zw,
367 Zwj,
368}
369
370impl Class {
371 fn from_u8(value: u8) -> Self {
372 match value {
373 0 => Self::Ambiguous,
374 1 => Self::Ak,
375 2 => Self::Al,
376 3 => Self::Ap,
377 4 => Self::As,
378 5 => Self::B2,
379 6 => Self::Ba,
380 7 => Self::Bb,
381 8 => Self::Bk,
382 9 => Self::Cb,
383 10 => Self::Cj,
384 11 => Self::Cl,
385 12 => Self::Cm,
386 13 => Self::Cp,
387 14 => Self::Cr,
388 15 => Self::Eb,
389 16 => Self::Em,
390 17 => Self::Ex,
391 18 => Self::Gl,
392 19 => Self::H2,
393 20 => Self::H3,
394 21 => Self::Hh,
395 22 => Self::Hl,
396 23 => Self::Hy,
397 24 => Self::Id,
398 25 => Self::In,
399 26 => Self::Is,
400 27 => Self::Jl,
401 28 => Self::Jt,
402 29 => Self::Jv,
403 30 => Self::Lf,
404 31 => Self::Nl,
405 32 => Self::Ns,
406 33 => Self::Nu,
407 34 => Self::Op,
408 35 => Self::Po,
409 36 => Self::Pr,
410 37 => Self::Qu,
411 38 => Self::Ri,
412 39 => Self::Sa,
413 40 => Self::Sg,
414 41 => Self::Sp,
415 42 => Self::Sy,
416 43 => Self::Vf,
417 44 => Self::Vi,
418 45 => Self::Wj,
419 46 => Self::Xx,
420 47 => Self::Zw,
421 48 => Self::Zwj,
422 _ => Self::Xx,
423 }
424 }
425
426 fn public(self) -> BreakClass {
427 match self {
428 Self::Ambiguous => BreakClass::Ambiguous,
429 Self::Ak => BreakClass::Aksara,
430 Self::Al => BreakClass::Alphabetic,
431 Self::Ap => BreakClass::AksaraPrebase,
432 Self::As => BreakClass::AksaraStart,
433 Self::B2 => BreakClass::BreakBoth,
434 Self::Ba => BreakClass::BreakAfter,
435 Self::Bb => BreakClass::BreakBefore,
436 Self::Bk => BreakClass::MandatoryBreak,
437 Self::Cb => BreakClass::ContingentBreak,
438 Self::Cj => BreakClass::ConditionalJapaneseStarter,
439 Self::Cl => BreakClass::ClosePunctuation,
440 Self::Cm => BreakClass::CombiningMark,
441 Self::Cp => BreakClass::CloseParenthesis,
442 Self::Cr => BreakClass::CarriageReturn,
443 Self::Eb => BreakClass::EBase,
444 Self::Em => BreakClass::EModifier,
445 Self::Ex => BreakClass::Exclamation,
446 Self::Gl => BreakClass::Glue,
447 Self::H2 => BreakClass::H2,
448 Self::H3 => BreakClass::H3,
449 Self::Hh => BreakClass::UnambiguousHyphen,
450 Self::Hl => BreakClass::HebrewLetter,
451 Self::Hy => BreakClass::Hyphen,
452 Self::Id => BreakClass::Ideographic,
453 Self::In => BreakClass::Inseparable,
454 Self::Is => BreakClass::InfixNumeric,
455 Self::Jl => BreakClass::JL,
456 Self::Jt => BreakClass::JT,
457 Self::Jv => BreakClass::JV,
458 Self::Lf => BreakClass::LineFeed,
459 Self::Nl => BreakClass::NextLine,
460 Self::Ns => BreakClass::Nonstarter,
461 Self::Nu => BreakClass::Numeric,
462 Self::Op => BreakClass::OpenPunctuation,
463 Self::Po => BreakClass::PostfixNumeric,
464 Self::Pr => BreakClass::PrefixNumeric,
465 Self::Qu => BreakClass::Quotation,
466 Self::Ri => BreakClass::RegionalIndicator,
467 Self::Sa => BreakClass::ComplexContext,
468 Self::Sg => BreakClass::Surrogate,
469 Self::Sp => BreakClass::Space,
470 Self::Sy => BreakClass::BreakSymbols,
471 Self::Vf => BreakClass::ViramaFinal,
472 Self::Vi => BreakClass::Virama,
473 Self::Wj => BreakClass::WordJoiner,
474 Self::Xx => BreakClass::Unknown,
475 Self::Zw => BreakClass::ZWSpace,
476 Self::Zwj => BreakClass::ZWJ,
477 }
478 }
479}
480
481#[derive(Copy, Clone, Debug)]
482struct State {
483 last: Property,
484 previous: Option<Property>,
485 space_anchor: Option<Property>,
486 pi_run: bool,
487 numeric: NumericState,
488 regional_indicator_odd: bool,
489 last_was_zwj: bool,
490}
491
492impl State {
493 #[inline(always)]
494 fn new(first: Property, raw_zwj: bool) -> Self {
495 Self {
496 last: first,
497 previous: None,
498 space_anchor: None,
499 pi_run: first.class == Class::Qu && first.has(Property::PI),
500 numeric: if first.class == Class::Nu {
501 NumericState::Number
502 } else {
503 NumericState::None
504 },
505 regional_indicator_odd: first.class == Class::Ri,
506 last_was_zwj: raw_zwj,
507 }
508 }
509
510 #[inline(always)]
511 fn push(&mut self, next: Property, raw_zwj: bool) {
512 let old_last = self.last;
513 let old_pi_run = self.pi_run;
514 if next.class == Class::Sp {
515 if old_last.class != Class::Sp {
516 self.space_anchor = Some(old_last);
517 }
518 } else {
519 self.space_anchor = None;
520 }
521 self.pi_run = if next.class == Class::Sp {
522 old_pi_run
523 } else if next.class == Class::Qu && next.has(Property::PI) {
524 pi_prefix(old_last.class)
525 } else {
526 false
527 };
528 self.numeric = match (self.numeric, next.class) {
529 (_, Class::Nu) => NumericState::Number,
530 (NumericState::Number, Class::Sy | Class::Is) => NumericState::Number,
531 (NumericState::Number, Class::Cl | Class::Cp) => NumericState::Closed,
532 _ => NumericState::None,
533 };
534 self.regional_indicator_odd = if next.class == Class::Ri {
535 !self.regional_indicator_odd
536 } else {
537 false
538 };
539 self.previous = Some(old_last);
540 self.last = next;
541 self.last_was_zwj = raw_zwj;
542 }
543
544 #[inline(always)]
545 fn push_common(&mut self, next: Property) {
546 self.last = next;
547 self.previous = None;
548 self.space_anchor = None;
549 self.pi_run = false;
550 self.numeric = NumericState::None;
551 self.regional_indicator_odd = false;
552 self.last_was_zwj = false;
553 }
554
555 #[inline(always)]
556 fn ends_with_spaced(&self, class: Class) -> bool {
557 self.last.class == class
558 || (self.last.class == Class::Sp
559 && self.space_anchor.map(|anchor| anchor.class) == Some(class))
560 }
561}
562
563#[derive(Copy, Clone, Debug, Eq, PartialEq)]
564enum NumericState {
565 None,
566 Number,
567 Closed,
568}
569
570#[inline(always)]
571fn property(codepoint: u32) -> Property {
572 if codepoint < 128 {
573 let packed = unicode_tables::ASCII_PROPERTY[codepoint as usize];
574 return Property {
575 class: Class::from_u8(packed as u8),
576 flags: (packed >> 8) as u8,
577 };
578 }
579 if (0x4e00..=0x9fff).contains(&codepoint) {
580 return Property {
581 class: Class::Id,
582 flags: Property::EAST_ASIAN,
583 };
584 }
585 if (0xac00..=0xd7a3).contains(&codepoint) {
586 return Property {
587 class: if (codepoint - 0xac00) % 28 == 0 {
588 Class::H2
589 } else {
590 Class::H3
591 },
592 flags: Property::EAST_ASIAN,
593 };
594 }
595 if matches!(
596 codepoint,
597 0x304a..=0x3062 | 0x3064..=0x3082 | 0x3084 | 0x3086 | 0x3088..=0x308d | 0x308f..=0x3094
598 ) {
599 return Property {
600 class: Class::Id,
601 flags: Property::EAST_ASIAN,
602 };
603 }
604 if codepoint == 0x3002 {
605 return Property {
606 class: Class::Cl,
607 flags: Property::EAST_ASIAN,
608 };
609 }
610 if codepoint == 0x0308 {
611 return Property {
612 class: Class::Cm,
613 flags: 0,
614 };
615 }
616 if codepoint > 0x10ffff {
617 return Property {
618 class: Class::Xx,
619 flags: 0,
620 };
621 }
622 let point = codepoint as usize;
623 let group = unicode_tables::GROUP_MAP[point >> 10] as usize;
624 let page = unicode_tables::GROUPS[group][(point >> 6) & 15] as usize;
625 let id = unicode_tables::PAGES[page][point & 63] as usize;
626 let packed = unicode_tables::PROPERTIES[id];
627 Property {
628 class: Class::from_u8(packed as u8),
629 flags: (packed >> 8) as u8,
630 }
631}
632
633#[inline(always)]
634fn resolved_property(codepoint: u32) -> Property {
635 let mut value = property(codepoint);
636 value.class = match value.class {
637 Class::Ambiguous | Class::Sg | Class::Xx => Class::Al,
638 Class::Sa if value.has(Property::COMBINING_CATEGORY) => Class::Cm,
639 Class::Sa => Class::Al,
640 Class::Cj => Class::Ns,
641 class => class,
642 };
643 value
644}
645
646#[inline(always)]
647fn normalize(value: Property, left: Option<Property>) -> Option<Property> {
648 if matches!(value.class, Class::Cm | Class::Zwj) {
649 if left
650 .map(|left| accepts_combining(left.class))
651 .unwrap_or(false)
652 {
653 return None;
654 }
655 return Some(Property {
656 class: Class::Al,
657 flags: 0,
658 });
659 }
660 Some(value)
661}
662
663#[inline(always)]
664fn accepts_combining(class: Class) -> bool {
665 !matches!(
666 class,
667 Class::Bk | Class::Cr | Class::Lf | Class::Nl | Class::Sp | Class::Zw
668 )
669}
670
671#[inline(always)]
672fn significant_lookahead<I, It>(mut chars: It, mut left: Property) -> [Option<Property>; 2]
673where
674 It: Iterator<Item = (I, char)>,
675{
676 let mut found = [None, None];
677 let mut count = 0;
678 while count < found.len() {
679 let (_, character) = match chars.next() {
680 Some(value) => value,
681 None => break,
682 };
683 let raw = resolved_property(character as u32);
684 if let Some(value) = normalize(raw, Some(left)) {
685 found[count] = Some(value);
686 left = value;
687 count += 1;
688 }
689 }
690 found
691}
692
693#[inline(always)]
694fn needs_lookahead(state: &State, right: Property) -> bool {
695 right.class == Class::Qu
696 || (state.last.class == Class::Sp && right.class == Class::Is)
697 || (matches!(state.last.class, Class::Po | Class::Pr) && right.class == Class::Op)
698 || (state.last.is_aksara_or_dotted() && right.is_aksara_or_dotted())
699}
700
701const PAIR_NO_BREAK: u8 = 0;
702const PAIR_ALLOWED: u8 = 1;
703const PAIR_MANDATORY: u8 = 2;
704const PAIR_CONTEXT: u8 = 3;
705
706static PAIR_BREAKS: [u8; 49 * 49] = build_pair_breaks();
707
708#[inline(always)]
709fn decide_pair(
710 state: &mut State,
711 right: Property,
712 raw_zwj: bool,
713) -> Result<Option<BreakOpportunity>, ()> {
714 use BreakOpportunity::{Allowed, Mandatory};
715 use Class::*;
716
717 let left = state.last;
718 if state.last_was_zwj {
719 state.push(right, raw_zwj);
720 return Ok(None);
721 }
722 if left.class == Sp {
723 let opportunity = if matches!(right.class, Bk | Cr | Lf | Nl | Sp | Zw) {
724 None
725 } else if state.ends_with_spaced(Zw) {
726 Some(Allowed)
727 } else if matches!(right.class, Wj | Cl | Cp | Ex | Sy)
728 || state.ends_with_spaced(Op)
729 || state.pi_run
730 {
731 None
732 } else if right.class == Qu {
733 if right.has(Property::PF) {
734 return Err(());
735 }
736 Some(Allowed)
737 } else if right.class == Is {
738 return Err(());
739 } else if (right.class == Ns && (state.ends_with_spaced(Cl) || state.ends_with_spaced(Cp)))
740 || (right.class == B2 && state.ends_with_spaced(B2))
741 {
742 None
743 } else {
744 Some(Allowed)
745 };
746 state.push(right, raw_zwj);
747 return Ok(opportunity);
748 }
749 if matches!(left.class, Al | Hl) && matches!(right.class, Al | Hl) {
750 state.push_common(right);
751 return Ok(None);
752 }
753 if (left.class == Id && right.class == Id)
754 || (matches!(left.class, H2 | H3) && matches!(right.class, H2 | H3))
755 {
756 state.push_common(right);
757 return Ok(Some(Allowed));
758 }
759 let opportunity = match PAIR_BREAKS[left.class as usize * 49 + right.class as usize] {
760 PAIR_NO_BREAK => None,
761 PAIR_ALLOWED => Some(Allowed),
762 PAIR_MANDATORY => Some(Mandatory),
763 _ => return Err(()),
764 };
765 state.push(right, raw_zwj);
766 Ok(opportunity)
767}
768
769const fn build_pair_breaks() -> [u8; 49 * 49] {
770 let mut table = [PAIR_CONTEXT; 49 * 49];
771 let mut left = 0;
772 while left < 49 {
773 let mut right = 0;
774 while right < 49 {
775 table[left * 49 + right] = pair_break(left as u8, right as u8);
776 right += 1;
777 }
778 left += 1;
779 }
780 table
781}
782
783const fn pair_break(left: u8, right: u8) -> u8 {
784 use Class::*;
785
786 if pair_has_context(left, right) {
787 return PAIR_CONTEXT;
788 }
789 if left == Bk as u8
790 || (left == Cr as u8 && right != Lf as u8)
791 || left == Lf as u8
792 || left == Nl as u8
793 {
794 return PAIR_MANDATORY;
795 }
796 if (left == Cr as u8 && right == Lf as u8)
797 || right == Bk as u8
798 || right == Cr as u8
799 || right == Lf as u8
800 || right == Nl as u8
801 || right == Sp as u8
802 || right == Zw as u8
803 {
804 return PAIR_NO_BREAK;
805 }
806 if left == Zw as u8 {
807 return PAIR_ALLOWED;
808 }
809 if left == Wj as u8 || right == Wj as u8 || left == Gl as u8 {
810 return PAIR_NO_BREAK;
811 }
812 if right == Gl as u8
813 && left != Sp as u8
814 && left != Ba as u8
815 && left != Hy as u8
816 && left != Hh as u8
817 {
818 return PAIR_NO_BREAK;
819 }
820 if right == Cl as u8 || right == Cp as u8 || right == Ex as u8 || right == Sy as u8 {
821 return PAIR_NO_BREAK;
822 }
823 if left == Op as u8 || right == Is as u8 {
824 return PAIR_NO_BREAK;
825 }
826 if (right == Ns as u8 && (left == Cl as u8 || left == Cp as u8))
827 || (left == B2 as u8 && right == B2 as u8)
828 {
829 return PAIR_NO_BREAK;
830 }
831 if left == Cb as u8 || right == Cb as u8 {
832 return PAIR_ALLOWED;
833 }
834 if right == Ba as u8
835 || right == Hh as u8
836 || right == Hy as u8
837 || right == Ns as u8
838 || left == Bb as u8
839 || (left == Sy as u8 && right == Hl as u8)
840 || right == In as u8
841 {
842 return PAIR_NO_BREAK;
843 }
844 if ((left == Al as u8 || left == Hl as u8) && right == Nu as u8)
845 || (left == Nu as u8 && (right == Al as u8 || right == Hl as u8))
846 || (left == Pr as u8 && (right == Id as u8 || right == Eb as u8 || right == Em as u8))
847 || ((left == Id as u8 || left == Eb as u8 || left == Em as u8) && right == Po as u8)
848 || ((left == Pr as u8 || left == Po as u8) && (right == Al as u8 || right == Hl as u8))
849 || ((left == Al as u8 || left == Hl as u8) && (right == Pr as u8 || right == Po as u8))
850 || ((left == Po as u8 || left == Pr as u8) && right == Nu as u8)
851 || ((left == Hy as u8 || left == Is as u8) && right == Nu as u8)
852 || (left == Nu as u8 && right == Nu as u8)
853 {
854 return PAIR_NO_BREAK;
855 }
856 if (left == Jl as u8
857 && (right == Jl as u8 || right == Jv as u8 || right == H2 as u8 || right == H3 as u8))
858 || ((left == Jv as u8 || left == H2 as u8) && (right == Jv as u8 || right == Jt as u8))
859 || ((left == Jt as u8 || left == H3 as u8) && right == Jt as u8)
860 || ((left == Jl as u8
861 || left == Jv as u8
862 || left == Jt as u8
863 || left == H2 as u8
864 || left == H3 as u8)
865 && right == Po as u8)
866 || (left == Pr as u8
867 && (right == Jl as u8
868 || right == Jv as u8
869 || right == Jt as u8
870 || right == H2 as u8
871 || right == H3 as u8))
872 || ((left == Al as u8 || left == Hl as u8) && (right == Al as u8 || right == Hl as u8))
873 || (left == Ap as u8 && (right == Ak as u8 || right == As as u8))
874 || ((left == Ak as u8 || left == As as u8) && (right == Vf as u8 || right == Vi as u8))
875 || (left == Is as u8 && (right == Al as u8 || right == Hl as u8))
876 || (left == Eb as u8 && right == Em as u8)
877 {
878 return PAIR_NO_BREAK;
879 }
880 PAIR_ALLOWED
881}
882
883const fn pair_has_context(left: u8, right: u8) -> bool {
884 use Class::*;
885
886 left == Sp as u8
887 || left == Qu as u8
888 || right == Qu as u8
889 || left == Hy as u8
890 || left == Hh as u8
891 || ((right == Po as u8 || right == Pr as u8)
892 && (left == Nu as u8
893 || left == Sy as u8
894 || left == Is as u8
895 || left == Cl as u8
896 || left == Cp as u8))
897 || (left == Sy as u8 && right == Nu as u8)
898 || ((left == Po as u8 || left == Pr as u8) && right == Op as u8)
899 || (left == Ri as u8 && right == Ri as u8)
900 || right == Em as u8
901 || ((left == Al as u8 || left == Hl as u8 || left == Nu as u8) && right == Op as u8)
902 || (left == Cp as u8 && (right == Al as u8 || right == Hl as u8 || right == Nu as u8))
903 || (left == Ap as u8 && right == Al as u8)
904 || ((left == Ak as u8 || left == As as u8) && right == Al as u8)
905 || (left == Al as u8
906 && (right == Ak as u8 || right == As as u8 || right == Vf as u8 || right == Vi as u8))
907 || ((left == Ak as u8 || left == As as u8) && (right == Ak as u8 || right == As as u8))
908 || (left == Vi as u8 && (right == Ak as u8 || right == As as u8 || right == Al as u8))
909 || left == Cm as u8
910 || right == Cm as u8
911 || left == Zwj as u8
912 || right == Zwj as u8
913 || left == Ambiguous as u8
914 || right == Ambiguous as u8
915 || left == Cj as u8
916 || right == Cj as u8
917 || left == Sa as u8
918 || right == Sa as u8
919 || left == Sg as u8
920 || right == Sg as u8
921 || left == Xx as u8
922 || right == Xx as u8
923}
924
925#[inline(never)]
926fn decide(state: &State, right: Property, next: [Option<Property>; 2]) -> Option<BreakOpportunity> {
927 use BreakOpportunity::{Allowed, Mandatory};
928 use Class::*;
929
930 let left = state.last;
931 if state.last_was_zwj {
932 return None;
933 }
934 if left.class == Sp {
935 if matches!(right.class, Bk | Cr | Lf | Nl | Sp | Zw) {
936 return None;
937 }
938 if state.ends_with_spaced(Zw) {
939 return Some(Allowed);
940 }
941 if matches!(right.class, Wj | Cl | Cp | Ex | Sy) {
942 return None;
943 }
944 if state.ends_with_spaced(Op) || state.pi_run {
945 return None;
946 }
947 if right.class == Qu {
948 return if right.has(Property::PF)
949 && next[0].map(|value| pf_suffix(value.class)).unwrap_or(true)
950 {
951 None
952 } else {
953 Some(Allowed)
954 };
955 }
956 if right.class == Is {
957 return if next[0].map(|value| value.class) == Some(Nu) {
958 Some(Allowed)
959 } else {
960 None
961 };
962 }
963 if (right.class == Ns && (state.ends_with_spaced(Cl) || state.ends_with_spaced(Cp)))
964 || (right.class == B2 && state.ends_with_spaced(B2))
965 {
966 return None;
967 }
968 return Some(Allowed);
969 }
970 if matches!(left.class, Al | Hl) && matches!(right.class, Al | Hl) {
971 return None;
972 }
973 if (left.class == Id && right.class == Id)
974 || (matches!(left.class, H2 | H3) && matches!(right.class, H2 | H3))
975 {
976 return Some(Allowed);
977 }
978 match PAIR_BREAKS[left.class as usize * 49 + right.class as usize] {
979 PAIR_NO_BREAK => return None,
980 PAIR_ALLOWED => return Some(Allowed),
981 PAIR_MANDATORY => return Some(Mandatory),
982 _ => {}
983 }
984 if left.class == Bk || (left.class == Cr && right.class != Lf) || matches!(left.class, Lf | Nl)
985 {
986 return Some(Mandatory);
987 }
988 if left.class == Cr && right.class == Lf {
989 return None;
990 }
991 if matches!(right.class, Bk | Cr | Lf | Nl) {
992 return None;
993 }
994 if matches!(right.class, Sp | Zw) {
995 return None;
996 }
997 if state.ends_with_spaced(Zw) {
998 return Some(Allowed);
999 }
1000 if matches!(left.class, Al | Hl) && matches!(right.class, Al | Hl) {
1001 return None;
1002 }
1003 if (matches!(left.class, Al | Hl) && right.class == Nu)
1004 || (left.class == Nu && matches!(right.class, Al | Hl | Nu))
1005 {
1006 return None;
1007 }
1008 if (left.class == Id && matches!(right.class, Al | Hl | Nu | Id))
1009 || (matches!(left.class, Al | Hl | Nu) && right.class == Id)
1010 {
1011 return Some(Allowed);
1012 }
1013 if left.class == Wj || right.class == Wj || left.class == Gl {
1014 return None;
1015 }
1016 if right.class == Gl && !matches!(left.class, Sp | Ba | Hy | Hh) {
1017 return None;
1018 }
1019 if matches!(right.class, Cl | Cp | Ex | Sy) {
1020 return None;
1021 }
1022 if state.ends_with_spaced(Op) || state.pi_run {
1023 return None;
1024 }
1025 if right.class == Qu
1026 && right.has(Property::PF)
1027 && next[0].map(|value| pf_suffix(value.class)).unwrap_or(true)
1028 {
1029 return None;
1030 }
1031 if right.class == Is {
1032 return None;
1033 }
1034 if right.class == Ns && (state.ends_with_spaced(Cl) || state.ends_with_spaced(Cp)) {
1035 return None;
1036 }
1037 if right.class == B2 && state.ends_with_spaced(B2) {
1038 return None;
1039 }
1040 if (right.class == Qu && !right.has(Property::PI))
1041 || (left.class == Qu && !left.has(Property::PF))
1042 {
1043 return None;
1044 }
1045 let right_followed_by_non_east_asian = next[0]
1046 .map(|value| !value.has(Property::EAST_ASIAN))
1047 .unwrap_or(true);
1048 if right.class == Qu && (!left.has(Property::EAST_ASIAN) || right_followed_by_non_east_asian) {
1049 return None;
1050 }
1051 if left.class == Qu
1052 && (!right.has(Property::EAST_ASIAN)
1053 || state
1054 .previous
1055 .map(|value| !value.has(Property::EAST_ASIAN))
1056 .unwrap_or(true))
1057 {
1058 return None;
1059 }
1060 if left.class == Cb || right.class == Cb {
1061 return Some(Allowed);
1062 }
1063 if matches!(left.class, Hy | Hh)
1064 && matches!(right.class, Al | Hl)
1065 && state
1066 .previous
1067 .map(|value| hyphen_prefix(value.class))
1068 .unwrap_or(true)
1069 {
1070 return None;
1071 }
1072 if matches!(right.class, Ba | Hh | Hy | Ns) || left.class == Bb {
1073 return None;
1074 }
1075 if matches!(left.class, Hy | Hh)
1076 && state.previous.map(|value| value.class) == Some(Hl)
1077 && right.class != Hl
1078 {
1079 return None;
1080 }
1081 if left.class == Sy && right.class == Hl {
1082 return None;
1083 }
1084 if right.class == In {
1085 return None;
1086 }
1087 if (matches!(left.class, Al | Hl) && right.class == Nu)
1088 || (left.class == Nu && matches!(right.class, Al | Hl))
1089 {
1090 return None;
1091 }
1092 if (left.class == Pr && matches!(right.class, Id | Eb | Em))
1093 || (matches!(left.class, Id | Eb | Em) && right.class == Po)
1094 {
1095 return None;
1096 }
1097 if (matches!(left.class, Pr | Po) && matches!(right.class, Al | Hl))
1098 || (matches!(left.class, Al | Hl) && matches!(right.class, Pr | Po))
1099 {
1100 return None;
1101 }
1102 if matches!(right.class, Po | Pr)
1103 && matches!(state.numeric, NumericState::Number | NumericState::Closed)
1104 {
1105 return None;
1106 }
1107 if matches!(left.class, Po | Pr)
1108 && (right.class == Nu
1109 || (right.class == Op
1110 && (next[0].map(|value| value.class) == Some(Nu)
1111 || (next[0].map(|value| value.class) == Some(Is)
1112 && next[1].map(|value| value.class) == Some(Nu)))))
1113 {
1114 return None;
1115 }
1116 if (matches!(left.class, Hy | Is) && right.class == Nu)
1117 || (state.numeric == NumericState::Number && right.class == Nu)
1118 {
1119 return None;
1120 }
1121 if (left.class == Jl && matches!(right.class, Jl | Jv | H2 | H3))
1122 || (matches!(left.class, Jv | H2) && matches!(right.class, Jv | Jt))
1123 || (matches!(left.class, Jt | H3) && right.class == Jt)
1124 {
1125 return None;
1126 }
1127 if (matches!(left.class, Jl | Jv | Jt | H2 | H3) && right.class == Po)
1128 || (left.class == Pr && matches!(right.class, Jl | Jv | Jt | H2 | H3))
1129 {
1130 return None;
1131 }
1132 if matches!(left.class, Al | Hl) && matches!(right.class, Al | Hl) {
1133 return None;
1134 }
1135 if (left.class == Ap && right.is_aksara_or_dotted())
1136 || (left.is_aksara_or_dotted() && matches!(right.class, Vf | Vi))
1137 || (state
1138 .previous
1139 .map(Property::is_aksara_or_dotted)
1140 .unwrap_or(false)
1141 && left.class == Vi
1142 && right.is_aksara_or_dotted())
1143 || (left.is_aksara_or_dotted()
1144 && right.is_aksara_or_dotted()
1145 && next[0].map(|value| value.class) == Some(Vf))
1146 {
1147 return None;
1148 }
1149 if left.class == Is && matches!(right.class, Al | Hl) {
1150 return None;
1151 }
1152 if (matches!(left.class, Al | Hl | Nu) && right.class == Op && !right.has(Property::EAST_ASIAN))
1153 || (left.class == Cp
1154 && !left.has(Property::EAST_ASIAN)
1155 && matches!(right.class, Al | Hl | Nu))
1156 {
1157 return None;
1158 }
1159 if left.class == Ri && right.class == Ri && state.regional_indicator_odd {
1160 return None;
1161 }
1162 if (left.class == Eb || left.has(Property::EXTENDED_PICTOGRAPHIC_CN)) && right.class == Em {
1163 return None;
1164 }
1165 Some(Allowed)
1166}
1167
1168fn pi_prefix(class: Class) -> bool {
1169 matches!(
1170 class,
1171 Class::Bk
1172 | Class::Cr
1173 | Class::Lf
1174 | Class::Nl
1175 | Class::Op
1176 | Class::Qu
1177 | Class::Gl
1178 | Class::Sp
1179 | Class::Zw
1180 )
1181}
1182
1183fn pf_suffix(class: Class) -> bool {
1184 matches!(
1185 class,
1186 Class::Sp
1187 | Class::Gl
1188 | Class::Wj
1189 | Class::Cl
1190 | Class::Qu
1191 | Class::Cp
1192 | Class::Ex
1193 | Class::Is
1194 | Class::Sy
1195 | Class::Bk
1196 | Class::Cr
1197 | Class::Lf
1198 | Class::Nl
1199 | Class::Zw
1200 )
1201}
1202
1203fn hyphen_prefix(class: Class) -> bool {
1204 matches!(
1205 class,
1206 Class::Bk
1207 | Class::Cr
1208 | Class::Lf
1209 | Class::Nl
1210 | Class::Sp
1211 | Class::Zw
1212 | Class::Cb
1213 | Class::Gl
1214 )
1215}
1216
1217fn is_safe_restart(value: Property) -> bool {
1218 unicode_tables::SAFE_RESTART_CLASSES & (1_u64 << value.class as u8) != 0
1219 && value.flags & unicode_tables::UNSAFE_RESTART_FLAGS == 0
1220}
1221
1222#[cfg(test)]
1223#[path = "unit_tests.rs"]
1224mod unit_tests;