The refusal this replaces was right about the constraint and wrong
about the conclusion. A PDF text layer really is absolutely-positioned
spans laid over a rendered page, and a translation appended under a
line really would land on the next one. But the pages themselves are
ordinary block elements stacked in `#viewer`, so a translation placed
*after a page* costs nothing — and reads in order: the page as the
author laid it out, then that page in the reader's language, then the
next page.
Which is also how a paper is read. Nobody wants a two-column PDF
reflowed into one; they want to see the figure and read the argument.
Pages render lazily — measured at 2 of 14 with a text layer at any
moment — so this follows the viewer rather than making one pass. The
page nearest the reader goes first, and pages the viewer draws later
are picked up by an observer as they arrive, which is the same bargain
whole-page translation makes.
Paragraphs are recovered from geometry, but less of it than expected.
PDF.js already emits `<br>` between lines, so the line breaks are given
and only the paragraph breaks have to be inferred. Those come from line
*pitch*, top to top, against the median pitch of that page — not from
the gap between line boxes, which is not ours to trust: this extension
pads every span by 5px a side to make lines easier to grab, so on a
real page the measured gaps run negative, -8, -9, -2 down a column of
ordinary prose. Pitch is untouched by symmetric padding, and a per-page
median calibrates itself to the font size and the zoom instead of being
right at one of them. The first version used gaps and a fixed ratio;
the histogram off a real paper is what replaced it.
Rects are injected rather than read, because jsdom returns zeros for
every one of them and a grouping rule that can only be exercised in a
browser is a rule nobody will change with confidence later.
`e2e:page` gains a twelfth phase, on the viewer's own sample paper: 41
paragraphs off the first two pages, every translation placed after its
page rather than inside it, the text actually in the target language,
and pressing again leaving the document as it was.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>