Skip to content

Commit 1de0cff

Browse files
authored
feat(testing): promote PdfVisualRegression + ImageDiff to public API (Track N N1, @SInCE 1.6.9) (#126)
Move the pixel-level visual-regression harness from the test source set to com.demcha.compose.testing.visual, sibling to the public com.demcha.compose.testing.layout.* snapshot helpers, so library consumers can run the same render-PDF -> diff-PNG baseline gate against their own presets. - PdfVisualRegression + ImageDiff + package-info public (@SInCE 1.6.9) - inline PDFBox PDFRenderer in renderPages(), drop the test-only PdfRenderBridge dependency from the public surface - eager 0..255 guard on perPixelTolerance(int) for fail-fast symmetry with renderScale/mismatchedPixelBudget, plus negative test - rewire 7 in-repo callers to the new package - sync test-your-document.md + CHANGELOG (v1.6.9 Public API) Behavior-preserving: visual baselines unchanged. Full suite 1059/1059 green.
1 parent a782797 commit 1de0cff

12 files changed

Lines changed: 74 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@ follow semantic versioning; release dates are ISO 8601.
55

66
## v1.6.9 — Planned
77

8-
Next bug-fix / housekeeping cycle. Track open in `docs/private/` taskboard.
8+
Housekeeping cycle plus the public pixel-level visual-regression API (Track N).
9+
10+
### Public API
11+
12+
- **Promoted the pixel-level visual-regression harness to public API.**
13+
`com.demcha.compose.testing.visual.PdfVisualRegression` and
14+
`com.demcha.compose.testing.visual.ImageDiff` (`@since 1.6.9`) move from the
15+
test source set into `src/main/java`, alongside the existing
16+
`com.demcha.compose.testing.layout.*` semantic snapshot helpers. Library
17+
consumers can now run the same render-PDF → diff-PNG baseline gate against
18+
their own presets and templates instead of copying the harness. Behaviour is
19+
unchanged; the PDF→image step is inlined on PDFBox's `PDFRenderer`.
920

1021
## v1.6.8 — 2026-06-01
1122

docs/operations/test-your-document.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,15 @@ shipped CV / cover-letter preset and for the engine showcase tests
221221
(see `CvV2VisualParityTest`, `CoverLetterV2VisualParityTest`,
222222
`TableRowSpanDemoTest` and friends).
223223

224-
The harness behind those tests
225-
(`com.demcha.testing.visual.PdfVisualRegression` +
226-
`ImageDiff`) is currently **test-only** inside the GraphCompose
227-
build. Promoting it to a public `com.demcha.compose.testing.visual.*`
228-
API so library consumers can adopt the same pixel-level gate against
229-
their own presets is queued as **v1.6.8 / v1.7.0 Track N** — see the
230-
release-readiness taskboard. Until that ships, the recommended
231-
public path is layout snapshot above; for pixel-level work, copy
232-
the pattern from `PdfVisualRegression` (it builds on the public
233-
`com.demcha.compose.devtool.PdfRenderBridge` for PDF page → image
234-
conversion).
224+
The harness behind those tests is the public
225+
`com.demcha.compose.testing.visual.PdfVisualRegression` +
226+
`ImageDiff` API (`@since 1.6.9`), a sibling to the
227+
`com.demcha.compose.testing.layout.*` snapshot helpers — library
228+
consumers can adopt the same pixel-level gate against their own
229+
presets. Start from `PdfVisualRegression.standard()`, point
230+
`baselineRoot(...)` at your own baseline directory, and call
231+
`assertMatchesBaseline(name, pdfBytes)`; run with
232+
`-Dgraphcompose.visual.approve=true` to (re)bless baselines.
235233

236234
---
237235

@@ -241,7 +239,7 @@ conversion).
241239
|---|---|
242240
| The document compiles + renders at all | smoke (just call `buildPdf()` in a test) |
243241
| The semantic graph and resolved coordinates are stable across engine refactors | **layout snapshot** |
244-
| The PDF visually looks identical, fonts/colours and all | pixel-level visual (Track N) |
242+
| The PDF visually looks identical, fonts/colours and all | **pixel-level visual** (`PdfVisualRegression`) |
245243
| A specific layout math rule holds | a focused unit test |
246244

247245
The advice scales: a flagship template or a preset you publish to

src/test/java/com/demcha/testing/visual/ImageDiff.java renamed to src/main/java/com/demcha/compose/testing/visual/ImageDiff.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.demcha.testing.visual;
1+
package com.demcha.compose.testing.visual;
22

33
import java.awt.Color;
44
import java.awt.image.BufferedImage;
@@ -21,6 +21,7 @@
2121
* can write the diff to disk for inspection.</p>
2222
*
2323
* @author Artem Demchyshyn
24+
* @since 1.6.9
2425
*/
2526
public final class ImageDiff {
2627

@@ -105,6 +106,7 @@ private static int blue(int rgb) {
105106
* @param maxChannelDelta largest single-channel delta observed
106107
* @param summary human-readable summary line for failure messages
107108
* @param diffImage optional visualisation; {@code null} when sizes differed
109+
* @since 1.6.9
108110
*/
109111
public record Result(
110112
boolean differs,

src/test/java/com/demcha/testing/visual/PdfVisualRegression.java renamed to src/main/java/com/demcha/compose/testing/visual/PdfVisualRegression.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
package com.demcha.testing.visual;
1+
package com.demcha.compose.testing.visual;
22

3-
import com.demcha.compose.devtool.PdfRenderBridge;
43
import org.apache.pdfbox.Loader;
54
import org.apache.pdfbox.pdmodel.PDDocument;
5+
import org.apache.pdfbox.rendering.ImageType;
6+
import org.apache.pdfbox.rendering.PDFRenderer;
67

78
import javax.imageio.ImageIO;
89
import java.awt.image.BufferedImage;
@@ -14,13 +15,19 @@
1415
import java.util.Objects;
1516

1617
/**
17-
* Test harness for "render PDF → diff PNG" visual regression checks.
18+
* Pixel-level visual-regression harness: renders a PDF and diffs each page
19+
* against a stored PNG baseline. Public companion to the semantic
20+
* {@code com.demcha.compose.testing.layout} snapshot layer — reach for this
21+
* when byte-for-byte pixel fidelity matters, and for the snapshot layer when
22+
* structural geometry is enough.
1823
*
19-
* <p>Each baseline lives at {@code src/test/resources/visual-baselines/&lt;name&gt;-page-N.png}.
20-
* In the default mode the harness renders the supplied PDF, converts each page
21-
* to a {@link BufferedImage} via {@link PdfRenderBridge}, and compares against
22-
* the baseline using {@link ImageDiff}. A failing comparison writes the actual
23-
* render and the diff image next to the baseline for inspection.</p>
24+
* <p>The default baseline directory is {@code src/test/resources/visual-baselines}
25+
* (override with {@link #baselineRoot(Path)}); each page is stored as
26+
* {@code &lt;name&gt;-page-N.png}. In the default mode the harness renders the
27+
* supplied PDF, converts each page to a {@link BufferedImage} with PDFBox's
28+
* {@link PDFRenderer}, and compares against the baseline using {@link ImageDiff}.
29+
* A failing comparison writes the actual render and the diff image next to the
30+
* baseline for inspection.</p>
2431
*
2532
* <p>To re-bless baselines, run the test with the system property
2633
* {@code -Dgraphcompose.visual.approve=true} (or environment variable
@@ -29,6 +36,7 @@
2936
* assertion.</p>
3037
*
3138
* @author Artem Demchyshyn
39+
* @since 1.6.9
3240
*/
3341
public final class PdfVisualRegression {
3442

@@ -94,8 +102,12 @@ public PdfVisualRegression renderScale(float renderScale) {
94102
*
95103
* @param perPixelTolerance tolerance per channel
96104
* @return updated harness
105+
* @throws IllegalArgumentException if {@code perPixelTolerance} is outside {@code 0..255}
97106
*/
98107
public PdfVisualRegression perPixelTolerance(int perPixelTolerance) {
108+
if (perPixelTolerance < 0 || perPixelTolerance > 255) {
109+
throw new IllegalArgumentException("perPixelTolerance must be 0..255, got " + perPixelTolerance);
110+
}
99111
return new PdfVisualRegression(baselineRoot, renderScale, perPixelTolerance, mismatchedPixelBudget);
100112
}
101113

@@ -176,9 +188,10 @@ public void assertMatchesBaseline(String baselineName, byte[] pdfBytes) throws I
176188
public List<BufferedImage> renderPages(byte[] pdfBytes) throws IOException {
177189
Objects.requireNonNull(pdfBytes, "pdfBytes");
178190
try (PDDocument document = Loader.loadPDF(pdfBytes)) {
191+
PDFRenderer renderer = new PDFRenderer(document);
179192
List<BufferedImage> pages = new ArrayList<>(document.getNumberOfPages());
180193
for (int i = 0; i < document.getNumberOfPages(); i++) {
181-
pages.add(PdfRenderBridge.renderToImage(document, i, renderScale));
194+
pages.add(renderer.renderImage(i, renderScale, ImageType.RGB));
182195
}
183196
return pages;
184197
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Public test helpers for pixel-level visual regression of rendered PDFs.
3+
*
4+
* <p>Ownership: Owned by the testing support surface and intended for consumer regression tests.</p>
5+
* <p>Extension rules: Extend with diff and render-harness utilities only; runtime engine code must not depend on this package.</p>
6+
*
7+
* @since 1.6.9
8+
*/
9+
package com.demcha.compose.testing.visual;

src/test/java/com/demcha/compose/document/templates/coverletter/presets/PresetVisualParityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterHeader;
88
import com.demcha.compose.document.templates.coverletter.spec.CoverLetterSpec;
99
import com.demcha.compose.document.theme.BusinessTheme;
10-
import com.demcha.testing.visual.PdfVisualRegression;
10+
import com.demcha.compose.testing.visual.PdfVisualRegression;
1111
import org.junit.jupiter.params.ParameterizedTest;
1212
import org.junit.jupiter.params.provider.Arguments;
1313
import org.junit.jupiter.params.provider.MethodSource;

src/test/java/com/demcha/compose/document/templates/coverletter/v2/presets/CoverLetterV2VisualParityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import com.demcha.compose.document.templates.api.DocumentTemplate;
77
import com.demcha.compose.document.templates.coverletter.v2.data.CoverLetterDocument;
88
import com.demcha.compose.document.templates.cv.v2.data.CvIdentity;
9-
import com.demcha.testing.visual.PdfVisualRegression;
9+
import com.demcha.compose.testing.visual.PdfVisualRegression;
1010
import org.junit.jupiter.params.ParameterizedTest;
1111
import org.junit.jupiter.params.provider.Arguments;
1212
import org.junit.jupiter.params.provider.MethodSource;

src/test/java/com/demcha/compose/document/templates/cv/presets/PresetVisualParityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import com.demcha.compose.document.templates.cv.spec.CvModule;
1414
import com.demcha.compose.document.templates.cv.spec.CvSpec;
1515
import com.demcha.compose.document.theme.BusinessTheme;
16-
import com.demcha.testing.visual.PdfVisualRegression;
16+
import com.demcha.compose.testing.visual.PdfVisualRegression;
1717
import org.junit.jupiter.params.ParameterizedTest;
1818
import org.junit.jupiter.params.provider.Arguments;
1919
import org.junit.jupiter.params.provider.MethodSource;

src/test/java/com/demcha/compose/document/templates/cv/v2/presets/CvV2VisualParityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import com.demcha.compose.document.templates.cv.v2.data.RowStyle;
1313
import com.demcha.compose.document.templates.cv.v2.data.RowsSection;
1414
import com.demcha.compose.document.templates.cv.v2.data.SkillsSection;
15-
import com.demcha.testing.visual.PdfVisualRegression;
15+
import com.demcha.compose.testing.visual.PdfVisualRegression;
1616
import org.junit.jupiter.params.ParameterizedTest;
1717
import org.junit.jupiter.params.provider.Arguments;
1818
import org.junit.jupiter.params.provider.MethodSource;

src/test/java/com/demcha/testing/visual/PdfVisualRegressionTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.demcha.compose.GraphCompose;
44
import com.demcha.compose.document.api.DocumentSession;
55
import com.demcha.compose.document.style.DocumentInsets;
6+
import com.demcha.compose.testing.visual.ImageDiff;
7+
import com.demcha.compose.testing.visual.PdfVisualRegression;
68
import org.junit.jupiter.api.Test;
79
import org.junit.jupiter.api.io.TempDir;
810

@@ -71,6 +73,16 @@ void differentSizesAreReportedAsMaxDelta() {
7173
assertThat(diff.diffImage()).isNull();
7274
}
7375

76+
@Test
77+
void perPixelToleranceOutsideChannelRangeIsRejected() {
78+
assertThatThrownBy(() -> PdfVisualRegression.standard().perPixelTolerance(-1))
79+
.isInstanceOf(IllegalArgumentException.class)
80+
.hasMessageContaining("perPixelTolerance");
81+
assertThatThrownBy(() -> PdfVisualRegression.standard().perPixelTolerance(256))
82+
.isInstanceOf(IllegalArgumentException.class)
83+
.hasMessageContaining("perPixelTolerance");
84+
}
85+
7486
@Test
7587
void renderingTheSameDocumentTwiceProducesPixelIdenticalPages() throws Exception {
7688
byte[] first = renderSampleDocument();

0 commit comments

Comments
 (0)