diff --git a/CHANGELOG.md b/CHANGELOG.md index dd74d9e58..ae4222209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ PDF `GoTo` actions. External links are unchanged. ### Public API +- **`chrome().viewerPreferences(...)` + `DocumentViewerPreferences` / + `DocumentPageMode` / `DocumentPageLayout`** (`@since 1.9.0`). Controls how a PDF + reader presents the document on open — the page mode (`USE_OUTLINES` opens the + bookmark panel, pairing with `bookmark(...)`), the page layout (single / one-column + / two-column / two-page), and the window flags (`displayDocTitle`, `hideToolbar`, + `hideMenubar`, `fitWindow`, `centerWindow`). Written to the PDF document catalog; + `DocumentViewerPreferences.openOutline()` is a one-line preset. PDF-only — other + backends ignore it. A document that sets none is unchanged. + - **Container `bookmark(...)`** (`@since 1.9.0`). `bookmark(DocumentBookmarkOptions)` on any container flow builder (`Section` / `Container` / page flow) — previously only the seven leaf builders carried a bookmark — adds a PDF outline entry pointing diff --git a/assets/readme/examples/viewer-preferences.pdf b/assets/readme/examples/viewer-preferences.pdf new file mode 100644 index 000000000..105e573e9 Binary files /dev/null and b/assets/readme/examples/viewer-preferences.pdf differ diff --git a/examples/README.md b/examples/README.md index 68d89bd9c..d2f004097 100644 --- a/examples/README.md +++ b/examples/README.md @@ -103,6 +103,7 @@ are with the canonical DSL, then jump to its detailed section below. | [Charts](#charts) | Native vector bar, line, and pie/donut charts — data/spec/style layers, axis & grid toggles, point markers, value labels, legend | [PDF](../assets/readme/examples/chart-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/charts/ChartShowcaseExample.java) | | [PDF chrome](#pdf-chrome) | `DocumentMetadata`, `DocumentWatermark`, `DocumentHeaderFooter`, `DocumentBookmarkOptions` | [PDF](../assets/readme/examples/pdf-chrome.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/PdfChromeExample.java) | | [Page numbering](#page-numbering) | `DocumentPageNumbering` — offset / restart / roman / suppress-on-first-page for `{page}` / `{pages}` footer tokens | [PDF](../assets/readme/examples/page-numbering.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/PageNumberingExample.java) | +| [Viewer preferences](#viewer-preferences) | `chrome().viewerPreferences(...)` — open with the bookmark panel (`USE_OUTLINES`), set page layout, or show the doc title in the window | [PDF](../assets/readme/examples/viewer-preferences.pdf) · [Source](src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java) | | [Page references](#page-references) | `addPageReference(anchor)` — print the page an `anchor(...)` lands on (a native "see page N" cross-reference), resolved in one authoring pass | [PDF](../assets/readme/examples/page-reference.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/PageReferenceExample.java) | | [Table of contents](#table-of-contents) | `addTableOfContents(toc -> toc.entry(label, anchor))` — a native clickable TOC with dot leaders and auto-resolved page numbers | [PDF](../assets/readme/examples/table-of-contents.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/TocExample.java) | | [Container bookmarks](#container-bookmarks) | `section.bookmark(new DocumentBookmarkOptions(title))` — make a section / container a PDF outline (bookmark-panel) target | [PDF](../assets/readme/examples/container-bookmark.pdf) · [Source](src/main/java/com/demcha/examples/features/navigation/ContainerBookmarkExample.java) | @@ -742,6 +743,24 @@ session.chrome().footer(DocumentHeaderFooter.builder() [📄 View PDF](../assets/readme/examples/page-numbering.pdf) · [📜 Full source](src/main/java/com/demcha/examples/features/chrome/PageNumberingExample.java) +### Viewer preferences + +`chrome().viewerPreferences(...)` controls how a reader presents the document when +it opens — the page mode (`USE_OUTLINES` opens the bookmark panel, pairing with +`bookmark(...)` on sections), the page layout (e.g. two-column), and window-chrome +flags (`displayDocTitle`, `hideToolbar`, `fitWindow`, …). Written to the PDF +catalog; readers honour the subset they support. PDF-only — other backends ignore it. + +```java +document.chrome().viewerPreferences(DocumentViewerPreferences.builder() + .pageMode(DocumentPageMode.USE_OUTLINES) // open with the bookmark panel + .displayDocTitle(true) + .build()); +``` + +[📄 View PDF](../assets/readme/examples/viewer-preferences.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java) + ### Page references `addPageReference(anchor)` prints the page a declared `anchor(...)` lands on — a diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index b059c5ee1..6d00df999 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -7,6 +7,7 @@ import com.demcha.examples.features.docx.WordExportExample; import com.demcha.examples.features.chrome.PageNumberingExample; import com.demcha.examples.features.chrome.PdfChromeExample; +import com.demcha.examples.features.chrome.ViewerPreferencesExample; import com.demcha.examples.features.layout.BleedExample; import com.demcha.examples.features.layout.RowColumnsExample; import com.demcha.examples.features.layout.RowFlexExample; @@ -185,6 +186,7 @@ public static void main(String[] args) throws Exception { System.out.println("Generated: " + CustomBusinessThemeExample.generate()); System.out.println("Generated: " + PdfChromeExample.generate()); System.out.println("Generated: " + PageNumberingExample.generate()); + System.out.println("Generated: " + ViewerPreferencesExample.generate()); // DOCX export System.out.println("Generated: " + WordExportExample.generate()); diff --git a/examples/src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java b/examples/src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java new file mode 100644 index 000000000..20188e2a8 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/features/chrome/ViewerPreferencesExample.java @@ -0,0 +1,103 @@ +package com.demcha.examples.features.chrome; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.node.DocumentBookmarkOptions; +import com.demcha.compose.document.output.DocumentMetadata; +import com.demcha.compose.document.output.DocumentViewerPreferences; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentPageMode; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Runnable showcase for v1.9 {@code chrome().viewerPreferences(...)}: how a PDF + * reader presents the document when it opens. Here the page mode is + * {@code USE_OUTLINES} so the reader opens with the bookmark panel showing — it + * pairs with {@code bookmark(...)} on the sections — and {@code displayDocTitle} + * shows the document title in the window title bar. Viewer preferences are + * advisory; readers honour the ones they support. + * + *
{@code
+ * document.chrome().viewerPreferences(DocumentViewerPreferences.builder()
+ * .pageMode(DocumentPageMode.USE_OUTLINES) // open the bookmark panel
+ * .displayDocTitle(true)
+ * .build());
+ * }
+ *
+ * @author Artem Demchyshyn
+ */
+public final class ViewerPreferencesExample {
+
+ private static final DocumentColor INK = DocumentColor.rgb(24, 28, 38);
+ private static final DocumentColor MUTED = DocumentColor.rgb(120, 126, 135);
+
+ private static final String[][] CHAPTERS = {
+ {"Habitat", "Where the species lives."},
+ {"Diet", "What it eats through the seasons."},
+ {"Behaviour", "How it moves, nests, and migrates."},
+ };
+
+ private ViewerPreferencesExample() {
+ }
+
+ /**
+ * Renders a short guide that opens with the bookmark panel showing.
+ *
+ * @return path to the generated PDF
+ * @throws Exception if rendering or file IO fails
+ */
+ public static Path generate() throws Exception {
+ Path pdfFile = ExampleOutputPaths.prepare("features/chrome", "viewer-preferences.pdf");
+
+ DocumentTextStyle heading = DocumentTextStyle.DEFAULT.withSize(13).withColor(INK);
+ DocumentTextStyle body = DocumentTextStyle.DEFAULT.withSize(10).withColor(MUTED);
+
+ try (DocumentSession document = GraphCompose.document(pdfFile)
+ .pageSize(360, 320)
+ .margin(DocumentInsets.of(34))
+ .create()) {
+ document.metadata(DocumentMetadata.builder().title("Field Guide").build());
+ document.chrome().viewerPreferences(DocumentViewerPreferences.builder()
+ .pageMode(DocumentPageMode.USE_OUTLINES)
+ .displayDocTitle(true)
+ .build());
+
+ document.pageFlow(page -> {
+ page.addParagraph(p -> p.text("Field Guide")
+ .textStyle(DocumentTextStyle.DEFAULT.withSize(18).withColor(INK)));
+ page.addParagraph(p -> p.text("opens with the bookmark panel — viewerPreferences(USE_OUTLINES)")
+ .textStyle(DocumentTextStyle.DEFAULT.withSize(9).withColor(MUTED))
+ .padding(DocumentInsets.bottom(10)));
+
+ for (String[] chapter : CHAPTERS) {
+ chapterSection(page, heading, body, chapter[0], chapter[1]);
+ }
+ });
+
+ document.buildPdf();
+ }
+
+ return pdfFile;
+ }
+
+ private static void chapterSection(PageFlowBuilder page,
+ DocumentTextStyle heading,
+ DocumentTextStyle body,
+ String title,
+ String summary) {
+ page.addSection(s -> s.bookmark(new DocumentBookmarkOptions(title))
+ .spacing(2)
+ .addParagraph(p -> p.text(title).textStyle(heading))
+ .addParagraph(p -> p.text(summary).textStyle(body)));
+ page.addSpacer(s -> s.height(8));
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/api/DocumentChromeOptions.java b/src/main/java/com/demcha/compose/document/api/DocumentChromeOptions.java
index af483d660..412d638eb 100644
--- a/src/main/java/com/demcha/compose/document/api/DocumentChromeOptions.java
+++ b/src/main/java/com/demcha/compose/document/api/DocumentChromeOptions.java
@@ -36,6 +36,7 @@ final class DocumentChromeOptions {
private DocumentMetadata metadata;
private DocumentWatermark watermark;
private DocumentProtection protection;
+ private DocumentViewerPreferences viewerPreferences;
DocumentChromeOptions() {
}
@@ -44,6 +45,10 @@ void setMetadata(DocumentMetadata metadata) {
this.metadata = metadata;
}
+ void setViewerPreferences(DocumentViewerPreferences viewerPreferences) {
+ this.viewerPreferences = viewerPreferences;
+ }
+
void setWatermark(DocumentWatermark watermark) {
this.watermark = watermark;
}
@@ -76,6 +81,7 @@ boolean isEmpty() {
return metadata == null
&& watermark == null
&& protection == null
+ && viewerPreferences == null
&& headersAndFooters.isEmpty();
}
@@ -89,7 +95,8 @@ DocumentOutputOptions snapshot() {
if (isEmpty()) {
return DocumentOutputOptions.EMPTY;
}
- return new DocumentOutputOptions(metadata, watermark, protection, List.copyOf(headersAndFooters));
+ return new DocumentOutputOptions(metadata, watermark, protection, viewerPreferences,
+ List.copyOf(headersAndFooters));
}
/**
@@ -110,7 +117,8 @@ PdfFixedLayoutBackend toConveniencePdfBackend(DocumentDebugOptions debug) {
.debug(debug)
.metadata(PdfOutputOptionsTranslator.toPdf(metadata))
.watermark(PdfOutputOptionsTranslator.toPdf(watermark))
- .protect(PdfOutputOptionsTranslator.toPdf(protection));
+ .protect(PdfOutputOptionsTranslator.toPdf(protection))
+ .viewerPreferences(PdfOutputOptionsTranslator.toPdf(viewerPreferences));
for (DocumentHeaderFooter entry : headersAndFooters) {
PdfHeaderFooterOptions translated = PdfOutputOptionsTranslator.toPdf(entry);
if (entry.getZone() == DocumentHeaderFooterZone.FOOTER) {
diff --git a/src/main/java/com/demcha/compose/document/api/DocumentSession.java b/src/main/java/com/demcha/compose/document/api/DocumentSession.java
index 0103acc86..2df6151ac 100644
--- a/src/main/java/com/demcha/compose/document/api/DocumentSession.java
+++ b/src/main/java/com/demcha/compose/document/api/DocumentSession.java
@@ -492,6 +492,22 @@ public DocumentSession metadata(DocumentMetadata metadata) {
return this;
}
+ /**
+ * Sets the PDF viewer preferences — how a reader presents the document when it
+ * opens (page mode, page layout, window flags). PDF-only; other backends
+ * ignore it.
+ *
+ * @param viewerPreferences viewer preferences, or {@code null} to clear
+ * @return this session
+ * @throws IllegalStateException if this session has already been closed
+ * @since 1.9.0
+ */
+ public DocumentSession viewerPreferences(DocumentViewerPreferences viewerPreferences) {
+ ensureOpen();
+ chromeOptions.setViewerPreferences(viewerPreferences);
+ return this;
+ }
+
/**
* @param options legacy PDF metadata options, or {@code null} to clear
* @return this session
diff --git a/src/main/java/com/demcha/compose/document/api/SessionChromeApi.java b/src/main/java/com/demcha/compose/document/api/SessionChromeApi.java
index be0e7058a..f2b873c95 100644
--- a/src/main/java/com/demcha/compose/document/api/SessionChromeApi.java
+++ b/src/main/java/com/demcha/compose/document/api/SessionChromeApi.java
@@ -3,6 +3,7 @@
import com.demcha.compose.document.output.DocumentHeaderFooter;
import com.demcha.compose.document.output.DocumentMetadata;
import com.demcha.compose.document.output.DocumentProtection;
+import com.demcha.compose.document.output.DocumentViewerPreferences;
import com.demcha.compose.document.output.DocumentWatermark;
import java.util.Objects;
@@ -67,6 +68,22 @@ public SessionChromeApi metadata(DocumentMetadata metadata) {
return this;
}
+ /**
+ * Sets the PDF viewer preferences — how a reader presents the document when it
+ * opens (page mode, page layout, window flags). PDF-only; other backends
+ * ignore it.
+ *
+ * @param viewerPreferences viewer preferences, or {@code null} to clear
+ * @return this facade for chaining
+ * @throws IllegalStateException if the owning session has already been closed
+ * @since 1.9.0
+ */
+ public SessionChromeApi viewerPreferences(DocumentViewerPreferences viewerPreferences) {
+ ensureOpen();
+ chromeOptions.setViewerPreferences(viewerPreferences);
+ return this;
+ }
+
/**
* Configures a backend-neutral document-wide watermark. Pass
* {@code null} to clear.
diff --git a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfDocumentPostProcessor.java b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfDocumentPostProcessor.java
index f1705dc50..1b5cbe744 100644
--- a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfDocumentPostProcessor.java
+++ b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfDocumentPostProcessor.java
@@ -3,16 +3,24 @@
import com.demcha.compose.document.backend.fixed.pdf.options.PdfHeaderFooterOptions;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfMetadataOptions;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfProtectionOptions;
+import com.demcha.compose.document.backend.fixed.pdf.options.PdfViewerPreferencesOptions;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfWatermarkOptions;
import com.demcha.compose.document.layout.LayoutCanvas;
+import com.demcha.compose.document.style.DocumentPageLayout;
+import com.demcha.compose.document.style.DocumentPageMode;
import com.demcha.compose.engine.components.style.Margin;
import com.demcha.compose.engine.render.pdf.helpers.PdfHeaderFooterRenderer;
import com.demcha.compose.engine.render.pdf.helpers.PdfWatermarkRenderer;
import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
+import org.apache.pdfbox.pdmodel.PageLayout;
+import org.apache.pdfbox.pdmodel.PageMode;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
+import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -132,6 +140,78 @@ public static void applyDocumentMetadataAndProtection(PDDocument document,
}
}
+ /**
+ * Writes document-global viewer preferences to the PDF catalog: page mode,
+ * page layout, and the window-chrome flags. Each unset field leaves the
+ * reader's default in place. A no-op when {@code options} is {@code null}.
+ *
+ * @param document target PDFBox document
+ * @param options viewer-preference options, or {@code null}
+ * @since 1.9.0
+ */
+ public static void applyViewerPreferences(PDDocument document, PdfViewerPreferencesOptions options) {
+ if (options == null) {
+ return;
+ }
+ PDDocumentCatalog catalog = document.getDocumentCatalog();
+ if (options.getPageMode() != null) {
+ catalog.setPageMode(toPageMode(options.getPageMode()));
+ }
+ if (options.getPageLayout() != null) {
+ catalog.setPageLayout(toPageLayout(options.getPageLayout()));
+ }
+
+ PDViewerPreferences prefs = catalog.getViewerPreferences();
+ if (prefs == null) {
+ prefs = new PDViewerPreferences(new COSDictionary());
+ }
+ boolean anyFlag = false;
+ if (options.getDisplayDocTitle() != null) {
+ prefs.setDisplayDocTitle(options.getDisplayDocTitle());
+ anyFlag = true;
+ }
+ if (options.getHideToolbar() != null) {
+ prefs.setHideToolbar(options.getHideToolbar());
+ anyFlag = true;
+ }
+ if (options.getHideMenubar() != null) {
+ prefs.setHideMenubar(options.getHideMenubar());
+ anyFlag = true;
+ }
+ if (options.getFitWindow() != null) {
+ prefs.setFitWindow(options.getFitWindow());
+ anyFlag = true;
+ }
+ if (options.getCenterWindow() != null) {
+ prefs.setCenterWindow(options.getCenterWindow());
+ anyFlag = true;
+ }
+ if (anyFlag) {
+ catalog.setViewerPreferences(prefs);
+ }
+ }
+
+ private static PageMode toPageMode(DocumentPageMode mode) {
+ return switch (mode) {
+ case USE_NONE -> PageMode.USE_NONE;
+ case USE_OUTLINES -> PageMode.USE_OUTLINES;
+ case USE_THUMBNAILS -> PageMode.USE_THUMBS;
+ case FULL_SCREEN -> PageMode.FULL_SCREEN;
+ case USE_ATTACHMENTS -> PageMode.USE_ATTACHMENTS;
+ };
+ }
+
+ private static PageLayout toPageLayout(DocumentPageLayout layout) {
+ return switch (layout) {
+ case SINGLE_PAGE -> PageLayout.SINGLE_PAGE;
+ case ONE_COLUMN -> PageLayout.ONE_COLUMN;
+ case TWO_COLUMN_LEFT -> PageLayout.TWO_COLUMN_LEFT;
+ case TWO_COLUMN_RIGHT -> PageLayout.TWO_COLUMN_RIGHT;
+ case TWO_PAGE_LEFT -> PageLayout.TWO_PAGE_LEFT;
+ case TWO_PAGE_RIGHT -> PageLayout.TWO_PAGE_RIGHT;
+ };
+ }
+
private static void applyMetadata(PDDocument document, PdfMetadataOptions metadataOptions) {
PDDocumentInformation info = document.getDocumentInformation();
if (metadataOptions.getTitle() != null) {
diff --git a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackend.java b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackend.java
index f0f3ad04d..26a36191a 100644
--- a/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackend.java
+++ b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/PdfFixedLayoutBackend.java
@@ -56,17 +56,18 @@ public final class PdfFixedLayoutBackend implements FixedLayoutBackendEvery field is optional: a {@code null} leaves the reader's own default in + * place. Non-PDF backends ignore viewer preferences (a documented part of the + * {@code DocumentOutputOptions} contract). Set via + * {@code chrome().viewerPreferences(...)}.
+ * + * @author Artem Demchyshyn + * @since 1.9.0 + */ +@Getter +@Builder(toBuilder = true) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class DocumentViewerPreferences { + + /** Page mode (e.g. open the bookmark panel), or {@code null} for the reader default. */ + private final DocumentPageMode pageMode; + + /** Page layout (e.g. two-column), or {@code null} for the reader default. */ + private final DocumentPageLayout pageLayout; + + /** Show the document title in the window title bar instead of the file name. */ + private final Boolean displayDocTitle; + + /** Hide the reader's toolbar. */ + private final Boolean hideToolbar; + + /** Hide the reader's menu bar. */ + private final Boolean hideMenubar; + + /** Resize the window to fit the first page. */ + private final Boolean fitWindow; + + /** Centre the window on screen. */ + private final Boolean centerWindow; + + /** + * Preset that opens the reader with the bookmark (outline) panel visible — + * pairs with {@code bookmark(...)} on sections and containers. + * + * @return viewer preferences with {@link DocumentPageMode#USE_OUTLINES} + */ + public static DocumentViewerPreferences openOutline() { + return builder().pageMode(DocumentPageMode.USE_OUTLINES).build(); + } +} diff --git a/src/main/java/com/demcha/compose/document/style/DocumentPageLayout.java b/src/main/java/com/demcha/compose/document/style/DocumentPageLayout.java new file mode 100644 index 000000000..ecc790034 --- /dev/null +++ b/src/main/java/com/demcha/compose/document/style/DocumentPageLayout.java @@ -0,0 +1,23 @@ +package com.demcha.compose.document.style; + +/** + * How a PDF reader should lay out pages — the page layout written to the document + * catalog. + * + * @author Artem Demchyshyn + * @since 1.9.0 + */ +public enum DocumentPageLayout { + /** One page at a time (the default). */ + SINGLE_PAGE, + /** A single continuously scrolling column. */ + ONE_COLUMN, + /** Two continuous columns, odd-numbered pages on the left. */ + TWO_COLUMN_LEFT, + /** Two continuous columns, odd-numbered pages on the right. */ + TWO_COLUMN_RIGHT, + /** Two pages side by side, odd-numbered pages on the left. */ + TWO_PAGE_LEFT, + /** Two pages side by side, odd-numbered pages on the right. */ + TWO_PAGE_RIGHT +} diff --git a/src/main/java/com/demcha/compose/document/style/DocumentPageMode.java b/src/main/java/com/demcha/compose/document/style/DocumentPageMode.java new file mode 100644 index 000000000..34c77ccc7 --- /dev/null +++ b/src/main/java/com/demcha/compose/document/style/DocumentPageMode.java @@ -0,0 +1,21 @@ +package com.demcha.compose.document.style; + +/** + * How a PDF reader should present the document when it first opens — the page + * mode written to the document catalog. + * + * @author Artem Demchyshyn + * @since 1.9.0 + */ +public enum DocumentPageMode { + /** Neither the outline nor thumbnails panel is shown (the default). */ + USE_NONE, + /** Open with the bookmark (outline) panel visible. */ + USE_OUTLINES, + /** Open with the page-thumbnail panel visible. */ + USE_THUMBNAILS, + /** Open in full-screen mode, with no menu bar, window controls, or panels. */ + FULL_SCREEN, + /** Open with the attachments panel visible. */ + USE_ATTACHMENTS +} diff --git a/src/test/java/com/demcha/compose/document/backend/fixed/pdf/ViewerPreferencesTest.java b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/ViewerPreferencesTest.java new file mode 100644 index 000000000..70b1ab1de --- /dev/null +++ b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/ViewerPreferencesTest.java @@ -0,0 +1,144 @@ +package com.demcha.compose.document.backend.fixed.pdf; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.MultiSectionDocument; +import com.demcha.compose.document.output.DocumentViewerPreferences; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentPageLayout; +import com.demcha.compose.document.style.DocumentPageMode; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentCatalog; +import org.apache.pdfbox.pdmodel.PageLayout; +import org.apache.pdfbox.pdmodel.PageMode; +import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@code chrome().viewerPreferences(...)} writes page mode, page layout, and the + * window-chrome flags to the PDF document catalog. Asserted against the catalog + * dictionary (PDFBox), not pixels — viewer preferences are advisory and many + * readers honour only a subset. + */ +class ViewerPreferencesTest { + + @TempDir + Path tempDir; + + private PDDocument render(String name, Consumer