Skip to main content

ferroui_base/media/text_formatting/unicode/
line_break_enumerator.rs

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/// Enumerates the line break opportunities of UTF-16 text (UAX #14).
11#[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    /// The text being enumerated (a public field upstream).
24    #[inline]
25    pub const fn text(&self) -> &'a [u16] {
26        self.text
27    }
28
29    /// Moves to the next [`LineBreak`]; returns `None` when there are no more.
30    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/// `Codepoint.ReadAt` for the signed indices this file computes: a negative
68/// index is outside of the text, exactly as upstream's unsigned comparison
69/// treats it.
70#[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    // Rules are invoked directly (not through a function table) so the compiler can
128    // inline trivial early-exit rules and eliminate per-rule indirect-call
129    // overhead. Order is significant: LB21a must run before LB21.
130    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) // always returns MayBreak
182    };
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/// LB3: Always break at the end of text.
210#[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/// LB4: Always break after hard line breaks.
220#[inline(always)]
221fn lb04(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
222    // BK !
223    if state.current.line_break_class == LineBreakClass::MandatoryBreak {
224        return RuleResult::MustBreak;
225    }
226
227    RuleResult::Pass
228}
229
230/// LB5: Treat CR followed by LF, as well as CR, LF, and NL as hard line
231fn 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; // CR × LF
236            }
237
238            RuleResult::MustBreak // CR !
239        }
240        // LF !
241        // NL !
242        LineBreakClass::LineFeed | LineBreakClass::NextLine => RuleResult::MustBreak,
243        _ => RuleResult::Pass,
244    }
245}
246
247/// LB6: Do not break before hard line breaks.
248#[inline(always)]
249fn lb06(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
250    // × ( BK | CR | LF | NL )
251    if is_break_class(state.next_class()) {
252        return RuleResult::NoBreak;
253    }
254
255    RuleResult::Pass
256}
257
258/// LB7: Do not break before spaces or zero width space.
259#[inline(always)]
260fn lb07(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
261    // × SP
262    // × ZW
263    match state.next_class() {
264        LineBreakClass::Space | LineBreakClass::ZWSpace => RuleResult::NoBreak,
265        _ => RuleResult::Pass,
266    }
267}
268
269/// LB8: Break before any character following a zero-width space, even if one or more spaces intervene.
270fn 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/// LB8a: Do not break after a zero width joiner.
279#[inline(always)]
280fn lb08a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
281    // ZWJ ×
282    if state.current.line_break_class == LineBreakClass::ZWJ {
283        return RuleResult::NoBreak;
284    }
285
286    RuleResult::Pass
287}
288
289/// LB9: Do not break a combining character sequence;
290/// treat it as if it has the line breaking class of the base character in all of the following rules.
291/// Treat ZWJ as if it were CM.
292fn lb09(text: &[u16], state: &mut LineBreakState) -> RuleResult {
293    // Treat X (CM | ZWJ)* as if it were X.
294    // where X is any line break class except BK, CR, LF, NL, SP, or ZW.
295    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
309/// LB10: Treat any remaining combining mark or ZWJ as AL.
310fn 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/// LB11: Do not break before or after Word joiner and related characters.
325#[inline(always)]
326fn lb11(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
327    if state.next_class() == LineBreakClass::WordJoiner /* × WJ */
328        || state.current.line_break_class == LineBreakClass::WordJoiner
329    /* WJ × */
330    {
331        return RuleResult::NoBreak;
332    }
333
334    RuleResult::Pass
335}
336
337/// LB12: Do not break after NBSP and related characters.
338#[inline(always)]
339fn lb12(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
340    // GL ×
341    if state.current.line_break_class == LineBreakClass::Glue {
342        return RuleResult::NoBreak;
343    }
344
345    RuleResult::Pass
346}
347
348/// LB12a: Do not break before NBSP and related characters, except after spaces and hyphens.
349fn lb12a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
350    // [^SP BA HY] × GL
351    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/// LB13: Do not break before ‘]’ or ‘!’ or ‘;’ or ‘/’, even after spaces.
365#[inline(always)]
366fn lb13(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
367    // × CL
368    // × CP
369    // × EX
370    // × SY
371    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/// LB14: Do not break after ‘[’, even after spaces.
381#[inline(always)]
382fn lb14(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
383    // OP SP* ×
384    if state.last_before_whitespace.line_break_class == LineBreakClass::OpenPunctuation {
385        return RuleResult::NoBreak;
386    }
387
388    RuleResult::Pass
389}
390
391/// LB15a: Do not break after an unresolved initial punctuation that lies at the start of the line,
392/// after a space, after opening punctuation, or after an unresolved quotation mark, even after spaces.
393fn 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    // (sot | BK | CR | LF | NL | OP | QU | GL | SP | ZW) [\p{Pi}&QU] SP* ×
410    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
420/// LB15b: Do not break before an unresolved final punctuation that lies at the end of the line,
421/// before a space, before a prohibited break, or before an unresolved quotation mark, even after spaces.
422fn lb15b(text: &[u16], state: &mut LineBreakState) -> RuleResult {
423    // × [\p{Pf}&QU] ( SP | GL | WJ | CL | QU | CP | EX | IS | SY | BK | CR | LF | NL | ZW | eot)
424    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            // Only on eot
431            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
458/// LB15c: Break before a decimal mark that follows a space, for instance, in ‘subtract .5’.
459fn lb15c(text: &[u16], state: &mut LineBreakState) -> RuleResult {
460    // SP ÷ IS NU
461    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/// LB15d: Otherwise, do not break before ‘;’, ‘,’, or ‘.’, even after spaces.
472#[inline(always)]
473fn lb15d(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
474    // × IS
475    if state.next_class() == LineBreakClass::InfixNumeric {
476        return RuleResult::NoBreak;
477    }
478
479    RuleResult::Pass
480}
481
482/// LB16: Do not break between closing punctuation and a nonstarter (lb=NS),
483/// even with intervening spaces.
484fn 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
499/// LB17: Do not break within ‘——’, even with intervening spaces.
500fn lb17(text: &[u16], state: &mut LineBreakState) -> RuleResult {
501    // B2 SP* × B2
502    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/// LB18: Break after spaces.
512#[inline(always)]
513fn lb18(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
514    // SP ÷
515    if state.current.line_break_class == LineBreakClass::Space {
516        return RuleResult::MayBreak;
517    }
518
519    RuleResult::Pass
520}
521
522/// LB19: Do not break before or after quotation marks.
523fn 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/// LB20: Break before and after unresolved CB.
566#[inline(always)]
567fn lb20(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
568    // ÷ CB
569    // CB ÷
570    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
579/// LB20a: Do not break after a word-initial hyphen.
580fn 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    // (sot | BK | CR | LF | NL | SP | ZW | CB | GL)(HY | HH) × (AL | HL)
600    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
610/// LB21: Do not break before hyphen-minus, other hyphens, fixed-width spaces, small kana, and other non-starters, or after acute accents.
611fn lb21(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
612    // × (BA | HY | NS)
613    if matches!(
614        state.next_class(),
615        // [21.01]
616        LineBreakClass::BreakAfter
617        // [21.02]
618        | LineBreakClass::UnambiguousHyphen
619        // [21.01]
620        | LineBreakClass::Hyphen
621        // [21.01]
622        | LineBreakClass::Nonstarter
623    ) {
624        return RuleResult::NoBreak;
625    }
626
627    // [21.04] BB ×
628    if state.current.line_break_class == LineBreakClass::BreakBefore {
629        return RuleResult::NoBreak;
630    }
631
632    RuleResult::Pass
633}
634
635/// LB21a: Don't break after Hebrew + Hyphen.
636fn lb21a(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
637    if state.next_class() != LineBreakClass::HebrewLetter {
638        // [21.1] HL(HY|HH) × [^HL]
639        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/// LB21b: Don’t break between Solidus and Hebrew letters.
650#[inline(always)]
651fn lb21b(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
652    // [21.2] SY × HL
653    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/// LB22: Do not break before ellipses.
662#[inline(always)]
663fn lb22(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
664    // × IN
665    if state.next_class() == LineBreakClass::Inseparable {
666        return RuleResult::NoBreak;
667    }
668
669    RuleResult::Pass
670}
671
672/// LB23: Do not break between digits and letters.
673fn lb23(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
674    match state.current.line_break_class {
675        LineBreakClass::Alphabetic | LineBreakClass::HebrewLetter => {
676            // (AL | HL) × NU
677            if state.next_class() == LineBreakClass::Numeric {
678                return RuleResult::NoBreak;
679            }
680        }
681        LineBreakClass::Numeric => {
682            // NU × (AL | HL)
683            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/// LB23a: Do not break between numeric prefixes and ideographs, or between
704/// ideographs and numeric postfixes.
705#[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    // LB23a: PR × (ID | EB | EM)
711    if current_class == LineBreakClass::PrefixNumeric && is_id_eb_em(next_class) {
712        return RuleResult::NoBreak;
713    }
714
715    //  (ID | EB | EM) × PO
716    if next_class == LineBreakClass::PostfixNumeric && is_id_eb_em(current_class) {
717        return RuleResult::NoBreak;
718    }
719
720    RuleResult::Pass
721}
722
723/// LB24: Do not break between numeric prefix/postfix and letters, or between
724/// letters and prefix/postfix.
725#[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    // LB24: (PR | PO) × (AL | HL);
731    if is_pr_po(current_class) && (next_class == LineBreakClass::Alphabetic || next_class == LineBreakClass::HebrewLetter) {
732        return RuleResult::NoBreak;
733    }
734
735    // (AL | HL) × (PR | PO)
736    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
744/// LB25: Do not break between the following pairs of classes relevant to numbers
745fn lb25(text: &[u16], state: &mut LineBreakState) -> RuleResult {
746    match state.next_class() {
747        // [25.06] NU(SY|IS)* x PR
748        LineBreakClass::PrefixNumeric => {
749            match state.current.line_break_class {
750                // [25.04] NU(SY|IS)* CP × PR
751                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                // [25.03] NU(SY|IS)* CL × PR
772                LineBreakClass::ClosePunctuation => match state.previous_class() {
773                    LineBreakClass::Numeric => {
774                        return RuleResult::NoBreak;
775                    }
776                    LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
777                        // Upstream re-tests the previous class here (it can never be
778                        // Numeric in this arm); kept as is.
779                        if state.previous_class() == LineBreakClass::Numeric {
780                            return RuleResult::NoBreak;
781                        }
782                    }
783                    _ => {}
784                },
785                _ => {}
786            }
787        }
788        // [25.15] NU(SY|IS)* ×	NU
789        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                // [25.01] NU(SY|IS)* CL × PO
803                LineBreakClass::ClosePunctuation => match state.previous_class() {
804                    LineBreakClass::Numeric => {
805                        return RuleResult::NoBreak;
806                    }
807                    LineBreakClass::BreakSymbols | LineBreakClass::InfixNumeric => {
808                        // Upstream re-tests the previous class here (it can never be
809                        // Numeric in this arm); kept as is.
810                        if state.previous_class() == LineBreakClass::Numeric {
811                            return RuleResult::NoBreak;
812                        }
813                    }
814                    _ => {}
815                },
816                // [25.05] NU(SY|IS)* ×	PO
817                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                // [25.1] PR × OP NU
837                if after_next.line_break_class == LineBreakClass::Numeric {
838                    return RuleResult::NoBreak;
839                }
840
841                // PR × OP IS NU
842                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            // PR × NU
849            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                // PO × OP NU
862                if after_next.line_break_class == LineBreakClass::Numeric {
863                    return RuleResult::NoBreak;
864                }
865
866                // PO × OP IS NU
867                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            // PO × NU
874            LineBreakClass::Numeric => {
875                return RuleResult::NoBreak;
876            }
877            _ => {}
878        }
879    }
880
881    // HY × NU
882    // [25.14] IS × NU
883    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
892/// LB26: Do not break a Korean syllable.
893fn lb26(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
894    match state.current.line_break_class {
895        LineBreakClass::JL => {
896            // JL × (JL | JV | H2 | H3)
897            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            // (JV | H2) × (JV | JT)
906            if matches!(state.next_class(), LineBreakClass::JV | LineBreakClass::JT) {
907                return RuleResult::NoBreak;
908            }
909        }
910        LineBreakClass::JT | LineBreakClass::H3 => {
911            // (JT | H3) × JT
912            if state.next_class() == LineBreakClass::JT {
913                return RuleResult::NoBreak;
914            }
915        }
916        _ => {}
917    }
918
919    RuleResult::Pass
920}
921
922/// LB27: Treat a Korean Syllable Block the same as ID.
923fn 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            // (JL | JV | JT | H2 | H3) × PO
927            if state.next_class() == LineBreakClass::PostfixNumeric {
928                return RuleResult::NoBreak;
929            }
930        }
931        LineBreakClass::PrefixNumeric => {
932            // PR × (JL | JV | JT | H2 | H3)
933            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/// LB28: Do not break between alphabetics (“at”).
947#[inline(always)]
948fn lb28(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
949    // [28.0] (AL | HL) × (AL | HL)
950    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
959/// LB28a: Do not break inside the orthographic syllables of Brahmic scripts.
960fn lb28a(text: &[u16], state: &mut LineBreakState) -> RuleResult {
961    // (AK | DottedCircle | AS)
962    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    // [28.11] AP × (AK | DottedCircle | AS)
972    if (current.line_break_class == LineBreakClass::AksaraPrebase) && is_match(state.next(text)) {
973        return RuleResult::NoBreak;
974    }
975
976    // [28.12] (AK | DottedCircle | AS) × (VF | VI)
977    if is_match(current)
978        && ((state.next_class() == LineBreakClass::ViramaFinal) || (state.next_class() == LineBreakClass::Virama))
979    {
980        return RuleResult::NoBreak;
981    }
982
983    // [28.13] (AK | DottedCircle| AS) VI × (AK | DottedCircle)
984    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    // [28.14] (AK | DottedCircle | AS) × (AK | DottedCircle | AS) VF
992    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/// LB29: Do not break between numeric punctuation and alphabetics (“e.g.”).
1003#[inline(always)]
1004fn lb29(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
1005    // IS × (AL | HL)
1006    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
1015/// LB30: Do not break between letters, numbers, or ordinary symbols and opening or closing parentheses.
1016fn 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            // (AL | HL | NU) × [OP-[\p{ea=F}\p{ea=W}\p{ea=H}]]
1022            if (next.line_break_class == LineBreakClass::OpenPunctuation) && !next.codepoint.is_east_asian() {
1023                return RuleResult::NoBreak;
1024            }
1025        }
1026        LineBreakClass::CloseParenthesis => {
1027            // [CP-[\p{ea=F}\p{ea=W}\p{ea=H}]] × (AL | HL | NU)
1028            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
1043/// LB30a: Break between two regional indicator symbols if and only if there
1044/// are an even number of regional indicators preceding the position of the
1045/// break.
1046fn 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
1057/// LB30b: Do not break between an emoji base (or potential emoji) and an emoji modifier.
1058fn lb30b(_text: &[u16], state: &mut LineBreakState) -> RuleResult {
1059    let current = if state.current.inherited { state.previous() } else { state.current };
1060
1061    // EB × EM
1062    if (current.line_break_class == LineBreakClass::EBase) && (state.next_class() == LineBreakClass::EModifier) {
1063        return RuleResult::NoBreak;
1064    }
1065
1066    // [\p{Extended_Pictographic}&&\p{Cn}] × EM
1067    //
1068    // The Extended_Pictographic property is used to customize segmentation (as
1069    // described in [UAX29] and [UAX14]) so that possible future emoji ZWJ
1070    // sequences will not break grapheme clusters, words, or lines. Unassigned
1071    // codepoints with Line_Break=ID in some blocks are also assigned the
1072    // Extended_Pictographic property. Those blocks are intended for future
1073    // allocation of emoji characters.
1074    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/// LB31: Break everywhere else.
1085#[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/// One codepoint of the text with its resolved line break class.
1099///
1100/// `start` and `length` are signed like upstream: looking before the first
1101/// unit yields a unit that starts at -1.
1102#[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    /// The parameterless constructor upstream: everything zero, class Unknown.
1116    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    /// `default(BreakUnit)` upstream: everything zero, including the class.
1130    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        // LB 1
1146        // ==========================================
1147        // Resolved Original    General_Category
1148        // ==========================================
1149        // AL       AI, SG, XX  Any
1150        // CM       SA          Only Mn or Mc
1151        // AL       SA          Any except Mn and Mc
1152        // NS       CJ          Any
1153        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 is resolved lazily on first read; subsequent reads eagerly
1190    // pre-peek so that next_class is a plain field load inside every rule
1191    // (avoiding the BreakUnit copy that reading the next unit
1192    // would otherwise incur per call).
1193    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    /// Cached line break class of the previous unit. Updated whenever
1223    /// the previous unit would be reassigned (in `read`
1224    /// or via the Ignored/Inherited fall-through here).
1225    #[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    /// Cached line break class of the next unit. Faster than
1255    /// reading the next unit because it avoids the
1256    /// BreakUnit copy. `read` is responsible for ensuring has_next
1257    /// is set before any rule sees this value.
1258    #[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        // Eagerly peek the new "next" so the cached next_class is valid
1328        // for every rule in the upcoming execute_rules pass.
1329        self.next = self.peek(text);
1330        self.next_class = self.next.line_break_class;
1331        self.has_next = true;
1332
1333        // LB9 ignored marks do not become the prior item for the next real boundary.
1334        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        // A negative position cannot be sliced (upstream throws there as well).
1365        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                // At the end the enumerator leaves the replacement codepoint in its out value.
1375                None => return Codepoint::REPLACEMENT_CODEPOINT.line_break_class(),
1376            }
1377        }
1378    }
1379}