com.hfg.svg.path.SvgPathEllipticalArcCmd Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.svg.path;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.Arrays;
import java.util.List;
//------------------------------------------------------------------------------
/**
* Object representation of an SVG (Scalable Vector Graphics) path elliptical arc ('A' or 'a') command.
*
* @author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class SvgPathEllipticalArcCmd extends SvgPathCmd
{
//---------------------------------------------------------------------------
public SvgPathEllipticalArcCmd()
{
super('A');
}
//---------------------------------------------------------------------------
@Override
public SvgPathEllipticalArcCmd setIsRelative(boolean inValue)
{
return (SvgPathEllipticalArcCmd) super.setIsRelative(inValue);
}
//---------------------------------------------------------------------------
@Override
public SvgPathEllipticalArcCmd setRawNumbers(List inValue)
{
if (inValue.size()%7 != 0)
{
throw new SvgPathDataException("7 numbers must be given to a elliptical arc path command!");
}
setNumSteps(inValue.size()/7);
super.setRawNumbers(inValue);
return this;
}
//---------------------------------------------------------------------------
public SvgPathEllipticalArcCmd setRawNumbers(Float... inValues)
{
if (inValues.length%7 != 0)
{
throw new SvgPathDataException("7 numbers must be given to a elliptical arc path command!");
}
setNumSteps(inValues.length/7);
super.setRawNumbers(Arrays.asList(inValues));
return this;
}
//--------------------------------------------------------------------------
// From http://www.w3.org/TR/SVG/paths.html
//
// Draws an elliptical arc from the current point to (x, y). The size and orientation of the ellipse are defined by
// two radii (rx, ry) and an x-axis-rotation, which indicates how the ellipse as a whole is rotated relative to the
// current coordinate system. The center (cx, cy) of the ellipse is calculated automatically to satisfy the constraints
// imposed by the other parameters. large-arc-flag and sweep-flag contribute to the automatic calculations and help
// determine how the arc is drawn.
//
// See http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
public Point2D.Float draw(Path2D.Float inPath)
{
List rawNumbers = getRawNumbers();
int numIndex = 0;
Point2D.Float currentPoint = getStartingPoint();
while (numIndex < rawNumbers.size() - 1)
{
Float rx = rawNumbers.get(numIndex++);
Float ry = rawNumbers.get(numIndex++);
Float xAxisRotation = rawNumbers.get(numIndex++);
int largeArcFlag = rawNumbers.get(numIndex++).intValue();
int sweepFlag = rawNumbers.get(numIndex++).intValue();
Point2D.Float endPoint = new Point2D.Float(rawNumbers.get(numIndex++), rawNumbers.get(numIndex++));
if (isRelative())
{
endPoint.setLocation(endPoint.getX() + currentPoint.getX(), endPoint.getY() + currentPoint.getY());
}
// If the endpoints (x1, y1) and (x2, y2) are identical, then this is equivalent to omitting the elliptical arc segment entirely.
if (currentPoint.getX() == endPoint.getX()
&& currentPoint.getY() == endPoint.getY())
{
// Do nothing
}
else if (0 == rx
|| 0 == ry)
{
// If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") joining the endpoints.
inPath.lineTo(endPoint.getX(), endPoint.getY());
}
else
{
Arc2D.Float arc = generateArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, endPoint);
// The rotation of the arc has to be done as a transform
AffineTransform t = AffineTransform.getRotateInstance(Math.toRadians(xAxisRotation), arc.getCenterX(), arc.getCenterY());
Shape s = t.createTransformedShape(arc);
inPath.append(s, true);
}
currentPoint = endPoint;
}
// Return the last point.
return currentPoint;
}
//--------------------------------------------------------------------------
private Arc2D.Float generateArc(float inRx,
float inRy,
float inXAxisRotationInDeg,
int inLargeArcFlag,
int inSweepFlag,
Point2D.Float inEndPoint)
{
// If rx or ry have negative signs, these are dropped; the absolute value is used instead.
float rx = Math.abs(inRx);
float ry = Math.abs(inRy);
// The equations simplify after a translation which places the origin at the midpoint of the line joining
// (x1, y1) to (x2, y2), followed by a rotation to line up the coordinate axes with the axes of the ellipse.
double halfDistanceX = (getStartingPoint().getX() - inEndPoint.getX()) / 2.0f;
double halfDistanceY = (getStartingPoint().getY() - inEndPoint.getY()) / 2.0f;
// Un-rotate to line up the coordinate axes with the axes of the ellipse - convert the angle from degrees to radians
double xAxisRotationInRad = Math.toRadians(inXAxisRotationInDeg % 360.0);
double cosRotation = Math.cos(xAxisRotationInRad);
double sinRotation = Math.sin(xAxisRotationInRad);
double unrotatedStartX = (cosRotation * halfDistanceX + sinRotation * halfDistanceY);
double unrotatedStartY = (-sinRotation * halfDistanceX + cosRotation * halfDistanceY);
// Ensure radii are large enough
double wedge = Math.pow(unrotatedStartX, 2) / Math.pow(rx, 2) + Math.pow(unrotatedStartY, 2) / Math.pow(ry, 2);
// If the result of the above equation is less than or equal to 1, then no further change need be made to rx and ry.
// If the result of the above equation is greater than 1, then make the replacements:
if (wedge > 1)
{
rx = (float) Math.sqrt(wedge) * rx;
ry = (float) Math.sqrt(wedge) * ry;
}
// Calculate the un-rotated center point
double rx_2 = Math.pow(rx, 2);
double ry_2 = Math.pow(ry, 2);
double unrotatedStartX_2 = Math.pow(unrotatedStartX, 2);
double unrotatedStartY_2 = Math.pow(unrotatedStartY, 2);
float sign = (inLargeArcFlag != inSweepFlag) ? 1 : -1;
double product = ((rx_2 * ry_2) - (rx_2 * unrotatedStartY_2) - (ry_2 * unrotatedStartX_2)) / ((rx_2 * unrotatedStartY_2) + (ry_2 * unrotatedStartX_2));
if (product < 0)
{
product = 0;
}
double sqrt = sign * Math.sqrt(product);
double unrotatedCenterX = sqrt * ((rx * unrotatedStartY) / ry);
double unrotatedCenterY = sqrt * -((ry * unrotatedStartX) / rx);
// Calculate the rotated center point
double rotatedCenterX = (cosRotation * unrotatedCenterX - sinRotation * unrotatedCenterY) + ((getStartingPoint().getX() + inEndPoint.getX()) / 2.0);
double rotatedCenterY = (sinRotation * unrotatedCenterX + cosRotation * unrotatedCenterY) + ((getStartingPoint().getY() + inEndPoint.getY()) / 2.0);
// Compute θ1 (angle start) and Δθ (angle extent)
double ux = (unrotatedStartX - unrotatedCenterX) / rx;
double uy = (unrotatedStartY - unrotatedCenterY) / ry;
double vx = (-unrotatedStartX - unrotatedCenterX) / rx;
double vy = (-unrotatedStartY - unrotatedCenterY) / ry;
sign = (uy < 0) ? -1 : 1;
float angleStart = (float) Math.toDegrees(sign * Math.acos(ux / Math.sqrt((ux * ux) + (uy * uy))));
// Compute the angle extent
sign = (ux * vy - uy * vx < 0) ? -1 : 1;
float angleExtent = (float) Math.toDegrees(sign * Math.acos((ux * vx + uy * vy) / Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))));
if (0 == inSweepFlag
&& angleExtent > 0)
{
angleExtent -= 360f;
}
else if (inSweepFlag > 0
&& angleExtent < 0)
{
angleExtent += 360f;
}
angleExtent %= 360f;
angleStart %= 360f;
return new Arc2D.Float((float) rotatedCenterX - rx,
(float) rotatedCenterY - ry,
rx * 2,
ry * 2,
-angleStart,
-angleExtent,
Arc2D.OPEN);
}
}