8. Code Examples
The bulk of the work for Wiggly Text is implemented in the class TransformedTextLabel.
This subclass of JComponet displays a string, where each letter of the
string can be transformed by its own AffineTransform instance. We
present three code examples here that are illustrative of how to compute with affine
transforms. Note that these examples are somewhat abbreviated for clarity.
The omitted code has nothing to do with affine transforms and the code presented here will
work.
Drawing with Transforms
The drawing is done in paintComponent() (shown here ):
public void paintComponent(Graphics gIn) {
Graphics2D g = (Graphics2D)gIn.create();
Rectangle2D bounds = getLetterBounds();
g.translate(
getWidth() / 2.0 - bounds.getCenterX(),
getHeight() / 2.0 - bounds.getCenterY());
AffineTransform baseTransform =
g.getTransform();
int n = length();
for (int i = 0; i < n; i += 1) {
g.setTransform(baseTransform);
g.transform(getTransform(i));
g.translate(-mCharWidths[i]/2.0, 0.0);
g.drawString(
mText.substring(i, i+1), 0, 0);
}
g.dispose();
}
On the first line we cast the passed in Graphics object to a Graphics2D.
In Java1.2, all Graphics objects are really instances of Graphics2D.
However, for compatibility, none of the existing method signatures were changed.
The next section gets the bounding box of all the transformed letters (more on that
later), and then translates the coordinate system from 0,0 in the upper left (as passed
in), so that the center of the bounding box is centered in the component. We then
save that transform away.
Then, for each letter, we do the following:
- The coordinate system is reset to the base transform, leaving the bounding box centered
in the component.
- The
AffineTransform object for the character is fetched with getTransform().
This object was precomputed in a subclass earlier and saved in an array. This is
then applied to the coordinate system with the transform() method of Graphics2D.
- The coordinate system is shifted by half the character's width. This is so that
drawing the character at 0,0 will position the center of the baseline of the character
exactly where the character's transform placed 0,0.
- Finally, the character is drawn at 0,0.
Creating Transforms
Subclasses of TransformedTextLabel are required to compute an AffineTransform
instance for each character on demand. The class ArcedTextLabel rotates
the coordinate system about a center point. Each letter is rotated a different
amount. The code looks like:
public AffineTransform computeTransform(int i)
{
double pos = getCharDist(i) / getTextDist();
double angle = mArcAngle * (pos - 0.5);
return AffineTransform.getRotateInstance(
angle, 0.0, mRadius);
}
A relative position, between 0 and 1, along the arc is computed using two utility
methods that give information about the text as if it were drawn normally: getCharDist()
returns the distance from the left edge to the center of a given character, and getTextDist()
returns the total width of the text. This position is then used to compute the
amount of rotation. Finally, we return an AffineTransform for rotation
about a point below the center of the component, (0.0, mRadius).
Transforming Shapes
TransformTextLabel needs to compute the bounding box of all the
transformed letters. It uses the bounding box to both provide a preferred size, and
for centering the letters in the component (see the drawing above). It
computes the bounding box this with this code:
protected Rectangle2D computeLetterBounds() {
Rectangle2D bounds = null;
int n = length();
GlyphVector gv = getFont().createGlyphVector(
new FontRenderContext(
((Graphics2D)getGraphics()).getTransform(),
true, true),
mText);
AffineTransform warpAT = new AffineTransform();
for (int i = 0; i < n; i += 1) {
warpAT.setToIdentity();
warpAT.concatenate(getTransform(i));
warpAT.translate(-mCharWidths[i]/2.0, 0.0);
Rectangle2D letterRect =
gv.getGlyphOutline(i).getBounds2D();
Shape warpedRect =
warpAT.createTransformedShape(letterRect);
Rectangle2D dispRect =
warpedRect.getBounds2D();
if (bounds == null)
bounds = dispRect;
else
Rectangle2D.union(
bounds, dispRect, bounds);
}
return bounds;
}
In the loop of this code, warpAT is set up to be the same transform we'll
use when drawing. Then letterRect is set to the bounding rectangle of
the characters outline. This rectangle is warped by the transform with the createTransformedShape()
method. Note that warpedRect is a Shape, not a Rectangle2D:
a rectangle transformed by an affine transform may not be a rectangle any more.
Then, the bounds of that warped shape is combined, via Rectangle2D.union,
with previous bounds to create the complete bounding rectangle.
| |
|