PngDemoC.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2025, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 *
 * If you like this project or if you find it useful, you can support us at:
 *
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 *
 * This file is part of PlantUML.
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 *
 */
package net.sourceforge.plantuml.teavm.c.demo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import net.sourceforge.plantuml.klimt.drawing.raster.RasterPath;
import net.sourceforge.plantuml.klimt.font.UFont;
import net.sourceforge.plantuml.klimt.font.UFontFace;
import net.sourceforge.plantuml.klimt.font.UFontFactory;
import net.sourceforge.plantuml.teavm.c.PngCanvas;

/**
 * Entry point validating PNG output in the TeaVM C build, before any PlantUML
 * code is involved: Java calls a C facade over plutovg through TeaVM's
 * {@code @Import}, draws shapes and text with an embedded font, and gets the
 * PNG back as a byte[].
 *
 * Build it with
 * {@code gradlew :generateC -PteavmCMainClass=PngDemoC},
 * then run {@code plantuml-c [output.png]} (default: demo.png).
 *
 * The picture is a small mind map whose boxes are sized from the measured
 * text, plus a text sample with its ascent, baseline and descent lines: the
 * text must sit exactly on the red baseline. Measurements are printed, to be
 * compared with Java (Arial has the same metrics as Liberation Sans).
 */
public class PngDemoC {
	// ::remove folder when JAVA8

	private static final int WHITE = 0xFFFFFFFF;
	private static final int BLACK = 0xFF000000;
	private static final int BORDER = 0xFF181818;
	private static final int FILL = 0xFFF1F1F1;
	private static final int RED = 0xFFE00000;
	private static final int BLUE = 0xFF2060E0;

	private static final int FONT_SIZE = 14;
	private static final double MARGIN_X = 10;
	private static final double MARGIN_Y = 8;

	public static void main(String[] args) throws IOException {
		final String output = args.length > 0 ? args[0] : "demo.png";
		final UFont font = UFontFactory.build("SansSerif", UFontFace.normal(), FONT_SIZE);

		final byte[] png;
		final PngCanvas canvas = new PngCanvas(480, 260, true);
		try {
			canvas.setColor(WHITE);
			canvas.fill(RasterPath.rectangle(0, 0, 480, 260, 0, 0));

			final double[] metrics = canvas.getFontMetrics(font);
			System.out.println("Font size " + FONT_SIZE + ": ascent=" + metrics[0] + " descent=" + metrics[1]
					+ " lineGap=" + metrics[2]);
			for (String s : new String[] { "Hello world", "PlantUML", "Accents: \u00e9\u00e0\u00e7\u00f6 \u20ac" })
				System.out.println("Width of \"" + s + "\": " + canvas.getTextWidth(s, font));

			// A small mind map: boxes sized from the measured text
			final double[] root = box(canvas, font, metrics, "PlantUML", 30, 90);
			final double[] svg = box(canvas, font, metrics, "SVG", 250, 30);
			final double[] png2 = box(canvas, font, metrics, "PNG natif", 250, 150);
			link(canvas, root, svg);
			link(canvas, root, png2);

			// Text sample on its baseline, with the ascent and descent lines
			final String sample = "Accents: \u00e9\u00e0\u00e7\u00f6 \u20ac \u2013 Hxgjp";
			final double x = 30;
			final double baseline = 230;
			final double width = canvas.getTextWidth(sample, font);
			canvas.setStroke(1, 0, 0);
			canvas.setColor(BLUE);
			canvas.stroke(RasterPath.line(x, baseline - metrics[0], x + width, baseline - metrics[0]));
			canvas.stroke(RasterPath.line(x, baseline + metrics[1], x + width, baseline + metrics[1]));
			canvas.setColor(RED);
			canvas.stroke(RasterPath.line(x, baseline, x + width, baseline));
			canvas.setColor(BLACK);
			canvas.drawString(sample, font, x, baseline, 0);

			png = canvas.toPng();
		} finally {
			canvas.dispose();
		}

		try (OutputStream os = new FileOutputStream(output)) {
			os.write(png);
		}
		System.out.println("Written " + output + " (" + png.length + " bytes)");
	}

	/**
	 * Draws a rounded box around the text, its top left corner at (x, y).
	 *
	 * @return x, y, width, height of the box
	 */
	private static double[] box(PngCanvas canvas, UFont font, double[] metrics, String text, double x, double y) {
		final double width = canvas.getTextWidth(text, font) + 2 * MARGIN_X;
		final double height = metrics[0] + metrics[1] + 2 * MARGIN_Y;
		final RasterPath shape = RasterPath.rectangle(x, y, width, height, 12, 12);
		canvas.setColor(FILL);
		canvas.fill(shape);
		canvas.setColor(BORDER);
		canvas.setStroke(1.5, 0, 0);
		canvas.stroke(shape);
		canvas.setColor(BLACK);
		canvas.drawString(text, font, x + MARGIN_X, y + MARGIN_Y + metrics[0], 0);
		return new double[] { x, y, width, height };
	}

	private static void link(PngCanvas canvas, double[] from, double[] to) {
		final double x1 = from[0] + from[2];
		final double y1 = from[1] + from[3] / 2;
		final double x2 = to[0];
		final double y2 = to[1] + to[3] / 2;
		final double middle = (x1 + x2) / 2;
		canvas.setColor(BORDER);
		canvas.setStroke(1, 0, 0);
		canvas.stroke(new RasterPath().moveTo(x1, y1).cubicTo(middle, y1, middle, y2, x2, y2));
	}

}