home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Java 1.2 How-To
/
JavaHowTo.iso
/
3rdParty
/
jbuilder
/
unsupported
/
JDK1.2beta3
/
SOURCE
/
SRC.ZIP
/
java
/
awt
/
StandardGlyphSet.java
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
UTF-8
Wrap
Java Source
|
1998-03-20
|
48.1 KB
|
1,482 lines
/*
* @(#)StandardGlyphSet.java 1.13 98/03/18
*
* Copyright 1997, 1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
*/
/*
* (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
* (C) Copyright IBM Corp. 1996 - 1998, All Rights Reserved
*
* The original version of this source code and documentation is
* copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
* of IBM. These materials are provided under terms of a License
* Agreement between Taligent and Sun. This technology is protected
* by multiple US and International patents.
*
* This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
*/
package java.awt;
import java.awt.font.GlyphSet;
import java.awt.font.GlyphMetrics;
import java.awt.font.TextAttributeSet;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Point2D;
import java.text.AttributedCharacterIterator;
import sun.awt.font.Ligaturizer;
final class StandardGlyphSet extends GlyphSet {
static final boolean DEBUG = false;
private Font font;
private int glyphCodes[];
private float xAdjust;
private float xAdvances[];
private float yAdjust;
private float yAdvances[];
private int visualOrder[];
private byte baseline;
private byte levels[];
private int glyphCount;
private int characterCount;
private boolean isVerticalLine; // cache from font, needed?
private boolean hasComponentGlyphs; // optimization
// cache
private float advance; // advance == 0 means cache is invalid
private float ascent;
private float descent;
/**
* Construct a glyph set.
*
* A GlyphSet represents a cache of glyphs used for rendering and
* measuring. Its line direction is either horizontal or vertical based
* on the font's line direction.
*
* @param font the font used for all the glyph codes. It must be non-null.
* @param glyphCodes the array of glyph codes in logical order. It must
* be non-null and of length >= glyphCount;
* @param baseline the baseline to which to align this glyphset
* @param xAdjust an offset by which to adjust the position of the initial
* glyph drawn.
* @param xAdvances the array of advances in the x direction in logical
* order. It may be null, if non-null its length must be >= glyphCount.
* @param yAdjust an offset by which to adjust the position of the initial
* glyph drawn.
* @param yAdvances the array of advances in the y direction in logical
* order. It may be null, if non-null its length must be >= glyphCount.
* @param visualOrder a reordering array that maps from visual position to
* logical position. For example, visualOrder[0] = 5 means that the first
* glyph drawn is at position five in the glyph codes, advances, and
* levels arrays. VisualOrder may be null, in which case the logical
* position is the same as the visual position. If non-null its length
* must be >= glyphCount. The visual order must map positions one to one.
* @param levels an array indicating bidirectional levels, in logical
* order. It may be null, in which case all characters are of level 0.
* If non-null its length must be >= glyphCount. Levels range from 0 to 15
* inclusive, even values are left to right, odd are right to left.
* @param glyphCount the number of glyphs in the set.
*/
public StandardGlyphSet(Font font, int glyphCodes[], byte baseline,
float xAdjust, float xAdvances[],
float yAdjust, float yAdvances[],
int visualOrder[], byte levels[],
int glyphCount)
{
if (font == null) {
throw new IllegalArgumentException("GlyphSet constructor passed null font");
}
if (glyphCodes == null) {
throw new IllegalArgumentException("GlyphSet constructor passed null glyphCodes");
}
if (glyphCodes.length < glyphCount) {
throw new IllegalArgumentException("GlyphSet constructor passed short glyphCodes array");
}
if (xAdvances != null && xAdvances.length < glyphCount) {
throw new IllegalArgumentException("GlyphSet constructor passed short x advances array");
}
if (yAdvances != null && yAdvances.length < glyphCount) {
throw new IllegalArgumentException("GlyphSet constructor passed short y advances array");
}
if (visualOrder != null && visualOrder.length < glyphCount) {
throw new IllegalArgumentException("GlyphSet constructor passed short visualOrder array");
}
if (levels != null && levels.length < glyphCount) {
throw new IllegalArgumentException("GlyphSet constructor passed short levels array");
}
if (glyphCount < 1) {
throw new IllegalArgumentException("GlyphSet constructor passed bad glyphCount");
}
// !!! DEBUGGING
// Caller must normalize visual order, this check is expensive so we may wish to leave it
// out for production code.
if (DEBUG && (visualOrder != null)) {
// like a selection sort. If at any point the next value found is not the
// same as the position we're at, we don't have a 1-1 ordering over the range.
//
int temp[] = new int[glyphCount];
System.arraycopy(visualOrder, 0, temp, 0, glyphCount);
outer: for (int i = 0; i < glyphCount; i++) {
for (int j = i; j < glyphCount; j++) {
if (temp[j] == i) {
temp[j] = temp[i];
continue outer;
}
}
throw new IllegalArgumentException("Font.getGlyphSet passed bad visualOrder");
}
}
this.font = font;
this.glyphCodes = glyphCodes;
this.baseline = baseline;
this.xAdjust = xAdjust;
this.xAdvances = xAdvances;
this.yAdjust = yAdjust;
this.yAdvances = yAdvances;
this.visualOrder = visualOrder;
this.levels = levels;
this.glyphCount = glyphCount;
this.isVerticalLine = font.isVerticalBaseline(); // cache
computeCache();
}
/**
* Create a copy of this glyphset.
*
* All clone arrays are of length == getNumGlyphs().
*/
public Object clone() {
try {
StandardGlyphSet result = (StandardGlyphSet)super.clone();
// font;
result.glyphCodes = new int[glyphCount];
System.arraycopy(glyphCodes, 0, result.glyphCodes, 0, glyphCount);
// baseline
// xAdjust
if (xAdvances != null) {
result.xAdvances = new float[glyphCount];
System.arraycopy(xAdvances, 0, result.xAdvances, 0, glyphCount);
}
// yAdjust
if (yAdvances != null) {
result.yAdvances = new float[glyphCount];
System.arraycopy(yAdvances, 0, result.yAdvances, 0, glyphCount);
}
if (visualOrder != null) {
result.visualOrder = new int[glyphCount];
System.arraycopy(visualOrder, 0, result.visualOrder, 0,
glyphCount);
}
if (levels != null) {
result.levels = new byte[glyphCount];
System.arraycopy(levels, 0, result.levels, 0, glyphCount);
}
// advance;
// ascent;
// descent;
// glyphCount;
// characterCount;
// isVerticalLine;
computeCache();
return result;
}
catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
/*
* Compute cached advance, ascent, and descent, characterCount, and the
* hasComponentGlyphs flag.
*
* Walk through the visual locations accumulating the base position of each
* glyph based on the advances. Offset the glyph bounding box by this
* position.
* Compute the top and bottom (for horizontal lines) or left and right (for
* vertical lines) of the union of these boxes.
*
* On horizontal lines, ascent is positive going towards lower y values, so
* invert ascent. On vertical lines, descent is positive going towards
* lower x values, so invert descent.
*
* Advance is just the sum of the advances in the appropriate direction.
*/
private void computeCache() {
advance = 0;
ascent = 0;
descent = 0;
int componentGlyphs = 0;
float dx = xAdjust;
float dy = yAdjust;
for (int vi = 0; vi < glyphCount; vi++) {
int i = visualOrder == null ? vi : visualOrder[vi];
GlyphMetrics metrics = font.getGlyphMetrics(glyphCodes[i]);
if (metrics.isComponent()) {
componentGlyphs++;
}
Rectangle2D glyphBounds = metrics.getBounds2D();
float boundsX = (float) glyphBounds.getX();
float boundsY = (float) glyphBounds.getY();
float boundsWidth = (float) glyphBounds.getWidth();
float boundsHeight = (float) glyphBounds.getHeight();
if (isVerticalLine) {
ascent = Math.max(ascent, boundsX + boundsWidth + dx);
descent = Math.min(descent, boundsX + dx);
} else {
ascent = Math.min(ascent, boundsY + dy);
descent = Math.max(descent, boundsY + boundsHeight + dy);
}
if (xAdvances != null) {
dx += xAdvances[i];
} else if (!isVerticalLine) {
dx += metrics.getAdvance();
}
if (yAdvances != null) {
dy += yAdvances[i];
}
else if (isVerticalLine) {
dy += metrics.getAdvance();
}
}
if (isVerticalLine) {
advance = dy;
descent = -descent;
} else {
advance = dx;
ascent = -ascent;
}
characterCount = glyphCount - componentGlyphs;
hasComponentGlyphs = componentGlyphs > 0;
}
/**
* Return the advance of the glyphset.
*/
public float getAdvance() {
return advance;
}
/**
* Return the ascent of the glyphset relative to the baseline.
*
* This is the distance above (to the right of) the baseline. The ascent
* is always positive or zero.
* @see #getBaseline
*/
public float getAscent() {
return ascent;
}
/**
* return the descent of the glyphset relative to the baseline.
*
* This is the distance below (to the left of) the baseline. The descent
* is always positive or zero.
* @see #getBaseline
*/
public float getDescent() {
return descent;
}
/**
* Return the font that created this glyphset.
*/
public Font getFont() {
return font;
}
/**
* Return the number of glyphs in this glyphset.
*/
public int getNumGlyphs() {
return glyphCount;
}
/**
* Return the number of characters represented by this glyphset.
*
* Kashida justification may lead to there being more glyphs than
* characters. There are never fewer glyphs than characters.
*/
public int getNumCharacters() {
return characterCount;
}
private void checkIndex(int index) {
if (index < 0 || index >= glyphCount) {
throw new IllegalArgumentException("index is out of range.");
}
}
/**
* Return an array of the glyphcodes in this glyphset.
*
* The glyph codes are in logical order, not display order. The actual
* number of valid codes can be determined by getNumGlyphs, the array
* may be longer.
*
* @see #getNumGlyphs
*/
public int[] getGlyphCodes() {
return glyphCodes;
}
public int getGlyphCode(int index) {
checkIndex(index);
return glyphCodes[index];
}
/**
* Return the baseline used by this glyphset. All glyphs in a single
* glyphset align to the same baseline.
* @see Font#getBaselineFor
*/
public byte getBaseline() {
return baseline;
}
/**
* Return the adjustment to the x position of the glyphset.
*
* The first visual glyph in the glyphset is offset by x, y from the
* origin of the glyphset. This supports such features as superscript
* and hanging punctuation.
* @see #getYAdjust
*/
public float getXAdjust() {
return xAdjust;
}
/**
* Return the array of x position advances.
*
* The array is in logical order, not visual order. It differs from a
* simple array of advance widths in that kerning adjustments may have
* been made to the glyphs. On vertical lines, this array is often null,
* indicating a zero x advance for all glyphs.
* <p>
* The array may be null, in which case the advance widths or zero will
* be used. Callers may indicate that they want the array to be filled
* in, in which case the advance widths will be used to fill in the array.
* A null array is an optimization used to save space.
* <p>
* The array may be longer than necessary. The actual number of valid
* values in the array is provided by getNumGlyphs().
*
* @param mustBeNonNull if true, always return a non-null result.
* @see #getYAdvances
* @see #getNumGlyphs
*/
public float[] getXAdvances(boolean mustBeNonNull) {
if (mustBeNonNull && (xAdvances == null)) {
xAdvances = new float[glyphCount];
if (!isVerticalLine) { // otherwise all zero
for (int i = 0; i < glyphCount; i++) {
xAdvances[i] = font.
getGlyphMetrics(glyphCodes[i]).getAdvance();
}
}
}
return xAdvances;
}
public float getGlyphXAdvance(int index) {
checkIndex(index);
// hmm, should we compute all advances now?
float[] advs = getXAdvances(true);
return advs[index];
}
/**
* Return the adjustment to the y position of the glyphset.
*
* The first visual glyph in the glyphset is offset by x, y from the
* origin of the glyphset. This supports such features as superscript and
* hanging punctuation.
* @see #getXAdjust
*/
public float getYAdjust() {
return yAdjust;
}
/**
* Return the array of y position advances.
*
* The array is in logical order, not visual order. It differs from a
* simple array of advance widths in that kerning adjustments may have
* been made to the glyphs. On horizontal lines, this array is often null,
* indicating a zero y advance for all glyphs.
* <p>
* The array may be null, in which case the advance widths or zero will be
* used. Callers may indicate that they want the array to be filled in,
* in which case the advance widths will be used to fill in the array.
* A null array is an optimization used to save space.
* <p>
* The array may be longer than necessary. The actual number of valid
* values in the array is provided by getNumGlyphs().
*
* @param mustBeNonNull if true, always return a non-null result.
* @see #getXAdvances
* @see #getNumGlyphs
*/
public float[] getYAdvances(boolean mustBeNonNull) {
if (mustBeNonNull && (yAdvances == null)) {
yAdvances = new float[glyphCount];
if (isVerticalLine) { // otherwise all zero
for (int i = 0; i < glyphCount; i++) {
yAdvances[i] = font.getGlyphMetrics(glyphCodes[i]).getAdvance();
}
}
}
return yAdvances;
}
public float getGlyphYAdvance(int index) {
checkIndex(index);
float[] advs = getYAdvances(true);
return advs[index];
}
/*
* Return XAdvances on a horizontal set, and YAdvances on a vertical set.
* This overload will never return a null set.
* @see #getXAdvances
* @see #getYAdvances
*/
public float[] getAdvances() {
return isVerticalLine ? getYAdvances(true) : getXAdvances(true);
}
public float getGlyphAdvance(int index) {
return isVerticalLine ? getGlyphYAdvance(index) :
getGlyphXAdvance(index);
}
/*
* Return XAdvances on a horizontal set, and YAdvances on a vertical set.
* @param mustBeNonNull if true, always return a non-null result.
* @see #getXAdvances
* @see #getYAdvances
*/
public float[] getAdvances(boolean mustNotBeNull) {
return isVerticalLine ? getYAdvances(mustNotBeNull) :
getXAdvances(mustNotBeNull);
}
/**
* Return a mapping array from visual position to logical position.
*
* For example, visualOrder[0] = 5 means that the first glyph drawn is at
* position five in the glyphCodes, levels, and advances arrays. The
* mapping may be null, in which case the logical position is the same
* as the visual position. If non-null it maps positions one to one.
* <p>
* The array may be longer than necessary. The actual number of valid
* values in the array is provided by getNumGlyphs().
* @see #getNumGlyphs
*/
public int[] getVisualOrder() {
return visualOrder;
}
public int visualToLogicalIndex(int index) {
checkIndex(index);
return (visualOrder==null)? index : visualOrder[index];
}
/**
* Return an array indicating the direction levels of each glyph.
*
* Glyphs whose level is even run from left to right (top to bottom),
* glyphs whose level is odd run from right to left (bottom to top).
* This array may be null, in which case the level of each glyph is zero.
* <p>
* The array may be longer than necessary. The actual number of valid
* values in the array is provided by getNumGlyphs().
* @see #getNumGlyphs
*/
public byte[] getLevels() {
return levels;
}
public byte getLevel(int index) {
checkIndex(index);
return (byte) ((levels == null)? 0x0 : levels[index]);
}
// {jbr} incremental editing wants to know when glyphsets are uniformly LTR. It
// used to check the level array, now it uses this. This can change if people
// think it's too ugly.
// Note that check can be expensive
public boolean isCompletelyLTR() {
if (levels == null) {
return true;
}
for (int i=0; i < glyphCount; i++) {
if ((levels[i] & (byte)0x1) != 0) {
return false;
}
}
return true;
}
/**
* Return a justified copy of this glyphset.
*
* @see Font#handleApplyJustification
*/
public GlyphSet applyJustification(float[] deltas, int index,
boolean[] shouldRejustify)
{
/*
* if we can cause rejustification, and we have ligatures, and their
* space limit is exceeded, then break the ligatures and request
* rejustification. otherwise, just apply space between the glyphs.
*/
int[] codes = glyphCodes;
int[] vorder = visualOrder;
if (shouldRejustify[0] == true) {
shouldRejustify[0] = false; // assume we won't be breaking any
for (int vi = 0; vi < glyphCount; vi++) {
int i = vorder == null ? vi : vorder[vi];
GlyphMetrics gm = font.getGlyphMetrics(codes[i]);
if (gm.isLigature()) { // check ligature glyphs
float ls = deltas[index + vi * 2];
float rs = deltas[index + vi * 2 + 1];
boolean grow = ls + rs > 0; // !!! hack
if (grow && (ls > font.getGlyphJustificationInfo(codes[i]).
growLeftLimit ||
rs > font.getGlyphJustificationInfo(codes[i]).
growRightLimit)) {
//
shouldRejustify[0] = true;
int[] chars = Ligaturizer.glyphChars(codes[i]);
for (int j = 0; j < chars.length; ++j)
codes[i + j] = chars[j];
}
}
}
if (shouldRejustify[0]) {
return new StandardGlyphSet(font, codes, baseline,
xAdjust, null,
yAdjust, null,
vorder, levels, glyphCount);
}
}
float[] advances = new float[glyphCount];
System.arraycopy(getAdvances(), 0, advances, 0, advances.length);
// skip left of first char, we'll offset entire glyphset
float adjust = deltas[index++];
for (int vi = 0; vi < glyphCount; vi++) {
int i = vorder == null ? vi : vorder[vi];
advances[i] += deltas[index++]; // add right of char
if (vi < glyphCount - 1) {
advances[i] += deltas[index++]; // add left of following char
}
}
if (font.isVerticalBaseline()) {
return new StandardGlyphSet(font, codes, baseline,
xAdjust, null,
adjust, advances,
vorder, levels, glyphCount);
}
else {
return new StandardGlyphSet(font, codes, baseline,
adjust, advances,
yAdjust, null,
vorder, levels, glyphCount);
}
}
/**
* Return the character index for a given glyph index.
*
* Combining glyphs refer to the same character as the first logically
* previous non-component glyph.
*
* This method should probably only be called internally and by the
* GlyphSet's font.
* We'll see...
*/
protected int glyphToCharacterIndex(int glyphIndex) {
if (hasComponentGlyphs) {
int charCount = 0;
for (int i=0; i < glyphIndex; i++) {
if (!font.getGlyphMetrics(glyphCodes[i]).isComponent()) {
charCount++;
}
}
return charCount;
}
return glyphIndex;
}
/**
* Return the glyph index for a given character index.
*
* A character index will map to the first logically subsequent
* non-combining glyph.
*
* This method should probably only be called internally and by the
* GlyphSet's font.
* We'll see...
*/
protected int characterToGlyphIndex(int charIndex) {
if (hasComponentGlyphs) {
int glyphNum;
for (glyphNum = 0; charIndex > 0; glyphNum++) {
if (!font.getGlyphMetrics(glyphCodes[glyphNum]).isComponent()) {
charIndex--;
}
}
while (glyphNum < glyphCount) {
if (font.getGlyphMetrics(glyphCodes[glyphNum]).isComponent()) {
glyphNum++;
}
else {
break;
}
}
return glyphNum;
}
return charIndex;
}
//--------------------------- hey {jf} - how about this?
/**
* Return the sum of the advances for the glyphs corresponding to characters
* in logical positions from start up to limit. Note these are character
* indices, not glyph indices. They will be mapped internally to glyph
* indices.
*
* @param start the character index at which to start measuring
* @param limit the character index at which to stop measuring
*/
public float getAdvanceBetween(int start, int limit) {
if (start < 0 || limit < start || limit > characterCount) {
throw new IllegalArgumentException("GlyphSet.advanceUpTo was passed a bad start and limit");
}
start = characterToGlyphIndex(start);
limit = characterToGlyphIndex(limit);
float result = 0.0f;
float[] advances = getAdvances();
for (int i = start; i < limit; i++) {
result += advances[i];
}
return result;
}
/**
* Return the logical position of a possible break. This adds up advances
* of glyphs in logical order starting from the glyph for the character
* at start, stopping before the first spacing glyph whose advance would
* cause hitAdvance to be exceeded. The character position of that glyph is
* returned. If no glyph would reach the hitAdvance, the number of
* characters in the set is returned.
* This will not break between combining or component glyphs and their
* base glyph.
*/
public int getLineBreakIndex(int start, float hitAdvance) {
if (start < 0 || start >= characterCount) {
throw new IllegalArgumentException("GlyphSet.getLineBreakIndex was passed a bad start index");
}
if (hitAdvance >= getAdvance()) { // optimize
return characterCount;
}
if (hitAdvance > 0) {
int result = characterToGlyphIndex(start);
float[] advances = getAdvances();
while (result < glyphCount &&
((hitAdvance -= advances[result]) >= 0)) {
result++;
}
if (result < glyphCount) {
do {
byte basetype = (byte)(font.getGlyphMetrics(glyphCodes[result]).getType() & 0x3);
if ((basetype != GlyphMetrics.COMBINING) &&
(basetype != GlyphMetrics.COMPONENT)) {
break;
}
--result;
} while (result >= 0);
}
return glyphToCharacterIndex(result);
}
return start;
}
/**
* Generate a GlyphSet that contains the subset of this from logical start
* up to limit. The new glyphset shares the same baseline, xAdjust,
* and yAdjust as the previous one.
*/
public final GlyphSet subset(int start, int limit) {
if (start < 0 || limit <= start || limit > getNumCharacters()) {
throw new IllegalArgumentException("GlyphSet.handleSubset was passed invalid range: " + start + "," + limit + " (" + getNumCharacters() + ")");
}
// might be able to return gs:
if (start == 0 && limit == getNumCharacters()) {
return this;
}
int glyphStart = characterToGlyphIndex(start);
int glyphLimit = characterToGlyphIndex(limit);
int newGlyphCount = glyphLimit - glyphStart;
int newGlyphCodes[] = new int[newGlyphCount];
System.arraycopy(glyphCodes, glyphStart, newGlyphCodes, 0,
newGlyphCount);
float newXAdvances[] = null;
if (xAdvances != null) {
newXAdvances = new float[newGlyphCount];
System.arraycopy(xAdvances, glyphStart, newXAdvances,
0, newGlyphCount);
}
float newYAdvances[] = null;
if (yAdvances != null) {
newYAdvances = new float[newGlyphCount];
System.arraycopy(yAdvances, glyphStart, newYAdvances,
0, newGlyphCount);
}
byte newLevels[] = null;
if (levels != null) {
newLevels = new byte[newGlyphCount];
System.arraycopy(levels, start, newLevels, 0, newGlyphCount);
}
int newVisualOrder[] = null;
if (visualOrder != null) {
// normalize the subrange to fit the new logical positions
int[] inverse = getInverseOrder(visualOrder);
newVisualOrder = getNormalizedOrder(inverse, levels, glyphStart, glyphLimit);
newVisualOrder = getInverseOrder(newVisualOrder);
}
return new StandardGlyphSet(font, newGlyphCodes, baseline,
xAdjust, newXAdvances,
yAdjust, newYAdvances,
newVisualOrder, newLevels, newGlyphCount);
}
public GlyphSet setDirection(boolean leftToRight) {
if (leftToRight && visualOrder == null && levels == null) {
return this;
}
byte[] levels = null;
int[] visualOrder = null;
if (!leftToRight) {
byte val = (byte) 0x1;
levels = new byte[glyphCount];
visualOrder = new int[glyphCount];
for (int i=0; i < glyphCount; i++) {
levels[i] = val;
visualOrder[i] = glyphCount - i - 1;
}
}
return new StandardGlyphSet(font, glyphCodes, baseline,
xAdjust, xAdvances,
yAdjust, yAdvances,
visualOrder, levels, glyphCount);
}
/**
* Return a hashCode for this glyphset.
*/
public int hashCode() {
return (glyphCount * 31) ^ glyphCodes[0];
}
/**
* Test for full equality. Font, glyphs, baseline, advances, adjusts,
* order, levels, count must all match.
*/
public boolean equals(Object set) {
try {
return equals((StandardGlyphSet) set);
}
catch(ClassCastException e) {
return false;
}
}
public boolean equals(GlyphSet set) {
try {
return equals((StandardGlyphSet) set);
}
catch(ClassCastException e) {
return false;
}
}
protected boolean equals(StandardGlyphSet set) {
if (set == null) {
return false;
}
if (this == set) {
return true;
}
if (this.hashCode() != set.hashCode()) {
return false;
}
// quick checks
if (glyphCount != set.glyphCount) {
return false;
}
if (characterCount != set.characterCount) {
return false;
}
if (baseline != set.baseline) {
return false;
}
if (xAdjust != set.xAdjust) {
return false;
}
if (yAdjust != set.yAdjust) {
return false;
}
if ((visualOrder != null) ^ (set.visualOrder != null)) {
return false;
}
if ((levels != null) ^ (set.levels != null)) {
return false;
}
// can't check advances, state is lazy evaluated
if (!font.equals(set.font)) return false;
for (int i = 0; i < glyphCount; i++) {
if (glyphCodes[i] != set.glyphCodes[i]) {
return false;
}
}
if (xAdvances != null || set.xAdvances != null) {
float[] myx = getXAdvances(true);
float[] itx = set.getXAdvances(true);
for (int i = 0; i < glyphCount; i++) {
if (myx[i] != itx[i]) {
return false;
}
}
}
if (yAdvances != null || set.yAdvances != null) {
float[] myy = getYAdvances(true);
float[] ity = set.getYAdvances(true);
for (int i = 0; i < glyphCount; i++) {
if (myy[i] != ity[i]) {
return false;
}
}
}
if (visualOrder != null) {
for (int i = 0; i < glyphCount; i++) {
if (visualOrder[i] != set.visualOrder[i]) {
return false;
}
}
}
if (levels != null) {
for (int i = 0; i < glyphCount; i++) {
if (levels[i] != set.levels[i]) {
return false;
}
}
}
return true;
}
/**
* Display the glyphcodes to System.out, for debugging purposes.
*/
public void printDebug()
{
for (int vi = 0; vi < glyphCount; vi++) {
int i = visualOrder == null ? vi : visualOrder[vi];
System.out.print((char)glyphCodes[i]);
}
}
/**
* Display the glyphset as a string, for debugging purposes.
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(super.toString());
buf.append("\n");
buf.append("font=");
buf.append(font.toString());
buf.append("\n");
buf.append("glyphCount=");
buf.append(glyphCount);
buf.append("\n");
buf.append("glyphCodes={");
buf.append(glyphCodes[0]);
for (int i = 1; i < glyphCount; i++) {
buf.append(",");
buf.append(glyphCodes[i]);
}
buf.append("}\n");
buf.append("xAdvances=");
if (xAdvances == null) {
buf.append("null");
} else {
buf.append("{");
buf.append(xAdvances[0]);
for (int i = 1; i < glyphCount; i++) {
buf.append(",");
buf.append(xAdvances[i]);
}
buf.append("}");
}
buf.append("\n");
buf.append("yAdvances=");
if (yAdvances == null) {
buf.append("null");
} else {
buf.append("{");
buf.append(yAdvances[0]);
for (int i = 1; i < glyphCount; i++) {
buf.append(",");
buf.append(yAdvances[i]);
}
buf.append("}");
}
buf.append("\n");
buf.append("visualOrder=");
if (visualOrder == null) {
buf.append("null");
} else {
buf.append("{");
buf.append(visualOrder[0]);
for (int i = 1; i < glyphCount; i++) {
buf.append(",");
buf.append(visualOrder[i]);
}
buf.append("}");
}
buf.append("\n");
buf.append("levels=");
if (levels == null) {
buf.append("null");
} else {
buf.append("{");
buf.append(levels[0]);
for (int i = 1; i < glyphCount; i++) {
buf.append(",");
buf.append(levels[i]);
}
buf.append("}");
}
buf.append("\n");
return buf.toString();
}
public GlyphSet insertChar(AttributedCharacterIterator newText,
int start, int limit, int insertPos,
int[] order, byte[] levels)
{
/*
* This method must return a glyphset over newText with the given
* order and level arrays. It can opt to incrementally modify oldSet,
* or it can make a whole new one.
*/
if (insertPos < start || insertPos >= limit) {
throw new IllegalArgumentException("insertPos is out of range in GlyphSet.insertChar.");
}
if (order != null || levels != null || glyphCount != characterCount) {
return font.getGlyphSet(newText, start, limit, baseline, order, levels);
}
int newLength = limit - start;
if (characterCount + 1 != newLength) {
throw new IllegalArgumentException("GlyphSet.insertChar can only insert a single character.");
}
// look to see if we're breaking an old ligature, or forming a new one.
// Neither case is one we really want to deal with.
char insertedChar = newText.setIndex(insertPos);
byte insCharType = Ligaturizer.charType(insertedChar);
byte prevCharType, nextCharType;
if (insertPos == newText.getBeginIndex()) {
prevCharType = Ligaturizer.NONCOMBINING;
} else {
prevCharType = Ligaturizer.charType(newText.previous());
}
if (insertPos == newText.getEndIndex()) {
nextCharType = Ligaturizer.NONCOMBINING;
} else {
char ch = newText.setIndex(insertPos + 1);
nextCharType = Ligaturizer.charType(ch);
}
boolean useGetGlyphSet = false;
if (prevCharType != Ligaturizer.NONCOMBINING &&
nextCharType != Ligaturizer.NONCOMBINING) {
useGetGlyphSet = true;
}
if (insCharType != Ligaturizer.NONCOMBINING) {
if (prevCharType != Ligaturizer.NONCOMBINING ||
nextCharType != Ligaturizer.NONCOMBINING) {
useGetGlyphSet = true;
}
}
if (useGetGlyphSet == true) {
return font.getGlyphSet(newText, start, limit, baseline, order, levels);
}
int[] oldCodes = glyphCodes, newCodes = new int[newLength];
float[] oldAdvances = xAdvances, newAdvances = null;
int gsInsertPos = insertPos - start;
System.arraycopy(oldCodes, 0, newCodes, 0, gsInsertPos);
System.arraycopy(oldCodes, gsInsertPos, newCodes,
gsInsertPos+1, newLength-gsInsertPos-1);
// REMIND jk. painfully evident that there seems to be no difference
// between characters and glyphs. Need to decide soon with df and jr
// on what should Font do in order to make this code simpler
//newCodes[gsInsertPos] = insertedChar;//old code
// float insCharAdvance = font.getGlyphMetrics(insertedChar).getAdvance();
char[] chArray = {insertedChar};
String str = new String(chArray);
GlyphSet gls = font.getGlyphSet(str);
int[] codes = gls.getGlyphCodes();
newCodes[gsInsertPos] = codes[0];
float insCharAdvance = font.getGlyphMetrics(codes[0]).getAdvance();
if (oldAdvances != null) {
newAdvances = new float[newLength];
System.arraycopy(oldAdvances, 0, newAdvances, 0, gsInsertPos);
System.arraycopy(oldAdvances, gsInsertPos, newAdvances,
gsInsertPos+1, newLength-gsInsertPos-1);
newAdvances[gsInsertPos] = insCharAdvance;
}
return new StandardGlyphSet(font, newCodes, baseline,
xAdjust, newAdvances,
yAdjust, null,
order, // visual order
levels, // levels
newLength); // glyph count
}
public GlyphSet deleteChar(AttributedCharacterIterator newText,
int start, int limit, int deletePos,
int[] order, byte[] levels)
{
if (deletePos < start || deletePos > limit) {
throw new IllegalArgumentException("insertPos is out of range in GlyphSet.insertChar.");
}
int newLength = limit - start;
if (characterCount - 1 != newLength) {
throw new IllegalArgumentException("GlyphSet.deleteChar can only delete a single character.");
}
if (newLength == 0) {
throw new IllegalArgumentException("GlyphSet.deleteChar was asked to produce a 0-length glyphset.");
}
if (order != null || levels != null || characterCount != glyphCount) {
return font.getGlyphSet(newText, start, limit, baseline, order, levels);
}
byte beforeCharType, afterCharType;
if (deletePos > newText.getBeginIndex()) {
beforeCharType = Ligaturizer.charType(newText.setIndex(deletePos-1));
} else {
beforeCharType = Ligaturizer.NONCOMBINING;
}
if (deletePos < newText.getEndIndex()) {
afterCharType = Ligaturizer.charType(newText.setIndex(deletePos));
} else {
afterCharType = Ligaturizer.NONCOMBINING;
}
if (beforeCharType != Ligaturizer.NONCOMBINING ||
afterCharType != Ligaturizer.NONCOMBINING) {
return font.getGlyphSet(newText, start, limit, baseline,
order, levels);
}
int[] oldCodes = glyphCodes;
int[] newCodes = new int[newLength];
int gsDeletePos = deletePos - start;
System.arraycopy(oldCodes, 0, newCodes, 0, gsDeletePos);
System.arraycopy(oldCodes, gsDeletePos+1, newCodes, gsDeletePos,
newLength-gsDeletePos);
float[] oldAdvances = xAdvances;
float[] newAdvances = null;
if (oldAdvances != null) {
newAdvances = new float[newLength];
System.arraycopy(oldAdvances, 0, newAdvances, 0, gsDeletePos);
System.arraycopy(oldAdvances, gsDeletePos+1, newAdvances,
gsDeletePos, newLength-gsDeletePos);
}
return new StandardGlyphSet(font, newCodes, baseline,
xAdjust, newAdvances,
yAdjust, null,
order, // visual order
levels, // levels
newLength); // glyph count
}
public GlyphSet reshape(AttributedCharacterIterator newText,
int start, int limit, int changePos, int[] order,
byte[] levels)
{
if (limit - start != getNumCharacters()) {
throw new IllegalArgumentException("Range size is wrong in GlyphSet.reshape.");
}
if (changePos != start-1 && changePos != limit) {
throw new IllegalArgumentException("changePos is not at range endpoint in Font2.reshape.");
}
/*
* TextLayout passes in previous and/or following glyphsets when an
* edit occurs on a glyphset boundary. This is to allow the Font to
* redo special shaping around the character which changed. Since
* this font doesn't shape, just return the old glyphset.
*/
return this;
}
public void draw(Graphics2D g2, float x, float y) {
Graphics g = (Graphics)g2;
// REMIND jk df the font shouldn't be handling the color.
Color oldColor = null;
Color newColor = (Color)font.getRequestedAttributes().
get(TextAttributeSet.FOREGROUND);
if (newColor != null) {
oldColor = g.getColor();
g.setColor(newColor);
}
Font oldFont = g.getFont();
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
float oldx = x;
float oldy = y;
x += xAdjust;
y += yAdjust;
// do 'fast path' check.
// of course, we should have a special glyphset that just calls this directly without our
// having to do the check each time we render
boolean fast = !isVerticalLine &&
xAdvances == null &&
yAdvances == null &&
visualOrder == null &&
glyphCount == characterCount;
if (fast) {
for (int i = 0; i < glyphCount; i++) {
if (!font.getGlyphMetrics(glyphCodes[i]).isStandard()) {
fast = false;
break;
}
}
}
if (fast) {
char[] chars = new char[glyphCount];
for (int i = 0; i < glyphCount; i++) {
chars[i] = (char)glyphCodes[i];
}
g2.drawString(this, x, y);
} else if (!isVerticalLine &&
xAdvances == null &&
yAdvances == null &&
glyphCount == characterCount)
{
char[] chars = new char[glyphCount];
for (int i = 0; i < glyphCount; i++) {
chars[i] = (char)glyphCodes[visualOrder[i]];
}
for (int i = 0; i < glyphCount; i++) {
glyphCodes[i] = chars[i];
}
g2.drawString(this, x, y);
//REMIND jk. reverse the glyphCode changes.
} else {
char[] cc = new char[1];
for (int i = 0; i < glyphCount; ++i) {
int vi = visualOrder == null ? i : visualOrder[i];
int code = glyphCodes[vi];
GlyphMetrics gm = font.getGlyphMetrics(code);
float dx;
if (xAdvances == null) {
if (isVerticalLine) {
dx = 0;
} else {
dx = gm.getAdvance();
}
} else {
dx = xAdvances[vi];
}
float dy;
if (yAdvances == null) {
if (isVerticalLine) {
dy = gm.getAdvance();
} else {
dy = 0;
}
} else {
dy = yAdvances[vi];
}
int lx;
int ly;
if (isVerticalLine) {
/*
* REMIND jk df, have to hack 'natural' vertical glyph origin
* back to where graphics expects it
*/
lx = (int)(x + gm.getBounds2D().getX());
ly = (int)(y + fm.getAscent());
} else {
lx = (int)x;
ly = (int)y;
}
if (gm.isStandard()) {
if (code != 0 && code != '\n' && code != '\r' && code != '\t') {
// ignore 'ligature' code, undrawable codes
cc[0] = (char)code;
g2.drawChars(cc, 0, 1, lx, ly);
}
} else if (gm.isLigature()) {
int[] tempgc = Ligaturizer.glyphChars(code);
// REMIND jk. this is a hack and should be removed soon
char[] gc = new char[tempgc.length];
for (int mm = 0; mm < tempgc.length; ++mm) {
gc[mm] = (char)tempgc[mm];
}
// REMIND jk . end of hack
for (int ii = 0; ii < gc.length; ++ii) {
g2.drawChars(gc, ii, 1, lx, ly);
lx += (int)(fm.charWidth(gc[ii]) - 4);
}
} else if (gm.isComponent()) {
cc[0] = '_';
g2.drawChars(cc, 0, 1, lx, ly);
}
x += dx;
y += dy;
}
}
float dx = isVerticalLine ? 0 : advance;
float dy = isVerticalLine ? advance : 0;
if (isVerticalLine) {
oldx += xAdjust;
} else {
oldy += yAdjust;
}
/*
* If we want uniform underline and strikethrough across an entire
* line, then font can't handle this. In fact, TextLayout can't
* either because you can have multiple layouts on a line. So for
* now, let's let font handle it.
*/
String uff = (String)font.getRequestedAttributes().
get(TextAttributeSet.UNDERLINE);
if (uff != null) {
float uline = font.getUnderlineOffsetFor(' '); // !!!
float nx = isVerticalLine ? oldx - uline : oldx;
float ny = isVerticalLine ? oldy : oldy + uline;
g.drawLine((int)nx, (int)ny, (int)(nx + dx), (int)(ny + dy));
}
String sff = (String)font.getRequestedAttributes().
get(TextAttributeSet.STRIKETHROUGH);
if (sff != null) {
float sline = font.getStrikethroughOffsetFor(' '); // !!!
float nx = isVerticalLine ? oldx - sline : oldx;
float ny = isVerticalLine ? oldy : oldy + sline;
g.drawLine((int)nx, (int)ny, (int)(nx + dx), (int)(ny + dy));
}
g.setFont(oldFont);
if (oldColor != null) {
g.setColor(oldColor);
}
}
public Rectangle2D getBounds() {
// remind: compute real bounds
return new Rectangle2D.Float(
xAdjust, -ascent, advance-xAdjust, ascent+descent);
}
}