Zentriere PDF-Vorschau via viewStack-Mindestgröße statt Timing-Hacks

Korrekte Ursachenanalyse: Im Zoom-Modus schrumpft der viewStack auf
Inhalts-Größe (ImageView). Ist der Inhalt kleiner als der Viewport,
positioniert ScrollPane den viewStack links/oben – setHvalue(0.5) ist
wirkungslos, weil nichts zu scrollen ist. Alle bisherigen runLater/
ChangeListener/AnimationTimer-Ansätze haben am falschen Hebel gedreht.

Korrekter Fix: viewportBoundsProperty-Listener im Konstruktor zwingt
viewStack auf mindestens Viewport-Größe. Pos.CENTER zentriert dann
die ImageView automatisch, wenn sie kleiner ist; bei größerem Inhalt
bleibt die Mindestgröße wirkungslos und der ScrollPane scrollt normal.

Ersatzlos entfernt: AnimationTimer-Block in applyZoom (wasInFitMode-
Zweig), Folge-Schritt-runLater (else-Zweig), setHvalue(0.5)/setVvalue(0.5)
in resetToFitView. Bindings in resetToFitView bleiben unverändert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 16:15:44 +02:00
parent 661894f1ec
commit 90d95b9ff8
@@ -17,7 +17,6 @@ import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Bounds;
@@ -244,6 +243,15 @@ public final class PdfPreviewPane {
viewStack.addEventHandler(MouseEvent.MOUSE_PRESSED, this::onPanMousePressed);
viewStack.addEventHandler(MouseEvent.MOUSE_DRAGGED, this::onPanMouseDragged);
viewStack.addEventHandler(MouseEvent.MOUSE_RELEASED, this::onPanMouseReleased);
// viewStack ist immer mindestens so groß wie der Viewport. Ist der Inhalt
// (ImageView) kleiner als der Viewport, sorgt diese Mindestgröße zusammen
// mit StackPane.Pos.CENTER dafür, dass die ImageView automatisch zentriert
// wird ohne manuelle setHvalue/setVvalue-Eingriffe. Ist der Inhalt größer,
// bleibt die Mindestgröße wirkungslos und der ScrollPane scrollt normal.
scrollPane.viewportBoundsProperty().addListener((obs, old, bounds) -> {
viewStack.setMinWidth(bounds.getWidth());
viewStack.setMinHeight(bounds.getHeight());
});
prevButton.setId("pdf-preview-prev-button");
prevButton.setOnAction(e -> navigateToPreviousPage());
@@ -726,33 +734,10 @@ public final class PdfPreviewPane {
zoomLevel = effective;
imageView.setFitWidth(naturalViewportWidth * zoomLevel);
imageView.setFitHeight(0);
if (wasInFitMode) {
// Erster Zoom-Schritt nach Verlassen des Fit-Modus: setFitToWidth(false)
// löst Layout-, CSS- und Property-Reset-Passes innerhalb desselben
// JavaFX-Frames aus. Ein AnimationTimer feuert handle() nach allen
// diesen Passes des aktuellen Frames; das ist der einzige Punkt,
// an dem setHvalue(0.5)/setVvalue(0.5) garantiert nicht mehr von
// einem nachträglichen Reset überschrieben werden. stop() im ersten
// handle()-Aufruf macht den Timer zum Single-Shot.
new AnimationTimer() {
@Override
public void handle(long now) {
stop();
scrollPane.setHvalue(0.5);
scrollPane.setVvalue(0.5);
}
}.start();
} else {
// Folge-Schritte: aktuelle Scroll-Position bewahren
double hval = scrollPane.getHvalue();
double vval = scrollPane.getVvalue();
Platform.runLater(() -> {
scrollPane.layout();
scrollPane.setHvalue(hval);
scrollPane.setVvalue(vval);
});
}
// Keine manuellen setHvalue/setVvalue-Eingriffe nötig: viewStack hat
// dank des viewportBoundsProperty-Listeners im Konstruktor mindestens
// Viewport-Größe, und Pos.CENTER sorgt für automatische Zentrierung,
// wenn der Inhalt kleiner als der Viewport ist.
}
/**
@@ -787,14 +772,11 @@ public final class PdfPreviewPane {
// den viewStack auf Viewport-Größe zurückrechnet
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
// 2./3. Bindings und H/V-Reset erst nach abgeschlossenem Layout-Pass,
// damit sie auf die zurückgerechneten Dimensionen wirken und
// Rounding-Reste aus Pan/Zoom die Zentrierung nicht verschieben
// 2. Bindings erst nach abgeschlossenem Layout-Pass, damit sie auf
// die zurückgerechneten viewStack-Dimensionen wirken
Platform.runLater(() -> {
imageView.fitWidthProperty().bind(viewStack.widthProperty());
imageView.fitHeightProperty().bind(viewStack.heightProperty());
scrollPane.setHvalue(0.5);
scrollPane.setVvalue(0.5);
});
}
}