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