2
|
1 |
/*
|
5506
|
2 |
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
|
2
|
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
|
5506
|
7 |
* published by the Free Software Foundation. Oracle designates this
|
2
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
5506
|
9 |
* by Oracle in the LICENSE file that accompanied this code.
|
2
|
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 |
*
|
5506
|
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.
|
2
|
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 |
}
|