有关在Java中实现文档打印的典型说法描述了一个复杂的过程,它要求对字体进行测定、对文本进行解析并将结果绘制到一个Graphics对象中。这个过程似乎执行起来相当困难,并且它和用于文档视频显示的高级编程方法不一致。假如你想要花费大量的精力来完成这个过程,那么你就不会首先想到要在Java中编程。
你一定不想自己完成测定、解析和绘制过程,而是希望通过简单地将文档发送到一个能为你处理所有事情的对象中来完成这个任务。本文就将介绍这样一个对象――DocumentRenderer,它将一个文档作为方法中的一个参数并处理指定任务来完成打印。比如,用这个类来显示一个Html文档需要两个步骤:构造一个DocumentRenderer类的实例并将HTML文档作为参数发送到print(HTMLDocument)方法中。DocumentRenderer类用于处理打印该文档所必需的开销,包括显示一个打印对话框以及格式化文本。下载用于该类的源代码(DocumentRenderer.java)。
我们设计了这个DocumentRenderer类以便利用这个已经在Java中可用的高级文本性能。 根据可重用和可扩展类的原则,我们使用了一些现有的对象(Java Swing Text Package用它来对显示结果进行格式化)使打印结果能够显示在纸上。用这种方法设计DocumentRenderer使我们能够用比前面讨论过的方法少写近200行的代码来建立这个类。
除了能少写代码之外,使用DocumentRenderer 实现中的现有对象还提供了一些额外的功能使得该类更加通用。在最初设计这个类的时候,我们只是打算将HTML文档打印出来。而添加一些功能来打印其他类型的文档则是后来的想法。当我们发现只需在用于HTML 打印类的代码中加上约6行的额外代码就可以打印一个Rich Text Format文档时,便在这个项目的后期添加了这个功能。
显示文档
DocumentRenderer能够用来打印几类包含在JEditorPane中的文档。我们用三种类型的文档(JEditorPane能够缺省识别的)测试了这个printer类:HTMLDocuments、PlainDocuments和Rich Text Format文档。只需要做一些很小的改动,这个类就应该可以将包含在JEditorPane中的其他类型文档打印出来了。
DocumentRenderer类将一个文档的打印形式从其视频显示中区分出来了。这就使你能够针对特定的打印结果进行文本格式化,而会不影响其屏幕显示。DocumentRenderer采用所有打印页面的实际大小来显示文本并计算出行间距(line break)。当文档的宽度不足以显示在打印页面上时,该类会答应使用缩放(scaling)。
DocumentRenderer相当智能。分页符(page breaks)不会将一行单一语句分放到两个页面上。字符也不会被切成两半,不会象浏览器在处理一般打印时会在这一页末尾显示一句话的上半部分而在下一页的顶部显示这句话的下半部分。这个类能够处理大量字体、颜色和小图标。分栏文本(columnar text)的显示也没有问题。对于每一个JEditorPane能够显示的文本特性,通常DocumentRenderer都可以将这种性能呈现到纸上。
你只需使用两行代码便可以将DocumentRenderer结合到你的程序中去了。用一个不带参数的构造器创建这个类的实例,再调用一个合适的打印方法来处理其他事情。比如,以下代码将打印这个htmlDocument,它是HTMLDocument类的一个有效实例:DocumentRenderer DocumentRenderer = new
DocumentRenderer();
DocumentRenderer.print(htmlDocument);
它给用户显示一个打印对话框,答应用户选择打印机、打印数量等,同时还有一个取消打印的选项。
PlainDocuments使用了print(PlainDocument)方法,采用和HTMLDocuments相同的方式执行打印。由于在Java中不能直接访问Rich Text Format文档,所以你必须将这种类型的文档发送到DocumentRenderer中(通过将它封装到一个JEditorPane里),就象这样:DocumentRenderer.print(jeditorPane);
这里的jeditorPane是JEditorPane的一个有效实例,其中包含一个Rich Text Format文档。
为了给用户提供方便,你可以调用DocumentRenderer的pageDialog方法来显示一个打印对话框以便用户可以调整页面大小、页边设置和纸的打印方向(orientation)。DocumentRenderer还提供了一种方法使开发人员可以选择是否对那些无法在打印宽度内完全显示的文档进行按比例缩放。我们认为能够进行缩放通常是比较好的选择,因为它能够防止文本在正确的页边处被分开,但它似乎比较适合让用户去选择。这种名为setScaleWidthToFit(boolean)的方法提供了按比例缩放的选项。你必须确定在调用打印方法之前调用这个scaling和pageDialog方法。
了解DocumentRenderer
DocumentRenderer用于执行显示一个打印对话框并通过使用在Java Swing Printing API中可用的标准化工具开始打印操作。由于在使用这个DocumentRenderer类时无需完全了解这个API,而且该API已经在很多地方被具体描述过(见资源),因此这里我们就不再介绍它了。这个用于DocumentRenderer类的源代码中还包括了这个打印逻辑的完整文档。
然而我们或许应该解释一下DocumentRenderer用来在单独打印页面中定位文本的过程,以便你了解这个类所提供的改进功能,这会帮你回顾这个Java打印过程通常遵循的显示文档的逻辑。
文档通常是以一种简单的方式进行打印的。首先,文档会被放入JEditorPane。你可以将打印过程想象为在JEditorPane 的上面放置一个矩形框(其大小和页面打印区的大小相等),并对其中的内容进行打印而无需关心外面的部分。
这个矩形的上沿与JEditorPane 顶部齐平,矩形框内部的区域会被绘制(paint)。假如矩形的下沿穿过文本,不用去管它;字符会在打印页面的底部被分开。打印第二页时,矩形的上沿被向下移至前面被下沿所占据的那一行,该过程被重复执行。由于第二页正好在第一页结束的地方开始,因此在第一页底部被分开的字符会出现在第二页的顶部。后面几页也是一样。
为了避免从行中断开,DocumentRenderer会仔细检侧文档以测定是否一个单独文本和页面完整契合。这样会比只是在JEditorPane中放一个矩形框并打印其中内容的效果更佳。
绘制视图(View)
假如将JEditorPane看作仅包含了一个文档的方法,你就不能测定所有文本的位置或大小了。一个文档对于这个任务来说太大了,文档或许会契合于一个单一页面,或许不会。为了使文档能够完整契合于打印纸,你必须将它分成一些小的部分以便对每个部分的位置进行检测。
幸运的是,Java Swing Text Package提供了一个View类,它能使你将文档分成单一的、适于绘制的部分。你可以将JEditorPane想象成是由几个视图部分组成的;现在你就可以完全基于这些小部分的大小和位置来打印文档了。
View类的子类用于处理可视性组件(visual component)中的显示和打印文本的任务。然而,处理打印文本的许多程序员没有意识到的是视图可以在显示到纸上时提供这些相同的功能。尽管对视图问题进行具体讨论是本文以外的话题,但在讨论文档打印时对它有一个大致的了解还是很有必要的。
在Swing中,视图被当作处理文本显示的容器。在树型目录中一个根视图可以有多个视图分支。在这些分支的端部会显示代表真实文本的叶视图(leaf view)。
将这个视图的树型结构当作一个由单一的、大的、包含整个文本的视图来考虑。这个文档视图被分成几个段落视图,它被依次分成几个单独的行。尽管真实工作中的视图情况要比这个简单的描述复杂的多,但该例子中显示了如何通过视图来将一个文档分成契合于打印纸的小的部分。通过查看每一行,你可以测定它是否完全契合于打印纸而不会在底部被分开。假如行数契合,就执行打印,假如不契合,则将它记录下来以便在下一页中执行打印。
包含在JEditorPane中的视图采取了一种和在JPanel中的组件行为相类似的操作。一个主要区别在于视图不要求布局治理器(layout managers)来进行位置处理;它们会自己参与布局。这样一来,在JEditorPane中的视图就会象一个真实的组件和布局治理器一样进行操作。视图知道如何查看、如何绘制自己以及在哪里显示其子文档。
视图并不是被直接建立的。更确切地说,它们是由ViewFactory子类的工厂(factory)来生成的。一个ViewFactory生成一个文档并将它们分成根视图以及所需的分支视图和叶视图。工厂会按照这种方式来处理这些乏味的解析文档和计算布局的工作。
你很少能够直接和这些工厂打交道。对很多部分来说,它们是被自动调用的。在JEditorPane中设置文档并调用JEditorPane.validate()方法来将文档发送的适合的工厂中,该工厂则会返回所需的视图。然后这些视图会被用在组件的布局上。
打印视图
DocumentRenderer类能够将需要打印的文档放入jeditorPane中,它是JEditorPane的一个实例。jeditorPane的宽度决定了打印页面的大小而且它会调用一个验证方法来执行布局。DocumentRenderer不会显示这个JEditorPane,因此屏幕显示不会生效。需要被打印的根视图是通过一个有点复杂的jeditorPane用户界面调用来获得的:View rootView =
jeditorPane.getUI().getRootView(jeditorPane);
这个rootView及其子视图可能会对所需信息进行查询以便对打印文档进行布局。这些视图在每部分文本的绘制环境(graphical context)中提供了坐标和大小。通过这些信息你就能够测定这部分文本是否和打印页面相契合。假如契合,则DocumentRenderer将执行打印;假如不契合,该类将对这个用来打印这部分文本而不将其分开的分页符进行测定。
由于视图知道如何自行绘制,因此你无需自己设置字体或颜色。DocumentRenderer以多种字体和颜色通过调用一个简单视图的paint方法来处理式样文本(styled text)。
然而这个视图的树型结构也存在一个问题。你不能确定在一个代表打印文本的叶视图结束之前分支视图从根视图中被分出了几次。你可以通过使用DocumentRenderer 类中的一个简单的循环方法来解决这个问题。
printView方法循环经过视图的分支结构来查找可打印的叶视图。这种方法将一个视图作为其中一个参数。它从根视图开始对每个视图进行检查以测定它是否有相关的子视图。假如有,则printView会依次调用每个子视图将其作为其视图参数。这样一来,这个方法最终会运行至整个树型结构。当该方法发现一个不带子视图的叶视图时,它会在用于打印的绘制环境中检测它的位置。这个方法只用于绘制那些完全契合于该环境的可打印部分的叶视图。当一个叶视图分跨(straddle)页面的底部时,这个方法就会记录该页面上沿的位置以便使下一页从这个位置开始。因此分页符就不会从文本的行当中断开了。
假如你想要更深入地了解打印视图的用法,你可以查找DocumentRenderer类的源代码。我们对它进行了注释讲解。由于它只有不到200行的代码,因此我们有充足的时间来给它加上注释。
该类的局限
我们在Java SDK 1.3和1.4版本中对DocumentRenderer进行过测试,尽管它应该是适用于实现Swing的所有Java版本的。DocumentRenderer是通过标准的Java技术来运行的,因此它不能实现比Java本身更好或更糟的文本绘制。在JEditorPane中不能显示的字符也将无法显示在纸上。
Java在Windows环境下对文本进行测定时有一个小问题。由于文本没有被精确地测定,一些位置可能有些偏离。由于这些错误很小所以通常不是什么大问题,但它会在对文本右对齐(right-justified )时变得很麻烦。因此,要尽量避免使用右对齐方式。DocumentRenderer类不会产生这个问题,它似乎也不会出现在Linux环境中。
最后要说的是,大图标是不能被打印出来的。在Java中不能将它们显示在页面中,但小图标是没有问题的。
在Java中执行打印不再会是一个复杂的问题了。只需简单地写两行代码并通过DocumentRenderer类来将高级的文本打印功能添加到你的程序中可以了。