/* * Copyright (c) 1997, 1998, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.awt.geom; import java.util.*; /** * The <code>FlatteningPathIterator</code> class returns a flattened view of * another {@link PathIterator} object. Other {@link java.awt.Shape Shape} * classes can use this class to provide flattening behavior for their paths * without having to perform the interpolation calculations themselves. * * @author Jim Graham */ public class FlatteningPathIterator implements PathIterator { static final int GROW_SIZE = 24; // Multiple of cubic & quad curve size PathIterator src; // The source iterator double squareflat; // Square of the flatness parameter // for testing against squared lengths int limit; // Maximum number of recursion levels double hold[] = new double[14]; // The cache of interpolated coords // Note that this must be long enough // to store a full cubic segment and // a relative cubic segment to avoid // aliasing when copying the coords // of a curve to the end of the array. // This is also serendipitously equal // to the size of a full quad segment // and 2 relative quad segments. double curx, cury; // The ending x,y of the last segment double movx, movy; // The x,y of the last move segment int holdType; // The type of the curve being held // for interpolation int holdEnd; // The index of the last curve segment // being held for interpolation int holdIndex; // The index of the curve segment // that was last interpolated. This // is the curve segment ready to be // returned in the next call to // currentSegment(). int levels[]; // The recursion level at which // each curve being held in storage // was generated. int levelIndex; // The index of the entry in the // levels array of the curve segment // at the holdIndex boolean done; // True when iteration is done /** * Constructs a new <code>FlatteningPathIterator</code> object that * flattens a path as it iterates over it. The iterator does not * subdivide any curve read from the source iterator to more than * 10 levels of subdivision which yields a maximum of 1024 line * segments per curve. * @param src the original unflattened path being iterated over * @param flatness the maximum allowable distance between the * control points and the flattened curve */ public FlatteningPathIterator(PathIterator src, double flatness) { this(src, flatness, 10); } /** * Constructs a new <code>FlatteningPathIterator</code> object * that flattens a path as it iterates over it. * The <code>limit</code> parameter allows you to control the * maximum number of recursive subdivisions that the iterator * can make before it assumes that the curve is flat enough * without measuring against the <code>flatness</code> parameter. * The flattened iteration therefore never generates more than * a maximum of <code>(2^limit)</code> line segments per curve. * @param src the original unflattened path being iterated over * @param flatness the maximum allowable distance between the * control points and the flattened curve * @param limit the maximum number of recursive subdivisions * allowed for any curved segment * @exception <code>IllegalArgumentException</code> if * <code>flatness</code> or <code>limit</code> * is less than zero */ public FlatteningPathIterator(PathIterator src, double flatness, int limit) { if (flatness < 0.0) { throw new IllegalArgumentException("flatness must be >= 0"); } if (limit < 0) { throw new IllegalArgumentException("limit must be >= 0"); } this.src = src; this.squareflat = flatness * flatness; this.limit = limit; this.levels = new int[limit + 1]; // prime the first path segment next(false); } /** * Returns the flatness of this iterator. * @return the flatness of this <code>FlatteningPathIterator</code>. */ public double getFlatness() { return Math.sqrt(squareflat); } /** * Returns the recursion limit of this iterator. * @return the recursion limit of this * <code>FlatteningPathIterator</code>. */ public int getRecursionLimit() { return limit; } /** * Returns the winding rule for determining the interior of the * path. * @return the winding rule of the original unflattened path being * iterated over. * @see PathIterator#WIND_EVEN_ODD * @see PathIterator#WIND_NON_ZERO */ public int getWindingRule() { return src.getWindingRule(); } /** * Tests if the iteration is complete. * @return <code>true</code> if all the segments have * been read; <code>false</code> otherwise. */ public boolean isDone() { return done; } /* * Ensures that the hold array can hold up to (want) more values. * It is currently holding (hold.length - holdIndex) values. */ void ensureHoldCapacity(int want) { if (holdIndex - want < 0) { int have = hold.length - holdIndex; int newsize = hold.length + GROW_SIZE; double newhold[] = new double[newsize]; System.arraycopy(hold, holdIndex, newhold, holdIndex + GROW_SIZE, have); hold = newhold; holdIndex += GROW_SIZE; holdEnd += GROW_SIZE; } } /** * Moves the iterator to the next segment of the path forwards * along the primary direction of traversal as long as there are * more points in that direction. */ public void next() { next(true); } private void next(boolean doNext) { int level; if (holdIndex >= holdEnd) { if (doNext) { src.next(); } if (src.isDone()) { done = true; return; } holdType = src.currentSegment(hold); levelIndex = 0; levels[0] = 0; } switch (holdType) { case SEG_MOVETO: case SEG_LINETO: curx = hold[0]; cury = hold[1]; if (holdType == SEG_MOVETO) { movx = curx; movy = cury; } holdIndex = 0; holdEnd = 0; break; case SEG_CLOSE: curx = movx; cury = movy; holdIndex = 0; holdEnd = 0; break; case SEG_QUADTO: if (holdIndex >= holdEnd) { // Move the coordinates to the end of the array. holdIndex = hold.length - 6; holdEnd = hold.length - 2; hold[holdIndex + 0] = curx; hold[holdIndex + 1] = cury; hold[holdIndex + 2] = hold[0]; hold[holdIndex + 3] = hold[1]; hold[holdIndex + 4] = curx = hold[2]; hold[holdIndex + 5] = cury = hold[3]; } level = levels[levelIndex]; while (level < limit) { if (QuadCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) { break; } ensureHoldCapacity(4); QuadCurve2D.subdivide(hold, holdIndex, hold, holdIndex - 4, hold, holdIndex); holdIndex -= 4; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; levels[levelIndex] = level; levelIndex++; levels[levelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+4 and holdIndex+5 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. holdIndex += 4; levelIndex--; break; case SEG_CUBICTO: if (holdIndex >= holdEnd) { // Move the coordinates to the end of the array. holdIndex = hold.length - 8; holdEnd = hold.length - 2; hold[holdIndex + 0] = curx; hold[holdIndex + 1] = cury; hold[holdIndex + 2] = hold[0]; hold[holdIndex + 3] = hold[1]; hold[holdIndex + 4] = hold[2]; hold[holdIndex + 5] = hold[3]; hold[holdIndex + 6] = curx = hold[4]; hold[holdIndex + 7] = cury = hold[5]; } level = levels[levelIndex]; while (level < limit) { if (CubicCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) { break; } ensureHoldCapacity(6); CubicCurve2D.subdivide(hold, holdIndex, hold, holdIndex - 6, hold, holdIndex); holdIndex -= 6; // Now that we have subdivided, we have constructed // two curves of one depth lower than the original // curve. One of those curves is in the place of // the former curve and one of them is in the next // set of held coordinate slots. We now set both // curves level values to the next higher level. level++; levels[levelIndex] = level; levelIndex++; levels[levelIndex] = level; } // This curve segment is flat enough, or it is too deep // in recursion levels to try to flatten any more. The // two coordinates at holdIndex+6 and holdIndex+7 now // contain the endpoint of the curve which can be the // endpoint of an approximating line segment. holdIndex += 6; levelIndex--; break; } } /** * Returns the coordinates and type of the current path segment in * the iteration. * The return value is the path segment type: * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE. * A float array of length 6 must be passed in and can be used to * store the coordinates of the point(s). * Each point is stored as a pair of float x,y coordinates. * SEG_MOVETO and SEG_LINETO types return one point, * and SEG_CLOSE does not return any points. * @param coords an array that holds the data returned from * this method * @return the path segment type of the current path segment. * @exception <code>NoSuchElementException</code> if there * are no more elements in the flattening path to be * returned. * @see PathIterator#SEG_MOVETO * @see PathIterator#SEG_LINETO * @see PathIterator#SEG_CLOSE */ public int currentSegment(float[] coords) { if (isDone()) { throw new NoSuchElementException("flattening iterator out of bounds"); } int type = holdType; if (type != SEG_CLOSE) { coords[0] = (float) hold[holdIndex + 0]; coords[1] = (float) hold[holdIndex + 1]; if (type != SEG_MOVETO) { type = SEG_LINETO; } } return type; } /** * Returns the coordinates and type of the current path segment in * the iteration. * The return value is the path segment type: * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE. * A double array of length 6 must be passed in and can be used to * store the coordinates of the point(s). * Each point is stored as a pair of double x,y coordinates. * SEG_MOVETO and SEG_LINETO types return one point, * and SEG_CLOSE does not return any points. * @param coords an array that holds the data returned from * this method * @return the path segment type of the current path segment. * @exception <code>NoSuchElementException</code> if there * are no more elements in the flattening path to be * returned. * @see PathIterator#SEG_MOVETO * @see PathIterator#SEG_LINETO * @see PathIterator#SEG_CLOSE */ public int currentSegment(double[] coords) { if (isDone()) { throw new NoSuchElementException("flattening iterator out of bounds"); } int type = holdType; if (type != SEG_CLOSE) { coords[0] = hold[holdIndex + 0]; coords[1] = hold[holdIndex + 1]; if (type != SEG_MOVETO) { type = SEG_LINETO; } } return type; } }