Skip to content

Commit 08bfa8b

Browse files
authored
Merge branch 'develop' into docs/roadmap-block-align
2 parents b037470 + 4bf11b1 commit 08bfa8b

62 files changed

Lines changed: 3319 additions & 24 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,41 @@ Entries land here as they merge.
6767
every flow builder authors design shapes directly, and
6868
`dashed(on, off, ...)` makes the stroke dashed with the same
6969
`DocumentDashPattern` contract as lines — the pattern follows the curve.
70+
- **SVG path import** (`@since 1.8.0`, **beta** — annotated `@Beta` while
71+
the surface hardens against real-world exporter output). `SvgPath.parse(d)` /
72+
`parse(d, viewBox...)` in the new `document.svg` package lowers the full
73+
SVG 1.1 path grammar — absolute/relative `M L H V C S Q T A Z`, implicit
74+
repetition, quadratics (exact cubic elevation), smooth shorthands, and
75+
elliptical arcs (deterministic W3C endpoint-to-center conversion, ≤90°
76+
cubic slices) — into normalized, y-flipped `DocumentPathSegment`s.
77+
`PathBuilder.svg(svgPath)` drops the result straight into `addPath(...)`:
78+
any icon's `d` string renders as native PDF curves, no tessellation.
79+
Syntax errors report the character position; fills keep SVG's default
80+
non-zero winding rule. On top of it, `SvgIcon.read(file)` / `parse(xml)`
81+
reads the practical subset of a whole SVG file — every `<path>` plus
82+
`rect` / `circle` / `ellipse` / `line` / `polyline` / `polygon` lowered to
83+
path data, `<g>` nesting with `translate` / `scale` / `rotate` / `matrix`
84+
transforms (affine maps are exact on Bézier control points), and
85+
`fill` / `stroke` / `stroke-width` styling with SVG inheritance and
86+
defaults — into ordered layers, and `addSvgIcon(icon, width)` stacks them
87+
back-to-front on the page. `SvgIcon#node(width)` packages the same layers
88+
as one ready-to-place node whose box is exactly the icon box, so it
89+
anchors true inside `ShapeContainer` / `LayerStack` nine-point grids (and
90+
rows now accept `ShapeContainerNode` children directly — it is the same
91+
atomic overlay composite as the already-allowed `LayerStackNode`).
92+
**Gradients render natively**: `linearGradient` / `radialGradient`
93+
referenced via `url(#id)` — on fills *and strokes* — map to PDF axial /
94+
radial shadings with exact endpoints (`userSpaceOnUse` and
95+
`objectBoundingBox` units, `gradientTransform`, percentage offsets,
96+
multi-stop stitching, one `href` hop for split definitions); gradient
97+
strokes ride a shading-pattern stroking colour. Underneath,
98+
`DocumentPaint` gains endpoint-exact `LinearAxis` / `RadialCircle` forms
99+
and `PathNode` / `PathBuilder` grow `fill(paint)` / `strokePaint(paint)`
100+
with solid paints normalising to the flat-colour path (byte-identical
101+
output for non-gradient documents). The XML reader refuses DOCTYPEs (no
102+
XXE); CSS, text, filters, focal radials, non-pad `spreadMethod` and
103+
translucent stops stay deliberately out of scope — the reader fails
104+
loudly rather than rendering them wrong.
70105
- **Inline sparklines** (`@since 1.8.0`). `RichText.sparkline(w, h, color,
71106
values...)` draws a filled mini-area silhouette on the text baseline, and
72107
`sparklineLine(w, h, thickness, color, values...)` a constant-thickness line
@@ -181,10 +216,12 @@ Entries land here as they merge.
181216
a code panel shows the exact API call, and the live result renders right
182217
under it — rich text, sparklines, nested lists, timelines, tables, every
183218
chart kind, images (COVER vs CONTAIN fit), gradients, translucency,
184-
polygons, shape basics (dividers, ellipses, soft cards), clipped
185-
containers, canvas, transforms, barcodes, and the document's own chrome —
186-
19 blocks across 6 pages. Blocks use `keepTogether()`, so a snippet is
187-
never orphaned from its result.
219+
polygons, vector paths (solid and dashed native Béziers), SVG path import
220+
and a beta `SvgIcon` tile row, shape basics (dividers, ellipses, soft
221+
cards), clipped containers, canvas, transforms, barcodes, the
222+
debug-overlay switch, and the document's own chrome — 23 blocks across
223+
7 pages. Blocks use `keepTogether()`, so a snippet is never orphaned
224+
from its result.
188225
- **Recipe coverage is complete.** Nine new cookbook pages close every gap the
189226
recipe index tracked: rich text, lists, timelines, barcodes, images,
190227
PDF chrome (metadata / watermark / running header-footer / protection /
70 KB
Binary file not shown.
968 Bytes
Binary file not shown.

examples/README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ are with the canonical DSL, then jump to its detailed section below.
8888
| Example | What it shows | Preview · Source |
8989
|---|---|---|
9090
| [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) |
91-
| [Vector paths (Bézier)](#vector-paths-bézier) | `addPath(...)` — design shapes with native cubic curves: waves, blobs, ribbons; zero tessellation | [PDF](../assets/readme/examples/vector-path.pdf) · [Source](src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java) |
91+
| [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) |
92+
| [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) |
9293
| [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) |
9394
| [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) |
9495
| [Charts](#charts) | Native vector bar, line, and pie/donut charts — data/spec/style layers, axis & grid toggles, point markers, value labels, legend | [PDF](../assets/readme/examples/chart-showcase.pdf) · [Source](src/main/java/com/demcha/examples/features/charts/ChartShowcaseExample.java) |
@@ -363,7 +364,10 @@ ribbons in one closed subpath. Curves render as native PDF `curveTo`
363364
operators — perfectly smooth at any zoom, no tessellation. Coordinates
364365
are normalized to the shape's box (`(0,0)` bottom-left, `y` up) and
365366
control points may overshoot it. Strokes can be dashed via
366-
`dashed(on, off, ...)` — the pattern follows the curve.
367+
`dashed(on, off, ...)` — the pattern follows the curve. SVG icons drop in
368+
through `SvgPath.parse(d, viewBox...)` + `.svg(...)`, or whole files via
369+
`SvgIcon.read(file)` + `addSvgIcon(icon, width)` — multi-layer icons with
370+
group transforms and per-layer paints, all as native curves.
367371

368372
```java
369373
flow.addPath(path -> path
@@ -377,6 +381,21 @@ flow.addPath(path -> path
377381
[📄 View PDF](../assets/readme/examples/vector-path.pdf) ·
378382
[📜 Full source](src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java)
379383

384+
### SVG icon gallery
385+
386+
A stress-test sheet for the beta SVG reader: 34 real-world multicolour
387+
icons (svgrepo.com) parsed by `SvgIcon.parse` and presented as a tile
388+
grid — each icon centred on a rounded card with a label plaque across
389+
the bottom, every layer a native vector path. The entire icon set weighs
390+
156 KB of `.svg` sources; the rendered page is a 70 KB PDF.
391+
392+
```java
393+
flow.addSvgIcon(SvgIcon.parse(readResource("/icons/apple.svg")), 50);
394+
```
395+
396+
[📄 View PDF](../assets/readme/examples/svg-icon-gallery.pdf) ·
397+
[📜 Full source](src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java)
398+
380399
### Advanced tables
381400

382401
`DocumentTableCell.rowSpan(int)` mirrors `colSpan(int)`.

examples/src/main/java/com/demcha/examples/GenerateAllExamples.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.demcha.examples.features.shapes.VectorPathExample;
1212
import com.demcha.examples.features.snapshots.LayoutSnapshotRegressionExample;
1313
import com.demcha.examples.features.streaming.HttpStreamingExample;
14+
import com.demcha.examples.features.svg.SvgIconGalleryExample;
1415
import com.demcha.examples.features.tables.ComposedTableCellExample;
1516
import com.demcha.examples.features.tables.TableAdvancedExample;
1617
import com.demcha.examples.features.text.InlineShapesExample;
@@ -130,6 +131,7 @@ public static void main(String[] args) throws Exception {
130131
// v1.5 visual primitives
131132
System.out.println("Generated: " + ShapeContainerExample.generate());
132133
System.out.println("Generated: " + VectorPathExample.generate());
134+
System.out.println("Generated: " + SvgIconGalleryExample.generate());
133135
System.out.println("Generated: " + TransformsExample.generate());
134136
System.out.println("Generated: " + TableAdvancedExample.generate());
135137

examples/src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import com.demcha.compose.document.api.DocumentSession;
55
import com.demcha.compose.document.style.DocumentColor;
66
import com.demcha.compose.document.style.DocumentInsets;
7+
import com.demcha.compose.document.style.DocumentPaint;
78
import com.demcha.compose.document.style.DocumentStroke;
9+
import com.demcha.compose.document.svg.SvgIcon;
10+
import com.demcha.compose.document.svg.SvgPath;
811
import com.demcha.examples.support.ExampleOutputPaths;
912

1013
import java.nio.file.Path;
14+
import java.util.List;
1115

1216
/**
1317
* Runnable showcase for the v1.8 vector-path primitive: free-form design
@@ -37,6 +41,27 @@ public final class VectorPathExample {
3741
private static final DocumentColor SAND_EDGE = DocumentColor.rgb(140, 90, 30);
3842
private static final DocumentColor MOSS = DocumentColor.rgb(208, 226, 213);
3943
private static final DocumentColor MOSS_EDGE = DocumentColor.rgb(60, 110, 80);
44+
private static final DocumentColor VIOLET = DocumentColor.rgb(167, 139, 250);
45+
private static final DocumentColor DEEP_VIOLET = DocumentColor.rgb(97, 40, 217);
46+
47+
/** Brand gradient along the top-left → bottom-right diagonal. */
48+
private static final DocumentPaint BRAND_AXIS = new DocumentPaint.LinearAxis(List.of(
49+
new DocumentPaint.Stop(0.0, VIOLET),
50+
new DocumentPaint.Stop(1.0, DEEP_VIOLET)), 0.0, 1.0, 1.0, 0.0);
51+
52+
/** Material Icons "favorite" path data (Apache 2.0), viewBox 0 0 24 24. */
53+
private static final String MATERIAL_HEART_D =
54+
"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3"
55+
+ "c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5"
56+
+ "c0 3.78-3.4 6.86-8.55 11.54L12 21.35z";
57+
58+
/** Inline two-tone badge: tinted disc behind the Material heart. */
59+
private static final String TWO_TONE_BADGE_SVG = """
60+
<svg viewBox="0 0 24 24">
61+
<circle cx="12" cy="12" r="11" fill="#fde9e3"/>
62+
<path fill="#c41e3a" d="%s"/>
63+
</svg>
64+
""".formatted(MATERIAL_HEART_D);
4065

4166
private VectorPathExample() {
4267
}
@@ -52,7 +77,7 @@ public static Path generate() throws Exception {
5277
Path pdfFile = ExampleOutputPaths.prepare("features/shapes", "vector-path.pdf");
5378

5479
try (DocumentSession document = GraphCompose.document(pdfFile)
55-
.pageSize(420, 540)
80+
.pageSize(420, 920)
5681
.margin(DocumentInsets.of(28))
5782
.create()) {
5883
document.pageFlow(page -> page
@@ -86,6 +111,34 @@ public static Path generate() throws Exception {
86111
.stroke(DocumentStroke.of(INK, 1.8))
87112
.dashed(6, 3)
88113
.margin(DocumentInsets.bottom(16)))
114+
.addParagraph("SVG path import — Material 'favorite' heart via SvgPath.parse")
115+
.addPath(path -> path
116+
.name("HeartIcon")
117+
.size(72, 72)
118+
.svg(SvgPath.parse(MATERIAL_HEART_D, 0, 0, 24, 24))
119+
.fillColor(DocumentColor.rgb(196, 30, 58))
120+
.margin(DocumentInsets.bottom(16)))
121+
.addParagraph("Whole-file icon — SvgIcon.read/parse stacks every layer")
122+
.addSvgIcon(SvgIcon.parse(TWO_TONE_BADGE_SVG), 64)
123+
.addParagraph("Gradient paints — strokePaint and fill ride native PDF shadings")
124+
.addPath(path -> path
125+
.name("GradientWave")
126+
.size(364, 52)
127+
.moveTo(0.0, 0.5)
128+
.curveTo(0.25, 1.1, 0.25, -0.1, 0.5, 0.5)
129+
.curveTo(0.75, 1.1, 0.75, -0.1, 1.0, 0.5)
130+
.stroke(DocumentStroke.of(VIOLET, 2.6))
131+
.strokePaint(BRAND_AXIS)
132+
.margin(DocumentInsets.bottom(8)))
133+
.addPath(path -> path
134+
.name("GradientBlob")
135+
.size(96, 56)
136+
.moveTo(0.5, 1.0)
137+
.curveTo(1.12, 0.94, 0.96, 0.08, 0.5, 0.0)
138+
.curveTo(0.04, 0.08, -0.12, 0.94, 0.5, 1.0)
139+
.closePath()
140+
.fill(BRAND_AXIS)
141+
.margin(DocumentInsets.bottom(16)))
89142
.addParagraph("Mixed ribbon — lines and curves in one closed, filled subpath")
90143
.addPath(path -> path
91144
.name("Ribbon")

0 commit comments

Comments
 (0)