Blog

JUCE 8 Feature Overview: Unicode

JUCE 8 invests heavily into lowe 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 FontOptions building class that uses the standard metrics. Existing calls to Font use the old metrics 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 by name and style.
  • Important groundwork has been laid for future bidirectional text support.
  • There are new juce::String overloads for the char8_t type returned by u8 literals. 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 Font are deprecated in favor of FontOptions. The deprecated Font constructors use legacy metrics to maintain backwards compatibility.
  • The CustomTypeface class has been removed.
  • Text on Android platforms might appear slightly smaller than on JUCE 7 (including text rendered with the old legacy metrics).

Definitions

  • Unicode is a standard designed to consistently encode, represent, and handle text expressed in most of the world's writing systems.
  • A typeface is a font family, a set of fonts of various styles and weights like "Arial."
  • A font is one specific weight or style of a typeface, for example "Arial Bold."
  • A glyph is a broad term that just means the shape or visual representation of a character in a font.
  • UTF-8 is 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.

Whereas in JUCE 7, you would write:

g.setFont (Font().withHeight (100));

In JUCE 8 you would now write:

g.setFont (FontOptions().withHeight (100));

It introduces the enum class TypefaceMetricsKind with portable being the new standard used by 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

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:

  1. As with JUCE 7, you can continue to use escape characters (prefixed with \x or \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.
  2. 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-8 may 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:

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().

More

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram