Skip to content

feat(render): crisper pages and a resolution-based 100% zoom with round zoom steps - #523

Merged
DutchSailor merged 2 commits into
OpenAEC-Foundation:mainfrom
philip-tam:feat/crisp-rendering-and-zoom
Oct 14, 2026
Merged

DutchSailor merged 2 commits into
OpenAEC-Foundation:mainfrom
philip-tam:feat/crisp-rendering-and-zoom

Conversation

@philip-tam

Copy link
Copy Markdown
Collaborator

Summary

Makes page content look noticeably crisper at every zoom level and makes the zoom percentages behave like other PDF viewers.

Rendering

  • Zoom tiles refined on settle. Between 150% and 300% a single 300%-quality coverage tile was shown shrunk by a non-integer factor (e.g. 1.5x at 200%), softening thin strokes; only exactly 300% was 1:1. The cached tile still shows instantly, then an exact zoom * dpr tile replaces it (tileNeedsExactRender).
  • Whole device pixels. The page bitmap and tiles are drawn with a rounded transform translation; a fractional offset made the canvas bilinearly blend every pixel with its neighbours, softening all text even when the bitmap was at the exact screen resolution.
  • Continuous view at device resolution. Pages were rendered at plain doc.scale and shown in a canvas with a floored CSS size, so the bitmap was resampled by a fraction of a pixel (uneven grey table rules/text at most zoom levels, and upscaled by dpr on high-DPI screens). They now render at scale * devicePixelRatio and the canvas is sized from the bitmap (1:1).
  • Page sharpening preference (Page Display: Off / Light / Medium / Strong / Maximum, default Medium): a light 3x3 unsharp mask on freshly rendered page and tile bitmaps. Measured on the same text at the same size, PDFium's output had lighter ink and ~20% weaker edges than a hinted text renderer; this closes most of that gap. Flat areas are untouched; changing the level flushes the bitmap caches. ~70 ms for a normal view, ~270 ms worst case for a 12 Mpx bitmap.

Zoom

  • 100% is now resolution based. A Page Display preference ("Screen resolution for 100% zoom", default 110 dpi) defines it: 1 PDF point = dpi/72 CSS px (96 would match browsers). The status bar shows/accepts the percentage in that unit and Actual Size uses it. The internal scale (doc.scale / viewport.zoom) is unchanged, so saved state and MCP app_set_zoom keep their meaning.
  • Round zoom steps. +/- buttons, keys, Ctrl+wheel and trackpad pinch (single-page and continuous view) step through 10, 25, 33, 50, 67, 75, 100, 125, 150, 200, 300, 400, 600, 800 ... instead of x1.25 / smooth zoom.

Notes for review: the 110 dpi default changes what "100%" looks like for everyone (it is a preference; 96 gives browser size). New i18n keys are added for en/nl only, like the neighbouring Page Display keys.

Test plan

  • New unit tests: zoom-display (percent/scale conversion, dpi clamping, step ladder), render-sharpen (flat areas, edges, alpha, border), tileNeedsExactRender
  • Full npm run test:unit passes; vite build passes
  • Manually checked on a multi-page text/table PDF: table rules and small text are crisp in single-page view at 123-200% after settling; continuous view and Ctrl+wheel stepping to be re-verified with the final build

…nd zoom steps

Rendering
- Zoom tiles are refined to the exact screen resolution once the view
  settles; between 150% and 300% a 300% coverage tile was shown shrunk by
  a non-integer factor, softening thin strokes.
- The page bitmap and tiles are drawn on whole device pixels (rounded
  transform translation) so the canvas no longer blends every pixel with
  its neighbours at a fractional offset.
- Continuous view renders each page at scale * devicePixelRatio and sizes
  the canvas from the bitmap, so it is drawn 1:1 instead of being resampled
  by a fraction of a pixel (blurry table rules and text).
- New Page Display preference 'Page sharpening' (Off/Light/Medium/Strong/
  Maximum, default Medium): a light 3x3 unsharp mask on fresh page and tile
  bitmaps for darker, crisper text. Changing it flushes the bitmap caches.

Zoom
- 100% is defined by a screen resolution preference (Page Display, default
  110 dpi: 1 PDF point = dpi/72 CSS px) instead of 1 point = 1 px. The
  status bar shows/accepts the percentage in that unit and Actual Size uses
  it; the internal scale is unchanged.
- Zoom buttons, keys, Ctrl+wheel and pinch (single-page and continuous view)
  step through round levels: 100, 125, 150, 200, 300, ...
@philip-tam

Copy link
Copy Markdown
Collaborator Author

Hi @mojtabakarimi, when you have a moment, could you review this PR? I merged the latest main into the branch (the unit test list now lives in scripts/unit-testlijst.txt, so I registered the two new test files there), so it is mergeable again and CI is re-running. All unit tests and the build pass locally.

The main review points are the new 110 dpi default for 100% zoom (a preference; 96 gives browser size) and the continuous-view render path (now 1:1 at device resolution). Happy to adjust anything. Thanks!

@DutchSailor
DutchSailor merged commit 130064f into OpenAEC-Foundation:main Oct 14, 2026
4 checks passed
@philip-tam
philip-tam deleted the feat/crisp-rendering-and-zoom branch October 14, 2026 09:25
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.

2 participants