Path: blob/master/src/java.desktop/share/classes/sun/font/LayoutPathImpl.java
41155 views
/*1* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24/*25* (C) Copyright IBM Corp. 2005, All Rights Reserved.26*/27package sun.font;2829//30// This is the 'simple' mapping implementation. It does things the most31// straightforward way even if that is a bit slow. It won't32// handle complex paths efficiently, and doesn't handle closed paths.33//3435import java.awt.Shape;36import java.awt.font.LayoutPath;37import java.awt.geom.AffineTransform;38import java.awt.geom.GeneralPath;39import java.awt.geom.NoninvertibleTransformException;40import java.awt.geom.PathIterator;41import java.awt.geom.Point2D;42import java.util.Formatter;43import java.util.ArrayList;4445import static java.awt.geom.PathIterator.*;46import static java.lang.Math.abs;47import static java.lang.Math.sqrt;4849public abstract class LayoutPathImpl extends LayoutPath {5051//52// Convenience APIs53//5455public Point2D pointToPath(double x, double y) {56Point2D.Double pt = new Point2D.Double(x, y);57pointToPath(pt, pt);58return pt;59}6061public Point2D pathToPoint(double a, double o, boolean preceding) {62Point2D.Double pt = new Point2D.Double(a, o);63pathToPoint(pt, preceding, pt);64return pt;65}6667public void pointToPath(double x, double y, Point2D pt) {68pt.setLocation(x, y);69pointToPath(pt, pt);70}7172public void pathToPoint(double a, double o, boolean preceding, Point2D pt) {73pt.setLocation(a, o);74pathToPoint(pt, preceding, pt);75}7677//78// extra utility APIs79//8081public abstract double start();82public abstract double end();83public abstract double length();84public abstract Shape mapShape(Shape s);8586//87// debugging flags88//8990private static final boolean LOGMAP = false;91private static final Formatter LOG = new Formatter(System.out);9293/**94* Indicate how positions past the start and limit of the95* path are treated. PINNED adjusts these positions so96* as to be within start and limit. EXTENDED ignores the97* start and limit and effectively extends the first and98* last segments of the path 'infinitely'. CLOSED wraps99* positions around the ends of the path.100*/101public static enum EndType {102PINNED, EXTENDED, CLOSED;103public boolean isPinned() { return this == PINNED; }104public boolean isExtended() { return this == EXTENDED; }105public boolean isClosed() { return this == CLOSED; }106};107108//109// Top level construction.110//111112/**113* Return a path representing the path from the origin through the points in order.114*/115public static LayoutPathImpl getPath(EndType etype, double ... coords) {116if ((coords.length & 0x1) != 0) {117throw new IllegalArgumentException("odd number of points not allowed");118}119120return SegmentPath.get(etype, coords);121}122123/**124* Use to build a SegmentPath. This takes the data and preanalyzes it for125* information that the SegmentPath needs, then constructs a SegmentPath126* from that. Mainly, this lets SegmentPath cache the lengths along127* the path to each line segment, and so avoid calculating them over and over.128*/129public static final class SegmentPathBuilder {130private double[] data;131private int w;132private double px;133private double py;134private double a;135private boolean pconnect;136137/**138* Construct a SegmentPathBuilder.139*/140public SegmentPathBuilder() {141}142143/**144* Reset the builder for a new path. Datalen is a hint of how many145* points will be in the path, and the working buffer will be sized146* to accommodate at least this number of points. If datalen is zero,147* the working buffer is freed (it will be allocated on first use).148*/149public void reset(int datalen) {150if (data == null || datalen > data.length) {151data = new double[datalen];152} else if (datalen == 0) {153data = null;154}155w = 0;156px = py = 0;157pconnect = false;158}159160/**161* Automatically build from a list of points represented by pairs of162* doubles. Initial advance is zero.163*/164public SegmentPath build(EndType etype, double... pts) {165assert(pts.length % 2 == 0);166167reset(pts.length / 2 * 3);168169for (int i = 0; i < pts.length; i += 2) {170nextPoint(pts[i], pts[i+1], i != 0);171}172173return complete(etype);174}175176/**177* Move to a new point. If there is no data, this will become the178* first point. If there is data, and the previous call was a lineTo, this179* point is checked against the previous point, and if different, this180* starts a new segment at the same advance as the end of the last181* segment. If there is data, and the previous call was a moveTo, this182* replaces the point used for that previous call.183*184* Calling this is optional, lineTo will suffice and the initial point185* will be set to 0, 0.186*/187public void moveTo(double x, double y) {188nextPoint(x, y, false);189}190191/**192* Connect to a new point. If there is no data, the previous point193* is presumed to be 0, 0. This point is checked against194* the previous point, and if different, this point is added to195* the path and the advance extended. If this point is the same as the196* previous point, the path remains unchanged.197*/198public void lineTo(double x, double y) {199nextPoint(x, y, true);200}201202/**203* Add a new point, and increment advance if connect is true.204*205* This automatically rejects duplicate points and multiple disconnected points.206*/207private void nextPoint(double x, double y, boolean connect) {208209// if zero length move or line, ignore210if (x == px && y == py) {211return;212}213214if (w == 0) { // this is the first point, make sure we have space215if (data == null) {216data = new double[6];217}218if (connect) {219w = 3; // default first point to 0, 0220}221}222223// if multiple disconnected move, just update position, leave advance alone224if (w != 0 && !connect && !pconnect) {225data[w-3] = px = x;226data[w-2] = py = y;227return;228}229230// grow data to deal with new point231if (w == data.length) {232double[] t = new double[w * 2];233System.arraycopy(data, 0, t, 0, w);234data = t;235}236237if (connect) {238double dx = x - px;239double dy = y - py;240a += sqrt(dx * dx + dy * dy);241}242243// update data244data[w++] = x;245data[w++] = y;246data[w++] = a;247248// update state249px = x;250py = y;251pconnect = connect;252}253254public SegmentPath complete() {255return complete(EndType.EXTENDED);256}257258/**259* Complete building a SegmentPath. Once this is called, the builder is restored260* to its initial state and information about the previous path is released. The261* end type indicates whether to treat the path as closed, extended, or pinned.262*/263public SegmentPath complete(EndType etype) {264SegmentPath result;265266if (data == null || w < 6) {267return null;268}269270if (w == data.length) {271result = new SegmentPath(data, etype);272reset(0); // releases pointer to data273} else {274double[] dataToAdopt = new double[w];275System.arraycopy(data, 0, dataToAdopt, 0, w);276result = new SegmentPath(dataToAdopt, etype);277reset(2); // reuses data, since we held on to it278}279280return result;281}282}283284/**285* Represents a path built from segments. Each segment is286* represented by a triple: x, y, and cumulative advance.287* These represent the end point of the segment. The start288* point of the first segment is represented by the triple289* at position 0.290*291* The path might have breaks in it, e.g. it is not connected.292* These will be represented by pairs of triplets that share the293* same advance.294*295* The path might be extended, pinned, or closed. If extended,296* the initial and final segments are considered to extend297* 'indefinitely' past the bounds of the advance. If pinned,298* they end at the bounds of the advance. If closed,299* advances before the start or after the end 'wrap around' the300* path.301*302* The start of the path is the initial triple. This provides303* the nominal advance at the given x, y position (typically304* zero). The end of the path is the final triple. This provides305* the advance at the end, the total length of the path is306* thus the ending advance minus the starting advance.307*308* Note: We might want to cache more auxiliary data than the309* advance, but this seems adequate for now.310*/311public static final class SegmentPath extends LayoutPathImpl {312private double[] data; // triplets x, y, a313EndType etype;314315public static SegmentPath get(EndType etype, double... pts) {316return new SegmentPathBuilder().build(etype, pts);317}318319/**320* Internal, use SegmentPathBuilder or one of the static321* helper functions to construct a SegmentPath.322*/323SegmentPath(double[] data, EndType etype) {324this.data = data;325this.etype = etype;326}327328//329// LayoutPath API330//331332public void pathToPoint(Point2D location, boolean preceding, Point2D point) {333locateAndGetIndex(location, preceding, point);334}335336// the path consists of line segments, which i'll call337// 'path vectors'. call each run of path vectors a 'path segment'.338// no path vector in a path segment is zero length (in the339// data, such vectors start a new path segment).340//341// for each path segment...342//343// for each path vector...344//345// we look at the dot product of the path vector and the vector from the346// origin of the path vector to the test point. if <0 (case347// A), the projection of the test point is before the start of348// the path vector. if > the square of the length of the path vector349// (case B), the projection is past the end point of the350// path vector. otherwise (case C), it lies on the path vector.351// determine the closeset point on the path vector. if case A, it352// is the start of the path vector. if case B and this is the last353// path vector in the path segment, it is the end of the path vector. If354// case C, it is the projection onto the path vector. Otherwise355// there is no closest point.356//357// if we have a closest point, compare the distance from it to358// the test point against our current closest distance.359// (culling should be fast, currently i am using distance360// squared, but there's probably better ways). if we're361// closer, save the new point as the current closest point,362// and record the path vector index so we can determine the final363// info if this turns out to be the closest point in the end.364//365// after we have processed all the segments we will have366// tested each path vector and each endpoint. if our point is not on367// an endpoint, we're done; we can compute the position and368// offset again, or if we saved it off we can just use it. if369// we're on an endpoint we need to see which path vector we should370// associate with. if we're at the start or end of a path segment,371// we're done-- the first or last vector of the segment is the372// one we associate with. we project against that vector to373// get the offset, and pin to that vector to get the length.374//375// otherwise, we compute the information as follows. if the376// dot product (see above) with the following vector is zero,377// we associate with that vector. otherwise, if the dot378// product with the previous vector is zero, we associate with379// that vector. otherwise we're beyond the end of the380// previous vector and before the start of the current vector.381// we project against both vectors and get the distance from382// the test point to the projection (this will be the offset).383// if they are the same, we take the following vector.384// otherwise use the vector from which the test point is the385// _farthest_ (this is because the point lies most clearly in386// the half of the plane defined by extending that vector).387//388// the returned position is the path length to the (possibly389// pinned) point, the offset is the projection onto the line390// along the vector, and we have a boolean flag which if false391// indicates that we associate with the previous vector at a392// junction (which is necessary when projecting such a393// location back to a point).394395public boolean pointToPath(Point2D pt, Point2D result) {396double x = pt.getX(); // test point397double y = pt.getY();398399double bx = data[0]; // previous point400double by = data[1];401double bl = data[2];402403// start with defaults404double cd2 = Double.MAX_VALUE; // current best distance from path, squared405double cx = 0; // current best x406double cy = 0; // current best y407double cl = 0; // current best position along path408int ci = 0; // current best index into data409410for (int i = 3; i < data.length; i += 3) {411double nx = data[i]; // current end point412double ny = data[i+1];413double nl = data[i+2];414415double dx = nx - bx; // vector from previous to current416double dy = ny - by;417double dl = nl - bl;418419double px = x - bx; // vector from previous to test point420double py = y - by;421422// determine sign of dot product of vectors from bx, by423// if < 0, we're before the start of this vector424425double dot = dx * px + dy * py; // dot product426double vcx, vcy, vcl; // hold closest point on vector as x, y, l427int vi; // hold index of line, is data.length if last point on path428do { // use break below, lets us avoid initializing vcx, vcy...429if (dl == 0 || // moveto, or430(dot < 0 && // before path vector and431(!etype.isExtended() ||432i != 3))) { // closest point is start of vector433vcx = bx;434vcy = by;435vcl = bl;436vi = i;437} else {438double l2 = dl * dl; // aka dx * dx + dy * dy, square of length439if (dot <= l2 || // closest point is not past end of vector, or440(etype.isExtended() && // we're extended and at the last segment441i == data.length - 3)) {442double p = dot / l2; // get parametric along segment443vcx = bx + p * dx; // compute closest point444vcy = by + p * dy;445vcl = bl + p * dl;446vi = i;447} else {448if (i == data.length - 3) {449vcx = nx; // special case, always test last point450vcy = ny;451vcl = nl;452vi = data.length;453} else {454break; // typical case, skip point, we'll pick it up next iteration455}456}457}458459double tdx = x - vcx; // compute distance from (usually pinned) projection to test point460double tdy = y - vcy;461double td2 = tdx * tdx + tdy * tdy;462if (td2 <= cd2) { // new closest point, record info on it463cd2 = td2;464cx = vcx;465cy = vcy;466cl = vcl;467ci = vi;468}469} while (false);470471bx = nx;472by = ny;473bl = nl;474}475476// we have our closest point, get the info477bx = data[ci-3];478by = data[ci-2];479if (cx != bx || cy != by) { // not on endpoint, no need to resolve480double nx = data[ci];481double ny = data[ci+1];482double co = sqrt(cd2); // have a true perpendicular, so can use distance483if ((x-cx)*(ny-by) > (y-cy)*(nx-bx)) {484co = -co; // determine sign of offset485}486result.setLocation(cl, co);487return false;488} else { // on endpoint, we need to resolve which segment489boolean havePrev = ci != 3 && data[ci-1] != data[ci-4];490boolean haveFoll = ci != data.length && data[ci-1] != data[ci+2];491boolean doExtend = etype.isExtended() && (ci == 3 || ci == data.length);492if (havePrev && haveFoll) {493Point2D.Double pp = new Point2D.Double(x, y);494calcoffset(ci - 3, doExtend, pp);495Point2D.Double fp = new Point2D.Double(x, y);496calcoffset(ci, doExtend, fp);497if (abs(pp.y) > abs(fp.y)) {498result.setLocation(pp);499return true; // associate with previous500} else {501result.setLocation(fp);502return false; // associate with following503}504} else if (havePrev) {505result.setLocation(x, y);506calcoffset(ci - 3, doExtend, result);507return true;508} else {509result.setLocation(x, y);510calcoffset(ci, doExtend, result);511return false;512}513}514}515516/**517* Return the location of the point passed in result as mapped to the518* line indicated by index. If doExtend is true, extend the519* x value without pinning to the ends of the line.520* this assumes that index is valid and references a line that has521* non-zero length.522*/523private void calcoffset(int index, boolean doExtend, Point2D result) {524double bx = data[index-3];525double by = data[index-2];526double px = result.getX() - bx;527double py = result.getY() - by;528double dx = data[index] - bx;529double dy = data[index+1] - by;530double l = data[index+2] - data[index - 1];531532// rx = A dot B / |B|533// ry = A dot invB / |B|534double rx = (px * dx + py * dy) / l;535double ry = (px * -dy + py * dx) / l;536if (!doExtend) {537if (rx < 0) rx = 0;538else if (rx > l) rx = l;539}540rx += data[index-1];541result.setLocation(rx, ry);542}543544//545// LayoutPathImpl API546//547548public Shape mapShape(Shape s) {549return new Mapper().mapShape(s);550}551552public double start() {553return data[2];554}555556public double end() {557return data[data.length - 1];558}559560public double length() {561return data[data.length-1] - data[2];562}563564//565// Utilities566//567568/**569* Get the 'modulus' of an advance on a closed path.570*/571private double getClosedAdvance(double a, boolean preceding) {572if (etype.isClosed()) {573a -= data[2];574int count = (int)(a/length());575a -= count * length();576if (a < 0 || (a == 0 && preceding)) {577a += length();578579}580a += data[2];581}582return a;583}584585/**586* Return the index of the segment associated with advance. This587* points to the start of the triple and is a multiple of 3 between588* 3 and data.length-3 inclusive. It never points to a 'moveto' triple.589*590* If the path is closed, 'a' is mapped to591* a value between the start and end of the path, inclusive.592* If preceding is true, and 'a' lies on a segment boundary,593* return the index of the preceding segment, else return the index594* of the current segment (if it is not a moveto segment) otherwise595* the following segment (which is never a moveto segment).596*597* Note: if the path is not closed, the advance might not actually598* lie on the returned segment-- it might be before the first, or599* after the last. The first or last segment (as appropriate)600* will be returned in this case.601*/602private int getSegmentIndexForAdvance(double a, boolean preceding) {603// must have local advance604a = getClosedAdvance(a, preceding);605606// note we must avoid 'moveto' segments. the first segment is607// always a moveto segment, so we always skip it.608int i, lim;609for (i = 5, lim = data.length-1; i < lim; i += 3) {610double v = data[i];611if (a < v || (a == v && preceding)) {612break;613}614}615return i-2; // adjust to start of segment616}617618/**619* Map a location based on the provided segment, returning in pt.620* Seg must be a valid 'lineto' segment. Note: if the path is621* closed, x must be within the start and end of the path.622*/623private void map(int seg, double a, double o, Point2D pt) {624double dx = data[seg] - data[seg-3];625double dy = data[seg+1] - data[seg-2];626double dl = data[seg+2] - data[seg-1];627628double ux = dx/dl; // could cache these, but is it worth it?629double uy = dy/dl;630631a -= data[seg-1];632633pt.setLocation(data[seg-3] + a * ux - o * uy,634data[seg-2] + a * uy + o * ux);635}636637/**638* Map the point, and return the segment index.639*/640private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) {641double a = loc.getX();642double o = loc.getY();643int seg = getSegmentIndexForAdvance(a, preceding);644map(seg, a, o, result);645646return seg;647}648649//650// Mapping classes.651// Map the path onto each path segment.652// Record points where the advance 'enters' and 'exits' the path segment, and connect successive653// points when appropriate.654//655656/**657* This represents a line segment from the iterator. Each target segment will658* interpret it, and since this process needs slope along the line659* segment, this lets us compute it once and pass it around easily.660*/661class LineInfo {662double sx, sy; // start663double lx, ly; // limit664double m; // slope dy/dx665666/**667* Set the lineinfo to this line668*/669void set(double sx, double sy, double lx, double ly) {670this.sx = sx;671this.sy = sy;672this.lx = lx;673this.ly = ly;674double dx = lx - sx;675if (dx == 0) {676m = 0; // we'll check for this elsewhere677} else {678double dy = ly - sy;679m = dy / dx;680}681}682683void set(LineInfo rhs) {684this.sx = rhs.sx;685this.sy = rhs.sy;686this.lx = rhs.lx;687this.ly = rhs.ly;688this.m = rhs.m;689}690691/**692* Return true if we intersect the infinitely tall rectangle with693* lo <= x < hi. If we do, also return the pinned portion of ourselves in694* result.695*/696boolean pin(double lo, double hi, LineInfo result) {697result.set(this);698if (lx >= sx) {699if (sx < hi && lx >= lo) {700if (sx < lo) {701if (m != 0) result.sy = sy + m * (lo - sx);702result.sx = lo;703}704if (lx > hi) {705if (m != 0) result.ly = ly + m * (hi - lx);706result.lx = hi;707}708return true;709}710} else {711if (lx < hi && sx >= lo) {712if (lx < lo) {713if (m != 0) result.ly = ly + m * (lo - lx);714result.lx = lo;715}716if (sx > hi) {717if (m != 0) result.sy = sy + m * (hi - sx);718result.sx = hi;719}720return true;721}722}723return false;724}725726/**727* Return true if we intersect the segment at ix. This takes728* the path end type into account and computes the relevant729* parameters to pass to pin(double, double, LineInfo).730*/731boolean pin(int ix, LineInfo result) {732double lo = data[ix-1];733double hi = data[ix+2];734switch (SegmentPath.this.etype) {735case PINNED:736break;737case EXTENDED:738if (ix == 3) lo = Double.NEGATIVE_INFINITY;739if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY;740break;741case CLOSED:742// not implemented743break;744}745746return pin(lo, hi, result);747}748}749750/**751* Each segment will construct its own general path, mapping the provided lines752* into its own simple space.753*/754class Segment {755final int ix; // index into data array for this segment756final double ux, uy; // unit vector757758final LineInfo temp; // working line info759760boolean broken; // true if a moveto has occurred since we last added to our path761double cx, cy; // last point in gp762GeneralPath gp; // path built for this segment763764Segment(int ix) {765this.ix = ix;766double len = data[ix+2] - data[ix-1];767this.ux = (data[ix] - data[ix-3]) / len;768this.uy = (data[ix+1] - data[ix-2]) / len;769this.temp = new LineInfo();770}771772void init() {773if (LOGMAP) LOG.format("s(%d) init\n", ix);774broken = true;775cx = cy = Double.MIN_VALUE;776this.gp = new GeneralPath();777}778779void move() {780if (LOGMAP) LOG.format("s(%d) move\n", ix);781broken = true;782}783784void close() {785if (!broken) {786if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix);787gp.closePath();788}789}790791void line(LineInfo li) {792if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly);793794if (li.pin(ix, temp)) {795if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly);796797temp.sx -= data[ix-1];798double sx = data[ix-3] + temp.sx * ux - temp.sy * uy;799double sy = data[ix-2] + temp.sx * uy + temp.sy * ux;800temp.lx -= data[ix-1];801double lx = data[ix-3] + temp.lx * ux - temp.ly * uy;802double ly = data[ix-2] + temp.lx * uy + temp.ly * ux;803804if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly);805806if (sx != cx || sy != cy) {807if (broken) {808if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy);809gp.moveTo((float)sx, (float)sy);810} else {811if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy);812gp.lineTo((float)sx, (float)sy);813}814}815if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly);816gp.lineTo((float)lx, (float)ly);817818broken = false;819cx = lx;820cy = ly;821}822}823}824825class Mapper {826final LineInfo li; // working line info827final ArrayList<Segment> segments; // cache additional data on segments, working objects828final Point2D.Double mpt; // last moveto source point829final Point2D.Double cpt; // current source point830boolean haveMT; // true when last op was a moveto831832Mapper() {833li = new LineInfo();834segments = new ArrayList<Segment>();835for (int i = 3; i < data.length; i += 3) {836if (data[i+2] != data[i-1]) { // a new segment837segments.add(new Segment(i));838}839}840841mpt = new Point2D.Double();842cpt = new Point2D.Double();843}844845void init() {846if (LOGMAP) LOG.format("init\n");847haveMT = false;848for (Segment s: segments) {849s.init();850}851}852853void moveTo(double x, double y) {854if (LOGMAP) LOG.format("moveto %g, %g\n", x, y);855mpt.x = x;856mpt.y = y;857haveMT = true;858}859860void lineTo(double x, double y) {861if (LOGMAP) LOG.format("lineto %g, %g\n", x, y);862863if (haveMT) {864// prepare previous point for no-op check865cpt.x = mpt.x;866cpt.y = mpt.y;867}868869if (x == cpt.x && y == cpt.y) {870// lineto is a no-op871return;872}873874if (haveMT) {875// current point is the most recent moveto point876haveMT = false;877for (Segment s: segments) {878s.move();879}880}881882li.set(cpt.x, cpt.y, x, y);883for (Segment s: segments) {884s.line(li);885}886887cpt.x = x;888cpt.y = y;889}890891void close() {892if (LOGMAP) LOG.format("close\n");893lineTo(mpt.x, mpt.y);894for (Segment s: segments) {895s.close();896}897}898899public Shape mapShape(Shape s) {900if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this);901PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves.902903if (LOGMAP) LOG.format("start\n");904init();905906final double[] coords = new double[2];907while (!pi.isDone()) {908switch (pi.currentSegment(coords)) {909case SEG_CLOSE: close(); break;910case SEG_MOVETO: moveTo(coords[0], coords[1]); break;911case SEG_LINETO: lineTo(coords[0], coords[1]); break;912default: break;913}914915pi.next();916}917if (LOGMAP) LOG.format("finish\n\n");918919GeneralPath gp = new GeneralPath();920for (Segment seg: segments) {921gp.append(seg.gp, false);922}923return gp;924}925}926927//928// for debugging929//930931public String toString() {932StringBuilder b = new StringBuilder();933b.append("{");934b.append(etype.toString());935b.append(" ");936for (int i = 0; i < data.length; i += 3) {937if (i > 0) {938b.append(",");939}940float x = ((int)(data[i] * 100))/100.0f;941float y = ((int)(data[i+1] * 100))/100.0f;942float l = ((int)(data[i+2] * 10))/10.0f;943b.append("{");944b.append(x);945b.append(",");946b.append(y);947b.append(",");948b.append(l);949b.append("}");950}951b.append("}");952return b.toString();953}954}955956957public static class EmptyPath extends LayoutPathImpl {958private AffineTransform tx;959960public EmptyPath(AffineTransform tx) {961this.tx = tx;962}963964public void pathToPoint(Point2D location, boolean preceding, Point2D point) {965if (tx != null) {966tx.transform(location, point);967} else {968point.setLocation(location);969}970}971972public boolean pointToPath(Point2D pt, Point2D result) {973result.setLocation(pt);974if (tx != null) {975try {976tx.inverseTransform(pt, result);977}978catch (NoninvertibleTransformException ex) {979}980}981return result.getX() > 0;982}983984public double start() { return 0; }985986public double end() { return 0; }987988public double length() { return 0; }989990public Shape mapShape(Shape s) {991if (tx != null) {992return tx.createTransformedShape(s);993}994return s;995}996}997}9989991000