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