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

com.hfg.svg.path.SvgPathEllipticalArcCmd Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
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);
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy