i
-th glyph is at pos[i * 2]
, its
* vertical position at pos[i * 2 + 1]
. The total
* advance width of the entire vector is stored at
* pos[numGlyphs]
, the total advance height at
* pos[numGlyphs + 1]
.
*/
private float[] pos;
private AffineTransform[] transforms;
private int layoutFlags;
/**
* The cached non-transformed outline of this glyph vector.
*/
private Shape cleanOutline;
/**
* Constructs a new GNUGlyphVector.
*
* @param fontDelegate the FontDelegate that creates this vector.
*
* @param font the Font that this GlyphVector will return for {@link
* #getFont()}. That object is also used to determine the point
* size, which affects the affine transformation used by the font
* scaler.
*
* @param renderContext an object with parameters for font
* rendering, such as whether anti-aliasing is enabled.
*
* @param glyphs the glyphs in this vector.
*/
public GNUGlyphVector(FontDelegate fontDelegate,
Font font,
FontRenderContext renderContext,
int[] glyphs)
{
this.fontDelegate = fontDelegate;
this.font = font;
this.renderContext = renderContext;
this.glyphs = glyphs;
fontSize = font.getSize2D();
transform = font.getTransform(); // returns a modifiable copy
//transform.concatenate(renderContext.getTransform());
}
/**
* Returns the font of the glyphs in this GlyphVector.
*/
public Font getFont()
{
return font;
}
/**
* Returns the FontRenderContext that is used to calculate the
* extent and position of the glyphs.
*/
public FontRenderContext getFontRenderContext()
{
return renderContext;
}
/**
* Moves each glyph in the vector to its default position.
*/
public void performDefaultLayout()
{
float x, y, advanceWidth, advanceHeight;
int i, p;
AffineTransform tx;
Point2D.Float advance = new Point2D.Float();
pos = new float[(glyphs.length + 1) * 2];
x = y = 0.0f;
p = 0;
for (i = p = 0; i < glyphs.length; i++)
{
p += 2;
if ((transforms == null) || (tx = transforms[i]) == null)
tx = this.transform;
else
{
tx = new AffineTransform(tx);
tx.concatenate(this.transform);
}
fontDelegate.getAdvance(glyphs[i], fontSize, tx,
renderContext.isAntiAliased(),
renderContext.usesFractionalMetrics(),
/* horizontal */ true,
advance);
// FIXME: We shouldn't round here, but instead hint the metrics
// correctly.
pos[p] = x += Math.round(advance.x);
pos[p + 1] = y += advance.y;
}
valid = true;
}
/**
* Determines the number of glyphs in this GlyphVector.
*/
public int getNumGlyphs()
{
return glyphs.length;
}
/**
* Determines the glyph number by index in this vector.
* Glyph numbers are specific to each font, so two fonts
* will likely assign different numbers to the same glyph.
*
* @param glyphIndex the index of the glyph whose glyph number is to
* be retrieved.
*
* @throws IndexOutOfBoundsException if glyphIndex
* is not in the range .
*/
public int getGlyphCode(int glyphIndex)
{
/* The exception is thrown automatically if the index is out
* of the valid bounds.
*/
return glyphs[glyphIndex];
}
/**
* Returns a slice of this GlyphVector.
*
* @param firstGlyphIndex the index of the first glyph in the
* returned slice.
*
* @param numEntries the size of the returned slice.
*
* @param outCodes a pre-allocated array for storing the slice,
* or null
to cause allocation of a new array.
*
* @return a slice of this GlyphVector. If outCodes
* is null
, the slice will be stored into a freshly
* allocated array; otherwise, the result will be stored into
* outCodes
.
*/
public int[] getGlyphCodes(int firstGlyphIndex,
int numEntries,
int[] outCodes)
{
if (numEntries < 0)
throw new IllegalArgumentException();
if (outCodes == null)
outCodes = new int[numEntries];
System.arraycopy(glyphs, firstGlyphIndex, outCodes, 0, numEntries);
return outCodes;
}
public Rectangle2D getLogicalBounds()
{
float ascent, descent;
validate();
return new Rectangle2D.Float(0, 0,
pos[pos.length - 2],
getAscent() - getDescent());
}
public Rectangle2D getVisualBounds()
{
validate();
// FIXME: Not yet implemented.
return getLogicalBounds();
}
/**
* Returns the shape of this GlyphVector.
*/
public Shape getOutline()
{
return getOutline(0.0f, 0.0f);
}
/**
* Returns the shape of this GlyphVector, translated to the
* specified position.
*
* @param x the horizontal position for rendering this vector.
* @param y the vertical position for rendering this vector.
*/
public Shape getOutline(float x, float y)
{
validate();
Shape outline;
if (cleanOutline == null)
{
GeneralPath path = new GeneralPath();
int len = glyphs.length;
for (int i = 0; i < len; i++)
{
GeneralPath p = new GeneralPath(getGlyphOutline(i));
path.append(p, false);
}
// Protect the cached instance from beeing modified by application
// code.
cleanOutline = new ShapeWrapper(path);
outline = cleanOutline;
}
else
{
outline = cleanOutline;
}
if (x != 0 || y != 0)
{
GeneralPath path = new GeneralPath(outline);
AffineTransform t = new AffineTransform();
t.translate(x, y);
path.transform(t);
outline = path;
}
return outline;
}
public Shape getOutline(float x, float y, int type)
{
validate();
GeneralPath outline = new GeneralPath();
int len = glyphs.length;
for (int i = 0; i < len; i++)
{
GeneralPath p = new GeneralPath(getGlyphOutline(i, type));
outline.append(p, false);
}
AffineTransform t = new AffineTransform();
t.translate(x, y);
outline.transform(t);
return outline;
}
/**
* Determines the shape of the specified glyph.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public Shape getGlyphOutline(int glyphIndex)
{
AffineTransform tx, glyphTx;
GeneralPath path;
validate();
if ((transforms != null)
&& ((glyphTx = transforms[glyphIndex]) != null))
{
tx = new AffineTransform(transform);
tx.concatenate(glyphTx);
}
else
tx = transform;
path = fontDelegate.getGlyphOutline(glyphs[glyphIndex], fontSize, tx,
renderContext.isAntiAliased(),
renderContext.usesFractionalMetrics(),
FontDelegate.FLAG_FITTED);
tx = new AffineTransform();
tx.translate(pos[glyphIndex * 2], pos[glyphIndex * 2 + 1]);
path.transform(tx);
return path;
}
public Shape getGlyphOutline(int glyphIndex, int type)
{
AffineTransform tx, glyphTx;
GeneralPath path;
validate();
if ((transforms != null)
&& ((glyphTx = transforms[glyphIndex]) != null))
{
tx = new AffineTransform(transform);
tx.concatenate(glyphTx);
}
else
tx = transform;
path = fontDelegate.getGlyphOutline(glyphs[glyphIndex], fontSize, tx,
renderContext.isAntiAliased(),
renderContext.usesFractionalMetrics(),
type);
tx = new AffineTransform();
tx.translate(pos[glyphIndex * 2], pos[glyphIndex * 2 + 1]);
path.transform(tx);
return path;
}
/**
* Determines the position of the specified glyph, or the
* total advance width and height of the vector.
*
* @param glyphIndex the index of the glyph in question.
* If this value equals getNumGlyphs()
, the
* position after the last glyph will be returned,
* which is the total advance width and height of the vector.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public Point2D getGlyphPosition(int glyphIndex)
{
validate();
return new Point2D.Float(pos[glyphIndex * 2],
pos[glyphIndex * 2 + 1]);
}
/**
* Moves the specified glyph to a new position, or changes the
* advance width and height of the entire glyph vector.
*
* Note that the position of an individual glyph may also
* affected by its affine transformation.
*
* @param glyphIndex the index of the moved glyph. If
* glyphIndex
equals the total number of glyphs in this
* vector, the advance width and height of the vector is changed.
*
* @param position the new position of the glyph.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public void setGlyphPosition(int glyphIndex, Point2D position)
{
validate();
pos[glyphIndex * 2] = (float) position.getX();
pos[glyphIndex * 2 + 1] = (float) position.getY();
}
/**
* Returns the affine transformation that is applied to the
* glyph at the specified index.
*
* @param glyphIndex the index of the glyph whose transformation
* is to be retrieved.
*
* @return an affine transformation, or null
* for the identity transformation.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public AffineTransform getGlyphTransform(int glyphIndex)
{
if (transforms == null)
return null;
else
return transforms[glyphIndex];
}
/**
* Applies an affine transformation to the glyph at the specified
* index.
*
* @param glyphIndex the index of the glyph to which the
* transformation is applied.
*
* @param transform the affine transformation for the glyph, or
* null
for an identity transformation.
*/
public void setGlyphTransform(int glyphIndex,
AffineTransform transform)
{
if (transforms == null)
transforms = new AffineTransform[glyphs.length];
transforms[glyphIndex] = transform;
/* If the GlyphVector has only a transform for a single glyph, and
* the caller clears its transform, the FLAG_HAS_TRANSFORMS bit
* should be cleared in layoutFlags. However, this would require
* that we keep track of the number of transformed glyphs, or that
* we count them when a transform is cleared. This would
* complicate the code quite a bit. Note that the only drawback of
* wrongly setting FLAG_HAS_TRANSFORMS is that a slower code path
* might be taken for rendering the vector. Right now, we never
* really look at the flag, so it does not make any difference.
*/
if (transform != null)
layoutFlags |= FLAG_HAS_TRANSFORMS;
valid = false;
}
/**
* Returns flags that can be used for optimizing the rendering
* of this GlyphVector.
*
* @return a bit mask with the applicable flags set.
*
* @since 1.4
*
* @see GlyphVector#FLAG_HAS_POSITION_ADJUSTMENTS
* @see GlyphVector#FLAG_HAS_TRANSFORMS
* @see GlyphVector#FLAG_RUN_RTL
* @see GlyphVector#FLAG_COMPLEX_GLYPHS
* @see GlyphVector#FLAG_MASK
*/
public int getLayoutFlags()
{
return layoutFlags;
}
/**
* Returns the positions of a range of glyphs in this vector.
*
* @param firstGlyphIndex the index of the first glyph whose
* position is retrieved.
*
* @param numGlyphs the number of glyphs whose positions
* are retrieved.
*
* @param outPositions an array for storing the results
* (the length must be at least twice numGlyphs
),
* or null
for freshly allocating an array.
*
* @return an array with the glyph positions. The horizontal
* position of the i
-th glyph is at index 2 *
* i
, the vertical position at index 2 * i + 1
.
*
* @throws IllegalArgumentException if numGlyphs
* is less than zero.
*
* @throws IndexOutOfBoundsException if either
* firstGlyphIndex
or (firstGlyphIndex +
* numGlyphs)
is not in the range [0 .. getNumGlyphs() -
* 1]
.
*/
public float[] getGlyphPositions(int firstGlyphIndex,
int numGlyphs,
float[] outPositions)
{
if (numGlyphs < 0)
throw new IllegalArgumentException();
validate();
if (outPositions == null)
outPositions = new float[numGlyphs * 2];
System.arraycopy(/*src */ pos, /* srcStart */ firstGlyphIndex * 2,
/* dest */ outPositions, /* destStart */ 0,
/* length */ numGlyphs * 2);
return outPositions;
}
private float getAscent()
{
return fontDelegate.getAscent(fontSize, transform,
renderContext.isAntiAliased(),
renderContext.usesFractionalMetrics(),
/* horizontal */ true);
}
private float getDescent()
{
return fontDelegate.getDescent(fontSize, transform,
renderContext.isAntiAliased(),
renderContext.usesFractionalMetrics(),
/* horizontal */ true);
}
public Shape getGlyphLogicalBounds(int glyphIndex)
{
float x, y, ascent;
validate();
ascent = getAscent();
x = pos[glyphIndex * 2];
y = pos[glyphIndex * 2 + 1];
return new Rectangle2D.Float(x, y - ascent,
pos[(glyphIndex + 1) * 2] - x,
ascent - getDescent());
}
public Shape getGlyphVisualBounds(int glyphIndex)
{
return getGlyphOutline(glyphIndex).getBounds2D();
}
/**
* Determines the metrics of the glyph at the specified index.
*
* @param glyphIndex the index of the glyph whose metrics is to be
* retrieved.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public GlyphMetrics getGlyphMetrics(int glyphIndex)
{
// FIXME: Not yet implemented.
throw new UnsupportedOperationException();
}
/**
* Determines the justification information for the glyph at the
* specified index.
*
* @param glyphIndex the index of the glyph whose justification
* information is to be retrieved.
*
* @throws IndexOutOfBoundsException if glyphIndex
is
* not in the range .
*/
public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
{
// FIXME: Not yet implemented.
throw new UnsupportedOperationException();
}
/**
* Determines whether another GlyphVector is for the same font and
* rendering context, uses the same glyphs and positions them to the
* same location.
*
* @param other the GlyphVector to compare with.
*
* @return true
if the two vectors are equal,
* false
otherwise.
*/
public boolean equals(GlyphVector other)
{
GNUGlyphVector o;
if (!(other instanceof GNUGlyphVector))
return false;
o = (GNUGlyphVector) other;
if ((this.font != o.font)
|| (this.fontDelegate != o.fontDelegate)
|| (this.renderContext != o.renderContext)
|| (this.glyphs.length != o.glyphs.length))
return false;
for (int i = 0; i < glyphs.length; i++)
if (this.glyphs[i] != o.glyphs[i])
return false;
validate();
o.validate();
for (int i = 0; i < pos.length; i++)
if (this.pos[i] != o.pos[i])
return false;
return true;
}
private void validate()
{
if (!valid)
performDefaultLayout();
}
}