libsdl-org/SDL_ttf

API: Support non-rounded outlines in TTF_SetFontOutline

Closed this issue · 9 comments

TTF_SetFontOutline always produces rounded outlines, which looks great for most fonts, but not for pixel art fonts. Here is an example:
image

It would be great to support non rounded outlines too, configurable with a flag, to get blocky outlines instead in those cases. This is how it should ideally look when the non-ronded flag is ON:
image

As a workaround, the same effect can be achieved by rendering the text 9 times, but SDL_ttf has an opportunity to do it more efficiently by controlling FT_Stroker_LineJoin and FT_Stroker_LineCap when calling FT_Stroker_Set.

An alternative could be to expose functions that allow controlling the line join and line cap of the outline, allowing all the variants supported by the freetype library.

I think passing through the stroker parameters is reasonable. What are the set of parameters you would need here?

That's a good question. I'll try a few combinations with the freetype library directly to understand which combination works best for pixel fonts, and circle back with the results. My suspicion is that FT_STROKER_LINEJOIN_MITER should be enough, but I need to test it.

I tried all 12 combinations of linecap and linejoin and none of them render the pixel outline properly. I end up with either round corners or angle corners. I'll have to fall back to rendering the text 9 times instead. I'll close this bug since there isn't much SDL_ttf can do here. Sorry for the noise 😞

I just realized I forgot to change the miter parameter when calling FT_Stroker_Set. If I set it correctly, to sqrt(2) (so that all the way up to right angles, you get sharp corners), then it works for all combinations that use either FT_STROKER_LINEJOIN_MITER_VARIABLE or FT_STROKER_LINEJOIN_MITER_FIXED . The linecap doesn't matter, all of them work correctly for pixel fonts.

So this would be the correct code to get pixel fonts rendering sharply:

#define RIGHT_ANGLE_MITER_LIMIT 92682  // sqrt(2) in 16.16 fixed-point format
[...]
FT_Stroker_Set(
    font->stroker, outline * 64,
    FT_STROKER_LINECAP_ROUND, // This one doesn't matter.
    FT_STROKER_LINEJOIN_MITER_FIXED,
    RIGHT_ANGLE_MITER_LIMIT);
[...]

Interesting, I'll see if I can add something for SDL_ttf 3.0. Thanks!

Thanks!

One last thing: I tested these options with non-pixel fonts, and while I know it's mostly a matter of preference, I also prefer the straight corners on HD fonts:

FT_STROKER_LINEJOIN_MITER_FIXED:
image

FT_STROKER_LINEJOIN_ROUND:
image

The rounded version feels a bit less formal to me, which could work well in some contexts, but not for what I need.

Go ahead and try setting the font properties like this:

SDL_PropertiesID props = SDL_GetFontProperties(font);
SDL_SetNumberProperty(props, TTF_PROP_FONT_OUTLINE_LINE_JOIN_NUMBER, FT_STROKER_LINEJOIN_MITER_FIXED);
SDL_SetNumberProperty(props, TTF_PROP_FONT_OUTLINE_MITER_LIMIT_NUMBER, RIGHT_ANGLE_MITER_LIMIT);
...
TTF_SetFontOutline(font, 1);

I just tested it. Works great! Thank you!
image

Any chance this could be backported to SDL2? I still can't use SDL3 in all the platforms I target, because the Switch port is still a work in progress. If SDL2_ttf's API is frozen, I'll apply your patch to a local copy of SDL2 when compiling for the Switch.

Great!

SDL_ttf in main is pretty closely tied to SDL3. What you probably want to do is add an SDL2 TTF function that sets the outline with the additional parameters.

You should also probably ping the folks doing the SDL Switch port so they remember there are people excited and waiting for it. ;)