edit: Don't do this! Do this instead. Only read this article if you're looking for context.

Starting in Buster, Debian enabled the subpixel hinting functionality in Freetype (commonly known as the “Infinality” patches, though the enabled functionality is a small, more performant subset of the patches actually submitted by Infinality). To no one's greater surprise than my own, this badly broke my font rendering. Here's how I fixed it.

The issue, as far as I can tell, is that the new hinter mostly or completely ignores hinting built into fonts, which is—apparently—an improvement most of the time, but for well-hinted fonts, can be a significant degradation at small pixel sizes. I've used the Liberation fonts for many years, and the change turned my text into a blurry mess.

How it looksHow I want it to look

(Look at the lowercase “j”! Terrible.)

What appears to be happening is that the hinter is trying to preserve the glyph forms, resulting in lines halfway between pixels that then get antialiased into a two-pixel-wide, 50% opacity smear. A blurry mess. The built-in hinting makes a hash of the glyph forms, hammering strokes onto the pixel grid, but at small sizes this is highly desirable as it keeps things crisp and legible.

(As a minor aside, I hate this about Mac OS's font rendering: it prioritizes the font's shapes at the expense of legibility. Literally form over function. Fortunately, high-DPI is making all this less of an issue, since it's mostly irrelevant at larger pixel sizes.)

So, to revert to the old Freetype behavior, we need to switch off subpixel hinting. (I don't remember exactly how I discovered that this was the precise switch that needed toggling. Sorry.) I couldn't figure out how to do this in configuration—I suspect it's not possible, but I am absolutely not and do not intend to become an expert in the arcana of fontconfig configuration. If it is possible and you know how, please let me know! I had to patch and rebuild libfreetype6. Here's how that went. I'm not an expert on Debian packaging, either, so this all comes with the major caveat that it's probably mostly wrong, but it worked for me. If you know better, I'd appreciate hearing about that, too.

  • First, build tools. You'll want build-essential, dpkg-dev, and devscripts.
    sudo apt install build-essential dpkg-dev devscripts
  • Second, build-deps for freetype. (freetype is the source package for libfreetype6. You can find this out on the Debian package page; there's probably a way through the package manager also, but I don't know how and the package pages are easy.) sudo apt build-dep freetype
  • Third, the source for freetype itself. This can and should be run as a regular user. The working directory you run it in will get cluttered, so you probably want a specific build directory, e.g. I used ~/projects/freetype. apt source freetype Now cd into the extracted source, freetype-version (for Buster, freetype-2.9.1-3+deb10u1).
  • Now to actually make the patch. Through painstaking research (read: I grepped the codebase for “Infinality”), I determined that you want to comment the lines setting TT_CONFIG_OPTION_SUBPIXEL_HINTING in the two files it appears in: devel/ftoption.h and include/freetype/config/ftoption.h. (You probably only need to get one of them, but both definitely works and this process is annoying enough I'm not interested in experimentation.) Do this however you want. A more intrepid, foolhardy blogger might have provided a sed snippet here, but I just used vim and editing source with sed is like performing surgery with a shotgun. My ultimate patch was --- freetype-2.10.2+dfsg.orig/devel/ftoption.h +++ freetype-2.10.2+dfsg/devel/ftoption.h @@ -675,7 +675,7 @@ FT_BEGIN_HEADER */ /* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING 1 */ /* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING 2 */ -#define TT_CONFIG_OPTION_SUBPIXEL_HINTING ( 1 | 2 ) +/*#define TT_CONFIG_OPTION_SUBPIXEL_HINTING ( 1 | 2 )*/ /************************************************************************** --- freetype-2.10.2+dfsg.orig/include/freetype/config/ftoption.h +++ freetype-2.10.2+dfsg/include/freetype/config/ftoption.h @@ -674,7 +674,7 @@ FT_BEGIN_HEADER * https://www.microsoft.com/typography/cleartype/truetypecleartype.aspx */ /* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING 1 */ -#define TT_CONFIG_OPTION_SUBPIXEL_HINTING 2 +/*#define TT_CONFIG_OPTION_SUBPIXEL_HINTING 2*/ /* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING ( 1 | 2 ) */
  • Turn your edits to the source into a Debian patch. This is the part I'm most certain is wrong. I couldn't find clear documentation on how this should be done for the purposes of maintaining a local patchset. The following works, but I'm not sure exactly what the build system thinks I'm doing. dpkg-source --commit freetype-version patch-name dpkg-source --build freetype-versionversion” is the freetype version, as above; “patch-name” can be whatever you want (I called mine “wb-interpreter-v35”, for reasons that are made clear if you read the source comments explaining what the line you just commented does).
  • Build it! debuild -us -uc
  • And finally, install your patched libfreetype6. The .deb file will be in the directory above the freetype-version directory, i.e. the directory you invoked apt source in. sudo dpkg -i ../libfreetype6_version_arch.deb

You'll have to restart any applications that were using the old libfreetype6 to get them to use the new one. Also, note that this builds the same version as the official package, so any updates will overwrite it. Freetype doesn't change that much, so I haven't figured out a smoother way to reapply this patch than to start over from scratch every time.

So there you go! On the other hand, if you're in the apparent majority of people who don't find subpixel hinting to be a massive font rendering regression—don't do this, I guess? I am very curious to know why this hugely popular patch was such a massive regression for my fonts.

Also, this whole process gives me a very strong suspicion about what it is I don't like about OpenBSD's font rendering and how to fix it, so that's on my list to investigate now.