home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / awt / geom / GeneralPath.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  17.0 KB  |  530 lines

  1. /*
  2.  * @(#)GeneralPath.java    1.36 98/03/18
  3.  *
  4.  * Copyright 1996-1998 by Sun Microsystems, Inc.,
  5.  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
  6.  * All rights reserved.
  7.  *
  8.  * This software is the confidential and proprietary information
  9.  * of Sun Microsystems, Inc. ("Confidential Information").  You
  10.  * shall not disclose such Confidential Information and shall use
  11.  * it only in accordance with the terms of the license agreement
  12.  * you entered into with Sun.
  13.  */
  14.  
  15. package java.awt.geom;
  16.  
  17. import java.awt.Shape;
  18.  
  19. /**
  20.  * This class represents a geometric path constructed from straight
  21.  * lines, quadratic and cubic (Bezier) curves.  It can contain multiple
  22.  * subpaths.
  23.  * <p>
  24.  * The winding rule specifies how the interior of a path will be
  25.  * determined.  There are two types of winding rules:  EVEN_ODD and
  26.  * NON_ZERO.  An EVEN-ODD winding rule means that enclosed regions
  27.  * of the path alternate between interior and exterior areas as
  28.  * traversed from the outside of the path towards a point inside
  29.  * the region.  For a NON_ZERO winding rule, we start by conceptually
  30.  * drawing a ray from a given point to infinity in any direction
  31.  * and then by examining all of the places where the path intersects
  32.  * the ray.  We now traverse the path in one direction and keep
  33.  * track of the number of times that the path crosses the ray from
  34.  * left to right, and the number of times that the path crosses the
  35.  * ray from right to left.  If these numbers are equal, the point
  36.  * is outside of the path.  If they are not equal, the point is
  37.  * inside.  
  38.  * @version 10 Feb 1997
  39.  * @author Jim Graham
  40.  */
  41. public final class GeneralPath implements Shape, Cloneable {
  42.     /**
  43.      * An even-odd winding rule for determining the interior of
  44.      * a path.  
  45.      */
  46.     public static final byte EVEN_ODD = PathIterator.WIND_EVEN_ODD;
  47.  
  48.     /**
  49.      * A non-zero winding rule for determining the interior of a
  50.      * path.  
  51.      */
  52.     public static final byte NON_ZERO = PathIterator.WIND_NON_ZERO;
  53.     
  54.     // For code simplicity, copy these constants to our namespace.
  55.     private static final byte SEG_MOVETO    = PathIterator.SEG_MOVETO;
  56.     private static final byte SEG_LINETO    = PathIterator.SEG_LINETO;
  57.     private static final byte SEG_QUADTO    = PathIterator.SEG_QUADTO;
  58.     private static final byte SEG_CUBICTO    = PathIterator.SEG_CUBICTO;
  59.     private static final byte SEG_CLOSE        = PathIterator.SEG_CLOSE;
  60.  
  61.     byte[] pointTypes;
  62.     float[] pointCoords;
  63.     int numTypes;
  64.     int numCoords;
  65.     int windingRule;
  66.  
  67.     static final int INIT_SIZE = 10;
  68.     static final int EXPAND_SIZE = 10;
  69.  
  70.     /**
  71.      * Constructs a new GeneralPath object.
  72.      * If an operation is performed on this path which requires the
  73.      * interior of the path to be defined, then the default NON_ZERO
  74.      * winding rule is used.
  75.      * @see #NON_ZERO
  76.      */
  77.     public GeneralPath() {
  78.     this(NON_ZERO, INIT_SIZE, INIT_SIZE);
  79.     }
  80.  
  81.     /**
  82.      * Constructs a new GeneralPath object with the specified winding
  83.      * rule to control operations that require the interior of the
  84.      * path to be defined.
  85.      * @param rule The winding rule.
  86.      * @see #EVEN_ODD
  87.      * @see #NON_ZERO
  88.      */
  89.     public GeneralPath(int rule) {
  90.     this(rule, INIT_SIZE, INIT_SIZE);
  91.     }
  92.  
  93.     /**
  94.      * Constructs a new GeneralPath object with the specified winding
  95.      * rule and the specified initial capacity to store path coordinates.
  96.      * This number is an initial guess as to how many coordinates will
  97.      * be in the path, but the storage will be expanded as needed to
  98.      * store whatever path segments are added to this path.
  99.      * @param rule The winding rule.
  100.      * @param initialCapacity The estimate for the number of coordinates
  101.      * in the path.
  102.      * @see #EVEN_ODD
  103.      * @see #NON_ZERO
  104.      */
  105.     public GeneralPath(int rule, int initialCapacity) {
  106.     this(rule, initialCapacity, initialCapacity);
  107.     }
  108.  
  109.     /**
  110.      * Constructs a new GeneralPath object with the specified winding
  111.      * rule and the specified initial capacities to store point types
  112.      * and coordinates.
  113.      * These numbers are an initial guess as to how many path segments
  114.      * and how many coordinates will be in the path, but the storage will
  115.      * be expanded as needed to store whatever path segments are added
  116.      * to this path.
  117.      * @param rule The winding rule.
  118.      * @param initialTypes The estimate for the type of segment.
  119.      * @param initialCapacity The estimate for the number of coordinates
  120.      * @see #EVEN_ODD
  121.      * @see #NON_ZERO
  122.      */
  123.     GeneralPath(int rule, int initialTypes, int initialCoords) {
  124.     setWindingRule(rule);
  125.     pointTypes = new byte[initialTypes];
  126.     pointCoords = new float[initialCoords];
  127.     }
  128.  
  129.     /**
  130.      * Constructs a new GeneralPath object from an arbitrary Shape object.
  131.      * All of the initial geometry and the winding rule for this path are
  132.      * taken from the specified Shape object.
  133.      */
  134.     public GeneralPath(Shape s) {
  135.     this(NON_ZERO, INIT_SIZE, INIT_SIZE);
  136.     PathIterator pi = s.getPathIterator(null);
  137.     setWindingRule(pi.getWindingRule());
  138.     append(pi, false);
  139.     }
  140.  
  141.     private void needRoom(int newTypes, int newCoords, boolean needMove) {
  142.     if (needMove && numTypes == 0) {
  143.         throw new IllegalPathStateException("missing initial moveto "+
  144.                         "in path definition");
  145.     }
  146.     if (numCoords + newCoords > pointCoords.length) {
  147.         int num = numCoords + Math.min(newCoords, EXPAND_SIZE * 2);
  148.         float[] arr = new float[num];
  149.         System.arraycopy(pointCoords, 0, arr, 0, numCoords);
  150.         pointCoords = arr;
  151.     }
  152.     if (numTypes + newTypes > pointTypes.length) {
  153.         int num = numTypes + Math.min(newTypes, EXPAND_SIZE);
  154.         byte[] arr = new byte[num];
  155.         System.arraycopy(pointTypes, 0, arr, 0, numTypes);
  156.         pointTypes = arr;
  157.     }
  158.     }
  159.  
  160.     /**
  161.      * Adds a point to the path by moving to the specified
  162.      * coordinates.
  163.      */
  164.     public synchronized void moveTo(float x, float y) {
  165.     if (numTypes > 0 && pointTypes[numTypes - 1] == SEG_MOVETO) {
  166.         pointCoords[numCoords - 2] = x;
  167.         pointCoords[numCoords - 1] = y;
  168.     } else {
  169.         needRoom(1, 2, false);
  170.         pointTypes[numTypes++] = SEG_MOVETO;
  171.         pointCoords[numCoords++] = x;
  172.         pointCoords[numCoords++] = y;
  173.     }
  174.     }
  175.  
  176.     /**
  177.      * Adds a point to the path by drawing a straight line from the
  178.      * current coordinates to the new specified coordinates.
  179.      */
  180.     public synchronized void lineTo(float x, float y) {
  181.     needRoom(1, 2, true);
  182.     pointTypes[numTypes++] = SEG_LINETO;
  183.     pointCoords[numCoords++] = x;
  184.     pointCoords[numCoords++] = y;
  185.     }
  186.  
  187.     /**
  188.      * Adds 2 points to the path by drawing a Quadratic curve from
  189.      * the current coordinates through the second set of specified
  190.      * coordinates, using the first set of specified coordinates as
  191.      * a quadratic parametric control point.
  192.      * @param x1 the X coordinate of the first quadratic control point
  193.      * @param y1 the Y coordinate of the first quadratic control point
  194.      * @param x2 the X coordinate of the final endpoint
  195.      * @param y2 the Y coordinate of the final endpoint
  196.      */
  197.     public synchronized void quadTo(float x1, float y1, float x2, float y2) {
  198.     needRoom(1, 4, true);
  199.     pointTypes[numTypes++] = SEG_QUADTO;
  200.     pointCoords[numCoords++] = x1;
  201.     pointCoords[numCoords++] = y1;
  202.     pointCoords[numCoords++] = x2;
  203.     pointCoords[numCoords++] = y2;
  204.     }
  205.  
  206.     /**
  207.      * Adds 3 points to the path by drawing a Bezier curve from the
  208.      * current coordinates through the third set of specified
  209.      * coordinates, using the first and second sets of specified
  210.      * coordinates as Bezier control points.
  211.      * @param x1 the X coordinate of the first Bezier control point
  212.      * @param y1 the Y coordinate of the first Bezier control point
  213.      * @param x2 the X coordinate of the second Bezier control point
  214.      * @param y2 the Y coordinate of the second Bezier control point
  215.      * @param x3 the X coordinate of the final endpoint
  216.      * @param y3 the Y coordinate of the final endpoint
  217.      */
  218.     public synchronized void curveTo(float x1, float y1,
  219.                      float x2, float y2,
  220.                      float x3, float y3) {
  221.     needRoom(1, 6, true);
  222.     pointTypes[numTypes++] = SEG_CUBICTO;
  223.     pointCoords[numCoords++] = x1;
  224.     pointCoords[numCoords++] = y1;
  225.     pointCoords[numCoords++] = x2;
  226.     pointCoords[numCoords++] = y2;
  227.     pointCoords[numCoords++] = x3;
  228.     pointCoords[numCoords++] = y3;
  229.     }
  230.  
  231.     /**
  232.      * Closes the current subpath by drawing a straight line back to
  233.      * the coordinates of the last moveTo.  If the path is already
  234.      * closed, then this method has no effect.
  235.      */
  236.     public synchronized void closePath() {
  237.     if (numTypes == 0 || pointTypes[numTypes - 1] != SEG_CLOSE) {
  238.         needRoom(1, 0, true);
  239.         pointTypes[numTypes++] = SEG_CLOSE;
  240.     }
  241.     }
  242.  
  243.     /**
  244.      * Appends the geometry of the specified Shape object to the
  245.      * path, possibly connecting the geometry to the existing path
  246.      * segments with a line segment.
  247.      * If the connect parameter is true and the path is not empty,
  248.      * then any initial moveTo in the geometry of the appended Shape
  249.      * will be turned into a lineTo segment.
  250.      * If the destination coordinates of such a connecting lineTo
  251.      * segment match the ending coordinates of a currently open
  252.      * subpath, then the segment will be omitted as superfluous.
  253.      * The winding rule of the specified Shape will be ignored
  254.      * and the appended geometry will be governed by the winding
  255.      * rule specified for this path.
  256.      * @param s the shape whose geometry will be appended to this path
  257.      * @param connect a boolean to control whether or not to turn an
  258.      * initial moveTo segment into a lineTo segment to connect the
  259.      * new geometry to the existing path
  260.      */
  261.     public void append(Shape s, boolean connect) {
  262.     PathIterator pi = s.getPathIterator(null);
  263.         append(pi,connect);
  264.     }
  265.  
  266.     /**
  267.      * Appends the geometry of the specified PathIterator object to the
  268.      * path, possibly connecting the geometry to the existing path
  269.      * segments with a line segment.
  270.      * If the connect parameter is true and the path is not empty,
  271.      * then any initial moveTo in the geometry of the appended Shape
  272.      * will be turned into a lineTo segment.
  273.      * If the destination coordinates of such a connecting lineTo
  274.      * segment match the ending coordinates of a currently open
  275.      * subpath, then the segment will be omitted as superfluous.
  276.      * The winding rule of the specified Shape will be ignored
  277.      * and the appended geometry will be governed by the winding
  278.      * rule specified for this path.
  279.      * @param s the shape whose geometry will be appended to this path
  280.      * @param connect a boolean to control whether or not to turn an
  281.      * initial moveTo segment into a lineTo segment to connect the
  282.      * new geometry to the existing path
  283.      */
  284.     public void append(PathIterator pi, boolean connect) {
  285.     float coords[] = new float[6];
  286.     while (!pi.isDone()) {
  287.         switch (pi.currentSegment(coords)) {
  288.         case SEG_MOVETO:
  289.         if (!connect || numTypes < 1 || numCoords < 2) {
  290.             moveTo(coords[0], coords[1]);
  291.             break;
  292.         }
  293.         if (pointTypes[numTypes - 1] != SEG_CLOSE &&
  294.             pointCoords[numCoords - 2] == coords[0] &&
  295.             pointCoords[numCoords - 1] == coords[1])
  296.         {
  297.             // Collapse out initial moveto/lineto
  298.             break;
  299.         }
  300.         // NO BREAK;
  301.         case SEG_LINETO:
  302.         lineTo(coords[0], coords[1]);
  303.         break;
  304.         case SEG_QUADTO:
  305.         quadTo(coords[0], coords[1],
  306.                coords[2], coords[3]);
  307.         break;
  308.         case SEG_CUBICTO:
  309.         curveTo(coords[0], coords[1],
  310.             coords[2], coords[3],
  311.             coords[4], coords[5]);
  312.         break;
  313.         case SEG_CLOSE:
  314.         closePath();
  315.         break;
  316.         }
  317.         pi.next();
  318.         connect = false;
  319.     }
  320.     }
  321.  
  322.     /**
  323.      * Returns the fill style winding rule.
  324.      */
  325.     public synchronized int getWindingRule() {
  326.         return windingRule;
  327.     }
  328.  
  329.     /**
  330.      * Sets the winding rule for this path to the specified value.
  331.      */
  332.     public void setWindingRule(int rule) {
  333.     if (rule != EVEN_ODD && rule != NON_ZERO) {
  334.         throw new IllegalArgumentException("winding rule must be "+
  335.                            "EVEN_ODD or NON_ZERO");
  336.     }
  337.     windingRule = rule;
  338.     }
  339.  
  340.     /**
  341.      * Returns the most recent point added to the end of the path.
  342.      * @return a Point object containing the ending coordinate of the
  343.      * path or null if there are no points in the path.
  344.      */
  345.     public synchronized Point2D getCurrentPoint() {
  346.     if (numTypes < 1 || numCoords < 2) {
  347.         return null;
  348.     }
  349.     int index = numCoords;
  350.     if (pointTypes[numTypes - 1] == SEG_CLOSE) {
  351.     loop:
  352.         for (int i = numTypes - 2; i > 0; i--) {
  353.         switch (pointTypes[i]) {
  354.         case SEG_MOVETO:
  355.             break loop;
  356.         case SEG_LINETO:
  357.             index -= 2;
  358.             break;
  359.         case SEG_QUADTO:
  360.             index -= 4;
  361.             break;
  362.         case SEG_CUBICTO:
  363.             index -= 6;
  364.             break;
  365.         case SEG_CLOSE:
  366.             break;
  367.         }
  368.         }
  369.     }
  370.     return new Point2D.Float(pointCoords[index - 2],
  371.                  pointCoords[index - 1]);
  372.     }
  373.  
  374.     /**
  375.      * Resets the path to empty.  The append position is set back to the
  376.      * beginning of the path and all coordinates and point types are
  377.      * forgotten.
  378.      */
  379.     public synchronized void reset() {
  380.     numTypes = numCoords = 0;
  381.     }
  382.  
  383.     static GeneralPath doTransform(GeneralPath p, AffineTransform t) {
  384.  
  385.         if (t.isIdentity()) {
  386.             return p;
  387.         }
  388.         
  389.     GeneralPath np = new GeneralPath(p.windingRule,
  390.                      p.numTypes,
  391.                      p.numCoords);
  392.     System.arraycopy(p.pointTypes, 0, np.pointTypes, 0, p.numTypes);
  393.     np.numTypes = p.numTypes;
  394.     t.transform(p.pointCoords, 0,
  395.             np.pointCoords, 0,
  396.             p.numCoords / 2);
  397.     np.numCoords = p.numCoords;
  398.     return np;
  399.     }
  400.  
  401.     /**
  402.      * Transforms the geometry of this Path using the specified transform.
  403.      * The geometry is transformed in place permanently changing the
  404.      * boundary defined by this object.
  405.      * @param t  The matrix used to transform the area.
  406.      */
  407.     public void transform(AffineTransform t) {
  408.     t.transform(pointCoords, 0, pointCoords, 0, numCoords / 2);
  409.     }
  410.  
  411.     /**
  412.      * Returns a new transformed Path.
  413.      */
  414.     public synchronized Shape createTransformedShape(AffineTransform t) {
  415.     return doTransform(this, (AffineTransform) t);
  416.     }
  417.  
  418.     /**
  419.      * Return the bounding box of the shape.
  420.      */
  421.     public java.awt.Rectangle getBounds() {
  422.     return getBounds2D().getBounds();
  423.     }
  424.  
  425.     /**
  426.      * Returns the bounding box of the path.
  427.      */
  428.     public synchronized Rectangle2D getBounds2D() {
  429.     float x1, y1, x2, y2;
  430.     int i = numCoords;
  431.     if (i > 0) {
  432.         y1 = y2 = pointCoords[--i];
  433.         x1 = x2 = pointCoords[--i];
  434.         while (i > 0) {
  435.         float y = pointCoords[--i];
  436.         float x = pointCoords[--i];
  437.         if (x < x1) x1 = x;
  438.         if (y < y1) y1 = y;
  439.         if (x > x2) x2 = x;
  440.         if (y > y2) y2 = y;
  441.         }
  442.     } else {
  443.         x1 = y1 = x2 = y2 = 0.0f;
  444.     }
  445.     return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
  446.     }
  447.  
  448.     /**
  449.      * Test if a given coordinate is inside the boundary of the shape.
  450.      */
  451.     public boolean contains(double x, double y) {
  452.     return new Area(this).contains(x, y);
  453.     }
  454.  
  455.     /**
  456.      * Test if a given Point is inside the boundary of the shape.
  457.      */
  458.     public boolean contains(Point2D p) {
  459.     return contains(p.getX(), p.getY());
  460.     }
  461.  
  462.     /**
  463.      * Test if a given coordinate is inside the boundary of the shape.
  464.      */
  465.     public boolean contains(double x, double y, double w, double h) {
  466.     return new Area(this).contains(x, y, w, h);
  467.     }
  468.  
  469.     /**
  470.      * Test if a given Point is inside the boundary of the shape.
  471.      */
  472.     public boolean contains(Rectangle2D r) {
  473.     return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  474.     }
  475.  
  476.     /**
  477.      * Test if the interior of the Shape intersects the interior of a given
  478.      * set of rectangular coordinates.
  479.      */
  480.     public boolean intersects(double x, double y, double w, double h) {
  481.     return new Area(this).intersects(x, y, w, h);
  482.     }
  483.  
  484.     /**
  485.      * Test if the interior of the Shape intersects the interior of a given
  486.      * Rectangle.
  487.      */
  488.     public boolean intersects(Rectangle2D r) {
  489.     return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
  490.     }
  491.  
  492.     /**
  493.      * Return an iterator object that iterates along the boundary of
  494.      * the shape and provides access to the geometry of the outline
  495.      * of the shape.
  496.      */
  497.     public PathIterator getPathIterator(AffineTransform at) {
  498.     return new GeneralPathIterator(this, at);
  499.     }
  500.  
  501.     /**
  502.      * Return an iterator object that iterates along the boundary of
  503.      * the flattened shape and provides access to the geometry of the
  504.      * outline of the shape.
  505.      */
  506.     public PathIterator getPathIterator(AffineTransform at, double flatness) {
  507.     return new FlatteningPathIterator(getPathIterator(at), flatness);
  508.     }
  509.  
  510.     /**
  511.      * Creates a new object of the same class as this object.
  512.      *
  513.      * @return     a clone of this instance.
  514.      * @exception  OutOfMemoryError            if there is not enough memory.
  515.      * @see        java.lang.Cloneable
  516.      * @since      JDK1.2
  517.      */
  518.     public Object clone() {
  519.     try {
  520.         GeneralPath copy = (GeneralPath) super.clone();
  521.         copy.pointTypes = (byte[]) pointTypes.clone();
  522.         copy.pointCoords = (float[]) pointCoords.clone();
  523.         return copy;
  524.     } catch (CloneNotSupportedException e) {
  525.         // this shouldn't happen, since we are Cloneable
  526.         throw new InternalError();
  527.     }
  528.     }
  529. }
  530.