From b68e72f6bf65b024374c14ac416c3f6acc1e3d05 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 5 Jul 2026 12:46:29 +0100 Subject: [PATCH 1/2] refactor(font): remove the PDFBox-typed API and FontShowcase from the core font package Ahead of the 2.0 module split, take the core font package a step closer to backend-neutral. - DefaultFonts no longer declares library(PDDocument) or library(PDDocument, Collection). They were thin delegators to document.backend.fixed.pdf.PdfFontLibraryFactory, which already exposes the identical methods; the (test-scope) callers now call the factory directly. compose.font is now free of org.apache.pdfbox. - FontShowcase moves from compose.font to document.backend.fixed.pdf: it composes a preview through the document DSL and renders it, so it belongs with the PDF backend it rides. Its two tests move alongside, and its public methods gain javadoc (the pdf package is javadoc-gated with doclint=all; the font package was not). GraphCompose.renderAvailableFontsPreview() imports it from the new home. DefaultFonts.standardLibrary() and its EntityManager caller are intentionally left untouched; that remaining font-to-backend delegation is cut when the render/measurement seam lands. Behaviour is unchanged: the FontShowcase render/snapshot tests and every layout snapshot pass with zero baseline updates. --- CONTRIBUTING.md | 4 +-- .../java/com/demcha/compose/GraphCompose.java | 2 +- .../backend/fixed/pdf}/FontShowcase.java | 31 ++++++++++++++++++- .../com/demcha/compose/font/DefaultFonts.java | 23 -------------- .../document/api/DocumentSessionTest.java | 4 +-- .../pdf}/FontShowcaseLayoutSnapshotTest.java | 2 +- .../fixed/pdf}/FontShowcaseRenderTest.java | 3 +- .../integration/BlockTextIntegrationTest.java | 6 ++-- .../SingleLineTextIntegrationTest.java | 4 +-- .../font/FontLibraryIntegrationTest.java | 5 +-- .../testsupport/EngineComposerHarness.java | 4 +-- 11 files changed, 48 insertions(+), 40 deletions(-) rename src/main/java/com/demcha/compose/{font => document/backend/fixed/pdf}/FontShowcase.java (80%) rename src/test/java/com/demcha/compose/{font => document/backend/fixed/pdf}/FontShowcaseLayoutSnapshotTest.java (93%) rename src/test/java/com/demcha/compose/{font => document/backend/fixed/pdf}/FontShowcaseRenderTest.java (91%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5395019c3..0744a53fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,7 +90,7 @@ See [docs/contributing/release-process.md](./docs/contributing/release-process.m - `src/main/java/com/demcha/compose/document/layout` Canonical functional layout pipeline: `LayoutCompiler`, `BuiltInNodeDefinitions`, `TableLayoutSupport`, `PreparedNode`, `PlacedFragment` - `src/main/java/com/demcha/compose/document/backend/fixed/pdf` - PDF backend: `PdfFixedLayoutBackend`, fragment handlers, and the option translators that bridge canonical types to PDFBox + PDF backend: `PdfFixedLayoutBackend`, fragment handlers, the option translators that bridge canonical types to PDFBox, and `FontShowcase` (bundled-font preview renderer) - `src/main/java/com/demcha/compose/document/backend/semantic` Semantic exporters: `DocxSemanticBackend` (Apache POI based), `PptxSemanticBackend` (manifest skeleton) - `src/main/java/com/demcha/compose/document/templates/*` @@ -98,7 +98,7 @@ See [docs/contributing/release-process.md](./docs/contributing/release-process.m - `src/main/java/com/demcha/compose/engine/*` Internal shared engine foundation under the canonical surface (measure, paginate, place, render). Not part of the recommended public API - `src/main/java/com/demcha/compose/font` - Public font registry, `FontName`, default fonts, `FontShowcase` + Public font registry, `FontName`, default fonts - `src/test/java/com/demcha/documentation/*` Examples used to keep README/documentation snippets honest - `src/test/java/com/demcha/compose/engine/integration/*` diff --git a/src/main/java/com/demcha/compose/GraphCompose.java b/src/main/java/com/demcha/compose/GraphCompose.java index c9f877f8c..d27bce5b1 100644 --- a/src/main/java/com/demcha/compose/GraphCompose.java +++ b/src/main/java/com/demcha/compose/GraphCompose.java @@ -2,7 +2,7 @@ import com.demcha.compose.font.FontFamilyDefinition; import com.demcha.compose.font.FontName; -import com.demcha.compose.font.FontShowcase; +import com.demcha.compose.document.backend.fixed.pdf.FontShowcase; import com.demcha.compose.font.DefaultFonts; import com.demcha.compose.document.api.DocumentPageSize; import com.demcha.compose.document.api.DocumentSession; diff --git a/src/main/java/com/demcha/compose/font/FontShowcase.java b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcase.java similarity index 80% rename from src/main/java/com/demcha/compose/font/FontShowcase.java rename to src/main/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcase.java index 65b5f661f..8164ad9ad 100644 --- a/src/main/java/com/demcha/compose/font/FontShowcase.java +++ b/src/main/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcase.java @@ -1,4 +1,4 @@ -package com.demcha.compose.font; +package com.demcha.compose.document.backend.fixed.pdf; import com.demcha.compose.GraphCompose; import com.demcha.compose.document.api.DocumentPageSize; @@ -9,6 +9,8 @@ import com.demcha.compose.document.style.DocumentInsets; import com.demcha.compose.document.style.DocumentTextDecoration; import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.DefaultFonts; +import com.demcha.compose.font.FontName; import java.awt.Color; import java.nio.file.Path; @@ -28,6 +30,12 @@ public final class FontShowcase { private FontShowcase() { } + /** + * Renders a preview of every bundled font family to a PDF file on disk. + * + * @param outputFile destination path for the generated preview PDF + * @throws Exception if the document cannot be built or written + */ public static void renderAvailableFontsPreview(Path outputFile) throws Exception { try (DocumentSession document = GraphCompose.document(outputFile) .pageSize(DocumentPageSize.A4) @@ -40,6 +48,12 @@ public static void renderAvailableFontsPreview(Path outputFile) throws Exception } } + /** + * Renders a preview of every bundled font family and returns the PDF bytes. + * + * @return the generated preview document as PDF bytes + * @throws Exception if the document cannot be built or rendered + */ public static byte[] renderAvailableFontsPreview() throws Exception { try (DocumentSession document = GraphCompose.document() .pageSize(DocumentPageSize.A4) @@ -52,6 +66,14 @@ public static byte[] renderAvailableFontsPreview() throws Exception { } } + /** + * Renders a preview of the given font families and returns the PDF bytes. + * + * @param fonts logical font names to include; when {@code null} the full + * bundled catalog is used + * @return the generated preview document as PDF bytes + * @throws Exception if the document cannot be built or rendered + */ public static byte[] renderFontsPreview(Collection fonts) throws Exception { try (DocumentSession document = GraphCompose.document() .pageSize(DocumentPageSize.A4) @@ -64,6 +86,13 @@ public static byte[] renderFontsPreview(Collection fonts) throws Excep } } + /** + * Builds the font-preview content into an existing document session. + * + * @param document open document session to populate; must not be {@code null} + * @param fonts logical font names to showcase; when {@code null} the full + * bundled catalog is used + */ public static void buildShowcase(DocumentSession document, Collection fonts) { Objects.requireNonNull(document, "document").pageFlow(flow -> { flow.name("AvailableFontsPreview") diff --git a/src/main/java/com/demcha/compose/font/DefaultFonts.java b/src/main/java/com/demcha/compose/font/DefaultFonts.java index 9a8dda790..66223c07f 100644 --- a/src/main/java/com/demcha/compose/font/DefaultFonts.java +++ b/src/main/java/com/demcha/compose/font/DefaultFonts.java @@ -1,10 +1,8 @@ package com.demcha.compose.font; import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; -import org.apache.pdfbox.pdmodel.PDDocument; import java.util.ArrayList; -import java.util.Collection; import java.util.List; /** @@ -76,27 +74,6 @@ public static FontLibrary standardLibrary() { return PdfFontLibraryFactory.standardLibrary(); } - /** - * Creates a PDF-backed font library containing bundled families. - * - * @param document PDF document that owns loaded fonts - * @return font library - */ - public static FontLibrary library(PDDocument document) { - return PdfFontLibraryFactory.library(document); - } - - /** - * Creates a PDF-backed font library containing bundled and custom families. - * - * @param document PDF document that owns loaded fonts - * @param customFamilies document-local custom font families - * @return font library - */ - public static FontLibrary library(PDDocument document, Collection customFamilies) { - return PdfFontLibraryFactory.library(document, customFamilies); - } - /** * Returns bundled font family definitions. * diff --git a/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java b/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java index cbfe6894f..f273924a7 100644 --- a/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java +++ b/src/test/java/com/demcha/compose/document/api/DocumentSessionTest.java @@ -4,7 +4,7 @@ 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.PdfWatermarkOptions; -import com.demcha.compose.font.DefaultFonts; +import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; import com.demcha.compose.font.FontLibrary; import com.demcha.compose.engine.components.content.table.TableCellContent; import com.demcha.compose.engine.components.content.table.TableCellLayoutStyle; @@ -1097,7 +1097,7 @@ private CountingPrepareContext(NodeRegistry registry, PDRectangle pageSize, Marg this.registry = registry; this.canvas = LayoutCanvas.from(pageSize.getWidth(), pageSize.getHeight(), margin); this.measurementDocument = new PDDocument(); - this.fonts = DefaultFonts.library(measurementDocument); + this.fonts = PdfFontLibraryFactory.library(measurementDocument); this.textMeasurement = new CountingTextMeasurementSystem( new FontLibraryTextMeasurementSystem(fonts, PdfFont.class)); } diff --git a/src/test/java/com/demcha/compose/font/FontShowcaseLayoutSnapshotTest.java b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java similarity index 93% rename from src/test/java/com/demcha/compose/font/FontShowcaseLayoutSnapshotTest.java rename to src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java index 502559821..6cfafc037 100644 --- a/src/test/java/com/demcha/compose/font/FontShowcaseLayoutSnapshotTest.java +++ b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseLayoutSnapshotTest.java @@ -1,4 +1,4 @@ -package com.demcha.compose.font; +package com.demcha.compose.document.backend.fixed.pdf; import com.demcha.compose.GraphCompose; import com.demcha.compose.document.api.DocumentPageSize; diff --git a/src/test/java/com/demcha/compose/font/FontShowcaseRenderTest.java b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseRenderTest.java similarity index 91% rename from src/test/java/com/demcha/compose/font/FontShowcaseRenderTest.java rename to src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseRenderTest.java index eb273220e..2316fa154 100644 --- a/src/test/java/com/demcha/compose/font/FontShowcaseRenderTest.java +++ b/src/test/java/com/demcha/compose/document/backend/fixed/pdf/FontShowcaseRenderTest.java @@ -1,6 +1,7 @@ -package com.demcha.compose.font; +package com.demcha.compose.document.backend.fixed.pdf; import com.demcha.compose.GraphCompose; +import com.demcha.compose.font.FontName; import com.demcha.testing.VisualTestOutputs; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; diff --git a/src/test/java/com/demcha/compose/engine/integration/BlockTextIntegrationTest.java b/src/test/java/com/demcha/compose/engine/integration/BlockTextIntegrationTest.java index 1eee72876..ce3a7e9fe 100644 --- a/src/test/java/com/demcha/compose/engine/integration/BlockTextIntegrationTest.java +++ b/src/test/java/com/demcha/compose/engine/integration/BlockTextIntegrationTest.java @@ -1,7 +1,7 @@ package com.demcha.compose.engine.integration; import com.demcha.compose.GraphCompose; -import com.demcha.compose.font.DefaultFonts; +import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; import com.demcha.compose.testsupport.engine.assembly.ComponentBuilder; import com.demcha.compose.engine.components.content.text.TextIndentStrategy; import com.demcha.compose.engine.components.content.text.BlockTextData; @@ -167,7 +167,7 @@ void shouldIncreaseVerticalGapAroundMarkdownHeading() throws Exception { double baseStep; try (PDDocument fontDocument = new PDDocument()) { - PdfFont font = (PdfFont) DefaultFonts.library(fontDocument) + PdfFont font = (PdfFont) PdfFontLibraryFactory.library(fontDocument) .getFont(style.fontName(), PdfFont.class) .orElseThrow(); baseStep = font.getLineHeight(style) + 2.0; @@ -206,7 +206,7 @@ void shouldRespectOwnPaddingWhenPositioningBlockTextLines() throws Exception { double baselineOffsetFromBottom; try (PDDocument fontDocument = new PDDocument()) { - PdfFont font = (PdfFont) DefaultFonts.library(fontDocument) + PdfFont font = (PdfFont) PdfFontLibraryFactory.library(fontDocument) .getFont(style.fontName(), PdfFont.class) .orElseThrow(); baselineOffsetFromBottom = font.verticalMetrics(style).baselineOffsetFromBottom(); diff --git a/src/test/java/com/demcha/compose/engine/integration/SingleLineTextIntegrationTest.java b/src/test/java/com/demcha/compose/engine/integration/SingleLineTextIntegrationTest.java index b5aee5dd0..eae373195 100644 --- a/src/test/java/com/demcha/compose/engine/integration/SingleLineTextIntegrationTest.java +++ b/src/test/java/com/demcha/compose/engine/integration/SingleLineTextIntegrationTest.java @@ -1,7 +1,7 @@ package com.demcha.compose.engine.integration; import com.demcha.compose.GraphCompose; -import com.demcha.compose.font.DefaultFonts; +import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; import com.demcha.compose.engine.components.content.text.TextStyle; import com.demcha.compose.engine.components.core.Entity; import com.demcha.compose.engine.components.layout.Anchor; @@ -48,7 +48,7 @@ void shouldRenderAutosizedSingleLineTextAtPaddingAdjustedBaseline() throws Excep Placement placement = textEntity.getComponent(Placement.class).orElseThrow(); PdfFont.VerticalMetrics metrics; try (PDDocument fontDocument = new PDDocument()) { - PdfFont pdfFont = (PdfFont) DefaultFonts.library(fontDocument) + PdfFont pdfFont = (PdfFont) PdfFontLibraryFactory.library(fontDocument) .getFont(style.fontName(), PdfFont.class) .orElseThrow(); metrics = pdfFont.verticalMetrics(style); diff --git a/src/test/java/com/demcha/compose/font/FontLibraryIntegrationTest.java b/src/test/java/com/demcha/compose/font/FontLibraryIntegrationTest.java index 6e6d4b759..e81b4664a 100644 --- a/src/test/java/com/demcha/compose/font/FontLibraryIntegrationTest.java +++ b/src/test/java/com/demcha/compose/font/FontLibraryIntegrationTest.java @@ -1,6 +1,7 @@ package com.demcha.compose.font; import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; import com.demcha.compose.testsupport.EngineComposerHarness; import com.demcha.compose.engine.render.pdf.PdfFont; import com.demcha.compose.engine.render.word.WordFont; @@ -24,7 +25,7 @@ void shouldExposeBundledGoogleFontsInEngineComposerHarness() throws Exception { } try (PDDocument document = new PDDocument()) { - FontLibrary fonts = DefaultFonts.library(document); + FontLibrary fonts = PdfFontLibraryFactory.library(document); assertThat(fonts.getFont(FontName.LATO, PdfFont.class)).isPresent(); assertThat(fonts.getFont(FontName.LATO, WordFont.class)).isPresent(); assertThat(fonts.getFont(FontName.KANIT, PdfFont.class)).isPresent(); @@ -61,7 +62,7 @@ void shouldRegisterCustomFontFamilyFromFilePaths() throws Exception { .build(); try (PDDocument document = new PDDocument()) { - FontLibrary fonts = DefaultFonts.library(document, List.of(customDefinition)); + FontLibrary fonts = PdfFontLibraryFactory.library(document, List.of(customDefinition)); assertThat(fonts.getFont(customFamily, PdfFont.class)).isPresent(); assertThat(fonts.getFont(customFamily, WordFont.class)).isPresent(); } diff --git a/src/test/java/com/demcha/compose/testsupport/EngineComposerHarness.java b/src/test/java/com/demcha/compose/testsupport/EngineComposerHarness.java index 1d69ccd4e..03acf456a 100644 --- a/src/test/java/com/demcha/compose/testsupport/EngineComposerHarness.java +++ b/src/test/java/com/demcha/compose/testsupport/EngineComposerHarness.java @@ -1,6 +1,6 @@ package com.demcha.compose.testsupport; -import com.demcha.compose.font.DefaultFonts; +import com.demcha.compose.document.backend.fixed.pdf.PdfFontLibraryFactory; import com.demcha.compose.font.FontFamilyDefinition; import com.demcha.compose.font.FontName; import com.demcha.compose.testsupport.engine.assembly.ComponentBuilder; @@ -72,7 +72,7 @@ private EngineComposerHarness(Path outputFile, Margin margin, Collection customFontFamilies) { this.doc = new PDDocument(); - this.entityManager = new EntityManager(DefaultFonts.library(doc, customFontFamilies), markdown); + this.entityManager = new EntityManager(PdfFontLibraryFactory.library(doc, customFontFamilies), markdown); this.entityManager.setGuideLines(guideLines); this.componentBuilder = createComponentBuilder(entityManager); this.canvas = new PdfCanvas(pageSize, 0.0f, 0.0f); From 7b6e6f9f5cc81543e34c64671038395ede2f80f0 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 5 Jul 2026 13:07:12 +0100 Subject: [PATCH 2/2] test(font): guard that the font catalog stays PDFBox-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend PdfBackendIsolationGuardTest to scan com.demcha.compose.font. The font package is the logical font catalog — it names fonts and never loads them, so a file under it that imports org.apache.pdfbox now fails the guard. FontShowcase, which renders a preview PDF, already lives under backend/fixed/pdf and is exempted along with the rest of the pdf backend. --- .../PdfBackendIsolationGuardTest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/demcha/documentation/PdfBackendIsolationGuardTest.java b/src/test/java/com/demcha/documentation/PdfBackendIsolationGuardTest.java index d69f60ed7..8e4989ab9 100644 --- a/src/test/java/com/demcha/documentation/PdfBackendIsolationGuardTest.java +++ b/src/test/java/com/demcha/documentation/PdfBackendIsolationGuardTest.java @@ -16,9 +16,12 @@ /** * Architectural guard that keeps PDFBox out of the canonical document API, the - * layered template surface, and non-PDF fixed-layout contracts. Templates - * compose against the canonical DSL and must stay backend-neutral, so a preset - * or component that reached for a PDFBox type would fail here. + * layered template surface, non-PDF fixed-layout contracts, and the public font + * catalog. Templates compose against the canonical DSL and must stay + * backend-neutral, so a preset or component that reached for a PDFBox type would + * fail here. The {@code compose.font} package is the logical font catalog — it + * names fonts, it never loads them (PDFBox loading belongs to the pdf backend + * factory), so it is guarded too. */ class PdfBackendIsolationGuardTest { @@ -37,7 +40,12 @@ class PdfBackendIsolationGuardTest { PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/layout"), PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/snapshot"), PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/templates"), - PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/backend/fixed")); + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/document/backend/fixed"), + // The public font catalog names logical fonts; it never loads them. + // PDFBox loading lives in the pdf backend factory, so compose.font + // must stay PDFBox-free (FontShowcase, which renders a preview PDF, + // lives under backend/fixed/pdf and is exempted like the rest of it). + PROJECT_ROOT.resolve("src/main/java/com/demcha/compose/font")); @Test void pdfboxShouldStayOutOfCanonicalAndNonPdfBackendContracts() throws IOException {