Skip to content

feat(html): lay out only the pdf pages near the viewport - #1248

Merged
andiwand merged 2 commits into
mainfrom
feat/pdf-chunks
Oct 24, 2026
Merged

andiwand merged 2 commits into
mainfrom
feat/pdf-chunks

Conversation

@andiwand

@andiwand andiwand commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Refs #764.

Summary

Step 3 of docs/design/lazy-loading.md: pdf at level 1. With Core_v5.1.pdf (2,985 pages, a 127 MB document.html) in headless Chrome:

first frame layout renderer RSS
before (.p{content-visibility:auto}) 12.0 s 4.0 s 5.2 GB
after 1.8 s 0.08 s 2.0 GB

A pdf page already had content-visibility: auto. That is the trap that #1247 measured: on the first frame, Chrome lays out every auto element once. So a page is now an odr-chunk, as the lines of a text file are:

  • write_chunk_style writes the chunk rules inline for both views:

    • a chunk is hidden until it is near the viewport
    • a chunk is visible in print, which also covers WebKit's blank print of a skipped page
    • inside <noscript>, a chunk is auto on screen

    A page states its own size, so it needs no contain-intrinsic-size.

  • viewport.js now holds the IntersectionObserver, moved from text.js, because every view loads it. A browser without IntersectionObserver shows every chunk.

  • The first page is near in the markup, so the first frame does not wait for the observer.

The reference output changes on both output repos' main, and both pins are advanced:

  • public f3724ee: 32 pdf pages, 3 txt pages, text.css, text.js, viewport.js
  • private 71bd517: 1,131 pdf pages, search.js, viewport.js

The copy is surgical. I checked each changed page: after this change's edits are reverted on both sides (the chunk classes, the chunk style, content-visibility on .p), it is byte-identical to the reference.

Test

  • All 2,529 tests pass, and the 52 checks of test/browser/plaintext pass.
  • A page in the middle of Core_v5.1.pdf renders after a scroll.
  • printToPDF of a 208-page pdf gives 208 sheets, and sheet 150 has its content.
  • The 1 M-line text file stays at 0.85 s to its first frame.

Review fixes

  • Rebased onto main after the squash of feat(html): lay out only the lines of a text file near the viewport #1247. The rebase had no conflicts.
  • write_chunk_style stood between the two search styles in frontend.cpp. It now comes after them.
  • Checked again after the rebase: the html, text and pdf tests (699) pass. test/browser viewport (17), plaintext (54) and annotation (86) pass. The public txt and pdf pages are byte-identical to f3724ee. Both new pins descend from the pins on main.

Base automatically changed from feat/text-chunks to main October 24, 2026 18:43
andiwand and others added 2 commits October 24, 2026 20:44
A pdf of 2985 pages took 12 s to its first frame in Chrome, and its
renderer held 5.2 GB. Now it takes 1.8 s and 2.0 GB.

Each page was `content-visibility: auto`, and Chrome lays out every
`auto` page once on the first frame. Now a page is an `odr-chunk`, as
the lines of a text file are:

- `write_chunk_style` writes the chunk rules inline for both views. A
  chunk is `hidden` until it is near, `visible` in print, and `auto`
  where no script runs.
- `viewport.js` marks the chunks near the viewport. The observer moves
  there from `text.js`, because every view loads `viewport.js`.

The reference output changes for every pdf and txt page, and for
`text.css`, `text.js`, `search.js` and `viewport.js`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QtawyvLP2SCnoBtnVfFed
`write_chunk_style` stood between `write_search_style` and
`write_search_dark_style`. It now comes after them, in the order of the
header.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
@andiwand
andiwand merged commit db0657b into main Oct 24, 2026
32 checks passed
@andiwand
andiwand deleted the feat/pdf-chunks branch October 24, 2026 18:56
andiwand added a commit that referenced this pull request Oct 24, 2026
The status said that nothing of the design has landed, but #1247 and
#1248 did. Also say how `spreadsheet.js` learns that a chunk became
near: `viewport.js` dispatches an event on it. A change of the class
alone tells no script.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
andiwand added a commit that referenced this pull request Oct 24, 2026
* docs: design the chunks of a sheet for lazy loading

`content-visibility` has no effect on a table row, so a chunk of a sheet
is a table of its own (decision 6):

- A `div.odr-sheet` takes the attributes of the sheet.
- The ruler gets a sticky table of its own.
- Every column gets a fixed width. A column that states none gets the
  width of its longest text.
- A chunk ends only where no merge is open.
- Each chunk states the estimated height of its rows.
- The first chunk stays near where shapes hang from A1.

For each script that reads the rows or the geometry, the design states
how it works with chunks. Level 2 of a text file drops out of the order
of work, because it gained little when measured.

* docs: state that level 1 of lazy loading has landed for text and pdf

The status said that nothing of the design has landed, but #1247 and
#1248 did. Also say how `spreadsheet.js` learns that a chunk became
near: `viewport.js` dispatches an event on it. A change of the class
alone tells no script.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
andiwand added a commit that referenced this pull request Oct 24, 2026
The views lay out only the parts near the viewport since #1247, #1248 and
#1250, and this PR adds `HtmlConfig::lazy_layout`. A consumer notices
both, but the changelog had no entry. Also check the round trip of the
jni test with `assertFalse`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHNzLmVv1mFWfPitHASucN
andiwand added a commit that referenced this pull request Oct 24, 2026
* feat(html): add a switch for lazy layout

`HtmlConfig::lazy_layout` is on by default. Off, no view writes a chunk
or the style that hides one, so the browser lays out everything. Its
own find and a screen reader then reach every line, row and page. A
host can take that where a file is small.

- The text and sheet views write all their lines or rows as one block.
  The pdf pages get no chunk class.
- `chunk_classes` gives the classes of a chunk, empty where the switch
  is off, and `write_chunk_style` writes nothing then.
- The text styles move from `.odr-chunk` to `.odr-text-lines`, so a
  text page without chunks keeps its line numbers.
- The apple, jni and python bindings carry the switch.

* test: advance the reference output to the lazy layout switch

The design names the switch, and the pins move to the reference output
of the renamed text class and the reordered pdf page classes.

* docs: add the lazy layout and its switch to the changelog

The views lay out only the parts near the viewport since #1247, #1248 and
#1250, and this PR adds `HtmlConfig::lazy_layout`. A consumer notices
both, but the changelog had no entry. Also check the round trip of the
jni test with `assertFalse`.

Co-authored-by: Claude Opus 5.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