Give a page a time budget, and take the fixes from underneath - #6
Merged
Conversation
tannevaled
force-pushed
the
bump-pdfkit-deps
branch
2 times, most recently
from
August 27, 2026 13:25
a3260b8 to
d78b221
Compare
ops v0.5.0 -> v0.7.0, render v0.7.0 -> v0.10.0, reader v0.4.1 -> v0.5.0,
pdffont -> v0.3.0 indirect. opentype comes along to v0.10.0 because render
asks for it.
What the workbench gains without asking:
- A page stops drawing its own compressed bytes. A /CCITTFaxDecode image
arrived still compressed and was painted through a stencil, so a scan
came out as noise -- and noise looks like content. The first page of
fr-cerfa/cerfa_11012.pdf was 86.9% covered in ink and is now 5.4%: a
black rectangle with a band of static across the top, in place of a
legible EU certificate of origin.
- Annotations are drawn. A filled-in form used to appear empty, because
what a person typed lives in the widget beside the content, not in it.
- Soft masks, and a transparency group drawn as one thing.
- A composite font's identifiers reach its glyphs through the charset, so
a CIDFontType2 subset carrying no cmap no longer hides every glyph.
- A clip is kept as the box it covers rather than as a value for every
pixel of the page.
- A form's field list and its structure tree survive a rotate, so the
boxes keep their meaning and a screen reader keeps its reading order.
- An inline image's dictionary is read in a settled order, so a page
carrying both spellings of a key -- /W beside /Width -- no longer draws
differently from one run to the next.
What it had to ask for is the time budget. render will stop drawing a page
when its time is up and hand back as far as it got, but only if the caller
says how long -- and a browser tab is exactly the caller that cannot wait:
of 59 432 corpus pages, 1 131 were still being drawn after twenty seconds
and one took two hundred and seventy-three. So renderPage sets five seconds
and shows the part that was drawn, instead of treating ErrTimedOut as a page
that cannot be drawn at all. Half a figure is worth more to somebody
scrolling than a sentence saying there was one.
Showing half a page without saying so would be the one thing worse than
showing nothing, so the status line says which it is. That is the only
place renderPage writes the note, and it overwrites what was there,
because what is on the screen now matters more than what happened before.
tannevaled
force-pushed
the
bump-pdfkit-deps
branch
from
August 27, 2026 13:43
d78b221 to
9d76944
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
opsv0.5.0 → v0.7.0,renderv0.7.0 → v0.10.0,readerv0.4.1 →v0.5.0,
pdffont→ v0.3.0 (indirect).opentypecomes along to v0.10.0because
renderasks for it. One code change, inrenderPage.What a person sees
Two binaries drawing the first page of every one of the 2 268 forms in
/Users/Shared/pdfformsat 72 dpi, each verified withgo version -mascarrying exactly the graph this branch resolves before and after:
Pixels are hashed; "ink" is pixels that are not the paper.
Over all 2 268 files including the 635 vendor fixtures: 870 differ, 0 status
changes.
The page that was drawing its own compressed bytes
fr-cerfa/cerfa_11012.pdf, page 1: 86.9% of the page covered in ink → 5.4%.435 115 inked pixels → 26 917.
Before, a
/CCITTFaxDecodeimage arrived still compressed and was painted througha stencil: a black rectangle with a band of static across the top. Noise looks
like content, so nothing reported this as a failure — the page drew, exited
zero, and was wrong.
After, it is a legible EU certificate of origin: the boxes, the six-language
headings, the diagonal POUR INFORMATION : DOCUMENT S° CURIS° watermark.
I rendered both to PNG and looked at them.
Of the 136 real first pages that were more than half covered in ink under
render v0.7.0, 89 are drawn identically by v0.10.0 — those are genuinely dark
pages — and 47 are drawn differently. Of the 71 real forms carrying a fax,
68 first pages changed and 24 of them lost ink.
The other 485
More ink, from what
rendergained across v0.8.0–v0.10.0: annotations are drawn,so a filled-in form no longer appears empty (
fr-cerfa/cerfa_12766.pdf67 256 →277 655 inked pixels; five
uk-govukstudent-finance forms all roughly 65 000 →205 000); soft masks and transparency groups; a
CIDFontType2subset with nocmapno longer hiding every glyph.The one code change
renderwill stop drawing a page when its time is up and hand back as far as itgot — but only if the caller says how long, and until now nothing here did. A
browser tab is exactly the caller that cannot wait: of 59 432 pages
renderwasmeasured against, 1 131 were still being drawn after twenty seconds and one took
273.
renderPagenow sets five seconds and shows the part that was drawnrather than treating
ErrTimedOutas a page that cannot be drawn at all.Showing half a page without saying so would be the one thing worse than showing
nothing, so the status line says which it is:
Two tests cover it — a timeout that returns a partial image, and a timeout that
returns none — and both assert the note as well as the pixels.
render.OptionsgainedAllLayersin v0.10.0. I left it at its default: a viewershould show the configuration the document itself asks for.
Checked, not changed
reader.ImageFilterno longer answers true for/CCITTFaxDecodeor/CCF.Nothing in this module calls
ImageFilteror switches on a filter name at all—
renderdoes that work now.Not measured
Wall clock and peak memory. Three other corpus jobs were running on this machine,
so any timing figure would be fiction. Pixel hashes do not depend on load.
Gates
go vet,gofmt,-race, exact 100% statement coverage, thebrowsercheckmodule's own vet and gofmt, the js/wasm build, and vet on all nine targets pass
locally with
GOWORK=off CGO_ENABLED=0. CI's Drive it in a real browser job —which starts Chrome, hands the file picker a document and reads the canvas back —
passes on this branch.