C

文本渲染和字体

概述

在 Qt Quick Ultralite 中,文本是通过使用 TextStaticText 项目渲染到屏幕上的。这些项目具有字体属性,用于控制选定的 字体 配置。阅读 字体 类型文档以了解可用 API 和字体引擎特定细节。

Qt Quick Ultralite 提供两个字体引擎选项:Monotype Spark 和 Static 字体引擎。

Monotype 字体引擎

  • 更好的支持使用广泛字符集的国际化应用程序。
  • 更好的支持需要文本形状以确保正确布局的语言。
  • 支持文本缩放,而不影响性能和文本质量。

Static 字体引擎

  • 更好的支持不需要复杂文本且占用空间较小的应用程序。
  • 在静态预计算数据上操作,允许非常快的查找操作。
  • 在构建时处理字体,从而导致没有运行时开销。

请参阅 功能比较表 以获取两个字体引擎之间的详细比较。

语言和书写系统

作为国际化支持的一部分,Qt Quick Ultralite 提供输入控件和文本绘制方法,这些方法内置了对所选引擎支持的所有书写系统的支持。文本子系统能够同时渲染包含来自各种不同书写系统字符的文本。

支持的语言、文本形状特性和字体文件特性因所选字体引擎而异。文本形状是准备文本以显示的必要部分。并非所有 Unicode 支持的脚本都需要文本形状,但对于其他脚本(例如,复杂文本布局),则需要它以渲染可读文本。如果应用程序不会使用依赖于复杂脚本的任何语言,则可以启用 MCU.Config.complexTextRendering 来启用将文本形状引擎链接到二进制文件。复杂脚本的示例包括阿拉伯字母和印度语脚本。

支持符合Unicode标准的双向文本,具体行为定义在Unicode技术附录#9中。例外是文本项,当应用程序使用静态字体引擎时,不支持双向文本。

特性比较表

以下表格突出显示了两种文本处理选项之间的关键差异,使您能够选择合适的选项。

特性静态字体引擎Monotype Spark字体引擎备注
字体光栅化构建时由fontcompiler利用FreeType完成。运行时由Monotype Spark完成。构建时通过Monotype Spark的StaticText
内存要求在MCU上使用传统的approach,预先光栅化所有使用的字符。缺点是对于国际化应用程序,它可能需要大量的字节数。使用静态字体引擎的唯一内存需求是为光栅化字形和字体度量带来的预计算数据提供足够的空间。静态字体引擎的实现是围绕此数据的细薄API。Monotype Spark字体引擎与应用程序链接。如果MCU.Config.complexTextRendering设置为true,则链接WorldType Shaper Spark引擎到应用程序。参阅示例代码大小<QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\。该引擎要求应用程序捆绑字体文件。字体引擎的运行时数据需要额外的RAM,请参阅MCU.Config.fontHeapSize。如果启用了字形缓存,则需要额外的RAM。将MCU.Config.maxParagraphSize设置为100时,所需的文本形状引擎RAM低至3.1 KB。
缓存系统不需要,因为所有数据都是预计算的。如果启用了字形缓存并分配了足够的缓存内存,则会大幅度提高性能。缓存内存用于存储字形、进位宽度以及Unicode字形id映射(CMA)。可选地,可以使用缓存预启动功能提前准备缓存内容。
动态文本必须使用font.unicodeCoverage属性预注册字形。这将增加预计算数据的大小。如果字体文件包含字形信息,则应用程序开发者无需进行特殊处理。本文档中的动态文本是指编译时未知的文本。例如,可以是来自网络的文本、从文件中读取的文本或用户输入。在Qt for MCU中,C++代码中定义的字符串也被视为动态字符串。
字体格式FreeType支持的任何字体文件格式。TrueType字体CCC压缩字体字体映射
字体提示使用FreeType的提示处理功能。Spark使用两种不同的提示方法——spark提示自动提示,这些方法针对微控制器平台上的有限资源进行了优化。由于节省运行时内存,不支持TrueType的提示指令。字体提示是应用在字符光栅化过程中的一个步骤。提示是控制字体渲染器分配像素值的指令。提示通常用于保持某些字体的一致性,例如字符笔划、粗细和高度。提示可以编码到字体中或在字体处理过程中动态应用。
字符压缩当前未实现。支持对字符数据进行运行长度编码,但此功能尚未集成。
字符渲染模式Qt for MCUs当前仅利用位图和灰度图8模式。Spark的光栅化器支持以下格式:位图、灰度图2、灰度图4、灰度图6、灰度图8。Qt for MCUs当前仅支持位图和灰度图8。可以使用font.quality属性配置渲染模式。当字体质量为Font.QualityVeryLow时,font.quality的值映射为bitmap;当字体质量为Font.QualityVeryHigh时,映射为graymap8。这些值适用于两个字体引擎。
文本形状仅通过StaticText元素支持。文本形状是在应用程序构建时执行的,使用HarfBuzz库。对于此字体引擎,忽略MCU.Config.complexTextRendering设置,因为在运行时没有对文本形状的实现。TextStaticText支持。使用Monotype的WorldType Shaper Spark引擎为了获得最佳性能,如果文本块不包含复杂脚本,我们将跳过将此类字符串传递给形状引擎。一些字体通过复杂的脚本形状表在非复杂脚本中实现纯外观功能。在Qt Quick Ultralite应用程序中使用此类字体可能导致文本渲染出现意外。在Qt Quick中,此行为由font.preferShaping属性控制。在Qt Quick Ultralite中,此属性不可用,而是始终假定为font.preferShaping = false

注意:以上内容不适用于静态字体引擎,因为MCU不对文本形状任务进行CPU密集型操作。

文本对齐

当文本项目的水平对齐没有明确定义时,文本元素会自动将其对齐到文本的自然阅读方向。默认情况下,从左到右的文本(如英语)居左对齐于文本区域,而从右到左的文本(如阿拉伯语)居右对齐于文本区域。

可以使用文本元素的horizontalAlignment属性来覆盖此隐式对齐。

// 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
}

Static 字体引擎

字体文件处理在应用程序构建时发生,由fontcompiler执行。此工具依赖于Qt的基于FreeType的字体引擎。由fontcompiler产生的输出是光栅化字符和字体度量信息的预计算数据。在运行时,这些数据不会添加或删除任何内容——可用的字符集在构建时确定。对此数据的所有操作都是O(1)或O(log n)。此字体引擎仅支持constant font configurations

字体

字体引擎使用FontFiles.files QmlProject属性来查找应用程序使用的字体并提取必要的字体数据。

预计算数据

为了支持在不同字体设置之间分配文本,默认情况下,fontcompiler会将应用程序中所有字体配置中使用的所有字符进行光栅化。例外情况包括font.unicodeCoverageStaticText,它们仅对使用的字体配置进行字符注册。

此外,fontcompiler默认包含一组常用的符号,例如数字。

要减少内存占用量,可以使用MCU.Config.autoGenerateGlyphs来禁用此行为。在这种情况下,只有font.unicodeCoverage中明确定义的字符将被光栅化。

预计算的数据是任何需要渲染文本的数据共享来源,包括TextStaticText

有关配置此数据的运行时存储的详细信息,请参阅MCU.Config.glyphsCachePolicyMCU.Config.glyphsStorageSectionMCU.Config.glyphsRuntimeAllocationType

符号嵌入

根据MCU.Config.autoGenerateGlyphs的设置,fontcompiler可以嵌入所有用于任何QML字符串字面量和所有字体配置中的字符的符号。可以通过使用font.unicodeCoverage属性来扩展字符选择。在字符串动态创建且在编译时未知的情况下,此操作是必要的。

如果MCU.Config.autoGenerateGlyphs设置为false,则仅嵌入使用font.unicodeCoverage定义的符号。通过精心设计应用程序设计,此技术可以实现显著的脚印减小。

设置font.unicodeCoverage属性会影响使用相同字体配置的所有QML项。

在QML中渲染动态字符串

C++模型中的字符串

C++模型可以产生在编译时未知动态字符串。以下是一个示例

// 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
        }
    }
}

由于fontcompiler无法预测所需的符号,因此无法为这样的模型数据生成符号。这导致渲染QML内容时缺少符号。

要告知fontcompiler模型可以产生哪些字符范围以及必须生成哪些符号,请使用代理字体font.unicodeCoverage属性。

Item {
    ListView {
        anchors.fill: parent
        model: MyModel { }
        delegate: Text {
            height: 20
            font: Qt.font({
                unicodeCoverage: [Font.UnicodeBlock_BasicLatin] // << define character set
            })
            text: model.stringField
        }
    }
}

C++函数中的字符串

返回字符串的C++函数遵循与C++模型相同的规则。以下是一个示例

// 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()
    }
}

除非为使用动态字符串的Text项设置font.unicodeCoverage属性,否则前面的示例代码无法正确渲染符号。

Item {
    MyObject { id: myobject }
    Text {
        text: myobject.getDynamicString()
        font: Qt.font({
            unicodeCoverage: [Font.UnicodeBlock_BasicLatin] // << define character set
        })
    }
}

Monotype

Monotype Spark产品的页面https://www.monotype.com/products/embedded-solutions/spark

文档安装在<QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\。它还包括包含FAQ的文档。

Monotype Spark字体引擎。

Monotype的Spark字体引擎是一个基于业界标准的TrueType字体标准的可缩放字体渲染子系统。它针对资源受限的环境而设计,例如汽车显示屏、医疗设备、家电、可穿戴设备、机顶盒、便携式媒体播放器和控制面板。它将可缩放文本和高质量多语言字体显示的优势带给嵌入式环境。

高性能架构,针对空间效率和速度进行了优化。Spark符合许多应用和设备严格的大小要求,包括那些支持东亚语言,需要数千个字符的应用和设备。结合Monotype的优化字体,Monotype Spark解决方案让您能够利用可缩放字体,这在之前可能因内存限制、复杂性或平台成本而不可能实现。

Monotype提供各种技术,可以应用于字体文件以减少内存要求并优化处理速度。以下章节中列出了其中一些技术。Monotype Spark库高度可配置。为了最佳性能和内存使用,请阅读Spark文档,其中包括一份详尽的性能指南。

WorldType Shaper Spark引擎。

WorldType Shaper Spark是一个用于从复杂语言脚本中塑造文本的库。它针对在小型设备上进行文本塑造进行了高度优化,如可穿戴设备等。

WorldType Shaper Spark不支持OpenType表,即GSUB和GPOS,它只能与具有苹果高级排版格式'morx'/'kerx'表或设计为支持阿拉伯文、希伯来文和泰语文本的Unicode到Unicode塑造的字体一起工作,即具有所有呈现形式的符号的字体。有限状态机的使用使'morx'表相对较小且处理速度相对较快。

它还处理双向脚本所需的全部处理,包括文本重新排序。WorldType Shaper Spark完全符合Unicode 12.1.0规范。

此库在应用程序将spark字体引擎作为字体引擎时由文本塑造引擎使用。

如何启用

默认情况下,Qt Quick Ultralite应用程序使用静态字体引擎。要选择Monotype Spark字体引擎,设置MCU.Config.fontEngine目标属性为"Spark"。有关受支持的字体引擎列表,请参阅受支持的字体引擎。设置此QmlProject属性会使应用程序链接到Monotype库。

当切换到Monotype Spark字体引擎时,确保FontFiles.files中仅包含一个指向支持格式的字体文件的条目。

MCU.Config {
    fontEngine: "Spark"
}
FontFiles {
    files: ["fonts/SampleFontmap.fmp"]
}

有关配置应用程序字体文件运行时存储的更多信息,请参阅MCU.Config.fontFilesCachePolicyMCU.Config.fontFilesStorageSectionMCU.Config.fontFilesRuntimeAllocationType

字体和字体格式

优化的Monotype字体文件安装到<QT_INSTALL_PATH>\QtMCUs\<VERSION>\src\3rdparty\monotype\fonts\

TrueType字体

支持任何TrueType字体,但请注意,TrueType提示指令被忽略。请参阅spark提示自动提示部分。

Fontmaps

Fontmap是一个可以允许将多个字体文件合并到单个Fontmap文件中的概念。Fontmap组件中只能使用TTF文件。此外,Fontmap提供基于Unicode范围、Unicode脚本和字体类的字体选择。在Fontmap中,Unicode脚本条目称为语言

字体映射编辑器是一个应用程序,可以用于创建和/或修改字体映射文件。编辑器提取到 <QT_INSTALL_PATH>\QtMCUs\<VERSION>\bin\。编辑器将以安装程序文件的形式提供,需要用户手动安装。从 Qt for MCUs 2.8 以后的版本附带字体映射编辑器 3.1.1 版本,它可以从字体映射文件中删除未使用的字形,从而减少其在应用程序中的占用空间。该工具的文档可以通过字体映射编辑器的菜单 帮助 -> 帮助主题 访问。有关字体选择算法和性能技巧的更多详情,请参阅 <QT_INSTALL_PATH>\QtMCUs\<VERSION>\docs\monotype\

注意:目前,字体映射中组件字体查找独立于语言。

CCC 压缩字体

CCC 字体是 Monotype 字体格式,它使用一种名为 CCC 的无损压缩算法来压缩大型字体表。由于解压缩开销很小,它可以在压缩比和资源之间做出良好的妥协。所有 TTF 字体都可以转换为 CCC。

注意:目前尚不支持将 CCC 压缩字体作为字体映射组件。

引擎配置

可以通过以下 QmlProject 属性配置字体引擎:

MCU.Config.fontCachePrealloc

控制字体缓存缓冲区预分配。

MCU.Config.fontCachePriming

控制字体缓存预填充。

MCU.Config.fontCacheSize

设置字体引擎使用的最大缓存大小。

MCU.Config.fontHeapPrealloc

控制字体引擎使用的堆缓冲区预分配。

MCU.Config.fontHeapSize

定义字体引擎的最大堆大小。

MCU.Config.fontVectorOutlinesDrawing

控制是否使用矢量轮廓进行文本渲染。

MCU.Config.maxParagraphSize

设置字符的最大段大小。

堆大小和缓存大小的最优值需要仔细测试和调整。

Spark 也可以与外部已分配的堆和缓存内存一起工作。

字体类映射

注意:本节仅适用于使用字体映射文件的情况。

影响从字体映射文件选择字体的组件之一是字体类名称。参见字体映射的文档,了解字体选择是如何工作的。支持的字体类命名格式是 "<font.family> <font.weight> <font.italic>",其中

  • font.family - 如果未设置,则使用来自 MCU.Config.defaultFontFamily 目标属性的值。如果已设置,则使用提供的字符串。
  • font.weight - 将枚举映射到字符串,例如,Font.ExtraLight 变为 "Extralight"。例外的是,Font.Normal 映射为空字符串。
  • font.bold - 是 font.weight: Font.Bold 的同义词
  • font.italic - 如果已设置,则映射到 "Italic"。如果没有设置,则映射到空字符串。

映射示例

参见字体绑定示例。

移植现有应用程序

假设我们有一个使用 无衬线 字体家族的 qml 应用程序,如下例所示:

Text {
    font.family: "sans-serif"
}

设计上面的示例中的Fontmap,您需要将字体文件(TTF)映射到sans-serif字体类名。这是一个简单的过程,使用了FontmapEditor图形界面工具。如果您不想修改大小写代码,但又想使用除SansSerif.ttf之外的字体文件,则不需要但在Fontmap文件中将sans-serif映射到FrutigerOTS_S12-29g.ttf。或者,您也可以在源代码中将所有的sans-serif更改为例,如FrutigerOTS。然后在FontmapEditor中将FrutigerOTS映射到FrutigerOTS_S12-29g.ttf

字体光栅

Spark 光栅

Spark 光栅指令是为非常低的内存要求和增强性能而设计的。请注意,Spark不会处理字体中存在的传统TrueType光栅提示。除了新的光栅指令之外,Spark还应用了其他独特的光栅技术,以显著减小字体大小。Spark提示可以选择性地压缩以进一步减少内存使用。

自动光栅

自动光栅内置在Spark字体引擎中,因为这可以保留运行时内存。如果系统字体不包含Spark提示,Spark将部署自动光栅。仅自动光栅,并不像自动光栅与Spark提示组合那样提供相同的高质量,尤其是在较小的文本大小上。

字符缓存

在首次渲染字符时,Spark创建字符并将其存储在缓存中。之后,如果您尝试再次渲染该字符,它将从缓存中直接提取,无需重新创建,从而提高了性能。与字符缓存一起,Spark还支持CMAP和Advance缓存。它使得与解析TrueType字体的“CMAP”和“HMTX”表相比,更快地检索字符ID和字符进位。当缓存满时,Spark会删除最不常用的条目。

在其他缓存配置中,Spark让您配置永不删除的缓存条目和不应添加到缓存中的字符大小阈值。其中一些功能目前在与MCU的Qt中不可用。

有关更多信息,请参阅MCU.Config.fontCacheSize文档。

缓存初始化

有时为了进一步提高性能,预先填充缓存很有用。这显著提高了应用程序的启动时间,尤其是在包含大量文本的情况下。

使用font.unicodeCoverage属性选择在构建应用程序时包含在缓存初始化数据中的字符。当字体引擎在应用程序运行时首次访问时,缓存初始化数据将从MCU.Config.glyphsStorageSection复制到Spark的缓存内存中。

有关更多信息,请参阅MCU.Config.fontCachePriming文档。

如果缓存初始化检测到用于< pregnancies="StaticText" >静态文本的定义目的的这些常用字符,它将在预填充运行时Spark缓存时进行优化,从< pregnancies="StaticText" >StaticText>数据中获取这些字符。这种优化可以在所需的闪存空间上节省空间。

文本缓存

Qt Quick Ultralite分别绘制文本中的每个字符或字符。默认行为带来了性能开销,因为经常需要调用绘图引擎。在导致性能较慢的平台,切换到文本缓存替代方案。它使得在CPU侧的单独图像中为每个文本元素缓存,减少了调用绘图引擎的次数。

此外,如果启用了MCU.Config.fontVectorOutlinesDrawing,文本轮廓也会被缓存。这避免了每次绘制文本元素时重新计算相应的PathData

为了使您的应用程序启用文本缓存,请为Qul::Application类实现一个替代构造函数。构造函数应接受一个Qul::ApplicationConfiguration实例,如下面的示例所示

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);
    }
}

注意:在NXP MIMXRT1170-EVKB、Renesas RH850/D1M1A和Infineon TRAVEO™ T2G板上,默认启用文本缓存。

如果未设置文本缓存大小,Qt Quick Ultralite将使用平台主CMakeLists.txt中设置的24KB默认大小。要更改大小,请在平台的CMakeLists.txt文件中为QUL_PLATFORM_DEFAULT_TEXT_CACHE_SIZE添加一个Platform目标属性。

当文本缓存满时,Qt Quick Ultralite将回退到默认行为,直到下一帧。

文本缓存不用于StaticText项目,因为fontcompiler工具可以使用--mergeStaticTextGlyphs选项将StaticText项目的字符组合成一个单独的图像。要使用此选项,平台应在BoardDefaults.qmlprojectconfig中的MCU.Config节点下启用MCU.Config.mergeStaticTextGlyphs

MCU.Config {
    mergeStaticTextGlyphs: true
}

mergeStaticTextGlyphs是一个实验性API,具有以下限制

可以在性能日志中查看应用程序使用的文本缓存大小。

有关选择合适缓存大小的提示,请参阅资源缓存

在特定Qt许可下可用。
了解详情。