diff --git a/CHANGELOG.md b/CHANGELOG.md index 708c39893..a3dc714b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ PDF `GoTo` actions. External links are unchanged. ### Public API +- **`LineBuilder.fill()`** (`@since 1.9.0`). A line stretches to the width + available where it is placed — its column inside a row, or the content width at + flow level — instead of its authored fixed width. Paired with a dotted stroke + (`dashed(0.1, 4).lineCap(ROUND)`) it is the flex leader behind a + table-of-contents row, drawn without measuring the gap by hand. A non-fill line + is unchanged, so existing line output stays byte-identical. + - **Negative-margin handling** (`@since 1.9.0`). A negative **page** margin (`DocumentSession.margin(...)` or the builder's `margin(...)`) is now rejected with an `IllegalArgumentException` — it would make the content area larger than diff --git a/assets/readme/examples/line-fill.pdf b/assets/readme/examples/line-fill.pdf new file mode 100644 index 000000000..a52746644 Binary files /dev/null and b/assets/readme/examples/line-fill.pdf differ diff --git a/examples/README.md b/examples/README.md index 867269018..8772780c6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -93,6 +93,7 @@ are with the canonical DSL, then jump to its detailed section below. | [Shape containers](#shape-containers) | Circles, ellipses, rounded cards with `ClipPolicy.CLIP_PATH` | [PDF](../assets/readme/examples/shape-container.pdf) · [Source](src/main/java/com/demcha/examples/features/shapes/ShapeContainerExample.java) | | [Vector paths (Bézier)](#vector-paths-bézier) | `addPath(...)` + `SvgPath.parse(...)` — design shapes and imported SVG icons as native curves; zero tessellation | [PDF](../assets/readme/examples/vector-path.pdf) · [Source](src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java) | | [Line caps & dotted lines](#line-caps--dotted-lines) | `line.lineCap(ROUND)` — round / square caps for plain lines; `dashed(0.1, 4).lineCap(ROUND)` draws a dotted leader | [PDF](../assets/readme/examples/line-cap.pdf) · [Source](src/main/java/com/demcha/examples/features/shapes/LineCapExample.java) | +| [Fill lines & dot leaders](#fill-lines--dot-leaders) | `line().fill()` — a line stretches to its row column or the content width; pair with `dashed(...).lineCap(ROUND)` for a dot leader drawn without measuring the gap | [PDF](../assets/readme/examples/line-fill.pdf) · [Source](src/main/java/com/demcha/examples/features/shapes/LineFillExample.java) | | [SVG icon gallery](#svg-icon-gallery) | 34 real-world multicolour svgrepo icons via `SvgIcon.parse` — up to 19 layers each, the whole set 156 KB of sources | [PDF](../assets/readme/examples/svg-icon-gallery.pdf) · [Source](src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java) | | [Advanced tables](#advanced-tables) | Row span, zebra rows, totals, repeating header on page break | [PDF](../assets/readme/examples/table-advanced.pdf) · [Source](src/main/java/com/demcha/examples/features/tables/TableAdvancedExample.java) | | [Barcodes](#barcodes) | QR, Code 128, Code 39, EAN-13, EAN-8, branded QR with theme colours | [PDF](../assets/readme/examples/barcode-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/barcodes/BarcodeShowcaseExample.java) | @@ -404,6 +405,23 @@ flow.addLine(l -> l.horizontal(w).stroke(stroke) [📄 View PDF](../assets/readme/examples/line-cap.pdf) · [📜 Full source](src/main/java/com/demcha/examples/features/shapes/LineCapExample.java) +### Fill lines & dot leaders + +`LineBuilder.fill()` stretches a line to the width available where it is placed — +the content width at flow level, or its column inside a row — instead of an +authored fixed width. Paired with a dotted stroke it is the flex leader behind a +table-of-contents row, drawn without measuring the gap by hand. A non-fill line +keeps its fixed width, so existing line output stays byte-identical. + +```java +flow.addRow(r -> r.weights(5, 1) + .addLine(l -> l.fill().stroke(s).dashed(0.1, 4).lineCap(DocumentLineCap.ROUND)) // leader fills its column + .addParagraph("p. 12")); +``` + +[📄 View PDF](../assets/readme/examples/line-fill.pdf) · +[📜 Full source](src/main/java/com/demcha/examples/features/shapes/LineFillExample.java) + ### SVG icon gallery A stress-test sheet for the beta SVG reader: 34 real-world multicolour diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index ef7b459b0..301f62812 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -11,6 +11,7 @@ import com.demcha.examples.features.layout.BlockAlignExample; import com.demcha.examples.features.lists.NestedListExample; import com.demcha.examples.features.shapes.LineCapExample; +import com.demcha.examples.features.shapes.LineFillExample; import com.demcha.examples.features.shapes.PhotoClipExample; import com.demcha.examples.features.shapes.ShapeContainerExample; import com.demcha.examples.features.shapes.VectorPathExample; @@ -147,6 +148,7 @@ public static void main(String[] args) throws Exception { System.out.println("Generated: " + ShapeContainerExample.generate()); System.out.println("Generated: " + VectorPathExample.generate()); System.out.println("Generated: " + LineCapExample.generate()); + System.out.println("Generated: " + LineFillExample.generate()); System.out.println("Generated: " + PhotoClipExample.generate()); System.out.println("Generated: " + SvgIconGalleryExample.generate()); System.out.println("Generated: " + BlockAlignExample.generate()); diff --git a/examples/src/main/java/com/demcha/examples/features/shapes/LineFillExample.java b/examples/src/main/java/com/demcha/examples/features/shapes/LineFillExample.java new file mode 100644 index 000000000..d7fb946b1 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/features/shapes/LineFillExample.java @@ -0,0 +1,87 @@ +package com.demcha.examples.features.shapes; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentLineCap; +import com.demcha.compose.document.style.DocumentStroke; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; + +/** + * Runnable showcase for v1.9 {@code LineBuilder.fill()}: a line stretches to the + * width available where it is placed — the content width at flow level, or its + * slot inside a row — instead of an authored fixed width. Paired with a dotted + * stroke it is the flex leader behind a table-of-contents row, drawn without + * measuring the gap by hand. + * + *
{@code
+ * flow.addLine(l -> l.fill().stroke(s).dashed(0.1, 4).lineCap(ROUND)); // full-width dots
+ * flow.addRow(r -> r.weights(4, 1)
+ * .addLine(l -> l.fill().stroke(s).dashed(0.1, 4).lineCap(ROUND)) // leader fills its column
+ * .addParagraph("p. 12"));
+ * }
+ *
+ * @author Artem Demchyshyn
+ */
+public final class LineFillExample {
+
+ private static final DocumentColor INK = DocumentColor.rgb(24, 28, 38);
+ private static final DocumentColor MUTED = DocumentColor.rgb(90, 96, 105);
+
+ private LineFillExample() {
+ }
+
+ /**
+ * Renders the fill sheet: a full-width rule, a full-width dotted leader, and
+ * a weighted leader-to-number row that previews a table-of-contents line.
+ *
+ * @return path to the generated PDF
+ * @throws Exception if rendering or file IO fails
+ */
+ public static Path generate() throws Exception {
+ Path pdfFile = ExampleOutputPaths.prepare("features/shapes", "line-fill.pdf");
+
+ DocumentTextStyle caption = DocumentTextStyle.DEFAULT.withSize(10).withColor(MUTED);
+ DocumentStroke rule = DocumentStroke.of(INK, 1.2);
+ DocumentStroke dots = DocumentStroke.of(MUTED, 1.4);
+
+ try (DocumentSession document = GraphCompose.document(pdfFile)
+ .pageSize(400, 260)
+ .margin(DocumentInsets.of(36))
+ .create()) {
+ document.pageFlow(page -> {
+ page.addParagraph(p -> p
+ .text("Fill lines & dot leaders")
+ .textStyle(DocumentTextStyle.DEFAULT.withSize(18)));
+
+ page.addParagraph(p -> p.text("line().fill() — rule spans the content width")
+ .textStyle(caption).padding(DocumentInsets.top(12)));
+ page.addLine(l -> l.fill().height(2).stroke(rule));
+
+ page.addParagraph(p -> p.text("line().fill().dashed(0.1, 4).lineCap(ROUND) — dotted leader")
+ .textStyle(caption).padding(DocumentInsets.top(12)));
+ page.addLine(l -> l.fill().height(2).stroke(dots)
+ .dashed(0.1, 4).lineCap(DocumentLineCap.ROUND));
+
+ page.addParagraph(p -> p.text("weighted row: leader fills the gap up to the page number")
+ .textStyle(caption).padding(DocumentInsets.top(12)));
+ page.addRow(r -> r.gap(6).weights(5, 1)
+ .addLine(l -> l.fill().height(8).stroke(dots)
+ .dashed(0.1, 4).lineCap(DocumentLineCap.ROUND))
+ .addParagraph(p -> p.text("p. 12").textStyle(caption)));
+ });
+
+ document.buildPdf();
+ }
+
+ return pdfFile;
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Generated: " + generate());
+ }
+}
diff --git a/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java b/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
index f5f9aaffa..f01eb7f83 100644
--- a/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
+++ b/src/main/java/com/demcha/compose/document/dsl/LineBuilder.java
@@ -31,6 +31,7 @@ public final class LineBuilder implements TransformableApplies to horizontal lines only (the default geometry); on a vertical + * or diagonal line it is a no-op, since stretching the end point would change + * the line's slope.
+ * + * @return this builder + * @since 1.9.0 + */ + public LineBuilder fill() { + this.fillWidth = true; + return this; + } + /** * Attaches line-level external link metadata. * @@ -382,7 +403,8 @@ public LineNode build() { transform, dashPattern, anchor, - lineCap); + lineCap, + fillWidth); } private boolean isHorizontalLine() { diff --git a/src/main/java/com/demcha/compose/document/layout/definitions/LineDefinition.java b/src/main/java/com/demcha/compose/document/layout/definitions/LineDefinition.java index eb457b771..f62af1a46 100644 --- a/src/main/java/com/demcha/compose/document/layout/definitions/LineDefinition.java +++ b/src/main/java/com/demcha/compose/document/layout/definitions/LineDefinition.java @@ -29,11 +29,25 @@ public Class