Skip to content

fix: stop a wide table from re-wrapping the rest of the document - #6

Open
adaasch wants to merge 1 commit into
masterfrom
fix/wide-table-wrapping
Open

fix: stop a wide table from re-wrapping the rest of the document#6
adaasch wants to merge 1 commit into
masterfrom
fix/wide-table-wrapping

Conversation

@adaasch

@adaasch adaasch commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Wide tables broke text wrapping for the whole document below them.

The bug

A table too wide to fit widened the page for everything after it: every
paragraph, heading and rule following it wrapped at the table's width, so
reading the rest of the document meant scrolling sideways.

ui.available_width() stops meaning "page width" the moment anything
overflows. egui grows a Ui's max_rect to contain every widget placed in it
(Placer::advance_after_rectsRegion::expand_to_include_rect), and every
later widget sizes itself from that grown rect. set_max_width cannot undo it,
because it refuses to shrink a Ui below the min_rect the table just
expanded.

Reproduced headlessly before changing anything: paragraphs before the table
wrapped at 884px, paragraphs after ran off the edge at ~2600px.

The fix

Two changes, because either alone leaves half the problem.

Tables that cannot fit are squeezed instead of overflowing. When even the
minimum column widths do not fit, columns shrink proportionally until they do,
and the long tokens that made them too wide break across lines (epaint already
falls back to breaking anywhere when a row offers no word boundary). A table
now only overflows when it has more columns than the viewport can hold at
MIN_TABLE_COL_PX each. At phone width (420px) a five-column table fits where
it used to blow the page apart.

The renderer tracks the page's right edge, sampled before anything can
widen the Ui, and wraps against that. It is an edge rather than a width
because the pollution spreads: the first attempt clamped a width, and a
blockquote opened inside an already-widened Ui inherited the pollution, so
its content came out 20px too wide and spilled past the quote's padding. An
edge composes — any nested Ui still knows where it starts. Labels and
separators ignore any width handed to them and size from the Ui, so those get
a child Ui narrow enough to hold them; that child is only created when the
page has actually been polluted.

A table that genuinely cannot fit still overflows and still gets a horizontal
scrollbar. The difference is that the overflow now stays inside the table.

Verification

  • 149 lib tests pass; no new clippy warnings (15, unchanged from master).
  • Screenshots under Xvfb across: wide tables, unfittable tables, tables inside
    blockquotes, tables inside list items, phone-width windows, and
    README.md / tests/sample.md for regressions.

Behaviour changes to review

Two existing tests asserted the old contract (overflow rather than squeeze).
Those are deliberate behaviour changes, so both were rewritten to state the new
contract rather than loosened, and four tests added covering the squeeze, the
floor, the redistribution and the give-up case.

One judgement call: giving wide tables their own horizontal scroll area (what
GitHub does) was considered and rejected — a nested scroll area competes with
the outer one for drag gestures, which would land squarely on the Android
build. Squeeze-plus-clamp gets the same result for every table that can
plausibly fit, without that risk.

🤖 Generated with Claude Code

A table too wide to fit widened the page for everything below it: every
paragraph, heading and rule after it wrapped at the *table's* width, so
reading the rest of the document meant scrolling sideways.

The cause is that `ui.available_width()` stops being the page width the
moment anything overflows. egui grows a `Ui`'s `max_rect` to contain any
widget placed in it (`Placer::advance_after_rects`), and every later
widget sizes itself from that grown rect. `set_max_width` cannot undo it,
because it refuses to shrink a `Ui` below the `min_rect` the table just
expanded.

Two changes, because either alone leaves half the problem:

- Tables that cannot fit at their natural minimum widths are now squeezed
  proportionally until they do, and the long tokens that made them too
  wide break across lines (epaint breaks anywhere when a row offers no
  word boundary). A table only overflows when it has more columns than
  the viewport can hold at MIN_TABLE_COL_PX each — on a phone-width
  window a five-column table now fits instead of overflowing.

- The renderer tracks the page's right edge, sampled before anything can
  widen the Ui, and wraps against that. It is an edge rather than a width
  because the pollution spreads: a blockquote opened inside a widened Ui
  is widened too, so its own width is no more trustworthy than its
  parent's. An edge composes — any nested Ui still knows where it starts.
  Labels and separators, which size themselves from the Ui and ignore any
  width handed to them, get a child Ui narrow enough to hold them; that
  child is only created when the page has actually been polluted.

So a table that genuinely cannot fit still overflows and still gets a
horizontal scrollbar, but the overflow stays inside the table.

Two tests asserted the old contract (overflow rather than squeeze) and
now assert the new one; four more cover the squeeze, the floor, the
redistribution and the give-up case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant