crossfont icon indicating copy to clipboard operation
crossfont copied to clipboard

Non-anti-aliasing config and rendering for macOS

Open p00ya opened this issue 10 months ago • 3 comments

I'm interested in contributing to alacritty & crossfont to provide an option to disable anti-aliasing for glyph rendering (at runtime, using the alacritty config). I'm primarily interested in contributing for the macOS backend (though I could be convinced re: Windows and freetype). I'm filing here in the crossfont tracker for the crossfont specific changes (because they're not trivial), but also happy to discuss the alacritty side of things on the related alacritty/alacritty#957.

Motivating Scenario

I like to use Monaco 10 as a terminal font on macOS, because the font is pre-installed on every machine, is decent enough for coding, and, importantly, is very clear even at such a small point size because it's perfectly pixel-hinted:

Image

However, with anti-aliasing turned on, it (and other fonts of the same size) are hopelessly blurry:

Image

The screenshots above don't even do the effect justice (github scaled the un-anti-aliased one, ruining the acuity, and the anti-aliased one looks way worse on a physical OLED display because of sub-pixel layouts).

Technical Considerations

I have a proof of concept (not yet in a PR-quality state) with alacritty/crossfont looking pretty reasonable:

Image

However, it's not just a matter of cg_context.set_should_antialias(false);. The current logic for the glyph metrics (using CTFontGetBoundingRectsForGlyphs gives the bbox for the anti-aliased glyph, which I've found can be multiple pixels out from the hinted glyph.

Here's an example from Monaco (screenshot of fontforge displaying the pixel hints):

Image

The bbox returned by CoreText is:

CGRect { origin: CGPoint { x: 1.1083984375, y: 0.0 }, size: CGSize { width: 4.1650390625, height: 7.578125 } }

but the correct origin without anti-aliasing should actually be x=0, i.e. it's not just a rounding error. The entire vertical stroke gets cropped out with the current logic.

This seems to be WAI from the font and OS perspective. i.e. it's not that the font designer has made a mistake by specifying hints >1 pixel away from the paths. And it's just a limitation of the particular CTFontGetBoundingRectsForGlyphs being used that there's no way to specify you want the bitmap rendering when requesting the bounding box.

The solution is to use a generous buffer for rendering and then post-process if you really need the metrics. This seems to be what other terminal emulators are doing. I wasn't even sure why crossfont tries so hard to render the minimal bbox given the other assumptions in crossfont about monospaced fonts - clients like alacritty will surely just be pushing the bitmap into atlas cells eventually.

The other consideration is how to pass the configuration down from the client app to crossfont. I figure injecting an Options struct into the Rasterizer (either in new or after initialization), with platform-specific contents, would be the way to go, rather than the weird NSUserDefaults side channel currently used for AppleFontSmoothing. Happy to take advice here - there are also specifics like whether it should just be a boolean or whether it should have something like the lcd, lcdv, gray, none enum from the alacritty issue.

Justification Discussion

This is something that was part of @jwilm's original proposal for alacritty/alacritty#957, and even before then had an alacritty PR https://github.com/alacritty/alacritty/pull/560/files.

Since then, in reading various other issues on the alacritty tracker, it does seem like @chrisduerr and @kchibisov aren't super keen for font customization features, typically suggesting they'd be better off implemented at the OS or fontconfig level. A related change is removing the use_thin_strokes alacritty configuration in favour of respecting the AppleFontSmoothing value from NSUserDefaults.

I'm advocating for having a config on the basis of:

  • Having anti-aliasing on or not is an aesthetic preference, similar to choosing a font size or face. Therefore it's something that makes sense to be user configurable at runtime.
  • For similar reasons, I don't think it makes sense to claim it's something that should just be configured at the platform-level - whether a particular font looks good without anti-aliasing is really dependent on the font and the context, and therefore I think it should be an alacritty-specific config. i.e. I think it's perfectly reasonable that a user might want other apps on the same platform to use/not-use anti-aliasing, even for the same font. That said, fontconfig probably does a good enough job that it's not really an issue for the freetype backend.
  • Other terminal emulators (including Apple's Terminal.app) do support explicit configuration of anti-aliasing. Yes, I realize arguments like these don't usually hold much sway with alacritty maintainers!

So, I think it's reasonable to add to crossfont and then alacritty. But if you don't, I'm also okay to just keep running my own patch/fork.

Let me know if you want the PR, and/or have any suggestions on the technical aspects.

p00ya avatar Jan 25 '25 04:01 p00ya

I think it's perfectly reasonable that a user might want other apps on the same platform to use/not-use anti-aliasing, even for the same font.

This just seems like an incredibly weak point. If rendering of a font is consistent on your platform, as it should be, then this makes very little sense.

So, I think it's reasonable to add to crossfont and then alacritty. But if you don't, I'm also okay to just keep running my own patch/fork.

I think there's some merit to expose configuration options from crossfont, however I don't really think they provide much value on platforms that have a reasonable system font configuration. As such I don't see how including these options into Alacritty itself would be the best possible solution.

However in this issue you've already outlined having to rely on hacks to get things working 'properly' with regards to glyph alignment, which immediately makes me lose interest in trying to figure this out in crossfont alone. With how clear macOS makes it that it does not want users to do this, I don't see why Alacritty would try to fight it.

Generally speaking all APIs introduced to crossfont would need to be platform-independent, since that's really the only reason for crossfont to exist. Contributing just a macOS implementation is unlikely to get accepted; at the very least macOS and Windows would be required since those platforms are not going to be contributed otherwise (and I have no interest in writing it myself).

chrisduerr avatar Jan 25 '25 05:01 chrisduerr

I think it's perfectly reasonable that a user might want other apps on the same platform to use/not-use anti-aliasing, even for the same font.

This just seems like an incredibly weak point. If rendering of a font is consistent on your platform, as it should be, then this makes very little sense.

For a font with pixel hints, the anti-aliased and un-anti-aliased renderings are effectively different faces (e.g. in the 'F' example above, even the middle horizontal bar is different). Consistency for consistency's sake seems like an incredibly weak point to me too; either position is just arbitrary dogma right?

Maybe the font designer should have actually split out a separately named face given that the hinted rendering is distinct? Maybe that's also reasonable in isolation, but font designers don't do this - they included two distinct renderings for the same face and size precisely so they could be used in different contexts.

So, I think it's reasonable to add to crossfont and then alacritty. But if you don't, I'm also okay to just keep running my own patch/fork.

I think there's some merit to expose configuration options from crossfont, however I don't really think they provide much value on platforms that have a reasonable system font configuration. As such I don't see how including these options into Alacritty itself would be the best possible solution.

Yeah, in the fontconfig/FT case I agree it's just better to do things that way. Even if you were right about consistency across apps being paramount, macOS doesn't provide anything like fontconfig where you can do per-font customization unfortunately. Practically speaking, it's better for users/developers to just follow the platform convention and implement it per-app than to expect Apple to make a change.

However in this issue you've already outlined having to rely on hacks to get things working 'properly' with regards to glyph alignment, which immediately makes me lose interest in trying to figure this out in crossfont alone. With how clear macOS makes it that it does not want users to do this, I don't see why Alacritty would try to fight it.

I don't think macOS is making it clear that it doesn't want users to turn anti-aliasing off. Apple in their own Terminal emulator (which I would consider as a reference implementation for the platform) do it correctly. And it's really only because crossfont is doing weird optimizations currently that any 'workaround' is required... The more straightforward code of just rendering to a buffer sized at max(font metrics, glyph metrics) will work OOTB.

Post-processing for the metrics is also a trivial O(n) pass. It may not even be any slower than CTFontGetBoundingRectsForGlyph (esp. at the small font sizes where hints are relevant). But I can measure this if you're concerned about performance (though why would you be - it would be insane for anyone to be using crossfont without caching the result in an atlas).

Generally speaking all APIs introduced to crossfont would need to be platform-independent, since that's really the only reason for crossfont to exist. Contributing just a macOS implementation is unlikely to get accepted; at the very least macOS and Windows would be required since those platforms are not going to be contributed otherwise (and I have no interest in writing it myself).

Sure, I can implement on Windows too. For FT, I feel like it's not really required because fontconfig already allows per-font and per-process customization by your own arguments. However, some of the more advanced options on the original RFC will only work on some platforms... so would you be okay with something closer to a boolean for now?

I'm sensing you're still pretty reluctant, but I don't mind doing the work and/or signing on to longer term maintenance. Please let me know if it's a hard-no though, and I'll save myself the effort. I respect alacritty for its minimalism, and appreciate stuff like this is the thin end of a wedge.

p00ya avatar Jan 25 '25 08:01 p00ya

Since then, in reading various other issues on the alacritty tracker, it does seem like @chrisduerr and @kchibisov aren't super keen for font customization features, typically suggesting they'd be better off implemented at the OS or fontconfig level. A related change is removing the use_thin_strokes alacritty configuration in favour of respecting the AppleFontSmoothing value from NSUserDefaults.

I personally don't mind all of that, I just don't have time to work on that myself, it's not like anyone has ever implemented api for antialias and other knobs for crossfont...

kchibisov avatar Jan 25 '25 09:01 kchibisov