/* * Copyright (c) 2003, 2020, 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 boundsMap; SoftReference> 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.useJDKScaler && !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) { } } /* 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); } 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.useJDKScaler && !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; } if (FontUtilities.isWindows) { FontUtilities.logInfo("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 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= 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 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(); glyphMetricsMapRef = new SoftReference>(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 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> outlineMapRef; GeneralPath getGlyphOutline(int glyphCode, float x, float y) { GeneralPath gp = null; ConcurrentHashMap 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(); outlineMapRef = new WeakReference >(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); } } }