当前位置: 首页 > 科技观察

浏览器如何渲染文本

时间:2023-03-18 22:29:34 科技观察

浏览器是最常用的软件之一,文本是网页中最重要的元素。浏览器显示文本的过程中有很多有趣的细节,值得一谈,或许可以减少一些误会。这是一个比较粗略和笼统的介绍,尽量不涉及过多的技术细节和具体实现,而是立足于为Web开发者和设计者提供一些正确的概念。下面的介绍主要是基于我对WebKit和Gecko(Firefox)的印象。其他浏览器也大致相同。如有遗漏,请指出。当浏览器从网络服务器接收到网页数据时,第一步是将其解码为可读的文本。由于历史原因,不同地区和语言的网页可能使用不同的编码方式,浏览器判断编码方式主要依据以下几种方式:Content-Type:text/html;charset=web服务器返回的HTTP头中的信息,一般优先级最高;网页本身的metaheader中Content-Type信息的charset部分,对于HTTPheader没有指定编码或者本地文件,一般都是这样判断的;如果找不到前两个,浏览器菜单通常允许用户强制指定编码;一些浏览器(如Firefox)可以选择自动编码检测功能,使用基于统计的方法判断未确定的编码。编码确定后,将网页解码成Unicode字符流,可以进行进一步的处理,比如HTML解析,但是我们这里略过HTML/XML解析的细节,只谈如何处理解析后的文本元素。因为我们得到的文本可能是多种语言混合的,里面可能有中文和英文,可能用不同的字体显示;也可能有从右到左书写的文本,例如阿拉伯语和希伯来语;它还可能涉及涉及复杂布局规则的印度脚本;另外可能还有网页本身指定的文字语言,比如Japanese标签,这样日文和汉字就可以使用日文字体显示(因为汉化导致这些汉字使用与中文汉字相同的码位,虽然很多写法不同),“lang”属性也可以出现在HTTP头中,或用于标记整个的全球语言文档,这通常是促进浏览器字体匹配的好习惯。为了统一处理所有这些复杂情况,我们需要将文本分成由不同语言组成的小段。在一些文本排版引擎中,这一步被称为“itemize”,分解后的文本段常被称为“textrun”,但具体的划分规则可能会根据不同的引擎而有所不同。比如HarfBuzz和ICU,一般是根据不同的排版类来划分的(通常称为“shaper”)。比如英文和法文可能使用同一个shaper排版,那么相邻的英文和法文文本会分到同一个run,而希伯来文需要另外一个shaper,所以分到自己的run。以HarfBuzz为例,它有这样的整形器:General(适用于大多数布局规则简单的语言,如中文、英文等)Arabic、Hebrew、Indian、Khmer、Burmese、Han,很多浏览器还是会在此划分下,在确定了具体使用的字体后,根据使用的字体不同,可能会把run分成更细的。这种运行可能被称为“SimpleTextRun”。以后shaper的工作就简化为将确定的文字用确定的语言、确定的字体排版,生成相应的字形以及它们应该放置占用的位置和空间。先详细说一下确定字体的步骤。说到字体,首先要提到的就是CSS中font和font-family的规则。比如这样一条规则:p{font-family:Helvetica,Arial,sans-serif;}strong{font-weight:bold;}如果对于这样一段文字:

Aquickbrownfoxjumpsoverthelazydog.

表示本段首先使用Helvetica家族的字体。如果找不到,可以找宋体。如果还是找不到,可以使用浏览器设置的默认sans-serif字体(有的浏览器,比如Safari,只给你设置,有的比如Firefox允许根据不同的语言设置,这时候你可以根据前面分析得到的文本运行语言信息来判断使用哪一个),这个过程很简单,大家都很好理解。稍微复杂一点的是“jumps”,它应该继承父元素的font-family,也使用Helvetica,但不是默认的Regular,而是使用Bold版本。找不到HelveticaBold就找ArialBold,不然就找浏览器设置的BrowseBold版本的字体,如果没有?需要考虑使用人工伪造来显示黑体。我们先不说这个。我们先来看看中文中常见的情况:CSS指定的字体没有覆盖我们需要的文字怎么办。比如上面的CSS规则,但是对于这样的文字:

Aswiftfox...

这里的“aswiftfox”应该用什么字体呢?假设在CSS中专门指定了中文字体,比如Helvetica、STHeiti、sans-serif,很简单,按照和英文字体一样的规则判断:逐个字符试一下,看当前字体是否提供了字形为字符,如果没有,再试下一步,如果最后没有找到匹配的字体怎么办?CSS规范简单的说执行“系统字体回退”,但是这个过程在不同的浏览器下可能会有很大的不同。例如,WebKit将使用字体系列列表中的第一个字体和该文本所属的语言。寻找后备字体。Times等衬线字体对应的中文后备字体是MacOSX下的STSong;而Firefox会根据设置中的sans-serif等通用字体系列和相应的语言进行匹配。对应语言的默认字体,例如MacOSX中默认的中文无衬线字体为华文黑体(STHeiti)。Linux下一般使用fontconfig根据语言、样式等参数选择fallback,但不同浏览器的实现可能不同;Windows下,一般使用系统的FontLinking机制,根据注册表中的FontSubstitutes信息寻找fallback。因为不同的浏览器在这里可能会有不同的行为,所以建议在CSS中指定对应平台应该使用的字体。具体的字体选择还是有一些细节不太容易注意,也是浏览器差异比较大的。可能会出现一些问题:是否支持选择字体的PostScript名称:比如STHeiti的Light版也叫STXihei,或者是否可以选择全名:部分浏览器无法正确映射font-weight或者font-styleCSS中字体对特定字体的要求,特别是当字体使用非标准样式名称时。其次,考虑到很多厂商都有自己的字体命名规则,这个其实很容易出现。HelveticaNeue的UltraLight、Light、Regular、Medium、Bold等不同字重如何对应CSSfont-weight值从100到900?向上?这是特别容易出现bug的地方。是否支持本地化名称选择:比如可以用“宋体”代表“SimSun”。以MacOSX下的浏览器为例,Firefox支持这种写法,而基于WebKit的浏览器一般不支持这样的问题。CSS规范并没有限制这样的问题,所以不管是什么情况,都是允许的。一般来说,如果要保证最大的兼容性,在写CSS的时候应该尽量选择清晰易错的写法,尽量少让浏览器决定(beexplicitinsteadofimplicit),虽然通常是隐式写法比较简洁,但是除非你百分百确定你要支持的浏览器在你要支持的平台上支持这种写法,否则不要轻易使用。CSS3中新增的@font-face规则是对现有规则的扩展,提供网页字体功能,但字体匹配算法的逻辑没有改变。详细的算法可以在CSS规范中找到。字体确定后,可以将文字、字体等参数交给具体的排版引擎生成字形和位置,然后根据不同的平台调用不同的字体光栅化器,将字形转换成最终显示在屏幕上的图案屏幕。一般浏览器会选择平台原生的rasterizer,比如MacOSX的CoreGraphics,Linux/X11的FreeType,Windows的GDI/DirectWrite等。关于这一步,可以参考typekit的这篇博客。浏览器的差异主要是因为使用的排版引擎可能对不同语言的支持不同,调用rasterizer的参数可能不同(比如是否开启亚像素渲染,使用的hintinglevel等),但是在同一个操作系统下效果不会有太大的不同。基于以上介绍,可以尝试提出一个在现有浏览器下为中文用户编写CSS字体选择规则的建议,如下:首先,确定元素选择字体应该使用的字体样式,比如serif字体,sans-serif字体应该是草书,奇幻等。确定样式后,优先选择西文字体,优先写本平台特有且在本平台效果较好的字体,比如下的Helvetica和ArialMacOSX,但是Helvetica(大概)效果更好,一般Windows下只有Arial,然后写Helvetica,Arial比Arial好,Helvetica或者只有Arial;然后列出中文字体,原理是一样的,多平台共享的字体尽量放在后面,特有的字体放在前面。还需要考虑到在MacOSX/Linux下,普通用户习惯使用(thin)黑体作为默认字体,而在Windows下,他们习惯使用Arial作为默认字体,比如STXihei,SimSun等写法比较普遍。如果写成SimSun,STXihei,但是安装在MacOSX上的SimSun效果不会很好。最后,你应该放上相应的通??用系列,比如sans-serif或serif。尝试使用字体的基本名称(例如在英语语言环境下显示)而不是本地化名称。特殊情况除外(Windows上的“某些”浏览器仅支持某些编码的本地化字体名称)。MacOSX下的字体名称可以在FontBook(菜单Preview->ShowFontInfo)中找到,Windows下的字体信息可以从微软的网站获取,Linux下可以使用fc-list命令查找/X11。请记住用空格将字体名称括起来,例如“AmericanTypewriter”和“MyriadPro”。最好在文档开头指定语言,比如,可以使用的语言标签在W3C说明中可以找到。原文链接:http://www.wufangbo.com/browser-render-text/