Skip to content

feat(pdf): share one image across the pages that place it - #751

Open
andiwand wants to merge 1 commit into
feat/pdf-skip-offscreen-pagesfrom
feat/pdf-dedupe-images
Open

feat(pdf): share one image across the pages that place it#751
andiwand wants to merge 1 commit into
feat/pdf-skip-offscreen-pagesfrom
feat/pdf-dedupe-images

Conversation

@andiwand

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stacked on #750, which is stacked on #749.

The pdf view inlined every image as a data url and ignored HtmlConfig::embed_images — every other view honours it. A logo on 100 pages was base64'd 100 times.

Measured on the private pdf corpus, splitting duplication by where it occurs:

file within one page across pages
Rockrider_e-Actv500_Allgemein_und_Garantie 0.0M 29.7M of 49.5M
Rockrider_e-Actv500_Gebrauchsinformationen 1.6M 14.3M of 28.0M
Laserentfernungsmesser_PLEM50_D5 0.0M 7.4M (10 images, 279 placements)
Normstahl_UltraS_Garagentorantrieb_Hans 0.0M 2.0M

It is almost entirely across pages, so collapsing repeats inside one page with <defs>/<use> would recover close to nothing. Sharing a resource does.

How

ImageRegistry keys on the image XObject. The parser already memoizes those per document, so the address identifies a repeat and no digest over the bytes is needed — the resource is named by a plain counter, and the extension comes from file_type_by_mimetype / file_extension_by_file_type rather than a new table.

The resource goes through config.resource_locator, so:

  • embed_images on (the default) — a data url, exactly as today
  • embed_images off — image1.jpg written once, linked from every page that places it

An inline (BI) image and a stencil get an entry each: a stencil's bytes are built from the fill colour in effect, so two placements in different colours are genuinely different images.

Verification

  • Two new tests: one image on two pages is one resource referenced twice with nothing inlined; with embedding on, both placements stay inline and no location is handed out.
  • Default-config output over the 11-file corpus is byte-identical — same totals, same per-page sizes, same node counts.
  • Full suite 1228 passed, 0 failed, 6 pre-existing skips.

Note for the apps

The saving only lands when the host sets embed_images = false. Before this PR that flag did nothing for pdf, so nothing changes until an app opts in.

The pdf view inlined every image as a data url and ignored
`HtmlConfig::embed_images`, so a logo on all 100 pages was base64'd 100 times.
On the private corpus that is 29.7M of a 49.5M render, and it is duplication
across pages, not within one, so a `<defs>`/`<use>` collapse would not reach it.

`ImageRegistry` keys on the image XObject, which the parser memoizes per
document, so its address identifies a repeat — no digest over the bytes. The
resource is named by a counter and run through the config's resource locator:
embedded as today by default, written once and linked when `embed_images` is
off. An inline (`BI`) image and a stencil get an entry each, a stencil's bytes
following the fill colour.

Default output is byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169dUoxqhqfi7ZU5ei8b9ov
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