Custom fonts not being rendered with Direct2D/DirectWrite on Windows

Hi all,

If I understand correctly, the following method lets you use a custom font file that is embedded as a JUCE binary resource:

Typeface::createSystemTypefaceFor (const void* data, size_t dataSize);

For example:

myCustomTypeface = Typeface::createSystemTypefaceFor (BinaryData::CustomFont_ttf, BinaryData::CustomFont_ttfSize);
if (myCustomTypeface != nullptr)
	myLookAndFeel.setDefaultSansSerifTypeface (myCustomTypeface);

It works, but unless I’m missing something, it always uses software rendering instead of Direct2D/DirectWrite! Some digging reveals the cause.

Firstly, JUCE uses the Win32 AddFontMemResourceEx() API to load/create the font behind the scenes.

Secondly, when rendering, TextLayout::createLayout() checks to see if it can use a native (DirectWrite) layout or if it must fall back to using a software layout:

juce::canAllTypefacesBeUsedInLayout(const juce::AttributedString & text)
juce::TextLayout::createNativeLayout(const juce::AttributedString & text)
juce::TextLayout::createLayout(const juce::AttributedString & text, float maxWidth, float maxHeight)
juce::TextLayout::createLayout(const juce::AttributedString & text, float maxWidth)
juce::AttributedString::draw(juce::Graphics & g, const juce::Rectangle<float> & area)

It does this by checking that the AttributedString font/s are of type WindowsDirectWriteTypeface, but this fails (canAllTypefacesBeUsedInLayout() returns false) because the custom font is of type WindowsTypeface. Consequently, it calls createStandardLayout() instead.

Further digging shows that IDWriteFontCollection can’t find the custom font in the WindowsDirectWriteTypeface constructor. After much Googling, it seems this is because the font was added using the Win32 AddFontMemResourceEx() API. There’s barely any info about it online - so much so that I can’t even find the stuff I originally Googled, but it’s definitely out there (somewhere), and stepping through the code shows it to be the case:

juce::WindowsDirectWriteTypeface::WindowsDirectWriteTypeface(const juce::Font & font, IDWriteFontCollection * fontCollection)
juce::Typeface::createSystemTypefaceFor(const juce::Font & font)
juce::LookAndFeel::getTypefaceForFont(const juce::Font & font)
juce::getTypefaceForFontFromLookAndFeel(const juce::Font & font)
juce::TypefaceCache::findTypefaceFor(const juce::Font & font)
juce::Font::getTypeface()

Note #1: I’m using a custom font that is not installed in C:\Windows\Fonts. That may be crucial. I suspect if the custom font also happens to be installed (ie. it shows up in C:\Windows\Fonts), then DirectWrite will find it, but that kind of defeats the object of using a custom, private font.

Note #2: It works if I add the font using the Win32 AddFontResourceEx() API to load the font file from disk. Again, this is consistent with what I’ve read online, if only I’d bookmarked the damn pages!

I’d be grateful if someone would look into it to confirm that it’s a JUCE issue.

Many thanks,
Ben Staton

Edit: This is best I can find so far, but it’s a bit vague. Will keep looking.
c++ - How to load font from resource using dwrite API? - Stack Overflow

1 Like

The JUCE DirectWrite implementation does not currently support memory loaded fonts.

At the risk of tooting my own horn, this is one of the issues I had to deal with when cleaning up the Direct2D renderer.

Matt

3 Likes

That would be great if the JUCE guys could port back your code for this :slight_smile:

2 Likes

Agreed! Would love to see the JUCE Direct2D implementation finished. Glad someone found the time to look into it, especially if it can be merged into the JUCE repo at some point.

In the meantime, D2D is capable of loading fonts from memory, and since JUCE currently fails silently and lulls devs into thinking they’re benefiting from hardware accelerated text rendering when they’re not, I’d still like to see the JUCE guys do something about this particular issue soon.

2 Likes

Is this true only for typefaces created with

Typeface::createSystemTypefaceFor (const void* data, size_t dataSize);

or it also applies to typefaces created with:

CustomTypeface (InputStream& serialisedTypefaceStream);

?

I haven’t tried CustomTypeface because, if I understand correctly, it involves significantly more work (eg. converting TrueType font files into a custom binary format that JUCE can use). I was hoping to avoid that extra step if possible, especially given that our app is likely to have lots of custom fonts that may change from time to time. Much simpler to deal with .ttf files directly in this case.

My app is currenrtly loading .ttf files from disk on startup, which only involved a few relatively minor JUCE tweaks (an extra createSystemTypefaceFor() overload that takes a file name, and a few lines of code for the Win & Mac implementations). Now I don’t have to worry about it.

That said, I’d prefer to embed fonts as binary resources (BinaryData). Am I overestimating the amount of work involved to get CustomTypeface working?

I haven’t worked with CustomTypeface, but there doesn’t seem to be any DirectWrite-specific code in the CustomTypeface class.

Typeface::createSystemTypefaceFor (const Font& font) can create a WindowsDirectWriteTypeface object if DirectWrite is enabled.

Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) will only create a WindowsTypeface object. I think this alternate form of createSystemTypefaceFor would also need to create a WindowsDirectWriteTypeface to use memory-loaded fonts with DirectWrite.

I had to put together a custom font collection loader to use custom fonts with the Direct2D renderer. I’ll try the custom font collection loader with JUCE_USE_DIRECTWRITE enabled, but JUCE_DIRECT2D disabled.

Matt

I don’t see a big difference with DirectWrite enabled for a memory-loaded custom font. The text looks noticeably different if both DirectWrite & Direct2D are enabled.

Matt