Hi all,
I’m using 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 paint()
method:
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:
And Mac:
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());
And voila!
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!
Many thanks,
Ben