Emoji in Qt 6.9
January 28, 2025 by Eskil Abrahamsen Blomfeldt | Comments
Emoji are quirky and fun, but it's also one of the world's most popular writing systems. In 2022 it was estimated that 92% of the world's online population used emoji for expressing themselves.
Supporting color fonts is a pre-requisite for supporting emoji, and Qt has had such support on macOS and iOS since Qt 5.2. For Windows and Linux/Android (Freetype), support came a bit later, in Qt 5.7. But as the domain has evolved, Qt has not quite kept up with everything. In Qt 6.9 we fill in the gaps and modernize our emoji/color font support across all platforms.
The word emoji itself has a history of slightly different meanings, and the idea of using pictographic fonts or emoticons to express emotions certainly predates the current understanding of the word. In Qt, we use the Unicode definition: A colorful, cartoony pictograph inlined in text. In this interpretation, the symbols that we typically know as emoji can also be presented as "text" (basically meaning monochrome), but an "emoji presentation" requires the use of a color font.
In this blog, I will first go through some improvements made to emoji handling itself in Qt, and then look at the history of color font formats, and how they will be supported in upcoming releases.
Emoji segmenter
Like for other writing systems, the Unicode standard has ranges of characters designated as "emoji" (i.e. defaulting to emoji presentation.) But the emoji concept is a bit broader than that: For instance, certain characters, that are text by default, can be turned into emoji by appending the control character variation selector 16 (U+FE0F).
One example is the airplane (U+2708) character, which is a text dingbat, added to Unicode 1.1 back in 1993. It should be presented in monochrome by default: ✈. However, by appending the VS-16 to it, and creating the sequence U+2708, U+FE0F, we ask for the emoji presentation of the same character instead: ✈️.
In addition, certain text presentation characters take on a special meaning when combined with emoji using zero-width joiners (U+200D). For instance, joining the woman emoji (U+1F469 👩) with the staff of Aesculapius (U+2695 ⚕) in the sequence U+1F469, U+200D, U+2695 resolves to a woman health worker emoji: 👩⚕.
Now, the font and shaper take care of selecting the correct glyph in these cases, but in order for that to happen, we need to apply the emoji font to the full sequence of characters, which means we need to know which characters belong to the sequence. This is where the problem arose: In Qt, text will initially be split into "script items" according to their writing system. Based on the writing system and font query, a main font is selected for each item, and a fall back mechanism ("font merging") queries other similar fonts whenever specific characters are not supported by the main one.
This system was written before the introduction of emoji, and it did not account for the fact that specific sequences such as these - combining characters from different Unicode blocks - should default to a specific subset of fonts. A lot of the issues that we saw were addressed with ad hoc heuristics over the years, so for most use cases it would work as expected, but the core problem was always there. We ended up with complicated, ad hoc code in Qt and a set of broken corner cases - some depending on which fonts were available on the target system.
In Qt 6.9, Google's emoji-segmenter has been introduced to Qt. This is small parser for the emoji specification in Unicode, and it allows us to detect substrings that are intended to be displayed in color. We treat this as a writing system in its own right when resolving fonts for these substrings, prioritizing color fonts.
The segmenter is on by default, so no action is needed to reap the benefits of this. It can, however, be disabled by a flag in QTextOption, for a little bit less overhead when you know the text will not contain emoji. If needed, the whole feature can also be disabled either by setting the environment variable QT_DISABLE_EMOJI_SEGMENTER=1, or by passing -no-emojisegmenter when you configure Qt.
Color font formats
Detecting emoji correctly is an important piece of the puzzle, but a greater impact on users is ultimately the support for the specific font file they want to use to display those emoji.
When the idea of color fonts was first introduced, multiple different standards were created, each backed by different major players. The whole area has been quite fragmented ever since.
- Apple backed the SBIX format: Bitmap fonts containing embedded JPEG, TIFF or PNG images to represent the glyphs.
- Google backed the CBLC/CBDT format: Bitmap fonts capable of containing PNGs as well as uncompressed image data.
(In practice, fonts in both these formats will typically embed PNG data due to its efficient, lossless compression and wide support.)
- Adobe and Mozilla backed the OpenType-SVG format: Embedded SVG files representing the glyphs.
- And Microsoft backed the COLR/CPAL format: In the original version of this format (v0), glyph outlines are stored in the same vector format as in monochrome fonts, but a glyph can consist of multiple such outlines layered on top of each other, each with a different predefined fill color.
Since the former two of these are stored as bitmap images, they can contain any image the designer creates. However, the downside of this is that they will look blurry when scaled to sizes beyond the images stored in the font.
Google's "Noto Color Emoji" font was originally a CBDT font, containing emoji with nice gradients, but with obvious scaling artifacts at large sizes, as can be seen in this screenshot.
The latter two formats are vector formats and do not suffer from the same blurry look as the bitmap fonts. OpenType-SVG is the most expressive of all the formats, but due to the complexity of creating an SVG renderer, it never really saw very wide adoption. To my knowledge (and I might be very wrong about this), it is primarily supported by Adobe's tools and Firefox.
The original version (v0) of the COLR format is, on the contrary, a straight-forward format to support for existing rasterizers. But since it only supports single color fills, it limits itself to quite "flat"-looking emoji.
Here is a rendering of a glyph from the COLRv0 version of the Twemoji font. It consists of a set of monochrome shapes layered on top of each other.
So the initial state was that all the formats had their own pros and cons, and different backends prioritized support for different font formats.
But in 2019, Google proposed a new version of the COLR/CPAL format to compromise between the needs of scalability and renderer simplicity: COLRv1. This format introduced gradients and Porter-Duff compositing to the vector layers, and thus gives crisp illustrations at any size, with approximately the same feature set as SVG fonts, but on top of a very simple scenegraph architecture.
Seen here is the same glyph as before, but from the newer, COLRv1 version of Noto Color Emoji. It has the nice gradients, but now scales to any size without any loss of precision.
Support in Qt
Support in older versions of Qt has been as fragmented as the area itself: On Windows, we supported COLRv0, which was the only natively supported format when the feature was added to Qt in 2016.
On Freetype (default on Linux/Android, but usable on all platforms) we only supported the CBDT bitmap fonts, which, in turn, was the only supported format in Freetype in 2016.
The exception was on macOS/iOS, where system support for different formats has been keeping up with development, and without requiring the use of new APIs. So support was gradually extended in Qt for free.
This meant that you could get emoji presented in the target platform's default font, but including your own, custom emoji fonts in a cross-platform app would be a lot of work.
In Qt 6.9, our support on Windows and Freetype has also been extended, so that bitmap color fonts, COLRv0 and COLRv1 are now supported across all platforms. Note that OpenType-SVG fonts are still not supported by Qt and there is currently no plan to implement this, given the current cross-platform traction of the COLRv1 format.
In addition, new API has been introduced to enable overriding the system default emoji font, making it easy to bundle custom emoji fonts with your application.
Note: For Freetype in particular, COLR/CPAL support is also extended to the upcoming patch release of Qt 6.8.x, since the default emoji font on Android 15 depends on it. For apps that target Android 15 and wish to continue using older Qt versions, we recommend bundling the CBDT version of Noto Color Emoji with your app.
If you have questions or additional information, feel free to add a comment here - or send me a message on BlueSky. For bug reports, though, please use our bug reporting tool, since they may otherwise risk being overlooked.
Blog Topics:
Comments
Subscribe to our newsletter
Subscribe Newsletter
Try Qt 6.8 Now!
Download the latest release here: www.qt.io/download.
Qt 6.8 release focuses on technology trends like spatial computing & XR, complex data visualization in 2D & 3D, and ARM-based development for desktop.
We're Hiring
Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.