Skip to content

Commit d14b6f2

Browse files
committed
docs(examples): icon gallery as uniform card grid - centred icons, label plaques
1 parent c3c8fb4 commit d14b6f2

3 files changed

Lines changed: 117 additions & 31 deletions

File tree

5.09 KB
Binary file not shown.

examples/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,10 @@ flow.addPath(path -> path
384384
### SVG icon gallery
385385

386386
A stress-test sheet for the beta SVG reader: 34 real-world multicolour
387-
icons (svgrepo.com) parsed by `SvgIcon.parse` and placed with
388-
`addSvgIcon(icon, 50)` — captions under every icon, every layer a native
389-
vector path. The entire icon set weighs 156 KB of `.svg` sources; the
390-
rendered page is a 65 KB PDF.
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.
391391

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

examples/src/main/java/com/demcha/examples/features/svg/SvgIconGalleryExample.java

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,37 @@
22

33
import com.demcha.compose.GraphCompose;
44
import com.demcha.compose.document.api.DocumentSession;
5-
import com.demcha.compose.document.dsl.SectionBuilder;
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;
69
import com.demcha.compose.document.node.DocumentNode;
10+
import com.demcha.compose.document.node.LayerAlign;
711
import com.demcha.compose.document.node.TextAlign;
812
import com.demcha.compose.document.style.DocumentColor;
913
import com.demcha.compose.document.style.DocumentInsets;
14+
import com.demcha.compose.document.style.DocumentStroke;
15+
import com.demcha.compose.document.style.DocumentTextDecoration;
1016
import com.demcha.compose.document.style.DocumentTextStyle;
1117
import com.demcha.compose.document.svg.SvgIcon;
18+
import com.demcha.compose.font.FontName;
1219
import com.demcha.examples.support.ExampleOutputPaths;
1320

1421
import java.io.InputStream;
1522
import java.nio.charset.StandardCharsets;
1623
import java.nio.file.Path;
1724
import java.util.List;
1825
import java.util.Locale;
26+
import java.util.Map;
1927
import java.util.Objects;
2028

2129
/**
2230
* Runnable stress-test gallery for the beta SVG icon reader: 34 real-world
2331
* multicolour icons (up to 19 layers each) read straight from {@code .svg}
24-
* resources via {@link SvgIcon#parse(String)} and stacked on the page with
25-
* {@code addSvgIcon(...)} — every curve a native PDF Bézier, the whole icon
26-
* set a fraction of one screenshot's weight.
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.
2736
*
2837
* <pre>{@code
2938
* flow.addSvgIcon(SvgIcon.read(Path.of("icons/apple.svg")), 52);
@@ -45,25 +54,56 @@ public final class SvgIconGalleryExample {
4554
"setting", "shopping-cart", "shopping", "social-contact", "starfish",
4655
"steak", "store-homepage-home", "toolbox", "upload");
4756

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+
4874
private static final int COLUMNS = 5;
49-
private static final double ICON_SIZE = 50;
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();
5094

5195
private SvgIconGalleryExample() {
5296
}
5397

5498
/**
55-
* Renders the 34-icon gallery sheet with a caption under every icon.
99+
* Renders the 34-icon gallery sheet as a uniform card grid.
56100
*
57101
* @return path to the generated PDF
58102
* @throws Exception if rendering or resource IO fails
59103
*/
60104
public static Path generate() throws Exception {
61105
Path pdfFile = ExampleOutputPaths.prepare("features/svg", "svg-icon-gallery.pdf");
62106

63-
DocumentTextStyle caption = DocumentTextStyle.DEFAULT
64-
.withSize(7.5)
65-
.withColor(DocumentColor.rgb(90, 96, 105));
66-
67107
try (DocumentSession document = GraphCompose.document(pdfFile)
68108
.pageSize(595, 842)
69109
.margin(DocumentInsets.of(34))
@@ -75,16 +115,21 @@ public static Path generate() throws Exception {
75115
page.addParagraph(p -> p
76116
.text("34 real-world multicolour icons (svgrepo.com) read by SvgIcon.parse "
77117
+ "— every layer a native vector path, the whole set 156 KB of sources.")
78-
.textStyle(DocumentTextStyle.DEFAULT.withSize(9.5)
79-
.withColor(DocumentColor.rgb(90, 96, 105)))
118+
.textStyle(DocumentTextStyle.DEFAULT.withSize(9.5).withColor(MUTED))
80119
.padding(DocumentInsets.bottom(14)));
81120

82121
for (int start = 0; start < ICONS.size(); start += COLUMNS) {
83122
List<String> chunk = ICONS.subList(start, Math.min(start + COLUMNS, ICONS.size()));
84123
page.addRow(row -> {
85-
row.spacing(10).evenWeights().margin(DocumentInsets.bottom(12));
124+
row.spacing(10).evenWeights().margin(DocumentInsets.bottom(10));
86125
for (String name : chunk) {
87-
row.add(cell(name, caption));
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);
88133
}
89134
});
90135
}
@@ -96,16 +141,58 @@ public static Path generate() throws Exception {
96141
return pdfFile;
97142
}
98143

99-
private static DocumentNode cell(String name, DocumentTextStyle caption) {
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) {
100164
SvgIcon icon = loadIcon(name);
101-
return new SectionBuilder()
102-
.name("Icon" + name.replace('-', '_'))
103-
.spacing(4)
104-
.addSvgIcon(icon, ICON_SIZE)
105-
.addParagraph(p -> p
106-
.text(pretty(name))
107-
.align(TextAlign.LEFT)
108-
.textStyle(caption))
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())
109196
.build();
110197
}
111198

@@ -119,10 +206,9 @@ private static SvgIcon loadIcon(String name) {
119206
}
120207
}
121208

122-
/** {@code "camera-take-pictures"} → {@code "Camera take pictures"}. */
123-
private static String pretty(String name) {
124-
String spaced = name.replace('-', ' ');
125-
return Character.toUpperCase(spaced.charAt(0)) + spaced.substring(1).toLowerCase(Locale.ROOT);
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);
126212
}
127213

128214
public static void main(String[] args) throws Exception {

0 commit comments

Comments
 (0)