22
33import com .demcha .compose .GraphCompose ;
44import 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 ;
69import com .demcha .compose .document .node .DocumentNode ;
10+ import com .demcha .compose .document .node .LayerAlign ;
711import com .demcha .compose .document .node .TextAlign ;
812import com .demcha .compose .document .style .DocumentColor ;
913import com .demcha .compose .document .style .DocumentInsets ;
14+ import com .demcha .compose .document .style .DocumentStroke ;
15+ import com .demcha .compose .document .style .DocumentTextDecoration ;
1016import com .demcha .compose .document .style .DocumentTextStyle ;
1117import com .demcha .compose .document .svg .SvgIcon ;
18+ import com .demcha .compose .font .FontName ;
1219import com .demcha .examples .support .ExampleOutputPaths ;
1320
1421import java .io .InputStream ;
1522import java .nio .charset .StandardCharsets ;
1623import java .nio .file .Path ;
1724import java .util .List ;
1825import java .util .Locale ;
26+ import java .util .Map ;
1927import 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