blob: edc9e67b6cbc47107f503148ff732fdf95fdaee9 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.font;
27
28import java.lang.ref.SoftReference;
29import java.awt.Font;
30import java.awt.Rectangle;
31import java.awt.geom.AffineTransform;
32import java.awt.geom.GeneralPath;
33import java.awt.geom.NoninvertibleTransformException;
34import java.awt.geom.Point2D;
35import java.awt.geom.Rectangle2D;
36import java.util.concurrent.ConcurrentHashMap;
37import static sun.awt.SunHints.*;
38
39
40public class FileFontStrike extends PhysicalStrike {
41
42 /* fffe and ffff are values we specially interpret as meaning
43 * invisible glyphs.
44 */
45 static final int INVISIBLE_GLYPHS = 0x0fffe;
46
47 private FileFont fileFont;
48
49 /* REMIND: replace this scheme with one that installs a cache
50 * instance of the appropriate type. It will require changes in
51 * FontStrikeDisposer and NativeStrike etc.
52 */
53 private static final int UNINITIALISED = 0;
54 private static final int INTARRAY = 1;
55 private static final int LONGARRAY = 2;
56 private static final int SEGINTARRAY = 3;
57 private static final int SEGLONGARRAY = 4;
58
59 private int glyphCacheFormat = UNINITIALISED;
60
61 /* segmented arrays are blocks of 256 */
62 private static final int SEGSHIFT = 8;
63 private static final int SEGSIZE = 1 << SEGSHIFT;
64
65 private boolean segmentedCache;
66 private int[][] segIntGlyphImages;
67 private long[][] segLongGlyphImages;
68
69 /* The "metrics" information requested by clients is usually nothing
70 * more than the horizontal advance of the character.
71 * In most cases this advance and other metrics information is stored
72 * in the glyph image cache.
73 * But in some cases we do not automatically retrieve the glyph
74 * image when the advance is requested. In those cases we want to
75 * cache the advances since this has been shown to be important for
76 * performance.
77 * The segmented cache is used in cases when the single array
78 * would be too large.
79 */
80 private float[] horizontalAdvances;
81 private float[][] segHorizontalAdvances;
82
83 /* Outline bounds are used when printing and when drawing outlines
84 * to the screen. On balance the relative rarity of these cases
85 * and the fact that getting this requires generating a path at
86 * the scaler level means that its probably OK to store these
87 * in a Java-level hashmap as the trade-off between time and space.
88 * Later can revisit whether to cache these at all, or elsewhere.
89 * Should also profile whether subsequent to getting the bounds, the
90 * outline itself is also requested. The 1.4 implementation doesn't
91 * cache outlines so you could generate the path twice - once to get
92 * the bounds and again to return the outline to the client.
93 * If the two uses are coincident then also look into caching outlines.
94 * One simple optimisation is that we could store the last single
95 * outline retrieved. This assumes that bounds then outline will always
96 * be retrieved for a glyph rather than retrieving bounds for all glyphs
97 * then outlines for all glyphs.
98 */
99 ConcurrentHashMap<Integer, Rectangle2D.Float> boundsMap;
100 SoftReference<ConcurrentHashMap<Integer, Point2D.Float>>
101 glyphMetricsMapRef;
102
103 AffineTransform invertDevTx;
104
105 boolean useNatives;
106 NativeStrike[] nativeStrikes;
107
108 FileFontStrike(FileFont fileFont, FontStrikeDesc desc) {
109 super(fileFont, desc);
110 this.fileFont = fileFont;
111
112 if (desc.style != fileFont.style) {
113 /* If using algorithmic styling, the base values are
114 * boldness = 1.0, italic = 0.0. The superclass constructor
115 * initialises these.
116 */
117 if ((desc.style & Font.ITALIC) == Font.ITALIC &&
118 (fileFont.style & Font.ITALIC) == 0) {
119 algoStyle = true;
120 italic = 0.7f;
121 }
122 if ((desc.style & Font.BOLD) == Font.BOLD &&
123 ((fileFont.style & Font.BOLD) == 0)) {
124 algoStyle = true;
125 boldness = 1.33f;
126 }
127 }
128 double[] matrix = new double[4];
129 AffineTransform at = desc.glyphTx;
130 at.getMatrix(matrix);
131 if (!desc.devTx.isIdentity() &&
132 desc.devTx.getType() != AffineTransform.TYPE_TRANSLATION) {
133 try {
134 invertDevTx = desc.devTx.createInverse();
135 } catch (NoninvertibleTransformException e) {
136 }
137 }
138
139 /* If any of the values is NaN then substitute the null scaler context.
140 * This will return null images, zero advance, and empty outlines
141 * as no rendering need take place in this case.
142 * We pass in the null scaler as the singleton null context
143 * requires it. However
144 */
145 if (Double.isNaN(matrix[0]) || Double.isNaN(matrix[1]) ||
146 Double.isNaN(matrix[2]) || Double.isNaN(matrix[3]) ||
147 fileFont.getScaler() == null) {
148 pScalerContext = NullFontScaler.getNullScalerContext();
149 } else {
150 pScalerContext = fileFont.getScaler().createScalerContext(matrix,
151 fileFont instanceof TrueTypeFont,
152 desc.aaHint, desc.fmHint,
153 boldness, italic);
154 }
155
156 mapper = fileFont.getMapper();
157 int numGlyphs = mapper.getNumGlyphs();
158
159 /* Always segment for fonts with > 2K glyphs, but also for smaller
160 * fonts with non-typical sizes and transforms.
161 * Segmenting for all non-typical pt sizes helps to minimise memory
162 * usage when very many distinct strikes are created.
163 * The size range of 0->5 and 37->INF for segmenting is arbitrary
164 * but the intention is that typical GUI integer point sizes (6->36)
165 * should not segment unless there's another reason to do so.
166 */
167 float ptSize = (float)matrix[3]; // interpreted only when meaningful.
168 int iSize = (int)ptSize;
169 boolean isSimpleTx = (at.getType() & complexTX) == 0;
170 segmentedCache =
171 (numGlyphs > SEGSIZE << 3) ||
172 ((numGlyphs > SEGSIZE << 1) &&
173 (!isSimpleTx || ptSize != iSize || iSize < 6 || iSize > 36));
174
175 /* This can only happen if we failed to allocate memory for context.
176 * NB: in such case we may still have some memory in java heap
177 * but subsequent attempt to allocate null scaler context
178 * may fail too (cause it is allocate in the native heap).
179 * It is not clear how to make this more robust but on the
180 * other hand getting NULL here seems to be extremely unlikely.
181 */
182 if (pScalerContext == 0L) {
183 /* REMIND: when the code is updated to install cache objects
184 * rather than using a switch this will be more efficient.
185 */
186 this.disposer = new FontStrikeDisposer(fileFont, desc);
187 initGlyphCache();
188 pScalerContext = NullFontScaler.getNullScalerContext();
189 FontManager.deRegisterBadFont(fileFont);
190 return;
191 }
192
193 if (fileFont.checkUseNatives() && desc.aaHint==0 && !algoStyle) {
194 /* Check its a simple scale of a pt size in the range
195 * where native bitmaps typically exist (6-36 pts) */
196 if (matrix[1] == 0.0 && matrix[2] == 0.0 &&
197 matrix[0] >= 6.0 && matrix[0] <= 36.0 &&
198 matrix[0] == matrix[3]) {
199 useNatives = true;
200 int numNatives = fileFont.nativeFonts.length;
201 nativeStrikes = new NativeStrike[numNatives];
202 /* Maybe initialise these strikes lazily?. But we
203 * know we need at least one
204 */
205 for (int i=0; i<numNatives; i++) {
206 nativeStrikes[i] =
207 new NativeStrike(fileFont.nativeFonts[i], desc, false);
208 }
209 }
210 }
211
212 this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext);
213
214 /* Always get the image and the advance together for smaller sizes
215 * that are likely to be important to rendering performance.
216 * The pixel size of 48.0 can be thought of as
217 * "maximumSizeForGetImageWithAdvance".
218 * This should be no greater than OutlineTextRender.THRESHOLD.
219 */
220 getImageWithAdvance = at.getScaleY() <= 48.0;
221
222 /* Some applications request advance frequently during layout.
223 * If we are not getting and caching the image with the advance,
224 * there is a potentially significant performance penalty if the
225 * advance is repeatedly requested before requesting the image.
226 * We should at least cache the horizontal advance.
227 * REMIND: could use info in the font, eg hmtx, to retrieve some
228 * advances. But still want to cache it here.
229 */
230
231 if (!getImageWithAdvance) {
232 if (!segmentedCache) {
233 horizontalAdvances = new float[numGlyphs];
234 /* use max float as uninitialised advance */
235 for (int i=0; i<numGlyphs; i++) {
236 horizontalAdvances[i] = Float.MAX_VALUE;
237 }
238 } else {
239 int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;
240 segHorizontalAdvances = new float[numSegments][];
241 }
242 }
243 }
244
245 /* A number of methods are delegated by the strike to the scaler
246 * context which is a shared resource on a physical font.
247 */
248
249 public int getNumGlyphs() {
250 return fileFont.getNumGlyphs();
251 }
252
253 /* Try the native strikes first, then try the fileFont strike */
254 long getGlyphImageFromNative(int glyphCode) {
255 long glyphPtr;
256 char charCode = fileFont.glyphToCharMap[glyphCode];
257 for (int i=0;i<nativeStrikes.length;i++) {
258 CharToGlyphMapper mapper = fileFont.nativeFonts[i].getMapper();
259 int gc = mapper.charToGlyph(charCode)&0xffff;
260 if (gc != mapper.getMissingGlyphCode()) {
261 glyphPtr = nativeStrikes[i].getGlyphImagePtrNoCache(gc);
262 if (glyphPtr != 0L) {
263 return glyphPtr;
264 }
265 }
266 }
267 return fileFont.getGlyphImage(pScalerContext, glyphCode);
268 }
269
270 long getGlyphImagePtr(int glyphCode) {
271 if (glyphCode >= INVISIBLE_GLYPHS) {
272 return StrikeCache.invisibleGlyphPtr;
273 }
274 long glyphPtr;
275 if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) {
276 return glyphPtr;
277 } else {
278 if (useNatives) {
279 glyphPtr = getGlyphImageFromNative(glyphCode);
280 } else {
281 glyphPtr = fileFont.getGlyphImage(pScalerContext,
282 glyphCode);
283 }
284 return setCachedGlyphPtr(glyphCode, glyphPtr);
285 }
286 }
287
288 void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
289
290 for (int i=0; i<len; i++) {
291 int glyphCode = glyphCodes[i];
292 if (glyphCode >= INVISIBLE_GLYPHS) {
293 images[i] = StrikeCache.invisibleGlyphPtr;
294 continue;
295 } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
296 continue;
297 } else {
298 long glyphPtr;
299 if (useNatives) {
300 glyphPtr = getGlyphImageFromNative(glyphCode);
301 } else {
302 glyphPtr = fileFont.getGlyphImage(pScalerContext,
303 glyphCode);
304 }
305 images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);
306 }
307 }
308 }
309
310 /* The following method is called from CompositeStrike as a special case.
311 */
312 private static final int SLOTZEROMAX = 0xffffff;
313 int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
314
315 int convertedCnt = 0;
316
317 for (int i=0; i<len; i++) {
318 int glyphCode = glyphCodes[i];
319 if (glyphCode >= SLOTZEROMAX) {
320 return convertedCnt;
321 } else {
322 convertedCnt++;
323 }
324 if (glyphCode >= INVISIBLE_GLYPHS) {
325 images[i] = StrikeCache.invisibleGlyphPtr;
326 continue;
327 } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {
328 continue;
329 } else {
330 long glyphPtr;
331 if (useNatives) {
332 glyphPtr = getGlyphImageFromNative(glyphCode);
333 } else {
334 glyphPtr = fileFont.getGlyphImage(pScalerContext,
335 glyphCode);
336 }
337 images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);
338 }
339 }
340 return convertedCnt;
341 }
342
343 /* Only look in the cache */
344 long getCachedGlyphPtr(int glyphCode) {
345 switch (glyphCacheFormat) {
346 case INTARRAY:
347 return intGlyphImages[glyphCode] & INTMASK;
348 case SEGINTARRAY:
349 int segIndex = glyphCode >> SEGSHIFT;
350 if (segIntGlyphImages[segIndex] != null) {
351 int subIndex = glyphCode % SEGSIZE;
352 return segIntGlyphImages[segIndex][subIndex] & INTMASK;
353 } else {
354 return 0L;
355 }
356 case LONGARRAY:
357 return longGlyphImages[glyphCode];
358 case SEGLONGARRAY:
359 segIndex = glyphCode >> SEGSHIFT;
360 if (segLongGlyphImages[segIndex] != null) {
361 int subIndex = glyphCode % SEGSIZE;
362 return segLongGlyphImages[segIndex][subIndex];
363 } else {
364 return 0L;
365 }
366 }
367 /* If reach here cache is UNINITIALISED. */
368 return 0L;
369 }
370
371 private synchronized long setCachedGlyphPtr(int glyphCode, long glyphPtr) {
372 switch (glyphCacheFormat) {
373 case INTARRAY:
374 if (intGlyphImages[glyphCode] == 0) {
375 intGlyphImages[glyphCode] = (int)glyphPtr;
376 return glyphPtr;
377 } else {
378 StrikeCache.freeIntPointer((int)glyphPtr);
379 return intGlyphImages[glyphCode] & INTMASK;
380 }
381
382 case SEGINTARRAY:
383 int segIndex = glyphCode >> SEGSHIFT;
384 int subIndex = glyphCode % SEGSIZE;
385 if (segIntGlyphImages[segIndex] == null) {
386 segIntGlyphImages[segIndex] = new int[SEGSIZE];
387 }
388 if (segIntGlyphImages[segIndex][subIndex] == 0) {
389 segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr;
390 return glyphPtr;
391 } else {
392 StrikeCache.freeIntPointer((int)glyphPtr);
393 return segIntGlyphImages[segIndex][subIndex] & INTMASK;
394 }
395
396 case LONGARRAY:
397 if (longGlyphImages[glyphCode] == 0L) {
398 longGlyphImages[glyphCode] = glyphPtr;
399 return glyphPtr;
400 } else {
401 StrikeCache.freeLongPointer(glyphPtr);
402 return longGlyphImages[glyphCode];
403 }
404
405 case SEGLONGARRAY:
406 segIndex = glyphCode >> SEGSHIFT;
407 subIndex = glyphCode % SEGSIZE;
408 if (segLongGlyphImages[segIndex] == null) {
409 segLongGlyphImages[segIndex] = new long[SEGSIZE];
410 }
411 if (segLongGlyphImages[segIndex][subIndex] == 0L) {
412 segLongGlyphImages[segIndex][subIndex] = glyphPtr;
413 return glyphPtr;
414 } else {
415 StrikeCache.freeLongPointer(glyphPtr);
416 return segLongGlyphImages[segIndex][subIndex];
417 }
418 }
419
420 /* Reach here only when the cache is not initialised which is only
421 * for the first glyph to be initialised in the strike.
422 * Initialise it and recurse. Note that we are already synchronized.
423 */
424 initGlyphCache();
425 return setCachedGlyphPtr(glyphCode, glyphPtr);
426 }
427
428 /* Called only from synchronized code or constructor */
429 private void initGlyphCache() {
430
431 int numGlyphs = mapper.getNumGlyphs();
432
433 if (segmentedCache) {
434 int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;
435 if (FontManager.longAddresses) {
436 glyphCacheFormat = SEGLONGARRAY;
437 segLongGlyphImages = new long[numSegments][];
438 this.disposer.segLongGlyphImages = segLongGlyphImages;
439 } else {
440 glyphCacheFormat = SEGINTARRAY;
441 segIntGlyphImages = new int[numSegments][];
442 this.disposer.segIntGlyphImages = segIntGlyphImages;
443 }
444 } else {
445 if (FontManager.longAddresses) {
446 glyphCacheFormat = LONGARRAY;
447 longGlyphImages = new long[numGlyphs];
448 this.disposer.longGlyphImages = longGlyphImages;
449 } else {
450 glyphCacheFormat = INTARRAY;
451 intGlyphImages = new int[numGlyphs];
452 this.disposer.intGlyphImages = intGlyphImages;
453 }
454 }
455 }
456
457 /* Metrics info is always retrieved. If the GlyphInfo address is non-zero
458 * then metrics info there is valid and can just be copied.
459 * This is in user space coordinates.
460 */
461 float getGlyphAdvance(int glyphCode) {
462 float advance;
463
464 if (glyphCode >= INVISIBLE_GLYPHS) {
465 return 0f;
466 }
467 if (horizontalAdvances != null) {
468 advance = horizontalAdvances[glyphCode];
469 if (advance != Float.MAX_VALUE) {
470 return advance;
471 }
472 } else if (segmentedCache && segHorizontalAdvances != null) {
473 int segIndex = glyphCode >> SEGSHIFT;
474 float[] subArray = segHorizontalAdvances[segIndex];
475 if (subArray != null) {
476 advance = subArray[glyphCode % SEGSIZE];
477 if (advance != Float.MAX_VALUE) {
478 return advance;
479 }
480 }
481 }
482
483 if (invertDevTx != null) {
484 /* If there is a device transform need x & y advance to
485 * transform back into user space.
486 */
487 advance = getGlyphMetrics(glyphCode).x;
488 } else {
489 long glyphPtr;
490 if (getImageWithAdvance) {
491 /* A heuristic optimisation says that for most cases its
492 * worthwhile retrieving the image at the same time as the
493 * advance. So here we get the image data even if its not
494 * already cached.
495 */
496 glyphPtr = getGlyphImagePtr(glyphCode);
497 } else {
498 glyphPtr = getCachedGlyphPtr(glyphCode);
499 }
500 if (glyphPtr != 0L) {
501 advance = StrikeCache.unsafe.getFloat
502 (glyphPtr + StrikeCache.xAdvanceOffset);
503
504 } else {
505 advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode);
506 }
507 }
508
509 if (horizontalAdvances != null) {
510 horizontalAdvances[glyphCode] = advance;
511 } else if (segmentedCache && segHorizontalAdvances != null) {
512 int segIndex = glyphCode >> SEGSHIFT;
513 int subIndex = glyphCode % SEGSIZE;
514 if (segHorizontalAdvances[segIndex] == null) {
515 segHorizontalAdvances[segIndex] = new float[SEGSIZE];
516 for (int i=0; i<SEGSIZE; i++) {
517 segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE;
518 }
519 }
520 segHorizontalAdvances[segIndex][subIndex] = advance;
521 }
522 return advance;
523 }
524
525 float getCodePointAdvance(int cp) {
526 return getGlyphAdvance(mapper.charToGlyph(cp));
527 }
528
529 /**
530 * Result and pt are both in device space.
531 */
532 void getGlyphImageBounds(int glyphCode, Point2D.Float pt,
533 Rectangle result) {
534
535 long ptr = getGlyphImagePtr(glyphCode);
536 float topLeftX, topLeftY;
537
538 /* With our current design NULL ptr is not possible
539 but if we eventually allow scalers to return NULL pointers
540 this check might be actually useful. */
541 if (ptr == 0L) {
542 result.x = (int) Math.floor(pt.x);
543 result.y = (int) Math.floor(pt.y);
544 result.width = result.height = 0;
545 return;
546 }
547
548 topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset);
549 topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset);
550
551 result.x = (int)Math.floor(pt.x + topLeftX);
552 result.y = (int)Math.floor(pt.y + topLeftY);
553 result.width =
554 StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff;
555 result.height =
556 StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff;
557
558 /* HRGB LCD text may have padding that is empty. This is almost always
559 * going to be when topLeftX is -2 or less.
560 * Try to return a tighter bounding box in that case.
561 * If the first three bytes of every row are all zero, then
562 * add 1 to "x" and reduce "width" by 1.
563 */
564 if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||
565 desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR)
566 && topLeftX <= -2.0f) {
567 int minx = getGlyphImageMinX(ptr, (int)result.x);
568 if (minx > result.x) {
569 result.x += 1;
570 result.width -=1;
571 }
572 }
573 }
574
575 private int getGlyphImageMinX(long ptr, int origMinX) {
576
577 int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset);
578 int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset);
579 int rowBytes =
580 StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset);
581
582 if (rowBytes == width) {
583 return origMinX;
584 }
585
586 long pixelData;
587 if (StrikeCache.nativeAddressSize == 4) {
588 pixelData = 0xffffffff &
589 StrikeCache.unsafe.getInt(ptr + StrikeCache.pixelDataOffset);
590 } else {
591 pixelData =
592 StrikeCache.unsafe.getLong(ptr + StrikeCache.pixelDataOffset);
593 }
594 if (pixelData == 0L) {
595 return origMinX;
596 }
597
598 for (int y=0;y<height;y++) {
599 for (int x=0;x<3;x++) {
600 if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) {
601 return origMinX;
602 }
603 }
604 }
605 return origMinX+1;
606 }
607
608 /* These 3 metrics methods below should be implemented to return
609 * values in user space.
610 */
611 StrikeMetrics getFontMetrics() {
612 if (strikeMetrics == null) {
613 strikeMetrics =
614 fileFont.getFontMetrics(pScalerContext);
615 if (invertDevTx != null) {
616 strikeMetrics.convertToUserSpace(invertDevTx);
617 }
618 }
619 return strikeMetrics;
620 }
621
622 Point2D.Float getGlyphMetrics(int glyphCode) {
623 Point2D.Float metrics = new Point2D.Float();
624
625 // !!! or do we force sgv user glyphs?
626 if (glyphCode >= INVISIBLE_GLYPHS) {
627 return metrics;
628 }
629 long glyphPtr;
630 if (getImageWithAdvance) {
631 /* A heuristic optimisation says that for most cases its
632 * worthwhile retrieving the image at the same time as the
633 * metrics. So here we get the image data even if its not
634 * already cached.
635 */
636 glyphPtr = getGlyphImagePtr(glyphCode);
637 } else {
638 glyphPtr = getCachedGlyphPtr(glyphCode);
639 }
640 if (glyphPtr != 0L) {
641 metrics = new Point2D.Float();
642 metrics.x = StrikeCache.unsafe.getFloat
643 (glyphPtr + StrikeCache.xAdvanceOffset);
644 metrics.y = StrikeCache.unsafe.getFloat
645 (glyphPtr + StrikeCache.yAdvanceOffset);
646 /* advance is currently in device space, need to convert back
647 * into user space.
648 * This must not include the translation component. */
649 if (invertDevTx != null) {
650 invertDevTx.deltaTransform(metrics, metrics);
651 }
652 } else {
653 /* We sometimes cache these metrics as they are expensive to
654 * generate for large glyphs.
655 * We never reach this path if we obtain images with advances.
656 * But if we do not obtain images with advances its possible that
657 * we first obtain this information, then the image, and never
658 * will access this value again.
659 */
660 Integer key = new Integer(glyphCode);
661 Point2D.Float value = null;
662 ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null;
663 if (glyphMetricsMapRef != null) {
664 glyphMetricsMap = glyphMetricsMapRef.get();
665 }
666 if (glyphMetricsMap != null) {
667 value = glyphMetricsMap.get(key);
668 if (value != null) {
669 metrics.x = value.x;
670 metrics.y = value.y;
671 /* already in user space */
672 return metrics;
673 }
674 }
675 if (value == null) {
676 fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);
677 /* advance is currently in device space, need to convert back
678 * into user space.
679 */
680 if (invertDevTx != null) {
681 invertDevTx.deltaTransform(metrics, metrics);
682 }
683 value = new Point2D.Float(metrics.x, metrics.y);
684 /* We aren't synchronizing here so it is possible to
685 * overwrite the map with another one but this is harmless.
686 */
687 if (glyphMetricsMap == null) {
688 glyphMetricsMap =
689 new ConcurrentHashMap<Integer, Point2D.Float>();
690 glyphMetricsMapRef =
691 new SoftReference<ConcurrentHashMap<Integer,
692 Point2D.Float>>(glyphMetricsMap);
693 }
694 glyphMetricsMap.put(key, value);
695 }
696 }
697 return metrics;
698 }
699
700 Point2D.Float getCharMetrics(char ch) {
701 return getGlyphMetrics(mapper.charToGlyph(ch));
702 }
703
704 /* The caller of this can be trusted to return a copy of this
705 * return value rectangle to public API. In fact frequently it
706 * can't use use this return value directly anyway.
707 * This returns bounds in device space. Currently the only
708 * caller is SGV and it converts back to user space.
709 * We could change things so that this code does the conversion so
710 * that all coords coming out of the font system are converted back
711 * into user space even if they were measured in device space.
712 * The same applies to the other methods that return outlines (below)
713 * But it may make particular sense for this method that caches its
714 * results.
715 * There'd be plenty of exceptions, to this too, eg getGlyphPoint needs
716 * device coords as its called from native layout and getGlyphImageBounds
717 * is used by GlyphVector.getGlyphPixelBounds which is specified to
718 * return device coordinates, the image pointers aren't really used
719 * up in Java code either.
720 */
721 Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
722
723 if (boundsMap == null) {
724 boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>();
725 }
726
727 Integer key = new Integer(glyphCode);
728 Rectangle2D.Float bounds = boundsMap.get(key);
729
730 if (bounds == null) {
731 bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);
732 boundsMap.put(key, bounds);
733 }
734 return bounds;
735 }
736
737 public Rectangle2D getOutlineBounds(int glyphCode) {
738 return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);
739 }
740
741 GeneralPath getGlyphOutline(int glyphCode, float x, float y) {
742 return fileFont.getGlyphOutline(pScalerContext, glyphCode, x, y);
743 }
744
745 GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
746 return fileFont.getGlyphVectorOutline(pScalerContext,
747 glyphs, glyphs.length, x, y);
748 }
749
750 protected void adjustPoint(Point2D.Float pt) {
751 if (invertDevTx != null) {
752 invertDevTx.deltaTransform(pt, pt);
753 }
754 }
755}