Visio是一款功能强大、拥有大量客户群的办公室矢量绘图软件系统,以其独具特色的模具、模板、拖曳式绘图方式及智能化绘图等技术而风靡全球。为了促进Visio文档信息与其他应用系统的充分融合与共享,特别是适应于基于Internet的应用模式,微软公司于2004年宣布以免费授权的方式提供Visio文档的Reference Schema for Visio——“DataDiagramML”。

    Visio XML文档以“VisioDocument”作为根标记,其子节点则主要以“表(Sheet)”、“节(Section)”、“行(Row)”及“单元(Cell)”的层次结构描述文档的形状信息、文本信息、数据信息、控制信息、结构信息及部分元信息。文档高层节点的子孙关系如图1所示,“StyleSheets”、“Masters”和“Pages”是Visio XML文档的三个重要一级子节点标记,分别定义了文档所基于的样式、主控形状和页面构成,而隶属于“Pages”层次结构中的“Shape”节点则是文档的基本、核心和主要的组成要素,并可作为“Masters”层次结构下相应主控形状的实例而被初始化,同时可继承 “StyleSheets”中所定义线型、填充和文本等样式属性。此外,“Shape”节点的“类型(Type)”属性设有“Group”和“Shape”选项,这样便可利用前者对形状进一步分组,使得“Shape”节点自身又可组成复杂的树型结构,而其中的底层节点又可继承高层节点的部分属性。此外,“Masters”层次下的“Shape”节点除作为“Pages”层次下“Shape”对象的“基类”外,还是用户自定义的线型、线段端点和填充形状的定义与描述区域。


    与微软其他的Office软件相类似,Visio规定了其内部的变量类型、度量单位及运算函数,但Visio利用公式的范围更加宽泛,特别是单元(Cell)级节点中属性的定义几乎全部规定为公式。而这种公式的应用又具备其鲜明的特点:其一,尽管随着用户利用Visio软件所进行的文档编辑操作其取值可能随时变化,但每个公式在任何时点的相应取值均被计算出来并在XML文档中被静态地记录,这样就使得第三方的程序不必涉及过多的函数及公式的解析与运算;其二是对于长度单位来说,无论用户指定何种度量单位,Visio均将其转化为内部的单位(英寸)并记录在XML文档之中,这样亦使得第三方程序不必涉及过多的单位换算。

2     Shape对象的构造
    2.1    Shape的坐标体系及变换


    Visio以ShapeSheet表格对Shape对象的几何结构及文本属性进行描述,其中含有两个重要的节,即“Shape Transform Section”和“Text Transform Section”,在Visio的XML文档中,这两个节的标记分别为“XForm”和“TextXForm”,其中分别描述了Shape对象的几何形状和文本的位置与范围。以“XForm”节点为例,通过其子节点“PinX”、“PinY”、“Width”、“Height”、“LocPinX”、“LocPinY”、“Angle”、“FlipX”、“FlipY”的节点值即可完全确定形状的几何位置、大小、旋转角度及水平或垂直翻转状态, 其原理如图2所示。


    利用Java AWT中的AffineTransform类可以容易地实现这种变换。假设Shape所基于的“上下文坐标变换”——对Visio文档显示进行缩放、平移及翻转等变换或其所属组对象的变换——为Tx,则为正确显示Shape所进行的坐标变换应该为:

    Tx.clone().translate(PinX, PinY). rotate(Angle). Translate(-LocPinX,-LocPinY).scale(-FlipX,-FlipY)

    2.2    Geom对象的构造
    Visio在ShapeSheet中以Geometry(XML文档中的标记为“Geom”)节来描述图形的几何形状,其中所包含的图元对象如图1中“Geom”的子节点所示。从概念和原理上来看,Visio Shape几何形状的类模型如图3所示,Geom对象则相当于其中的Path对象,而图1所示作为“Geom”子节点的诸多图元则相当于其中的Curve对象。


    Jave AWT中的GeneralPath类被我们用来实现图3中“Paths”对象的相关功能,但为此必须进行两种必要的功能拓展。首先是Geom通过其子节点“NoLine”和“NoFill”节点值可指定“无线条”或“无填充”的绘图方式,因此我们在实现的过程中实例化了两个GeneralPath对象,即“linePath”和“fillPath”,分别用于管理Shape中线条与填充的路径信息;其次是我们针对GeneralPath类的“append(Shape s, boolean connect)”方法填加了“inverseAppend(Shape s, boolean connect)”方法,用来逆向追加路径节点。

对于Geom所属的“MoveTo”、“LineTo”、“InfiniteLine”和“PolylineTo”图元,GeneralPath类具有实现相应功能的函数,而对于其中的“ArcTo”、“EllipticalArcTo”或“Ellipse”图元,则利用初等变换的方法即可实现其到Java AWT中的“Arc2D”类和“Ellipse2D”类的转换。以“EllipticalArcTo”图元为例,其与“Arc2D”类在构造函数的语义上存在着较大的差别,如图4所示。不难看出,通过稍有复杂的初等参数变换,两种模型即可以相互转换。


对于Geom节点所属的“NURBSTo”和“Spline”这两个高级图元,我们根据相应样条函数的插值算法对GeneralPath对象进行了直接描绘,从而避开了与Java相应类的复杂转换与接合问题。



2.3    Foreign对象的构造
作为微软公司的产品,对象链接与嵌入(OLE)技术的普遍应用是Visio的必备功能。在Visio文档中Foreign对象作为Shape对象的一个子类型而被定义,主要通过“Foreign Image Info Section(在XML文档中的标记为Foreign)”和“ForeignData”两个节来封装有关功能,其中前者说明了对象的显示窗口区域,后者则封装了所链接或嵌入的对象与数据。

“ForeignData”元素的“ForeignType”属性对所链接或嵌入对象的类型进行了说明,包括三个选项,即“Bitmap”、“Metafile”和“Object”。其中第一个选项表示嵌入的对象是栅格类型(raster-based)的图片,具体的子类型将由“CompressionType”属性进一步说明,包括DIB、JPG、PNG、TIFF、GIF等子选项;第二个选项表示嵌入的对象是MS元文件,并由图形数据本身来区别标准元文件或增强型的元文件;第三个选项表示所嵌入的是OLE2对象,并由“ObjectType”进一步说明其链接或嵌入的方式。“Bitmap”和“Metafile”两个选项说明所嵌入的对象是Visio内部的数据类型,因此在“ForeignData”节点值中存储着相应标准图形文件的Base64编码;而在“Object”选项的情况下,节点值则封装了相应的OLE对象与数据。

在如上所述的嵌入或链接的对象类型中,一些流行的图形标准如JPG、PNG、TIFF、GIF等被Java所支持,而OLE和Com技术作为Windows操作系统的核心很难具有跨平台的现实意义。因此,Bitmap和Metafile两种类型的图形对象便成为我们构造的重点。利用纯Java技术,我们全面重构了这两种图形文件的解析、显示及缩放等功能,篇幅所限,这里不能对此进行更深入的讨论。

2.4    Text对象的构造
Text对象是Visio文档中最为复杂的对象,可以说它基本上具备了MS Word软件文本处理的所有功能,在ShapeSheet表格中设有多个区(对象)来设定或说明文本显示的位置、格式与方式信息,也可从StyleSheet中继承多种格式对象,这些对象与Text对象的关系如图5所示,图中的“pp”、“cp”、“tp”和“fp”分别是作为Text节点值的格式化文本中指向相应段落、字符、制表位及字段对象的指针。

在重构中,我们通过Java Text包中AttributedString类的实例来作为Text对象中格式化文本的承载体。具体过程为,首先根据Text节点值中诸多的的“fp”指针对其中的公式或格式进行计算或转换,然后基于转化后的字符串实例化一个AttributedString对象;尔后基于诸多的“cp”指针和相应“Char”对象中的属性设置已实例化AttributedString对象的TextAttribute类中相应的静态属性,并基此创建一个LineBreakMeasurer对象;最终根据段落、文档块等其它相关对象的设定,利用LineBreakMeasurer对象对格式化文本进行逐行裁剪,并显示在指定窗口所要求的位置上。

尽管Visio中的Char类与Java Text包中的TextAttribute类对文本格式进行设定的“属性——值”设计不尽相同,如双下划线、文本的上下标等,但基本上能够完整地再现Shape中文本的格式与内容。此外,由于Visio设有“中文字体”和“西文字体”两种格式选项,这就迫使我们利用Java String类中的正规表达式匹配技术来识别文档中中文与西文字符并设置相应的字体,在效率上稍有损失。

3     样式对象的构造与应用
这里所谓的样式对象是指图1顶部标记为“样式”的三个对象,即“Colors”、“FaceNames”和“StyleSheets”类型的对象。其中前两者即“Colors”和“FaceName”分别是文档的颜色表和字体表,以便相关的样式、形状或图层对象以表中的序号对它们进行引用,而第三者“StyleSheets”对象才是实现文本样式定义功能的主要机制,它由多个“StyleSheet”对象组成,其中每个对象以文档中唯一的“ID”作为索引号。

“StyleSheet”可在“Line Format Section”(XML文档中的标记为“Line”)和“Fill Format Section”(XML标记为“Fill”)分别定义线型与填充的模式,亦可在与图5相对应的“Char”、“Para”、“Tab”和“Field”等标记下定义相应的文本显示样式,还可以通过“StyleSheet”元素的“LineStyle”、“FillStyle”和“TextStyle”属性值作为ID来继承或引用 “StyleSheet”对象中的相关属性。当然,Shape等对象在继承或引用“StyleSheet”中的样式对象时,亦可以通过上述三个属性值作为ID来引用不同StyleSheet对象的属性,同时还继承了来自MasterShape中的样式定义,从而形成了复杂的样式对象继承、引用或重定义关系。



对于Visio的内置线型(Line元素的LinePattern属性取值范围为1~23),利用实例化Java AWT的BasicStroke类即可完整地实现;对于Visio内置的前24种填充模式,利用Java AWT的TexturePaint实例亦可方便地实现,对于编号为25~40的填充模式,利用Java AWT的GradientPaint亦可实现其中的大部分功能。对于Visio所支持的自定义填充模式,利用构造TexturePaint对象也可全面实现有关的功能,但对于Visio所支持的自定义线型功能,情况则略有复杂。

Visio利用“Master”节点下的“Shape”对象,可以描述用户自定义线型、填充及线段端点的图形样式。由于Java AWT的Graphics2D类中描绘线条的“g.draw(shape)”函数与“g.fill(g.getStroke().getStrokedShape(shape))”函数的功能相同,而Visio的自定义线型要求在描绘中保留完整的自定义形状属性,如不同的色彩、不同的填充模式等,如图6所示,因此试图通过实例化BasicStroke类或者通过实现Stroke接口的方法均无法完整地实现这种功能。

在重构过程中,我们利用Java AWT的FlatteningPathIterator类,构造如前如述Shape的线条路径对象“lineShape”和用户自定义线型形状的直线线段迭代器,并根据“lineShape”迭代器中线段的倾斜度而对自定义线型中相应线段进行转角,最终描绘在形状轮廓线附近的适当位置,算法较为复杂,其效果如图6所示。



4     组件、接口及嵌入式应用
为了尽可能地拓展系统的应用范围,我们将Visio浏览器的所有相关功能打包成一个Java库文件,即“VisioCom.jar”,并精心设计了系统的接口,使得浏览器组件既可以嵌入到基于Java的本地应用中,亦可以通过Java Applet嵌入到基于Web的网络应用中,其中网络应用模式如图7所示,并在我们所承担的某电子政务项目中得到了成功的应用。Applet小程序及VisioCom.jar组件可通过CodeBase加载并嵌入到网页中,Applet小程序建立VisioViewer对象,并可调用该对象的有关函数打开远程的Visio XML文档,执行平移、旋转、关闭、平铺、放大、缩小、打印等操作。在此过程中,整个Visio文档作为一个独立的操作对象,即用户不能对其中的形状进行个别操作。

除了全面实现Visio浏览器的功能类别外,在“VisioCom.jar”库中还提供了三个接口,即“VisioPanel”、“VisioListener”和“CoordTransfer”三个接口,其中第一个接口用于组件获取显示Visio文档的窗口,这样可使组件向不同的屏幕窗口或打印外设进行绘图;第二个接口便于应用程序获取组件对鼠标等事件的处理结果;第三个接口设计了两个坐标转换回调函数,即“transCoord(Rectangle2D.Float rec)”和“inverseTransCoord(Rectangle2D.Float rec)”,便于用户的应用程序在屏幕坐标和诸如地理坐标等逻辑坐标间进行相互转换,增强了接口的自然性和可用性。

用户的应用程序在实例化VisioViewer对象后,只需利用Java AWT中的“addMouseListener”和“addMouseMotionListener”方法将该实例加入到事件列表,便可实现VisioViewer组件与用户应用程序的事件接口,使得对Visio文档显示的诸多操作如平移、旋转、缩放、打印等与用户的应用程序完全透明,降低了系统的耦合度。

5     结束语
本文描述了一种跨平台网络浏览器的实现方法及关键技术,由于篇幅所限,我们不能对其中的相关技术进行详细与深入的讨论。需要说明的是,由于Java AWT与Visio软件在图形标准、规范和处理方式上的细微差别,使得我们所实现的浏览器不能与Visio软件的显示效果百分之百地保持一致,如上面所述对GradientPaint类的应用以及元文件显示所采用的RasterOp等。尽管如此,由于Visio是一款功能强大的矢量绘图软件,本文所述的重构亦不失为一项有意义的工作,特别是对于后续的带有编辑功能的完整绘图软件的开发,将起到先导和基础性的作用。