Repository: typst/typst
File: crates/typst-layout/src/inline/linebreak.rs (Rust)

Code before:
        } else {
            const LTR_ISOLATE: char = '\u{2066}';
            const OBJ_REPLACE: char = '\u{FFFC}';
            match lb.get(c) {
                LineBreak::MandatoryBreak
                | LineBreak::CarriageReturn
                | LineBreak::LineFeed
                | LineBreak::NextLine => Breakpoint::Mandatory,

                // https://github.com/typst/typst/issues/5489
                //
                // OBJECT-REPLACEMENT-CHARACTERs provide Contingent Break
                // opportunities before and after by default. This behaviour
                // is however tailorable, see:
                // https://www.unicode.org/reports/tr14/#CB
                // https://www.unicode.org/reports/tr14/#TailorableBreakingRules
                // https://www.unicode.org/reports/tr14/#LB20
                //
                // Don't provide a line breaking opportunity between a LTR-
                // ISOLATE and an OBJECT-REPLACEMENT-CHARACTER representing
                // and inline item, if the LTR-ISOLATE could end up as the
                // only character on the previous line.
                LineBreak::CombiningMark
                    if c == LTR_ISOLATE
                        && text[point..].starts_with(OBJ_REPLACE)
                        && last == (point - LTR_ISOLATE.len_utf8()) =>
                {
                    continue;
                }

                _ => Breakpoint::Normal,
            }
        };

        // Hyphenate between the last and current breakpoint.
        if hyphenate && last < point {
            for segment in text[last..point].split_word_bounds() {
                if !segment.is_empty() && segment.chars().all(char::is_alphabetic) {
                    hyphenations(p, &lb, last, segment, &mut f);
                }
                last += segment.len();
            }
        }

        // Call `f` for the UAX #14 break opportunity.
        f(point, breakpoint);
        last = point;
    }
}

/// Generate breakpoints for hyphenations within a word.

Review comment (suggestion):
I was thinking about whether characters other than the LTR-ISOLATE could cause this situation. My first thought was the RTL-ISOLATE, but I can't quite get an example of it in Typst (I tried just setting `set text(dir: rtl)` in the test case, but all seems to work. I haven't had the chance to look into it any further.) But then I figure we may as well generalise it completely:
```suggestion
                LineBreak::CombiningMark
                    if text[point..].starts_with(OBJ_REPLACE)
                        && last == (point - c.len_utf8()) =>
```
A proper combining mark (i.e. not one of the control characters) will never end up as the only character on the previous line (I think?), and would be treated as the character preceding it anyways. Maybe this will have unforeseen consequences; all the tests pass though, which is reassuring.

Code after:
        } else {
            const OBJ_REPLACE: char = '\u{FFFC}';
            match lb.get(c) {
                LineBreak::MandatoryBreak
                | LineBreak::CarriageReturn
                | LineBreak::LineFeed
                | LineBreak::NextLine => Breakpoint::Mandatory,

                // https://github.com/typst/typst/issues/5489
                //
                // OBJECT-REPLACEMENT-CHARACTERs provide Contingent Break
                // opportunities before and after by default. This behaviour
                // is however tailorable, see:
                // https://www.unicode.org/reports/tr14/#CB
                // https://www.unicode.org/reports/tr14/#TailorableBreakingRules
                // https://www.unicode.org/reports/tr14/#LB20
                //
                // Don't provide a line breaking opportunity between a LTR-
                // ISOLATE (or any other Combining Mark) and an OBJECT-
                // REPLACEMENT-CHARACTER representing an inline item, if the
                // LTR-ISOLATE could end up as the only character on the
                // previous line.
                LineBreak::CombiningMark
                    if text[point..].starts_with(OBJ_REPLACE)
                        && last + c.len_utf8() == point =>
                {
                    continue;
                }

                _ => Breakpoint::Normal,
            }
        };

        // Hyphenate between the last and current breakpoint.
        if hyphenate && last < point {
            for segment in text[last..point].split_word_bounds() {
                if !segment.is_empty() && segment.chars().all(char::is_alphabetic) {
                    hyphenations(p, &lb, last, segment, &mut f);
                }
                last += segment.len();
            }
        }

        // Call `f` for the UAX #14 break opportunity.
        f(point, breakpoint);
        last = point;
    }
}

/// Generate breakpoints for hyphenations within a word.
fn hyphenations(