Thursday, August 19, 2010

Java Text – A Closer Look at the Ugly

Because the failings of the native Java text renderer have had such an adverse impact on my recent work, I think additional examination of the issue is merited. So, to further illustrate the difference between text rendered by Java 6 on Mac OS X and Linux, I’ve taken two paragraphs of text and written a program that fits them on-the-fly into an image of a specified size. Text fitting is vital to the operation of the application from which yesterday’s examples were taken, so it seemed appropriate to create new examples based on text fitting as well.

[By the way, this is in no sense a dig at Linux. I believe the text rendering seen on Linux is representative of Java’s built-in text rendering capabilities, whereas I believe the text rendering seen on Mac OS X is representative of OS X’s native text renderer, which I suspect Apple engineers substituted for Java’s built-in text renderer. Running the code on Linux just happens to let the ugly truth shine through. As with yesterday’s examples, the version of Java used on the Linux box was “Java(TM) SE Runtime Environment (build 1.6.0_12-b04)” and the version on Mac OS X was “Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)”.]

The font used in all cases is Georgia, and the font binary is identical on both installations. The quotation used is from Aldo Leopold’s book A Sand County Almanac, and Sketches Here and There, which I highly recommend. (More of the quote can be found on my page of random Leopold quotes.)

The fitting errors cited below are the difference between the height, in pixels, of the area into which the text was fit, and the actual height of the text. The closer the error is to zero, the better. Considering that the errors are spread over 14 lines of text in the small font example, and 30 lines in the large font example, even the big errors aren’t significant. Nonetheless, the errors consistently show a better fit is achieved when the code is run on Mac OS X, which suggests, at a minimum, the availability of more precise font metrics, and possibly the ability of the font renderer to actually produce (subtly) different glyphs for fonts whose sizes vary by only small fractions of a point.

Small Text Rendering

This is where the difference between Java’s text rendering on Mac OS X and Linux is most obvious. Figure 1 shows a 4X magnification of approximately 12 point text when the Java test program is run on Linux. Figure 2 shows the same magnification of the text produced by the test program when run on Mac OS X. (Figure 3 shows the full renderings at normal size, in an alternating animation.)

The difference in the antialiasing algorithms is striking. In figure 2, Mac OS X shows that antialiasing is applied to every pixel of every glyph, and that the sub-pixel glyph placement this permits allows for more consistent spacing between glyphs (although one can readily find places where either renderer could do a better job of kerning). It also provides for a more consistent “color” (uniform darkness or lightness) of text. Figure 1, by comparison, shows a minimal use of antialiasing, primitive glyph shapes, some highly irregular glyph spacing, and poor color.

An examination with Adobe Photoshop shows only 127 shades of gray are used in the text from which figure 1 was taken. In contrast (no pun intended), the same examination of the text from which figure 2 was taken shows 249 shades of gray are used. So, in this case the default Java font renderer is using half of the available shades of gray to form its glyphs, while the Mac OS X renderer is using almost all of the available shades. That alone gives the Mac OS X renderer almost twice the ability to represent sub-pixel glyph features, an ability of which it seems to take full advantage. Every glyph in these two examples demonstrates this, but, if you want a specific example, take the word “July” on the sixth line. Observe that in figure 1 the bottom of the J’s curve is flat against the baseline (so much for the curve) and very nearly black, while in figure 2 its curve is carefully nuanced right down to two faint gray pixels dropping below the baseline (one is relatively obvious; the other you will probably need further magnification, or a pixel sampler, to see). The descender on the letter “y” is also worth a look. As is plainly visible in figures 4 and 5, that descender is meant to be terminated with a feature similar to a dot. However, in figure 1, there’s no trace of that dot, though it’s clearly present in figure 2.

Another difference in this rendering of small text is most obvious in the figure 3 animation: While the two renderers agree about overall line heights, they disagree on the height of the glyphs by a full pixel. Given that the lines are about 14 pixels high (leading included), that’s a 7% error, though the lack of subtlety in the rendering produced on the Linux machine results in an even greater apparent difference, at least to my eyes.

Next, there’s the matter of large text rendering.

Figure 1. Text rendered by Java 6 on Linux at 11.968737 points
(fitting error: -0.107483), magnified four times.

Figure 2. Text rendered by Java 6 on Mac OS X at 11.944245 points
(fitting error: -0.000177), magnified four times.

Small text rendering examples.

Figure 3. An animated comparison of small text rendering by Java 6 on Mac OS X and Linux. The magnified text seen in figures 1 and 2 was taken from the two renderings from which this animation was produced, seen here at their original size.

Large Text Rendering

The differences between what I’m assuming to be the native Mac OS X text renderer as used by Java 6 on Mac OS X and Java 6’s built-in text renderer, as used on Linux, are less significant at the relatively large font sizes (almost 26.5 points) used in the following figures, but they’re real enough. The figure 6 animation clearly shows many differences in letter spacing, and, while neither renderer is producing ideal results, Mac OS X looks to my eyes to be doing better overall.

Also, the magnifications shown in figures 4 and 5 suggest that, once again, the Java renderer is using fewer shades of gray when it antialiases text than the Mac OS X renderer. And that is, in fact, the case. An examination with Photoshop shows only 123 shades are used in the Java-on-Linux rendering, while the Java-on-Mac-OS-X rendering uses all 256 shades (both numbers include black and white).

Once again, that—and perhaps other failings of the renderer—leads to a lack of subtle detail in the text. Observe that in figure 5 almost every curve or vertical serif that touches the baseline actually extends around half a pixel below it. That doesn’t happen in figure 4. Yet, extending a curve slightly below the baseline is a basic trick of typography – as a curve reaches its bottom it becomes less and less visually significant. If it bottoms-out precisely on the baseline, then it touches the baseline, in principle, at an infinitely small point, and—though an infinitely small point can’t be rendered—the curve nonetheless has reduced contact with the baseline relative to surrounding features that have large areas in contact with the baseline (horizontal serifs, for instance). And that difference in the degree of baseline contact tends to make the glyph with a curved bottom appear to float slightly above the baseline. So, typographers design their fonts to correct for this perceptual error, by dropping the bottom of curves slightly below the baseline. Typographers have a lot of little tricks like that – more, I’m sure, than I’m aware of. And that’s why antialiasing is so important to accurately preserving the look of text on computer displays, at least until our displays exceed something on the order of 300 dots-per-inch of resolution.

The folks behind the text renderer used by Java on Mac OS X clearly demonstrate an understanding of these issues and their importance. There are, nonetheless, strange imperfections in their results, but those seem to stand-out because so much else is done well. The folks responsible for the text renderer used by Java on Linux, which I assume to be Java’s native text renderer, don’t seem to care about these issues. Or, if this article is correct (and I have no reason to doubt it), the Java text engineers get it, but their management doesn’t. (Or it hasn’t – we’ll see if the new Oracle management is any better than the old Sun management, though I see no reason for optimism.) Thus, not only the subtle aspects of the art of typography—those that bend type around the particulars of human perception—but the grossest qualities of glyph formation and spacing are … well, I struggle for the right word. “Trashed,” isn’t quite it, but it’s in the correct neighborhood. And that neighborhood should also include either a pervasive failure of perception, or a cretinous disregard for the quality of what one perceives. Maybe both.

Figure 4. Text rendered by Java 6 on Linux at 26.406231 points
(fitting error: -0.957642), magnified four times.

Figure 5. Text rendered by Java 6 on Mac OS X at 26.403095 points
(fitting error: 0.000000), magnified four times.

Large text rendering examples.

Figure 6. An animated comparison of large text rendering by Java 6 on Mac OS X and Linux. The magnified text seen in figures 4 and 5 was taken from the two renderings from which this animation was produced, seen here at their original size.

The Original Renderings

If you’d like to see the original renderings from which the magnifications and animations were generated, here they are.


  1. Anonymous9:48 AM CDT

    Neither of them uses subpixel rendering (ClearType), which is superior to simple antialiasing.

  2. When I wrote of subpixel rendering I meant rendering intended to give the impression of resolution greater than the target pixel map, which is antialiasing in a nutshell.

    The subpixel rendering you are referring to, I believe, is using the red, green and blue components of pixels in LCD screens independently of one another to actually create subpixel features, albeit of the wrong color (which is supposed to be imperceptible, but frequently, in my experience, isn't).

    Java 6 supports this (or claims to - I haven't tested) by setting a graphics context's rendering hint "KEY_TEXT_LCD_CONTRAST" to one of the values: "VALUE_TEXT_ANTIALIAS_LCD_HBGR", "VALUE_TEXT_ANTIALIAS_LCD_HRGB", "VALUE_TEXT_ANTIALIAS_LCD_VBGR", or "VALUE_TEXT_ANTIALIAS_LCD_VRGB".

    Unfortunately, that facility is useless unless you know the pixel component layout of the LCD on which the graphic will be viewed. It should go without saying that neither the web application I wrote that was undermined by Java's lousy text rendering when run on Linux, nor the example application I wrote to create the example text for this posting, have any way of knowing what any viewer's LCD layout is, so I couldn't use that facility in either of them.