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
51 changes: 49 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,56 @@
All notable changes to GraphCompose are documented here. Versions
follow semantic versioning; release dates are ISO 8601.

## v1.6.10 — Planned
## v1.7.0 — Planned

Next bug-fix / housekeeping cycle. Track open in `docs/private/` taskboard.
Canonical DSL primitives — additive only, zero breaking changes. Adding public
API turns the open cycle into a minor.

### Public API

- **Inline shape runs — geometry-based dots, diamonds, stars and bullets.** New
`com.demcha.compose.document.node.InlineShapeRun` (`@since 1.7.0`) joins the
sealed `InlineRun` hierarchy alongside text and image runs. It draws any
`ShapeOutline` figure on the paragraph baseline directly from geometry — no
raster payload, no font glyph — so skill rating dots (`Java ●●●●○`), custom
bullets and inline status markers no longer depend on a font shipping
`U+25CF` and friends. Authored through `ParagraphBuilder` / `RichText`
`dot(...)`, `ellipse(...)`, `diamond(...)`, `triangle(...)`, `star(...)` and
the generic `shape(ShapeOutline, ...)`; measured into line width and height
like inline images. A `null` fill paints an outlined figure, a `null` stroke
a filled one; at least one must be present.
- **New polygon shape geometry, usable block-level and inline.** `ShapeOutline`
(`com.demcha.compose.document.style`) gains a `Polygon` kind plus a family of
factories built from normalized `ShapePoint` vertices (`@since 1.7.0`):
`diamond`, `triangle`, `star`, `polygon`, `arrow` / `arrowRight` / `arrowLeft`
(4-way `Direction`), `chevron`, `checkmark`, `plus` and `regularPolygon(sides)`.
Arrows and chevrons read as directional list bullets or inline markers
between text ("Step 1 → Step 2", "Home › Docs"). `ParagraphBuilder` /
`RichText` add `arrow(size, Direction, fill)` and `chevron(...)` shortcuts
(every other kind is reachable through `shape(ShapeOutline, ...)`);
`ShapeContainerBuilder` exposes matching block outlines. Rectangle,
rounded-rectangle and ellipse shape containers are unchanged.
- **Inline checkboxes + composite (multi-layer) inline figures.** An inline
shape run is now a stack of paint layers
(`com.demcha.compose.document.node.ShapeLayer`, `@since 1.7.0`) drawn overlaid
and centred, so a figure can compose several outlines — each with its own
fill/stroke — and still measure and place as one unit on the baseline.
`ParagraphBuilder` / `RichText` gain `checkbox(size, checked, color)` /
`checkbox(size, checked, boxColor, checkColor)` (`@since 1.7.0`): a rounded
frame plus, in the checked state, a centred tick — the todo / checklist marker
for "some items done, some not". The single-outline `InlineShapeRun`
convenience constructors are unchanged; every other kind still renders as one
layer.
- **Swappable tick and arrow designs (the "pick your figure" seam).**
`ShapeOutline` adds `CheckmarkStyle` (`CLASSIC`, `HEAVY`) and `ArrowStyle`
(`BLOCK`, `TRIANGLE`) enums plus the overloads
`checkmark(w, h, CheckmarkStyle)` and `arrow(w, h, Direction, ArrowStyle)`
(`@since 1.7.0`); the no-style factories delegate to `CLASSIC` / `BLOCK`, so
the default look is unchanged. `checkbox(...)`, `RichText.arrow(...)` and
`ParagraphBuilder.arrow(...)` gain matching style overloads, and `checkbox`
also accepts a raw `ShapeOutline` mark for fully custom ticks. Adding a new
design is one enum constant plus its vertex ring — the foundation for letting
a caller choose which tick or arrow to render.

## v1.6.9 — 2026-06-03

Expand Down
Binary file added assets/readme/examples/inline-shapes.pdf
Binary file not shown.
Binary file modified assets/readme/examples/rich-text-showcase.pdf
Binary file not shown.
25 changes: 25 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ are with the canonical DSL, then jump to its detailed section below.
| Example | What it shows | Preview · Source |
|---|---|---|
| [Rich text](#rich-text) | Every `RichText` method (bold / italic / underline / link / colour / accent / size / append) | [PDF](../assets/readme/examples/rich-text-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java) |
| [Inline shapes](#inline-shapes) | `InlineShapeRun` — dots, arrows, chevrons, diamonds, stars, checkmarks and checkboxes drawn as geometry on the text baseline | [PDF](../assets/readme/examples/inline-shapes.pdf) · [Source](src/main/java/com/demcha/examples/features/text/InlineShapesExample.java) |
| [Section presets](#section-presets) | `pageBackground`, `band`, `softPanel`, `accentLeft / Right / Top / Bottom`, per-corner `DocumentCornerRadius` | [PDF](../assets/readme/examples/section-presets.pdf) · [Source](src/main/java/com/demcha/examples/features/text/SectionPresetsExample.java) |
| [Nested lists](#nested-lists-v16) | `ListBuilder.addItem(label, Consumer)` — depth cascade, per-depth markers, mixed flat / nested authoring | [PDF](../assets/readme/examples/nested-list-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/lists/NestedListExample.java) |
| [Composed table cells](#composed-table-cells-v16) | `DocumentTableCell.node(DocumentNode)` — paragraphs, lists, sub-tables inside cells with two-pass measurement | [PDF](../assets/readme/examples/composed-table-cell-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/tables/ComposedTableCellExample.java) |
Expand Down Expand Up @@ -409,6 +410,30 @@ visual reference when picking which call to make for inline text.
[📄 View PDF](../assets/readme/examples/rich-text-showcase.pdf) ·
[📜 Full source](src/main/java/com/demcha/examples/features/text/RichTextShowcaseExample.java)

### Inline shapes

`InlineShapeRun` (`@since 1.7.0`) draws geometric figures on the text
baseline from geometry — no font glyph needed — so rating dots, arrows,
chevrons, diamonds, stars, checkmarks, checkboxes (checked / unchecked
todo markers) and any other `ShapeOutline` work between text and as list
bullets, at any size and colour. The tick and arrow designs are swappable
via `CheckmarkStyle` / `ArrowStyle`.

```java
.addRich(rich -> rich
.plain("Draft ")
.arrow(8, ShapeOutline.Direction.RIGHT, accent)
.plain(" Review ")
.arrow(8, ShapeOutline.Direction.RIGHT, accent)
.plain(" Published"))
// also: dot(size, fill), diamond, triangle, star, chevron,
// checkbox(size, checked, color) for todo markers, and
// arrow(size, dir, ArrowStyle.TRIANGLE, fill) to pick a design variant
```

[📄 View PDF](../assets/readme/examples/inline-shapes.pdf) ·
[📜 Full source](src/main/java/com/demcha/examples/features/text/InlineShapesExample.java)

### Section presets

`pageBackground`, `band`, `softPanel`, the four
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.demcha.examples.features.streaming.HttpStreamingExample;
import com.demcha.examples.features.tables.ComposedTableCellExample;
import com.demcha.examples.features.tables.TableAdvancedExample;
import com.demcha.examples.features.text.InlineShapesExample;
import com.demcha.examples.features.text.RichTextShowcaseExample;
import com.demcha.examples.features.text.SectionPresetsExample;
import com.demcha.examples.features.themes.CustomBusinessThemeExample;
Expand Down Expand Up @@ -127,6 +128,7 @@ public static void main(String[] args) throws Exception {
System.out.println("Generated: " + TableAdvancedExample.generate());

// Text + sections
System.out.println("Generated: " + InlineShapesExample.generate());
System.out.println("Generated: " + RichTextShowcaseExample.generate());
System.out.println("Generated: " + SectionPresetsExample.generate());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.demcha.examples.features.text;

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentPageSize;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.dsl.RichText;
import com.demcha.compose.document.dsl.SectionBuilder;
import com.demcha.compose.document.style.DocumentColor;
import com.demcha.compose.document.style.DocumentInsets;
import com.demcha.compose.document.style.DocumentStroke;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.document.style.ShapeOutline;
import com.demcha.compose.document.theme.BusinessTheme;
import com.demcha.compose.font.FontName;
import com.demcha.examples.support.ExampleOutputPaths;

import java.nio.file.Path;
import java.util.function.Consumer;

/**
* Runnable showcase for inline shape runs ({@code @since 1.7.0}).
*
* <p>Geometric figures — rating dots, arrows, chevrons, diamonds, stars,
* checkmarks, plus signs, regular polygons and checkboxes (checked / unchecked
* todo markers) — drawn on the text baseline from geometry (no font glyphs),
* used between text and as list bullets. The closing rows show the swappable
* {@code CheckmarkStyle} / {@code ArrowStyle} designs. Each row pairs the
* rendered output with the {@code ParagraphBuilder} / {@code RichText} call that
* produced it, so the PDF reads like a quick reference.</p>
*/
public final class InlineShapesExample {
private static final BusinessTheme THEME = BusinessTheme.modern();
private static final DocumentColor MUTED = DocumentColor.rgb(112, 116, 128);
private static final DocumentColor BRAND = DocumentColor.rgb(20, 80, 95);
private static final DocumentColor ACCENT = DocumentColor.rgb(196, 153, 76);
private static final DocumentColor GREEN = DocumentColor.rgb(34, 130, 92);
private static final DocumentColor PANEL = DocumentColor.rgb(248, 244, 234);

private InlineShapesExample() {
}

public static Path generate() throws Exception {
Path outputFile = ExampleOutputPaths.prepare("features/text", "inline-shapes.pdf");

try (DocumentSession document = GraphCompose.document(outputFile)
.pageSize(DocumentPageSize.A4)
.pageBackground(THEME.pageBackground())
.margin(34, 34, 34, 34)
.create()) {

document.pageFlow()
.name("InlineShapesShowcase")
.spacing(14)
.addSection("Hero", section -> section
.softPanel(THEME.palette().surfaceMuted(), 10, 16)
.accentLeft(ACCENT, 4)
.spacing(6)
.addParagraph(p -> p
.text("Inline shapes")
.textStyle(THEME.text().h1())
.margin(DocumentInsets.zero()))
.addRich(rich -> rich
.plain("Geometric figures drawn on the text baseline ")
.accent("from geometry, not font glyphs", BRAND)
.plain(" — between text and as list bullets, at any size and colour.")))
.addSection("Ratings", section -> labelledRow(section,
"dot(size, fill) — filled and outlined rating dots",
rich -> rich
.plain("Java ")
.dot(5, BRAND).dot(5, BRAND).dot(5, BRAND).dot(5, BRAND)
.dot(5, null, DocumentStroke.of(BRAND, 0.6))
.plain(" Kotlin ")
.dot(5, BRAND).dot(5, BRAND).dot(5, BRAND)
.dot(5, null, DocumentStroke.of(BRAND, 0.6))
.dot(5, null, DocumentStroke.of(BRAND, 0.6))))
.addSection("Flows", section -> labelledRow(section,
"arrow(size, Direction, fill) — direction between text",
rich -> rich
.plain("Draft ").arrow(8, ShapeOutline.Direction.RIGHT, ACCENT)
.plain(" Review ").arrow(8, ShapeOutline.Direction.RIGHT, ACCENT)
.plain(" Published")))
.addSection("Breadcrumb", section -> labelledRow(section,
"chevron(size, Direction, fill) — light directional separator",
rich -> rich
.plain("Home ").chevron(6, ShapeOutline.Direction.RIGHT, MUTED)
.plain(" Docs ").chevron(6, ShapeOutline.Direction.RIGHT, MUTED)
.plain(" API ").chevron(6, ShapeOutline.Direction.RIGHT, MUTED)
.plain(" InlineShapeRun")))
.addSection("Checklist", section -> section
.softPanel(PANEL, 6, 12)
.spacing(5)
.addParagraph(p -> p
.text("checkbox(size, checked, color) — todo markers, checked and unchecked")
.textStyle(caption())
.margin(DocumentInsets.zero()))
.addRich(rich -> rich.checkbox(10, true, GREEN)
.plain(" A checked box stamps a filled tick inside the frame"))
.addRich(rich -> rich.checkbox(10, true, GREEN)
.plain(" Both states share the same geometry pipeline"))
.addRich(rich -> rich.checkbox(10, false, MUTED)
.plain(" An empty box is the unchecked state"))
.addRich(rich -> rich.checkbox(10, false, MUTED)
.plain(" No font glyph, so it renders anywhere")))
.addSection("Bullets", section -> labelledRow(section,
"any ShapeOutline as a list bullet",
rich -> rich
.diamond(7, ACCENT).plain(" Diamond ")
.star(8, ACCENT).plain(" Star ")
.triangle(7, BRAND).plain(" Triangle ")
.arrow(8, ShapeOutline.Direction.RIGHT, BRAND).plain(" Arrow ")
.shape(ShapeOutline.regularPolygon(8, 8, 6), MUTED).plain(" Hexagon")))
.addSection("Variants", section -> section
.softPanel(PANEL, 6, 12)
.spacing(5)
.addParagraph(p -> p
.text("checkmark(w, h, CheckmarkStyle) · arrow(w, h, Direction, ArrowStyle) — swap the design")
.textStyle(caption())
.margin(DocumentInsets.zero()))
.addRich(rich -> rich
.plain("Tick ")
.shape(ShapeOutline.checkmark(9, 9, ShapeOutline.CheckmarkStyle.CLASSIC), GREEN)
.plain(" classic ")
.shape(ShapeOutline.checkmark(9, 9, ShapeOutline.CheckmarkStyle.HEAVY), GREEN)
.plain(" heavy Arrow ")
.arrow(9, ShapeOutline.Direction.RIGHT, ShapeOutline.ArrowStyle.BLOCK, ACCENT)
.plain(" block ")
.arrow(9, ShapeOutline.Direction.RIGHT, ShapeOutline.ArrowStyle.TRIANGLE, ACCENT)
.plain(" triangle"))
.addRich(rich -> rich
.checkbox(11, true, ShapeOutline.CheckmarkStyle.HEAVY, BRAND, BRAND)
.plain(" A checkbox takes any tick variant — here HEAVY")))
.addSection("Footer", section -> section
.accentTop(THEME.palette().rule(), 0.6)
.padding(new DocumentInsets(8, 0, 0, 0))
.addRich(rich -> rich
.plain("Source: ")
.style("examples/.../InlineShapesExample.java",
DocumentTextStyle.builder()
.fontName(FontName.COURIER)
.size(8)
.color(MUTED)
.build())))
.build();

document.buildPdf();
}

return outputFile;
}

public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}

private static void labelledRow(SectionBuilder section, String label, Consumer<RichText> body) {
section
.softPanel(PANEL, 6, 12)
.spacing(4)
.addParagraph(p -> p
.text(label)
.textStyle(caption())
.margin(DocumentInsets.zero()))
.addRich(body::accept);
}

private static DocumentTextStyle caption() {
return DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD)
.size(8.5)
.color(MUTED)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
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.DocumentStroke;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.document.style.ShapeOutline;
import com.demcha.compose.document.theme.BusinessTheme;
import com.demcha.compose.font.FontName;
import com.demcha.examples.support.ExampleOutputPaths;
Expand All @@ -17,7 +19,8 @@
*
* <p>Walks through every fluent method on {@code RichText} —
* {@code plain / bold / italic / boldItalic / underline / strikethrough /
* color / accent / size / style / link / append} — laid out as labelled
* color / accent / size / style / link / append / dot / ellipse / diamond /
* star / shape} — laid out as labelled
* "what does this look like" rows on a single A4 page so the rendered PDF
* reads like a quick reference.</p>
*/
Expand Down Expand Up @@ -121,6 +124,24 @@ public static Path generate() throws Exception {
.plain("Pre-built ")
.append(reusableRun())
.plain(" composes with ad-hoc fragments — share recurring fragments across paragraphs.")))
.addSection("Inline shapes", section -> labelledRow(section,
"dot / diamond / star / arrow / chevron / shape",
rich -> rich
.plain("Java ")
.dot(5, BRAND)
.dot(5, BRAND)
.dot(5, BRAND)
.dot(5, null, DocumentStroke.of(BRAND, 0.6))
.plain(" Step 1 ")
.arrow(8, ShapeOutline.Direction.RIGHT, ACCENT)
.plain(" Step 2 Home ")
.chevron(6, ShapeOutline.Direction.RIGHT, MUTED)
.plain(" Docs ")
.diamond(7, ACCENT)
.plain(" ")
.star(8, ACCENT)
.plain(" ")
.shape(ShapeOutline.checkmark(8, 8), BRAND)))
.addSection("Footer", section -> section
.accentTop(THEME.palette().rule(), 0.6)
.padding(new DocumentInsets(8, 0, 0, 0))
Expand Down
Loading