All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.plantuml.klimt.drawing.g2d.ExtendedGeneralPath Maven / Gradle / Ivy

There is a newer version: 1.2024.8
Show newest version
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
 * |
 * |      PlantUML : a free UML diagram generator
 * |
 * +=======================================================================
 *
 * (C) Copyright 2009-2024, 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/liberapay (only 1€ per month!)
 * https://plantuml.com/paypal
 *
 *
 * PlantUML is free software; you can redistribute it and/or modify it
 * under the terms of the MIT License.
 *
 * See http://opensource.org/licenses/MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * PlantUML can occasionally display sponsored or advertising messages. Those
 * messages are usually generated on welcome or error images and never on
 * functional diagrams.
 * See https://plantuml.com/professional if you want to remove them
 *
 * Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
 * are owned by the author of their corresponding sources code (that is, their
 * textual description in PlantUML language). Those images are not covered by
 * this MIT license.
 *
 * The generated images can then be used without any reference to the MIT license.
 * It is not even necessary to stipulate that they have been generated with PlantUML,
 * although this will be appreciated by the PlantUML team.
 *
 * There is an exception : if the textual description in PlantUML language is also covered
 * by any license, then the generated images are logically covered
 * by the very same license.
 *
 * This is the IGY distribution (Install GraphViz by Yourself).
 * You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
 * (see https://plantuml.com/graphviz-dot )
 *
 * Icons provided by OpenIconic :  https://useiconic.com/open
 * Archimate sprites provided by Archi :  http://www.archimatetool.com
 * Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
 * Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
 * ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
 * ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
 * CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
 * Brotli (c) by the Brotli Authors https://github.com/google/brotli
 * Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
 * Twemoji (c) by Twitter at https://twemoji.twitter.com/
 *
 */
package net.sourceforge.plantuml.klimt.drawing.g2d;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;

import net.sourceforge.plantuml.klimt.geom.XPoint2D;
import net.sourceforge.plantuml.log.Logme;

/**
 * The ExtendedGeneralPath class represents a geometric path
 * constructed from straight lines, quadratic and cubic (Bezier) curves and
 * elliptical arc. This class delegates lines and curves to an enclosed
 * GeneralPath. Elliptical arc is implemented using an
 * Arc2D in double precision.
 * 
 * 

* Warning : An elliptical arc may be composed of several path segments. * For further details, see the SVG Appendix F.6 * * @author Thierry Kormann * @version $Id: ExtendedGeneralPath.java 594018 2007-11-12 04:17:41Z cam $ */ public class ExtendedGeneralPath implements Shape, Cloneable { /** The enclosed general path. */ private GeneralPath path; private int numVals = 0; private int numSeg = 0; private double[] values = null; private int[] types = null; private double mx; private double my; private double cx; private double cy; /** * Constructs a new ExtendedGeneralPath. */ public ExtendedGeneralPath() { path = new GeneralPath(); } /** * Constructs a new ExtendedGeneralPath with the specified winding * rule to control operations that require the interior of the path to be * defined. */ public ExtendedGeneralPath(int rule) { path = new GeneralPath(rule); } /** * Constructs a new ExtendedGeneralPath object with the specified * winding rule and the specified initial capacity to store path coordinates. */ public ExtendedGeneralPath(int rule, int initialCapacity) { path = new GeneralPath(rule, initialCapacity); } /** * Constructs a new ExtendedGeneralPath object from an arbitrary * Shape object. */ public ExtendedGeneralPath(Shape s) { this(); append(s, false); } /** * Adds an elliptical arc, defined by two radii, an angle from the x-axis, a * flag to choose the large arc or not, a flag to indicate if we increase or * decrease the angles and the final point of the arc. * * @param rx the x radius of the ellipse * @param ry the y radius of the ellipse * * @param angle the angle from the x-axis of the current coordinate * system to the x-axis of the ellipse in degrees. * * @param largeArcFlag the large arc flag. If true the arc spanning less than or * equal to 180 degrees is chosen, otherwise the arc * spanning greater than 180 degrees is chosen * * @param sweepFlag the sweep flag. If true the line joining center to arc * sweeps through decreasing angles otherwise it sweeps * through increasing angles * * @param x the absolute x coordinate of the final point of the arc. * @param y the absolute y coordinate of the final point of the arc. */ public void arcTo(double rx, double ry, double angle, boolean largeArcFlag, boolean sweepFlag, double x, double y) { // Ensure radii are valid if (rx == 0 || ry == 0) { lineTo(x, y); return; } checkMoveTo(); // check if prev command was moveto // Get the current (x, y) coordinates of the path final double x0 = cx; final double y0 = cy; if (x0 == x && y0 == y) { // If the endpoints (x, y) and (x0, y0) are identical, then this // is equivalent to omitting the elliptical arc segment entirely. return; } final Arc2D arc = computeArc(x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y); if (arc == null) { return; } final AffineTransform t = AffineTransform.getRotateInstance(Math.toRadians(angle), arc.getCenterX(), arc.getCenterY()); final Shape s = t.createTransformedShape(arc); path.append(s, true); makeRoom(7); types[numSeg++] = ExtendedPathIterator.SEG_ARCTO; values[numVals++] = rx; values[numVals++] = ry; values[numVals++] = angle; values[numVals++] = largeArcFlag ? 1 : 0; values[numVals++] = sweepFlag ? 1 : 0; cx = values[numVals++] = x; cy = values[numVals++] = y; } /** * This constructs an unrotated Arc2D from the SVG specification of an * Elliptical arc. To get the final arc you need to apply a rotation transform * such as: * * AffineTransform.getRotateInstance (angle, arc.getX()+arc.getWidth()/2, * arc.getY()+arc.getHeight()/2); */ public static Arc2D computeArc(double x0, double y0, double rx, double ry, double angle, boolean largeArcFlag, boolean sweepFlag, double x, double y) { // // Elliptical arc implementation based on the SVG specification notes // // Compute the half distance between the current and the final point final double dx2 = (x0 - x) / 2.0; final double dy2 = (y0 - y) / 2.0; // Convert angle from degrees to radians angle = Math.toRadians(angle % 360.0); final double cosAngle = Math.cos(angle); final double sinAngle = Math.sin(angle); // // Step 1 : Compute (x1, y1) // final double x1 = cosAngle * dx2 + sinAngle * dy2; final double y1 = -sinAngle * dx2 + cosAngle * dy2; // Ensure radii are large enough rx = Math.abs(rx); ry = Math.abs(ry); double prx = rx * rx; double pry = ry * ry; final double px1 = x1 * x1; final double py1 = y1 * y1; // check that radii are large enough final double radiiCheck = px1 / prx + py1 / pry; if (radiiCheck > 1) { rx = Math.sqrt(radiiCheck) * rx; ry = Math.sqrt(radiiCheck) * ry; prx = rx * rx; pry = ry * ry; } // // Step 2 : Compute (cx1, cy1) // double sign = (largeArcFlag == sweepFlag) ? -1 : 1; double sq = ((prx * pry) - (prx * py1) - (pry * px1)) / ((prx * py1) + (pry * px1)); sq = (sq < 0) ? 0 : sq; final double coef = sign * Math.sqrt(sq); final double cx1 = coef * ((rx * y1) / ry); final double cy1 = coef * -((ry * x1) / rx); // // Step 3 : Compute (cx, cy) from (cx1, cy1) // final double sx2 = (x0 + x) / 2.0; final double sy2 = (y0 + y) / 2.0; final double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1); final double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1); // // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) // final double ux = (x1 - cx1) / rx; final double uy = (y1 - cy1) / ry; final double vx = (-x1 - cx1) / rx; final double vy = (-y1 - cy1) / ry; // Compute the angle start double n = Math.sqrt((ux * ux) + (uy * uy)); double p = ux; // (1 * ux) + (0 * uy) sign = (uy < 0) ? -1.0 : 1.0; double angleStart = Math.toDegrees(sign * Math.acos(p / n)); // Compute the angle extent n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); p = ux * vx + uy * vy; sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0; double angleExtent = Math.toDegrees(sign * Math.acos(p / n)); if (!sweepFlag && angleExtent > 0) { angleExtent -= 360f; } else if (sweepFlag && angleExtent < 0) { angleExtent += 360f; } angleExtent %= 360f; angleStart %= 360f; // // We can now build the resulting Arc2D in double precision // final Arc2D.Double arc = new Arc2D.Double(); arc.x = cx - rx; arc.y = cy - ry; arc.width = rx * 2.0; arc.height = ry * 2.0; arc.start = -angleStart; arc.extent = -angleExtent; return arc; } /** * Delegates to the enclosed GeneralPath. */ public void moveTo(double x, double y) { // Don't add moveto to general path unless there is a reason. makeRoom(2); types[numSeg++] = PathIterator.SEG_MOVETO; cx = mx = values[numVals++] = x; cy = my = values[numVals++] = y; } /** * Delegates to the enclosed GeneralPath. */ public void lineTo(double x, double y) { checkMoveTo(); // check if prev command was moveto path.lineTo(x, y); makeRoom(2); types[numSeg++] = PathIterator.SEG_LINETO; cx = values[numVals++] = x; cy = values[numVals++] = y; } /** * Delegates to the enclosed GeneralPath. */ public void quadTo(double x1, double y1, double x2, double y2) { checkMoveTo(); // check if prev command was moveto path.quadTo(x1, y1, x2, y2); makeRoom(4); types[numSeg++] = PathIterator.SEG_QUADTO; values[numVals++] = x1; values[numVals++] = y1; cx = values[numVals++] = x2; cy = values[numVals++] = y2; } /** * Delegates to the enclosed GeneralPath. */ public void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { checkMoveTo(); // check if prev command was moveto path.curveTo(x1, y1, x2, y2, x3, y3); makeRoom(6); types[numSeg++] = PathIterator.SEG_CUBICTO; values[numVals++] = x1; values[numVals++] = y1; values[numVals++] = x2; values[numVals++] = y2; cx = values[numVals++] = x3; cy = values[numVals++] = y3; } /** * Delegates to the enclosed GeneralPath. */ public void closePath() { // Don't double close path. if (numSeg != 0 && types[numSeg - 1] == PathIterator.SEG_CLOSE) { return; } // Only close path if the previous command wasn't a moveto if (numSeg != 0 && types[numSeg - 1] != PathIterator.SEG_MOVETO) { path.closePath(); } makeRoom(0); types[numSeg++] = PathIterator.SEG_CLOSE; cx = mx; cy = my; } /** * Checks if previous command was a moveto command, skipping a close command (if * present). */ protected void checkMoveTo() { if (numSeg == 0) { return; } switch (types[numSeg - 1]) { case PathIterator.SEG_MOVETO: path.moveTo(values[numVals - 2], values[numVals - 1]); break; case PathIterator.SEG_CLOSE: if (numSeg == 1) { return; } if (types[numSeg - 2] == PathIterator.SEG_MOVETO) { path.moveTo(values[numVals - 2], values[numVals - 1]); } break; default: break; } } /** * Delegates to the enclosed GeneralPath. */ public void append(Shape s, boolean connect) { append(s.getPathIterator(new AffineTransform()), connect); } /** * Delegates to the enclosed GeneralPath. */ public void append(PathIterator pi, boolean connect) { final double[] vals = new double[6]; while (!pi.isDone()) { Arrays.fill(vals, 0); int type = pi.currentSegment(vals); pi.next(); if (connect && numVals != 0) { if (type == PathIterator.SEG_MOVETO) { final double x = vals[0]; final double y = vals[1]; if (x != cx || y != cy) { // Change MOVETO to LINETO. type = PathIterator.SEG_LINETO; } else { // Redundant segment (move to current loc) drop it... if (pi.isDone()) { break; // Nothing interesting } type = pi.currentSegment(vals); pi.next(); } } connect = false; } switch (type) { case PathIterator.SEG_CLOSE: closePath(); break; case PathIterator.SEG_MOVETO: moveTo(vals[0], vals[1]); break; case PathIterator.SEG_LINETO: lineTo(vals[0], vals[1]); break; case PathIterator.SEG_QUADTO: quadTo(vals[0], vals[1], vals[2], vals[3]); break; case PathIterator.SEG_CUBICTO: curveTo(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); break; } } } /** * Delegates to the enclosed GeneralPath. */ public void append(ExtendedPathIterator epi, boolean connect) { final double[] vals = new double[7]; while (!epi.isDone()) { Arrays.fill(vals, 0); int type = epi.currentSegment(vals); epi.next(); if (connect && numVals != 0) { if (type == PathIterator.SEG_MOVETO) { final double x = vals[0]; final double y = vals[1]; if ((x != cx) || (y != cy)) { // Change MOVETO to LINETO. type = PathIterator.SEG_LINETO; } else { // Redundant segment (move to current loc) drop it... if (epi.isDone()) { break; // Nothing interesting } type = epi.currentSegment(vals); epi.next(); } } connect = false; } switch (type) { case PathIterator.SEG_CLOSE: closePath(); break; case PathIterator.SEG_MOVETO: moveTo(vals[0], vals[1]); break; case PathIterator.SEG_LINETO: lineTo(vals[0], vals[1]); break; case PathIterator.SEG_QUADTO: quadTo(vals[0], vals[1], vals[2], vals[3]); break; case PathIterator.SEG_CUBICTO: curveTo(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); break; case ExtendedPathIterator.SEG_ARCTO: arcTo(vals[0], vals[1], vals[2], vals[3] != 0, vals[4] != 0, vals[5], vals[6]); break; } } } /** * Delegates to the enclosed GeneralPath. */ public int getWindingRule() { return path.getWindingRule(); } /** * Delegates to the enclosed GeneralPath. */ public void setWindingRule(int rule) { path.setWindingRule(rule); } /** * get the current position or null. */ public XPoint2D getCurrentPoint() { if (numVals == 0) { return null; } return new XPoint2D(cx, cy); } /** * Delegates to the enclosed GeneralPath. */ public void reset() { path.reset(); numSeg = 0; numVals = 0; values = null; types = null; } /** * Delegates to the enclosed GeneralPath. */ public void transform(AffineTransform at) { if (at.getType() != AffineTransform.TYPE_IDENTITY) { throw new IllegalArgumentException("ExtendedGeneralPaths can not be transformed"); } } /** * Delegates to the enclosed GeneralPath. */ public Shape createTransformedShape(AffineTransform at) { return path.createTransformedShape(at); } /** * Delegates to the enclosed GeneralPath. */ public Rectangle getBounds() { return path.getBounds(); } /** * Delegates to the enclosed GeneralPath. */ public Rectangle2D getBounds2D() { return path.getBounds2D(); } /** * Delegates to the enclosed GeneralPath. */ public boolean contains(double x, double y) { return path.contains(x, y); } /** * Delegates to the enclosed GeneralPath. */ public boolean contains(Point2D p) { return path.contains(p); } /** * Delegates to the enclosed GeneralPath. */ public boolean contains(double x, double y, double w, double h) { return path.contains(x, y, w, h); } /** * Delegates to the enclosed GeneralPath. */ public boolean contains(Rectangle2D r) { return path.contains(r); } /** * Delegates to the enclosed GeneralPath. */ public boolean intersects(double x, double y, double w, double h) { return path.intersects(x, y, w, h); } /** * Delegates to the enclosed GeneralPath. */ public boolean intersects(Rectangle2D r) { return path.intersects(r); } /** * Delegates to the enclosed GeneralPath. */ public PathIterator getPathIterator(AffineTransform at) { return path.getPathIterator(at); } /** * Delegates to the enclosed GeneralPath. */ public PathIterator getPathIterator(AffineTransform at, double flatness) { return path.getPathIterator(at, flatness); } /** * Delegates to the enclosed GeneralPath. */ public ExtendedPathIterator getExtendedPathIterator() { return new EPI(); } class EPI implements ExtendedPathIterator { private int segNum = 0; private int valsIdx = 0; public int currentSegment() { return types[segNum]; } public int currentSegment(double[] coords) { final int ret = types[segNum]; switch (ret) { case SEG_CLOSE: break; case SEG_MOVETO: case SEG_LINETO: coords[0] = values[valsIdx]; coords[1] = values[valsIdx + 1]; break; case SEG_QUADTO: coords[0] = values[valsIdx]; coords[1] = values[valsIdx + 1]; coords[2] = values[valsIdx + 2]; coords[3] = values[valsIdx + 3]; break; case SEG_CUBICTO: coords[0] = values[valsIdx]; coords[1] = values[valsIdx + 1]; coords[2] = values[valsIdx + 2]; coords[3] = values[valsIdx + 3]; coords[4] = values[valsIdx + 4]; coords[5] = values[valsIdx + 5]; break; case SEG_ARCTO: coords[0] = values[valsIdx]; coords[1] = values[valsIdx + 1]; coords[2] = values[valsIdx + 2]; coords[3] = values[valsIdx + 3]; coords[4] = values[valsIdx + 4]; coords[5] = values[valsIdx + 5]; coords[6] = values[valsIdx + 6]; break; } return ret; } public int getWindingRule() { return path.getWindingRule(); } public boolean isDone() { return segNum == numSeg; } public void next() { final int type = types[segNum++]; switch (type) { case SEG_CLOSE: break; case SEG_MOVETO: // fallthrough is intended case SEG_LINETO: valsIdx += 2; break; case SEG_QUADTO: valsIdx += 4; break; case SEG_CUBICTO: valsIdx += 6; break; case SEG_ARCTO: valsIdx += 7; break; } } } /** * Delegates to the enclosed GeneralPath. */ public Object clone() { try { final ExtendedGeneralPath result = (ExtendedGeneralPath) super.clone(); result.path = (GeneralPath) path.clone(); if (values != null) { result.values = new double[values.length]; System.arraycopy(values, 0, result.values, 0, values.length); } result.numVals = numVals; if (types != null) { result.types = new int[types.length]; System.arraycopy(types, 0, result.types, 0, types.length); } result.numSeg = numSeg; return result; } catch (CloneNotSupportedException ex) { Logme.error(ex); } return null; } /** * Make sure, that the requested number of slots in vales[] are available. Must * be called even for numValues = 0, because it is also used for initialization * of those arrays. * * @param numValues number of requested coordinates */ private void makeRoom(int numValues) { if (values == null) { values = new double[2 * numValues]; types = new int[2]; numVals = 0; numSeg = 0; return; } final int newSize = numVals + numValues; if (newSize > values.length) { int nlen = values.length * 2; if (nlen < newSize) { nlen = newSize; } final double[] nvals = new double[nlen]; System.arraycopy(values, 0, nvals, 0, numVals); values = nvals; } if (numSeg == types.length) { final int[] ntypes = new int[types.length * 2]; System.arraycopy(types, 0, ntypes, 0, types.length); types = ntypes; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy