Slow TextLayout on OSX with memory-based fonts


#1

Hi,

I noticed TextLayouts using memory-based fonts were rendering very slowly on OSX compared to Windows and ultimately tracked it down to a line which stops CoreText being used to render the Layout in TextLayout::createNativeLayout() (in juce_mac_Fonts.mm). This behaviour was added a few years ago with an explanatory comment stating there is a bug in CoreText which prevents the layout working with memory-based fonts. However, if I change the code such that CoreText is used to perform the layout I see a dramatic increase in speed and no apparent bugs. I have tested with a few versions of OSX (10.10, 10.11, 10.12, 10.13) and it appears 10.11 and later show no issues, those before that show garbled text.

The code below is what I am thinking of using as a “fix”, does this look appropriate?

bool TextLayout::createNativeLayout (const AttributedString& text)
{
    #if JUCE_CORETEXT_AVAILABLE
     #if JUCE_MAC
      if (SystemStats::getOperatingSystemType() >= SystemStats::OperatingSystemType::MacOSX_10_11)
      {
          CoreTextTypeLayout::createLayout (*this, text);
          return true;
      }
     #endif
     // Seems to be an unfathomable bug in CoreText which prevents the layout working with
     // typefaces that were loaded from memory, so have to fallback if we hit any of those..
     if (canAllTypefacesBeUsedInLayout (text))
     {
         CoreTextTypeLayout::createLayout (*this, text);
         return true;
     }
    #endif
    ignoreUnused (text);
    return false;
}

Thanks,

George


#2

Just tried this on 10.12 with some fairly text heavy sections of my application and the time spent rendering reduced significantly
It’d be cool to get the opinion of the JUCE guys to see whether this could be added

Thanks for the tip


#3

Yep, thanks for the heads-up, George, I’ll take a look asap!


#4

I saw the fix in the commit 2e0f6b5, but does that mean that memory-based fonts will now render incorrectly below 10.11? george-spitfire stated that OSX versions 10.10 and earlier show garbled text for memory-based fonts. Doesn’t that mean all that removed code would still be needed to support 10.7-10.10.


#5

No, I didn’t remove any code relating to this. I did take the opportunity to rip out a pile of old legacy stuff for 10.5. But the fallback for pre-10.11 memory fonts is still there and older systems should still use it.


#6

Hmm unfortunately the fix destroys my tooltips which are using TextLayout and fonts stored in memory. This is using the OSX 10.11 SDK with 10.11 deployment target on a 10.11 machine.
Before the commit:
before

After the commit:
after

I have my font in BinaryData and use LookAndFeel::getTypefaceForFont(…) to override the default fonts. Obviously it is using characters from the font, but the wrong ones.

Everything else in my GUIs seems unaffected, but as far as I know the tooltip window is the only spot a textlayout is used.

Edit:: weirdly enough it looks like all the char codes are just offset by 2 for rendering.


#7

Setting canAllTypefacesBeUsedInLayout (…) to return false fixes my issue, but I can’t do that without patching JUCE.
My font does work correctly on OSX outside Juce and the same code/font does work on windows.
My conclusion is that at least with my configuration the old workaround is still needed even on pure 10.11 and I would very much like to have a way to override the new behaviour with the old one as I have no issues with speed.

Update: I tried running the same thing on 10.13 and there it is displaying correctly. I have no access to 10.12. Anything below 10.11 will use the old workaround and probably work in my case.


Bug with Alert Window text and embedded fonts in 5.3.1
#8

Anyone else seeing this issue? Maybe it’s just my font that’s somehow weird.

I really wonder what that bug looked like that caused the workaround to be added… was it similar to what I’m seeing?


#9

I just updated to the latest JUCE and have garbled tooltips as well… :frowning:


#10

This is already fixed on develop. See here:

Hot fix will appear on master soon.


#11

Thanks, good to hear! BTW, the bug also affected MacOS 10.13.2 which I´m running. I´ll check out the latest develop.


#12

I have been doing some tests in an attempt to track down where the behavioural discrepancies are arising from by building with various versions of the SDK, running on various versions of OSX and isolating different parts the changes I made. It seems to me that the reason we are observing different behaviour is because I also added some code in my LookAndFeel class which registers the font with the CoreText font manager:

#if JUCE_MAC
bool myLookAndFeel::registerCFTypeface (const char *data, size_t length)
{
    CFDataRef fontData = CFDataCreate (kCFAllocatorDefault, (uint8_t *) data, (CFIndex) length);
    
    if (fontData == nullptr)
        return false;
    
    bool result = false;
    CGDataProviderRef provider = CGDataProviderCreateWithCFData ((CFDataRef) fontData);
    if (CGFontRef font = CGFontCreateWithDataProvider (provider))
    {
        CFErrorRef error;
        result = CTFontManagerRegisterGraphicsFont (font, &error);
        CFRelease (font);
    }
    
    CFRelease (provider);
    CFRelease (fontData);
    
    return result;
}
#endif

If I don’t include this I also see garbled text on OSX 10.11 and 10.12 with both versions of the SDK. With this included the results are as mentioned in my original post (even with the 10.11 version of the SDK) – on OSX 10.11 and above, big layouts will render much faster and will not show garbled text, versions lower than 10.10 are excluded and will go via the old route (slower but still no garbled text).


#13

Thanks George! So we could achieve the same thing just by adding a call to CTFontManagerRegisterGraphicsFont in the OSXTypeface constructor at line 505:

    if (fontRef != nullptr)
    {
        CTFontManagerRegisterGraphicsFont (fontRef, nullptr);

        ctFontRef = CTFontCreateWithGraphicsFont (fontRef, referenceFontSize, nullptr, nullptr);

?


#14

FYI I think this function was the missing piece of the puzzle - rather than checking the OS version number, we can just check the return value of CTFontManagerRegisterGraphicsFont. Will push something to develop shortly and would appreciate feedback on whether it finally fixes this one!


#15

I am pretty sure using that function fixes fallback for unknown characters too.


#16

That would be most excellent


#17

Great, that’ll be a double-whammy!


#18

Jules, I tried the fix you just committed on 10.11.6 and for me it fixes the issue with the font I’m using and textlayouts inside tooltips.


#19

We’re still seeing garbled tooltips on the latest develop tip (on at least macOS 10.12) with memory-based fonts, though it seems to occur very infrequently — maybe 10% of the time the plugin is instantiated.

38%20PM


#20

A workaround that worked well for me was using the GlyphArrangement class to create a Path object that draws the text with a custom font. No issues with just drawing the path as opposed to the text.

void paint (Graphics& g) override {
    GlyphArrangement textGlyph;
    Path textPath;

    // The text will be centered within the component
    textGlyph.addLineOfText (someFontToUse, someStringToDisplay,
                             proportionOfWidth(0.5f) - (someStringWidth / 2.0f),
                             proportionOfHeight(0.5f) + (someStringHeight / 2.0f));
    textGlyph.createPath (textPath);
    g.setColour (whateverColourToUse);
    g.fillPath (textPath);
}

This saved about ~20% CPU during animations as well as got rid of any weirdness with rendering memory-based fonts, especially when I took actually creating the Glyph out of paint(). This was only on necessary on Mac for me.