} else { result.setLocation(fp); return false; // associate with following } } else if (havePrev) { result.setLocation(x, y); calcoffset(ci - 3, doExtend, result); return true; } else { result.setLocation(x, y); calcoffset(ci, doExtend, result); return false; } } } /** * Return the location of the point passed in result as mapped to the * line indicated by index. If doExtend is true, extend the * x value without pinning to the ends of the line. * this assumes that index is valid and references a line that has * non-zero length. */ private void calcoffset(int index, boolean doExtend, Point2D result) { double bx = data[index-3]; double by = data[index-2]; double px = result.getX() - bx; double py = result.getY() - by; double dx = data[index] - bx; double dy = data[index+1] - by; double l = data[index+2] - data[index - 1]; // rx = A dot B / |B| // ry = A dot invB / |B| double rx = (px * dx + py * dy) / l; double ry = (px * -dy + py * dx) / l; if (!doExtend) { if (rx < 0) rx = 0; else if (rx > l) rx = l; } rx += data[index-1]; result.setLocation(rx, ry); } // // LayoutPathImpl API // public Shape mapShape(Shape s) { return new Mapper().mapShape(s); } public double start() { return data[2]; } public double end() { return data[data.length - 1]; } public double length() { return data[data.length-1] - data[2]; } // // Utilities // /** * Get the 'modulus' of an advance on a closed path. */ private double getClosedAdvance(double a, boolean preceding) { if (etype.isClosed()) { a -= data[2]; int count = (int)(a/length()); a -= count * length(); if (a < 0 || (a == 0 && preceding)) { a += length(); } a += data[2]; } return a; } /** * Return the index of the segment associated with advance. This * points to the start of the triple and is a multiple of 3 between * 3 and data.length-3 inclusive. It never points to a 'moveto' triple. * * If the path is closed, 'a' is mapped to * a value between the start and end of the path, inclusive. * If preceding is true, and 'a' lies on a segment boundary, * return the index of the preceding segment, else return the index * of the current segment (if it is not a moveto segment) otherwise * the following segment (which is never a moveto segment). * * Note: if the path is not closed, the advance might not actually * lie on the returned segment-- it might be before the first, or * after the last. The first or last segment (as appropriate) * will be returned in this case. */ private int getSegmentIndexForAdvance(double a, boolean preceding) { // must have local advance a = getClosedAdvance(a, preceding); // note we must avoid 'moveto' segments. the first segment is // always a moveto segment, so we always skip it. int i, lim; for (i = 5, lim = data.length-1; i < lim; i += 3) { double v = data[i]; if (a < v || (a == v && preceding)) { break; } } return i-2; // adjust to start of segment } /** * Map a location based on the provided segment, returning in pt. * Seg must be a valid 'lineto' segment. Note: if the path is * closed, x must be within the start and end of the path. */ private void map(int seg, double a, double o, Point2D pt) { double dx = data[seg] - data[seg-3]; double dy = data[seg+1] - data[seg-2]; double dl = data[seg+2] - data[seg-1]; double ux = dx/dl; // could cache these, but is it worth it? double uy = dy/dl; a -= data[seg-1]; pt.setLocation(data[seg-3] + a * ux - o * uy, data[seg-2] + a * uy + o * ux); } /** * Map the point, and return the segment index. */ private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) { double a = loc.getX(); double o = loc.getY(); int seg = getSegmentIndexForAdvance(a, preceding); map(seg, a, o, result); return seg; } // // Mapping classes. // Map the path onto each path segment. // Record points where the advance 'enters' and 'exits' the path segment, and connect successive // points when appropriate. // /** * This represents a line segment from the iterator. Each target segment will * interpret it, and since this process needs slope along the line * segment, this lets us compute it once and pass it around easily. */ class LineInfo { double sx, sy; // start double lx, ly; // limit double m; // slope dy/dx /** * Set the lineinfo to this line */ void set(double sx, double sy, double lx, double ly) { this.sx = sx; this.sy = sy; this.lx = lx; this.ly = ly; double dx = lx - sx; if (dx == 0) { m = 0; // we'll check for this elsewhere } else { double dy = ly - sy; m = dy / dx; } } void set(LineInfo rhs) { this.sx = rhs.sx; this.sy = rhs.sy; this.lx = rhs.lx; this.ly = rhs.ly; this.m = rhs.m; } /** * Return true if we intersect the infinitely tall rectangle with * lo <= x < hi. If we do, also return the pinned portion of ourselves in * result. */ boolean pin(double lo, double hi, LineInfo result) { result.set(this); if (lx >= sx) { if (sx < hi && lx >= lo) { if (sx < lo) { if (m != 0) result.sy = sy + m * (lo - sx); result.sx = lo; } if (lx > hi) { if (m != 0) result.ly = ly + m * (hi - lx); result.lx = hi; } return true; } } else { if (lx < hi && sx >= lo) { if (lx < lo) { if (m != 0) result.ly = ly + m * (lo - lx); result.lx = lo; } if (sx > hi) { if (m != 0) result.sy = sy + m * (hi - sx); result.sx = hi; } return true; } } return false; } /** * Return true if we intersect the segment at ix. This takes * the path end type into account and computes the relevant * parameters to pass to pin(double, double, LineInfo). */ boolean pin(int ix, LineInfo result) { double lo = data[ix-1]; double hi = data[ix+2]; switch (SegmentPath.this.etype) { case PINNED: break; case EXTENDED: if (ix == 3) lo = Double.NEGATIVE_INFINITY; if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY; break; case CLOSED: // not implemented break; } return pin(lo, hi, result); } } /** * Each segment will construct its own general path, mapping the provided lines * into its own simple space. */ class Segment { final int ix; // index into data array for this segment final double ux, uy; // unit vector final LineInfo temp; // working line info boolean broken; // true if a moveto has occurred since we last added to our path double cx, cy; // last point in gp GeneralPath gp; // path built for this segment Segment(int ix) { this.ix = ix; double len = data[ix+2] - data[ix-1]; this.ux = (data[ix] - data[ix-3]) / len; this.uy = (data[ix+1] - data[ix-2]) / len; this.temp = new LineInfo(); } void init() { if (LOGMAP) LOG.format("s(%d) init\n", ix); broken = true; cx = cy = Double.MIN_VALUE; this.gp = new GeneralPath(); } void move() { if (LOGMAP) LOG.format("s(%d) move\n", ix); broken = true; } void close() { if (!broken) { if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix); gp.closePath(); } } void line(LineInfo li) { if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly); if (li.pin(ix, temp)) { if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly); temp.sx -= data[ix-1]; double sx = data[ix-3] + temp.sx * ux - temp.sy * uy; double sy = data[ix-2] + temp.sx * uy + temp.sy * ux; temp.lx -= data[ix-1]; double lx = data[ix-3] + temp.lx * ux - temp.ly * uy; double ly = data[ix-2] + temp.lx * uy + temp.ly * ux; if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly); if (sx != cx || sy != cy) { if (broken) { if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy); gp.moveTo((float)sx, (float)sy); } else { if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy); gp.lineTo((float)sx, (float)sy); } } if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly); gp.lineTo((float)lx, (float)ly); broken = false; cx = lx; cy = ly; } } } class Mapper { final LineInfo li; // working line info final ArrayList<Segment> segments; // cache additional data on segments, working objects final Point2D.Double mpt; // last moveto source point final Point2D.Double cpt; // current source point boolean haveMT; // true when last op was a moveto Mapper() { li = new LineInfo(); segments = new ArrayList<Segment>(); for (int i = 3; i < data.length; i += 3) { if (data[i+2] != data[i-1]) { // a new segment segments.add(new Segment(i)); } } mpt = new Point2D.Double(); cpt = new Point2D.Double(); } void init() { if (LOGMAP) LOG.format("init\n"); haveMT = false; for (Segment s: segments) { s.init(); } } void moveTo(double x, double y) { if (LOGMAP) LOG.format("moveto %g, %g\n", x, y); mpt.x = x; mpt.y = y; haveMT = true; } void lineTo(double x, double y) { if (LOGMAP) LOG.format("lineto %g, %g\n", x, y); if (haveMT) { // prepare previous point for no-op check cpt.x = mpt.x; cpt.y = mpt.y; } if (x == cpt.x && y == cpt.y) { // lineto is a no-op return; } if (haveMT) { // current point is the most recent moveto point haveMT = false; for (Segment s: segments) { s.move(); } } li.set(cpt.x, cpt.y, x, y); for (Segment s: segments) { s.line(li); } cpt.x = x; cpt.y = y; } void close() { if (LOGMAP) LOG.format("close\n"); lineTo(mpt.x, mpt.y); for (Segment s: segments) { s.close(); } } public Shape mapShape(Shape s) { if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this); PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves. if (LOGMAP) LOG.format("start\n"); init(); final double[] coords = new double[2]; while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case SEG_CLOSE: close(); break; case SEG_MOVETO: moveTo(coords[0], coords[1]); break; case SEG_LINETO: lineTo(coords[0], coords[1]); break; default: break; } pi.next(); } if (LOGMAP) LOG.format("finish\n\n"); GeneralPath gp = new GeneralPath(); for (Segment seg: segments) { gp.append(seg.gp, false); } return gp; } } // // for debugging // public String toString() { StringBuilder b = new StringBuilder(); b.append("{"); b.append(etype.toString()); b.append(" "); for (int i = 0; i < data.length; i += 3) { if (i > 0) { b.append(","); } float x = ((int)(data[i] * 100))/100.0f; float y = ((int)(data[i+1] * 100))/100.0f; float l = ((int)(data[i+2] * 10))/10.0f; b.append("{"); b.append(x); b.append(","); b.append(y); b.append(","); b.append(l); b.append("}"); } b.append("}"); return b.toString(); } } public static class EmptyPath extends LayoutPathImpl { private AffineTransform tx; public EmptyPath(AffineTransform tx) { this.tx = tx; } public void pathToPoint(Point2D location, boolean preceding, Point2D point) { if (tx != null) { tx.transform(location, point); } else { point.setLocation(location); } } public boolean pointToPath(Point2D pt, Point2D result) { result.setLocation(pt); if (tx != null) { try { tx.inverseTransform(pt, result); } catch (NoninvertibleTransformException ex) { } } return result.getX() > 0; } public double start() { return 0; } public double end() { return 0; } public double length() { return 0; } public Shape mapShape(Shape s) { if (tx != null) { return tx.createTransformedShape(s); } return s; } }}Lines 501–998 of 998. Previous lines
} else { result.setLocation(fp); return false; // associate with following } } else if (havePrev) { result.setLocation(x, y); calcoffset(ci - 3, doExtend, result); return true; } else { result.setLocation(x, y); calcoffset(ci, doExtend, result); return false; } } } /** * Return the location of the point passed in result as mapped to the * line indicated by index. If doExtend is true, extend the * x value without pinning to the ends of the line. * this assumes that index is valid and references a line that has * non-zero length. */ private void calcoffset(int index, boolean doExtend, Point2D result) { double bx = data[index-3]; double by = data[index-2]; double px = result.getX() - bx; double py = result.getY() - by; double dx = data[index] - bx; double dy = data[index+1] - by; double l = data[index+2] - data[index - 1]; // rx = A dot B / |B| // ry = A dot invB / |B| double rx = (px * dx + py * dy) / l; double ry = (px * -dy + py * dx) / l; if (!doExtend) { if (rx < 0) rx = 0; else if (rx > l) rx = l; } rx += data[index-1]; result.setLocation(rx, ry); } // // LayoutPathImpl API // public Shape mapShape(Shape s) { return new Mapper().mapShape(s); } public double start() { return data[2]; } public double end() { return data[data.length - 1]; } public double length() { return data[data.length-1] - data[2]; } // // Utilities // /** * Get the 'modulus' of an advance on a closed path. */ private double getClosedAdvance(double a, boolean preceding) { if (etype.isClosed()) { a -= data[2]; int count = (int)(a/length()); a -= count * length(); if (a < 0 || (a == 0 && preceding)) { a += length(); } a += data[2]; } return a; } /** * Return the index of the segment associated with advance. This * points to the start of the triple and is a multiple of 3 between * 3 and data.length-3 inclusive. It never points to a 'moveto' triple. * * If the path is closed, 'a' is mapped to * a value between the start and end of the path, inclusive. * If preceding is true, and 'a' lies on a segment boundary, * return the index of the preceding segment, else return the index * of the current segment (if it is not a moveto segment) otherwise * the following segment (which is never a moveto segment). * * Note: if the path is not closed, the advance might not actually * lie on the returned segment-- it might be before the first, or * after the last. The first or last segment (as appropriate) * will be returned in this case. */ private int getSegmentIndexForAdvance(double a, boolean preceding) { // must have local advance a = getClosedAdvance(a, preceding); // note we must avoid 'moveto' segments. the first segment is // always a moveto segment, so we always skip it. int i, lim; for (i = 5, lim = data.length-1; i < lim; i += 3) { double v = data[i]; if (a < v || (a == v && preceding)) { break; } } return i-2; // adjust to start of segment } /** * Map a location based on the provided segment, returning in pt. * Seg must be a valid 'lineto' segment. Note: if the path is * closed, x must be within the start and end of the path. */ private void map(int seg, double a, double o, Point2D pt) { double dx = data[seg] - data[seg-3]; double dy = data[seg+1] - data[seg-2]; double dl = data[seg+2] - data[seg-1]; double ux = dx/dl; // could cache these, but is it worth it? double uy = dy/dl; a -= data[seg-1]; pt.setLocation(data[seg-3] + a * ux - o * uy, data[seg-2] + a * uy + o * ux); } /** * Map the point, and return the segment index. */ private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) { double a = loc.getX(); double o = loc.getY(); int seg = getSegmentIndexForAdvance(a, preceding); map(seg, a, o, result); return seg; } // // Mapping classes. // Map the path onto each path segment. // Record points where the advance 'enters' and 'exits' the path segment, and connect successive // points when appropriate. // /** * This represents a line segment from the iterator. Each target segment will * interpret it, and since this process needs slope along the line * segment, this lets us compute it once and pass it around easily. */ class LineInfo { double sx, sy; // start double lx, ly; // limit double m; // slope dy/dx /** * Set the lineinfo to this line */ void set(double sx, double sy, double lx, double ly) { this.sx = sx; this.sy = sy; this.lx = lx; this.ly = ly; double dx = lx - sx; if (dx == 0) { m = 0; // we'll check for this elsewhere } else { double dy = ly - sy; m = dy / dx; } } void set(LineInfo rhs) { this.sx = rhs.sx; this.sy = rhs.sy; this.lx = rhs.lx; this.ly = rhs.ly; this.m = rhs.m; } /** * Return true if we intersect the infinitely tall rectangle with * lo <= x < hi. If we do, also return the pinned portion of ourselves in * result. */ boolean pin(double lo, double hi, LineInfo result) { result.set(this); if (lx >= sx) { if (sx < hi && lx >= lo) { if (sx < lo) { if (m != 0) result.sy = sy + m * (lo - sx); result.sx = lo; } if (lx > hi) { if (m != 0) result.ly = ly + m * (hi - lx); result.lx = hi; } return true; } } else { if (lx < hi && sx >= lo) { if (lx < lo) { if (m != 0) result.ly = ly + m * (lo - lx); result.lx = lo; } if (sx > hi) { if (m != 0) result.sy = sy + m * (hi - sx); result.sx = hi; } return true; } } return false; } /** * Return true if we intersect the segment at ix. This takes * the path end type into account and computes the relevant * parameters to pass to pin(double, double, LineInfo). */ boolean pin(int ix, LineInfo result) { double lo = data[ix-1]; double hi = data[ix+2]; switch (SegmentPath.this.etype) { case PINNED: break; case EXTENDED: if (ix == 3) lo = Double.NEGATIVE_INFINITY; if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY; break; case CLOSED: // not implemented break; } return pin(lo, hi, result); } } /** * Each segment will construct its own general path, mapping the provided lines * into its own simple space. */ class Segment { final int ix; // index into data array for this segment final double ux, uy; // unit vector final LineInfo temp; // working line info boolean broken; // true if a moveto has occurred since we last added to our path double cx, cy; // last point in gp GeneralPath gp; // path built for this segment Segment(int ix) { this.ix = ix; double len = data[ix+2] - data[ix-1]; this.ux = (data[ix] - data[ix-3]) / len; this.uy = (data[ix+1] - data[ix-2]) / len; this.temp = new LineInfo(); } void init() { if (LOGMAP) LOG.format("s(%d) init\n", ix); broken = true; cx = cy = Double.MIN_VALUE; this.gp = new GeneralPath(); } void move() { if (LOGMAP) LOG.format("s(%d) move\n", ix); broken = true; } void close() { if (!broken) { if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix); gp.closePath(); } } void line(LineInfo li) { if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly); if (li.pin(ix, temp)) { if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly); temp.sx -= data[ix-1]; double sx = data[ix-3] + temp.sx * ux - temp.sy * uy; double sy = data[ix-2] + temp.sx * uy + temp.sy * ux; temp.lx -= data[ix-1]; double lx = data[ix-3] + temp.lx * ux - temp.ly * uy; double ly = data[ix-2] + temp.lx * uy + temp.ly * ux; if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly); if (sx != cx || sy != cy) { if (broken) { if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy); gp.moveTo((float)sx, (float)sy); } else { if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy); gp.lineTo((float)sx, (float)sy); } } if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly); gp.lineTo((float)lx, (float)ly); broken = false; cx = lx; cy = ly; } } } class Mapper { final LineInfo li; // working line info final ArrayList<Segment> segments; // cache additional data on segments, working objects final Point2D.Double mpt; // last moveto source point final Point2D.Double cpt; // current source point boolean haveMT; // true when last op was a moveto Mapper() { li = new LineInfo(); segments = new ArrayList<Segment>(); for (int i = 3; i < data.length; i += 3) { if (data[i+2] != data[i-1]) { // a new segment segments.add(new Segment(i)); } } mpt = new Point2D.Double(); cpt = new Point2D.Double(); } void init() { if (LOGMAP) LOG.format("init\n"); haveMT = false; for (Segment s: segments) { s.init(); } } void moveTo(double x, double y) { if (LOGMAP) LOG.format("moveto %g, %g\n", x, y); mpt.x = x; mpt.y = y; haveMT = true; } void lineTo(double x, double y) { if (LOGMAP) LOG.format("lineto %g, %g\n", x, y); if (haveMT) { // prepare previous point for no-op check cpt.x = mpt.x; cpt.y = mpt.y; } if (x == cpt.x && y == cpt.y) { // lineto is a no-op return; } if (haveMT) { // current point is the most recent moveto point haveMT = false; for (Segment s: segments) { s.move(); } } li.set(cpt.x, cpt.y, x, y); for (Segment s: segments) { s.line(li); } cpt.x = x; cpt.y = y; } void close() { if (LOGMAP) LOG.format("close\n"); lineTo(mpt.x, mpt.y); for (Segment s: segments) { s.close(); } } public Shape mapShape(Shape s) { if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this); PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves. if (LOGMAP) LOG.format("start\n"); init(); final double[] coords = new double[2]; while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case SEG_CLOSE: close(); break; case SEG_MOVETO: moveTo(coords[0], coords[1]); break; case SEG_LINETO: lineTo(coords[0], coords[1]); break; default: break; } pi.next(); } if (LOGMAP) LOG.format("finish\n\n"); GeneralPath gp = new GeneralPath(); for (Segment seg: segments) { gp.append(seg.gp, false); } return gp; } } // // for debugging // public String toString() { StringBuilder b = new StringBuilder(); b.append("{"); b.append(etype.toString()); b.append(" "); for (int i = 0; i < data.length; i += 3) { if (i > 0) { b.append(","); } float x = ((int)(data[i] * 100))/100.0f; float y = ((int)(data[i+1] * 100))/100.0f; float l = ((int)(data[i+2] * 10))/10.0f; b.append("{"); b.append(x); b.append(","); b.append(y); b.append(","); b.append(l); b.append("}"); } b.append("}"); return b.toString(); } } public static class EmptyPath extends LayoutPathImpl { private AffineTransform tx; public EmptyPath(AffineTransform tx) { this.tx = tx; } public void pathToPoint(Point2D location, boolean preceding, Point2D point) { if (tx != null) { tx.transform(location, point); } else { point.setLocation(location); } } public boolean pointToPath(Point2D pt, Point2D result) { result.setLocation(pt); if (tx != null) { try { tx.inverseTransform(pt, result); } catch (NoninvertibleTransformException ex) { } } return result.getX() > 0; } public double start() { return 0; } public double end() { return 0; } public double length() { return 0; } public Shape mapShape(Shape s) { if (tx != null) { return tx.createTransformedShape(s); } return s; } } }