Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/font/LayoutPathImpl.java
41155 views
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
*/
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 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
1000