blob: d1be8baf1df4cc8f172ff7f1219b7ee646467d04 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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 accomodate 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}