文字渲染讨厌你


文字渲染:有多复杂? 事实证明这是极具挑战性的! 据我所知,几乎没有系统显示“完美”的文本。 更好的地方,更糟的地方。

假设您想要具有任意字体,颜色和样式的任意文本,并具有换行和文本突出显示支持。 实际上,这些是正确显示复杂文本,终端窗口,网页等的最低要求。

通常,让我们马上说:没有连续的正确答案,一切都比您想像的重要得多,并且一切都会影响到其他一切。

我们将讨论不包含在单个概念中的主题,这些只是我在Firefox中渲染文本的几年中不得不面对的问题。 例如,由于我对此不太感兴趣,因此我们不会过多地讨论文本分割或特定平台的各种文本库的管理问题。

1.术语


文本的性质很复杂,英语很难传达所有细微差别。 对于本文档,我将尝试遵守以下条款。 请注意,这些词不是“正确的”,我只是发现它们对于将主要概念传达给没有语言学经验的英语母语者很有用。

字符:

  • 标量(标量):Unicode标量,Unicode中的“最小单位”(它也是一个代码点)。
  • 字符:扩展的Unicode字素簇(EGC),Unicode中的“最大单位”(可能由几个标量组成)。
  • 字形(glyph):以字体给出的渲染的原子单位。 它通常在字体中具有唯一标识符。
  • 连字:由多个标量和可能甚至几个字符组成的字形(母语者可以将连字表示为多个字符,但对于字体,它只是一个字符)。
  • 表情符号:“全彩”字形。

字型

  • 字体:将字符映射到字形的文档。
  • 写作/写作(脚本):一组构成某种语言的字形(通常,字体实现某些脚本)。
  • 手写字体(草书):字形相互接触并相互融合的任何字体(例如阿拉伯语)。
  • 颜色:字体的RGB和alpha值(在某些用例中不是必需的,但这很有趣)。
  • 样式:字体的粗体和斜体修饰符(在实际实现中,通常还会提供提示,别名和其他设置)。

2.样式,布局和形式是否相互依赖?


以下是简要概述,可让您大致了解典型的文本呈现管道的工作方式:

  1. 风格化(解析标记,字体查询系统)。
  2. 布局(将文本分成几行)。
  3. 成形,成形(字形及其位置的计算)。
  4. 将所需字形栅格化到纹理图集/缓存)。
  5. 组成(将字形从图集复制到所需位置)。

不幸的是,这些步骤并不像看起来那样简单。

大多数字体实际上并不会按需生成所有可能的字形。 字形过多,因此字体通常仅实现特定的字母。 最终用户通常不知道或不关心此问题,因此如果字符不可用,可靠的系统应级联其他字体。

例如,尽管以下文本的标记并不表示多个字体,但是在任何系统上进行正确渲染都是必要的:hello 好नीببسم好。 因此,我们危险地接近步骤1(样式化)开始依赖于步骤3(成形)的事实!

(或者,您可以采用Noto方法,并使用包含所有字符的一种Uber字体。尽管那样,用户无法自定义字体,也无法为所有平台上的用户提供“本机”文本界面。但是,假设您需要一个更可靠的决定)。

同样,对于布局,您需要知道每段文本要占用多少空间,但这只有在整形后才能知道! 步骤2是否取决于步骤3的结果?

但是要进行造型,您需要了解布局和样式,因此我们似乎陷入了困境。 怎么办

首先,程式化应用作弊。 尽管我们确实希望获得完整的字形,但是标量足以用于样式设置。 如果字体不支持正确书写,它将不会声称对该书写的标量一无所知。 因此,您可以轻松地找到“最佳”字体,如下所示:

对于文本中的每个符号(EGC),我们都会查询列表(级联)中的每种字体,是否知道组成该符号的所有标量。 如果是这样,请使用它们。 如果我们没有结果到达列表末尾,我们就会得到豆腐( ,缺少字形指示符)。

与表情符号会面时,您可能已经看到了这样的指示! 由于某些表情符号实际上是几种较简单表情符号的连字,因此字体可以通过仅发行单个组件来表示对字符的支持。 这样 可能看起来像 如果字体“太旧”以致于无法了解新的连字。 如果您有一个“过旧”的Unicode实现,它不知道新字符,从而迫使样式系统接受这样的部分匹配,也会发生这种情况。

因此,现在我们确切地知道我们将使用哪种字体,而不必引用布局或形式(尽管整形可以改变我们的颜色,更多内容请参见下一部分)。 我们可以类似地处理布局和形式的相互依赖性吗? 不行 诸如段落分隔符之类的东西会给您带来艰难的换行符,但是唯一的成型方法是通过迭代成型!

有必要假定文本放在一行上,并形成一行直到空格用完。 此时,您可以执行排版操作,并找出在何处打断文本并开始下一行。 重复直到一切完成。

3.文字不是单独的字符


仅从英语来看,您可能会认为连字是一种奇怪的废话。 我的意思是,谁真正在乎“æ”被拼写为“ ae”? 但是事实证明,某些语言基本上完全由连字组成。 例如,ड्ببسم由各个字符«ببسم组成。 在任何高级文本呈现系统(即在任何主浏览器中)中,这两行看起来都将非常不同。

不,这与Unicode标量和扩展字素簇之间的区别无关。 如果您要求可靠的Unicode系统(例如Swift)给出该行的扩展字素簇,它将给出这五个字符!

字符的形状取决于其邻居: 文本不能正确显示一个字符一个字符

也就是说,您应该使用成形库。 这里的行业标准是HarfBuzz ,这些任务很难自行解决。 因此,请使用HarfBuzz。

3.1。 文字叠加


在手写字体中,字形通常会重叠以避免接缝,这可能会引起问题。

让我们再来看看मनीممنش。 看起来正常吗? 现在增加:



它看起来仍然很漂亮,但让我们将文本部分透明。 如果您使用的是Safari或Edge,则文字可能看起来不错! 但是在Firefox或Chrome上,视图很糟糕:



问题是Chrome和Firefox试图作弊 。 他们正确地形成了文本,但是一旦遇到此类字形,他们仍在尝试分别绘制它们。 这通常可以正常工作,除非透明和重叠会导致这种变暗。

“正确”的实现方式会将文本带到没有透明感的临时表面,然后带到透明的场景。 Firefox和Chrome不会这样做,因为它很昂贵,主要西方语言通常不需要。 有趣的是,他们真的很了解这个问题,因为他们专门处理了表情符号这样的脚本(但我们稍后会再讲)。

3.2。 样式可以改变连字


好的,我们这个例子主要是出于好奇,我们正在分析标记如何破坏,尽管我不知道有什么可能会严重损害标记的合理情况。 这是两条内容相同但颜色不同的文本:



它们在Safari中的外观如下:



这就是他们在Chrome中的外观(使用新的样机实现时 ):



它们在Firefox中:



总结:

  • Safari不足
  • Chrome解析字形,但掉落许多颜色
  • Firefox同时解析字形并显示颜色

我认为每个人都应该使用Firefox,对吗? 但是如果放大,我们会发现他做的事情很奇怪:



他只是简单地将这种连字分为四个相等的部分,并用不同的颜色!

问题在于,对于这里该做什么,确实没有合理的答案。 我们将连字分为不同的样式,并且从某种意义上讲,由于连字是渲染的“单位”,因此简单地拒绝支持这种分离是有意义的(就像大多数人一样)。

由于某些原因, Firefox中的某个人真的很热衷于进行更优雅的实现 。 他的方法是使用最佳蒙版和不同颜色绘制多次连字,效果出奇的好!

尝试支持这些“部分连字”有某种意义:只有整形可以知道是否会显示特定的连字,而这取决于系统字体,因此连字可能会出现在没有人期望的地方! 一个经典的英语示例是从用户安装的超链接边框上的字体进行连字。

英文可以在单词中间改变,但不能手写字体,这也很奇怪。

甚至不问有关用部分连字打断行的代码。

4.表情符号的颜色和样式


如果像本机系统一样显示表情符号,则需要忽略文本颜色设置(透明性除外):



通常,表情符号具有自己的本机颜色,并且该颜色甚至可能具有语义,如肤色修改器一样。 而且:它们可以有几种颜色!

据我所知,表情符号之前没有这样的问题,因此不同的平台有不同的解决方法。 有些显示表情符号为实心图片(Apple),另一些显示为一系列单色图层 (Microsoft)。

后一种方法还不错,因为它可以与现有的文本呈现管道很好地集成在一起,“只是”将字形分解为每个人都习惯使用的一系列单色字形。

但是,这意味着当您绘制“单个”字形时,样式可能会反复变化。 这也意味着“一个”字形自身可以重叠,从而导致上一节提到的透明性问题。 尽管如此,浏览器确实可以正确地结合表情符号中图层的透明度!

可以用三种方式解释这种差异:

  • 您已经在寻找彩色字形以特殊方式处理它们,因此它们很容易选择特殊的布局路径。
  • 透明度差的手写字体看起来有些丑陋,但表情符号会完全分解并变成难以辨认的字符集,因此额外的工作是合理的。
  • 西方开发者更关心表情符号,而不是阿拉伯和马拉地语等语言。

选择您喜欢的选项。

但是,如何用斜体或粗体突出显示图释? 忽略这些样式? 它们应该合成吗? 谁知道...

另外,这些表情符号看起来不奇怪吗?

是的,由于某种原因,许多系统秘密地增加了表情符号的字体大小,以使它们看起来更好。

5.平滑是地狱


文本中的字符非常小而详细。 文本易于阅读非常重要。 听起来像是一项平滑的任务! 地狱,480p确实是低分辨率。 更加平滑!!!

因此,主要有两种类型:

  • 灰度平滑
  • 亚像素平滑

灰度平滑是一种“自然”的方法。 基本思想是部分涂层的像素获得部分透明性。 在构图期间,这将导致像素获得适当的色相,从而改善整体细节。

术语“灰色阴影”用于一维颜色,就像我们的一维透明度(否则,字形以一种纯色显示)一样。 此外,在白色背景上的黑色文本的典型情况下,抗锯齿实际上沿边缘显示灰色阴影。

亚像素抗锯齿是一种滥用显示器正常像素放置的技巧。 它要复杂得多,因此,如果您真的有兴趣,则必须阅读更详细的文档,这里只是对高级概念的简要说明。

监视器中的像素实际上是红色,绿色和蓝色的三小列。 如果要变红,您说“白色黑色黑色”。 同样,如果要获得蓝色,请指定“黑色黑色白色”。 换句话说,如果您喜欢鲜花,则可以水平分辨率提高三倍,并获得更多细节!

您可能会认为这样的“彩虹”会很丑陋,但实际上该系统运行良好(尽管有些人对此表示反对)。 人脑喜欢识别模式并使之平滑。 但是,如果您使用亚像素平滑处理来截取文本的屏幕快照,则在调整图像大小或只是在显示器上以不同的亚像素布局查看图像时,您会清楚地看到所有其他颜色。 这就是为什么带有文本的屏幕截图通常看起来非常奇怪和糟糕的原因。

(通常,此系统还意味着图标的颜色可能会意外更改其感知的大小和位置,这确实很烦人)。

因此,亚像素抗锯齿是一个非常干净的技巧,可以显着提高文本的清晰度,太好了! 但是,不幸的是,这也是一个巨大的分裂!

注意,在任何抗混叠系统中都会发生子像素字形偏移 。 您始终希望将栅格化的字形捕捉到完整像素,但是栅格化本身是针对特定的子像素偏移量(介于0和1之间的值)设计的。

要理解这一点,请想象一个具有灰度平滑的1x1黑色正方形:

  • 如果其子像素偏移为0,则在栅格化过程中只会出现黑色像素。
  • 如果子像素偏移为0.5,则在光栅化时,两个像素的灰度为50%。

5.1。 亚像素偏移打破字形缓存


栅格化字形需要大量的计算,因此最好将它们缓存在纹理地图集中。 但是如何缓存具有亚像素偏移量的纹理? 每个偏移都有其独特的光栅化!

在这里,您需要找到质量和性能之间的折衷,这可以通过优化子像素偏移来实现。 对于英文文本,合理的平衡将是缺乏垂直子像素精度,而水平偏移与整数的四分之一相关。 这样只剩下四个子像素位置,这仍然可以极大地提高质量,同时保持合理的缓存大小。

5.2。 平滑子像素不能合成


灰色阴影下的抗锯齿的一个不错的功能是您可以随意使用它,并且可以优雅地降级。 例如,如果用文本(缩放,旋转或变换)转换纹理,则纹理可能会变得有点模糊,但通常看起来是正常的。

如果对子像素抗锯齿功能执行相同操作,则效果会很糟糕。 他的整个想法是操纵显示器上的像素。 如果显示像素与纹理像素不匹配,则红色和蓝色边缘将清晰可见!

您可能会认为,这只是通过在新位置进行新的字形栅格化来“解决”。 确实,如果转换是静态的,则可能可行。 但是,如果变换是动画 ,它将变得更糟。 : , , , .

, , ( ). , JS, «».

, . , R, G B ( ), , , .

-, . , .

… Firefox. , - : -. , , R, G B. , .

, :

  • Retina .
  • ( ).
  • MacOS .
  • Chrome, , ( , ).
  • Firefox (webrender) Alpha.

6.


— , .

6.1. SVG


. Adobe, SVG. SVG ( , Source Code Pro SVG, -), SVG, .

SVG ? 不行吗 好啊 , , (Firefox - - -).

6.2.


( ), , , . :

  • ( ).
  • ( , ).
  • (, ).

6.3. — ,


, (), () ().

, :

بسم الله لا !!


, - . , , .

, , . : . , .

, .

.


, .

6.4. , ?


, . «». () . , .

, , , ? 嗯

, , 0-9 AF, . , Firefox : !

Firefox - 16 . , .



6.5. ( , )


, , .

, - .

, . Webrender .

, . , :

: .

: .

, ! , «». , .

6.6.


, , . , , , . ( ).

, , . « » ( ).

:

  • ( ).
  • (, ).
  • / , , , , . .

, , (Core Text, DirectWrite FreeType ).

7.


:

Source: https://habr.com/ru/post/zh-CN469529/


All Articles