jdk/src/java.desktop/share/classes/sun/font/FileFontStrike.java
author martin
Thu, 30 Oct 2014 07:31:41 -0700
changeset 28059 e576535359cc
parent 26037 508779ce6619
child 32289 e7e19bfe2948
permissions -rw-r--r--
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);
        }
    }
}