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
37 changes: 22 additions & 15 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,28 @@ Entries land here as they merge.
Consumers who relied on the helper can copy the former ~100-line class into
their own codebase or load configs directly with Jackson
(`new ObjectMapper(new YAMLFactory()).readValue(...)`).
- **PDF debug node labels** (`@since 1.8.0`). The debug overlay grew a second
layer: `PdfDebugOptions` (guides + node labels + label-text mode) configures
the canonical PDF backend via `GraphCompose.document(...).debug(...)`,
`DocumentSession.debug(...)`, or `PdfFixedLayoutBackend.builder().debug(...)`.
With `nodeLabels()` enabled, every rendered node prints its stable semantic
path — the same path `layoutSnapshot()` reports — once per node and page at
the node's top-left corner (5pt Helvetica on a pale halo), so a misplaced
block on the sheet reads straight back to the builder call that authored it.
`LabelText.NAME` (default) prints the compact own segment
(`PriceSummaryTitle[0]`); `FULL_PATH` prints the whole ancestry. The overlay
uses the base-14 Helvetica font (non-WinAnsi name characters degrade to
`?`), draws strictly on top of content, and never touches measurement or
pagination. `guideLines(boolean)` everywhere became sugar over the new
options — node-label settings survive the toggle — and disabled debug
output stays byte-identical.
- **Debug node labels** (`@since 1.8.0`). The debug overlay grew a second
layer: backend-neutral `DocumentDebugOptions` (guides + node labels +
label-text mode, in `document.output` next to the other neutral output
options) configures fixed-layout rendering via
`GraphCompose.document(...).debug(...)`, `DocumentSession.debug(...)`, or
`PdfFixedLayoutBackend.builder().debug(...)`. With `nodeLabels()` enabled,
every rendered node prints its stable semantic path — the same path
`layoutSnapshot()` reports — once per node and page, as a small corner
badge straddling the top edge of the node's bounds (right-aligned 5pt
Helvetica on a pale halo), so a misplaced block on the sheet reads straight
back to the builder call that authored it. Labels paint as a single
deterministic post-pass after all content, so badges always sit on top —
a container's children or a higher layer can never overdraw the label that
annotates them. `LabelText.NAME` (default) prints the compact own segment
(`PriceSummaryTitle[0]`); `FULL_PATH` prints the whole ancestry. Label text
degrades through the shared WinAnsi fallback (accents like `é` survive,
anything outside WinAnsi becomes `?` with a `glyph.missing` log). The
overlay draws strictly on top of content and never touches measurement or
pagination. `guideLines(boolean)` everywhere became sugar over the options
object with uniform last-write-wins semantics on all three surfaces —
node-label settings survive the toggle, `debug(none())` reliably disables
everything — and disabled debug output stays byte-identical.

### Bug fixes

Expand Down
Binary file modified assets/readme/examples/debug-overlay.pdf
Binary file not shown.
6 changes: 3 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ are with the canonical DSL, then jump to its detailed section below.
| [HTTP streaming](#http-streaming) | `writePdf(OutputStream)` for Servlet / S3 / GCS — caller's stream is not closed | [PDF](../assets/readme/examples/invoice-http-stream.pdf) · [Source](src/main/java/com/demcha/examples/features/streaming/HttpStreamingExample.java) |
| [Word export (DOCX)](#word-export-docx) | `DocxSemanticBackend` — the same session renders a fixed-layout PDF and an editable Word file; paragraphs / lists / tables / images map 1:1, charts fall back to their data table | [PDF](../assets/readme/examples/word-export-companion.pdf) · [DOCX](../assets/readme/examples/word-export-companion.docx) · [Source](src/main/java/com/demcha/examples/features/docx/WordExportExample.java) |
| [Layout snapshot regression](#layout-snapshot-regression) | Deterministic `layoutSnapshot()` workflow with baseline + drift report — production regression-testing pattern | [PDF](../assets/readme/examples/invoice-snapshot-regression.pdf) · [Source](src/main/java/com/demcha/examples/features/snapshots/LayoutSnapshotRegressionExample.java) |
| [Debug overlay](#debug-overlay) | `PdfDebugOptions` — guide lines + semantic node-path labels on the sheet; trace any misplaced block back to the builder call that authored it | [PDF](../assets/readme/examples/debug-overlay.pdf) · [Source](src/main/java/com/demcha/examples/features/debug/DebugOverlayExample.java) |
| [Debug overlay](#debug-overlay) | `DocumentDebugOptions` — guide lines + semantic node-path labels on the sheet; trace any misplaced block back to the builder call that authored it | [PDF](../assets/readme/examples/debug-overlay.pdf) · [Source](src/main/java/com/demcha/examples/features/debug/DebugOverlayExample.java) |
| [Business report cover](#business-report-cover) | Single-page Q1 investor brief — hero image, KPI cards, bar chart, metrics table | [PDF](../assets/readme/examples/business-report.pdf) · [Source](src/main/java/com/demcha/examples/flagships/BusinessReportExample.java) |
| [Master showcase](#master-showcase) | Kitchen-sink "Q2 sample report" combining the canonical surface end-to-end | [PDF](../assets/readme/examples/master-showcase.pdf) · [Source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) |
| Feature catalog | Browsable reference PDF: every shipped capability as a block — outline-clickable heading, the exact API call, the rendered result right under it | [PDF](../assets/readme/examples/feature-catalog.pdf) · [Source](src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java) |
Expand Down Expand Up @@ -653,7 +653,7 @@ label, then search that name in your builder code.

```java
try (DocumentSession document = GraphCompose.document(outputFile)
.debug(PdfDebugOptions.guidesAndNodeLabels())
.debug(DocumentDebugOptions.guidesAndNodeLabels())
.create()) {
document.pageFlow(page -> page
.module("InvoiceHeader", m -> m.paragraph("ACME Corp — Invoice 2026-104")));
Expand All @@ -662,7 +662,7 @@ try (DocumentSession document = GraphCompose.document(outputFile)
```

Labels default to the compact own segment (`InvoiceHeaderTitle[0]`);
`PdfDebugOptions.LabelText.FULL_PATH` prints the whole ancestor chain
`DocumentDebugOptions.LabelText.FULL_PATH` prints the whole ancestor chain
instead. Debug overlays draw strictly on top of content and never
affect measurement or pagination — disabling them returns the exact
production bytes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfDebugOptions;
import com.demcha.compose.document.output.DocumentDebugOptions;
import com.demcha.compose.document.style.DocumentInsets;
import com.demcha.examples.support.ExampleOutputPaths;

Expand All @@ -18,16 +18,17 @@
*
* <pre>{@code
* GraphCompose.document(out)
* .debug(PdfDebugOptions.guidesAndNodeLabels())
* .debug(DocumentDebugOptions.guidesAndNodeLabels())
* .create()
* }</pre>
*
* <p>Every rendered node then prints its stable semantic path — the same
* path {@code DocumentSession.layoutSnapshot()} reports — once per node
* and page at the node's top-left corner, next to the familiar fragment
* boxes and dashed margin/padding guides. Spot a misplaced block on
* and page as a corner badge straddling the top edge of the node's bounds
* (right-aligned), next to the familiar fragment boxes and dashed
* margin/padding guides. Spot a misplaced block on
* paper, read its label, and grep that name straight in your builder
* code. {@code PdfDebugOptions.LabelText.FULL_PATH} switches the labels
* code. {@code DocumentDebugOptions.LabelText.FULL_PATH} switches the labels
* from the compact own segment to the whole ancestor chain.</p>
*
* <p>Debug overlays draw strictly on top of regular content and never
Expand All @@ -53,7 +54,7 @@ public static Path generate() throws Exception {
try (DocumentSession document = GraphCompose.document(pdfFile)
.pageSize(DocumentPageSize.A4)
.margin(DocumentInsets.of(28))
.debug(PdfDebugOptions.guidesAndNodeLabels())
.debug(DocumentDebugOptions.guidesAndNodeLabels())
.create()) {
document.pageFlow(page -> page
.module("HowToReadThisSheet", module -> module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
feature("streaming", "invoice-http-stream", "HTTP Streaming", "Stream PDF directly to a Servlet response with no buffering.", "streaming", "http");
feature("snapshots", "invoice-snapshot-regression", "Layout Snapshots", "How LayoutSnapshotAssertions captures the resolved layout graph for regression testing.", "snapshots", "testing");
feature("docx", "word-export-companion", "Word Export (DOCX)", "DocxSemanticBackend — the same document as a fixed-layout PDF and an editable Word file; charts fall back to their data table.", "docx", "word", "export");
feature("debug", "debug-overlay", "Debug Overlay", "PdfDebugOptions — guide lines plus semantic node-path labels on the rendered sheet; trace any misplaced block back to the builder call that authored it.", "debug", "labels", "v1.8");
feature("debug", "debug-overlay", "Debug Overlay", "DocumentDebugOptions — guide lines plus semantic node-path labels on the rendered sheet; trace any misplaced block back to the builder call that authored it.", "debug", "labels", "v1.8");

// ===== Flagships =====
flagship("master-showcase", "Master Showcase", "Kitchen-sink demo combining every primitive into a single document — the full GraphCompose surface.", "showcase");
Expand Down
33 changes: 18 additions & 15 deletions src/main/java/com/demcha/compose/GraphCompose.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.demcha.compose.font.DefaultFonts;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfDebugOptions;
import com.demcha.compose.document.output.DocumentDebugOptions;
import com.demcha.compose.document.style.DocumentInsets;

import java.nio.file.Path;
Expand Down Expand Up @@ -140,8 +140,7 @@ public static final class DocumentBuilder {
private DocumentPageSize pageSize = DocumentPageSize.A4;
private DocumentInsets margin = DocumentInsets.zero();
private boolean markdown = true;
private boolean guideLines;
private PdfDebugOptions debug;
private DocumentDebugOptions debug = DocumentDebugOptions.none();
private com.demcha.compose.document.style.DocumentColor pageBackground;
private java.util.List<com.demcha.compose.document.api.PageBackgroundFill> pageBackgrounds;
private final List<FontFamilyDefinition> customFontFamilies = new ArrayList<>();
Expand Down Expand Up @@ -220,29 +219,35 @@ public DocumentBuilder markdown(boolean enabled) {
* {@link DocumentSession#toPdfBytes()}. It does not alter semantic layout
* geometry or layout snapshots.</p>
*
* <p>Shorthand for toggling only the guide overlay on the current
* {@link #debug(DocumentDebugOptions) debug} configuration — node-label
* settings are preserved, and the call order with {@code debug(...)}
* follows last-write-wins, exactly like the equivalent switches on
* {@code DocumentSession} and the PDF backend builder.</p>
*
* @param enabled {@code true} to draw debug guide-line overlays
* @return this builder
*/
public DocumentBuilder guideLines(boolean enabled) {
this.guideLines = enabled;
this.debug = this.debug.withGuides(enabled);
return this;
}

/**
* Configures PDF debug overlays (guide lines and semantic node labels)
* Configures debug overlays (guide lines and semantic node labels)
* for the session's convenience PDF output.
*
* <p>Combines with {@link #guideLines(boolean)}: when both switches are
* used, the guide overlay is enabled if either of them requests it.
* Like guide lines, debug overlays draw on top of regular content and
* never alter semantic layout geometry or layout snapshots.</p>
* <p>Replaces the whole debug configuration; {@code null} resets to
* {@link DocumentDebugOptions#none()}. Debug overlays draw on top of
* regular content and never alter semantic layout geometry or layout
* snapshots.</p>
*
* @param options debug overlay options, or {@code null} for none
* @return this builder
* @since 1.8.0
*/
public DocumentBuilder debug(PdfDebugOptions options) {
this.debug = options;
public DocumentBuilder debug(DocumentDebugOptions options) {
this.debug = options == null ? DocumentDebugOptions.none() : options;
return this;
}

Expand Down Expand Up @@ -410,10 +415,8 @@ public DocumentSession create() {
margin,
List.copyOf(customFontFamilies),
markdown,
guideLines);
if (debug != null) {
session.debug(debug.withGuides(debug.showGuides() || guideLines));
}
debug.showGuides());
session.debug(debug);
if (pageBackgrounds != null) {
// Explicit pageBackgrounds() call wins over a prior
// pageBackground(color). Empty list = clear; see builder Javadoc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ DocumentOutputOptions snapshot() {
* never {@code null}
* @return ready-to-use PDF backend
*/
PdfFixedLayoutBackend toConveniencePdfBackend(PdfDebugOptions debug) {
PdfFixedLayoutBackend toConveniencePdfBackend(DocumentDebugOptions debug) {
if (!debug.enabled() && isEmpty()) {
return new PdfFixedLayoutBackend();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.demcha.compose.document.backend.fixed.FixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.pdf.PdfMeasurementResources;
import com.demcha.compose.document.backend.fixed.pdf.options.PdfDebugOptions;
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;
Expand Down Expand Up @@ -74,7 +73,7 @@ public final class DocumentSession implements AutoCloseable {
private DocumentInsets margin;
private LayoutCanvas canvas;
private boolean markdown;
private PdfDebugOptions debug = PdfDebugOptions.none();
private DocumentDebugOptions debug = DocumentDebugOptions.none();
private List<PageBackgroundFill> pageBackgrounds = List.of();
private PdfMeasurementResources measurementResources;
private boolean closed;
Expand All @@ -101,7 +100,7 @@ public DocumentSession(Path defaultOutputFile,
this.margin = margin == null ? DocumentInsets.zero() : margin;
this.canvas = LayoutCanvas.from(pageSize.width(), pageSize.height(), toEngineMargin(this.margin));
this.markdown = markdown;
this.debug = PdfDebugOptions.none().withGuides(guideLines);
this.debug = DocumentDebugOptions.none().withGuides(guideLines);
this.registry = BuiltInNodeDefinitions.registerDefaults(new InvalidatingNodeRegistry());
this.compiler = new LayoutCompiler(registry);
this.customFontFamilies.addAll(List.copyOf(customFontFamilies));
Expand Down Expand Up @@ -310,7 +309,7 @@ public DocumentSession markdown(boolean enabled) {
* so existing layout cache entries remain valid.</p>
*
* <p>Shorthand for toggling only the guide overlay on the current
* {@link #debug(PdfDebugOptions) debug} configuration; node-label
* {@link #debug(DocumentDebugOptions) debug} configuration; node-label
* settings are preserved.</p>
*
* @param enabled {@code true} to draw debug guide-line overlays
Expand Down Expand Up @@ -340,9 +339,9 @@ public DocumentSession guideLines(boolean enabled) {
* @return this session
* @since 1.8.0
*/
public DocumentSession debug(PdfDebugOptions options) {
public DocumentSession debug(DocumentDebugOptions options) {
ensureOpen();
this.debug = options == null ? PdfDebugOptions.none() : options;
this.debug = options == null ? DocumentDebugOptions.none() : options;
return this;
}

Expand Down
Loading