JUCE 8 invests heavily into low level text rendering and Unicode support, while maintaining a focus on backwards compatibility.
What’s new in JUCE 8
- Emoji ?
- Font metrics are now standardized across platforms. There’s a new
FontOptionsbuilding class that uses theseportablemetrics. Existing calls toFontuse the oldlegacymetrics for backwards compatibility. - Fonts now have fallbacks (and a way to manually specify them).
- JUCE now depends on the industry standard text shaping HarfBuzz library.
- You can more easily access custom loaded fonts via
withNameandwithStyle. - Important groundwork has been laid for future bidirectional text support.
- There are new
juce::Stringoverloads for thechar8_ttype returned byu8literals. This is C++20 only and allows for nicer unicode literals.
Breaking changes and deprecations
A lot of effort and testing was put into glyph positioning. Positioning with the new (portable) metrics is as close as possible to positioning with legacy metrics.
Please look through BREAKING_CHANGES.md for details. Be aware of the following:
- Calls to
Fontare deprecated in favor ofFontOptions. The deprecatedFontconstructors uselegacymetrics to maintain backwards compatibility. - The
CustomTypefaceclass has been removed. - Text on Android platforms might appear slightly smaller than on JUCE 7 (including text rendered with the old legacy metrics).
Definitions
Unicodeis a standard designed to consistently encode, represent, and handle text expressed in most of the world’s writing systems.- A
typefaceis a font family, a set of fonts of various styles and weights like “Arial.” - A
fontis one specific weight or style of a typeface, for example “Arial Bold.” - A
glyphis a broad term that just means the shape or visual representation of a character in a font. UTF-8is the most common unicode character encoding (for example on the web). It’s backwards compatible with ASCII, and can handle all unicode characters.
You can find more font and typography definitions here.
Specifying font heights
Prior to JUCE 8, there were cross platform differences in exact rendering sizes when using Font::withHeight or Font::withPointHeight.
JUCE 8 standardizes font metrics across platforms. It does so without breaking backwards compatibility, by introducing a new class: FontOptions.
When you want to use the new portable metrics, use FontOptions.
Whereas in JUCE 7, you would write:
g.setFont (Font().withHeight (100));In JUCE 8 you would now write:
g.setFont (FontOptions().withHeight (100));Under the hood, the enum class TypefaceMetricsKind is set to portable when using FontOptions constructors. The portable metrics are based on modern sTypoAscender metrics.
The deprecated Font constructors use legacy metrics to maintain backwards compatibility.
Using unicode literals in source code
This is a complex topic. To go deeper, please read the technical deep dive.
Even in 2024, it can be challenging to ensure all parts of your C++ toolchains are correctly set to utf-8. You have two options when working with JUCE 8:
- As with JUCE 7, you can continue to use escape characters (prefixed with
\xor\u) in your source code to represent extended characters, such as:juce::String::fromUTF8 ("Hello world! \xf0\x9f\x98\x8a"); The Projucer has a handy string literal generator utility that will convert any unicode string to a valid C++ string literal. This is the surefire way to ensure the correct bytes are produced for every compiler. - If you want to use utf-8 literals (such as emoji) directly in your source code it’s imperative that you configure your toolchains correctly. On Windows Visual Studio,
/utf-8may need to be set. This sets the source charset, allowing you to use utf-8 literals (such as emoji) in source files. It also sets the execution charset, ensuring that utf-8 is properly handled by your executable at runtime.
In Visual Studio, go to Configuration Properties > C/C++ > Command Line > Additional Options and add /utf-8 as described here.
Even if your IDE shows an emoji, the data may be interpreted differently by the toolchain.
Working with unicode literals
As noted above, only work directly with unicode literals in your source if you can guarantee all of your compilers across platforms are set to utf-8.
In C++17 and below, you will need to use the juce::String::fromUTF8 helper:
g.drawText (juce::String::fromUTF8 (u8"JUCE is great!?", ...On C++20 and above, the u8 literal now returns a char8_t type, which JUCE will implicitly convert to a juce::String, making for succinct syntax:
g.drawText (u8"JUCE is great!?", ...All classes that use JUCE’s GlyphArrangement use the new unicode rendering and shaping pipeline. That includes components like juce::Label and functions such as g.drawText().
Fallback fonts
When rendering the string “JUCE is great! ?”, the font being used to render the text will most likely not include a glyph for the thumbs up emoji. It’s normal that single font might not be able to render every unicode code point in a string. Fonts that contain emojis are very large, and we typically expect our system to render the emojis.
JUCE 8 introduces fallback fonts to help accomplish this expected behavior. They are enabled by default. All the magic is performed under the hood and they should just work out of the box.
They can be disabled via Font::setFallbackEnabled (false) on a per-font basis. You can also override the fallback fonts checked on a per-font basis. This allows you to do things like bundle your own emoji font to be used before falling back to the system font.
You can figure out exactly which font is being used for a glyph with Font::findSuitableFontForText.
Colored glyphs (Emoji)
Emoji should appear similar to everywhere else they appear on your OS. ?
Currently JUCE 8 supports the following Emoji fonts:
- AppleColorEmoji on macOS and iOS (png based)
- Segoe on Windows (COLR v0 only).
- Noto Color Emoji on Android and Linux (png based)
Looking up fonts by name
Font lookup has been improved in JUCE 8.
Once a font has been created with createSystemTypefaceFor, for example:
juce::Typeface::createSystemTypefaceFor (BinaryData::MyExpensiveFont_ttf, BinaryData::MyExpensiveFont_ttfSize)));You can look it up by name and style, like so:
FontOptions{}.withName ("My Expensive Font").withStyle ("Bold")Note that withStyle can take any arbitrary string, allowing you access to the full range of the Typeface’s styles, such as Medium, Light Italic and so on.
If you previously used a static getter for specific fonts like so:
static const juce::Font& getMediumFont()
{
return juce::Font (juce::Typeface::createSystemTypefaceFor (BinaryData::MyCustomFontMedium_ttf,
BinaryData::MyCustomFontMedium_ttfSize);
} You can now replace these calls with calls to FontOptions{}.withName().
