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
241 changes: 240 additions & 1 deletion CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

> **Release status** —
> 🟢 **Latest stable**: [v2.2.0](https://github.com/DemchaAV/GraphCompose/releases/tag/v2.2.0) — the **right-to-left** release: Hebrew and Arabic lay out, shape, join and mirror through PDF, PowerPoint and Word — in paragraphs and in table cells — with the fonts to render them. See [CHANGELOG.md](./CHANGELOG.md).
>  ·  🟡 **In development**: v2.2.1 on `develop` — see [CHANGELOG.md](./CHANGELOG.md).
>  ·  🟡 **In development**: v2.3.0 on `develop` — see [CHANGELOG.md](./CHANGELOG.md).

<p align="center">
<a href="https://demchaav.github.io/GraphCompose/"><b>Live Showcase</b></a>
Expand Down
Binary file modified assets/readme/examples/cv-monogram-sidebar-v2.pdf
Binary file not shown.
Binary file modified assets/readme/examples/cv-sidebar-portrait-v2.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-build</artifactId>
<version>2.2.1-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion bundle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
graph-compose and graph-compose-templates dependencies below use
${project.version}, so they follow automatically.
-->
<version>2.2.1-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>GraphCompose Bundle</name>
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.demchaav</groupId>
<artifactId>graph-compose-core</artifactId>
<version>2.2.1-SNAPSHOT</version>
<version>2.3.0-SNAPSHOT</version>

<name>GraphCompose Core</name>
<description>A declarative layout engine for programmatic document generation, implemented primarily in Java. This is the lean engine coordinate; depend on the `graph-compose` artifact for the drop-in, PDF-capable install.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,23 @@ public LayoutCanvas canvas() {
return canvas;
}

/**
* Returns the document-wide page margin — whatever was last passed to
* {@link #margin(DocumentInsets)}, or zero insets when none was.
*
* <p>Composition code that has to reason about the page's own edges reads it
* here rather than through {@link #canvas()}, whose margin is an engine type.
* A template that derives a {@link PageMarginRule} from it — reserving a safe
* area on continuation pages, say — keeps working when the caller chooses a
* margin other than the one the template recommends.</p>
*
* @return the current page margin
* @since 2.3.0
*/
public DocumentInsets margin() {
return margin;
}

/**
* Returns the usable content height of the page in points — the page height
* minus the top and bottom margins. Convenience alias for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,36 @@ public T addRow(String name, Consumer<RowBuilder> spec) {
return add(BuilderSupport.configure(new RowBuilder().name(name), spec).build());
}

/**
* Adds a multi-column flow: columns side by side, each continuing on the
* next page.
*
* <p>The page-spanning counterpart to {@link #addRow(Consumer)}. A row is
* one band and is atomic, so a two-column body built from a row holds only
* as much as fits the page it starts on; a column flow lets each column
* break and resume.</p>
*
* @param spec column-flow builder callback
* @return this builder
* @since 2.3.0
*/
public T addColumnFlow(Consumer<ColumnFlowBuilder> spec) {
return add(BuilderSupport.configure(new ColumnFlowBuilder(), spec).build());
}

/**
* Adds a named multi-column flow without repeating the name inside the
* nested builder.
*
* @param name flow name used in snapshots and layout graph paths
* @param spec column-flow builder callback
* @return this builder
* @since 2.3.0
*/
public T addColumnFlow(String name, Consumer<ColumnFlowBuilder> spec) {
return add(BuilderSupport.configure(new ColumnFlowBuilder().name(name), spec).build());
}

/**
* Adds a section configured through a nested builder.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package com.demcha.compose.document.dsl;

import com.demcha.compose.document.dsl.internal.BuilderSupport;
import com.demcha.compose.document.node.ColumnFlowNode;
import com.demcha.compose.document.node.ContainerNode;
import com.demcha.compose.document.node.DocumentNode;
import com.demcha.compose.document.node.SectionNode;
import com.demcha.compose.document.style.DocumentInsets;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
* Builder for a multi-column flow: columns side by side, each continuing on
* the next page.
*
* <p>Use this where a {@link RowBuilder} would cap the document at a page. A
* row places one band and is atomic — the whole band must fit where it starts,
* and a two-column body built that way can hold only as much as one page. A
* column flow places the same two columns and lets each break where it runs
* out of room:</p>
*
* <pre>{@code
* flow.addColumnFlow("Body", body -> body
* .gap(18)
* .weights(0.72, 1.28)
* .addColumn(side -> side.spacing(6).addParagraph(...))
* .addColumn(main -> main.spacing(8).addParagraph(...)));
* }</pre>
*
* <p>Every column is a vertical container, because a column <em>is</em> a
* vertical flow — that is what lets its contents paginate. Anything else
* (a paragraph, an image, a nested row) belongs inside one of those columns
* rather than beside them.</p>
*
* <p>The flow has no fill or border of its own. A column that wants a panel is
* a section with a fill, and the engine repeats a section's fill on every page
* it spans; chrome that must reach the page edge belongs in a page background,
* which paints on every page by definition.</p>
*
* @author Artem Demchyshyn
* @since 2.3.0
*/
public final class ColumnFlowBuilder {
private final List<DocumentNode> columns = new ArrayList<>();
private final List<Double> weights = new ArrayList<>();
private String name = "";
private double gap;
private DocumentInsets padding = DocumentInsets.zero();
private DocumentInsets margin = DocumentInsets.zero();

/**
* Creates a column-flow builder.
*/
public ColumnFlowBuilder() {
}

/**
* Sets the diagnostic name used in snapshots and layout-graph paths.
*
* @param value flow name
* @return this builder
*/
public ColumnFlowBuilder name(String value) {
this.name = value == null ? "" : value;
return this;
}

/**
* Sets the horizontal gap between columns.
*
* @param value gap in points; must be finite and non-negative
* @return this builder
*/
public ColumnFlowBuilder gap(double value) {
this.gap = value;
return this;
}

/**
* Sets the relative column widths. Omit to split the width evenly.
*
* @param values one positive weight per column
* @return this builder
*/
public ColumnFlowBuilder weights(double... values) {
this.weights.clear();
if (values != null) {
for (double value : values) {
this.weights.add(value);
}
}
return this;
}

/**
* Sets the inner padding of the whole flow.
*
* @param value padding insets
* @return this builder
*/
public ColumnFlowBuilder padding(DocumentInsets value) {
this.padding = value == null ? DocumentInsets.zero() : value;
return this;
}

/**
* Sets the outer margin of the whole flow.
*
* @param value margin insets
* @return this builder
*/
public ColumnFlowBuilder margin(DocumentInsets value) {
this.margin = value == null ? DocumentInsets.zero() : value;
return this;
}

/**
* Appends a column configured through a nested section builder.
*
* @param spec column builder callback
* @return this builder
*/
public ColumnFlowBuilder addColumn(Consumer<SectionBuilder> spec) {
columns.add(BuilderSupport.configure(new SectionBuilder(), spec).build());
return this;
}

/**
* Appends a named column configured through a nested section builder.
*
* @param name column name used in snapshots and layout-graph paths
* @param spec column builder callback
* @return this builder
*/
public ColumnFlowBuilder addColumn(String name, Consumer<SectionBuilder> spec) {
columns.add(BuilderSupport.configure(new SectionBuilder().name(name), spec).build());
return this;
}

/**
* Appends a pre-built column.
*
* @param column a vertical container — a section or a container
* @return this builder
* @throws IllegalArgumentException if the node is not a vertical container
*/
public ColumnFlowBuilder addColumn(DocumentNode column) {
if (!(column instanceof SectionNode) && !(column instanceof ContainerNode)) {
throw new IllegalArgumentException(
"A column flow's children are columns, and a column is a vertical container "
+ "(section or container) because that is what paginates. Received: "
+ (column == null ? "null" : column.nodeKind())
+ ". Wrap it in a column instead.");
}
columns.add(column);
return this;
}

/**
* Builds the immutable flow node.
*
* @return the assembled column flow
*/
public ColumnFlowNode build() {
return new ColumnFlowNode(name, List.copyOf(columns), List.copyOf(weights),
gap, padding, margin);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static NodeRegistry registerDefaults(NodeRegistry registry) {
.register(new ContainerDefinition())
.register(new SectionDefinition())
.register(new RowDefinition())
.register(new ColumnFlowDefinition())
.register(new PageReferenceDefinition())
.register(new LayerStackDefinition())
.register(new ShapeContainerDefinition())
Expand Down
Loading
Loading