3
8
2022
8

Qt 的字体渲染问题

本文来自依云's Blog,转载请注明。

GUI 程序我现在依然倾向于 GTK,因为虽然 Qt 拥有良好的跨平台性,但可能是太注重跨平台性了,在 Linux 平台上反而有一些水土不服的问题。

字体太多,支持太少

你可能觉得,系统上字体太少,所以经常会遇到不常见的字符无法显示的情况。然而对于 Qt 来说,字体越多,反而越容易遇到个别字符不能显示的情况。

这是我的 /etc/fonts/conf.d/66-qt.conf 中的一段。因为顺序的原因,我只能放到 /etc 下。除了针对 sans-serif 配置外,我也有同样的配置应用于 serif 和 monospace。

<fontconfig>
  <!-- Adjust font order for Qt applications -->
  <alias>
    <family>sans-serif</family>
    <prefer>
      <!-- 格拉哥里字母:Ⰽⱁⱀⱄⱅⰰⱀⱅⰹⱀ Ⰹⱍⰹⰳⱁⰲ -->
      <family>Noto Sans Glagolitic</family>
      <!-- 爪哇文:꧁   ꧂ -->
      <family>Noto Sans Javanese</family>
      <!-- 西夏文:𗷲𗒅 -->
      <family>Noto Serif Tangut</family>
      <!-- 埃及象形文字:𓁹 -->
      <family>Noto Sans Egyptian Hieroglyphs</family>
      <!-- 苏美尔楔形文字:𒆠𒂗𒂠 -->
      <family>Noto Sans Cuneiform</family>
      <!-- 中日韩统一表意文字扩展 C:𫚥 -->
      <family>HanaMinB</family>
      <!-- 拉让文:ꥃ -->
      <family>Noto Sans Rejang</family>
      <!-- 越南傣文:ꪀꪑ -->
      <family>Noto Sans Tai Viet</family>
      <!-- 切罗基文:ꮳꮧꮢ ᨣ -->
      <family>Noto Sans Cherokee</family>
      <!-- 老傣仂文:ᨣ -->
      <family>Noto Sans Tai Tham</family>
      <!-- 安纳托利亚象形文字:𔘓 -->
      <family>Noto Sans Anatolian Hieroglyphs</family>
      <!-- 马姆穆文补充:𖤍  -->
      <family>Noto Sans Bamum</family>
      <!-- 图标字体(PUA): -->
      <family>OperatorMonoSSmLig Nerd Font</family>
      <!-- 巴塔克文:ᯤ -->
      <family>Noto Sans Batak</family>
      <!-- 古北欧文:ᛋᛖᚱᚣᚨᛚᚳᚨᚾᛞᛚᛖ -->
      <family>Noto Sans Runic</family>
    </prefer>
  </alias>
</fontconfig>

这个配置的意思是,把这些字体的优先级提高一些。当使用 fontconfig 的程序要显示字符的时候,它会指定一个模式,匹配到一个字体列表。渲染文字的时候,就可以遍历这个列表,直到找到可以显示这个字符的字体,所以一般来说,只要系统上装了对应字符的字体,它就能显示出来。

但是 Qt 额外地需要这个配置,因为 Qt 只会检查列表中的前255项。而世界上的不同文字那么多,所以想要能够显示它们,就得有一堆字体。比如 noto-fonts 这个包里就有614个字体文件,远超 Qt 支持的数量。总有些奇奇怪怪的文字被网友用来当颜文字,或者挂在名字上彰显个性。不这么调整一下,Qt 遇到了就只能「吃豆腐」了。

空心豆腐

当一个字符显示不出来的时候,那么怎么显示好呢?一般会显示成某种方框。Pango火狐会将该字符的 Unicode 码点以十六进制的形式显示在方框里边,这样虽然不知道这个字符长什么样子,但至少知道它是哪个字符,也知道多块豆腐是不是同一字符,在不能复制字符本身的时候很有用。比如当它出现在求助者的截图里的时候,比如当它出现在不能复制的地方的时候。

然而 Qt 不这样做。管你什么字符,Qt 统一显示为空心方框。从视觉上完全无法知晓它到底是什么字符,要是复制不到的话,就别想弄明白你缺什么字体了。

PS: Matrix 客户端 fluffychat 的 Web 版,使用的是 Fluffy 图形界面库,即使在 Web 版,文字渲染依然完全是自己做的。不管浏览器的设置不管系统的设置,豆腐块是带叉号的方框,还不能选中,十分讨厌。

非 BMP 字符

所有使用 UTF-16 的平台(Java、JavaScript、Windows、Qt),外加 MySQL 容易遇到的一个问题:非 BMP 字符(也就是那些 U+FFFF 之后的字符)会被当作是两个字符处理。随着 emoji 的流行,大家应该都修了不少。然而,Qt 在展示非 BMP 字符的时候,你可以选中半个字符。如果不小心漏掉半个的话,复制出来的半个字符就会变成问号(还好不是 GBK 时代那样弄乱后续所有字符)。

font features

一些字体可以通过 fontconfig 设置 fontfeatures 属性来启用(或者禁用)一些特性,比如连字,带斜杠的 0,小型大写字母,居中的中文标点,等等。Pango 很早就支持了,火狐最近也支持了,但 Qt 那边依旧没啥动静。(感谢 Coelacanthus 的评论。)

Category: Linux | Tags: linux 字体 Qt | Read Count: 16464
Coelacanthus 说:
Mar 08, 2022 05:17:22 PM

还有一个水土不服的问题是,Qt 并不完全听从 fontconfig 的设置,他会忽略 fontconfig 提供的字体 feature 设置,然后用一个 hardcode 的列表取而代之
https://github.com/qt/qtbase/blob/a1fb3971f222afa01583e41f4d8f0e037d2c7892/src/gui/text/qtextengine.cpp#L1687-L1705
这导致我必须专门为其编译一个字体而不是使用 OpenType feature 切换字形等功能
而这个功能,Pango 早在九年前就已经实现,前段时间我参考原有代码给火狐写了一个,然后转过头来想给 Qt 也写一个的时候,看到这个 hardcode 的列表真的是哭笑不得
https://bugreports.qt.io/browse/QTBUG-78645

Avatar_small
依云 说:
Mar 09, 2022 11:40:41 AM

哦对,我是记得好像漏掉了一个群友最近告诉我的问题。我自己不用这个所以搞忘了。

SilverRainZ 说:
Mar 11, 2022 06:57:56 PM

GTK 感觉走一条相反的道路。

GTK4 移除了好多抹除跨平台差异的 API,如果一件事情无法用平台通用的方法实现,他们就放弃它,这对开发者来说很难受。

Avatar_small
依云 说:
Mar 12, 2022 10:28:37 AM

呃,能具体说说吗?

「如果一件事情无法用平台通用的方法实现,他们就放弃它」——这听着很 Qt / Java 啊。

SilverRainZ 说:
Mar 12, 2022 11:09:29 PM

比如说这个: https://stackoverflow.com/questions/62614703/how-to-make-window-centered-in-gtk4

Avatar_small
依云 说:
Mar 14, 2022 02:06:27 PM

这就很 Java 了 QAQ
flameshot 不支持选窗口截图也是这种理由唉。

Coelacanthus 说:
Aug 20, 2022 08:37:14 PM

最近又发现了另一个问题,Qt 在加载可变字体时默认的 Regular 字重似乎是按字体最细渲染的

Avatar_small
依云 说:
Aug 21, 2022 10:23:57 AM

啊咧,我好像没遇到过。有复现的例子吗?


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com