1/* 2 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25/* 26 * (C) Copyright IBM Corp. 2005, All Rights Reserved. 27 */ 28package sun.font; 29 30// 31// This is the 'simple' mapping implementation. It does things the most 32// straightforward way even if that is a bit slow. It won't 33// handle complex paths efficiently, and doesn't handle closed paths. 34// 35 36import java.awt.Shape; 37import java.awt.font.LayoutPath; 38import java.awt.geom.AffineTransform; 39import java.awt.geom.GeneralPath; 40import java.awt.geom.NoninvertibleTransformException; 41import java.awt.geom.PathIterator; 42import java.awt.geom.Point2D; 43import java.util.Formatter; 44import java.util.ArrayList; 45 46import static java.awt.geom.PathIterator.*; 47import static java.lang.Math.abs; 48import static java.lang.Math.sqrt; 49 50public abstract class LayoutPathImpl extends LayoutPath { 51 52 // 53 // Convenience APIs 54 // 55 56 public Point2D pointToPath(double x, double y) { 57 Point2D.Double pt = new Point2D.Double(x, y); 58 pointToPath(pt, pt); 59 return pt; 60 } 61 62 public Point2D pathToPoint(double a, double o, boolean preceding) { 63 Point2D.Double pt = new Point2D.Double(a, o); 64 pathToPoint(pt, preceding, pt); 65 return pt; 66 } 67 68 public void pointToPath(double x, double y, Point2D pt) { 69 pt.setLocation(x, y); 70 pointToPath(pt, pt); 71 } 72 73 public void pathToPoint(double a, double o, boolean preceding, Point2D pt) { 74 pt.setLocation(a, o); 75 pathToPoint(pt, preceding, pt); 76 } 77 78 // 79 // extra utility APIs 80 // 81 82 public abstract double start(); 83 public abstract double end(); 84 public abstract double length(); 85 public abstract Shape mapShape(Shape s); 86 87 // 88 // debugging flags 89 // 90 91 private static final boolean LOGMAP = false; 92 private static final Formatter LOG = new Formatter(System.out); 93 94 /** 95 * Indicate how positions past the start and limit of the 96 * path are treated. PINNED adjusts these positions so 97 * as to be within start and limit. EXTENDED ignores the 98 * start and limit and effectively extends the first and 99 * last segments of the path 'infinitely'. CLOSED wraps 100 * positions around the ends of the path. 101 */ 102 public static enum EndType { 103 PINNED, EXTENDED, CLOSED; 104 public boolean isPinned() { return this == PINNED; } 105 public boolean isExtended() { return this == EXTENDED; } 106 public boolean isClosed() { return this == CLOSED; } 107 }; 108 109 // 110 // Top level construction. 111 // 112 113 /** 114 * Return a path representing the path from the origin through the points in order. 115 */ 116 public static LayoutPathImpl getPath(EndType etype, double ... coords) { 117 if ((coords.length & 0x1) != 0) { 118 throw new IllegalArgumentException("odd number of points not allowed"); 119 } 120 121 return SegmentPath.get(etype, coords); 122 } 123 124 /** 125 * Use to build a SegmentPath. This takes the data and preanalyzes it for 126 * information that the SegmentPath needs, then constructs a SegmentPath 127 * from that. Mainly, this lets SegmentPath cache the lengths along 128 * the path to each line segment, and so avoid calculating them over and over. 129 */ 130 public static final class SegmentPathBuilder { 131 private double[] data; 132 private int w; 133 private double px; 134 private double py; 135 private double a; 136 private boolean pconnect; 137 138 /** 139 * Construct a SegmentPathBuilder. 140 */ 141 public SegmentPathBuilder() { 142 } 143 144 /** 145 * Reset the builder for a new path. Datalen is a hint of how many 146 * points will be in the path, and the working buffer will be sized 147 * to accommodate at least this number of points. If datalen is zero, 148 * the working buffer is freed (it will be allocated on first use). 149 */ 150 public void reset(int datalen) { 151 if (data == null || datalen > data.length) { 152 data = new double[datalen]; 153 } else if (datalen == 0) { 154 data = null; 155 } 156 w = 0; 157 px = py = 0; 158 pconnect = false; 159 } 160 161 /** 162 * Automatically build from a list of points represented by pairs of 163 * doubles. Initial advance is zero. 164 */ 165 public SegmentPath build(EndType etype, double... pts) { 166 assert(pts.length % 2 == 0); 167 168 reset(pts.length / 2 * 3); 169 170 for (int i = 0; i < pts.length; i += 2) { 171 nextPoint(pts[i], pts[i+1], i != 0); 172 } 173 174 return complete(etype); 175 } 176 177 /** 178 * Move to a new point. If there is no data, this will become the 179 * first point. If there is data, and the previous call was a lineTo, this 180 * point is checked against the previous point, and if different, this 181 * starts a new segment at the same advance as the end of the last 182 * segment. If there is data, and the previous call was a moveTo, this 183 * replaces the point used for that previous call. 184 * 185 * Calling this is optional, lineTo will suffice and the initial point 186 * will be set to 0, 0. 187 */ 188 public void moveTo(double x, double y) { 189 nextPoint(x, y, false); 190 } 191 192 /** 193 * Connect to a new point. If there is no data, the previous point 194 * is presumed to be 0, 0. This point is checked against 195 * the previous point, and if different, this point is added to 196 * the path and the advance extended. If this point is the same as the 197 * previous point, the path remains unchanged. 198 */ 199 public void lineTo(double x, double y) { 200 nextPoint(x, y, true); 201 } 202 203 /** 204 * Add a new point, and increment advance if connect is true. 205 * 206 * This automatically rejects duplicate points and multiple disconnected points. 207 */ 208 private void nextPoint(double x, double y, boolean connect) { 209 210 // if zero length move or line, ignore 211 if (x == px && y == py) { 212 return; 213 } 214 215 if (w == 0) { // this is the first point, make sure we have space 216 if (data == null) { 217 data = new double[6]; 218 } 219 if (connect) { 220 w = 3; // default first point to 0, 0 221 } 222 } 223 224 // if multiple disconnected move, just update position, leave advance alone 225 if (w != 0 && !connect && !pconnect) { 226 data[w-3] = px = x; 227 data[w-2] = py = y; 228 return; 229 } 230 231 // grow data to deal with new point 232 if (w == data.length) { 233 double[] t = new double[w * 2]; 234 System.arraycopy(data, 0, t, 0, w); 235 data = t; 236 } 237 238 if (connect) { 239 double dx = x - px; 240 double dy = y - py; 241 a += sqrt(dx * dx + dy * dy); 242 } 243 244 // update data 245 data[w++] = x; 246 data[w++] = y; 247 data[w++] = a; 248 249 // update state 250 px = x; 251 py = y; 252 pconnect = connect; 253 } 254 255 public SegmentPath complete() { 256 return complete(EndType.EXTENDED); 257 } 258 259 /** 260 * Complete building a SegmentPath. Once this is called, the builder is restored 261 * to its initial state and information about the previous path is released. The 262 * end type indicates whether to treat the path as closed, extended, or pinned. 263 */ 264 public SegmentPath complete(EndType etype) { 265 SegmentPath result; 266 267 if (data == null || w < 6) { 268 return null; 269 } 270 271 if (w == data.length) { 272 result = new SegmentPath(data, etype); 273 reset(0); // releases pointer to data 274 } else { 275 double[] dataToAdopt = new double[w]; 276 System.arraycopy(data, 0, dataToAdopt, 0, w); 277 result = new SegmentPath(dataToAdopt, etype); 278 reset(2); // reuses data, since we held on to it 279 } 280 281 return result; 282 } 283 } 284 285 /** 286 * Represents a path built from segments. Each segment is 287 * represented by a triple: x, y, and cumulative advance. 288 * These represent the end point of the segment. The start 289 * point of the first segment is represented by the triple 290 * at position 0. 291 * 292 * The path might have breaks in it, e.g. it is not connected. 293 * These will be represented by pairs of triplets that share the 294 * same advance. 295 * 296 * The path might be extended, pinned, or closed. If extended, 297 * the initial and final segments are considered to extend 298 * 'indefinitely' past the bounds of the advance. If pinned, 299 * they end at the bounds of the advance. If closed, 300 * advances before the start or after the end 'wrap around' the 301 * path. 302 * 303 * The start of the path is the initial triple. This provides 304 * the nominal advance at the given x, y position (typically 305 * zero). The end of the path is the final triple. This provides 306 * the advance at the end, the total length of the path is 307 * thus the ending advance minus the starting advance. 308 * 309 * Note: We might want to cache more auxiliary data than the 310 * advance, but this seems adequate for now. 311 */ 312 public static final class SegmentPath extends LayoutPathImpl { 313 private double[] data; // triplets x, y, a 314 EndType etype; 315 316 public static SegmentPath get(EndType etype, double... pts) { 317 return new SegmentPathBuilder().build(etype, pts); 318 } 319 320 /** 321 * Internal, use SegmentPathBuilder or one of the static 322 * helper functions to construct a SegmentPath. 323 */ 324 SegmentPath(double[] data, EndType etype) { 325 this.data = data; 326 this.etype = etype; 327 } 328 329 // 330 // LayoutPath API 331 // 332 333 public void pathToPoint(Point2D location, boolean preceding, Point2D point) { 334 locateAndGetIndex(location, preceding, point); 335 } 336 337 // the path consists of line segments, which i'll call 338 // 'path vectors'. call each run of path vectors a 'path segment'. 339 // no path vector in a path segment is zero length (in the 340 // data, such vectors start a new path segment). 341 // 342 // for each path segment... 343 // 344 // for each path vector... 345 // 346 // we look at the dot product of the path vector and the vector from the 347 // origin of the path vector to the test point. if <0 (case 348 // A), the projection of the test point is before the start of 349 // the path vector. if > the square of the length of the path vector 350 // (case B), the projection is past the end point of the 351 // path vector. otherwise (case C), it lies on the path vector. 352 // determine the closeset point on the path vector. if case A, it 353 // is the start of the path vector. if case B and this is the last 354 // path vector in the path segment, it is the end of the path vector. If 355 // case C, it is the projection onto the path vector. Otherwise 356 // there is no closest point. 357 // 358 // if we have a closest point, compare the distance from it to 359 // the test point against our current closest distance. 360 // (culling should be fast, currently i am using distance 361 // squared, but there's probably better ways). if we're 362 // closer, save the new point as the current closest point, 363 // and record the path vector index so we can determine the final 364 // info if this turns out to be the closest point in the end. 365 // 366 // after we have processed all the segments we will have 367 // tested each path vector and each endpoint. if our point is not on 368 // an endpoint, we're done; we can compute the position and 369 // offset again, or if we saved it off we can just use it. if 370 // we're on an endpoint we need to see which path vector we should 371 // associate with. if we're at the start or end of a path segment, 372 // we're done-- the first or last vector of the segment is the 373 // one we associate with. we project against that vector to 374 // get the offset, and pin to that vector to get the length. 375 // 376 // otherwise, we compute the information as follows. if the 377 // dot product (see above) with the following vector is zero, 378 // we associate with that vector. otherwise, if the dot 379 // product with the previous vector is zero, we associate with 380 // that vector. otherwise we're beyond the end of the 381 // previous vector and before the start of the current vector. 382 // we project against both vectors and get the distance from 383 // the test point to the projection (this will be the offset). 384 // if they are the same, we take the following vector. 385 // otherwise use the vector from which the test point is the 386 // _farthest_ (this is because the point lies most clearly in 387 // the half of the plane defined by extending that vector). 388 // 389 // the returned position is the path length to the (possibly 390 // pinned) point, the offset is the projection onto the line 391 // along the vector, and we have a boolean flag which if false 392 // indicates that we associate with the previous vector at a 393 // junction (which is necessary when projecting such a 394 // location back to a point). 395 396 public boolean pointToPath(Point2D pt, Point2D result) { 397 double x = pt.getX(); // test point 398 double y = pt.getY(); 399 400 double bx = data[0]; // previous point 401 double by = data[1]; 402 double bl = data[2]; 403 404 // start with defaults 405 double cd2 = Double.MAX_VALUE; // current best distance from path, squared 406 double cx = 0; // current best x 407 double cy = 0; // current best y 408 double cl = 0; // current best position along path 409 int ci = 0; // current best index into data 410 411 for (int i = 3; i < data.length; i += 3) { 412 double nx = data[i]; // current end point 413 double ny = data[i+1]; 414 double nl = data[i+2]; 415 416 double dx = nx - bx; // vector from previous to current 417 double dy = ny - by; 418 double dl = nl - bl; 419 420 double px = x - bx; // vector from previous to test point 421 double py = y - by; 422 423 // determine sign of dot product of vectors from bx, by 424 // if < 0, we're before the start of this vector 425 426 double dot = dx * px + dy * py; // dot product 427 double vcx, vcy, vcl; // hold closest point on vector as x, y, l 428 int vi; // hold index of line, is data.length if last point on path 429 do { // use break below, lets us avoid initializing vcx, vcy... 430 if (dl == 0 || // moveto, or 431 (dot < 0 && // before path vector and 432 (!etype.isExtended() || 433 i != 3))) { // closest point is start of vector 434 vcx = bx; 435 vcy = by; 436 vcl = bl; 437 vi = i; 438 } else { 439 double l2 = dl * dl; // aka dx * dx + dy * dy, square of length 440 if (dot <= l2 || // closest point is not past end of vector, or 441 (etype.isExtended() && // we're extended and at the last segment 442 i == data.length - 3)) { 443 double p = dot / l2; // get parametric along segment 444 vcx = bx + p * dx; // compute closest point 445 vcy = by + p * dy; 446 vcl = bl + p * dl; 447 vi = i; 448 } else { 449 if (i == data.length - 3) { 450 vcx = nx; // special case, always test last point 451 vcy = ny; 452 vcl = nl; 453 vi = data.length; 454 } else { 455 break; // typical case, skip point, we'll pick it up next iteration 456 } 457 } 458 } 459 460 double tdx = x - vcx; // compute distance from (usually pinned) projection to test point 461 double tdy = y - vcy; 462 double td2 = tdx * tdx + tdy * tdy; 463 if (td2 <= cd2) { // new closest point, record info on it 464 cd2 = td2; 465 cx = vcx; 466 cy = vcy; 467 cl = vcl; 468 ci = vi; 469 } 470 } while (false); 471 472 bx = nx; 473 by = ny; 474 bl = nl; 475 } 476 477 // we have our closest point, get the info 478 bx = data[ci-3]; 479 by = data[ci-2]; 480 if (cx != bx || cy != by) { // not on endpoint, no need to resolve 481 double nx = data[ci]; 482 double ny = data[ci+1]; 483 double co = sqrt(cd2); // have a true perpendicular, so can use distance 484 if ((x-cx)*(ny-by) > (y-cy)*(nx-bx)) { 485 co = -co; // determine sign of offset 486 } 487 result.setLocation(cl, co); 488 return false; 489 } else { // on endpoint, we need to resolve which segment 490 boolean havePrev = ci != 3 && data[ci-1] != data[ci-4]; 491 boolean haveFoll = ci != data.length && data[ci-1] != data[ci+2]; 492 boolean doExtend = etype.isExtended() && (ci == 3 || ci == data.length); 493 if (havePrev && haveFoll) { 494 Point2D.Double pp = new Point2D.Double(x, y); 495 calcoffset(ci - 3, doExtend, pp); 496 Point2D.Double fp = new Point2D.Double(x, y); 497 calcoffset(ci, doExtend, fp); 498 if (abs(pp.y) > abs(fp.y)) { 499 result.setLocation(pp); 500 return true; // associate with previous 501 } else { 502 result.setLocation(fp); 503 return false; // associate with following 504 } 505 } else if (havePrev) { 506 result.setLocation(x, y); 507 calcoffset(ci - 3, doExtend, result); 508 return true; 509 } else { 510 result.setLocation(x, y); 511 calcoffset(ci, doExtend, result); 512 return false; 513 } 514 } 515 } 516 517 /** 518 * Return the location of the point passed in result as mapped to the 519 * line indicated by index. If doExtend is true, extend the 520 * x value without pinning to the ends of the line. 521 * this assumes that index is valid and references a line that has 522 * non-zero length. 523 */ 524 private void calcoffset(int index, boolean doExtend, Point2D result) { 525 double bx = data[index-3]; 526 double by = data[index-2]; 527 double px = result.getX() - bx; 528 double py = result.getY() - by; 529 double dx = data[index] - bx; 530 double dy = data[index+1] - by; 531 double l = data[index+2] - data[index - 1]; 532 533 // rx = A dot B / |B| 534 // ry = A dot invB / |B| 535 double rx = (px * dx + py * dy) / l; 536 double ry = (px * -dy + py * dx) / l; 537 if (!doExtend) { 538 if (rx < 0) rx = 0; 539 else if (rx > l) rx = l; 540 } 541 rx += data[index-1]; 542 result.setLocation(rx, ry); 543 } 544 545 // 546 // LayoutPathImpl API 547 // 548 549 public Shape mapShape(Shape s) { 550 return new Mapper().mapShape(s); 551 } 552 553 public double start() { 554 return data[2]; 555 } 556 557 public double end() { 558 return data[data.length - 1]; 559 } 560 561 public double length() { 562 return data[data.length-1] - data[2]; 563 } 564 565 // 566 // Utilities 567 // 568 569 /** 570 * Get the 'modulus' of an advance on a closed path. 571 */ 572 private double getClosedAdvance(double a, boolean preceding) { 573 if (etype.isClosed()) { 574 a -= data[2]; 575 int count = (int)(a/length()); 576 a -= count * length(); 577 if (a < 0 || (a == 0 && preceding)) { 578 a += length(); 579 580 } 581 a += data[2]; 582 } 583 return a; 584 } 585 586 /** 587 * Return the index of the segment associated with advance. This 588 * points to the start of the triple and is a multiple of 3 between 589 * 3 and data.length-3 inclusive. It never points to a 'moveto' triple. 590 * 591 * If the path is closed, 'a' is mapped to 592 * a value between the start and end of the path, inclusive. 593 * If preceding is true, and 'a' lies on a segment boundary, 594 * return the index of the preceding segment, else return the index 595 * of the current segment (if it is not a moveto segment) otherwise 596 * the following segment (which is never a moveto segment). 597 * 598 * Note: if the path is not closed, the advance might not actually 599 * lie on the returned segment-- it might be before the first, or 600 * after the last. The first or last segment (as appropriate) 601 * will be returned in this case. 602 */ 603 private int getSegmentIndexForAdvance(double a, boolean preceding) { 604 // must have local advance 605 a = getClosedAdvance(a, preceding); 606 607 // note we must avoid 'moveto' segments. the first segment is 608 // always a moveto segment, so we always skip it. 609 int i, lim; 610 for (i = 5, lim = data.length-1; i < lim; i += 3) { 611 double v = data[i]; 612 if (a < v || (a == v && preceding)) { 613 break; 614 } 615 } 616 return i-2; // adjust to start of segment 617 } 618 619 /** 620 * Map a location based on the provided segment, returning in pt. 621 * Seg must be a valid 'lineto' segment. Note: if the path is 622 * closed, x must be within the start and end of the path. 623 */ 624 private void map(int seg, double a, double o, Point2D pt) { 625 double dx = data[seg] - data[seg-3]; 626 double dy = data[seg+1] - data[seg-2]; 627 double dl = data[seg+2] - data[seg-1]; 628 629 double ux = dx/dl; // could cache these, but is it worth it? 630 double uy = dy/dl; 631 632 a -= data[seg-1]; 633 634 pt.setLocation(data[seg-3] + a * ux - o * uy, 635 data[seg-2] + a * uy + o * ux); 636 } 637 638 /** 639 * Map the point, and return the segment index. 640 */ 641 private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) { 642 double a = loc.getX(); 643 double o = loc.getY(); 644 int seg = getSegmentIndexForAdvance(a, preceding); 645 map(seg, a, o, result); 646 647 return seg; 648 } 649 650 // 651 // Mapping classes. 652 // Map the path onto each path segment. 653 // Record points where the advance 'enters' and 'exits' the path segment, and connect successive 654 // points when appropriate. 655 // 656 657 /** 658 * This represents a line segment from the iterator. Each target segment will 659 * interpret it, and since this process needs slope along the line 660 * segment, this lets us compute it once and pass it around easily. 661 */ 662 class LineInfo { 663 double sx, sy; // start 664 double lx, ly; // limit 665 double m; // slope dy/dx 666 667 /** 668 * Set the lineinfo to this line 669 */ 670 void set(double sx, double sy, double lx, double ly) { 671 this.sx = sx; 672 this.sy = sy; 673 this.lx = lx; 674 this.ly = ly; 675 double dx = lx - sx; 676 if (dx == 0) { 677 m = 0; // we'll check for this elsewhere 678 } else { 679 double dy = ly - sy; 680 m = dy / dx; 681 } 682 } 683 684 void set(LineInfo rhs) { 685 this.sx = rhs.sx; 686 this.sy = rhs.sy; 687 this.lx = rhs.lx; 688 this.ly = rhs.ly; 689 this.m = rhs.m; 690 } 691 692 /** 693 * Return true if we intersect the infinitely tall rectangle with 694 * lo <= x < hi. If we do, also return the pinned portion of ourselves in 695 * result. 696 */ 697 boolean pin(double lo, double hi, LineInfo result) { 698 result.set(this); 699 if (lx >= sx) { 700 if (sx < hi && lx >= lo) { 701 if (sx < lo) { 702 if (m != 0) result.sy = sy + m * (lo - sx); 703 result.sx = lo; 704 } 705 if (lx > hi) { 706 if (m != 0) result.ly = ly + m * (hi - lx); 707 result.lx = hi; 708 } 709 return true; 710 } 711 } else { 712 if (lx < hi && sx >= lo) { 713 if (lx < lo) { 714 if (m != 0) result.ly = ly + m * (lo - lx); 715 result.lx = lo; 716 } 717 if (sx > hi) { 718 if (m != 0) result.sy = sy + m * (hi - sx); 719 result.sx = hi; 720 } 721 return true; 722 } 723 } 724 return false; 725 } 726 727 /** 728 * Return true if we intersect the segment at ix. This takes 729 * the path end type into account and computes the relevant 730 * parameters to pass to pin(double, double, LineInfo). 731 */ 732 boolean pin(int ix, LineInfo result) { 733 double lo = data[ix-1]; 734 double hi = data[ix+2]; 735 switch (SegmentPath.this.etype) { 736 case PINNED: 737 break; 738 case EXTENDED: 739 if (ix == 3) lo = Double.NEGATIVE_INFINITY; 740 if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY; 741 break; 742 case CLOSED: 743 // not implemented 744 break; 745 } 746 747 return pin(lo, hi, result); 748 } 749 } 750 751 /** 752 * Each segment will construct its own general path, mapping the provided lines 753 * into its own simple space. 754 */ 755 class Segment { 756 final int ix; // index into data array for this segment 757 final double ux, uy; // unit vector 758 759 final LineInfo temp; // working line info 760 761 boolean broken; // true if a moveto has occurred since we last added to our path 762 double cx, cy; // last point in gp 763 GeneralPath gp; // path built for this segment 764 765 Segment(int ix) { 766 this.ix = ix; 767 double len = data[ix+2] - data[ix-1]; 768 this.ux = (data[ix] - data[ix-3]) / len; 769 this.uy = (data[ix+1] - data[ix-2]) / len; 770 this.temp = new LineInfo(); 771 } 772 773 void init() { 774 if (LOGMAP) LOG.format("s(%d) init\n", ix); 775 broken = true; 776 cx = cy = Double.MIN_VALUE; 777 this.gp = new GeneralPath(); 778 } 779 780 void move() { 781 if (LOGMAP) LOG.format("s(%d) move\n", ix); 782 broken = true; 783 } 784 785 void close() { 786 if (!broken) { 787 if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix); 788 gp.closePath(); 789 } 790 } 791 792 void line(LineInfo li) { 793 if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly); 794 795 if (li.pin(ix, temp)) { 796 if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly); 797 798 temp.sx -= data[ix-1]; 799 double sx = data[ix-3] + temp.sx * ux - temp.sy * uy; 800 double sy = data[ix-2] + temp.sx * uy + temp.sy * ux; 801 temp.lx -= data[ix-1]; 802 double lx = data[ix-3] + temp.lx * ux - temp.ly * uy; 803 double ly = data[ix-2] + temp.lx * uy + temp.ly * ux; 804 805 if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly); 806 807 if (sx != cx || sy != cy) { 808 if (broken) { 809 if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy); 810 gp.moveTo((float)sx, (float)sy); 811 } else { 812 if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy); 813 gp.lineTo((float)sx, (float)sy); 814 } 815 } 816 if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly); 817 gp.lineTo((float)lx, (float)ly); 818 819 broken = false; 820 cx = lx; 821 cy = ly; 822 } 823 } 824 } 825 826 class Mapper { 827 final LineInfo li; // working line info 828 final ArrayList<Segment> segments; // cache additional data on segments, working objects 829 final Point2D.Double mpt; // last moveto source point 830 final Point2D.Double cpt; // current source point 831 boolean haveMT; // true when last op was a moveto 832 833 Mapper() { 834 li = new LineInfo(); 835 segments = new ArrayList<Segment>(); 836 for (int i = 3; i < data.length; i += 3) { 837 if (data[i+2] != data[i-1]) { // a new segment 838 segments.add(new Segment(i)); 839 } 840 } 841 842 mpt = new Point2D.Double(); 843 cpt = new Point2D.Double(); 844 } 845 846 void init() { 847 if (LOGMAP) LOG.format("init\n"); 848 haveMT = false; 849 for (Segment s: segments) { 850 s.init(); 851 } 852 } 853 854 void moveTo(double x, double y) { 855 if (LOGMAP) LOG.format("moveto %g, %g\n", x, y); 856 mpt.x = x; 857 mpt.y = y; 858 haveMT = true; 859 } 860 861 void lineTo(double x, double y) { 862 if (LOGMAP) LOG.format("lineto %g, %g\n", x, y); 863 864 if (haveMT) { 865 // prepare previous point for no-op check 866 cpt.x = mpt.x; 867 cpt.y = mpt.y; 868 } 869 870 if (x == cpt.x && y == cpt.y) { 871 // lineto is a no-op 872 return; 873 } 874 875 if (haveMT) { 876 // current point is the most recent moveto point 877 haveMT = false; 878 for (Segment s: segments) { 879 s.move(); 880 } 881 } 882 883 li.set(cpt.x, cpt.y, x, y); 884 for (Segment s: segments) { 885 s.line(li); 886 } 887 888 cpt.x = x; 889 cpt.y = y; 890 } 891 892 void close() { 893 if (LOGMAP) LOG.format("close\n"); 894 lineTo(mpt.x, mpt.y); 895 for (Segment s: segments) { 896 s.close(); 897 } 898 } 899 900 public Shape mapShape(Shape s) { 901 if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this); 902 PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves. 903 904 if (LOGMAP) LOG.format("start\n"); 905 init(); 906 907 final double[] coords = new double[2]; 908 while (!pi.isDone()) { 909 switch (pi.currentSegment(coords)) { 910 case SEG_CLOSE: close(); break; 911 case SEG_MOVETO: moveTo(coords[0], coords[1]); break; 912 case SEG_LINETO: lineTo(coords[0], coords[1]); break; 913 default: break; 914 } 915 916 pi.next(); 917 } 918 if (LOGMAP) LOG.format("finish\n\n"); 919 920 GeneralPath gp = new GeneralPath(); 921 for (Segment seg: segments) { 922 gp.append(seg.gp, false); 923 } 924 return gp; 925 } 926 } 927 928 // 929 // for debugging 930 // 931 932 public String toString() { 933 StringBuilder b = new StringBuilder(); 934 b.append("{"); 935 b.append(etype.toString()); 936 b.append(" "); 937 for (int i = 0; i < data.length; i += 3) { 938 if (i > 0) { 939 b.append(","); 940 } 941 float x = ((int)(data[i] * 100))/100.0f; 942 float y = ((int)(data[i+1] * 100))/100.0f; 943 float l = ((int)(data[i+2] * 10))/10.0f; 944 b.append("{"); 945 b.append(x); 946 b.append(","); 947 b.append(y); 948 b.append(","); 949 b.append(l); 950 b.append("}"); 951 } 952 b.append("}"); 953 return b.toString(); 954 } 955 } 956 957 958 public static class EmptyPath extends LayoutPathImpl { 959 private AffineTransform tx; 960 961 public EmptyPath(AffineTransform tx) { 962 this.tx = tx; 963 } 964 965 public void pathToPoint(Point2D location, boolean preceding, Point2D point) { 966 if (tx != null) { 967 tx.transform(location, point); 968 } else { 969 point.setLocation(location); 970 } 971 } 972 973 public boolean pointToPath(Point2D pt, Point2D result) { 974 result.setLocation(pt); 975 if (tx != null) { 976 try { 977 tx.inverseTransform(pt, result); 978 } 979 catch (NoninvertibleTransformException ex) { 980 } 981 } 982 return result.getX() > 0; 983 } 984 985 public double start() { return 0; } 986 987 public double end() { return 0; } 988 989 public double length() { return 0; } 990 991 public Shape mapShape(Shape s) { 992 if (tx != null) { 993 return tx.createTransformedShape(s); 994 } 995 return s; 996 } 997 } 998} 999