diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java index e1b023d..4786780 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/PdfPreviewPane.java @@ -126,8 +126,12 @@ public final class PdfPreviewPane { private double zoomAccumulator = 0.0; /** - * Viewport-Breite zum Zeitpunkt des ersten manuellen Zooms; dient als Referenzbreite - * für alle nachfolgenden Zoomstufen. 0.0 bedeutet Fit-to-View-Modus ist aktiv. + * Referenzbreite für die manuelle Zoom-Skalierung; gilt + * {@code imageView.fitWidth = naturalViewportWidth × zoomLevel} im manuellen + * Zoom-Modus. Beim Verlassen des Fit-Modus wird der Wert auf die natürliche + * Bildbreite gesetzt, sodass {@code zoomLevel = 1.0} der pixel-genauen + * Originalgröße entspricht und {@code zoomLevel} damit gleich dem visuellen + * Skalierungsfaktor ist. {@code 0.0} bedeutet Fit-to-View-Modus ist aktiv. */ private double naturalViewportWidth = 0.0; @@ -655,12 +659,25 @@ public final class PdfPreviewPane { /** * Setzt den Zoomfaktor und verlässt beim ersten Aufruf den Fit-to-View-Modus. - * Die Referenzbreite wird einmalig aus dem Viewport übernommen. + *
+ * Beim ersten Aufruf (Wechsel aus dem Fit-Modus) wird {@code zoomLevel} auf + * den aktuellen visuellen Skalierungsfaktor kalibriert: aktuelle visuelle + * Breite der ImageView (mit {@code preserveRatio} bereits aspekt-korrekt + * verkleinert) geteilt durch die natürliche Bildbreite. Damit entspricht + * {@code zoomLevel = 1.0} der pixel-genauen Originalgröße, und der erste + * Zoom-Schritt addiert sich auf den realen Skalierungsfaktor. Ohne diese + * Kalibrierung springt die ImageView abrupt auf {@code Viewport-Breite × 1.10}, + * weil im Fit-Modus die {@code fitHeight}-Bindung das Bild aspekt-erhaltend + * deutlich kleiner zwingt als {@code naturalViewportWidth × 1.0} ergibt. + * Da der Caller den Delta-Schritt auf dem alten {@code zoomLevel = 1.0} + * berechnet hat, wird er nach der Kalibrierung auf den neuen, kalibrierten + * {@code zoomLevel} re-appliziert. *
* Beim Wechsel aus dem Fit-to-View-Modus wird die Ansicht auf die Bildmitte * zentriert (H/V = 0.5). Bei weiteren Zoom-Schritten bleibt die aktuelle - * Scrollposition erhalten. Ein {@code layout()}-Aufruf vor der Positionswiederherstellung - * stellt sicher, dass die neuen Inhaltsgrenzen bereits berechnet sind. + * Scrollposition erhalten. Ein {@code layout()}-Aufruf vor der + * Positionswiederherstellung stellt sicher, dass die neuen Inhaltsgrenzen + * bereits berechnet sind. * * @param newZoom gewünschter Zoomfaktor, wird auf [{@link #ZOOM_MIN}, {@link #ZOOM_MAX}] begrenzt */ @@ -669,17 +686,35 @@ public final class PdfPreviewPane { boolean wasInFitMode = scrollPane.isFitToWidth(); if (wasInFitMode) { - Bounds viewport = scrollPane.getViewportBounds(); - double vpWidth = viewport != null ? viewport.getWidth() : viewStack.getWidth(); - if (vpWidth <= 0) { - return; // Layout noch nicht abgeschlossen + Image image = imageView.getImage(); + if (image == null || image.getWidth() <= 0) { + return; // Kein Bild – Zoom-Kalibrierung nicht möglich } - naturalViewportWidth = vpWidth; + double naturalImageWidth = image.getWidth(); + double currentVisualWidth = imageView.getBoundsInLocal().getWidth(); + if (currentVisualWidth <= 0) { + Bounds viewport = scrollPane.getViewportBounds(); + currentVisualWidth = viewport != null ? viewport.getWidth() : viewStack.getWidth(); + if (currentVisualWidth <= 0) { + return; // Layout noch nicht abgeschlossen + } + } + + // Vom Caller intendierten Delta-Schritt vor der Kalibrierung sichern + double requestedDelta = newZoom - zoomLevel; + + // zoomLevel auf den aktuellen visuellen Skalierungsfaktor kalibrieren + naturalViewportWidth = naturalImageWidth; + zoomLevel = currentVisualWidth / naturalImageWidth; + + // effective neu berechnen, weil zoomLevel sich geändert hat + effective = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, zoomLevel + requestedDelta)); + scrollPane.setFitToWidth(false); scrollPane.setFitToHeight(false); imageView.fitWidthProperty().unbind(); imageView.fitHeightProperty().unbind(); - // 32d: Mauszeiger signalisiert Pan-Modus + // Mauszeiger signalisiert Pan-Modus viewStack.setCursor(Cursor.OPEN_HAND); } @@ -687,7 +722,7 @@ public final class PdfPreviewPane { return; } - // 32b: Beim ersten Zoom Mitte beibehalten; danach aktuelle Position bewahren + // Beim ersten Zoom Mitte beibehalten; danach aktuelle Position bewahren double hval = wasInFitMode ? 0.5 : scrollPane.getHvalue(); double vval = wasInFitMode ? 0.5 : scrollPane.getVvalue(); @@ -695,7 +730,7 @@ public final class PdfPreviewPane { imageView.setFitWidth(naturalViewportWidth * zoomLevel); imageView.setFitHeight(0); - // 32b: layout() stellt sicher, dass die neuen Inhaltsgrenzen bekannt sind, + // layout() stellt sicher, dass die neuen Inhaltsgrenzen bekannt sind, // bevor die Scroll-Werte restauriert werden Platform.runLater(() -> { scrollPane.layout();