55import com .demcha .compose .document .api .DocumentSession ;
66import com .demcha .compose .document .dsl .ParagraphBuilder ;
77import com .demcha .compose .document .dsl .SectionBuilder ;
8- import com .demcha .compose .document .image .DocumentImageData ;
9- import com .demcha .compose .document .image .DocumentImageFitMode ;
108import com .demcha .compose .document .node .DocumentNode ;
119import com .demcha .compose .document .node .LayerAlign ;
1210import com .demcha .compose .document .node .TextAlign ;
2119import com .demcha .compose .font .FontName ;
2220import com .demcha .examples .support .ExampleOutputPaths ;
2321
24- import javax .imageio .ImageIO ;
25- import java .awt .GradientPaint ;
26- import java .awt .Graphics2D ;
27- import java .awt .Polygon ;
28- import java .awt .RenderingHints ;
29- import java .awt .image .BufferedImage ;
30- import java .io .ByteArrayOutputStream ;
3122import java .nio .file .Path ;
3223
3324/**
@@ -86,8 +77,6 @@ private BusinessReportExample() {
8677 public static Path generate () throws Exception {
8778 Path outputFile = ExampleOutputPaths .prepare ("flagships" , "business-report.pdf" );
8879
89- DocumentImageData heroImage = DocumentImageData .fromBytes (renderHeroImage (380 , 200 ));
90-
9180 try (DocumentSession document = GraphCompose .document (outputFile )
9281 .pageSize (DocumentPageSize .A4 )
9382 .pageBackground (PAPER )
@@ -155,22 +144,17 @@ public static Path generate() throws Exception {
155144 .margin (new DocumentInsets (4 , 0 , 0 , 0 ))))
156145 .addSection ("HeroImage" , section -> section
157146 .padding (DocumentInsets .zero ())
158- // Hero image lives inside a rounded
159- // shape container so the navy edges
160- // soften into a frame instead of
161- // bleeding straight to the page edge .
147+ // The hero scene is fully vector now: a
148+ // gradient-sky shape with two polygon
149+ // mountain ranges, clipped to the rounded
150+ // frame. No raster, no AWT .
162151 .addContainer (frame -> frame
163152 .name ("HeroFrame" )
164153 .roundedRect (210 , 110 , 12 )
165154 .fillColor (NAVY_DARK )
166155 .stroke (DocumentStroke .of (GOLD , 0.6 ))
167156 .clipPolicy (ClipPolicy .CLIP_PATH )
168- .center (new com .demcha .compose .document .dsl .ImageBuilder ()
169- .name ("HeroImage" )
170- .source (heroImage )
171- .size (204 , 104 )
172- .fitMode (DocumentImageFitMode .COVER )
173- .build ()))))
157+ .center (buildHeroScene (204 , 104 )))))
174158
175159 // Three KPI cards
176160 .addRow ("KpiRow" , row -> row
@@ -444,8 +428,8 @@ private static DocumentNode buildChart() {
444428 .build ();
445429 com .demcha .compose .document .chart .ChartStyle style =
446430 com .demcha .compose .document .chart .ChartStyle .builder ()
447- .seriesPaint (0 , com .demcha .compose .document .chart .DocumentPaint .solid (NAVY ))
448- .seriesPaint (1 , com .demcha .compose .document .chart .DocumentPaint .solid (GOLD ))
431+ .seriesPaint (0 , com .demcha .compose .document .style .DocumentPaint .solid (NAVY ))
432+ .seriesPaint (1 , com .demcha .compose .document .style .DocumentPaint .solid (GOLD ))
449433 .build ();
450434 return new com .demcha .compose .document .node .ChartNode (
451435 "PerformanceChart" , spec , style , null , null );
@@ -454,53 +438,60 @@ private static DocumentNode buildChart() {
454438 // ─────────────────── Hero image generator ─────────────────────
455439
456440 /**
457- * Renders a simple gradient hero image (sunset sky + mountain
458- * silhouette) so the example does not depend on any external image
459- * asset. The result is encoded as PNG bytes and embedded directly.
460- *
461- * <p>This is the one remaining raster block in the example, and it is
462- * deliberate: the sky requires a smooth linear gradient, which the engine
463- * does not paint natively yet ({@code DocumentPaint.linear} is reserved
464- * for the gradient work). Once gradients land, the mountains become
465- * {@code PolygonNode}s, the glow a translucent fill, and this method goes
466- * away like the old chart raster did.</p>
441+ * Builds the hero scene fully from vector primitives: a gradient-sky
442+ * shape (warm cream at the horizon rising into slate) with two polygon
443+ * mountain ranges — the distant one translucent, the foreground one
444+ * solid. The same sunset the old Graphics2D raster painted, now native:
445+ * deterministic, crisp at any zoom, and free of the AWT dependency.
467446 */
468- private static byte [] renderHeroImage (int width , int height ) throws Exception {
469- BufferedImage img = new BufferedImage (width , height , BufferedImage .TYPE_INT_RGB );
470- Graphics2D g = img .createGraphics ();
471- try {
472- g .setRenderingHint (RenderingHints .KEY_ANTIALIASING , RenderingHints .VALUE_ANTIALIAS_ON );
473- // Sky gradient: slate at the top, warm cream at the horizon.
474- g .setPaint (new GradientPaint (
475- 0 , 0 , new java .awt .Color (58 , 70 , 100 ),
476- 0 , height * 0.7f , new java .awt .Color (218 , 196 , 162 )));
477- g .fillRect (0 , 0 , width , height );
478- // Distant mountain silhouette.
479- g .setColor (new java .awt .Color (50 , 62 , 88 , 230 ));
480- Polygon farRange = new Polygon (
481- new int []{0 , (int ) (width * 0.18 ), (int ) (width * 0.36 ), (int ) (width * 0.54 ),
482- (int ) (width * 0.72 ), (int ) (width * 0.88 ), width , width , 0 },
483- new int []{(int ) (height * 0.55 ), (int ) (height * 0.40 ), (int ) (height * 0.50 ),
484- (int ) (height * 0.34 ), (int ) (height * 0.46 ), (int ) (height * 0.36 ),
485- (int ) (height * 0.50 ), height , height },
486- 9 );
487- g .fill (farRange );
488- // Foreground mountain wedge.
489- g .setColor (new java .awt .Color (28 , 36 , 60 ));
490- Polygon foreRange = new Polygon (
491- new int []{0 , (int ) (width * 0.22 ), (int ) (width * 0.40 ), (int ) (width * 0.60 ),
492- (int ) (width * 0.82 ), width , width , 0 },
493- new int []{(int ) (height * 0.78 ), (int ) (height * 0.55 ), (int ) (height * 0.68 ),
494- (int ) (height * 0.50 ), (int ) (height * 0.62 ), (int ) (height * 0.74 ),
495- height , height },
496- 8 );
497- g .fill (foreRange );
498- } finally {
499- g .dispose ();
447+ private static DocumentNode buildHeroScene (double width , double height ) {
448+ com .demcha .compose .document .node .ShapeNode sky =
449+ new com .demcha .compose .document .dsl .ShapeBuilder ()
450+ .name ("HeroSky" )
451+ .size (width , height )
452+ .fill (new com .demcha .compose .document .style .DocumentPaint .Linear (
453+ java .util .List .of (
454+ new com .demcha .compose .document .style .DocumentPaint .Stop (
455+ 0.0 , DocumentColor .rgb (218 , 196 , 162 )),
456+ new com .demcha .compose .document .style .DocumentPaint .Stop (
457+ 0.30 , DocumentColor .rgb (218 , 196 , 162 )),
458+ new com .demcha .compose .document .style .DocumentPaint .Stop (
459+ 1.0 , DocumentColor .rgb (58 , 70 , 100 ))),
460+ 90.0 ))
461+ .build ();
462+ com .demcha .compose .document .node .PolygonNode farRange =
463+ new com .demcha .compose .document .node .PolygonNode (
464+ "HeroFarRange" , width , height ,
465+ heroPoints (new double [][] {
466+ {0 , .45 }, {.18 , .60 }, {.36 , .50 }, {.54 , .66 },
467+ {.72 , .54 }, {.88 , .64 }, {1 , .50 }, {1 , 0 }, {0 , 0 }}),
468+ DocumentColor .rgba (50 , 62 , 88 , 230 ), null ,
469+ DocumentInsets .zero (), DocumentInsets .zero ());
470+ com .demcha .compose .document .node .PolygonNode foreRange =
471+ new com .demcha .compose .document .node .PolygonNode (
472+ "HeroForeRange" , width , height ,
473+ heroPoints (new double [][] {
474+ {0 , .22 }, {.22 , .45 }, {.40 , .32 }, {.60 , .50 },
475+ {.82 , .38 }, {1 , .26 }, {1 , 0 }, {0 , 0 }}),
476+ DocumentColor .rgb (28 , 36 , 60 ), null ,
477+ DocumentInsets .zero (), DocumentInsets .zero ());
478+ return new com .demcha .compose .document .node .CanvasLayerNode (
479+ "HeroScene" , width , height ,
480+ java .util .List .of (
481+ new com .demcha .compose .document .node .CanvasChild (sky , 0 , 0 ),
482+ new com .demcha .compose .document .node .CanvasChild (farRange , 0 , 0 ),
483+ new com .demcha .compose .document .node .CanvasChild (foreRange , 0 , 0 )),
484+ ClipPolicy .OVERFLOW_VISIBLE , DocumentInsets .zero (), DocumentInsets .zero ());
485+ }
486+
487+ private static java .util .List <com .demcha .compose .document .style .ShapePoint > heroPoints (
488+ double [][] xy ) {
489+ java .util .List <com .demcha .compose .document .style .ShapePoint > points =
490+ new java .util .ArrayList <>(xy .length );
491+ for (double [] p : xy ) {
492+ points .add (new com .demcha .compose .document .style .ShapePoint (p [0 ], p [1 ]));
500493 }
501- ByteArrayOutputStream out = new ByteArrayOutputStream ();
502- ImageIO .write (img , "png" , out );
503- return out .toByteArray ();
494+ return points ;
504495 }
505496
506497 // ─────────────────── Text styles ──────────────────────────────
0 commit comments