History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/font/LayoutPathImpl.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/font/LayoutPathImpl.java+998−0
@@ -0,0 +1,998 @@
1+/*
2+ * Copyright (c) 2005, 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+ */
28+package 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+
36+import java.awt.Shape;
37+import java.awt.font.LayoutPath;
38+import java.awt.geom.AffineTransform;
39+import java.awt.geom.GeneralPath;
40+import java.awt.geom.NoninvertibleTransformException;
41+import java.awt.geom.PathIterator;
42+import java.awt.geom.Point2D;
43+import java.util.Formatter;
44+import java.util.ArrayList;
45+
46+import static java.awt.geom.PathIterator.*;
47+import static java.lang.Math.abs;
48+import static java.lang.Math.sqrt;
49+
50+public 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+}