Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/font/FileFontStrike.java
38829 views
/*1* Copyright (c) 2003, 2013, 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.useT2K &&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/* Amble fonts are better rendered unhinted although there's the154* inevitable fuzziness that accompanies this due to no longer155* snapping stems to the pixel grid. The exception is that in B&W156* mode they are worse without hinting. The down side to that is that157* B&W metrics will differ which normally isn't the case, although158* since AA mode is part of the measuring context that should be OK.159* We don't expect Amble to be installed in the Windows fonts folder.160* If we were to, then we'd also might want to disable using the161* native rasteriser path which is used for LCD mode for platform162* fonts. since we have no way to disable hinting by GDI.163* In the case of Amble, since its 'gasp' table says to disable164* hinting, I'd expect GDI to follow that, so likely it should165* all be consistent even if GDI used.166*/167boolean disableHinting = desc.aaHint != INTVAL_TEXT_ANTIALIAS_OFF &&168fileFont.familyName.startsWith("Amble");169170/* If any of the values is NaN then substitute the null scaler context.171* This will return null images, zero advance, and empty outlines172* as no rendering need take place in this case.173* We pass in the null scaler as the singleton null context174* requires it. However175*/176if (Double.isNaN(matrix[0]) || Double.isNaN(matrix[1]) ||177Double.isNaN(matrix[2]) || Double.isNaN(matrix[3]) ||178fileFont.getScaler() == null) {179pScalerContext = NullFontScaler.getNullScalerContext();180} else {181pScalerContext = fileFont.getScaler().createScalerContext(matrix,182desc.aaHint, desc.fmHint,183boldness, italic, disableHinting);184}185186mapper = fileFont.getMapper();187int numGlyphs = mapper.getNumGlyphs();188189/* Always segment for fonts with > 256 glyphs, but also for smaller190* fonts with non-typical sizes and transforms.191* Segmenting for all non-typical pt sizes helps to minimize memory192* usage when very many distinct strikes are created.193* The size range of 0->5 and 37->INF for segmenting is arbitrary194* but the intention is that typical GUI integer point sizes (6->36)195* should not segment unless there's another reason to do so.196*/197float ptSize = (float)matrix[3]; // interpreted only when meaningful.198int iSize = intPtSize = (int)ptSize;199boolean isSimpleTx = (at.getType() & complexTX) == 0;200segmentedCache =201(numGlyphs > SEGSIZE << 3) ||202((numGlyphs > SEGSIZE << 1) &&203(!isSimpleTx || ptSize != iSize || iSize < 6 || iSize > 36));204205/* This can only happen if we failed to allocate memory for context.206* NB: in such case we may still have some memory in java heap207* but subsequent attempt to allocate null scaler context208* may fail too (cause it is allocate in the native heap).209* It is not clear how to make this more robust but on the210* other hand getting NULL here seems to be extremely unlikely.211*/212if (pScalerContext == 0L) {213/* REMIND: when the code is updated to install cache objects214* rather than using a switch this will be more efficient.215*/216this.disposer = new FontStrikeDisposer(fileFont, desc);217initGlyphCache();218pScalerContext = NullFontScaler.getNullScalerContext();219SunFontManager.getInstance().deRegisterBadFont(fileFont);220return;221}222/* First, see if native code should be used to create the glyph.223* GDI will return the integer metrics, not fractional metrics, which224* may be requested for this strike, so we would require here that :225* desc.fmHint != INTVAL_FRACTIONALMETRICS_ON226* except that the advance returned by GDI is always overwritten by227* the JDK rasteriser supplied one (see getGlyphImageFromWindows()).228*/229if (FontUtilities.isWindows && isXPorLater &&230!FontUtilities.useT2K &&231!GraphicsEnvironment.isHeadless() &&232!fileFont.useJavaRasterizer &&233(desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||234desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR) &&235(matrix[1] == 0.0 && matrix[2] == 0.0 &&236matrix[0] == matrix[3] &&237matrix[0] >= 3.0 && matrix[0] <= 100.0) &&238!((TrueTypeFont)fileFont).useEmbeddedBitmapsForSize(intPtSize)) {239useNatives = true;240}241else if (fileFont.checkUseNatives() && desc.aaHint==0 && !algoStyle) {242/* Check its a simple scale of a pt size in the range243* where native bitmaps typically exist (6-36 pts) */244if (matrix[1] == 0.0 && matrix[2] == 0.0 &&245matrix[0] >= 6.0 && matrix[0] <= 36.0 &&246matrix[0] == matrix[3]) {247useNatives = true;248int numNatives = fileFont.nativeFonts.length;249nativeStrikes = new NativeStrike[numNatives];250/* Maybe initialise these strikes lazily?. But we251* know we need at least one252*/253for (int i=0; i<numNatives; i++) {254nativeStrikes[i] =255new NativeStrike(fileFont.nativeFonts[i], desc, false);256}257}258}259if (FontUtilities.isLogging() && FontUtilities.isWindows) {260FontUtilities.getLogger().info261("Strike for " + fileFont + " at size = " + intPtSize +262" use natives = " + useNatives +263" useJavaRasteriser = " + fileFont.useJavaRasterizer +264" AAHint = " + desc.aaHint +265" Has Embedded bitmaps = " +266((TrueTypeFont)fileFont).267useEmbeddedBitmapsForSize(intPtSize));268}269this.disposer = new FontStrikeDisposer(fileFont, desc, pScalerContext);270271/* Always get the image and the advance together for smaller sizes272* that are likely to be important to rendering performance.273* The pixel size of 48.0 can be thought of as274* "maximumSizeForGetImageWithAdvance".275* This should be no greater than OutlineTextRender.THRESHOLD.276*/277double maxSz = 48.0;278getImageWithAdvance =279Math.abs(at.getScaleX()) <= maxSz &&280Math.abs(at.getScaleY()) <= maxSz &&281Math.abs(at.getShearX()) <= maxSz &&282Math.abs(at.getShearY()) <= maxSz;283284/* Some applications request advance frequently during layout.285* If we are not getting and caching the image with the advance,286* there is a potentially significant performance penalty if the287* advance is repeatedly requested before requesting the image.288* We should at least cache the horizontal advance.289* REMIND: could use info in the font, eg hmtx, to retrieve some290* advances. But still want to cache it here.291*/292293if (!getImageWithAdvance) {294if (!segmentedCache) {295horizontalAdvances = new float[numGlyphs];296/* use max float as uninitialised advance */297for (int i=0; i<numGlyphs; i++) {298horizontalAdvances[i] = Float.MAX_VALUE;299}300} else {301int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;302segHorizontalAdvances = new float[numSegments][];303}304}305}306307/* A number of methods are delegated by the strike to the scaler308* context which is a shared resource on a physical font.309*/310311public int getNumGlyphs() {312return fileFont.getNumGlyphs();313}314315long getGlyphImageFromNative(int glyphCode) {316if (FontUtilities.isWindows) {317return getGlyphImageFromWindows(glyphCode);318} else {319return getGlyphImageFromX11(glyphCode);320}321}322323/* There's no global state conflicts, so this method is not324* presently synchronized.325*/326private native long _getGlyphImageFromWindows(String family,327int style,328int size,329int glyphCode,330boolean fracMetrics,331int fontDataSize);332333long getGlyphImageFromWindows(int glyphCode) {334String family = fileFont.getFamilyName(null);335int style = desc.style & Font.BOLD | desc.style & Font.ITALIC336| fileFont.getStyle();337int size = intPtSize;338long ptr = _getGlyphImageFromWindows339(family, style, size, glyphCode,340desc.fmHint == INTVAL_FRACTIONALMETRICS_ON,341((TrueTypeFont)fileFont).fontDataSize);342if (ptr != 0) {343/* Get the advance from the JDK rasterizer. This is mostly344* necessary for the fractional metrics case, but there are345* also some very small number (<0.25%) of marginal cases where346* there is some rounding difference between windows and JDK.347* After these are resolved, we can restrict this extra348* work to the FM case.349*/350float advance = getGlyphAdvance(glyphCode, false);351StrikeCache.unsafe.putFloat(ptr + StrikeCache.xAdvanceOffset,352advance);353return ptr;354} else {355if (FontUtilities.isLogging()) {356FontUtilities.getLogger().warning(357"Failed to render glyph using GDI: code=" + glyphCode358+ ", fontFamily=" + family + ", style=" + style359+ ", size=" + size);360}361return fileFont.getGlyphImage(pScalerContext, glyphCode);362}363}364365/* Try the native strikes first, then try the fileFont strike */366long getGlyphImageFromX11(int glyphCode) {367long glyphPtr;368char charCode = fileFont.glyphToCharMap[glyphCode];369for (int i=0;i<nativeStrikes.length;i++) {370CharToGlyphMapper mapper = fileFont.nativeFonts[i].getMapper();371int gc = mapper.charToGlyph(charCode)&0xffff;372if (gc != mapper.getMissingGlyphCode()) {373glyphPtr = nativeStrikes[i].getGlyphImagePtrNoCache(gc);374if (glyphPtr != 0L) {375return glyphPtr;376}377}378}379return fileFont.getGlyphImage(pScalerContext, glyphCode);380}381382long getGlyphImagePtr(int glyphCode) {383if (glyphCode >= INVISIBLE_GLYPHS) {384return StrikeCache.invisibleGlyphPtr;385}386long glyphPtr = 0L;387if ((glyphPtr = getCachedGlyphPtr(glyphCode)) != 0L) {388return glyphPtr;389} else {390if (useNatives) {391glyphPtr = getGlyphImageFromNative(glyphCode);392if (glyphPtr == 0L && FontUtilities.isLogging()) {393FontUtilities.getLogger().info394("Strike for " + fileFont +395" at size = " + intPtSize +396" couldn't get native glyph for code = " + glyphCode);397}398} if (glyphPtr == 0L) {399glyphPtr = fileFont.getGlyphImage(pScalerContext,400glyphCode);401}402return setCachedGlyphPtr(glyphCode, glyphPtr);403}404}405406void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {407408for (int i=0; i<len; i++) {409int glyphCode = glyphCodes[i];410if (glyphCode >= INVISIBLE_GLYPHS) {411images[i] = StrikeCache.invisibleGlyphPtr;412continue;413} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {414continue;415} else {416long glyphPtr = 0L;417if (useNatives) {418glyphPtr = getGlyphImageFromNative(glyphCode);419} if (glyphPtr == 0L) {420glyphPtr = fileFont.getGlyphImage(pScalerContext,421glyphCode);422}423images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);424}425}426}427428/* The following method is called from CompositeStrike as a special case.429*/430int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {431432int convertedCnt = 0;433434for (int i=0; i<len; i++) {435int glyphCode = glyphCodes[i];436if (glyphCode >>> 24 != 0) {437return convertedCnt;438} else {439convertedCnt++;440}441if (glyphCode >= INVISIBLE_GLYPHS) {442images[i] = StrikeCache.invisibleGlyphPtr;443continue;444} else if ((images[i] = getCachedGlyphPtr(glyphCode)) != 0L) {445continue;446} else {447long glyphPtr = 0L;448if (useNatives) {449glyphPtr = getGlyphImageFromNative(glyphCode);450}451if (glyphPtr == 0L) {452glyphPtr = fileFont.getGlyphImage(pScalerContext,453glyphCode);454}455images[i] = setCachedGlyphPtr(glyphCode, glyphPtr);456}457}458return convertedCnt;459}460461/* Only look in the cache */462long getCachedGlyphPtr(int glyphCode) {463try {464return getCachedGlyphPtrInternal(glyphCode);465} catch (Exception e) {466NullFontScaler nullScaler =467(NullFontScaler)FontScaler.getNullScaler();468long nullSC = NullFontScaler.getNullScalerContext();469return nullScaler.getGlyphImage(nullSC, glyphCode);470}471}472473private long getCachedGlyphPtrInternal(int glyphCode) {474switch (glyphCacheFormat) {475case INTARRAY:476return intGlyphImages[glyphCode] & INTMASK;477case SEGINTARRAY:478int segIndex = glyphCode >> SEGSHIFT;479if (segIntGlyphImages[segIndex] != null) {480int subIndex = glyphCode % SEGSIZE;481return segIntGlyphImages[segIndex][subIndex] & INTMASK;482} else {483return 0L;484}485case LONGARRAY:486return longGlyphImages[glyphCode];487case SEGLONGARRAY:488segIndex = glyphCode >> SEGSHIFT;489if (segLongGlyphImages[segIndex] != null) {490int subIndex = glyphCode % SEGSIZE;491return segLongGlyphImages[segIndex][subIndex];492} else {493return 0L;494}495}496/* If reach here cache is UNINITIALISED. */497return 0L;498}499500private synchronized long setCachedGlyphPtr(int glyphCode, long glyphPtr) {501try {502return setCachedGlyphPtrInternal(glyphCode, glyphPtr);503} catch (Exception e) {504switch (glyphCacheFormat) {505case INTARRAY:506case SEGINTARRAY:507StrikeCache.freeIntPointer((int)glyphPtr);508break;509case LONGARRAY:510case SEGLONGARRAY:511StrikeCache.freeLongPointer(glyphPtr);512break;513}514NullFontScaler nullScaler =515(NullFontScaler)FontScaler.getNullScaler();516long nullSC = NullFontScaler.getNullScalerContext();517return nullScaler.getGlyphImage(nullSC, glyphCode);518}519}520521private long setCachedGlyphPtrInternal(int glyphCode, long glyphPtr) {522switch (glyphCacheFormat) {523case INTARRAY:524if (intGlyphImages[glyphCode] == 0) {525intGlyphImages[glyphCode] = (int)glyphPtr;526return glyphPtr;527} else {528StrikeCache.freeIntPointer((int)glyphPtr);529return intGlyphImages[glyphCode] & INTMASK;530}531532case SEGINTARRAY:533int segIndex = glyphCode >> SEGSHIFT;534int subIndex = glyphCode % SEGSIZE;535if (segIntGlyphImages[segIndex] == null) {536segIntGlyphImages[segIndex] = new int[SEGSIZE];537}538if (segIntGlyphImages[segIndex][subIndex] == 0) {539segIntGlyphImages[segIndex][subIndex] = (int)glyphPtr;540return glyphPtr;541} else {542StrikeCache.freeIntPointer((int)glyphPtr);543return segIntGlyphImages[segIndex][subIndex] & INTMASK;544}545546case LONGARRAY:547if (longGlyphImages[glyphCode] == 0L) {548longGlyphImages[glyphCode] = glyphPtr;549return glyphPtr;550} else {551StrikeCache.freeLongPointer(glyphPtr);552return longGlyphImages[glyphCode];553}554555case SEGLONGARRAY:556segIndex = glyphCode >> SEGSHIFT;557subIndex = glyphCode % SEGSIZE;558if (segLongGlyphImages[segIndex] == null) {559segLongGlyphImages[segIndex] = new long[SEGSIZE];560}561if (segLongGlyphImages[segIndex][subIndex] == 0L) {562segLongGlyphImages[segIndex][subIndex] = glyphPtr;563return glyphPtr;564} else {565StrikeCache.freeLongPointer(glyphPtr);566return segLongGlyphImages[segIndex][subIndex];567}568}569570/* Reach here only when the cache is not initialised which is only571* for the first glyph to be initialised in the strike.572* Initialise it and recurse. Note that we are already synchronized.573*/574initGlyphCache();575return setCachedGlyphPtr(glyphCode, glyphPtr);576}577578/* Called only from synchronized code or constructor */579private synchronized void initGlyphCache() {580581int numGlyphs = mapper.getNumGlyphs();582int tmpFormat = UNINITIALISED;583if (segmentedCache) {584int numSegments = (numGlyphs + SEGSIZE-1)/SEGSIZE;585if (longAddresses) {586tmpFormat = SEGLONGARRAY;587segLongGlyphImages = new long[numSegments][];588this.disposer.segLongGlyphImages = segLongGlyphImages;589} else {590tmpFormat = SEGINTARRAY;591segIntGlyphImages = new int[numSegments][];592this.disposer.segIntGlyphImages = segIntGlyphImages;593}594} else {595if (longAddresses) {596tmpFormat = LONGARRAY;597longGlyphImages = new long[numGlyphs];598this.disposer.longGlyphImages = longGlyphImages;599} else {600tmpFormat = INTARRAY;601intGlyphImages = new int[numGlyphs];602this.disposer.intGlyphImages = intGlyphImages;603}604}605glyphCacheFormat = tmpFormat;606}607608float getGlyphAdvance(int glyphCode) {609return getGlyphAdvance(glyphCode, true);610}611612/* Metrics info is always retrieved. If the GlyphInfo address is non-zero613* then metrics info there is valid and can just be copied.614* This is in user space coordinates unless getUserAdv == false.615* Device space advance should not be propagated out of this class.616*/617private float getGlyphAdvance(int glyphCode, boolean getUserAdv) {618float advance;619620if (glyphCode >= INVISIBLE_GLYPHS) {621return 0f;622}623624/* Notes on the (getUserAdv == false) case.625*626* Setting getUserAdv == false is internal to this class.627* If there's no graphics transform we can let628* getGlyphAdvance take its course, and potentially caching in629* advances arrays, except for signalling that630* getUserAdv == false means there is no need to create an image.631* It is possible that code already calculated the user advance,632* and it is desirable to take advantage of that work.633* But, if there's a transform and we want device advance, we634* can't use any values cached in the advances arrays - unless635* first re-transform them into device space using 'desc.devTx'.636* invertDevTx is null if the graphics transform is identity,637* a translate, or non-invertible. The latter case should638* not ever occur in the getUserAdv == false path.639* In other words its either null, or the inversion of a640* simple uniform scale. If its null, we can populate and641* use the advance caches as normal.642*643* If we don't find a cached value, obtain the device advance and644* return it. This will get stashed on the image by the caller and any645* subsequent metrics calls will be able to use it as is the case646* whenever an image is what is initially requested.647*648* Don't query if there's a value cached on the image, since this649* getUserAdv==false code path is entered solely when none exists.650*/651if (horizontalAdvances != null) {652advance = horizontalAdvances[glyphCode];653if (advance != Float.MAX_VALUE) {654if (!getUserAdv && invertDevTx != null) {655Point2D.Float metrics = new Point2D.Float(advance, 0f);656desc.devTx.deltaTransform(metrics, metrics);657return metrics.x;658} else {659return advance;660}661}662} else if (segmentedCache && segHorizontalAdvances != null) {663int segIndex = glyphCode >> SEGSHIFT;664float[] subArray = segHorizontalAdvances[segIndex];665if (subArray != null) {666advance = subArray[glyphCode % SEGSIZE];667if (advance != Float.MAX_VALUE) {668if (!getUserAdv && invertDevTx != null) {669Point2D.Float metrics = new Point2D.Float(advance, 0f);670desc.devTx.deltaTransform(metrics, metrics);671return metrics.x;672} else {673return advance;674}675}676}677}678679if (!getUserAdv && invertDevTx != null) {680Point2D.Float metrics = new Point2D.Float();681fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);682return metrics.x;683}684685if (invertDevTx != null || !getUserAdv) {686/* If there is a device transform need x & y advance to687* transform back into user space.688*/689advance = getGlyphMetrics(glyphCode, getUserAdv).x;690} else {691long glyphPtr;692if (getImageWithAdvance) {693/* A heuristic optimisation says that for most cases its694* worthwhile retrieving the image at the same time as the695* advance. So here we get the image data even if its not696* already cached.697*/698glyphPtr = getGlyphImagePtr(glyphCode);699} else {700glyphPtr = getCachedGlyphPtr(glyphCode);701}702if (glyphPtr != 0L) {703advance = StrikeCache.unsafe.getFloat704(glyphPtr + StrikeCache.xAdvanceOffset);705706} else {707advance = fileFont.getGlyphAdvance(pScalerContext, glyphCode);708}709}710711if (horizontalAdvances != null) {712horizontalAdvances[glyphCode] = advance;713} else if (segmentedCache && segHorizontalAdvances != null) {714int segIndex = glyphCode >> SEGSHIFT;715int subIndex = glyphCode % SEGSIZE;716if (segHorizontalAdvances[segIndex] == null) {717segHorizontalAdvances[segIndex] = new float[SEGSIZE];718for (int i=0; i<SEGSIZE; i++) {719segHorizontalAdvances[segIndex][i] = Float.MAX_VALUE;720}721}722segHorizontalAdvances[segIndex][subIndex] = advance;723}724return advance;725}726727float getCodePointAdvance(int cp) {728return getGlyphAdvance(mapper.charToGlyph(cp));729}730731/**732* Result and pt are both in device space.733*/734void getGlyphImageBounds(int glyphCode, Point2D.Float pt,735Rectangle result) {736737long ptr = getGlyphImagePtr(glyphCode);738float topLeftX, topLeftY;739740/* With our current design NULL ptr is not possible741but if we eventually allow scalers to return NULL pointers742this check might be actually useful. */743if (ptr == 0L) {744result.x = (int) Math.floor(pt.x);745result.y = (int) Math.floor(pt.y);746result.width = result.height = 0;747return;748}749750topLeftX = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftXOffset);751topLeftY = StrikeCache.unsafe.getFloat(ptr+StrikeCache.topLeftYOffset);752753result.x = (int)Math.floor(pt.x + topLeftX);754result.y = (int)Math.floor(pt.y + topLeftY);755result.width =756StrikeCache.unsafe.getShort(ptr+StrikeCache.widthOffset) &0x0ffff;757result.height =758StrikeCache.unsafe.getShort(ptr+StrikeCache.heightOffset) &0x0ffff;759760/* HRGB LCD text may have padding that is empty. This is almost always761* going to be when topLeftX is -2 or less.762* Try to return a tighter bounding box in that case.763* If the first three bytes of every row are all zero, then764* add 1 to "x" and reduce "width" by 1.765*/766if ((desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HRGB ||767desc.aaHint == INTVAL_TEXT_ANTIALIAS_LCD_HBGR)768&& topLeftX <= -2.0f) {769int minx = getGlyphImageMinX(ptr, (int)result.x);770if (minx > result.x) {771result.x += 1;772result.width -=1;773}774}775}776777private int getGlyphImageMinX(long ptr, int origMinX) {778779int width = StrikeCache.unsafe.getChar(ptr+StrikeCache.widthOffset);780int height = StrikeCache.unsafe.getChar(ptr+StrikeCache.heightOffset);781int rowBytes =782StrikeCache.unsafe.getChar(ptr+StrikeCache.rowBytesOffset);783784if (rowBytes == width) {785return origMinX;786}787788long pixelData =789StrikeCache.unsafe.getAddress(ptr + StrikeCache.pixelDataOffset);790791if (pixelData == 0L) {792return origMinX;793}794795for (int y=0;y<height;y++) {796for (int x=0;x<3;x++) {797if (StrikeCache.unsafe.getByte(pixelData+y*rowBytes+x) != 0) {798return origMinX;799}800}801}802return origMinX+1;803}804805/* These 3 metrics methods below should be implemented to return806* values in user space.807*/808StrikeMetrics getFontMetrics() {809if (strikeMetrics == null) {810strikeMetrics =811fileFont.getFontMetrics(pScalerContext);812if (invertDevTx != null) {813strikeMetrics.convertToUserSpace(invertDevTx);814}815}816return strikeMetrics;817}818819Point2D.Float getGlyphMetrics(int glyphCode) {820return getGlyphMetrics(glyphCode, true);821}822823private Point2D.Float getGlyphMetrics(int glyphCode, boolean getImage) {824Point2D.Float metrics = new Point2D.Float();825826// !!! or do we force sgv user glyphs?827if (glyphCode >= INVISIBLE_GLYPHS) {828return metrics;829}830long glyphPtr;831if (getImageWithAdvance && getImage) {832/* A heuristic optimisation says that for most cases its833* worthwhile retrieving the image at the same time as the834* metrics. So here we get the image data even if its not835* already cached.836*/837glyphPtr = getGlyphImagePtr(glyphCode);838} else {839glyphPtr = getCachedGlyphPtr(glyphCode);840}841if (glyphPtr != 0L) {842metrics = new Point2D.Float();843metrics.x = StrikeCache.unsafe.getFloat844(glyphPtr + StrikeCache.xAdvanceOffset);845metrics.y = StrikeCache.unsafe.getFloat846(glyphPtr + StrikeCache.yAdvanceOffset);847/* advance is currently in device space, need to convert back848* into user space.849* This must not include the translation component. */850if (invertDevTx != null) {851invertDevTx.deltaTransform(metrics, metrics);852}853} else {854/* We sometimes cache these metrics as they are expensive to855* generate for large glyphs.856* We never reach this path if we obtain images with advances.857* But if we do not obtain images with advances its possible that858* we first obtain this information, then the image, and never859* will access this value again.860*/861Integer key = Integer.valueOf(glyphCode);862Point2D.Float value = null;863ConcurrentHashMap<Integer, Point2D.Float> glyphMetricsMap = null;864if (glyphMetricsMapRef != null) {865glyphMetricsMap = glyphMetricsMapRef.get();866}867if (glyphMetricsMap != null) {868value = glyphMetricsMap.get(key);869if (value != null) {870metrics.x = value.x;871metrics.y = value.y;872/* already in user space */873return metrics;874}875}876if (value == null) {877fileFont.getGlyphMetrics(pScalerContext, glyphCode, metrics);878/* advance is currently in device space, need to convert back879* into user space.880*/881if (invertDevTx != null) {882invertDevTx.deltaTransform(metrics, metrics);883}884value = new Point2D.Float(metrics.x, metrics.y);885/* We aren't synchronizing here so it is possible to886* overwrite the map with another one but this is harmless.887*/888if (glyphMetricsMap == null) {889glyphMetricsMap =890new ConcurrentHashMap<Integer, Point2D.Float>();891glyphMetricsMapRef =892new SoftReference<ConcurrentHashMap<Integer,893Point2D.Float>>(glyphMetricsMap);894}895glyphMetricsMap.put(key, value);896}897}898return metrics;899}900901Point2D.Float getCharMetrics(char ch) {902return getGlyphMetrics(mapper.charToGlyph(ch));903}904905/* The caller of this can be trusted to return a copy of this906* return value rectangle to public API. In fact frequently it907* can't use use this return value directly anyway.908* This returns bounds in device space. Currently the only909* caller is SGV and it converts back to user space.910* We could change things so that this code does the conversion so911* that all coords coming out of the font system are converted back912* into user space even if they were measured in device space.913* The same applies to the other methods that return outlines (below)914* But it may make particular sense for this method that caches its915* results.916* There'd be plenty of exceptions, to this too, eg getGlyphPoint needs917* device coords as its called from native layout and getGlyphImageBounds918* is used by GlyphVector.getGlyphPixelBounds which is specified to919* return device coordinates, the image pointers aren't really used920* up in Java code either.921*/922Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {923924if (boundsMap == null) {925boundsMap = new ConcurrentHashMap<Integer, Rectangle2D.Float>();926}927928Integer key = Integer.valueOf(glyphCode);929Rectangle2D.Float bounds = boundsMap.get(key);930931if (bounds == null) {932bounds = fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);933boundsMap.put(key, bounds);934}935return bounds;936}937938public Rectangle2D getOutlineBounds(int glyphCode) {939return fileFont.getGlyphOutlineBounds(pScalerContext, glyphCode);940}941942private943WeakReference<ConcurrentHashMap<Integer,GeneralPath>> outlineMapRef;944945GeneralPath getGlyphOutline(int glyphCode, float x, float y) {946947GeneralPath gp = null;948ConcurrentHashMap<Integer, GeneralPath> outlineMap = null;949950if (outlineMapRef != null) {951outlineMap = outlineMapRef.get();952if (outlineMap != null) {953gp = (GeneralPath)outlineMap.get(glyphCode);954}955}956957if (gp == null) {958gp = fileFont.getGlyphOutline(pScalerContext, glyphCode, 0, 0);959if (outlineMap == null) {960outlineMap = new ConcurrentHashMap<Integer, GeneralPath>();961outlineMapRef =962new WeakReference963<ConcurrentHashMap<Integer,GeneralPath>>(outlineMap);964}965outlineMap.put(glyphCode, gp);966}967gp = (GeneralPath)gp.clone(); // mutable!968if (x != 0f || y != 0f) {969gp.transform(AffineTransform.getTranslateInstance(x, y));970}971return gp;972}973974GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {975return fileFont.getGlyphVectorOutline(pScalerContext,976glyphs, glyphs.length, x, y);977}978979protected void adjustPoint(Point2D.Float pt) {980if (invertDevTx != null) {981invertDevTx.deltaTransform(pt, pt);982}983}984}985986987