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

2009-06-09 22:59

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

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

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

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

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

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

Figure 1. Incunabulum, the end of 15'th century

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

Figure 2. City of Words, by Vito Acconi, 1999 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 图形库的文章。文中谈到,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 Figure 3. Layout implementation in Webkit

4

Figure 4. Paint implementation in Webkit

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 格式的一 个子集来看待?
登录,参与讨论前请先登录

评论在审核通过后将对所有人可见

正在加载中

移动互联网的围观者、起哄者、以及肇事者。

本篇来自栏目

解锁订阅模式,获得更多专属优质内容