deep-c-rsc/JCGO/sunawt/fix/sun/awt/font/AdvanceCache.java
2021-07-16 17:12:20 -05:00

425 lines
15 KiB
Java

/*
* This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com>
* Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/)
*/
/*
* A cache of latin-1, kana, and cjk advances, to speed up TextLayout metrics operations.
*
* @(#)AdvanceCache.java 1.4 02/10/09
*/
package sun.awt.font;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.lang.ref.SoftReference;
public final class AdvanceCache {
private Font font;
private FontRenderContext frc;
private LineMetrics lineMetrics;
private float[] latinAdvances;
private float[] kanaAdvances; // hira, kana almost always monospaced, but just in case
private float cjkAdvance;
private float cjkFullAdvance; // probably redundant with cjkAdvance, but might have a bad composite font
private float cjkHalfAdvance;
private float[] latin1GlyphInfo; // redundant with advances, but advances are quicker to index?
private float[] kanaGlyphInfo;
private float[] missingGlyphInfo;
private float missingGlyphAdvance;
private static final char KANA_MIN = '\u3040';
private static final char KANA_LIM = '\u3100';
private static final char CJK_SAMPLE1 = '\u4e9e';
private static final char CJK_SAMPLE2 = '\u4e9a';
private static final char CJK_SAMPLE3 = '\u4e9c';
private static final char CJK_MIN = '\u3200';
private static final char CJK_LIM = '\u9fb0';
private static final char CJKFULL_SAMPLE = '\uff01';
private static final char CJKFULL1_MIN = '\uff00';
private static final char CJKFULL1_LIM = '\uff5f';
private static final char CJKFULL2_MIN = '\uffe0';
private static final char CJKFULL2_LIM = '\uffe7';
private static final char CJKHALF_SAMPLE = '\uff61';
private static final char CJKHALF_MIN = '\uff61';
private static final char CJKHALF_LIM = '\uff9e';
// not used
private static final char KSYL_SAMPLE = '\uac00';
private static final char KSYL_MIN = '\uac00';
private static final char KSYL_MAX = '\ud7a0';
private static final Object cacheLock = new Object();
private static final int CACHE_SIZE = 30;
private static SoftReference[] cache = new SoftReference[CACHE_SIZE];
private static int cacheNum;
private static boolean enabled = true;
private static String latin1;
private static String kana;
static {
char[] chars = new char[0x100];
for (int i = 0; i < chars.length; ++i) {
chars[i] = (char)i;
}
// these aren't rendered by textlayout, but are by drawstring...
chars[9] = chars[0xa] = chars[0xd] = ' ';
latin1 = new String(chars);
int kanaLen = KANA_LIM - KANA_MIN;
chars = new char[kanaLen + 5];
for (int i = 0; i < kanaLen; ++i) {
chars[i] = (char)(KANA_MIN + i);
}
chars[kanaLen] = CJK_SAMPLE1;
chars[kanaLen+1] = CJKFULL_SAMPLE;
chars[kanaLen+2] = CJKHALF_SAMPLE;
chars[kanaLen+3] = CJK_SAMPLE2;
chars[kanaLen+4] = CJK_SAMPLE3;
kana = new String(chars);
try {
String useCache = System.getProperty("sun.awt.font.advancecache");
if (useCache != null && useCache.equals("off")) {
enabled = false;
}
} catch(SecurityException e) {
}
}
private boolean equals(Font font, FontRenderContext frc) {
return this.font.equals(font) && this.frc.equals(frc);
}
/**
* Return true if the advance cache supports queries on the indicated text.
* This implies also that no special layout is required.
*/
public static boolean supportsText(char[] chars, int start, int limit) {
if (!enabled) return false;
for (int i = start; i < limit; ++i) {
char c = chars[i];
if (!((c < 0x100) ||
(c >= KANA_MIN && c < KANA_LIM) ||
(c >= CJK_MIN && c < CJK_LIM) ||
(c >= CJKHALF_MIN && c < CJKHALF_LIM) ||
(c >= CJKFULL1_MIN && c < CJKFULL1_LIM) ||
(c >= CJKFULL2_MIN && c < CJKFULL2_LIM))) {
return false;
}
}
return true;
}
public static boolean supportsText(char[] chars) {
return supportsText(chars, 0, chars.length);
}
static int statCount = 0;
static int statTest = 0;
static int statMiss = 0;
static int statFlush = 0;
static int statFlushDelta = 0;
static int statLastFlushCount = 0;
public static AdvanceCache get(Font font, FontRenderContext frc) {
if (!enabled) return null;
synchronized (cacheLock) {
++statCount;
int i = 0;
while (i < cacheNum) {
AdvanceCache ac = (AdvanceCache)cache[i].get();
if (ac == null) {
// gc flushed our cache, assume all empty
++statFlush;
statFlushDelta = statCount - statLastFlushCount;
statLastFlushCount = statCount;
cache = new SoftReference[CACHE_SIZE]; // reset to initial size
cacheNum = 0;
break;
} else {
++statTest;
if (ac.equals(font, frc)) {
if (i > 0) {
SoftReference ref = cache[i];
while (i > 0) {
cache[i] = cache[--i];
}
cache[0] = ref;
}
return ac;
}
++i;
}
}
++statMiss;
if (i == cache.length) { // cache is full, so grow cache (maybe just use a large cache?)
SoftReference[] ncache = new SoftReference[cache.length + CACHE_SIZE]; // linear growth, gc should control it
System.arraycopy(cache, 0, ncache, 1, cache.length);
cache = ncache;
} else {
while (--i >= 0) {
cache[i+1] = cache[i];
}
}
AdvanceCache ac = new AdvanceCache(font, frc);
cache[0] = new SoftReference(ac);
++cacheNum;
return ac;
}
}
private void initLatinAdvances() {
StandardGlyphVector sgv = new StandardGlyphVector(font, latin1, frc);
latin1GlyphInfo = sgv.getGlyphInfo();
latinAdvances = new float[256];
for (int i = 0, n = 0; i < latinAdvances.length; ++i, n += 8) {
latinAdvances[i] = latin1GlyphInfo[n+2];
latin1GlyphInfo[n+4] -= latin1GlyphInfo[n]; // normalize visual bounds to position
latin1GlyphInfo[n+6] += latin1GlyphInfo[n+4]; // width -> right
latin1GlyphInfo[n+7] += latin1GlyphInfo[n+5]; // height -> bottom
}
// these aren't rendered by textlayout
// earlier mapping to spaces ensures glyph info width/height is empty
// we don't use glyphInfo advances so this is ok
latinAdvances[9] = latinAdvances[0xa] = latinAdvances[0xd] = 0;
}
private void initKanaAdvances() {
StandardGlyphVector sgv = new StandardGlyphVector(font, kana, frc);
kanaGlyphInfo = sgv.getGlyphInfo();
int missingGlyph = font.getMissingGlyphCode();
kanaAdvances = new float[kana.length()];
for (int i = 0, n = 0; i < kanaAdvances.length; ++i, n += 8) {
kanaAdvances[i] = kanaGlyphInfo[n+2];
kanaGlyphInfo[n+4] -= kanaGlyphInfo[n]; // normalize visual bounds to position
kanaGlyphInfo[n+6] += kanaGlyphInfo[n+4]; // width -> right
kanaGlyphInfo[n+7] += kanaGlyphInfo[n+5]; // height -> bottom
}
// init other cjk here
int kanaLen = KANA_LIM - KANA_MIN;
int cjkIndex = kanaLen;
if (sgv.getGlyphCode(kanaLen) == missingGlyph) {
if (sgv.getGlyphCode(kanaLen+3) != missingGlyph) {
cjkIndex = kanaLen+3;
} else if (sgv.getGlyphCode(kanaLen+4) != missingGlyph) {
cjkIndex = kanaLen+4;
}
}
cjkAdvance = kanaAdvances[cjkIndex];
if (cjkIndex != kanaLen) {
System.arraycopy(kanaGlyphInfo, cjkIndex * 8, kanaGlyphInfo, kanaLen * 8, 8);
}
cjkFullAdvance = kanaAdvances[kanaLen + 1];
cjkHalfAdvance = kanaAdvances[kanaLen + 2];
}
private void initMissingGlyphInfo() {
int[] glyphIDs = { font.getMissingGlyphCode() };
StandardGlyphVector sgv = new StandardGlyphVector(font, glyphIDs, frc);
missingGlyphInfo = sgv.getGlyphInfo();
missingGlyphInfo[4] -= missingGlyphInfo[0]; // normalize visual bounds to position
missingGlyphInfo[6] += missingGlyphInfo[4]; // width -> right
missingGlyphInfo[7] += missingGlyphInfo[5]; // height -> bottom
missingGlyphAdvance = missingGlyphInfo[2];
}
private AdvanceCache(Font font, FontRenderContext frc) {
this.font = font;
this.frc = frc;
initLatinAdvances();
// defer init of kana advances?
initKanaAdvances();
initMissingGlyphInfo();
// sigh, just assume ASCII metrics
// or perhaps remove metrics API altogether
this.lineMetrics = font.getLineMetrics(latin1, frc);
}
public LineMetrics getLineMetrics() {
return lineMetrics;
}
public float getAdvance(char c) {
if (c < 0x100) { // this check doesn't slow down ASCII significantly
return latinAdvances[c];
} else if (c >= KANA_MIN && c < KANA_LIM) {
return kanaAdvances[c - KANA_MIN];
} else if (c >= CJK_MIN && c < CJK_LIM) {
return font.canDisplay(c) ? cjkAdvance : missingGlyphAdvance;
} else if (c >= CJKHALF_MIN && c < CJKHALF_LIM) {
return font.canDisplay(c) ? cjkHalfAdvance : missingGlyphAdvance;
} else if (c >= CJKFULL1_MIN && c < CJKFULL1_LIM) {
return font.canDisplay(c) ? cjkFullAdvance : missingGlyphAdvance;
} else if (c >= CJKFULL2_MIN && c < CJKFULL2_LIM) {
return font.canDisplay(c) ? cjkFullAdvance : missingGlyphAdvance;
} else {
// throw same error as if we'd just looked up in a table
throw new IndexOutOfBoundsException("no advance for char " + Integer.toHexString(c));
}
}
public float getAdvance(String str) {
return getAdvance(str.toCharArray());
}
public float getAdvance(char[] chars) {
return getAdvance(chars, 0, chars.length);
}
public float getAdvance(char[] chars, int start, int limit) {
float adv = 0;
for (int i = start; i < limit; ++i) {
//adv += getAdvance(chars[i]); // this adds 5-10% to the time
char c = chars[i];
if (c < 0x100) { // this check doesn't slow down ASCII significantly
adv += latinAdvances[c];
} else if (c >= KANA_MIN && c < KANA_LIM) {
adv += kanaAdvances[c - KANA_MIN];
} else if (c >= CJK_MIN && c < CJK_LIM) {
adv += font.canDisplay(c) ? cjkAdvance : missingGlyphAdvance;
} else if (c >= CJKHALF_MIN && c < CJKHALF_LIM) {
adv += font.canDisplay(c) ? cjkHalfAdvance : missingGlyphAdvance;
} else if (c >= CJKFULL1_MIN && c < CJKFULL1_LIM) {
adv += font.canDisplay(c) ? cjkFullAdvance : missingGlyphAdvance;
} else if (c >= CJKFULL2_MIN && c < CJKFULL2_LIM) {
adv += font.canDisplay(c) ? cjkFullAdvance : missingGlyphAdvance;
} else {
// throw same error as if we'd just looked up in a table
throw new IndexOutOfBoundsException("no advance for char " + Integer.toHexString(c));
}
}
return adv;
}
public Rectangle2D getLogicalBounds(String str) {
return getLogicalBounds(str.toCharArray());
}
public Rectangle2D getLogicalBounds(char[] chars) {
return getLogicalBounds(chars, 0, chars.length);
}
public Rectangle2D getLogicalBounds(char[] chars, int start, int limit) {
float adv = getAdvance(chars, start, limit);
return new Rectangle2D.Float(0,
-lineMetrics.getAscent(),
adv,
lineMetrics.getHeight());
}
public Rectangle2D getVisualBounds(String str) {
return getVisualBounds(str.toCharArray());
}
public Rectangle2D getVisualBounds(char[] chars) {
return getVisualBounds(chars, 0, chars.length);
}
public Rectangle2D getVisualBounds(char[] chars, int start, int limit) {
float l = Float.MAX_VALUE;
float t = l;
float r = 0;
float b = r;
float x = 0;
float[] data = null;
int n = 0;
for (int i = start; i < limit; ++i) {
char c = chars[i];
if (c < 0x100) {
n = c;
data = latin1GlyphInfo;
} else if (c >= KANA_MIN && c < KANA_LIM) {
n = c - KANA_MIN;
data = kanaGlyphInfo;
} else if (c >= CJK_MIN && c < CJK_LIM) {
if (font.canDisplay(c)) {
n = KANA_LIM - KANA_MIN;
data = kanaGlyphInfo;
} else {
n = 0;
data = missingGlyphInfo;
}
} else if (c >= CJKHALF_MIN && c < CJKHALF_LIM) {
if (font.canDisplay(c)) {
n = KANA_LIM - KANA_MIN + 1;
data = kanaGlyphInfo;
} else {
n = 0;
data = missingGlyphInfo;
}
} else if (c >= CJKFULL1_MIN && c < CJKFULL1_LIM) {
if (font.canDisplay(c)) {
n = KANA_LIM - KANA_MIN + 2;
data = kanaGlyphInfo;
} else {
n = 0;
data = missingGlyphInfo;
}
} else if (c >= CJKFULL2_MIN && c < CJKFULL2_LIM) {
if (font.canDisplay(c)) {
n = KANA_LIM - KANA_MIN + 2;
data = kanaGlyphInfo;
} else {
n = 0;
data = missingGlyphInfo;
}
} else {
// throw same error as if we'd just looked up in a table
throw new IndexOutOfBoundsException("no advance for char " + Integer.toHexString(c));
}
n *= 8;
float cl = x + data[n+4];
float ct = data[n+5];
float cr = x + data[n+6];
float cb = data[n+7];
if (cr > cl && cb > ct) { // empty paths aren't added
if (cl < l) l = cl;
if (ct < t) t = ct;
if (cr > r) r = cr;
if (cb > b) b = cb;
}
x += data[n+2];
}
return new Rectangle2D.Float(l, t, r-l, b-t);
}
public void getStats(int[] results) {
synchronized(cacheLock) {
results[0] = statCount;
results[1] = statTest;
results[2] = statMiss;
results[3] = statFlush;
results[4] = statFlushDelta;
}
}
}