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
Binary file added assets/readme/examples/svg-icon-gallery.pdf
Binary file not shown.
16 changes: 16 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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) |
| [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) |
| [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) |
Expand Down Expand Up @@ -380,6 +381,21 @@ flow.addPath(path -> path
[📄 View PDF](../assets/readme/examples/vector-path.pdf) ·
[📜 Full source](src/main/java/com/demcha/examples/features/shapes/VectorPathExample.java)

### SVG icon gallery

A stress-test sheet for the beta SVG reader: 34 real-world multicolour
icons (svgrepo.com) parsed by `SvgIcon.parse` and presented as a tile
grid — each icon centred on a rounded card with a label plaque across
the bottom, every layer a native vector path. The entire icon set weighs
156 KB of `.svg` sources; the rendered page is a 70 KB PDF.

```java
flow.addSvgIcon(SvgIcon.parse(readResource("/icons/apple.svg")), 50);
```

[📄 View PDF](../assets/readme/examples/svg-icon-gallery.pdf) ·
[📜 Full source](src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java)

### Advanced tables

`DocumentTableCell.rowSpan(int)` mirrors `colSpan(int)`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.demcha.examples.features.shapes.VectorPathExample;
import com.demcha.examples.features.snapshots.LayoutSnapshotRegressionExample;
import com.demcha.examples.features.streaming.HttpStreamingExample;
import com.demcha.examples.features.svg.SvgIconGalleryExample;
import com.demcha.examples.features.tables.ComposedTableCellExample;
import com.demcha.examples.features.tables.TableAdvancedExample;
import com.demcha.examples.features.text.InlineShapesExample;
Expand Down Expand Up @@ -130,6 +131,7 @@ public static void main(String[] args) throws Exception {
// v1.5 visual primitives
System.out.println("Generated: " + ShapeContainerExample.generate());
System.out.println("Generated: " + VectorPathExample.generate());
System.out.println("Generated: " + SvgIconGalleryExample.generate());
System.out.println("Generated: " + TransformsExample.generate());
System.out.println("Generated: " + TableAdvancedExample.generate());

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

import com.demcha.compose.GraphCompose;
import com.demcha.compose.document.api.DocumentSession;
import com.demcha.compose.document.dsl.LayerStackBuilder;
import com.demcha.compose.document.dsl.ParagraphBuilder;
import com.demcha.compose.document.dsl.PathBuilder;
import com.demcha.compose.document.dsl.ShapeContainerBuilder;
import com.demcha.compose.document.node.DocumentNode;
import com.demcha.compose.document.node.LayerAlign;
import com.demcha.compose.document.node.TextAlign;
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.DocumentTextDecoration;
import com.demcha.compose.document.style.DocumentTextStyle;
import com.demcha.compose.document.svg.SvgIcon;
import com.demcha.compose.font.FontName;
import com.demcha.examples.support.ExampleOutputPaths;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/**
* Runnable stress-test gallery for the beta SVG icon reader: 34 real-world
* multicolour icons (up to 19 layers each) read straight from {@code .svg}
* resources via {@link SvgIcon#parse(String)} and presented as a tile grid —
* each icon centred on a rounded card with a label plaque across the bottom,
* every curve a native PDF Bézier, the whole icon set a fraction of one
* screenshot's weight.
*
* <pre>{@code
* flow.addSvgIcon(SvgIcon.read(Path.of("icons/apple.svg")), 52);
* }</pre>
*
* <p>Icon artwork: <a href="https://www.svgrepo.com">svgrepo.com</a>
* collections (see each icon's page for its licence).</p>
*
* @author Artem Demchyshyn
*/
public final class SvgIconGalleryExample {

private static final List<String> ICONS = List.of(
"apple", "avocado", "banana", "boxing", "calendar",
"camera-take-pictures", "chat-chat", "cherry", "diagnosis", "eye-password-eye-password",
"feet", "food", "grape", "headphones-music", "key-password",
"kiwi-fruit", "magnifying-glass-find-search", "microphone-singing", "movie", "peach",
"pencil-revision", "personal-account-account", "picture", "record", "reminder-alert",
"setting", "shopping-cart", "shopping", "social-contact", "starfish",
"steak", "store-homepage-home", "toolbox", "upload");

/** Short display labels for the verbose svgrepo file names. */
private static final Map<String, String> SHORT_LABELS = Map.ofEntries(
Map.entry("camera-take-pictures", "Camera"),
Map.entry("chat-chat", "Chat"),
Map.entry("eye-password-eye-password", "Eye"),
Map.entry("headphones-music", "Headphones"),
Map.entry("key-password", "Key"),
Map.entry("kiwi-fruit", "Kiwi"),
Map.entry("magnifying-glass-find-search", "Search"),
Map.entry("microphone-singing", "Microphone"),
Map.entry("pencil-revision", "Pencil"),
Map.entry("personal-account-account", "Account"),
Map.entry("reminder-alert", "Reminder"),
Map.entry("shopping-cart", "Cart"),
Map.entry("social-contact", "Contact"),
Map.entry("store-homepage-home", "Store"));

private static final int COLUMNS = 5;
private static final double CARD_WIDTH = 97;
private static final double CARD_HEIGHT = 84;
private static final double CARD_RADIUS = 9;
private static final double PLAQUE_HEIGHT = 17;
/** Icons contain-fit into this square inside the card body. */
private static final double ICON_BOX = 44;

private static final DocumentColor CARD_FILL = DocumentColor.rgb(248, 249, 251);
private static final DocumentColor CARD_BORDER = DocumentColor.rgb(228, 231, 236);
private static final DocumentColor PLAQUE_FILL = DocumentColor.rgb(235, 238, 243);
private static final DocumentColor LABEL_INK = DocumentColor.rgb(82, 90, 102);
private static final DocumentColor MUTED = DocumentColor.rgb(90, 96, 105);

private static final DocumentTextStyle LABEL_STYLE = DocumentTextStyle.builder()
.fontName(FontName.HELVETICA_BOLD)
.size(6.4)
.decoration(DocumentTextDecoration.BOLD)
.color(LABEL_INK)
.build();

private SvgIconGalleryExample() {
}

/**
* Renders the 34-icon gallery sheet as a uniform card grid.
*
* @return path to the generated PDF
* @throws Exception if rendering or resource IO fails
*/
public static Path generate() throws Exception {
Path pdfFile = ExampleOutputPaths.prepare("features/svg", "svg-icon-gallery.pdf");

try (DocumentSession document = GraphCompose.document(pdfFile)
.pageSize(595, 842)
.margin(DocumentInsets.of(34))
.create()) {
document.pageFlow(page -> {
page.addParagraph(p -> p
.text("SVG icon gallery")
.textStyle(DocumentTextStyle.DEFAULT.withSize(22)));
page.addParagraph(p -> p
.text("34 real-world multicolour icons (svgrepo.com) read by SvgIcon.parse "
+ "— every layer a native vector path, the whole set 156 KB of sources.")
.textStyle(DocumentTextStyle.DEFAULT.withSize(9.5).withColor(MUTED))
.padding(DocumentInsets.bottom(14)));

for (int start = 0; start < ICONS.size(); start += COLUMNS) {
List<String> chunk = ICONS.subList(start, Math.min(start + COLUMNS, ICONS.size()));
page.addRow(row -> {
row.spacing(10).evenWeights().margin(DocumentInsets.bottom(10));
for (String name : chunk) {
// Rows host sections; the fixed-size card rides inside one.
row.addSection("Tile" + name.replace('-', '_'),
s -> s.add(card(name)));
}
// Pad the last row so its cells line up with the full rows.
for (int filler = chunk.size(); filler < COLUMNS; filler++) {
row.addSpacer(CARD_WIDTH);
}
});
}
});

document.buildPdf();
}

return pdfFile;
}

/** One tile: rounded card, icon centred in the body, label plaque across the bottom. */
private static DocumentNode card(String name) {
String id = name.replace('-', '_');
return new ShapeContainerBuilder()
.name("Card" + id)
.roundedRect(CARD_WIDTH, CARD_HEIGHT, CARD_RADIUS)
.fillColor(CARD_FILL)
.stroke(DocumentStroke.of(CARD_BORDER, 0.8))
.position(iconStack(name, id), 0, -PLAQUE_HEIGHT / 2.0, LayerAlign.CENTER)
.bottomCenter(plaque(name, id))
.build();
}

/**
* Builds the icon as a standalone layer stack whose box is exactly the
* icon's contain-fit size, so the card's CENTER anchor lands true.
* (The flow-level {@code addSvgIcon(...)} sugar targets flows; a card
* layer needs the node form of the same composition.)
*/
private static DocumentNode iconStack(String name, String id) {
SvgIcon icon = loadIcon(name);
double width = Math.min(ICON_BOX, ICON_BOX * icon.aspectRatio());
double height = width / icon.aspectRatio();
LayerStackBuilder stack = new LayerStackBuilder().name("Icon" + id);
for (int i = 0; i < icon.layers().size(); i++) {
SvgIcon.Layer layer = icon.layers().get(i);
stack.layer(new PathBuilder()
.name("SvgLayer" + i)
.size(width, height)
.svg(layer.geometry())
.fillColor(layer.fill())
.stroke(layer.stroke())
.build());
}
return stack.build();
}

/**
* Full-width label band across the card bottom; the card's CLIP_PATH
* rounds its outer corners automatically.
*/
private static DocumentNode plaque(String name, String id) {
return new ShapeContainerBuilder()
.name("Plaque" + id)
.rectangle(CARD_WIDTH, PLAQUE_HEIGHT)
.fillColor(PLAQUE_FILL)
.center(new ParagraphBuilder()
.text(label(name))
.textStyle(LABEL_STYLE)
.align(TextAlign.CENTER)
.margin(DocumentInsets.zero())
.build())
.build();
}

private static SvgIcon loadIcon(String name) {
try (InputStream in = Objects.requireNonNull(
SvgIconGalleryExample.class.getResourceAsStream("/icons/" + name + ".svg"),
"icon resource missing: " + name)) {
return SvgIcon.parse(new String(in.readAllBytes(), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new IllegalStateException("failed to load icon: " + name, e);
}
}

/** {@code "camera-take-pictures"} → {@code "CAMERA"}; plain names just uppercase. */
private static String label(String name) {
return SHORT_LABELS.getOrDefault(name, name.replace('-', ' ')).toUpperCase(Locale.ROOT);
}

public static void main(String[] args) throws Exception {
System.out.println("Generated: " + generate());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ record Entry(String title, String description, List<String> tags, String codeUrl
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");
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");
feature("shapes", "shape-container", "Shape-as-Container", "Rounded rect, ellipse, circle containers with ClipPolicy and layered children.", "shapes", "clip");
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");
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");
feature("transforms", "transforms", "Layers + Transforms", "rotate / scale on every leaf builder + LayerStack with explicit z-index.", "transforms", "layers");
feature("text", "rich-text-showcase", "Rich Text", "Inline runs with bold / italic / colour / link options, markdown parsing.", "text", "rich");
Expand Down Expand Up @@ -143,6 +144,7 @@ static String groupLabel(String category, String group) {
case "features/chrome" -> "PDF Chrome (header / footer / watermark)";
case "features/streaming" -> "Streaming & I/O";
case "features/snapshots" -> "Snapshot Testing";
case "features/svg" -> "SVG Import";
case "features/debug" -> "Debug & Diagnostics";
case "flagships/default" -> "Flagship Demos";
default -> capitalize(group);
Expand Down
3 changes: 3 additions & 0 deletions examples/src/main/resources/icons/apple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading