Qt6.7带来的文本改进

Future text improvements in Qt 6.7

Qt6.7带来的文本改进

December 08, 2023 by Eskil Abrahamsen Blomfeldt | Comments

2023年12月8日:Eskil Abrahamsen Blomfeldt |评论

The Qt 6.7 feature freeze is today, and we now start the process of further stabilizing the code. The actual release is still a few months off, but I thought I would look ahead at bit at what improvements are in store for font handling in Qt and Qt Quick. More specifically there are three things discussed: Variable font support, a brand new "large font" text renderer and font shaping feature support.

Qt 6.7的功能冻结在今天,我们现在开始进一步稳定代码的过程。实际发布还有几个月的时间,但我想我会展望一下Qt和QtQuick中字体处理的改进。更具体地说,讨论了三件事:可变字体支持、全新的“大字体”文本渲染器和字体塑造功能支持。

Variable fonts

可变字体

Back in the day, fonts came in a maximum of four variants (or so-called sub-families): Regular, Bold, Italic and Italic Bold. If you wanted any variations on top of this, you would have to create a brand new family name for these, since there were no other ways to disambiguate the font selection than providing a font family + one of these four styles.

当时,字体最多有四种变量(或所谓的子族):正则、粗体、斜体和斜体加粗。如果想在此基础上进行任何更改,必须为这些字体创建一个全新的系列名称,因为除了提供一个字体系列+这四种样式中的一种之外,没有其他方法可以消除字体选择的歧义。

These days, there's more flexibility: Fonts can have an arbitrary "style name" (or "sub-family name"), and many fonts will have large sets of variations to choose from. The variations can be for weight or italics like before, but also for other properties such as character width - and that way, it's possible to create very specific sub-families such as for instance "Bahnschrift Semibold Semicondensed". (History note: Compatibility with the older font system is actually still visible on Windows, where the sub-family name is often baked into a "legacy" family name for backwards compatibility, and you have a secondary set of "typographic" names which give you the real family and sub-family of the font.)

如今,有了更多的灵活性:字体可以有一个任意的“样式名”(或“子族名”),许多字体都有大量的变量可供选择。变量可以是像以前一样的权重或斜体,也可以是其他属性,如字符宽度-这样,就可以创建非常特定的子族,例如“Bahnchrift Semibold Semicontended”。(历史说明:与旧字体系统的兼容性实际上在Windows上仍然可见,在Windows中,子家族名称通常被烘焙为“传统”家族名称以实现向后兼容性,并且有一组次要的“印刷”名称,这些名称提供了字体的真正家族和子家族。)

Logically, sub-families all belong to a common font family, but from a file system perspective they have been implemented as stand-alone font files. (In some cases, you can have a so-called "TrueType collection" file that contains a set of multiple fonts, but this is actually just a wrapper for easier distribution and it contains complete copies of each individual font file inside.)

从逻辑上讲,子族都属于一个通用字体族,但从文件系统的角度来看,它们是作为独立的字体文件实现的。(在某些情况下,可以有一个所谓的“TrueType集合”文件,其中包含一组多个字体,但这实际上只是一个更容易分发的包装,其中包含每个单独字体文件的完整副本。)

In recent years, a new approach to sub-families has emerged: Variable fonts have floating point parameters called "axes" which alter how the glyphs of the font appear. The font designer provides a set of "masters" for each axis which identify how each glyph looks at the minimum and maximum values (and possibly others). Then they may define "named instances" for specific combinations of different values along the axes and the font system can interpolate between the masters to provide the exact requested appearance.  

近年来,出现了一种新的子族方法:可变字体具有称为“轴”的浮点参数,可以改变字体的字形显示方式。字体设计者为每个轴提供了一组“主控形状”,用于识别每个字形在最小值和最大值(可能还有其他值)下的外观。然后,它们可以为沿轴的不同值的特定组合定义“命名实例”,字体系统可以在主控形状之间进行插值,以提供所需的确切外观。

For instance, a font may support the standard 'wdth' and 'wght' axes for modifying the width and weight of the glyphs with four masters: wdth=50, wght=100, wdth=200 and wght=900. Once this is in place, the font designer can just define an instance called "Semibold Semicondensed" where 'wdth' is set to 87 and 'wght' is set to 600 and the interpolation will do the rest - no need to manually design each glyph for that particular combination of width and weight.

例如,字体可以支持标准的“wdth”和“wght”轴,用于使用四个母版修改字形的宽度和重量:wdth=50、wght=100、wdth=200和wght=900。一旦实现了这一点,字体设计师就可以定义一个名为“半粗体-半浓缩”的实例,其中“wdth”设置为87,“wght”设置为600,插值将完成其余操作-无需为宽度和重量的特定组合手动设计每个字形。

In previous Qt versions, loading such fonts as applications fonts was not supported at all, and you would get very poor results if you tried. Take, for instance, this screenshot of the variable Anybody font loaded in a Qt 6.6 application. The variable axes in the font are stuck at their minimum values and bold/italic is synthesized by the rasterizer in the cases where it's explicitly requested. Luckily, most variable fonts are still distributed with a backwards-compatible set of "static" font files, which has so far been the only way to load them in Qt.

​在以前的Qt版本中,根本不支持将这些字体加载为应用程序字体,如果尝试,结果会非常糟糕。举个例子,这张Qt6.6应用程序中加载的变量Anyone字体的屏幕截图。字体中的可变轴固定在其最小值,在明确要求的情况下,光栅化器会合成粗体/斜体。幸运的是,大多数可变字体仍然与一组向后兼容的“静态”字体文件一起分发,这是迄今为止在Qt中加载它们的唯一方法。

anybody-before-1

In Qt 6.7, we are adding support for loading variable application fonts on all platforms. The QFontDatabase will look up the named instances stored in the font and add these as selectable sub-families. They can be selected using either the style name property or by weight/style, same as with traditional sub-families stored as separate files.

​在Qt 6.7中,我们增加了对在所有平台上加载可变应用程序字体的支持。QFontDatabase将查找存储在字体中的命名实例,并将其添加为可选择的子族。它们可以使用样式名称特性或按重量/样式进行选择,与存储为单独文件的传统子族相同。

anybody-after-1

When selecting fonts using the traditional properties such as weight and width, the predefined named instances will be preferred, and Qt will try to find the closest match. In some advanced use cases, however, you may want more direct access to the variables axes to get the exact look you want.

当使用传统属性(如权重和宽度)选择字体时,将首选预定义的命名实例,Qt将尝试找到最匹配的实例。然而,在一些高级用例中,可能希望更直接地访问变量轴,以获得所需的确切外观。

Qt provides support for this through new APIs: QFont::setVariableAxis() and font.variableAxes.

​Qt通过新的API:QFont::setVariableAxis()和font.variableAxes对此提供支持。

anybody

While many fonts will only support standardized axes such as 'wght', 'wdth' and 'ital', font designers are not limited to these. Any tags of four latin-1 characters may be used to specify an axis and can alter the appearance of glyphs in dramatic ways, as long as the number of contours and control points remain the same in all masters. (By convention, custom axes will be in uppercase to ensure there is no conflict with official axes that are introduced in the future.)

虽然许多字体只支持标准轴,如“wght”、“wdth”和“ital”,但字体设计者并不局限于这些。四个latin-1字符的任何标签都可以用于指定轴,并且可以以戏剧性的方式改变字形的外观,只要轮廓和控制点的数量在所有主图形中保持不变。(按照惯例,自定义轴将是大写的,以确保与未来引入的官方轴不冲突。)

Take, for instance, the Anicons font which supports the custom "TIME" axis. Changing the TIME will cause glyphs to animate between key frames defined as "masters" in the font.

​以支持自定义“TIME”轴的Anicons字体为例。更改“时间”将导致字形在字体中定义为“主帧”的关键帧之间设置动画。

Screen capture of changing TIME to animate icons from Anicons font

Note: As mentioned, this feature is available on all platforms, but on Windows it requires that you run your application with either the DirectWrite backend or the cross-platform Freetype backend. This can be done by passing e.g. -platform windows:fontengine=directwrite as command-line arguments to the application. Or it can alternatively be customized in the application's qt.conf. The DirectWrite backend is planned to take over as the default on Windows in the future, but for now it is opt-in. This is because it currently does not support legacy font names or bitmap fonts, so it will return a slightly smaller set of fonts than with the default GDI backend.

​注意:如前所述,此功能在所有平台上都可用,但在Windows上,它要求使用DirectWrite后端或跨平台Freetype后端运行应用程序。这可以通过将例如-platform-windows:fontengine=directwrite作为命令行参数传递给应用程序来实现。或者,也可以在应用程序的qt.conf中进行自定义。DirectWrite后端计划在未来作为Windows上的默认后端,但目前它是可选的。这是因为它目前不支持传统的字体名称或位图字体,因此它将返回比默认GDI后端稍小的字体集。

Curve renderer for text

文本的曲线渲染器

The default way of rendering text in Qt Quick (since version 2.0) is something we simply chose to call QtRendering. This was mainly because we did not want to lock ourselves to a specific implementation for the default, so we picked a more general name. Under the hood, it's using an approach which is well-established these days: two-dimensional distance fields stored in textures to represent the contours of glyphs, and a fragment shader which can render these efficiently at a range of different sizes and scales with the same source texture.

在Qt Quick中渲染文本的默认方式(自2.0版本以来)是我们简单地选择称为Qt渲染的方式。这主要是因为我们不想将自己锁定到默认的特定实现,所以我们选择了一个更通用的名称。在引擎盖下,它使用了一种目前公认的方法:存储在纹理中的二维距离场来表示字形的轮廓,以及一个片段着色器,该着色器可以使用相同的源纹理在不同大小和比例的范围内有效地渲染这些图形。

This has turned out to be a very good compromise between performance and quality, and it is unlikely that the default approach will change at this point. However, there are some cases where the limitations of the algorithm are visible to the user.

事实证明,这是性能和质量之间的一个很好的折衷方案,默认方法在这一点上不太可能改变。然而,在某些情况下,算法的局限性对用户是可见的。

Primarily, the problems arise when the font becomes too big for the source texture. The algorithm is no longer able to faithfully re-create the glyphs at the target size, and you will see artifacts, especially on sharp corners or thin features.

问题主要出现在字体对于源纹理来说太大时。该算法不再能够忠实地以目标大小重新创建字形,将看到伪影,尤其是在尖角或薄特征上。

"Qt Rocks" text at pixel sizes 60, 200 and 500 with QtRendering backend

As is evident from this screen shot, there are artifacts visible at pixel size 200 which may or may not be acceptable, depending on your use case. At pixel size 500 the artifacts are warping the text and for most use cases you will be looking for work arounds if you need to use the font at that size. 

从这个屏幕截图中可以明显看出,在像素大小200处存在可见的伪影,根据用例,这可能是可接受的,也可能是不可接受的。在像素大小为500的情况下,工件会扭曲文本,对于大多数用例,如果需要使用该大小的字体,将寻找解决方案。

Luckily we do already have solutions for this in place: Prior to Qt 6.7, the main ways to mitigate have been to either increase the renderTypeQuality (which will cause memory consumption to increase for the font in question) or to use the Text.NativeRendering render type (which requires pre-rasterization of the glyphs at the target size, so it will come at both a CPU time cost as well as memory cost.)

​幸运的是,我们已经有了解决方案:在Qt 6.7之前,主要的缓解方法是提高renderTypeQuality(这将导致相关字体的内存消耗增加)或使用Text.NativeRendering渲染类型(这需要以目标大小对字形进行预光栅化,因此这既需要CPU时间成本,也需要内存成本。)

In Qt 6.6 we introduced a new renderer for Qt Quick Shapes that we called the Curve Renderer, and in Qt 6.7 this is now included as an optional text renderer as well, and it should be a perfect fit for rendering large text.

​在Qt 6.6中,我们为Qt Quick Shapes引入了一种新的渲染器,我们称之为曲线渲染器,在Qt 6.7中,它现在也作为可选的文本渲染器包括在内,它应该非常适合渲染大文本。

It does have a higher GPU time cost than QtRendering, so it isn't going to replace the default any time soon, but for larger text it will give you beautiful results even at very large scales, without eating up too much of your graphics memory.

它的GPU时间成本确实比QtRendering高,所以它不会很快取代默认值,但对于更大的文本,即使在非常大的范围内,它也会给你带来漂亮的结果,而不会占用太多的图形内存。

"Qt Rocks" text at pixel sizes 60, 200 and 500 with CurveRendering backend

To try this out, set the renderType property to Text.CurveRenderer and run against Qt 6.7. For most application text the difference should not be very visible, but for large banners or headlines, it may give substantial improvements.

​若要尝试此操作,请将renderType属性设置为Text.CurveRenderer并在Qt 6.7上运行。对于大多数应用程序文本,差异应该不是很明显,但对于大型横幅或标题,它可能会带来实质性的改进。

Font shaping features

字体塑造功能

Finally, a new API for the advanced users: For context, "text shaping" is what we call the process of taking a sequence of Unicode characters and a font and then converting this into a set of glyphs and their positions relative to each other. This covers both grammatically necessary processing, such as combining certain sequences of characters into ligatures in e.g. Arabic or Indic languages, and it also covers more cosmetic features such as kerning.

最后,为高级用户提供了一个新的API:对于上下文,“文本整形”是我们所说的获取Unicode字符和字体序列,然后将其转换为一组字形及其相对位置的过程。这涵盖了语法上必要的处理,例如在阿拉伯语或印度语中将某些字符序列组合成连字,还涵盖了更多的修饰特征,如紧排。

While the default behavior of the font shaper in Qt is most commonly what you want and expect, in some situations it is convenient to be able to directly influence this process. To enable this for users, we are now exposing such font shaping features through Qt's font APIs: QFont::setFeature() in C++ and font.features in QML.

​虽然Qt中字体整形器的默认行为通常是想要和期望的,但在某些情况下,能够直接影响这个过程是很方便的。为了为用户启用这一功能,我们现在通过Qt的字体API:C++中的QFont::setFeature()和QML中的font.features来公开此类字体整形功能。

The font features are similar to variable axes in that they associate a value with a four-character "tag". For features, however, the value is an integer and is in practice usually either 0 or 1 - off or on. Enabling or disabling a given feature decides whether it will be included when deciding which glyph will be selected and what its relative position will be.

字体功能类似于可变轴,因为它们将值与四个字符的“标记”相关联。然而,对于功能,该值是一个整数,在实践中通常是0或1-关闭或打开。启用或禁用给定的功能决定了在决定选择哪个字形及其相对位置时是否包括该功能。

OpenType® provides a list of registered features. Like with variable axes, a font can include features with any valid tag, and by convention upper-case tags are used for custom features.

​OpenType®提供了已注册功能的列表。与可变轴一样,字体可以包含具有任何有效标记的功能,并且按照惯例,大写标记用于自定义功能。

Certain features will be enabled by default in Qt, depending on what other font properties are set. The API grants you access to overriding whatever Qt would otherwise default to, and force the behavior you need. Any feature you leave unset in the QFont will keep its default value.

​某些功能将在Qt中默认启用,具体取决于设置的其他字体属性。API授予重写Qt默认为的任何内容的权限,并强制执行需要的行为。在QFont中未设置的任何功能都将保持其默认值。

For instance, Qt will enable the use of optional ligatures by default unless a custom letter spacing is set. For some fonts, this means certain typical combination of characters will be shown as a single, merged glyphs, which may be more visually pleasing than the individual letters next to each other. A common example is 'fi', and 'ffi' which in many fonts have corresponding ligatures. For instance, here's the Calibri font from Windows:

例如,除非设置了自定义字母间距,否则默认情况下Qt将启用可选的连字。对于某些字体,这意味着某些典型的字符组合将显示为单个合并的字形,这可能比相邻的单个字母在视觉上更令人愉悦。一个常见的例子是“fi”和“ffi”,它们在许多字体中都有相应的连字。例如,以下是Windows中的Calibri字体:

priceconsole-howmuch

When this font is used and the ligature feature is on (as it is by default), any immediate sequence of 'f' and 'i' will be replaced by a single, combined glyph which visually binds the text together.

当使用此字体并启用连字功能时(默认情况下),“f”和“i”的任何立即序列都将被一个单独的组合字形所取代,该字形将文本视觉地绑定在一起。

However, there are some cases where the letters are logically separate and should not be combined in this way. One example could be in the documentation of keyboard shortcuts:

然而,在某些情况下,字母在逻辑上是分开的,不应该以这种方式组合。一个例子可能出现在键盘快捷键的文档中:

priceconsole-liga

Merging the characters makes it slightly less clear that these are individual key strokes. To make sure the this does not accidentally happen, we can set the 'liga' feature to 0, thus disabling the feature in the shaping process.

将这些字符合并会使其不太清楚这些是单独的关键笔划。为了确保这种情况不会意外发生,我们可以将“liga”功能设置为0,从而在成型过程中禁用该功能。

priceconsole-noliga

There are many more of these smaller adjustments that can be made by enabling/disabling font features. In addition, certain fonts contain different stylistic alternatives of glyphs which can be selected using e.g. the 'salt' feature.

​通过启用/禁用字体功能,可以进行更多这些较小的调整。此外,某些字体包含不同风格的字形替代品,可以使用例如“salt”功能进行选择。

Conclusion

结论

As mentioned at the start, this is all upcoming in the next release of Qt. Font features are already available through a tech preview API in Qt 6.6, but please take note that the C++ API is due to change in Qt 6.7 based on feedback, so any code using it will have to be updated for the new API.

正如开头所提到的,这一切都将在Qt的下一个版本中出现。字体功能已经通过技术预览版API在Qt 6.6中提供,但请注意,C++API将根据反馈在Qt 6.7中更改,因此任何使用它的代码都必须为新的API更新。

For the adventurous among us, everything discussed here can be tested already by fetching the dev branch of Qt at code.qt.io and building it manually. But as always when using the development branch of any open source project, be prepared that errors may occur, as this branch is being updated continuously through the day (if you do find bugs, we would of course love to hear about them so we can make sure they are fixed before we release.)

​对于我们当中的冒险者来说,这里讨论的一切都可以通过在code.Qt.io中获取Qt的dev分支并手动构建来进行测试。但与往常一样,在使用任何开源项目的开发分支时,要做好可能发生错误的准备,因为这个分支每天都在不断更新(如果你确实发现了错误,我们当然很乐意听到它们,这样我们就可以确保在发布前修复它们。)

Final note: Qt 6.7 is still under development, so it is still possible that new APIs and features may change, as a result of our many-phased review process. So make sure to check the snapshot documentation if you encounter problems after upgrading to a new snapshot.

​最后一点:Qt 6.7仍在开发中,因此,由于我们的多阶段审查过程,新的API和功能仍有可能发生变化。因此,如果升级到新快照后遇到问题,请务必查看快照文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值