Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ for this cycle.
library. It lives on only as a styling helper inside the examples module; author
documents with explicit `DocumentColor` / `DocumentTextStyle` values or a template
`BrandTheme`.
- The DSL name-aliases `DocumentSession.builder()` and `DocumentDsl.text()` have been
removed. Use `DocumentSession.dsl()` and `DocumentDsl.paragraph()`.
- The PDF-typed document-chrome overloads on `DocumentSession` —
`metadata(PdfMetadataOptions)`, `watermark(PdfWatermarkOptions)`,
`protect(PdfProtectionOptions)`, `header(PdfHeaderFooterOptions)` and
`footer(PdfHeaderFooterOptions)` — have been removed in favour of the canonical,
backend-neutral overloads (`metadata(DocumentMetadata)`, `watermark(DocumentWatermark)`,
`protect(DocumentProtection)`, `header(DocumentHeaderFooter)`,
`footer(DocumentHeaderFooter)`). The PDF option types remain available on
`PdfFixedLayoutBackend.builder()` for advanced backend-level configuration.
- The `linkOptions()` accessor on the document nodes and inline runs (paragraph, table,
image, shape, ellipse, line, barcode, and the inline image / shape / text runs) has
been removed. Use `linkTarget()` and read the external URI from
`ExternalLinkTarget.options()`.
- The unused engine-internal `Font.adjustFontSizeToFit(...)` (and its `PdfFont` /
`WordFont` implementations) has been removed; text auto-sizing is resolved by the
layout compiler.

### Public API

Expand Down
16 changes: 16 additions & 0 deletions docs/migration/v2.0.0-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ Depend on `graph-compose` (or `graph-compose-bundle`) instead of `graph-compose-
the PDF backend is already present. See the
[troubleshooting entry](../troubleshooting.md#missingbackendexception-when-rendering).

## Removed deprecated APIs

2.0 also retires the `@Deprecated(forRemoval)` surface carried through the 1.6–1.9 line.
Every entry has a canonical replacement that already exists in 1.x, so migrate the call
sites before you upgrade:

| Removed | Replacement |
|---|---|
| `DocumentSession.builder()` | `DocumentSession.dsl()` |
| `DocumentDsl.text()` | `DocumentDsl.paragraph()` |
| `DocumentSession.metadata(PdfMetadataOptions)` and the matching `watermark` / `protect` / `header` / `footer` PDF-typed overloads | the canonical `metadata(DocumentMetadata)` / `watermark(DocumentWatermark)` / `protect(DocumentProtection)` / `header(DocumentHeaderFooter)` / `footer(DocumentHeaderFooter)` overloads |
| `linkOptions()` on the document nodes and inline runs | `linkTarget()` (read the external URI from `ExternalLinkTarget.options()`) |

The PDF option types (`PdfMetadataOptions` and friends) still exist for advanced
`PdfFixedLayoutBackend.builder()` configuration — only the session-level shortcuts are gone.

## Reference

- [README install matrix](../../README.md#installation) — the same table with copy-paste snippets.
Expand Down
11 changes: 5 additions & 6 deletions docs/recipes/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,19 @@ mapping.

## Header / footer chrome

`PdfHeaderFooterOptions` (configured on the document builder) adds
`DocumentHeaderFooter` (configured on the session) adds
a header zone and / or a footer zone that renders on every page
without affecting layout snapshots. The chrome ignores the layout
graph entirely — it's painted by the PDF backend after the body
fragments.

```java
import com.demcha.compose.document.backend.fixed.pdf.options.PdfHeaderFooterOptions;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfHeaderFooterZone;
import com.demcha.compose.document.output.DocumentHeaderFooter;

try (DocumentSession document = GraphCompose.document(Path.of("report.pdf"))
.header(PdfHeaderFooterOptions.builder()
.zone(PdfHeaderFooterZone.LEFT, "Quarterly Report")
.zone(PdfHeaderFooterZone.RIGHT, "Page {page} / {totalPages}")
.header(DocumentHeaderFooter.builder()
.leftText("Quarterly Report")
.rightText("Page {page} / {pages}")
.build())
.create()) {
// ... pageFlow content ...
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.demcha.compose.document.api;

import com.demcha.compose.GraphCompose;
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.PdfWatermarkOptions;
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.DocumentWatermark;
import com.demcha.compose.document.style.DocumentTextStyle;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
Expand All @@ -20,19 +20,15 @@
/**
* Verifies that the canonical convenience PDF entrypoints
* ({@code buildPdf}, {@code writePdf}, {@code toPdfBytes}) honour the
* document-level options configured directly on {@link DocumentSession}.
* backend-neutral document-level chrome configured directly on
* {@link DocumentSession} — metadata, watermark, protection, and
* header/footer.
*
* <p>This guarantees that callers do not need to instantiate
* {@link com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend}
* just to attach metadata, watermark, protection, or header/footer chrome.</p>
*
* <p>This test deliberately exercises the deprecated
* {@code DocumentSession#metadata(PdfMetadataOptions)} family of overloads
* (and their watermark/protect/header/footer siblings) to keep the
* compatibility path covered until removal in v2.0.</p>
* just to attach chrome; the session-level options reach the rendered PDF.</p>
*/
@SuppressWarnings("deprecation")
class DocumentSessionConveniencePdfOptionsTest {
class DocumentSessionConvenienceChromeTest {

@Test
void buildPdfAppliesSessionLevelMetadataWatermarkProtectionAndChrome() throws Exception {
Expand All @@ -42,28 +38,28 @@ void buildPdfAppliesSessionLevelMetadataWatermarkProtectionAndChrome() throws Ex
.margin(36, 36, 48, 36)
.create()) {

document.metadata(PdfMetadataOptions.builder()
document.metadata(DocumentMetadata.builder()
.title("Convenience Title")
.author("Session Author")
.subject("Session Subject")
.keywords("session, convenience")
.build())
.watermark(PdfWatermarkOptions.builder()
.watermark(DocumentWatermark.builder()
.text("DRAFT")
.fontSize(36)
.fontSize(36f)
.opacity(0.1f)
.build())
.protect(PdfProtectionOptions.builder()
.protect(DocumentProtection.builder()
.ownerPassword("owner-pass")
.userPassword("")
.canCopyContent(false)
.canPrint(true)
.build())
.header(PdfHeaderFooterOptions.builder()
.header(DocumentHeaderFooter.builder()
.centerText("Convenience Header")
.showSeparator(true)
.build())
.footer(PdfHeaderFooterOptions.builder()
.footer(DocumentHeaderFooter.builder()
.centerText("Page {page} of {pages}")
.build());

Expand Down Expand Up @@ -110,7 +106,7 @@ void toPdfBytesAppliesSessionLevelMetadata() throws Exception {
.margin(24, 24, 24, 24)
.create()) {

document.metadata(PdfMetadataOptions.builder()
document.metadata(DocumentMetadata.builder()
.title("In-Memory Title")
.author("Bytes Author")
.build());
Expand Down Expand Up @@ -141,10 +137,10 @@ void clearHeadersAndFootersRemovesPreviouslyRegisteredChrome() throws Exception
.margin(24, 24, 24, 24)
.create()) {

document.header(PdfHeaderFooterOptions.builder()
document.header(DocumentHeaderFooter.builder()
.centerText("Should Not Appear")
.build())
.footer(PdfHeaderFooterOptions.builder()
.footer(DocumentHeaderFooter.builder()
.centerText("Should Also Not Appear")
.build())
.clearHeadersAndFooters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfWatermarkOptions;
import com.demcha.compose.document.output.DocumentWatermark;
import com.demcha.compose.document.style.DocumentInsets;
import com.demcha.compose.testing.visual.ImageDiff;
import com.demcha.compose.testing.visual.PdfVisualRegression;
Expand Down Expand Up @@ -136,7 +136,7 @@ void postProcessingDecorationsLandInTheRaster() {
// rasterizer reads, so it must show up in the image (parity with the PDF).
try (DocumentSession plain = singlePage();
DocumentSession watermarked = singlePage()) {
watermarked.watermark(PdfWatermarkOptions.builder().text("DRAFT").build());
watermarked.watermark(DocumentWatermark.builder().text("DRAFT").build());

BufferedImage plainImage = plain.toImage(0, 96);
BufferedImage watermarkedImage = watermarked.toImage(0, 96);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.demcha.compose.document.dsl;

import com.demcha.compose.document.node.DocumentLinkOptions;
import com.demcha.compose.document.node.ExternalLinkTarget;
import com.demcha.compose.document.node.InlineImageAlignment;
import com.demcha.compose.document.node.InlineRun;
import com.demcha.compose.document.node.InlineShapeRun;
Expand Down Expand Up @@ -39,7 +40,7 @@ void textFactorySeedsBuilderWithPlainRun() {
InlineTextRun run = (InlineTextRun) runs.get(0);
assertThat(run.text()).isEqualTo("Hello");
assertThat(run.textStyle()).isNull();
assertThat(run.linkOptions()).isNull();
assertThat(run.linkTarget()).isNull();
}

@Test
Expand Down Expand Up @@ -104,8 +105,8 @@ void sizeRunOverridesFontSize() {
@Test
void linkRunCarriesLinkOptionsWithoutStyle() {
InlineTextRun run = (InlineTextRun) RichText.empty().link("Click", "https://example.com").runs().get(0);
assertThat(run.linkOptions()).isNotNull();
assertThat(run.linkOptions().uri()).isEqualTo("https://example.com");
assertThat(run.linkTarget()).isInstanceOf(ExternalLinkTarget.class);
assertThat(((ExternalLinkTarget) run.linkTarget()).options().uri()).isEqualTo("https://example.com");
assertThat(run.textStyle()).isNull();
}

Expand Down Expand Up @@ -189,7 +190,7 @@ void sectionBuilderAddRichWithExistingBuilderWorksToo() {
void linkRunWithExplicitOptionsPreservesAllFields() {
DocumentLinkOptions options = new DocumentLinkOptions("https://demcha.io");
InlineTextRun run = (InlineTextRun) RichText.empty().link("Author", options).runs().get(0);
assertThat(run.linkOptions()).isSameAs(options);
assertThat(((ExternalLinkTarget) run.linkTarget()).options()).isSameAs(options);
assertThat(run.text()).isEqualTo("Author");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ private static class DefaultMetricsFont extends FontBase<Object> {
@Override public double getTextHeight(TextStyle style) { return lineHeight; }
@Override public double getCapHeight(TextStyle style) { return 0.0; }
@Override public double scale(double size) { return size; }
@Override public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth) { return style; }
@Override public ContentSize getTightBounds(String text, TextStyle style) { return new ContentSize(0.0, 0.0); }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,6 @@ public PdfFont(PDFont defaultFont, PDFont bold, PDFont italic, PDFont boldItalic
super(defaultFont, bold, italic, boldItalic);
}


/**
* @param text the text to fit
* @param style the starting style
* @param availableWidth the width to fit within
* @return a re-sized style
* @deprecated Unused and incorrect — it re-measures with the unchanged
* {@code style}, so {@code textWidth} never shrinks and the loop runs to the
* minimum size regardless of fit. Canonical auto-size is handled by the
* layout compiler; kept only for binary compatibility.
*/
@Deprecated
@Override
public TextStyle adjustFontSizeToFit(String text, TextStyle style, double availableWidth) {
double textWidth = getTextWidth(style, text);

// If textWidth exceeds availableWidth, reduce the font size
double newSize = style.size();
while (textWidth > availableWidth && newSize > 1) {
newSize--; // Reduce size
textWidth = getTextWidth(style, text); // Recalculate text width
}
return new TextStyle(style.fontName(), newSize, style.decoration(), style.color());
}

public VerticalMetrics verticalMetrics(TextStyle style) {
PDFont pdfFont = fontType(style.decoration());
PDFontDescriptor descriptor = pdfFont.getFontDescriptor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.demcha.compose.document.backend.fixed.BackendProviders;
import com.demcha.compose.document.backend.fixed.FixedLayoutRenderer;
import com.demcha.compose.document.backend.fixed.pdf.options.*;
import com.demcha.compose.document.output.*;

import java.util.ArrayList;
Expand All @@ -16,14 +15,12 @@
*
* <p>The class is package-private and serves as a focused collaborator that
* keeps the session's public facade free of chrome-specific assembly logic.
* It owns three responsibilities:</p>
* It owns two responsibilities:</p>
*
* <ol>
* <li>persisting the canonical, backend-neutral chrome values</li>
* <li>snapshotting them into an immutable {@link DocumentOutputOptions}
* passed to semantic export backends</li>
* <li>translating them into the PDF backend's option types when the
* convenience PDF entrypoints assemble a {@link PdfFixedLayoutBackend}</li>
* passed to the output backends</li>
* </ol>
*
* <p>Instances are not thread-safe; the owning {@link DocumentSession}
Expand Down Expand Up @@ -111,35 +108,4 @@ DocumentOutputOptions snapshot() {
FixedLayoutRenderer toConveniencePdfBackend(DocumentDebugOptions debug) {
return BackendProviders.fixedLayout().create(snapshot(), debug);
}

// PDF-flavoured compatibility setters -----------------------------------

void setMetadata(PdfMetadataOptions options) {
setMetadata(options == null ? null : DocumentMetadata.builder()
.title(options.getTitle())
.author(options.getAuthor())
.subject(options.getSubject())
.keywords(options.getKeywords())
.creator(options.getCreator())
.producer(options.getProducer())
.build());
}

void setWatermark(PdfWatermarkOptions options) {
setWatermark(options == null ? null : PdfOutputOptionsToCanonical.toCanonical(options));
}

void setProtection(PdfProtectionOptions options) {
setProtection(options == null ? null : PdfOutputOptionsToCanonical.toCanonical(options));
}

void addHeader(PdfHeaderFooterOptions options) {
Objects.requireNonNull(options, "options");
addHeader(PdfOutputOptionsToCanonical.toCanonical(options.withZone(PdfHeaderFooterZone.HEADER)));
}

void addFooter(PdfHeaderFooterOptions options) {
Objects.requireNonNull(options, "options");
addFooter(PdfOutputOptionsToCanonical.toCanonical(options.withZone(PdfHeaderFooterZone.FOOTER)));
}
}
Loading