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 for MCUs offering includes the static font engine and the Monotype Spark font engine.

The advantage of Spark is its low memory requirements, especially when an application depends on a wide range of characters (for example, CJK characters). This can be a natural requirement for internationalized applications. Moreover, an application developer need not do anything special to support dynamic text with Spark. Spark enables text scaling support at run-time, without compromising on performance and text quality.

On the other hand, the static font engine's advantage is its simplicity. It operates on a static precomputed data, with very fast lookup operations. It does not have any font processing overhead at run-time, because fonts are processed while building the application.

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 QUL_COMPLEX_TEXT_RENDERING 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.

FeatureStatic font engineMonotype Spark font engineNotes
Font rasterizationBuild-time by fontcompiler, using FreeType.Run-time by Monotype Spark. StaticText at build-time by Monotype Spark.
Memory requirementsUses 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 QUL_COMPLEX_TEXT_RENDERING is ON, 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 QUL_FONT_HEAP_SIZE. If Glyph Caching is enabled, additional RAM is required. With QUL_MAX_PARAGRAPH_SIZE=100, the required RAM for text shaping engine can be as low as 3.1 KB.
Caching systemNot 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 textThe 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 formatsAny font file format that is supported by FreeType.TrueType fonts, CCC compressed fonts and Fontmaps.
Font hintingUses 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 compressionCurrently not implemented.Supports run-length encoding on glyph data, but this feature is not integrated yet.
Glyph render modesQt 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 shapingSupported only through StaticText item. The text shaping is performed at application build-time and for that is uses HarfBuzz library. The QUL_COMPLEX_TEXT_RENDERING 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 QUL_FONT_FILES CMake target 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 QUL_AUTO_GENERATE_GLYPHS. 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 QUL_GLYPHS_RESOURCE_CACHE_POLICY, QUL_GLYPHS_RESOURCE_STORAGE_SECTION, and QUL_GLYPHS_RESOURCE_RUNTIME_ALLOCATION_TYPE for more information about configuring the run-time storage of this data.

Embedding glyphs

Depending on the QUL_AUTO_GENERATE_GLYPHS 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 QUL_AUTO_GENERATE_GLYPHS is set to OFF 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 QUL_FONT_ENGINE target property to "Spark". See supported font engines for the list of supported font engines. Setting this CMake variable causes your application to be linked with the Monotype library.

When switching to Monotype Spark font engine, it is important to make sure that QUL_FONT_FILES contains a single entry that points to font file in supported format.

set_target_properties(my_app
                    PROPERTIES
                        QUL_FONT_ENGINE "Spark"
                        QUL_FONT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/fonts/SampleFontmap.fmp")

See QUL_FONT_FILES_RESOURCE_CACHE_POLICY, QUL_FONT_FILES_RESOURCE_STORAGE_SECTION, and QUL_FONT_FILES_RESOURCE_RUNTIME_ALLOCATION_TYPE 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 CMake target properties:

QUL_FONT_CACHE_PREALLOC

Controls preallocation of the font cache buffer.

QUL_FONT_CACHE_PRIMING

Enable font cache priming.

QUL_FONT_CACHE_SIZE

Set maximum cache size used by font engine.

QUL_FONT_HEAP_PREALLOC

Controls preallocation of the heap buffer used by font engine.

QUL_FONT_HEAP_SIZE

Set maximum heap size for the font engine.

QUL_FONT_VECTOR_OUTLINES_DRAWING

Use vector outlines for text rendering.

QUL_MAX_PARAGRAPH_SIZE

Set the maximum paragraph size.

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 QUL_DEFAULT_FONT_FAMILY 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 QUL_FONT_CACHE_SIZE 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 QUL_GLYPHS_RESOURCE_STORAGE_SECTION into the Spark's cache memory.

See QUL_FONT_CACHE_PRIMING 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 QUL_FONT_VECTOR_OUTLINES_DRAWING 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) {
    auto now = Qul::Platform::getPlatformInstance()->currentTimestamp();
    // <handle timers>
    auto 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 size set by QUL_PLATFORM_DEFAULT_TEXT_CACHE_SIZE in the platform's BoardDefaults.cmake 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 QUL_PRIVATE_PLATFORM_MERGE_STATIC_TEXT_GLYPHS in BoardDefaults.cmake:

set(QUL_PRIVATE_PLATFORM_MERGE_STATIC_TEXT_GLYPHS ON)

QUL_PRIVATE_PLATFORM_MERGE_STATIC_TEXT_GLYPHS is an experimental API, which comes with the following limitations:

Refer to Resource cache for tips about choosing appropriate cache size.

Available under certain Qt licenses.
Find out more.