0N/A/*
2362N/A * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A/*
0N/A * (C) Copyright IBM Corp. 2005, All Rights Reserved.
0N/A */
0N/Apackage sun.font;
0N/A
0N/A//
0N/A// This is the 'simple' mapping implementation. It does things the most
0N/A// straightforward way even if that is a bit slow. It won't
0N/A// handle complex paths efficiently, and doesn't handle closed paths.
0N/A//
0N/A
0N/Aimport java.awt.Shape;
0N/Aimport java.awt.font.LayoutPath;
0N/Aimport java.awt.geom.AffineTransform;
0N/Aimport java.awt.geom.GeneralPath;
0N/Aimport java.awt.geom.NoninvertibleTransformException;
0N/Aimport java.awt.geom.PathIterator;
0N/Aimport java.awt.geom.Point2D;
0N/Aimport java.util.Formatter;
0N/Aimport java.util.ArrayList;
0N/A
0N/Aimport static java.awt.geom.PathIterator.*;
0N/Aimport static java.lang.Math.abs;
0N/Aimport static java.lang.Math.sqrt;
0N/A
0N/Apublic abstract class LayoutPathImpl extends LayoutPath {
0N/A
0N/A //
0N/A // Convenience APIs
0N/A //
0N/A
0N/A public Point2D pointToPath(double x, double y) {
0N/A Point2D.Double pt = new Point2D.Double(x, y);
0N/A pointToPath(pt, pt);
0N/A return pt;
0N/A }
0N/A
0N/A public Point2D pathToPoint(double a, double o, boolean preceding) {
0N/A Point2D.Double pt = new Point2D.Double(a, o);
0N/A pathToPoint(pt, preceding, pt);
0N/A return pt;
0N/A }
0N/A
0N/A public void pointToPath(double x, double y, Point2D pt) {
0N/A pt.setLocation(x, y);
0N/A pointToPath(pt, pt);
0N/A }
0N/A
0N/A public void pathToPoint(double a, double o, boolean preceding, Point2D pt) {
0N/A pt.setLocation(a, o);
0N/A pathToPoint(pt, preceding, pt);
0N/A }
0N/A
0N/A //
0N/A // extra utility APIs
0N/A //
0N/A
0N/A public abstract double start();
0N/A public abstract double end();
0N/A public abstract double length();
0N/A public abstract Shape mapShape(Shape s);
0N/A
0N/A //
0N/A // debugging flags
0N/A //
0N/A
0N/A private static final boolean LOGMAP = false;
0N/A private static final Formatter LOG = new Formatter(System.out);
0N/A
0N/A /**
0N/A * Indicate how positions past the start and limit of the
0N/A * path are treated. PINNED adjusts these positions so
0N/A * as to be within start and limit. EXTENDED ignores the
0N/A * start and limit and effectively extends the first and
0N/A * last segments of the path 'infinitely'. CLOSED wraps
0N/A * positions around the ends of the path.
0N/A */
0N/A public static enum EndType {
0N/A PINNED, EXTENDED, CLOSED;
0N/A public boolean isPinned() { return this == PINNED; }
0N/A public boolean isExtended() { return this == EXTENDED; }
0N/A public boolean isClosed() { return this == CLOSED; }
0N/A };
0N/A
0N/A //
0N/A // Top level construction.
0N/A //
0N/A
0N/A /**
0N/A * Return a path representing the path from the origin through the points in order.
0N/A */
0N/A public static LayoutPathImpl getPath(EndType etype, double ... coords) {
0N/A if ((coords.length & 0x1) != 0) {
0N/A throw new IllegalArgumentException("odd number of points not allowed");
0N/A }
0N/A
0N/A return SegmentPath.get(etype, coords);
0N/A }
0N/A
0N/A /**
0N/A * Use to build a SegmentPath. This takes the data and preanalyzes it for
0N/A * information that the SegmentPath needs, then constructs a SegmentPath
0N/A * from that. Mainly, this lets SegmentPath cache the lengths along
0N/A * the path to each line segment, and so avoid calculating them over and over.
0N/A */
0N/A public static final class SegmentPathBuilder {
0N/A private double[] data;
0N/A private int w;
0N/A private double px;
0N/A private double py;
0N/A private double a;
0N/A private boolean pconnect;
0N/A
0N/A /**
0N/A * Construct a SegmentPathBuilder.
0N/A */
0N/A public SegmentPathBuilder() {
0N/A }
0N/A
0N/A /**
0N/A * Reset the builder for a new path. Datalen is a hint of how many
0N/A * points will be in the path, and the working buffer will be sized
0N/A * to accomodate at least this number of points. If datalen is zero,
0N/A * the working buffer is freed (it will be allocated on first use).
0N/A */
0N/A public void reset(int datalen) {
0N/A if (data == null || datalen > data.length) {
0N/A data = new double[datalen];
0N/A } else if (datalen == 0) {
0N/A data = null;
0N/A }
0N/A w = 0;
0N/A px = py = 0;
0N/A pconnect = false;
0N/A }
0N/A
0N/A /**
0N/A * Automatically build from a list of points represented by pairs of
0N/A * doubles. Initial advance is zero.
0N/A */
0N/A public SegmentPath build(EndType etype, double... pts) {
0N/A assert(pts.length % 2 == 0);
0N/A
0N/A reset(pts.length / 2 * 3);
0N/A
0N/A for (int i = 0; i < pts.length; i += 2) {
0N/A nextPoint(pts[i], pts[i+1], i != 0);
0N/A }
0N/A
0N/A return complete(etype);
0N/A }
0N/A
0N/A /**
0N/A * Move to a new point. If there is no data, this will become the
0N/A * first point. If there is data, and the previous call was a lineTo, this
0N/A * point is checked against the previous point, and if different, this
0N/A * starts a new segment at the same advance as the end of the last
0N/A * segment. If there is data, and the previous call was a moveTo, this
0N/A * replaces the point used for that previous call.
0N/A *
0N/A * Calling this is optional, lineTo will suffice and the initial point
0N/A * will be set to 0, 0.
0N/A */
0N/A public void moveTo(double x, double y) {
0N/A nextPoint(x, y, false);
0N/A }
0N/A
0N/A /**
0N/A * Connect to a new point. If there is no data, the previous point
0N/A * is presumed to be 0, 0. This point is checked against
0N/A * the previous point, and if different, this point is added to
0N/A * the path and the advance extended. If this point is the same as the
0N/A * previous point, the path remains unchanged.
0N/A */
0N/A public void lineTo(double x, double y) {
0N/A nextPoint(x, y, true);
0N/A }
0N/A
0N/A /**
0N/A * Add a new point, and increment advance if connect is true.
0N/A *
0N/A * This automatically rejects duplicate points and multiple disconnected points.
0N/A */
0N/A private void nextPoint(double x, double y, boolean connect) {
0N/A
0N/A // if zero length move or line, ignore
0N/A if (x == px && y == py) {
0N/A return;
0N/A }
0N/A
0N/A if (w == 0) { // this is the first point, make sure we have space
0N/A if (data == null) {
0N/A data = new double[6];
0N/A }
0N/A if (connect) {
0N/A w = 3; // default first point to 0, 0
0N/A }
0N/A }
0N/A
0N/A // if multiple disconnected move, just update position, leave advance alone
0N/A if (w != 0 && !connect && !pconnect) {
0N/A data[w-3] = px = x;
0N/A data[w-2] = py = y;
0N/A return;
0N/A }
0N/A
0N/A // grow data to deal with new point
0N/A if (w == data.length) {
0N/A double[] t = new double[w * 2];
0N/A System.arraycopy(data, 0, t, 0, w);
0N/A data = t;
0N/A }
0N/A
0N/A if (connect) {
0N/A double dx = x - px;
0N/A double dy = y - py;
0N/A a += sqrt(dx * dx + dy * dy);
0N/A }
0N/A
0N/A // update data
0N/A data[w++] = x;
0N/A data[w++] = y;
0N/A data[w++] = a;
0N/A
0N/A // update state
0N/A px = x;
0N/A py = y;
0N/A pconnect = connect;
0N/A }
0N/A
0N/A public SegmentPath complete() {
0N/A return complete(EndType.EXTENDED);
0N/A }
0N/A
0N/A /**
0N/A * Complete building a SegmentPath. Once this is called, the builder is restored
0N/A * to its initial state and information about the previous path is released. The
0N/A * end type indicates whether to treat the path as closed, extended, or pinned.
0N/A */
0N/A public SegmentPath complete(EndType etype) {
0N/A SegmentPath result;
0N/A
0N/A if (data == null || w < 6) {
0N/A return null;
0N/A }
0N/A
0N/A if (w == data.length) {
0N/A result = new SegmentPath(data, etype);
0N/A reset(0); // releases pointer to data
0N/A } else {
0N/A double[] dataToAdopt = new double[w];
0N/A System.arraycopy(data, 0, dataToAdopt, 0, w);
0N/A result = new SegmentPath(dataToAdopt, etype);
0N/A reset(2); // reuses data, since we held on to it
0N/A }
0N/A
0N/A return result;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Represents a path built from segments. Each segment is
0N/A * represented by a triple: x, y, and cumulative advance.
0N/A * These represent the end point of the segment. The start
0N/A * point of the first segment is represented by the triple
0N/A * at position 0.
0N/A *
0N/A * The path might have breaks in it, e.g. it is not connected.
0N/A * These will be represented by pairs of triplets that share the
0N/A * same advance.
0N/A *
0N/A * The path might be extended, pinned, or closed. If extended,
0N/A * the initial and final segments are considered to extend
0N/A * 'indefinitely' past the bounds of the advance. If pinned,
0N/A * they end at the bounds of the advance. If closed,
0N/A * advances before the start or after the end 'wrap around' the
0N/A * path.
0N/A *
0N/A * The start of the path is the initial triple. This provides
0N/A * the nominal advance at the given x, y position (typically
0N/A * zero). The end of the path is the final triple. This provides
0N/A * the advance at the end, the total length of the path is
0N/A * thus the ending advance minus the starting advance.
0N/A *
0N/A * Note: We might want to cache more auxiliary data than the
0N/A * advance, but this seems adequate for now.
0N/A */
0N/A public static final class SegmentPath extends LayoutPathImpl {
0N/A private double[] data; // triplets x, y, a
0N/A EndType etype;
0N/A
0N/A public static SegmentPath get(EndType etype, double... pts) {
0N/A return new SegmentPathBuilder().build(etype, pts);
0N/A }
0N/A
0N/A /**
0N/A * Internal, use SegmentPathBuilder or one of the static
0N/A * helper functions to construct a SegmentPath.
0N/A */
0N/A SegmentPath(double[] data, EndType etype) {
0N/A this.data = data;
0N/A this.etype = etype;
0N/A }
0N/A
0N/A //
0N/A // LayoutPath API
0N/A //
0N/A
0N/A public void pathToPoint(Point2D location, boolean preceding, Point2D point) {
0N/A locateAndGetIndex(location, preceding, point);
0N/A }
0N/A
0N/A // the path consists of line segments, which i'll call
0N/A // 'path vectors'. call each run of path vectors a 'path segment'.
0N/A // no path vector in a path segment is zero length (in the
0N/A // data, such vectors start a new path segment).
0N/A //
0N/A // for each path segment...
0N/A //
0N/A // for each path vector...
0N/A //
0N/A // we look at the dot product of the path vector and the vector from the
0N/A // origin of the path vector to the test point. if <0 (case
0N/A // A), the projection of the test point is before the start of
0N/A // the path vector. if > the square of the length of the path vector
0N/A // (case B), the projection is past the end point of the
0N/A // path vector. otherwise (case C), it lies on the path vector.
0N/A // determine the closeset point on the path vector. if case A, it
0N/A // is the start of the path vector. if case B and this is the last
0N/A // path vector in the path segment, it is the end of the path vector. If
0N/A // case C, it is the projection onto the path vector. Otherwise
0N/A // there is no closest point.
0N/A //
0N/A // if we have a closest point, compare the distance from it to
0N/A // the test point against our current closest distance.
0N/A // (culling should be fast, currently i am using distance
0N/A // squared, but there's probably better ways). if we're
0N/A // closer, save the new point as the current closest point,
0N/A // and record the path vector index so we can determine the final
0N/A // info if this turns out to be the closest point in the end.
0N/A //
0N/A // after we have processed all the segments we will have
0N/A // tested each path vector and each endpoint. if our point is not on
0N/A // an endpoint, we're done; we can compute the position and
0N/A // offset again, or if we saved it off we can just use it. if
0N/A // we're on an endpoint we need to see which path vector we should
0N/A // associate with. if we're at the start or end of a path segment,
0N/A // we're done-- the first or last vector of the segment is the
0N/A // one we associate with. we project against that vector to
0N/A // get the offset, and pin to that vector to get the length.
0N/A //
0N/A // otherwise, we compute the information as follows. if the
0N/A // dot product (see above) with the following vector is zero,
0N/A // we associate with that vector. otherwise, if the dot
0N/A // product with the previous vector is zero, we associate with
0N/A // that vector. otherwise we're beyond the end of the
0N/A // previous vector and before the start of the current vector.
0N/A // we project against both vectors and get the distance from
0N/A // the test point to the projection (this will be the offset).
0N/A // if they are the same, we take the following vector.
0N/A // otherwise use the vector from which the test point is the
0N/A // _farthest_ (this is because the point lies most clearly in
0N/A // the half of the plane defined by extending that vector).
0N/A //
0N/A // the returned position is the path length to the (possibly
0N/A // pinned) point, the offset is the projection onto the line
0N/A // along the vector, and we have a boolean flag which if false
0N/A // indicates that we associate with the previous vector at a
0N/A // junction (which is necessary when projecting such a
0N/A // location back to a point).
0N/A
0N/A public boolean pointToPath(Point2D pt, Point2D result) {
0N/A double x = pt.getX(); // test point
0N/A double y = pt.getY();
0N/A
0N/A double bx = data[0]; // previous point
0N/A double by = data[1];
0N/A double bl = data[2];
0N/A
0N/A // start with defaults
0N/A double cd2 = Double.MAX_VALUE; // current best distance from path, squared
0N/A double cx = 0; // current best x
0N/A double cy = 0; // current best y
0N/A double cl = 0; // current best position along path
0N/A int ci = 0; // current best index into data
0N/A
0N/A for (int i = 3; i < data.length; i += 3) {
0N/A double nx = data[i]; // current end point
0N/A double ny = data[i+1];
0N/A double nl = data[i+2];
0N/A
0N/A double dx = nx - bx; // vector from previous to current
0N/A double dy = ny - by;
0N/A double dl = nl - bl;
0N/A
0N/A double px = x - bx; // vector from previous to test point
0N/A double py = y - by;
0N/A
0N/A // determine sign of dot product of vectors from bx, by
0N/A // if < 0, we're before the start of this vector
0N/A
0N/A double dot = dx * px + dy * py; // dot product
0N/A double vcx, vcy, vcl; // hold closest point on vector as x, y, l
0N/A int vi; // hold index of line, is data.length if last point on path
0N/A do { // use break below, lets us avoid initializing vcx, vcy...
0N/A if (dl == 0 || // moveto, or
0N/A (dot < 0 && // before path vector and
0N/A (!etype.isExtended() ||
0N/A i != 3))) { // closest point is start of vector
0N/A vcx = bx;
0N/A vcy = by;
0N/A vcl = bl;
0N/A vi = i;
0N/A } else {
0N/A double l2 = dl * dl; // aka dx * dx + dy * dy, square of length
0N/A if (dot <= l2 || // closest point is not past end of vector, or
0N/A (etype.isExtended() && // we're extended and at the last segment
0N/A i == data.length - 3)) {
0N/A double p = dot / l2; // get parametric along segment
0N/A vcx = bx + p * dx; // compute closest point
0N/A vcy = by + p * dy;
0N/A vcl = bl + p * dl;
0N/A vi = i;
0N/A } else {
0N/A if (i == data.length - 3) {
0N/A vcx = nx; // special case, always test last point
0N/A vcy = ny;
0N/A vcl = nl;
0N/A vi = data.length;
0N/A } else {
0N/A break; // typical case, skip point, we'll pick it up next iteration
0N/A }
0N/A }
0N/A }
0N/A
0N/A double tdx = x - vcx; // compute distance from (usually pinned) projection to test point
0N/A double tdy = y - vcy;
0N/A double td2 = tdx * tdx + tdy * tdy;
0N/A if (td2 <= cd2) { // new closest point, record info on it
0N/A cd2 = td2;
0N/A cx = vcx;
0N/A cy = vcy;
0N/A cl = vcl;
0N/A ci = vi;
0N/A }
0N/A } while (false);
0N/A
0N/A bx = nx;
0N/A by = ny;
0N/A bl = nl;
0N/A }
0N/A
0N/A // we have our closest point, get the info
0N/A bx = data[ci-3];
0N/A by = data[ci-2];
0N/A if (cx != bx || cy != by) { // not on endpoint, no need to resolve
0N/A double nx = data[ci];
0N/A double ny = data[ci+1];
0N/A double co = sqrt(cd2); // have a true perpendicular, so can use distance
0N/A if ((x-cx)*(ny-by) > (y-cy)*(nx-bx)) {
0N/A co = -co; // determine sign of offset
0N/A }
0N/A result.setLocation(cl, co);
0N/A return false;
0N/A } else { // on endpoint, we need to resolve which segment
0N/A boolean havePrev = ci != 3 && data[ci-1] != data[ci-4];
0N/A boolean haveFoll = ci != data.length && data[ci-1] != data[ci+2];
0N/A boolean doExtend = etype.isExtended() && (ci == 3 || ci == data.length);
0N/A if (havePrev && haveFoll) {
0N/A Point2D.Double pp = new Point2D.Double(x, y);
0N/A calcoffset(ci - 3, doExtend, pp);
0N/A Point2D.Double fp = new Point2D.Double(x, y);
0N/A calcoffset(ci, doExtend, fp);
0N/A if (abs(pp.y) > abs(fp.y)) {
0N/A result.setLocation(pp);
0N/A return true; // associate with previous
0N/A } else {
0N/A result.setLocation(fp);
0N/A return false; // associate with following
0N/A }
0N/A } else if (havePrev) {
0N/A result.setLocation(x, y);
0N/A calcoffset(ci - 3, doExtend, result);
0N/A return true;
0N/A } else {
0N/A result.setLocation(x, y);
0N/A calcoffset(ci, doExtend, result);
0N/A return false;
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Return the location of the point passed in result as mapped to the
0N/A * line indicated by index. If doExtend is true, extend the
0N/A * x value without pinning to the ends of the line.
0N/A * this assumes that index is valid and references a line that has
0N/A * non-zero length.
0N/A */
0N/A private void calcoffset(int index, boolean doExtend, Point2D result) {
0N/A double bx = data[index-3];
0N/A double by = data[index-2];
0N/A double px = result.getX() - bx;
0N/A double py = result.getY() - by;
0N/A double dx = data[index] - bx;
0N/A double dy = data[index+1] - by;
0N/A double l = data[index+2] - data[index - 1];
0N/A
0N/A // rx = A dot B / |B|
0N/A // ry = A dot invB / |B|
0N/A double rx = (px * dx + py * dy) / l;
0N/A double ry = (px * -dy + py * dx) / l;
0N/A if (!doExtend) {
0N/A if (rx < 0) rx = 0;
0N/A else if (rx > l) rx = l;
0N/A }
0N/A rx += data[index-1];
0N/A result.setLocation(rx, ry);
0N/A }
0N/A
0N/A //
0N/A // LayoutPathImpl API
0N/A //
0N/A
0N/A public Shape mapShape(Shape s) {
0N/A return new Mapper().mapShape(s);
0N/A }
0N/A
0N/A public double start() {
0N/A return data[2];
0N/A }
0N/A
0N/A public double end() {
0N/A return data[data.length - 1];
0N/A }
0N/A
0N/A public double length() {
0N/A return data[data.length-1] - data[2];
0N/A }
0N/A
0N/A //
0N/A // Utilities
0N/A //
0N/A
0N/A /**
0N/A * Get the 'modulus' of an advance on a closed path.
0N/A */
0N/A private double getClosedAdvance(double a, boolean preceding) {
0N/A if (etype.isClosed()) {
0N/A a -= data[2];
0N/A int count = (int)(a/length());
0N/A a -= count * length();
0N/A if (a < 0 || (a == 0 && preceding)) {
0N/A a += length();
0N/A
0N/A }
0N/A a += data[2];
0N/A }
0N/A return a;
0N/A }
0N/A
0N/A /**
0N/A * Return the index of the segment associated with advance. This
0N/A * points to the start of the triple and is a multiple of 3 between
0N/A * 3 and data.length-3 inclusive. It never points to a 'moveto' triple.
0N/A *
0N/A * If the path is closed, 'a' is mapped to
0N/A * a value between the start and end of the path, inclusive.
0N/A * If preceding is true, and 'a' lies on a segment boundary,
0N/A * return the index of the preceding segment, else return the index
0N/A * of the current segment (if it is not a moveto segment) otherwise
0N/A * the following segment (which is never a moveto segment).
0N/A *
0N/A * Note: if the path is not closed, the advance might not actually
0N/A * lie on the returned segment-- it might be before the first, or
0N/A * after the last. The first or last segment (as appropriate)
0N/A * will be returned in this case.
0N/A */
0N/A private int getSegmentIndexForAdvance(double a, boolean preceding) {
0N/A // must have local advance
0N/A a = getClosedAdvance(a, preceding);
0N/A
0N/A // note we must avoid 'moveto' segments. the first segment is
0N/A // always a moveto segment, so we always skip it.
0N/A int i, lim;
0N/A for (i = 5, lim = data.length-1; i < lim; i += 3) {
0N/A double v = data[i];
0N/A if (a < v || (a == v && preceding)) {
0N/A break;
0N/A }
0N/A }
0N/A return i-2; // adjust to start of segment
0N/A }
0N/A
0N/A /**
0N/A * Map a location based on the provided segment, returning in pt.
0N/A * Seg must be a valid 'lineto' segment. Note: if the path is
0N/A * closed, x must be within the start and end of the path.
0N/A */
0N/A private void map(int seg, double a, double o, Point2D pt) {
0N/A double dx = data[seg] - data[seg-3];
0N/A double dy = data[seg+1] - data[seg-2];
0N/A double dl = data[seg+2] - data[seg-1];
0N/A
0N/A double ux = dx/dl; // could cache these, but is it worth it?
0N/A double uy = dy/dl;
0N/A
0N/A a -= data[seg-1];
0N/A
0N/A pt.setLocation(data[seg-3] + a * ux - o * uy,
0N/A data[seg-2] + a * uy + o * ux);
0N/A }
0N/A
0N/A /**
0N/A * Map the point, and return the segment index.
0N/A */
0N/A private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) {
0N/A double a = loc.getX();
0N/A double o = loc.getY();
0N/A int seg = getSegmentIndexForAdvance(a, preceding);
0N/A map(seg, a, o, result);
0N/A
0N/A return seg;
0N/A }
0N/A
0N/A //
0N/A // Mapping classes.
0N/A // Map the path onto each path segment.
0N/A // Record points where the advance 'enters' and 'exits' the path segment, and connect successive
0N/A // points when appropriate.
0N/A //
0N/A
0N/A /**
0N/A * This represents a line segment from the iterator. Each target segment will
0N/A * interpret it, and since this process needs slope along the line
0N/A * segment, this lets us compute it once and pass it around easily.
0N/A */
0N/A class LineInfo {
0N/A double sx, sy; // start
0N/A double lx, ly; // limit
0N/A double m; // slope dy/dx
0N/A
0N/A /**
0N/A * Set the lineinfo to this line
0N/A */
0N/A void set(double sx, double sy, double lx, double ly) {
0N/A this.sx = sx;
0N/A this.sy = sy;
0N/A this.lx = lx;
0N/A this.ly = ly;
0N/A double dx = lx - sx;
0N/A if (dx == 0) {
0N/A m = 0; // we'll check for this elsewhere
0N/A } else {
0N/A double dy = ly - sy;
0N/A m = dy / dx;
0N/A }
0N/A }
0N/A
0N/A void set(LineInfo rhs) {
0N/A this.sx = rhs.sx;
0N/A this.sy = rhs.sy;
0N/A this.lx = rhs.lx;
0N/A this.ly = rhs.ly;
0N/A this.m = rhs.m;
0N/A }
0N/A
0N/A /**
0N/A * Return true if we intersect the infinitely tall rectangle with
0N/A * lo <= x < hi. If we do, also return the pinned portion of ourselves in
0N/A * result.
0N/A */
0N/A boolean pin(double lo, double hi, LineInfo result) {
0N/A result.set(this);
0N/A if (lx >= sx) {
0N/A if (sx < hi && lx >= lo) {
0N/A if (sx < lo) {
0N/A if (m != 0) result.sy = sy + m * (lo - sx);
0N/A result.sx = lo;
0N/A }
0N/A if (lx > hi) {
0N/A if (m != 0) result.ly = ly + m * (hi - lx);
0N/A result.lx = hi;
0N/A }
0N/A return true;
0N/A }
0N/A } else {
0N/A if (lx < hi && sx >= lo) {
0N/A if (lx < lo) {
0N/A if (m != 0) result.ly = ly + m * (lo - lx);
0N/A result.lx = lo;
0N/A }
0N/A if (sx > hi) {
0N/A if (m != 0) result.sy = sy + m * (hi - sx);
0N/A result.sx = hi;
0N/A }
0N/A return true;
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A /**
0N/A * Return true if we intersect the segment at ix. This takes
0N/A * the path end type into account and computes the relevant
0N/A * parameters to pass to pin(double, double, LineInfo).
0N/A */
0N/A boolean pin(int ix, LineInfo result) {
0N/A double lo = data[ix-1];
0N/A double hi = data[ix+2];
0N/A switch (SegmentPath.this.etype) {
0N/A case PINNED:
0N/A break;
0N/A case EXTENDED:
0N/A if (ix == 3) lo = Double.NEGATIVE_INFINITY;
0N/A if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY;
0N/A break;
0N/A case CLOSED:
0N/A // not implemented
0N/A break;
0N/A }
0N/A
0N/A return pin(lo, hi, result);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Each segment will construct its own general path, mapping the provided lines
0N/A * into its own simple space.
0N/A */
0N/A class Segment {
0N/A final int ix; // index into data array for this segment
0N/A final double ux, uy; // unit vector
0N/A
0N/A final LineInfo temp; // working line info
0N/A
0N/A boolean broken; // true if a moveto has occurred since we last added to our path
0N/A double cx, cy; // last point in gp
0N/A GeneralPath gp; // path built for this segment
0N/A
0N/A Segment(int ix) {
0N/A this.ix = ix;
0N/A double len = data[ix+2] - data[ix-1];
0N/A this.ux = (data[ix] - data[ix-3]) / len;
0N/A this.uy = (data[ix+1] - data[ix-2]) / len;
0N/A this.temp = new LineInfo();
0N/A }
0N/A
0N/A void init() {
0N/A if (LOGMAP) LOG.format("s(%d) init\n", ix);
0N/A broken = true;
0N/A cx = cy = Double.MIN_VALUE;
0N/A this.gp = new GeneralPath();
0N/A }
0N/A
0N/A void move() {
0N/A if (LOGMAP) LOG.format("s(%d) move\n", ix);
0N/A broken = true;
0N/A }
0N/A
0N/A void close() {
0N/A if (!broken) {
0N/A if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix);
0N/A gp.closePath();
0N/A }
0N/A }
0N/A
0N/A void line(LineInfo li) {
0N/A if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly);
0N/A
0N/A if (li.pin(ix, temp)) {
0N/A if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly);
0N/A
0N/A temp.sx -= data[ix-1];
0N/A double sx = data[ix-3] + temp.sx * ux - temp.sy * uy;
0N/A double sy = data[ix-2] + temp.sx * uy + temp.sy * ux;
0N/A temp.lx -= data[ix-1];
0N/A double lx = data[ix-3] + temp.lx * ux - temp.ly * uy;
0N/A double ly = data[ix-2] + temp.lx * uy + temp.ly * ux;
0N/A
0N/A if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly);
0N/A
0N/A if (sx != cx || sy != cy) {
0N/A if (broken) {
0N/A if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy);
0N/A gp.moveTo((float)sx, (float)sy);
0N/A } else {
0N/A if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy);
0N/A gp.lineTo((float)sx, (float)sy);
0N/A }
0N/A }
0N/A if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly);
0N/A gp.lineTo((float)lx, (float)ly);
0N/A
0N/A broken = false;
0N/A cx = lx;
0N/A cy = ly;
0N/A }
0N/A }
0N/A }
0N/A
0N/A class Mapper {
0N/A final LineInfo li; // working line info
0N/A final ArrayList<Segment> segments; // cache additional data on segments, working objects
0N/A final Point2D.Double mpt; // last moveto source point
0N/A final Point2D.Double cpt; // current source point
0N/A boolean haveMT; // true when last op was a moveto
0N/A
0N/A Mapper() {
0N/A li = new LineInfo();
0N/A segments = new ArrayList<Segment>();
0N/A for (int i = 3; i < data.length; i += 3) {
0N/A if (data[i+2] != data[i-1]) { // a new segment
0N/A segments.add(new Segment(i));
0N/A }
0N/A }
0N/A
0N/A mpt = new Point2D.Double();
0N/A cpt = new Point2D.Double();
0N/A }
0N/A
0N/A void init() {
0N/A if (LOGMAP) LOG.format("init\n");
0N/A haveMT = false;
0N/A for (Segment s: segments) {
0N/A s.init();
0N/A }
0N/A }
0N/A
0N/A void moveTo(double x, double y) {
0N/A if (LOGMAP) LOG.format("moveto %g, %g\n", x, y);
0N/A mpt.x = x;
0N/A mpt.y = y;
0N/A haveMT = true;
0N/A }
0N/A
0N/A void lineTo(double x, double y) {
0N/A if (LOGMAP) LOG.format("lineto %g, %g\n", x, y);
0N/A
0N/A if (haveMT) {
0N/A // prepare previous point for no-op check
0N/A cpt.x = mpt.x;
0N/A cpt.y = mpt.y;
0N/A }
0N/A
0N/A if (x == cpt.x && y == cpt.y) {
0N/A // lineto is a no-op
0N/A return;
0N/A }
0N/A
0N/A if (haveMT) {
0N/A // current point is the most recent moveto point
0N/A haveMT = false;
0N/A for (Segment s: segments) {
0N/A s.move();
0N/A }
0N/A }
0N/A
0N/A li.set(cpt.x, cpt.y, x, y);
0N/A for (Segment s: segments) {
0N/A s.line(li);
0N/A }
0N/A
0N/A cpt.x = x;
0N/A cpt.y = y;
0N/A }
0N/A
0N/A void close() {
0N/A if (LOGMAP) LOG.format("close\n");
0N/A lineTo(mpt.x, mpt.y);
0N/A for (Segment s: segments) {
0N/A s.close();
0N/A }
0N/A }
0N/A
0N/A public Shape mapShape(Shape s) {
0N/A if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this);
0N/A PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves.
0N/A
0N/A if (LOGMAP) LOG.format("start\n");
0N/A init();
0N/A
0N/A final double[] coords = new double[2];
0N/A while (!pi.isDone()) {
0N/A switch (pi.currentSegment(coords)) {
0N/A case SEG_CLOSE: close(); break;
0N/A case SEG_MOVETO: moveTo(coords[0], coords[1]); break;
0N/A case SEG_LINETO: lineTo(coords[0], coords[1]); break;
0N/A default: break;
0N/A }
0N/A
0N/A pi.next();
0N/A }
0N/A if (LOGMAP) LOG.format("finish\n\n");
0N/A
0N/A GeneralPath gp = new GeneralPath();
0N/A for (Segment seg: segments) {
0N/A gp.append(seg.gp, false);
0N/A }
0N/A return gp;
0N/A }
0N/A }
0N/A
0N/A //
0N/A // for debugging
0N/A //
0N/A
0N/A public String toString() {
0N/A StringBuilder b = new StringBuilder();
0N/A b.append("{");
0N/A b.append(etype.toString());
0N/A b.append(" ");
0N/A for (int i = 0; i < data.length; i += 3) {
0N/A if (i > 0) {
0N/A b.append(",");
0N/A }
0N/A float x = ((int)(data[i] * 100))/100.0f;
0N/A float y = ((int)(data[i+1] * 100))/100.0f;
0N/A float l = ((int)(data[i+2] * 10))/10.0f;
0N/A b.append("{");
0N/A b.append(x);
0N/A b.append(",");
0N/A b.append(y);
0N/A b.append(",");
0N/A b.append(l);
0N/A b.append("}");
0N/A }
0N/A b.append("}");
0N/A return b.toString();
0N/A }
0N/A }
0N/A
0N/A
0N/A public static class EmptyPath extends LayoutPathImpl {
0N/A private AffineTransform tx;
0N/A
0N/A public EmptyPath(AffineTransform tx) {
0N/A this.tx = tx;
0N/A }
0N/A
0N/A public void pathToPoint(Point2D location, boolean preceding, Point2D point) {
0N/A if (tx != null) {
0N/A tx.transform(location, point);
0N/A } else {
0N/A point.setLocation(location);
0N/A }
0N/A }
0N/A
0N/A public boolean pointToPath(Point2D pt, Point2D result) {
0N/A result.setLocation(pt);
0N/A if (tx != null) {
0N/A try {
0N/A tx.inverseTransform(pt, result);
0N/A }
0N/A catch (NoninvertibleTransformException ex) {
0N/A }
0N/A }
0N/A return result.getX() > 0;
0N/A }
0N/A
0N/A public double start() { return 0; }
0N/A
0N/A public double end() { return 0; }
0N/A
0N/A public double length() { return 0; }
0N/A
0N/A public Shape mapShape(Shape s) {
0N/A if (tx != null) {
0N/A return tx.createTransformedShape(s);
0N/A }
0N/A return s;
0N/A }
0N/A }
0N/A}