-
Notifications
You must be signed in to change notification settings - Fork 5.3k
/
StandardGlyphVector.java
1928 lines (1684 loc) · 69.6 KB
/
StandardGlyphVector.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.font;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import static java.awt.RenderingHints.*;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphJustificationInfo;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.SoftReference;
import java.text.CharacterIterator;
import sun.awt.SunHints;
import sun.java2d.loops.FontInfo;
/**
* Standard implementation of GlyphVector used by Font, GlyphList, and
* SunGraphics2D.
*
* The main issues involve the semantics of the various transforms
* (font, glyph, device) and their effect on rendering and metrics.
*
* Very, very unfortunately, the translation component of the font
* transform affects where the text gets rendered. It offsets the
* rendering origin. None of the other metrics of the glyphvector
* are affected, making them inconsistent with the rendering behavior.
* I think the translation component of the font would be better
* interpreted as the translation component of a per-glyph transform,
* but I don't know if this is possible to change.
*
* After the font transform is applied, the glyph transform is
* applied. This makes glyph transforms relative to font transforms,
* if the font transform changes, the glyph transform will have the
* same (relative) effect on the outline of the glyph. The outline
* and logical bounds are passed through the glyph transform before
* being returned. The glyph metrics ignore the glyph transform, but
* provide the outline bounds and the advance vector of the glyph (the
* latter will be rotated if the font is rotated). The default layout
* places each glyph at the end of the advance vector of the previous
* glyph, and since the glyph transform translates the advance vector,
* this means a glyph transform affects the positions of all
* subsequent glyphs if defaultLayout is called after setting a glyph
* transform. In the glyph info array, the bounds are the outline
* bounds including the glyph transform, and the positions are as
* computed, and the advances are the deltas between the positions.
*
* (There's a bug in the logical bounds of a rotated glyph for
* composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The
* problem is that the rotated composite doesn't handle the multiple
* ascents and descents properly in both x and y. You end up with
* a rotated advance vector but an unrotated ascent and descent.)
*
* Finally, the whole thing is transformed by the device transform to
* position it on the page.
*
* Another bug: The glyph outline seems to ignore fractional point
* size information, but the images (and advances) don't ignore it.
*
* Small fonts drawn at large magnification have odd advances when
* fractional metrics is off-- that's because the advances depend on
* the frc. When the frc is scaled appropriately, the advances are
* fine. FM or a large frc (high numbers) make the advances right.
*
* The buffer aa flag doesn't affect rendering, the glyph vector
* renders as AA if aa is set in its frc, and as non-aa if aa is not
* set in its frc.
*
* font rotation, baseline, vertical etc.
*
* Font rotation and baseline Line metrics should be measured along a
* unit vector pi/4 cc from the baseline vector. For 'horizontal'
* fonts the baseline vector is the x vector passed through the font
* transform (ignoring translation), for 'vertical' it is the y
* vector. This definition makes ascent, descent, etc independent of
* shear, so shearing can be used to simulate italic. This means no
* fonts have 'negative ascents' or 'zero ascents' etc.
*
* Having a coordinate system with orthogonal axes where one is
* parallel to the baseline means we could use rectangles and interpret
* them in terms of this coordinate system. Unfortunately there
* is support for rotated fonts in the jdk already so maintaining
* the semantics of existing code (getlogical bounds, etc) might
* be difficult.
*
* A font transform transforms both the baseline and all the glyphs
* in the font, so it does not rotate the glyph w.r.t the baseline.
* If you do want to rotate individual glyphs, you need to apply a
* glyph transform. If performDefaultLayout is called after this,
* the transformed glyph advances will affect the glyph positions.
*
* useful additions
* - select vertical metrics - glyphs are rotated pi/4 cc and vertical
* metrics are used to align them to the baseline.
* - define baseline for font (glyph rotation not linked to baseline)
* - define extra space (delta between each glyph along baseline)
* - define offset (delta from 'true' baseline, impacts ascent and
* descent as these are still computed from true basline and pinned
* to zero, used in superscript).
*/
public class StandardGlyphVector extends GlyphVector {
private Font font;
private FontRenderContext frc;
private int[] glyphs; // always
private int[] userGlyphs; // used to return glyphs to the client.
private float[] positions; // only if not default advances
private int[] charIndices; // only if interesting
private int flags; // indicates whether positions, charIndices is interesting
private static final int UNINITIALIZED_FLAGS = -1;
// transforms information
private GlyphTransformInfo gti; // information about per-glyph transforms
// !!! can we get rid of any of this extra stuff?
private AffineTransform ftx; // font transform without translation
private AffineTransform dtx; // device transform used for strike calculations, no translation
private AffineTransform invdtx; // inverse of dtx or null if dtx is identity
private AffineTransform frctx; // font render context transform, wish we could just share it
private Font2D font2D; // basic strike-independent stuff
private SoftReference<GlyphStrike> fsref; // font strike reference for glyphs with no per-glyph transform
/////////////////////////////
// Constructors and Factory methods
/////////////////////////////
public StandardGlyphVector(Font font, String str, FontRenderContext frc) {
init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);
}
public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
}
public StandardGlyphVector(Font font, char[] text, int start, int count,
FontRenderContext frc) {
init(font, text, start, count, frc, UNINITIALIZED_FLAGS);
}
private float getTracking(Font font) {
if (font.hasLayoutAttributes()) {
AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();
return values.getTracking();
}
return 0;
}
// used by GlyphLayout to construct a glyphvector
public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
int[] indices, int flags) {
initGlyphVector(font, frc, glyphs, positions, indices, flags);
// this code should go into layout
float track = getTracking(font);
if (track != 0) {
track *= font.getSize2D();
Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta
if (font.isTransformed()) {
AffineTransform at = font.getTransform();
at.deltaTransform(trackPt, trackPt);
}
// how do we know its a base glyph
// for now, it is if the natural advance of the glyph is non-zero
float[] deltas = { trackPt.x, trackPt.y };
for (int j = 0; j < deltas.length; ++j) {
float inc = deltas[j];
float prevPos = 0;
if (inc != 0) {
float delta = 0;
for (int i = j; i < positions.length; i += 2) {
if (i == j || prevPos != positions[i]) {
prevPos = positions[i];
positions[i] += delta;
delta += inc;
} else if (prevPos == positions[i]) {
positions[i] = positions[i - 2];
}
}
}
}
}
}
public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
int[] indices, int flags) {
this.font = font;
this.frc = frc;
this.glyphs = glyphs;
this.userGlyphs = glyphs; // no need to check
this.positions = positions;
this.charIndices = indices;
this.flags = flags;
initFontData();
}
public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) {
int offset = iter.getBeginIndex();
char[] text = new char [iter.getEndIndex() - offset];
for(char c = iter.first();
c != CharacterIterator.DONE;
c = iter.next()) {
text[iter.getIndex() - offset] = c;
}
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
}
public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) {
// !!! find callers of this
// should be able to fully init from raw data, e.g. charmap, flags too.
this.font = font;
this.frc = frc;
this.flags = UNINITIALIZED_FLAGS;
initFontData();
this.userGlyphs = glyphs;
this.glyphs = getValidatedGlyphs(this.userGlyphs);
}
/* This is called from the rendering loop. FontInfo is supplied
* because a GV caches a strike and glyph images suitable for its FRC.
* LCD text isn't currently supported on all surfaces, in which case
* standard AA must be used. This is most likely to occur when LCD text
* is requested and the surface is some non-standard type or hardware
* surface for which there are no accelerated loops.
* We can detect this as being AA=="ON" in the FontInfo and AA!="ON"
* and AA!="GASP" in the FRC - since this only occurs for LCD text we don't
* need to check any more precisely what value is in the FRC.
*/
public static StandardGlyphVector getStandardGV(GlyphVector gv,
FontInfo info) {
if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) {
Object aaHint = gv.getFontRenderContext().getAntiAliasingHint();
if (aaHint != VALUE_TEXT_ANTIALIAS_ON &&
aaHint != VALUE_TEXT_ANTIALIAS_GASP) {
/* We need to create a new GV with AA==ON for rendering */
FontRenderContext frc = gv.getFontRenderContext();
frc = new FontRenderContext(frc.getTransform(),
VALUE_TEXT_ANTIALIAS_ON,
frc.getFractionalMetricsHint());
return new StandardGlyphVector(gv, frc);
}
}
if (gv instanceof StandardGlyphVector) {
return (StandardGlyphVector)gv;
}
return new StandardGlyphVector(gv, gv.getFontRenderContext());
}
/////////////////////////////
// GlyphVector API
/////////////////////////////
public Font getFont() {
return this.font;
}
public FontRenderContext getFontRenderContext() {
return this.frc;
}
public void performDefaultLayout() {
positions = null;
if (getTracking(font) == 0) {
clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
}
}
public int getNumGlyphs() {
return glyphs.length;
}
public int getGlyphCode(int glyphIndex) {
return userGlyphs[glyphIndex];
}
public int[] getGlyphCodes(int start, int count, int[] result) {
if (count < 0) {
throw new IllegalArgumentException("count = " + count);
}
if (start < 0) {
throw new IndexOutOfBoundsException("start = " + start);
}
if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge
throw new IndexOutOfBoundsException("start + count = " + (start + count));
}
if (result == null) {
result = new int[count];
}
// if arraycopy were faster, we wouldn't code this
for (int i = 0; i < count; ++i) {
result[i] = userGlyphs[i + start];
}
return result;
}
public int getGlyphCharIndex(int ix) {
if (ix < 0 && ix >= glyphs.length) {
throw new IndexOutOfBoundsException("" + ix);
}
if (charIndices == null) {
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
return glyphs.length - 1 - ix;
}
return ix;
}
return charIndices[ix];
}
public int[] getGlyphCharIndices(int start, int count, int[] result) {
if (start < 0 || count < 0 || (count > glyphs.length - start)) {
throw new IndexOutOfBoundsException("" + start + ", " + count);
}
if (result == null) {
result = new int[count];
}
if (charIndices == null) {
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
for (int i = 0, n = glyphs.length - 1 - start;
i < count; ++i, --n) {
result[i] = n;
}
} else {
for (int i = 0, n = start; i < count; ++i, ++n) {
result[i] = n;
}
}
} else {
for (int i = 0; i < count; ++i) {
result[i] = charIndices[i + start];
}
}
return result;
}
// !!! not cached, assume TextLayout will cache if necessary
// !!! reexamine for per-glyph-transforms
// !!! revisit for text-on-a-path, vertical
public Rectangle2D getLogicalBounds() {
setFRCTX();
initPositions();
LineMetrics lm = font.getLineMetrics("", frc);
float minX, minY, maxX, maxY;
// horiz only for now...
minX = 0;
minY = -lm.getAscent();
maxX = 0;
maxY = lm.getDescent() + lm.getLeading();
if (glyphs.length > 0) {
maxX = positions[positions.length - 2];
}
return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);
}
// !!! not cached, assume TextLayout will cache if necessary
public Rectangle2D getVisualBounds() {
Rectangle2D result = null;
for (int i = 0; i < glyphs.length; ++i) {
Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();
if (!glyphVB.isEmpty()) {
if (result == null) {
result = glyphVB;
} else {
Rectangle2D.union(result, glyphVB, result);
}
}
}
if (result == null) {
result = new Rectangle2D.Float(0, 0, 0, 0);
}
return result;
}
// !!! not cached, assume TextLayout will cache if necessary
// !!! fontStrike needs a method for this
public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {
return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);
}
public Shape getOutline() {
return getGlyphsOutline(0, glyphs.length, 0, 0);
}
public Shape getOutline(float x, float y) {
return getGlyphsOutline(0, glyphs.length, x, y);
}
// relative to gv origin
public Shape getGlyphOutline(int ix) {
return getGlyphsOutline(ix, 1, 0, 0);
}
// relative to gv origin offset by x, y
public Shape getGlyphOutline(int ix, float x, float y) {
return getGlyphsOutline(ix, 1, x, y);
}
public Point2D getGlyphPosition(int ix) {
initPositions();
ix *= 2;
return new Point2D.Float(positions[ix], positions[ix + 1]);
}
public void setGlyphPosition(int ix, Point2D pos) {
if (ix < 0 || ix > glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
initPositions();
int ix2 = ix << 1;
positions[ix2] = (float)pos.getX();
positions[ix2 + 1] = (float)pos.getY();
if (ix < glyphs.length) {
clearCaches(ix);
}
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
}
public AffineTransform getGlyphTransform(int ix) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
if (gti != null) {
return gti.getGlyphTransform(ix);
}
return null; // spec'd as returning null
}
public void setGlyphTransform(int ix, AffineTransform newTX) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
if (gti == null) {
if (newTX == null || newTX.isIdentity()) {
return;
}
gti = new GlyphTransformInfo(this);
}
gti.setGlyphTransform(ix, newTX); // sets flags
if (gti.transformCount() == 0) {
gti = null;
}
}
public int getLayoutFlags() {
if (flags == UNINITIALIZED_FLAGS) {
flags = 0;
if (charIndices != null && glyphs.length > 1) {
boolean ltr = true;
boolean rtl = true;
int rtlix = charIndices.length; // rtl index
for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {
int cx = charIndices[i];
ltr = ltr && (cx == i);
rtl = rtl && (cx == --rtlix);
}
if (rtl) flags |= FLAG_RUN_RTL;
if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS;
}
}
return flags;
}
public float[] getGlyphPositions(int start, int count, float[] result) {
if (count < 0) {
throw new IllegalArgumentException("count = " + count);
}
if (start < 0) {
throw new IndexOutOfBoundsException("start = " + start);
}
if (start > glyphs.length + 1 - count) { // watch for overflow
throw new IndexOutOfBoundsException("start + count = " + (start + count));
}
return internalGetGlyphPositions(start, count, 0, result);
}
public Shape getGlyphLogicalBounds(int ix) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
Shape[] lbcache;
if (lbcacheRef == null || (lbcache = lbcacheRef.get()) == null) {
lbcache = new Shape[glyphs.length];
lbcacheRef = new SoftReference<>(lbcache);
}
Shape result = lbcache[ix];
if (result == null) {
setFRCTX();
initPositions();
// !!! ought to return a rectangle2d for simple cases, though the following works for all
// get the position, the tx offset, and the x,y advance and x,y adl. The
// shape is the box formed by adv (width) and adl (height) offset by
// the position plus the tx offset minus the ascent.
ADL adl = new ADL();
GlyphStrike gs = getGlyphStrike(ix);
gs.getADL(adl);
Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]);
float wx = adv.x;
float wy = adv.y;
float hx = adl.descentX + adl.leadingX + adl.ascentX;
float hy = adl.descentY + adl.leadingY + adl.ascentY;
float x = positions[ix*2] + gs.dx - adl.ascentX;
float y = positions[ix*2+1] + gs.dy - adl.ascentY;
GeneralPath gp = new GeneralPath();
gp.moveTo(x, y);
gp.lineTo(x + wx, y + wy);
gp.lineTo(x + wx + hx, y + wy + hy);
gp.lineTo(x + hx, y + hy);
gp.closePath();
result = new DelegatingShape(gp);
lbcache[ix] = result;
}
return result;
}
private SoftReference<Shape[]> lbcacheRef;
public Shape getGlyphVisualBounds(int ix) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
Shape[] vbcache;
if (vbcacheRef == null || (vbcache = vbcacheRef.get()) == null) {
vbcache = new Shape[glyphs.length];
vbcacheRef = new SoftReference<>(vbcache);
}
Shape result = vbcache[ix];
if (result == null) {
result = new DelegatingShape(getGlyphOutlineBounds(ix));
vbcache[ix] = result;
}
return result;
}
private SoftReference<Shape[]> vbcacheRef;
public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) {
return getGlyphsPixelBounds(renderFRC, x, y, index, 1);
}
public GlyphMetrics getGlyphMetrics(int ix) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D();
Point2D pt = getGlyphPosition(ix);
vb.setRect(vb.getMinX() - pt.getX(),
vb.getMinY() - pt.getY(),
vb.getWidth(),
vb.getHeight());
Point2D.Float adv =
getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]);
GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y,
vb,
GlyphMetrics.STANDARD);
return gm;
}
public GlyphJustificationInfo getGlyphJustificationInfo(int ix) {
if (ix < 0 || ix >= glyphs.length) {
throw new IndexOutOfBoundsException("ix = " + ix);
}
// currently we don't have enough information to do this right. should
// get info from the font and use real OT/GX justification. Right now
// sun/font/ExtendedTextSourceLabel assigns one of three infos
// based on whether the char is kanji, space, or other.
return null;
}
public boolean equals(GlyphVector rhs) {
if (this == rhs) {
return true;
}
if (rhs == null) {
return false;
}
try {
StandardGlyphVector other = (StandardGlyphVector)rhs;
if (glyphs.length != other.glyphs.length) {
return false;
}
for (int i = 0; i < glyphs.length; ++i) {
if (glyphs[i] != other.glyphs[i]) {
return false;
}
}
if (!font.equals(other.font)) {
return false;
}
if (!frc.equals(other.frc)) {
return false;
}
if ((other.positions == null) != (positions == null)) {
if (positions == null) {
initPositions();
} else {
other.initPositions();
}
}
if (positions != null) {
for (int i = 0; i < positions.length; ++i) {
if (positions[i] != other.positions[i]) {
return false;
}
}
}
if (gti == null) {
return other.gti == null;
} else {
return gti.equals(other.gti);
}
}
catch (ClassCastException e) {
// assume they are different simply by virtue of the class difference
return false;
}
}
/**
* As a concrete subclass of Object that implements equality, this must
* implement hashCode.
*/
public int hashCode() {
return font.hashCode() ^ glyphs.length;
}
/**
* Since we implement equality comparisons for GlyphVector, we implement
* the inherited Object.equals(Object) as well. GlyphVector should do
* this, and define two glyphvectors as not equal if the classes differ.
*/
public boolean equals(Object rhs) {
try {
return equals((GlyphVector)rhs);
}
catch (ClassCastException e) {
return false;
}
}
/**
* Sometimes I wish java had covariant return types...
*/
public StandardGlyphVector copy() {
return (StandardGlyphVector)clone();
}
/**
* As a concrete subclass of GlyphVector, this must implement clone.
*/
public Object clone() {
// positions, gti are mutable so we have to clone them
// font2d can be shared
// fsref is a cache and can be shared
try {
StandardGlyphVector result = (StandardGlyphVector)super.clone();
result.clearCaches();
if (positions != null) {
result.positions = positions.clone();
}
if (gti != null) {
result.gti = new GlyphTransformInfo(result, gti);
}
return result;
}
catch (CloneNotSupportedException e) {
}
return this;
}
//////////////////////
// StandardGlyphVector new public methods
/////////////////////
/*
* Set a multiple glyph positions at one time. GlyphVector only
* provides API to set a single glyph at a time.
*/
public void setGlyphPositions(float[] srcPositions, int srcStart,
int start, int count) {
if (count < 0) {
throw new IllegalArgumentException("count = " + count);
}
initPositions();
for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) {
positions[i] = srcPositions[p];
}
clearCaches();
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
}
/**
* Set all the glyph positions, including the 'after last glyph' position.
* The srcPositions array must be of length (numGlyphs + 1) * 2.
*/
public void setGlyphPositions(float[] srcPositions) {
int requiredLength = glyphs.length * 2 + 2;
if (srcPositions.length != requiredLength) {
throw new IllegalArgumentException("srcPositions.length != " + requiredLength);
}
positions = srcPositions.clone();
clearCaches();
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
}
/**
* This is a convenience overload that gets all the glyph positions, which
* is what you usually want to do if you're getting more than one.
* !!! should I bother taking result parameter?
*/
public float[] getGlyphPositions(float[] result) {
return internalGetGlyphPositions(0, glyphs.length + 1, 0, result);
}
/**
* Get transform information for the requested range of glyphs.
* If no glyphs have a transform, return null.
* If a glyph has no transform (or is the identity transform) its entry in the result array will be null.
* If the passed-in result is null an array will be allocated for the caller.
* Each transform instance in the result array will unique, and independent of the GlyphVector's transform.
*/
public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) {
if (start < 0 || count < 0 || start + count > glyphs.length) {
throw new IllegalArgumentException("start: " + start + " count: " + count);
}
if (gti == null) {
return null;
}
if (result == null) {
result = new AffineTransform[count];
}
for (int i = 0; i < count; ++i, ++start) {
result[i] = gti.getGlyphTransform(start);
}
return result;
}
/**
* Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int);
*/
public AffineTransform[] getGlyphTransforms() {
return getGlyphTransforms(0, glyphs.length, null);
}
/**
* Set a number of glyph transforms.
* Original transforms are unchanged. The array may contain nulls, and also may
* contain multiple references to the same transform instance.
*/
public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) {
for (int i = start, e = start + count; i < e; ++i) {
setGlyphTransform(i, srcTransforms[srcStart + i]);
}
}
/**
* Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int).
*/
public void setGlyphTransforms(AffineTransform[] srcTransforms) {
setGlyphTransforms(srcTransforms, 0, 0, glyphs.length);
}
/**
* For each glyph return posx, posy, advx, advy, visx, visy, visw, vish.
*/
public float[] getGlyphInfo() {
setFRCTX();
initPositions();
float[] result = new float[glyphs.length * 8];
for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) {
float x = positions[i*2];
float y = positions[i*2+1];
result[n] = x;
result[n+1] = y;
int glyphID = glyphs[i];
GlyphStrike s = getGlyphStrike(i);
Point2D.Float adv = s.strike.getGlyphMetrics(glyphID);
result[n+2] = adv.x;
result[n+3] = adv.y;
Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D();
result[n+4] = (float)(vb.getMinX());
result[n+5] = (float)(vb.getMinY());
result[n+6] = (float)(vb.getWidth());
result[n+7] = (float)(vb.getHeight());
}
return result;
}
//////////////////////
// StandardGlyphVector package private methods
/////////////////////
// used by glyphlist to determine if it needs to allocate/size positions array
// gti always uses positions because the gtx might have translation. We also
// need positions if the rendering dtx is different from the frctx.
boolean needsPositions(double[] devTX) {
return gti != null ||
(getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 ||
!matchTX(devTX, frctx);
}
// used by glyphList to get strong refs to font strikes for duration of rendering call
// if devTX matches current devTX, we're ready to go
// if we don't have multiple transforms, we're already ok
// !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle
// the multiple-strikes case
/*
* GlyphList calls this to set up its images data. First it calls needsPositions,
* passing the devTX, to see if it should provide us a positions array to fill.
* It only doesn't need them if we're a simple glyph vector whose frctx matches the
* devtx.
* Then it calls setupGlyphImages. If we need positions, we make sure we have our
* default positions based on the frctx first. Then we set the devTX, and use
* strikes based on it to generate the images. Finally, we fill in the positions
* array.
* If we have transforms, we delegate to gti. It depends on our having first
* initialized the positions and devTX.
*/
Object setupGlyphImages(long[] images, float[] positions, double[] devTX) {
initPositions(); // FIRST ensure we have positions based on our frctx
setRenderTransform(devTX); // THEN make sure we are using the desired devTX
if (gti != null) {
return gti.setupGlyphImages(images, positions, dtx);
}
GlyphStrike gs = getDefaultStrike();
gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length);
if (positions != null) {
if (dtx.isIdentity()) {
System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2);
} else {
dtx.transform(this.positions, 0, positions, 0, glyphs.length);
}
}
return gs;
}
//////////////////////
// StandardGlyphVector private methods
/////////////////////
// We keep translation in our frctx since getPixelBounds uses it. But
// GlyphList pulls out the translation and applies it separately, so
// we strip it out when we set the dtx. Basically nothing uses the
// translation except getPixelBounds.
// called by needsPositions, setRenderTransform
private static boolean matchTX(double[] lhs, AffineTransform rhs) {
return
lhs[0] == rhs.getScaleX() &&
lhs[1] == rhs.getShearY() &&
lhs[2] == rhs.getShearX() &&
lhs[3] == rhs.getScaleY();
}
// returns new tx if old one has translation, otherwise returns old one
private static AffineTransform getNonTranslateTX(AffineTransform tx) {
if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) {
tx = new AffineTransform(tx.getScaleX(), tx.getShearY(),
tx.getShearX(), tx.getScaleY(),
0, 0);
}
return tx;
}
private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) {
return lhs.getScaleX() == rhs.getScaleX() &&
lhs.getShearY() == rhs.getShearY() &&
lhs.getShearX() == rhs.getShearX() &&
lhs.getScaleY() == rhs.getScaleY();
}
// called by setupGlyphImages (after needsPositions, so redundant match check?)
private void setRenderTransform(double[] devTX) {
assert(devTX.length == 4);
if (!matchTX(devTX, dtx)) {
resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4.
}
}
// called by getGlyphsPixelBounds
private void setDTX(AffineTransform tx) {
if (!equalNonTranslateTX(dtx, tx)) {
resetDTX(getNonTranslateTX(tx));
}
}
// called by most functions
private void setFRCTX() {
if (!equalNonTranslateTX(frctx, dtx)) {
resetDTX(getNonTranslateTX(frctx));
}
}
/**
* Change the dtx for the strike refs we use. Keeps a reference to the at. At
* must not contain translation.
* Called by setRenderTransform, setDTX, initFontData.
*/
private void resetDTX(AffineTransform at) {
fsref = null;
dtx = at;
invdtx = null;