# 新时代新潮流WebOS 【21】WebKit，为了布局，忙并美丽

- 作者：邓侃
- 发布时间：2009-06-09 22:59
- 原文：https://www.ifanr.com/17017

如果没有1440年以后活字印刷术的大规模普及，或许就不会有文艺复兴运动，更不会有后来的启蒙运动。如果没有这两个运动的开展，或许就不会有世界范围的工业化。

在活字印刷术出现以前，每出版一本书，都必须先刻制一套模版，称为雕版，每套雕版上的每一个字，都是手工雕刻的。不仅制作雕版费时费力，而且有了错误不容易 更改。活字印刷术的进步在于，可以预先批量生产各种样式和大小的字体，称为活字。需要出版某一本书籍时，先制作该书的页面模版，模版做好以后，只需要把这 些活字摆放在模版上即可。如果出现错误，只需要调换某些活字，既省时又省力。如果某本书的模版不需要长期保存，还可以把模版中摆放的活字拆解下来，在印刷 其它图书时用，节约成本。

活字印刷术没有解决的问题:

- 图像的印刷。起初不能印刷笔触丰富，层次复杂的图像，一直到1796年，石板印刷术(lithography)出现以后，才能印刷表现手段丰富的图像。
- 灵活的布局排版。纸张大小不同，布局排版也不同，布局变了，需要重新摆放活字，而且有时候还需要改变字体和大小。

灵活的布局排版对于纸质书籍来说，或许并不太重要，但是对于电脑浏览器来说，却必须实现完全的自动化。否则，每当用户改变浏览器窗口的大小的时候，页面内容就不能正确显示。对于手机浏览器来说，布局排版的自动化尤其重要，因为不同手机的屏幕不一致，而且屏幕分辨率也不同。

但是即便是浏览器，也没有摆脱传统的排版方式。所谓传统的排版方式，基本是 横平竖直的，单一的鸟瞰视角。

[![Figure 1. Incunabulum, the end of 15'th century](https://s3.ifanr.com/wp-content/uploads/2010/08/1_thumb4.jpg "Figure 1. Incunabulum, the end of 15'th century")](https://s3.ifanr.com/wp-content/uploads/2010/08/117.jpg)

*Figure 1. Incunabulum, the end of 15’th century*

[![Figure 2. City of Words, by Vito Acconi, 1999](https://s3.ifanr.com/wp-content/uploads/2010/08/2_thumb3.jpg "Figure 2. City of Words, by Vito Acconi, 1999")](https://s3.ifanr.com/wp-content/uploads/2010/08/27.jpg) *Figure 2. City of Words, by Vito Acconi, 1999*

Figure 1 中显示的是1490年代的书籍，不难看出，现代书报中广泛使用的双列，边注，页码，首字母大写等等，都是继承了500多年以前的做法。而CSS规范，囊括了所有这些页面设计的要素。

在当今信息爆炸的形势下，如何安排页面的布局排版，在有限的页面面积内，承载更多内容，突出读者关注的内容，增强页面设计的视觉美感，成为不可回避的问题。 例如，手机购物的UI设计，既要包含商品简介，又要包含用户意见反馈，还要包含实物照片，以及各个不同商场的标价等等。完美的页面设计，不仅要求简练而清 晰，而且也不能遗漏相关内容，实在是一件困难的事情。可以说，手机购物之所以不普及，与手机购物的UI设计笨拙而丑陋是相关的。

要巧妙地 设计手机应用的UI设计，终极而言，需要突破传统的单一鸟瞰视角的方式，Figure 2 就是这方面的尝试。Webkit能不能做到这一点？原理上是可以做到的，但是必须修改源代码。但是在改造以前，我们还是先踏踏实实研究一下，Webkit 的布局排版的内部机制是什么。只有充分了解对方之长，才有可能改进对方之短。

读解Webkit排版布局与绘制的具体实现以前，首先需要明确的是，Webkit把排版布局(layout)，与绘制(paint)，分开处理。

Layout负责确定Render Tree中，每个叶子和中间节点的位置。每个节点在屏幕上的显示，都呈长方形格局。所谓位置，指的是这个长方形左上角起始坐标(X,Y)，以及长方形的宽度和高度。每个中间节点的长方形，里面嵌套着若干小长方形，对应这个中间节点的后代节点等等。

在Layout过程结束以后，Webkit启动 Paint过程，负责把Render Tree中各个叶子节点，在相应的位置绘制出来。Webkit 把具体绘制的工作，交给第三方图形工具库(Graphics Library)去完成。常用的第三方图形工具库包括QT，GTK+，Wx，Skia，Cairo等等。

打个比方，图形工具库相当于活字，以及绘制图像的石板(lithography)，它们负责paint。而从严格意义上来说，Webkit的主要工作是layout，也就是排版布局，相当于版面模版。

关于图形库，台湾的开源高手，黄敬群(Jim Huang / jserv)，写过一篇[介绍Google Skia 图形库的文章](http://blog.linux.org.tw/~jserv/archives/002095.html)。文中谈到，Google 为了搭建Android平台，于2005年8月并购了Android公司。同年11月份，Google还收购了Skia公司。2007年11 月，Google发布Android，并公开部分源代码。当人们热衷于探究Android Dalvik VM的奥秘的时候，忽略了Skia的意义。

2008年9月，Google发布了以改良的Webkit为核心的Chrome PC浏览器。当人们热衷于探究V8 JavaScript引擎等等功能模块时，再次忽略了Skia的意义。  
Skia是一个2D图形工具库，该产品的特色在于，能够在手机等等移动设备中，以较低的内存和CPU消耗，呈现高品质的2D图形。

Skia 的创办人，Mike Reed，是图形技术方面的顶尖人物。Mike早年任职于Apple，参与QuickDraw GX项目，处理字型和图像显示。后来他跳槽到OpenWave，开发手机浏览器。在OpenWave工作期间，与Benoit Schillings合作，在50-300KB的内存空间内，提供图层之间alpha blended方式的预览，以及全功能向量矩阵转换等等，真可谓螺丝壳里做道场。后来Benoit Schillings离开OpenWave，去Trolltech任职CTO。Trolltech的主打产品是大名鼎鼎的QT。再后来Trolltech 被Nokia并购，Benoit随之加入Nokia。Benoit Schillings离开OpenWave不久，Mike Reed也离开了OpenWave，去创建Skia公司。

[![Figure 3. Layout implementation in Webkit](https://s3.ifanr.com/wp-content/uploads/2010/08/3_thumb2.jpg "Figure 3. Layout implementation in Webkit")](https://s3.ifanr.com/wp-content/uploads/2010/08/35.jpg) [*Figure 3. Layout implementation in Webkit*](http://www.flickr.com/photos/87209438%40N00/3609632247/sizes/l/)

[![4](https://s3.ifanr.com/wp-content/uploads/2010/08/4_thumb3.jpg "4")](https://s3.ifanr.com/wp-content/uploads/2010/08/45.jpg)

[*Figure 4. Paint implementation in Webkit*](http://www.flickr.com/photos/87209438%40N00/3609632249/sizes/l/)

Figure 3 和 Figure 4， 分别显示了Webkit执行排版布局(layout)，以及绘制(paint)的两个过程。仔细查看这两张sequence diagrams，会发现以下特点：

1. Layout 和 Paint 这两个过程完全分开。开始执行Paint过程以前，必然预先执行过Layout，否则图形库就不知道在哪里写字以及显示图像。但是这并不意味 着，Layout执行结束后，随即就立刻执行Paint。实际上，Layout执行结束后，触发一个事件，这个事件启动Paint过程。但是Paint过 程也可以被其它事件触发，譬如屏幕内容的切换，以及把隐藏的浏览器窗口复原等等。
2. Layout 涵盖了所有CSS规定的布局要素。包括页面边缘与内容之间的空白，文字对插入图像的避让(floating)，单列与多列，上下层覆盖(z-index)等等。
3. 图像，视频播放器插件，Applet等等，在 Layout 被称作 Replaced Render Object。这些 Replaced 元素的宽度和高度可以由CSS规定。如果CSS没有规定，就解析这些元素的数据流，譬如一个JPG照片的metadata里，规定了这幅照片原件的宽度和 高度。如果元素自己也没有规定宽度高度，就使用Webkit提供的缺省值。
4. 文字的宽度根据页面的排版来确定。譬如一页中包含多列文字，则每列文字宽度相等。每列文字的宽度，乘以列数，加上列与列之间的夹缝，加上页面边缘空白等 等，应当等于页面总的宽度。假设页面总的宽度已知，边缘空白，和列与列之间的夹缝的宽度也已知，就可以反推文字的宽度。
5. Render Tree中每个节点在屏幕上的显示，都呈长方形格局。前面第3点和第4点，描述了宽度的确定。而高度的确定，取决于这个中间节点的所有后代节点的高度的总 和。对于 Replaced 元素来说，它的高度相对比较容易确定，而文字段落的高度，需要根据字数，字型，以及字体大小计算得出。
6. 在 Layout 过程中，反复出现以 Repaint 为开头的子过程，例如 repaintAfterLayoutIfNeeded()。这些子过程的意义在于，当确定了某个节点的高度和宽度以后，需要对其前辈节点，和左右兄弟节 点的位置，做适当调整。严格意义上来讲，这不是repaint，而是relayout。
7. 相对于 Layout 过程，Paint 过程的逻辑要简单得多。Paint的过程，大致按照深度优先的顺序，遍历整棵RenderTree。也就是说，从最左边的叶子节点开始，从左向右逐个绘制 RenderTree所有可以显示的叶子节点。所谓“可以显示的叶子节点”，是因为CSS中可以规定，不显示某些叶子。

反复研究以上Layout和Paint的过程，我们有以下看法：

1. Layout 是一个计算量很繁重的过程。之所以繁重，主要体现在估算完每个RenderTree节点的宽度尤其是高度以后，需要相应调整这个节点的前辈节点以及左邻右舍兄弟节点的位置。对于文字段落而言，它的高度有赖于字数，字体和大小，所以估算不容易准确。  
    有没有可能把Layout 过程，与第一遍 Paint 过程合二为一？ 只要遍历一次RenderTree的所有叶子节点，绘制图像并码字。Paint过程结束后，各个叶子节点对应的长方形的起始位置的(X,Y)坐标，以及宽 度和高度都自然迎刃而解。然后再由叶子节点开始，逐步确定RenderTree中，各个中间节点的起始位置和宽度高度。这样做的好处是，可以大大降低 Layout 过程的成本。
2. Layout 过程假设每个RenderTree 的节点都对应一个长方形屏幕区域。受限于这个规定，类似于Figure 2的效果，就显示不出来。有没有可能取消这个限制？SVG不仅提供了强大的绘图能力，而且也提供了强大的排版布局能力。能不能把CSS当着SVG格式的一 个子集来看待？
