Skip to content

Improve PDF table readability - #14

Merged
sehkone merged 1 commit into
mainfrom
jin/improve-table-layout
Aug 8, 2026
Merged

Improve PDF table readability#14
sehkone merged 1 commit into
mainfrom
jin/improve-table-layout

Conversation

@jinjoolee07

Copy link
Copy Markdown
Contributor

Closes #13

Material draws its table grid with a custom property that only a
browser's colour scheme defines. The PDF render never sets one, so the
declaration was invalid at computed-value time and the border fell away
entirely: a table arrived as unruled columns of text, with the setting
name in one column vertically centred and its description top-aligned
several lines above, and nothing to say which cell belonged to which
row. The grid, the header shading, and the cell padding are drawn here
because nothing else will draw them.

A dotted setting name has no break opportunity of its own. Left
unbreakable it becomes its column's minimum width, and the auto layout
pays for it out of the other columns: a three-column table squeezed its
description into a ribbon four words wide. `overflow-wrap: anywhere` on
the inline code lets the name wrap and gives the column back.

That property stays off the cell. On a cell it also collapses the cell's
own minimum width, so the column shrinks to one character and the
renderer chops whatever is in it: a `Default` heading came back as
`Defau` / `lt`, `604800` as `60480` / `0`, and a `Key` column as seven
stacked letters. It is also pathologically slow -- a cell holding a page
of prose took 9 minutes to lay out where the same document takes 5
seconds, because every line break is re-tried at every character. Cells
get `overflow-wrap: break-word`, which breaks a word only once it
actually overflows and leaves the minimum width alone.

A row that splits leaves its cells misaligned either side of the break,
so a row moves whole. The table around it still splits one row at a
time, and the two-row guards already on `tbody > tr` still hold. A row
too tall for a page of its own splits anyway rather than being dropped.

`styles/pdf.css` carries the same fragmentation and wrapping rules, so
browser print and PDF export still break a line and a page at the same
places.

`pdf-test.sh` reads the table fixture back with `pdftotext` twice over.
Its line breaks are kept in one reading, because the whitespace-stripped
reading every other assertion uses glues a chopped word back together
and hides exactly this class of bug; the other reading is `-raw`, which
is the only order that reunites a word split inside a cell -- the
default order walks the whole row between the two halves. The chopped
heading, the unwrapped setting name, a row split across a page, and the
content after a row taller than a page are each asserted. The checks
fail against the stylesheet this replaces.

Closes #13

Co-authored-by: jinjoolee07 <jinjoolee07@gmail.com>
@sehkone
sehkone force-pushed the jin/improve-table-layout branch from f0d2a02 to df43a4a Compare August 8, 2026 04:42

@sehkone sehkone left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed, revised, and rebased onto main. Sorry for the force-push over your two commits — the details are below so nothing is lost.

Rebase

main moved after this PR was opened. c3829c4 ("Keep chapter content from dropping out of the PDF", #11) already covers the first commit here, "Improve PDF content flow within chapters", so that commit is dropped as superseded — its section + section, heading, and fragmentation rules are on main in a form that also drops the !importants and adds pdftotext coverage. Only the table work is left, now a single commit on top of current main.

One thing to note about that rebase: the CHANGELOG entries in this PR belonged to the dropped commit, so after the rebase there was no changelog record of the table change at all. New entries are added under both templates.

The issue is real, and worse than #13 describes

Confirmed by rendering the fixture rather than reading the CSS. Material draws the table grid with var(--md-typeset-table-color), a custom property only a browser colour scheme defines. The PDF render never sets one, so the whole border declaration is invalid at computed-value time and falls away: a table arrives as unruled columns of text, with the setting name vertically centred in one column and its description top-aligned several lines above it, and nothing to say which cell belongs to which row. Separately, a long dotted setting name has no break opportunity of its own, so it became its column's minimum width and the auto layout paid for it out of the others — a three-column table squeezed its description into a ribbon four words wide.

So the direction of this PR is right, and the grid, header shading, and cell padding it adds are all kept.

Two defects in the proposed CSS

Both trace to overflow-wrap: anywhere on th/td.

1. It chops words. anywhere also collapses the cell's minimum width, so a column shrinks to one character and the renderer breaks whatever is in it. In the rendered output of this branch: Default came back as Defau / lt, 604800 as 60480 / 0, and a Key column as seven letters stacked vertically. That is the opposite of what the PR is for, and the existing test helpers cannot see it — they strip whitespace before asserting, which glues a chopped word back together.

2. It is pathologically slow. A single cell holding a page of prose took 9 minutes to lay out, against 5 seconds for the same document on main. Isolated rule by rule, overflow-wrap: anywhere on the cell is the sole cause; tr { break-inside: avoid } and hyphens: auto are each ~5s on their own.

The fix is to scope it: cells get overflow-wrap: break-word, which breaks a word only once it actually overflows and leaves the minimum width alone, and anywhere is confined to inline code inside a cell, which is the only thing that needs to break mid-token. Build time is back to 5s and the headings are intact.

Other changes

  • !important removed throughout. The selector already carries two IDs (article:not(#doc-cover):not(#doc-toc)), so it outranks Material's .md-typeset table:not([class]) without help — verified by rendering, not by counting. This also matches the reasoning c3829c4 recorded for the pagination rules.
  • font-size: 11pt / line-height: 1.25 dropped. They contradicted p, li, td, th { font-size: 12pt !important } two hundred lines above in the same file, leaving two conflicting rules for the same elements. Tables read fine at the document size.
  • hyphens: auto dropped. #13 does not ask for it, it hyphenates English prose only inside table cells (inconsistent with body text), and it needs a Pyphen dictionary that does not exist for ko.
  • Initial-value redeclarations dropped: border-spacing, table-layout, word-break, max-width.
  • styles/pdf.css updated to match. c3829c4 established that the two files must agree on how a block fragments, or browser print and PDF export break at different points. This PR changed fragmentation (tr) and wrapping in the PDF only, so the same rules are now in both.
  • tr { break-inside: avoid } is kept as #13 asks. A row too tall for a page of its own still splits rather than losing its content — verified.

Tests

tests/pdf-test.sh gains a table fixture and four assertions: a long inline-code value wraps and still arrives whole, a heading and a short code value are not broken mid-word, no row is split across a page boundary, and the content after a row taller than a page survives. They fail against the stylesheet this replaces.

Two new readers were needed. One keeps the renderer's line breaks, because the whitespace-stripped reading every other assertion uses hides exactly this class of bug. The other is pdftotext -raw, which is the only order that reunites a word split inside a cell — the default reading order walks the whole row between the two halves.

Known limitation

A single token wider than the page with no break opportunity anywhere in it (no /, -, ., or _) still pushes its table off the page edge, because break-word does not reduce the minimum content width. Checked against realistic content and none of it hits this: a plain-text URL wraps at its slashes, a plain-text dotted setting name wraps, a backticked one wraps, and an eight-column table fits. Only a contrived 106-character unbreakable word overflows. Removing that case means putting anywhere back on the cell, which costs every table its intact headings and the build its 5 seconds, so it is deliberately left.

Verification

Full pdf-test.sh (26 checks), shellcheck, markdownlint, and PDF builds of both samples in en and ko, inspected as rendered pages.

@sehkone
sehkone merged commit e636b12 into main Aug 8, 2026
6 checks passed
@sehkone
sehkone deleted the jin/improve-table-layout branch August 8, 2026 05:07
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.

Improve PDF table readability and wrapping

2 participants