级别: 初级 Benoit Marchal (bmarchal@pineapplesoft.com), 顾问, Pineapplesoft
2003 年 9 月 01 日 XSL 格式化对象(XSL Formatting Objects,XSL-FO)标准为控制印刷文档的布局提供了一些功能强大的特性。这篇技巧文章向您演示如何控制换页符的插入以获得外观更好的文档。我将首先介绍使用商业 XSL-FO 渲染器的标准方法,然后介绍一个变通方法,让您可以使用开放源码的 FOP 来应用同一技术。
XSL 格式化对象(XSL-FO)标准是 XSL 标准中最鲜为人知的部分之一(人们更熟悉的部分是 XSLT)。这种情形令人遗憾,因为对于将 XML 文档制作成 PDF 或 Postscript 文件这样的任务,XSL-FO 会非常有用。一般说来,XSL-FO 控制 XML 文档的布局和表现形式。实际上,大多数用户发现 HTML 页面适合在电脑屏幕上阅读,但他们更愿意 PDF(因此使用 XSL-FO)作为印刷副本。
控制换页
XSL-FO 的优点之一是可以自动将文档的文本按需要放在多个页面上。若一页满了,XSL-FO 自动插入下一页。遗憾的是,这一算法有时可能会在错误的位置(如在段标题与段内容之间)插入换页符。同样,它有时将图像标签与实际图像分开,或将表头与表中的行分开。
图 1 和图 2 说明了这种问题。在图 1 中,段标题在页的底部,而该段的余下部分却在另一页上。
图 1. 段标题后面不恰当的换页
在图 2 中,图像标签与图像分开。
图 2. 图像标签后面不恰当的换页
之所以出现这些问题,是因为 XSL-FO 渲染器要处理一长串块,但却不知道这些块之间的关系。它不知道标题是……嗯,一个标题。解决方案是告诉 XSL-FO 渲染器哪些块相互关联,或(更明确地说)哪些块应保留在一起。为此,标准在 keep 和 break 类别中定义了几种特性。
keep-with-previous 和
keep-with-next 特性指定块应和前一块还是下一块保留在一起。
这些特性应用于
within-line 、
within-column 和
within-page 组件。顾名思义,这些组件控制进行块分组的级别。通常,我使用
within-page 组件。
可用值有
auto (不作特殊处理)、
always (始终将这些块放在同一页)或一个整数。整数指定优先级,这样,当数个
keep 特性发生冲突时,优先级数字最大的居先。
always 在所有值中优先级最高。
清单 1 摘录自一个样式表,使用了
keep 特性来防止标题与段落之间、或标签与图像之间的换页。在 XSL-FO 中,其特性就是样式表的属性。(请参阅
参考资料,下载附带的代码以获得完整的样式表。)
清单 1.
keep 特性
<xsl:template match="doc:title" mode="keep">
<fo:block font-family="Times Roman"
font-size="12pt"
space-after="0.6em"
keep-with-next.within-page="always">
<xsl:apply-templates mode="keep"/>
</fo:block>
</xsl:template>
<xsl:template match="doc:figure" mode="keep">
<fo:block font-family="Times Roman"
font-size="8pt"
font-style="italic"
space-after="0.5em">
<xsl:value-of select="@label"/>
</fo:block>
<fo:block keep-with-previous.within-page="always">
<fo:external-graphic src="url({@src})"
width="{@width}"
height="{@height}"/>
</fo:block>
</xsl:template>
|
然而,需要警告的是,FOP(Formatting Objects Processor,格式化对象处理器)— Apache 的开放源码 XSL 处理器 — 没有完全实现
keep 特性。如果您使用
清单 1,FOP 就会忽略
keep 特性,而这样仍可能会导致不恰当的换页。就我所知,只有象 RenderX 的 XEP 或 Antenna House 的 XSL Formatter(请参阅
参考资料)这样的商业渲染器实现了
keep 特性 — 至少在我写这篇文章时是如此。
巧用 FOP
既然商业实现对标准提供了更全面的支持,购买其副本可能是目前最佳的解决方案。但是,获得许可证的费用大约是每台服务器 US$5,000(尽管受限的工作站许可证可能只要 US$79),所以并不是每个项目都有能力购买自己的 XSL-FO 渲染器。在项目的早期(此时预算有限),情况更是如此。
尽管存在通过 FOP 使用
keep 特性的变通方法,但它只是局部的解决方案。FOP 只识别表中行的
keep 特性。这一有限的支持可用来将表头与表的余下部分保留在一起,但在完全支持出现之前,也可以利用它作为变通方法。该解决方案依赖所谓的
隐形表(专为布局引入的表,但正如其名,它是不可见的)。如果您编码过 HTML,那么对于仅为布局而使用表的方式(譬如为了模拟列),您应该已经比较熟悉了。
清单 2 摘录了另一个样式表,它演示了使用 FOP 的隐形表中的
keep 特性。这个版本的样式表可防止不恰当的换页。模板
doc:figure 创建一个隐形表,其中标签在第一行而图像在第二行。
keep-with-previous 特性应用于第二行。
清单 2. 隐形表
<xsl:template match="doc:figure" mode="blind">
<fo:table table-layout="fixed" width="100%">
<fo:table-column column-width="proportional-column-width(1)"/>
<fo:table-body>
<fo:table-row padding-bottom="0.5em">
<fo:table-cell>
<fo:block font-family="Times Roman"
font-style="italic"
font-size="8pt">
<xsl:value-of select="@label"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row keep-with-previous="always">
<fo:table-cell>
<fo:block>
<fo:external-graphic src="url({@src})"
width="{@width}"
height="{@height}"/>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
<xsl:template match="doc:section" mode="blind">
<fo:table table-layout="fixed" width="100%">
<fo:table-column column-number="1"/>
<fo:table-body>
<fo:table-row keep-with-next="always">
<fo:table-cell>
<xsl:apply-templates select="doc:title" mode="blind"/>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell>
<xsl:apply-templates select="*[2]" mode="blind"/>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
<xsl:apply-templates select="*[position() > 2]" mode="blind"/>
</xsl:template>
|
模板
doc:section 类似。它同样创建了一个有两行的隐形表。标题在第一行,第一段在第二行。选择第一段时必须当心。我使用根据位置来选择段的方法以提高灵活性。
实际上,隐形表最适合用于图像标签和表头,而不是段落标题。FOP 不能分割表单元,这意味着如果将较长的段包括在隐形表中,那么 FOP 将不会分割这一段。这进而会导致一些严格说来不必要的换页。您需要考虑在文档的何处使用这一技术。
改进方法之一可能是只对短的段生成隐形表(可以用
string-length() 函数计算段的长度)。
结束语
感谢 XSL-FO,有了它,将 XML 文档转换成 PDF 就不再困难,XSL-FO 赋予您对文档最终布局的许多控制权,本篇技巧已经演示了这一点。
参考资料
关于作者
对本文的评价
|