feat(api): resolve rendering and text metrics through a ServiceLoader backend seam#297
Merged
Conversation
…finish the font/document decoupling Introduce a ServiceLoader-based font-metrics seam so the document API measures text without naming the PDF backend, and cut the last font->document edge. - New core SPI (com.demcha.compose.document.backend.fixed): FontMetricsProvider returns MeasurementResources (FontLibrary + TextMeasurementSystem, both backend-neutral); BackendProviders resolves it lazily via ServiceLoader and throws MissingBackendException (naming graph-compose-render-pdf) when absent. - PDF side: PdfFontMetricsProvider (registered in META-INF/services) delegates to the existing PdfMeasurementResources, which now implements MeasurementResources. DocumentSession opens measurement through the provider instead of importing PdfMeasurementResources. - DefaultFonts drops standardLibrary() (its last PdfFontLibraryFactory import); EntityManager's defaulting constructors now start from an empty FontLibrary instead of a PDF-backed standard set. compose.font is now free of any com.demcha.compose.document import — the engine->font->document cycle is broken. - Tests that need the standard families call PdfFontLibraryFactory.standardLibrary() directly. A new guard (fontCatalogStaysDocumentFree) freezes the invariant. Single-jar builds always discover the PDF provider, so measurement and layout are behaviour-identical; every layout snapshot passes with zero baseline updates.
Introduce the backend-neutral surface the document API's convenience output methods will drive, ahead of rewiring them off the concrete PDF backend: - FixedLayoutRenderer extends FixedLayoutBackend<byte[]> with the convenience operations the single render(...) contract does not cover: write(...), renderToImages(...), renderSections(...), writeSections(...). - SectionUnit is the backend-neutral multi-section input; its chrome is a FixedLayoutRenderer rather than a concrete backend. - FixedLayoutBackendProvider (ServiceLoader SPI) creates a FixedLayoutRenderer from a session's DocumentOutputOptions + DocumentDebugOptions. No behaviour change yet: these types are not wired in until the PDF backend implements FixedLayoutRenderer and the convenience paths resolve the provider.
… a ServiceLoader seam Wire the document API's convenience output methods onto the neutral render seam so they no longer import the concrete PDF backend. - FixedLayoutBackendProvider (ServiceLoader) creates a FixedLayoutRenderer from a session's DocumentOutputOptions + DocumentDebugOptions; BackendProviders gains a fixedLayout() locator alongside fontMetrics(). - PdfFixedLayoutBackend now implements FixedLayoutRenderer; its multi-section API takes the neutral SectionUnit (downcasting the section chrome back to the PDF backend it owns), and the PDF-only Section record is removed. - PdfFixedLayoutBackendProvider (registered in META-INF/services) absorbs the chrome -> PDF-option translation that lived in DocumentChromeOptions. - DocumentChromeOptions, MultiSectionDocument, DocumentSession, and DocumentRenderingFacade resolve the backend through the provider and expose FixedLayoutRenderer / SectionUnit instead of PdfFixedLayoutBackend. Single-jar builds discover the PDF provider, so rendering, rasterization, and multi-section assembly are behaviour-identical: every layout snapshot and visual baseline passes with zero updates.
…impls Add BackendProvidersTest asserting the bundled PDF providers resolve through BackendProviders (font-metrics + fixed-layout) and produce usable output — the explicit check behind the seam's transitive render/measurement coverage. Mark PdfFixedLayoutBackend.write / renderToImages / renderSections / writeSections @OverRide now that they implement FixedLayoutRenderer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The 2.0 module split needs a lean core that can render and measure text without compiling against a concrete backend — otherwise
graph-composecannot be separated from the PDF backend. This PR inverts the last hard couplings in the document API onto a runtime seam and, in doing so, finishes breaking theengine → font → documentpackage cycle.What changed
Two
ServiceLoaderSPIs in core (document.backend.fixed), resolved lazily and cached byBackendProviders, each failing withMissingBackendException(namingio.github.demchaav:graph-compose-render-pdf) when no provider is on the classpath:FontMetricsProvider→MeasurementResources(backend-neutralFontLibrary+TextMeasurementSystem).DocumentSessionnow opens measurement through it instead of importingPdfMeasurementResources.FixedLayoutBackendProvider→FixedLayoutRenderer(render / stream-write / rasterize / section-render).DocumentChromeOptions,MultiSectionDocument,DocumentRenderingFacade, andDocumentSessionresolve the backend through it; the multi-section input is the neutralSectionUnit.The PDF backend registers
PdfFontMetricsProvider+PdfFixedLayoutBackendProviderthroughMETA-INF/services;PdfFixedLayoutBackendimplementsFixedLayoutRenderer, and the chrome→PDF-option translation moved into its provider.render(FixedLayoutBackend)/export(SemanticBackend)remain the explicit, ServiceLoader-free paths.Cycle break:
DefaultFonts.standardLibrary()is removed (its lastdocumentimport),EntityManager's defaulting constructors start from an emptyFontLibrary, andcom.demcha.compose.fontnow imports nothing fromcom.demcha.compose.document— frozen by a newPdfBackendIsolationGuardTest.fontCatalogStaysDocumentFreecheck. The font catalog becomes a pure logical registry: it names fonts, backends load them.Verification
./mvnw clean verify -pl .— green. Every layout snapshot and visual baseline passes with zero updates, so rendering, rasterization, and multi-section assembly stay behaviour- and pixel-identical; the single-jar build always discovers the PDF providers../mvnw javadoc:javadoc -pl .— clean underdoclint=all; all new public types carry@since 2.0.0and full Javadoc.BackendProvidersTestpins the ServiceLoader resolution of both providers; the new guard freezescompose.font's document-freedom (bite-verified).BackendProvidersresolves each provider once and caches it — no ServiceLoader lookup per render or per node.The no-backend path of
MissingBackendExceptionbecomes exercisable once the PDF backend is a separately installable artifact; in the single jar it is satisfied by construction.