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 7d9c670..1d8fe0d 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 @@ -29,10 +29,10 @@ import javafx.scene.layout.VBox; * *

Die Komponente zeigt eine einzelne Seite der PDF-Datei vollständig eingepasst * (fit-to-view) an. Das Laden erfolgt auf einem Hintergrund-Worker-Thread; UI-Updates - * laufen ausschließlich über den JavaFX Application Thread. Nach dem Laden wird das - * Seitenverhältnis asynchron ermittelt und der Zoom so gesetzt, dass die Seite ohne - * Scrollbalken vollständig sichtbar ist. Bei Größenänderungen der Anzeigefläche wird - * der Zoom automatisch neu berechnet. + * laufen ausschließlich über den JavaFX Application Thread. Der Zoom wird anhand der + * aktuellen Anzeigefläche und eines A4-Seiten-Fallbacks berechnet, sodass die Seite + * ohne Scrollbalken vollständig sichtbar ist. Bei Größenänderungen der Anzeigefläche + * wird der Zoom automatisch neu berechnet. * *

Es gilt das Prinzip „Latest Preview Request Wins": Veraltete Lade-Ergebnisse * werden verworfen, sobald eine neue Anforderung eingeht. @@ -95,12 +95,6 @@ public final class PdfPreviewPane { /** Gibt an ob die Navigation bedienbar ist. */ private boolean enabled = true; - /** - * Zuletzt ermitteltes Seitenverhältnis (Höhe/Breite) der gerenderten Seite. - * Wird asynchron nach dem Laden gesetzt; -1.0 wenn noch nicht bekannt. - */ - private double lastKnownAspectRatio = -1.0; - /** * Erstellt die Komponente im deaktivierten Platzhalter-Zustand. */ @@ -112,7 +106,12 @@ public final class PdfPreviewPane { pdfView.setShowToolBar(false); pdfView.setId("pdf-preview-view"); - // Nach der Skin-Installation Scrollbalken im internen ScrollPane ausblenden. + // Scrollbalken per CSS ausblenden – gilt für den internen ScrollPane der Skin + pdfView.setStyle( + ".scroll-bar:vertical { -fx-pref-width: 0; visibility: hidden; }" + + ".scroll-bar:horizontal { -fx-pref-height: 0; visibility: hidden; }"); + + // Nach der Skin-Installation Scrollbalken im internen ScrollPane auch per Policy deaktivieren pdfView.skinProperty().addListener((obs, oldSkin, newSkin) -> { if (newSkin != null) { Platform.runLater(() -> { @@ -143,9 +142,9 @@ public final class PdfPreviewPane { StackPane.setAlignment(progressIndicator, Pos.CENTER); VBox.setVgrow(viewStack, Priority.ALWAYS); - // Bei Größenänderungen der Anzeigefläche Zoom neu berechnen - viewStack.widthProperty().addListener((obs, ov, nv) -> fitToView()); - viewStack.heightProperty().addListener((obs, ov, nv) -> fitToView()); + // Bei Größenänderungen der PDF-Ansicht Zoom neu berechnen + pdfView.widthProperty().addListener((obs, ov, nv) -> updateZoom()); + pdfView.heightProperty().addListener((obs, ov, nv) -> updateZoom()); prevButton.setId("pdf-preview-prev-button"); prevButton.setOnAction(e -> navigateToPreviousPage()); @@ -204,7 +203,6 @@ public final class PdfPreviewPane { currentSourceFile = null; currentPage = 0; totalPages = -1; - lastKnownAspectRatio = -1.0; // Neue Sequenznummer: laufende Requests werden verworfen currentRequestSequence.incrementAndGet(); pdfView.unload(); @@ -270,7 +268,8 @@ public final class PdfPreviewPane { currentPage = targetPage; updatePageLabel(); updateNavigationButtons(); - fitToView(); + // Zoom nach dem Rendering der neuen Seite neu berechnen + Platform.runLater(this::updateZoom); } private void navigateToNextPage() { @@ -282,72 +281,29 @@ public final class PdfPreviewPane { currentPage = targetPage; updatePageLabel(); updateNavigationButtons(); - fitToView(); + // Zoom nach dem Rendering der neuen Seite neu berechnen + Platform.runLater(this::updateZoom); } // --- Zoom-Berechnung (fit-to-view) ---------------------------------------- /** - * Berechnet den optimalen Zoomfaktor anhand des bekannten Seitenverhältnisses und - * der aktuellen Größe der Anzeigefläche und setzt ihn auf {@code pdfView}. - * Hat keinen Effekt wenn das Seitenverhältnis noch nicht bekannt ist oder die - * Anzeigefläche noch keine Größe hat. + * Berechnet den optimalen Zoomfaktor anhand der aktuellen Größe von {@code pdfView} + * und setzt ihn. Als Seitenmaß wird A4 im Hochformat (595 × 842 Punkte) als Fallback + * verwendet. Hat keinen Effekt, solange {@code pdfView} noch keine Größe hat. */ - private void fitToView() { - if (lastKnownAspectRatio <= 0.0) { + private void updateZoom() { + double panelWidth = pdfView.getWidth(); + double panelHeight = pdfView.getHeight(); + if (panelWidth <= 0 || panelHeight <= 0) { return; } - double vw = viewStack.getWidth(); - double vh = viewStack.getHeight(); - if (vw <= 0.0 || vh <= 0.0) { - return; - } - PDFView.Document doc = pdfView.getDocument(); - if (doc == null || currentPage <= 0) { - return; - } - // Querformat: Zoom=1 → Höhe füllt den Viewport; Breite kann überlaufen. - // Hochformat: Zoom=1 → Breite füllt den Viewport; Höhe kann überlaufen. - boolean landscape = doc.isLandscape(currentPage - 1); - double zoom; - if (!landscape) { - zoom = Math.min(1.0, vh / (lastKnownAspectRatio * vw)); - } else { - zoom = Math.min(1.0, vw / (lastKnownAspectRatio * vh)); - } - pdfView.setZoomFactor(Math.max(0.05, zoom)); - } - - /** - * Ermittelt das Seitenverhältnis der angegebenen Seite asynchron auf dem - * Worker-Thread und aktualisiert danach den Zoom auf dem FX-Thread. - * Veraltete Ergebnisse werden anhand der Sequenznummer verworfen. - * - * @param seq die Sequenznummer der aktuellen Lade-Anforderung - */ - private void fetchAspectRatioAsync(long seq) { - PDFView.Document doc = pdfView.getDocument(); - if (doc == null) { - return; - } - int pageIdx = Math.max(0, currentPage - 1); - executor.submit(() -> { - try { - // Seite bei sehr kleiner Skala rendern, um das Seitenverhältnis zu ermitteln - var img = doc.renderPage(pageIdx, 0.05f); - if (img != null && img.getWidth() > 0) { - double ratio = (double) img.getHeight() / img.getWidth(); - Platform.runLater(() -> { - if (currentRequestSequence.get() == seq) { - lastKnownAspectRatio = ratio; - fitToView(); - } - }); - } - } catch (Exception e) { - LOG.debug("PDF-Vorschau: Seitenverhältnis nicht ermittelbar: {}", e.getMessage()); - } - }); + double pageWidth = 595; // A4 Breite in Punkten (Fallback) + double pageHeight = 842; // A4 Höhe in Punkten (Fallback) + double zoomByWidth = panelWidth / pageWidth; + double zoomByHeight = panelHeight / pageHeight; + double zoom = Math.min(zoomByWidth, zoomByHeight) * 0.95; + pdfView.setZoomFactor(zoom); } // --- Asynchrones Laden --------------------------------------------------- @@ -405,8 +361,8 @@ public final class PdfPreviewPane { showContent(); updateNavigationButtons(); updatePageLabel(); - // Seitenverhältnis asynchron ermitteln und Zoom anpassen - fetchAspectRatioAsync(seq); + // Zoom nach dem ersten Rendering berechnen + Platform.runLater(this::updateZoom); LOG.debug("PDF-Vorschau: Rendering angestoßen – {} Seite(n)", totalPages); } catch (Exception e) { String msg = classifyLoadException(e);