Subtleties in Java text rendering

Lately Mario and I have been looking a little at a rendering bug in OpenJDK:

The above is with OpenJDK on Linux, below is the same with closed JDK. As can be seen, in the above picture the undershoot of the ‘g’ is cut off. Mario did a lot of research and debugging in the low level Freetype font code. There are a lot of little things to consider, and for quite a long time it seemed that this bug has something to do with the Freetype font scaler. Let’s have a look at how the bounds appear for the ‘g’ glyph:

On the left is the bounding box of  ‘g’ with OpenJDK, right the same on closed JDK. As you can see, the height of the bounding box appears a little to small, leaving out the undershoot of the glyph. Why is this so?

To explain this, we need to see where is this bounding box coming from. In particular the height. The height here is the FontMetrics.getHeight(). In fact, it is wrong to render it like this, because the FontMetrics.getHeight() is not defined as the ‘(max) height’ of glyphs of a font, but as the distance from baseline to baseline of two rows of rendered text. As we will see, this is different. So let’s see where this is coming from. If you look at the FontMetrics.getHeight(), it is computed as the sum of ascent, descent and leading:

public int getHeight() {
    return getLeading() + getAscent() + getDescent();
}

Ascent is the amount of space that’s needed above the baseline, descent the amount of space below the baseline, and leading is the gap between two rows of text.

Interestingly, if you follow the code where these 3 values are coming from, you can find that in freetypescaler.c, the ascent and descent are taken from FreeType, but the leading is actually calculated from the ascent, descent and height that are reported from FreeType (FT does not directly report about the leading). I.e. we calculate leading as height – ascent – descent (I am talking absolute values here, Freetype does infact report descent as negative because it’s measured in the opposite direction as ascent). In particular, for the font that’s used above, we get height = 14, descent =3 and ascent = 12. Using these values, we end up with a leading of -1. This is an impossible value though, because negative leading never makes sense. So why do we get these strange values? These are effects of grid fitting on the font. The original values are scaled and grid fitted, and it happens that there’s a small overlap between lines.

This is correct from a text rendering point of view, but it confuses some parts of Swing. That is because in many places it is assumed that ‘height’ is the actual height of a line of text, which is not necessarily true. Even the API docs of FontMetrics.getHeight() state this.

Funnily, I see a lot of code that seems to cover up for this. For example, in FontDesignMetrics.getHeight() (this is the subclass of FontMetrics that’s used in most – if not all – cases:

public int getHeight() {
    if (height < 0) {
        height = getAscent() + (int)(roundingUpValue + descent + leading);
    }
    return height;
}

I believe (I don’t know for sure though) that the roundingUpValue is actually there to cover up for the misconception of the height of a font. Also, it appears that the closed font scaler never reports seemingly inconsistent (but actually correct) values for ascent, descent, height and leading. I can only guess that there are also workarounds for this in the closed font scaler code (haven’t checked yet). But what it leads to is (IMO) subtle mistakes in how text is rendered. For example, the height of a label is computed by the font’s height, which includes the leading. But leading actually doesn’t make sense for a single line of text, and if there actually is a positive leading, this results in additional space at the bottom of a label text. Also, if the closed font scaler really fixes up the height so that it always includes the whole glyph, disregarding the font’s preferred line spacing, this can result in text rendered a little (1 pixel) too sparse.

To sum up, I think we have found several related issues that (at least in part) cover up each other, but subtly affect text rendering in Java and in particular Swing:

  • FontMetrics calculating the height as sum of ascent, descent and leading, while it should use what is coming from Freetype. And leading should never be negative.
  • Swing wrongly assuming FontMetrics.getHeight() is the height of a row of text, while it should use height as baseline-to-baseline distance instead, and use the ascent as the space needed at the top of a text frame, and descent as the space that’s needed at the bottom of a text frame, and therefore, for a single line of text, ascent+descent as the space taken up by this single line of text.
  • A couple of adjustments and workarounds that seem to be there to cover up the misconceptions.

I want to thank Mario, who did most of the work to figure out how these things play together.

About these ads

2 Responses to Subtleties in Java text rendering

  1. Clemens Eisserer says:

    Mario, thanks a lot for working on this – don’t want to guess how many hours of research and debugging went into this. Glad to hear you succeeded :)

    This thing bothered me quite for some time now ^^

  2. Mario Torre says:

    @Roman

    Thanks for blogging about that :)

    @Clemens

    Thanks, yeah, it was quite lot of digging indeed :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 159 other followers

%d bloggers like this: