Path: blob/master/src/java.desktop/share/classes/sun/font/FileFontStrike.java
41155 views
/*1* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.font;2627import java.lang.ref.SoftReference;28import java.lang.ref.WeakReference;29import java.awt.Font;30import java.awt.GraphicsEnvironment;31import java.awt.Rectangle;32import java.awt.geom.AffineTransform;33import java.awt.geom.GeneralPath;34import java.awt.geom.NoninvertibleTransformException;35import java.awt.geom.Point2D;36import java.awt.geom.Rectangle2D;37import java.util.concurrent.ConcurrentHashMap;38import static sun.awt.SunHints.*;394041public class FileFontStrike extends PhysicalStrike {4243/* fffe and ffff are values we specially interpret as meaning44* invisible glyphs.45*/46static final int INVISIBLE_GLYPHS = 0x0fffe;4748private FileFont fileFont;4950/* REMIND: replace this scheme with one that installs a cache51* instance of the appropriate type. It will require changes in52* FontStrikeDisposer and NativeStrike etc.53*/54private static final int UNINITIALISED = 0;55private static final int INTARRAY = 1;56private static final int LONGARRAY = 2;57private static final int SEGINTARRAY = 3;58private static final int SEGLONGARRAY = 4;5960private volatile int glyphCacheFormat = UNINITIALISED;6162/* segmented arrays are blocks of 32 */63private static final int SEGSHIFT = 5;64private static final int SEGSIZE = 1 << SEGSHIFT;6566private boolean segmentedCache;67private int[][] segIntGlyphImages;68private long[][] segLongGlyphImages;6970/* The "metrics" information requested by clients is usually nothing71* more than the horizontal advance of the character.72* In most cases this advance and other metrics information is stored73* in the glyph image cache.74* But in some cases we do not automatically retrieve the glyph75* image when the advance is requested. In those cases we want to76* cache the advances since this has been shown to be important for77* performance.78* The segmented cache is used in cases when the single array79* would be too large.80*/81private float[] horizontalAdvances;82private float[][] segHorizontalAdvances;8384/* Outline bounds are used when printing and when drawing outlines85* to the screen. On balance the relative rarity of these cases86* and the fact that getting this requires generating a path at87* the scaler level means that its probably OK to store these88* in a Java-level hashmap as the trade-off between time and space.89* Later can revisit whether to cache these at all, or elsewhere.90* Should also profile whether subsequent to getting the bounds, the91* outline itself is also requested. The 1.4 implementation doesn't92* cache outlines so you could generate the path twice - once to get93* the bounds and again to return the outline to the client.94* If the two uses are coincident then also look into caching outlines.95* One simple optimisation is that we could store the last single96* outline retrieved. This assumes that bounds then outline will always97* be retrieved for a glyph rather than retrieving bounds for all glyphs98* then outlines for all glyphs.99*/100ConcurrentHashMap<Integer, Rectangle2D.Float> boundsMap;101SoftReference<ConcurrentHashMap<Integer, Point2D.Float>>102glyphMetricsMapRef;103104AffineTransform invertDevTx;105106boolean useNatives;107NativeStrike[] nativeStrikes;108109/* Used only for communication to native layer */110private int intPtSize;111112/* Perform global initialisation needed for Windows native rasterizer */113private static native boolean initNative();114private static boolean isXPorLater = false;115static {116if (FontUtilities.isWindows && !FontUtilities.useJDKScaler &&117!GraphicsEnvironment.isHeadless()) {118isXPorLater = initNative();119}120}121122FileFontStrike(FileFont fileFont, FontStrikeDesc desc) {123super(fileFont, desc);124this.fileFont = fileFont;125126if (desc.style != fileFont.style) {127/* If using algorithmic styling, the base values are128* boldness = 1.0, italic = 0.0. The superclass constructor129* initialises these.130*/131if ((desc.style & Font.ITALIC) == Font.ITALIC &&132(fileFont.style & Font.ITALIC) == 0) {133algoStyle = true;134italic = 0.7f;135}136if ((desc.style & Font.BOLD) == Font.BOLD &&137((fileFont.style & Font.BOLD) == 0)) {138algoStyle = true;139boldness = 1.33f;140}141}142double[] matrix = new double[4];143AffineTransform at = desc.glyphTx;144at.getMatrix(matrix);145if (!desc.devTx.isIdentity() &&146desc.devTx.getType() != AffineTransform.TYPE_TRANSLATION) {147try {148invertDevTx = desc.devTx.createInverse();149} catch (NoninvertibleTransformException e) {150}151}152153/* If any of the values is NaN then substitute the null scaler context.154* This will return null images, zero advance, and empty outlines155* as no rendering need take place in this case.156* We pass in the null scaler as the singleton null context157* requires it. However158*/159if (Double.isNaN(matrix[0]) || Double.isNaN(matrix[1]) ||160Double.isNaN(matrix[2]) || Double.isNaN(matrix[3]) ||161fileFont.getScaler() == null) {162pScalerContext = NullFontScaler.getNullScalerContext();163} else {164pScalerContext = fileFont.getScaler().createScalerContext(matrix,165desc.aaHint, desc.fmHint,166boldness, italic);167}168169mapper = fileFont.getMapper();170int numGlyphs = mapper.getNumGlyphs();171172/* Always segment for fonts with > 256 glyphs, but also for smaller173* fonts with non-typical sizes and transforms.174* Segmenting for all non-typical pt sizes helps to minimize memory175* usage when very many distinct strikes are created.176* The size range of 0->5 and 37->INF for segmenting is arbitrary177* but the intention is that typical GUI integer point sizes (6->36)178* should not segment unless there's another reason to do so.179*/180float ptSize = (float)matrix[3]; // interpreted only when meaningful.181int iSize = intPtSize = (int)ptSize;182boolean isSimpleTx = (at.getType() & complexTX) == 0;183segmentedCache =184(numGlyphs > SEGSIZE << 3) ||185((numGlyphs > SEGSIZE << 1) &&186(!isSimpleTx || ptSize != iSize || iSize < 6 || iSize > 36));187188/* This can only happen if we failed to allocate memory for context.189* NB: in such case we may still have some memory in java heap190* but subsequent attempt to allocate null scaler context191* may fail too (cause it is allocate in the native heap).192* It is not clear how to make this more robust but on the193* other hand getting NULL here seems to be extremely unlikely.194*/195if (pScalerContext == 0L) {196/* REMIND: when the code is updated to install cache objects197* rather than using a switch this will be more efficient.198*/199this.disposer = new FontStrikeDisposer(fileFont, desc);200initGlyphCache();201pScalerContext = NullFontScaler.getNullScalerContext();202SunFontManager.getInstance().deRegisterBadFont(fileFont);203return;204}205/* First, see if native code should be used to create the glyph.206* GDI will return the integer metrics, not fractional metrics, which207* may be requested for this strike, so we would require here that :208* desc.fmHint != INTVAL_FRACTIONALMETRICS_ON209* except that the advance returned by GDI is always overwritten by210* the JDK rasteriser supplied one (see getGlyphImageFromWindows()).211*/212if (FontUtilities.isWindows && isXPorLater &&213!FontUtilities.useJDKScaler &&214!GraphicsEnvironment.isHeadless() &&215!fileFont.useJavaRasterizer &&216(desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||217desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) &&218(matrix[1] == 0.0 && matrix[2] == 0.0 &&219matrix[0] == matrix[3] &&220matrix[0] >= 3.0 && matrix[0] <= 100.0) &&221!((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) {222useNatives = true;223}224if (FontUtilities.isLogging() && FontUtilities.isWindows) {225FontUtilities.logInfo("Strike for " + fileFont + " at size = " + intPtSize +226" use natives = " + useNatives +227" useJavaRasteriser = " + fileFont.useJavaRasterizer +228" AAHint = " + desc.aaHint +229" Has Embedded bitmaps = " +230((TrueTypeFont)fileFont).231useEmbeddedBitmapsForSize(intPtSize));232}233this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext);234235/* Always get the image and the advance together for smaller sizes236* that are likely to be important to rendering performance.237* The pixel size of 48.0 can be thought of as238* "maximumSizeForGetImageWithAdvance".239* This should be no greater than OutlineTextRender.THRESHOLD.240*/241double maxSz = 48.0;242getImageWithAdvance =243Math.abs(at.getScaleX()) <= maxSz &&244Math.abs(at.getScaleY()) <= maxSz &&245Math.abs(at.getShearX()) <= maxSz &&246Math.abs(at.getShearY()) <= maxSz;247248/* Some applications request advance frequently during layout.249* If we are not getting and caching the image with the advance,250* there is a potentially significant performance penalty if the251* advance is repeatedly requested before requesting the image.252* We should at least cache the horizontal advance.253* REMIND: could use info in the font, eg hmtx, to retrieve some254* advances. But still want to cache it here.255*/256257if (!getImageWithAdvance) {258if (!segmentedCache) {259horizontalAdvances = new float[numGlyphs];260/* use max float as uninitialised advance */261for (int i=0; i<numGlyphs; i++) {262horizontalAdvances[i] = Float.MAX_VALUE;263}264} else {265int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;266segHorizontalAdvances = new float[numSegments][];267}268}269}270271/* A number of methods are delegated by the strike to the scaler272* context which is a shared resource on a physical font.273*/274275public int getNumGlyphs() {276return fileFont.getNumGlyphs();277}278279long getGlyphImageFromNative(int glyphCode) {280if (FontUtilities.isWindows) {281return getGlyphImageFromWindows(glyphCode);282} else {283return getGlyphImageFromX11(glyphCode);284}285}286287/* There's no global state conflicts, so this method is not288* presently synchronized.289*/290private native long _getGlyphImageFromWindows(String family,291int style,292int size,293int glyphCode,294boolean fracMetrics,295int fontDataSize);296297long getGlyphImageFromWindows(int glyphCode) {298String family = fileFont.getFamilyName(null);299int style = desc.style & Font.BOLD | desc.style & Font.ITALIC300| fileFont.getStyle();301int size = intPtSize;302long ptr = _getGlyphImageFromWindows303(family, style, size, glyphCode,304desc.fmHint == INTVAL_FRACTIONALMETRICS_ON,305((TrueTypeFont)fileFont).fontDataSize);306if (ptr != 0) {307/* Get the advance from the JDK rasterizer. This is mostly308* necessary for the fractional metrics case, but there are309* also some very small number (<0.25%) of marginal cases where310* there is some rounding difference between windows and JDK.311* After these are resolved, we can restrict this extra312* work to the FM case.313*/314float advance = getGlyphAdvance(glyphCode, false);315StrikeCache.unsafe.putFloat(ptr + StrikeCache.xAdvanceOffset,316advance);317return ptr;318} else {319if (FontUtilities.isLogging()) {320FontUtilities.logWarning("Failed to render glyph using GDI: code=" + glyphCode321+ ", fontFamily=" + family + ", style=" + style322+ ", size=" + size);323}324return fileFont.getGlyphImage(pScalerContext, glyphCode);325}326}327328/* Try the native strikes first, then try the fileFont strike */329long getGlyphImageFromX11(int glyphCode) {330long glyphPtr;331char charCode = fileFont.glyphToCharMap[glyphCode];332for (int i=0;i<nativeStrikes.length;i++) {333CharToGlyphMapper mapper = fileFont.nativeFonts[i].getMapper();334int gc = mapper.charToGlyph(charCode)&0xffff;335if (gc != mapper.getMissingGlyphCode()) {336glyphPtr = nativeStrikes[i].getGlyphImagePtrNoCache(gc);337if (glyphPtr != 0L) {338return glyphPtr;339}340}341}342return fileFont.getGlyphImage(pScalerContext, glyphCode);343}344345long getGlyphImagePtr(int glyphCode) {346if (glyphCode >= INVISIBLE_GLYPHS) {347return StrikeCache.invisibleGlyphPtr;348}349long glyphPtr = 0L;350if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) {351return glyphPtr;352} else {353if (useNatives) {354glyphPtr = getGlyphImageFromNative(glyphCode);355if (glyphPtr == 0L && FontUtilities.isLogging()) {356FontUtilities.logInfo("Strike for " + fileFont +357" at size = " + intPtSize +358" couldn't get native glyph for code = " + glyphCode);359}360}361if (glyphPtr == 0L) {362glyphPtr = fileFont.getGlyphImage(pScalerContext, glyphCode);363}364return setCachedGlyphPtr(glyphCode, glyphPtr);365}366}367368void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {369370for (int i=0; i<len; i++) {371int glyphCode = glyphCodes[i];372if (glyphCode >= INVISIBLE_GLYPHS) {373images[i] = StrikeCache.invisibleGlyphPtr;374continue;375} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {376continue;377} else {378long glyphPtr = 0L;379if (useNatives) {380glyphPtr = getGlyphImageFromNative(glyphCode);381} if (glyphPtr == 0L) {382glyphPtr = fileFont.getGlyphImage(pScalerContext,383glyphCode);384}385images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);386}387}388}389390/* The following method is called from CompositeStrike as a special case.391*/392int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {393394int convertedCnt = 0;395396for (int i=0; i<len; i++) {397int glyphCode = glyphCodes[i];398if (glyphCode >>> 24 != 0) {399return convertedCnt;400} else {401convertedCnt++;402}403if (glyphCode >= INVISIBLE_GLYPHS) {404images[i] = StrikeCache.invisibleGlyphPtr;405continue;406} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {407continue;408} else {409long glyphPtr = 0L;410if (useNatives) {411glyphPtr = getGlyphImageFromNative(glyphCode);412}413if (glyphPtr == 0L) {414glyphPtr = fileFont.getGlyphImage(pScalerContext,415glyphCode);416}417images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);418}419}420return convertedCnt;421}422423/* Only look in the cache */424long getCachedGlyphPtr(int glyphCode) {425try {426return getCachedGlyphPtrInternal(glyphCode);427} catch (Exception e) {428NullFontScaler nullScaler =429(NullFontScaler)FontScaler.getNullScaler();430long nullSC = NullFontScaler.getNullScalerContext();431return nullScaler.getGlyphImage(nullSC, glyphCode);432}433}434435private long getCachedGlyphPtrInternal(int glyphCode) {436switch (glyphCacheFormat) {437case INTARRAY:438return intGlyphImages[glyphCode] & INTMASK;439case SEGINTARRAY:440int segIndex = glyphCode >> SEGSHIFT;441if (segIntGlyphImages[segIndex] != null) {442int subIndex = glyphCode % SEGSIZE;443return segIntGlyphImages[segIndex][subIndex] & INTMASK;444} else {445return 0L;446}447case LONGARRAY:448return longGlyphImages[glyphCode];449case SEGLONGARRAY:450segIndex = glyphCode >> SEGSHIFT;451if (segLongGlyphImages[segIndex] != null) {452int subIndex = glyphCode % SEGSIZE;453return segLongGlyphImages[segIndex][subIndex];454} else {455return 0L;456}457}458/* If reach here cache is UNINITIALISED. */459return 0L;460}461462private synchronized long setCachedGlyphPtr(int glyphCode, long glyphPtr) {463try {464return setCachedGlyphPtrInternal(glyphCode, glyphPtr);465} catch (Exception e) {466switch (glyphCacheFormat) {467case INTARRAY:468case SEGINTARRAY:469StrikeCache.freeIntPointer((int)glyphPtr);470break;471case LONGARRAY:472case SEGLONGARRAY:473StrikeCache.freeLongPointer(glyphPtr);474break;475}476NullFontScaler nullScaler =477(NullFontScaler)FontScaler.getNullScaler();478long nullSC = NullFontScaler.getNullScalerContext();479return nullScaler.getGlyphImage(nullSC, glyphCode);480}481}482483private long setCachedGlyphPtrInternal(int glyphCode, long glyphPtr) {484switch (glyphCacheFormat) {485case INTARRAY:486if (intGlyphImages[glyphCode] == 0) {487intGlyphImages[glyphCode] = (int)glyphPtr;488return glyphPtr;489} else {490StrikeCache.freeIntPointer((int)glyphPtr);491return intGlyphImages[glyphCode] & INTMASK;492}493494case SEGINTARRAY:495int segIndex = glyphCode >> SEGSHIFT;496int subIndex = glyphCode % SEGSIZE;497if (segIntGlyphImages[segIndex] == null) {498segIntGlyphImages[segIndex] = new int[SEGSIZE];499}500if (segIntGlyphImages[segIndex][subIndex] == 0) {501segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr;502return glyphPtr;503} else {504StrikeCache.freeIntPointer((int)glyphPtr);505return segIntGlyphImages[segIndex][subIndex] & INTMASK;506}507508case LONGARRAY:509if (longGlyphImages[glyphCode] == 0L) {510longGlyphImages[glyphCode] = glyphPtr;511return glyphPtr;512} else {513StrikeCache.freeLongPointer(glyphPtr);514return longGlyphImages[glyphCode];515}516517case SEGLONGARRAY:518segIndex = glyphCode >> SEGSHIFT;519subIndex = glyphCode % SEGSIZE;520if (segLongGlyphImages[segIndex] == null) {521segLongGlyphImages[segIndex] = new long[SEGSIZE];522}523if (segLongGlyphImages[segIndex][subIndex] == 0L) {524segLongGlyphImages[segIndex][subIndex] = glyphPtr;525return glyphPtr;526} else {527StrikeCache.freeLongPointer(glyphPtr);528return segLongGlyphImages[segIndex][subIndex];529}530}531532/* Reach here only when the cache is not initialised which is only533* for the first glyph to be initialised in the strike.534* Initialise it and recurse. Note that we are already synchronized.535*/536initGlyphCache();537return setCachedGlyphPtr(glyphCode, glyphPtr);538}539540/* Called only from synchronized code or constructor */541private synchronized void initGlyphCache() {542543int numGlyphs = mapper.getNumGlyphs();544int tmpFormat = UNINITIALISED;545if (segmentedCache) {546int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;547if (longAddresses) {548tmpFormat = SEGLONGARRAY;549segLongGlyphImages = new long[numSegments][];550this.disposer.segLongGlyphImages = segLongGlyphImages;551} else {552tmpFormat = SEGINTARRAY;553segIntGlyphImages = new int[numSegments][];554this.disposer.segIntGlyphImages = segIntGlyphImages;555}556} else {557if (longAddresses) {558tmpFormat = LONGARRAY;559longGlyphImages = new long[numGlyphs];560this.disposer.longGlyphImages = longGlyphImages;561} else {562tmpFormat = INTARRAY;563intGlyphImages = new int[numGlyphs];564this.disposer.intGlyphImages = intGlyphImages;565}566}567glyphCacheFormat = tmpFormat;568}569570float getGlyphAdvance(int glyphCode) {571return getGlyphAdvance(glyphCode, true);572}573574/* Metrics info is always retrieved. If the GlyphInfo address is non-zero575* then metrics info there is valid and can just be copied.576* This is in user space coordinates unless getUserAdv == false.577* Device space advance should not be propagated out of this class.578*/579private float getGlyphAdvance(int glyphCode, boolean getUserAdv) {580float advance;581582if (glyphCode >= INVISIBLE_GLYPHS) {583return 0f;584}585586/* Notes on the (getUserAdv == false) case.587*588* Setting getUserAdv == false is internal to this class.589* If there's no graphics transform we can let590* getGlyphAdvance take its course, and potentially caching in591* advances arrays, except for signalling that592* getUserAdv == false means there is no need to create an image.593* It is possible that code already calculated the user advance,594* and it is desirable to take advantage of that work.595* But, if there's a transform and we want device advance, we596* can't use any values cached in the advances arrays - unless597* first re-transform them into device space using 'desc.devTx'.598* invertDevTx is null if the graphics transform is identity,599* a translate, or non-invertible. The latter case should600* not ever occur in the getUserAdv == false path.601* In other words its either null, or the inversion of a602* simple uniform scale. If its null, we can populate and603* use the advance caches as normal.604*605* If we don't find a cached value, obtain the device advance and606* return it. This will get stashed on the image by the caller and any607* subsequent metrics calls will be able to use it as is the case608* whenever an image is what is initially requested.609*610* Don't query if there's a value cached on the image, since this611* getUserAdv==false code path is entered solely when none exists.612*/613if (horizontalAdvances != null) {614advance = horizontalAdvances[glyphCode];615if (advance != Float.MAX_VALUE) {616if (!getUserAdv && invertDevTx != null) {617Point2D.Float metrics = new Point2D.Float(advance, 0f);618desc.devTx.deltaTransform(metrics, metrics);619return metrics.x;620} else {621return advance;622}623}624} else if (segmentedCache && segHorizontalAdvances != null) {625int segIndex = glyphCode >> SEGSHIFT;626float[] subArray = segHorizontalAdvances[segIndex];627if (subArray != null) {628advance = subArray[glyphCode % SEGSIZE];629if (advance != Float.MAX_VALUE) {630if (!getUserAdv && invertDevTx != null) {631Point2D.Float metrics = new Point2D.Float(advance, 0f);632desc.devTx.deltaTransform(metrics, metrics);633return metrics.x;634} else {635return advance;636}637}638}639}640641if (!getUserAdv && invertDevTx != null) {642Point2D.Float metrics = new Point2D.Float();643fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);644return metrics.x;645}646647if (invertDevTx != null || !getUserAdv) {648/* If there is a device transform need x & y advance to649* transform back into user space.650*/651advance = getGlyphMetrics(glyphCode, getUserAdv).x;652} else {653long glyphPtr;654if (getImageWithAdvance) {655/* A heuristic optimisation says that for most cases its656* worthwhile retrieving the image at the same time as the657* advance. So here we get the image data even if its not658* already cached.659*/660glyphPtr = getGlyphImagePtr(glyphCode);661} else {662glyphPtr = getCachedGlyphPtr(glyphCode);663}664if (glyphPtr != 0L) {665advance = StrikeCache.unsafe.getFloat666(glyphPtr + StrikeCache.xAdvanceOffset);667668} else {669advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode);670}671}672673if (horizontalAdvances != null) {674horizontalAdvances[glyphCode] = advance;675} else if (segmentedCache && segHorizontalAdvances != null) {676int segIndex = glyphCode >> SEGSHIFT;677int subIndex = glyphCode % SEGSIZE;678if (segHorizontalAdvances[segIndex] == null) {679segHorizontalAdvances[segIndex] = new float[SEGSIZE];680for (int i=0; i<SEGSIZE; i++) {681segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE;682}683}684segHorizontalAdvances[segIndex][subIndex] = advance;685}686return advance;687}688689float getCodePointAdvance(int cp) {690return getGlyphAdvance(mapper.charToGlyph(cp));691}692693/**694* Result and pt are both in device space.695*/696void getGlyphImageBounds(int glyphCode, Point2D.Float pt,697Rectangle result) {698699long ptr = getGlyphImagePtr(glyphCode);700float topLeftX, topLeftY;701702/* With our current design NULL ptr is not possible703but if we eventually allow scalers to return NULL pointers704this check might be actually useful. */705if (ptr == 0L) {706result.x = (int) Math.floor(pt.x+0.5f);707result.y = (int) Math.floor(pt.y+0.5f);708result.width = result.height = 0;709return;710}711712topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset);713topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset);714715result.x = (int)Math.floor(pt.x + topLeftX + 0.5f);716result.y = (int)Math.floor(pt.y + topLeftY + 0.5f);717result.width =718StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff;719result.height =720StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff;721722/* HRGB LCD text may have padding that is empty. This is almost always723* going to be when topLeftX is -2 or less.724* Try to return a tighter bounding box in that case.725* If the first three bytes of every row are all zero, then726* add 1 to "x" and reduce "width" by 1.727*/728if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||729desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR)730&& topLeftX <= -2.0f) {731int minx = getGlyphImageMinX(ptr, result.x);732if (minx > result.x) {733result.x += 1;734result.width -=1;735}736}737}738739private int getGlyphImageMinX(long ptr, int origMinX) {740741int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset);742int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset);743int rowBytes =744StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset);745746if (rowBytes == width) {747return origMinX;748}749750long pixelData =751StrikeCache.unsafe.getAddress(ptr + StrikeCache.pixelDataOffset);752753if (pixelData == 0L) {754return origMinX;755}756757for (int y=0;y<height;y++) {758for (int x=0;x<3;x++) {759if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) {760return origMinX;761}762}763}764return origMinX+1;765}766767/* These 3 metrics methods below should be implemented to return768* values in user space.769*/770StrikeMetrics getFontMetrics() {771if (strikeMetrics == null) {772strikeMetrics =773fileFont.getFontMetrics(pScalerContext);774if (invertDevTx != null) {775strikeMetrics.convertToUserSpace(invertDevTx);776}777}778return strikeMetrics;779}780781Point2D.Float getGlyphMetrics(int glyphCode) {782return getGlyphMetrics(glyphCode, true);783}784785private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) {786Point2D.Float metrics = new Point2D.Float();787788// !!! or do we force sgv user glyphs?789if (glyphCode >= INVISIBLE_GLYPHS) {790return metrics;791}792long glyphPtr;793if (getImageWithAdvance && getImage) {794/* A heuristic optimisation says that for most cases its795* worthwhile retrieving the image at the same time as the796* metrics. So here we get the image data even if its not797* already cached.798*/799glyphPtr = getGlyphImagePtr(glyphCode);800} else {801glyphPtr = getCachedGlyphPtr(glyphCode);802}803if (glyphPtr != 0L) {804metrics = new Point2D.Float();805metrics.x = StrikeCache.unsafe.getFloat806(glyphPtr + StrikeCache.xAdvanceOffset);807metrics.y = StrikeCache.unsafe.getFloat808(glyphPtr + StrikeCache.yAdvanceOffset);809/* advance is currently in device space, need to convert back810* into user space.811* This must not include the translation component. */812if (invertDevTx != null) {813invertDevTx.deltaTransform(metrics, metrics);814}815} else {816/* We sometimes cache these metrics as they are expensive to817* generate for large glyphs.818* We never reach this path if we obtain images with advances.819* But if we do not obtain images with advances its possible that820* we first obtain this information, then the image, and never821* will access this value again.822*/823Integer key = Integer.valueOf(glyphCode);824Point2D.Float value = null;825ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null;826if (glyphMetricsMapRef != null) {827glyphMetricsMap = glyphMetricsMapRef.get();828}829if (glyphMetricsMap != null) {830value = glyphMetricsMap.get(key);831if (value != null) {832metrics.x = value.x;833metrics.y = value.y;834/* already in user space */835return metrics;836}837}838if (value == null) {839fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);840/* advance is currently in device space, need to convert back841* into user space.842*/843if (invertDevTx != null) {844invertDevTx.deltaTransform(metrics, metrics);845}846value = new Point2D.Float(metrics.x, metrics.y);847/* We aren't synchronizing here so it is possible to848* overwrite the map with another one but this is harmless.849*/850if (glyphMetricsMap == null) {851glyphMetricsMap =852new ConcurrentHashMap<Integer, Point2D.Float>();853glyphMetricsMapRef =854new SoftReference<ConcurrentHashMap<Integer,855Point2D.Float>>(glyphMetricsMap);856}857glyphMetricsMap.put(key, value);858}859}860return metrics;861}862863Point2D.Float getCharMetrics(char ch) {864return getGlyphMetrics(mapper.charToGlyph(ch));865}866867/* The caller of this can be trusted to return a copy of this868* return value rectangle to public API. In fact frequently it869* can't use this return value directly anyway.870* This returns bounds in device space. Currently the only871* caller is SGV and it converts back to user space.872* We could change things so that this code does the conversion so873* that all coords coming out of the font system are converted back874* into user space even if they were measured in device space.875* The same applies to the other methods that return outlines (below)876* But it may make particular sense for this method that caches its877* results.878* There'd be plenty of exceptions, to this too, eg getGlyphPoint needs879* device coords as its called from native layout and getGlyphImageBounds880* is used by GlyphVector.getGlyphPixelBounds which is specified to881* return device coordinates, the image pointers aren't really used882* up in Java code either.883*/884Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {885886if (boundsMap == null) {887boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>();888}889890Integer key = Integer.valueOf(glyphCode);891Rectangle2D.Float bounds = boundsMap.get(key);892893if (bounds == null) {894bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);895boundsMap.put(key, bounds);896}897return bounds;898}899900public Rectangle2D getOutlineBounds(int glyphCode) {901return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);902}903904private905WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef;906907GeneralPath getGlyphOutline(int glyphCode, float x, float y) {908909GeneralPath gp = null;910ConcurrentHashMap<Integer, GeneralPath> outlineMap = null;911912if (outlineMapRef != null) {913outlineMap = outlineMapRef.get();914if (outlineMap != null) {915gp = outlineMap.get(glyphCode);916}917}918919if (gp == null) {920gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0);921if (outlineMap == null) {922outlineMap = new ConcurrentHashMap<Integer, GeneralPath>();923outlineMapRef =924new WeakReference925<ConcurrentHashMap<Integer,GeneralPath>>(outlineMap);926}927outlineMap.put(glyphCode, gp);928}929gp = (GeneralPath)gp.clone(); // mutable!930if (x != 0f || y != 0f) {931gp.transform(AffineTransform.getTranslateInstance(x, y));932}933return gp;934}935936GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {937return fileFont.getGlyphVectorOutline(pScalerContext,938glyphs, glyphs.length, x, y);939}940941protected void adjustPoint(Point2D.Float pt) {942if (invertDevTx != null) {943invertDevTx.deltaTransform(pt, pt);944}945}946}947948949