8067377: My hobby: caning, then then canning, the the can-can
Summary: Fix ALL the stutters!
Reviewed-by: rriggs, mchung, lancea
/*
* 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) {
try {
return getCachedGlyphPtrInternal(glyphCode);
} catch (Exception e) {
NullFontScaler nullScaler =
(NullFontScaler)FontScaler.getNullScaler();
long nullSC = NullFontScaler.getNullScalerContext();
return nullScaler.getGlyphImage(nullSC, glyphCode);
}
}
private long getCachedGlyphPtrInternal(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) {
try {
return setCachedGlyphPtrInternal(glyphCode, glyphPtr);
} catch (Exception e) {
switch (glyphCacheFormat) {
case INTARRAY:
case SEGINTARRAY:
StrikeCache.freeIntPointer((int)glyphPtr);
break;
case LONGARRAY:
case SEGLONGARRAY:
StrikeCache.freeLongPointer(glyphPtr);
break;
}
NullFontScaler nullScaler =
(NullFontScaler)FontScaler.getNullScaler();
long nullSC = NullFontScaler.getNullScalerContext();
return nullScaler.getGlyphImage(nullSC, glyphCode);
}
}
private long setCachedGlyphPtrInternal(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 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);
}
}
}