| /* | |
| * Copyright (c) 2011, 2025, 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.awt.Rectangle; | |
| import java.awt.geom.*; | |
| import java.util.*; | |
| public final class CStrike extends PhysicalStrike { | |
| // Creates the native strike | |
| private static native long createNativeStrikePtr(long nativeFontPtr, | |
| double[] glyphTx, | |
| double[] invDevTxMatrix, | |
| int aaHint, | |
| int fmHint); | |
| // Disposes the native strike | |
| private static native void disposeNativeStrikePtr(long nativeStrikePtr); | |
| // Creates a StrikeMetrics from the underlying native system fonts | |
| private static native StrikeMetrics getFontMetrics(long nativeStrikePtr); | |
| // Returns native struct pointers used by the Sun 2D Renderer | |
| private static native void getGlyphImagePtrsNative(long nativeStrikePtr, | |
| long[] glyphInfos, | |
| int[] uniCodes, int len); | |
| // Returns the advance give a glyph code. It should be used only | |
| // when the glyph code belongs to the CFont passed in. | |
| private static native float getNativeGlyphAdvance(long nativeStrikePtr, | |
| int glyphCode); | |
| // Returns the outline shape of a glyph | |
| private static native GeneralPath getNativeGlyphOutline(long nativeStrikePtr, | |
| int glyphCode, | |
| double x, | |
| double y); | |
| // returns the bounding rect for a glyph | |
| private static native void getNativeGlyphImageBounds(long nativeStrikePtr, | |
| int glyphCode, | |
| Rectangle2D.Float result, | |
| double x, double y); | |
| private final CFont nativeFont; | |
| private AffineTransform invDevTx; | |
| private final GlyphInfoCache glyphInfoCache; | |
| private final GlyphAdvanceCache glyphAdvanceCache; | |
| private long nativeStrikePtr; | |
| CStrike(final CFont font, final FontStrikeDesc inDesc) { | |
| nativeFont = font; | |
| desc = inDesc; | |
| glyphInfoCache = new GlyphInfoCache(font, desc); | |
| glyphAdvanceCache = new GlyphAdvanceCache(); | |
| disposer = glyphInfoCache; | |
| // Normally the device transform should be the identity transform | |
| // for screen operations. The device transform only becomes | |
| // interesting when we are outputting between different dpi surfaces, | |
| // like when we are printing to postscript or use retina. | |
| if (inDesc.devTx != null && !inDesc.devTx.isIdentity()) { | |
| try { | |
| invDevTx = inDesc.devTx.createInverse(); | |
| } catch (NoninvertibleTransformException ignored) { | |
| // ignored, since device transforms should not be that | |
| // complicated, and if they are - there is nothing we can do, | |
| // so we won't worry about it. | |
| } | |
| } | |
| } | |
| public long getNativeStrikePtr() { | |
| if (nativeStrikePtr != 0) { | |
| return nativeStrikePtr; | |
| } | |
| final double[] glyphTx = new double[6]; | |
| desc.glyphTx.getMatrix(glyphTx); | |
| for (int i = 0; i < 6; i++) { | |
| if (Double.isFinite(glyphTx[i])) { | |
| continue; | |
| } | |
| for (int j = 0; j < 6; j++) { | |
| glyphTx[j] = 0; | |
| } | |
| invDevTx = null; | |
| break; | |
| } | |
| final double[] invDevTxMatrix = new double[6]; | |
| if (invDevTx == null) { | |
| invDevTxMatrix[0] = 1; | |
| invDevTxMatrix[3] = 1; | |
| } else { | |
| invDevTx.getMatrix(invDevTxMatrix); | |
| } | |
| final int aaHint = desc.aaHint; | |
| final int fmHint = desc.fmHint; | |
| synchronized (this) { | |
| if (nativeStrikePtr != 0) { | |
| return nativeStrikePtr; | |
| } | |
| nativeStrikePtr = | |
| createNativeStrikePtr(nativeFont.getNativeFontPtr(), | |
| glyphTx, invDevTxMatrix, aaHint, fmHint); | |
| } | |
| return nativeStrikePtr; | |
| } | |
| @SuppressWarnings("removal") | |
| protected synchronized void finalize() throws Throwable { | |
| if (nativeStrikePtr != 0) { | |
| disposeNativeStrikePtr(nativeStrikePtr); | |
| } | |
| nativeStrikePtr = 0; | |
| } | |
| @Override | |
| public int getNumGlyphs() { | |
| return nativeFont.getNumGlyphs(); | |
| } | |
| @Override | |
| StrikeMetrics getFontMetrics() { | |
| if (strikeMetrics == null) { | |
| StrikeMetrics metrics = getFontMetrics(getNativeStrikePtr()); | |
| if (invDevTx != null) { | |
| metrics.convertToUserSpace(invDevTx); | |
| } | |
| metrics.convertToUserSpace(desc.glyphTx); | |
| strikeMetrics = metrics; | |
| } | |
| return strikeMetrics; | |
| } | |
| @Override | |
| float getGlyphAdvance(final int glyphCode) { | |
| return getCachedNativeGlyphAdvance(glyphCode); | |
| } | |
| @Override | |
| float getCodePointAdvance(final int cp) { | |
| return getGlyphAdvance(nativeFont.getMapper().charToGlyph(cp)); | |
| } | |
| @Override | |
| Point2D.Float getCharMetrics(final char ch) { | |
| return getGlyphMetrics(nativeFont.getMapper().charToGlyph(ch)); | |
| } | |
| @Override | |
| Point2D.Float getGlyphMetrics(final int glyphCode) { | |
| return new Point2D.Float(getGlyphAdvance(glyphCode), 0.0f); | |
| } | |
| Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) { | |
| GeneralPath gp = getGlyphOutline(glyphCode, 0f, 0f); | |
| Rectangle2D r2d = gp.getBounds2D(); | |
| Rectangle2D.Float r2df; | |
| if (r2d instanceof Rectangle2D.Float) { | |
| r2df = (Rectangle2D.Float)r2d; | |
| } else { | |
| float x = (float)r2d.getX(); | |
| float y = (float)r2d.getY(); | |
| float w = (float)r2d.getWidth(); | |
| float h = (float)r2d.getHeight(); | |
| r2df = new Rectangle2D.Float(x, y, w, h); | |
| } | |
| return r2df; | |
| } | |
| // pt, result in device space | |
| void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) { | |
| Rectangle2D.Float floatRect = new Rectangle2D.Float(); | |
| if (invDevTx != null) { | |
| invDevTx.transform(pt, pt); | |
| } | |
| getGlyphImageBounds(glyphCode, pt.x, pt.y, floatRect); | |
| if (floatRect.width == 0 && floatRect.height == 0) { | |
| result.setRect(0, 0, 0, 0); | |
| return; | |
| } | |
| result.setRect(floatRect.x + pt.x, floatRect.y + pt.y, floatRect.width, floatRect.height); | |
| } | |
| private void getGlyphImageBounds(int glyphCode, float x, float y, Rectangle2D.Float floatRect) { | |
| getNativeGlyphImageBounds(getNativeStrikePtr(), glyphCode, floatRect, x, y); | |
| } | |
| GeneralPath getGlyphOutline(int glyphCode, float x, float y) { | |
| return getNativeGlyphOutline(getNativeStrikePtr(), glyphCode, x, y); | |
| } | |
| // should implement, however not called though any path that is publicly exposed | |
| GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) { | |
| throw new Error("not implemented yet"); | |
| } | |
| // called from the Sun2D renderer | |
| long getGlyphImagePtr(int glyphCode) { | |
| synchronized (glyphInfoCache) { | |
| long ptr = glyphInfoCache.get(glyphCode); | |
| if (ptr != 0L) return ptr; | |
| long[] ptrs = new long[1]; | |
| int[] codes = new int[1]; | |
| codes[0] = glyphCode; | |
| getGlyphImagePtrs(codes, ptrs, 1); | |
| ptr = ptrs[0]; | |
| glyphInfoCache.put(glyphCode, ptr); | |
| return ptr; | |
| } | |
| } | |
| // called from the Sun2D renderer | |
| void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) { | |
| synchronized (glyphInfoCache) { | |
| // fill the image pointer array with existing pointers | |
| // from the cache | |
| int missed = 0; | |
| for (int i = 0; i < len; i++) { | |
| int code = glyphCodes[i]; | |
| final long ptr = glyphInfoCache.get(code); | |
| if (ptr != 0L) { | |
| images[i] = ptr; | |
| } else { | |
| // zero this element out, because the caller does not | |
| // promise to keep it clean | |
| images[i] = 0L; | |
| missed++; | |
| } | |
| } | |
| if (missed == 0) { | |
| return; // horray! we got away without touching native! | |
| } | |
| // all distinct glyph codes requested (partially filled) | |
| final int[] filteredCodes = new int[missed]; | |
| // indices into filteredCodes array (totally filled) | |
| final int[] filteredIndicies = new int[missed]; | |
| // scan, mark, and store the requested glyph codes again to | |
| // send into native | |
| int j = 0; | |
| int dupes = 0; | |
| for (int i = 0; i < len; i++) { | |
| if (images[i] != 0L) continue; // already filled | |
| final int code = glyphCodes[i]; | |
| // we have already promised to strike this glyph - this is | |
| // a dupe | |
| if (glyphInfoCache.get(code) == -1L) { | |
| filteredIndicies[j] = -1; | |
| dupes++; | |
| j++; | |
| continue; | |
| } | |
| // this is a distinct glyph we have not struck before, or | |
| // promised to strike mark this one as "promise to strike" | |
| // in the global cache with a -1L | |
| final int k = j - dupes; | |
| filteredCodes[k] = code; | |
| glyphInfoCache.put(code, -1L); | |
| filteredIndicies[j] = k; | |
| j++; | |
| } | |
| final int filteredRunLen = j - dupes; | |
| final long[] filteredImages = new long[filteredRunLen]; | |
| // bulk call to fill in the distinct glyph pointers from native | |
| getFilteredGlyphImagePtrs(filteredImages, filteredCodes, filteredRunLen); | |
| // scan the requested glyph list, and fill in pointers from our | |
| // distinct glyph list which has been filled from native | |
| j = 0; | |
| for (int i = 0; i < len; i++) { | |
| if (images[i] != 0L && images[i] != -1L) { | |
| continue; // already placed | |
| } | |
| // index into filteredImages array | |
| final int k = filteredIndicies[j]; | |
| final int code = glyphCodes[i]; | |
| if (k == -1L) { | |
| // we should have already filled the cache with this pointer | |
| images[i] = glyphInfoCache.get(code); | |
| } else { | |
| // fill the particular glyph code request, and store | |
| // in the cache | |
| final long ptr = filteredImages[k]; | |
| images[i] = ptr; | |
| glyphInfoCache.put(code, ptr); | |
| } | |
| j++; | |
| } | |
| } | |
| } | |
| private void getFilteredGlyphImagePtrs(long[] glyphInfos, | |
| int[] uniCodes, int len) | |
| { | |
| getGlyphImagePtrsNative(getNativeStrikePtr(), glyphInfos, uniCodes, len); | |
| } | |
| private float getCachedNativeGlyphAdvance(int glyphCode) { | |
| synchronized(glyphAdvanceCache) { | |
| float advance = glyphAdvanceCache.get(glyphCode); | |
| if (advance != 0) { | |
| return advance; | |
| } | |
| advance = getNativeGlyphAdvance(getNativeStrikePtr(), glyphCode); | |
| glyphAdvanceCache.put(glyphCode, advance); | |
| return advance; | |
| } | |
| } | |
| // This class stores glyph pointers, and is indexed based on glyph codes, | |
| // and negative unicode values. See the comments in | |
| // CCharToGlyphMapper for more details on our glyph code strategy. | |
| private static class GlyphInfoCache extends CStrikeDisposer { | |
| private static final int FIRST_LAYER_SIZE = 256; | |
| private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 | |
| // rdar://problem/5204197 | |
| private boolean disposed = false; | |
| private final long[] firstLayerCache; | |
| private SparseBitShiftingTwoLayerArray secondLayerCache; | |
| private HashMap<Integer, Long> generalCache; | |
| GlyphInfoCache(final Font2D nativeFont, final FontStrikeDesc desc) { | |
| super(nativeFont, desc); | |
| firstLayerCache = new long[FIRST_LAYER_SIZE]; | |
| } | |
| public synchronized long get(final int index) { | |
| if (index < 0) { | |
| if (-index < SECOND_LAYER_SIZE) { | |
| // catch common unicodes | |
| if (secondLayerCache == null) { | |
| return 0L; | |
| } | |
| return secondLayerCache.get(-index); | |
| } | |
| } else { | |
| if (index < FIRST_LAYER_SIZE) { | |
| // catch common glyphcodes | |
| return firstLayerCache[index]; | |
| } | |
| } | |
| if (generalCache == null) { | |
| return 0L; | |
| } | |
| final Long value = generalCache.get(Integer.valueOf(index)); | |
| if (value == null) { | |
| return 0L; | |
| } | |
| return value.longValue(); | |
| } | |
| public synchronized void put(final int index, final long value) { | |
| if (index < 0) { | |
| if (-index < SECOND_LAYER_SIZE) { | |
| // catch common unicodes | |
| if (secondLayerCache == null) { | |
| secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 | |
| } | |
| secondLayerCache.put(-index, value); | |
| return; | |
| } | |
| } else { | |
| if (index < FIRST_LAYER_SIZE) { | |
| // catch common glyphcodes | |
| firstLayerCache[index] = value; | |
| return; | |
| } | |
| } | |
| if (generalCache == null) { | |
| generalCache = new HashMap<Integer, Long>(); | |
| } | |
| generalCache.put(Integer.valueOf(index), Long.valueOf(value)); | |
| } | |
| public synchronized void dispose() { | |
| // rdar://problem/5204197 | |
| // Note that sun.font.Font2D.getStrike() actively disposes | |
| // cleared strikeRef. We need to check the disposed flag to | |
| // prevent double frees of native resources. | |
| if (disposed) { | |
| return; | |
| } | |
| super.dispose(); | |
| // clean out the first array | |
| disposeLongArray(firstLayerCache); | |
| // clean out the two layer arrays | |
| if (secondLayerCache != null) { | |
| final long[][] secondLayerLongArrayArray = secondLayerCache.cache; | |
| for (int i = 0; i < secondLayerLongArrayArray.length; i++) { | |
| final long[] longArray = secondLayerLongArrayArray[i]; | |
| if (longArray != null) disposeLongArray(longArray); | |
| } | |
| } | |
| // clean up everyone else | |
| if (generalCache != null) { | |
| for (long longValue : generalCache.values()) { | |
| if (longValue != -1 && longValue != 0) { | |
| removeGlyphInfoFromCache(longValue); | |
| StrikeCache.freeLongPointer(longValue); | |
| } | |
| } | |
| } | |
| // rdar://problem/5204197 | |
| // Finally, set the flag. | |
| disposed = true; | |
| } | |
| private static void disposeLongArray(final long[] longArray) { | |
| for (int i = 0; i < longArray.length; i++) { | |
| final long ptr = longArray[i]; | |
| if (ptr != 0 && ptr != -1) { | |
| removeGlyphInfoFromCache(ptr); | |
| StrikeCache.freeLongPointer(ptr); // free's the native struct pointer | |
| } | |
| } | |
| } | |
| private static class SparseBitShiftingTwoLayerArray { | |
| final long[][] cache; | |
| final int shift; | |
| final int secondLayerLength; | |
| SparseBitShiftingTwoLayerArray(final int size, final int shift) { | |
| this.shift = shift; | |
| this.cache = new long[1 << shift][]; | |
| this.secondLayerLength = size >> shift; | |
| } | |
| public long get(final int index) { | |
| final int firstIndex = index >> shift; | |
| final long[] firstLayerRow = cache[firstIndex]; | |
| if (firstLayerRow == null) return 0L; | |
| return firstLayerRow[index - (firstIndex * (1 << shift))]; | |
| } | |
| public void put(final int index, final long value) { | |
| final int firstIndex = index >> shift; | |
| long[] firstLayerRow = cache[firstIndex]; | |
| if (firstLayerRow == null) { | |
| cache[firstIndex] = firstLayerRow = new long[secondLayerLength]; | |
| } | |
| firstLayerRow[index - (firstIndex * (1 << shift))] = value; | |
| } | |
| } | |
| } | |
| private static class GlyphAdvanceCache { | |
| private static final int FIRST_LAYER_SIZE = 256; | |
| private static final int SECOND_LAYER_SIZE = 16384; // 16384 = 128x128 | |
| private final float[] firstLayerCache = new float[FIRST_LAYER_SIZE]; | |
| private SparseBitShiftingTwoLayerArray secondLayerCache; | |
| private HashMap<Integer, Float> generalCache; | |
| public synchronized float get(final int index) { | |
| if (index < 0) { | |
| if (-index < SECOND_LAYER_SIZE) { | |
| // catch common unicodes | |
| if (secondLayerCache == null) return 0; | |
| return secondLayerCache.get(-index); | |
| } | |
| } else { | |
| if (index < FIRST_LAYER_SIZE) { | |
| // catch common glyphcodes | |
| return firstLayerCache[index]; | |
| } | |
| } | |
| if (generalCache == null) return 0; | |
| final Float value = generalCache.get(Integer.valueOf(index)); | |
| if (value == null) return 0; | |
| return value.floatValue(); | |
| } | |
| public synchronized void put(final int index, final float value) { | |
| if (index < 0) { | |
| if (-index < SECOND_LAYER_SIZE) { | |
| // catch common unicodes | |
| if (secondLayerCache == null) { | |
| secondLayerCache = new SparseBitShiftingTwoLayerArray(SECOND_LAYER_SIZE, 7); // 128x128 | |
| } | |
| secondLayerCache.put(-index, value); | |
| return; | |
| } | |
| } else { | |
| if (index < FIRST_LAYER_SIZE) { | |
| // catch common glyphcodes | |
| firstLayerCache[index] = value; | |
| return; | |
| } | |
| } | |
| if (generalCache == null) { | |
| generalCache = new HashMap<Integer, Float>(); | |
| } | |
| generalCache.put(Integer.valueOf(index), Float.valueOf(value)); | |
| } | |
| private static class SparseBitShiftingTwoLayerArray { | |
| final float[][] cache; | |
| final int shift; | |
| final int secondLayerLength; | |
| SparseBitShiftingTwoLayerArray(final int size, final int shift) { | |
| this.shift = shift; | |
| this.cache = new float[1 << shift][]; | |
| this.secondLayerLength = size >> shift; | |
| } | |
| public float get(final int index) { | |
| final int firstIndex = index >> shift; | |
| final float[] firstLayerRow = cache[firstIndex]; | |
| if (firstLayerRow == null) return 0L; | |
| return firstLayerRow[index - (firstIndex * (1 << shift))]; | |
| } | |
| public void put(final int index, final float value) { | |
| final int firstIndex = index >> shift; | |
| float[] firstLayerRow = cache[firstIndex]; | |
| if (firstLayerRow == null) { | |
| cache[firstIndex] = firstLayerRow = | |
| new float[secondLayerLength]; | |
| } | |
| firstLayerRow[index - (firstIndex * (1 << shift))] = value; | |
| } | |
| } | |
| } | |
| } |