C
Text Rendering and Fonts
Overview
In Qt Quick Ultralite, text is rendered to the screen using the Text and StaticText items. These items have a font property for controlling the selected font configuration. Read the font type documentation to learn about available APIs and font engine-specific details.
Qt Quick Ultralite provides two font engine options: Monotype Spark and Static font engines.
Monotype font engine
- Better support for internationalized applications that use a wide range of characters.
- Better support for languages that require text shaping to ensure correct layout.
- Runtime support for text scaling, without compromising on performance and text quality.
Static font engine
- Better support for applications that do not use complex text and has lower footprint.
- Operates on static precomputed data, enabling very fast lookup operations.
- Processes font at build time, resulting in no runtime overhead.
Refer to the Feature comparison table for detailed comparison between the two font engines.
Languages and Writing Systems
As part of internationalization support, Qt Quick Ultralite provides input controls and text drawing methods that has built-in support for all writing systems supported by the chosen engine. The text subsystem is capable of rendering text that contains characters from a variety of different writing systems at the same time.
The supported languages, text shaping features, and font file features vary depending on the chosen font engine. Text shaping is an integral part of preparing text for display. Not all scripts supported by Unicode need text shaping, but for others (a.k.a. complex scripts) it is required to render legible text. If an application won't use any languages that rely on complex scripts, then MCU.Config.complexTextRendering can be used to disable linking the text shaping engine to the binary. Examples of complex scripts include the Arabic alphabet and Indic scripts.
Unicode compliant bidirectional text is supported as well, and exact behavior is defined in the Unicode Technical Annex #9. The exception is Text item, it does not support bidirectional text when static font engine is used by the application.
Feature comparison table
The following table highlights the key differences between the two text handling options, enabling you to choose the appropriate one.
Feature | Static font engine | Monotype Spark font engine | Notes |
---|---|---|---|
Font rasterization | Build-time by fontcompiler, using FreeType. | Run-time by Monotype Spark. StaticText at build-time by Monotype Spark. | |
Memory requirements | Uses a traditional approach on MCUs, which is rasterizing all used characters ahead of time. The downside is that it can become a lot of bytes with internationalized applications. The only memory requirement that comes from using the static font engine is to have sufficient space for the precomputed data of rasterized glyphs and font metrics. The static font engine's implementation is a thin API around this data. | Monotype Spark font engine is linked to the application. If MCU.Config.complexTextRendering is set to true , then WorldType Shaper Spark engine is linked to the application. For reference code sizes see <QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\ . The engine requires a font file to be bundled with an application. Additional RAM is required by font engine's run-time data, see MCU.Config.fontHeapSize. If Glyph Caching is enabled, additional RAM is required. With MCU.Config.maxParagraphSize set to 100, the required RAM for text shaping engine can be as low as 3.1 KB. | |
Caching system | Not needed, because all data is precomputed. | Glyph Caching improves performance dramatically if enabled, and sufficient memory has been allocated for the cache. Cache memory is used to store glyphs, advance widths, and Unicode-glyph id mappings (CMAP). The cache content can optionally be prepared ahead of time with the cache priming feature. | |
Dynamic text | The glyphs must be pre-registered using the font.unicodeCoverage property. This increases the precomputed data size. | No special handling required from an application developer if the font file contains the glyphs information. | Dynamic text in this document refers to a text that is not known at compile time. This, for example, can be text fetched from a network, read from a file, or user input. Strings defined in C++ code also count as dynamic strings in Qt for MCUs. |
Font formats | Any font file format that is supported by FreeType. | TrueType fonts, CCC compressed fonts and Fontmaps. | |
Font hinting | Uses FreeType's hint processing capabilities. | Spark utilizes two different hinting methods – spark hinting and auto hinting, which are optimized for the limited resources on micro-controller platforms. TrueType's hinting instructions are not supported to conserve runtime memory. | Font hinting is a step that is applied during the glyph rasterization process. Hints are instructions that can control how the font renderer assigns pixel values. Hints are commonly used to preserve certain typeface consistencies, such as glyph stem, weights, and heights. Hints can be encoded into a font or applied dynamically during font processing. |
Glyph compression | Currently not implemented. | Supports run-length encoding on glyph data, but this feature is not integrated yet. | |
Glyph render modes | Qt for MCUs currently utilizes bitmap and graymap8 modes only. | Spark's rasterizer supports the following formats: bitmap, graymap2, graymap4, graymap6, graymap8. Qt for MCUs currently supports only bitmap and graymap8. | The render mode can be configured using the font.quality property. The values of font.quality are mapped to bitmap for Font.QualityVeryLow , and to graymap8 for Font.QualityVeryHigh . These values are the same for both font engines. |
Text shaping | Supported only through StaticText item. The text shaping is performed at application build-time and for that is uses HarfBuzz library. The MCU.Config.complexTextRendering setting is ignored, as there is no shaping implementation at run-time for this font engine. | Supported by Text and StaticText. Uses WorldType Shaper Spark engine from Monotype. | For optimal performance, if a text block does not contain complex scripts, we skip passing such strings to the shaping engine. Some fonts implement purely cosmetic features for non-complex scripts through complex script shaping tables. Using such fonts in Qt Quick Ultralite applications can result in undesired text rendering. In Qt Quick, this behaviour is controlled by the font.preferShaping property. In Qt Quick Ultralite, this property is not available, but instead always assumed font.preferShaping = false .Note: The above does not apply to the static font engine, as the CPU-intensive text shaping task is not performed on MCU. |
Text Alignment
When the horizontal alignment of a text item is not explicitly set, the text element is automatically aligned to the natural reading direction of the text. By default left-to-right text like English is aligned to the left side of the text area, and right-to-left text like Arabic is aligned to the right side of the text area.
This implicit alignment can be overridden by setting the horizontalAlignment property for the text element.
// automatically aligned to the left Text { text: "Phone" width: 200 } // automatically aligned to the right Text { text: "خامل" width: 200 } // aligned to the left Text { text: "خامل" horizontalAlignment: Text.AlignLeft width: 200 }
Font properties
Bindings
Starting with version 1.7, it is possible to define bindings on font subproperties with the Spark font engine.
Text { text: "Hello world from Qt" font.italic: italicSwitch.checked font.pixelSize: pixelSizeSlider.value }
See font bindings example for more details on how binding evaluation map to a font class name in a fontmap file.
Both font engines permit bindings on the font property. For example:
Text { id: conf1 font.pixelSize: 30 } Text { id: conf2 font.pixelSize: 100 } Text { // Binding on font property. font: someCondition ? conf1.font : conf2.font text: "some text" }
This allows runtime font changes with the static engine, albeit without the fine-grained control on font subproperty bindings that is possible with the Spark engine.
Optimizations
The memory usage can be optimized by sharing a font configuration (Text.font) where texts use the same style (see also Qt.font()).
Text { // Sharing the font configuration with other Text element through a binding. font: myText.font text: "some text" }
Constant font configurations are automatically shared in the emitted C++ code.
Assigning
The font basic type in Qt Quick Ultralite is a constant value, so assigning to font subproperties is not supported.
// error: Assigning to properties of 'font' is not supported, use bindings onClicked: myText.font.italic = false
As suggested by the error message, one can use a binding to achieve the same results.
property bool isItalic: true Text { text: "hello world" font.italic: isItalic } MouseArea { anchors.fill: parent onClicked: isItalic = false }
Static font engine
The font file processing happens at application build-time and is done by the fontcompiler. The fontcompiler tool relies on the FreeType-based font engine from Qt. The output from the fontcompiler is the precomputed data of rasterized glyphs and font metrics. Nothing gets added or removed from this data at run-time - the set of available glyphs is decided at build-time. All operations on this data are O(1) or O(log n). This font engine supports constant font configurations only.
Fonts
The font engine uses the FontFiles.files QmlProject property to find the fonts used by the application, and extract the necessary font data.
Precomputed data
To support assigning text between items with different font settings, by default fontcompiler rasterizes all used characters in all used font configurations in the application. Exceptions to this are font.unicodeCoverage and StaticText, which register characters only for the used font configuration.
In addition, fontcompiler by default includes small set of commonly used glyphs, for example, digits.
To reduce the memory footprint this behavior can be disabled using MCU.Config.autoGenerateGlyphs. In such case only characters that where explicitly defined in font.unicodeCoverage will be rasterized.
The precomputed data is a shared source of data for anything that needs to render a text, this includes Text and StaticText.
See MCU.Config.glyphsCachePolicy, MCU.Config.glyphsStorageSection, and MCU.Config.glyphsRuntimeAllocationType for more information about configuring the run-time storage of this data.
Embedding glyphs
Depending on the MCU.Config.autoGenerateGlyphs setting, the fontcompiler can embed the glyphs for all characters that are used in any QML string literal and all font configurations into the resulting binary. The character selection can be extended by using the font.unicodeCoverage property. This is necessary in scenarios where the strings are dynamically created and not known at compile time.
If MCU.Config.autoGenerateGlyphs is set to false
only glyphs defined using font.unicodeCoverage will be embedded. By careful application design this technique can lead to significant footprint reduction.
Setting the font.unicodeCoverage property affects all QML items that use the same font configuration.
Rendering dynamic strings in QML
Strings from C++ models
C++ models can produce dynamic strings that are not known at compile time. Consider the following example:
// MyModel.h struct MyModelData { std::string stringField; }; inline bool operator==(const MyModelData &lhs, const MyModelData &rhs) { return lhs.stringField == rhs.stringField; } struct MyModel : public Qul::ListModel<MyModelData> { int count() const override { return 10; }; MyModelData data(int index) const override { std::string data; for (int i = 0; i <= index; ++i) { data += 'a' + i; } return {data}; } }; // MyView.qml Item { ListView { anchors.fill: parent model: MyModel { } delegate: Text { height: 20 text: model.stringField } } }
The fontcompiler cannot generate glyphs for such model data, as it cannot predict what glyphs are needed. This results in glyphs missing when rendering QML content.
To tell fontcompiler what character ranges can be produced by the model and which glyphs must be generated, use the font.unicodeCoverage property for font used by the delegate.
Item { ListView { anchors.fill: parent model: MyModel { } delegate: Text { height: 20 font: Qt.font({ unicodeCoverage: [Font.UnicodeBlock_BasicLatin] // << define character set }) text: model.stringField } } }
Strings from C++ functions
The C++ functions that return strings follows the same rules as the C++ models. Consider the following example:
// MyObject.h struct MyObject : public Qul::Object { std::string getDynamicString() const { std::string data; for (int i = 0; i < 26; ++i) { data += 'A' + i; } return data; } }; // MyView.qml Item { MyObject { id: myObject } Text { text: myObject.getDynamicString() } }
The earlier example code cannot render glyphs correctly unless the font.unicodeCoverage property is set for the Text item that uses a dynamic string:
Item { MyObject { id: myobject } Text { text: myobject.getDynamicString() font: Qt.font({ unicodeCoverage: [Font.UnicodeBlock_BasicLatin] // << define character set }) } }
Monotype
The Monotype Spark product's page https://www.monotype.com/products/embedded-solutions/spark.
The documentation is installed in <QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\
. It includes also documents with FAQs.
iType Spark font engine.
Monotype’s Spark font engine is a scalable font rendering subsystem based on the industry standard TrueType font standard. It is designed for resource constrained environments such as automotive displays, medical devices, white goods, wearable devices, television set-top boxes, portable media players, and control panels. It brings the benefits of scalable type and high-quality multilingual font display to the embedded environment.
High performance architecture optimized for both space efficiency and speed. Spark meets stringent size requirements for many applications and devices, including those that support East Asian languages, requiring thousands of characters. Combined with Monotype's tuned fonts, the Monotype Spark solution lets you to take advantage of scalable font in situations where previously it may not have been possible, due to memory restrictions, complexity, or platform costs.
Monotype offers various techniques that can be applied to a font file to reduce memory requirements and optimize processing speed. Some of the techinques are listed in the following sections. The Monotype Spark library is highly configurable. For optimal performance and memory usage, read the Spark documentation, which includes a thorough performance guide.
WorldType Shaper Spark engine.
The WorldType Shaper Spark is a library for shaping text from complex language scripts. It is highly optimized for shaping to be done on small devices like wearable’s etc.
WorldType Shaper Spark does not support OpenType tables i.e. GSUB and GPOS, it will work only with the fonts that have Apple’s Advanced Typography format ‘morx’/’kerx’ tables or are designed to support Unicode to Unicode shaping for Arabic, Hebrew and Thai, i.e. fonts having glyphs for all presentation forms. The use of finite state machines allows ‘morx’ tables to be relatively small and to be processed relatively quickly.
It also handles all the processing needed for bidirectional scripts, including text reordering. WorldType Shaper Spark is fully compliant with the Unicode 12.1.0 specification.
This library is used by text shaping engine when spark font engine is selected as font engine by the application.
How to enable
By default, Qt Quick Ultralite applications use the static font engine. To select the Monotype Spark font engine, set MCU.Config.fontEngine target property to "Spark". See supported font engines for the list of supported font engines. Setting this QmlProject property causes your application to be linked with the Monotype library.
When switching to Monotype Spark font engine, it is important to make sure that FontFiles.files contain a single entry that points to a font file in a supported format.
MCU.Config { fontEngine: "Spark" } FontFiles { files: ["fonts/SampleFontmap.fmp"] }
See MCU.Config.fontFilesCachePolicy, MCU.Config.fontFilesStorageSection, and MCU.Config.fontFilesRuntimeAllocationType for more information about configuring the run-time storage of the application's font files.
Fonts and font formats
The optimized Monotype's font files for are installed to <QT_INSTALL_PATH>\QtMCUs\<VERSION>\src\3rdparty\monotype\fonts\
.
TrueType fonts
Any TrueType font is supported, but note that TrueType hinting instructions are ignored. See spark hinting and auto hinting sections.
Fontmaps
The Fontmap is a concept that allows combining multiple font files into a single Fontmap file. Only TTF files can be used in Fontmap components. In addition, Fontmap offers font selection based on the Unicode range, Unicode Script, and font class. In a Fontmap, Unicode Script entry is called language.
Fontmap editor is an application that can be used to create and/or modify Fontmap files. The editor is extracted to <QT_INSTALL_PATH>\QtMCUs\<VERSION>\bin\
. The editor will be provided as an installer file that requires manual installation by the user. The documentation for the tool is accessible through Fontmap editor's menu Help -> HelpTopics
. For more details on the font selection algorithm and performance tips see <QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\
.
Note: Currently, the compoment font lookup in Fontmap is independent of the language.
CCC compressed fonts
CCC font is a Monotype font format that uses a lossless compression algorithm called CCC, to compress large size font tables. With little overhead to decompress, it makes a good compromise between compression ratio and resources. All TTF fonts can be converted to CCC.
Note: CCC compressed fonts currently are not yet supported as Fontmap components.
Engine configuration
The font engine can be configured through the following QmlProject properties:
Controls the font cache buffer preallocation. | |
Controls font cache priming. | |
Set maximum cache size used by font engine. | |
Controls preallocation of the heap buffer used by the font engine. | |
Defines a maximum heap size for the font engine. | |
Controls whether to use vector outlines for text rendering | |
Set the maximum paragraph size in characters. |
Optimal values for heap size and cache size require careful testing and tuning.
Spark can also work with heap and cache memory that has been allocated externally.
Font class mapping
Note: This section is applicable only if using a Fontmap font file.
One of the components that affect font selection from a Fontmap file is the font class name. See Fontmap's documentation, to know how font selection works. The supported font class naming format is "<font.family> <font.weight> <font.italic>"
, where:
- font.family - If not set, uses value from MCU.Config.defaultFontFamily target property. If set, uses the provided string as is.
- font.weight - Maps enums to strings, for example,
Font.ExtraLight
becomes "Extralight". An exception to this is,Font.Normal
, which maps to an empty string. - font.bold - Is a synonym for
font.weight: Font.Bold
- font.italic - If set, maps to "Italic", otherwise maps to an empty string.
Mapping examples
See font bindings example.
Porting an existing application
Lets assume that we have a qml application, which uses sans-serif font family as in the following example:
Text { font.family: "sans-serif" }
To design your Fontmap for the above example, you need to map a font file (TTF) to the sans-serif font class name. This is a straightforward process with the FontmapEditor GUI tool. If you do not wish to modify your qml sources, but would like to use a different font file than SansSerif.ttf, nothing prevents you from mapping sans-serif to FrutigerOTS_S12-29g.ttf in the Fontmap file. Or you can change all occurrences of sans-serif in your source code with, for example, FrutigerOTS. And then in the FontmapEditor map FrutigerOTS to FrutigerOTS_S12-29g.ttf.
Font hinting
Spark hinting
Spark hinting instructions are designed for very low memory requirements and for enhanced performance. Note that Spark does not process conventional TrueType hints if they are present in the font. Besides new hinting instructions, Spark applies other unique hinting techniques to significantly reduce the font size. Spark hints may be optionally compressed to reduce memory usage further.
Auto hinting
Auto-hinting is built into the Spark font engine because TrueType font hinting is not supported to conserve runtime memory. In the event that the system font does not contain Spark hints, Spark deploys auto-hinting. Auto-hinting alone, does not yield the same high quality as the auto-hinting with Spark hints combination, especially at smaller text sizes.
Glyph Caching
On rendering a glyph for the first time, Spark creates the glyph and stores it in the cache. After that, if you try to render that glyph again, it is fetched directly from the cache without having to re-create it, improving the performance. Along with the glyph cache, Spark also supports CMAP and Advance cache. It enables faster retrieval of glyph ID and glyph advance compared to parsing of the “CMAP” and “HMTX” table of a TrueType font, respectively. When the cache is full, Spark removes the least used entries from the cache.
Among other cache configuration, Spark lets you configure the cache entries that are never removed and size threshold of glyphs that should not be added into the cache. Some of these capabilites are not yet available with Qt for MCUs.
See MCU.Config.fontCacheSize documentation for more information.
Cache priming
It is sometimes useful to prepopulate the cache in order to improve the performance further. This improves the application startup time significantly, especially if it contains a lot of text.
Use the font.unicodeCoverage property to select characters to include in the cache priming data during application build-time. When the font engine is accessed for the first time at an application run-time, the cache priming data is copied from MCU.Config.glyphsStorageSection into the Spark's cache memory.
See MCU.Config.fontCachePriming documentation for more information.
If cache priming detects common glyphs with those stored for StaticText purposes, it will optimize by fetching those glyphs from StaticText data when populating the Spark cache at the run-time. This optimization enables to save on the required flash memory space.
Text caching
Qt Quick Ultralite draws each glyph or character in a text separately. This default behavior comes with a performance overhead caused by the frequent calls to drawing engine. On platforms where this behavior leads to slower performance, switch to the text caching alternative. It enables caching each text element in a separate image on the CPU side, reducing the number of calls to the drawing engine.
Additionally, the text outlines are also cached if MCU.Config.fontVectorOutlinesDrawing is enabled. This avoids recomputing the corresponding PathData each time a text element is drawn.
To enable text cache for your application, implement an alternate constructor for the Qul::Application class. The constructor should accept an Qul::ApplicationConfiguration instance as shown in the following example:
Qul::initHardware(); Qul::initPlatform(); Qul::ApplicationConfiguration appConfig; appConfig.setTextCacheEnabled(true); appConfig.setTextCacheSize(128 * 1024); Qul::Application app(appConfig); static MainScreen item; app.setRootItem(&item); while (true) { uint64_t now = Qul::Platform::getPlatformInstance()->currentTimestamp(); // <handle timers> uint64_t nextUpdate = app.update(); if (nextUpdate > now) { // Device can go to sleep until next update is due // enterLowPowerMode(nextUpdate - now); } }
If text cache size is not set, Qt Quick Ultralite uses the default size of 24KB set in the platform's main CMakeLists.txt
. To change the size, add a Platform
target property for QUL_PLATFORM_DEFAULT_TEXT_CACHE_SIZE to the platform's CMakeLists.txt
file.
When the text cache is full, Qt Quick Ultralite falls back to the default behavior until the next frame.
The text cache is not used for StaticText items, as the fontcompiler tool can combine the glyphs of a StaticText item into a single image, with the --mergeStaticTextGlyphs
option. To use this option, the platform should enable MCU.Config.mergeStaticTextGlyphs under MCU.Config
node in BoardDefaults.qmlprojectconfig
:
MCU.Config { mergeStaticTextGlyphs: true }
mergeStaticTextGlyphs
is an experimental API, which comes with the following limitations:
- The text is always left-aligned, ignoring the properties set at runtime.
- Monotype Spark font engine is not supported.
The text cache size used by the application can be seen by looking at the performance logs.
Refer to Resource cache for tips about choosing appropriate cache size.
Available under certain Qt licenses.
Find out more.