Fonts and the Font Class in Java
FontMetrics measures characters and strings in one font and is obtained from g.getFontMetrics(font).
Predicting Where drawString Puts Text
Drawing text in a Java window looks simple: you call drawString() with a string and a pair of coordinates. The difficulty is that a naive guess at those coordinates will almost always look wrong. A string that starts at x equals 100 occupies a different width on screen depending on the font and the characters in it, and the same is true vertically. If you want text centered in a window, or want two lines to sit at a predictable distance apart, you need to know how large the rendered text will be before you draw it.
What do you think happens?
When you call drawString() with a y-coordinate, which part of the text does that coordinate refer to: the top edge of the letters, or something else?
Reveal answer
The baseline, the line on which the letters rest
Many learners guess the top edge because it is the edge a reader sees first. The measurements in Java are built around the baseline instead, so the top of the string sits above the y-coordinate you passed in.
The Vertical Structure of a Font
Every font carries a family of vertical measurements, and they are all defined relative to one reference line. The baseline is the line that the letters stand on. The ascent is how far the tallest characters reach above that baseline. The descent is how far characters with tails, such as the letter y, reach below it. The suggested distance from one baseline to the next for single-spaced text is called the lineheight of the font. Between two adjacent lines there is also extra blank space called leading.
The four numbers do not simply add up to the lineheight. Ascent and descent together cover the characters on one line, but they leave out the gap that keeps the tops of one line's characters from colliding with the tails of the line above. That gap is the leading. So lineheight equals ascent plus descent plus leading, and the lineheight is not just the sum of the ascent and the descent.
Where the Baseline Sits in drawString
Because the font's measurements are anchored to the baseline, drawString() treats its y-coordinate as the baseline. The letters are drawn so that they rest on that line. Their tallest parts extend upward from it by the ascent, and any tails extend downward from it by the descent. The top of the string therefore sits above the y-coordinate you passed in, not at it. This is why a value that looks like 'the top' in your head produces text that appears to float too low or too high.
Getting a FontMetrics Object
A Font object describes the typeface and size you want. A FontMetrics object is always tied to one particular font and answers questions about text in that font only. To obtain one, ask the graphics context for the metrics of a font. If you already have a Graphics object named g and a Font object named font, the call is g.getFontMetrics(font). The result is commonly stored in a variable named fm, and that variable then provides the measuring methods.
- The Graphics context g supplies the metrics through
getFontMetrics(font). getAscent(),getDescent(),getLeading(), andgetHeight()describe the vertical structure of the font.stringWidth(str)reports the width of the string drawn in this font.
Each method answers one specific question. getAscent(), getDescent(), getLeading(), and getHeight() describe vertical structure. charWidth(ch) gives the width of one character drawn in that font, and stringWidth(str) gives the width of a string drawn in that font. The width methods depend on the font, so the same string produces different widths when the font changes. That dependency is why measurements must be taken from the font you are actually going to draw with.
Centering a String Step by Step
The main use of these measurements is centering. The procedure has four moves. First find the center of the component. Next find the size of the string. Then move back from the center by half the string's width to find the left edge, and move up from the center by half the string's height to find the top edge. The last move is the one that trips people up: drawString() needs the baseline, not the top, so you move down from the top by the ascent.
- baseX moves back from the center by half the string width.
stringHeightis the ascent alone when no character has a tail, and ascent plus descent when one does.topOfStringmoves up from the center by half the string height.- baseY is built from
topOfStringplus ascent, becausedrawStringneeds the baseline.
Centering Hello World Without Descenders
Center the string Hello World in a component, where no character has a tail below the baseline.
- 1Pick the height formula
No character in Hello World drops below the baseline, so the visible height is the ascent alone.
- 2Compute baseX
Start at the center x and move left by half the string width.
- 3Compute topOfString
Start at the center y and move up by half the string height, which here is the ascent.
- 4Compute baseY
Move down from
topOfStringby the ascent, becausedrawStringneeds the baseline.
baseX is centerX minus half the string width, topOfString is centerY minus half the ascent, and baseY is topOfString plus the ascent, which is the value passed to drawString.
Choosing the Height for Centering
The height used for vertical centering depends on the characters in the string. The source example Hello World has no tails, so the ascent alone describes its visible height. If the string were Goodbye World, the letter y has a tail, so the vertical size must include the descent as well, and the height becomes the ascent plus the descent.
Mistakes With Font Measurements
Common mistake Treating the y-coordinate of drawString as the top of the text
What it looks likePassing centerY directly as the y value and expecting the letters to be centered.
Why it failsdrawStringuses the baseline, and the top of the string lies above it by the ascent.Do this insteadCompute topOfString first, then set baseY to topOfString plus the ascent.
Common mistake Equating lineheight with ascent plus descent
What it looks likeSpacing two lines by adding only ascent and descent.
Why it failsThose two values leave out the leading, which is the gap between lines.
Do this insteadUse lineheight, which is ascent plus descent plus leading, when spacing lines.
Common mistake Reusing one font's metrics for a different font
What it looks likeMeasuring a string with the metrics of an old font and drawing it in a new one.
Why it failsA FontMetrics object is tied to one font, and width results change with the font.
Do this insteadObtain the FontMetrics object from g.getFontMetrics(font) using the same font you draw with.
Common mistake Forgetting to add descent to the height for strings with tails
Practice: Centering Goodbye
Suppose you want to center the word Goodbye in a component. Decide which height formula applies, then write the three assignments that produce baseX, topOfString, and baseY in that order. Confirm that baseY is built from topOfString and does not use centerY directly.
Hints
- Look at each letter of Goodbye and ask whether any of them has a tail below the baseline.
- The letter y has a tail, so the height must include the descent.
- baseX uses half the string width,
topOfStringuses half the height, and baseY adds the ascent totopOfString.
Key Takeaways
- A FontMetrics object measures characters and strings for one particular font and is obtained with
g.getFontMetrics(font)from a Graphics context. - The baseline is the line the letters rest on. Ascent is the height above it, descent is the reach below it, leading is the gap between lines, and lineheight equals ascent plus descent plus leading.
drawString()takes its y-coordinate as the baseline, so the top of the string sits above that value by the ascent.- To center a string, use half the string width for baseX and half the string height for
topOfString, then add the ascent to get baseY. The height includes descent only when the string has tails.