|
27 | 27 | import com.demcha.compose.document.style.DocumentTextStyle; |
28 | 28 | import com.demcha.compose.document.style.DocumentTransform; |
29 | 29 | import com.demcha.compose.document.style.ShapePoint; |
| 30 | +import com.demcha.compose.document.svg.SvgIcon; |
| 31 | +import com.demcha.compose.document.svg.SvgPath; |
30 | 32 | import com.demcha.compose.document.theme.BusinessTheme; |
31 | 33 | import com.demcha.compose.font.FontName; |
32 | 34 | import com.demcha.examples.support.ExampleOutputPaths; |
@@ -57,6 +59,21 @@ public final class FeatureCatalogExample { |
57 | 59 | private static final DocumentColor CODE_BG = DocumentColor.rgb(244, 245, 248); |
58 | 60 | private static final DocumentColor CODE_INK = DocumentColor.rgb(52, 74, 94); |
59 | 61 |
|
| 62 | + /** Material Icons "favorite" path data (Apache 2.0), viewBox 0 0 24 24. */ |
| 63 | + private static final String MATERIAL_HEART_D = |
| 64 | + "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" |
| 65 | + + "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" |
| 66 | + + "c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"; |
| 67 | + |
| 68 | + /** The svgrepo.com icons shown on the beta SvgIcon card row: file → label. */ |
| 69 | + private static final List<String[]> CATALOG_ICONS = List.of( |
| 70 | + new String[] {"apple", "APPLE"}, |
| 71 | + new String[] {"headphones-music", "HEADPHONES"}, |
| 72 | + new String[] {"shopping-cart", "CART"}, |
| 73 | + new String[] {"camera-take-pictures", "CAMERA"}, |
| 74 | + new String[] {"starfish", "STARFISH"}, |
| 75 | + new String[] {"toolbox", "TOOLBOX"}); |
| 76 | + |
60 | 77 | private FeatureCatalogExample() { |
61 | 78 | } |
62 | 79 |
|
@@ -300,6 +317,47 @@ public static Path generate() throws Exception { |
300 | 317 | TEAL, DocumentStroke.of(GOLD, 1.2), |
301 | 318 | DocumentInsets.zero(), DocumentInsets.zero()))); |
302 | 319 |
|
| 320 | + feature(flow, "Vector paths — native Bézier curves, dashed strokes", """ |
| 321 | + section.addPath(path -> path.size(420, 48) // unit box, y-up; controls may overshoot |
| 322 | + .moveTo(0.0, 0.5) |
| 323 | + .curveTo(0.25, 1.1, 0.25, -0.1, 0.5, 0.5) |
| 324 | + .curveTo(0.75, 1.1, 0.75, -0.1, 1.0, 0.5) |
| 325 | + .stroke(DocumentStroke.of(TEAL, 2.2))); |
| 326 | + // .dashed(6, 3) turns the same stroke into a dash pattern that follows the curve""", |
| 327 | + demo -> demo |
| 328 | + .addPath(path -> path.size(420, 48) |
| 329 | + .moveTo(0.0, 0.5) |
| 330 | + .curveTo(0.25, 1.1, 0.25, -0.1, 0.5, 0.5) |
| 331 | + .curveTo(0.75, 1.1, 0.75, -0.1, 1.0, 0.5) |
| 332 | + .stroke(DocumentStroke.of(TEAL, 2.2))) |
| 333 | + .addPath(path -> path.size(420, 48) |
| 334 | + .moveTo(0.0, 0.5) |
| 335 | + .curveTo(0.25, 1.1, 0.25, -0.1, 0.5, 0.5) |
| 336 | + .curveTo(0.75, 1.1, 0.75, -0.1, 1.0, 0.5) |
| 337 | + .stroke(DocumentStroke.of(GOLD, 2.2)) |
| 338 | + .dashed(6, 3))); |
| 339 | + |
| 340 | + feature(flow, "SVG path import (beta) — any d string as native curves", """ |
| 341 | + // Material Icons "favorite" (Apache 2.0), viewBox 0 0 24 24 |
| 342 | + section.addPath(path -> path.size(64, 64) |
| 343 | + .svg(SvgPath.parse(HEART_D, 0, 0, 24, 24)) // curves stay native PDF operators |
| 344 | + .fillColor(rgb(196, 30, 58)))""", |
| 345 | + demo -> demo.addPath(path -> path.size(64, 64) |
| 346 | + .svg(SvgPath.parse(MATERIAL_HEART_D, 0, 0, 24, 24)) |
| 347 | + .fillColor(DocumentColor.rgb(196, 30, 58)))); |
| 348 | + |
| 349 | + feature(flow, "SVG icons (beta) — multicolour files centred on tile cards", """ |
| 350 | + SvgIcon icon = SvgIcon.read(Path.of("icons/apple.svg")); // layers + resolved paints |
| 351 | + card.roundedRect(74, 64, 8) // fixed box = the tile |
| 352 | + .position(iconNode(icon), 0, -7, LayerAlign.CENTER) // anchor centres the icon's |
| 353 | + .bottomCenter(plaque("APPLE")) // own tight box inside the card""", |
| 354 | + demo -> demo.addRow(r -> { |
| 355 | + r.spacing(8).evenWeights(); |
| 356 | + for (String[] entry : CATALOG_ICONS) { |
| 357 | + r.addSection("Tile" + entry[1], s -> s.add(iconCard(entry[0], entry[1]))); |
| 358 | + } |
| 359 | + })); |
| 360 | + |
303 | 361 | feature(flow, "Shape as container — children clipped to the outline", """ |
304 | 362 | section.addCircle(72, TEAL, c -> c |
305 | 363 | .stroke(DocumentStroke.of(GOLD, 1.2)) |
@@ -344,6 +402,21 @@ public static Path generate() throws Exception { |
344 | 402 | .addSection("C128", c -> c.addBarcode(b -> b.code128() |
345 | 403 | .data("GC-2026-001").size(200, 44))))); |
346 | 404 |
|
| 405 | + feature(flow, "Debug overlay — corner badges name every box", """ |
| 406 | + GraphCompose.document(out) |
| 407 | + .debug(DocumentDebugOptions.nodeLabels()) // or guidesAndNodeLabels() |
| 408 | + .create(); |
| 409 | + // LabelText.FULL_PATH prints whole layout paths; guides() adds box outlines""", |
| 410 | + demo -> demo.addParagraph(p -> p |
| 411 | + .text("Re-render any document with nodeLabels() and every placed box " |
| 412 | + + "grows a corner badge with its semantic name (PriceSummaryTitle[0], " |
| 413 | + + "SvgLayer3, …) — a misplaced component traces straight back to the " |
| 414 | + + "builder call that produced it. The full annotated sheet is " |
| 415 | + + "debug-overlay.pdf among the examples.") |
| 416 | + .textStyle(THEME.text().body()) |
| 417 | + .lineSpacing(1.35) |
| 418 | + .margin(DocumentInsets.zero()))); |
| 419 | + |
347 | 420 | feature(flow, "Page chrome — this document's own header, footer, outline", """ |
348 | 421 | document.metadata(DocumentMetadata.builder().title("…").author("…").build()); |
349 | 422 | document.header(DocumentHeaderFooter.builder().zone(HEADER) |
@@ -413,6 +486,63 @@ private static com.demcha.compose.document.image.DocumentImageData catalogImage( |
413 | 486 | } |
414 | 487 | } |
415 | 488 |
|
| 489 | + /** Classpath icon for the beta SvgIcon card row (same set as the gallery). */ |
| 490 | + private static SvgIcon catalogIcon(String name) { |
| 491 | + try (java.io.InputStream stream = java.util.Objects.requireNonNull( |
| 492 | + FeatureCatalogExample.class.getResourceAsStream("/icons/" + name + ".svg"), |
| 493 | + "icon resource missing: " + name)) { |
| 494 | + return SvgIcon.parse(new String(stream.readAllBytes(), |
| 495 | + java.nio.charset.StandardCharsets.UTF_8)); |
| 496 | + } catch (java.io.IOException e) { |
| 497 | + throw new IllegalStateException("failed to load icon: " + name, e); |
| 498 | + } |
| 499 | + } |
| 500 | + |
| 501 | + /** |
| 502 | + * Mini tile for the icon row: fixed rounded card, the icon centred in the |
| 503 | + * body as a tight-box layer stack, a label plaque across the bottom. The |
| 504 | + * stack keeps the icon's exact contain-fit size — that is what makes the |
| 505 | + * card's CENTER anchor land true. |
| 506 | + */ |
| 507 | + private static com.demcha.compose.document.node.DocumentNode iconCard(String name, String label) { |
| 508 | + SvgIcon icon = catalogIcon(name); |
| 509 | + double box = 34; |
| 510 | + double width = Math.min(box, box * icon.aspectRatio()); |
| 511 | + double height = width / icon.aspectRatio(); |
| 512 | + var stack = new com.demcha.compose.document.dsl.LayerStackBuilder().name("Icon" + label); |
| 513 | + for (int i = 0; i < icon.layers().size(); i++) { |
| 514 | + SvgIcon.Layer layer = icon.layers().get(i); |
| 515 | + stack.layer(new com.demcha.compose.document.dsl.PathBuilder() |
| 516 | + .name("SvgLayer" + i) |
| 517 | + .size(width, height) |
| 518 | + .svg(layer.geometry()) |
| 519 | + .fillColor(layer.fill()) |
| 520 | + .stroke(layer.stroke()) |
| 521 | + .build()); |
| 522 | + } |
| 523 | + double plaqueHeight = 14; |
| 524 | + return new com.demcha.compose.document.dsl.ShapeContainerBuilder() |
| 525 | + .name("IconCard" + label) |
| 526 | + .roundedRect(74, 64, 8) |
| 527 | + .fillColor(DocumentColor.rgb(248, 249, 251)) |
| 528 | + .stroke(DocumentStroke.of(DocumentColor.rgb(228, 231, 236), 0.8)) |
| 529 | + .position(stack.build(), 0, -plaqueHeight / 2.0, |
| 530 | + com.demcha.compose.document.node.LayerAlign.CENTER) |
| 531 | + .bottomCenter(new com.demcha.compose.document.dsl.ShapeContainerBuilder() |
| 532 | + .name("Plaque" + label) |
| 533 | + .rectangle(74, plaqueHeight) |
| 534 | + .fillColor(DocumentColor.rgb(235, 238, 243)) |
| 535 | + .center(new com.demcha.compose.document.dsl.ParagraphBuilder() |
| 536 | + .text(label) |
| 537 | + .textStyle(DocumentTextStyle.builder() |
| 538 | + .fontName(FontName.HELVETICA_BOLD).size(6.2) |
| 539 | + .color(DocumentColor.rgb(82, 90, 102)).build()) |
| 540 | + .align(com.demcha.compose.document.node.TextAlign.CENTER) |
| 541 | + .build()) |
| 542 | + .build()) |
| 543 | + .build(); |
| 544 | + } |
| 545 | + |
416 | 546 | private static com.demcha.compose.document.node.DocumentNode badge(String text) { |
417 | 547 | return new com.demcha.compose.document.dsl.ShapeContainerBuilder() |
418 | 548 | .roundedRect(64, 22, 11) |
|
0 commit comments