Fix #29: Fit-to-view via statischem A4-Fallback statt asynchronem Seitenverhältnis
Ersetzt fetchAspectRatioAsync/fitToView durch updateZoom() mit A4-Dimensionen (595x842 Punkte) als Fallback. Scrollbalken werden per CSS und ScrollPane-Policy ausgeblendet. Zoom-Listener auf pdfView statt viewStack. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-76
@@ -29,10 +29,10 @@ import javafx.scene.layout.VBox;
|
|||||||
*
|
*
|
||||||
* <p>Die Komponente zeigt eine einzelne Seite der PDF-Datei vollständig eingepasst
|
* <p>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
|
* (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
|
* laufen ausschließlich über den JavaFX Application Thread. Der Zoom wird anhand der
|
||||||
* Seitenverhältnis asynchron ermittelt und der Zoom so gesetzt, dass die Seite ohne
|
* aktuellen Anzeigefläche und eines A4-Seiten-Fallbacks berechnet, sodass die Seite
|
||||||
* Scrollbalken vollständig sichtbar ist. Bei Größenänderungen der Anzeigefläche wird
|
* ohne Scrollbalken vollständig sichtbar ist. Bei Größenänderungen der Anzeigefläche
|
||||||
* der Zoom automatisch neu berechnet.
|
* wird der Zoom automatisch neu berechnet.
|
||||||
*
|
*
|
||||||
* <p>Es gilt das Prinzip „Latest Preview Request Wins": Veraltete Lade-Ergebnisse
|
* <p>Es gilt das Prinzip „Latest Preview Request Wins": Veraltete Lade-Ergebnisse
|
||||||
* werden verworfen, sobald eine neue Anforderung eingeht.
|
* werden verworfen, sobald eine neue Anforderung eingeht.
|
||||||
@@ -95,12 +95,6 @@ public final class PdfPreviewPane {
|
|||||||
/** Gibt an ob die Navigation bedienbar ist. */
|
/** Gibt an ob die Navigation bedienbar ist. */
|
||||||
private boolean enabled = true;
|
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.
|
* Erstellt die Komponente im deaktivierten Platzhalter-Zustand.
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +106,12 @@ public final class PdfPreviewPane {
|
|||||||
pdfView.setShowToolBar(false);
|
pdfView.setShowToolBar(false);
|
||||||
pdfView.setId("pdf-preview-view");
|
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) -> {
|
pdfView.skinProperty().addListener((obs, oldSkin, newSkin) -> {
|
||||||
if (newSkin != null) {
|
if (newSkin != null) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -143,9 +142,9 @@ public final class PdfPreviewPane {
|
|||||||
StackPane.setAlignment(progressIndicator, Pos.CENTER);
|
StackPane.setAlignment(progressIndicator, Pos.CENTER);
|
||||||
VBox.setVgrow(viewStack, Priority.ALWAYS);
|
VBox.setVgrow(viewStack, Priority.ALWAYS);
|
||||||
|
|
||||||
// Bei Größenänderungen der Anzeigefläche Zoom neu berechnen
|
// Bei Größenänderungen der PDF-Ansicht Zoom neu berechnen
|
||||||
viewStack.widthProperty().addListener((obs, ov, nv) -> fitToView());
|
pdfView.widthProperty().addListener((obs, ov, nv) -> updateZoom());
|
||||||
viewStack.heightProperty().addListener((obs, ov, nv) -> fitToView());
|
pdfView.heightProperty().addListener((obs, ov, nv) -> updateZoom());
|
||||||
|
|
||||||
prevButton.setId("pdf-preview-prev-button");
|
prevButton.setId("pdf-preview-prev-button");
|
||||||
prevButton.setOnAction(e -> navigateToPreviousPage());
|
prevButton.setOnAction(e -> navigateToPreviousPage());
|
||||||
@@ -204,7 +203,6 @@ public final class PdfPreviewPane {
|
|||||||
currentSourceFile = null;
|
currentSourceFile = null;
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
totalPages = -1;
|
totalPages = -1;
|
||||||
lastKnownAspectRatio = -1.0;
|
|
||||||
// Neue Sequenznummer: laufende Requests werden verworfen
|
// Neue Sequenznummer: laufende Requests werden verworfen
|
||||||
currentRequestSequence.incrementAndGet();
|
currentRequestSequence.incrementAndGet();
|
||||||
pdfView.unload();
|
pdfView.unload();
|
||||||
@@ -270,7 +268,8 @@ public final class PdfPreviewPane {
|
|||||||
currentPage = targetPage;
|
currentPage = targetPage;
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
fitToView();
|
// Zoom nach dem Rendering der neuen Seite neu berechnen
|
||||||
|
Platform.runLater(this::updateZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateToNextPage() {
|
private void navigateToNextPage() {
|
||||||
@@ -282,72 +281,29 @@ public final class PdfPreviewPane {
|
|||||||
currentPage = targetPage;
|
currentPage = targetPage;
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
fitToView();
|
// Zoom nach dem Rendering der neuen Seite neu berechnen
|
||||||
|
Platform.runLater(this::updateZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Zoom-Berechnung (fit-to-view) ----------------------------------------
|
// --- Zoom-Berechnung (fit-to-view) ----------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet den optimalen Zoomfaktor anhand des bekannten Seitenverhältnisses und
|
* Berechnet den optimalen Zoomfaktor anhand der aktuellen Größe von {@code pdfView}
|
||||||
* der aktuellen Größe der Anzeigefläche und setzt ihn auf {@code pdfView}.
|
* und setzt ihn. Als Seitenmaß wird A4 im Hochformat (595 × 842 Punkte) als Fallback
|
||||||
* Hat keinen Effekt wenn das Seitenverhältnis noch nicht bekannt ist oder die
|
* verwendet. Hat keinen Effekt, solange {@code pdfView} noch keine Größe hat.
|
||||||
* Anzeigefläche noch keine Größe hat.
|
|
||||||
*/
|
*/
|
||||||
private void fitToView() {
|
private void updateZoom() {
|
||||||
if (lastKnownAspectRatio <= 0.0) {
|
double panelWidth = pdfView.getWidth();
|
||||||
|
double panelHeight = pdfView.getHeight();
|
||||||
|
if (panelWidth <= 0 || panelHeight <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double vw = viewStack.getWidth();
|
double pageWidth = 595; // A4 Breite in Punkten (Fallback)
|
||||||
double vh = viewStack.getHeight();
|
double pageHeight = 842; // A4 Höhe in Punkten (Fallback)
|
||||||
if (vw <= 0.0 || vh <= 0.0) {
|
double zoomByWidth = panelWidth / pageWidth;
|
||||||
return;
|
double zoomByHeight = panelHeight / pageHeight;
|
||||||
}
|
double zoom = Math.min(zoomByWidth, zoomByHeight) * 0.95;
|
||||||
PDFView.Document doc = pdfView.getDocument();
|
pdfView.setZoomFactor(zoom);
|
||||||
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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Asynchrones Laden ---------------------------------------------------
|
// --- Asynchrones Laden ---------------------------------------------------
|
||||||
@@ -405,8 +361,8 @@ public final class PdfPreviewPane {
|
|||||||
showContent();
|
showContent();
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
updatePageLabel();
|
updatePageLabel();
|
||||||
// Seitenverhältnis asynchron ermitteln und Zoom anpassen
|
// Zoom nach dem ersten Rendering berechnen
|
||||||
fetchAspectRatioAsync(seq);
|
Platform.runLater(this::updateZoom);
|
||||||
LOG.debug("PDF-Vorschau: Rendering angestoßen – {} Seite(n)", totalPages);
|
LOG.debug("PDF-Vorschau: Rendering angestoßen – {} Seite(n)", totalPages);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String msg = classifyLoadException(e);
|
String msg = classifyLoadException(e);
|
||||||
|
|||||||
Reference in New Issue
Block a user