Concepts / Fonts and the Font Class in Java

Fonts and the Font Class in Java

FontMetrics measures characters and strings in one font and is obtained from g.getFontMetrics(font).

  • Programming

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?

  • The top edge of the tallest letters

  • The baseline, the line on which the letters rest

  • The bottom edge of the lowest descender

Reveal answer
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.

lineheightbaseline to next baselineascentabove the baselinebaselineline the letters rest ondescentbelow the baselineleadingblank gap between linesincludesincludesincludesmeasured frommeasured from
How do ascent, descent, leading, and lineheight sit relative to the baseline, and how does lineheight relate to the other three?

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.

Rule

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.

top of stringtallest glyph edgeascent regionabove the baselinebaselinethe y passed to drawStringdescent regiontails below the baseline
When I call drawString at a given y, which part of the glyphs lands on that y, and why does the top of the string sit above it?

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.

java
Notes on the code
  • The Graphics context g supplies the metrics through getFontMetrics(font).
  • getAscent(), getDescent(), getLeading(), and getHeight() 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.

corner placementtext starts near the topleftcentered placementtext sits in the middlebaseX and baseY unsetcoordinates are guessesbaseX fromstringWidthhalf the width left ofcenterbaseY from top plusascentbaseline below the topafter computing
Given the component size and the string's width and ascent, which x and y place the string in the middle, and how do they change from a naive top-left placement?
java
Notes on the code
  • baseX moves back from the center by half the string width.
  • stringHeight is the ascent alone when no character has a tail, and ascent plus descent when one does.
  • topOfString moves up from the center by half the string height.
  • baseY is built from topOfString plus ascent, because drawString needs the baseline.
Worked example

Centering Hello World Without Descenders

Center the string Hello World in a component, where no character has a tail below the baseline.

  1. 1
    Pick the height formula

    No character in Hello World drops below the baseline, so the visible height is the ascent alone.

  2. 2
    Compute baseX

    Start at the center x and move left by half the string width.

  3. 3
    Compute topOfString

    Start at the center y and move up by half the string height, which here is the ascent.

  4. 4
    Compute baseY

    Move down from topOfString by the ascent, because drawString needs the baseline.

Result

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 like

    Passing centerY directly as the y value and expecting the letters to be centered.

    Why it fails

    drawString uses the baseline, and the top of the string lies above it by the ascent.

    Do this instead

    Compute topOfString first, then set baseY to topOfString plus the ascent.

  • Common mistake

    Equating lineheight with ascent plus descent

    What it looks like

    Spacing two lines by adding only ascent and descent.

    Why it fails

    Those two values leave out the leading, which is the gap between lines.

    Do this instead

    Use 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 like

    Measuring a string with the metrics of an old font and drawing it in a new one.

    Why it fails

    A FontMetrics object is tied to one font, and width results change with the font.

    Do this instead

    Obtain 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

MEDIUM

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, topOfString uses half the height, and baseY adds the ascent to topOfString.

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.