Skip to content
This repository was archived by the owner on Oct 21, 2026. It is now read-only.

Preview zoom control and horizontal scrolling (#38, #39) - #41

Merged
sbeitzel merged 2 commits into
mainfrom
feature/38_preview_zoom
Sep 24, 2026
Merged

sbeitzel merged 2 commits into
mainfrom
feature/38_preview_zoom

Conversation

@sbeitzel

@sbeitzel sbeitzel commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #38
Closes #39
Closes #42

#39 — preview scrolls in both directions

The preview's document view was pinned to the pane's width, and its height came from the SwiftUI content's ideal size rather than its size at that width. PreviewLayout now computes page and document geometry explicitly: the document is max(pages, viewport) wide and exactly as tall as its pages, so it scrolls horizontally when the pages are wider than the pane.

#38 — zoom control

  • Overlay control on the preview (zoom out / level picker / zoom in) with Fit Width and presets from 50% to 400%.
  • View menu: Zoom In ⌘+, Zoom Out ⌘−, Actual Size ⌘0, Fit Width — act on the focused window.
  • Zoom is per window and kept in @SceneStorage, so restored windows keep it.
  • Stepping from Fit Width goes to the neighbouring preset of the effective fit scale.
  • Relayout (zoom or pane resize) keeps the same point of the score centered.

Rendering fixes needed for zoom to take effect

  • SVGKit draws at the image's size, not the view's frame. Pages now set SVGKImage.size to their displayed size. CeolKit's pages have a viewBox, so the drawing scales. Without a size, SVGKit converts CeolKit's pt dimensions to ~1.83×, so pages had never matched their frames, even at Fit Width.
  • SVGKLayer ignores re-assignment of the same image, and resizing an image discards the layer tree the layer would need to remove. Resizing now detaches, resizes, then reattaches.
  • SwiftUI kept a page's earlier size after a zoom change, centering it (offset up and left) in its new frame. SVGPageView now reports its size via sizeThatFits.
  • SVGPageView.svgSize never matched CeolKit's 612pt dimensions, so every page was treated as portrait Letter (Page size is misread for CeolKit SVGs, so every page is treated as portrait Letter #42). This affected layout and scroll-sync anchor offsets for landscape pages. It now reads the viewBox first and accepts pt/px.

Scroll sync

The preview now reports its content height in SVG units, matching the anchors' svgY, so editor↔preview sync behaves the same at any zoom.

Tests

88 → 122 tests (10 → 15 suites):

  • PreviewLayoutTests / SVGPageSizeTests: layout geometry, page-size parsing
  • PreviewZoomTests: raw values, preset stepping, stepping from fit width
  • SVGPageScalingTests: SVGKit scales a real CeolKit page with image.size; resizing a live view replaces its layer tree
  • ScrollablePreviewHostTests: drives a real ScrollablePreviewHost in a window through zoom sequences and checks every page's frame (fails without the sizeThatFits fix)

Zoom was checked by hand in the running app.

Not included

Pinch-to-zoom (optional in #38), now tracked in #43: NSScrollView.allowsMagnification would conflict with the explicit page sizing. It could be a follow-up.

🤖 Generated with Claude Code

sbeitzel and others added 2 commits September 24, 2026 08:12
…h ways

The hosting view's width was pinned to the clip view, so it could never be
wider than the pane, and its height came from the SwiftUI content's ideal
size rather than its size at the pinned width. Compute the page and document
geometry in PreviewLayout and set the document view's frame from it: the
document is max(pages, viewport) wide and exactly as tall as its pages.

The preview now reports its content height in SVG units so the scroll-sync
anchors stay comparable to it regardless of how the pages are scaled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A control overlaid on the preview (zoom out, level picker, zoom in) and View
menu commands (Zoom In ⌘+, Zoom Out ⌘−, Actual Size ⌘0, Fit Width) set the
preview's zoom: fit width or a preset from 50% to 400%. Stepping from fit
width goes to the neighbouring preset of the scale it currently resolves to.
Zoom is per window and kept in scene storage, so a restored window keeps it.
Relayout keeps the same point of the score centered in the viewport.

Making pages actually draw at the zoomed size needed three fixes in how
pages are rendered:
- SVGKit draws at the image's own size, not the view's frame, so the page
  sets SVGKImage.size to its displayed size (CeolKit's pages carry a
  viewBox, so the drawing scales). Without a size, SVGKit converts the pt
  dimensions to ~1.83x, so pages had never matched their frames.
- SVGKLayer ignores re-assignment of the image it already shows, and
  resizing an image discards the layer tree it would need to remove, so a
  resize detaches the image first, then resizes and reattaches it.
- SVGPageView reports its size through sizeThatFits; otherwise SwiftUI kept
  a page's earlier size after a zoom change and centered it, offset, in
  its new frame.

Also fixes SVGPageView.svgSize, which never matched CeolKit's "612pt"
dimensions and so treated every page as portrait Letter; it now reads the
viewBox first and accepts pt/px units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sbeitzel
sbeitzel merged commit 01103e5 into main Sep 24, 2026
1 check passed
@sbeitzel
sbeitzel deleted the feature/38_preview_zoom branch September 25, 2026 19:32
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

1 participant