Using unclipped painting for text elements

The docs for juce::Component::setPaintingIsUnclipped() seem to make it very clear that one should never set this to true unless you know 100% that your component won’t paint outside its own bounds.

However, I’m curious if this can be done in a safe way for components that want to intensionally paint outside their bounds. Text elements, for example, in most design tools will often have glyphs that extend beyond their bounds, as shown below with Figma:

Here, the line height of this font is 13.2px - with the “Hug” sizing option enabled for its height, the element is rounded to 13px tall, and thus the decent on the “p” goes outside the element’s bounds.

If I were to naively call setPaintingIsUnclipped(true) without anything else, anytime the component is moved, the pixels that were drawn outside its bounds wouldn’t be removed, which I assume is the “artifacts” that the docs refer to?

However, assuming I properly handle this by ensuring that all areas of the screen that the component’s graphics might draw to are repainted any time the component moves or is resized, are there any other issues that can come from not having a component’s graphics clipped?

Bump.

Does anyone have any insight on this? Anyone on the JUCE team who can expand on exactly what the docs are referring to by “artifacts” and what the other risks would be?

I can’t say for sure, but I think if you were to know exactly where you were drawing outside the bounds it might well be possible to keep on top of the drawing however, I think it would be very difficult and error prone, I certainly wouldn’t recommend it. I think you would have to also keep track of near by components, when they redraw (and the text doesn’t) you would have to force a repaint on the text too or I suspect the overhang from the text would/could be wiped by drawing that component.

1 Like

Ahh yeah I think the neighbouring components thing would make this more effort than it’s worth! I can think of a few hacky ways to do that, but at that point you’re almost writing your own renderer from scratch!


Slightly off topic, but related: do you have any plans to look at the reported font height differences between platforms? I remember it was something you looked into a while back.

Ultimately the issue we’re seeing is that since we base the height of a lot of elements on their text’s height, those elements are very different sizes on Windows compared to macOS. The odd one here and there isn’t too bad, but when you have a window with a lot of text, the resultant window height can be >100px different.

1 Like

We even see thickness differences between Windows and macOS and, even worse: between Windows 10 and 11!!! So if we render the same text in Windows 10, the font seems bigger overall.

We use an embedded “Roboto Medium” font, and they very clearly render at different sizes.

Maybe it would be a good idea to use the “FreeType” library, not only for Linux, but in general to get the Glyph outlines. Then at least it would be consistent on all platforms. Same sizes, same anti-aliasing, same kerning, same everything.

2 Likes

I expect this will be resolved in time however, if you’re in need of a shorter term fix I suggest if at all possible editing the font so the different descent values are all the same (you can use FontForge for this, see below).

To the best of my knowledge this is just due to the differences between the anti-aliasing in CoreText (macOS) and the software renderer which is used on Windows.

That’s a new one for me I’ll raise that with the team (see below for more details but I wonder if Windows 11 could be reporting the Typo values rather than the Win values when the “Really use Typo metrics” bit is set :thinking:).

It’s not my area of expertise but I would have thought even if we get the Glyph outlines this way the anti-aliasing will still not be the same?

I agree in general though that improving consistency in this area would be beneficial and I think that is likely to come in time but unfortunately it’s not a small chunk of work and has the potentially to significantly change the look of existing products so we would need to be cautious about the way we approach any change.

I’ve also thought the same as you in regards to how we use the “FreeType” library on Linux and potentially adopting it on other platforms. I’ll raise this again with the team but I strongly suspect it’s one of those “it’s more complicated than it first appears” scenarios with a bunch of caveats attached.

That being said one thing you can do to improve consistency is take a look at some of the font metadata. If I open Roboto-Medium with FontForge I can see the following…

There is also some discussion on this issue here.

Now the key here is that the different platforms read different values, so for example on Windows the Ascent is 1946 and the Descent 512, although the “Really use Typo metrics” is enabled so it should read the Typo details, 1536 and 512 - I have a feeling JUCE doesn’t adhere to this but I also recall checking and I don’t recall it being clear to me how it would do that with Windows API it uses (it was a while ago that I looked I may have that wrong). On Mac (which uses the HHead values I believe) it’s 1946 and 500, to the best of my knowledge there is no standard on Linux, it could pick any of them. I would have to check which values FreeFont uses I can’t remember, although I strongly expect it will be Typo. If memory serves me correctly not all these values are accessible by each of the platform interfaces so FreeFont is probably the best way to create consistency as IIRC it reads the metadata directly and uses just one of these values for all platforms.

What you can do to see if this is the source of some of your problems is edit these values in the font to be the same (although keep the Win Descent positive and the other two descent values negative). I realise in some cases this may not be a practical solution but it will at least tell me if this is one of the key issues you’re facing.

To edit these values

  • download FontForge
  • Open the font
  • In the menu goto “Element >> Font Info”
  • On the left hand panel select “OS/2”
  • Select the “Metrics” tab
  • Edit the values
  • “Ctrl + s” to save

For more information on this topic you can read the following…

4 Likes

Hi Anthony,

No, I’m not talking about the line thickness; I’m talking about the measurable width of the string.

I get different widths if I use “drawText” on macOS and Windows. If the string is, e.g., 30 characters long, the difference is around 70-100 pixels wider for a 14px tall font—similar results between Windows 10 and 11.

So I think your theory might be correct.

Since the Linux version already uses FreeType2, wouldn’t adding a #define for other platforms be easiest?

Maybe create JUCE_USE_FREETYPE_MACOS and JUCE_USE_FREETYPE_WINDOWS and then not compile the other code and use FreeType where needed.

This way, nothing would break for existing programs. They would continue to use the OS-related functions, but one could also opt-in with these new options and thus create visual consistency between platforms.

Regards,
Mike

1 Like

Is it an issue with the kerning? Are the individual glyphs the same size, but the spacing between them is different?

No. The font overall is bigger. If I fiddle with the font size, I can get them to match very closely (within a pixel tolerance).

I think that approach is sensible, and is roughly what I had in mind too. I won’t promise to look into this just yet, it may be something we pick up however, unicode support is in the pipelines and with it there will be breaking changes so this may be the best opportunity we have to fix this stuff once and for all.

I have no idea how full Unicode support factors into this. I assume you will have to break the string to be drawn into several substrings; Strings that contain only characters the current font supports, and then have a fallback font for the other characters.

I’m not sure how it would be relevant for which functions are used to retrieve these Glyphs. It seems those two problems are independent of each other.

I think it also needs to support multiple glyphs layered on top of each other as different characters can be combined - but as I said earlier it’s not an area of expertise for me, I believe this is a good post on the general subject of text rendering. I do however know that a good amount of unicode work has already been carried out and it looks like we’ll have to make breaking changes in and around these kind of areas anyway so potentially a good opportunity for introducing fixes like this too. That isn’t to say we definitely won’t do something else in the short term, just that it’s potentially a good opportunity to introduce breaking changes.

I hope that makes sense. If you get a chance to edit the font file and feedback the results that could be a useful data point though.

Thanks.
Anthony.

Hi guys, I would like to throw in my two cents on the topic. This is based off my current experience and hoepefully will be useful for improving font rendering in JUCE. Follows a tldr at the end.

My current standalone app project uses different look and feel implementations. I am using the Open Sans font by Google which has different ascent/descent values for both Win and HHead and as expected the font has different heights between Windows and Mac. Now, problems start when loading from memory fonts with edited ascend/descends:

Edited font at launch. L&f has been set just once within the main method:

However, as soon as I change the active look and feel, the same text renders as such:
Looks like all glyphs have shifted by two positions and kerning seems all over the place.

When opening the same edited font with Font Book it seems to render fine

All I did to the edited font was updating the Ascent and Descent values as pointed out by @anthony-nicholls a few posts ago. I personally used this hack in the past and never had garbled text as a result.

Now you might think that I messed something up while tweaking the font with FontForge so let’s try with a version of the same font (open sans) available on the fontsquirrel website. This version of the font has matching ascent and descent values out of the box so no font forge editing is needed.

How it looks when launching the standalone app for the first time:

After updating the active look and feel:


Glyphs are mostly fine this time, apart from the capital “i” letter becoming the symbol for the Laotian local currency. Also I am not 100% sure this post lookandfeel update text is still in open sans by looking at some of the glyphs (especially the letter “A”). Additionally the new text seems to be taller.
Calling Typeface::clearTypefaceCache() after changing the active look and feel didn’t help.

This issue occurs on both Windows and Mac. Using JUCE 7.0.5.

Look and feel is set first with:
juce::LookAndFeel::setDefaultLookAndFeel ();

And then updated with:

setLookAndFeel ();

getTypefaceForFont() is implemented like so for all look and feel implementations:

Typeface::Ptr CustomLookAndFeel::getTypefaceForFont (const Font& font)
{
    auto const bold = Typeface::createSystemTypefaceFor (BinaryData::OpenSansBold_ttf, BinaryData::OpenSansBold_ttfSize);
    auto const semiBold = Typeface::createSystemTypefaceFor (BinaryData::OpenSansSemiBold_ttf, BinaryData::OpenSansSemiBold_ttfSize);
    auto const regular = Typeface::createSystemTypefaceFor (BinaryData::OpenSansRegular_ttf, BinaryData::OpenSansRegular_ttfSize);

    if (font.isBold()) {
        return bold;
    }

    if (font.getTypefaceStyle() == "SemiBold") {
        return semiBold;
    }

    return regular;
}

Note getTypefaceForFont() gets called only at start even after resetting the typeface cache.

Removing const from the typefaces doesn’t help.

Tldr; In some cases editing the ascend and descend values of a font would cause garbled text, also, it seems there are some issues with font rendering somewhere within juce after updating the look and feel. This issue seems to be platform independent.

Wow I’ve not seen anything like that before. It looks to me like in both cases a different font is being rendered, for example look at both the capital and lowercase G. It might be useful if you can reproduce this issue in the simplest example possible. Something you could share. Maybe just double checking that using the latest version of JUCE doesn’t change the behaviour at all either (although I don’t expect it to).

To be clear are you saying when you don’t change the font ascent/descent and you call setLookAndFeel the font is rendered correctly (albeit with slightly different sizes on different platforms)? I would be very surprised if changing the ascent/descent is problem shown here, but I could be wrong.

Ah wait, can you try calling LookAndFeel_V4::setDefaultLookAndFeel (myCustomLookAndFeel); rather than setLookAndFeel (myCustomLookAndFeel).

1 Like

Ah wait, can you try calling LookAndFeel_V4::setDefaultLookAndFeel (myCustomLookAndFeel); rather than setLookAndFeel (myCustomLookAndFeel) .

Amazing, that did the trick! It would be great if you could explain what the difference between LookAndFeel_V4::setDefaultLookAndFeel (myCustomLookAndFeel); and setLookAndFeel (myCustomLookAndFeel) actually is from a font rendering stand point. But in any case thanks a lot for saving me from hours of troubleshooting :slight_smile:

I haven’t looked into it much just yet but I think setDefaultLookAndFeel triggers the call to getTypefaceForFont, whereas setLookAndFeel doesn’t. There may well be a reason for this but at the very least we could improve documentation to make that distinction clearer!

1 Like

Hello.

I’ve been rewriting our ‘text systems’ for the past year (ish); We’ve taken to calling the work ‘Unicode support’ but it actually involves a change in almost all of our text-related systems.

(When I refer to our current ‘anything’ I mean the code that is in the repo now, not my new code).

Freetype on each platform is doable, but Freetype is an inferior text ‘engine’ to CoreText and DirectWrite and it’s also the most complicated API to support as it doesn’t try to abstract as much away. (Our current implementation is very barebones). Performance is another thing to consider, Freetype is notoriously slow.

Our current layout engine is very barebones, it has no concept of proper layout rules, grapheme cluster, right-to-left language support, etc.

Our new backend supports all of this and more with Harfbuzz and a custom layout engine. The Text → Render pipeline will also allow the ability to add Platform and API-specific tweaks to make text look consistent across all platforms.

You might actually be seeing a GDI-rendered font on Windows 10. Sometimes it will fail to load a font from memory and fall back to GDI, it appears to be a long-standing DirectWrite bug. This might explain the differences in glyph characteristics (but also maybe not).

The typographic metrics used by font formats are a nightmare of multiple standards that have been misinterpreted and made standard over the years.
The OS/2 table (which is what ‘Really use Typo metrics’ is toggling) is the ‘newer’ Microsoft standard. FontForge appears to be notorious for misrepresenting the WinAscent and WinDescent values as NOT in the OS/2 tables even though that is exactly where they originate.

If you do any research into font rendering you will see the word ‘perceived’ used A LOT. UPEM (that is units per EM) can be arbitrary per font and historically was designed to scale the metrics into a square but that is no longer the case. If you look at something like ascender and descender values which should define the absolute vertical range of a glyph… It’s not even remotely true. This becomes a real issue because our current code uses them to calculate font height and scaling.

You also have to take into account that CFF, OpenType, TrueType, Apple, and Adobe have their own font formats, each with differences in metric handling. Our font loading, defaults, and fallback systems have all been replaced too.

This is a bit of a long-winded post but the issues you are seeing are Unicode-related. We can try to make text look consistent on each platform but which platform should we choose? If we do that we run the risk of making JUCE text look perceptually ‘odd’ vs other software.

Thanks @anthony-nicholls for keeping an eye on text-related things that pop up. There is so much to anything text-related that I seldom get to go off and research these issues as they come up. Hopefully our new backend will mitigate some of the issues we’re seeing.

5 Likes