feat(jdk8): move files to new folder to avoid resources compiled.

This commit is contained in:
2025-09-07 15:25:52 +08:00
parent 3f0047bf6f
commit 8c35cfb1c0
17415 changed files with 217 additions and 213 deletions

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2004, 2005, 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.
*/
/*
What is the dead simplest thing to do?
Extend AbstractMap and don't optimize for anything.
The only new api is 'getValues()' which returns the values struct as
long as no map api has been called. If any map api is called,
create a real map and forward to it, and nuke values because of the
possibility that the map has been changed. This is easier than
trying to create a map that only clears values if the map has been
changed, or implementing the map API directly on top of the values
struct. We can always do that later if need be.
*/
package sun.font;
import java.awt.Paint;
import java.awt.font.GraphicAttribute;
import java.awt.font.NumericShaper;
import java.awt.font.TextAttribute;
import java.awt.font.TransformAttribute;
import java.awt.geom.AffineTransform;
import java.awt.im.InputMethodHighlight;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import static sun.font.AttributeValues.*;
public final class AttributeMap extends AbstractMap<TextAttribute, Object> {
private AttributeValues values;
private Map<TextAttribute, Object> delegateMap;
public AttributeMap(AttributeValues values) {
this.values = values;
}
public Set<Entry<TextAttribute, Object>> entrySet() {
return delegate().entrySet();
}
public Object put(TextAttribute key, Object value) {
return delegate().put(key, value);
}
// internal API
public AttributeValues getValues() {
return values;
}
private static boolean first = false; // debug
private Map<TextAttribute, Object> delegate() {
if (delegateMap == null) {
if (first) {
first = false;
Thread.dumpStack();
}
delegateMap = values.toMap(new HashMap<TextAttribute, Object>(27));
// nuke values, once map is accessible it might be mutated and values would
// no longer reflect its contents
values = null;
}
return delegateMap;
}
public String toString() {
if (values != null) {
return "map of " + values.toString();
}
return super.toString();
}
}

View File

@@ -0,0 +1,907 @@
/*
* Copyright (c) 2004, 2008, 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.
*/
/*
*
* (C) Copyright IBM Corp. 2005 - All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by IBM. These materials are provided
* under terms of a License Agreement between IBM and Sun.
* This technology is protected by multiple US and International
* patents. This notice and attribution to IBM may not be removed.
*/
package sun.font;
import static sun.font.EAttribute.*;
import static java.lang.Math.*;
import java.awt.Font;
import java.awt.Paint;
import java.awt.Toolkit;
import java.awt.font.GraphicAttribute;
import java.awt.font.NumericShaper;
import java.awt.font.TextAttribute;
import java.awt.font.TransformAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.im.InputMethodHighlight;
import java.io.Serializable;
import java.text.Annotation;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
public final class AttributeValues implements Cloneable {
private int defined;
private int nondefault;
private String family = "Default";
private float weight = 1f;
private float width = 1f;
private float posture; // 0f
private float size = 12f;
private float tracking; // 0f
private NumericShaper numericShaping; // null
private AffineTransform transform; // null == identity
private GraphicAttribute charReplacement; // null
private Paint foreground; // null
private Paint background; // null
private float justification = 1f;
private Object imHighlight; // null
// (can be either Attribute wrapping IMH, or IMH itself
private Font font; // here for completeness, don't actually use
private byte imUnderline = -1; // same default as underline
private byte superscript; // 0
private byte underline = -1; // arrgh, value for ON is 0
private byte runDirection = -2; // BIDI.DIRECTION_DEFAULT_LEFT_TO_RIGHT
private byte bidiEmbedding; // 0
private byte kerning; // 0
private byte ligatures; // 0
private boolean strikethrough; // false
private boolean swapColors; // false
private AffineTransform baselineTransform; // derived from transform
private AffineTransform charTransform; // derived from transform
private static final AttributeValues DEFAULT = new AttributeValues();
// type-specific API
public String getFamily() { return family; }
public void setFamily(String f) { this.family = f; update(EFAMILY); }
public float getWeight() { return weight; }
public void setWeight(float f) { this.weight = f; update(EWEIGHT); }
public float getWidth() { return width; }
public void setWidth(float f) { this.width = f; update(EWIDTH); }
public float getPosture() { return posture; }
public void setPosture(float f) { this.posture = f; update(EPOSTURE); }
public float getSize() { return size; }
public void setSize(float f) { this.size = f; update(ESIZE); }
public AffineTransform getTransform() { return transform; }
public void setTransform(AffineTransform f) {
this.transform = (f == null || f.isIdentity())
? DEFAULT.transform
: new AffineTransform(f);
updateDerivedTransforms();
update(ETRANSFORM);
}
public void setTransform(TransformAttribute f) {
this.transform = (f == null || f.isIdentity())
? DEFAULT.transform
: f.getTransform();
updateDerivedTransforms();
update(ETRANSFORM);
}
public int getSuperscript() { return superscript; }
public void setSuperscript(int f) {
this.superscript = (byte)f; update(ESUPERSCRIPT); }
public Font getFont() { return font; }
public void setFont(Font f) { this.font = f; update(EFONT); }
public GraphicAttribute getCharReplacement() { return charReplacement; }
public void setCharReplacement(GraphicAttribute f) {
this.charReplacement = f; update(ECHAR_REPLACEMENT); }
public Paint getForeground() { return foreground; }
public void setForeground(Paint f) {
this.foreground = f; update(EFOREGROUND); }
public Paint getBackground() { return background; }
public void setBackground(Paint f) {
this.background = f; update(EBACKGROUND); }
public int getUnderline() { return underline; }
public void setUnderline(int f) {
this.underline = (byte)f; update(EUNDERLINE); }
public boolean getStrikethrough() { return strikethrough; }
public void setStrikethrough(boolean f) {
this.strikethrough = f; update(ESTRIKETHROUGH); }
public int getRunDirection() { return runDirection; }
public void setRunDirection(int f) {
this.runDirection = (byte)f; update(ERUN_DIRECTION); }
public int getBidiEmbedding() { return bidiEmbedding; }
public void setBidiEmbedding(int f) {
this.bidiEmbedding = (byte)f; update(EBIDI_EMBEDDING); }
public float getJustification() { return justification; }
public void setJustification(float f) {
this.justification = f; update(EJUSTIFICATION); }
public Object getInputMethodHighlight() { return imHighlight; }
public void setInputMethodHighlight(Annotation f) {
this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }
public void setInputMethodHighlight(InputMethodHighlight f) {
this.imHighlight = f; update(EINPUT_METHOD_HIGHLIGHT); }
public int getInputMethodUnderline() { return imUnderline; }
public void setInputMethodUnderline(int f) {
this.imUnderline = (byte)f; update(EINPUT_METHOD_UNDERLINE); }
public boolean getSwapColors() { return swapColors; }
public void setSwapColors(boolean f) {
this.swapColors = f; update(ESWAP_COLORS); }
public NumericShaper getNumericShaping() { return numericShaping; }
public void setNumericShaping(NumericShaper f) {
this.numericShaping = f; update(ENUMERIC_SHAPING); }
public int getKerning() { return kerning; }
public void setKerning(int f) {
this.kerning = (byte)f; update(EKERNING); }
public float getTracking() { return tracking; }
public void setTracking(float f) {
this.tracking = (byte)f; update(ETRACKING); }
public int getLigatures() { return ligatures; }
public void setLigatures(int f) {
this.ligatures = (byte)f; update(ELIGATURES); }
public AffineTransform getBaselineTransform() { return baselineTransform; }
public AffineTransform getCharTransform() { return charTransform; }
// mask api
public static int getMask(EAttribute att) {
return att.mask;
}
public static int getMask(EAttribute ... atts) {
int mask = 0;
for (EAttribute a: atts) {
mask |= a.mask;
}
return mask;
}
public static final int MASK_ALL =
getMask(EAttribute.class.getEnumConstants());
public void unsetDefault() {
defined &= nondefault;
}
public void defineAll(int mask) {
defined |= mask;
if ((defined & EBASELINE_TRANSFORM.mask) != 0) {
throw new InternalError("can't define derived attribute");
}
}
public boolean allDefined(int mask) {
return (defined & mask) == mask;
}
public boolean anyDefined(int mask) {
return (defined & mask) != 0;
}
public boolean anyNonDefault(int mask) {
return (nondefault & mask) != 0;
}
// generic EAttribute API
public boolean isDefined(EAttribute a) {
return (defined & a.mask) != 0;
}
public boolean isNonDefault(EAttribute a) {
return (nondefault & a.mask) != 0;
}
public void setDefault(EAttribute a) {
if (a.att == null) {
throw new InternalError("can't set default derived attribute: " + a);
}
i_set(a, DEFAULT);
defined |= a.mask;
nondefault &= ~a.mask;
}
public void unset(EAttribute a) {
if (a.att == null) {
throw new InternalError("can't unset derived attribute: " + a);
}
i_set(a, DEFAULT);
defined &= ~a.mask;
nondefault &= ~a.mask;
}
public void set(EAttribute a, AttributeValues src) {
if (a.att == null) {
throw new InternalError("can't set derived attribute: " + a);
}
if (src == null || src == DEFAULT) {
setDefault(a);
} else {
if ((src.defined & a.mask) != 0) {
i_set(a, src);
update(a);
}
}
}
public void set(EAttribute a, Object o) {
if (a.att == null) {
throw new InternalError("can't set derived attribute: " + a);
}
if (o != null) {
try {
i_set(a, o);
update(a);
return;
} catch (Exception e) {
}
}
setDefault(a);
}
public Object get(EAttribute a) {
if (a.att == null) {
throw new InternalError("can't get derived attribute: " + a);
}
if ((nondefault & a.mask) != 0) {
return i_get(a);
}
return null;
}
// merging
public AttributeValues merge(Map<? extends Attribute, ?>map) {
return merge(map, MASK_ALL);
}
public AttributeValues merge(Map<? extends Attribute, ?>map,
int mask) {
if (map instanceof AttributeMap &&
((AttributeMap) map).getValues() != null) {
merge(((AttributeMap)map).getValues(), mask);
} else if (map != null && !map.isEmpty()) {
for (Map.Entry<? extends Attribute, ?> e: map.entrySet()) {
try {
EAttribute ea = EAttribute.forAttribute(e.getKey());
if (ea!= null && (mask & ea.mask) != 0) {
set(ea, e.getValue());
}
} catch (ClassCastException cce) {
// IGNORED
}
}
}
return this;
}
public AttributeValues merge(AttributeValues src) {
return merge(src, MASK_ALL);
}
public AttributeValues merge(AttributeValues src, int mask) {
int m = mask & src.defined;
for (EAttribute ea: EAttribute.atts) {
if (m == 0) {
break;
}
if ((m & ea.mask) != 0) {
m &= ~ea.mask;
i_set(ea, src);
update(ea);
}
}
return this;
}
// creation API
public static AttributeValues fromMap(Map<? extends Attribute, ?> map) {
return fromMap(map, MASK_ALL);
}
public static AttributeValues fromMap(Map<? extends Attribute, ?> map,
int mask) {
return new AttributeValues().merge(map, mask);
}
public Map<TextAttribute, Object> toMap(Map<TextAttribute, Object> fill) {
if (fill == null) {
fill = new HashMap<TextAttribute, Object>();
}
for (int m = defined, i = 0; m != 0; ++i) {
EAttribute ea = EAttribute.atts[i];
if ((m & ea.mask) != 0) {
m &= ~ea.mask;
fill.put(ea.att, get(ea));
}
}
return fill;
}
// key must be serializable, so use String, not Object
private static final String DEFINED_KEY =
"sun.font.attributevalues.defined_key";
public static boolean is16Hashtable(Hashtable<Object, Object> ht) {
return ht.containsKey(DEFINED_KEY);
}
public static AttributeValues
fromSerializableHashtable(Hashtable<Object, Object> ht)
{
AttributeValues result = new AttributeValues();
if (ht != null && !ht.isEmpty()) {
for (Map.Entry<Object, Object> e: ht.entrySet()) {
Object key = e.getKey();
Object val = e.getValue();
if (key.equals(DEFINED_KEY)) {
result.defineAll(((Integer)val).intValue());
} else {
try {
EAttribute ea =
EAttribute.forAttribute((Attribute)key);
if (ea != null) {
result.set(ea, val);
}
}
catch (ClassCastException ex) {
}
}
}
}
return result;
}
public Hashtable<Object, Object> toSerializableHashtable() {
Hashtable ht = new Hashtable();
int hashkey = defined;
for (int m = defined, i = 0; m != 0; ++i) {
EAttribute ea = EAttribute.atts[i];
if ((m & ea.mask) != 0) {
m &= ~ea.mask;
Object o = get(ea);
if (o == null) {
// hashkey will handle it
} else if (o instanceof Serializable) { // check all...
ht.put(ea.att, o);
} else {
hashkey &= ~ea.mask;
}
}
}
ht.put(DEFINED_KEY, Integer.valueOf(hashkey));
return ht;
}
// boilerplate
public int hashCode() {
return defined << 8 ^ nondefault;
}
public boolean equals(Object rhs) {
try {
return equals((AttributeValues)rhs);
}
catch (ClassCastException e) {
}
return false;
}
public boolean equals(AttributeValues rhs) {
// test in order of most likely to differ and easiest to compare
// also assumes we're generally calling this only if family,
// size, weight, posture are the same
if (rhs == null) return false;
if (rhs == this) return true;
return defined == rhs.defined
&& nondefault == rhs.nondefault
&& underline == rhs.underline
&& strikethrough == rhs.strikethrough
&& superscript == rhs.superscript
&& width == rhs.width
&& kerning == rhs.kerning
&& tracking == rhs.tracking
&& ligatures == rhs.ligatures
&& runDirection == rhs.runDirection
&& bidiEmbedding == rhs.bidiEmbedding
&& swapColors == rhs.swapColors
&& equals(transform, rhs.transform)
&& equals(foreground, rhs.foreground)
&& equals(background, rhs.background)
&& equals(numericShaping, rhs.numericShaping)
&& equals(justification, rhs.justification)
&& equals(charReplacement, rhs.charReplacement)
&& size == rhs.size
&& weight == rhs.weight
&& posture == rhs.posture
&& equals(family, rhs.family)
&& equals(font, rhs.font)
&& imUnderline == rhs.imUnderline
&& equals(imHighlight, rhs.imHighlight);
}
public AttributeValues clone() {
try {
AttributeValues result = (AttributeValues)super.clone();
if (transform != null) { // AffineTransform is mutable
result.transform = new AffineTransform(transform);
result.updateDerivedTransforms();
}
// if transform is null, derived transforms are null
// so there's nothing to do
return result;
}
catch (CloneNotSupportedException e) {
// never happens
return null;
}
}
public String toString() {
StringBuilder b = new StringBuilder();
b.append('{');
for (int m = defined, i = 0; m != 0; ++i) {
EAttribute ea = EAttribute.atts[i];
if ((m & ea.mask) != 0) {
m &= ~ea.mask;
if (b.length() > 1) {
b.append(", ");
}
b.append(ea);
b.append('=');
switch (ea) {
case EFAMILY: b.append('"');
b.append(family);
b.append('"'); break;
case EWEIGHT: b.append(weight); break;
case EWIDTH: b.append(width); break;
case EPOSTURE: b.append(posture); break;
case ESIZE: b.append(size); break;
case ETRANSFORM: b.append(transform); break;
case ESUPERSCRIPT: b.append(superscript); break;
case EFONT: b.append(font); break;
case ECHAR_REPLACEMENT: b.append(charReplacement); break;
case EFOREGROUND: b.append(foreground); break;
case EBACKGROUND: b.append(background); break;
case EUNDERLINE: b.append(underline); break;
case ESTRIKETHROUGH: b.append(strikethrough); break;
case ERUN_DIRECTION: b.append(runDirection); break;
case EBIDI_EMBEDDING: b.append(bidiEmbedding); break;
case EJUSTIFICATION: b.append(justification); break;
case EINPUT_METHOD_HIGHLIGHT: b.append(imHighlight); break;
case EINPUT_METHOD_UNDERLINE: b.append(imUnderline); break;
case ESWAP_COLORS: b.append(swapColors); break;
case ENUMERIC_SHAPING: b.append(numericShaping); break;
case EKERNING: b.append(kerning); break;
case ELIGATURES: b.append(ligatures); break;
case ETRACKING: b.append(tracking); break;
default: throw new InternalError();
}
if ((nondefault & ea.mask) == 0) {
b.append('*');
}
}
}
b.append("[btx=" + baselineTransform + ", ctx=" + charTransform + "]");
b.append('}');
return b.toString();
}
// internal utilities
private static boolean equals(Object lhs, Object rhs) {
return lhs == null ? rhs == null : lhs.equals(rhs);
}
private void update(EAttribute a) {
defined |= a.mask;
if (i_validate(a)) {
if (i_equals(a, DEFAULT)) {
nondefault &= ~a.mask;
} else {
nondefault |= a.mask;
}
} else {
setDefault(a);
}
}
// dispatch
private void i_set(EAttribute a, AttributeValues src) {
switch (a) {
case EFAMILY: family = src.family; break;
case EWEIGHT: weight = src.weight; break;
case EWIDTH: width = src.width; break;
case EPOSTURE: posture = src.posture; break;
case ESIZE: size = src.size; break;
case ETRANSFORM: transform = src.transform; updateDerivedTransforms(); break;
case ESUPERSCRIPT: superscript = src.superscript; break;
case EFONT: font = src.font; break;
case ECHAR_REPLACEMENT: charReplacement = src.charReplacement; break;
case EFOREGROUND: foreground = src.foreground; break;
case EBACKGROUND: background = src.background; break;
case EUNDERLINE: underline = src.underline; break;
case ESTRIKETHROUGH: strikethrough = src.strikethrough; break;
case ERUN_DIRECTION: runDirection = src.runDirection; break;
case EBIDI_EMBEDDING: bidiEmbedding = src.bidiEmbedding; break;
case EJUSTIFICATION: justification = src.justification; break;
case EINPUT_METHOD_HIGHLIGHT: imHighlight = src.imHighlight; break;
case EINPUT_METHOD_UNDERLINE: imUnderline = src.imUnderline; break;
case ESWAP_COLORS: swapColors = src.swapColors; break;
case ENUMERIC_SHAPING: numericShaping = src.numericShaping; break;
case EKERNING: kerning = src.kerning; break;
case ELIGATURES: ligatures = src.ligatures; break;
case ETRACKING: tracking = src.tracking; break;
default: throw new InternalError();
}
}
private boolean i_equals(EAttribute a, AttributeValues src) {
switch (a) {
case EFAMILY: return equals(family, src.family);
case EWEIGHT: return weight == src.weight;
case EWIDTH: return width == src.width;
case EPOSTURE: return posture == src.posture;
case ESIZE: return size == src.size;
case ETRANSFORM: return equals(transform, src.transform);
case ESUPERSCRIPT: return superscript == src.superscript;
case EFONT: return equals(font, src.font);
case ECHAR_REPLACEMENT: return equals(charReplacement, src.charReplacement);
case EFOREGROUND: return equals(foreground, src.foreground);
case EBACKGROUND: return equals(background, src.background);
case EUNDERLINE: return underline == src.underline;
case ESTRIKETHROUGH: return strikethrough == src.strikethrough;
case ERUN_DIRECTION: return runDirection == src.runDirection;
case EBIDI_EMBEDDING: return bidiEmbedding == src.bidiEmbedding;
case EJUSTIFICATION: return justification == src.justification;
case EINPUT_METHOD_HIGHLIGHT: return equals(imHighlight, src.imHighlight);
case EINPUT_METHOD_UNDERLINE: return imUnderline == src.imUnderline;
case ESWAP_COLORS: return swapColors == src.swapColors;
case ENUMERIC_SHAPING: return equals(numericShaping, src.numericShaping);
case EKERNING: return kerning == src.kerning;
case ELIGATURES: return ligatures == src.ligatures;
case ETRACKING: return tracking == src.tracking;
default: throw new InternalError();
}
}
private void i_set(EAttribute a, Object o) {
switch (a) {
case EFAMILY: family = ((String)o).trim(); break;
case EWEIGHT: weight = ((Number)o).floatValue(); break;
case EWIDTH: width = ((Number)o).floatValue(); break;
case EPOSTURE: posture = ((Number)o).floatValue(); break;
case ESIZE: size = ((Number)o).floatValue(); break;
case ETRANSFORM: {
if (o instanceof TransformAttribute) {
TransformAttribute ta = (TransformAttribute)o;
if (ta.isIdentity()) {
transform = null;
} else {
transform = ta.getTransform();
}
} else {
transform = new AffineTransform((AffineTransform)o);
}
updateDerivedTransforms();
} break;
case ESUPERSCRIPT: superscript = (byte)((Integer)o).intValue(); break;
case EFONT: font = (Font)o; break;
case ECHAR_REPLACEMENT: charReplacement = (GraphicAttribute)o; break;
case EFOREGROUND: foreground = (Paint)o; break;
case EBACKGROUND: background = (Paint)o; break;
case EUNDERLINE: underline = (byte)((Integer)o).intValue(); break;
case ESTRIKETHROUGH: strikethrough = ((Boolean)o).booleanValue(); break;
case ERUN_DIRECTION: {
if (o instanceof Boolean) {
runDirection = (byte)(TextAttribute.RUN_DIRECTION_LTR.equals(o) ? 0 : 1);
} else {
runDirection = (byte)((Integer)o).intValue();
}
} break;
case EBIDI_EMBEDDING: bidiEmbedding = (byte)((Integer)o).intValue(); break;
case EJUSTIFICATION: justification = ((Number)o).floatValue(); break;
case EINPUT_METHOD_HIGHLIGHT: {
if (o instanceof Annotation) {
Annotation at = (Annotation)o;
imHighlight = (InputMethodHighlight)at.getValue();
} else {
imHighlight = (InputMethodHighlight)o;
}
} break;
case EINPUT_METHOD_UNDERLINE: imUnderline = (byte)((Integer)o).intValue();
break;
case ESWAP_COLORS: swapColors = ((Boolean)o).booleanValue(); break;
case ENUMERIC_SHAPING: numericShaping = (NumericShaper)o; break;
case EKERNING: kerning = (byte)((Integer)o).intValue(); break;
case ELIGATURES: ligatures = (byte)((Integer)o).intValue(); break;
case ETRACKING: tracking = ((Number)o).floatValue(); break;
default: throw new InternalError();
}
}
private Object i_get(EAttribute a) {
switch (a) {
case EFAMILY: return family;
case EWEIGHT: return Float.valueOf(weight);
case EWIDTH: return Float.valueOf(width);
case EPOSTURE: return Float.valueOf(posture);
case ESIZE: return Float.valueOf(size);
case ETRANSFORM:
return transform == null
? TransformAttribute.IDENTITY
: new TransformAttribute(transform);
case ESUPERSCRIPT: return Integer.valueOf(superscript);
case EFONT: return font;
case ECHAR_REPLACEMENT: return charReplacement;
case EFOREGROUND: return foreground;
case EBACKGROUND: return background;
case EUNDERLINE: return Integer.valueOf(underline);
case ESTRIKETHROUGH: return Boolean.valueOf(strikethrough);
case ERUN_DIRECTION: {
switch (runDirection) {
// todo: figure out a way to indicate this value
// case -1: return Integer.valueOf(runDirection);
case 0: return TextAttribute.RUN_DIRECTION_LTR;
case 1: return TextAttribute.RUN_DIRECTION_RTL;
default: return null;
}
} // not reachable
case EBIDI_EMBEDDING: return Integer.valueOf(bidiEmbedding);
case EJUSTIFICATION: return Float.valueOf(justification);
case EINPUT_METHOD_HIGHLIGHT: return imHighlight;
case EINPUT_METHOD_UNDERLINE: return Integer.valueOf(imUnderline);
case ESWAP_COLORS: return Boolean.valueOf(swapColors);
case ENUMERIC_SHAPING: return numericShaping;
case EKERNING: return Integer.valueOf(kerning);
case ELIGATURES: return Integer.valueOf(ligatures);
case ETRACKING: return Float.valueOf(tracking);
default: throw new InternalError();
}
}
private boolean i_validate(EAttribute a) {
switch (a) {
case EFAMILY: if (family == null || family.length() == 0)
family = DEFAULT.family; return true;
case EWEIGHT: return weight > 0 && weight < 10;
case EWIDTH: return width >= .5f && width < 10;
case EPOSTURE: return posture >= -1 && posture <= 1;
case ESIZE: return size >= 0;
case ETRANSFORM: if (transform != null && transform.isIdentity())
transform = DEFAULT.transform; return true;
case ESUPERSCRIPT: return superscript >= -7 && superscript <= 7;
case EFONT: return true;
case ECHAR_REPLACEMENT: return true;
case EFOREGROUND: return true;
case EBACKGROUND: return true;
case EUNDERLINE: return underline >= -1 && underline < 6;
case ESTRIKETHROUGH: return true;
case ERUN_DIRECTION: return runDirection >= -2 && runDirection <= 1;
case EBIDI_EMBEDDING: return bidiEmbedding >= -61 && bidiEmbedding < 62;
case EJUSTIFICATION: justification = max(0, min (justification, 1));
return true;
case EINPUT_METHOD_HIGHLIGHT: return true;
case EINPUT_METHOD_UNDERLINE: return imUnderline >= -1 && imUnderline < 6;
case ESWAP_COLORS: return true;
case ENUMERIC_SHAPING: return true;
case EKERNING: return kerning >= 0 && kerning <= 1;
case ELIGATURES: return ligatures >= 0 && ligatures <= 1;
case ETRACKING: return tracking >= -1 && tracking <= 10;
default: throw new InternalError("unknown attribute: " + a);
}
}
// Until textlayout is fixed to use AttributeValues, we'll end up
// creating a map from the values for it. This is a compromise between
// creating the whole map and just checking a particular value.
// Plan to remove these.
public static float getJustification(Map<?, ?> map) {
if (map != null) {
if (map instanceof AttributeMap &&
((AttributeMap) map).getValues() != null) {
return ((AttributeMap)map).getValues().justification;
}
Object obj = map.get(TextAttribute.JUSTIFICATION);
if (obj != null && obj instanceof Number) {
return max(0, min(1, ((Number)obj).floatValue()));
}
}
return DEFAULT.justification;
}
public static NumericShaper getNumericShaping(Map<?, ?> map) {
if (map != null) {
if (map instanceof AttributeMap &&
((AttributeMap) map).getValues() != null) {
return ((AttributeMap)map).getValues().numericShaping;
}
Object obj = map.get(TextAttribute.NUMERIC_SHAPING);
if (obj != null && obj instanceof NumericShaper) {
return (NumericShaper)obj;
}
}
return DEFAULT.numericShaping;
}
/**
* If this has an imHighlight, create copy of this with those attributes
* applied to it. Otherwise return this unchanged.
*/
public AttributeValues applyIMHighlight() {
if (imHighlight != null) {
InputMethodHighlight hl = null;
if (imHighlight instanceof InputMethodHighlight) {
hl = (InputMethodHighlight)imHighlight;
} else {
hl = (InputMethodHighlight)((Annotation)imHighlight).getValue();
}
Map imStyles = hl.getStyle();
if (imStyles == null) {
Toolkit tk = Toolkit.getDefaultToolkit();
imStyles = tk.mapInputMethodHighlight(hl);
}
if (imStyles != null) {
return clone().merge(imStyles);
}
}
return this;
}
public static AffineTransform getBaselineTransform(Map<?, ?> map) {
if (map != null) {
AttributeValues av = null;
if (map instanceof AttributeMap &&
((AttributeMap) map).getValues() != null) {
av = ((AttributeMap)map).getValues();
} else if (map.get(TextAttribute.TRANSFORM) != null) {
av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
}
if (av != null) {
return av.baselineTransform;
}
}
return null;
}
public static AffineTransform getCharTransform(Map<?, ?> map) {
if (map != null) {
AttributeValues av = null;
if (map instanceof AttributeMap &&
((AttributeMap) map).getValues() != null) {
av = ((AttributeMap)map).getValues();
} else if (map.get(TextAttribute.TRANSFORM) != null) {
av = AttributeValues.fromMap((Map<Attribute, ?>)map); // yuck
}
if (av != null) {
return av.charTransform;
}
}
return null;
}
public void updateDerivedTransforms() {
// this also updates the mask for the baseline transform
if (transform == null) {
baselineTransform = null;
charTransform = null;
} else {
charTransform = new AffineTransform(transform);
baselineTransform = extractXRotation(charTransform, true);
if (charTransform.isIdentity()) {
charTransform = null;
}
if (baselineTransform.isIdentity()) {
baselineTransform = null;
}
}
if (baselineTransform == null) {
nondefault &= ~EBASELINE_TRANSFORM.mask;
} else {
nondefault |= EBASELINE_TRANSFORM.mask;
}
}
public static AffineTransform extractXRotation(AffineTransform tx,
boolean andTranslation) {
return extractRotation(new Point2D.Double(1, 0), tx, andTranslation);
}
public static AffineTransform extractYRotation(AffineTransform tx,
boolean andTranslation) {
return extractRotation(new Point2D.Double(0, 1), tx, andTranslation);
}
private static AffineTransform extractRotation(Point2D.Double pt,
AffineTransform tx, boolean andTranslation) {
tx.deltaTransform(pt, pt);
AffineTransform rtx = AffineTransform.getRotateInstance(pt.x, pt.y);
try {
AffineTransform rtxi = rtx.createInverse();
double dx = tx.getTranslateX();
double dy = tx.getTranslateY();
tx.preConcatenate(rtxi);
if (andTranslation) {
if (dx != 0 || dy != 0) {
tx.setTransform(tx.getScaleX(), tx.getShearY(),
tx.getShearX(), tx.getScaleY(), 0, 0);
rtx.setTransform(rtx.getScaleX(), rtx.getShearY(),
rtx.getShearX(), rtx.getScaleY(), dx, dy);
}
}
}
catch (NoninvertibleTransformException e) {
return null;
}
return rtx;
}
}

View File

@@ -0,0 +1,386 @@
/*
* Copyright (c) 2000, 2003, 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.
*/
/*
* (C) Copyright IBM Corp. 1999-2000 - All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by IBM. These materials are provided
* under terms of a License Agreement between IBM and Sun.
* This technology is protected by multiple US and International
* patents. This notice and attribution to IBM may not be removed.
*/
package sun.font;
import java.text.Bidi;
public final class BidiUtils {
/**
* Return the level of each character into the levels array starting at start.
* This is a convenience method for clients who prefer to use an explicit levels
* array instead of iterating over the runs.
*
* @param levels the array to receive the character levels
* @param start the starting offset into the the array
* @throws IndexOutOfBoundsException if <code>start</code> is less than 0 or
* <code>start + getLength()</code> is greater than <code>levels.length</code>.
*/
public static void getLevels(Bidi bidi, byte[] levels, int start) {
int limit = start + bidi.getLength();
if (start < 0 || limit > levels.length) {
throw new IndexOutOfBoundsException("levels.length = " + levels.length +
" start: " + start + " limit: " + limit);
}
int runCount = bidi.getRunCount();
int p = start;
for (int i = 0; i < runCount; ++i) {
int rlimit = start + bidi.getRunLimit(i);
byte rlevel = (byte)bidi.getRunLevel(i);
while (p < rlimit) {
levels[p++] = rlevel;
}
}
}
/**
* Return an array containing the resolved bidi level of each character, in logical order.
* @return an array containing the level of each character, in logical order.
*/
public static byte[] getLevels(Bidi bidi) {
byte[] levels = new byte[bidi.getLength()];
getLevels(bidi, levels, 0);
return levels;
}
static final char NUMLEVELS = 62;
/**
* Given level data, compute a a visual to logical mapping.
* The leftmost (or topmost) character is at visual index zero. The
* logical index of the character is derived from the visual index
* by the expression <code>li = map[vi];</code>.
* @param levels the levels array
* @return the mapping array from visual to logical
*/
public static int[] createVisualToLogicalMap(byte[] levels) {
int len = levels.length;
int[] mapping = new int[len];
byte lowestOddLevel = (byte)(NUMLEVELS + 1);
byte highestLevel = 0;
// initialize mapping and levels
for (int i = 0; i < len; i++) {
mapping[i] = i;
byte level = levels[i];
if (level > highestLevel) {
highestLevel = level;
}
if ((level & 0x01) != 0 && level < lowestOddLevel) {
lowestOddLevel = level;
}
}
while (highestLevel >= lowestOddLevel) {
int i = 0;
for (;;) {
while (i < len && levels[i] < highestLevel) {
i++;
}
int begin = i++;
if (begin == levels.length) {
break; // no more runs at this level
}
while (i < len && levels[i] >= highestLevel) {
i++;
}
int end = i - 1;
while (begin < end) {
int temp = mapping[begin];
mapping[begin] = mapping[end];
mapping[end] = temp;
++begin;
--end;
}
}
--highestLevel;
}
return mapping;
}
/**
* Return the inverse position map. The source array must map one-to-one (each value
* is distinct and the values run from zero to the length of the array minus one).
* For example, if <code>values[i] = j</code>, then <code>inverse[j] = i</code>.
* @param values the source ordering array
* @return the inverse array
*/
public static int[] createInverseMap(int[] values) {
if (values == null) {
return null;
}
int[] result = new int[values.length];
for (int i = 0; i < values.length; i++) {
result[values[i]] = i;
}
return result;
}
/**
* Return an array containing contiguous values from 0 to length
* having the same ordering as the source array. If this would be
* a canonical ltr ordering, return null. The data in values[] is NOT
* required to be a permutation, but elements in values are required
* to be distinct.
* @param values an array containing the discontiguous values
* @return the contiguous values
*/
public static int[] createContiguousOrder(int[] values) {
if (values != null) {
return computeContiguousOrder(values, 0, values.length);
}
return null;
}
/**
* Compute a contiguous order for the range start, limit.
*/
private static int[] computeContiguousOrder(int[] values, int start,
int limit) {
int[] result = new int[limit-start];
for (int i=0; i < result.length; i++) {
result[i] = i + start;
}
// now we'll sort result[], with the following comparison:
// result[i] lessthan result[j] iff values[result[i]] < values[result[j]]
// selection sort for now; use more elaborate sorts if desired
for (int i=0; i < result.length-1; i++) {
int minIndex = i;
int currentValue = values[result[minIndex]];
for (int j=i; j < result.length; j++) {
if (values[result[j]] < currentValue) {
minIndex = j;
currentValue = values[result[minIndex]];
}
}
int temp = result[i];
result[i] = result[minIndex];
result[minIndex] = temp;
}
// shift result by start:
if (start != 0) {
for (int i=0; i < result.length; i++) {
result[i] -= start;
}
}
// next, check for canonical order:
int k;
for (k=0; k < result.length; k++) {
if (result[k] != k) {
break;
}
}
if (k == result.length) {
return null;
}
// now return inverse of result:
return createInverseMap(result);
}
/**
* Return an array containing the data in the values array from start up to limit,
* normalized to fall within the range from 0 up to limit - start.
* If this would be a canonical ltr ordering, return null.
* NOTE: This method assumes that values[] is a logical to visual map
* generated from levels[].
* @param values the source mapping
* @param levels the levels corresponding to the values
* @param start the starting offset in the values and levels arrays
* @param limit the limiting offset in the values and levels arrays
* @return the normlized map
*/
public static int[] createNormalizedMap(int[] values, byte[] levels,
int start, int limit) {
if (values != null) {
if (start != 0 || limit != values.length) {
// levels optimization
boolean copyRange, canonical;
byte primaryLevel;
if (levels == null) {
primaryLevel = (byte) 0x0;
copyRange = true;
canonical = true;
}
else {
if (levels[start] == levels[limit-1]) {
primaryLevel = levels[start];
canonical = (primaryLevel & (byte)0x1) == 0;
// scan for levels below primary
int i;
for (i=start; i < limit; i++) {
if (levels[i] < primaryLevel) {
break;
}
if (canonical) {
canonical = levels[i] == primaryLevel;
}
}
copyRange = (i == limit);
}
else {
copyRange = false;
// these don't matter; but the compiler cares:
primaryLevel = (byte) 0x0;
canonical = false;
}
}
if (copyRange) {
if (canonical) {
return null;
}
int[] result = new int[limit-start];
int baseValue;
if ((primaryLevel & (byte)0x1) != 0) {
baseValue = values[limit-1];
} else {
baseValue = values[start];
}
if (baseValue == 0) {
System.arraycopy(values, start, result, 0, limit-start);
}
else {
for (int j=0; j < result.length; j++) {
result[j] = values[j+start] - baseValue;
}
}
return result;
}
else {
return computeContiguousOrder(values, start, limit);
}
}
else {
return values;
}
}
return null;
}
/**
* Reorder the objects in the array into visual order based on their levels.
* This is a utility function to use when you have a collection of objects
* representing runs of text in logical order, each run containing text
* at a single level. The elements in the objects array will be reordered
* into visual order assuming each run of text has the level provided
* by the corresponding element in the levels array.
* @param levels an array representing the bidi level of each object
* @param objects the array of objects to be reordered into visual order
*/
public static void reorderVisually(byte[] levels, Object[] objects) {
int len = levels.length;
byte lowestOddLevel = (byte)(NUMLEVELS + 1);
byte highestLevel = 0;
// initialize mapping and levels
for (int i = 0; i < len; i++) {
byte level = levels[i];
if (level > highestLevel) {
highestLevel = level;
}
if ((level & 0x01) != 0 && level < lowestOddLevel) {
lowestOddLevel = level;
}
}
while (highestLevel >= lowestOddLevel) {
int i = 0;
for (;;) {
while (i < len && levels[i] < highestLevel) {
i++;
}
int begin = i++;
if (begin == levels.length) {
break; // no more runs at this level
}
while (i < len && levels[i] >= highestLevel) {
i++;
}
int end = i - 1;
while (begin < end) {
Object temp = objects[begin];
objects[begin] = objects[end];
objects[end] = temp;
++begin;
--end;
}
}
--highestLevel;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2003, 2006, 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;
/*
* NB the versions that take a char as an int are used by the opentype
* layout engine. If that remains in native these methods may not be
* needed in the Java class.
*/
public abstract class CharToGlyphMapper {
public static final int HI_SURROGATE_START = 0xD800;
public static final int HI_SURROGATE_END = 0xDBFF;
public static final int LO_SURROGATE_START = 0xDC00;
public static final int LO_SURROGATE_END = 0xDFFF;
public static final int UNINITIALIZED_GLYPH = -1;
public static final int INVISIBLE_GLYPH_ID = 0xffff;
public static final int INVISIBLE_GLYPHS = 0xfffe; // and above
protected int missingGlyph = CharToGlyphMapper.UNINITIALIZED_GLYPH;
public int getMissingGlyphCode() {
return missingGlyph;
}
/* Default implementations of these methods may be overridden by
* subclasses which have special requirements or optimisations
*/
public boolean canDisplay(char ch) {
int glyph = charToGlyph(ch);
return glyph != missingGlyph;
}
public boolean canDisplay(int cp) {
int glyph = charToGlyph(cp);
return glyph != missingGlyph;
}
public int charToGlyph(char unicode) {
char[] chars = new char[1];
int[] glyphs = new int[1];
chars[0] = unicode;
charsToGlyphs(1, chars, glyphs);
return glyphs[0];
}
public int charToGlyph(int unicode) {
int[] chars = new int[1];
int [] glyphs = new int[1];
chars[0] = unicode;
charsToGlyphs(1, chars, glyphs);
return glyphs[0];
}
public abstract int getNumGlyphs();
public abstract void charsToGlyphs(int count,
char[] unicodes, int[] glyphs);
public abstract boolean charsToGlyphsNS(int count,
char[] unicodes, int[] glyphs);
public abstract void charsToGlyphs(int count,
int[] unicodes, int[] glyphs);
}

View File

@@ -0,0 +1,501 @@
/*
* Copyright (c) 2003, 2006, 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.Font;
/* Remind: need to enhance to extend component list with a fallback
* list, which is not used in metrics or queries on the composite, but
* is used in drawing primitives and queries which supply an actual string.
* ie for a codepoint that is only in a fallback, font-wide queries such
* as FontMetrics.getHeight() will not take it into account.
* But getStringBounds(..) would take it into account.
* Its fuzzier for queries such as "canDisplay". If this does not include
* the fallback, then we probably want to add "canDisplayFallback()"
* But its probably OK to include it so long as only composites include
* fallbacks. If physicals do then it would be really confusing ..
*/
public final class CompositeFont extends Font2D {
private boolean[] deferredInitialisation;
String[] componentFileNames;
String[] componentNames;
/* because components can be lazily initialised the components field is
* private, to ensure all clients call getSlotFont()
*/
private PhysicalFont[] components;
int numSlots;
int numMetricsSlots;
int[] exclusionRanges;
int[] maxIndices;
int numGlyphs = 0;
int localeSlot = -1; // primary slot for this locale.
/* See isStdComposite() for when/how this is used */
boolean isStdComposite = true;
public CompositeFont(String name, String[] compFileNames,
String[] compNames, int metricsSlotCnt,
int[] exclRanges, int[] maxIndexes,
boolean defer, SunFontManager fm) {
handle = new Font2DHandle(this);
fullName = name;
componentFileNames = compFileNames;
componentNames = compNames;
if (compNames == null) {
numSlots = componentFileNames.length;
} else {
numSlots = componentNames.length;
}
/* We will limit the number of slots to 254.
* We store the slot for a glyph id in a byte and we may use one slot
* for an EUDC font, and we may also create a composite
* using this composite as a backup for a physical font.
* So we want to leave space for the two additional slots.
*/
numSlots = (numSlots <= 254) ? numSlots : 254;
/* Only the first "numMetricsSlots" slots are used for font metrics.
* the rest are considered "fallback" slots".
*/
numMetricsSlots = metricsSlotCnt;
exclusionRanges = exclRanges;
maxIndices = maxIndexes;
/*
* See if this is a windows locale which has a system EUDC font.
* If so add it as the final fallback component of the composite.
* The caller could be responsible for this, but for now it seems
* better that it is handled internally to the CompositeFont class.
*/
if (fm.getEUDCFont() != null) {
int msCnt = numMetricsSlots;
int fbCnt = numSlots - msCnt;
numSlots++;
if (componentNames != null) {
componentNames = new String[numSlots];
System.arraycopy(compNames, 0, componentNames, 0, msCnt);
componentNames[msCnt] = fm.getEUDCFont().getFontName(null);
System.arraycopy(compNames, msCnt,
componentNames, msCnt+1, fbCnt);
}
if (componentFileNames != null) {
componentFileNames = new String[numSlots];
System.arraycopy(compFileNames, 0,
componentFileNames, 0, msCnt);
System.arraycopy(compFileNames, msCnt,
componentFileNames, msCnt+1, fbCnt);
}
components = new PhysicalFont[numSlots];
components[msCnt] = fm.getEUDCFont();
deferredInitialisation = new boolean[numSlots];
if (defer) {
for (int i=0; i<numSlots-1; i++) {
deferredInitialisation[i] = true;
}
}
} else {
components = new PhysicalFont[numSlots];
deferredInitialisation = new boolean[numSlots];
if (defer) {
for (int i=0; i<numSlots; i++) {
deferredInitialisation[i] = true;
}
}
}
fontRank = Font2D.FONT_CONFIG_RANK;
int index = fullName.indexOf('.');
if (index>0) {
familyName = fullName.substring(0, index);
/* composites don't call setStyle() as parsing the style
* takes place at the same time as parsing the family name.
* Do I really have to parse the style from the name?
* Need to look into having the caller provide this. */
if (index+1 < fullName.length()) {
String styleStr = fullName.substring(index+1);
if ("plain".equals(styleStr)) {
style = Font.PLAIN;
} else if ("bold".equals(styleStr)) {
style = Font.BOLD;
} else if ("italic".equals(styleStr)) {
style = Font.ITALIC;
} else if ("bolditalic".equals(styleStr)) {
style = Font.BOLD | Font.ITALIC;
}
}
} else {
familyName = fullName;
}
}
/*
* Build a composite from a set of individual slot fonts.
*/
CompositeFont(PhysicalFont[] slotFonts) {
isStdComposite = false;
handle = new Font2DHandle(this);
fullName = slotFonts[0].fullName;
familyName = slotFonts[0].familyName;
style = slotFonts[0].style;
numMetricsSlots = 1; /* Only the physical Font */
numSlots = slotFonts.length;
components = new PhysicalFont[numSlots];
System.arraycopy(slotFonts, 0, components, 0, numSlots);
deferredInitialisation = new boolean[numSlots]; // all false.
}
/* This method is currently intended to be called only from
* FontManager.getCompositeFontUIResource(Font)
* It creates a new CompositeFont with the contents of the Physical
* one pre-pended as slot 0.
*/
CompositeFont(PhysicalFont physFont, CompositeFont compFont) {
isStdComposite = false;
handle = new Font2DHandle(this);
fullName = physFont.fullName;
familyName = physFont.familyName;
style = physFont.style;
numMetricsSlots = 1; /* Only the physical Font */
numSlots = compFont.numSlots+1;
/* Ugly though it is, we synchronize here on the FontManager class
* because it is the lock used to do deferred initialisation.
* We need to ensure that the arrays have consistent information.
* But it may be possible to dispense with the synchronisation if
* it is harmless that we do not know a slot is already initialised
* and just need to discover that and mark it so.
*/
synchronized (FontManagerFactory.getInstance()) {
components = new PhysicalFont[numSlots];
components[0] = physFont;
System.arraycopy(compFont.components, 0,
components, 1, compFont.numSlots);
if (compFont.componentNames != null) {
componentNames = new String[numSlots];
componentNames[0] = physFont.fullName;
System.arraycopy(compFont.componentNames, 0,
componentNames, 1, compFont.numSlots);
}
if (compFont.componentFileNames != null) {
componentFileNames = new String[numSlots];
componentFileNames[0] = null;
System.arraycopy(compFont.componentFileNames, 0,
componentFileNames, 1, compFont.numSlots);
}
deferredInitialisation = new boolean[numSlots];
deferredInitialisation[0] = false;
System.arraycopy(compFont.deferredInitialisation, 0,
deferredInitialisation, 1, compFont.numSlots);
}
}
/* This is used for deferred initialisation, so that the components of
* a logical font are initialised only when the font is used.
* This can have a positive impact on start-up of most UI applications.
* Note that this technique cannot be used with a TTC font as it
* doesn't know which font in the collection is needed. The solution to
* this is that the initialisation checks if the returned font is
* really the one it wants by comparing the name against the name that
* was passed in (if none was passed in then you aren't using a TTC
* as you would have to specify the name in such a case).
* Assuming there's only two or three fonts in a collection then it
* may be sufficient to verify the returned name is the expected one.
* But half the time it won't be. However since initialisation of the
* TTC will initialise all its components then just do a findFont2D call
* to locate the right one.
* This code allows for initialisation of each slot on demand.
* There are two issues with this.
* 1) All metrics slots probably may be initialised anyway as many
* apps will query the overall font metrics. However this is not an
* absolute requirement
* 2) Some font configuration files on Solaris reference two versions
* of a TT font: a Latin-1 version, then a Pan-European version.
* One from /usr/openwin/lib/X11/fonts/TrueType, the other from
* a euro_fonts directory which is symlinked from numerous locations.
* This is difficult to avoid because the two do not share XLFDs so
* both will be consequently mapped by separate XLFDs needed by AWT.
* The difficulty this presents for lazy initialisation is that if
* all the components are not mapped at once, the smaller version may
* have been used only to be replaced later, and what is the consequence
* for a client that displayed the contents of this font already.
* After some thought I think this will not be a problem because when
* client tries to display a glyph only in the Euro font, the composite
* will ask all components of this font for that glyph and will get
* the euro one. Subsequent uses will all come from the 100% compatible
* euro one.
*/
private void doDeferredInitialisation(int slot) {
if (deferredInitialisation[slot] == false) {
return;
}
/* Synchronize on FontManager so that is the global lock
* to update its static set of deferred fonts.
* This global lock is rarely likely to be an issue as there
* are only going to be a few calls into this code.
*/
SunFontManager fm = SunFontManager.getInstance();
synchronized (fm) {
if (componentNames == null) {
componentNames = new String[numSlots];
}
if (components[slot] == null) {
/* Warning: it is possible that the returned component is
* not derived from the file name argument, this can happen if:
* - the file can't be found
* - the file has a bad font
* - the font in the file is superseded by a more complete one
* This should not be a problem for composite font as it will
* make no further use of this file, but code debuggers/
* maintainers need to be conscious of this possibility.
*/
if (componentFileNames != null &&
componentFileNames[slot] != null) {
components[slot] =
fm.initialiseDeferredFont(componentFileNames[slot]);
}
if (components[slot] == null) {
components[slot] = fm.getDefaultPhysicalFont();
}
String name = components[slot].getFontName(null);
if (componentNames[slot] == null) {
componentNames[slot] = name;
} else if (!componentNames[slot].equalsIgnoreCase(name)) {
/* If a component specifies the file with a bad font,
* the corresponding slot will be initialized by
* default physical font. In such case findFont2D may
* return composite font which cannot be casted to
* physical font.
*/
try {
components[slot] =
(PhysicalFont) fm.findFont2D(componentNames[slot],
style,
FontManager.PHYSICAL_FALLBACK);
} catch (ClassCastException cce) {
/* Assign default physical font to the slot */
components[slot] = fm.getDefaultPhysicalFont();
}
}
}
deferredInitialisation[slot] = false;
}
}
/* To called only by FontManager.replaceFont */
void replaceComponentFont(PhysicalFont oldFont, PhysicalFont newFont) {
if (components == null) {
return;
}
for (int slot=0; slot<numSlots; slot++) {
if (components[slot] == oldFont) {
components[slot] = newFont;
if (componentNames != null) {
componentNames[slot] = newFont.getFontName(null);
}
}
}
}
public boolean isExcludedChar(int slot, int charcode) {
if (exclusionRanges == null || maxIndices == null ||
slot >= numMetricsSlots) {
return false;
}
int minIndex = 0;
int maxIndex = maxIndices[slot];
if (slot > 0) {
minIndex = maxIndices[slot - 1];
}
int curIndex = minIndex;
while (maxIndex > curIndex) {
if ((charcode >= exclusionRanges[curIndex])
&& (charcode <= exclusionRanges[curIndex+1])) {
return true; // excluded
}
curIndex += 2;
}
return false;
}
public void getStyleMetrics(float pointSize, float[] metrics, int offset) {
PhysicalFont font = getSlotFont(0);
if (font == null) { // possible?
super.getStyleMetrics(pointSize, metrics, offset);
} else {
font.getStyleMetrics(pointSize, metrics, offset);
}
}
public int getNumSlots() {
return numSlots;
}
public PhysicalFont getSlotFont(int slot) {
/* This is essentially the runtime overhead for deferred font
* initialisation: a boolean test on obtaining a slot font,
* which will happen per slot, on initialisation of a strike
* (as that is the only frequent call site of this method.
*/
if (deferredInitialisation[slot]) {
doDeferredInitialisation(slot);
}
SunFontManager fm = SunFontManager.getInstance();
try {
PhysicalFont font = components[slot];
if (font == null) {
try {
font = (PhysicalFont) fm.
findFont2D(componentNames[slot], style,
FontManager.PHYSICAL_FALLBACK);
components[slot] = font;
} catch (ClassCastException cce) {
font = fm.getDefaultPhysicalFont();
}
}
return font;
} catch (Exception e) {
return fm.getDefaultPhysicalFont();
}
}
FontStrike createStrike(FontStrikeDesc desc) {
return new CompositeStrike(this, desc);
}
/* This is set false when the composite is created using a specified
* physical font as the first slot and called by code which
* selects composites by locale preferences to know that this
* isn't a font which should be adjusted.
*/
public boolean isStdComposite() {
return isStdComposite;
}
/* This isn't very efficient but its infrequently used.
* StandardGlyphVector uses it when the client assigns the glyph codes.
* These may not be valid. This validates them substituting the missing
* glyph elsewhere.
*/
protected int getValidatedGlyphCode(int glyphCode) {
int slot = glyphCode >>> 24;
if (slot >= numSlots) {
return getMapper().getMissingGlyphCode();
}
int slotglyphCode = glyphCode & CompositeStrike.SLOTMASK;
PhysicalFont slotFont = getSlotFont(slot);
if (slotFont.getValidatedGlyphCode(slotglyphCode) ==
slotFont.getMissingGlyphCode()) {
return getMapper().getMissingGlyphCode();
} else {
return glyphCode;
}
}
public CharToGlyphMapper getMapper() {
if (mapper == null) {
mapper = new CompositeGlyphMapper(this);
}
return mapper;
}
public boolean hasSupplementaryChars() {
for (int i=0; i<numSlots; i++) {
if (getSlotFont(i).hasSupplementaryChars()) {
return true;
}
}
return false;
}
public int getNumGlyphs() {
if (numGlyphs == 0) {
numGlyphs = getMapper().getNumGlyphs();
}
return numGlyphs;
}
public int getMissingGlyphCode() {
return getMapper().getMissingGlyphCode();
}
public boolean canDisplay(char c) {
return getMapper().canDisplay(c);
}
public boolean useAAForPtSize(int ptsize) {
/* Find the first slot that supports the default encoding and use
* that to decide the "gasp" behaviour of the composite font.
* REMIND "default encoding" isn't applicable to a Unicode locale
* and we need to replace this with a better mechanism for deciding
* if a font "supports" the user's language. See TrueTypeFont.java
*/
if (localeSlot == -1) {
/* Ordinarily check numMetricsSlots, but non-standard composites
* set that to "1" whilst not necessarily supporting the default
* encoding with that first slot. In such a case check all slots.
*/
int numCoreSlots = numMetricsSlots;
if (numCoreSlots == 1 && !isStdComposite()) {
numCoreSlots = numSlots;
}
for (int slot=0; slot<numCoreSlots; slot++) {
if (getSlotFont(slot).supportsEncoding(null)) {
localeSlot = slot;
break;
}
}
if (localeSlot == -1) {
localeSlot = 0;
}
}
return getSlotFont(localeSlot).useAAForPtSize(ptsize);
}
public String toString() {
String ls = (String)java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
String componentsStr = "";
for (int i=0; i<numSlots; i++) {
componentsStr += " Slot["+i+"]="+getSlotFont(i)+ls;
}
return "** Composite Font: Family=" + familyName +
" Name=" + fullName + " style=" + style + ls + componentsStr;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2003, 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;
/**
* Encapsulates the information that 2D needs to create a composite font,
* the runtime representation of a logical font.
*/
public class CompositeFontDescriptor {
private String faceName;
private int coreComponentCount;
private String[] componentFaceNames;
private String[] componentFileNames;
private int[] exclusionRanges;
private int[] exclusionRangeLimits;
/**
* Constructs a composite font descriptor.
* @param faceName the font face name, i.e., the family name suffixed
* with ".plain", ".bold", ".italic", ".bolditalic".
* @param coreComponentCount the number of core fonts, i.e., the ones
* derived from a non-fallback sequence.
* @param componentFaceNames the face names for the component fonts
* @param componentFileNames the file names for the component fonts
* @param exclusionRanges an array holding lower and upper boundaries
* for all exclusion ranges for all component fonts
* @param exclusionRangeLimits an array holding the limits of the
* sections for each component font within the previous
* array
*/
public CompositeFontDescriptor(String faceName,
int coreComponentCount,
String[] componentFaceNames,
String[] componentFileNames,
int[] exclusionRanges,
int[] exclusionRangeLimits) {
this.faceName = faceName;
this.coreComponentCount = coreComponentCount;
this.componentFaceNames = componentFaceNames;
this.componentFileNames = componentFileNames;
this.exclusionRanges = exclusionRanges;
this.exclusionRangeLimits = exclusionRangeLimits;
}
public String getFaceName() {
return faceName;
}
public int getCoreComponentCount() {
return coreComponentCount;
}
public String[] getComponentFaceNames() {
return componentFaceNames;
}
public String[] getComponentFileNames() {
return componentFileNames;
}
public int[] getExclusionRanges() {
return exclusionRanges;
}
public int[] getExclusionRangeLimits() {
return exclusionRangeLimits;
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2003, 2006, 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;
/* remember that the API requires a Font use a
* consistent glyph id. for a code point, and this is a
* problem if a particular strike uses native scaler sometimes
* and T2K others. That needs to be dealt with somewhere, but
* here we can just always get the same glyph code without
* needing a strike.
*
* The C implementation would cache the results of anything up
* to the maximum surrogate pair code point.
* This implementation will not cache as much, since the storage
* requirements are not justifiable. Even so it still can use up
* to 216*256*4 bytes of storage per composite font. If an app
* calls canDisplay on this range for all 20 composite fonts that's
* over 1Mb of cached data. May need to employ WeakReferences if
* this appears to cause problems.
*/
public class CompositeGlyphMapper extends CharToGlyphMapper {
public static final int SLOTMASK = 0xff000000;
public static final int GLYPHMASK = 0x00ffffff;
public static final int NBLOCKS = 216;
public static final int BLOCKSZ = 256;
public static final int MAXUNICODE = NBLOCKS*BLOCKSZ;
CompositeFont font;
CharToGlyphMapper slotMappers[];
int[][] glyphMaps;
private boolean hasExcludes;
public CompositeGlyphMapper(CompositeFont compFont) {
font = compFont;
initMapper();
/* This is often false which saves the overhead of a
* per-mapped char method call.
*/
hasExcludes = compFont.exclusionRanges != null &&
compFont.maxIndices != null;
}
public final int compositeGlyphCode(int slot, int glyphCode) {
return (slot << 24 | (glyphCode & GLYPHMASK));
}
private final void initMapper() {
if (missingGlyph == CharToGlyphMapper.UNINITIALIZED_GLYPH) {
if (glyphMaps == null) {
glyphMaps = new int[NBLOCKS][];
}
slotMappers = new CharToGlyphMapper[font.numSlots];
/* This requires that slot 0 is never empty. */
missingGlyph = font.getSlotFont(0).getMissingGlyphCode();
missingGlyph = compositeGlyphCode(0, missingGlyph);
}
}
private int getCachedGlyphCode(int unicode) {
if (unicode >= MAXUNICODE) {
return UNINITIALIZED_GLYPH; // don't cache surrogates
}
int[] gmap;
if ((gmap = glyphMaps[unicode >> 8]) == null) {
return UNINITIALIZED_GLYPH;
}
return gmap[unicode & 0xff];
}
private void setCachedGlyphCode(int unicode, int glyphCode) {
if (unicode >= MAXUNICODE) {
return; // don't cache surrogates
}
int index0 = unicode >> 8;
if (glyphMaps[index0] == null) {
glyphMaps[index0] = new int[BLOCKSZ];
for (int i=0;i<BLOCKSZ;i++) {
glyphMaps[index0][i] = UNINITIALIZED_GLYPH;
}
}
glyphMaps[index0][unicode & 0xff] = glyphCode;
}
private final CharToGlyphMapper getSlotMapper(int slot) {
CharToGlyphMapper mapper = slotMappers[slot];
if (mapper == null) {
mapper = font.getSlotFont(slot).getMapper();
slotMappers[slot] = mapper;
}
return mapper;
}
private final int convertToGlyph(int unicode) {
for (int slot = 0; slot < font.numSlots; slot++) {
if (!hasExcludes || !font.isExcludedChar(slot, unicode)) {
CharToGlyphMapper mapper = getSlotMapper(slot);
int glyphCode = mapper.charToGlyph(unicode);
if (glyphCode != mapper.getMissingGlyphCode()) {
glyphCode = compositeGlyphCode(slot, glyphCode);
setCachedGlyphCode(unicode, glyphCode);
return glyphCode;
}
}
}
return missingGlyph;
}
public int getNumGlyphs() {
int numGlyphs = 0;
/* The number of glyphs in a composite is affected by
* exclusion ranges and duplicates (ie the same code point is
* mapped by two different fonts) and also whether or not to
* count fallback fonts. A nearly correct answer would be very
* expensive to generate. A rough ballpark answer would
* just count the glyphs in all the slots. However this would
* initialize mappers for all slots when they aren't necessarily
* needed. For now just use the first slot as JDK 1.4 did.
*/
for (int slot=0; slot<1 /*font.numSlots*/; slot++) {
CharToGlyphMapper mapper = slotMappers[slot];
if (mapper == null) {
mapper = font.getSlotFont(slot).getMapper();
slotMappers[slot] = mapper;
}
numGlyphs += mapper.getNumGlyphs();
}
return numGlyphs;
}
public int charToGlyph(int unicode) {
int glyphCode = getCachedGlyphCode(unicode);
if (glyphCode == UNINITIALIZED_GLYPH) {
glyphCode = convertToGlyph(unicode);
}
return glyphCode;
}
public int charToGlyph(int unicode, int prefSlot) {
if (prefSlot >= 0) {
CharToGlyphMapper mapper = getSlotMapper(prefSlot);
int glyphCode = mapper.charToGlyph(unicode);
if (glyphCode != mapper.getMissingGlyphCode()) {
return compositeGlyphCode(prefSlot, glyphCode);
}
}
return charToGlyph(unicode);
}
public int charToGlyph(char unicode) {
int glyphCode = getCachedGlyphCode(unicode);
if (glyphCode == UNINITIALIZED_GLYPH) {
glyphCode = convertToGlyph(unicode);
}
return glyphCode;
}
/* This variant checks if shaping is needed and immediately
* returns true if it does. A caller of this method should be expecting
* to check the return type because it needs to know how to handle
* the character data for display.
*/
public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code = unicodes[i]; // char is unsigned.
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
glyphs[i + 1] = INVISIBLE_GLYPH_ID;
}
}
int gc = glyphs[i] = getCachedGlyphCode(code);
if (gc == UNINITIALIZED_GLYPH) {
glyphs[i] = convertToGlyph(code);
}
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
continue;
}
else if (FontUtilities.isComplexCharCode(code)) {
return true;
}
else if (code >= 0x10000) {
i += 1; // Empty glyph slot after surrogate
continue;
}
}
return false;
}
/* The conversion is not very efficient - looping as it does, converting
* one char at a time. However the cache should fill very rapidly.
*/
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code = unicodes[i]; // char is unsigned.
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
int gc = glyphs[i] = getCachedGlyphCode(code);
if (gc == UNINITIALIZED_GLYPH) {
glyphs[i] = convertToGlyph(code);
}
i += 1; // Empty glyph slot after surrogate
glyphs[i] = INVISIBLE_GLYPH_ID;
continue;
}
}
int gc = glyphs[i] = getCachedGlyphCode(code);
if (gc == UNINITIALIZED_GLYPH) {
glyphs[i] = convertToGlyph(code);
}
}
}
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code = unicodes[i];
glyphs[i] = getCachedGlyphCode(code);
if (glyphs[i] == UNINITIALIZED_GLYPH) {
glyphs[i] = convertToGlyph(code);
}
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* Copyright (c) 2003, 2005, 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.Font;
import java.awt.Rectangle;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/*
* performance:
* it seems expensive that when using a composite font for
* every char you have to find which "slot" can display it.
* Just the fact that you need to check at all ..
* A composite glyph code ducks this by encoding the slot into the
* glyph code, but you still need to get from char to glyph code.
*/
public final class CompositeStrike extends FontStrike {
static final int SLOTMASK = 0xffffff;
private CompositeFont compFont;
private PhysicalStrike[] strikes;
int numGlyphs = 0;
CompositeStrike(CompositeFont font2D, FontStrikeDesc desc) {
this.compFont = font2D;
this.desc = desc;
this.disposer = new FontStrikeDisposer(compFont, desc);
if (desc.style != compFont.style) {
algoStyle = true;
if ((desc.style & Font.BOLD) == Font.BOLD &&
((compFont.style & Font.BOLD) == 0)) {
boldness = 1.33f;
}
if ((desc.style & Font.ITALIC) == Font.ITALIC &&
(compFont.style & Font.ITALIC) == 0) {
italic = 0.7f;
}
}
strikes = new PhysicalStrike[compFont.numSlots];
}
/* do I need this (see Strike::compositeStrikeForGlyph) */
PhysicalStrike getStrikeForGlyph(int glyphCode) {
return getStrikeForSlot(glyphCode >>> 24);
}
PhysicalStrike getStrikeForSlot(int slot) {
if (slot >= strikes.length) {
slot = 0;
}
PhysicalStrike strike = strikes[slot];
if (strike == null) {
strike =
(PhysicalStrike)(compFont.getSlotFont(slot).getStrike(desc));
strikes[slot] = strike;
}
return strike;
}
public int getNumGlyphs() {
return compFont.getNumGlyphs();
}
StrikeMetrics getFontMetrics() {
if (strikeMetrics == null) {
StrikeMetrics compMetrics = new StrikeMetrics();
for (int s=0; s<compFont.numMetricsSlots; s++) {
compMetrics.merge(getStrikeForSlot(s).getFontMetrics());
}
strikeMetrics = compMetrics;
}
return strikeMetrics;
}
/* Performance tweak: Slot 0 can often return all the glyphs
* Note slot zero doesn't need to be masked.
* Could go a step further and support getting a run of glyphs.
* This would help many locales a little.
*
* Note that if a client constructs an invalid a composite glyph that
* references an invalid slot, that the behaviour is currently
* that this slot index falls through to CompositeFont.getSlotFont(int)
* which will substitute a default font, from which to obtain the
* strike. If its an invalid glyph code for a valid slot, then the
* physical font for that slot will substitute the missing glyph.
*/
void getGlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
PhysicalStrike strike = getStrikeForSlot(0);
int numptrs = strike.getSlot0GlyphImagePtrs(glyphCodes, images, len);
if (numptrs == len) {
return;
}
for (int i=numptrs; i< len; i++) {
strike = getStrikeForGlyph(glyphCodes[i]);
images[i] = strike.getGlyphImagePtr(glyphCodes[i] & SLOTMASK);
}
}
long getGlyphImagePtr(int glyphCode) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
return strike.getGlyphImagePtr(glyphCode & SLOTMASK);
}
void getGlyphImageBounds(int glyphCode, Point2D.Float pt, Rectangle result) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
strike.getGlyphImageBounds(glyphCode & SLOTMASK, pt, result);
}
Point2D.Float getGlyphMetrics(int glyphCode) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
return strike.getGlyphMetrics(glyphCode & SLOTMASK);
}
Point2D.Float getCharMetrics(char ch) {
return getGlyphMetrics(compFont.getMapper().charToGlyph(ch));
}
float getGlyphAdvance(int glyphCode) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
return strike.getGlyphAdvance(glyphCode & SLOTMASK);
}
/* REMIND where to cache?
* The glyph advance is already cached by physical strikes and that's a lot
* of the work.
* Also FontDesignMetrics maintains a latin char advance cache, so don't
* cache advances here as apps tend to hold onto metrics objects when
* performance is sensitive to it. Revisit this assumption later.
*/
float getCodePointAdvance(int cp) {
return getGlyphAdvance(compFont.getMapper().charToGlyph(cp));
}
Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
return strike.getGlyphOutlineBounds(glyphCode & SLOTMASK);
}
GeneralPath getGlyphOutline(int glyphCode, float x, float y) {
PhysicalStrike strike = getStrikeForGlyph(glyphCode);
GeneralPath path = strike.getGlyphOutline(glyphCode & SLOTMASK, x, y);
if (path == null) {
return new GeneralPath();
} else {
return path;
}
}
/* The physical font slot for each glyph is encoded in the glyph ID
* To be as efficient as possible we find a run of glyphs from the
* same slot and create a temporary array of these glyphs decoded
* to the slot. The slot font is then queried for the GeneralPath
* for that run of glyphs. GeneralPaths from each run are appended
* to create the shape for the whole glyph array.
*/
GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
GeneralPath path = null;
GeneralPath gp;
int glyphIndex = 0;
int[] tmpGlyphs;
while (glyphIndex < glyphs.length) {
int start = glyphIndex;
int slot = glyphs[glyphIndex] >>> 24;
while (glyphIndex < glyphs.length &&
(glyphs[glyphIndex+1] >>> 24) == slot) {
glyphIndex++;
}
int tmpLen = glyphIndex-start+1;
tmpGlyphs = new int[tmpLen];
for (int i=0;i<tmpLen;i++) {
tmpGlyphs[i] = glyphs[i] & SLOTMASK;
}
gp = getStrikeForSlot(slot).getGlyphVectorOutline(tmpGlyphs, x, y);
if (path == null) {
path = gp;
} else if (gp != null) {
path.append(gp, false);
}
}
if (path == null) {
return new GeneralPath();
} else {
return path;
}
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003, All Rights Reserved
*
*/
package sun.font;
import java.awt.font.LineMetrics;
import java.awt.font.GraphicAttribute;
public final class CoreMetrics {
public CoreMetrics(float ascent,
float descent,
float leading,
float height,
int baselineIndex,
float[] baselineOffsets,
float strikethroughOffset,
float strikethroughThickness,
float underlineOffset,
float underlineThickness,
float ssOffset,
float italicAngle) {
this.ascent = ascent;
this.descent = descent;
this.leading = leading;
this.height = height;
this.baselineIndex = baselineIndex;
this.baselineOffsets = baselineOffsets;
this.strikethroughOffset = strikethroughOffset;
this.strikethroughThickness = strikethroughThickness;
this.underlineOffset = underlineOffset;
this.underlineThickness = underlineThickness;
this.ssOffset = ssOffset;
this.italicAngle = italicAngle;
}
public static CoreMetrics get(LineMetrics lm) {
return ((FontLineMetrics)lm).cm;
}
public final int hashCode() {
return Float.floatToIntBits(ascent + ssOffset);
}
public final boolean equals(Object rhs) {
try {
return equals((CoreMetrics)rhs);
}
catch(ClassCastException e) {
return false;
}
}
public final boolean equals(CoreMetrics rhs) {
if (rhs != null) {
if (this == rhs) {
return true;
}
return ascent == rhs.ascent
&& descent == rhs.descent
&& leading == rhs.leading
&& baselineIndex == rhs.baselineIndex
&& baselineOffsets[0] == rhs.baselineOffsets[0]
&& baselineOffsets[1] == rhs.baselineOffsets[1]
&& baselineOffsets[2] == rhs.baselineOffsets[2]
&& strikethroughOffset == rhs.strikethroughOffset
&& strikethroughThickness == rhs.strikethroughThickness
&& underlineOffset == rhs.underlineOffset
&& underlineThickness == rhs.underlineThickness
&& ssOffset == rhs.ssOffset
&& italicAngle == rhs.italicAngle;
}
return false;
}
// fullOffsets is an array of 5 baseline offsets,
// roman, center, hanging, bottom, and top in that order
// this does NOT add the ssOffset
public final float effectiveBaselineOffset(float[] fullOffsets) {
switch (baselineIndex) {
case GraphicAttribute.TOP_ALIGNMENT:
return fullOffsets[4] + ascent;
case GraphicAttribute.BOTTOM_ALIGNMENT:
return fullOffsets[3] - descent;
default:
return fullOffsets[baselineIndex];
}
}
public final float ascent;
public final float descent;
public final float leading;
public final float height;
public final int baselineIndex;
public final float[] baselineOffsets; // !! this is a hole, don't expose this class
public final float strikethroughOffset;
public final float strikethroughThickness;
public final float underlineOffset;
public final float underlineThickness;
public final float ssOffset;
public final float italicAngle;
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) 2008, 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.io.File;
import java.io.OutputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import sun.awt.AppContext;
import sun.misc.ThreadGroupUtils;
public class CreatedFontTracker {
public static final int MAX_FILE_SIZE = 32 * 1024 * 1024;
public static final int MAX_TOTAL_BYTES = 10 * MAX_FILE_SIZE;
static CreatedFontTracker tracker;
int numBytes;
public static synchronized CreatedFontTracker getTracker() {
if (tracker == null) {
tracker = new CreatedFontTracker();
}
return tracker;
}
private CreatedFontTracker() {
numBytes = 0;
}
public synchronized int getNumBytes() {
return numBytes;
}
public synchronized void addBytes(int sz) {
numBytes += sz;
}
public synchronized void subBytes(int sz) {
numBytes -= sz;
}
/**
* Returns an AppContext-specific counting semaphore.
*/
private static synchronized Semaphore getCS() {
final AppContext appContext = AppContext.getAppContext();
Semaphore cs = (Semaphore) appContext.get(CreatedFontTracker.class);
if (cs == null) {
// Make a semaphore with 5 permits that obeys the first-in first-out
// granting of permits.
cs = new Semaphore(5, true);
appContext.put(CreatedFontTracker.class, cs);
}
return cs;
}
public boolean acquirePermit() throws InterruptedException {
// This does a timed-out wait.
return getCS().tryAcquire(120, TimeUnit.SECONDS);
}
public void releasePermit() {
getCS().release();
}
public void add(File file) {
TempFileDeletionHook.add(file);
}
public void set(File file, OutputStream os) {
TempFileDeletionHook.set(file, os);
}
public void remove(File file) {
TempFileDeletionHook.remove(file);
}
/**
* Helper class for cleanup of temp files created while processing fonts.
* Note that this only applies to createFont() from an InputStream object.
*/
private static class TempFileDeletionHook {
private static HashMap<File, OutputStream> files = new HashMap<>();
private static Thread t = null;
static void init() {
if (t == null) {
// Add a shutdown hook to remove the temp file.
AccessController.doPrivileged(
(PrivilegedAction<Void>) () -> {
/* The thread must be a member of a thread group
* which will not get GCed before VM exit.
* Make its parent the top-level thread group.
*/
ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup();
t = new Thread(rootTG, TempFileDeletionHook::runHooks);
t.setContextClassLoader(null);
Runtime.getRuntime().addShutdownHook(t);
return null;
});
}
}
private TempFileDeletionHook() {}
static synchronized void add(File file) {
init();
files.put(file, null);
}
static synchronized void set(File file, OutputStream os) {
files.put(file, os);
}
static synchronized void remove(File file) {
files.remove(file);
}
static synchronized void runHooks() {
if (files.isEmpty()) {
return;
}
for (Map.Entry<File, OutputStream> entry : files.entrySet()) {
// Close the associated output stream, and then delete the file.
try {
if (entry.getValue() != null) {
entry.getValue().close();
}
} catch (Exception e) {}
entry.getKey().delete();
}
}
}
}

View File

@@ -0,0 +1,443 @@
/*
* 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.
*
*/
/*
* (C) Copyright IBM Corp. 1999-2003, All Rights Reserved
*
*/
package sun.font;
import java.util.Map;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.TextAttribute;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;
import static sun.font.AttributeValues.*;
import static sun.font.EAttribute.*;
/**
* This class handles underlining, strikethrough, and foreground and
* background styles on text. Clients simply acquire instances
* of this class and hand them off to ExtendedTextLabels or GraphicComponents.
*/
public class Decoration {
/**
* This interface is implemented by clients that use Decoration.
* Unfortunately, interface methods have to public; ideally these
* would be package-private.
*/
public interface Label {
CoreMetrics getCoreMetrics();
Rectangle2D getLogicalBounds();
void handleDraw(Graphics2D g2d, float x, float y);
Rectangle2D handleGetCharVisualBounds(int index);
Rectangle2D handleGetVisualBounds();
Shape handleGetOutline(float x, float y);
}
private Decoration() {
}
/**
* Return a Decoration which does nothing.
*/
public static Decoration getPlainDecoration() {
return PLAIN;
}
private static final int VALUES_MASK =
AttributeValues.getMask(EFOREGROUND, EBACKGROUND, ESWAP_COLORS,
ESTRIKETHROUGH, EUNDERLINE, EINPUT_METHOD_HIGHLIGHT,
EINPUT_METHOD_UNDERLINE);
public static Decoration getDecoration(AttributeValues values) {
if (values == null || !values.anyDefined(VALUES_MASK)) {
return PLAIN;
}
values = values.applyIMHighlight();
return new DecorationImpl(values.getForeground(),
values.getBackground(),
values.getSwapColors(),
values.getStrikethrough(),
Underline.getUnderline(values.getUnderline()),
Underline.getUnderline(values.getInputMethodUnderline()));
}
/**
* Return a Decoration appropriate for the the given Map.
* @param attributes the Map used to determine the Decoration
*/
public static Decoration getDecoration(Map attributes) {
if (attributes == null) {
return PLAIN;
}
return getDecoration(AttributeValues.fromMap(attributes));
}
public void drawTextAndDecorations(Label label,
Graphics2D g2d,
float x,
float y) {
label.handleDraw(g2d, x, y);
}
public Rectangle2D getVisualBounds(Label label) {
return label.handleGetVisualBounds();
}
public Rectangle2D getCharVisualBounds(Label label, int index) {
return label.handleGetCharVisualBounds(index);
}
Shape getOutline(Label label,
float x,
float y) {
return label.handleGetOutline(x, y);
}
private static final Decoration PLAIN = new Decoration();
private static final class DecorationImpl extends Decoration {
private Paint fgPaint = null;
private Paint bgPaint = null;
private boolean swapColors = false;
private boolean strikethrough = false;
private Underline stdUnderline = null; // underline from TextAttribute.UNDERLINE_ON
private Underline imUnderline = null; // input method underline
DecorationImpl(Paint foreground,
Paint background,
boolean swapColors,
boolean strikethrough,
Underline stdUnderline,
Underline imUnderline) {
fgPaint = (Paint) foreground;
bgPaint = (Paint) background;
this.swapColors = swapColors;
this.strikethrough = strikethrough;
this.stdUnderline = stdUnderline;
this.imUnderline = imUnderline;
}
private static boolean areEqual(Object lhs, Object rhs) {
if (lhs == null) {
return rhs == null;
}
else {
return lhs.equals(rhs);
}
}
public boolean equals(Object rhs) {
if (rhs == this) {
return true;
}
if (rhs == null) {
return false;
}
DecorationImpl other = null;
try {
other = (DecorationImpl) rhs;
}
catch(ClassCastException e) {
return false;
}
if (!(swapColors == other.swapColors &&
strikethrough == other.strikethrough)) {
return false;
}
if (!areEqual(stdUnderline, other.stdUnderline)) {
return false;
}
if (!areEqual(fgPaint, other.fgPaint)) {
return false;
}
if (!areEqual(bgPaint, other.bgPaint)) {
return false;
}
return areEqual(imUnderline, other.imUnderline);
}
public int hashCode() {
int hc = 1;
if (strikethrough) {
hc |= 2;
}
if (swapColors) {
hc |= 4;
}
if (stdUnderline != null) {
hc += stdUnderline.hashCode();
}
return hc;
}
/**
* Return the bottom of the Rectangle which encloses pixels
* drawn by underlines.
*/
private float getUnderlineMaxY(CoreMetrics cm) {
float maxY = 0;
if (stdUnderline != null) {
float ulBottom = cm.underlineOffset;
ulBottom += stdUnderline.getLowerDrawLimit(cm.underlineThickness);
maxY = Math.max(maxY, ulBottom);
}
if (imUnderline != null) {
float ulBottom = cm.underlineOffset;
ulBottom += imUnderline.getLowerDrawLimit(cm.underlineThickness);
maxY = Math.max(maxY, ulBottom);
}
return maxY;
}
private void drawTextAndEmbellishments(Label label,
Graphics2D g2d,
float x,
float y) {
label.handleDraw(g2d, x, y);
if (!strikethrough && stdUnderline == null && imUnderline == null) {
return;
}
float x1 = x;
float x2 = x1 + (float)label.getLogicalBounds().getWidth();
CoreMetrics cm = label.getCoreMetrics();
if (strikethrough) {
Stroke savedStroke = g2d.getStroke();
g2d.setStroke(new BasicStroke(cm.strikethroughThickness,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER));
float strikeY = y + cm.strikethroughOffset;
g2d.draw(new Line2D.Float(x1, strikeY, x2, strikeY));
g2d.setStroke(savedStroke);
}
float ulOffset = cm.underlineOffset;
float ulThickness = cm.underlineThickness;
if (stdUnderline != null) {
stdUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset);
}
if (imUnderline != null) {
imUnderline.drawUnderline(g2d, ulThickness, x1, x2, y + ulOffset);
}
}
public void drawTextAndDecorations(Label label,
Graphics2D g2d,
float x,
float y) {
if (fgPaint == null && bgPaint == null && swapColors == false) {
drawTextAndEmbellishments(label, g2d, x, y);
}
else {
Paint savedPaint = g2d.getPaint();
Paint foreground, background;
if (swapColors) {
background = fgPaint==null? savedPaint : fgPaint;
if (bgPaint == null) {
if (background instanceof Color) {
Color bg = (Color)background;
// 30/59/11 is standard weights, tweaked a bit
int brightness = 33 * bg.getRed()
+ 53 * bg.getGreen()
+ 14 * bg.getBlue();
foreground = brightness > 18500 ? Color.BLACK : Color.WHITE;
} else {
foreground = Color.WHITE;
}
} else {
foreground = bgPaint;
}
}
else {
foreground = fgPaint==null? savedPaint : fgPaint;
background = bgPaint;
}
if (background != null) {
Rectangle2D bgArea = label.getLogicalBounds();
bgArea = new Rectangle2D.Float(x + (float)bgArea.getX(),
y + (float)bgArea.getY(),
(float)bgArea.getWidth(),
(float)bgArea.getHeight());
g2d.setPaint(background);
g2d.fill(bgArea);
}
g2d.setPaint(foreground);
drawTextAndEmbellishments(label, g2d, x, y);
g2d.setPaint(savedPaint);
}
}
public Rectangle2D getVisualBounds(Label label) {
Rectangle2D visBounds = label.handleGetVisualBounds();
if (swapColors || bgPaint != null || strikethrough
|| stdUnderline != null || imUnderline != null) {
float minX = 0;
Rectangle2D lb = label.getLogicalBounds();
float minY = 0, maxY = 0;
if (swapColors || bgPaint != null) {
minY = (float)lb.getY();
maxY = minY + (float)lb.getHeight();
}
maxY = Math.max(maxY, getUnderlineMaxY(label.getCoreMetrics()));
Rectangle2D ab = new Rectangle2D.Float(minX, minY, (float)lb.getWidth(), maxY-minY);
visBounds.add(ab);
}
return visBounds;
}
Shape getOutline(Label label,
float x,
float y) {
if (!strikethrough && stdUnderline == null && imUnderline == null) {
return label.handleGetOutline(x, y);
}
CoreMetrics cm = label.getCoreMetrics();
// NOTE: The performace of the following code may
// be very poor.
float ulThickness = cm.underlineThickness;
float ulOffset = cm.underlineOffset;
Rectangle2D lb = label.getLogicalBounds();
float x1 = x;
float x2 = x1 + (float)lb.getWidth();
Area area = null;
if (stdUnderline != null) {
Shape ul = stdUnderline.getUnderlineShape(ulThickness,
x1, x2, y+ulOffset);
area = new Area(ul);
}
if (strikethrough) {
Stroke stStroke = new BasicStroke(cm.strikethroughThickness,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
float shiftY = y + cm.strikethroughOffset;
Line2D line = new Line2D.Float(x1, shiftY, x2, shiftY);
Area slArea = new Area(stStroke.createStrokedShape(line));
if(area == null) {
area = slArea;
} else {
area.add(slArea);
}
}
if (imUnderline != null) {
Shape ul = imUnderline.getUnderlineShape(ulThickness,
x1, x2, y+ulOffset);
Area ulArea = new Area(ul);
if (area == null) {
area = ulArea;
}
else {
area.add(ulArea);
}
}
// area won't be null here, since at least one underline exists.
area.add(new Area(label.handleGetOutline(x, y)));
return new GeneralPath(area);
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(super.toString());
buf.append("[");
if (fgPaint != null) buf.append("fgPaint: " + fgPaint);
if (bgPaint != null) buf.append(" bgPaint: " + bgPaint);
if (swapColors) buf.append(" swapColors: true");
if (strikethrough) buf.append(" strikethrough: true");
if (stdUnderline != null) buf.append(" stdUnderline: " + stdUnderline);
if (imUnderline != null) buf.append(" imUnderline: " + imUnderline);
buf.append("]");
return buf.toString();
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2004, 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.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/**
* To avoid people downcasting Shape to a known mutable subclass and
* mucking with its internals, we need to interpose a subclass that
* cannot be mutated or downcasted.
*/
public final class DelegatingShape implements Shape {
Shape delegate;
public DelegatingShape(Shape delegate) {
this.delegate = delegate;
}
public Rectangle getBounds() {
return delegate.getBounds(); // assumes all delegates are immutable via the returned Rectangle
}
public Rectangle2D getBounds2D() {
return delegate.getBounds2D(); // assumes all delegates are immutable via the returned Rectangle2D
}
public boolean contains(double x, double y) {
return delegate.contains(x, y);
}
public boolean contains(Point2D p) {
return delegate.contains(p);
}
public boolean intersects(double x, double y, double w, double h) {
return delegate.intersects(x, y, w, h);
}
public boolean intersects(Rectangle2D r) {
return delegate.intersects(r);
}
public boolean contains(double x, double y, double w, double h) {
return delegate.contains(x, y, w, h);
}
public boolean contains(Rectangle2D r) {
return delegate.contains(r);
}
public PathIterator getPathIterator(AffineTransform at) {
return delegate.getPathIterator(at);
}
public PathIterator getPathIterator(AffineTransform at, double flatness) {
return delegate.getPathIterator(at, flatness);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2005, 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.
*/
/*
*
* (C) Copyright IBM Corp. 2005 - All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by IBM. These materials are provided
* under terms of a License Agreement between IBM and Sun.
* This technology is protected by multiple US and International
* patents. This notice and attribution to IBM may not be removed.
*/
package sun.font;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator.Attribute;
import static java.awt.font.TextAttribute.*;
public enum EAttribute {
EFAMILY(FAMILY),
EWEIGHT(WEIGHT),
EWIDTH(WIDTH),
EPOSTURE(POSTURE),
ESIZE(SIZE),
ETRANSFORM(TRANSFORM),
ESUPERSCRIPT(SUPERSCRIPT),
EFONT(FONT),
ECHAR_REPLACEMENT(CHAR_REPLACEMENT),
EFOREGROUND(FOREGROUND),
EBACKGROUND(BACKGROUND),
EUNDERLINE(UNDERLINE),
ESTRIKETHROUGH(STRIKETHROUGH),
ERUN_DIRECTION(RUN_DIRECTION),
EBIDI_EMBEDDING(BIDI_EMBEDDING),
EJUSTIFICATION(JUSTIFICATION),
EINPUT_METHOD_HIGHLIGHT(INPUT_METHOD_HIGHLIGHT),
EINPUT_METHOD_UNDERLINE(INPUT_METHOD_UNDERLINE),
ESWAP_COLORS(SWAP_COLORS),
ENUMERIC_SHAPING(NUMERIC_SHAPING),
EKERNING(KERNING),
ELIGATURES(LIGATURES),
ETRACKING(TRACKING),
EBASELINE_TRANSFORM(null);
/* package */ final int mask;
/* package */ final TextAttribute att;
EAttribute(TextAttribute ta) {
mask = 1 << ordinal();
att = ta;
}
/* package */ static final EAttribute[] atts = EAttribute.class.getEnumConstants();
public static EAttribute forAttribute(Attribute ta) {
for (EAttribute ea: atts) {
if (ea.att == ta) {
return ea;
}
}
return null;
}
public String toString() {
return name().substring(1).toLowerCase();
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 1998, 2003, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998-2003- All Rights Reserved.
*/
package sun.font;
import java.awt.Font;
import java.awt.font.GlyphJustificationInfo;
import java.awt.font.LineMetrics;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/**
* An extension of TextLabel that maintains information
* about characters.
*/
public abstract class ExtendedTextLabel extends TextLabel
implements TextLineComponent{
/**
* Return the number of characters represented by this label.
*/
public abstract int getNumCharacters();
/**
* Return the line metrics for all text in this label.
*/
public abstract CoreMetrics getCoreMetrics();
/**
* Return the x location of the character at the given logical index.
*/
public abstract float getCharX(int logicalIndex);
/**
* Return the y location of the character at the given logical index.
*/
public abstract float getCharY(int logicalIndex);
/**
* Return the advance of the character at the given logical index.
*/
public abstract float getCharAdvance(int logicalIndex);
/**
* Return the visual bounds of the character at the given logical index.
* This bounds encloses all the pixels of the character when the label is rendered
* at x, y.
*/
public abstract Rectangle2D getCharVisualBounds(int logicalIndex, float x, float y);
/**
* Return the visual index of the character at the given logical index.
*/
public abstract int logicalToVisual(int logicalIndex);
/**
* Return the logical index of the character at the given visual index.
*/
public abstract int visualToLogical(int visualIndex);
/**
* Return the logical index of the character, starting with the character at
* logicalStart, whose accumulated advance exceeds width. If the advances of
* all characters do not exceed width, return getNumCharacters. If width is
* less than zero, return logicalStart - 1.
*/
public abstract int getLineBreakIndex(int logicalStart, float width);
/**
* Return the accumulated advances of all characters between logicalStart and
* logicalLimit.
*/
public abstract float getAdvanceBetween(int logicalStart, int logicalLimit);
/**
* Return whether a caret can exist on the leading edge of the
* character at offset. If the character is part of a ligature
* (for example) a caret may not be appropriate at offset.
*/
public abstract boolean caretAtOffsetIsValid(int offset);
/**
* A convenience overload of getCharVisualBounds that defaults the label origin
* to 0, 0.
*/
public Rectangle2D getCharVisualBounds(int logicalIndex) {
return getCharVisualBounds(logicalIndex, 0, 0);
}
public abstract TextLineComponent getSubset(int start, int limit, int dir);
/**
* Return the number of justification records this uses.
*/
public abstract int getNumJustificationInfos();
/**
* Return GlyphJustificationInfo objects for the characters between
* charStart and charLimit, starting at offset infoStart. Infos
* will be in visual order. All positions between infoStart and
* getNumJustificationInfos will be set. If a position corresponds
* to a character outside the provided range, it is set to null.
*/
public abstract void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit);
/**
* Apply deltas to the data in this component, starting at offset
* deltaStart, and return the new component. There are two floats
* for each justification info, for a total of 2 * getNumJustificationInfos.
* The first delta is the left adjustment, the second is the right
* adjustment.
* <p>
* If flags[0] is true on entry, rejustification is allowed. If
* the new component requires rejustification (ligatures were
* formed or split), flags[0] will be set on exit.
*/
public abstract TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,334 @@
/*
* Copyright (c) 2003, 2012, 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.Reference;
import java.awt.FontFormatException;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.nio.ByteBuffer;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
public abstract class FileFont extends PhysicalFont {
protected boolean useJavaRasterizer = true;
/* I/O and file operations are always synchronized on the font
* object. Two threads can be accessing the font and retrieving
* information, and synchronized only to the extent that filesystem
* operations require.
* A limited number of files can be open at a time, to limit the
* absorption of file descriptors. If a file needs to be opened
* when there are none free, then the synchronization of all I/O
* ensures that any in progress operation will complete before some
* other thread closes the descriptor in order to allocate another one.
*/
// NB consider using a RAF. FIS has finalize method so may take a
// little longer to be GC'd. We don't use this stream at all anyway.
// In fact why increase the size of a FileFont object if the stream
// isn't needed ..
//protected FileInputStream stream;
//protected FileChannel channel;
protected int fileSize;
protected FontScaler scaler;
/* The following variables are used, (and in the case of the arrays,
* only initialised) for select fonts where a native scaler may be
* used to get glyph images and metrics.
* glyphToCharMap is filled in on the fly and used to do a reverse
* lookup when a FileFont needs to get the charcode back from a glyph
* code so it can re-map via a NativeGlyphMapper to get a native glyph.
* This isn't a big hit in time, since a boolean test is sufficient
* to choose the usual default path, nor in memory for fonts which take
* the native path, since fonts have contiguous zero-based glyph indexes,
* and these obviously do all exist in the font.
*/
protected boolean checkedNatives;
protected boolean useNatives;
protected NativeFont[] nativeFonts;
protected char[] glyphToCharMap;
/*
* @throws FontFormatException - if the font can't be opened
*/
FileFont(String platname, Object nativeNames)
throws FontFormatException {
super(platname, nativeNames);
}
FontStrike createStrike(FontStrikeDesc desc) {
if (!checkedNatives) {
checkUseNatives();
}
return new FileFontStrike(this, desc);
}
protected boolean checkUseNatives() {
checkedNatives = true;
return useNatives;
}
/* This method needs to be accessible to FontManager if there is
* file pool management. It may be a no-op.
*/
protected abstract void close();
/*
* This is the public interface. The subclasses need to implement
* this. The returned block may be longer than the requested length.
*/
abstract ByteBuffer readBlock(int offset, int length);
public boolean canDoStyle(int style) {
return true;
}
void setFileToRemove(File file, CreatedFontTracker tracker) {
Disposer.addObjectRecord(this,
new CreatedFontFileDisposerRecord(file, tracker));
}
// MACOSX begin -- Make this static so that we can pass in CFont
static void setFileToRemove(Object font, File file, CreatedFontTracker tracker) {
Disposer.addObjectRecord(font,
new CreatedFontFileDisposerRecord(file, tracker));
}
// MACOSX - end
/* This is called when a font scaler is determined to
* be unusable (ie bad).
* We want to replace current scaler with NullFontScaler, so
* we never try to use same font scaler again.
* Scaler native resources could have already been disposed
* or they will be eventually by Java2D disposer.
* However, it should be safe to call dispose() explicitly here.
*
* For safety we also invalidate all strike's scaler context.
* So, in case they cache pointer to native scaler
* it will not ever be used.
*
* It also appears desirable to remove all the entries from the
* cache so no other code will pick them up. But we can't just
* 'delete' them as code may be using them. And simply dropping
* the reference to the cache will make the reference objects
* unreachable and so they will not get disposed.
* Since a strike may hold (via java arrays) native pointers to many
* rasterised glyphs, this would be a memory leak.
* The solution is :
* - to move all the entries to another map where they
* are no longer locatable
* - update FontStrikeDisposer to be able to distinguish which
* map they are held in via a boolean flag
* Since this isn't expected to be anything other than an extremely
* rare maybe it is not worth doing this last part.
*/
synchronized void deregisterFontAndClearStrikeCache() {
SunFontManager fm = SunFontManager.getInstance();
fm.deRegisterBadFont(this);
for (Reference strikeRef : strikeCache.values()) {
if (strikeRef != null) {
/* NB we know these are all FileFontStrike instances
* because the cache is on this FileFont
*/
FileFontStrike strike = (FileFontStrike)strikeRef.get();
if (strike != null && strike.pScalerContext != 0L) {
scaler.invalidateScalerContext(strike.pScalerContext);
}
}
}
if (scaler != null) {
scaler.disposeScaler();
}
scaler = FontScaler.getNullScaler();
}
StrikeMetrics getFontMetrics(long pScalerContext) {
try {
return getScaler().getFontMetrics(pScalerContext);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getFontMetrics(pScalerContext);
}
}
float getGlyphAdvance(long pScalerContext, int glyphCode) {
try {
return getScaler().getGlyphAdvance(pScalerContext, glyphCode);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getGlyphAdvance(pScalerContext, glyphCode);
}
}
void getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics) {
try {
getScaler().getGlyphMetrics(pScalerContext, glyphCode, metrics);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
getGlyphMetrics(pScalerContext, glyphCode, metrics);
}
}
long getGlyphImage(long pScalerContext, int glyphCode) {
try {
return getScaler().getGlyphImage(pScalerContext, glyphCode);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getGlyphImage(pScalerContext, glyphCode);
}
}
Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) {
try {
return getScaler().getGlyphOutlineBounds(pScalerContext, glyphCode);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getGlyphOutlineBounds(pScalerContext, glyphCode);
}
}
GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) {
try {
return getScaler().getGlyphOutline(pScalerContext, glyphCode, x, y);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getGlyphOutline(pScalerContext, glyphCode, x, y);
}
}
GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y) {
try {
return getScaler().getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
return getGlyphVectorOutline(pScalerContext, glyphs, numGlyphs, x, y);
}
}
/* T1 & TT implementation differ so this method is abstract.
NB: null should not be returned here! */
protected abstract FontScaler getScaler();
protected long getUnitsPerEm() {
return getScaler().getUnitsPerEm();
}
private static class CreatedFontFileDisposerRecord
implements DisposerRecord {
File fontFile = null;
CreatedFontTracker tracker;
private CreatedFontFileDisposerRecord(File file,
CreatedFontTracker tracker) {
fontFile = file;
this.tracker = tracker;
}
public void dispose() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
if (fontFile != null) {
try {
if (tracker != null) {
tracker.subBytes((int)fontFile.length());
}
/* REMIND: is it possible that the file is
* still open? It will be closed when the
* font2D is disposed but could this code
* execute first? If so the file would not
* be deleted on MS-windows.
*/
fontFile.delete();
/* remove from delete on exit hook list : */
// FIXME: still need to be refactored
SunFontManager.getInstance().tmpFontFiles.remove(fontFile);
} catch (Exception e) {
}
}
return null;
}
});
}
}
protected String getPublicFileName() {
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
return platName;
}
boolean canReadProperty = true;
try {
sm.checkPropertyAccess("java.io.tmpdir");
} catch (SecurityException e) {
canReadProperty = false;
}
if (canReadProperty) {
return platName;
}
final File f = new File(platName);
Boolean isTmpFile = Boolean.FALSE;
try {
isTmpFile = AccessController.doPrivileged(
new PrivilegedExceptionAction<Boolean>() {
public Boolean run() {
File tmp = new File(System.getProperty("java.io.tmpdir"));
try {
String tpath = tmp.getCanonicalPath();
String fpath = f.getCanonicalPath();
return (fpath == null) || fpath.startsWith(tpath);
} catch (IOException e) {
return Boolean.TRUE;
}
}
}
);
} catch (PrivilegedActionException e) {
// unable to verify whether value of java.io.tempdir will be
// exposed, so return only a name of the font file.
isTmpFile = Boolean.TRUE;
}
return isTmpFile ? "temp file" : platName;
}
}

View File

@@ -0,0 +1,985 @@
/*
* Copyright (c) 2003, 2013, 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,
int fontDataSize);
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,
((TrueTypeFont)fileFont).fontDataSize);
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 {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().warning(
"Failed to render glyph using GDI: code=" + glyphCode
+ ", fontFamily=" + family + ", style=" + style
+ ", size=" + size);
}
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.
*/
int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
int convertedCnt = 0;
for (int i=0; i<len; i++) {
int glyphCode = glyphCodes[i];
if (glyphCode >>> 24 != 0) {
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, (int)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 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 = (GeneralPath)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);
}
}
}

View File

@@ -0,0 +1,570 @@
/*
* Copyright (c) 2003, 2010, 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.Font;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Locale;
import java.util.Set;
public abstract class Font2D {
/* Note: JRE and FONT_CONFIG ranks are identical. I don't know of a reason
* to distingish these. Possibly if a user adds fonts to the JRE font
* directory that are the same font as the ones specified in the font
* configuration but that is more likely to be the legitimate intention
* than a problem. One reason why these should be the same is that on
* Linux the JRE fonts ARE the font configuration fonts, and although I
* believe all are assigned FONT_CONFIG rank, it is conceivable that if
* this were not so, that some JRE font would not be allowed to joint the
* family of its siblings which were assigned FONT_CONFIG rank. Giving
* them the same rank is the easy solution for now at least.
*/
public static final int FONT_CONFIG_RANK = 2;
public static final int JRE_RANK = 2;
public static final int TTF_RANK = 3;
public static final int TYPE1_RANK = 4;
public static final int NATIVE_RANK = 5;
public static final int UNKNOWN_RANK = 6;
public static final int DEFAULT_RANK = 4;
private static final String[] boldNames = {
"bold", "demibold", "demi-bold", "demi bold", "negreta", "demi", };
private static final String[] italicNames = {
"italic", "cursiva", "oblique", "inclined", };
private static final String[] boldItalicNames = {
"bolditalic", "bold-italic", "bold italic",
"boldoblique", "bold-oblique", "bold oblique",
"demibold italic", "negreta cursiva","demi oblique", };
private static final FontRenderContext DEFAULT_FRC =
new FontRenderContext(null, false, false);
public Font2DHandle handle;
protected String familyName; /* Family font name (english) */
protected String fullName; /* Full font name (english) */
protected int style = Font.PLAIN;
protected FontFamily family;
protected int fontRank = DEFAULT_RANK;
/*
* A mapper can be independent of the strike.
* Perhaps the reference to the mapper ought to be held on the
* scaler, as it may be implemented via scaler functionality anyway
* and so the mapper would be useless if its native portion was
* freed when the scaler was GC'd.
*/
protected CharToGlyphMapper mapper;
/*
* The strike cache is maintained per "Font2D" as that is the
* principal object by which you look up fonts.
* It means more Hashmaps, but look ups can be quicker because
* the map will have fewer entries, and there's no need to try to
* make the Font2D part of the key.
*/
protected ConcurrentHashMap<FontStrikeDesc, Reference>
strikeCache = new ConcurrentHashMap<FontStrikeDesc, Reference>();
/* Store the last Strike in a Reference object.
* Similarly to the strike that was stored on a C++ font object,
* this is an optimisation which helps if multiple clients (ie
* typically SunGraphics2D instances) are using the same font, then
* as may be typical of many UIs, they are probably using it in the
* same style, so it can be a win to first quickly check if the last
* strike obtained from this Font2D satifies the needs of the next
* client too.
* This pre-supposes that a FontStrike is a shareable object, which
* it should.
*/
protected Reference<FontStrike> lastFontStrike = new WeakReference<>(null);
/*
* if useWeak is true, proactively clear the cache after this
* many strikes are present. 0 means leave it alone.
*/
private int strikeCacheMax = 0;
/*
* Whether to use weak refs for this font, even if soft refs is the default.
*/
private boolean useWeak;
void setUseWeakRefs(boolean weak, int maxStrikes) {
this.useWeak = weak;
this.strikeCacheMax = weak && maxStrikes > 0 ? maxStrikes : 0;
}
/*
* POSSIBLE OPTIMISATION:
* Array of length 1024 elements of 64 bits indicating if a font
* contains these. This kind of information can be shared between
* all point sizes.
* if corresponding bit in knownBitmaskMap is set then canDisplayBitmaskMap
* is valid. This is 16Kbytes of data per composite font style.
* What about UTF-32 and surrogates?
* REMIND: This is too much storage. Probably can only cache this
* information for latin range, although possibly OK to store all
* for just the "logical" fonts.
* Or instead store arrays of subranges of 1024 bits (128 bytes) in
* the range below surrogate pairs.
*/
// protected long[] knownBitmaskMap;
// protected long[] canDisplayBitmaskMap;
/* Returns the "real" style of this Font2D. Eg the font face
* Lucida Sans Bold" has a real style of Font.BOLD, even though
* it may be able to used to simulate bold italic
*/
public int getStyle() {
return style;
}
protected void setStyle() {
String fName = fullName.toLowerCase();
for (int i=0; i < boldItalicNames.length; i++) {
if (fName.indexOf(boldItalicNames[i]) != -1) {
style = Font.BOLD|Font.ITALIC;
return;
}
}
for (int i=0; i < italicNames.length; i++) {
if (fName.indexOf(italicNames[i]) != -1) {
style = Font.ITALIC;
return;
}
}
for (int i=0; i < boldNames.length; i++) {
if (fName.indexOf(boldNames[i]) != -1 ) {
style = Font.BOLD;
return;
}
}
}
public static final int FWIDTH_NORMAL = 5; // OS/2 usWidthClass
public static final int FWEIGHT_NORMAL = 400; // OS/2 usWeightClass
public static final int FWEIGHT_BOLD = 700; // OS/2 usWeightClass
public int getWidth() {
return FWIDTH_NORMAL;
}
public int getWeight() {
if ((style & Font.BOLD) !=0) {
return FWEIGHT_BOLD;
} else {
return FWEIGHT_NORMAL;
}
}
int getRank() {
return fontRank;
}
void setRank(int rank) {
fontRank = rank;
}
abstract CharToGlyphMapper getMapper();
/* This isn't very efficient but its infrequently used.
* StandardGlyphVector uses it when the client assigns the glyph codes.
* These may not be valid. This validates them substituting the missing
* glyph elsewhere.
*/
protected int getValidatedGlyphCode(int glyphCode) {
if (glyphCode < 0 || glyphCode >= getMapper().getNumGlyphs()) {
glyphCode = getMapper().getMissingGlyphCode();
}
return glyphCode;
}
/*
* Creates an appropriate strike for the Font2D subclass
*/
abstract FontStrike createStrike(FontStrikeDesc desc);
/* this may be useful for APIs like canDisplay where the answer
* is dependent on the font and its scaler, but not the strike.
* If no strike has ever been returned, then create a one that matches
* this font with the default FRC. It will become the lastStrike and
* there's a good chance that the next call will be to get exactly that
* strike.
*/
public FontStrike getStrike(Font font) {
FontStrike strike = (FontStrike)lastFontStrike.get();
if (strike != null) {
return strike;
} else {
return getStrike(font, DEFAULT_FRC);
}
}
/* SunGraphics2D has font, tx, aa and fm. From this info
* can get a Strike object from the cache, creating it if necessary.
* This code is designed for multi-threaded access.
* For that reason it creates a local FontStrikeDesc rather than filling
* in a shared one. Up to two AffineTransforms and one FontStrikeDesc will
* be created by every lookup. This appears to perform more than
* adequately. But it may make sense to expose FontStrikeDesc
* as a parameter so a caller can use its own.
* In such a case if a FontStrikeDesc is stored as a key then
* we would need to use a private copy.
*
* Note that this code doesn't prevent two threads from creating
* two different FontStrike instances and having one of the threads
* overwrite the other in the map. This is likely to be a rare
* occurrence and the only consequence is that these callers will have
* different instances of the strike, and there'd be some duplication of
* population of the strikes. However since users of these strikes are
* transient, then the one that was overwritten would soon be freed.
* If there is any problem then a small synchronized block would be
* required with its attendant consequences for MP scaleability.
*/
public FontStrike getStrike(Font font, AffineTransform devTx,
int aa, int fm) {
/* Create the descriptor which is used to identify a strike
* in the strike cache/map. A strike is fully described by
* the attributes of this descriptor.
*/
/* REMIND: generating garbage and doing computation here in order
* to include pt size in the tx just for a lookup! Figure out a
* better way.
*/
double ptSize = font.getSize2D();
AffineTransform glyphTx = (AffineTransform)devTx.clone();
glyphTx.scale(ptSize, ptSize);
if (font.isTransformed()) {
glyphTx.concatenate(font.getTransform());
}
if (glyphTx.getTranslateX() != 0 || glyphTx.getTranslateY() != 0) {
glyphTx.setTransform(glyphTx.getScaleX(),
glyphTx.getShearY(),
glyphTx.getShearX(),
glyphTx.getScaleY(),
0.0, 0.0);
}
FontStrikeDesc desc = new FontStrikeDesc(devTx, glyphTx,
font.getStyle(), aa, fm);
return getStrike(desc, false);
}
public FontStrike getStrike(Font font, AffineTransform devTx,
AffineTransform glyphTx,
int aa, int fm) {
/* Create the descriptor which is used to identify a strike
* in the strike cache/map. A strike is fully described by
* the attributes of this descriptor.
*/
FontStrikeDesc desc = new FontStrikeDesc(devTx, glyphTx,
font.getStyle(), aa, fm);
return getStrike(desc, false);
}
public FontStrike getStrike(Font font, FontRenderContext frc) {
AffineTransform at = frc.getTransform();
double ptSize = font.getSize2D();
at.scale(ptSize, ptSize);
if (font.isTransformed()) {
at.concatenate(font.getTransform());
if (at.getTranslateX() != 0 || at.getTranslateY() != 0) {
at.setTransform(at.getScaleX(),
at.getShearY(),
at.getShearX(),
at.getScaleY(),
0.0, 0.0);
}
}
int aa = FontStrikeDesc.getAAHintIntVal(this, font, frc);
int fm = FontStrikeDesc.getFMHintIntVal(frc.getFractionalMetricsHint());
FontStrikeDesc desc = new FontStrikeDesc(frc.getTransform(),
at, font.getStyle(),
aa, fm);
return getStrike(desc, false);
}
void updateLastStrikeRef(FontStrike strike) {
lastFontStrike.clear();
if (useWeak) {
lastFontStrike = new WeakReference<>(strike);
} else {
lastFontStrike = new SoftReference<>(strike);
}
}
FontStrike getStrike(FontStrikeDesc desc) {
return getStrike(desc, true);
}
private FontStrike getStrike(FontStrikeDesc desc, boolean copy) {
/* Before looking in the map, see if the descriptor matches the
* last strike returned from this Font2D. This should often be a win
* since its common for the same font, in the same size to be
* used frequently, for example in many parts of a UI.
*
* If its not the same then we use the descriptor to locate a
* Reference to the strike. If it exists and points to a strike,
* then we update the last strike to refer to that and return it.
*
* If the key isn't in the map, or its reference object has been
* collected, then we create a new strike, put it in the map and
* set it to be the last strike.
*/
FontStrike strike = (FontStrike)lastFontStrike.get();
if (strike != null && desc.equals(strike.desc)) {
return strike;
} else {
Reference strikeRef = strikeCache.get(desc);
if (strikeRef != null) {
strike = (FontStrike)strikeRef.get();
if (strike != null) {
updateLastStrikeRef(strike);
StrikeCache.refStrike(strike);
return strike;
}
}
/* When we create a new FontStrike instance, we *must*
* ask the StrikeCache for a reference. We must then ensure
* this reference remains reachable, by storing it in the
* Font2D's strikeCache map.
* So long as the Reference is there (reachable) then if the
* reference is cleared, it will be enqueued for disposal.
* If for some reason we explicitly remove this reference, it
* must only be done when holding a strong reference to the
* referent (the FontStrike), or if the reference is cleared,
* then we must explicitly "dispose" of the native resources.
* The only place this currently happens is in this same method,
* where we find a cleared reference and need to overwrite it
* here with a new reference.
* Clearing the whilst holding a strong reference, should only
* be done if the
*/
if (copy) {
desc = new FontStrikeDesc(desc);
}
strike = createStrike(desc);
//StrikeCache.addStrike();
/* If we are creating many strikes on this font which
* involve non-quadrant rotations, or more general
* transforms which include shears, then force the use
* of weak references rather than soft references.
* This means that it won't live much beyond the next GC,
* which is what we want for what is likely a transient strike.
*/
int txType = desc.glyphTx.getType();
if (useWeak ||
txType == AffineTransform.TYPE_GENERAL_TRANSFORM ||
(txType & AffineTransform.TYPE_GENERAL_ROTATION) != 0 &&
strikeCache.size() > 10) {
strikeRef = StrikeCache.getStrikeRef(strike, true);
} else {
strikeRef = StrikeCache.getStrikeRef(strike, useWeak);
}
strikeCache.put(desc, strikeRef);
updateLastStrikeRef(strike);
StrikeCache.refStrike(strike);
return strike;
}
}
/**
* The length of the metrics array must be >= 8. This method will
* store the following elements in that array before returning:
* metrics[0]: ascent
* metrics[1]: descent
* metrics[2]: leading
* metrics[3]: max advance
* metrics[4]: strikethrough offset
* metrics[5]: strikethrough thickness
* metrics[6]: underline offset
* metrics[7]: underline thickness
*/
public void getFontMetrics(Font font, AffineTransform at,
Object aaHint, Object fmHint,
float metrics[]) {
/* This is called in just one place in Font with "at" == identity.
* Perhaps this can be eliminated.
*/
int aa = FontStrikeDesc.getAAHintIntVal(aaHint, this, font.getSize());
int fm = FontStrikeDesc.getFMHintIntVal(fmHint);
FontStrike strike = getStrike(font, at, aa, fm);
StrikeMetrics strikeMetrics = strike.getFontMetrics();
metrics[0] = strikeMetrics.getAscent();
metrics[1] = strikeMetrics.getDescent();
metrics[2] = strikeMetrics.getLeading();
metrics[3] = strikeMetrics.getMaxAdvance();
getStyleMetrics(font.getSize2D(), metrics, 4);
}
/**
* The length of the metrics array must be >= offset+4, and offset must be
* >= 0. Typically offset is 4. This method will
* store the following elements in that array before returning:
* metrics[off+0]: strikethrough offset
* metrics[off+1]: strikethrough thickness
* metrics[off+2]: underline offset
* metrics[off+3]: underline thickness
*
* Note that this implementation simply returns default values;
* subclasses can override this method to provide more accurate values.
*/
public void getStyleMetrics(float pointSize, float[] metrics, int offset) {
metrics[offset] = -metrics[0] / 2.5f;
metrics[offset+1] = pointSize / 12;
metrics[offset+2] = metrics[offset+1] / 1.5f;
metrics[offset+3] = metrics[offset+1];
}
/**
* The length of the metrics array must be >= 4. This method will
* store the following elements in that array before returning:
* metrics[0]: ascent
* metrics[1]: descent
* metrics[2]: leading
* metrics[3]: max advance
*/
public void getFontMetrics(Font font, FontRenderContext frc,
float metrics[]) {
StrikeMetrics strikeMetrics = getStrike(font, frc).getFontMetrics();
metrics[0] = strikeMetrics.getAscent();
metrics[1] = strikeMetrics.getDescent();
metrics[2] = strikeMetrics.getLeading();
metrics[3] = strikeMetrics.getMaxAdvance();
}
/* Currently the layout code calls this. May be better for layout code
* to check the font class before attempting to run, rather than needing
* to promote this method up from TrueTypeFont
*/
protected byte[] getTableBytes(int tag) {
return null;
}
/* implemented for fonts backed by an sfnt that has
* OpenType or AAT layout tables.
*/
protected long getLayoutTableCache() {
return 0L;
}
/* for layout code */
protected long getUnitsPerEm() {
return 2048;
}
boolean supportsEncoding(String encoding) {
return false;
}
public boolean canDoStyle(int style) {
return (style == this.style);
}
/*
* All the important subclasses override this which is principally for
* the TrueType 'gasp' table.
*/
public boolean useAAForPtSize(int ptsize) {
return true;
}
public boolean hasSupplementaryChars() {
return false;
}
/* The following methods implement public methods on java.awt.Font */
public String getPostscriptName() {
return fullName;
}
public String getFontName(Locale l) {
return fullName;
}
public String getFamilyName(Locale l) {
return familyName;
}
public int getNumGlyphs() {
return getMapper().getNumGlyphs();
}
public int charToGlyph(int wchar) {
return getMapper().charToGlyph(wchar);
}
public int getMissingGlyphCode() {
return getMapper().getMissingGlyphCode();
}
public boolean canDisplay(char c) {
return getMapper().canDisplay(c);
}
public boolean canDisplay(int cp) {
return getMapper().canDisplay(cp);
}
public byte getBaselineFor(char c) {
return Font.ROMAN_BASELINE;
}
public float getItalicAngle(Font font, AffineTransform at,
Object aaHint, Object fmHint) {
/* hardwire psz=12 as that's typical and AA vs non-AA for 'gasp' mode
* isn't important for the caret slope of this rarely used API.
*/
int aa = FontStrikeDesc.getAAHintIntVal(aaHint, this, 12);
int fm = FontStrikeDesc.getFMHintIntVal(fmHint);
FontStrike strike = getStrike(font, at, aa, fm);
StrikeMetrics metrics = strike.getFontMetrics();
if (metrics.ascentY == 0 || metrics.ascentX == 0) {
return 0f;
} else {
/* ascent is "up" from the baseline so its typically
* a negative value, so we need to compensate
*/
return metrics.ascentX/-metrics.ascentY;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2003, 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;
/*
* This class is used so that a java.awt.Font does not directly
* reference a Font2D object. This introduces occasional minor
* de-referencing overhead but increases robustness of the
* implementation when "bad fonts" are encountered.
* A handle is created by a Font2D constructor and references
* the Font2D itself. In the event that the Font2D implementation
* determines it the font resource has errors (a bad font file)
* it makes its handle point at another "stable" Font2D.
* Once all referers no longer have a reference to the Font2D it
* may be GC'd and its resources freed.
* This does not immediately help in the case that objects are
* already using a bad Font2D (ie have already dereferenced the
* handle) so there is a window for more problems. However this
* is already the case as this is the code which must detect the
* problem.
* However there is also the possibility of intercepting problems
* even when a font2D reference is already directly held. Certain
* validation points may check that font2Dhandle.font2D == font2D
* If this is not true, then this font2D is not valid. Arguably
* this check also just needs to be a de-referencing assignment :
* font2D = font2DHandle.font2D.
* The net effect of these steps is that very soon after a font
* is identified as bad, that references and uses of it will be
* eliminated.
* In the initial implementation a Font2DHandle is what is held by
* - java.awt.Font
* - FontManager.initialisedFonts map
* Font2D is held by
* - FontFamily objects
* - FontManager.registeredFonts map
* - FontInfo object on a SunGraphics2D
*
* On discovering a bad font, all but the latter remove references to
* the font. See FontManager.deRegisterBadFont(Font2D)
*/
public final class Font2DHandle {
public Font2D font2D;
public Font2DHandle(Font2D font) {
font2D = font;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2008, 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.Font;
public abstract class FontAccess {
private static FontAccess access;
public static synchronized void setFontAccess(FontAccess acc) {
if (access != null) {
throw new InternalError("Attempt to set FontAccessor twice");
}
access = acc;
}
public static synchronized FontAccess getFontAccess() {
return access;
}
public abstract Font2D getFont2D(Font f);
public abstract void setFont2D(Font f, Font2DHandle h);
public abstract void setCreatedFont(Font f);
public abstract boolean isCreatedFont(Font f);
}

View File

@@ -0,0 +1,590 @@
/*
* Copyright (c) 1997, 2006, 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.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.awt.FontMetrics;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.concurrent.ConcurrentHashMap;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
/*
* This class provides a summary of the glyph measurements for a Font
* and a set of hints that guide their display. It provides more metrics
* information for the Font than the java.awt.FontMetrics class. There
* is also some redundancy with that class.
* <p>
* The design metrics for a Font are obtained from Font.getDesignMetrics().
* The FontDesignMetrics object returned will be independent of the
* point size of the Font.
* Most users are familiar with the idea of using <i>point size</i> to
* specify the size of glyphs in a font. This point size defines a
* measurement between the baseline of one line to the baseline of the
* following line in a single spaced text document. The point size is
* based on <i>typographic points</i>, approximately 1/72 of an inch.
* <p>
* The Java2D API adopts the convention that one point is equivalent
* to one unit in user coordinates. When using a normalized transform
* for converting user space coordinates to device space coordinates (see
* GraphicsConfiguration.getDefaultTransform() and
* GraphicsConfiguration.getNormalizingTransform()), 72 user space units
* equal 1 inch in device space. In this case one point is 1/72 of an inch.
* <p>
* The FontDesignMetrics class expresses font metrics in terms of arbitrary
* <i>typographic units</i> (not points) chosen by the font supplier
* and used in the underlying platform font representations. These units are
* defined by dividing the em-square into a grid. The em-sqaure is the
* theoretical square whose dimensions are the full body height of the
* font. A typographic unit is the smallest measurable unit in the
* em-square. The number of units-per-em is determined by the font
* designer. The greater the units-per-em, the greater the precision
* in metrics. For example, Type 1 fonts divide the em-square into a
* 1000 x 1000 grid, while TrueType fonts typically use a 2048 x 2048
* grid. The scale of these units can be obtained by calling
* getUnitsPerEm().
* <p>
* Typographic units are relative -- their absolute size changes as the
* size of the of the em-square changes. An em-square is 9 points high
* in a 9-point font. Because typographic units are relative to the
* em-square, a given location on a glyph will have the same coordinates
* in typographic units regardless of the point size.
* <p>
* Converting typographic units to pixels requires computing pixels-per-em
* (ppem). This can be computed as:
* <pre>
ppem = device_resolution * (inches-per-point) * pointSize
* </pre>
* where device resolution could be measured in pixels/inch and the point
* size of a font is effectively points/em. Using a normalized transform
* from user space to device space (see above), results in 1/72 inch/point.
* In this case, ppem is equal to the point size on a 72 dpi monitor, so
* that an N point font displays N pixels high. In general,
* <pre>
pixel_units = typographic_units * (ppem / units_per_em)
* </pre>
* @see java.awt.Font
* @see java.awt.GraphicsConfiguration#getDefaultTransform
* @see java.awt.GraphicsConfiguration#getNormalizingTransform
*/
public final class FontDesignMetrics extends FontMetrics {
static final long serialVersionUID = 4480069578560887773L;
private static final float UNKNOWN_WIDTH = -1;
private static final int CURRENT_VERSION = 1;
// height, ascent, descent, leading are reported to the client
// as an integer this value is added to the true fp value to
// obtain a value which is usually going to result in a round up
// to the next integer except for very marginal cases.
private static float roundingUpValue = 0.95f;
// These fields are all part of the old serialization representation
private Font font;
private float ascent;
private float descent;
private float leading;
private float maxAdvance;
private double[] matrix;
private int[] cache; // now unused, still here only for serialization
// End legacy serialization fields
private int serVersion = 0; // If 1 in readObject, these fields are on the input stream:
private boolean isAntiAliased;
private boolean usesFractionalMetrics;
private AffineTransform frcTx;
private transient float[] advCache; // transient since values could change across runtimes
private transient int height = -1;
private transient FontRenderContext frc;
private transient double[] devmatrix = null;
private transient FontStrike fontStrike;
private static FontRenderContext DEFAULT_FRC = null;
private static FontRenderContext getDefaultFrc() {
if (DEFAULT_FRC == null) {
AffineTransform tx;
if (GraphicsEnvironment.isHeadless()) {
tx = new AffineTransform();
} else {
tx = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getDefaultTransform();
}
DEFAULT_FRC = new FontRenderContext(tx, false, false);
}
return DEFAULT_FRC;
}
/* Strongly cache up to 5 most recently requested FontMetrics objects,
* and softly cache as many as GC allows. In practice this means we
* should keep references around until memory gets low.
* We key the cache either by a Font or a combination of the Font and
* and FRC. A lot of callers use only the font so although there's code
* duplication, we allow just a font to be a key implying a default FRC.
* Also we put the references on a queue so that if they do get nulled
* out we can clear the keys from the table.
*/
private static class KeyReference extends SoftReference
implements DisposerRecord, Disposer.PollDisposable {
static ReferenceQueue queue = Disposer.getQueue();
Object key;
KeyReference(Object key, Object value) {
super(value, queue);
this.key = key;
Disposer.addReference(this, this);
}
/* It is possible that since this reference object has been
* enqueued, that a new metrics has been put into the table
* for the same key value. So we'll test to see if the table maps
* to THIS reference. If its a new one, we'll leave it alone.
* It is possible that a new entry comes in after our test, but
* it is unlikely and if this were a problem we would need to
* synchronize all 'put' and 'remove' accesses to the cache which
* I would prefer not to do.
*/
public void dispose() {
if (metricsCache.get(key) == this) {
metricsCache.remove(key);
}
}
}
private static class MetricsKey {
Font font;
FontRenderContext frc;
int hash;
MetricsKey() {
}
MetricsKey(Font font, FontRenderContext frc) {
init(font, frc);
}
void init(Font font, FontRenderContext frc) {
this.font = font;
this.frc = frc;
this.hash = font.hashCode() + frc.hashCode();
}
public boolean equals(Object key) {
if (!(key instanceof MetricsKey)) {
return false;
}
return
font.equals(((MetricsKey)key).font) &&
frc.equals(((MetricsKey)key).frc);
}
public int hashCode() {
return hash;
}
/* Synchronize access to this on the class */
static final MetricsKey key = new MetricsKey();
}
/* All accesses to a CHM do not in general need to be synchronized,
* as incomplete operations on another thread would just lead to
* harmless cache misses.
*/
private static final ConcurrentHashMap<Object, KeyReference>
metricsCache = new ConcurrentHashMap<Object, KeyReference>();
private static final int MAXRECENT = 5;
private static final FontDesignMetrics[]
recentMetrics = new FontDesignMetrics[MAXRECENT];
private static int recentIndex = 0;
public static FontDesignMetrics getMetrics(Font font) {
return getMetrics(font, getDefaultFrc());
}
public static FontDesignMetrics getMetrics(Font font,
FontRenderContext frc) {
/* When using alternate composites, can't cache based just on
* the java.awt.Font. Since this is rarely used and we can still
* cache the physical fonts, its not a problem to just return a
* new instance in this case.
* Note that currently Swing native L&F composites are not handled
* by this code as they use the metrics of the physical anyway.
*/
SunFontManager fm = SunFontManager.getInstance();
if (fm.maybeUsingAlternateCompositeFonts() &&
FontUtilities.getFont2D(font) instanceof CompositeFont) {
return new FontDesignMetrics(font, frc);
}
FontDesignMetrics m = null;
KeyReference r;
/* There are 2 possible keys used to perform lookups in metricsCache.
* If the FRC is set to all defaults, we just use the font as the key.
* If the FRC is non-default in any way, we construct a hybrid key
* that combines the font and FRC.
*/
boolean usefontkey = frc.equals(getDefaultFrc());
if (usefontkey) {
r = metricsCache.get(font);
} else /* use hybrid key */ {
// NB synchronization is not needed here because of updates to
// the metrics cache but is needed for the shared key.
synchronized (MetricsKey.class) {
MetricsKey.key.init(font, frc);
r = metricsCache.get(MetricsKey.key);
}
}
if (r != null) {
m = (FontDesignMetrics)r.get();
}
if (m == null) {
/* either there was no reference, or it was cleared. Need a new
* metrics instance. The key to use in the map is a new
* MetricsKey instance when we've determined the FRC is
* non-default. Its constructed from local vars so we are
* thread-safe - no need to worry about the shared key changing.
*/
m = new FontDesignMetrics(font, frc);
if (usefontkey) {
metricsCache.put(font, new KeyReference(font, m));
} else /* use hybrid key */ {
MetricsKey newKey = new MetricsKey(font, frc);
metricsCache.put(newKey, new KeyReference(newKey, m));
}
}
/* Here's where we keep the recent metrics */
for (int i=0; i<recentMetrics.length; i++) {
if (recentMetrics[i]==m) {
return m;
}
}
synchronized (recentMetrics) {
recentMetrics[recentIndex++] = m;
if (recentIndex == MAXRECENT) {
recentIndex = 0;
}
}
return m;
}
/*
* Constructs a new FontDesignMetrics object for the given Font.
* Its private to enable caching - call getMetrics() instead.
* @param font a Font object.
*/
private FontDesignMetrics(Font font) {
this(font, getDefaultFrc());
}
/* private to enable caching - call getMetrics() instead. */
private FontDesignMetrics(Font font, FontRenderContext frc) {
super(font);
this.font = font;
this.frc = frc;
this.isAntiAliased = frc.isAntiAliased();
this.usesFractionalMetrics = frc.usesFractionalMetrics();
frcTx = frc.getTransform();
matrix = new double[4];
initMatrixAndMetrics();
initAdvCache();
}
private void initMatrixAndMetrics() {
Font2D font2D = FontUtilities.getFont2D(font);
fontStrike = font2D.getStrike(font, frc);
StrikeMetrics metrics = fontStrike.getFontMetrics();
this.ascent = metrics.getAscent();
this.descent = metrics.getDescent();
this.leading = metrics.getLeading();
this.maxAdvance = metrics.getMaxAdvance();
devmatrix = new double[4];
frcTx.getMatrix(devmatrix);
}
private void initAdvCache() {
advCache = new float[256];
// 0 is a valid metric so force it to -1
for (int i = 0; i < 256; i++) {
advCache[i] = UNKNOWN_WIDTH;
}
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
if (serVersion != CURRENT_VERSION) {
frc = getDefaultFrc();
isAntiAliased = frc.isAntiAliased();
usesFractionalMetrics = frc.usesFractionalMetrics();
frcTx = frc.getTransform();
}
else {
frc = new FontRenderContext(frcTx, isAntiAliased, usesFractionalMetrics);
}
// when deserialized, members are set to their default values for their type--
// not to the values assigned during initialization before the constructor
// body!
height = -1;
cache = null;
initMatrixAndMetrics();
initAdvCache();
}
private void writeObject(ObjectOutputStream out) throws IOException {
cache = new int[256];
for (int i=0; i < 256; i++) {
cache[i] = -1;
}
serVersion = CURRENT_VERSION;
out.defaultWriteObject();
cache = null;
}
private float handleCharWidth(int ch) {
return fontStrike.getCodePointAdvance(ch); // x-component of result only
}
// Uses advCache to get character width
// It is incorrect to call this method for ch > 255
private float getLatinCharWidth(char ch) {
float w = advCache[ch];
if (w == UNKNOWN_WIDTH) {
w = handleCharWidth(ch);
advCache[ch] = w;
}
return w;
}
/* Override of FontMetrics.getFontRenderContext() */
public FontRenderContext getFontRenderContext() {
return frc;
}
public int charWidth(char ch) {
// default metrics for compatibility with legacy code
float w;
if (ch < 0x100) {
w = getLatinCharWidth(ch);
}
else {
w = handleCharWidth(ch);
}
return (int)(0.5 + w);
}
public int charWidth(int ch) {
if (!Character.isValidCodePoint(ch)) {
ch = 0xffff;
}
float w = handleCharWidth(ch);
return (int)(0.5 + w);
}
public int stringWidth(String str) {
float width = 0;
if (font.hasLayoutAttributes()) {
/* TextLayout throws IAE for null, so throw NPE explicitly */
if (str == null) {
throw new NullPointerException("str is null");
}
if (str.length() == 0) {
return 0;
}
width = new TextLayout(str, font, frc).getAdvance();
} else {
int length = str.length();
for (int i=0; i < length; i++) {
char ch = str.charAt(i);
if (ch < 0x100) {
width += getLatinCharWidth(ch);
} else if (FontUtilities.isNonSimpleChar(ch)) {
width = new TextLayout(str, font, frc).getAdvance();
break;
} else {
width += handleCharWidth(ch);
}
}
}
return (int) (0.5 + width);
}
public int charsWidth(char data[], int off, int len) {
float width = 0;
if (font.hasLayoutAttributes()) {
if (len == 0) {
return 0;
}
String str = new String(data, off, len);
width = new TextLayout(str, font, frc).getAdvance();
} else {
/* Explicit test needed to satisfy superclass spec */
if (len < 0) {
throw new IndexOutOfBoundsException("len="+len);
}
int limit = off + len;
for (int i=off; i < limit; i++) {
char ch = data[i];
if (ch < 0x100) {
width += getLatinCharWidth(ch);
} else if (FontUtilities.isNonSimpleChar(ch)) {
String str = new String(data, off, len);
width = new TextLayout(str, font, frc).getAdvance();
break;
} else {
width += handleCharWidth(ch);
}
}
}
return (int) (0.5 + width);
}
/**
* Gets the advance widths of the first 256 characters in the
* <code>Font</code>. The advance is the
* distance from the leftmost point to the rightmost point on the
* character's baseline. Note that the advance of a
* <code>String</code> is not necessarily the sum of the advances
* of its characters.
* @return an array storing the advance widths of the
* characters in the <code>Font</code>
* described by this <code>FontMetrics</code> object.
*/
// More efficient than base class implementation - reuses existing cache
public int[] getWidths() {
int[] widths = new int[256];
for (char ch = 0 ; ch < 256 ; ch++) {
float w = advCache[ch];
if (w == UNKNOWN_WIDTH) {
w = advCache[ch] = handleCharWidth(ch);
}
widths[ch] = (int) (0.5 + w);
}
return widths;
}
public int getMaxAdvance() {
return (int)(0.99f + this.maxAdvance);
}
/*
* Returns the typographic ascent of the font. This is the maximum distance
* glyphs in this font extend above the base line (measured in typographic
* units).
*/
public int getAscent() {
return (int)(roundingUpValue + this.ascent);
}
/*
* Returns the typographic descent of the font. This is the maximum distance
* glyphs in this font extend below the base line.
*/
public int getDescent() {
return (int)(roundingUpValue + this.descent);
}
public int getLeading() {
// nb this ensures the sum of the results of the public methods
// for leading, ascent & descent sum to height.
// if the calculations in any other methods change this needs
// to be changed too.
// the 0.95 value used here and in the other methods allows some
// tiny fraction of leeway before rouding up. A higher value (0.99)
// caused some excessive rounding up.
return
(int)(roundingUpValue + descent + leading) -
(int)(roundingUpValue + descent);
}
// height is calculated as the sum of two separately rounded up values
// because typically clients use ascent to determine the y location to
// pass to drawString etc and we need to ensure that the height has enough
// space below the baseline to fully contain any descender.
public int getHeight() {
if (height < 0) {
height = getAscent() + (int)(roundingUpValue + descent + leading);
}
return height;
}
}

View File

@@ -0,0 +1,451 @@
/*
* Copyright (c) 2003, 2006, 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.io.File;
import java.awt.Font;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Locale;
public class FontFamily {
private static ConcurrentHashMap<String, FontFamily>
familyNameMap = new ConcurrentHashMap<String, FontFamily>();
private static HashMap<String, FontFamily> allLocaleNames;
protected String familyName;
protected Font2D plain;
protected Font2D bold;
protected Font2D italic;
protected Font2D bolditalic;
protected boolean logicalFont = false;
protected int familyRank;
public static FontFamily getFamily(String name) {
return familyNameMap.get(name.toLowerCase(Locale.ENGLISH));
}
public static String[] getAllFamilyNames() {
return null;
}
/* Only for use by FontManager.deRegisterBadFont(..).
* If this was the only font in the family, the family is removed
* from the map
*/
static void remove(Font2D font2D) {
String name = font2D.getFamilyName(Locale.ENGLISH);
FontFamily family = getFamily(name);
if (family == null) {
return;
}
if (family.plain == font2D) {
family.plain = null;
}
if (family.bold == font2D) {
family.bold = null;
}
if (family.italic == font2D) {
family.italic = null;
}
if (family.bolditalic == font2D) {
family.bolditalic = null;
}
if (family.plain == null && family.bold == null &&
family.plain == null && family.bold == null) {
familyNameMap.remove(name);
}
}
public FontFamily(String name, boolean isLogFont, int rank) {
logicalFont = isLogFont;
familyName = name;
familyRank = rank;
familyNameMap.put(name.toLowerCase(Locale.ENGLISH), this);
}
/* Create a family for created fonts which aren't listed in the
* main map.
*/
FontFamily(String name) {
logicalFont = false;
familyName = name;
familyRank = Font2D.DEFAULT_RANK;
}
public String getFamilyName() {
return familyName;
}
public int getRank() {
return familyRank;
}
private boolean isFromSameSource(Font2D font) {
if (!(font instanceof FileFont)) {
return false;
}
FileFont existingFont = null;
if (plain instanceof FileFont) {
existingFont = (FileFont)plain;
} else if (bold instanceof FileFont) {
existingFont = (FileFont)bold;
} else if (italic instanceof FileFont) {
existingFont = (FileFont)italic;
} else if (bolditalic instanceof FileFont) {
existingFont = (FileFont)bolditalic;
}
// A family isn't created until there's a font.
// So if we didn't find a file font it means this
// isn't a file-based family.
if (existingFont == null) {
return false;
}
File existDir = (new File(existingFont.platName)).getParentFile();
FileFont newFont = (FileFont)font;
File newDir = (new File(newFont.platName)).getParentFile();
if (existDir != null) {
try {
existDir = existDir.getCanonicalFile();
} catch (IOException ignored) {}
}
if (newDir != null) {
try {
newDir = newDir.getCanonicalFile();
} catch (IOException ignored) {}
}
return java.util.Objects.equals(newDir, existDir);
}
/*
* We want a family to be of the same width and prefer medium/normal width.
* Once we find a particular width we accept more of the same width
* until we find one closer to normal when we 'evict' all existing fonts.
* So once we see a 'normal' width font we evict all members that are not
* normal width and then accept only new ones that are normal width.
*
* Once a font passes the width test we subject it to the weight test.
* For Plain we target the weight the closest that is <= NORMAL (400)
* For Bold we target the weight that is closest to BOLD (700).
*
* In the future, rather than discarding these fonts, we should
* extend the family to include these so lookups on these properties
* can locate them, as presently they will only be located by full name
* based lookup.
*/
private int familyWidth = 0;
private boolean preferredWidth(Font2D font) {
int newWidth = font.getWidth();
if (familyWidth == 0) {
familyWidth = newWidth;
return true;
}
if (newWidth == familyWidth) {
return true;
}
if (Math.abs(Font2D.FWIDTH_NORMAL - newWidth) <
Math.abs(Font2D.FWIDTH_NORMAL - familyWidth))
{
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger().info(
"Found more preferred width. New width = " + newWidth +
" Old width = " + familyWidth + " in font " + font +
" nulling out fonts plain: " + plain + " bold: " + bold +
" italic: " + italic + " bolditalic: " + bolditalic);
}
familyWidth = newWidth;
plain = bold = italic = bolditalic = null;
return true;
} else if (FontUtilities.debugFonts()) {
FontUtilities.getLogger().info(
"Family rejecting font " + font +
" of less preferred width " + newWidth);
}
return false;
}
private boolean closerWeight(Font2D currFont, Font2D font, int style) {
if (familyWidth != font.getWidth()) {
return false;
}
if (currFont == null) {
return true;
}
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger().info(
"New weight for style " + style + ". Curr.font=" + currFont +
" New font="+font+" Curr.weight="+ + currFont.getWeight()+
" New weight="+font.getWeight());
}
int newWeight = font.getWeight();
switch (style) {
case Font.PLAIN:
case Font.ITALIC:
return (newWeight <= Font2D.FWEIGHT_NORMAL &&
newWeight > currFont.getWeight());
case Font.BOLD:
case Font.BOLD|Font.ITALIC:
return (Math.abs(newWeight - Font2D.FWEIGHT_BOLD) <
Math.abs(currFont.getWeight() - Font2D.FWEIGHT_BOLD));
default:
return false;
}
}
public void setFont(Font2D font, int style) {
if (FontUtilities.isLogging()) {
String msg;
if (font instanceof CompositeFont) {
msg = "Request to add " + font.getFamilyName(null) +
" with style " + style + " to family " + familyName;
} else {
msg = "Request to add " + font +
" with style " + style + " to family " + this;
}
FontUtilities.getLogger().info(msg);
}
/* Allow a lower-rank font only if its a file font
* from the exact same source as any previous font.
*/
if ((font.getRank() > familyRank) && !isFromSameSource(font)) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger()
.warning("Rejecting adding " + font +
" of lower rank " + font.getRank() +
" to family " + this +
" of rank " + familyRank);
}
return;
}
switch (style) {
case Font.PLAIN:
if (preferredWidth(font) && closerWeight(plain, font, style)) {
plain = font;
}
break;
case Font.BOLD:
if (preferredWidth(font) && closerWeight(bold, font, style)) {
bold = font;
}
break;
case Font.ITALIC:
if (preferredWidth(font) && closerWeight(italic, font, style)) {
italic = font;
}
break;
case Font.BOLD|Font.ITALIC:
if (preferredWidth(font) && closerWeight(bolditalic, font, style)) {
bolditalic = font;
}
break;
default:
break;
}
}
public Font2D getFontWithExactStyleMatch(int style) {
switch (style) {
case Font.PLAIN:
return plain;
case Font.BOLD:
return bold;
case Font.ITALIC:
return italic;
case Font.BOLD|Font.ITALIC:
return bolditalic;
default:
return null;
}
}
/* REMIND: if the callers of this method are operating in an
* environment in which not all fonts are registered, the returned
* font may be a algorithmically styled one, where in fact if loadfonts
* were executed, a styled font may be located. Our present "solution"
* to this is to register all fonts in a directory and assume that this
* registered all the styles of a font, since they would all be in the
* same location.
*/
public Font2D getFont(int style) {
switch (style) {
case Font.PLAIN:
return plain;
case Font.BOLD:
if (bold != null) {
return bold;
} else if (plain != null && plain.canDoStyle(style)) {
return plain;
} else {
return null;
}
case Font.ITALIC:
if (italic != null) {
return italic;
} else if (plain != null && plain.canDoStyle(style)) {
return plain;
} else {
return null;
}
case Font.BOLD|Font.ITALIC:
if (bolditalic != null) {
return bolditalic;
} else if (bold != null && bold.canDoStyle(style)) {
return bold;
} else if (italic != null && italic.canDoStyle(style)) {
return italic;
} else if (plain != null && plain.canDoStyle(style)) {
return plain;
} else {
return null;
}
default:
return null;
}
}
/* Only to be called if getFont(style) returns null
* This method will only return null if the family is completely empty!
* Note that it assumes the font of the style you need isn't in the
* family. The logic here is that if we must substitute something
* it might as well be from the same family.
*/
Font2D getClosestStyle(int style) {
switch (style) {
/* if you ask for a plain font try to return a non-italic one,
* then a italic one, finally a bold italic one */
case Font.PLAIN:
if (bold != null) {
return bold;
} else if (italic != null) {
return italic;
} else {
return bolditalic;
}
/* if you ask for a bold font try to return a non-italic one,
* then a bold italic one, finally an italic one */
case Font.BOLD:
if (plain != null) {
return plain;
} else if (bolditalic != null) {
return bolditalic;
} else {
return italic;
}
/* if you ask for a italic font try to return a bold italic one,
* then a plain one, finally an bold one */
case Font.ITALIC:
if (bolditalic != null) {
return bolditalic;
} else if (plain != null) {
return plain;
} else {
return bold;
}
case Font.BOLD|Font.ITALIC:
if (italic != null) {
return italic;
} else if (bold != null) {
return bold;
} else {
return plain;
}
}
return null;
}
/* Font may have localized names. Store these in a separate map, so
* that only clients who use these names need be affected.
*/
static synchronized void addLocaleNames(FontFamily family, String[] names){
if (allLocaleNames == null) {
allLocaleNames = new HashMap<String, FontFamily>();
}
for (int i=0; i<names.length; i++) {
allLocaleNames.put(names[i].toLowerCase(), family);
}
}
public static synchronized FontFamily getLocaleFamily(String name) {
if (allLocaleNames == null) {
return null;
}
return allLocaleNames.get(name.toLowerCase());
}
public static FontFamily[] getAllFontFamilies() {
Collection<FontFamily> families = familyNameMap.values();
return families.toArray(new FontFamily[0]);
}
public String toString() {
return
"Font family: " + familyName +
" plain="+plain+
" bold=" + bold +
" italic=" + italic +
" bolditalic=" + bolditalic;
}
}

View File

@@ -0,0 +1,119 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003, All Rights Reserved
*
*/
package sun.font;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
/**
* Metrics from a font for layout of characters along a line
* and layout of set of lines.
* This and CoreMetrics replace what was previously a private internal class of Font
*/
public final class FontLineMetrics extends LineMetrics implements Cloneable {
public int numchars; // mutated by Font
public final CoreMetrics cm;
public final FontRenderContext frc;
public FontLineMetrics(int numchars, CoreMetrics cm, FontRenderContext frc) {
this.numchars = numchars;
this.cm = cm;
this.frc = frc;
}
public final int getNumChars() {
return numchars;
}
public final float getAscent() {
return cm.ascent;
}
public final float getDescent() {
return cm.descent;
}
public final float getLeading() {
return cm.leading;
}
public final float getHeight() {
return cm.height;
}
public final int getBaselineIndex() {
return cm.baselineIndex;
}
public final float[] getBaselineOffsets() {
return (float[])cm.baselineOffsets.clone();
}
public final float getStrikethroughOffset() {
return cm.strikethroughOffset;
}
public final float getStrikethroughThickness() {
return cm.strikethroughThickness;
}
public final float getUnderlineOffset() {
return cm.underlineOffset;
}
public final float getUnderlineThickness() {
return cm.underlineThickness;
}
public final int hashCode() {
return cm.hashCode();
}
public final boolean equals(Object rhs) {
try {
return cm.equals(((FontLineMetrics)rhs).cm);
}
catch (ClassCastException e) {
return false;
}
}
public final Object clone() {
// frc, cm do not need deep clone
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2003, 2013, 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.Font;
import java.awt.FontFormatException;
import java.io.File;
import java.util.Locale;
import java.util.TreeMap;
import javax.swing.plaf.FontUIResource;
/**
* Interface between Java Fonts (java.awt.Font) and the underlying
* font files/native font resources and the Java and native font scalers.
*/
public interface FontManager {
// These constants are used in findFont().
public static final int NO_FALLBACK = 0;
public static final int PHYSICAL_FALLBACK = 1;
public static final int LOGICAL_FALLBACK = 2;
/**
* Register a new font. Please, note that {@code null} is not a valid
* argument, and it's caller's responsibility to ensure that, but to keep
* compatibility, if {@code null} is passed as an argument, {@code false}
* is returned, and no {@link NullPointerException}
* is thrown.
*
* As additional note, an implementation should ensure that this font
* cannot override existing installed fonts.
*
* @param font
* @return {@code true} is the font is successfully registered,
* {@code false} otherwise.
*/
public boolean registerFont(Font font);
public void deRegisterBadFont(Font2D font2D);
/**
* The client supplies a name and a style.
* The name could be a family name, or a full name.
* A font may exist with the specified style, or it may
* exist only in some other style. For non-native fonts the scaler
* may be able to emulate the required style.
*/
public Font2D findFont2D(String name, int style, int fallback);
/**
* Creates a Font2D for the specified font file, that is expected
* to be in the specified font format (according to the constants
* in java.awt.Font). The parameter {@code isCopy} is set to true
* when the specified font file is actually a copy of the font data
* and needs to be deleted afterwards. This method is called
* for the Font.createFont() methods.
*
* @param fontFile the file holding the font data
* @param fontFormat the expected font format
* @param isCopy {@code true} if the file is a copy and needs to be
* deleted, {@code false} otherwise
*
* @return the created Font2D instance
*/
public Font2D createFont2D(File fontFile, int fontFormat,
boolean isCopy, CreatedFontTracker tracker)
throws FontFormatException;
/**
* If usingPerAppContextComposites is true, we are in "applet"
* (eg browser) environment and at least one context has selected
* an alternate composite font behaviour.
*/
public boolean usingPerAppContextComposites();
/**
* Creates a derived composite font from the specified font (handle).
*
* @param family the font family of the derived font
* @param style the font style of the derived font
* @param handle the original font (handle)
*
* @return the handle for the derived font
*/
public Font2DHandle getNewComposite(String family, int style,
Font2DHandle handle);
/**
* Indicates a preference for locale-specific fonts in the mapping of
* logical fonts to physical fonts. Calling this method indicates that font
* rendering should primarily use fonts specific to the primary writing
* system (the one indicated by the default encoding and the initial
* default locale). For example, if the primary writing system is
* Japanese, then characters should be rendered using a Japanese font
* if possible, and other fonts should only be used for characters for
* which the Japanese font doesn't have glyphs.
* <p>
* The actual change in font rendering behavior resulting from a call
* to this method is implementation dependent; it may have no effect at
* all, or the requested behavior may already match the default behavior.
* The behavior may differ between font rendering in lightweight
* and peered components. Since calling this method requests a
* different font, clients should expect different metrics, and may need
* to recalculate window sizes and layout. Therefore this method should
* be called before user interface initialisation.
*
* @see #preferProportionalFonts()
* @since 1.5
*/
public void preferLocaleFonts();
/**
* preferLocaleFonts() and preferProportionalFonts() are called to inform
* that the application could be using an alternate set of composite
* fonts, and so the implementation should try to create a CompositeFonts
* with this directive in mind.
*
* @see #preferLocaleFonts()
*/
public void preferProportionalFonts();
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2008, 2012, 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.AWTError;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.security.action.GetPropertyAction;
/**
* Factory class used to retrieve a valid FontManager instance for the current
* platform.
*
* A default implementation is given for Linux, Solaris and Windows.
* You can alter the behaviour of the {@link #getInstance()} method by setting
* the {@code sun.font.fontmanager} property. For example:
* {@code sun.font.fontmanager=sun.awt.X11FontManager}
*/
public final class FontManagerFactory {
/** Our singleton instance. */
private static FontManager instance = null;
private static final String DEFAULT_CLASS;
static {
if (FontUtilities.isWindows) {
DEFAULT_CLASS = "sun.awt.Win32FontManager";
} else if (FontUtilities.isMacOSX) {
DEFAULT_CLASS = "sun.font.CFontManager";
} else {
DEFAULT_CLASS = "sun.awt.X11FontManager";
}
}
/**
* Get a valid FontManager implementation for the current platform.
*
* @return a valid FontManager instance for the current platform
*/
public static synchronized FontManager getInstance() {
if (instance != null) {
return instance;
}
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
String fmClassName =
System.getProperty("sun.font.fontmanager",
DEFAULT_CLASS);
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class fmClass = Class.forName(fmClassName, true, cl);
instance = (FontManager) fmClass.newInstance();
} catch (ClassNotFoundException |
InstantiationException |
IllegalAccessException ex) {
throw new InternalError(ex);
}
return null;
}
});
return instance;
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2008, 2011, 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.Font;
import java.util.Locale;
import java.util.TreeMap;
/**
* This is an extension of the {@link FontManager} interface which has to
* be implemented on systems that want to use SunGraphicsEnvironment. It
* adds a couple of methods that are only required by SGE. Graphics
* implementations that use their own GraphicsEnvironment are not required
* to implement this and can use plain FontManager instead.
*/
public interface FontManagerForSGE extends FontManager {
/**
* Return an array of created Fonts, or null, if no fonts were created yet.
*/
public Font[] getCreatedFonts();
/**
* Similar to getCreatedFonts, but returns a TreeMap of fonts by family name.
*/
public TreeMap<String, String> getCreatedFontFamilyNames();
/**
* Returns all fonts installed in this environment.
*/
public Font[] getAllInstalledFonts();
public String[] getInstalledFontFamilyNames(Locale requestedLocale);
/* Modifies the behaviour of a subsequent call to preferLocaleFonts()
* to use Mincho instead of Gothic for dialoginput in JA locales
* on windows. Not needed on other platforms.
*/
public void useAlternateFontforJALocales();
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2007, 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 sun.java2d.SunGraphicsEnvironment;
public class FontManagerNativeLibrary {
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
/* REMIND do we really have to load awt here? */
System.loadLibrary("awt");
if (FontUtilities.isOpenJDK &&
System.getProperty("os.name").startsWith("Windows")) {
/* Ideally fontmanager library should not depend on
particular implementation of the font scaler.
However, freetype scaler is basically small wrapper on
top of freetype library (that is used in binary form).
This wrapper is compiled into fontmanager and this make
fontmanger library depending on freetype library.
On Windows DLL's in the JRE's BIN directory cannot be
found by windows DLL loading as that directory is not
on the Windows PATH.
To avoid link error we have to load freetype explicitly
before we load fontmanager.
Note that we do not need to do this for T2K because
fontmanager.dll does not depend on t2k.dll.
NB: consider moving freetype wrapper part to separate
shared library in order to avoid dependency. */
System.loadLibrary("freetype");
}
System.loadLibrary("fontmanager");
return null;
}
});
}
/*
* Call this method to ensure libraries are loaded.
*
* Method acts as trigger to ensure this class is loaded
* (and therefore initializer code is executed).
* Actual loading is performed by static initializer.
* (no need to execute doPrivilledged block more than once)
*/
public static void load() {}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) 1999, 2008, 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.
*
*/
/*
* (C) Copyright IBM Corp. 1999, All rights reserved.
*/
package sun.font;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.font.TextAttribute;
import java.util.ArrayList;
import java.util.Map;
import sun.text.CodePointIterator;
/**
* This class maps an individual character to a Font family which can
* display it. The character-to-Font mapping does not depend on the
* character's context, so a particular character will be mapped to the
* same font family each time.
* <p>
* Typically, clients will call getIndexFor(char) for each character
* in a style run. When getIndexFor() returns a different value from
* ones seen previously, the characters up to that point will be assigned
* a font obtained from getFont().
*/
public final class FontResolver {
// An array of all fonts available to the runtime. The fonts
// will be searched in order.
private Font[] allFonts;
private Font[] supplementaryFonts;
private int[] supplementaryIndices;
// Default size of Fonts (if created from an empty Map, for instance).
private static final int DEFAULT_SIZE = 12; // from Font
private Font defaultFont = new Font(Font.DIALOG, Font.PLAIN, DEFAULT_SIZE);
// The results of previous lookups are cached in a two-level
// table. The value for a character c is found in:
// blocks[c>>SHIFT][c&MASK]
// although the second array is only allocated when needed.
// A 0 value means the character's font has not been looked up.
// A positive value means the character's font is in the allFonts
// array at index (value-1).
private static final int SHIFT = 9;
private static final int BLOCKSIZE = 1<<(16-SHIFT);
private static final int MASK = BLOCKSIZE-1;
private int[][] blocks = new int[1<<SHIFT][];
private FontResolver() {
}
private Font[] getAllFonts() {
if (allFonts == null) {
allFonts =
GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
for (int i=0; i < allFonts.length; i++) {
allFonts[i] = allFonts[i].deriveFont((float)DEFAULT_SIZE);
}
}
return allFonts;
}
/**
* Search fonts in order, and return "1" to indicate its in the default
* font, (or not found at all), or the index of the first font
* which can display the given character, plus 2, if it is not
* in the default font.
*/
private int getIndexFor(char c) {
if (defaultFont.canDisplay(c)) {
return 1;
}
for (int i=0; i < getAllFonts().length; i++) {
if (allFonts[i].canDisplay(c)) {
return i+2;
}
}
return 1;
}
private Font [] getAllSCFonts() {
if (supplementaryFonts == null) {
ArrayList<Font> fonts = new ArrayList<Font>();
ArrayList<Integer> indices = new ArrayList<Integer>();
for (int i=0; i<getAllFonts().length; i++) {
Font font = allFonts[i];
Font2D font2D = FontUtilities.getFont2D(font);
if (font2D.hasSupplementaryChars()) {
fonts.add(font);
indices.add(Integer.valueOf(i));
}
}
int len = fonts.size();
supplementaryIndices = new int[len];
for (int i=0; i<len; i++) {
supplementaryIndices[i] = indices.get(i);
}
supplementaryFonts = fonts.toArray(new Font[len]);
}
return supplementaryFonts;
}
/* This method is called only for character codes >= 0x10000 - which
* are assumed to be legal supplementary characters.
* It looks first at the default font (to avoid calling getAllFonts if at
* all possible) and if that doesn't map the code point, it scans
* just the fonts that may contain supplementary characters.
* The index that is returned is into the "allFonts" array so that
* callers see the same value for both supplementary and base chars.
*/
private int getIndexFor(int cp) {
if (defaultFont.canDisplay(cp)) {
return 1;
}
for (int i = 0; i < getAllSCFonts().length; i++) {
if (supplementaryFonts[i].canDisplay(cp)) {
return supplementaryIndices[i]+2;
}
}
return 1;
}
/**
* Return an index for the given character. The index identifies a
* font family to getFont(), and has no other inherent meaning.
* @param c the character to map
* @return a value for consumption by getFont()
* @see #getFont
*/
public int getFontIndex(char c) {
int blockIndex = c>>SHIFT;
int[] block = blocks[blockIndex];
if (block == null) {
block = new int[BLOCKSIZE];
blocks[blockIndex] = block;
}
int index = c & MASK;
if (block[index] == 0) {
block[index] = getIndexFor(c);
}
return block[index];
}
public int getFontIndex(int cp) {
if (cp < 0x10000) {
return getFontIndex((char)cp);
}
return getIndexFor(cp);
}
/**
* Determines the font index for the code point at the current position in the
* iterator, then advances the iterator to the first code point that has
* a different index or until the iterator is DONE, and returns the font index.
* @param iter a code point iterator, this will be advanced past any code
* points that have the same font index
* @return the font index for the initial code point found, or 1 if the iterator
* was empty.
*/
public int nextFontRunIndex(CodePointIterator iter) {
int cp = iter.next();
int fontIndex = 1;
if (cp != CodePointIterator.DONE) {
fontIndex = getFontIndex(cp);
while ((cp = iter.next()) != CodePointIterator.DONE) {
if (getFontIndex(cp) != fontIndex) {
iter.prev();
break;
}
}
}
return fontIndex;
}
/**
* Return a Font from a given font index with properties
* from attributes. The font index, which should have been produced
* by getFontIndex(), determines a font family. The size and style
* of the Font reflect the properties in attributes. Any Font or
* font family specifications in attributes are ignored, on the
* assumption that clients have already handled them.
* @param index an index from getFontIndex() which determines the
* font family
* @param attributes a Map from which the size and style of the Font
* are determined. The default size is 12 and the default style
* is Font.PLAIN
* @see #getFontIndex
*/
public Font getFont(int index, Map attributes) {
Font font = defaultFont;
if (index >= 2) {
font = allFonts[index-2];
}
return font.deriveFont(attributes);
}
private static FontResolver INSTANCE;
/**
* Return a shared instance of FontResolver.
*/
public static FontResolver getInstance() {
if (INSTANCE == null) {
INSTANCE = new FontResolver();
}
return INSTANCE;
}
}

View File

@@ -0,0 +1,171 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003 - All Rights Reserved
*/
package sun.font;
/**
* Iterates over runs of fonts in a CompositeFont, optionally taking script runs into account.
*/
public final class FontRunIterator {
CompositeFont font;
char[] text;
int start;
int limit;
CompositeGlyphMapper mapper; // handy cache
int slot = -1;
int pos;
public void init(CompositeFont font, char[] text, int start, int limit) {
if (font == null || text == null || start < 0 || limit < start || limit > text.length) {
throw new IllegalArgumentException();
}
this.font = font;
this.text = text;
this.start = start;
this.limit = limit;
this.mapper = (CompositeGlyphMapper)font.getMapper();
this.slot = -1;
this.pos = start;
}
public PhysicalFont getFont() {
return slot == -1 ? null : font.getSlotFont(slot);
}
public int getGlyphMask() {
return slot << 24;
}
public int getPos() {
return pos;
}
/*
* characters that are in the 'common' script become part of the
* surrounding script run. we want to fetch these from the same font
* used to get surrounding characters, where possible. but we don't
* want to force non-common characters to come from other than their
* standard font.
*
* what we really want to do is this:
* 1) fetch a code point from the text.
* 2) get its 'native' script code
* 3) determine its 'resolved' script code
* 4) if its native script is COMMON, and its resolved script is the same as the previous
* code point's, then see if the previous font supports this code point. if so, use it.
* 5) otherwise resolve the font as usual
* 6) break the run when either the physical font or the resolved script changes.
*
* problems: we optimize latin-1 and cjk text assuming a fixed
* width for each character. since latin-1 digits and punctuation
* are common, following this algorithm they will change to match
* the fonts used for the preceding text, and potentially change metrics.
*
* this also seems to have the potential for changing arbitrary runs of text, e.g.
* any number of digits and spaces can change depending on the preceding (or following!)
* non-COMMON character's font assignment. this is not good.
*
* since the goal is to enable layout to be performed using as few physical fonts as
* possible, and the primary cause of switching fonts is to handle spaces, perhaps
* we should just special-case spaces and assign them from the current font, whatever
* it may be.
*
* One could also argue that the job of the composite font is to assign physical fonts
* to text runs, however it wishes. we don't necessarily have to provide script info
* to let it do this. it can determine based on whatever. so having a special 'next'
* function that takes script (and limit) is redundant. It can fetch the script again
* if need be.
*
* both this and the script iterator are turning char sequences into code point
* sequences. maybe it would be better to feed a single code point into each iterator-- push
* the data instead of pull it?
*/
public boolean next(int script, int lim) {
if (pos == lim) {
return false;
}
int ch = nextCodePoint(lim);
int sl = mapper.charToGlyph(ch) & CompositeGlyphMapper.SLOTMASK;
slot = sl >>> 24;
while ((ch = nextCodePoint(lim)) != DONE && (mapper.charToGlyph(ch) & CompositeGlyphMapper.SLOTMASK) == sl);
pushback(ch);
return true;
}
public boolean next() {
return next(Script.COMMON, limit);
}
static final int SURROGATE_START = 0x10000;
static final int LEAD_START = 0xd800;
static final int LEAD_LIMIT = 0xdc00;
static final int TAIL_START = 0xdc00;
static final int TAIL_LIMIT = 0xe000;
static final int LEAD_SURROGATE_SHIFT = 10;
static final int SURROGATE_OFFSET = SURROGATE_START - (LEAD_START << LEAD_SURROGATE_SHIFT) - TAIL_START;
static final int DONE = -1;
final int nextCodePoint() {
return nextCodePoint(limit);
}
final int nextCodePoint(int lim) {
if (pos >= lim) {
return DONE;
}
int ch = text[pos++];
if (ch >= LEAD_START && ch < LEAD_LIMIT && pos < lim) {
int nch = text[pos];
if (nch >= TAIL_START && nch < TAIL_LIMIT) {
++pos;
ch = (ch << LEAD_SURROGATE_SHIFT) + nch + SURROGATE_OFFSET;
}
}
return ch;
}
final void pushback(int ch) {
if (ch >= 0) {
if (ch >= 0x10000) {
pos -= 2;
} else {
pos -= 1;
}
}
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (c) 2007, 2011, 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.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
/* FontScaler is "internal interface" to font rasterizer library.
*
* Access to native rasterizers without going through this interface is
* strongly discouraged. In particular, this is important because native
* data could be disposed due to runtime font processing error at any time.
*
* FontScaler represents combination of particular rasterizer implementation
* and particular font. It does not include rasterization attributes such as
* transform. These attributes are part of native scalerContext object.
* This approach allows to share same scaler for different requests related
* to the same font file.
*
* Note that scaler may throw FontScalerException on any operation.
* Generally this means that runtime error had happened and scaler is not
* usable. Subsequent calls to this scaler should not cause crash but will
* likely cause exceptions to be thrown again.
*
* It is recommended that callee should replace its reference to the scaler
* with something else. For instance it could be FontManager.getNullScaler().
* Note that NullScaler is trivial and will not actually rasterize anything.
*
* Alternatively, callee can use more sophisticated error recovery strategies
* and for instance try to substitute failed scaler with new scaler instance
* using another font.
*
* Note that in case of error there is no need to call dispose(). Moreover,
* dispose() generally is called by Disposer thread and explicit calls to
* dispose might have unexpected sideeffects because scaler can be shared.
*
* Current disposing logic is the following:
* - scaler is registered in the Disposer by the FontManager (on creation)
* - scalers are disposed when associated Font2D object (e.g. TruetypeFont)
* is garbage collected. That's why this object implements DisposerRecord
* interface directly (as it is not used as indicator when it is safe
* to release native state) and that's why we have to use WeakReference
* to Font internally.
* - Majority of Font2D objects are linked from various mapping arrays
* (e.g. FontManager.localeFullNamesToFont). So, they are not collected.
* This logic only works for fonts created with Font.createFont()
*
* Notes:
* - Eventually we may consider releasing some of the scaler resources if
* it was not used for a while but we do not want to be too aggressive on
* this (and this is probably more important for Type1 fonts).
*/
public abstract class FontScaler implements DisposerRecord {
private static FontScaler nullScaler = null;
private static Constructor<FontScaler> scalerConstructor = null;
//Find preferred font scaler
//
//NB: we can allow property based preferences
// (theoretically logic can be font type specific)
static {
Class scalerClass = null;
Class arglst[] = new Class[] {Font2D.class, int.class,
boolean.class, int.class};
try {
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
} catch (ClassNotFoundException e) {
scalerClass = NullFontScaler.class;
}
//NB: rewrite using factory? constructor is ugly way
try {
scalerConstructor = scalerClass.getConstructor(arglst);
} catch (NoSuchMethodException e) {
//should not happen
}
}
/* This is the only place to instantiate new FontScaler.
* Therefore this is very convinient place to register
* scaler with Disposer as well as trigger deregistring bad font
* in case when scaler reports this.
*/
public static FontScaler getScaler(Font2D font,
int indexInCollection,
boolean supportsCJK,
int filesize) {
FontScaler scaler = null;
try {
Object args[] = new Object[] {font, indexInCollection,
supportsCJK, filesize};
scaler = scalerConstructor.newInstance(args);
Disposer.addObjectRecord(font, scaler);
} catch (Throwable e) {
scaler = nullScaler;
//if we can not instantiate scaler assume bad font
//NB: technically it could be also because of internal scaler
// error but here we are assuming scaler is ok.
FontManager fm = FontManagerFactory.getInstance();
fm.deRegisterBadFont(font);
}
return scaler;
}
/*
* At the moment it is harmless to create 2 null scalers so, technically,
* syncronized keyword is not needed.
*
* But it is safer to keep it to avoid subtle problems if we will be adding
* checks like whether scaler is null scaler.
*/
public static synchronized FontScaler getNullScaler() {
if (nullScaler == null) {
nullScaler = new NullFontScaler();
}
return nullScaler;
}
protected WeakReference<Font2D> font = null;
protected long nativeScaler = 0; //used by decendants
//that have native state
protected boolean disposed = false;
abstract StrikeMetrics getFontMetrics(long pScalerContext)
throws FontScalerException;
abstract float getGlyphAdvance(long pScalerContext, int glyphCode)
throws FontScalerException;
abstract void getGlyphMetrics(long pScalerContext, int glyphCode,
Point2D.Float metrics)
throws FontScalerException;
/*
* Returns pointer to native GlyphInfo object.
* Callee is responsible for freeing this memory.
*
* Note:
* currently this method has to return not 0L but pointer to valid
* GlyphInfo object. Because Strike and drawing releated logic does
* expect that.
* In the future we may want to rework this to allow 0L here.
*/
abstract long getGlyphImage(long pScalerContext, int glyphCode)
throws FontScalerException;
abstract Rectangle2D.Float getGlyphOutlineBounds(long pContext,
int glyphCode)
throws FontScalerException;
abstract GeneralPath getGlyphOutline(long pScalerContext, int glyphCode,
float x, float y)
throws FontScalerException;
abstract GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs,
int numGlyphs, float x, float y)
throws FontScalerException;
/* Used by Java2D disposer to ensure native resources are released.
Note: this method does not release any of created
scaler context objects! */
public void dispose() {}
/**
* Used when the native resources held by the scaler need
* to be released before the 2D disposer runs.
*/
public void disposeScaler() {}
/* At the moment these 3 methods are needed for Type1 fonts only.
* For Truetype fonts we extract required info outside of scaler
* on java layer.
*/
abstract int getNumGlyphs() throws FontScalerException;
abstract int getMissingGlyphCode() throws FontScalerException;
abstract int getGlyphCode(char charCode) throws FontScalerException;
/* This method returns table cache used by native layout engine.
* This cache is essentially just small collection of
* pointers to various truetype tables. See definition of TTLayoutTableCache
* in the fontscalerdefs.h for more details.
*
* Note that tables themselves have same format as defined in the truetype
* specification, i.e. font scaler do not need to perform any preprocessing.
*
* Probably it is better to have API to request pointers to each table
* separately instead of requesting pointer to some native structure.
* (then there is not need to share its definition by different
* implementations of scaler).
* However, this means multiple JNI calls and potential impact on performance.
*
* Note: return value 0 is legal.
* This means tables are not available (e.g. type1 font).
*/
abstract long getLayoutTableCache() throws FontScalerException;
/* Used by the OpenType engine for mark positioning. */
abstract Point2D.Float getGlyphPoint(long pScalerContext,
int glyphCode, int ptNumber)
throws FontScalerException;
abstract long getUnitsPerEm();
/* Returns pointer to native structure describing rasterization attributes.
Format of this structure is scaler-specific.
Callee is responsible for freeing scaler context (using free()).
Note:
Context is tightly associated with strike and it is actually
freed when corresponding strike is being released.
*/
abstract long createScalerContext(double[] matrix,
int aa, int fm,
float boldness, float italic,
boolean disableHinting);
/* Marks context as invalid because native scaler is invalid.
Notes:
- pointer itself is still valid and has to be released
- if pointer to native scaler was cached it
should not be neither disposed nor used.
it is very likely it is already disposed by this moment. */
abstract void invalidateScalerContext(long ppScalerContext);
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2007, 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;
public class FontScalerException extends Exception {
public FontScalerException() {
super("Font scaler encountered runtime problem.");
}
public FontScalerException(String reason) {
super (reason);
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2003, 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.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Point2D;
public abstract class FontStrike {
protected FontStrikeDisposer disposer;
protected FontStrikeDesc desc;
protected StrikeMetrics strikeMetrics;
protected boolean algoStyle = false;
protected float boldness = 1f;
protected float italic = 0f;
/*
* lastLookupTime is updated by Font2D.getStrike and can be used to
* choose strikes that have not been newly referenced for purging when
* memory usage gets too high. Active strikes will never be purged
* because purging is via GC of WeakReferences.
*/
//protected long lastlookupTime/* = System.currentTimeMillis()*/;
public abstract int getNumGlyphs();
abstract StrikeMetrics getFontMetrics();
abstract void getGlyphImagePtrs(int[] glyphCodes, long[] images,int len);
abstract long getGlyphImagePtr(int glyphcode);
// pt, result in device space
abstract void getGlyphImageBounds(int glyphcode,
Point2D.Float pt,
Rectangle result);
abstract Point2D.Float getGlyphMetrics(int glyphcode);
abstract Point2D.Float getCharMetrics(char ch);
abstract float getGlyphAdvance(int glyphCode);
abstract float getCodePointAdvance(int cp);
abstract Rectangle2D.Float getGlyphOutlineBounds(int glyphCode);
abstract GeneralPath
getGlyphOutline(int glyphCode, float x, float y);
abstract GeneralPath
getGlyphVectorOutline(int[] glyphs, float x, float y);
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright (c) 2003, 2005, 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.Font;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import static sun.awt.SunHints.*;
/*
* This class encapsulates every thing needed that distinguishes a strike.
* It can be used as a key to locate a FontStrike in a Hashmap/cache.
* It is not mutatable, but contains mutatable AffineTransform objects,
* which for performance reasons it does not keep private copies of.
* Therefore code constructing these must pass in transforms it guarantees
* not to mutate.
*/
public class FontStrikeDesc {
/* Values to use as a mask that is used for faster comparison of
* two strikes using just an int equality test.
* The ones we don't use are listed here but commented out.
* ie style is already built and hint "OFF" values are zero.
* Note that this is used as a strike key and the same strike is used
* for HRGB and HBGR, so only the orientation needed (H or V) is needed
* to construct and distinguish a FontStrikeDesc. The rgb ordering
* needed for rendering is stored in the graphics state.
*/
// static final int STYLE_PLAIN = Font.PLAIN; // 0x0000
// static final int STYLE_BOLD = Font.BOLD; // 0x0001
// static final int STYLE_ITALIC = Font.ITALIC; // 0x0002
// static final int STYLE_BOLDITALIC = Font.BOLD|Font.ITALIC; // 0x0003
// static final int AA_OFF = 0x0000;
static final int AA_ON = 0x0010;
static final int AA_LCD_H = 0x0020;
static final int AA_LCD_V = 0x0040;
// static final int FRAC_METRICS_OFF = 0x0000;
static final int FRAC_METRICS_ON = 0x0100;
static final int FRAC_METRICS_SP = 0x0200;
/* devTx is to get an inverse transform to get user space values
* for metrics. Its not used otherwise, as the glyphTx is the important
* one. But it does mean that a strike representing a 6pt font and identity
* graphics transform is not equal to one for a 12 pt font and 2x scaled
* graphics transform. Its likely to be very rare that this causes
* duplication.
*/
AffineTransform devTx;
AffineTransform glyphTx; // all of ptSize, Font tx and Graphics tx.
int style;
int aaHint;
int fmHint;
private int hashCode;
private int valuemask;
public int hashCode() {
/* Can cache hashcode since a strike(desc) is immutable.*/
if (hashCode == 0) {
hashCode = glyphTx.hashCode() + devTx.hashCode() + valuemask;
}
return hashCode;
}
public boolean equals(Object obj) {
try {
FontStrikeDesc desc = (FontStrikeDesc)obj;
return (desc.valuemask == this.valuemask &&
desc.glyphTx.equals(this.glyphTx) &&
desc.devTx.equals(this.devTx));
} catch (Exception e) {
/* class cast or NP exceptions should not happen often, if ever,
* and I am hoping that this is faster than an instanceof check.
*/
return false;
}
}
FontStrikeDesc() {
// used with init
}
/* This maps a public text AA hint value into one of the subset of values
* used to index strikes. For the purpose of the strike cache there are
* only 4 values : OFF, ON, LCD_HRGB, LCD_VRGB.
* Font and ptSize are needed to resolve the 'gasp' table. The ptSize
* must therefore include device and font transforms.
*/
public static int getAAHintIntVal(Object aa, Font2D font2D, int ptSize) {
if (FontUtilities.isMacOSX14 &&
(aa == VALUE_TEXT_ANTIALIAS_OFF ||
aa == VALUE_TEXT_ANTIALIAS_DEFAULT ||
aa == VALUE_TEXT_ANTIALIAS_ON ||
aa == VALUE_TEXT_ANTIALIAS_GASP))
{
return INTVAL_TEXT_ANTIALIAS_ON;
}
if (aa == VALUE_TEXT_ANTIALIAS_OFF ||
aa == VALUE_TEXT_ANTIALIAS_DEFAULT) {
return INTVAL_TEXT_ANTIALIAS_OFF;
} else if (aa == VALUE_TEXT_ANTIALIAS_ON) {
return INTVAL_TEXT_ANTIALIAS_ON;
} else if (aa == VALUE_TEXT_ANTIALIAS_GASP) {
if (font2D.useAAForPtSize(ptSize)) {
return INTVAL_TEXT_ANTIALIAS_ON;
} else {
return INTVAL_TEXT_ANTIALIAS_OFF;
}
} else if (aa == VALUE_TEXT_ANTIALIAS_LCD_HRGB ||
aa == VALUE_TEXT_ANTIALIAS_LCD_HBGR) {
return INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
} else if (aa == VALUE_TEXT_ANTIALIAS_LCD_VRGB ||
aa == VALUE_TEXT_ANTIALIAS_LCD_VBGR) {
return INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
} else {
return INTVAL_TEXT_ANTIALIAS_OFF;
}
}
/* This maps a public text AA hint value into one of the subset of values
* used to index strikes. For the purpose of the strike cache there are
* only 4 values : OFF, ON, LCD_HRGB, LCD_VRGB.
* Font and FontRenderContext are needed to resolve the 'gasp' table.
* This is similar to the method above, but used by callers which have not
* already calculated the glyph device point size.
*/
public static int getAAHintIntVal(Font2D font2D, Font font,
FontRenderContext frc) {
Object aa = frc.getAntiAliasingHint();
if (FontUtilities.isMacOSX14 &&
(aa == VALUE_TEXT_ANTIALIAS_OFF ||
aa == VALUE_TEXT_ANTIALIAS_DEFAULT ||
aa == VALUE_TEXT_ANTIALIAS_ON ||
aa == VALUE_TEXT_ANTIALIAS_GASP))
{
return INTVAL_TEXT_ANTIALIAS_ON;
}
if (aa == VALUE_TEXT_ANTIALIAS_OFF ||
aa == VALUE_TEXT_ANTIALIAS_DEFAULT) {
return INTVAL_TEXT_ANTIALIAS_OFF;
} else if (aa == VALUE_TEXT_ANTIALIAS_ON) {
return INTVAL_TEXT_ANTIALIAS_ON;
} else if (aa == VALUE_TEXT_ANTIALIAS_GASP) {
/* FRC.isIdentity() would have been useful */
int ptSize;
AffineTransform tx = frc.getTransform();
if (tx.isIdentity() && !font.isTransformed()) {
ptSize = font.getSize();
} else {
/* one or both transforms is not identity */
float size = font.getSize2D();
if (tx.isIdentity()) {
tx = font.getTransform();
tx.scale(size, size);
} else {
tx.scale(size, size);
if (font.isTransformed()) {
tx.concatenate(font.getTransform());
}
}
double shearx = tx.getShearX();
double scaley = tx.getScaleY();
if (shearx != 0) {
scaley = Math.sqrt(shearx * shearx + scaley * scaley);
}
ptSize = (int)(Math.abs(scaley)+0.5);
}
if (font2D.useAAForPtSize(ptSize)) {
return INTVAL_TEXT_ANTIALIAS_ON;
} else {
return INTVAL_TEXT_ANTIALIAS_OFF;
}
} else if (aa == VALUE_TEXT_ANTIALIAS_LCD_HRGB ||
aa == VALUE_TEXT_ANTIALIAS_LCD_HBGR) {
return INTVAL_TEXT_ANTIALIAS_LCD_HRGB;
} else if (aa == VALUE_TEXT_ANTIALIAS_LCD_VRGB ||
aa == VALUE_TEXT_ANTIALIAS_LCD_VBGR) {
return INTVAL_TEXT_ANTIALIAS_LCD_VRGB;
} else {
return INTVAL_TEXT_ANTIALIAS_OFF;
}
}
public static int getFMHintIntVal(Object fm) {
if (fm == VALUE_FRACTIONALMETRICS_OFF ||
fm == VALUE_FRACTIONALMETRICS_DEFAULT) {
return INTVAL_FRACTIONALMETRICS_OFF;
} else {
return INTVAL_FRACTIONALMETRICS_ON;
}
}
public FontStrikeDesc(AffineTransform devAt, AffineTransform at,
int fStyle, int aa, int fm) {
devTx = devAt;
glyphTx = at; // not cloning glyphTx. Callers trusted to not mutate it.
style = fStyle;
aaHint = aa;
fmHint = fm;
valuemask = fStyle;
switch (aa) {
case INTVAL_TEXT_ANTIALIAS_OFF :
break;
case INTVAL_TEXT_ANTIALIAS_ON :
valuemask |= AA_ON;
break;
case INTVAL_TEXT_ANTIALIAS_LCD_HRGB :
case INTVAL_TEXT_ANTIALIAS_LCD_HBGR :
valuemask |= AA_LCD_H;
break;
case INTVAL_TEXT_ANTIALIAS_LCD_VRGB :
case INTVAL_TEXT_ANTIALIAS_LCD_VBGR :
valuemask |= AA_LCD_V;
break;
default: break;
}
if (fm == INTVAL_FRACTIONALMETRICS_ON) {
valuemask |= FRAC_METRICS_ON;
}
}
FontStrikeDesc(FontStrikeDesc desc) {
devTx = desc.devTx;
// Clone the TX in this case as this is called when its known
// that "desc" is being re-used by its creator.
glyphTx = (AffineTransform)desc.glyphTx.clone();
style = desc.style;
aaHint = desc.aaHint;
fmHint = desc.fmHint;
hashCode = desc.hashCode;
valuemask = desc.valuemask;
}
public String toString() {
return "FontStrikeDesc: Style="+style+ " AA="+aaHint+ " FM="+fmHint+
" devTx="+devTx+ " devTx.FontTx.ptSize="+glyphTx;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2003, 2006, 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.Reference;
import java.util.concurrent.ConcurrentHashMap;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
/*
* This keeps track of data that needs to be cleaned up once a
* strike is freed.
* a) The native memory that is the glyph image cache.
* b) removing the "desc" key from the strike's map.
* This is safe to do because this disposer is invoked only when the
* reference object has been cleared, which means the value indexed by
* this key is just an empty reference object.
* It is possible that a new FontStrike has been created that would
* be referenced by the same (equals) key. If it is placed in the map
* before this disposer is executed, then we do not want to remove that
* object. We should only remove an object where the value is null.
* So we first verify that the key still points to a cleared reference.
* Updates to the map thus need to be synchronized.
*
* A WeakHashmap will automatically clean up, but we might maintain a
* reference to the "desc" key in the FontStrike (value) which would
* prevent the keys from being discarded. And since the strike is the only
* place is likely we would maintain such a strong reference, then the map
* entries would be removed much more promptly than we need.
*/
class FontStrikeDisposer
implements DisposerRecord, Disposer.PollDisposable {
ConcurrentHashMap<FontStrikeDesc, Reference> strikeCache;
FontStrikeDesc desc;
long[] longGlyphImages;
int [] intGlyphImages;
int [][] segIntGlyphImages;
long[][] segLongGlyphImages;
long pScalerContext = 0L;
boolean disposed = false;
boolean comp = false;
public FontStrikeDisposer(Font2D font2D, FontStrikeDesc desc,
long pContext, int[] images) {
this.strikeCache = font2D.strikeCache;
this.desc = desc;
this.pScalerContext = pContext;
this.intGlyphImages = images;
}
public FontStrikeDisposer(Font2D font2D, FontStrikeDesc desc,
long pContext, long[] images) {
this.strikeCache = font2D.strikeCache;
this.desc = desc;
this.pScalerContext = pContext;
this.longGlyphImages = images;
}
public FontStrikeDisposer(Font2D font2D, FontStrikeDesc desc,
long pContext) {
this.strikeCache = font2D.strikeCache;
this.desc = desc;
this.pScalerContext = pContext;
}
public FontStrikeDisposer(Font2D font2D, FontStrikeDesc desc) {
this.strikeCache = font2D.strikeCache;
this.desc = desc;
this.comp = true;
}
public synchronized void dispose() {
if (!disposed) {
Reference<FontStrike> ref = strikeCache.get(desc);
if (ref != null) {
Object o = ref.get();
if (o == null) {
strikeCache.remove(desc);
}
}
StrikeCache.disposeStrike(this);
disposed = true;
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2015, 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;
/**
* Interface that indicates a Font2D that is not a Composite but has the
* property that it internally behaves like one, substituting glyphs
* from another font at render time.
* In this case the Font must provide a way to behave like a regular
* composite when that behaviour is not wanted.
*/
public interface FontSubstitution {
public CompositeFont getCompositeFont2D();
}

View File

@@ -0,0 +1,536 @@
/*
* Copyright (c) 2008, 2013, 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.Font;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.swing.plaf.FontUIResource;
import sun.util.logging.PlatformLogger;
/**
* A collection of utility methods.
*/
public final class FontUtilities {
public static boolean isSolaris;
public static boolean isLinux;
public static boolean isMacOSX;
public static boolean isMacOSX14;
public static boolean isSolaris8;
public static boolean isSolaris9;
public static boolean isOpenSolaris;
public static boolean useT2K;
public static boolean isWindows;
public static boolean isOpenJDK;
static final String LUCIDA_FILE_NAME = "LucidaSansRegular.ttf";
private static boolean debugFonts = false;
private static PlatformLogger logger = null;
private static boolean logging;
// This static initializer block figures out the OS constants.
static {
AccessController.doPrivileged(new PrivilegedAction () {
public Object run() {
String osName = System.getProperty("os.name", "unknownOS");
isSolaris = osName.startsWith("SunOS");
isLinux = osName.startsWith("Linux");
isMacOSX = osName.contains("OS X"); // TODO: MacOSX
if (isMacOSX) {
// os.version has values like 10.13.6, 10.14.6
// If it is not positively recognised as 10.13 or less,
// assume it means 10.14 or some later version.
isMacOSX14 = true;
String version = System.getProperty("os.version", "");
if (version.startsWith("10.")) {
version = version.substring(3);
int periodIndex = version.indexOf('.');
if (periodIndex != -1) {
version = version.substring(0, periodIndex);
}
try {
int v = Integer.parseInt(version);
isMacOSX14 = (v >= 14);
} catch (NumberFormatException e) {
}
}
}
String t2kStr = System.getProperty("sun.java2d.font.scaler");
if (t2kStr != null) {
useT2K = "t2k".equals(t2kStr);
} else {
useT2K = false;
}
if (isSolaris) {
String version = System.getProperty("os.version", "0.0");
isSolaris8 = version.startsWith("5.8");
isSolaris9 = version.startsWith("5.9");
float ver = Float.parseFloat(version);
if (ver > 5.10f) {
File f = new File("/etc/release");
String line = null;
try {
FileInputStream fis = new FileInputStream(f);
InputStreamReader isr = new InputStreamReader(
fis, "ISO-8859-1");
BufferedReader br = new BufferedReader(isr);
line = br.readLine();
fis.close();
} catch (Exception ex) {
// Nothing to do here.
}
if (line != null && line.indexOf("OpenSolaris") >= 0) {
isOpenSolaris = true;
} else {
isOpenSolaris = false;
}
} else {
isOpenSolaris = false;
}
} else {
isSolaris8 = false;
isSolaris9 = false;
isOpenSolaris = false;
}
isWindows = osName.startsWith("Windows");
String jreLibDirName = System.getProperty("java.home", "")
+ File.separator + "lib";
String jreFontDirName =
jreLibDirName + File.separator + "fonts";
File lucidaFile = new File(jreFontDirName + File.separator
+ LUCIDA_FILE_NAME);
isOpenJDK = !lucidaFile.exists();
String debugLevel =
System.getProperty("sun.java2d.debugfonts");
if (debugLevel != null && !debugLevel.equals("false")) {
debugFonts = true;
logger = PlatformLogger.getLogger("sun.java2d");
if (debugLevel.equals("warning")) {
logger.setLevel(PlatformLogger.Level.WARNING);
} else if (debugLevel.equals("severe")) {
logger.setLevel(PlatformLogger.Level.SEVERE);
}
}
if (debugFonts) {
logger = PlatformLogger.getLogger("sun.java2d");
logging = logger.isEnabled();
}
return null;
}
});
}
/**
* Referenced by code in the JDK which wants to test for the
* minimum char code for which layout may be required.
* Note that even basic latin text can benefit from ligatures,
* eg "ffi" but we presently apply those only if explicitly
* requested with TextAttribute.LIGATURES_ON.
* The value here indicates the lowest char code for which failing
* to invoke layout would prevent acceptable rendering.
*/
public static final int MIN_LAYOUT_CHARCODE = 0x0300;
/**
* Referenced by code in the JDK which wants to test for the
* maximum char code for which layout may be required.
* Note this does not account for supplementary characters
* where the caller interprets 'layout' to mean any case where
* one 'char' (ie the java type char) does not map to one glyph
*/
public static final int MAX_LAYOUT_CHARCODE = 0x206F;
/**
* Calls the private getFont2D() method in java.awt.Font objects.
*
* @param font the font object to call
*
* @return the Font2D object returned by Font.getFont2D()
*/
public static Font2D getFont2D(Font font) {
return FontAccess.getFontAccess().getFont2D(font);
}
/**
* If there is anything in the text which triggers a case
* where char->glyph does not map 1:1 in straightforward
* left->right ordering, then this method returns true.
* Scripts which might require it but are not treated as such
* due to JDK implementations will not return true.
* ie a 'true' return is an indication of the treatment by
* the implementation.
* Whether supplementary characters should be considered is dependent
* on the needs of the caller. Since this method accepts the 'char' type
* then such chars are always represented by a pair. From a rendering
* perspective these will all (in the cases I know of) still be one
* unicode character -> one glyph. But if a caller is using this to
* discover any case where it cannot make naive assumptions about
* the number of chars, and how to index through them, then it may
* need the option to have a 'true' return in such a case.
*/
public static boolean isComplexText(char [] chs, int start, int limit) {
for (int i = start; i < limit; i++) {
if (chs[i] < MIN_LAYOUT_CHARCODE) {
continue;
}
else if (isNonSimpleChar(chs[i])) {
return true;
}
}
return false;
}
/* This is almost the same as the method above, except it takes a
* char which means it may include undecoded surrogate pairs.
* The distinction is made so that code which needs to identify all
* cases in which we do not have a simple mapping from
* char->unicode character->glyph can be be identified.
* For example measurement cannot simply sum advances of 'chars',
* the caret in editable text cannot advance one 'char' at a time, etc.
* These callers really are asking for more than whether 'layout'
* needs to be run, they need to know if they can assume 1->1
* char->glyph mapping.
*/
public static boolean isNonSimpleChar(char ch) {
return
isComplexCharCode(ch) ||
(ch >= CharToGlyphMapper.HI_SURROGATE_START &&
ch <= CharToGlyphMapper.LO_SURROGATE_END);
}
/* If the character code falls into any of a number of unicode ranges
* where we know that simple left->right layout mapping chars to glyphs
* 1:1 and accumulating advances is going to produce incorrect results,
* we want to know this so the caller can use a more intelligent layout
* approach. A caller who cares about optimum performance may want to
* check the first case and skip the method call if its in that range.
* Although there's a lot of tests in here, knowing you can skip
* CTL saves a great deal more. The rest of the checks are ordered
* so that rather than checking explicitly if (>= start & <= end)
* which would mean all ranges would need to be checked so be sure
* CTL is not needed, the method returns as soon as it recognises
* the code point is outside of a CTL ranges.
* NOTE: Since this method accepts an 'int' it is asssumed to properly
* represent a CHARACTER. ie it assumes the caller has already
* converted surrogate pairs into supplementary characters, and so
* can handle this case and doesn't need to be told such a case is
* 'complex'.
*/
public static boolean isComplexCharCode(int code) {
if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) {
return false;
}
else if (code <= 0x036f) {
// Trigger layout for combining diacriticals 0x0300->0x036f
return true;
}
else if (code < 0x0590) {
// No automatic layout for Greek, Cyrillic, Armenian.
return false;
}
else if (code <= 0x06ff) {
// Hebrew 0590 - 05ff
// Arabic 0600 - 06ff
return true;
}
else if (code < 0x0900) {
return false; // Syriac and Thaana
}
else if (code <= 0x0e7f) {
// if Indic, assume shaping for conjuncts, reordering:
// 0900 - 097F Devanagari
// 0980 - 09FF Bengali
// 0A00 - 0A7F Gurmukhi
// 0A80 - 0AFF Gujarati
// 0B00 - 0B7F Oriya
// 0B80 - 0BFF Tamil
// 0C00 - 0C7F Telugu
// 0C80 - 0CFF Kannada
// 0D00 - 0D7F Malayalam
// 0D80 - 0DFF Sinhala
// 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks
return true;
}
else if (code < 0x0f00) {
return false;
}
else if (code <= 0x0fff) { // U+0F00 - U+0FFF Tibetan
return true;
}
else if (code < 0x1100) {
return false;
}
else if (code < 0x11ff) { // U+1100 - U+11FF Old Hangul
return true;
}
else if (code < 0x1780) {
return false;
}
else if (code <= 0x17ff) { // 1780 - 17FF Khmer
return true;
}
else if (code < 0x200c) {
return false;
}
else if (code <= 0x200d) { // zwj or zwnj
return true;
}
else if (code >= 0x202a && code <= 0x202e) { // directional control
return true;
}
else if (code >= 0x206a && code <= 0x206f) { // directional control
return true;
}
return false;
}
public static PlatformLogger getLogger() {
return logger;
}
public static boolean isLogging() {
return logging;
}
public static boolean debugFonts() {
return debugFonts;
}
// The following methods are used by Swing.
/* Revise the implementation to in fact mean "font is a composite font.
* This ensures that Swing components will always benefit from the
* fall back fonts
*/
public static boolean fontSupportsDefaultEncoding(Font font) {
return getFont2D(font) instanceof CompositeFont;
}
/**
* This method is provided for internal and exclusive use by Swing.
*
* It may be used in conjunction with fontSupportsDefaultEncoding(Font)
* In the event that a desktop properties font doesn't directly
* support the default encoding, (ie because the host OS supports
* adding support for the current locale automatically for native apps),
* then Swing calls this method to get a font which uses the specified
* font for the code points it covers, but also supports this locale
* just as the standard composite fonts do.
* Note: this will over-ride any setting where an application
* specifies it prefers locale specific composite fonts.
* The logic for this, is that this method is used only where the user or
* application has specified that the native L&F be used, and that
* we should honour that request to use the same font as native apps use.
*
* The behaviour of this method is to construct a new composite
* Font object that uses the specified physical font as its first
* component, and adds all the components of "dialog" as fall back
* components.
* The method currently assumes that only the size and style attributes
* are set on the specified font. It doesn't copy the font transform or
* other attributes because they aren't set on a font created from
* the desktop. This will need to be fixed if use is broadened.
*
* Operations such as Font.deriveFont will work properly on the
* font returned by this method for deriving a different point size.
* Additionally it tries to support a different style by calling
* getNewComposite() below. That also supports replacing slot zero
* with a different physical font but that is expected to be "rare".
* Deriving with a different style is needed because its been shown
* that some applications try to do this for Swing FontUIResources.
* Also operations such as new Font(font.getFontName(..), Font.PLAIN, 14);
* will NOT yield the same result, as the new underlying CompositeFont
* cannot be "looked up" in the font registry.
* This returns a FontUIResource as that is the Font sub-class needed
* by Swing.
* Suggested usage is something like :
* FontUIResource fuir;
* Font desktopFont = getDesktopFont(..);
* // NOTE even if fontSupportsDefaultEncoding returns true because
* // you get Tahoma and are running in an English locale, you may
* // still want to just call getCompositeFontUIResource() anyway
* // as only then will you get fallback fonts - eg for CJK.
* if (FontManager.fontSupportsDefaultEncoding(desktopFont)) {
* fuir = new FontUIResource(..);
* } else {
* fuir = FontManager.getCompositeFontUIResource(desktopFont);
* }
* return fuir;
*/
private static volatile
SoftReference<ConcurrentHashMap<PhysicalFont, CompositeFont>>
compMapRef = new SoftReference(null);
public static FontUIResource getCompositeFontUIResource(Font font) {
FontUIResource fuir = new FontUIResource(font);
Font2D font2D = FontUtilities.getFont2D(font);
if (!(font2D instanceof PhysicalFont)) {
/* Swing should only be calling this when a font is obtained
* from desktop properties, so should generally be a physical font,
* an exception might be for names like "MS Serif" which are
* automatically mapped to "Serif", so there's no need to do
* anything special in that case. But note that suggested usage
* is first to call fontSupportsDefaultEncoding(Font) and this
* method should not be called if that were to return true.
*/
return fuir;
}
FontManager fm = FontManagerFactory.getInstance();
Font2D dialog = fm.findFont2D("dialog", font.getStyle(), FontManager.NO_FALLBACK);
// Should never be null, but MACOSX fonts are not CompositeFonts
if (dialog == null || !(dialog instanceof CompositeFont)) {
return fuir;
}
CompositeFont dialog2D = (CompositeFont)dialog;
PhysicalFont physicalFont = (PhysicalFont)font2D;
ConcurrentHashMap<PhysicalFont, CompositeFont> compMap = compMapRef.get();
if (compMap == null) { // Its been collected.
compMap = new ConcurrentHashMap<PhysicalFont, CompositeFont>();
compMapRef = new SoftReference(compMap);
}
CompositeFont compFont = compMap.get(physicalFont);
if (compFont == null) {
compFont = new CompositeFont(physicalFont, dialog2D);
compMap.put(physicalFont, compFont);
}
FontAccess.getFontAccess().setFont2D(fuir, compFont.handle);
/* marking this as a created font is needed as only created fonts
* copy their creator's handles.
*/
FontAccess.getFontAccess().setCreatedFont(fuir);
return fuir;
}
/* A small "map" from GTK/fontconfig names to the equivalent JDK
* logical font name.
*/
private static final String[][] nameMap = {
{"sans", "sansserif"},
{"sans-serif", "sansserif"},
{"serif", "serif"},
{"monospace", "monospaced"}
};
public static String mapFcName(String name) {
for (int i = 0; i < nameMap.length; i++) {
if (name.equals(nameMap[i][0])) {
return nameMap[i][1];
}
}
return null;
}
/* This is called by Swing passing in a fontconfig family name
* such as "sans". In return Swing gets a FontUIResource instance
* that has queried fontconfig to resolve the font(s) used for this.
* Fontconfig will if asked return a list of fonts to give the largest
* possible code point coverage.
* For now we use only the first font returned by fontconfig, and
* back it up with the most closely matching JDK logical font.
* Essentially this means pre-pending what we return now with fontconfig's
* preferred physical font. This could lead to some duplication in cases,
* if we already included that font later. We probably should remove such
* duplicates, but it is not a significant problem. It can be addressed
* later as part of creating a Composite which uses more of the
* same fonts as fontconfig. At that time we also should pay more
* attention to the special rendering instructions fontconfig returns,
* such as whether we should prefer embedded bitmaps over antialiasing.
* There's no way to express that via a Font at present.
*/
public static FontUIResource getFontConfigFUIR(String fcFamily,
int style, int size) {
String mapped = mapFcName(fcFamily);
if (mapped == null) {
mapped = "sansserif";
}
FontUIResource fuir;
FontManager fm = FontManagerFactory.getInstance();
if (fm instanceof SunFontManager) {
SunFontManager sfm = (SunFontManager) fm;
fuir = sfm.getFontConfigFUIR(mapped, style, size);
} else {
fuir = new FontUIResource(mapped, style, size);
}
return fuir;
}
/**
* Used by windows printing to assess if a font is likely to
* be layout compatible with JDK
* TrueType fonts should be, but if they have no GPOS table,
* but do have a GSUB table, then they are probably older
* fonts GDI handles differently.
*/
public static boolean textLayoutIsCompatible(Font font) {
Font2D font2D = getFont2D(font);
if (font2D instanceof TrueTypeFont) {
TrueTypeFont ttf = (TrueTypeFont) font2D;
return
ttf.getDirectoryEntry(TrueTypeFont.GSUBTag) == null ||
ttf.getDirectoryEntry(TrueTypeFont.GPOSTag) != null;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,280 @@
/*
* Copyright (c) 2007, 2013, 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.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.ref.WeakReference;
/* This is Freetype based implementation of FontScaler.
*
* Note that in case of runtime error it is expected that
* native code will release all native resources and
* call invalidateScaler() (that will throw FontScalerException).
*
* Note that callee is responsible for releasing native scaler context.
*/
class FreetypeFontScaler extends FontScaler {
/* constants aligned with native code */
private static final int TRUETYPE_FONT = 1;
private static final int TYPE1_FONT = 2;
static {
/* At the moment fontmanager library depends on freetype library
and therefore no need to load it explicitly here */
FontManagerNativeLibrary.load();
initIDs(FreetypeFontScaler.class);
}
private static native void initIDs(Class FFS);
private void invalidateScaler() throws FontScalerException {
nativeScaler = 0;
font = null;
throw new FontScalerException();
}
public FreetypeFontScaler(Font2D font, int indexInCollection,
boolean supportsCJK, int filesize) {
int fonttype = TRUETYPE_FONT;
if (font instanceof Type1Font) {
fonttype = TYPE1_FONT;
}
nativeScaler = initNativeScaler(font,
fonttype,
indexInCollection,
supportsCJK,
filesize);
this.font = new WeakReference(font);
}
synchronized StrikeMetrics getFontMetrics(long pScalerContext)
throws FontScalerException {
if (nativeScaler != 0L) {
return getFontMetricsNative(font.get(),
pScalerContext,
nativeScaler);
}
return FontScaler.getNullScaler().getFontMetrics(0L);
}
synchronized float getGlyphAdvance(long pScalerContext, int glyphCode)
throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphAdvanceNative(font.get(),
pScalerContext,
nativeScaler,
glyphCode);
}
return FontScaler.getNullScaler().
getGlyphAdvance(0L, glyphCode);
}
synchronized void getGlyphMetrics(long pScalerContext,
int glyphCode, Point2D.Float metrics)
throws FontScalerException {
if (nativeScaler != 0L) {
getGlyphMetricsNative(font.get(),
pScalerContext,
nativeScaler,
glyphCode,
metrics);
return;
}
FontScaler.getNullScaler().
getGlyphMetrics(0L, glyphCode, metrics);
}
synchronized long getGlyphImage(long pScalerContext, int glyphCode)
throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphImageNative(font.get(),
pScalerContext,
nativeScaler,
glyphCode);
}
return FontScaler.getNullScaler().
getGlyphImage(0L, glyphCode);
}
synchronized Rectangle2D.Float getGlyphOutlineBounds(
long pScalerContext, int glyphCode)
throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphOutlineBoundsNative(font.get(),
pScalerContext,
nativeScaler,
glyphCode);
}
return FontScaler.getNullScaler().
getGlyphOutlineBounds(0L,glyphCode);
}
synchronized GeneralPath getGlyphOutline(
long pScalerContext, int glyphCode, float x, float y)
throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphOutlineNative(font.get(),
pScalerContext,
nativeScaler,
glyphCode,
x, y);
}
return FontScaler.getNullScaler().
getGlyphOutline(0L, glyphCode, x,y);
}
synchronized GeneralPath getGlyphVectorOutline(
long pScalerContext, int[] glyphs, int numGlyphs,
float x, float y) throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphVectorOutlineNative(font.get(),
pScalerContext,
nativeScaler,
glyphs,
numGlyphs,
x, y);
}
return FontScaler
.getNullScaler().getGlyphVectorOutline(0L, glyphs, numGlyphs, x, y);
}
synchronized long getLayoutTableCache() throws FontScalerException {
return getLayoutTableCacheNative(nativeScaler);
}
/* This method should not be called directly, in case
* it is being invoked from a thread with a native context.
*/
public synchronized void dispose() {
if (nativeScaler != 0L) {
disposeNativeScaler(font.get(), nativeScaler);
nativeScaler = 0L;
}
}
public synchronized void disposeScaler() {
if (nativeScaler != 0L) {
/*
* The current thread may be calling this method from the context
* of a JNI up-call. It will hold the native lock from the
* original down-call so can directly enter dispose and free
* the resources. So we need to schedule the disposal to happen
* only once we've returned from native. So by running the dispose
* on another thread which does nothing except that disposal we
* are sure that this is safe.
*/
new Thread(null, () -> dispose(), "free scaler", 0).start();
}
}
synchronized int getNumGlyphs() throws FontScalerException {
if (nativeScaler != 0L) {
return getNumGlyphsNative(nativeScaler);
}
return FontScaler.getNullScaler().getNumGlyphs();
}
synchronized int getMissingGlyphCode() throws FontScalerException {
if (nativeScaler != 0L) {
return getMissingGlyphCodeNative(nativeScaler);
}
return FontScaler.getNullScaler().getMissingGlyphCode();
}
synchronized int getGlyphCode(char charCode) throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphCodeNative(font.get(), nativeScaler, charCode);
}
return FontScaler.getNullScaler().getGlyphCode(charCode);
}
synchronized Point2D.Float getGlyphPoint(long pScalerContext,
int glyphCode, int ptNumber)
throws FontScalerException {
if (nativeScaler != 0L) {
return getGlyphPointNative(font.get(), pScalerContext,
nativeScaler, glyphCode, ptNumber);
}
return FontScaler.getNullScaler().getGlyphPoint(
pScalerContext, glyphCode, ptNumber);
}
synchronized long getUnitsPerEm() {
return getUnitsPerEMNative(nativeScaler);
}
synchronized long createScalerContext(double[] matrix,
int aa, int fm, float boldness, float italic,
boolean disableHinting) {
if (nativeScaler != 0L) {
return createScalerContextNative(nativeScaler, matrix,
aa, fm, boldness, italic);
}
return NullFontScaler.getNullScalerContext();
}
//Note: native methods can throw RuntimeException if processing fails
private native long initNativeScaler(Font2D font, int type,
int indexInCollection, boolean supportsCJK, int filesize);
private native StrikeMetrics getFontMetricsNative(Font2D font,
long pScalerContext, long pScaler);
private native float getGlyphAdvanceNative(Font2D font,
long pScalerContext, long pScaler, int glyphCode);
private native void getGlyphMetricsNative(Font2D font,
long pScalerContext, long pScaler,
int glyphCode, Point2D.Float metrics);
private native long getGlyphImageNative(Font2D font,
long pScalerContext, long pScaler, int glyphCode);
private native Rectangle2D.Float getGlyphOutlineBoundsNative(Font2D font,
long pScalerContext, long pScaler, int glyphCode);
private native GeneralPath getGlyphOutlineNative(Font2D font,
long pScalerContext, long pScaler,
int glyphCode, float x, float y);
private native GeneralPath getGlyphVectorOutlineNative(Font2D font,
long pScalerContext, long pScaler,
int[] glyphs, int numGlyphs, float x, float y);
private native Point2D.Float getGlyphPointNative(Font2D font,
long pScalerContext, long pScaler, int glyphCode, int ptNumber);
private native long getLayoutTableCacheNative(long pScaler);
private native void disposeNativeScaler(Font2D font2D, long pScaler);
private native int getGlyphCodeNative(Font2D font, long pScaler, char charCode);
private native int getNumGlyphsNative(long pScaler);
private native int getMissingGlyphCodeNative(long pScaler);
private native long getUnitsPerEMNative(long pScaler);
private native long createScalerContextNative(long pScaler, double[] matrix,
int aa, int fm, float boldness, float italic);
/* Freetype scaler context does not contain any pointers that
has to be invalidated if native scaler is bad */
void invalidateScalerContext(long pScalerContext) {}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2010, 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.util.*;
public interface GlyphDisposedListener {
public void glyphDisposed(ArrayList<Long> glyphs);
}

View File

@@ -0,0 +1,689 @@
/*
* Copyright (c) 2003, 2013, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1999-2003 - All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by IBM. These materials are provided
* under terms of a License Agreement between IBM and Sun.
* This technology is protected by multiple US and International
* patents. This notice and attribution to IBM may not be removed.
*/
/*
* GlyphLayout is used to process a run of text into a run of run of
* glyphs, optionally with position and char mapping info.
*
* The text has already been processed for numeric shaping and bidi.
* The run of text that layout works on has a single bidi level. It
* also has a single font/style. Some operations need context to work
* on (shaping, script resolution) so context for the text run text is
* provided. It is assumed that the text array contains sufficient
* context, and the offset and count delimit the portion of the text
* that needs to actually be processed.
*
* The font might be a composite font. Layout generally requires
* tables from a single physical font to operate, and so it must
* resolve the 'single' font run into runs of physical fonts.
*
* Some characters are supported by several fonts of a composite, and
* in order to properly emulate the glyph substitution behavior of a
* single physical font, these characters might need to be mapped to
* different physical fonts. The script code that is assigned
* characters normally considered 'common script' can be used to
* resolve which physical font to use for these characters. The input
* to the char to glyph mapper (which assigns physical fonts as it
* processes the glyphs) should include the script code, and the
* mapper should operate on runs of a single script.
*
* To perform layout, call get() to get a new (or reuse an old)
* GlyphLayout, call layout on it, then call done(GlyphLayout) when
* finished. There's no particular problem if you don't call done,
* but it assists in reuse of the GlyphLayout.
*/
package sun.font;
import java.lang.ref.SoftReference;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.Character.*;
public final class GlyphLayout {
// data for glyph vector
private GVData _gvdata;
// cached glyph layout data for reuse
private static volatile GlyphLayout cache; // reusable
private LayoutEngineFactory _lef; // set when get is called, unset when done is called
private TextRecord _textRecord; // the text we're working on, used by iterators
private ScriptRun _scriptRuns; // iterator over script runs
private FontRunIterator _fontRuns; // iterator over physical fonts in a composite
private int _ercount;
private ArrayList _erecords;
private Point2D.Float _pt;
private FontStrikeDesc _sd;
private float[] _mat;
private int _typo_flags;
private int _offset;
public static final class LayoutEngineKey {
private Font2D font;
private int script;
private int lang;
LayoutEngineKey() {
}
LayoutEngineKey(Font2D font, int script, int lang) {
init(font, script, lang);
}
void init(Font2D font, int script, int lang) {
this.font = font;
this.script = script;
this.lang = lang;
}
LayoutEngineKey copy() {
return new LayoutEngineKey(font, script, lang);
}
Font2D font() {
return font;
}
int script() {
return script;
}
int lang() {
return lang;
}
public boolean equals(Object rhs) {
if (this == rhs) return true;
if (rhs == null) return false;
try {
LayoutEngineKey that = (LayoutEngineKey)rhs;
return this.script == that.script &&
this.lang == that.lang &&
this.font.equals(that.font);
}
catch (ClassCastException e) {
return false;
}
}
public int hashCode() {
return script ^ lang ^ font.hashCode();
}
}
public static interface LayoutEngineFactory {
/**
* Given a font, script, and language, determine a layout engine to use.
*/
public LayoutEngine getEngine(Font2D font, int script, int lang);
/**
* Given a key, determine a layout engine to use.
*/
public LayoutEngine getEngine(LayoutEngineKey key);
}
public static interface LayoutEngine {
/**
* Given a strike descriptor, text, rtl flag, and starting point, append information about
* glyphs, positions, and character indices to the glyphvector data, and advance the point.
*
* If the GVData does not have room for the glyphs, throws an IndexOutOfBoundsException and
* leave pt and the gvdata unchanged.
*/
public void layout(FontStrikeDesc sd, float[] mat, int gmask,
int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data);
}
/**
* Return a new instance of GlyphLayout, using the provided layout engine factory.
* If null, the system layout engine factory will be used.
*/
public static GlyphLayout get(LayoutEngineFactory lef) {
if (lef == null) {
lef = SunLayoutEngine.instance();
}
GlyphLayout result = null;
synchronized(GlyphLayout.class) {
if (cache != null) {
result = cache;
cache = null;
}
}
if (result == null) {
result = new GlyphLayout();
}
result._lef = lef;
return result;
}
/**
* Return the old instance of GlyphLayout when you are done. This enables reuse
* of GlyphLayout objects.
*/
public static void done(GlyphLayout gl) {
gl._lef = null;
cache = gl; // object reference assignment is thread safe, it says here...
}
private static final class SDCache {
public Font key_font;
public FontRenderContext key_frc;
public AffineTransform dtx;
public AffineTransform invdtx;
public AffineTransform gtx;
public Point2D.Float delta;
public FontStrikeDesc sd;
private SDCache(Font font, FontRenderContext frc) {
key_font = font;
key_frc = frc;
// !!! add getVectorTransform and hasVectorTransform to frc? then
// we could just skip this work...
dtx = frc.getTransform();
dtx.setTransform(dtx.getScaleX(), dtx.getShearY(),
dtx.getShearX(), dtx.getScaleY(),
0, 0);
if (!dtx.isIdentity()) {
try {
invdtx = dtx.createInverse();
}
catch (NoninvertibleTransformException e) {
throw new InternalError(e);
}
}
float ptSize = font.getSize2D();
if (font.isTransformed()) {
gtx = font.getTransform();
gtx.scale(ptSize, ptSize);
delta = new Point2D.Float((float)gtx.getTranslateX(),
(float)gtx.getTranslateY());
gtx.setTransform(gtx.getScaleX(), gtx.getShearY(),
gtx.getShearX(), gtx.getScaleY(),
0, 0);
gtx.preConcatenate(dtx);
} else {
delta = ZERO_DELTA;
gtx = new AffineTransform(dtx);
gtx.scale(ptSize, ptSize);
}
/* Similar logic to that used in SunGraphics2D.checkFontInfo().
* Whether a grey (AA) strike is needed is size dependent if
* AA mode is 'gasp'.
*/
int aa =
FontStrikeDesc.getAAHintIntVal(frc.getAntiAliasingHint(),
FontUtilities.getFont2D(font),
(int)Math.abs(ptSize));
int fm = FontStrikeDesc.getFMHintIntVal
(frc.getFractionalMetricsHint());
sd = new FontStrikeDesc(dtx, gtx, font.getStyle(), aa, fm);
}
private static final Point2D.Float ZERO_DELTA = new Point2D.Float();
private static
SoftReference<ConcurrentHashMap<SDKey, SDCache>> cacheRef;
private static final class SDKey {
private final Font font;
private final FontRenderContext frc;
private final int hash;
SDKey(Font font, FontRenderContext frc) {
this.font = font;
this.frc = frc;
this.hash = font.hashCode() ^ frc.hashCode();
}
public int hashCode() {
return hash;
}
public boolean equals(Object o) {
try {
SDKey rhs = (SDKey)o;
return
hash == rhs.hash &&
font.equals(rhs.font) &&
frc.equals(rhs.frc);
}
catch (ClassCastException e) {
}
return false;
}
}
public static SDCache get(Font font, FontRenderContext frc) {
// It is possible a translation component will be in the FRC.
// It doesn't affect us except adversely as we would consider
// FRC's which are really the same to be different. If we
// detect a translation component, then we need to exclude it
// by creating a new transform which excludes the translation.
if (frc.isTransformed()) {
AffineTransform transform = frc.getTransform();
if (transform.getTranslateX() != 0 ||
transform.getTranslateY() != 0) {
transform = new AffineTransform(transform.getScaleX(),
transform.getShearY(),
transform.getShearX(),
transform.getScaleY(),
0, 0);
frc = new FontRenderContext(transform,
frc.getAntiAliasingHint(),
frc.getFractionalMetricsHint()
);
}
}
SDKey key = new SDKey(font, frc); // garbage, yuck...
ConcurrentHashMap<SDKey, SDCache> cache = null;
SDCache res = null;
if (cacheRef != null) {
cache = cacheRef.get();
if (cache != null) {
res = cache.get(key);
}
}
if (res == null) {
res = new SDCache(font, frc);
if (cache == null) {
cache = new ConcurrentHashMap<SDKey, SDCache>(10);
cacheRef = new
SoftReference<ConcurrentHashMap<SDKey, SDCache>>(cache);
} else if (cache.size() >= 512) {
cache.clear();
}
cache.put(key, res);
}
return res;
}
}
/**
* Create a glyph vector.
* @param font the font to use
* @param frc the font render context
* @param text the text, including optional context before start and after start + count
* @param offset the start of the text to lay out
* @param count the length of the text to lay out
* @param flags bidi and context flags {@see #java.awt.Font}
* @param result a StandardGlyphVector to modify, can be null
* @return the layed out glyphvector, if result was passed in, it is returned
*/
public StandardGlyphVector layout(Font font, FontRenderContext frc,
char[] text, int offset, int count,
int flags, StandardGlyphVector result)
{
if (text == null || offset < 0 || count < 0 || (count > text.length - offset)) {
throw new IllegalArgumentException();
}
init(count);
// need to set after init
// go through the back door for this
if (font.hasLayoutAttributes()) {
AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();
if (values.getKerning() != 0) _typo_flags |= 0x1;
if (values.getLigatures() != 0) _typo_flags |= 0x2;
}
_offset = offset;
// use cache now - can we use the strike cache for this?
SDCache txinfo = SDCache.get(font, frc);
_mat[0] = (float)txinfo.gtx.getScaleX();
_mat[1] = (float)txinfo.gtx.getShearY();
_mat[2] = (float)txinfo.gtx.getShearX();
_mat[3] = (float)txinfo.gtx.getScaleY();
_pt.setLocation(txinfo.delta);
int lim = offset + count;
int min = 0;
int max = text.length;
if (flags != 0) {
if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) {
_typo_flags |= 0x80000000; // RTL
}
if ((flags & Font.LAYOUT_NO_START_CONTEXT) != 0) {
min = offset;
}
if ((flags & Font.LAYOUT_NO_LIMIT_CONTEXT) != 0) {
max = lim;
}
}
int lang = -1; // default for now
Font2D font2D = FontUtilities.getFont2D(font);
if (font2D instanceof FontSubstitution) {
font2D = ((FontSubstitution)font2D).getCompositeFont2D();
}
_textRecord.init(text, offset, lim, min, max);
int start = offset;
if (font2D instanceof CompositeFont) {
_scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars
_fontRuns.init((CompositeFont)font2D, text, offset, lim);
while (_scriptRuns.next()) {
int limit = _scriptRuns.getScriptLimit();
int script = _scriptRuns.getScriptCode();
while (_fontRuns.next(script, limit)) {
Font2D pfont = _fontRuns.getFont();
/* layout can't deal with NativeFont instances. The
* native font is assumed to know of a suitable non-native
* substitute font. This currently works because
* its consistent with the way NativeFonts delegate
* in other cases too.
*/
if (pfont instanceof NativeFont) {
pfont = ((NativeFont)pfont).getDelegateFont();
}
int gmask = _fontRuns.getGlyphMask();
int pos = _fontRuns.getPos();
nextEngineRecord(start, pos, script, lang, pfont, gmask);
start = pos;
}
}
} else {
_scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars
while (_scriptRuns.next()) {
int limit = _scriptRuns.getScriptLimit();
int script = _scriptRuns.getScriptCode();
nextEngineRecord(start, limit, script, lang, font2D, 0);
start = limit;
}
}
int ix = 0;
int stop = _ercount;
int dir = 1;
if (_typo_flags < 0) { // RTL
ix = stop - 1;
stop = -1;
dir = -1;
}
// _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics());
_sd = txinfo.sd;
for (;ix != stop; ix += dir) {
EngineRecord er = (EngineRecord)_erecords.get(ix);
for (;;) {
try {
er.layout();
break;
}
catch (IndexOutOfBoundsException e) {
if (_gvdata._count >=0) {
_gvdata.grow();
}
}
}
// Break out of the outer for loop if layout fails.
if (_gvdata._count < 0) {
break;
}
}
// if (txinfo.invdtx != null) {
// _gvdata.adjustPositions(txinfo.invdtx);
// }
// If layout fails (negative glyph count) create an un-laid out GV instead.
// ie default positions. This will be a lot better than the alternative of
// a complete blank layout.
StandardGlyphVector gv;
if (_gvdata._count < 0) {
gv = new StandardGlyphVector(font, text, offset, count, frc);
if (FontUtilities.debugFonts()) {
FontUtilities.getLogger().warning("OpenType layout failed on font: " +
font);
}
} else {
gv = _gvdata.createGlyphVector(font, frc, result);
}
// System.err.println("Layout returns: " + gv);
return gv;
}
//
// private methods
//
private GlyphLayout() {
this._gvdata = new GVData();
this._textRecord = new TextRecord();
this._scriptRuns = new ScriptRun();
this._fontRuns = new FontRunIterator();
this._erecords = new ArrayList(10);
this._pt = new Point2D.Float();
this._sd = new FontStrikeDesc();
this._mat = new float[4];
}
private void init(int capacity) {
this._typo_flags = 0;
this._ercount = 0;
this._gvdata.init(capacity);
}
private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {
EngineRecord er = null;
if (_ercount == _erecords.size()) {
er = new EngineRecord();
_erecords.add(er);
} else {
er = (EngineRecord)_erecords.get(_ercount);
}
er.init(start, limit, font, script, lang, gmask);
++_ercount;
}
/**
* Storage for layout to build glyph vector data, then generate a real GlyphVector
*/
public static final class GVData {
public int _count; // number of glyphs, >= number of chars
public int _flags;
public int[] _glyphs;
public float[] _positions;
public int[] _indices;
private static final int UNINITIALIZED_FLAGS = -1;
public void init(int size) {
_count = 0;
_flags = UNINITIALIZED_FLAGS;
if (_glyphs == null || _glyphs.length < size) {
if (size < 20) {
size = 20;
}
_glyphs = new int[size];
_positions = new float[size * 2 + 2];
_indices = new int[size];
}
}
public void grow() {
grow(_glyphs.length / 4); // always grows because min length is 20
}
public void grow(int delta) {
int size = _glyphs.length + delta;
int[] nglyphs = new int[size];
System.arraycopy(_glyphs, 0, nglyphs, 0, _count);
_glyphs = nglyphs;
float[] npositions = new float[size * 2 + 2];
System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);
_positions = npositions;
int[] nindices = new int[size];
System.arraycopy(_indices, 0, nindices, 0, _count);
_indices = nindices;
}
public void adjustPositions(AffineTransform invdtx) {
invdtx.transform(_positions, 0, _positions, 0, _count);
}
public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {
// !!! default initialization until we let layout engines do it
if (_flags == UNINITIALIZED_FLAGS) {
_flags = 0;
if (_count > 1) { // if only 1 glyph assume LTR
boolean ltr = true;
boolean rtl = true;
int rtlix = _count; // rtl index
for (int i = 0; i < _count && (ltr || rtl); ++i) {
int cx = _indices[i];
ltr = ltr && (cx == i);
rtl = rtl && (cx == --rtlix);
}
if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;
if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;
}
// !!! layout engines need to tell us whether they performed
// position adjustments. currently they don't tell us, so
// we must assume they did
_flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;
}
int[] glyphs = new int[_count];
System.arraycopy(_glyphs, 0, glyphs, 0, _count);
float[] positions = null;
if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
positions = new float[_count * 2 + 2];
System.arraycopy(_positions, 0, positions, 0, positions.length);
}
int[] indices = null;
if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {
indices = new int[_count];
System.arraycopy(_indices, 0, indices, 0, _count);
}
if (result == null) {
result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);
} else {
result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);
}
return result;
}
}
/**
* Utility class to keep track of script runs, which may have to be reordered rtl when we're
* finished.
*/
private final class EngineRecord {
private int start;
private int limit;
private int gmask;
private int eflags;
private LayoutEngineKey key;
private LayoutEngine engine;
EngineRecord() {
key = new LayoutEngineKey();
}
void init(int start, int limit, Font2D font, int script, int lang, int gmask) {
this.start = start;
this.limit = limit;
this.gmask = gmask;
this.key.init(font, script, lang);
this.eflags = 0;
// only request canonical substitution if we have combining marks
for (int i = start; i < limit; ++i) {
int ch = _textRecord.text[i];
if (isHighSurrogate((char)ch) &&
i < limit - 1 &&
isLowSurrogate(_textRecord.text[i+1])) {
// rare case
ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc
}
int gc = getType(ch);
if (gc == NON_SPACING_MARK ||
gc == ENCLOSING_MARK ||
gc == COMBINING_SPACING_MARK) { // could do range test also
this.eflags = 0x4;
break;
}
}
this.engine = _lef.getEngine(key); // flags?
}
void layout() {
_textRecord.start = start;
_textRecord.limit = limit;
engine.layout(_sd, _mat, gmask, start - _offset, _textRecord,
_typo_flags | eflags, _pt, _gvdata);
}
}
}

View File

@@ -0,0 +1,514 @@
/*
* Copyright (c) 2000, 2013, 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.Font;
import java.awt.font.GlyphVector;
import java.awt.font.FontRenderContext;
import sun.java2d.loops.FontInfo;
/*
* This class represents a list of actual renderable glyphs.
* It can be constructed from a number of text sources, representing
* the various ways in which a programmer can ask a Graphics2D object
* to render some text. Once constructed, it provides a way of iterating
* through the device metrics and graybits of the individual glyphs that
* need to be rendered to the screen.
*
* Note that this class holds pointers to native data which must be
* disposed. It is not marked as finalizable since it is intended
* to be very lightweight and finalization is a comparitively expensive
* procedure. The caller must specifically use try{} finally{} to
* manually ensure that the object is disposed after use, otherwise
* native data structures might be leaked.
*
* Here is a code sample for using this class:
*
* public void drawString(String str, FontInfo info, float x, float y) {
* GlyphList gl = GlyphList.getInstance();
* try {
* gl.setFromString(info, str, x, y);
* int strbounds[] = gl.getBounds();
* int numglyphs = gl.getNumGlyphs();
* for (int i = 0; i < numglyphs; i++) {
* gl.setGlyphIndex(i);
* int metrics[] = gl.getMetrics();
* byte bits[] = gl.getGrayBits();
* int glyphx = metrics[0];
* int glyphy = metrics[1];
* int glyphw = metrics[2];
* int glyphh = metrics[3];
* int off = 0;
* for (int j = 0; j < glyphh; j++) {
* for (int i = 0; i < glyphw; i++) {
* int dx = glyphx + i;
* int dy = glyphy + j;
* int alpha = bits[off++];
* drawPixel(alpha, dx, dy);
* }
* }
* }
* } finally {
* gl.dispose();
* }
* }
*/
public final class GlyphList {
private static final int MINGRAYLENGTH = 1024;
private static final int MAXGRAYLENGTH = 8192;
private static final int DEFAULT_LENGTH = 32;
int glyphindex;
int metrics[];
byte graybits[];
/* A reference to the strike is needed for the case when the GlyphList
* may be added to a queue for batch processing, (e.g. OpenGL) and we need
* to be completely certain that the strike is still valid when the glyphs
* images are later referenced. This does mean that if such code discards
* GlyphList and places only the data it contains on the queue, that the
* strike needs to be part of that data held by a strong reference.
* In the cases of drawString() and drawChars(), this is a single strike,
* although it may be a composite strike. In the case of
* drawGlyphVector() it may be a single strike, or a list of strikes.
*/
Object strikelist; // hold multiple strikes during rendering of complex gv
/* In normal usage, the same GlyphList will get recycled, so
* it makes sense to allocate arrays that will get reused along with
* it, rather than generating garbage. Garbage will be generated only
* in MP envts where multiple threads are executing. Throughput should
* still be higher in those cases.
*/
int len = 0;
int maxLen = 0;
int maxPosLen = 0;
int glyphData[];
char chData[];
long images[];
float positions[];
float x, y;
float gposx, gposy;
boolean usePositions;
/* lcdRGBOrder is used only by LCD text rendering. Its here because
* the Graphics may have a different hint value than the one used
* by a GlyphVector, so it has to be stored here - and is obtained
* from the right FontInfo. Another approach would have been to have
* install a separate pipe for that case but that's a lot of extra
* code when a simple boolean will suffice. The overhead to non-LCD
* text is a redundant boolean assign per call.
*/
boolean lcdRGBOrder;
/*
* lcdSubPixPos is used only by LCD text rendering. Its here because
* the Graphics may have a different hint value than the one used
* by a GlyphVector, so it has to be stored here - and is obtained
* from the right FontInfo. Its also needed by the code which
* calculates glyph positions which already needs to access this
* GlyphList and would otherwise need the FontInfo.
* This is true only if LCD text and fractional metrics hints
* are selected on the graphics.
* When this is true and the glyph positions as determined by the
* advances are non-integral, it requests adjustment of the positions.
* Setting this for surfaces which do not support it through accelerated
* loops may cause a slow-down as software loops are invoked instead.
*/
boolean lcdSubPixPos;
/* This scheme creates a singleton GlyphList which is checked out
* for use. Callers who find its checked out create one that after use
* is discarded. This means that in a MT-rendering environment,
* there's no need to synchronise except for that one instance.
* Fewer threads will then need to synchronise, perhaps helping
* throughput on a MP system. If for some reason the reusable
* GlyphList is checked out for a long time (or never returned?) then
* we would end up always creating new ones. That situation should not
* occur and if if did, it would just lead to some extra garbage being
* created.
*/
private static GlyphList reusableGL = new GlyphList();
private static boolean inUse;
void ensureCapacity(int len) {
/* Note len must not be -ve! only setFromChars should be capable
* of passing down a -ve len, and this guards against it.
*/
if (len < 0) {
len = 0;
}
if (usePositions && len > maxPosLen) {
positions = new float[len * 2 + 2];
maxPosLen = len;
}
if (maxLen == 0 || len > maxLen) {
glyphData = new int[len];
chData = new char[len];
images = new long[len];
maxLen = len;
}
}
private GlyphList() {
// ensureCapacity(DEFAULT_LENGTH);
}
// private GlyphList(int arraylen) {
// ensureCapacity(arraylen);
// }
public static GlyphList getInstance() {
/* The following heuristic is that if the reusable instance is
* in use, it probably still will be in a micro-second, so avoid
* synchronising on the class and just allocate a new instance.
* The cost is one extra boolean test for the normal case, and some
* small number of cases where we allocate an extra object when
* in fact the reusable one would be freed very soon.
*/
if (inUse) {
return new GlyphList();
} else {
synchronized(GlyphList.class) {
if (inUse) {
return new GlyphList();
} else {
inUse = true;
return reusableGL;
}
}
}
}
/* In some cases the caller may be able to estimate the size of
* array needed, and it will usually be long enough. This avoids
* the unnecessary reallocation that occurs if our default
* values are too small. This is useful because this object
* will be discarded so the re-allocation overhead is high.
*/
// public static GlyphList getInstance(int sz) {
// if (inUse) {
// return new GlyphList(sz);
// } else {
// synchronized(GlyphList.class) {
// if (inUse) {
// return new GlyphList();
// } else {
// inUse = true;
// return reusableGL;
// }
// }
// }
// }
/* GlyphList is in an invalid state until setFrom* method is called.
* After obtaining a new GlyphList it is the caller's responsibility
* that one of these methods is executed before handing off the
* GlyphList
*/
public boolean setFromString(FontInfo info, String str, float x, float y) {
this.x = x;
this.y = y;
this.strikelist = info.fontStrike;
this.lcdRGBOrder = info.lcdRGBOrder;
this.lcdSubPixPos = info.lcdSubPixPos;
len = str.length();
ensureCapacity(len);
str.getChars(0, len, chData, 0);
return mapChars(info, len);
}
public boolean setFromChars(FontInfo info, char[] chars, int off, int alen,
float x, float y) {
this.x = x;
this.y = y;
this.strikelist = info.fontStrike;
this.lcdRGBOrder = info.lcdRGBOrder;
this.lcdSubPixPos = info.lcdSubPixPos;
len = alen;
if (alen < 0) {
len = 0;
} else {
len = alen;
}
ensureCapacity(len);
System.arraycopy(chars, off, chData, 0, len);
return mapChars(info, len);
}
private final boolean mapChars(FontInfo info, int len) {
/* REMIND.Is it worthwhile for the iteration to convert
* chars to glyph ids to directly map to images?
*/
if (info.font2D.getMapper().charsToGlyphsNS(len, chData, glyphData)) {
return false;
}
info.fontStrike.getGlyphImagePtrs(glyphData, images, len);
glyphindex = -1;
return true;
}
public void setFromGlyphVector(FontInfo info, GlyphVector gv,
float x, float y) {
this.x = x;
this.y = y;
this.lcdRGBOrder = info.lcdRGBOrder;
this.lcdSubPixPos = info.lcdSubPixPos;
/* A GV may be rendered in different Graphics. It is possible it is
* used for one case where LCD text is available, and another where
* it is not. Pass in the "info". to ensure get a suitable one.
*/
StandardGlyphVector sgv = StandardGlyphVector.getStandardGV(gv, info);
// call before ensureCapacity :-
usePositions = sgv.needsPositions(info.devTx);
len = sgv.getNumGlyphs();
ensureCapacity(len);
strikelist = sgv.setupGlyphImages(images,
usePositions ? positions : null,
info.devTx);
glyphindex = -1;
}
public int[] getBounds() {
/* We co-opt the 5 element array that holds per glyph metrics in order
* to return the bounds. So a caller must copy the data out of the
* array before calling any other methods on this GlyphList
*/
if (glyphindex >= 0) {
throw new InternalError("calling getBounds after setGlyphIndex");
}
if (metrics == null) {
metrics = new int[5];
}
/* gposx and gposy are used to accumulate the advance.
* Add 0.5f for consistent rounding to pixel position. */
gposx = x + 0.5f;
gposy = y + 0.5f;
fillBounds(metrics);
return metrics;
}
/* This method now assumes "state", so must be called 0->len
* The metrics it returns are accumulated on the fly
* So it could be renamed "nextGlyph()".
* Note that a laid out GlyphVector which has assigned glyph positions
* doesn't have this stricture..
*/
public void setGlyphIndex(int i) {
glyphindex = i;
if (images[i] == 0L) {
metrics[0] = (int)gposx;
metrics[1] = (int)gposy;
metrics[2] = 0;
metrics[3] = 0;
metrics[4] = 0;
return;
}
float gx =
StrikeCache.unsafe.getFloat(images[i]+StrikeCache.topLeftXOffset);
float gy =
StrikeCache.unsafe.getFloat(images[i]+StrikeCache.topLeftYOffset);
if (usePositions) {
metrics[0] = (int)Math.floor(positions[(i<<1)] + gposx + gx);
metrics[1] = (int)Math.floor(positions[(i<<1)+1] + gposy + gy);
} else {
metrics[0] = (int)Math.floor(gposx + gx);
metrics[1] = (int)Math.floor(gposy + gy);
/* gposx and gposy are used to accumulate the advance */
gposx += StrikeCache.unsafe.getFloat
(images[i]+StrikeCache.xAdvanceOffset);
gposy += StrikeCache.unsafe.getFloat
(images[i]+StrikeCache.yAdvanceOffset);
}
metrics[2] =
StrikeCache.unsafe.getChar(images[i]+StrikeCache.widthOffset);
metrics[3] =
StrikeCache.unsafe.getChar(images[i]+StrikeCache.heightOffset);
metrics[4] =
StrikeCache.unsafe.getChar(images[i]+StrikeCache.rowBytesOffset);
}
public int[] getMetrics() {
return metrics;
}
public byte[] getGrayBits() {
int len = metrics[4] * metrics[3];
if (graybits == null) {
graybits = new byte[Math.max(len, MINGRAYLENGTH)];
} else {
if (len > graybits.length) {
graybits = new byte[len];
}
}
if (images[glyphindex] == 0L) {
return graybits;
}
long pixelDataAddress =
StrikeCache.unsafe.getAddress(images[glyphindex] +
StrikeCache.pixelDataOffset);
if (pixelDataAddress == 0L) {
return graybits;
}
/* unsafe is supposed to be fast, but I doubt if this loop can beat
* a native call which does a getPrimitiveArrayCritical and a
* memcpy for the typical amount of image data (30-150 bytes)
* Consider a native method if there is a performance problem (which
* I haven't seen so far).
*/
for (int i=0; i<len; i++) {
graybits[i] = StrikeCache.unsafe.getByte(pixelDataAddress+i);
}
return graybits;
}
public long[] getImages() {
return images;
}
public boolean usePositions() {
return usePositions;
}
public float[] getPositions() {
return positions;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public Object getStrike() {
return strikelist;
}
public boolean isSubPixPos() {
return lcdSubPixPos;
}
public boolean isRGBOrder() {
return lcdRGBOrder;
}
/* There's a reference equality test overhead here, but it allows us
* to avoid synchronizing for GL's that will just be GC'd. This
* helps MP throughput.
*/
public void dispose() {
if (this == reusableGL) {
if (graybits != null && graybits.length > MAXGRAYLENGTH) {
graybits = null;
}
usePositions = false;
strikelist = null; // remove reference to the strike list
inUse = false;
}
}
/* The value here is for use by the rendering engine as it reflects
* the number of glyphs in the array to be blitted. Surrogates pairs
* may have two slots (the second of these being a dummy entry of the
* invisible glyph), whereas an application client would expect only
* one glyph. In other words don't propagate this value up to client code.
*
* {dlf} an application client should have _no_ expectations about the
* number of glyphs per char. This ultimately depends on the font
* technology and layout process used, which in general clients will
* know nothing about.
*/
public int getNumGlyphs() {
return len;
}
/* We re-do all this work as we iterate through the glyphs
* but it seems unavoidable without re-working the Java TextRenderers.
*/
private void fillBounds(int[] bounds) {
/* Faster to access local variables in the for loop? */
int xOffset = StrikeCache.topLeftXOffset;
int yOffset = StrikeCache.topLeftYOffset;
int wOffset = StrikeCache.widthOffset;
int hOffset = StrikeCache.heightOffset;
int xAdvOffset = StrikeCache.xAdvanceOffset;
int yAdvOffset = StrikeCache.yAdvanceOffset;
if (len == 0) {
bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0;
return;
}
float bx0, by0, bx1, by1;
bx0 = by0 = Float.POSITIVE_INFINITY;
bx1 = by1 = Float.NEGATIVE_INFINITY;
int posIndex = 0;
float glx = x + 0.5f;
float gly = y + 0.5f;
char gw, gh;
float gx, gy, gx0, gy0, gx1, gy1;
for (int i=0; i<len; i++) {
if (images[i] == 0L) {
continue;
}
gx = StrikeCache.unsafe.getFloat(images[i]+xOffset);
gy = StrikeCache.unsafe.getFloat(images[i]+yOffset);
gw = StrikeCache.unsafe.getChar(images[i]+wOffset);
gh = StrikeCache.unsafe.getChar(images[i]+hOffset);
if (usePositions) {
gx0 = positions[posIndex++] + gx + glx;
gy0 = positions[posIndex++] + gy + gly;
} else {
gx0 = glx + gx;
gy0 = gly + gy;
glx += StrikeCache.unsafe.getFloat(images[i]+xAdvOffset);
gly += StrikeCache.unsafe.getFloat(images[i]+yAdvOffset);
}
gx1 = gx0 + gw;
gy1 = gy0 + gh;
if (bx0 > gx0) bx0 = gx0;
if (by0 > gy0) by0 = gy0;
if (bx1 < gx1) bx1 = gx1;
if (by1 < gy1) by1 = gy1;
}
/* floor is safe and correct because all glyph widths, heights
* and offsets are integers
*/
bounds[0] = (int)Math.floor(bx0);
bounds[1] = (int)Math.floor(by0);
bounds[2] = (int)Math.floor(bx1);
bounds[3] = (int)Math.floor(by1);
}
}

View File

@@ -0,0 +1,376 @@
/*
* Copyright (c) 1998, 2005, 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.
*/
/*
* (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
*
*/
package sun.font;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.font.GraphicAttribute;
import java.awt.font.GlyphJustificationInfo;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.text.Bidi;
import java.util.Map;
public final class GraphicComponent implements TextLineComponent,
Decoration.Label {
public static final float GRAPHIC_LEADING = 2;
private GraphicAttribute graphic;
private int graphicCount;
private int[] charsLtoV; // possibly null
private byte[] levels; // possibly null
// evaluated in computeVisualBounds
private Rectangle2D visualBounds = null;
// used everywhere so we'll cache it
private float graphicAdvance;
private AffineTransform baseTx;
private CoreMetrics cm;
private Decoration decorator;
/**
* Create a new GraphicComponent. start and limit are indices
* into charLtoV and levels. charsLtoV and levels may be adopted.
*/
public GraphicComponent(GraphicAttribute graphic,
Decoration decorator,
int[] charsLtoV,
byte[] levels,
int start,
int limit,
AffineTransform baseTx) {
if (limit <= start) {
throw new IllegalArgumentException("0 or negative length in GraphicComponent");
}
this.graphic = graphic;
this.graphicAdvance = graphic.getAdvance();
this.decorator = decorator;
this.cm = createCoreMetrics(graphic);
this.baseTx = baseTx;
initLocalOrdering(charsLtoV, levels, start, limit);
}
private GraphicComponent(GraphicComponent parent, int start, int limit, int dir) {
this.graphic = parent.graphic;
this.graphicAdvance = parent.graphicAdvance;
this.decorator = parent.decorator;
this.cm = parent.cm;
this.baseTx = parent.baseTx;
int[] charsLtoV = null;
byte[] levels = null;
if (dir == UNCHANGED) {
charsLtoV = parent.charsLtoV;
levels = parent.levels;
}
else if (dir == LEFT_TO_RIGHT || dir == RIGHT_TO_LEFT) {
limit -= start;
start = 0;
if (dir == RIGHT_TO_LEFT) {
charsLtoV = new int[limit];
levels = new byte[limit];
for (int i=0; i < limit; i++) {
charsLtoV[i] = limit-i-1;
levels[i] = (byte) 1;
}
}
}
else {
throw new IllegalArgumentException("Invalid direction flag");
}
initLocalOrdering(charsLtoV, levels, start, limit);
}
/**
* Initialize graphicCount, also charsLtoV and levels arrays.
*/
private void initLocalOrdering(int[] charsLtoV,
byte[] levels,
int start,
int limit) {
this.graphicCount = limit - start; // todo: should be codepoints?
if (charsLtoV == null || charsLtoV.length == graphicCount) {
this.charsLtoV = charsLtoV;
}
else {
this.charsLtoV = BidiUtils.createNormalizedMap(charsLtoV, levels, start, limit);
}
if (levels == null || levels.length == graphicCount) {
this.levels = levels;
}
else {
this.levels = new byte[graphicCount];
System.arraycopy(levels, start, this.levels, 0, graphicCount);
}
}
public boolean isSimple() {
return false;
}
public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
throw new InternalError("do not call if isSimple returns false");
}
public Rectangle2D handleGetVisualBounds() {
Rectangle2D bounds = graphic.getBounds();
float width = (float) bounds.getWidth() +
graphicAdvance * (graphicCount-1);
return new Rectangle2D.Float((float) bounds.getX(),
(float) bounds.getY(),
width,
(float) bounds.getHeight());
}
public CoreMetrics getCoreMetrics() {
return cm;
}
public static CoreMetrics createCoreMetrics(GraphicAttribute graphic) {
return new CoreMetrics(graphic.getAscent(),
graphic.getDescent(),
GRAPHIC_LEADING,
graphic.getAscent() + graphic.getDescent() + GRAPHIC_LEADING,
graphic.getAlignment(),
new float[] { 0, -graphic.getAscent() / 2, -graphic.getAscent() },
-graphic.getAscent() / 2,
graphic.getAscent() / 12,
graphic.getDescent() / 3,
graphic.getAscent() / 12,
0, // ss offset
0); // italic angle -- need api for this
}
public float getItalicAngle() {
return 0;
}
public Rectangle2D getVisualBounds() {
if (visualBounds == null) {
visualBounds = decorator.getVisualBounds(this);
}
Rectangle2D.Float bounds = new Rectangle2D.Float();
bounds.setRect(visualBounds);
return bounds;
}
public Shape handleGetOutline(float x, float y) {
double[] matrix = { 1, 0, 0, 1, x, y };
if (graphicCount == 1) {
AffineTransform tx = new AffineTransform(matrix);
return graphic.getOutline(tx);
}
GeneralPath gp = new GeneralPath();
for (int i = 0; i < graphicCount; ++i) {
AffineTransform tx = new AffineTransform(matrix);
gp.append(graphic.getOutline(tx), false);
matrix[4] += graphicAdvance;
}
return gp;
}
public AffineTransform getBaselineTransform() {
return baseTx;
}
public Shape getOutline(float x, float y) {
return decorator.getOutline(this, x, y);
}
public void handleDraw(Graphics2D g2d, float x, float y) {
for (int i=0; i < graphicCount; i++) {
graphic.draw(g2d, x, y);
x += graphicAdvance;
}
}
public void draw(Graphics2D g2d, float x, float y) {
decorator.drawTextAndDecorations(this, g2d, x, y);
}
public Rectangle2D getCharVisualBounds(int index) {
return decorator.getCharVisualBounds(this, index);
}
public int getNumCharacters() {
return graphicCount;
}
public float getCharX(int index) {
int visIndex = charsLtoV==null? index : charsLtoV[index];
return graphicAdvance * visIndex;
}
public float getCharY(int index) {
return 0;
}
public float getCharAdvance(int index) {
return graphicAdvance;
}
public boolean caretAtOffsetIsValid(int index) {
return true;
}
public Rectangle2D handleGetCharVisualBounds(int index) {
Rectangle2D bounds = graphic.getBounds();
// don't modify their rectangle, just in case they don't copy
Rectangle2D.Float charBounds = new Rectangle2D.Float();
charBounds.setRect(bounds);
charBounds.x += graphicAdvance * index;
return charBounds;
}
// measures characters in context, in logical order
public int getLineBreakIndex(int start, float width) {
int index = (int) (width / graphicAdvance);
if (index > graphicCount - start) {
index = graphicCount - start;
}
return index;
}
// measures characters in context, in logical order
public float getAdvanceBetween(int start, int limit) {
return graphicAdvance * (limit - start);
}
public Rectangle2D getLogicalBounds() {
float left = 0;
float top = -cm.ascent;
float width = graphicAdvance * graphicCount;
float height = cm.descent - top;
return new Rectangle2D.Float(left, top, width, height);
}
public float getAdvance() {
return graphicAdvance * graphicCount;
}
public Rectangle2D getItalicBounds() {
return getLogicalBounds();
}
public TextLineComponent getSubset(int start, int limit, int dir) {
if (start < 0 || limit > graphicCount || start >= limit) {
throw new IllegalArgumentException("Invalid range. start="
+start+"; limit="+limit);
}
if (start == 0 && limit == graphicCount && dir == UNCHANGED) {
return this;
}
return new GraphicComponent(this, start, limit, dir);
}
public String toString() {
return "[graphic=" + graphic + ":count=" + getNumCharacters() + "]";
}
/**
* Return the number of justification records this uses.
*/
public int getNumJustificationInfos() {
return 0;
}
/**
* Return GlyphJustificationInfo objects for the characters between
* charStart and charLimit, starting at offset infoStart. Infos
* will be in visual order. All positions between infoStart and
* getNumJustificationInfos will be set. If a position corresponds
* to a character outside the provided range, it is set to null.
*/
public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit) {
}
/**
* Apply deltas to the data in this component, starting at offset
* deltaStart, and return the new component. There are two floats
* for each justification info, for a total of 2 * getNumJustificationInfos.
* The first delta is the left adjustment, the second is the right
* adjustment.
* <p>
* If flags[0] is true on entry, rejustification is allowed. If
* the new component requires rejustification (ligatures were
* formed or split), flags[0] will be set on exit.
*/
public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags) {
return this;
}
}

View File

@@ -0,0 +1,998 @@
/*
* Copyright (c) 2005, 2013, 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.
*/
/*
* (C) Copyright IBM Corp. 2005, All Rights Reserved.
*/
package sun.font;
//
// This is the 'simple' mapping implementation. It does things the most
// straightforward way even if that is a bit slow. It won't
// handle complex paths efficiently, and doesn't handle closed paths.
//
import java.awt.Shape;
import java.awt.font.LayoutPath;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.Formatter;
import java.util.ArrayList;
import static java.awt.geom.PathIterator.*;
import static java.lang.Math.abs;
import static java.lang.Math.sqrt;
public abstract class LayoutPathImpl extends LayoutPath {
//
// Convenience APIs
//
public Point2D pointToPath(double x, double y) {
Point2D.Double pt = new Point2D.Double(x, y);
pointToPath(pt, pt);
return pt;
}
public Point2D pathToPoint(double a, double o, boolean preceding) {
Point2D.Double pt = new Point2D.Double(a, o);
pathToPoint(pt, preceding, pt);
return pt;
}
public void pointToPath(double x, double y, Point2D pt) {
pt.setLocation(x, y);
pointToPath(pt, pt);
}
public void pathToPoint(double a, double o, boolean preceding, Point2D pt) {
pt.setLocation(a, o);
pathToPoint(pt, preceding, pt);
}
//
// extra utility APIs
//
public abstract double start();
public abstract double end();
public abstract double length();
public abstract Shape mapShape(Shape s);
//
// debugging flags
//
private static final boolean LOGMAP = false;
private static final Formatter LOG = new Formatter(System.out);
/**
* Indicate how positions past the start and limit of the
* path are treated. PINNED adjusts these positions so
* as to be within start and limit. EXTENDED ignores the
* start and limit and effectively extends the first and
* last segments of the path 'infinitely'. CLOSED wraps
* positions around the ends of the path.
*/
public static enum EndType {
PINNED, EXTENDED, CLOSED;
public boolean isPinned() { return this == PINNED; }
public boolean isExtended() { return this == EXTENDED; }
public boolean isClosed() { return this == CLOSED; }
};
//
// Top level construction.
//
/**
* Return a path representing the path from the origin through the points in order.
*/
public static LayoutPathImpl getPath(EndType etype, double ... coords) {
if ((coords.length & 0x1) != 0) {
throw new IllegalArgumentException("odd number of points not allowed");
}
return SegmentPath.get(etype, coords);
}
/**
* Use to build a SegmentPath. This takes the data and preanalyzes it for
* information that the SegmentPath needs, then constructs a SegmentPath
* from that. Mainly, this lets SegmentPath cache the lengths along
* the path to each line segment, and so avoid calculating them over and over.
*/
public static final class SegmentPathBuilder {
private double[] data;
private int w;
private double px;
private double py;
private double a;
private boolean pconnect;
/**
* Construct a SegmentPathBuilder.
*/
public SegmentPathBuilder() {
}
/**
* Reset the builder for a new path. Datalen is a hint of how many
* points will be in the path, and the working buffer will be sized
* to accommodate at least this number of points. If datalen is zero,
* the working buffer is freed (it will be allocated on first use).
*/
public void reset(int datalen) {
if (data == null || datalen > data.length) {
data = new double[datalen];
} else if (datalen == 0) {
data = null;
}
w = 0;
px = py = 0;
pconnect = false;
}
/**
* Automatically build from a list of points represented by pairs of
* doubles. Initial advance is zero.
*/
public SegmentPath build(EndType etype, double... pts) {
assert(pts.length % 2 == 0);
reset(pts.length / 2 * 3);
for (int i = 0; i < pts.length; i += 2) {
nextPoint(pts[i], pts[i+1], i != 0);
}
return complete(etype);
}
/**
* Move to a new point. If there is no data, this will become the
* first point. If there is data, and the previous call was a lineTo, this
* point is checked against the previous point, and if different, this
* starts a new segment at the same advance as the end of the last
* segment. If there is data, and the previous call was a moveTo, this
* replaces the point used for that previous call.
*
* Calling this is optional, lineTo will suffice and the initial point
* will be set to 0, 0.
*/
public void moveTo(double x, double y) {
nextPoint(x, y, false);
}
/**
* Connect to a new point. If there is no data, the previous point
* is presumed to be 0, 0. This point is checked against
* the previous point, and if different, this point is added to
* the path and the advance extended. If this point is the same as the
* previous point, the path remains unchanged.
*/
public void lineTo(double x, double y) {
nextPoint(x, y, true);
}
/**
* Add a new point, and increment advance if connect is true.
*
* This automatically rejects duplicate points and multiple disconnected points.
*/
private void nextPoint(double x, double y, boolean connect) {
// if zero length move or line, ignore
if (x == px && y == py) {
return;
}
if (w == 0) { // this is the first point, make sure we have space
if (data == null) {
data = new double[6];
}
if (connect) {
w = 3; // default first point to 0, 0
}
}
// if multiple disconnected move, just update position, leave advance alone
if (w != 0 && !connect && !pconnect) {
data[w-3] = px = x;
data[w-2] = py = y;
return;
}
// grow data to deal with new point
if (w == data.length) {
double[] t = new double[w * 2];
System.arraycopy(data, 0, t, 0, w);
data = t;
}
if (connect) {
double dx = x - px;
double dy = y - py;
a += sqrt(dx * dx + dy * dy);
}
// update data
data[w++] = x;
data[w++] = y;
data[w++] = a;
// update state
px = x;
py = y;
pconnect = connect;
}
public SegmentPath complete() {
return complete(EndType.EXTENDED);
}
/**
* Complete building a SegmentPath. Once this is called, the builder is restored
* to its initial state and information about the previous path is released. The
* end type indicates whether to treat the path as closed, extended, or pinned.
*/
public SegmentPath complete(EndType etype) {
SegmentPath result;
if (data == null || w < 6) {
return null;
}
if (w == data.length) {
result = new SegmentPath(data, etype);
reset(0); // releases pointer to data
} else {
double[] dataToAdopt = new double[w];
System.arraycopy(data, 0, dataToAdopt, 0, w);
result = new SegmentPath(dataToAdopt, etype);
reset(2); // reuses data, since we held on to it
}
return result;
}
}
/**
* Represents a path built from segments. Each segment is
* represented by a triple: x, y, and cumulative advance.
* These represent the end point of the segment. The start
* point of the first segment is represented by the triple
* at position 0.
*
* The path might have breaks in it, e.g. it is not connected.
* These will be represented by pairs of triplets that share the
* same advance.
*
* The path might be extended, pinned, or closed. If extended,
* the initial and final segments are considered to extend
* 'indefinitely' past the bounds of the advance. If pinned,
* they end at the bounds of the advance. If closed,
* advances before the start or after the end 'wrap around' the
* path.
*
* The start of the path is the initial triple. This provides
* the nominal advance at the given x, y position (typically
* zero). The end of the path is the final triple. This provides
* the advance at the end, the total length of the path is
* thus the ending advance minus the starting advance.
*
* Note: We might want to cache more auxiliary data than the
* advance, but this seems adequate for now.
*/
public static final class SegmentPath extends LayoutPathImpl {
private double[] data; // triplets x, y, a
EndType etype;
public static SegmentPath get(EndType etype, double... pts) {
return new SegmentPathBuilder().build(etype, pts);
}
/**
* Internal, use SegmentPathBuilder or one of the static
* helper functions to construct a SegmentPath.
*/
SegmentPath(double[] data, EndType etype) {
this.data = data;
this.etype = etype;
}
//
// LayoutPath API
//
public void pathToPoint(Point2D location, boolean preceding, Point2D point) {
locateAndGetIndex(location, preceding, point);
}
// the path consists of line segments, which i'll call
// 'path vectors'. call each run of path vectors a 'path segment'.
// no path vector in a path segment is zero length (in the
// data, such vectors start a new path segment).
//
// for each path segment...
//
// for each path vector...
//
// we look at the dot product of the path vector and the vector from the
// origin of the path vector to the test point. if <0 (case
// A), the projection of the test point is before the start of
// the path vector. if > the square of the length of the path vector
// (case B), the projection is past the end point of the
// path vector. otherwise (case C), it lies on the path vector.
// determine the closeset point on the path vector. if case A, it
// is the start of the path vector. if case B and this is the last
// path vector in the path segment, it is the end of the path vector. If
// case C, it is the projection onto the path vector. Otherwise
// there is no closest point.
//
// if we have a closest point, compare the distance from it to
// the test point against our current closest distance.
// (culling should be fast, currently i am using distance
// squared, but there's probably better ways). if we're
// closer, save the new point as the current closest point,
// and record the path vector index so we can determine the final
// info if this turns out to be the closest point in the end.
//
// after we have processed all the segments we will have
// tested each path vector and each endpoint. if our point is not on
// an endpoint, we're done; we can compute the position and
// offset again, or if we saved it off we can just use it. if
// we're on an endpoint we need to see which path vector we should
// associate with. if we're at the start or end of a path segment,
// we're done-- the first or last vector of the segment is the
// one we associate with. we project against that vector to
// get the offset, and pin to that vector to get the length.
//
// otherwise, we compute the information as follows. if the
// dot product (see above) with the following vector is zero,
// we associate with that vector. otherwise, if the dot
// product with the previous vector is zero, we associate with
// that vector. otherwise we're beyond the end of the
// previous vector and before the start of the current vector.
// we project against both vectors and get the distance from
// the test point to the projection (this will be the offset).
// if they are the same, we take the following vector.
// otherwise use the vector from which the test point is the
// _farthest_ (this is because the point lies most clearly in
// the half of the plane defined by extending that vector).
//
// the returned position is the path length to the (possibly
// pinned) point, the offset is the projection onto the line
// along the vector, and we have a boolean flag which if false
// indicates that we associate with the previous vector at a
// junction (which is necessary when projecting such a
// location back to a point).
public boolean pointToPath(Point2D pt, Point2D result) {
double x = pt.getX(); // test point
double y = pt.getY();
double bx = data[0]; // previous point
double by = data[1];
double bl = data[2];
// start with defaults
double cd2 = Double.MAX_VALUE; // current best distance from path, squared
double cx = 0; // current best x
double cy = 0; // current best y
double cl = 0; // current best position along path
int ci = 0; // current best index into data
for (int i = 3; i < data.length; i += 3) {
double nx = data[i]; // current end point
double ny = data[i+1];
double nl = data[i+2];
double dx = nx - bx; // vector from previous to current
double dy = ny - by;
double dl = nl - bl;
double px = x - bx; // vector from previous to test point
double py = y - by;
// determine sign of dot product of vectors from bx, by
// if < 0, we're before the start of this vector
double dot = dx * px + dy * py; // dot product
double vcx, vcy, vcl; // hold closest point on vector as x, y, l
int vi; // hold index of line, is data.length if last point on path
do { // use break below, lets us avoid initializing vcx, vcy...
if (dl == 0 || // moveto, or
(dot < 0 && // before path vector and
(!etype.isExtended() ||
i != 3))) { // closest point is start of vector
vcx = bx;
vcy = by;
vcl = bl;
vi = i;
} else {
double l2 = dl * dl; // aka dx * dx + dy * dy, square of length
if (dot <= l2 || // closest point is not past end of vector, or
(etype.isExtended() && // we're extended and at the last segment
i == data.length - 3)) {
double p = dot / l2; // get parametric along segment
vcx = bx + p * dx; // compute closest point
vcy = by + p * dy;
vcl = bl + p * dl;
vi = i;
} else {
if (i == data.length - 3) {
vcx = nx; // special case, always test last point
vcy = ny;
vcl = nl;
vi = data.length;
} else {
break; // typical case, skip point, we'll pick it up next iteration
}
}
}
double tdx = x - vcx; // compute distance from (usually pinned) projection to test point
double tdy = y - vcy;
double td2 = tdx * tdx + tdy * tdy;
if (td2 <= cd2) { // new closest point, record info on it
cd2 = td2;
cx = vcx;
cy = vcy;
cl = vcl;
ci = vi;
}
} while (false);
bx = nx;
by = ny;
bl = nl;
}
// we have our closest point, get the info
bx = data[ci-3];
by = data[ci-2];
if (cx != bx || cy != by) { // not on endpoint, no need to resolve
double nx = data[ci];
double ny = data[ci+1];
double co = sqrt(cd2); // have a true perpendicular, so can use distance
if ((x-cx)*(ny-by) > (y-cy)*(nx-bx)) {
co = -co; // determine sign of offset
}
result.setLocation(cl, co);
return false;
} else { // on endpoint, we need to resolve which segment
boolean havePrev = ci != 3 && data[ci-1] != data[ci-4];
boolean haveFoll = ci != data.length && data[ci-1] != data[ci+2];
boolean doExtend = etype.isExtended() && (ci == 3 || ci == data.length);
if (havePrev && haveFoll) {
Point2D.Double pp = new Point2D.Double(x, y);
calcoffset(ci - 3, doExtend, pp);
Point2D.Double fp = new Point2D.Double(x, y);
calcoffset(ci, doExtend, fp);
if (abs(pp.y) > abs(fp.y)) {
result.setLocation(pp);
return true; // associate with previous
} else {
result.setLocation(fp);
return false; // associate with following
}
} else if (havePrev) {
result.setLocation(x, y);
calcoffset(ci - 3, doExtend, result);
return true;
} else {
result.setLocation(x, y);
calcoffset(ci, doExtend, result);
return false;
}
}
}
/**
* Return the location of the point passed in result as mapped to the
* line indicated by index. If doExtend is true, extend the
* x value without pinning to the ends of the line.
* this assumes that index is valid and references a line that has
* non-zero length.
*/
private void calcoffset(int index, boolean doExtend, Point2D result) {
double bx = data[index-3];
double by = data[index-2];
double px = result.getX() - bx;
double py = result.getY() - by;
double dx = data[index] - bx;
double dy = data[index+1] - by;
double l = data[index+2] - data[index - 1];
// rx = A dot B / |B|
// ry = A dot invB / |B|
double rx = (px * dx + py * dy) / l;
double ry = (px * -dy + py * dx) / l;
if (!doExtend) {
if (rx < 0) rx = 0;
else if (rx > l) rx = l;
}
rx += data[index-1];
result.setLocation(rx, ry);
}
//
// LayoutPathImpl API
//
public Shape mapShape(Shape s) {
return new Mapper().mapShape(s);
}
public double start() {
return data[2];
}
public double end() {
return data[data.length - 1];
}
public double length() {
return data[data.length-1] - data[2];
}
//
// Utilities
//
/**
* Get the 'modulus' of an advance on a closed path.
*/
private double getClosedAdvance(double a, boolean preceding) {
if (etype.isClosed()) {
a -= data[2];
int count = (int)(a/length());
a -= count * length();
if (a < 0 || (a == 0 && preceding)) {
a += length();
}
a += data[2];
}
return a;
}
/**
* Return the index of the segment associated with advance. This
* points to the start of the triple and is a multiple of 3 between
* 3 and data.length-3 inclusive. It never points to a 'moveto' triple.
*
* If the path is closed, 'a' is mapped to
* a value between the start and end of the path, inclusive.
* If preceding is true, and 'a' lies on a segment boundary,
* return the index of the preceding segment, else return the index
* of the current segment (if it is not a moveto segment) otherwise
* the following segment (which is never a moveto segment).
*
* Note: if the path is not closed, the advance might not actually
* lie on the returned segment-- it might be before the first, or
* after the last. The first or last segment (as appropriate)
* will be returned in this case.
*/
private int getSegmentIndexForAdvance(double a, boolean preceding) {
// must have local advance
a = getClosedAdvance(a, preceding);
// note we must avoid 'moveto' segments. the first segment is
// always a moveto segment, so we always skip it.
int i, lim;
for (i = 5, lim = data.length-1; i < lim; i += 3) {
double v = data[i];
if (a < v || (a == v && preceding)) {
break;
}
}
return i-2; // adjust to start of segment
}
/**
* Map a location based on the provided segment, returning in pt.
* Seg must be a valid 'lineto' segment. Note: if the path is
* closed, x must be within the start and end of the path.
*/
private void map(int seg, double a, double o, Point2D pt) {
double dx = data[seg] - data[seg-3];
double dy = data[seg+1] - data[seg-2];
double dl = data[seg+2] - data[seg-1];
double ux = dx/dl; // could cache these, but is it worth it?
double uy = dy/dl;
a -= data[seg-1];
pt.setLocation(data[seg-3] + a * ux - o * uy,
data[seg-2] + a * uy + o * ux);
}
/**
* Map the point, and return the segment index.
*/
private int locateAndGetIndex(Point2D loc, boolean preceding, Point2D result) {
double a = loc.getX();
double o = loc.getY();
int seg = getSegmentIndexForAdvance(a, preceding);
map(seg, a, o, result);
return seg;
}
//
// Mapping classes.
// Map the path onto each path segment.
// Record points where the advance 'enters' and 'exits' the path segment, and connect successive
// points when appropriate.
//
/**
* This represents a line segment from the iterator. Each target segment will
* interpret it, and since this process needs slope along the line
* segment, this lets us compute it once and pass it around easily.
*/
class LineInfo {
double sx, sy; // start
double lx, ly; // limit
double m; // slope dy/dx
/**
* Set the lineinfo to this line
*/
void set(double sx, double sy, double lx, double ly) {
this.sx = sx;
this.sy = sy;
this.lx = lx;
this.ly = ly;
double dx = lx - sx;
if (dx == 0) {
m = 0; // we'll check for this elsewhere
} else {
double dy = ly - sy;
m = dy / dx;
}
}
void set(LineInfo rhs) {
this.sx = rhs.sx;
this.sy = rhs.sy;
this.lx = rhs.lx;
this.ly = rhs.ly;
this.m = rhs.m;
}
/**
* Return true if we intersect the infinitely tall rectangle with
* lo <= x < hi. If we do, also return the pinned portion of ourselves in
* result.
*/
boolean pin(double lo, double hi, LineInfo result) {
result.set(this);
if (lx >= sx) {
if (sx < hi && lx >= lo) {
if (sx < lo) {
if (m != 0) result.sy = sy + m * (lo - sx);
result.sx = lo;
}
if (lx > hi) {
if (m != 0) result.ly = ly + m * (hi - lx);
result.lx = hi;
}
return true;
}
} else {
if (lx < hi && sx >= lo) {
if (lx < lo) {
if (m != 0) result.ly = ly + m * (lo - lx);
result.lx = lo;
}
if (sx > hi) {
if (m != 0) result.sy = sy + m * (hi - sx);
result.sx = hi;
}
return true;
}
}
return false;
}
/**
* Return true if we intersect the segment at ix. This takes
* the path end type into account and computes the relevant
* parameters to pass to pin(double, double, LineInfo).
*/
boolean pin(int ix, LineInfo result) {
double lo = data[ix-1];
double hi = data[ix+2];
switch (SegmentPath.this.etype) {
case PINNED:
break;
case EXTENDED:
if (ix == 3) lo = Double.NEGATIVE_INFINITY;
if (ix == data.length - 3) hi = Double.POSITIVE_INFINITY;
break;
case CLOSED:
// not implemented
break;
}
return pin(lo, hi, result);
}
}
/**
* Each segment will construct its own general path, mapping the provided lines
* into its own simple space.
*/
class Segment {
final int ix; // index into data array for this segment
final double ux, uy; // unit vector
final LineInfo temp; // working line info
boolean broken; // true if a moveto has occurred since we last added to our path
double cx, cy; // last point in gp
GeneralPath gp; // path built for this segment
Segment(int ix) {
this.ix = ix;
double len = data[ix+2] - data[ix-1];
this.ux = (data[ix] - data[ix-3]) / len;
this.uy = (data[ix+1] - data[ix-2]) / len;
this.temp = new LineInfo();
}
void init() {
if (LOGMAP) LOG.format("s(%d) init\n", ix);
broken = true;
cx = cy = Double.MIN_VALUE;
this.gp = new GeneralPath();
}
void move() {
if (LOGMAP) LOG.format("s(%d) move\n", ix);
broken = true;
}
void close() {
if (!broken) {
if (LOGMAP) LOG.format("s(%d) close\n[cp]\n", ix);
gp.closePath();
}
}
void line(LineInfo li) {
if (LOGMAP) LOG.format("s(%d) line %g, %g to %g, %g\n", ix, li.sx, li.sy, li.lx, li.ly);
if (li.pin(ix, temp)) {
if (LOGMAP) LOG.format("pin: %g, %g to %g, %g\n", temp.sx, temp.sy, temp.lx, temp.ly);
temp.sx -= data[ix-1];
double sx = data[ix-3] + temp.sx * ux - temp.sy * uy;
double sy = data[ix-2] + temp.sx * uy + temp.sy * ux;
temp.lx -= data[ix-1];
double lx = data[ix-3] + temp.lx * ux - temp.ly * uy;
double ly = data[ix-2] + temp.lx * uy + temp.ly * ux;
if (LOGMAP) LOG.format("points: %g, %g to %g, %g\n", sx, sy, lx, ly);
if (sx != cx || sy != cy) {
if (broken) {
if (LOGMAP) LOG.format("[mt %g, %g]\n", sx, sy);
gp.moveTo((float)sx, (float)sy);
} else {
if (LOGMAP) LOG.format("[lt %g, %g]\n", sx, sy);
gp.lineTo((float)sx, (float)sy);
}
}
if (LOGMAP) LOG.format("[lt %g, %g]\n", lx, ly);
gp.lineTo((float)lx, (float)ly);
broken = false;
cx = lx;
cy = ly;
}
}
}
class Mapper {
final LineInfo li; // working line info
final ArrayList<Segment> segments; // cache additional data on segments, working objects
final Point2D.Double mpt; // last moveto source point
final Point2D.Double cpt; // current source point
boolean haveMT; // true when last op was a moveto
Mapper() {
li = new LineInfo();
segments = new ArrayList<Segment>();
for (int i = 3; i < data.length; i += 3) {
if (data[i+2] != data[i-1]) { // a new segment
segments.add(new Segment(i));
}
}
mpt = new Point2D.Double();
cpt = new Point2D.Double();
}
void init() {
if (LOGMAP) LOG.format("init\n");
haveMT = false;
for (Segment s: segments) {
s.init();
}
}
void moveTo(double x, double y) {
if (LOGMAP) LOG.format("moveto %g, %g\n", x, y);
mpt.x = x;
mpt.y = y;
haveMT = true;
}
void lineTo(double x, double y) {
if (LOGMAP) LOG.format("lineto %g, %g\n", x, y);
if (haveMT) {
// prepare previous point for no-op check
cpt.x = mpt.x;
cpt.y = mpt.y;
}
if (x == cpt.x && y == cpt.y) {
// lineto is a no-op
return;
}
if (haveMT) {
// current point is the most recent moveto point
haveMT = false;
for (Segment s: segments) {
s.move();
}
}
li.set(cpt.x, cpt.y, x, y);
for (Segment s: segments) {
s.line(li);
}
cpt.x = x;
cpt.y = y;
}
void close() {
if (LOGMAP) LOG.format("close\n");
lineTo(mpt.x, mpt.y);
for (Segment s: segments) {
s.close();
}
}
public Shape mapShape(Shape s) {
if (LOGMAP) LOG.format("mapshape on path: %s\n", LayoutPathImpl.SegmentPath.this);
PathIterator pi = s.getPathIterator(null, 1); // cheap way to handle curves.
if (LOGMAP) LOG.format("start\n");
init();
final double[] coords = new double[2];
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case SEG_CLOSE: close(); break;
case SEG_MOVETO: moveTo(coords[0], coords[1]); break;
case SEG_LINETO: lineTo(coords[0], coords[1]); break;
default: break;
}
pi.next();
}
if (LOGMAP) LOG.format("finish\n\n");
GeneralPath gp = new GeneralPath();
for (Segment seg: segments) {
gp.append(seg.gp, false);
}
return gp;
}
}
//
// for debugging
//
public String toString() {
StringBuilder b = new StringBuilder();
b.append("{");
b.append(etype.toString());
b.append(" ");
for (int i = 0; i < data.length; i += 3) {
if (i > 0) {
b.append(",");
}
float x = ((int)(data[i] * 100))/100.0f;
float y = ((int)(data[i+1] * 100))/100.0f;
float l = ((int)(data[i+2] * 10))/10.0f;
b.append("{");
b.append(x);
b.append(",");
b.append(y);
b.append(",");
b.append(l);
b.append("}");
}
b.append("}");
return b.toString();
}
}
public static class EmptyPath extends LayoutPathImpl {
private AffineTransform tx;
public EmptyPath(AffineTransform tx) {
this.tx = tx;
}
public void pathToPoint(Point2D location, boolean preceding, Point2D point) {
if (tx != null) {
tx.transform(location, point);
} else {
point.setLocation(location);
}
}
public boolean pointToPath(Point2D pt, Point2D result) {
result.setLocation(pt);
if (tx != null) {
try {
tx.inverseTransform(pt, result);
}
catch (NoninvertibleTransformException ex) {
}
}
return result.getX() > 0;
}
public double start() { return 0; }
public double end() { return 0; }
public double length() { return 0; }
public Shape mapShape(Shape s) {
if (tx != null) {
return tx.createTransformedShape(s);
}
return s;
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2003, 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.FontFormatException;
import java.awt.font.FontRenderContext;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/*
* This class should never be invoked on the windows implementation
* So the constructor throws a FontFormatException, which is caught
* and the font is ignored.
*/
public class NativeFont extends PhysicalFont {
/**
* Verifies native font is accessible.
* @throws FontFormatException - if the font can't be located.
*/
public NativeFont(String platName, boolean isBitmapDelegate)
throws FontFormatException {
throw new FontFormatException("NativeFont not used on Windows");
}
static boolean hasExternalBitmaps(String platName) {
return false;
}
public CharToGlyphMapper getMapper() {
return null;
}
PhysicalFont getDelegateFont() {
return null;
}
FontStrike createStrike(FontStrikeDesc desc) {
return null;
}
public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
return null;
}
StrikeMetrics getFontMetrics(long pScalerContext) {
return null;
}
public GeneralPath getGlyphOutline(long pScalerContext,
int glyphCode,
float x, float y) {
return null;
}
public GeneralPath getGlyphVectorOutline(long pScalerContext,
int[] glyphs, int numGlyphs,
float x, float y) {
return null;
}
long getGlyphImage(long pScalerContext, int glyphCode) {
return 0L;
}
void getGlyphMetrics(long pScalerContext, int glyphCode,
Point2D.Float metrics) {
}
float getGlyphAdvance(long pScalerContext, int glyphCode) {
return 0f;
}
Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext,
int glyphCode) {
return new Rectangle2D.Float(0f, 0f, 0f, 0f);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2003, 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.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
public class NativeStrike extends PhysicalStrike {
NativeFont nativeFont;
NativeStrike(NativeFont nativeFont, FontStrikeDesc desc) {
super(nativeFont, desc);
throw new RuntimeException("NativeFont not used on Windows");
}
NativeStrike(NativeFont nativeFont, FontStrikeDesc desc,
boolean nocache) {
super(nativeFont, desc);
throw new RuntimeException("NativeFont not used on Windows");
}
void getGlyphImagePtrs(int[] glyphCodes, long[] images,int len) {
}
long getGlyphImagePtr(int glyphCode) {
return 0L;
}
long getGlyphImagePtrNoCache(int glyphCode) {
return 0L;
}
void getGlyphImageBounds(int glyphcode,
Point2D.Float pt,
Rectangle result) {
}
Point2D.Float getGlyphMetrics(int glyphCode) {
return null;
}
float getGlyphAdvance(int glyphCode) {
return 0f;
}
Rectangle2D.Float getGlyphOutlineBounds(int glyphCode) {
return null;
}
GeneralPath getGlyphOutline(int glyphCode, float x, float y) {
return null;
}
GeneralPath getGlyphVectorOutline(int[] glyphs, float x, float y) {
return null;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2007, 2011, 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.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
class NullFontScaler extends FontScaler {
NullFontScaler() {}
public NullFontScaler(Font2D font, int indexInCollection,
boolean supportsCJK, int filesize) {}
StrikeMetrics getFontMetrics(long pScalerContext) {
return new StrikeMetrics(0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,
0xf0,0xf0,0xf0,0xf0);
}
float getGlyphAdvance(long pScalerContext, int glyphCode) {
return 0.0f;
}
void getGlyphMetrics(long pScalerContext, int glyphCode,
Point2D.Float metrics) {
metrics.x = 0;
metrics.y = 0;
}
Rectangle2D.Float getGlyphOutlineBounds(long pContext, int glyphCode) {
return new Rectangle2D.Float(0, 0, 0, 0);
}
GeneralPath getGlyphOutline(long pScalerContext, int glyphCode,
float x, float y) {
return new GeneralPath();
}
GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs,
int numGlyphs, float x, float y) {
return new GeneralPath();
}
long getLayoutTableCache() {return 0L;}
long createScalerContext(double[] matrix, int aa,
int fm, float boldness, float italic, boolean disableHinting) {
return getNullScalerContext();
}
void invalidateScalerContext(long ppScalerContext) {
//nothing to do
}
int getNumGlyphs() throws FontScalerException {
return 1;
}
int getMissingGlyphCode() throws FontScalerException {
return 0;
}
int getGlyphCode(char charCode) throws FontScalerException {
return 0;
}
long getUnitsPerEm() {
return 2048;
}
Point2D.Float getGlyphPoint(long pScalerContext,
int glyphCode, int ptNumber) {
return null;
}
/* Ideally NullFontScaler should not have native code.
However, at this moment we need these methods to be native because:
- glyph cache code assumes null pointers to GlyphInfo structures
- FileFontStrike needs native context
*/
static native long getNullScalerContext();
native long getGlyphImage(long pScalerContext, int glyphCode);
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2003, 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.FontFormatException;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.FileInputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public abstract class PhysicalFont extends Font2D {
protected String platName;
// nativeNames is a String or a (possibly null) String[].
protected Object nativeNames;
public boolean equals(Object o) {
return (o != null && o.getClass() == this.getClass() &&
((Font2D)o).fullName.equals(this.fullName));
}
public int hashCode() {
return fullName.hashCode();
}
/**
* Opens the file (temporarily) and does basic verification.
* Initializes the CMAP
* @throws FontFormatException - if the font can't be opened
* or fails verification, or there's no usable cmap
*/
PhysicalFont(String platname, Object nativeNames)
throws FontFormatException {
handle = new Font2DHandle(this);
this.platName = platname;
this.nativeNames = nativeNames;
}
protected PhysicalFont() {
handle = new Font2DHandle(this);
}
/* The following methods are delegated to the font by the strike
* for physical fonts as the PhysicalFont holds a shared reference
* to the native resource, so all invocations need to be directed
* through a synchronization point. Implementations of these methods
* will typically be "synchronized native"
*/
Point2D.Float getGlyphPoint(long pScalerContext,
int glyphCode, int ptNumber) {
return new Point2D.Float();
}
/* These 3 metrics methods should be implemented to return
* values in user space.
*/
abstract StrikeMetrics getFontMetrics(long pScalerContext);
abstract float getGlyphAdvance(long pScalerContext, int glyphCode);
abstract void getGlyphMetrics(long pScalerContext, int glyphCode,
Point2D.Float metrics);
abstract long getGlyphImage(long pScalerContext, int glyphCode);
/* These 3 outline methods should be implemented to return
* values in device space. Callers need to be aware of this
* as typically Java client code will need to have them in user space.
*/
abstract Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext,
int glyphCode);
abstract GeneralPath getGlyphOutline(long pScalerContext, int glyphCode,
float x, float y);
abstract GeneralPath getGlyphVectorOutline(long pScalerContext,
int[] glyphs, int numGlyphs,
float x, float y);
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2003, 2008, 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.Font;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.concurrent.ConcurrentHashMap;
public abstract class PhysicalStrike extends FontStrike {
static final long INTMASK = 0xffffffffL;
static boolean longAddresses;
static {
switch (StrikeCache.nativeAddressSize) {
case 8: longAddresses = true; break;
case 4: longAddresses = false; break;
default: throw new RuntimeException("Unexpected address size");
}
}
private PhysicalFont physicalFont;
protected CharToGlyphMapper mapper;
/* the ScalerContext is a native structure pre-filled with the
* info needed to setup the scaler for this strike. Its immutable
* so we set it up when the strike is created and free it when the
* strike is disposed. There's then no need to pass the info down
* separately to native on every call to the scaler.
*/
protected long pScalerContext;
/* Only one of these two arrays is non-null.
* use the one that matches size of an address (32 or 64 bits)
*/
protected long[] longGlyphImages;
protected int[] intGlyphImages;
/* Used by the TrueTypeFont subclass, which is the only client
* of getGlyphPoint(). The field and method are here because
* there is no TrueTypeFontStrike subclass.
* This map is a cache of the positions of points on the outline
* of a TrueType glyph. It is used by the OpenType layout engine
* to perform mark positioning. Without this cache every position
* request involves scaling and hinting the glyph outline potentially
* over and over again.
*/
ConcurrentHashMap<Integer, Point2D.Float> glyphPointMapCache;
protected boolean getImageWithAdvance;
protected static final int complexTX =
AffineTransform.TYPE_FLIP |
AffineTransform.TYPE_GENERAL_SCALE |
AffineTransform.TYPE_GENERAL_ROTATION |
AffineTransform.TYPE_GENERAL_TRANSFORM |
AffineTransform.TYPE_QUADRANT_ROTATION;
PhysicalStrike(PhysicalFont physicalFont, FontStrikeDesc desc) {
this.physicalFont = physicalFont;
this.desc = desc;
}
protected PhysicalStrike() {
}
/* 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 physicalFont.getNumGlyphs();
}
/* These 3 metrics methods below should be implemented to return
* values in user space.
*/
StrikeMetrics getFontMetrics() {
if (strikeMetrics == null) {
strikeMetrics =
physicalFont.getFontMetrics(pScalerContext);
}
return strikeMetrics;
}
float getCodePointAdvance(int cp) {
return getGlyphAdvance(physicalFont.getMapper().charToGlyph(cp));
}
Point2D.Float getCharMetrics(char ch) {
return getGlyphMetrics(physicalFont.getMapper().charToGlyph(ch));
}
int getSlot0GlyphImagePtrs(int[] glyphCodes, long[] images, int len) {
return 0;
}
/* Used by the OpenType engine for mark positioning.
*/
Point2D.Float getGlyphPoint(int glyphCode, int ptNumber) {
Point2D.Float gp = null;
Integer ptKey = Integer.valueOf(glyphCode<<16|ptNumber);
if (glyphPointMapCache == null) {
synchronized (this) {
if (glyphPointMapCache == null) {
glyphPointMapCache =
new ConcurrentHashMap<Integer, Point2D.Float>();
}
}
} else {
gp = glyphPointMapCache.get(ptKey);
}
if (gp == null) {
gp = (physicalFont.getGlyphPoint(pScalerContext, glyphCode, ptNumber));
adjustPoint(gp);
glyphPointMapCache.put(ptKey, gp);
}
return gp;
}
protected void adjustPoint(Point2D.Float pt) {
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003 - All Rights Reserved
*/
package sun.font;
public final class Script {
public static final int INVALID_CODE = -1;
public static final int COMMON = 0; /* Zyyy */
public static final int INHERITED = 1; /* Qaai */
public static final int ARABIC = 2; /* Arab */
public static final int ARMENIAN = 3; /* Armn */
public static final int BENGALI = 4; /* Beng */
public static final int BOPOMOFO = 5; /* Bopo */
public static final int CHEROKEE = 6; /* Cher */
public static final int COPTIC = 7; /* Qaac */
public static final int CYRILLIC = 8; /* Cyrl (Cyrs) */
public static final int DESERET = 9; /* Dsrt */
public static final int DEVANAGARI = 10; /* Deva */
public static final int ETHIOPIC = 11; /* Ethi */
public static final int GEORGIAN = 12; /* Geor (Geon; Geoa) */
public static final int GOTHIC = 13; /* Goth */
public static final int GREEK = 14; /* Grek */
public static final int GUJARATI = 15; /* Gujr */
public static final int GURMUKHI = 16; /* Guru */
public static final int HAN = 17; /* Hani */
public static final int HANGUL = 18; /* Hang */
public static final int HEBREW = 19; /* Hebr */
public static final int HIRAGANA = 20; /* Hira */
public static final int KANNADA = 21; /* Knda */
public static final int KATAKANA = 22; /* Kana */
public static final int KHMER = 23; /* Khmr */
public static final int LAO = 24; /* Laoo */
public static final int LATIN = 25; /* Latn (Latf; Latg) */
public static final int MALAYALAM = 26; /* Mlym */
public static final int MONGOLIAN = 27; /* Mong */
public static final int MYANMAR = 28; /* Mymr */
public static final int OGHAM = 29; /* Ogam */
public static final int OLD_ITALIC = 30; /* Ital */
public static final int ORIYA = 31; /* Orya */
public static final int RUNIC = 32; /* Runr */
public static final int SINHALA = 33; /* Sinh */
public static final int SYRIAC = 34; /* Syrc (Syrj; Syrn; Syre) */
public static final int TAMIL = 35; /* Taml */
public static final int TELUGU = 36; /* Telu */
public static final int THAANA = 37; /* Thaa */
public static final int THAI = 38; /* Thai */
public static final int TIBETAN = 39; /* Tibt */
public static final int CANADIAN_ABORIGINAL = 40; /* Cans */
public static final int UCAS = CANADIAN_ABORIGINAL; /* Cans */
public static final int YI = 41; /* Yiii */
public static final int TAGALOG = 42; /* Tglg */
public static final int HANUNOO = 43; /* Hano */
public static final int BUHID = 44; /* Buhd */
public static final int TAGBANWA = 45; /* Tagb */
public static final int CODE_LIMIT = 46;
}

View File

@@ -0,0 +1,380 @@
/*
* 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.
*
*/
/*
*******************************************************************************
*
* Copyright (C) 1999-2003, International Business Machines
* Corporation and others. All Rights Reserved.
*
*******************************************************************************
*/
package sun.font;
/**
* <code>ScriptRun</code> is used to find runs of characters in
* the same script, as defined in the <code>Script</code> class.
* It implements a simple iterator over an array of characters.
* The iterator will assign <code>COMMON</code> and <code>INHERITED</code>
* characters to the same script as the preceding characters. If the
* COMMON and INHERITED characters are first, they will be assigned to
* the same script as the following characters.
*
* The iterator will try to match paired punctuation. If it sees an
* opening punctuation character, it will remember the script that
* was assigned to that character, and assign the same script to the
* matching closing punctuation.
*
* No attempt is made to combine related scripts into a single run. In
* particular, Hiragana, Katakana, and Han characters will appear in seperate
* runs.
* Here is an example of how to iterate over script runs:
* <pre>
* void printScriptRuns(char[] text)
* {
* ScriptRun scriptRun = new ScriptRun(text, 0, text.length);
*
* while (scriptRun.next()) {
* int start = scriptRun.getScriptStart();
* int limit = scriptRun.getScriptLimit();
* int script = scriptRun.getScriptCode();
*
* System.out.println("Script \"" + Script.getName(script) + "\" from " +
* start + " to " + limit + ".");
* }
* }
* </pre>
*
*/
public final class ScriptRun
{
private char[] text; // fixed once set by constructor
private int textStart;
private int textLimit;
private int scriptStart; // change during iteration
private int scriptLimit;
private int scriptCode;
private int stack[]; // stack used to handle paired punctuation if encountered
private int parenSP;
public ScriptRun() {
// must call init later or we die.
}
/**
* Construct a <code>ScriptRun</code> object which iterates over a subrange
* of the given characetrs.
*
* @param chars the array of characters over which to iterate.
* @param start the index of the first character over which to iterate
* @param count the number of characters over which to iterate
*/
public ScriptRun(char[] chars, int start, int count)
{
init(chars, start, count);
}
public void init(char[] chars, int start, int count)
{
if (chars == null || start < 0 || count < 0 || count > chars.length - start) {
throw new IllegalArgumentException();
}
text = chars;
textStart = start;
textLimit = start + count;
scriptStart = textStart;
scriptLimit = textStart;
scriptCode = Script.INVALID_CODE;
parenSP = 0;
}
/**
* Get the starting index of the current script run.
*
* @return the index of the first character in the current script run.
*/
public final int getScriptStart() {
return scriptStart;
}
/**
* Get the index of the first character after the current script run.
*
* @return the index of the first character after the current script run.
*/
public final int getScriptLimit() {
return scriptLimit;
}
/**
* Get the script code for the script of the current script run.
*
* @return the script code for the script of the current script run.
* @see #Script
*/
public final int getScriptCode() {
return scriptCode;
}
/**
* Find the next script run. Returns <code>false</code> if there
* isn't another run, returns <code>true</code> if there is.
*
* @return <code>false</code> if there isn't another run, <code>true</code> if there is.
*/
public final boolean next() {
int startSP = parenSP; // used to find the first new open character
// if we've fallen off the end of the text, we're done
if (scriptLimit >= textLimit) {
return false;
}
scriptCode = Script.COMMON;
scriptStart = scriptLimit;
int ch;
while ((ch = nextCodePoint()) != DONE) {
int sc = ScriptRunData.getScript(ch);
int pairIndex = sc == Script.COMMON ? getPairIndex(ch) : -1;
// Paired character handling:
//
// if it's an open character, push it onto the stack.
// if it's a close character, find the matching open on the
// stack, and use that script code. Any non-matching open
// characters above it on the stack will be popped.
if (pairIndex >= 0) {
if ((pairIndex & 1) == 0) {
if (stack == null) {
stack = new int[32];
} else if (parenSP == stack.length) {
int[] newstack = new int[stack.length + 32];
System.arraycopy(stack, 0, newstack, 0, stack.length);
stack = newstack;
}
stack[parenSP++] = pairIndex;
stack[parenSP++] = scriptCode;
} else if (parenSP > 0) {
int pi = pairIndex & ~1;
while ((parenSP -= 2) >= 0 && stack[parenSP] != pi);
if (parenSP >= 0) {
sc = stack[parenSP+1];
} else {
parenSP = 0;
}
if (parenSP < startSP) {
startSP = parenSP;
}
}
}
if (sameScript(scriptCode, sc)) {
if (scriptCode <= Script.INHERITED && sc > Script.INHERITED) {
scriptCode = sc;
// now that we have a final script code, fix any open
// characters we pushed before we knew the script code.
while (startSP < parenSP) {
stack[startSP+1] = scriptCode;
startSP += 2;
}
}
// if this character is a close paired character,
// pop it from the stack
if (pairIndex > 0 && (pairIndex & 1) != 0 && parenSP > 0) {
parenSP -= 2;
}
} else {
// We've just seen the first character of
// the next run. Back over it so we'll see
// it again the next time.
pushback(ch);
// we're outta here
break;
}
}
return true;
}
static final int SURROGATE_START = 0x10000;
static final int LEAD_START = 0xd800;
static final int LEAD_LIMIT = 0xdc00;
static final int TAIL_START = 0xdc00;
static final int TAIL_LIMIT = 0xe000;
static final int LEAD_SURROGATE_SHIFT = 10;
static final int SURROGATE_OFFSET = SURROGATE_START - (LEAD_START << LEAD_SURROGATE_SHIFT) - TAIL_START;
static final int DONE = -1;
private final int nextCodePoint() {
if (scriptLimit >= textLimit) {
return DONE;
}
int ch = text[scriptLimit++];
if (ch >= LEAD_START && ch < LEAD_LIMIT && scriptLimit < textLimit) {
int nch = text[scriptLimit];
if (nch >= TAIL_START && nch < TAIL_LIMIT) {
++scriptLimit;
ch = (ch << LEAD_SURROGATE_SHIFT) + nch + SURROGATE_OFFSET;
}
}
return ch;
}
private final void pushback(int ch) {
if (ch >= 0) {
if (ch >= 0x10000) {
scriptLimit -= 2;
} else {
scriptLimit -= 1;
}
}
}
/**
* Compare two script codes to see if they are in the same script. If one script is
* a strong script, and the other is INHERITED or COMMON, it will compare equal.
*
* @param scriptOne one of the script codes.
* @param scriptTwo the other script code.
* @return <code>true</code> if the two scripts are the same.
* @see com.ibm.icu.lang.Script
*/
private static boolean sameScript(int scriptOne, int scriptTwo) {
return scriptOne == scriptTwo || scriptOne <= Script.INHERITED || scriptTwo <= Script.INHERITED;
}
/**
* Find the highest bit that's set in a word. Uses a binary search through
* the bits.
*
* @param n the word in which to find the highest bit that's set.
* @return the bit number (counting from the low order bit) of the highest bit.
*/
private static final byte highBit(int n)
{
if (n <= 0) {
return -32;
}
byte bit = 0;
if (n >= 1 << 16) {
n >>= 16;
bit += 16;
}
if (n >= 1 << 8) {
n >>= 8;
bit += 8;
}
if (n >= 1 << 4) {
n >>= 4;
bit += 4;
}
if (n >= 1 << 2) {
n >>= 2;
bit += 2;
}
if (n >= 1 << 1) {
n >>= 1;
bit += 1;
}
return bit;
}
/**
* Search the pairedChars array for the given character.
*
* @param ch the character for which to search.
* @return the index of the character in the table, or -1 if it's not there.
*/
private static int getPairIndex(int ch)
{
int probe = pairedCharPower;
int index = 0;
if (ch >= pairedChars[pairedCharExtra]) {
index = pairedCharExtra;
}
while (probe > (1 << 0)) {
probe >>= 1;
if (ch >= pairedChars[index + probe]) {
index += probe;
}
}
if (pairedChars[index] != ch) {
index = -1;
}
return index;
}
// all common
private static int pairedChars[] = {
0x0028, 0x0029, // ascii paired punctuation // common
0x003c, 0x003e, // common
0x005b, 0x005d, // common
0x007b, 0x007d, // common
0x00ab, 0x00bb, // guillemets // common
0x2018, 0x2019, // general punctuation // common
0x201c, 0x201d, // common
0x2039, 0x203a, // common
0x3008, 0x3009, // chinese paired punctuation // common
0x300a, 0x300b,
0x300c, 0x300d,
0x300e, 0x300f,
0x3010, 0x3011,
0x3014, 0x3015,
0x3016, 0x3017,
0x3018, 0x3019,
0x301a, 0x301b
};
private static final int pairedCharPower = 1 << highBit(pairedChars.length);
private static final int pairedCharExtra = pairedChars.length - pairedCharPower;
}

View File

@@ -0,0 +1,795 @@
/*
* 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.
*
*/
/*
*******************************************************************************
* Copyright (C) 2003, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package sun.font;
public final class ScriptRunData {
private ScriptRunData() {}
private static final int CHAR_START = 0;
private static final int CHAR_LIMIT = 0x110000;
private static int cache = 0;
public static final int getScript(int cp) {
// optimize for runs of characters in the same script
if (cp >= data[cache] && cp < data[cache+2]) {
return data[cache+1];
}
if ((cp >= CHAR_START) && (cp < CHAR_LIMIT)) {
int probe = dataPower;
int index = 0;
if (cp >= data[dataExtra]) {
index = dataExtra;
}
while (probe > 2) {
probe >>= 1;
if (cp >= data[index + probe]) {
index += probe;
}
}
cache = index;
return data[index+1];
}
throw new IllegalArgumentException(Integer.toString(cp));
}
private static final int[] data = {
0x000000, 0x00,
0x000041, 0x19, // 'latn' latin
0x00005B, 0x00,
0x000061, 0x19, // 'latn' latin
0x00007B, 0x00,
0x0000AA, 0x19, // 'latn' latin
0x0000AB, 0x00,
0x0000B5, 0x0E, // 'grek' greek
0x0000B6, 0x00,
0x0000BA, 0x19, // 'latn' latin
0x0000BB, 0x00,
0x0000C0, 0x19, // 'latn' latin
0x0000D7, 0x00,
0x0000D8, 0x19, // 'latn' latin
0x0000F7, 0x00,
0x0000F8, 0x19, // 'latn' latin
0x000221, 0x00,
0x000222, 0x19, // 'latn' latin
0x000234, 0x00,
0x000250, 0x19, // 'latn' latin
0x0002AE, 0x00,
0x0002B0, 0x19, // 'latn' latin
0x0002B9, 0x00,
0x0002E0, 0x19, // 'latn' latin
0x0002E5, 0x00,
0x000300, 0x01, // 'qaai' inherited
0x000350, 0x00,
0x000360, 0x01, // 'qaai' inherited
0x000370, 0x00,
0x00037A, 0x0E, // 'grek' greek
0x00037B, 0x00,
0x000386, 0x0E, // 'grek' greek
0x000387, 0x00,
0x000388, 0x0E, // 'grek' greek
0x00038B, 0x00,
0x00038C, 0x0E, // 'grek' greek
0x00038D, 0x00,
0x00038E, 0x0E, // 'grek' greek
0x0003A2, 0x00,
0x0003A3, 0x0E, // 'grek' greek
0x0003CF, 0x00,
0x0003D0, 0x0E, // 'grek' greek
0x0003F6, 0x00,
0x000400, 0x08, // 'cyrl' cyrillic
0x000482, 0x00,
0x000483, 0x08, // 'cyrl' cyrillic
0x000487, 0x00,
0x000488, 0x01, // 'qaai' inherited
0x00048A, 0x08, // 'cyrl' cyrillic
0x0004CF, 0x00,
0x0004D0, 0x08, // 'cyrl' cyrillic
0x0004F6, 0x00,
0x0004F8, 0x08, // 'cyrl' cyrillic
0x0004FA, 0x00,
0x000500, 0x08, // 'cyrl' cyrillic
0x000510, 0x00,
0x000531, 0x03, // 'armn' armenian
0x000557, 0x00,
0x000559, 0x03, // 'armn' armenian
0x00055A, 0x00,
0x000561, 0x03, // 'armn' armenian
0x000588, 0x00,
0x000591, 0x01, // 'qaai' inherited
0x0005A2, 0x00,
0x0005A3, 0x01, // 'qaai' inherited
0x0005BA, 0x00,
0x0005BB, 0x01, // 'qaai' inherited
0x0005BE, 0x00,
0x0005BF, 0x01, // 'qaai' inherited
0x0005C0, 0x00,
0x0005C1, 0x01, // 'qaai' inherited
0x0005C3, 0x00,
0x0005C4, 0x01, // 'qaai' inherited
0x0005C5, 0x00,
0x0005D0, 0x13, // 'hebr' hebrew
0x0005EB, 0x00,
0x0005F0, 0x13, // 'hebr' hebrew
0x0005F3, 0x00,
0x000621, 0x02, // 'arab' arabic
0x00063B, 0x00,
0x000641, 0x02, // 'arab' arabic
0x00064B, 0x01, // 'qaai' inherited
0x000656, 0x00,
0x00066E, 0x02, // 'arab' arabic
0x000670, 0x01, // 'qaai' inherited
0x000671, 0x02, // 'arab' arabic
0x0006D4, 0x00,
0x0006D5, 0x02, // 'arab' arabic
0x0006D6, 0x01, // 'qaai' inherited
0x0006E5, 0x02, // 'arab' arabic
0x0006E7, 0x01, // 'qaai' inherited
0x0006E9, 0x00,
0x0006EA, 0x01, // 'qaai' inherited
0x0006EE, 0x00,
0x0006FA, 0x02, // 'arab' arabic
0x0006FD, 0x00,
0x000710, 0x22, // 'syrc' syriac
0x00072D, 0x00,
0x000730, 0x22, // 'syrc' syriac
0x00074B, 0x00,
0x000780, 0x25, // 'thaa' thaana
0x0007B2, 0x00,
0x000901, 0x0A, // 'deva' devanagari
0x000904, 0x00,
0x000905, 0x0A, // 'deva' devanagari
0x00093A, 0x00,
0x00093C, 0x0A, // 'deva' devanagari
0x00094E, 0x00,
0x000950, 0x0A, // 'deva' devanagari
0x000955, 0x00,
0x000958, 0x0A, // 'deva' devanagari
0x000964, 0x00,
0x000966, 0x0A, // 'deva' devanagari
0x000970, 0x00,
0x000981, 0x04, // 'beng' bengali
0x000984, 0x00,
0x000985, 0x04, // 'beng' bengali
0x00098D, 0x00,
0x00098F, 0x04, // 'beng' bengali
0x000991, 0x00,
0x000993, 0x04, // 'beng' bengali
0x0009A9, 0x00,
0x0009AA, 0x04, // 'beng' bengali
0x0009B1, 0x00,
0x0009B2, 0x04, // 'beng' bengali
0x0009B3, 0x00,
0x0009B6, 0x04, // 'beng' bengali
0x0009BA, 0x00,
0x0009BC, 0x04, // 'beng' bengali
0x0009BD, 0x00,
0x0009BE, 0x04, // 'beng' bengali
0x0009C5, 0x00,
0x0009C7, 0x04, // 'beng' bengali
0x0009C9, 0x00,
0x0009CB, 0x04, // 'beng' bengali
0x0009CE, 0x00,
0x0009D7, 0x04, // 'beng' bengali
0x0009D8, 0x00,
0x0009DC, 0x04, // 'beng' bengali
0x0009DE, 0x00,
0x0009DF, 0x04, // 'beng' bengali
0x0009E4, 0x00,
0x0009E6, 0x04, // 'beng' bengali
0x0009F2, 0x00,
0x000A02, 0x10, // 'guru' gurmukhi
0x000A03, 0x00,
0x000A05, 0x10, // 'guru' gurmukhi
0x000A0B, 0x00,
0x000A0F, 0x10, // 'guru' gurmukhi
0x000A11, 0x00,
0x000A13, 0x10, // 'guru' gurmukhi
0x000A29, 0x00,
0x000A2A, 0x10, // 'guru' gurmukhi
0x000A31, 0x00,
0x000A32, 0x10, // 'guru' gurmukhi
0x000A34, 0x00,
0x000A35, 0x10, // 'guru' gurmukhi
0x000A37, 0x00,
0x000A38, 0x10, // 'guru' gurmukhi
0x000A3A, 0x00,
0x000A3C, 0x10, // 'guru' gurmukhi
0x000A3D, 0x00,
0x000A3E, 0x10, // 'guru' gurmukhi
0x000A43, 0x00,
0x000A47, 0x10, // 'guru' gurmukhi
0x000A49, 0x00,
0x000A4B, 0x10, // 'guru' gurmukhi
0x000A4E, 0x00,
0x000A59, 0x10, // 'guru' gurmukhi
0x000A5D, 0x00,
0x000A5E, 0x10, // 'guru' gurmukhi
0x000A5F, 0x00,
0x000A66, 0x10, // 'guru' gurmukhi
0x000A75, 0x00,
0x000A81, 0x0F, // 'gujr' gujarati
0x000A84, 0x00,
0x000A85, 0x0F, // 'gujr' gujarati
0x000A8C, 0x00,
0x000A8D, 0x0F, // 'gujr' gujarati
0x000A8E, 0x00,
0x000A8F, 0x0F, // 'gujr' gujarati
0x000A92, 0x00,
0x000A93, 0x0F, // 'gujr' gujarati
0x000AA9, 0x00,
0x000AAA, 0x0F, // 'gujr' gujarati
0x000AB1, 0x00,
0x000AB2, 0x0F, // 'gujr' gujarati
0x000AB4, 0x00,
0x000AB5, 0x0F, // 'gujr' gujarati
0x000ABA, 0x00,
0x000ABC, 0x0F, // 'gujr' gujarati
0x000AC6, 0x00,
0x000AC7, 0x0F, // 'gujr' gujarati
0x000ACA, 0x00,
0x000ACB, 0x0F, // 'gujr' gujarati
0x000ACE, 0x00,
0x000AD0, 0x0F, // 'gujr' gujarati
0x000AD1, 0x00,
0x000AE0, 0x0F, // 'gujr' gujarati
0x000AE1, 0x00,
0x000AE6, 0x0F, // 'gujr' gujarati
0x000AF0, 0x00,
0x000B01, 0x1F, // 'orya' oriya
0x000B04, 0x00,
0x000B05, 0x1F, // 'orya' oriya
0x000B0D, 0x00,
0x000B0F, 0x1F, // 'orya' oriya
0x000B11, 0x00,
0x000B13, 0x1F, // 'orya' oriya
0x000B29, 0x00,
0x000B2A, 0x1F, // 'orya' oriya
0x000B31, 0x00,
0x000B32, 0x1F, // 'orya' oriya
0x000B34, 0x00,
0x000B36, 0x1F, // 'orya' oriya
0x000B3A, 0x00,
0x000B3C, 0x1F, // 'orya' oriya
0x000B44, 0x00,
0x000B47, 0x1F, // 'orya' oriya
0x000B49, 0x00,
0x000B4B, 0x1F, // 'orya' oriya
0x000B4E, 0x00,
0x000B56, 0x1F, // 'orya' oriya
0x000B58, 0x00,
0x000B5C, 0x1F, // 'orya' oriya
0x000B5E, 0x00,
0x000B5F, 0x1F, // 'orya' oriya
0x000B62, 0x00,
0x000B66, 0x1F, // 'orya' oriya
0x000B70, 0x00,
0x000B82, 0x23, // 'taml' tamil
0x000B84, 0x00,
0x000B85, 0x23, // 'taml' tamil
0x000B8B, 0x00,
0x000B8E, 0x23, // 'taml' tamil
0x000B91, 0x00,
0x000B92, 0x23, // 'taml' tamil
0x000B96, 0x00,
0x000B99, 0x23, // 'taml' tamil
0x000B9B, 0x00,
0x000B9C, 0x23, // 'taml' tamil
0x000B9D, 0x00,
0x000B9E, 0x23, // 'taml' tamil
0x000BA0, 0x00,
0x000BA3, 0x23, // 'taml' tamil
0x000BA5, 0x00,
0x000BA8, 0x23, // 'taml' tamil
0x000BAB, 0x00,
0x000BAE, 0x23, // 'taml' tamil
0x000BB6, 0x00,
0x000BB7, 0x23, // 'taml' tamil
0x000BBA, 0x00,
0x000BBE, 0x23, // 'taml' tamil
0x000BC3, 0x00,
0x000BC6, 0x23, // 'taml' tamil
0x000BC9, 0x00,
0x000BCA, 0x23, // 'taml' tamil
0x000BCE, 0x00,
0x000BD7, 0x23, // 'taml' tamil
0x000BD8, 0x00,
0x000BE7, 0x23, // 'taml' tamil
0x000BF3, 0x00,
0x000C01, 0x24, // 'telu' telugu
0x000C04, 0x00,
0x000C05, 0x24, // 'telu' telugu
0x000C0D, 0x00,
0x000C0E, 0x24, // 'telu' telugu
0x000C11, 0x00,
0x000C12, 0x24, // 'telu' telugu
0x000C29, 0x00,
0x000C2A, 0x24, // 'telu' telugu
0x000C34, 0x00,
0x000C35, 0x24, // 'telu' telugu
0x000C3A, 0x00,
0x000C3E, 0x24, // 'telu' telugu
0x000C45, 0x00,
0x000C46, 0x24, // 'telu' telugu
0x000C49, 0x00,
0x000C4A, 0x24, // 'telu' telugu
0x000C4E, 0x00,
0x000C55, 0x24, // 'telu' telugu
0x000C57, 0x00,
0x000C60, 0x24, // 'telu' telugu
0x000C62, 0x00,
0x000C66, 0x24, // 'telu' telugu
0x000C70, 0x00,
0x000C82, 0x15, // 'knda' kannada
0x000C84, 0x00,
0x000C85, 0x15, // 'knda' kannada
0x000C8D, 0x00,
0x000C8E, 0x15, // 'knda' kannada
0x000C91, 0x00,
0x000C92, 0x15, // 'knda' kannada
0x000CA9, 0x00,
0x000CAA, 0x15, // 'knda' kannada
0x000CB4, 0x00,
0x000CB5, 0x15, // 'knda' kannada
0x000CBA, 0x00,
0x000CBE, 0x15, // 'knda' kannada
0x000CC5, 0x00,
0x000CC6, 0x15, // 'knda' kannada
0x000CC9, 0x00,
0x000CCA, 0x15, // 'knda' kannada
0x000CCE, 0x00,
0x000CD5, 0x15, // 'knda' kannada
0x000CD7, 0x00,
0x000CDE, 0x15, // 'knda' kannada
0x000CDF, 0x00,
0x000CE0, 0x15, // 'knda' kannada
0x000CE2, 0x00,
0x000CE6, 0x15, // 'knda' kannada
0x000CF0, 0x00,
0x000D02, 0x1A, // 'mlym' malayalam
0x000D04, 0x00,
0x000D05, 0x1A, // 'mlym' malayalam
0x000D0D, 0x00,
0x000D0E, 0x1A, // 'mlym' malayalam
0x000D11, 0x00,
0x000D12, 0x1A, // 'mlym' malayalam
0x000D29, 0x00,
0x000D2A, 0x1A, // 'mlym' malayalam
0x000D3A, 0x00,
0x000D3E, 0x1A, // 'mlym' malayalam
0x000D44, 0x00,
0x000D46, 0x1A, // 'mlym' malayalam
0x000D49, 0x00,
0x000D4A, 0x1A, // 'mlym' malayalam
0x000D4E, 0x00,
0x000D57, 0x1A, // 'mlym' malayalam
0x000D58, 0x00,
0x000D60, 0x1A, // 'mlym' malayalam
0x000D62, 0x00,
0x000D66, 0x1A, // 'mlym' malayalam
0x000D70, 0x00,
0x000D82, 0x21, // 'sinh' sinhala
0x000D84, 0x00,
0x000D85, 0x21, // 'sinh' sinhala
0x000D97, 0x00,
0x000D9A, 0x21, // 'sinh' sinhala
0x000DB2, 0x00,
0x000DB3, 0x21, // 'sinh' sinhala
0x000DBC, 0x00,
0x000DBD, 0x21, // 'sinh' sinhala
0x000DBE, 0x00,
0x000DC0, 0x21, // 'sinh' sinhala
0x000DC7, 0x00,
0x000DCA, 0x21, // 'sinh' sinhala
0x000DCB, 0x00,
0x000DCF, 0x21, // 'sinh' sinhala
0x000DD5, 0x00,
0x000DD6, 0x21, // 'sinh' sinhala
0x000DD7, 0x00,
0x000DD8, 0x21, // 'sinh' sinhala
0x000DE0, 0x00,
0x000DF2, 0x21, // 'sinh' sinhala
0x000DF4, 0x00,
0x000E01, 0x26, // 'thai' thai
0x000E3B, 0x00,
0x000E40, 0x26, // 'thai' thai
0x000E4F, 0x00,
0x000E50, 0x26, // 'thai' thai
0x000E5A, 0x00,
0x000E81, 0x18, // 'laoo' lao
0x000E83, 0x00,
0x000E84, 0x18, // 'laoo' lao
0x000E85, 0x00,
0x000E87, 0x18, // 'laoo' lao
0x000E89, 0x00,
0x000E8A, 0x18, // 'laoo' lao
0x000E8B, 0x00,
0x000E8D, 0x18, // 'laoo' lao
0x000E8E, 0x00,
0x000E94, 0x18, // 'laoo' lao
0x000E98, 0x00,
0x000E99, 0x18, // 'laoo' lao
0x000EA0, 0x00,
0x000EA1, 0x18, // 'laoo' lao
0x000EA4, 0x00,
0x000EA5, 0x18, // 'laoo' lao
0x000EA6, 0x00,
0x000EA7, 0x18, // 'laoo' lao
0x000EA8, 0x00,
0x000EAA, 0x18, // 'laoo' lao
0x000EAC, 0x00,
0x000EAD, 0x18, // 'laoo' lao
0x000EBA, 0x00,
0x000EBB, 0x18, // 'laoo' lao
0x000EBE, 0x00,
0x000EC0, 0x18, // 'laoo' lao
0x000EC5, 0x00,
0x000EC6, 0x18, // 'laoo' lao
0x000EC7, 0x00,
0x000EC8, 0x18, // 'laoo' lao
0x000ECE, 0x00,
0x000ED0, 0x18, // 'laoo' lao
0x000EDA, 0x00,
0x000EDC, 0x18, // 'laoo' lao
0x000EDE, 0x00,
0x000F00, 0x27, // 'tibt' tibetan
0x000F01, 0x00,
0x000F18, 0x27, // 'tibt' tibetan
0x000F1A, 0x00,
0x000F20, 0x27, // 'tibt' tibetan
0x000F34, 0x00,
0x000F35, 0x27, // 'tibt' tibetan
0x000F36, 0x00,
0x000F37, 0x27, // 'tibt' tibetan
0x000F38, 0x00,
0x000F39, 0x27, // 'tibt' tibetan
0x000F3A, 0x00,
0x000F40, 0x27, // 'tibt' tibetan
0x000F48, 0x00,
0x000F49, 0x27, // 'tibt' tibetan
0x000F6B, 0x00,
0x000F71, 0x27, // 'tibt' tibetan
0x000F85, 0x00,
0x000F86, 0x27, // 'tibt' tibetan
0x000F8C, 0x00,
0x000F90, 0x27, // 'tibt' tibetan
0x000F98, 0x00,
0x000F99, 0x27, // 'tibt' tibetan
0x000FBD, 0x00,
0x000FC6, 0x27, // 'tibt' tibetan
0x000FC7, 0x00,
0x001000, 0x1C, // 'mymr' myanmar
0x001022, 0x00,
0x001023, 0x1C, // 'mymr' myanmar
0x001028, 0x00,
0x001029, 0x1C, // 'mymr' myanmar
0x00102B, 0x00,
0x00102C, 0x1C, // 'mymr' myanmar
0x001033, 0x00,
0x001036, 0x1C, // 'mymr' myanmar
0x00103A, 0x00,
0x001040, 0x1C, // 'mymr' myanmar
0x00104A, 0x00,
0x001050, 0x1C, // 'mymr' myanmar
0x00105A, 0x00,
0x0010A0, 0x0C, // 'geor' georgian
0x0010C6, 0x00,
0x0010D0, 0x0C, // 'geor' georgian
0x0010F9, 0x00,
0x001100, 0x12, // 'hang' hangul
0x00115A, 0x00,
0x00115F, 0x12, // 'hang' hangul
0x0011A3, 0x00,
0x0011A8, 0x12, // 'hang' hangul
0x0011FA, 0x00,
0x001200, 0x0B, // 'ethi' ethiopic
0x001207, 0x00,
0x001208, 0x0B, // 'ethi' ethiopic
0x001247, 0x00,
0x001248, 0x0B, // 'ethi' ethiopic
0x001249, 0x00,
0x00124A, 0x0B, // 'ethi' ethiopic
0x00124E, 0x00,
0x001250, 0x0B, // 'ethi' ethiopic
0x001257, 0x00,
0x001258, 0x0B, // 'ethi' ethiopic
0x001259, 0x00,
0x00125A, 0x0B, // 'ethi' ethiopic
0x00125E, 0x00,
0x001260, 0x0B, // 'ethi' ethiopic
0x001287, 0x00,
0x001288, 0x0B, // 'ethi' ethiopic
0x001289, 0x00,
0x00128A, 0x0B, // 'ethi' ethiopic
0x00128E, 0x00,
0x001290, 0x0B, // 'ethi' ethiopic
0x0012AF, 0x00,
0x0012B0, 0x0B, // 'ethi' ethiopic
0x0012B1, 0x00,
0x0012B2, 0x0B, // 'ethi' ethiopic
0x0012B6, 0x00,
0x0012B8, 0x0B, // 'ethi' ethiopic
0x0012BF, 0x00,
0x0012C0, 0x0B, // 'ethi' ethiopic
0x0012C1, 0x00,
0x0012C2, 0x0B, // 'ethi' ethiopic
0x0012C6, 0x00,
0x0012C8, 0x0B, // 'ethi' ethiopic
0x0012CF, 0x00,
0x0012D0, 0x0B, // 'ethi' ethiopic
0x0012D7, 0x00,
0x0012D8, 0x0B, // 'ethi' ethiopic
0x0012EF, 0x00,
0x0012F0, 0x0B, // 'ethi' ethiopic
0x00130F, 0x00,
0x001310, 0x0B, // 'ethi' ethiopic
0x001311, 0x00,
0x001312, 0x0B, // 'ethi' ethiopic
0x001316, 0x00,
0x001318, 0x0B, // 'ethi' ethiopic
0x00131F, 0x00,
0x001320, 0x0B, // 'ethi' ethiopic
0x001347, 0x00,
0x001348, 0x0B, // 'ethi' ethiopic
0x00135B, 0x00,
0x001369, 0x0B, // 'ethi' ethiopic
0x00137D, 0x00,
0x0013A0, 0x06, // 'cher' cherokee
0x0013F5, 0x00,
0x001401, 0x28, // 'cans' canadian_aboriginal
0x00166D, 0x00,
0x00166F, 0x28, // 'cans' canadian_aboriginal
0x001677, 0x00,
0x001681, 0x1D, // 'ogam' ogham
0x00169B, 0x00,
0x0016A0, 0x20, // 'runr' runic
0x0016EB, 0x00,
0x0016EE, 0x20, // 'runr' runic
0x0016F1, 0x00,
0x001700, 0x2A, // 'tglg' tagalog
0x00170D, 0x00,
0x00170E, 0x2A, // 'tglg' tagalog
0x001715, 0x00,
0x001720, 0x2B, // 'hano' hanunoo
0x001735, 0x00,
0x001740, 0x2C, // 'buhd' buhid
0x001754, 0x00,
0x001760, 0x2D, // 'tagb' tagbanwa
0x00176D, 0x00,
0x00176E, 0x2D, // 'tagb' tagbanwa
0x001771, 0x00,
0x001772, 0x2D, // 'tagb' tagbanwa
0x001774, 0x00,
0x001780, 0x17, // 'khmr' khmer
0x0017D4, 0x00,
0x0017E0, 0x17, // 'khmr' khmer
0x0017EA, 0x00,
0x00180B, 0x01, // 'qaai' inherited
0x00180E, 0x00,
0x001810, 0x1B, // 'mong' mongolian
0x00181A, 0x00,
0x001820, 0x1B, // 'mong' mongolian
0x001878, 0x00,
0x001880, 0x1B, // 'mong' mongolian
0x0018AA, 0x00,
0x001E00, 0x19, // 'latn' latin
0x001E9C, 0x00,
0x001EA0, 0x19, // 'latn' latin
0x001EFA, 0x00,
0x001F00, 0x0E, // 'grek' greek
0x001F16, 0x00,
0x001F18, 0x0E, // 'grek' greek
0x001F1E, 0x00,
0x001F20, 0x0E, // 'grek' greek
0x001F46, 0x00,
0x001F48, 0x0E, // 'grek' greek
0x001F4E, 0x00,
0x001F50, 0x0E, // 'grek' greek
0x001F58, 0x00,
0x001F59, 0x0E, // 'grek' greek
0x001F5A, 0x00,
0x001F5B, 0x0E, // 'grek' greek
0x001F5C, 0x00,
0x001F5D, 0x0E, // 'grek' greek
0x001F5E, 0x00,
0x001F5F, 0x0E, // 'grek' greek
0x001F7E, 0x00,
0x001F80, 0x0E, // 'grek' greek
0x001FB5, 0x00,
0x001FB6, 0x0E, // 'grek' greek
0x001FBD, 0x00,
0x001FBE, 0x0E, // 'grek' greek
0x001FBF, 0x00,
0x001FC2, 0x0E, // 'grek' greek
0x001FC5, 0x00,
0x001FC6, 0x0E, // 'grek' greek
0x001FCD, 0x00,
0x001FD0, 0x0E, // 'grek' greek
0x001FD4, 0x00,
0x001FD6, 0x0E, // 'grek' greek
0x001FDC, 0x00,
0x001FE0, 0x0E, // 'grek' greek
0x001FED, 0x00,
0x001FF2, 0x0E, // 'grek' greek
0x001FF5, 0x00,
0x001FF6, 0x0E, // 'grek' greek
0x001FFD, 0x00,
0x002071, 0x19, // 'latn' latin
0x002072, 0x00,
0x00207F, 0x19, // 'latn' latin
0x002080, 0x00,
0x0020D0, 0x01, // 'qaai' inherited
0x0020EB, 0x00,
0x002126, 0x0E, // 'grek' greek
0x002127, 0x00,
0x00212A, 0x19, // 'latn' latin
0x00212C, 0x00,
0x002E80, 0x11, // 'hani' han
0x002E9A, 0x00,
0x002E9B, 0x11, // 'hani' han
0x002EF4, 0x00,
0x002F00, 0x11, // 'hani' han
0x002FD6, 0x00,
0x003005, 0x11, // 'hani' han
0x003006, 0x00,
0x003007, 0x11, // 'hani' han
0x003008, 0x00,
0x003021, 0x11, // 'hani' han
0x00302A, 0x01, // 'qaai' inherited
0x003030, 0x00,
0x003038, 0x11, // 'hani' han
0x00303C, 0x00,
0x003041, 0x14, // 'hira' hiragana
0x003097, 0x00,
0x003099, 0x01, // 'qaai' inherited
0x00309B, 0x00,
0x00309D, 0x14, // 'hira' hiragana
0x0030A0, 0x00,
0x0030A1, 0x16, // 'kana' katakana
0x0030FB, 0x00,
0x0030FD, 0x16, // 'kana' katakana
0x003100, 0x00,
0x003105, 0x05, // 'bopo' bopomofo
0x00312D, 0x00,
0x003131, 0x12, // 'hang' hangul
0x00318F, 0x00,
0x0031A0, 0x05, // 'bopo' bopomofo
0x0031B8, 0x00,
0x0031F0, 0x16, // 'kana' katakana
0x003200, 0x00,
0x003400, 0x11, // 'hani' han
0x004DB6, 0x00,
0x004E00, 0x11, // 'hani' han
0x009FA6, 0x00,
0x00A000, 0x29, // 'yiii' yi
0x00A48D, 0x00,
0x00A490, 0x29, // 'yiii' yi
0x00A4A2, 0x00,
0x00A4A4, 0x29, // 'yiii' yi
0x00A4B4, 0x00,
0x00A4B5, 0x29, // 'yiii' yi
0x00A4C1, 0x00,
0x00A4C2, 0x29, // 'yiii' yi
0x00A4C5, 0x00,
0x00A4C6, 0x29, // 'yiii' yi
0x00A4C7, 0x00,
0x00AC00, 0x12, // 'hang' hangul
0x00D7A4, 0x00,
0x00F900, 0x11, // 'hani' han
0x00FA2E, 0x00,
0x00FA30, 0x11, // 'hani' han
0x00FA6B, 0x00,
0x00FB00, 0x19, // 'latn' latin
0x00FB07, 0x00,
0x00FB13, 0x03, // 'armn' armenian
0x00FB18, 0x00,
0x00FB1D, 0x13, // 'hebr' hebrew
0x00FB1E, 0x01, // 'qaai' inherited
0x00FB1F, 0x13, // 'hebr' hebrew
0x00FB29, 0x00,
0x00FB2A, 0x13, // 'hebr' hebrew
0x00FB37, 0x00,
0x00FB38, 0x13, // 'hebr' hebrew
0x00FB3D, 0x00,
0x00FB3E, 0x13, // 'hebr' hebrew
0x00FB3F, 0x00,
0x00FB40, 0x13, // 'hebr' hebrew
0x00FB42, 0x00,
0x00FB43, 0x13, // 'hebr' hebrew
0x00FB45, 0x00,
0x00FB46, 0x13, // 'hebr' hebrew
0x00FB50, 0x02, // 'arab' arabic
0x00FBB2, 0x00,
0x00FBD3, 0x02, // 'arab' arabic
0x00FD3E, 0x00,
0x00FD50, 0x02, // 'arab' arabic
0x00FD90, 0x00,
0x00FD92, 0x02, // 'arab' arabic
0x00FDC8, 0x00,
0x00FDF0, 0x02, // 'arab' arabic
0x00FDFC, 0x00,
0x00FE00, 0x01, // 'qaai' inherited
0x00FE10, 0x00,
0x00FE20, 0x01, // 'qaai' inherited
0x00FE24, 0x00,
0x00FE70, 0x02, // 'arab' arabic
0x00FE75, 0x00,
0x00FE76, 0x02, // 'arab' arabic
0x00FEFD, 0x00,
0x00FF21, 0x19, // 'latn' latin
0x00FF3B, 0x00,
0x00FF41, 0x19, // 'latn' latin
0x00FF5B, 0x00,
0x00FF66, 0x16, // 'kana' katakana
0x00FF70, 0x00,
0x00FF71, 0x16, // 'kana' katakana
0x00FF9E, 0x00,
0x00FFA0, 0x12, // 'hang' hangul
0x00FFBF, 0x00,
0x00FFC2, 0x12, // 'hang' hangul
0x00FFC8, 0x00,
0x00FFCA, 0x12, // 'hang' hangul
0x00FFD0, 0x00,
0x00FFD2, 0x12, // 'hang' hangul
0x00FFD8, 0x00,
0x00FFDA, 0x12, // 'hang' hangul
0x00FFDD, 0x00,
0x010300, 0x1E, // 'ital' old_italic
0x01031F, 0x00,
0x010330, 0x0D, // 'goth' gothic
0x01034B, 0x00,
0x010400, 0x09, // 'dsrt' deseret
0x010426, 0x00,
0x010428, 0x09, // 'dsrt' deseret
0x01044E, 0x00,
0x01D167, 0x01, // 'qaai' inherited
0x01D16A, 0x00,
0x01D17B, 0x01, // 'qaai' inherited
0x01D183, 0x00,
0x01D185, 0x01, // 'qaai' inherited
0x01D18C, 0x00,
0x01D1AA, 0x01, // 'qaai' inherited
0x01D1AE, 0x00,
0x020000, 0x11, // 'hani' han
0x02A6D7, 0x00,
0x02F800, 0x11, // 'hani' han
0x02FA1E, 0x00,
0x110000, -1, // (NO NAME)
};
private static final int dataPower = 1 << 10;
private static final int dataExtra = data.length - dataPower;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
/*
* Copyright (c) 1998, 2015, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998-2003 - All Rights Reserved
*/
package sun.font;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
final class StandardTextSource extends TextSource {
private final char[] chars;
private final int start;
private final int len;
private final int cstart;
private final int clen;
private final int level; // assumed all uniform
private final int flags; // see GlyphVector.java
private final Font font;
private final FontRenderContext frc;
private final CoreMetrics cm;
/**
* Create a simple implementation of a TextSource.
*
* Chars is an array containing clen chars in the context, in
* logical order, contiguously starting at cstart. Start and len
* represent that portion of the context representing the true
* source; start, like cstart, is relative to the start of the
* character array.
*
* Level is the bidi level (0-63 for the entire context. Flags is
* the layout flags. Font is the font, frc is the render context,
* and lm is the line metrics for the entire source text, but not
* necessarily the context.
*/
StandardTextSource(char[] chars,
int start,
int len,
int cstart,
int clen,
int level,
int flags,
Font font,
FontRenderContext frc,
CoreMetrics cm) {
if (chars == null) {
throw new IllegalArgumentException("bad chars: null");
}
if (cstart < 0) {
throw new IllegalArgumentException("bad cstart: " + cstart);
}
if (start < cstart) {
throw new IllegalArgumentException("bad start: " + start + " for cstart: " + cstart);
}
if (clen < 0) {
throw new IllegalArgumentException("bad clen: " + clen);
}
if (cstart + clen > chars.length) {
throw new IllegalArgumentException("bad clen: " + clen + " cstart: " + cstart + " for array len: " + chars.length);
}
if (len < 0) {
throw new IllegalArgumentException("bad len: " + len);
}
if ((start + len) > (cstart + clen)) {
throw new IllegalArgumentException("bad len: " + len + " start: " + start + " for cstart: " + cstart + " clen: " + clen);
}
if (font == null) {
throw new IllegalArgumentException("bad font: null");
}
if (frc == null) {
throw new IllegalArgumentException("bad frc: null");
}
this.chars = chars;
this.start = start;
this.len = len;
this.cstart = cstart;
this.clen = clen;
this.level = level;
this.flags = flags;
this.font = font;
this.frc = frc;
if (cm != null) {
this.cm = cm;
} else {
LineMetrics metrics = font.getLineMetrics(chars, cstart, clen, frc);
this.cm = ((FontLineMetrics)metrics).cm;
}
}
// TextSource API
public char[] getChars() {
return chars;
}
public int getStart() {
return start;
}
public int getLength() {
return len;
}
public int getContextStart() {
return cstart;
}
public int getContextLength() {
return clen;
}
public int getLayoutFlags() {
return flags;
}
public int getBidiLevel() {
return level;
}
public Font getFont() {
return font;
}
public FontRenderContext getFRC() {
return frc;
}
public CoreMetrics getCoreMetrics() {
return cm;
}
public TextSource getSubSource(int start, int length, int dir) {
if (start < 0 || length < 0 || (start + length) > len) {
throw new IllegalArgumentException("bad start (" + start + ") or length (" + length + ")");
}
int level = this.level;
if (dir != TextLineComponent.UNCHANGED) {
boolean ltr = (flags & 0x8) == 0;
if (!(dir == TextLineComponent.LEFT_TO_RIGHT && ltr) &&
!(dir == TextLineComponent.RIGHT_TO_LEFT && !ltr)) {
throw new IllegalArgumentException("direction flag is invalid");
}
level = ltr? 0 : 1;
}
return new StandardTextSource(chars, this.start + start, length, cstart, clen, level, flags, font, frc, cm);
}
public String toString() {
return toString(WITH_CONTEXT);
}
public String toString(boolean withContext) {
StringBuffer buf = new StringBuffer(super.toString());
buf.append("[start:");
buf.append(start);
buf.append(", len:" );
buf.append(len);
buf.append(", cstart:");
buf.append(cstart);
buf.append(", clen:" );
buf.append(clen);
buf.append(", chars:\"");
int chStart, chLimit;
if (withContext == WITH_CONTEXT) {
chStart = cstart;
chLimit = cstart + clen;
}
else {
chStart = start;
chLimit = start + len;
}
for (int i = chStart; i < chLimit; ++i) {
if (i > chStart) {
buf.append(" ");
}
buf.append(Integer.toHexString(chars[i]));
}
buf.append("\"");
buf.append(", level:");
buf.append(level);
buf.append(", flags:");
buf.append(flags);
buf.append(", font:");
buf.append(font);
buf.append(", frc:");
buf.append(frc);
buf.append(", cm:");
buf.append(cm);
buf.append("]");
return buf.toString();
}
}

View File

@@ -0,0 +1,444 @@
/*
* Copyright (c) 2003, 2013, 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.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.*;
import sun.java2d.Disposer;
import sun.java2d.pipe.BufferedContext;
import sun.java2d.pipe.RenderQueue;
import sun.java2d.pipe.hw.AccelGraphicsConfig;
import sun.misc.Unsafe;
/**
A FontStrike is the keeper of scaled glyph image data which is expensive
to compute so needs to be cached.
So long as that data may be being used it cannot be invalidated.
Yet we also need to limit the amount of native memory and number of
strike objects in use.
For scaleability and ease of use, a key goal is multi-threaded read
access to a strike, so that it may be shared by multiple client objects,
potentially executing on different threads, with no special reference
counting or "check-out/check-in" requirements which would pass on the
burden of keeping track of strike references to the SG2D and other clients.
A cache of strikes is maintained via Reference objects.
This helps in two ways :
1. The VM will free references when memory is low or they have not been
used in a long time.
2. Reference queues provide a way to get notification of this so we can
free native memory resources.
*/
public final class StrikeCache {
static final Unsafe unsafe = Unsafe.getUnsafe();
static ReferenceQueue refQueue = Disposer.getQueue();
static ArrayList<GlyphDisposedListener> disposeListeners = new ArrayList<GlyphDisposedListener>(1);
/* Reference objects may have their referents cleared when GC chooses.
* During application client start-up there is typically at least one
* GC which causes the hotspot VM to clear soft (not just weak) references
* Thus not only is there a GC pause, but the work done do rasterise
* glyphs that are fairly certain to be needed again almost immediately
* is thrown away. So for performance reasons a simple optimisation is to
* keep up to 8 strong references to strikes to reduce the chance of
* GC'ing strikes that have been used recently. Note that this may not
* suffice in Solaris UTF-8 locales where a single composite strike may be
* composed of 15 individual strikes, plus the composite strike.
* And this assumes the new architecture doesn't maintain strikes for
* natively accessed bitmaps. It may be worth "tuning" the number of
* strikes kept around for the platform or locale.
* Since no attempt is made to ensure uniqueness or ensure synchronized
* access there is no guarantee that this cache will ensure that unique
* strikes are cached. Every time a strike is looked up it is added
* to the current index in this cache. All this cache has to do to be
* worthwhile is prevent excessive cache flushing of strikes that are
* referenced frequently. The logic that adds references here could be
* tweaked to keep only strikes that represent untransformed, screen
* sizes as that's the typical performance case.
*/
static int MINSTRIKES = 8; // can be overridden by property
static int recentStrikeIndex = 0;
static FontStrike[] recentStrikes;
static boolean cacheRefTypeWeak;
/*
* Native sizes and offsets for glyph cache
* There are 10 values.
*/
static int nativeAddressSize;
static int glyphInfoSize;
static int xAdvanceOffset;
static int yAdvanceOffset;
static int boundsOffset;
static int widthOffset;
static int heightOffset;
static int rowBytesOffset;
static int topLeftXOffset;
static int topLeftYOffset;
static int pixelDataOffset;
static int cacheCellOffset;
static int managedOffset;
static long invisibleGlyphPtr;
/* Native method used to return information used for unsafe
* access to native data.
* return values as follows:-
* arr[0] = size of an address/pointer.
* arr[1] = size of a GlyphInfo
* arr[2] = offset of advanceX
* arr[3] = offset of advanceY
* arr[4] = offset of width
* arr[5] = offset of height
* arr[6] = offset of rowBytes
* arr[7] = offset of topLeftX
* arr[8] = offset of topLeftY
* arr[9] = offset of pixel data.
* arr[10] = address of a GlyphImageRef representing the invisible glyph
*/
static native void getGlyphCacheDescription(long[] infoArray);
static {
long[] nativeInfo = new long[13];
getGlyphCacheDescription(nativeInfo);
//Can also get address size from Unsafe class :-
//nativeAddressSize = unsafe.addressSize();
nativeAddressSize = (int)nativeInfo[0];
glyphInfoSize = (int)nativeInfo[1];
xAdvanceOffset = (int)nativeInfo[2];
yAdvanceOffset = (int)nativeInfo[3];
widthOffset = (int)nativeInfo[4];
heightOffset = (int)nativeInfo[5];
rowBytesOffset = (int)nativeInfo[6];
topLeftXOffset = (int)nativeInfo[7];
topLeftYOffset = (int)nativeInfo[8];
pixelDataOffset = (int)nativeInfo[9];
invisibleGlyphPtr = nativeInfo[10];
cacheCellOffset = (int) nativeInfo[11];
managedOffset = (int) nativeInfo[12];
if (nativeAddressSize < 4) {
throw new InternalError("Unexpected address size for font data: " +
nativeAddressSize);
}
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
/* Allow a client to override the reference type used to
* cache strikes. The default is "soft" which hints to keep
* the strikes around. This property allows the client to
* override this to "weak" which hint to the GC to free
* memory more aggressively.
*/
String refType =
System.getProperty("sun.java2d.font.reftype", "soft");
cacheRefTypeWeak = refType.equals("weak");
String minStrikesStr =
System.getProperty("sun.java2d.font.minstrikes");
if (minStrikesStr != null) {
try {
MINSTRIKES = Integer.parseInt(minStrikesStr);
if (MINSTRIKES <= 0) {
MINSTRIKES = 1;
}
} catch (NumberFormatException e) {
}
}
recentStrikes = new FontStrike[MINSTRIKES];
return null;
}
});
}
static void refStrike(FontStrike strike) {
int index = recentStrikeIndex;
recentStrikes[index] = strike;
index++;
if (index == MINSTRIKES) {
index = 0;
}
recentStrikeIndex = index;
}
private static final void doDispose(FontStrikeDisposer disposer) {
if (disposer.intGlyphImages != null) {
freeCachedIntMemory(disposer.intGlyphImages,
disposer.pScalerContext);
} else if (disposer.longGlyphImages != null) {
freeCachedLongMemory(disposer.longGlyphImages,
disposer.pScalerContext);
} else if (disposer.segIntGlyphImages != null) {
/* NB Now making multiple JNI calls in this case.
* But assuming that there's a reasonable amount of locality
* rather than sparse references then it should be OK.
*/
for (int i=0; i<disposer.segIntGlyphImages.length; i++) {
if (disposer.segIntGlyphImages[i] != null) {
freeCachedIntMemory(disposer.segIntGlyphImages[i],
disposer.pScalerContext);
/* native will only free the scaler context once */
disposer.pScalerContext = 0L;
disposer.segIntGlyphImages[i] = null;
}
}
/* This may appear inefficient but it should only be invoked
* for a strike that never was asked to rasterise a glyph.
*/
if (disposer.pScalerContext != 0L) {
freeCachedIntMemory(new int[0], disposer.pScalerContext);
}
} else if (disposer.segLongGlyphImages != null) {
for (int i=0; i<disposer.segLongGlyphImages.length; i++) {
if (disposer.segLongGlyphImages[i] != null) {
freeCachedLongMemory(disposer.segLongGlyphImages[i],
disposer.pScalerContext);
disposer.pScalerContext = 0L;
disposer.segLongGlyphImages[i] = null;
}
}
if (disposer.pScalerContext != 0L) {
freeCachedLongMemory(new long[0], disposer.pScalerContext);
}
} else if (disposer.pScalerContext != 0L) {
/* Rarely a strike may have been created that never cached
* any glyphs. In this case we still want to free the scaler
* context.
*/
if (longAddresses()) {
freeCachedLongMemory(new long[0], disposer.pScalerContext);
} else {
freeCachedIntMemory(new int[0], disposer.pScalerContext);
}
}
}
private static boolean longAddresses() {
return nativeAddressSize == 8;
}
static void disposeStrike(final FontStrikeDisposer disposer) {
// we need to execute the strike disposal on the rendering thread
// because they may be accessed on that thread at the time of the
// disposal (for example, when the accel. cache is invalidated)
// Whilst this is a bit heavyweight, in most applications
// strike disposal is a relatively infrequent operation, so it
// doesn't matter. But in some tests that use vast numbers
// of strikes, the switching back and forth is measurable.
// So the "pollRemove" call is added to batch up the work.
// If we are polling we know we've already been called back
// and can directly dispose the record.
// Also worrisome is the necessity of getting a GC here.
if (Disposer.pollingQueue) {
doDispose(disposer);
return;
}
RenderQueue rq = null;
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
if (!ge.isHeadless()) {
GraphicsConfiguration gc =
ge.getDefaultScreenDevice().getDefaultConfiguration();
if (gc instanceof AccelGraphicsConfig) {
AccelGraphicsConfig agc = (AccelGraphicsConfig)gc;
BufferedContext bc = agc.getContext();
if (bc != null) {
rq = bc.getRenderQueue();
}
}
}
if (rq != null) {
rq.lock();
try {
rq.flushAndInvokeNow(new Runnable() {
public void run() {
doDispose(disposer);
Disposer.pollRemove();
}
});
} finally {
rq.unlock();
}
} else {
doDispose(disposer);
}
}
static native void freeIntPointer(int ptr);
static native void freeLongPointer(long ptr);
private static native void freeIntMemory(int[] glyphPtrs, long pContext);
private static native void freeLongMemory(long[] glyphPtrs, long pContext);
private static void freeCachedIntMemory(int[] glyphPtrs, long pContext) {
synchronized(disposeListeners) {
if (disposeListeners.size() > 0) {
ArrayList<Long> gids = null;
for (int i = 0; i < glyphPtrs.length; i++) {
if (glyphPtrs[i] != 0 && unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
if (gids == null) {
gids = new ArrayList<Long>();
}
gids.add((long) glyphPtrs[i]);
}
}
if (gids != null) {
// Any reference by the disposers to the native glyph ptrs
// must be done before this returns.
notifyDisposeListeners(gids);
}
}
}
freeIntMemory(glyphPtrs, pContext);
}
private static void freeCachedLongMemory(long[] glyphPtrs, long pContext) {
synchronized(disposeListeners) {
if (disposeListeners.size() > 0) {
ArrayList<Long> gids = null;
for (int i=0; i < glyphPtrs.length; i++) {
if (glyphPtrs[i] != 0
&& unsafe.getByte(glyphPtrs[i] + managedOffset) == 0) {
if (gids == null) {
gids = new ArrayList<Long>();
}
gids.add((long) glyphPtrs[i]);
}
}
if (gids != null) {
// Any reference by the disposers to the native glyph ptrs
// must be done before this returns.
notifyDisposeListeners(gids);
}
}
}
freeLongMemory(glyphPtrs, pContext);
}
public static void addGlyphDisposedListener(GlyphDisposedListener listener) {
synchronized(disposeListeners) {
disposeListeners.add(listener);
}
}
private static void notifyDisposeListeners(ArrayList<Long> glyphs) {
for (GlyphDisposedListener listener : disposeListeners) {
listener.glyphDisposed(glyphs);
}
}
public static Reference getStrikeRef(FontStrike strike) {
return getStrikeRef(strike, cacheRefTypeWeak);
}
public static Reference getStrikeRef(FontStrike strike, boolean weak) {
/* Some strikes may have no disposer as there's nothing
* for them to free, as they allocated no native resource
* eg, if they did not allocate resources because of a problem,
* or they never hold native resources. So they create no disposer.
* But any strike that reaches here that has a null disposer is
* a potential memory leak.
*/
if (strike.disposer == null) {
if (weak) {
return new WeakReference(strike);
} else {
return new SoftReference(strike);
}
}
if (weak) {
return new WeakDisposerRef(strike);
} else {
return new SoftDisposerRef(strike);
}
}
static interface DisposableStrike {
FontStrikeDisposer getDisposer();
}
static class SoftDisposerRef
extends SoftReference implements DisposableStrike {
private FontStrikeDisposer disposer;
public FontStrikeDisposer getDisposer() {
return disposer;
}
SoftDisposerRef(FontStrike strike) {
super(strike, StrikeCache.refQueue);
disposer = strike.disposer;
Disposer.addReference(this, disposer);
}
}
static class WeakDisposerRef
extends WeakReference implements DisposableStrike {
private FontStrikeDisposer disposer;
public FontStrikeDisposer getDisposer() {
return disposer;
}
WeakDisposerRef(FontStrike strike) {
super(strike, StrikeCache.refQueue);
disposer = strike.disposer;
Disposer.addReference(this, disposer);
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (c) 2003, 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.geom.AffineTransform;
import java.awt.geom.Point2D;
/* These are font metrics: they are in user space, not device space.
* Hence they are not truly "strike" metrics. However it is convenient to
* treat them as such since we need to have a scaler context to obtain them
* and also to cache them. The old implementation obtained a C++ strike object
* that matched the Font TX + pt size only. It was wasteful of strike objects.
* This new implementation still has separate StrikeMetrics for 2 fonts that
* are really the same but are used in different device transforms, but at
* least it doesn't create a whole new strike just to get the metrics for
* a strike in a transformed graphics.
* So these metrics do not take into account the device transform. They
* are considered inherent properties of the font. Hence it may be that we
* should use the device transform to obtain the most accurate metrics, but
* typically 1.1 APIs do not provide for this. So some APIs may want to
* ignore the dev. tx and others may want to use it, and then apply an
* inverse transform. For now we ignore the dev. tx.
* "Font" metrics are representative of a typical glyph in the font.
* Generally speaking these values are the choice of the font designer and
* are stored in the font, from which we retrieve the values. They do
* not necessarily equate to the maximum bounds of all glyphs in the font.
* Note that the ascent fields are typically a -ve value as we use a top-left
* origin user space, and text is positioned relative to its baseline.
*/
public final class StrikeMetrics {
public float ascentX;
public float ascentY;
public float descentX;
public float descentY;
public float baselineX;
public float baselineY;
public float leadingX;
public float leadingY;
public float maxAdvanceX;
public float maxAdvanceY;
/* The no-args constructor is used by CompositeStrike, which then
* merges in the metrics of physical fonts.
* The approach here is the same as earlier releases but it is flawed
* take for example the following which ignores leading for simplicity.
* Say we have a composite with an element asc=-9, dsc=2, and another with
* asc=-7, dsc=3. The merged font is (-9,3) for height of -(-9)+3=12.
* Suppose this same font has been derived with a 180% rotation
* Now its signs for ascent/descent are reversed. Its (9,-2) and (7,-3)
* Its merged values are (using the code in this class) (7,-2) for
* a height of -(7)+-2 = =-9!
* We need to have a more intelligent merging algorithm,
* which so far as I can see needs to apply an inverse of the font
* tx, do its merging, and then reapply the font tx.
* This wouldn't often be a problem as there rarely is a font TX, and
* the tricky part is getting the information. Probably the no-args
* constructor needs to pass a TX in to be applied to all merges.
* CompositeStrike would be left with the problem of figuring out what
* tx to use.
* But at least for now we are probably no worse than 1.4 ...
* REMIND: FIX THIS.
*/
StrikeMetrics() {
ascentX = ascentY = Integer.MAX_VALUE;
descentX = descentY = leadingX = leadingY = Integer.MIN_VALUE;
baselineX = baselineX = maxAdvanceX = maxAdvanceY = Integer.MIN_VALUE;
}
StrikeMetrics(float ax, float ay, float dx, float dy, float bx, float by,
float lx, float ly, float mx, float my) {
ascentX = ax;
ascentY = ay;
descentX = dx;
descentY = dy;
baselineX = bx;
baselineY = by;
leadingX = lx;
leadingY = ly;
maxAdvanceX = mx;
maxAdvanceY = my;
}
public float getAscent() {
return -ascentY;
}
public float getDescent() {
return descentY;
}
public float getLeading() {
return leadingY;
}
public float getMaxAdvance() {
return maxAdvanceX;
}
/*
* Currently only used to merge together slot metrics to create
* the metrics for a composite font.
*/
void merge(StrikeMetrics other) {
if (other == null) {
return;
}
if (other.ascentX < ascentX) {
ascentX = other.ascentX;
}
if (other.ascentY < ascentY) {
ascentY = other.ascentY;
}
if (other.descentX > descentX) {
descentX = other.descentX;
}
if (other.descentY > descentY) {
descentY = other.descentY;
}
if (other.baselineX > baselineX) {
baselineX = other.baselineX;
}
if (other.baselineY > baselineY) {
baselineY = other.baselineY;
}
if (other.leadingX > leadingX) {
leadingX = other.leadingX;
}
if (other.leadingY > leadingY) {
leadingY = other.leadingY;
}
if (other.maxAdvanceX > maxAdvanceX) {
maxAdvanceX = other.maxAdvanceX;
}
if (other.maxAdvanceY > maxAdvanceY) {
maxAdvanceY = other.maxAdvanceY;
}
}
/* Used to transform the values back into user space.
* This is done ONCE by the strike so clients should not need
* to worry about this
*/
void convertToUserSpace(AffineTransform invTx) {
Point2D.Float pt2D = new Point2D.Float();
pt2D.x = ascentX; pt2D.y = ascentY;
invTx.deltaTransform(pt2D, pt2D);
ascentX = pt2D.x; ascentY = pt2D.y;
pt2D.x = descentX; pt2D.y = descentY;
invTx.deltaTransform(pt2D, pt2D);
descentX = pt2D.x; descentY = pt2D.y;
pt2D.x = baselineX; pt2D.y = baselineY;
invTx.deltaTransform(pt2D, pt2D);
baselineX = pt2D.x; baselineY = pt2D.y;
pt2D.x = leadingX; pt2D.y = leadingY;
invTx.deltaTransform(pt2D, pt2D);
leadingX = pt2D.x; leadingY = pt2D.y;
pt2D.x = maxAdvanceX; pt2D.y = maxAdvanceY;
invTx.deltaTransform(pt2D, pt2D);
maxAdvanceX = pt2D.x; maxAdvanceY = pt2D.y;
}
public String toString() {
return "ascent:x=" + ascentX + " y=" + ascentY +
" descent:x=" + descentX + " y=" + descentY +
" baseline:x=" + baselineX + " y=" + baselineY +
" leading:x=" + leadingX + " y=" + leadingY +
" maxAdvance:x=" + maxAdvanceX + " y=" + maxAdvanceY;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,198 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003 - All Rights Reserved
*/
package sun.font;
import sun.font.GlyphLayout.*;
import java.awt.geom.Point2D;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Locale;
import java.util.WeakHashMap;
/*
* different ways to do this
* 1) each physical font2d keeps a hashtable mapping scripts to layout
* engines, we query and fill this cache.
* 2) we keep a mapping independent of font using the key Most likely
* few fonts will be used, so option 2 seems better
*
* Once we know which engine to use for a font, we always know, so we
* shouldn't have to recheck each time we do layout. So the cache is
* ok.
*
* Should we reuse engines? We could instantiate an engine for each
* font/script pair. The engine would hold onto the table(s) from the
* font that it needs. If we have multiple threads using the same
* engine, we still need to keep the state separate, so the native
* engines would still need to be allocated for each call, since they
* keep their state in themselves. If they used the passed-in GVData
* arrays directly (with some checks for space) then since each GVData
* is different per thread, we could reuse the layout engines. This
* still requires a separate layout engine per font, because of the
* table state in the engine. If we pushed that out too and passed it
* in with the native call as well, we'd be ok if the layout engines
* keep all their process state on the stack, but I don't know if this
* is true. Then we'd basically just be down to an engine index which
* we pass into native and then invoke the engine code (now a
* procedure call, not an object invocation) based on a switch on the
* index. There would be only half a dozen engine objects then, not
* potentially half a dozen per font. But we'd have to stack-allocate
* some state that included the pointer to the required font tables.
*
* Seems for now that the way to do things is to come in with a
* selector and the font. The selector indicates which engine to use,
* the engine is stack allocated and initialized with the required
* font tables (the selector indicates which). Then layout is called,
* the contents are copied (or not), and the stack is destroyed on
* exit. So the association is between the font/script (layout engine
* desc) and and one of a few permanent engine objects, which are
* handed the key when they need to process something. In the native
* case, the engine holds an index, and just passes it together with
* the key info down to native. Some default cases are the 'default
* layout' case that just runs the c2gmapper, this stays in java and
* just uses the mapper from the font/strike. Another default case
* might be the unicode arabic shaper, since this doesn't care about
* the font (or script or lang?) it wouldn't need to extract this
* data. It could be (yikes) ported back to java even to avoid
* upcalls to check if the font supports a particular unicode
* character.
*
* I'd expect that the majority of scripts use the default mapper for
* a particular font. Loading the hastable with 40 or so keys 30+ of
* which all map to the same object is unfortunate. It might be worth
* instead having a per-font list of 'scripts with non-default
* engines', e.g. the factory has a hashtable mapping fonts to 'script
* lists' (the factory has this since the design potentially has other
* factories, though I admit there's no client for this yet and no
* public api) and then the script list is queried for the script in
* question. it can be preloaded at creation time with all the
* scripts that don't have default engines-- either a list or a hash
* table, so a null return from the table means 'default' and not 'i
* don't know yet'.
*
* On the other hand, in most all cases the number of unique
* script/font combinations will be small, so a flat hashtable should
* suffice.
* */
public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory {
private static native void initGVIDs();
static {
FontManagerNativeLibrary.load();
initGVIDs();
}
private LayoutEngineKey key;
private static LayoutEngineFactory instance;
public static LayoutEngineFactory instance() {
if (instance == null) {
instance = new SunLayoutEngine();
}
return instance;
}
private SunLayoutEngine() {
// actually a factory, key is null so layout cannot be called on it
}
public LayoutEngine getEngine(Font2D font, int script, int lang) {
return getEngine(new LayoutEngineKey(font, script, lang));
}
// !!! don't need this unless we have more than one sun layout engine...
public LayoutEngine getEngine(LayoutEngineKey key) {
ConcurrentHashMap cache = (ConcurrentHashMap)cacheref.get();
if (cache == null) {
cache = new ConcurrentHashMap();
cacheref = new SoftReference(cache);
}
LayoutEngine e = (LayoutEngine)cache.get(key);
if (e == null) {
LayoutEngineKey copy = key.copy();
e = new SunLayoutEngine(copy);
cache.put(copy, e);
}
return e;
}
private SoftReference cacheref = new SoftReference(null);
private SunLayoutEngine(LayoutEngineKey key) {
this.key = key;
}
static WeakHashMap<Font2D, Boolean> aatInfo = new WeakHashMap<>();
private boolean isAAT(Font2D font) {
Boolean aatObj;
synchronized (aatInfo) {
aatObj = aatInfo.get(font);
}
if (aatObj != null) {
return aatObj.booleanValue();
}
boolean aat = false;
if (font instanceof TrueTypeFont) {
TrueTypeFont ttf = (TrueTypeFont)font;
aat = ttf.getDirectoryEntry(TrueTypeFont.morxTag) != null ||
ttf.getDirectoryEntry(TrueTypeFont.mortTag) != null;
} else if (font instanceof PhysicalFont) {
PhysicalFont pf = (PhysicalFont)font;
aat = pf.getTableBytes(TrueTypeFont.morxTag) != null ||
pf.getTableBytes(TrueTypeFont.mortTag) != null;
}
synchronized (aatInfo) {
aatInfo.put(font, Boolean.valueOf(aat));
}
return aat;
}
public void layout(FontStrikeDesc desc, float[] mat, int gmask,
int baseIndex, TextRecord tr, int typo_flags,
Point2D.Float pt, GVData data) {
Font2D font = key.font();
FontStrike strike = font.getStrike(desc);
// Ignore layout tables for RTL AAT fonts due to lack of support in ICU
long layoutTables = (((typo_flags & 0x80000000) != 0) && isAAT(font)) ? 0 :
font.getLayoutTableCache();
nativeLayout(font, strike, mat, gmask, baseIndex,
tr.text, tr.start, tr.limit, tr.min, tr.max,
key.script(), key.lang(), typo_flags, pt, data,
font.getUnitsPerEm(), layoutTables);
}
private static native void
nativeLayout(Font2D font, FontStrike strike, float[] mat, int gmask,
int baseIndex, char[] chars, int offset, int limit,
int min, int max, int script, int lang, int typo_flags,
Point2D.Float pt, GVData data, long upem, long layoutTables);
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (c) 1998, 2003, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998-2003 All Rights Reserved
*/
package sun.font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
/**
* A label.
* Visual bounds is a rect that encompasses the entire rendered area.
* Logical bounds is a rect that defines how to position this next
* to other objects.
* Align bounds is a rect that defines how to align this to margins.
* it generally allows some overhang that logical bounds would prevent.
*/
public abstract class TextLabel {
/**
* Return a rectangle that surrounds the text outline when this label is rendered at x, y.
*/
public abstract Rectangle2D getVisualBounds(float x, float y);
/**
* Return a rectangle that corresponds to the logical bounds of the text
* when this label is rendered at x, y.
* This rectangle is used when positioning text next to other text.
*/
public abstract Rectangle2D getLogicalBounds(float x, float y);
/**
* Return a rectangle that corresponds to the alignment bounds of the text
* when this label is rendered at x, y. This rectangle is used when positioning text next
* to a margin. It differs from the logical bounds in that it does not include leading or
* trailing whitespace.
*/
public abstract Rectangle2D getAlignBounds(float x, float y);
/**
* Return a rectangle that corresponds to the logical bounds of the text, adjusted
* to angle the leading and trailing edges by the italic angle.
*/
public abstract Rectangle2D getItalicBounds(float x, float y);
/**
* Return an outline of the characters in the label when rendered at x, y.
*/
public abstract Shape getOutline(float x, float y);
/**
* Render the label at x, y in the graphics.
*/
public abstract void draw(Graphics2D g, float x, float y);
/**
* A convenience method that returns the visual bounds when rendered at 0, 0.
*/
public Rectangle2D getVisualBounds() {
return getVisualBounds(0f, 0f);
}
/**
* A convenience method that returns the logical bounds when rendered at 0, 0.
*/
public Rectangle2D getLogicalBounds() {
return getLogicalBounds(0f, 0f);
}
/**
* A convenience method that returns the align bounds when rendered at 0, 0.
*/
public Rectangle2D getAlignBounds() {
return getAlignBounds(0f, 0f);
}
/**
* A convenience method that returns the italic bounds when rendered at 0, 0.
*/
public Rectangle2D getItalicBounds() {
return getItalicBounds(0f, 0f);
}
/**
* A convenience method that returns the outline when rendered at 0, 0.
*/
public Shape getOutline() {
return getOutline(0f, 0f);
}
/**
* A convenience method that renders the label at 0, 0.
*/
public void draw(Graphics2D g) {
draw(g, 0f, 0f);
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 1998, 2015, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998-2003 All Rights Reserved
*/
package sun.font;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.text.Bidi;
/**
* A factory for text labels. Basically this just holds onto the stuff that
* doesn't change-- the render context, context, and bidi info for the context-- and gets
* called for each subrange you want to create.
*
* @see Font
* @see FontRenderContext
* @see GlyphVector
* @see TextLabel
* @see ExtendedTextLabel
* @see Bidi
* @see TextLayout
*/
public final class TextLabelFactory {
private final FontRenderContext frc;
private final char[] text;
private final Bidi bidi;
private Bidi lineBidi;
private final int flags;
private int lineStart;
private int lineLimit;
/**
* Initialize a factory to produce glyph arrays.
* @param frc the FontRenderContext to use for the arrays to be produced.
* @param text the text of the paragraph.
* @param bidi the bidi information for the paragraph text, or null if the
* entire text is left-to-right text.
*/
public TextLabelFactory(FontRenderContext frc,
char[] text,
Bidi bidi,
int flags) {
this.frc = frc;
this.text = text.clone();
this.bidi = bidi;
this.flags = flags;
this.lineBidi = bidi;
this.lineStart = 0;
this.lineLimit = text.length;
}
public FontRenderContext getFontRenderContext() {
return frc;
}
public Bidi getLineBidi() {
return lineBidi;
}
/**
* Set a line context for the factory. Shaping only occurs on this line.
* Characters are ordered as they would appear on this line.
* @param lineStart the index within the text of the start of the line.
* @param lineLimit the index within the text of the limit of the line.
*/
public void setLineContext(int lineStart, int lineLimit) {
this.lineStart = lineStart;
this.lineLimit = lineLimit;
if (bidi != null) {
lineBidi = bidi.createLineBidi(lineStart, lineLimit);
}
}
/**
* Create an extended glyph array for the text between start and limit.
*
* @param font the font to use to generate glyphs and character positions.
* @param start the start of the subrange for which to create the glyph array
* @param limit the limit of the subrange for which to create glyph array
*
* Start and limit must be within the bounds of the current line. If no
* line context has been set, the entire text is used as the current line.
* The text between start and limit will be treated as though it all has
* the same bidi level (and thus the same directionality) as the character
* at start. Clients should ensure that all text between start and limit
* has the same bidi level for the current line.
*/
public ExtendedTextLabel createExtended(Font font,
CoreMetrics lm,
Decoration decorator,
int start,
int limit) {
if (start >= limit || start < lineStart || limit > lineLimit) {
throw new IllegalArgumentException("bad start: " + start + " or limit: " + limit);
}
int level = lineBidi == null ? 0 : lineBidi.getLevelAt(start - lineStart);
int linedir = (lineBidi == null || lineBidi.baseIsLeftToRight()) ? 0 : 1;
int layoutFlags = flags & ~0x9; // remove bidi, line direction flags
if ((level & 0x1) != 0) layoutFlags |= 1; // rtl
if ((linedir & 0x1) != 0) layoutFlags |= 8; // line rtl
TextSource source = new StandardTextSource(text, start, limit - start, lineStart, lineLimit - lineStart, level, layoutFlags, font, frc, lm);
return new ExtendedTextSourceLabel(source, decorator);
}
/**
* Create a simple glyph array for the text between start and limit.
*
* @param font the font to use to generate glyphs and character positions.
* @param start the start of the subrange for which to create the glyph array
* @param limit the limit of the subrange for which to create glyph array
*/
public TextLabel createSimple(Font font,
CoreMetrics lm,
int start,
int limit) {
if (start >= limit || start < lineStart || limit > lineLimit) {
throw new IllegalArgumentException("bad start: " + start + " or limit: " + limit);
}
int level = lineBidi == null ? 0 : lineBidi.getLevelAt(start - lineStart);
int linedir = (lineBidi == null || lineBidi.baseIsLeftToRight()) ? 0 : 1;
int layoutFlags = flags & ~0x9; // remove bidi, line direction flags
if ((level & 0x1) != 0) layoutFlags |= 1; // rtl
if ((linedir & 0x1) != 0) layoutFlags |= 8; // line rtl
TextSource source = new StandardTextSource(text, start, limit - start, lineStart, lineLimit - lineStart, level, layoutFlags, font, frc, lm);
return new TextSourceLabel(source);
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (c) 1998, 2005, 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.
*/
/*
* (C) Copyright IBM Corp. 1998-2003 All Rights Reserved
*
*/
package sun.font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.GlyphJustificationInfo;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
public interface TextLineComponent {
public CoreMetrics getCoreMetrics();
public void draw(Graphics2D g2d, float x, float y);
public Rectangle2D getCharVisualBounds(int index);
public Rectangle2D getVisualBounds();
public float getAdvance();
public Shape getOutline(float x, float y);
public int getNumCharacters();
public float getCharX(int index);
public float getCharY(int index);
public float getCharAdvance(int index);
public boolean caretAtOffsetIsValid(int index);
// measures characters in context, in logical order
public int getLineBreakIndex(int start, float width);
// measures characters in context, in logical order
public float getAdvanceBetween(int start, int limit);
public Rectangle2D getLogicalBounds();
public Rectangle2D getItalicBounds();
public AffineTransform getBaselineTransform();
// return true if this wraps a glyphvector with no baseline rotation and
// has no styles requiring complex pixel bounds calculations.
public boolean isSimple();
// return the pixel bounds if we wrap a glyphvector, else throw an
// internal error
public Rectangle getPixelBounds(FontRenderContext frc, float x, float y);
/**
* Force subset characters to run left-to-right.
*/
public static final int LEFT_TO_RIGHT = 0;
/**
* Force subset characters to run right-to-left.
*/
public static final int RIGHT_TO_LEFT = 1;
/**
* Leave subset character direction and ordering unchanged.
*/
public static final int UNCHANGED = 2;
/**
* Return a TextLineComponent for the characters in the range
* start, limit. The range is relative to this TextLineComponent
* (ie, the first character is at 0).
* @param dir one of the constants LEFT_TO_RIGHT, RIGHT_TO_LEFT, or UNCHANGED
*/
public TextLineComponent getSubset(int start, int limit, int dir);
/**
* Return the number of justification records this uses.
*/
public int getNumJustificationInfos();
/**
* Return GlyphJustificationInfo objects for the characters between
* charStart and charLimit, starting at offset infoStart. Infos
* will be in visual order. All positions between infoStart and
* getNumJustificationInfos will be set. If a position corresponds
* to a character outside the provided range, it is set to null.
*/
public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit);
/**
* Apply deltas to the data in this component, starting at offset
* deltaStart, and return the new component. There are two floats
* for each justification info, for a total of 2 * getNumJustificationInfos.
* The first delta is the left adjustment, the second is the right
* adjustment.
* <p>
* If flags[0] is true on entry, rejustification is allowed. If
* the new component requires rejustification (ligatures were
* formed or split), flags[0] will be set on exit.
*/
public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags);
}

View File

@@ -0,0 +1,50 @@
/*
* 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.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003 - All Rights Reserved
*/
package sun.font;
/**
* Represents a region of text and context
*/
public final class TextRecord {
public char[] text;
public int start;
public int limit;
public int min;
public int max;
public void init(char[] text, int start, int limit, int min, int max) {
this.text = text;
this.start = start;
this.limit = limit;
this.min = min;
this.max = max;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 1998, 2003, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998-2003 - All Rights Reserved
*/
package sun.font;
import java.awt.Font;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
/**
* A text source represents text for rendering, plus context information.
* All text in the source uses the same font, metrics, and render context,
* and is at the same bidi level.
*/
public abstract class TextSource {
/** Source character data. */
public abstract char[] getChars();
/** Start of source data in char array returned from getChars. */
public abstract int getStart();
/** Length of source data. */
public abstract int getLength();
/** Start of context data in char array returned from getChars. */
public abstract int getContextStart();
/** Length of context data. */
public abstract int getContextLength();
/** Return the layout flags */
public abstract int getLayoutFlags();
/** Bidi level of all the characters in context. */
public abstract int getBidiLevel();
/** Font for source data. */
public abstract Font getFont();
/** Font render context to use when measuring or rendering source data. */
public abstract FontRenderContext getFRC();
/** Line metrics for source data. */
public abstract CoreMetrics getCoreMetrics();
/** Get subrange of this TextSource. dir is one of the TextLineComponent constants */
public abstract TextSource getSubSource(int start, int length, int dir);
/** Constant for toString(boolean). Indicates that toString should not return info
outside of the context of this instance. */
public static final boolean WITHOUT_CONTEXT = false;
/** Constant for toString(boolean). Indicates that toString should return info
outside of the context of this instance. */
public static final boolean WITH_CONTEXT = true;
/** Get debugging info about this TextSource instance. Default implementation just
returns toString. Subclasses should implement this to match the semantics of
the toString constants. */
public abstract String toString(boolean withContext);
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (c) 1998, 2005, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998, 1999 - All Rights Reserved
*/
package sun.font;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
/**
* Implementation of TextLabel based on String.
*/
public class TextSourceLabel extends TextLabel {
TextSource source;
// caches
Rectangle2D lb;
Rectangle2D ab;
Rectangle2D vb;
Rectangle2D ib;
GlyphVector gv;
public TextSourceLabel(TextSource source) {
this(source, null, null, null);
}
public TextSourceLabel(TextSource source, Rectangle2D lb, Rectangle2D ab, GlyphVector gv) {
this.source = source;
this.lb = lb;
this.ab = ab;
this.gv = gv;
}
public TextSource getSource() {
return source;
}
public final Rectangle2D getLogicalBounds(float x, float y) {
if (lb == null) {
lb = createLogicalBounds();
}
return new Rectangle2D.Float((float)(lb.getX() + x),
(float)(lb.getY() + y),
(float)lb.getWidth(),
(float)lb.getHeight());
}
public final Rectangle2D getVisualBounds(float x, float y) {
if (vb == null) {
vb = createVisualBounds();
}
return new Rectangle2D.Float((float)(vb.getX() + x),
(float)(vb.getY() + y),
(float)vb.getWidth(),
(float)vb.getHeight());
}
public final Rectangle2D getAlignBounds(float x, float y) {
if (ab == null) {
ab = createAlignBounds();
}
return new Rectangle2D.Float((float)(ab.getX() + x),
(float)(ab.getY() + y),
(float)ab.getWidth(),
(float)ab.getHeight());
}
public Rectangle2D getItalicBounds(float x, float y) {
if (ib == null) {
ib = createItalicBounds();
}
return new Rectangle2D.Float((float)(ib.getX() + x),
(float)(ib.getY() + y),
(float)ib.getWidth(),
(float)ib.getHeight());
}
public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
return getGV().getPixelBounds(frc, x, y); // no cache
}
public AffineTransform getBaselineTransform() {
Font font = source.getFont();
if (font.hasLayoutAttributes()) {
return AttributeValues.getBaselineTransform(font.getAttributes());
}
return null;
}
public Shape getOutline(float x, float y) {
return getGV().getOutline(x, y);
}
public void draw(Graphics2D g, float x, float y) {
g.drawGlyphVector(getGV(), x, y);
}
protected Rectangle2D createLogicalBounds() {
return getGV().getLogicalBounds();
}
protected Rectangle2D createVisualBounds() {
return getGV().getVisualBounds();
}
protected Rectangle2D createItalicBounds() {
// !!! fix
return getGV().getLogicalBounds();
}
protected Rectangle2D createAlignBounds() {
return createLogicalBounds();
}
private final GlyphVector getGV() {
if (gv == null) {
gv = createGV();
}
return gv;
}
protected GlyphVector createGV() {
Font font = source.getFont();
FontRenderContext frc = source.getFRC();
int flags = source.getLayoutFlags();
char[] context = source.getChars();
int start = source.getStart();
int length = source.getLength();
GlyphLayout gl = GlyphLayout.get(null); // !!! no custom layout engines
StandardGlyphVector gv = gl.layout(font, frc, context, start, length,
flags, null); // ??? use textsource
GlyphLayout.done(gl);
return gv;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2003, 2006, 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.nio.ByteBuffer;
import java.util.Locale;
public class TrueTypeGlyphMapper extends CharToGlyphMapper {
static final char REVERSE_SOLIDUS = 0x005c; // the backslash char.
static final char JA_YEN = 0x00a5;
static final char JA_FULLWIDTH_TILDE_CHAR = 0xff5e;
static final char JA_WAVE_DASH_CHAR = 0x301c;
/* if running on Solaris and default Locale is ja_JP then
* we map need to remap reverse solidus (backslash) to Yen as
* apparently expected there.
*/
static final boolean isJAlocale = Locale.JAPAN.equals(Locale.getDefault());
private final boolean needsJAremapping;
private boolean remapJAWaveDash;
TrueTypeFont font;
CMap cmap;
int numGlyphs;
public TrueTypeGlyphMapper(TrueTypeFont font) {
this.font = font;
try {
cmap = CMap.initialize(font);
} catch (Exception e) {
cmap = null;
}
if (cmap == null) {
handleBadCMAP();
}
missingGlyph = 0; /* standard for TrueType fonts */
ByteBuffer buffer = font.getTableBuffer(TrueTypeFont.maxpTag);
if (buffer != null && buffer.capacity() >= 6) {
numGlyphs = buffer.getChar(4); // offset 4 bytes in MAXP table.
} else {
handleBadCMAP();
}
if (FontUtilities.isSolaris && isJAlocale && font.supportsJA()) {
needsJAremapping = true;
if (FontUtilities.isSolaris8 &&
getGlyphFromCMAP(JA_WAVE_DASH_CHAR) == missingGlyph) {
remapJAWaveDash = true;
}
} else {
needsJAremapping = false;
}
}
public int getNumGlyphs() {
return numGlyphs;
}
private char getGlyphFromCMAP(int charCode) {
try {
char glyphCode = cmap.getGlyph(charCode);
if (glyphCode < numGlyphs ||
glyphCode >= FileFontStrike.INVISIBLE_GLYPHS) {
return glyphCode;
} else {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().warning
(font + " out of range glyph id=" +
Integer.toHexString((int)glyphCode) +
" for char " + Integer.toHexString(charCode));
}
return (char)missingGlyph;
}
} catch(Exception e) {
handleBadCMAP();
return (char) missingGlyph;
}
}
private void handleBadCMAP() {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().severe("Null Cmap for " + font +
"substituting for this font");
}
SunFontManager.getInstance().deRegisterBadFont(font);
/* The next line is not really a solution, but might
* reduce the exceptions until references to this font2D
* are gone.
*/
cmap = CMap.theNullCmap;
}
private final char remapJAChar(char unicode) {
switch (unicode) {
case REVERSE_SOLIDUS:
return JA_YEN;
/* This is a workaround for bug 4533422.
* Japanese wave dash missing from Solaris JA TrueType fonts.
*/
case JA_WAVE_DASH_CHAR:
if (remapJAWaveDash) {
return JA_FULLWIDTH_TILDE_CHAR;
}
default: return unicode;
}
}
private final int remapJAIntChar(int unicode) {
switch (unicode) {
case REVERSE_SOLIDUS:
return JA_YEN;
/* This is a workaround for bug 4533422.
* Japanese wave dash missing from Solaris JA TrueType fonts.
*/
case JA_WAVE_DASH_CHAR:
if (remapJAWaveDash) {
return JA_FULLWIDTH_TILDE_CHAR;
}
default: return unicode;
}
}
public int charToGlyph(char unicode) {
if (needsJAremapping) {
unicode = remapJAChar(unicode);
}
int glyph = getGlyphFromCMAP(unicode);
if (font.checkUseNatives() && glyph < font.glyphToCharMap.length) {
font.glyphToCharMap[glyph] = unicode;
}
return glyph;
}
public int charToGlyph(int unicode) {
if (needsJAremapping) {
unicode = remapJAIntChar(unicode);
}
int glyph = getGlyphFromCMAP(unicode);
if (font.checkUseNatives() && glyph < font.glyphToCharMap.length) {
font.glyphToCharMap[glyph] = (char)unicode;
}
return glyph;
}
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
for (int i=0;i<count;i++) {
if (needsJAremapping) {
glyphs[i] = getGlyphFromCMAP(remapJAIntChar(unicodes[i]));
} else {
glyphs[i] = getGlyphFromCMAP(unicodes[i]);
}
if (font.checkUseNatives() &&
glyphs[i] < font.glyphToCharMap.length) {
font.glyphToCharMap[glyphs[i]] = (char)unicodes[i];
}
}
}
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code;
if (needsJAremapping) {
code = remapJAChar(unicodes[i]);
} else {
code = unicodes[i]; // char is unsigned.
}
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
glyphs[i] = getGlyphFromCMAP(code);
i += 1; // Empty glyph slot after surrogate
glyphs[i] = INVISIBLE_GLYPH_ID;
continue;
}
}
glyphs[i] = getGlyphFromCMAP(code);
if (font.checkUseNatives() &&
glyphs[i] < font.glyphToCharMap.length) {
font.glyphToCharMap[glyphs[i]] = (char)code;
}
}
}
/* This variant checks if shaping is needed and immediately
* returns true if it does. A caller of this method should be expecting
* to check the return type because it needs to know how to handle
* the character data for display.
*/
public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code;
if (needsJAremapping) {
code = remapJAChar(unicodes[i]);
} else {
code = unicodes[i]; // char is unsigned.
}
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
glyphs[i + 1] = INVISIBLE_GLYPH_ID;
}
}
glyphs[i] = getGlyphFromCMAP(code);
if (font.checkUseNatives() &&
glyphs[i] < font.glyphToCharMap.length) {
font.glyphToCharMap[glyphs[i]] = (char)code;
}
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
continue;
}
else if (FontUtilities.isComplexCharCode(code)) {
return true;
}
else if (code >= 0x10000) {
i += 1; // Empty glyph slot after surrogate
continue;
}
}
return false;
}
/* A pretty good heuristic is that the cmap we are using
* supports 32 bit character codes.
*/
boolean hasSupplementaryChars() {
return
cmap instanceof CMap.CMapFormat8 ||
cmap instanceof CMap.CMapFormat10 ||
cmap instanceof CMap.CMapFormat12;
}
}

View File

@@ -0,0 +1,682 @@
/*
* Copyright (c) 2003, 2013, 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.WeakReference;
import java.awt.FontFormatException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.BufferUnderflowException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import java.util.HashSet;
import java.util.HashMap;
import java.awt.Font;
/*
* Adobe Technical Note 5040 details the format of PFB files.
* the file is divided into ascii and binary sections. Each section
* starts with a header
* 0x8001 - start of binary data, is followed by 4 bytes length, then data
* 0x8002 - start of ascii data, is followed by 4 bytes length, then data
* 0x8003 - end of data segment
* The length is organised as LSB->MSB.
*
* Note: I experimented with using a MappedByteBuffer and
* there were two problems/questions.
* 1. If a global buffer is used rather than one allocated in the calling
* context, then we need to synchronize on all uses of that data, which
* means more code would beed to be synchronized with probable repercussions
* elsewhere.
* 2. It is not clear whether to free the buffer when the file is closed.
* If we have the contents in memory then why keep open files around?
* The mmapped buffer doesn't need it.
* Also regular GC is what frees the buffer. So closing the file and nulling
* out the reference still needs to wait for the buffer to be GC'd to
* reclaim the storage.
* If the contents of the buffer are persistent there's no need
* to worry about synchronization.
* Perhaps could use a WeakReference, and when its referent is gone, and
* need it can just reopen the file.
* Type1 fonts thus don't use up file descriptor references, but can
* use memory footprint in a way that's managed by the host O/S.
* The main "pain" may be the different model means code needs to be written
* without assumptions as to how this is handled by the different subclasses
* of FileFont.
*/
public class Type1Font extends FileFont {
private static class T1DisposerRecord implements DisposerRecord {
String fileName = null;
T1DisposerRecord(String name) {
fileName = name;
}
public synchronized void dispose() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
if (fileName != null) {
(new java.io.File(fileName)).delete();
}
return null;
}
});
}
}
WeakReference bufferRef = new WeakReference(null);
private String psName = null;
static private HashMap styleAbbreviationsMapping;
static private HashSet styleNameTokes;
static {
styleAbbreviationsMapping = new HashMap();
styleNameTokes = new HashSet();
/* These abbreviation rules are taken from Appendix 1 of Adobe Technical Note #5088 */
/* NB: this list is not complete - we did not include abbreviations which contain
several capital letters because current expansion algorithm do not support this.
(namely we have omited MM aka "Multiple Master", OsF aka "Oldstyle figures",
OS aka "Oldstyle", SC aka "Small caps" and DS aka "Display" */
String nm[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
"Meduium", "Nord", "Poster", "Regular", "Super", "Thin",
"Compressed", "Condensed", "Compact", "Extended", "Narrow",
"Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped",
"Semi", "Ultra", "Extra",
"Alternate", "Alternate", "Deutsche Fraktur", "Expert", "Inline", "Ornaments",
"Outline", "Roman", "Rounded", "Script", "Shaded", "Swash", "Titling", "Typewriter"};
String abbrv[] = {"Blk", "Bd", "Bk", "Dm", "Hv", "Lt",
"Md", "Nd", "Po", "Rg", "Su", "Th",
"Cm", "Cn", "Ct", "Ex", "Nr",
"Ic", "It", "Ks", "Obl", "Up", "Sl",
"Sm", "Ult", "X",
"A", "Alt", "Dfr", "Exp", "In", "Or",
"Ou", "Rm", "Rd", "Scr", "Sh", "Sw", "Ti", "Typ"};
/* This is only subset of names from nm[] because we want to distinguish things
like "Lucida Sans TypeWriter Bold" and "Lucida Sans Bold".
Names from "Design and/or special purpose" group are omitted. */
String styleTokens[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
"Medium", "Nord", "Poster", "Regular", "Super", "Thin",
"Compressed", "Condensed", "Compact", "Extended", "Narrow",
"Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped", "Slanted",
"Semi", "Ultra", "Extra"};
for(int i=0; i<nm.length; i++) {
styleAbbreviationsMapping.put(abbrv[i], nm[i]);
}
for(int i=0; i<styleTokens.length; i++) {
styleNameTokes.add(styleTokens[i]);
}
}
/**
* Constructs a Type1 Font.
* @param platname - Platform identifier of the font. Typically file name.
* @param nativeNames - Native names - typically XLFDs on Unix.
*/
public Type1Font(String platname, Object nativeNames)
throws FontFormatException {
this(platname, nativeNames, false);
}
/**
* - does basic verification of the file
* - reads the names (full, family).
* - determines the style of the font.
* @throws FontFormatException - if the font can't be opened
* or fails verification, or there's no usable cmap
*/
public Type1Font(String platname, Object nativeNames, boolean createdCopy)
throws FontFormatException {
super(platname, nativeNames);
fontRank = Font2D.TYPE1_RANK;
checkedNatives = true;
try {
verify();
} catch (Throwable t) {
if (createdCopy) {
T1DisposerRecord ref = new T1DisposerRecord(platname);
Disposer.addObjectRecord(bufferRef, ref);
bufferRef = null;
}
if (t instanceof FontFormatException) {
throw (FontFormatException)t;
} else {
throw new FontFormatException("Unexpected runtime exception.");
}
}
}
private synchronized ByteBuffer getBuffer() throws FontFormatException {
MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
if (mapBuf == null) {
//System.out.println("open T1 " + platName);
try {
RandomAccessFile raf = (RandomAccessFile)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
return new RandomAccessFile(platName, "r");
} catch (FileNotFoundException ffne) {
}
return null;
}
});
FileChannel fc = raf.getChannel();
fileSize = (int)fc.size();
mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
mapBuf.position(0);
bufferRef = new WeakReference(mapBuf);
fc.close();
} catch (NullPointerException e) {
throw new FontFormatException(e.toString());
} catch (ClosedChannelException e) {
/* NIO I/O is interruptible, recurse to retry operation.
* Clear interrupts before recursing in case NIO didn't.
*/
Thread.interrupted();
return getBuffer();
} catch (IOException e) {
throw new FontFormatException(e.toString());
}
}
return mapBuf;
}
protected void close() {
}
/* called from native code to read file into a direct byte buffer */
void readFile(ByteBuffer buffer) {
RandomAccessFile raf = null;
FileChannel fc;
try {
raf = (RandomAccessFile)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
return new RandomAccessFile(platName, "r");
} catch (FileNotFoundException fnfe) {
}
return null;
}
});
fc = raf.getChannel();
while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
} catch (NullPointerException npe) {
} catch (ClosedChannelException e) {
try {
if (raf != null) {
raf.close();
raf = null;
}
} catch (IOException ioe) {
}
/* NIO I/O is interruptible, recurse to retry operation.
* Clear interrupts before recursing in case NIO didn't.
*/
Thread.interrupted();
readFile(buffer);
} catch (IOException e) {
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
}
}
}
}
public synchronized ByteBuffer readBlock(int offset, int length) {
ByteBuffer mappedBuf = null;
try {
mappedBuf = getBuffer();
if (offset > fileSize) {
offset = fileSize;
}
mappedBuf.position(offset);
return mappedBuf.slice();
} catch (FontFormatException e) {
return null;
}
}
private void verify() throws FontFormatException {
/* Normal usage should not call getBuffer(), as its state
* ie endianness, position etc, are shared. verify() can do
* this as its called only from within the constructor before
* there are other users of this object.
*/
ByteBuffer bb = getBuffer();
if (bb.capacity() < 6) {
throw new FontFormatException("short file");
}
int val = bb.get(0) & 0xff;
if ((bb.get(0) & 0xff) == 0x80) {
verifyPFB(bb);
bb.position(6);
} else {
verifyPFA(bb);
bb.position(0);
}
initNames(bb);
if (familyName == null || fullName == null) {
throw new FontFormatException("Font name not found");
}
setStyle();
}
public int getFileSize() {
if (fileSize == 0) {
try {
getBuffer();
} catch (FontFormatException e) {
}
}
return fileSize;
}
private void verifyPFA(ByteBuffer bb) throws FontFormatException {
if (bb.getShort() != 0x2521) { // 0x2521 is %!
throw new FontFormatException("bad pfa font");
}
// remind - additional verification needed?
}
private void verifyPFB(ByteBuffer bb) throws FontFormatException {
int pos = 0;
while (true) {
try {
int segType = bb.getShort(pos) & 0xffff;
if (segType == 0x8001 || segType == 0x8002) {
bb.order(ByteOrder.LITTLE_ENDIAN);
int segLen = bb.getInt(pos+2);
bb.order(ByteOrder.BIG_ENDIAN);
if (segLen <= 0) {
throw new FontFormatException("bad segment length");
}
pos += segLen+6;
} else if (segType == 0x8003) {
return;
} else {
throw new FontFormatException("bad pfb file");
}
} catch (BufferUnderflowException bue) {
throw new FontFormatException(bue.toString());
} catch (Exception e) {
throw new FontFormatException(e.toString());
}
}
}
private static final int PSEOFTOKEN = 0;
private static final int PSNAMETOKEN = 1;
private static final int PSSTRINGTOKEN = 2;
/* Need to parse the ascii contents of the Type1 font file,
* looking for FullName, FamilyName and FontName.
* If explicit names are not found then extract them from first text line.
* Operating on bytes so can't use Java String utilities, which
* is a large part of why this is a hack.
*
* Also check for mandatory FontType and verify if it is supported.
*/
private void initNames(ByteBuffer bb) throws FontFormatException {
boolean eof = false;
String fontType = null;
try {
//Parse font looking for explicit FullName, FamilyName and FontName
// (according to Type1 spec they are optional)
while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
int tokenType = nextTokenType(bb);
if (tokenType == PSNAMETOKEN) {
int pos = bb.position();
if (bb.get(pos) == 'F') {
String s = getSimpleToken(bb);
if ("FullName".equals(s)) {
if (nextTokenType(bb)==PSSTRINGTOKEN) {
fullName = getString(bb);
}
} else if ("FamilyName".equals(s)) {
if (nextTokenType(bb)==PSSTRINGTOKEN) {
familyName = getString(bb);
}
} else if ("FontName".equals(s)) {
if (nextTokenType(bb)==PSNAMETOKEN) {
psName = getSimpleToken(bb);
}
} else if ("FontType".equals(s)) {
/* look for
/FontType id def
*/
String token = getSimpleToken(bb);
if ("def".equals(getSimpleToken(bb))) {
fontType = token;
}
}
} else {
while (bb.get() > ' '); // skip token
}
} else if (tokenType == PSEOFTOKEN) {
eof = true;
}
}
} catch (Exception e) {
throw new FontFormatException(e.toString());
}
/* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
if (!"1".equals(fontType)) {
throw new FontFormatException("Unsupported font type");
}
if (psName == null) { //no explicit FontName
// Try to extract font name from the first text line.
// According to Type1 spec first line consist of
// "%!FontType1-SpecVersion: FontName FontVersion"
// or
// "%!PS-AdobeFont-1.0: FontName version"
bb.position(0);
if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
//skip segment header and "%!"
bb.position(8);
//NB: assume that first segment is ASCII one
// (is it possible to have valid Type1 font with first binary segment?)
}
String formatType = getSimpleToken(bb);
if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
throw new FontFormatException("Unsupported font format [" + formatType + "]");
}
psName = getSimpleToken(bb);
}
//if we got to the end of file then we did not find at least one of FullName or FamilyName
//Try to deduce missing names from present ones
//NB: At least psName must be already initialized by this moment
if (eof) {
//if we find fullName or familyName then use it as another name too
if (fullName != null) {
familyName = fullName2FamilyName(fullName);
} else if (familyName != null) {
fullName = familyName;
} else { //fallback - use postscript font name to deduce full and family names
fullName = psName2FullName(psName);
familyName = psName2FamilyName(psName);
}
}
}
private String fullName2FamilyName(String name) {
String res, token;
int len, start, end; //length of family name part
//FamilyName is truncated version of FullName
//Truncated tail must contain only style modifiers
end = name.length();
while (end > 0) {
start = end - 1;
while (start > 0 && name.charAt(start) != ' ')
start--;
//as soon as we meet first non style token truncate
// current tail and return
if (!isStyleToken(name.substring(start+1, end))) {
return name.substring(0, end);
}
end = start;
}
return name; //should not happen
}
private String expandAbbreviation(String abbr) {
if (styleAbbreviationsMapping.containsKey(abbr))
return (String) styleAbbreviationsMapping.get(abbr);
return abbr;
}
private boolean isStyleToken(String token) {
return styleNameTokes.contains(token);
}
private String psName2FullName(String name) {
String res;
int pos;
//According to Adobe technical note #5088 psName (aka FontName) has form
// <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
//where spaces are not allowed.
//Conversion: Expand abbreviations in style portion (everything after '-'),
// replace '-' with space and insert missing spaces
pos = name.indexOf("-");
if (pos >= 0) {
res = expandName(name.substring(0, pos), false);
res += " " + expandName(name.substring(pos+1), true);
} else {
res = expandName(name, false);
}
return res;
}
private String psName2FamilyName(String name) {
String tmp = name;
//According to Adobe technical note #5088 psName (aka FontName) has form
// <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
//where spaces are not allowed.
//Conversion: Truncate style portion (everything after '-')
// and insert missing spaces
if (tmp.indexOf("-") > 0) {
tmp = tmp.substring(0, tmp.indexOf("-"));
}
return expandName(tmp, false);
}
private int nextCapitalLetter(String s, int off) {
for (; (off >=0) && off < s.length(); off++) {
if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
return off;
}
return -1;
}
private String expandName(String s, boolean tryExpandAbbreviations) {
StringBuffer res = new StringBuffer(s.length() + 10);
int start=0, end;
while(start < s.length()) {
end = nextCapitalLetter(s, start + 1);
if (end < 0) {
end = s.length();
}
if (start != 0) {
res.append(" ");
}
if (tryExpandAbbreviations) {
res.append(expandAbbreviation(s.substring(start, end)));
} else {
res.append(s.substring(start, end));
}
start = end;
}
return res.toString();
}
/* skip lines beginning with "%" and leading white space on a line */
private byte skip(ByteBuffer bb) {
byte b = bb.get();
while (b == '%') {
while (true) {
b = bb.get();
if (b == '\r' || b == '\n') {
break;
}
}
}
while (b <= ' ') {
b = bb.get();
}
return b;
}
/*
* Token types:
* PSNAMETOKEN - /
* PSSTRINGTOKEN - literal text string
*/
private int nextTokenType(ByteBuffer bb) {
try {
byte b = skip(bb);
while (true) {
if (b == (byte)'/') { // PS defined name follows.
return PSNAMETOKEN;
} else if (b == (byte)'(') { // PS string follows
return PSSTRINGTOKEN;
} else if ((b == (byte)'\r') || (b == (byte)'\n')) {
b = skip(bb);
} else {
b = bb.get();
}
}
} catch (BufferUnderflowException e) {
return PSEOFTOKEN;
}
}
/* Read simple token (sequence of non-whitespace characters)
starting from the current position.
Skip leading whitespaces (if any). */
private String getSimpleToken(ByteBuffer bb) {
while (bb.get() <= ' ');
int pos1 = bb.position()-1;
while (bb.get() > ' ');
int pos2 = bb.position();
byte[] nameBytes = new byte[pos2-pos1-1];
bb.position(pos1);
bb.get(nameBytes);
try {
return new String(nameBytes, "US-ASCII");
} catch (UnsupportedEncodingException e) {
return new String(nameBytes);
}
}
private String getString(ByteBuffer bb) {
int pos1 = bb.position();
while (bb.get() != ')');
int pos2 = bb.position();
byte[] nameBytes = new byte[pos2-pos1-1];
bb.position(pos1);
bb.get(nameBytes);
try {
return new String(nameBytes, "US-ASCII");
} catch (UnsupportedEncodingException e) {
return new String(nameBytes);
}
}
public String getPostscriptName() {
return psName;
}
protected synchronized FontScaler getScaler() {
if (scaler == null) {
scaler = FontScaler.getScaler(this, 0, false, fileSize);
}
return scaler;
}
CharToGlyphMapper getMapper() {
if (mapper == null) {
mapper = new Type1GlyphMapper(this);
}
return mapper;
}
public int getNumGlyphs() {
try {
return getScaler().getNumGlyphs();
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return getNumGlyphs();
}
}
public int getMissingGlyphCode() {
try {
return getScaler().getMissingGlyphCode();
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return getMissingGlyphCode();
}
}
public int getGlyphCode(char charCode) {
try {
return getScaler().getGlyphCode(charCode);
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return getGlyphCode(charCode);
}
}
public String toString() {
return "** Type1 Font: Family="+familyName+ " Name="+fullName+
" style="+style+" fileName="+getPublicFileName();
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (c) 2003, 2006, 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;
/*
* This isn't a critical performance case, so don't do any
* char->glyph map caching for Type1 fonts. The ones that are used
* in composites will be cached there.
*/
public final class Type1GlyphMapper extends CharToGlyphMapper {
Type1Font font;
FontScaler scaler;
public Type1GlyphMapper(Type1Font font) {
this.font = font;
initMapper();
}
private void initMapper() {
scaler = font.getScaler();
try {
missingGlyph = scaler.getMissingGlyphCode();
} catch (FontScalerException fe) {
scaler = FontScaler.getNullScaler();
try {
missingGlyph = scaler.getMissingGlyphCode();
} catch (FontScalerException e) { //should not happen
missingGlyph = 0;
}
}
}
public int getNumGlyphs() {
try {
return scaler.getNumGlyphs();
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return getNumGlyphs();
}
}
public int getMissingGlyphCode() {
return missingGlyph;
}
public boolean canDisplay(char ch) {
try {
return scaler.getGlyphCode(ch) != missingGlyph;
} catch(FontScalerException e) {
scaler = FontScaler.getNullScaler();
return canDisplay(ch);
}
}
public int charToGlyph(char ch) {
try {
return scaler.getGlyphCode(ch);
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return charToGlyph(ch);
}
}
public int charToGlyph(int ch) {
if (ch < 0 || ch > 0xffff) {
return missingGlyph;
} else {
try {
return scaler.getGlyphCode((char)ch);
} catch (FontScalerException e) {
scaler = FontScaler.getNullScaler();
return charToGlyph(ch);
}
}
}
public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
/* The conversion into surrogates is misleading.
* The Type1 glyph mapper only accepts 16 bit unsigned shorts.
* If its > not in the range it can use assign the missing glyph.
*/
for (int i=0; i<count; i++) {
int code = unicodes[i]; // char is unsigned.
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
glyphs[i + 1] = 0xFFFF; // invisible glyph
}
}
glyphs[i] = charToGlyph(code);
if (code >= 0x10000) {
i += 1; // Empty glyph slot after surrogate
}
}
}
public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
/* I believe this code path is never exercised. Its there mainly
* for surrogates and/or the opentype engine which aren't likely
* to be an issue for Type1 fonts. So no need to optimise it.
*/
for (int i=0; i<count; i++) {
glyphs[i] = charToGlyph(unicodes[i]);
}
}
/* This variant checks if shaping is needed and immediately
* returns true if it does. A caller of this method should be expecting
* to check the return type because it needs to know how to handle
* the character data for display.
*/
public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) {
for (int i=0; i<count; i++) {
int code = unicodes[i]; // char is unsigned.
if (code >= HI_SURROGATE_START &&
code <= HI_SURROGATE_END && i < count - 1) {
char low = unicodes[i + 1];
if (low >= LO_SURROGATE_START &&
low <= LO_SURROGATE_END) {
code = (code - HI_SURROGATE_START) *
0x400 + low - LO_SURROGATE_START + 0x10000;
glyphs[i + 1] = INVISIBLE_GLYPH_ID;
}
}
glyphs[i] = charToGlyph(code);
if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
continue;
}
else if (FontUtilities.isComplexCharCode(code)) {
return true;
}
else if (code >= 0x10000) {
i += 1; // Empty glyph slot after surrogate
continue;
}
}
return false;
}
}

View File

@@ -0,0 +1,308 @@
/*
* Copyright (c) 1998, 2006, 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.
*/
/*
*
* (C) Copyright IBM Corp. 1998, All Rights Reserved
*/
package sun.font;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.font.TextAttribute;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class provides drawing and bounds-measurement of
* underlines. Additionally, it has a factory method for
* obtaining underlines from values of underline attributes.
*/
abstract class Underline {
/**
* Draws the underline into g2d. The thickness should be obtained
* from a LineMetrics object. Note that some underlines ignore the
* thickness parameter.
* The underline is drawn from (x1, y) to (x2, y).
*/
abstract void drawUnderline(Graphics2D g2d,
float thickness,
float x1,
float x2,
float y);
/**
* Returns the bottom of the bounding rectangle for this underline.
*/
abstract float getLowerDrawLimit(float thickness);
/**
* Returns a Shape representing the underline. The thickness should be obtained
* from a LineMetrics object. Note that some underlines ignore the
* thickness parameter.
*/
abstract Shape getUnderlineShape(float thickness,
float x1,
float x2,
float y);
// Implementation of underline for standard and Input Method underlines.
// These classes are private.
// IM Underlines ignore thickness param, and instead use
// DEFAULT_THICKNESS
private static final float DEFAULT_THICKNESS = 1.0f;
// StandardUnderline's constructor takes a boolean param indicating
// whether to override the default thickness. These values clarify
// the semantics of the parameter.
private static final boolean USE_THICKNESS = true;
private static final boolean IGNORE_THICKNESS = false;
// Implementation of standard underline and all input method underlines
// except UNDERLINE_LOW_GRAY.
private static final class StandardUnderline extends Underline {
// the amount by which to move the underline
private float shift;
// the actual line thickness is this value times
// the requested thickness
private float thicknessMultiplier;
// if non-null, underline is drawn with a BasicStroke
// with this dash pattern
private float[] dashPattern;
// if false, all underlines are DEFAULT_THICKNESS thick
// if true, use thickness param
private boolean useThickness;
// cached BasicStroke
private BasicStroke cachedStroke;
StandardUnderline(float shift,
float thicknessMultiplier,
float[] dashPattern,
boolean useThickness) {
this.shift = shift;
this.thicknessMultiplier = thicknessMultiplier;
this.dashPattern = dashPattern;
this.useThickness = useThickness;
this.cachedStroke = null;
}
private BasicStroke createStroke(float lineThickness) {
if (dashPattern == null) {
return new BasicStroke(lineThickness,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER);
}
else {
return new BasicStroke(lineThickness,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f,
dashPattern,
0);
}
}
private float getLineThickness(float thickness) {
if (useThickness) {
return thickness * thicknessMultiplier;
}
else {
return DEFAULT_THICKNESS * thicknessMultiplier;
}
}
private Stroke getStroke(float thickness) {
float lineThickness = getLineThickness(thickness);
BasicStroke stroke = cachedStroke;
if (stroke == null ||
stroke.getLineWidth() != lineThickness) {
stroke = createStroke(lineThickness);
cachedStroke = stroke;
}
return stroke;
}
void drawUnderline(Graphics2D g2d,
float thickness,
float x1,
float x2,
float y) {
Stroke saveStroke = g2d.getStroke();
g2d.setStroke(getStroke(thickness));
g2d.draw(new Line2D.Float(x1, y + shift, x2, y + shift));
g2d.setStroke(saveStroke);
}
float getLowerDrawLimit(float thickness) {
return shift + getLineThickness(thickness);
}
Shape getUnderlineShape(float thickness,
float x1,
float x2,
float y) {
Stroke ulStroke = getStroke(thickness);
Line2D line = new Line2D.Float(x1, y + shift, x2, y + shift);
return ulStroke.createStrokedShape(line);
}
}
// Implementation of UNDERLINE_LOW_GRAY.
private static class IMGrayUnderline extends Underline {
private BasicStroke stroke;
IMGrayUnderline() {
stroke = new BasicStroke(DEFAULT_THICKNESS,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f,
new float[] {1, 1},
0);
}
void drawUnderline(Graphics2D g2d,
float thickness,
float x1,
float x2,
float y) {
Stroke saveStroke = g2d.getStroke();
g2d.setStroke(stroke);
Line2D.Float drawLine = new Line2D.Float(x1, y, x2, y);
g2d.draw(drawLine);
drawLine.y1 += DEFAULT_THICKNESS;
drawLine.y2 += DEFAULT_THICKNESS;
drawLine.x1 += DEFAULT_THICKNESS;
g2d.draw(drawLine);
g2d.setStroke(saveStroke);
}
float getLowerDrawLimit(float thickness) {
return DEFAULT_THICKNESS * 2;
}
Shape getUnderlineShape(float thickness,
float x1,
float x2,
float y) {
GeneralPath gp = new GeneralPath();
Line2D.Float line = new Line2D.Float(x1, y, x2, y);
gp.append(stroke.createStrokedShape(line), false);
line.y1 += DEFAULT_THICKNESS;
line.y2 += DEFAULT_THICKNESS;
line.x1 += DEFAULT_THICKNESS;
gp.append(stroke.createStrokedShape(line), false);
return gp;
}
}
// Keep a map of underlines, one for each type
// of underline. The Underline objects are Flyweights
// (shared across multiple clients), so they should be immutable.
// If this implementation changes then clone underline
// instances in getUnderline before returning them.
private static final ConcurrentHashMap<Object, Underline>
UNDERLINES = new ConcurrentHashMap<Object, Underline>(6);
private static final Underline[] UNDERLINE_LIST;
static {
Underline[] uls = new Underline[6];
uls[0] = new StandardUnderline(0, 1, null, USE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_ON, uls[0]);
uls[1] = new StandardUnderline(1, 1, null, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_ONE_PIXEL, uls[1]);
uls[2] = new StandardUnderline(1, 2, null, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_TWO_PIXEL, uls[2]);
uls[3] = new StandardUnderline(1, 1, new float[] { 1, 1 }, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DOTTED, uls[3]);
uls[4] = new IMGrayUnderline();
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_GRAY, uls[4]);
uls[5] = new StandardUnderline(1, 1, new float[] { 4, 4 }, IGNORE_THICKNESS);
UNDERLINES.put(TextAttribute.UNDERLINE_LOW_DASHED, uls[5]);
UNDERLINE_LIST = uls;
}
/**
* Return the Underline for the given value of
* TextAttribute.INPUT_METHOD_UNDERLINE or
* TextAttribute.UNDERLINE.
* If value is not an input method underline value or
* TextAttribute.UNDERLINE_ON, null is returned.
*/
static Underline getUnderline(Object value) {
if (value == null) {
return null;
}
return (Underline) UNDERLINES.get(value);
}
static Underline getUnderline(int index) {
return index < 0 ? null : UNDERLINE_LIST[index];
}
}