Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/font/FileFontStrike.java
41155 views
1
/*
2
* Copyright (c) 2003, 2020, 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.lang.ref.SoftReference;
29
import java.lang.ref.WeakReference;
30
import java.awt.Font;
31
import java.awt.GraphicsEnvironment;
32
import java.awt.Rectangle;
33
import java.awt.geom.AffineTransform;
34
import java.awt.geom.GeneralPath;
35
import java.awt.geom.NoninvertibleTransformException;
36
import java.awt.geom.Point2D;
37
import java.awt.geom.Rectangle2D;
38
import java.util.concurrent.ConcurrentHashMap;
39
import static sun.awt.SunHints.*;
40
41
42
public class FileFontStrike extends PhysicalStrike {
43
44
/* fffe and ffff are values we specially interpret as meaning
45
* invisible glyphs.
46
*/
47
static final int INVISIBLE_GLYPHS = 0x0fffe;
48
49
private FileFont fileFont;
50
51
/* REMIND: replace this scheme with one that installs a cache
52
* instance of the appropriate type. It will require changes in
53
* FontStrikeDisposer and NativeStrike etc.
54
*/
55
private static final int UNINITIALISED = 0;
56
private static final int INTARRAY = 1;
57
private static final int LONGARRAY = 2;
58
private static final int SEGINTARRAY = 3;
59
private static final int SEGLONGARRAY = 4;
60
61
private volatile int glyphCacheFormat = UNINITIALISED;
62
63
/* segmented arrays are blocks of 32 */
64
private static final int SEGSHIFT = 5;
65
private static final int SEGSIZE = 1 << SEGSHIFT;
66
67
private boolean segmentedCache;
68
private int[][] segIntGlyphImages;
69
private long[][] segLongGlyphImages;
70
71
/* The "metrics" information requested by clients is usually nothing
72
* more than the horizontal advance of the character.
73
* In most cases this advance and other metrics information is stored
74
* in the glyph image cache.
75
* But in some cases we do not automatically retrieve the glyph
76
* image when the advance is requested. In those cases we want to
77
* cache the advances since this has been shown to be important for
78
* performance.
79
* The segmented cache is used in cases when the single array
80
* would be too large.
81
*/
82
private float[] horizontalAdvances;
83
private float[][] segHorizontalAdvances;
84
85
/* Outline bounds are used when printing and when drawing outlines
86
* to the screen. On balance the relative rarity of these cases
87
* and the fact that getting this requires generating a path at
88
* the scaler level means that its probably OK to store these
89
* in a Java-level hashmap as the trade-off between time and space.
90
* Later can revisit whether to cache these at all, or elsewhere.
91
* Should also profile whether subsequent to getting the bounds, the
92
* outline itself is also requested. The 1.4 implementation doesn't
93
* cache outlines so you could generate the path twice - once to get
94
* the bounds and again to return the outline to the client.
95
* If the two uses are coincident then also look into caching outlines.
96
* One simple optimisation is that we could store the last single
97
* outline retrieved. This assumes that bounds then outline will always
98
* be retrieved for a glyph rather than retrieving bounds for all glyphs
99
* then outlines for all glyphs.
100
*/
101
ConcurrentHashMap<Integer, Rectangle2D.Float> boundsMap;
102
SoftReference<ConcurrentHashMap<Integer, Point2D.Float>>
103
glyphMetricsMapRef;
104
105
AffineTransform invertDevTx;
106
107
boolean useNatives;
108
NativeStrike[] nativeStrikes;
109
110
/* Used only for communication to native layer */
111
private int intPtSize;
112
113
/* Perform global initialisation needed for Windows native rasterizer */
114
private static native boolean initNative();
115
private static boolean isXPorLater = false;
116
static {
117
if (FontUtilities.isWindows && !FontUtilities.useJDKScaler &&
118
!GraphicsEnvironment.isHeadless()) {
119
isXPorLater = initNative();
120
}
121
}
122
123
FileFontStrike(FileFont fileFont, FontStrikeDesc desc) {
124
super(fileFont, desc);
125
this.fileFont = fileFont;
126
127
if (desc.style != fileFont.style) {
128
/* If using algorithmic styling, the base values are
129
* boldness = 1.0, italic = 0.0. The superclass constructor
130
* initialises these.
131
*/
132
if ((desc.style & Font.ITALIC) == Font.ITALIC &&
133
(fileFont.style & Font.ITALIC) == 0) {
134
algoStyle = true;
135
italic = 0.7f;
136
}
137
if ((desc.style & Font.BOLD) == Font.BOLD &&
138
((fileFont.style & Font.BOLD) == 0)) {
139
algoStyle = true;
140
boldness = 1.33f;
141
}
142
}
143
double[] matrix = new double[4];
144
AffineTransform at = desc.glyphTx;
145
at.getMatrix(matrix);
146
if (!desc.devTx.isIdentity() &&
147
desc.devTx.getType() != AffineTransform.TYPE_TRANSLATION) {
148
try {
149
invertDevTx = desc.devTx.createInverse();
150
} catch (NoninvertibleTransformException e) {
151
}
152
}
153
154
/* If any of the values is NaN then substitute the null scaler context.
155
* This will return null images, zero advance, and empty outlines
156
* as no rendering need take place in this case.
157
* We pass in the null scaler as the singleton null context
158
* requires it. However
159
*/
160
if (Double.isNaN(matrix[0]) || Double.isNaN(matrix[1]) ||
161
Double.isNaN(matrix[2]) || Double.isNaN(matrix[3]) ||
162
fileFont.getScaler() == null) {
163
pScalerContext = NullFontScaler.getNullScalerContext();
164
} else {
165
pScalerContext = fileFont.getScaler().createScalerContext(matrix,
166
desc.aaHint, desc.fmHint,
167
boldness, italic);
168
}
169
170
mapper = fileFont.getMapper();
171
int numGlyphs = mapper.getNumGlyphs();
172
173
/* Always segment for fonts with > 256 glyphs, but also for smaller
174
* fonts with non-typical sizes and transforms.
175
* Segmenting for all non-typical pt sizes helps to minimize memory
176
* usage when very many distinct strikes are created.
177
* The size range of 0->5 and 37->INF for segmenting is arbitrary
178
* but the intention is that typical GUI integer point sizes (6->36)
179
* should not segment unless there's another reason to do so.
180
*/
181
float ptSize = (float)matrix[3]; // interpreted only when meaningful.
182
int iSize = intPtSize = (int)ptSize;
183
boolean isSimpleTx = (at.getType() & complexTX) == 0;
184
segmentedCache =
185
(numGlyphs > SEGSIZE << 3) ||
186
((numGlyphs > SEGSIZE << 1) &&
187
(!isSimpleTx || ptSize != iSize || iSize < 6 || iSize > 36));
188
189
/* This can only happen if we failed to allocate memory for context.
190
* NB: in such case we may still have some memory in java heap
191
* but subsequent attempt to allocate null scaler context
192
* may fail too (cause it is allocate in the native heap).
193
* It is not clear how to make this more robust but on the
194
* other hand getting NULL here seems to be extremely unlikely.
195
*/
196
if (pScalerContext == 0L) {
197
/* REMIND: when the code is updated to install cache objects
198
* rather than using a switch this will be more efficient.
199
*/
200
this.disposer = new FontStrikeDisposer(fileFont, desc);
201
initGlyphCache();
202
pScalerContext = NullFontScaler.getNullScalerContext();
203
SunFontManager.getInstance().deRegisterBadFont(fileFont);
204
return;
205
}
206
/* First, see if native code should be used to create the glyph.
207
* GDI will return the integer metrics, not fractional metrics, which
208
* may be requested for this strike, so we would require here that :
209
* desc.fmHint != INTVAL_FRACTIONALMETRICS_ON
210
* except that the advance returned by GDI is always overwritten by
211
* the JDK rasteriser supplied one (see getGlyphImageFromWindows()).
212
*/
213
if (FontUtilities.isWindows && isXPorLater &&
214
!FontUtilities.useJDKScaler &&
215
!GraphicsEnvironment.isHeadless() &&
216
!fileFont.useJavaRasterizer &&
217
(desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||
218
desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) &&
219
(matrix[1] == 0.0 && matrix[2] == 0.0 &&
220
matrix[0] == matrix[3] &&
221
matrix[0] >= 3.0 && matrix[0] <= 100.0) &&
222
!((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) {
223
useNatives = true;
224
}
225
if (FontUtilities.isLogging() && FontUtilities.isWindows) {
226
FontUtilities.logInfo("Strike for " + fileFont + " at size = " + intPtSize +
227
" use natives = " + useNatives +
228
" useJavaRasteriser = " + fileFont.useJavaRasterizer +
229
" AAHint = " + desc.aaHint +
230
" Has Embedded bitmaps = " +
231
((TrueTypeFont)fileFont).
232
useEmbeddedBitmapsForSize(intPtSize));
233
}
234
this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext);
235
236
/* Always get the image and the advance together for smaller sizes
237
* that are likely to be important to rendering performance.
238
* The pixel size of 48.0 can be thought of as
239
* "maximumSizeForGetImageWithAdvance".
240
* This should be no greater than OutlineTextRender.THRESHOLD.
241
*/
242
double maxSz = 48.0;
243
getImageWithAdvance =
244
Math.abs(at.getScaleX()) <= maxSz &&
245
Math.abs(at.getScaleY()) <= maxSz &&
246
Math.abs(at.getShearX()) <= maxSz &&
247
Math.abs(at.getShearY()) <= maxSz;
248
249
/* Some applications request advance frequently during layout.
250
* If we are not getting and caching the image with the advance,
251
* there is a potentially significant performance penalty if the
252
* advance is repeatedly requested before requesting the image.
253
* We should at least cache the horizontal advance.
254
* REMIND: could use info in the font, eg hmtx, to retrieve some
255
* advances. But still want to cache it here.
256
*/
257
258
if (!getImageWithAdvance) {
259
if (!segmentedCache) {
260
horizontalAdvances = new float[numGlyphs];
261
/* use max float as uninitialised advance */
262
for (int i=0; i<numGlyphs; i++) {
263
horizontalAdvances[i] = Float.MAX_VALUE;
264
}
265
} else {
266
int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;
267
segHorizontalAdvances = new float[numSegments][];
268
}
269
}
270
}
271
272
/* A number of methods are delegated by the strike to the scaler
273
* context which is a shared resource on a physical font.
274
*/
275
276
public int getNumGlyphs() {
277
return fileFont.getNumGlyphs();
278
}
279
280
long getGlyphImageFromNative(int glyphCode) {
281
if (FontUtilities.isWindows) {
282
return getGlyphImageFromWindows(glyphCode);
283
} else {
284
return getGlyphImageFromX11(glyphCode);
285
}
286
}
287
288
/* There's no global state conflicts, so this method is not
289
* presently synchronized.
290
*/
291
private native long _getGlyphImageFromWindows(String family,
292
int style,
293
int size,
294
int glyphCode,
295
boolean fracMetrics,
296
int fontDataSize);
297
298
long getGlyphImageFromWindows(int glyphCode) {
299
String family = fileFont.getFamilyName(null);
300
int style = desc.style & Font.BOLD | desc.style & Font.ITALIC
301
| fileFont.getStyle();
302
int size = intPtSize;
303
long ptr = _getGlyphImageFromWindows
304
(family, style, size, glyphCode,
305
desc.fmHint == INTVAL_FRACTIONALMETRICS_ON,
306
((TrueTypeFont)fileFont).fontDataSize);
307
if (ptr != 0) {
308
/* Get the advance from the JDK rasterizer. This is mostly
309
* necessary for the fractional metrics case, but there are
310
* also some very small number (<0.25%) of marginal cases where
311
* there is some rounding difference between windows and JDK.
312
* After these are resolved, we can restrict this extra
313
* work to the FM case.
314
*/
315
float advance = getGlyphAdvance(glyphCode, false);
316
StrikeCache.unsafe.putFloat(ptr + StrikeCache.xAdvanceOffset,
317
advance);
318
return ptr;
319
} else {
320
if (FontUtilities.isLogging()) {
321
FontUtilities.logWarning("Failed to render glyph using GDI: code=" + glyphCode
322
+ ", fontFamily=" + family + ", style=" + style
323
+ ", size=" + size);
324
}
325
return fileFont.getGlyphImage(pScalerContext, glyphCode);
326
}
327
}
328
329
/* Try the native strikes first, then try the fileFont strike */
330
long getGlyphImageFromX11(int glyphCode) {
331
long glyphPtr;
332
char charCode = fileFont.glyphToCharMap[glyphCode];
333
for (int i=0;i<nativeStrikes.length;i++) {
334
CharToGlyphMapper mapper = fileFont.nativeFonts[i].getMapper();
335
int gc = mapper.charToGlyph(charCode)&0xffff;
336
if (gc != mapper.getMissingGlyphCode()) {
337
glyphPtr = nativeStrikes[i].getGlyphImagePtrNoCache(gc);
338
if (glyphPtr != 0L) {
339
return glyphPtr;
340
}
341
}
342
}
343
return fileFont.getGlyphImage(pScalerContext, glyphCode);
344
}
345
346
long getGlyphImagePtr(int glyphCode) {
347
if (glyphCode >= INVISIBLE_GLYPHS) {
348
return StrikeCache.invisibleGlyphPtr;
349
}
350
long glyphPtr = 0L;
351
if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) {
352
return glyphPtr;
353
} else {
354
if (useNatives) {
355
glyphPtr = getGlyphImageFromNative(glyphCode);
356
if (glyphPtr == 0L && FontUtilities.isLogging()) {
357
FontUtilities.logInfo("Strike for " + fileFont +
358
" at size = " + intPtSize +
359
" couldn't get native glyph for code = " + glyphCode);
360
}
361
}
362
if (glyphPtr == 0L) {
363
glyphPtr = fileFont.getGlyphImage(pScalerContext, glyphCode);
364
}
365
return setCachedGlyphPtr(glyphCode, glyphPtr);
366
}
367
}
368
369
void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
370
371
for (int i=0; i<len; i++) {
372
int glyphCode = glyphCodes[i];
373
if (glyphCode >= INVISIBLE_GLYPHS) {
374
images[i] = StrikeCache.invisibleGlyphPtr;
375
continue;
376
} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
377
continue;
378
} else {
379
long glyphPtr = 0L;
380
if (useNatives) {
381
glyphPtr = getGlyphImageFromNative(glyphCode);
382
} if (glyphPtr == 0L) {
383
glyphPtr = fileFont.getGlyphImage(pScalerContext,
384
glyphCode);
385
}
386
images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);
387
}
388
}
389
}
390
391
/* The following method is called from CompositeStrike as a special case.
392
*/
393
int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
394
395
int convertedCnt = 0;
396
397
for (int i=0; i<len; i++) {
398
int glyphCode = glyphCodes[i];
399
if (glyphCode >>> 24 != 0) {
400
return convertedCnt;
401
} else {
402
convertedCnt++;
403
}
404
if (glyphCode >= INVISIBLE_GLYPHS) {
405
images[i] = StrikeCache.invisibleGlyphPtr;
406
continue;
407
} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
408
continue;
409
} else {
410
long glyphPtr = 0L;
411
if (useNatives) {
412
glyphPtr = getGlyphImageFromNative(glyphCode);
413
}
414
if (glyphPtr == 0L) {
415
glyphPtr = fileFont.getGlyphImage(pScalerContext,
416
glyphCode);
417
}
418
images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);
419
}
420
}
421
return convertedCnt;
422
}
423
424
/* Only look in the cache */
425
long getCachedGlyphPtr(int glyphCode) {
426
try {
427
return getCachedGlyphPtrInternal(glyphCode);
428
} catch (Exception e) {
429
NullFontScaler nullScaler =
430
(NullFontScaler)FontScaler.getNullScaler();
431
long nullSC = NullFontScaler.getNullScalerContext();
432
return nullScaler.getGlyphImage(nullSC, glyphCode);
433
}
434
}
435
436
private long getCachedGlyphPtrInternal(int glyphCode) {
437
switch (glyphCacheFormat) {
438
case INTARRAY:
439
return intGlyphImages[glyphCode] & INTMASK;
440
case SEGINTARRAY:
441
int segIndex = glyphCode >> SEGSHIFT;
442
if (segIntGlyphImages[segIndex] != null) {
443
int subIndex = glyphCode % SEGSIZE;
444
return segIntGlyphImages[segIndex][subIndex] & INTMASK;
445
} else {
446
return 0L;
447
}
448
case LONGARRAY:
449
return longGlyphImages[glyphCode];
450
case SEGLONGARRAY:
451
segIndex = glyphCode >> SEGSHIFT;
452
if (segLongGlyphImages[segIndex] != null) {
453
int subIndex = glyphCode % SEGSIZE;
454
return segLongGlyphImages[segIndex][subIndex];
455
} else {
456
return 0L;
457
}
458
}
459
/* If reach here cache is UNINITIALISED. */
460
return 0L;
461
}
462
463
private synchronized long setCachedGlyphPtr(int glyphCode, long glyphPtr) {
464
try {
465
return setCachedGlyphPtrInternal(glyphCode, glyphPtr);
466
} catch (Exception e) {
467
switch (glyphCacheFormat) {
468
case INTARRAY:
469
case SEGINTARRAY:
470
StrikeCache.freeIntPointer((int)glyphPtr);
471
break;
472
case LONGARRAY:
473
case SEGLONGARRAY:
474
StrikeCache.freeLongPointer(glyphPtr);
475
break;
476
}
477
NullFontScaler nullScaler =
478
(NullFontScaler)FontScaler.getNullScaler();
479
long nullSC = NullFontScaler.getNullScalerContext();
480
return nullScaler.getGlyphImage(nullSC, glyphCode);
481
}
482
}
483
484
private long setCachedGlyphPtrInternal(int glyphCode, long glyphPtr) {
485
switch (glyphCacheFormat) {
486
case INTARRAY:
487
if (intGlyphImages[glyphCode] == 0) {
488
intGlyphImages[glyphCode] = (int)glyphPtr;
489
return glyphPtr;
490
} else {
491
StrikeCache.freeIntPointer((int)glyphPtr);
492
return intGlyphImages[glyphCode] & INTMASK;
493
}
494
495
case SEGINTARRAY:
496
int segIndex = glyphCode >> SEGSHIFT;
497
int subIndex = glyphCode % SEGSIZE;
498
if (segIntGlyphImages[segIndex] == null) {
499
segIntGlyphImages[segIndex] = new int[SEGSIZE];
500
}
501
if (segIntGlyphImages[segIndex][subIndex] == 0) {
502
segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr;
503
return glyphPtr;
504
} else {
505
StrikeCache.freeIntPointer((int)glyphPtr);
506
return segIntGlyphImages[segIndex][subIndex] & INTMASK;
507
}
508
509
case LONGARRAY:
510
if (longGlyphImages[glyphCode] == 0L) {
511
longGlyphImages[glyphCode] = glyphPtr;
512
return glyphPtr;
513
} else {
514
StrikeCache.freeLongPointer(glyphPtr);
515
return longGlyphImages[glyphCode];
516
}
517
518
case SEGLONGARRAY:
519
segIndex = glyphCode >> SEGSHIFT;
520
subIndex = glyphCode % SEGSIZE;
521
if (segLongGlyphImages[segIndex] == null) {
522
segLongGlyphImages[segIndex] = new long[SEGSIZE];
523
}
524
if (segLongGlyphImages[segIndex][subIndex] == 0L) {
525
segLongGlyphImages[segIndex][subIndex] = glyphPtr;
526
return glyphPtr;
527
} else {
528
StrikeCache.freeLongPointer(glyphPtr);
529
return segLongGlyphImages[segIndex][subIndex];
530
}
531
}
532
533
/* Reach here only when the cache is not initialised which is only
534
* for the first glyph to be initialised in the strike.
535
* Initialise it and recurse. Note that we are already synchronized.
536
*/
537
initGlyphCache();
538
return setCachedGlyphPtr(glyphCode, glyphPtr);
539
}
540
541
/* Called only from synchronized code or constructor */
542
private synchronized void initGlyphCache() {
543
544
int numGlyphs = mapper.getNumGlyphs();
545
int tmpFormat = UNINITIALISED;
546
if (segmentedCache) {
547
int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;
548
if (longAddresses) {
549
tmpFormat = SEGLONGARRAY;
550
segLongGlyphImages = new long[numSegments][];
551
this.disposer.segLongGlyphImages = segLongGlyphImages;
552
} else {
553
tmpFormat = SEGINTARRAY;
554
segIntGlyphImages = new int[numSegments][];
555
this.disposer.segIntGlyphImages = segIntGlyphImages;
556
}
557
} else {
558
if (longAddresses) {
559
tmpFormat = LONGARRAY;
560
longGlyphImages = new long[numGlyphs];
561
this.disposer.longGlyphImages = longGlyphImages;
562
} else {
563
tmpFormat = INTARRAY;
564
intGlyphImages = new int[numGlyphs];
565
this.disposer.intGlyphImages = intGlyphImages;
566
}
567
}
568
glyphCacheFormat = tmpFormat;
569
}
570
571
float getGlyphAdvance(int glyphCode) {
572
return getGlyphAdvance(glyphCode, true);
573
}
574
575
/* Metrics info is always retrieved. If the GlyphInfo address is non-zero
576
* then metrics info there is valid and can just be copied.
577
* This is in user space coordinates unless getUserAdv == false.
578
* Device space advance should not be propagated out of this class.
579
*/
580
private float getGlyphAdvance(int glyphCode, boolean getUserAdv) {
581
float advance;
582
583
if (glyphCode >= INVISIBLE_GLYPHS) {
584
return 0f;
585
}
586
587
/* Notes on the (getUserAdv == false) case.
588
*
589
* Setting getUserAdv == false is internal to this class.
590
* If there's no graphics transform we can let
591
* getGlyphAdvance take its course, and potentially caching in
592
* advances arrays, except for signalling that
593
* getUserAdv == false means there is no need to create an image.
594
* It is possible that code already calculated the user advance,
595
* and it is desirable to take advantage of that work.
596
* But, if there's a transform and we want device advance, we
597
* can't use any values cached in the advances arrays - unless
598
* first re-transform them into device space using 'desc.devTx'.
599
* invertDevTx is null if the graphics transform is identity,
600
* a translate, or non-invertible. The latter case should
601
* not ever occur in the getUserAdv == false path.
602
* In other words its either null, or the inversion of a
603
* simple uniform scale. If its null, we can populate and
604
* use the advance caches as normal.
605
*
606
* If we don't find a cached value, obtain the device advance and
607
* return it. This will get stashed on the image by the caller and any
608
* subsequent metrics calls will be able to use it as is the case
609
* whenever an image is what is initially requested.
610
*
611
* Don't query if there's a value cached on the image, since this
612
* getUserAdv==false code path is entered solely when none exists.
613
*/
614
if (horizontalAdvances != null) {
615
advance = horizontalAdvances[glyphCode];
616
if (advance != Float.MAX_VALUE) {
617
if (!getUserAdv && invertDevTx != null) {
618
Point2D.Float metrics = new Point2D.Float(advance, 0f);
619
desc.devTx.deltaTransform(metrics, metrics);
620
return metrics.x;
621
} else {
622
return advance;
623
}
624
}
625
} else if (segmentedCache && segHorizontalAdvances != null) {
626
int segIndex = glyphCode >> SEGSHIFT;
627
float[] subArray = segHorizontalAdvances[segIndex];
628
if (subArray != null) {
629
advance = subArray[glyphCode % SEGSIZE];
630
if (advance != Float.MAX_VALUE) {
631
if (!getUserAdv && invertDevTx != null) {
632
Point2D.Float metrics = new Point2D.Float(advance, 0f);
633
desc.devTx.deltaTransform(metrics, metrics);
634
return metrics.x;
635
} else {
636
return advance;
637
}
638
}
639
}
640
}
641
642
if (!getUserAdv && invertDevTx != null) {
643
Point2D.Float metrics = new Point2D.Float();
644
fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);
645
return metrics.x;
646
}
647
648
if (invertDevTx != null || !getUserAdv) {
649
/* If there is a device transform need x & y advance to
650
* transform back into user space.
651
*/
652
advance = getGlyphMetrics(glyphCode, getUserAdv).x;
653
} else {
654
long glyphPtr;
655
if (getImageWithAdvance) {
656
/* A heuristic optimisation says that for most cases its
657
* worthwhile retrieving the image at the same time as the
658
* advance. So here we get the image data even if its not
659
* already cached.
660
*/
661
glyphPtr = getGlyphImagePtr(glyphCode);
662
} else {
663
glyphPtr = getCachedGlyphPtr(glyphCode);
664
}
665
if (glyphPtr != 0L) {
666
advance = StrikeCache.unsafe.getFloat
667
(glyphPtr + StrikeCache.xAdvanceOffset);
668
669
} else {
670
advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode);
671
}
672
}
673
674
if (horizontalAdvances != null) {
675
horizontalAdvances[glyphCode] = advance;
676
} else if (segmentedCache && segHorizontalAdvances != null) {
677
int segIndex = glyphCode >> SEGSHIFT;
678
int subIndex = glyphCode % SEGSIZE;
679
if (segHorizontalAdvances[segIndex] == null) {
680
segHorizontalAdvances[segIndex] = new float[SEGSIZE];
681
for (int i=0; i<SEGSIZE; i++) {
682
segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE;
683
}
684
}
685
segHorizontalAdvances[segIndex][subIndex] = advance;
686
}
687
return advance;
688
}
689
690
float getCodePointAdvance(int cp) {
691
return getGlyphAdvance(mapper.charToGlyph(cp));
692
}
693
694
/**
695
* Result and pt are both in device space.
696
*/
697
void getGlyphImageBounds(int glyphCode, Point2D.Float pt,
698
Rectangle result) {
699
700
long ptr = getGlyphImagePtr(glyphCode);
701
float topLeftX, topLeftY;
702
703
/* With our current design NULL ptr is not possible
704
but if we eventually allow scalers to return NULL pointers
705
this check might be actually useful. */
706
if (ptr == 0L) {
707
result.x = (int) Math.floor(pt.x+0.5f);
708
result.y = (int) Math.floor(pt.y+0.5f);
709
result.width = result.height = 0;
710
return;
711
}
712
713
topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset);
714
topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset);
715
716
result.x = (int)Math.floor(pt.x + topLeftX + 0.5f);
717
result.y = (int)Math.floor(pt.y + topLeftY + 0.5f);
718
result.width =
719
StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff;
720
result.height =
721
StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff;
722
723
/* HRGB LCD text may have padding that is empty. This is almost always
724
* going to be when topLeftX is -2 or less.
725
* Try to return a tighter bounding box in that case.
726
* If the first three bytes of every row are all zero, then
727
* add 1 to "x" and reduce "width" by 1.
728
*/
729
if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||
730
desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR)
731
&& topLeftX <= -2.0f) {
732
int minx = getGlyphImageMinX(ptr, result.x);
733
if (minx > result.x) {
734
result.x += 1;
735
result.width -=1;
736
}
737
}
738
}
739
740
private int getGlyphImageMinX(long ptr, int origMinX) {
741
742
int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset);
743
int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset);
744
int rowBytes =
745
StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset);
746
747
if (rowBytes == width) {
748
return origMinX;
749
}
750
751
long pixelData =
752
StrikeCache.unsafe.getAddress(ptr + StrikeCache.pixelDataOffset);
753
754
if (pixelData == 0L) {
755
return origMinX;
756
}
757
758
for (int y=0;y<height;y++) {
759
for (int x=0;x<3;x++) {
760
if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) {
761
return origMinX;
762
}
763
}
764
}
765
return origMinX+1;
766
}
767
768
/* These 3 metrics methods below should be implemented to return
769
* values in user space.
770
*/
771
StrikeMetrics getFontMetrics() {
772
if (strikeMetrics == null) {
773
strikeMetrics =
774
fileFont.getFontMetrics(pScalerContext);
775
if (invertDevTx != null) {
776
strikeMetrics.convertToUserSpace(invertDevTx);
777
}
778
}
779
return strikeMetrics;
780
}
781
782
Point2D.Float getGlyphMetrics(int glyphCode) {
783
return getGlyphMetrics(glyphCode, true);
784
}
785
786
private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) {
787
Point2D.Float metrics = new Point2D.Float();
788
789
// !!! or do we force sgv user glyphs?
790
if (glyphCode >= INVISIBLE_GLYPHS) {
791
return metrics;
792
}
793
long glyphPtr;
794
if (getImageWithAdvance && getImage) {
795
/* A heuristic optimisation says that for most cases its
796
* worthwhile retrieving the image at the same time as the
797
* metrics. So here we get the image data even if its not
798
* already cached.
799
*/
800
glyphPtr = getGlyphImagePtr(glyphCode);
801
} else {
802
glyphPtr = getCachedGlyphPtr(glyphCode);
803
}
804
if (glyphPtr != 0L) {
805
metrics = new Point2D.Float();
806
metrics.x = StrikeCache.unsafe.getFloat
807
(glyphPtr + StrikeCache.xAdvanceOffset);
808
metrics.y = StrikeCache.unsafe.getFloat
809
(glyphPtr + StrikeCache.yAdvanceOffset);
810
/* advance is currently in device space, need to convert back
811
* into user space.
812
* This must not include the translation component. */
813
if (invertDevTx != null) {
814
invertDevTx.deltaTransform(metrics, metrics);
815
}
816
} else {
817
/* We sometimes cache these metrics as they are expensive to
818
* generate for large glyphs.
819
* We never reach this path if we obtain images with advances.
820
* But if we do not obtain images with advances its possible that
821
* we first obtain this information, then the image, and never
822
* will access this value again.
823
*/
824
Integer key = Integer.valueOf(glyphCode);
825
Point2D.Float value = null;
826
ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null;
827
if (glyphMetricsMapRef != null) {
828
glyphMetricsMap = glyphMetricsMapRef.get();
829
}
830
if (glyphMetricsMap != null) {
831
value = glyphMetricsMap.get(key);
832
if (value != null) {
833
metrics.x = value.x;
834
metrics.y = value.y;
835
/* already in user space */
836
return metrics;
837
}
838
}
839
if (value == null) {
840
fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);
841
/* advance is currently in device space, need to convert back
842
* into user space.
843
*/
844
if (invertDevTx != null) {
845
invertDevTx.deltaTransform(metrics, metrics);
846
}
847
value = new Point2D.Float(metrics.x, metrics.y);
848
/* We aren't synchronizing here so it is possible to
849
* overwrite the map with another one but this is harmless.
850
*/
851
if (glyphMetricsMap == null) {
852
glyphMetricsMap =
853
new ConcurrentHashMap<Integer, Point2D.Float>();
854
glyphMetricsMapRef =
855
new SoftReference<ConcurrentHashMap<Integer,
856
Point2D.Float>>(glyphMetricsMap);
857
}
858
glyphMetricsMap.put(key, value);
859
}
860
}
861
return metrics;
862
}
863
864
Point2D.Float getCharMetrics(char ch) {
865
return getGlyphMetrics(mapper.charToGlyph(ch));
866
}
867
868
/* The caller of this can be trusted to return a copy of this
869
* return value rectangle to public API. In fact frequently it
870
* can't use this return value directly anyway.
871
* This returns bounds in device space. Currently the only
872
* caller is SGV and it converts back to user space.
873
* We could change things so that this code does the conversion so
874
* that all coords coming out of the font system are converted back
875
* into user space even if they were measured in device space.
876
* The same applies to the other methods that return outlines (below)
877
* But it may make particular sense for this method that caches its
878
* results.
879
* There'd be plenty of exceptions, to this too, eg getGlyphPoint needs
880
* device coords as its called from native layout and getGlyphImageBounds
881
* is used by GlyphVector.getGlyphPixelBounds which is specified to
882
* return device coordinates, the image pointers aren't really used
883
* up in Java code either.
884
*/
885
Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
886
887
if (boundsMap == null) {
888
boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>();
889
}
890
891
Integer key = Integer.valueOf(glyphCode);
892
Rectangle2D.Float bounds = boundsMap.get(key);
893
894
if (bounds == null) {
895
bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);
896
boundsMap.put(key, bounds);
897
}
898
return bounds;
899
}
900
901
public Rectangle2D getOutlineBounds(int glyphCode) {
902
return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);
903
}
904
905
private
906
WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef;
907
908
GeneralPath getGlyphOutline(int glyphCode, float x, float y) {
909
910
GeneralPath gp = null;
911
ConcurrentHashMap<Integer, GeneralPath> outlineMap = null;
912
913
if (outlineMapRef != null) {
914
outlineMap = outlineMapRef.get();
915
if (outlineMap != null) {
916
gp = outlineMap.get(glyphCode);
917
}
918
}
919
920
if (gp == null) {
921
gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0);
922
if (outlineMap == null) {
923
outlineMap = new ConcurrentHashMap<Integer, GeneralPath>();
924
outlineMapRef =
925
new WeakReference
926
<ConcurrentHashMap<Integer,GeneralPath>>(outlineMap);
927
}
928
outlineMap.put(glyphCode, gp);
929
}
930
gp = (GeneralPath)gp.clone(); // mutable!
931
if (x != 0f || y != 0f) {
932
gp.transform(AffineTransform.getTranslateInstance(x, y));
933
}
934
return gp;
935
}
936
937
GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
938
return fileFont.getGlyphVectorOutline(pScalerContext,
939
glyphs, glyphs.length, x, y);
940
}
941
942
protected void adjustPoint(Point2D.Float pt) {
943
if (invertDevTx != null) {
944
invertDevTx.deltaTransform(pt, pt);
945
}
946
}
947
}
948
949