Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/font/StandardGlyphVector.java
38829 views
1
/*
2
* Copyright (c) 1998, 2012, 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
package sun.font;
27
28
import java.awt.Font;
29
import java.awt.Graphics2D;
30
import java.awt.Point;
31
import java.awt.Rectangle;
32
import static java.awt.RenderingHints.*;
33
import java.awt.Shape;
34
import java.awt.font.FontRenderContext;
35
import java.awt.font.GlyphMetrics;
36
import java.awt.font.GlyphJustificationInfo;
37
import java.awt.font.GlyphVector;
38
import java.awt.font.LineMetrics;
39
import java.awt.font.TextAttribute;
40
import java.awt.geom.AffineTransform;
41
import java.awt.geom.GeneralPath;
42
import java.awt.geom.NoninvertibleTransformException;
43
import java.awt.geom.PathIterator;
44
import java.awt.geom.Point2D;
45
import java.awt.geom.Rectangle2D;
46
import java.lang.ref.SoftReference;
47
import java.text.CharacterIterator;
48
49
import sun.awt.SunHints;
50
import sun.java2d.loops.FontInfo;
51
52
/**
53
* Standard implementation of GlyphVector used by Font, GlyphList, and
54
* SunGraphics2D.
55
*
56
* The main issues involve the semantics of the various transforms
57
* (font, glyph, device) and their effect on rendering and metrics.
58
*
59
* Very, very unfortunately, the translation component of the font
60
* transform affects where the text gets rendered. It offsets the
61
* rendering origin. None of the other metrics of the glyphvector
62
* are affected, making them inconsistent with the rendering behavior.
63
* I think the translation component of the font would be better
64
* interpreted as the translation component of a per-glyph transform,
65
* but I don't know if this is possible to change.
66
*
67
* After the font transform is applied, the glyph transform is
68
* applied. This makes glyph transforms relative to font transforms,
69
* if the font transform changes, the glyph transform will have the
70
* same (relative) effect on the outline of the glyph. The outline
71
* and logical bounds are passed through the glyph transform before
72
* being returned. The glyph metrics ignore the glyph transform, but
73
* provide the outline bounds and the advance vector of the glyph (the
74
* latter will be rotated if the font is rotated). The default layout
75
* places each glyph at the end of the advance vector of the previous
76
* glyph, and since the glyph transform translates the advance vector,
77
* this means a glyph transform affects the positions of all
78
* subsequent glyphs if defaultLayout is called after setting a glyph
79
* transform. In the glyph info array, the bounds are the outline
80
* bounds including the glyph transform, and the positions are as
81
* computed, and the advances are the deltas between the positions.
82
*
83
* (There's a bug in the logical bounds of a rotated glyph for
84
* composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The
85
* problem is that the rotated composite doesn't handle the multiple
86
* ascents and descents properly in both x and y. You end up with
87
* a rotated advance vector but an unrotated ascent and descent.)
88
*
89
* Finally, the whole thing is transformed by the device transform to
90
* position it on the page.
91
*
92
* Another bug: The glyph outline seems to ignore fractional point
93
* size information, but the images (and advances) don't ignore it.
94
*
95
* Small fonts drawn at large magnification have odd advances when
96
* fractional metrics is off-- that's because the advances depend on
97
* the frc. When the frc is scaled appropriately, the advances are
98
* fine. FM or a large frc (high numbers) make the advances right.
99
*
100
* The buffer aa flag doesn't affect rendering, the glyph vector
101
* renders as AA if aa is set in its frc, and as non-aa if aa is not
102
* set in its frc.
103
*
104
* font rotation, baseline, vertical etc.
105
*
106
* Font rotation and baseline Line metrics should be measured along a
107
* unit vector pi/4 cc from the baseline vector. For 'horizontal'
108
* fonts the baseline vector is the x vector passed through the font
109
* transform (ignoring translation), for 'vertical' it is the y
110
* vector. This definition makes ascent, descent, etc independent of
111
* shear, so shearing can be used to simulate italic. This means no
112
* fonts have 'negative ascents' or 'zero ascents' etc.
113
*
114
* Having a coordinate system with orthogonal axes where one is
115
* parallel to the baseline means we could use rectangles and interpret
116
* them in terms of this coordinate system. Unfortunately there
117
* is support for rotated fonts in the jdk already so maintaining
118
* the semantics of existing code (getlogical bounds, etc) might
119
* be difficult.
120
*
121
* A font transform transforms both the baseline and all the glyphs
122
* in the font, so it does not rotate the glyph w.r.t the baseline.
123
* If you do want to rotate individual glyphs, you need to apply a
124
* glyph transform. If performDefaultLayout is called after this,
125
* the transformed glyph advances will affect the glyph positions.
126
*
127
* useful additions
128
* - select vertical metrics - glyphs are rotated pi/4 cc and vertical
129
* metrics are used to align them to the baseline.
130
* - define baseline for font (glyph rotation not linked to baseline)
131
* - define extra space (delta between each glyph along baseline)
132
* - define offset (delta from 'true' baseline, impacts ascent and
133
* descent as these are still computed from true basline and pinned
134
* to zero, used in superscript).
135
*/
136
public class StandardGlyphVector extends GlyphVector {
137
private Font font;
138
private FontRenderContext frc;
139
private int[] glyphs; // always
140
private int[] userGlyphs; // used to return glyphs to the client.
141
private float[] positions; // only if not default advances
142
private int[] charIndices; // only if interesting
143
private int flags; // indicates whether positions, charIndices is interesting
144
145
private static final int UNINITIALIZED_FLAGS = -1;
146
147
// transforms information
148
private GlyphTransformInfo gti; // information about per-glyph transforms
149
150
// !!! can we get rid of any of this extra stuff?
151
private AffineTransform ftx; // font transform without translation
152
private AffineTransform dtx; // device transform used for strike calculations, no translation
153
private AffineTransform invdtx; // inverse of dtx or null if dtx is identity
154
private AffineTransform frctx; // font render context transform, wish we could just share it
155
private Font2D font2D; // basic strike-independent stuff
156
private SoftReference fsref; // font strike reference for glyphs with no per-glyph transform
157
158
/////////////////////////////
159
// Constructors and Factory methods
160
/////////////////////////////
161
162
public StandardGlyphVector(Font font, String str, FontRenderContext frc) {
163
init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);
164
}
165
166
public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {
167
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
168
}
169
170
public StandardGlyphVector(Font font, char[] text, int start, int count,
171
FontRenderContext frc) {
172
init(font, text, start, count, frc, UNINITIALIZED_FLAGS);
173
}
174
175
private float getTracking(Font font) {
176
if (font.hasLayoutAttributes()) {
177
AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();
178
return values.getTracking();
179
}
180
return 0;
181
}
182
183
// used by GlyphLayout to construct a glyphvector
184
public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
185
int[] indices, int flags) {
186
initGlyphVector(font, frc, glyphs, positions, indices, flags);
187
188
// this code should go into layout
189
float track = getTracking(font);
190
if (track != 0) {
191
track *= font.getSize2D();
192
Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta
193
if (font.isTransformed()) {
194
AffineTransform at = font.getTransform();
195
at.deltaTransform(trackPt, trackPt);
196
}
197
198
// how do we know its a base glyph
199
// for now, it is if the natural advance of the glyph is non-zero
200
Font2D f2d = FontUtilities.getFont2D(font);
201
FontStrike strike = f2d.getStrike(font, frc);
202
203
float[] deltas = { trackPt.x, trackPt.y };
204
for (int j = 0; j < deltas.length; ++j) {
205
float inc = deltas[j];
206
if (inc != 0) {
207
float delta = 0;
208
for (int i = j, n = 0; n < glyphs.length; i += 2) {
209
if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test
210
positions[i] += delta;
211
delta += inc;
212
}
213
}
214
positions[positions.length-2+j] += delta;
215
}
216
}
217
}
218
}
219
220
public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,
221
int[] indices, int flags) {
222
this.font = font;
223
this.frc = frc;
224
this.glyphs = glyphs;
225
this.userGlyphs = glyphs; // no need to check
226
this.positions = positions;
227
this.charIndices = indices;
228
this.flags = flags;
229
230
initFontData();
231
}
232
233
public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) {
234
int offset = iter.getBeginIndex();
235
char[] text = new char [iter.getEndIndex() - offset];
236
for(char c = iter.first();
237
c != CharacterIterator.DONE;
238
c = iter.next()) {
239
text[iter.getIndex() - offset] = c;
240
}
241
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);
242
}
243
244
public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) {
245
// !!! find callers of this
246
// should be able to fully init from raw data, e.g. charmap, flags too.
247
this.font = font;
248
this.frc = frc;
249
this.flags = UNINITIALIZED_FLAGS;
250
251
initFontData();
252
this.userGlyphs = glyphs;
253
this.glyphs = getValidatedGlyphs(this.userGlyphs);
254
}
255
256
/* This is called from the rendering loop. FontInfo is supplied
257
* because a GV caches a strike and glyph images suitable for its FRC.
258
* LCD text isn't currently supported on all surfaces, in which case
259
* standard AA must be used. This is most likely to occur when LCD text
260
* is requested and the surface is some non-standard type or hardward
261
* surface for which there are no accelerated loops.
262
* We can detect this as being AA=="ON" in the FontInfo and AA!="ON"
263
* and AA!="GASP" in the FRC - since this only occurs for LCD text we don't
264
* need to check any more precisely what value is in the FRC.
265
*/
266
public static StandardGlyphVector getStandardGV(GlyphVector gv,
267
FontInfo info) {
268
if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) {
269
Object aaHint = gv.getFontRenderContext().getAntiAliasingHint();
270
if (aaHint != VALUE_TEXT_ANTIALIAS_ON &&
271
aaHint != VALUE_TEXT_ANTIALIAS_GASP) {
272
/* We need to create a new GV with AA==ON for rendering */
273
FontRenderContext frc = gv.getFontRenderContext();
274
frc = new FontRenderContext(frc.getTransform(),
275
VALUE_TEXT_ANTIALIAS_ON,
276
frc.getFractionalMetricsHint());
277
return new StandardGlyphVector(gv, frc);
278
}
279
}
280
if (gv instanceof StandardGlyphVector) {
281
return (StandardGlyphVector)gv;
282
}
283
return new StandardGlyphVector(gv, gv.getFontRenderContext());
284
}
285
286
/////////////////////////////
287
// GlyphVector API
288
/////////////////////////////
289
290
public Font getFont() {
291
return this.font;
292
}
293
294
public FontRenderContext getFontRenderContext() {
295
return this.frc;
296
}
297
298
public void performDefaultLayout() {
299
positions = null;
300
if (getTracking(font) == 0) {
301
clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
302
}
303
}
304
305
public int getNumGlyphs() {
306
return glyphs.length;
307
}
308
309
public int getGlyphCode(int glyphIndex) {
310
return userGlyphs[glyphIndex];
311
}
312
313
public int[] getGlyphCodes(int start, int count, int[] result) {
314
if (count < 0) {
315
throw new IllegalArgumentException("count = " + count);
316
}
317
if (start < 0) {
318
throw new IndexOutOfBoundsException("start = " + start);
319
}
320
if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge
321
throw new IndexOutOfBoundsException("start + count = " + (start + count));
322
}
323
324
if (result == null) {
325
result = new int[count];
326
}
327
328
// if arraycopy were faster, we wouldn't code this
329
for (int i = 0; i < count; ++i) {
330
result[i] = userGlyphs[i + start];
331
}
332
333
return result;
334
}
335
336
public int getGlyphCharIndex(int ix) {
337
if (ix < 0 && ix >= glyphs.length) {
338
throw new IndexOutOfBoundsException("" + ix);
339
}
340
if (charIndices == null) {
341
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
342
return glyphs.length - 1 - ix;
343
}
344
return ix;
345
}
346
return charIndices[ix];
347
}
348
349
public int[] getGlyphCharIndices(int start, int count, int[] result) {
350
if (start < 0 || count < 0 || (count > glyphs.length - start)) {
351
throw new IndexOutOfBoundsException("" + start + ", " + count);
352
}
353
if (result == null) {
354
result = new int[count];
355
}
356
if (charIndices == null) {
357
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {
358
for (int i = 0, n = glyphs.length - 1 - start;
359
i < count; ++i, --n) {
360
result[i] = n;
361
}
362
} else {
363
for (int i = 0, n = start; i < count; ++i, ++n) {
364
result[i] = n;
365
}
366
}
367
} else {
368
for (int i = 0; i < count; ++i) {
369
result[i] = charIndices[i + start];
370
}
371
}
372
return result;
373
}
374
375
// !!! not cached, assume TextLayout will cache if necessary
376
// !!! reexamine for per-glyph-transforms
377
// !!! revisit for text-on-a-path, vertical
378
public Rectangle2D getLogicalBounds() {
379
setFRCTX();
380
initPositions();
381
382
LineMetrics lm = font.getLineMetrics("", frc);
383
384
float minX, minY, maxX, maxY;
385
// horiz only for now...
386
minX = 0;
387
minY = -lm.getAscent();
388
maxX = 0;
389
maxY = lm.getDescent() + lm.getLeading();
390
if (glyphs.length > 0) {
391
maxX = positions[positions.length - 2];
392
}
393
394
return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);
395
}
396
397
// !!! not cached, assume TextLayout will cache if necessary
398
public Rectangle2D getVisualBounds() {
399
Rectangle2D result = null;
400
for (int i = 0; i < glyphs.length; ++i) {
401
Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();
402
if (!glyphVB.isEmpty()) {
403
if (result == null) {
404
result = glyphVB;
405
} else {
406
Rectangle2D.union(result, glyphVB, result);
407
}
408
}
409
}
410
if (result == null) {
411
result = new Rectangle2D.Float(0, 0, 0, 0);
412
}
413
return result;
414
}
415
416
// !!! not cached, assume TextLayout will cache if necessary
417
// !!! fontStrike needs a method for this
418
public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {
419
return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);
420
}
421
422
public Shape getOutline() {
423
return getGlyphsOutline(0, glyphs.length, 0, 0);
424
}
425
426
public Shape getOutline(float x, float y) {
427
return getGlyphsOutline(0, glyphs.length, x, y);
428
}
429
430
// relative to gv origin
431
public Shape getGlyphOutline(int ix) {
432
return getGlyphsOutline(ix, 1, 0, 0);
433
}
434
435
// relative to gv origin offset by x, y
436
public Shape getGlyphOutline(int ix, float x, float y) {
437
return getGlyphsOutline(ix, 1, x, y);
438
}
439
440
public Point2D getGlyphPosition(int ix) {
441
initPositions();
442
443
ix *= 2;
444
return new Point2D.Float(positions[ix], positions[ix + 1]);
445
}
446
447
public void setGlyphPosition(int ix, Point2D pos) {
448
initPositions();
449
450
int ix2 = ix << 1;
451
positions[ix2] = (float)pos.getX();
452
positions[ix2 + 1] = (float)pos.getY();
453
454
clearCaches(ix);
455
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
456
}
457
458
public AffineTransform getGlyphTransform(int ix) {
459
if (ix < 0 || ix >= glyphs.length) {
460
throw new IndexOutOfBoundsException("ix = " + ix);
461
}
462
if (gti != null) {
463
return gti.getGlyphTransform(ix);
464
}
465
return null; // spec'd as returning null
466
}
467
468
public void setGlyphTransform(int ix, AffineTransform newTX) {
469
if (ix < 0 || ix >= glyphs.length) {
470
throw new IndexOutOfBoundsException("ix = " + ix);
471
}
472
473
if (gti == null) {
474
if (newTX == null || newTX.isIdentity()) {
475
return;
476
}
477
gti = new GlyphTransformInfo(this);
478
}
479
gti.setGlyphTransform(ix, newTX); // sets flags
480
if (gti.transformCount() == 0) {
481
gti = null;
482
}
483
}
484
485
public int getLayoutFlags() {
486
if (flags == UNINITIALIZED_FLAGS) {
487
flags = 0;
488
489
if (charIndices != null && glyphs.length > 1) {
490
boolean ltr = true;
491
boolean rtl = true;
492
493
int rtlix = charIndices.length; // rtl index
494
for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {
495
int cx = charIndices[i];
496
497
ltr = ltr && (cx == i);
498
rtl = rtl && (cx == --rtlix);
499
}
500
501
if (rtl) flags |= FLAG_RUN_RTL;
502
if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS;
503
}
504
}
505
506
return flags;
507
}
508
509
public float[] getGlyphPositions(int start, int count, float[] result) {
510
if (count < 0) {
511
throw new IllegalArgumentException("count = " + count);
512
}
513
if (start < 0) {
514
throw new IndexOutOfBoundsException("start = " + start);
515
}
516
if (start > glyphs.length + 1 - count) { // watch for overflow
517
throw new IndexOutOfBoundsException("start + count = " + (start + count));
518
}
519
520
return internalGetGlyphPositions(start, count, 0, result);
521
}
522
523
public Shape getGlyphLogicalBounds(int ix) {
524
if (ix < 0 || ix >= glyphs.length) {
525
throw new IndexOutOfBoundsException("ix = " + ix);
526
}
527
528
Shape[] lbcache;
529
if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) {
530
lbcache = new Shape[glyphs.length];
531
lbcacheRef = new SoftReference(lbcache);
532
}
533
534
Shape result = lbcache[ix];
535
if (result == null) {
536
setFRCTX();
537
initPositions();
538
539
// !!! ought to return a rectangle2d for simple cases, though the following works for all
540
541
// get the position, the tx offset, and the x,y advance and x,y adl. The
542
// shape is the box formed by adv (width) and adl (height) offset by
543
// the position plus the tx offset minus the ascent.
544
545
ADL adl = new ADL();
546
GlyphStrike gs = getGlyphStrike(ix);
547
gs.getADL(adl);
548
549
Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]);
550
551
float wx = adv.x;
552
float wy = adv.y;
553
float hx = adl.descentX + adl.leadingX + adl.ascentX;
554
float hy = adl.descentY + adl.leadingY + adl.ascentY;
555
float x = positions[ix*2] + gs.dx - adl.ascentX;
556
float y = positions[ix*2+1] + gs.dy - adl.ascentY;
557
558
GeneralPath gp = new GeneralPath();
559
gp.moveTo(x, y);
560
gp.lineTo(x + wx, y + wy);
561
gp.lineTo(x + wx + hx, y + wy + hy);
562
gp.lineTo(x + hx, y + hy);
563
gp.closePath();
564
565
result = new DelegatingShape(gp);
566
lbcache[ix] = result;
567
}
568
569
return result;
570
}
571
private SoftReference lbcacheRef;
572
573
public Shape getGlyphVisualBounds(int ix) {
574
if (ix < 0 || ix >= glyphs.length) {
575
throw new IndexOutOfBoundsException("ix = " + ix);
576
}
577
578
Shape[] vbcache;
579
if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) {
580
vbcache = new Shape[glyphs.length];
581
vbcacheRef = new SoftReference(vbcache);
582
}
583
584
Shape result = vbcache[ix];
585
if (result == null) {
586
result = new DelegatingShape(getGlyphOutlineBounds(ix));
587
vbcache[ix] = result;
588
}
589
590
return result;
591
}
592
private SoftReference vbcacheRef;
593
594
public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) {
595
return getGlyphsPixelBounds(renderFRC, x, y, index, 1);
596
}
597
598
public GlyphMetrics getGlyphMetrics(int ix) {
599
if (ix < 0 || ix >= glyphs.length) {
600
throw new IndexOutOfBoundsException("ix = " + ix);
601
}
602
603
Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D();
604
Point2D pt = getGlyphPosition(ix);
605
vb.setRect(vb.getMinX() - pt.getX(),
606
vb.getMinY() - pt.getY(),
607
vb.getWidth(),
608
vb.getHeight());
609
Point2D.Float adv =
610
getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]);
611
GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y,
612
vb,
613
GlyphMetrics.STANDARD);
614
return gm;
615
}
616
617
public GlyphJustificationInfo getGlyphJustificationInfo(int ix) {
618
if (ix < 0 || ix >= glyphs.length) {
619
throw new IndexOutOfBoundsException("ix = " + ix);
620
}
621
622
// currently we don't have enough information to do this right. should
623
// get info from the font and use real OT/GX justification. Right now
624
// sun/font/ExtendedTextSourceLabel assigns one of three infos
625
// based on whether the char is kanji, space, or other.
626
627
return null;
628
}
629
630
public boolean equals(GlyphVector rhs) {
631
if (this == rhs) {
632
return true;
633
}
634
if (rhs == null) {
635
return false;
636
}
637
638
try {
639
StandardGlyphVector other = (StandardGlyphVector)rhs;
640
641
if (glyphs.length != other.glyphs.length) {
642
return false;
643
}
644
645
for (int i = 0; i < glyphs.length; ++i) {
646
if (glyphs[i] != other.glyphs[i]) {
647
return false;
648
}
649
}
650
651
if (!font.equals(other.font)) {
652
return false;
653
}
654
655
if (!frc.equals(other.frc)) {
656
return false;
657
}
658
659
if ((other.positions == null) != (positions == null)) {
660
if (positions == null) {
661
initPositions();
662
} else {
663
other.initPositions();
664
}
665
}
666
667
if (positions != null) {
668
for (int i = 0; i < positions.length; ++i) {
669
if (positions[i] != other.positions[i]) {
670
return false;
671
}
672
}
673
}
674
675
if (gti == null) {
676
return other.gti == null;
677
} else {
678
return gti.equals(other.gti);
679
}
680
}
681
catch (ClassCastException e) {
682
// assume they are different simply by virtue of the class difference
683
684
return false;
685
}
686
}
687
688
/**
689
* As a concrete subclass of Object that implements equality, this must
690
* implement hashCode.
691
*/
692
public int hashCode() {
693
return font.hashCode() ^ glyphs.length;
694
}
695
696
/**
697
* Since we implement equality comparisons for GlyphVector, we implement
698
* the inherited Object.equals(Object) as well. GlyphVector should do
699
* this, and define two glyphvectors as not equal if the classes differ.
700
*/
701
public boolean equals(Object rhs) {
702
try {
703
return equals((GlyphVector)rhs);
704
}
705
catch (ClassCastException e) {
706
return false;
707
}
708
}
709
710
/**
711
* Sometimes I wish java had covariant return types...
712
*/
713
public StandardGlyphVector copy() {
714
return (StandardGlyphVector)clone();
715
}
716
717
/**
718
* As a concrete subclass of GlyphVector, this must implement clone.
719
*/
720
public Object clone() {
721
// positions, gti are mutable so we have to clone them
722
// font2d can be shared
723
// fsref is a cache and can be shared
724
try {
725
StandardGlyphVector result = (StandardGlyphVector)super.clone();
726
727
result.clearCaches();
728
729
if (positions != null) {
730
result.positions = (float[])positions.clone();
731
}
732
733
if (gti != null) {
734
result.gti = new GlyphTransformInfo(result, gti);
735
}
736
737
return result;
738
}
739
catch (CloneNotSupportedException e) {
740
}
741
742
return this;
743
}
744
745
//////////////////////
746
// StandardGlyphVector new public methods
747
/////////////////////
748
749
/*
750
* Set a multiple glyph positions at one time. GlyphVector only
751
* provides API to set a single glyph at a time.
752
*/
753
public void setGlyphPositions(float[] srcPositions, int srcStart,
754
int start, int count) {
755
if (count < 0) {
756
throw new IllegalArgumentException("count = " + count);
757
}
758
759
initPositions();
760
for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) {
761
positions[i] = srcPositions[p];
762
}
763
764
clearCaches();
765
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
766
}
767
768
/**
769
* Set all the glyph positions, including the 'after last glyph' position.
770
* The srcPositions array must be of length (numGlyphs + 1) * 2.
771
*/
772
public void setGlyphPositions(float[] srcPositions) {
773
int requiredLength = glyphs.length * 2 + 2;
774
if (srcPositions.length != requiredLength) {
775
throw new IllegalArgumentException("srcPositions.length != " + requiredLength);
776
}
777
778
positions = (float[])srcPositions.clone();
779
780
clearCaches();
781
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
782
}
783
784
/**
785
* This is a convenience overload that gets all the glyph positions, which
786
* is what you usually want to do if you're getting more than one.
787
* !!! should I bother taking result parameter?
788
*/
789
public float[] getGlyphPositions(float[] result) {
790
return internalGetGlyphPositions(0, glyphs.length + 1, 0, result);
791
}
792
793
/**
794
* Get transform information for the requested range of glyphs.
795
* If no glyphs have a transform, return null.
796
* If a glyph has no transform (or is the identity transform) its entry in the result array will be null.
797
* If the passed-in result is null an array will be allocated for the caller.
798
* Each transform instance in the result array will unique, and independent of the GlyphVector's transform.
799
*/
800
public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) {
801
if (start < 0 || count < 0 || start + count > glyphs.length) {
802
throw new IllegalArgumentException("start: " + start + " count: " + count);
803
}
804
805
if (gti == null) {
806
return null;
807
}
808
809
if (result == null) {
810
result = new AffineTransform[count];
811
}
812
813
for (int i = 0; i < count; ++i, ++start) {
814
result[i] = gti.getGlyphTransform(start);
815
}
816
817
return result;
818
}
819
820
/**
821
* Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int);
822
*/
823
public AffineTransform[] getGlyphTransforms() {
824
return getGlyphTransforms(0, glyphs.length, null);
825
}
826
827
/**
828
* Set a number of glyph transforms.
829
* Original transforms are unchanged. The array may contain nulls, and also may
830
* contain multiple references to the same transform instance.
831
*/
832
public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) {
833
for (int i = start, e = start + count; i < e; ++i) {
834
setGlyphTransform(i, srcTransforms[srcStart + i]);
835
}
836
}
837
838
/**
839
* Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int).
840
*/
841
public void setGlyphTransforms(AffineTransform[] srcTransforms) {
842
setGlyphTransforms(srcTransforms, 0, 0, glyphs.length);
843
}
844
845
/**
846
* For each glyph return posx, posy, advx, advy, visx, visy, visw, vish.
847
*/
848
public float[] getGlyphInfo() {
849
setFRCTX();
850
initPositions();
851
float[] result = new float[glyphs.length * 8];
852
for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) {
853
float x = positions[i*2];
854
float y = positions[i*2+1];
855
result[n] = x;
856
result[n+1] = y;
857
858
int glyphID = glyphs[i];
859
GlyphStrike s = getGlyphStrike(i);
860
Point2D.Float adv = s.strike.getGlyphMetrics(glyphID);
861
result[n+2] = adv.x;
862
result[n+3] = adv.y;
863
864
Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D();
865
result[n+4] = (float)(vb.getMinX());
866
result[n+5] = (float)(vb.getMinY());
867
result[n+6] = (float)(vb.getWidth());
868
result[n+7] = (float)(vb.getHeight());
869
}
870
return result;
871
}
872
873
/**
874
* !!! not used currently, but might be by getPixelbounds?
875
*/
876
public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) {
877
if (renderFRC == null) {
878
renderFRC = frc;
879
}
880
881
// it is a total pain that you have to copy the transform.
882
883
AffineTransform at = renderFRC.getTransform();
884
at.transform(loc, loc);
885
pxResult.x = (int)loc.getX(); // but must not behave oddly around zero
886
pxResult.y = (int)loc.getY();
887
loc.setLocation(pxResult.x, pxResult.y);
888
try {
889
at.inverseTransform(loc, loc);
890
}
891
catch (NoninvertibleTransformException e) {
892
throw new IllegalArgumentException("must be able to invert frc transform");
893
}
894
}
895
896
//////////////////////
897
// StandardGlyphVector package private methods
898
/////////////////////
899
900
// used by glyphlist to determine if it needs to allocate/size positions array
901
// gti always uses positions because the gtx might have translation. We also
902
// need positions if the rendering dtx is different from the frctx.
903
904
boolean needsPositions(double[] devTX) {
905
return gti != null ||
906
(getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 ||
907
!matchTX(devTX, frctx);
908
}
909
910
// used by glyphList to get strong refs to font strikes for duration of rendering call
911
// if devTX matches current devTX, we're ready to go
912
// if we don't have multiple transforms, we're already ok
913
914
// !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle
915
// the multiple-strikes case
916
917
/*
918
* GlyphList calls this to set up its images data. First it calls needsPositions,
919
* passing the devTX, to see if it should provide us a positions array to fill.
920
* It only doesn't need them if we're a simple glyph vector whose frctx matches the
921
* devtx.
922
* Then it calls setupGlyphImages. If we need positions, we make sure we have our
923
* default positions based on the frctx first. Then we set the devTX, and use
924
* strikes based on it to generate the images. Finally, we fill in the positions
925
* array.
926
* If we have transforms, we delegate to gti. It depends on our having first
927
* initialized the positions and devTX.
928
*/
929
Object setupGlyphImages(long[] images, float[] positions, double[] devTX) {
930
initPositions(); // FIRST ensure we have positions based on our frctx
931
setRenderTransform(devTX); // THEN make sure we are using the desired devTX
932
933
if (gti != null) {
934
return gti.setupGlyphImages(images, positions, dtx);
935
}
936
937
GlyphStrike gs = getDefaultStrike();
938
gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length);
939
940
if (positions != null) {
941
if (dtx.isIdentity()) {
942
System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2);
943
} else {
944
dtx.transform(this.positions, 0, positions, 0, glyphs.length);
945
}
946
}
947
948
return gs;
949
}
950
951
//////////////////////
952
// StandardGlyphVector private methods
953
/////////////////////
954
955
// We keep translation in our frctx since getPixelBounds uses it. But
956
// GlyphList pulls out the translation and applies it separately, so
957
// we strip it out when we set the dtx. Basically nothing uses the
958
// translation except getPixelBounds.
959
960
// called by needsPositions, setRenderTransform
961
private static boolean matchTX(double[] lhs, AffineTransform rhs) {
962
return
963
lhs[0] == rhs.getScaleX() &&
964
lhs[1] == rhs.getShearY() &&
965
lhs[2] == rhs.getShearX() &&
966
lhs[3] == rhs.getScaleY();
967
}
968
969
// returns new tx if old one has translation, otherwise returns old one
970
private static AffineTransform getNonTranslateTX(AffineTransform tx) {
971
if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) {
972
tx = new AffineTransform(tx.getScaleX(), tx.getShearY(),
973
tx.getShearX(), tx.getScaleY(),
974
0, 0);
975
}
976
return tx;
977
}
978
979
private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) {
980
return lhs.getScaleX() == rhs.getScaleX() &&
981
lhs.getShearY() == rhs.getShearY() &&
982
lhs.getShearX() == rhs.getShearX() &&
983
lhs.getScaleY() == rhs.getScaleY();
984
}
985
986
// called by setupGlyphImages (after needsPositions, so redundant match check?)
987
private void setRenderTransform(double[] devTX) {
988
assert(devTX.length == 4);
989
if (!matchTX(devTX, dtx)) {
990
resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4.
991
}
992
}
993
994
// called by getGlyphsPixelBounds
995
private final void setDTX(AffineTransform tx) {
996
if (!equalNonTranslateTX(dtx, tx)) {
997
resetDTX(getNonTranslateTX(tx));
998
}
999
}
1000
1001
// called by most functions
1002
private final void setFRCTX() {
1003
if (!equalNonTranslateTX(frctx, dtx)) {
1004
resetDTX(getNonTranslateTX(frctx));
1005
}
1006
}
1007
1008
/**
1009
* Change the dtx for the strike refs we use. Keeps a reference to the at. At
1010
* must not contain translation.
1011
* Called by setRenderTransform, setDTX, initFontData.
1012
*/
1013
private final void resetDTX(AffineTransform at) {
1014
fsref = null;
1015
dtx = at;
1016
invdtx = null;
1017
if (!dtx.isIdentity()) {
1018
try {
1019
invdtx = dtx.createInverse();
1020
}
1021
catch (NoninvertibleTransformException e) {
1022
// we needn't care for rendering
1023
}
1024
}
1025
if (gti != null) {
1026
gti.strikesRef = null;
1027
}
1028
}
1029
1030
/**
1031
* Utility used by getStandardGV.
1032
* Constructs a StandardGlyphVector from a generic glyph vector.
1033
* Do not call this from new contexts without considering the comment
1034
* about "userGlyphs".
1035
*/
1036
private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) {
1037
this.font = gv.getFont();
1038
this.frc = frc;
1039
initFontData();
1040
1041
int nGlyphs = gv.getNumGlyphs();
1042
this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null);
1043
if (gv instanceof StandardGlyphVector) {
1044
/* userGlyphs will be OK because this is a private constructor
1045
* and the returned instance is used only for rendering.
1046
* It's not constructable by user code, nor returned to the
1047
* application. So we know "userGlyphs" are valid as having
1048
* been either already validated or are the result of layout.
1049
*/
1050
this.glyphs = userGlyphs;
1051
} else {
1052
this.glyphs = getValidatedGlyphs(this.userGlyphs);
1053
}
1054
this.flags = gv.getLayoutFlags() & FLAG_MASK;
1055
1056
if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1057
this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null);
1058
}
1059
1060
if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1061
this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null);
1062
}
1063
1064
if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1065
AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case
1066
for (int i = 0; i < nGlyphs; ++i) {
1067
txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms
1068
}
1069
1070
setGlyphTransforms(txs);
1071
}
1072
}
1073
1074
/* Before asking the Font we see if the glyph code is
1075
* FFFE or FFFF which are special values that we should be internally
1076
* ready to handle as meaning invisible glyphs. The Font would report
1077
* those as the missing glyph.
1078
*/
1079
int[] getValidatedGlyphs(int[] oglyphs) {
1080
int len = oglyphs.length;
1081
int[] vglyphs = new int[len];
1082
for (int i=0; i<len; i++) {
1083
if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) {
1084
vglyphs[i] = oglyphs[i];
1085
} else {
1086
vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]);
1087
}
1088
}
1089
return vglyphs;
1090
}
1091
1092
// utility used by constructors
1093
private void init(Font font, char[] text, int start, int count,
1094
FontRenderContext frc, int flags) {
1095
1096
if (start < 0 || count < 0 || start + count > text.length) {
1097
throw new ArrayIndexOutOfBoundsException("start or count out of bounds");
1098
}
1099
1100
this.font = font;
1101
this.frc = frc;
1102
this.flags = flags;
1103
1104
if (getTracking(font) != 0) {
1105
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1106
}
1107
1108
// !!! change mapper interface?
1109
if (start != 0) {
1110
char[] temp = new char[count];
1111
System.arraycopy(text, start, temp, 0, count);
1112
text = temp;
1113
}
1114
1115
initFontData(); // sets up font2D
1116
1117
// !!! no layout for now, should add checks
1118
// !!! need to support creating a StandardGlyphVector from a TextMeasurer's info...
1119
glyphs = new int[count]; // hmmm
1120
/* Glyphs obtained here are already validated by the font */
1121
userGlyphs = glyphs;
1122
font2D.getMapper().charsToGlyphs(count, text, glyphs);
1123
}
1124
1125
private void initFontData() {
1126
font2D = FontUtilities.getFont2D(font);
1127
if (font2D instanceof FontSubstitution) {
1128
font2D = ((FontSubstitution)font2D).getCompositeFont2D();
1129
}
1130
float s = font.getSize2D();
1131
if (font.isTransformed()) {
1132
ftx = font.getTransform();
1133
if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) {
1134
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);
1135
}
1136
ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0);
1137
ftx.scale(s, s);
1138
} else {
1139
ftx = AffineTransform.getScaleInstance(s, s);
1140
}
1141
1142
frctx = frc.getTransform();
1143
resetDTX(getNonTranslateTX(frctx));
1144
}
1145
1146
/**
1147
* Copy glyph position data into a result array starting at the indicated
1148
* offset in the array. If the passed-in result array is null, a new
1149
* array will be allocated and returned.
1150
*
1151
* This is an internal method and does no extra argument checking.
1152
*
1153
* @param start the index of the first glyph to get
1154
* @param count the number of glyphs to get
1155
* @param offset the offset into result at which to put the data
1156
* @param result an array to hold the x,y positions
1157
* @return the modified position array
1158
*/
1159
private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) {
1160
if (result == null) {
1161
result = new float[offset + count * 2];
1162
}
1163
1164
initPositions();
1165
1166
// System.arraycopy is slow for stuff like this
1167
for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) {
1168
result[i] = positions[p];
1169
}
1170
1171
return result;
1172
}
1173
1174
private Rectangle2D getGlyphOutlineBounds(int ix) {
1175
setFRCTX();
1176
initPositions();
1177
return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]);
1178
}
1179
1180
/**
1181
* Used by getOutline, getGlyphsOutline
1182
*/
1183
private Shape getGlyphsOutline(int start, int count, float x, float y) {
1184
setFRCTX();
1185
initPositions();
1186
1187
GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO);
1188
for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) {
1189
float px = x + positions[n];
1190
float py = y + positions[n+1];
1191
1192
getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py);
1193
}
1194
1195
return result;
1196
}
1197
1198
private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) {
1199
initPositions(); // FIRST ensure we have positions based on our frctx
1200
1201
AffineTransform tx = null;
1202
if (frc == null || frc.equals(this.frc)) {
1203
tx = frctx;
1204
} else {
1205
tx = frc.getTransform();
1206
}
1207
setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points
1208
1209
if (gti != null) {
1210
return gti.getGlyphsPixelBounds(tx, x, y, start, count);
1211
}
1212
1213
FontStrike fs = getDefaultStrike().strike;
1214
Rectangle result = null;
1215
Rectangle r = new Rectangle();
1216
Point2D.Float pt = new Point.Float();
1217
int n = start * 2;
1218
while (--count >= 0) {
1219
pt.x = x + positions[n++];
1220
pt.y = y + positions[n++];
1221
tx.transform(pt, pt);
1222
fs.getGlyphImageBounds(glyphs[start++], pt, r);
1223
if (!r.isEmpty()) {
1224
if (result == null) {
1225
result = new Rectangle(r);
1226
} else {
1227
result.add(r);
1228
}
1229
}
1230
}
1231
return result != null ? result : r;
1232
}
1233
1234
private void clearCaches(int ix) {
1235
if (lbcacheRef != null) {
1236
Shape[] lbcache = (Shape[])lbcacheRef.get();
1237
if (lbcache != null) {
1238
lbcache[ix] = null;
1239
}
1240
}
1241
1242
if (vbcacheRef != null) {
1243
Shape[] vbcache = (Shape[])vbcacheRef.get();
1244
if (vbcache != null) {
1245
vbcache[ix] = null;
1246
}
1247
}
1248
}
1249
1250
private void clearCaches() {
1251
lbcacheRef = null;
1252
vbcacheRef = null;
1253
}
1254
1255
// internal use only for possible future extension
1256
1257
/**
1258
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1259
* a vertical baseline.
1260
*/
1261
public static final int FLAG_USES_VERTICAL_BASELINE = 128;
1262
1263
/**
1264
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1265
* vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a
1266
* horizontal line, or vice versa.
1267
*/
1268
public static final int FLAG_USES_VERTICAL_METRICS = 256;
1269
1270
/**
1271
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses
1272
* the 'alternate orientation.' Glyphs have a default orientation given a
1273
* particular baseline and metrics orientation, this is the orientation appropriate
1274
* for left-to-right text. For example, the letter 'A' can have four orientations,
1275
* with the point at 12, 3, 6, or 9 'o clock. The following table shows where the
1276
* point displays for different values of vertical baseline (vb), vertical
1277
* metrics (vm) and alternate orientation (fo):<br>
1278
* <blockquote>
1279
* vb vm ao
1280
* -- -- -- --
1281
* f f f 12 ^ horizontal metrics on horizontal lines
1282
* f f t 6 v
1283
* f t f 9 < vertical metrics on horizontal lines
1284
* f t t 3 >
1285
* t f f 3 > horizontal metrics on vertical lines
1286
* t f t 9 <
1287
* t t f 12 ^ vertical metrics on vertical lines
1288
* t t t 6 v
1289
* </blockquote>
1290
*/
1291
public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512;
1292
1293
1294
/**
1295
* Ensure that the positions array exists and holds position data.
1296
* If the array is null, this allocates it and sets default positions.
1297
*/
1298
private void initPositions() {
1299
if (positions == null) {
1300
setFRCTX();
1301
1302
positions = new float[glyphs.length * 2 + 2];
1303
1304
Point2D.Float trackPt = null;
1305
float track = getTracking(font);
1306
if (track != 0) {
1307
track *= font.getSize2D();
1308
trackPt = new Point2D.Float(track, 0); // advance delta
1309
}
1310
1311
Point2D.Float pt = new Point2D.Float(0, 0);
1312
if (font.isTransformed()) {
1313
AffineTransform at = font.getTransform();
1314
at.transform(pt, pt);
1315
positions[0] = pt.x;
1316
positions[1] = pt.y;
1317
1318
if (trackPt != null) {
1319
at.deltaTransform(trackPt, trackPt);
1320
}
1321
}
1322
for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) {
1323
getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt);
1324
if (trackPt != null) {
1325
pt.x += trackPt.x;
1326
pt.y += trackPt.y;
1327
}
1328
positions[n] = pt.x;
1329
positions[n+1] = pt.y;
1330
}
1331
}
1332
}
1333
1334
/**
1335
* OR newFlags with existing flags. First computes existing flags if needed.
1336
*/
1337
private void addFlags(int newflags) {
1338
flags = getLayoutFlags() | newflags;
1339
}
1340
1341
/**
1342
* AND the complement of clearedFlags with existing flags. First computes existing flags if needed.
1343
*/
1344
private void clearFlags(int clearedFlags) {
1345
flags = getLayoutFlags() & ~clearedFlags;
1346
}
1347
1348
// general utility methods
1349
1350
// encapsulate the test to check whether we have per-glyph transforms
1351
private GlyphStrike getGlyphStrike(int ix) {
1352
if (gti == null) {
1353
return getDefaultStrike();
1354
} else {
1355
return gti.getStrike(ix);
1356
}
1357
}
1358
1359
// encapsulate access to cached default glyph strike
1360
private GlyphStrike getDefaultStrike() {
1361
GlyphStrike gs = null;
1362
if (fsref != null) {
1363
gs = (GlyphStrike)fsref.get();
1364
}
1365
if (gs == null) {
1366
gs = GlyphStrike.create(this, dtx, null);
1367
fsref = new SoftReference(gs);
1368
}
1369
return gs;
1370
}
1371
1372
1373
/////////////////////
1374
// Internal utility classes
1375
/////////////////////
1376
1377
// !!! I have this as a separate class instead of just inside SGV,
1378
// but I previously didn't bother. Now I'm trying this again.
1379
// Probably still not worth it, but I'd like to keep sgv's small in the common case.
1380
1381
static final class GlyphTransformInfo {
1382
StandardGlyphVector sgv; // reference back to glyph vector - yuck
1383
int[] indices; // index into unique strikes
1384
double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate
1385
SoftReference strikesRef; // ref to unique strikes, one per transform
1386
boolean haveAllStrikes; // true if the strike array has been filled by getStrikes().
1387
1388
// used when first setting a transform
1389
GlyphTransformInfo(StandardGlyphVector sgv) {
1390
this.sgv = sgv;
1391
}
1392
1393
// used when cloning a glyph vector, need to set back link
1394
GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) {
1395
this.sgv = sgv;
1396
1397
this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone();
1398
this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone();
1399
this.strikesRef = null; // can't share cache, so rather than clone, we just null out
1400
}
1401
1402
// used in sgv equality
1403
public boolean equals(GlyphTransformInfo rhs) {
1404
if (rhs == null) {
1405
return false;
1406
}
1407
if (rhs == this) {
1408
return true;
1409
}
1410
if (this.indices.length != rhs.indices.length) {
1411
return false;
1412
}
1413
if (this.transforms.length != rhs.transforms.length) {
1414
return false;
1415
}
1416
1417
// slow since we end up processing the same transforms multiple
1418
// times, but since transforms can be in any order, we either do
1419
// this or create a mapping. Equality tests aren't common so
1420
// leave it like this.
1421
for (int i = 0; i < this.indices.length; ++i) {
1422
int tix = this.indices[i];
1423
int rix = rhs.indices[i];
1424
if ((tix == 0) != (rix == 0)) {
1425
return false;
1426
}
1427
if (tix != 0) {
1428
tix *= 6;
1429
rix *= 6;
1430
for (int j = 6; j > 0; --j) {
1431
if (this.indices[--tix] != rhs.indices[--rix]) {
1432
return false;
1433
}
1434
}
1435
}
1436
}
1437
return true;
1438
}
1439
1440
// implements sgv.setGlyphTransform
1441
void setGlyphTransform(int glyphIndex, AffineTransform newTX) {
1442
1443
// we store all the glyph transforms as a double array, and for each glyph there
1444
// is an entry in the txIndices array indicating which transform to use. 0 means
1445
// there's no transform, 1 means use the first transform (the 6 doubles at offset
1446
// 0), 2 means use the second transform (the 6 doubles at offset 6), etc.
1447
//
1448
// Since this can be called multiple times, and since the number of transforms
1449
// affects the time it takes to construct the glyphs, we try to keep the arrays as
1450
// compact as possible, by removing transforms that are no longer used, and reusing
1451
// transforms where we already have them.
1452
1453
double[] temp = new double[6];
1454
boolean isIdentity = true;
1455
if (newTX == null || newTX.isIdentity()) {
1456
// Fill in temp
1457
temp[0] = temp[3] = 1.0;
1458
}
1459
else {
1460
isIdentity = false;
1461
newTX.getMatrix(temp);
1462
}
1463
1464
if (indices == null) {
1465
if (isIdentity) { // no change
1466
return;
1467
}
1468
1469
indices = new int[sgv.glyphs.length];
1470
indices[glyphIndex] = 1;
1471
transforms = temp;
1472
} else {
1473
boolean addSlot = false; // assume we're not growing
1474
int newIndex = -1;
1475
if (isIdentity) {
1476
newIndex = 0; // might shrink
1477
} else {
1478
addSlot = true; // assume no match
1479
int i;
1480
loop:
1481
for (i = 0; i < transforms.length; i += 6) {
1482
for (int j = 0; j < 6; ++j) {
1483
if (transforms[i + j] != temp[j]) {
1484
continue loop;
1485
}
1486
}
1487
addSlot = false;
1488
break;
1489
}
1490
newIndex = i / 6 + 1; // if no match, end of list
1491
}
1492
1493
// if we're using the same transform, nothing to do
1494
int oldIndex = indices[glyphIndex];
1495
if (newIndex != oldIndex) {
1496
// see if we are removing last use of the old slot
1497
boolean removeSlot = false;
1498
if (oldIndex != 0) {
1499
removeSlot = true;
1500
for (int i = 0; i < indices.length; ++i) {
1501
if (indices[i] == oldIndex && i != glyphIndex) {
1502
removeSlot = false;
1503
break;
1504
}
1505
}
1506
}
1507
1508
if (removeSlot && addSlot) { // reuse old slot with new transform
1509
newIndex = oldIndex;
1510
System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6);
1511
} else if (removeSlot) {
1512
if (transforms.length == 6) { // removing last one, so clear arrays
1513
indices = null;
1514
transforms = null;
1515
1516
sgv.clearCaches(glyphIndex);
1517
sgv.clearFlags(FLAG_HAS_TRANSFORMS);
1518
strikesRef = null;
1519
1520
return;
1521
}
1522
1523
double[] ttemp = new double[transforms.length - 6];
1524
System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6);
1525
System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6,
1526
transforms.length - oldIndex * 6);
1527
transforms = ttemp;
1528
1529
// clean up indices
1530
for (int i = 0; i < indices.length; ++i) {
1531
if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away
1532
indices[i] -= 1;
1533
}
1534
}
1535
if (newIndex > oldIndex) { // don't forget to decrement this too if we need to
1536
--newIndex;
1537
}
1538
} else if (addSlot) {
1539
double[] ttemp = new double[transforms.length + 6];
1540
System.arraycopy(transforms, 0, ttemp, 0, transforms.length);
1541
System.arraycopy(temp, 0, ttemp, transforms.length, 6);
1542
transforms = ttemp;
1543
}
1544
1545
indices[glyphIndex] = newIndex;
1546
}
1547
}
1548
1549
sgv.clearCaches(glyphIndex);
1550
sgv.addFlags(FLAG_HAS_TRANSFORMS);
1551
strikesRef = null;
1552
}
1553
1554
// implements sgv.getGlyphTransform
1555
AffineTransform getGlyphTransform(int ix) {
1556
int index = indices[ix];
1557
if (index == 0) {
1558
return null;
1559
}
1560
1561
int x = (index - 1) * 6;
1562
return new AffineTransform(transforms[x + 0],
1563
transforms[x + 1],
1564
transforms[x + 2],
1565
transforms[x + 3],
1566
transforms[x + 4],
1567
transforms[x + 5]);
1568
}
1569
1570
int transformCount() {
1571
if (transforms == null) {
1572
return 0;
1573
}
1574
return transforms.length / 6;
1575
}
1576
1577
/**
1578
* The strike cache works like this.
1579
*
1580
* -Each glyph is thought of as having a transform, usually identity.
1581
* -Each request for a strike is based on a device transform, either the
1582
* one in the frc or the rendering transform.
1583
* -For general info, strikes are held with soft references.
1584
* -When rendering, strikes must be held with hard references for the
1585
* duration of the rendering call. GlyphList will have to hold this
1586
* info along with the image and position info, but toss the strike info
1587
* when done.
1588
* -Build the strike cache as needed. If the dev transform we want to use
1589
* has changed from the last time it is built, the cache is flushed by
1590
* the caller before these methods are called.
1591
*
1592
* Use a tx that doesn't include translation components of dst tx.
1593
*/
1594
Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) {
1595
int len = sgv.glyphs.length;
1596
1597
GlyphStrike[] sl = getAllStrikes();
1598
for (int i = 0; i < len; ++i) {
1599
GlyphStrike gs = sl[indices[i]];
1600
int glyphID = sgv.glyphs[i];
1601
images[i] = gs.strike.getGlyphImagePtr(glyphID);
1602
1603
gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions);
1604
}
1605
tx.transform(positions, 0, positions, 0, len);
1606
1607
return sl;
1608
}
1609
1610
Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) {
1611
Rectangle result = null;
1612
Rectangle r = new Rectangle();
1613
Point2D.Float pt = new Point.Float();
1614
int n = start * 2;
1615
while (--count >= 0) {
1616
GlyphStrike gs = getStrike(start);
1617
pt.x = x + sgv.positions[n++] + gs.dx;
1618
pt.y = y + sgv.positions[n++] + gs.dy;
1619
tx.transform(pt, pt);
1620
gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r);
1621
if (!r.isEmpty()) {
1622
if (result == null) {
1623
result = new Rectangle(r);
1624
} else {
1625
result.add(r);
1626
}
1627
}
1628
}
1629
return result != null ? result : r;
1630
}
1631
1632
GlyphStrike getStrike(int glyphIndex) {
1633
if (indices != null) {
1634
GlyphStrike[] strikes = getStrikeArray();
1635
return getStrikeAtIndex(strikes, indices[glyphIndex]);
1636
}
1637
return sgv.getDefaultStrike();
1638
}
1639
1640
private GlyphStrike[] getAllStrikes() {
1641
if (indices == null) {
1642
return null;
1643
}
1644
1645
GlyphStrike[] strikes = getStrikeArray();
1646
if (!haveAllStrikes) {
1647
for (int i = 0; i < strikes.length; ++i) {
1648
getStrikeAtIndex(strikes, i);
1649
}
1650
haveAllStrikes = true;
1651
}
1652
1653
return strikes;
1654
}
1655
1656
private GlyphStrike[] getStrikeArray() {
1657
GlyphStrike[] strikes = null;
1658
if (strikesRef != null) {
1659
strikes = (GlyphStrike[])strikesRef.get();
1660
}
1661
if (strikes == null) {
1662
haveAllStrikes = false;
1663
strikes = new GlyphStrike[transformCount() + 1];
1664
strikesRef = new SoftReference(strikes);
1665
}
1666
1667
return strikes;
1668
}
1669
1670
private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) {
1671
GlyphStrike strike = strikes[strikeIndex];
1672
if (strike == null) {
1673
if (strikeIndex == 0) {
1674
strike = sgv.getDefaultStrike();
1675
} else {
1676
int ix = (strikeIndex - 1) * 6;
1677
AffineTransform gtx = new AffineTransform(transforms[ix],
1678
transforms[ix+1],
1679
transforms[ix+2],
1680
transforms[ix+3],
1681
transforms[ix+4],
1682
transforms[ix+5]);
1683
1684
strike = GlyphStrike.create(sgv, sgv.dtx, gtx);
1685
}
1686
strikes[strikeIndex] = strike;
1687
}
1688
return strike;
1689
}
1690
}
1691
1692
// This adjusts the metrics by the translation components of the glyph
1693
// transform. It is done here since the translation is not known by the
1694
// strike.
1695
// It adjusts the position of the image and the advance.
1696
1697
public static final class GlyphStrike {
1698
StandardGlyphVector sgv;
1699
FontStrike strike; // hard reference
1700
float dx;
1701
float dy;
1702
1703
static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) {
1704
float dx = 0;
1705
float dy = 0;
1706
1707
AffineTransform tx = sgv.ftx;
1708
if (!dtx.isIdentity() || gtx != null) {
1709
tx = new AffineTransform(sgv.ftx);
1710
if (gtx != null) {
1711
tx.preConcatenate(gtx);
1712
dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation
1713
dy = (float)tx.getTranslateY();
1714
}
1715
if (!dtx.isIdentity()) {
1716
tx.preConcatenate(dtx);
1717
}
1718
}
1719
1720
int ptSize = 1; // only matters for 'gasp' case.
1721
Object aaHint = sgv.frc.getAntiAliasingHint();
1722
if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) {
1723
/* Must pass in the calculated point size for rendering.
1724
* If the glyph tx is anything other than identity or a
1725
* simple translate, calculate the transformed point size.
1726
*/
1727
if (!tx.isIdentity() &&
1728
(tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) {
1729
double shearx = tx.getShearX();
1730
if (shearx != 0) {
1731
double scaley = tx.getScaleY();
1732
ptSize =
1733
(int)Math.sqrt(shearx * shearx + scaley * scaley);
1734
} else {
1735
ptSize = (int)(Math.abs(tx.getScaleY()));
1736
}
1737
}
1738
}
1739
int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize);
1740
int fm = FontStrikeDesc.getFMHintIntVal
1741
(sgv.frc.getFractionalMetricsHint());
1742
FontStrikeDesc desc = new FontStrikeDesc(dtx,
1743
tx,
1744
sgv.font.getStyle(),
1745
aa, fm);
1746
// Get the strike via the handle. Shouldn't matter
1747
// if we've invalidated the font but its an extra precaution.
1748
// do we want the CompFont from CFont here ?
1749
Font2D f2d = sgv.font2D;
1750
if (f2d instanceof FontSubstitution) {
1751
f2d = ((FontSubstitution)f2d).getCompositeFont2D();
1752
}
1753
FontStrike strike = f2d.handle.font2D.getStrike(desc); // !!! getStrike(desc, false)
1754
1755
return new GlyphStrike(sgv, strike, dx, dy);
1756
}
1757
1758
private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) {
1759
this.sgv = sgv;
1760
this.strike = strike;
1761
this.dx = dx;
1762
this.dy = dy;
1763
}
1764
1765
void getADL(ADL result) {
1766
StrikeMetrics sm = strike.getFontMetrics();
1767
Point2D.Float delta = null;
1768
if (sgv.font.isTransformed()) {
1769
delta = new Point2D.Float();
1770
delta.x = (float)sgv.font.getTransform().getTranslateX();
1771
delta.y = (float)sgv.font.getTransform().getTranslateY();
1772
}
1773
1774
result.ascentX = -sm.ascentX;
1775
result.ascentY = -sm.ascentY;
1776
result.descentX = sm.descentX;
1777
result.descentY = sm.descentY;
1778
result.leadingX = sm.leadingX;
1779
result.leadingY = sm.leadingY;
1780
}
1781
1782
void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) {
1783
result[ix] = positions[ix] + dx;
1784
++ix;
1785
result[ix] = positions[ix] + dy;
1786
}
1787
1788
void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) {
1789
// !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit.
1790
// strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-)
1791
Point2D.Float adv = strike.getGlyphMetrics(glyphID);
1792
result.x += adv.x + dx;
1793
result.y += adv.y + dy;
1794
}
1795
1796
Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) {
1797
Rectangle2D result = null;
1798
if (sgv.invdtx == null) {
1799
result = new Rectangle2D.Float();
1800
result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect
1801
} else {
1802
GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0);
1803
gp.transform(sgv.invdtx);
1804
result = gp.getBounds2D();
1805
}
1806
/* Since x is the logical advance of the glyph to this point.
1807
* Because of the way that Rectangle.union is specified, this
1808
* means that subsequent unioning of a rect including that
1809
* will be affected, even if the glyph is empty. So skip such
1810
* cases. This alone isn't a complete solution since x==0
1811
* may also not be what is wanted. The code that does the
1812
* unioning also needs to be aware to ignore empty glyphs.
1813
*/
1814
if (!result.isEmpty()) {
1815
result.setRect(result.getMinX() + x + dx,
1816
result.getMinY() + y + dy,
1817
result.getWidth(), result.getHeight());
1818
}
1819
return result;
1820
}
1821
1822
void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) {
1823
// !!! fontStrike needs a method for this. For that matter, GeneralPath does.
1824
GeneralPath gp = null;
1825
if (sgv.invdtx == null) {
1826
gp = strike.getGlyphOutline(glyphID, x + dx, y + dy);
1827
} else {
1828
gp = strike.getGlyphOutline(glyphID, 0, 0);
1829
gp.transform(sgv.invdtx);
1830
gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy));
1831
}
1832
PathIterator iterator = gp.getPathIterator(null);
1833
result.append(iterator, false);
1834
}
1835
}
1836
1837
public String toString() {
1838
return appendString(null).toString();
1839
}
1840
1841
StringBuffer appendString(StringBuffer buf) {
1842
if (buf == null) {
1843
buf = new StringBuffer();
1844
}
1845
try {
1846
buf.append("SGV{font: ");
1847
buf.append(font.toString());
1848
buf.append(", frc: ");
1849
buf.append(frc.toString());
1850
buf.append(", glyphs: (");
1851
buf.append(glyphs.length);
1852
buf.append(")[");
1853
for (int i = 0; i < glyphs.length; ++i) {
1854
if (i > 0) {
1855
buf.append(", ");
1856
}
1857
buf.append(Integer.toHexString(glyphs[i]));
1858
}
1859
buf.append("]");
1860
if (positions != null) {
1861
buf.append(", positions: (");
1862
buf.append(positions.length);
1863
buf.append(")[");
1864
for (int i = 0; i < positions.length; i += 2) {
1865
if (i > 0) {
1866
buf.append(", ");
1867
}
1868
buf.append(positions[i]);
1869
buf.append("@");
1870
buf.append(positions[i+1]);
1871
}
1872
buf.append("]");
1873
}
1874
if (charIndices != null) {
1875
buf.append(", indices: (");
1876
buf.append(charIndices.length);
1877
buf.append(")[");
1878
for (int i = 0; i < charIndices.length; ++i) {
1879
if (i > 0) {
1880
buf.append(", ");
1881
}
1882
buf.append(charIndices[i]);
1883
}
1884
buf.append("]");
1885
}
1886
buf.append(", flags:");
1887
if (getLayoutFlags() == 0) {
1888
buf.append(" default");
1889
} else {
1890
if ((flags & FLAG_HAS_TRANSFORMS) != 0) {
1891
buf.append(" tx");
1892
}
1893
if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
1894
buf.append(" pos");
1895
}
1896
if ((flags & FLAG_RUN_RTL) != 0) {
1897
buf.append(" rtl");
1898
}
1899
if ((flags & FLAG_COMPLEX_GLYPHS) != 0) {
1900
buf.append(" complex");
1901
}
1902
}
1903
}
1904
catch(Exception e) {
1905
buf.append(" " + e.getMessage());
1906
}
1907
buf.append("}");
1908
1909
return buf;
1910
}
1911
1912
static class ADL {
1913
public float ascentX;
1914
public float ascentY;
1915
public float descentX;
1916
public float descentY;
1917
public float leadingX;
1918
public float leadingY;
1919
1920
public String toString() {
1921
return toStringBuffer(null).toString();
1922
}
1923
1924
protected StringBuffer toStringBuffer(StringBuffer result) {
1925
if (result == null) {
1926
result = new StringBuffer();
1927
}
1928
result.append("ax: ");
1929
result.append(ascentX);
1930
result.append(" ay: ");
1931
result.append(ascentY);
1932
result.append(" dx: ");
1933
result.append(descentX);
1934
result.append(" dy: ");
1935
result.append(descentY);
1936
result.append(" lx: ");
1937
result.append(leadingX);
1938
result.append(" ly: ");
1939
result.append(leadingY);
1940
1941
return result;
1942
}
1943
}
1944
}
1945
1946