Skip to content

fix: prevent rich inline CJK fragments from overflowing maxWidth#132

Open
voidborne-d wants to merge 1 commit intochenglou:mainfrom
voidborne-d:fix/rich-inline-cjk-overflow
Open

fix: prevent rich inline CJK fragments from overflowing maxWidth#132
voidborne-d wants to merge 1 commit intochenglou:mainfrom
voidborne-d:fix/rich-inline-cjk-overflow

Conversation

@voidborne-d
Copy link
Copy Markdown

Problem

When CJK text is split across multiple rich inline items (e.g. for styled highlighting with different colors), lines can overflow maxWidth. This is visible in the reproduction at https://codehz.github.io/pretext-bug-diag/ — switching to Chinese + inline rich mode shows text extending past the container boundary.

Root Cause

stepPreparedLineGeometry guarantees at least one content unit per line to prevent infinite loops in standalone layout. When the rich inline stepper (stepRichInlineLine) calls it with a small availableWidth (the remaining space on a partially-filled line), the line-break engine may return a fragment wider than availableWidth — typically one CJK character that doesn't fit. The rich inline stepper then adds this fragment to the current line without rechecking against the overall line budget, causing overflow.

The existing "wrap before item" heuristic at this code path only triggers when gapBefore > 0 (collapsed inter-item whitespace), so it misses the case where CJK items abut directly without whitespace.

Fix

Add an overflow guard in both stepRichInlineLine and its stats-only counterpart stepRichInlineLineStats: when the line already has content and the new item's fragment (including gap and extra width) would exceed the remaining space, break the line before the item so it starts fresh on the next line with the full width available.

This is placed before the existing "wrap before item" heuristic so it catches all overflow cases regardless of whether there's a gap between items.

Testing

Added two regression tests to the rich-inline invariants suite:

  • CJK inline-rich items do not overflow maxWidth — three CJK items with no inter-item gap, verifying no line exceeds maxWidth
  • CJK inline-rich items with inter-item gap do not overflow — three CJK items with collapsed whitespace gaps

Both tests verify the fragment collection path (walkRichInlineLineRanges + materializeRichInlineLineRange) and the stats path (measureRichInlineStats) stay consistent.

All 86 existing tests continue to pass.

Fixes #120

When CJK text is split across multiple rich inline items (e.g. for
styled highlighting), stepPreparedLineGeometry's 'always place at
least one content unit' guarantee can push a line past maxWidth.
This happens because the line-break engine forces the first segment
onto a fresh internal line even when the available width is too
small, but the rich inline stepper treats the returned width as
fitting without rechecking against the overall line budget.

Add an overflow guard in both stepRichInlineLine and its stats-only
counterpart: when the line already has content and the new item's
fragment (including gap and extra width) would exceed the remaining
space, break the line before the item so it starts fresh on the
next line with the full width available.

Fixes chenglou#120
shaun0927 added a commit to shaun0927/pretext that referenced this pull request Apr 16, 2026
…rapper

Remove stepRichInlineLineStats, a ~130-line near-duplicate of
stepRichInlineLine. The only difference was whether the optional
collectFragment callback fired — passing undefined achieves the
same result. PR chenglou#132 demonstrated the drift risk by having to
apply the same overflow guard to both functions in parallel.

Also inline the single-call-site containsCJKText() wrapper, which
was a passthrough to the already-public isCJK(). Similar to the
isCJK dedup in PR chenglou#118.

Neither change affects behavior. All 84 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inline-rich mode + CJK fragment cause overflow

1 participant