Skip to content

Commit c0c59df

Browse files
authored
Merge pull request #176 from DemchaAV/feat/svg-icon-gallery
docs(examples): SVG icon gallery - 34 real-world multicolour icons via SvgIcon
2 parents 6040f37 + f4c8293 commit c0c59df

39 files changed

Lines changed: 382 additions & 0 deletions
70 KB
Binary file not shown.

examples/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ are with the canonical DSL, then jump to its detailed section below.
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) |
9191
| [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) |
@@ -380,6 +381,21 @@ flow.addPath(path -> path
380381
[📄 View PDF](../assets/readme/examples/vector-path.pdf) ·
381382
[📜 Full source](src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java)
382383

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+
383399
### Advanced tables
384400

385401
`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

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package com.demcha.examples.features.svg;
2+
3+
import com.demcha.compose.GraphCompose;
4+
import com.demcha.compose.document.api.DocumentSession;
5+
import com.demcha.compose.document.dsl.LayerStackBuilder;
6+
import com.demcha.compose.document.dsl.ParagraphBuilder;
7+
import com.demcha.compose.document.dsl.PathBuilder;
8+
import com.demcha.compose.document.dsl.ShapeContainerBuilder;
9+
import com.demcha.compose.document.node.DocumentNode;
10+
import com.demcha.compose.document.node.LayerAlign;
11+
import com.demcha.compose.document.node.TextAlign;
12+
import com.demcha.compose.document.style.DocumentColor;
13+
import com.demcha.compose.document.style.DocumentInsets;
14+
import com.demcha.compose.document.style.DocumentStroke;
15+
import com.demcha.compose.document.style.DocumentTextDecoration;
16+
import com.demcha.compose.document.style.DocumentTextStyle;
17+
import com.demcha.compose.document.svg.SvgIcon;
18+
import com.demcha.compose.font.FontName;
19+
import com.demcha.examples.support.ExampleOutputPaths;
20+
21+
import java.io.InputStream;
22+
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Path;
24+
import java.util.List;
25+
import java.util.Locale;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
29+
/**
30+
* Runnable stress-test gallery for the beta SVG icon reader: 34 real-world
31+
* multicolour icons (up to 19 layers each) read straight from {@code .svg}
32+
* resources via {@link SvgIcon#parse(String)} and presented as a tile grid —
33+
* each icon centred on a rounded card with a label plaque across the bottom,
34+
* every curve a native PDF Bézier, the whole icon set a fraction of one
35+
* screenshot's weight.
36+
*
37+
* <pre>{@code
38+
* flow.addSvgIcon(SvgIcon.read(Path.of("icons/apple.svg")), 52);
39+
* }</pre>
40+
*
41+
* <p>Icon artwork: <a href="https://www.svgrepo.com">svgrepo.com</a>
42+
* collections (see each icon's page for its licence).</p>
43+
*
44+
* @author Artem Demchyshyn
45+
*/
46+
public final class SvgIconGalleryExample {
47+
48+
private static final List<String> ICONS = List.of(
49+
"apple", "avocado", "banana", "boxing", "calendar",
50+
"camera-take-pictures", "chat-chat", "cherry", "diagnosis", "eye-password-eye-password",
51+
"feet", "food", "grape", "headphones-music", "key-password",
52+
"kiwi-fruit", "magnifying-glass-find-search", "microphone-singing", "movie", "peach",
53+
"pencil-revision", "personal-account-account", "picture", "record", "reminder-alert",
54+
"setting", "shopping-cart", "shopping", "social-contact", "starfish",
55+
"steak", "store-homepage-home", "toolbox", "upload");
56+
57+
/** Short display labels for the verbose svgrepo file names. */
58+
private static final Map<String, String> SHORT_LABELS = Map.ofEntries(
59+
Map.entry("camera-take-pictures", "Camera"),
60+
Map.entry("chat-chat", "Chat"),
61+
Map.entry("eye-password-eye-password", "Eye"),
62+
Map.entry("headphones-music", "Headphones"),
63+
Map.entry("key-password", "Key"),
64+
Map.entry("kiwi-fruit", "Kiwi"),
65+
Map.entry("magnifying-glass-find-search", "Search"),
66+
Map.entry("microphone-singing", "Microphone"),
67+
Map.entry("pencil-revision", "Pencil"),
68+
Map.entry("personal-account-account", "Account"),
69+
Map.entry("reminder-alert", "Reminder"),
70+
Map.entry("shopping-cart", "Cart"),
71+
Map.entry("social-contact", "Contact"),
72+
Map.entry("store-homepage-home", "Store"));
73+
74+
private static final int COLUMNS = 5;
75+
private static final double CARD_WIDTH = 97;
76+
private static final double CARD_HEIGHT = 84;
77+
private static final double CARD_RADIUS = 9;
78+
private static final double PLAQUE_HEIGHT = 17;
79+
/** Icons contain-fit into this square inside the card body. */
80+
private static final double ICON_BOX = 44;
81+
82+
private static final DocumentColor CARD_FILL = DocumentColor.rgb(248, 249, 251);
83+
private static final DocumentColor CARD_BORDER = DocumentColor.rgb(228, 231, 236);
84+
private static final DocumentColor PLAQUE_FILL = DocumentColor.rgb(235, 238, 243);
85+
private static final DocumentColor LABEL_INK = DocumentColor.rgb(82, 90, 102);
86+
private static final DocumentColor MUTED = DocumentColor.rgb(90, 96, 105);
87+
88+
private static final DocumentTextStyle LABEL_STYLE = DocumentTextStyle.builder()
89+
.fontName(FontName.HELVETICA_BOLD)
90+
.size(6.4)
91+
.decoration(DocumentTextDecoration.BOLD)
92+
.color(LABEL_INK)
93+
.build();
94+
95+
private SvgIconGalleryExample() {
96+
}
97+
98+
/**
99+
* Renders the 34-icon gallery sheet as a uniform card grid.
100+
*
101+
* @return path to the generated PDF
102+
* @throws Exception if rendering or resource IO fails
103+
*/
104+
public static Path generate() throws Exception {
105+
Path pdfFile = ExampleOutputPaths.prepare("features/svg", "svg-icon-gallery.pdf");
106+
107+
try (DocumentSession document = GraphCompose.document(pdfFile)
108+
.pageSize(595, 842)
109+
.margin(DocumentInsets.of(34))
110+
.create()) {
111+
document.pageFlow(page -> {
112+
page.addParagraph(p -> p
113+
.text("SVG icon gallery")
114+
.textStyle(DocumentTextStyle.DEFAULT.withSize(22)));
115+
page.addParagraph(p -> p
116+
.text("34 real-world multicolour icons (svgrepo.com) read by SvgIcon.parse "
117+
+ "— every layer a native vector path, the whole set 156 KB of sources.")
118+
.textStyle(DocumentTextStyle.DEFAULT.withSize(9.5).withColor(MUTED))
119+
.padding(DocumentInsets.bottom(14)));
120+
121+
for (int start = 0; start < ICONS.size(); start += COLUMNS) {
122+
List<String> chunk = ICONS.subList(start, Math.min(start + COLUMNS, ICONS.size()));
123+
page.addRow(row -> {
124+
row.spacing(10).evenWeights().margin(DocumentInsets.bottom(10));
125+
for (String name : chunk) {
126+
// Rows host sections; the fixed-size card rides inside one.
127+
row.addSection("Tile" + name.replace('-', '_'),
128+
s -> s.add(card(name)));
129+
}
130+
// Pad the last row so its cells line up with the full rows.
131+
for (int filler = chunk.size(); filler < COLUMNS; filler++) {
132+
row.addSpacer(CARD_WIDTH);
133+
}
134+
});
135+
}
136+
});
137+
138+
document.buildPdf();
139+
}
140+
141+
return pdfFile;
142+
}
143+
144+
/** One tile: rounded card, icon centred in the body, label plaque across the bottom. */
145+
private static DocumentNode card(String name) {
146+
String id = name.replace('-', '_');
147+
return new ShapeContainerBuilder()
148+
.name("Card" + id)
149+
.roundedRect(CARD_WIDTH, CARD_HEIGHT, CARD_RADIUS)
150+
.fillColor(CARD_FILL)
151+
.stroke(DocumentStroke.of(CARD_BORDER, 0.8))
152+
.position(iconStack(name, id), 0, -PLAQUE_HEIGHT / 2.0, LayerAlign.CENTER)
153+
.bottomCenter(plaque(name, id))
154+
.build();
155+
}
156+
157+
/**
158+
* Builds the icon as a standalone layer stack whose box is exactly the
159+
* icon's contain-fit size, so the card's CENTER anchor lands true.
160+
* (The flow-level {@code addSvgIcon(...)} sugar targets flows; a card
161+
* layer needs the node form of the same composition.)
162+
*/
163+
private static DocumentNode iconStack(String name, String id) {
164+
SvgIcon icon = loadIcon(name);
165+
double width = Math.min(ICON_BOX, ICON_BOX * icon.aspectRatio());
166+
double height = width / icon.aspectRatio();
167+
LayerStackBuilder stack = new LayerStackBuilder().name("Icon" + id);
168+
for (int i = 0; i < icon.layers().size(); i++) {
169+
SvgIcon.Layer layer = icon.layers().get(i);
170+
stack.layer(new PathBuilder()
171+
.name("SvgLayer" + i)
172+
.size(width, height)
173+
.svg(layer.geometry())
174+
.fillColor(layer.fill())
175+
.stroke(layer.stroke())
176+
.build());
177+
}
178+
return stack.build();
179+
}
180+
181+
/**
182+
* Full-width label band across the card bottom; the card's CLIP_PATH
183+
* rounds its outer corners automatically.
184+
*/
185+
private static DocumentNode plaque(String name, String id) {
186+
return new ShapeContainerBuilder()
187+
.name("Plaque" + id)
188+
.rectangle(CARD_WIDTH, PLAQUE_HEIGHT)
189+
.fillColor(PLAQUE_FILL)
190+
.center(new ParagraphBuilder()
191+
.text(label(name))
192+
.textStyle(LABEL_STYLE)
193+
.align(TextAlign.CENTER)
194+
.margin(DocumentInsets.zero())
195+
.build())
196+
.build();
197+
}
198+
199+
private static SvgIcon loadIcon(String name) {
200+
try (InputStream in = Objects.requireNonNull(
201+
SvgIconGalleryExample.class.getResourceAsStream("/icons/" + name + ".svg"),
202+
"icon resource missing: " + name)) {
203+
return SvgIcon.parse(new String(in.readAllBytes(), StandardCharsets.UTF_8));
204+
} catch (Exception e) {
205+
throw new IllegalStateException("failed to load icon: " + name, e);
206+
}
207+
}
208+
209+
/** {@code "camera-take-pictures"} → {@code "CAMERA"}; plain names just uppercase. */
210+
private static String label(String name) {
211+
return SHORT_LABELS.getOrDefault(name, name.replace('-', ' ')).toUpperCase(Locale.ROOT);
212+
}
213+
214+
public static void main(String[] args) throws Exception {
215+
System.out.println("Generated: " + generate());
216+
}
217+
}

examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
9595
feature("tables", "composed-table-cell-showcase", "Composed Table Cells", "DocumentTableCell.node(DocumentNode) — paragraphs, lists, sub-tables inside cells with two-pass measurement.", "tables", "v1.6");
9696
feature("canvas", "canvas-layer-showcase", "Canvas Layer (free-canvas)", "CanvasLayerNode — pixel-precise (x,y) placement of children inside a fixed bounding box.", "canvas", "v1.6", "absolute");
9797
feature("shapes", "shape-container", "Shape-as-Container", "Rounded rect, ellipse, circle containers with ClipPolicy and layered children.", "shapes", "clip");
98+
feature("svg", "svg-icon-gallery", "SVG Icon Gallery", "34 real-world multicolour svgrepo icons through SvgIcon.parse — native vector layers, the whole set 156 KB of sources.", "svg", "icons", "v1.8");
9899
feature("shapes", "vector-path", "Vector Paths (Bézier)", "addPath(...) — free-form design shapes with native cubic Bézier curves: stroked waves, filled blobs, mixed line/curve ribbons. No tessellation.", "shapes", "bezier", "v1.8");
99100
feature("transforms", "transforms", "Layers + Transforms", "rotate / scale on every leaf builder + LayerStack with explicit z-index.", "transforms", "layers");
100101
feature("text", "rich-text-showcase", "Rich Text", "Inline runs with bold / italic / colour / link options, markdown parsing.", "text", "rich");
@@ -143,6 +144,7 @@ static String groupLabel(String category, String group) {
143144
case "features/chrome" -> "PDF Chrome (header / footer / watermark)";
144145
case "features/streaming" -> "Streaming & I/O";
145146
case "features/snapshots" -> "Snapshot Testing";
147+
case "features/svg" -> "SVG Import";
146148
case "features/debug" -> "Debug & Diagnostics";
147149
case "flagships/default" -> "Flagship Demos";
148150
default -> capitalize(group);
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)