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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.demcha.compose.document.api;

import com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.pdf.PdfOutputOptionsTranslator;
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.*;

Expand Down Expand Up @@ -100,34 +100,16 @@ DocumentOutputOptions snapshot() {
}

/**
* Builds a configured {@link PdfFixedLayoutBackend} for the session's
* convenience PDF methods. When no debug overlay is enabled and no
* chrome is attached, returns the bare default backend so callers do
* not pay for empty option arrays.
* Resolves and configures the fixed-layout backend for the session's
* convenience output methods, translating the attached chrome through the
* registered
* {@link com.demcha.compose.document.backend.fixed.FixedLayoutBackendProvider}.
*
* @param debug debug overlay options for the convenience PDF backend;
* never {@code null}
* @return ready-to-use PDF backend
* @param debug debug overlay options; never {@code null}
* @return a configured renderer
*/
PdfFixedLayoutBackend toConveniencePdfBackend(DocumentDebugOptions debug) {
if (!debug.enabled() && isEmpty()) {
return new PdfFixedLayoutBackend();
}
PdfFixedLayoutBackend.Builder builder = PdfFixedLayoutBackend.builder()
.debug(debug)
.metadata(PdfOutputOptionsTranslator.toPdf(metadata))
.watermark(PdfOutputOptionsTranslator.toPdf(watermark))
.protect(PdfOutputOptionsTranslator.toPdf(protection))
.viewerPreferences(PdfOutputOptionsTranslator.toPdf(viewerPreferences));
for (DocumentHeaderFooter entry : headersAndFooters) {
PdfHeaderFooterOptions translated = PdfOutputOptionsTranslator.toPdf(entry);
if (entry.getZone() == DocumentHeaderFooterZone.FOOTER) {
builder.footer(translated);
} else {
builder.header(translated);
}
}
return builder.build();
FixedLayoutRenderer toConveniencePdfBackend(DocumentDebugOptions debug) {
return BackendProviders.fixedLayout().create(snapshot(), debug);
}

// PDF-flavoured compatibility setters -----------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.demcha.compose.document.backend.fixed.FixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.FixedLayoutRenderContext;
import com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.FixedLayoutRenderer;
import com.demcha.compose.document.backend.semantic.SemanticBackend;
import com.demcha.compose.document.backend.semantic.SemanticExportContext;
import com.demcha.compose.document.layout.DocumentGraph;
Expand Down Expand Up @@ -224,6 +224,6 @@ interface Context {

DocumentOutputOptions outputOptions();

PdfFixedLayoutBackend conveniencePdfBackend();
FixedLayoutRenderer conveniencePdfBackend();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.demcha.compose.document.api;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.backend.fixed.BackendProviders;
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.FixedLayoutRenderer;
import com.demcha.compose.document.backend.fixed.MeasurementResources;
import com.demcha.compose.document.backend.fixed.SectionUnit;
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 @@ -89,7 +91,7 @@ public final class DocumentSession implements AutoCloseable {
private DocumentDebugOptions debug = DocumentDebugOptions.none();
private List<PageBackgroundFill> pageBackgrounds = List.of();
private List<PageMarginRule> pageMargins = List.of();
private PdfMeasurementResources measurementResources;
private MeasurementResources measurementResources;
private boolean closed;


Expand Down Expand Up @@ -1239,7 +1241,7 @@ private void refreshMeasurementServices() {
// Best-effort close while reloading measurement resources.
}

measurementResources = PdfMeasurementResources.open(customFontFamilies);
measurementResources = BackendProviders.fontMetrics().openMeasurement(customFontFamilies);
}

private void invalidate() {
Expand Down Expand Up @@ -1293,10 +1295,10 @@ public <E extends DocumentNode> NodeRegistry register(NodeDefinition<E> definiti
*
* @return a render unit for this section
*/
com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend.Section toSectionRenderUnit() {
SectionUnit toSectionRenderUnit() {
ensureOpen();
ensureRenderable();
return new com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend.Section(
return new SectionUnit(
layoutGraph(),
canvas,
List.copyOf(customFontFamilies),
Expand Down Expand Up @@ -1359,7 +1361,7 @@ public DocumentOutputOptions outputOptions() {
}

@Override
public PdfFixedLayoutBackend conveniencePdfBackend() {
public FixedLayoutRenderer conveniencePdfBackend() {
return chromeOptions.toConveniencePdfBackend(debug);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.demcha.compose.document.api;

import com.demcha.compose.document.backend.fixed.pdf.PdfFixedLayoutBackend;
import com.demcha.compose.document.backend.fixed.BackendProviders;
import com.demcha.compose.document.backend.fixed.FixedLayoutRenderer;
import com.demcha.compose.document.backend.fixed.SectionUnit;
import com.demcha.compose.document.exceptions.DocumentRenderingException;
import com.demcha.compose.document.output.DocumentDebugOptions;
import com.demcha.compose.document.output.DocumentOutputOptions;
import com.demcha.compose.document.snapshot.LayoutSnapshot;

import java.io.OutputStream;
Expand Down Expand Up @@ -43,7 +47,8 @@ public final class MultiSectionDocument implements AutoCloseable {

private final Path defaultOutputFile;
private final List<DocumentSession> sections;
private final PdfFixedLayoutBackend backend = new PdfFixedLayoutBackend();
private final FixedLayoutRenderer backend =
BackendProviders.fixedLayout().create(DocumentOutputOptions.EMPTY, DocumentDebugOptions.none());
private boolean closed;

MultiSectionDocument(Path defaultOutputFile, List<DocumentSession> sections) {
Expand Down Expand Up @@ -143,7 +148,7 @@ public void close() {
}
}

private List<PdfFixedLayoutBackend.Section> renderUnits() {
private List<SectionUnit> renderUnits() {
ensureOpen();
return sections.stream().map(DocumentSession::toSectionRenderUnit).toList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.demcha.compose.document.backend.fixed;

import com.demcha.compose.document.exceptions.MissingBackendException;

import java.util.ServiceLoader;
import java.util.function.Supplier;

/**
* Locates the fixed-layout backend service providers registered on the
* classpath.
*
* <p>Since GraphCompose 2.0 the render/measurement backend ships as a separate
* artifact discovered through {@link ServiceLoader}. This locator resolves each
* provider once, lazily, and caches it. When no provider is registered it throws
* {@link MissingBackendException} naming the artifact to add. The explicit
* {@code render(FixedLayoutBackend)} / {@code export(SemanticBackend)} paths do
* not go through here — they use a caller-supplied backend.</p>
*
* @since 2.0.0
*/
public final class BackendProviders {

private static final String MISSING_BACKEND_MESSAGE =
"No fixed-layout render backend on the classpath: add the "
+ "io.github.demchaav:graph-compose-render-pdf artifact (or the "
+ "io.github.demchaav:graph-compose-bundle aggregate, or another "
+ "provider implementation) to render, rasterize, or measure a document.";

private static volatile FontMetricsProvider fontMetrics;
private static volatile FixedLayoutBackendProvider fixedLayout;

private BackendProviders() {
}

/**
* Returns the registered fixed-layout backend provider, resolving and caching
* it on first use.
*
* @return the fixed-layout backend provider
* @throws MissingBackendException if no provider is registered on the classpath
*/
public static FixedLayoutBackendProvider fixedLayout() {
FixedLayoutBackendProvider provider = fixedLayout;
if (provider == null) {
provider = load(FixedLayoutBackendProvider.class);
fixedLayout = provider;
}
return provider;
}

/**
* Returns the registered font-metrics provider, resolving and caching it on
* first use.
*
* @return the font-metrics provider
* @throws MissingBackendException if no provider is registered on the classpath
*/
public static FontMetricsProvider fontMetrics() {
FontMetricsProvider provider = fontMetrics;
if (provider == null) {
provider = load(FontMetricsProvider.class);
fontMetrics = provider;
}
return provider;
}

private static <T> T load(Class<T> service) {
return ServiceLoader.load(service, service.getClassLoader())
.findFirst()
.orElseThrow(missingBackend());
}

private static Supplier<MissingBackendException> missingBackend() {
return () -> new MissingBackendException(MISSING_BACKEND_MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.demcha.compose.document.backend.fixed;

import com.demcha.compose.document.output.DocumentDebugOptions;
import com.demcha.compose.document.output.DocumentOutputOptions;

/**
* Service-provider that supplies a fixed-layout render backend to the document
* API's convenience output methods without exposing a concrete backend type.
*
* <p>Implementations are discovered through {@link java.util.ServiceLoader};
* each render backend artifact registers one (for example the PDF backend in
* {@code io.github.demchaav:graph-compose-render-pdf}). The core resolves a
* provider lazily via {@link BackendProviders#fixedLayout()} and fails with a
* {@link com.demcha.compose.document.exceptions.MissingBackendException} naming
* the artifact to add when none is registered.</p>
*
* @since 2.0.0
*/
public interface FixedLayoutBackendProvider {

/**
* Returns the output format this provider renders, used for diagnostics and
* manifests.
*
* @return a stable format identifier such as {@code "pdf"}
*/
String format();

/**
* Creates a backend configured with the session's chrome and debug overlay.
*
* @param chrome document-level chrome (metadata, watermark, protection,
* repeating headers and footers); never {@code null}
* @param debug debug overlay options; never {@code null}
* @return a configured, ready-to-use renderer
*/
FixedLayoutRenderer create(DocumentOutputOptions chrome, DocumentDebugOptions debug);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.demcha.compose.document.backend.fixed;

import com.demcha.compose.document.layout.LayoutGraph;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.util.List;

/**
* A configured fixed-layout backend ready to render a resolved document — the
* backend-neutral surface the document API's convenience output methods drive.
*
* <p>A {@link FixedLayoutBackendProvider} produces one of these from a session's
* chrome, so {@code DocumentSession} and {@code MultiSectionDocument} can render
* to bytes, stream, rasterize, or concatenate sections without importing a
* concrete backend. It extends {@link FixedLayoutBackend} with the extra
* convenience operations the single {@code render(...)} contract does not
* cover.</p>
*
* @since 2.0.0
*/
public interface FixedLayoutRenderer extends FixedLayoutBackend<byte[]> {

/**
* Renders one resolved graph and writes the result to the stream carried by
* the render context.
*
* @param graph resolved layout graph
* @param context render-pass configuration and stream target
* @throws Exception if rendering fails
*/
void write(LayoutGraph graph, FixedLayoutRenderContext context) throws Exception;

/**
* Rasterizes a resolved graph to one image per page (or a single page).
*
* @param graph resolved layout graph
* @param context render-pass configuration
* @param dpi raster resolution in dots per inch
* @param transparent {@code true} for an alpha channel instead of a white background
* @param pageIndex zero-based page to render, or a negative value for all pages
* @return one image per rendered page
* @throws Exception if rendering or rasterization fails
* @throws IndexOutOfBoundsException if {@code pageIndex} is out of range
*/
List<BufferedImage> renderToImages(LayoutGraph graph,
FixedLayoutRenderContext context,
int dpi,
boolean transparent,
int pageIndex) throws Exception;

/**
* Renders several sections concatenated into a single document and returns
* the bytes.
*
* @param sections ordered sections to concatenate
* @return rendered document bytes
* @throws Exception if rendering fails
*/
byte[] renderSections(List<SectionUnit> sections) throws Exception;

/**
* Renders several sections concatenated into a single document and streams
* the result to the caller-owned stream (not closed here).
*
* @param sections ordered sections to concatenate
* @param output destination stream
* @throws Exception if rendering fails
*/
void writeSections(List<SectionUnit> sections, OutputStream output) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.demcha.compose.document.backend.fixed;

import com.demcha.compose.font.FontFamilyDefinition;

import java.util.Collection;

/**
* Service-provider that supplies backend-specific font metrics to the layout
* pipeline without exposing backend font types to the document API.
*
* <p>Implementations are discovered through {@link java.util.ServiceLoader};
* each render backend artifact registers one (for example the PDF backend in
* {@code io.github.demchaav:graph-compose-render-pdf}). The core resolves a
* provider lazily via {@link BackendProviders#fontMetrics()} and fails with a
* {@link com.demcha.compose.document.exceptions.MissingBackendException} naming
* the artifact to add when none is registered.</p>
*
* @since 2.0.0
*/
public interface FontMetricsProvider {

/**
* Opens measurement resources for the given document-local font families.
*
* @param customFontFamilies document-local font families to register in
* addition to the bundled catalog; never
* {@code null} (may be empty)
* @return owned, document-scoped measurement resources
*/
MeasurementResources openMeasurement(Collection<FontFamilyDefinition> customFontFamilies);
}
Loading
Loading