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