The crisp fonts saga

Philip Race philip.race at oracle.com
Tue Dec 12 20:38:50 UTC 2023


Usage of hinting in UIs is on the way out.
macOS stopped applying hints ages ago. Apple also canned LCD text.
High DPI displays are obsoleting the raison d'etre of both of these.
A big problem with hinting is that it distorts both the size and the 
shape, so UIs do not scale evenly
and animations look jerky.
Another is that a poorly hinted font is far worse than no hinting at all.


-phil.


On 12/12/23 6:10 AM, Mark Raynsford wrote:
> Hello!
>
> I've never been particularly satisfied with the font rendering in
> JavaFX. In particular, on Linux, the text always appears very soft and
> blurry compared to non-JavaFX applications on the same system. Even
> applications that render antialiased text with Java2D seem to look
> better.
>
> I decided to take a look into it to see if anything could be done about
> this, and I have some questions. I'm only looking at the Freetype
> implementation in Prism currently, as that's all I can realistically
> test on at the moment.
>
> For reference, here's how text rendered at 16px using Terminus TTF
> looks today:
>
> https://ataxia.io7m.com/2023/12/12/hinting_nobitmaps_normal.png
>
> I'm not sure if I'm alone on this, but I find this almost migraine-
> inducing. No other application on my system, including those that use
> Freetype, using that font at that size will render as blurry as that.
>
> Looking at
> modules/javafx.graphics/src/main/java/com/sun/javafx/font/freetype/FTFo
> ntFile.java, I see this in initGlyph():
>
> ```
> int flags = OSFreetype.FT_LOAD_RENDER | OSFreetype.FT_LOAD_NO_HINTING |
> OSFreetype.FT_LOAD_NO_BITMAP;
> ```
>
> Additionally, the code might also add the FT_LOAD_TARGET_NORMAL
> or FT_LOAD_TARGET_LCD flags later, but I'll assume
> FT_LOAD_TARGET_NORMAL for the sake of avoiding combinatorial explosions
> in testing at this point.
>
> Essentially, we discard hinting information, and we discard bitmap
> information. I'm not sure why we do either of these things. I decided
> to try different combinations of flags to see what would happen.
>
> Here's FT_LOAD_RENDER | FT_LOAD_NO_BITMAP (no bitmaps, but using
> hinting data):
>
> https://ataxia.io7m.com/2023/12/12/hinting_nobitmaps_normal.png
>
> That's no real improvement. Here's FT_LOAD_RENDER | FT_LOAD_NO_HINTING
> (ignore hinting data, but use bitmaps if they are included):
>
> https://ataxia.io7m.com/2023/12/12/nohinting_bitmaps_normal.png
>
> That, to my poor suffering eyes, is already a _vast_ improvement.
>
> Let's try including both hinting and bitmaps (FT_LOAD_RENDER):
>
> https://ataxia.io7m.com/2023/12/12/hinting_bitmaps_normal.png
>
> Inspecting that in an image editor shows the pixels of the text to be
> identical.
>
> So, clearly, Terminus TTF includes bitmaps for smaller text sizes.
> Let's try another font such as Droid Sans that renders crisply at ~10pt
> sizes on my system, and that I'm reasonably confident doesn't include
> any bitmaps.
>
> Here's the JavaFX default (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP):
>
> https://ataxia.io7m.com/2023/12/12/droid_12_nohinting_nobitmaps.png
>
> That's pretty nasty. Let's enable hinting (FT_LOAD_NO_BITMAP):
>
> https://ataxia.io7m.com/2023/12/12/droid_12_hinting_nobitmaps.png
>
> That's already a lot better. If you overlay the two images in an image
> editor, it's clear that the glyph shapes are not quite the same (with
> hinting, some glyphs are ever-so-slightly taller).
>
> For completeness, let's allow bitmaps:
>
> https://ataxia.io7m.com/2023/12/12/droid_12_hinting_bitmaps.png
>
> The rendered glyphs are pixel-identical.
>
> Now, most modern desktops have options to disable antialiasing for text
> under a given size. Antialiasing on 10pt text is rarely an improvement
> over just not having it as there are so few pixels to work with. I
> decided to experiment a bit with turning off antialiasing. This
> requires setting the load target to FT_LOAD_TARGET_MONO so that
> Freetype returns a monochrome image instead of what amounts to an alpha
> coverage map. Unfortunately, this does also change the format of the
> image returned to a 1bpp image instead of an 8bpp greyscale image, and
> JavaFX isn't equipped to handle that. However, we can do the conversion
> manually if we see that bitmap.pixel_mode == 1, and then the rest of
> JavaFX doesn't need to care about it:
>
> ```
> if (bitmap.pixel_mode == 1) {
>    byte[] newBuffer = new byte[width * height];
>    for (int y = 0; y < height; y++) {
>      final var rowOffset = y * width;
>      for (int x = 0; x < width; x++) {
>        final var byteOffset = rowOffset + x;
>        newBuffer[byteOffset] = bitAt(buffer, x, y, pitch);
>      }
>    }
>    buffer = newBuffer;
> }
>
> private static byte bitAt(byte[] buffer, int x, int y, int pitch)
> {
>    final var byteOffset = (y * pitch) + (x / 8);
>    final var bitOffset = 7 - (x % 8);
>    final var bit = (buffer[byteOffset] >>> bitOffset) & 1;
>    return (byte) (bit == 1 ? 0xff : 0x00);
> }
> ```
>
> Here's the JavaFX default of (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)
> combined with FT_LOAD_TARGET_MONO:
>
> https://ataxia.io7m.com/2023/12/12/droid_12_nohinting_nobitmaps_mono.png
>
> That's not a typeface even a mother could love. :)
>
> However, what happens if we enable hinting?
>
> Here's (FT_LOAD_NO_BITMAP | FT_LOAD_TARGET_MONO):
>
> https://ataxia.io7m.com/2023/12/12/droid_12_hinting_nobitmaps_mono.png
>
> I mean, it's not exactly wonderful for Droid Sans 12 (the O is a little
> mangled), but that's more an issue with the font itself. It's certainly
> better than the result _without_ hinting.
>
> Amusingly, here's DejaVu Sans at 7pt, (FT_LOAD_NO_BITMAP |
> FT_LOAD_TARGET_MONO):
>
> https://ataxia.io7m.com/2023/12/12/dejavu_12_hinting_nobitmaps_mono.png
>
> That, to my eyes, looks pretty good. The JavaFX defaults for the same
> font are not good:
>
> https://ataxia.io7m.com/2023/12/12/dejavu_12_nohinting_nobitmaps_normal.png
>
> I've tried on multiple systems (all Linux, however), and I've yet to be
> able to contrive a situation where the JavaFX defaults give better
> rendering results with any combinations of font sizes, with or without
> AA. A brief inspection of the JDK's Java2D sources show that it does
> conditionally use FT_LOAD_NO_HINTING depending on various settings
> (there are comments about FT_LOAD_NO_HINTING yielding constant-sized
> glyphs, which supposedly can make things easier in some cases). The
> Java2D results on the systems I tested are consistently better.
>
> So I guess my questions are:
>
>    * Why do we discard hinting information?
>    * Why do we discard bitmaps?
>    * Would JavaFX accept patches to allow hinting, bitmaps, and
> FT_LOAD_TARGET_MONO? Ideally this should be developer-controlled, so I
> would need to come up with a pleasant API for it.
>
> My experience has been that most JavaFX applications tend to bundle
> fonts rather than relying on anything the system has. I suspect that,
> given that developers are including their own fonts, they are the best
> equipped to answer questions about hinting and AA, rather than just
> setting values and hoping that the font they get will work well, so an
> explicit API might be fine.
>



More information about the openjfx-dev mailing list