RasterPath.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.klimt.drawing.raster;

import java.util.Arrays;

/**
 * A shape to fill or stroke on a {@link RasterCanvas}: a sequence of segments,
 * as a {@code java.awt.geom.GeneralPath}, where rectangles, ellipses and arcs
 * stay whole (each canvas draws them the way it draws them best, Java2D with
 * its own shapes).
 */
public final class RasterPath {

	public static final int MOVE_TO = 0;
	public static final int LINE_TO = 1;
	/** Control point 1, control point 2, end point. */
	public static final int CUBIC_TO = 2;
	public static final int CLOSE = 3;
	/**
	 * x, y, width, height, arcWidth, arcHeight: as {@code RoundRectangle2D}, the
	 * arcs are the diameters of the corners (a plain rectangle when both are 0).
	 */
	public static final int RECTANGLE = 4;
	/** x, y, width, height of the bounding box, as {@code Ellipse2D}. */
	public static final int ELLIPSE = 5;
	/**
	 * x, y, width, height of the bounding box, start, extent: an open arc, as
	 * {@code Arc2D} (degrees, counterclockwise on screen, 0 at 3 o'clock).
	 */
	public static final int ARC = 6;
	/**
	 * rx, ry, x axis rotation (degrees), large arc flag, sweep flag, x, y: an
	 * elliptical arc from the current point, as in SVG.
	 */
	public static final int ARC_TO = 7;

	private static final int[] SIZES = { 2, 2, 6, 0, 6, 4, 6, 7 };

	private int[] types = new int[8];
	private double[] coords = new double[32];
	private int count;
	private int coordCount;

	public static RasterPath rectangle(double x, double y, double width, double height, double arcWidth,
			double arcHeight) {
		return new RasterPath().add(RECTANGLE, x, y, width, height, arcWidth, arcHeight);
	}

	public static RasterPath ellipse(double x, double y, double width, double height) {
		return new RasterPath().add(ELLIPSE, x, y, width, height);
	}

	public static RasterPath arc(double x, double y, double width, double height, double start, double extent) {
		return new RasterPath().add(ARC, x, y, width, height, start, extent);
	}

	public static RasterPath line(double x1, double y1, double x2, double y2) {
		return new RasterPath().moveTo(x1, y1).lineTo(x2, y2);
	}

	public RasterPath moveTo(double x, double y) {
		return add(MOVE_TO, x, y);
	}

	public RasterPath lineTo(double x, double y) {
		return add(LINE_TO, x, y);
	}

	public RasterPath cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) {
		return add(CUBIC_TO, x1, y1, x2, y2, x3, y3);
	}

	public RasterPath arcTo(double rx, double ry, double rotation, boolean largeArc, boolean sweep, double x,
			double y) {
		return add(ARC_TO, rx, ry, rotation, largeArc ? 1 : 0, sweep ? 1 : 0, x, y);
	}

	public RasterPath closePath() {
		return add(CLOSE);
	}

	private RasterPath add(int type, double... values) {
		if (values.length != SIZES[type])
			throw new IllegalArgumentException();
		if (count == types.length)
			types = Arrays.copyOf(types, count * 2);
		if (coordCount + values.length > coords.length)
			coords = Arrays.copyOf(coords, Math.max(coords.length * 2, coordCount + values.length));
		types[count++] = type;
		System.arraycopy(values, 0, coords, coordCount, values.length);
		coordCount += values.length;
		return this;
	}

	public boolean isEmpty() {
		return count == 0;
	}

	/**
	 * Calls the visitor for every segment, in order.
	 */
	public void visit(Visitor visitor) {
		int c = 0;
		for (int i = 0; i < count; i++) {
			final double[] v = coords;
			switch (types[i]) {
			case MOVE_TO:
				visitor.moveTo(v[c], v[c + 1]);
				break;
			case LINE_TO:
				visitor.lineTo(v[c], v[c + 1]);
				break;
			case CUBIC_TO:
				visitor.cubicTo(v[c], v[c + 1], v[c + 2], v[c + 3], v[c + 4], v[c + 5]);
				break;
			case CLOSE:
				visitor.closePath();
				break;
			case RECTANGLE:
				visitor.rectangle(v[c], v[c + 1], v[c + 2], v[c + 3], v[c + 4], v[c + 5]);
				break;
			case ELLIPSE:
				visitor.ellipse(v[c], v[c + 1], v[c + 2], v[c + 3]);
				break;
			case ARC:
				visitor.arc(v[c], v[c + 1], v[c + 2], v[c + 3], v[c + 4], v[c + 5]);
				break;
			case ARC_TO:
				visitor.arcTo(v[c], v[c + 1], v[c + 2], v[c + 3] != 0, v[c + 4] != 0, v[c + 5], v[c + 6]);
				break;
			default:
				throw new IllegalStateException();
			}
			c += SIZES[types[i]];
		}
	}

	/**
	 * The segments of a {@link RasterPath}, see its constants.
	 */
	public interface Visitor {

		public void moveTo(double x, double y);

		public void lineTo(double x, double y);

		public void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3);

		public void closePath();

		public void rectangle(double x, double y, double width, double height, double arcWidth, double arcHeight);

		public void ellipse(double x, double y, double width, double height);

		public void arc(double x, double y, double width, double height, double start, double extent);

		public void arcTo(double rx, double ry, double rotation, boolean largeArc, boolean sweep, double x, double y);

	}

}