| /* |
| * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.font; |
| |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.awt.Font; |
| import java.awt.GraphicsEnvironment; |
| import java.awt.Rectangle; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.NoninvertibleTransformException; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.util.concurrent.ConcurrentHashMap; |
| import static sun.awt.SunHints.*; |
| |
| |
| public class FileFontStrike extends PhysicalStrike { |
| |
| /* fffe and ffff are values we specially interpret as meaning |
| * invisible glyphs. |
| */ |
| static final int INVISIBLE_GLYPHS = 0x0fffe; |
| |
| private FileFont fileFont; |
| |
| /* REMIND: replace this scheme with one that installs a cache |
| * instance of the appropriate type. It will require changes in |
| * FontStrikeDisposer and NativeStrike etc. |
| */ |
| private static final int UNINITIALISED = 0; |
| private static final int INTARRAY = 1; |
| private static final int LONGARRAY = 2; |
| private static final int SEGINTARRAY = 3; |
| private static final int SEGLONGARRAY = 4; |
| |
| private volatile int glyphCacheFormat = UNINITIALISED; |
| |
| /* segmented arrays are blocks of 32 */ |
| private static final int SEGSHIFT = 5; |
| private static final int SEGSIZE = 1 << SEGSHIFT; |
| |
| private boolean segmentedCache; |
| private int[][] segIntGlyphImages; |
| private long[][] segLongGlyphImages; |
| |
| /* The "metrics" information requested by clients is usually nothing |
| * more than the horizontal advance of the character. |
| * In most cases this advance and other metrics information is stored |
| * in the glyph image cache. |
| * But in some cases we do not automatically retrieve the glyph |
| * image when the advance is requested. In those cases we want to |
| * cache the advances since this has been shown to be important for |
| * performance. |
| * The segmented cache is used in cases when the single array |
| * would be too large. |
| */ |
| private float[] horizontalAdvances; |
| private float[][] segHorizontalAdvances; |
| |
| /* Outline bounds are used when printing and when drawing outlines |
| * to the screen. On balance the relative rarity of these cases |
| * and the fact that getting this requires generating a path at |
| * the scaler level means that its probably OK to store these |
| * in a Java-level hashmap as the trade-off between time and space. |
| * Later can revisit whether to cache these at all, or elsewhere. |
| * Should also profile whether subsequent to getting the bounds, the |
| * outline itself is also requested. The 1.4 implementation doesn't |
| * cache outlines so you could generate the path twice - once to get |
| * the bounds and again to return the outline to the client. |
| * If the two uses are coincident then also look into caching outlines. |
| * One simple optimisation is that we could store the last single |
| * outline retrieved. This assumes that bounds then outline will always |
| * be retrieved for a glyph rather than retrieving bounds for all glyphs |
| * then outlines for all glyphs. |
| */ |
| ConcurrentHashMap<Integer, Rectangle2D.Float> boundsMap; |
| SoftReference<ConcurrentHashMap<Integer, Point2D.Float>> |
| glyphMetricsMapRef; |
| |
| AffineTransform invertDevTx; |
| |
| boolean useNatives; |
| NativeStrike[] nativeStrikes; |
| |
| /* Used only for communication to native layer */ |
| private int intPtSize; |
| |
| /* Perform global initialisation needed for Windows native rasterizer */ |
| private static native boolean initNative(); |
| private static boolean isXPorLater = false; |
| static { |
| if (FontUtilities.isWindows && !FontUtilities.useT2K && |
| !GraphicsEnvironment.isHeadless()) { |
| isXPorLater = initNative(); |
| } |
| } |
| |
| FileFontStrike(FileFont fileFont, FontStrikeDesc desc) { |
| super(fileFont, desc); |
| this.fileFont = fileFont; |
| |
| if (desc.style != fileFont.style) { |
| /* If using algorithmic styling, the base values are |
| * boldness = 1.0, italic = 0.0. The superclass constructor |
| * initialises these. |
| */ |
| if ((desc.style & Font.ITALIC) == Font.ITALIC && |
| (fileFont.style & Font.ITALIC) == 0) { |
| algoStyle = true; |
| italic = 0.7f; |
| } |
| if ((desc.style & Font.BOLD) == Font.BOLD && |
| ((fileFont.style & Font.BOLD) == 0)) { |
| algoStyle = true; |
| boldness = 1.33f; |
| } |
| } |
| double[] matrix = new double[4]; |
| AffineTransform at = desc.glyphTx; |
| at.getMatrix(matrix); |
| if (!desc.devTx.isIdentity() && |
| desc.devTx.getType() != AffineTransform.TYPE_TRANSLATION) { |
| try { |
| invertDevTx = desc.devTx.createInverse(); |
| } catch (NoninvertibleTransformException e) { |
| } |
| } |
| |
| /* Amble fonts are better rendered unhinted although there's the |
| * inevitable fuzziness that accompanies this due to no longer |
| * snapping stems to the pixel grid. The exception is that in B&W |
| * mode they are worse without hinting. The down side to that is that |
| * B&W metrics will differ which normally isn't the case, although |
| * since AA mode is part of the measuring context that should be OK. |
| * We don't expect Amble to be installed in the Windows fonts folder. |
| * If we were to, then we'd also might want to disable using the |
| * native rasteriser path which is used for LCD mode for platform |
| * fonts. since we have no way to disable hinting by GDI. |
| * In the case of Amble, since its 'gasp' table says to disable |
| * hinting, I'd expect GDI to follow that, so likely it should |
| * all be consistent even if GDI used. |
| */ |
| boolean disableHinting = desc.aaHint != INTVAL_TEXT_ANTIALIAS_OFF && |
| fileFont.familyName.startsWith("Amble"); |
| |
| /* If any of the values is NaN then substitute the null scaler context. |
| * This will return null images, zero advance, and empty outlines |
| * as no rendering need take place in this case. |
| * We pass in the null scaler as the singleton null context |
| * requires it. However |
| */ |
| if (Double.isNaN(matrix[0]) || Double.isNaN(matrix[1]) || |
| Double.isNaN(matrix[2]) || Double.isNaN(matrix[3]) || |
| fileFont.getScaler() == null) { |
| pScalerContext = NullFontScaler.getNullScalerContext(); |
| } else { |
| pScalerContext = fileFont.getScaler().createScalerContext(matrix, |
| desc.aaHint, desc.fmHint, |
| boldness, italic, disableHinting); |
| } |
| |
| mapper = fileFont.getMapper(); |
| int numGlyphs = mapper.getNumGlyphs(); |
| |
| /* Always segment for fonts with > 256 glyphs, but also for smaller |
| * fonts with non-typical sizes and transforms. |
| * Segmenting for all non-typical pt sizes helps to minimize memory |
| * usage when very many distinct strikes are created. |
| * The size range of 0->5 and 37->INF for segmenting is arbitrary |
| * but the intention is that typical GUI integer point sizes (6->36) |
| * should not segment unless there's another reason to do so. |
| */ |
| float ptSize = (float)matrix[3]; // interpreted only when meaningful. |
| int iSize = intPtSize = (int)ptSize; |
| boolean isSimpleTx = (at.getType() & complexTX) == 0; |
| segmentedCache = |
| (numGlyphs > SEGSIZE << 3) || |
| ((numGlyphs > SEGSIZE << 1) && |
| (!isSimpleTx || ptSize != iSize || iSize < 6 || iSize > 36)); |
| |
| /* This can only happen if we failed to allocate memory for context. |
| * NB: in such case we may still have some memory in java heap |
| * but subsequent attempt to allocate null scaler context |
| * may fail too (cause it is allocate in the native heap). |
| * It is not clear how to make this more robust but on the |
| * other hand getting NULL here seems to be extremely unlikely. |
| */ |
| if (pScalerContext == 0L) { |
| /* REMIND: when the code is updated to install cache objects |
| * rather than using a switch this will be more efficient. |
| */ |
| this.disposer = new FontStrikeDisposer(fileFont, desc); |
| initGlyphCache(); |
| pScalerContext = NullFontScaler.getNullScalerContext(); |
| SunFontManager.getInstance().deRegisterBadFont(fileFont); |
| return; |
| } |
| /* First, see if native code should be used to create the glyph. |
| * GDI will return the integer metrics, not fractional metrics, which |
| * may be requested for this strike, so we would require here that : |
| * desc.fmHint != INTVAL_FRACTIONALMETRICS_ON |
| * except that the advance returned by GDI is always overwritten by |
| * the JDK rasteriser supplied one (see getGlyphImageFromWindows()). |
| */ |
| if (FontUtilities.isWindows && isXPorLater && |
| !FontUtilities.useT2K && |
| !GraphicsEnvironment.isHeadless() && |
| !fileFont.useJavaRasterizer && |
| (desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB || |
| desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) && |
| (matrix[1] == 0.0 && matrix[2] == 0.0 && |
| matrix[0] == matrix[3] && |
| matrix[0] >= 3.0 && matrix[0] <= 100.0) && |
| !((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) { |
| useNatives = true; |
| } |
| else if (fileFont.checkUseNatives() && desc.aaHint==0 && !algoStyle) { |
| /* Check its a simple scale of a pt size in the range |
| * where native bitmaps typically exist (6-36 pts) */ |
| if (matrix[1] == 0.0 && matrix[2] == 0.0 && |
| matrix[0] >= 6.0 && matrix[0] <= 36.0 && |
| matrix[0] == matrix[3]) { |
| useNatives = true; |
| int numNatives = fileFont.nativeFonts.length; |
| nativeStrikes = new NativeStrike[numNatives]; |
| /* Maybe initialise these strikes lazily?. But we |
| * know we need at least one |
| */ |
| for (int i=0; i<numNatives; i++) { |
| nativeStrikes[i] = |
| new NativeStrike(fileFont.nativeFonts[i], desc, false); |
| } |
| } |
| } |
| if (FontUtilities.isLogging() && FontUtilities.isWindows) { |
| FontUtilities.getLogger().info |
| ("Strike for " + fileFont + " at size = " + intPtSize + |
| " use natives = " + useNatives + |
| " useJavaRasteriser = " + fileFont.useJavaRasterizer + |
| " AAHint = " + desc.aaHint + |
| " Has Embedded bitmaps = " + |
| ((TrueTypeFont)fileFont). |
| useEmbeddedBitmapsForSize(intPtSize)); |
| } |
| this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext); |
| |
| /* Always get the image and the advance together for smaller sizes |
| * that are likely to be important to rendering performance. |
| * The pixel size of 48.0 can be thought of as |
| * "maximumSizeForGetImageWithAdvance". |
| * This should be no greater than OutlineTextRender.THRESHOLD. |
| */ |
| double maxSz = 48.0; |
| getImageWithAdvance = |
| Math.abs(at.getScaleX()) <= maxSz && |
| Math.abs(at.getScaleY()) <= maxSz && |
| Math.abs(at.getShearX()) <= maxSz && |
| Math.abs(at.getShearY()) <= maxSz; |
| |
| /* Some applications request advance frequently during layout. |
| * If we are not getting and caching the image with the advance, |
| * there is a potentially significant performance penalty if the |
| * advance is repeatedly requested before requesting the image. |
| * We should at least cache the horizontal advance. |
| * REMIND: could use info in the font, eg hmtx, to retrieve some |
| * advances. But still want to cache it here. |
| */ |
| |
| if (!getImageWithAdvance) { |
| if (!segmentedCache) { |
| horizontalAdvances = new float[numGlyphs]; |
| /* use max float as uninitialised advance */ |
| for (int i=0; i<numGlyphs; i++) { |
| horizontalAdvances[i] = Float.MAX_VALUE; |
| } |
| } else { |
| int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE; |
| segHorizontalAdvances = new float[numSegments][]; |
| } |
| } |
| } |
| |
| /* A number of methods are delegated by the strike to the scaler |
| * context which is a shared resource on a physical font. |
| */ |
| |
| public int getNumGlyphs() { |
| return fileFont.getNumGlyphs(); |
| } |
| |
| long getGlyphImageFromNative(int glyphCode) { |
| if (FontUtilities.isWindows) { |
| return getGlyphImageFromWindows(glyphCode); |
| } else { |
| return getGlyphImageFromX11(glyphCode); |
| } |
| } |
| |
| /* There's no global state conflicts, so this method is not |
| * presently synchronized. |
| */ |
| private native long _getGlyphImageFromWindows(String family, |
| int style, |
| int size, |
| int glyphCode, |
| boolean fracMetrics); |
| |
| long getGlyphImageFromWindows(int glyphCode) { |
| String family = fileFont.getFamilyName(null); |
| int style = desc.style & Font.BOLD | desc.style & Font.ITALIC |
| | fileFont.getStyle(); |
| int size = intPtSize; |
| long ptr = _getGlyphImageFromWindows |
| (family, style, size, glyphCode, |
| desc.fmHint == INTVAL_FRACTIONALMETRICS_ON); |
| if (ptr != 0) { |
| /* Get the advance from the JDK rasterizer. This is mostly |
| * necessary for the fractional metrics case, but there are |
| * also some very small number (<0.25%) of marginal cases where |
| * there is some rounding difference between windows and JDK. |
| * After these are resolved, we can restrict this extra |
| * work to the FM case. |
| */ |
| float advance = getGlyphAdvance(glyphCode, false); |
| StrikeCache.unsafe.putFloat(ptr + StrikeCache.xAdvanceOffset, |
| advance); |
| return ptr; |
| } else { |
| return fileFont.getGlyphImage(pScalerContext, glyphCode); |
| } |
| } |
| |
| /* Try the native strikes first, then try the fileFont strike */ |
| long getGlyphImageFromX11(int glyphCode) { |
| long glyphPtr; |
| char charCode = fileFont.glyphToCharMap[glyphCode]; |
| for (int i=0;i<nativeStrikes.length;i++) { |
| CharToGlyphMapper mapper = fileFont.nativeFonts[i].getMapper(); |
| int gc = mapper.charToGlyph(charCode)&0xffff; |
| if (gc != mapper.getMissingGlyphCode()) { |
| glyphPtr = nativeStrikes[i].getGlyphImagePtrNoCache(gc); |
| if (glyphPtr != 0L) { |
| return glyphPtr; |
| } |
| } |
| } |
| return fileFont.getGlyphImage(pScalerContext, glyphCode); |
| } |
| |
| long getGlyphImagePtr(int glyphCode) { |
| if (glyphCode >= INVISIBLE_GLYPHS) { |
| return StrikeCache.invisibleGlyphPtr; |
| } |
| long glyphPtr = 0L; |
| if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) { |
| return glyphPtr; |
| } else { |
| if (useNatives) { |
| glyphPtr = getGlyphImageFromNative(glyphCode); |
| if (glyphPtr == 0L && FontUtilities.isLogging()) { |
| FontUtilities.getLogger().info |
| ("Strike for " + fileFont + |
| " at size = " + intPtSize + |
| " couldn't get native glyph for code = " + glyphCode); |
| } |
| } if (glyphPtr == 0L) { |
| glyphPtr = fileFont.getGlyphImage(pScalerContext, |
| glyphCode); |
| } |
| return setCachedGlyphPtr(glyphCode, glyphPtr); |
| } |
| } |
| |
| void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { |
| |
| for (int i=0; i<len; i++) { |
| int glyphCode = glyphCodes[i]; |
| if (glyphCode >= INVISIBLE_GLYPHS) { |
| images[i] = StrikeCache.invisibleGlyphPtr; |
| continue; |
| } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) { |
| continue; |
| } else { |
| long glyphPtr = 0L; |
| if (useNatives) { |
| glyphPtr = getGlyphImageFromNative(glyphCode); |
| } if (glyphPtr == 0L) { |
| glyphPtr = fileFont.getGlyphImage(pScalerContext, |
| glyphCode); |
| } |
| images[i] = setCachedGlyphPtr(glyphCode, glyphPtr); |
| } |
| } |
| } |
| |
| /* The following method is called from CompositeStrike as a special case. |
| */ |
| private static final int SLOTZEROMAX = 0xffffff; |
| int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) { |
| |
| int convertedCnt = 0; |
| |
| for (int i=0; i<len; i++) { |
| int glyphCode = glyphCodes[i]; |
| if (glyphCode >= SLOTZEROMAX) { |
| return convertedCnt; |
| } else { |
| convertedCnt++; |
| } |
| if (glyphCode >= INVISIBLE_GLYPHS) { |
| images[i] = StrikeCache.invisibleGlyphPtr; |
| continue; |
| } else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) { |
| continue; |
| } else { |
| long glyphPtr = 0L; |
| if (useNatives) { |
| glyphPtr = getGlyphImageFromNative(glyphCode); |
| } |
| if (glyphPtr == 0L) { |
| glyphPtr = fileFont.getGlyphImage(pScalerContext, |
| glyphCode); |
| } |
| images[i] = setCachedGlyphPtr(glyphCode, glyphPtr); |
| } |
| } |
| return convertedCnt; |
| } |
| |
| /* Only look in the cache */ |
| long getCachedGlyphPtr(int glyphCode) { |
| switch (glyphCacheFormat) { |
| case INTARRAY: |
| return intGlyphImages[glyphCode] & INTMASK; |
| case SEGINTARRAY: |
| int segIndex = glyphCode >> SEGSHIFT; |
| if (segIntGlyphImages[segIndex] != null) { |
| int subIndex = glyphCode % SEGSIZE; |
| return segIntGlyphImages[segIndex][subIndex] & INTMASK; |
| } else { |
| return 0L; |
| } |
| case LONGARRAY: |
| return longGlyphImages[glyphCode]; |
| case SEGLONGARRAY: |
| segIndex = glyphCode >> SEGSHIFT; |
| if (segLongGlyphImages[segIndex] != null) { |
| int subIndex = glyphCode % SEGSIZE; |
| return segLongGlyphImages[segIndex][subIndex]; |
| } else { |
| return 0L; |
| } |
| } |
| /* If reach here cache is UNINITIALISED. */ |
| return 0L; |
| } |
| |
| private synchronized long setCachedGlyphPtr(int glyphCode, long glyphPtr) { |
| switch (glyphCacheFormat) { |
| case INTARRAY: |
| if (intGlyphImages[glyphCode] == 0) { |
| intGlyphImages[glyphCode] = (int)glyphPtr; |
| return glyphPtr; |
| } else { |
| StrikeCache.freeIntPointer((int)glyphPtr); |
| return intGlyphImages[glyphCode] & INTMASK; |
| } |
| |
| case SEGINTARRAY: |
| int segIndex = glyphCode >> SEGSHIFT; |
| int subIndex = glyphCode % SEGSIZE; |
| if (segIntGlyphImages[segIndex] == null) { |
| segIntGlyphImages[segIndex] = new int[SEGSIZE]; |
| } |
| if (segIntGlyphImages[segIndex][subIndex] == 0) { |
| segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr; |
| return glyphPtr; |
| } else { |
| StrikeCache.freeIntPointer((int)glyphPtr); |
| return segIntGlyphImages[segIndex][subIndex] & INTMASK; |
| } |
| |
| case LONGARRAY: |
| if (longGlyphImages[glyphCode] == 0L) { |
| longGlyphImages[glyphCode] = glyphPtr; |
| return glyphPtr; |
| } else { |
| StrikeCache.freeLongPointer(glyphPtr); |
| return longGlyphImages[glyphCode]; |
| } |
| |
| case SEGLONGARRAY: |
| segIndex = glyphCode >> SEGSHIFT; |
| subIndex = glyphCode % SEGSIZE; |
| if (segLongGlyphImages[segIndex] == null) { |
| segLongGlyphImages[segIndex] = new long[SEGSIZE]; |
| } |
| if (segLongGlyphImages[segIndex][subIndex] == 0L) { |
| segLongGlyphImages[segIndex][subIndex] = glyphPtr; |
| return glyphPtr; |
| } else { |
| StrikeCache.freeLongPointer(glyphPtr); |
| return segLongGlyphImages[segIndex][subIndex]; |
| } |
| } |
| |
| /* Reach here only when the cache is not initialised which is only |
| * for the first glyph to be initialised in the strike. |
| * Initialise it and recurse. Note that we are already synchronized. |
| */ |
| initGlyphCache(); |
| return setCachedGlyphPtr(glyphCode, glyphPtr); |
| } |
| |
| /* Called only from synchronized code or constructor */ |
| private synchronized void initGlyphCache() { |
| |
| int numGlyphs = mapper.getNumGlyphs(); |
| int tmpFormat = UNINITIALISED; |
| if (segmentedCache) { |
| int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE; |
| if (longAddresses) { |
| tmpFormat = SEGLONGARRAY; |
| segLongGlyphImages = new long[numSegments][]; |
| this.disposer.segLongGlyphImages = segLongGlyphImages; |
| } else { |
| tmpFormat = SEGINTARRAY; |
| segIntGlyphImages = new int[numSegments][]; |
| this.disposer.segIntGlyphImages = segIntGlyphImages; |
| } |
| } else { |
| if (longAddresses) { |
| tmpFormat = LONGARRAY; |
| longGlyphImages = new long[numGlyphs]; |
| this.disposer.longGlyphImages = longGlyphImages; |
| } else { |
| tmpFormat = INTARRAY; |
| intGlyphImages = new int[numGlyphs]; |
| this.disposer.intGlyphImages = intGlyphImages; |
| } |
| } |
| glyphCacheFormat = tmpFormat; |
| } |
| |
| float getGlyphAdvance(int glyphCode) { |
| return getGlyphAdvance(glyphCode, true); |
| } |
| |
| /* Metrics info is always retrieved. If the GlyphInfo address is non-zero |
| * then metrics info there is valid and can just be copied. |
| * This is in user space coordinates unless getUserAdv == false. |
| * Device space advance should not be propagated out of this class. |
| */ |
| private float getGlyphAdvance(int glyphCode, boolean getUserAdv) { |
| float advance; |
| |
| if (glyphCode >= INVISIBLE_GLYPHS) { |
| return 0f; |
| } |
| |
| /* Notes on the (getUserAdv == false) case. |
| * |
| * Setting getUserAdv == false is internal to this class. |
| * If there's no graphics transform we can let |
| * getGlyphAdvance take its course, and potentially caching in |
| * advances arrays, except for signalling that |
| * getUserAdv == false means there is no need to create an image. |
| * It is possible that code already calculated the user advance, |
| * and it is desirable to take advantage of that work. |
| * But, if there's a transform and we want device advance, we |
| * can't use any values cached in the advances arrays - unless |
| * first re-transform them into device space using 'desc.devTx'. |
| * invertDevTx is null if the graphics transform is identity, |
| * a translate, or non-invertible. The latter case should |
| * not ever occur in the getUserAdv == false path. |
| * In other words its either null, or the inversion of a |
| * simple uniform scale. If its null, we can populate and |
| * use the advance caches as normal. |
| * |
| * If we don't find a cached value, obtain the device advance and |
| * return it. This will get stashed on the image by the caller and any |
| * subsequent metrics calls will be able to use it as is the case |
| * whenever an image is what is initially requested. |
| * |
| * Don't query if there's a value cached on the image, since this |
| * getUserAdv==false code path is entered solely when none exists. |
| */ |
| if (horizontalAdvances != null) { |
| advance = horizontalAdvances[glyphCode]; |
| if (advance != Float.MAX_VALUE) { |
| if (!getUserAdv && invertDevTx != null) { |
| Point2D.Float metrics = new Point2D.Float(advance, 0f); |
| desc.devTx.deltaTransform(metrics, metrics); |
| return metrics.x; |
| } else { |
| return advance; |
| } |
| } |
| } else if (segmentedCache && segHorizontalAdvances != null) { |
| int segIndex = glyphCode >> SEGSHIFT; |
| float[] subArray = segHorizontalAdvances[segIndex]; |
| if (subArray != null) { |
| advance = subArray[glyphCode % SEGSIZE]; |
| if (advance != Float.MAX_VALUE) { |
| if (!getUserAdv && invertDevTx != null) { |
| Point2D.Float metrics = new Point2D.Float(advance, 0f); |
| desc.devTx.deltaTransform(metrics, metrics); |
| return metrics.x; |
| } else { |
| return advance; |
| } |
| } |
| } |
| } |
| |
| if (!getUserAdv && invertDevTx != null) { |
| Point2D.Float metrics = new Point2D.Float(); |
| fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); |
| return metrics.x; |
| } |
| |
| if (invertDevTx != null || !getUserAdv) { |
| /* If there is a device transform need x & y advance to |
| * transform back into user space. |
| */ |
| advance = getGlyphMetrics(glyphCode, getUserAdv).x; |
| } else { |
| long glyphPtr; |
| if (getImageWithAdvance) { |
| /* A heuristic optimisation says that for most cases its |
| * worthwhile retrieving the image at the same time as the |
| * advance. So here we get the image data even if its not |
| * already cached. |
| */ |
| glyphPtr = getGlyphImagePtr(glyphCode); |
| } else { |
| glyphPtr = getCachedGlyphPtr(glyphCode); |
| } |
| if (glyphPtr != 0L) { |
| advance = StrikeCache.unsafe.getFloat |
| (glyphPtr + StrikeCache.xAdvanceOffset); |
| |
| } else { |
| advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode); |
| } |
| } |
| |
| if (horizontalAdvances != null) { |
| horizontalAdvances[glyphCode] = advance; |
| } else if (segmentedCache && segHorizontalAdvances != null) { |
| int segIndex = glyphCode >> SEGSHIFT; |
| int subIndex = glyphCode % SEGSIZE; |
| if (segHorizontalAdvances[segIndex] == null) { |
| segHorizontalAdvances[segIndex] = new float[SEGSIZE]; |
| for (int i=0; i<SEGSIZE; i++) { |
| segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE; |
| } |
| } |
| segHorizontalAdvances[segIndex][subIndex] = advance; |
| } |
| return advance; |
| } |
| |
| float getCodePointAdvance(int cp) { |
| return getGlyphAdvance(mapper.charToGlyph(cp)); |
| } |
| |
| /** |
| * Result and pt are both in device space. |
| */ |
| void getGlyphImageBounds(int glyphCode, Point2D.Float pt, |
| Rectangle result) { |
| |
| long ptr = getGlyphImagePtr(glyphCode); |
| float topLeftX, topLeftY; |
| |
| /* With our current design NULL ptr is not possible |
| but if we eventually allow scalers to return NULL pointers |
| this check might be actually useful. */ |
| if (ptr == 0L) { |
| result.x = (int) Math.floor(pt.x); |
| result.y = (int) Math.floor(pt.y); |
| result.width = result.height = 0; |
| return; |
| } |
| |
| topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset); |
| topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset); |
| |
| result.x = (int)Math.floor(pt.x + topLeftX); |
| result.y = (int)Math.floor(pt.y + topLeftY); |
| result.width = |
| StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff; |
| result.height = |
| StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff; |
| |
| /* HRGB LCD text may have padding that is empty. This is almost always |
| * going to be when topLeftX is -2 or less. |
| * Try to return a tighter bounding box in that case. |
| * If the first three bytes of every row are all zero, then |
| * add 1 to "x" and reduce "width" by 1. |
| */ |
| if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB || |
| desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) |
| && topLeftX <= -2.0f) { |
| int minx = getGlyphImageMinX(ptr, result.x); |
| if (minx > result.x) { |
| result.x += 1; |
| result.width -=1; |
| } |
| } |
| } |
| |
| private int getGlyphImageMinX(long ptr, int origMinX) { |
| |
| int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset); |
| int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset); |
| int rowBytes = |
| StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset); |
| |
| if (rowBytes == width) { |
| return origMinX; |
| } |
| |
| long pixelData = |
| StrikeCache.unsafe.getAddress(ptr + StrikeCache.pixelDataOffset); |
| |
| if (pixelData == 0L) { |
| return origMinX; |
| } |
| |
| for (int y=0;y<height;y++) { |
| for (int x=0;x<3;x++) { |
| if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) { |
| return origMinX; |
| } |
| } |
| } |
| return origMinX+1; |
| } |
| |
| /* These 3 metrics methods below should be implemented to return |
| * values in user space. |
| */ |
| StrikeMetrics getFontMetrics() { |
| if (strikeMetrics == null) { |
| strikeMetrics = |
| fileFont.getFontMetrics(pScalerContext); |
| if (invertDevTx != null) { |
| strikeMetrics.convertToUserSpace(invertDevTx); |
| } |
| } |
| return strikeMetrics; |
| } |
| |
| Point2D.Float getGlyphMetrics(int glyphCode) { |
| return getGlyphMetrics(glyphCode, true); |
| } |
| |
| private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) { |
| Point2D.Float metrics = new Point2D.Float(); |
| |
| // !!! or do we force sgv user glyphs? |
| if (glyphCode >= INVISIBLE_GLYPHS) { |
| return metrics; |
| } |
| long glyphPtr; |
| if (getImageWithAdvance && getImage) { |
| /* A heuristic optimisation says that for most cases its |
| * worthwhile retrieving the image at the same time as the |
| * metrics. So here we get the image data even if its not |
| * already cached. |
| */ |
| glyphPtr = getGlyphImagePtr(glyphCode); |
| } else { |
| glyphPtr = getCachedGlyphPtr(glyphCode); |
| } |
| if (glyphPtr != 0L) { |
| metrics = new Point2D.Float(); |
| metrics.x = StrikeCache.unsafe.getFloat |
| (glyphPtr + StrikeCache.xAdvanceOffset); |
| metrics.y = StrikeCache.unsafe.getFloat |
| (glyphPtr + StrikeCache.yAdvanceOffset); |
| /* advance is currently in device space, need to convert back |
| * into user space. |
| * This must not include the translation component. */ |
| if (invertDevTx != null) { |
| invertDevTx.deltaTransform(metrics, metrics); |
| } |
| } else { |
| /* We sometimes cache these metrics as they are expensive to |
| * generate for large glyphs. |
| * We never reach this path if we obtain images with advances. |
| * But if we do not obtain images with advances its possible that |
| * we first obtain this information, then the image, and never |
| * will access this value again. |
| */ |
| Integer key = Integer.valueOf(glyphCode); |
| Point2D.Float value = null; |
| ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null; |
| if (glyphMetricsMapRef != null) { |
| glyphMetricsMap = glyphMetricsMapRef.get(); |
| } |
| if (glyphMetricsMap != null) { |
| value = glyphMetricsMap.get(key); |
| if (value != null) { |
| metrics.x = value.x; |
| metrics.y = value.y; |
| /* already in user space */ |
| return metrics; |
| } |
| } |
| if (value == null) { |
| fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics); |
| /* advance is currently in device space, need to convert back |
| * into user space. |
| */ |
| if (invertDevTx != null) { |
| invertDevTx.deltaTransform(metrics, metrics); |
| } |
| value = new Point2D.Float(metrics.x, metrics.y); |
| /* We aren't synchronizing here so it is possible to |
| * overwrite the map with another one but this is harmless. |
| */ |
| if (glyphMetricsMap == null) { |
| glyphMetricsMap = |
| new ConcurrentHashMap<Integer, Point2D.Float>(); |
| glyphMetricsMapRef = |
| new SoftReference<ConcurrentHashMap<Integer, |
| Point2D.Float>>(glyphMetricsMap); |
| } |
| glyphMetricsMap.put(key, value); |
| } |
| } |
| return metrics; |
| } |
| |
| Point2D.Float getCharMetrics(char ch) { |
| return getGlyphMetrics(mapper.charToGlyph(ch)); |
| } |
| |
| /* The caller of this can be trusted to return a copy of this |
| * return value rectangle to public API. In fact frequently it |
| * can't use use this return value directly anyway. |
| * This returns bounds in device space. Currently the only |
| * caller is SGV and it converts back to user space. |
| * We could change things so that this code does the conversion so |
| * that all coords coming out of the font system are converted back |
| * into user space even if they were measured in device space. |
| * The same applies to the other methods that return outlines (below) |
| * But it may make particular sense for this method that caches its |
| * results. |
| * There'd be plenty of exceptions, to this too, eg getGlyphPoint needs |
| * device coords as its called from native layout and getGlyphImageBounds |
| * is used by GlyphVector.getGlyphPixelBounds which is specified to |
| * return device coordinates, the image pointers aren't really used |
| * up in Java code either. |
| */ |
| Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { |
| |
| if (boundsMap == null) { |
| boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>(); |
| } |
| |
| Integer key = Integer.valueOf(glyphCode); |
| Rectangle2D.Float bounds = boundsMap.get(key); |
| |
| if (bounds == null) { |
| bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); |
| boundsMap.put(key, bounds); |
| } |
| return bounds; |
| } |
| |
| public Rectangle2D getOutlineBounds(int glyphCode) { |
| return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode); |
| } |
| |
| private |
| WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef; |
| |
| GeneralPath getGlyphOutline(int glyphCode, float x, float y) { |
| |
| GeneralPath gp = null; |
| ConcurrentHashMap<Integer, GeneralPath> outlineMap = null; |
| |
| if (outlineMapRef != null) { |
| outlineMap = outlineMapRef.get(); |
| if (outlineMap != null) { |
| gp = outlineMap.get(glyphCode); |
| } |
| } |
| |
| if (gp == null) { |
| gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0); |
| if (outlineMap == null) { |
| outlineMap = new ConcurrentHashMap<Integer, GeneralPath>(); |
| outlineMapRef = |
| new WeakReference |
| <ConcurrentHashMap<Integer,GeneralPath>>(outlineMap); |
| } |
| outlineMap.put(glyphCode, gp); |
| } |
| gp = (GeneralPath)gp.clone(); // mutable! |
| if (x != 0f || y != 0f) { |
| gp.transform(AffineTransform.getTranslateInstance(x, y)); |
| } |
| return gp; |
| } |
| |
| GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { |
| return fileFont.getGlyphVectorOutline(pScalerContext, |
| glyphs, glyphs.length, x, y); |
| } |
| |
| protected void adjustPoint(Point2D.Float pt) { |
| if (invertDevTx != null) { |
| invertDevTx.deltaTransform(pt, pt); |
| } |
| } |
| } |