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