Skip to content

fix(reader): fit wide vector PDF pages to the screen - #953

Merged
ajslater merged 1 commit into
developfrom
fix-pdf-canvas-fit
Sep 28, 2026
Merged

ajslater merged 1 commit into
developfrom
fix-pdf-canvas-fit

Conversation

@ajslater

Copy link
Copy Markdown
Owner

Summary

Follow-up to #951. None of the four canvas rules in pdf-doc.vue matched the rendered canvas, in any fit mode. They have been dead since the Vuetify 3 port (Dec 2022), when an unscoped <style> block became top-level :deep() selectors:

  1. Scoping. :deep(.vue-pdf-embed.X > div > canvas) compiles to [data-v-x] .vue-pdf-embed.X > div > canvas, which needs a scoped ancestor. The embed is the component's root, so there isn't one. The rules now scope on the root (.pdfDoc.X :deep(canvas) → .pdfDoc.X[data-v-x] canvas).
  2. Depth. vue-pdf-embed 2.1.6 renders > div > div.vue-pdf-embed__page > canvas, one level deeper than > div > canvas. A descendant canvas covers it.

Per-rule decisions

I made each rule match, then measured it live (Force vector, DPR 2, 1024×768 and 390×844, portrait pages plus a wide page):

Rule Effect once live Decision
fitToHeightTwo, fitToScreenTwo: width: inherit !important None. vue-pdf-embed already sets an aspect-correct inline width/height, and auto derives the same width. Deleted
fitToWidthTwo: height: inherit !important None (a 1px rounding difference) Deleted
fitToOrigTwo: width/height: inherit !important Both become auto, so the canvas shows at its bitmap size: 1023×1581 instead of 512×790, 2x on HiDPI, with a 2046px scroll width. Zoom > 1 also produces fitToOrig*, so zoomed pages would blow up too. Deleted
fitToScreen*: object-fit: contain None. The box already matches the bitmap, so there is nothing to contain. Kept and adjusted. Paired with max-width: 100vw (single/vertical) or 50vw (two-page), as page-img.vue does for images

Original Size already renders like Fit to Width (pdf-doc.vue's width computed), and that doesn't change here. The fitToOrigTwo rule would only have taken effect once #951 landed. The misleading "Wide PDFs will not fit to SCREEN well" comment is replaced.

Visible change

This affects only Fit to Screen, and only pages wider than the screen:

  • Two-page, 1024×768, wide spread + portrait page: the scroll width goes from 1491px to 1024px. The spread is capped at 512px and letterboxed.
  • Single page, 390×844, wide page: the canvas goes from 1092px (overflowing, the same as Fit to Height) to a 390×844 box with the page letterboxed.
  • Vertical, 390×844: a page is capped at 390px inside its 80%-height box. This includes portrait pages, which were 437px wide there. Letterbox bands appear above and below wide pages, as in image mode.

Every other mode (Width, Height, Original × single/two/vertical) measured identical to before, down to the pixel. Zoom (fitToOrig* class) is not capped: at 2x the canvas stays at 2185×1688.

NEWS: "Fit to Screen fits wide pages when PDFs render as vectors."

Tests

  • make fix: no changes beyond this diff. make lint: clean (two basedpyright warnings in cover.py were already there).
  • bunx vitest run: 88 files, 919 tests passed.
  • No unit test: this is canvas layout, which happy-dom can't measure. I verified it in a real browser against a dev stack with a generated 6-page PDF (four portrait pages, one wide spread, one portrait page).

🤖 Generated with Claude Code

None of pdf-doc.vue's four canvas rules had matched anything since the
Vuetify 3 port (Dec 2022). That port moved them from an unscoped block
into top-level :deep() selectors, which compile to
`[data-v-x] .vue-pdf-embed.X > div > canvas` and need a scoped ancestor.
The embed is the component's root, so it has none. vue-pdf-embed 2.x
also nests the canvas one div deeper than `> div > canvas`.

With the selectors fixed (`.pdfDoc.X :deep(canvas)`), each rule was
checked in a browser with Force vector at DPR 2:

- Fit to Height/Screen two-page `width: inherit` and Fit to Width
  two-page `height: inherit` did nothing. vue-pdf-embed already gives
  the canvas an aspect-correct inline size. Deleted.
- Original two-page `width/height: inherit` resolved to auto, so each
  canvas showed at its bitmap size, 2x on HiDPI (1023x1581 instead of
  512x790). Zoom uses these classes too. Deleted.
- Fit to Screen `object-fit: contain` did nothing, because the box
  already matched the bitmap. Kept, and paired with max-width
  100vw/50vw as page-img.vue does for images. Wide pages now fit the
  screen instead of overflowing it: a spread plus a page in two-page
  mode at 1024px went from a 1491px scroll width to 1024px, and a wide
  page on a 390px phone from 1092px to 390px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ajslater
ajslater merged commit 94defbd into develop Sep 28, 2026
7 checks passed
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