juce::AttributedString to render Unicode text (UTF-8) containing fancy characters such as emojis.
It works, but it stops short of rendering the full string. Here’s some test code taken from a component
juce::AttributedString attrString; attrString.setText( L"Abc🍕😎🏈Def" ); // I know - don't embed actual Unicode chars in source code*. attrString.setFont( juce::Font( 20.f ) ); attrString.setJustification( juce::Justification::centred ); attrString.setWordWrap( juce::AttributedString::WordWrap::none ); attrString.setColour( juce::Colours::white ); attrString.draw( g, getLocalBounds().toFloat() );
This is the result on Windows:
Note that it chopped the last 3 characters off on Windows, and the last 3 characters are the wrong font/color on Mac.
The first bug is in
juce::AttributedString::setText (const& String newText):
auto newLength = newText.length();
In this example,
newLength is set to 9 which seems correct, right? There are 9 characters in total.
Not quite. It should actually be 18! Why? Because it needs the size in bytes not characters. And since each emoji is represented by 4 bytes in UTF-8, the total for 6 normal characters (6 bytes) plus 3 emojis (3 * 4 = 12 bytes) is 18.
Strictly speaking, I’m not 100% sure if it’s bytes or something more ‘Unicodey’ like ‘code points’ or whatever, but they might be equivalent with UTF-8 anyway.
Regardless, changing it to this gets it working on Mac only:
const auto newLength = (int) newText.getNumBytesAsUTF8(); // Returns 18 in this example.
Unfortunately, an additional fix is required on Windows:
juce::DirectWriteLayout::setupLayout() passes the wrong string length to IDWriteFactory::CreateTextLayout() (see juce_DirectWriteLayout.cpp line 370 in JUCE 6.1.5). Instead of using
String::length() it should pass the length in UTF-16 characters (Unicode code points?), which is 12 in this example (6 normal characters plus 3 x 2 bytes for the emojis). For example:
const std::wstring wstr (text.getText().toWideCharPointer()); const auto textLen = (UINT32)wstr.length(); hr = directWriteFactory.CreateTextLayout (wstr.c_str(), textLen, dwTextFormat, maxWidth, maxHeight, textLayout.resetAndGetPointerAddress());
There may be more to it than this - I’m not a Unicode expert, nor am I intimately familiar with the JUCE text rendering code - but it’s working well here.
Would be nice to see it properly fixed in JUCE, followed by an update for
juce::TextEditor so we can let users actually edit Unicode text in 2022!