前言 OFD是国家标准版式文档格式,于2016年生效。OFD文档国家标准参见《电子文件存储与交换格式版式文档》。既然是国家标准,OFD随后肯定会首先在政务系统使用,并逐步推向社会各个方面。OFD是在研究当下各类文件格式后,推出的标准,有如下优点:

1 产权属于自主产权

2 具有便携性:文件小,可压缩比率大。测试显示生成的文件体量比PDF还要小。

3 具有开放性:易于入门,对于使用者来说更具开放性。

4 具有扩展性:预留了可扩展入口和自定义标引,设置了非接触式引用机制,为特性化提供支持。

5 呈现效果与设备无关,在各种设备上阅读、打印或印刷时,版面固定、不跑版。

6 应用广泛:无论是电子商务、电子公务,还是信息发布、文件交换,档案管理等都需要版式文档的技术支持。

  关于标准,我也要吐槽一下。OFD标准是国内几家专业的电子文档处理公司参与起草的;标准文档(注:以下用”标准”特指OFD标准)只有126页,在我看来,标准对技术细节的描述过于简单,没有一定的技术背景很难看懂。与此形成鲜明对比的是pdf标准,有1000多页。我在网上也没找到文字版的标准,特别不利于阅读和参考。

  我最近一直研究ofd标准,试图写一款阅读器,已初有成果,界面如下:

阅读器下载地址: https://download.csdn.net/download/qq_29939347/11799156

本文就把我开发的过程做简单介绍。

OFD标准简介

  简而言之,OFD存储是采用压缩技术,描述采用XML格式。这一点与微软的word文档(docx)格式很类似。标准可能参考了微软的处理方式;在技术上也要实事求是,国标这种格式不是独创和领先的。将OFD格式文件解压后,会看到如下目录和文件:

文件中会包括资源文件(图片、字体库等)。XML会对资源存放,图元(文字、图像等)显示做描述,阅读软件会根据这些描述呈现出一致的显示效果。

开发OFD阅读软件步骤

国内流行的ofd阅读软件应该是福昕和数科开发的,这两款我都用过。我还要吐槽一下:

  1)福昕阅读器帮助文档是ofd格式,但是无法用数科的阅读器打开。

  2)有些ofd文档中xml标记,在标准中找不到,是某些公司独创的?

  这些软件都是用C++开发的,用到了QT。同样情况下,相比于C#,C++开发软件难度肯定会大增。在windows平台开发界面,WPF应该是最好的库了。WPF虽然出现十几年了,大家好像对此还很陌生。主要现在是BS的天下;不是WPF不够好,是生不逢时。

1 对OFD文件解压缩

  OFD文件其实就是压缩文件,解压后的文件也有目录结构。该模块的功能是获取每个文件的路径和数据。

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;namespace WpfOfdReader.OfdFileType
{class OfdFileReader{ZipArchive _zipArchive;public void ReadZipFile(string fileName){_zipArchive = ZipFile.OpenRead(fileName);}public void Close(){if (_zipArchive != null)_zipArchive.Dispose();}public List<OfdFileItemInfo> AllFileItem{get{return _zipArchive.Entries.Select(o => new OfdFileItemInfo(o)).ToList();}}private ZipArchiveEntry GetArchiveEntry(ZipFilePath path){foreach (ZipArchiveEntry entry in _zipArchive.Entries){if (entry.FullName == path.FulleName){return entry;}}return null;}public static byte[] GetFileBuffer(ZipArchiveEntry entry){List<byte[]> listBuffer = new List<byte[]>();using (Stream s = entry.Open()){while (true){byte[] buffer = new byte[10];int n = s.Read(buffer, 0, buffer.Length);if (n <= 0)break;if (n == buffer.Length){listBuffer.Add(buffer);}else{Array.Resize(ref buffer, n);listBuffer.Add(buffer);break;}}}int totalLen = 0;listBuffer.ForEach(o => totalLen += o.Length);byte[] result = new byte[totalLen];int index = 0;foreach (byte[] buffer in listBuffer){Buffer.BlockCopy(buffer, 0, result, index, buffer.Length);index += buffer.Length;}return result;}}
}

 2 找到需要展示的page

  顺着路线 OFD.xml --> Document.xml --> Pages,找到最终需要展示的page页。Page页包含三类节点:TextObject、PathObject、ImageObject,暨对应标准中的三类图元。需要对这三类节点建模。这三个类共同继承父类PageObject。所有的图元都有绘制区域、坐标变换、裁剪等共性,所有这些由PageObject类处理。

    public class PageObject{public string ID { get; set; }public PageLayer ParentLayer { get; set; }public string PageFileLoc => ParentLayer.ParentPage.PageFileLoc;XmlNode _xmlNode;public string Boundary { get; set; }public string CTM { get; set; }public OfdClipsGroup ClipsGroup { get; set; }public void SetPageObject(PageLayer layer, XmlNode xmlNode){_xmlNode = xmlNode;ID = XmlHelper.GetXmlAttributeValue(xmlNode, "ID");ParentLayer = layer;Boundary = XmlHelper.GetXmlAttributeValue(xmlNode, "Boundary");CTM = XmlHelper.GetXmlAttributeValue(xmlNode, "CTM");foreach (XmlNode childNode in xmlNode.ChildNodes){if (childNode.Name == OfdClipsGroup.XML_Name){ClipsGroup = OfdClipsGroup.FromXml(childNode);break;}}}public string GetAttributeValue(string name){string result = XmlHelper.GetXmlAttributeValue(_xmlNode, name);return result;}}

 3 创建WPF显示模型

图像精确定位需要用到Canvas控件作为容器。绘制大量图形需要用到轻量级绘制模型DrawingVisual。在此基础上,派生了绘制基础模型OfdVisual,此模型对应PageObject。

    public class OfdVisual : DrawingVisual{public OfdVisual(){}protected DrawingCanvas _drawingCanvas;public DrawingCanvas DrawingCanvas{get{return _drawingCanvas;}}public bool IsAddToCanvas{get{return _drawingCanvas != null;}}internal void AddToCanvas(DrawingCanvas drawingCanvas){if (_drawingCanvas == drawingCanvas)return;_drawingCanvas = drawingCanvas;_drawingCanvas.AddVisual(this);}public void ReomveFromCanvas(){if (_drawingCanvas != null){_drawingCanvas.DeleteVisual(this);}}public virtual void Show(bool visiable, bool even = false){}public Point BoundaryLocation { get; set; }public Size BoundarySize { get; set; }public MatrixTransform ObjectTransform { get; protected set; }public VisualClipsGroup ObjectClipsGroup { get; protected set; }public void SetPageObject(PageObject pageObject){OfdHelper.ParseBoundary(pageObject.Boundary, out Point location, out Size size);BoundaryLocation = location;BoundarySize = size;if (!string.IsNullOrEmpty(pageObject.CTM)){ObjectTransform = OfdHelper.OfdTextToTransform(pageObject.CTM);}if (pageObject.ClipsGroup != null){ObjectClipsGroup = new VisualClipsGroup() { ClipsGroup = pageObject.ClipsGroup };}}protected Rect ClipRect{get{return new Rect(0, 0, BoundarySize.Width, BoundarySize.Height);}}protected RectangleGeometry ClipGeometry{get{RectangleGeometry geometry = new RectangleGeometry(ClipRect);return geometry;}}protected void PutBoundary(DrawingContext dc){TranslateTransform translateBoundary = new TranslateTransform(BoundaryLocation.X, BoundaryLocation.Y);dc.PushTransform(translateBoundary);dc.PushClip(ClipGeometry);}protected void PopBoundary(DrawingContext dc){dc.Pop();dc.Pop();}protected void PutTransform(DrawingContext dc){if (ObjectTransform != null){dc.PushTransform(ObjectTransform);}}protected void PopTransform(DrawingContext dc){if (ObjectTransform != null){dc.Pop();}}}

有三种类型绘制对象OfdVisualText、OfdVisualPath、OfdVisualImage,派生自OfdVisual。分别处理三种图元数据。所有的绘制操作在函数

public override void Show(bool visiable, bool even = false);

对应文本,绘制函数如下:

 void DrawText(){using (DrawingContext dc = RenderOpen()){if (ObjectClipsGroup == null){PutBoundary(dc);PutTransform(dc);DrawTextInner(dc);PopTransform(dc);PopBoundary(dc);}else{foreach (VisulClip visulClip in ObjectClipsGroup){PutBoundary(dc);visulClip.PutClip(dc);PutTransform(dc);DrawTextInner(dc);PopTransform(dc);visulClip.PopClip(dc);PopBoundary(dc);}}}}private void DrawTextInner(DrawingContext dc){int i = -1;double deltaXTotal = 0;double deltaYTotal = 0;Point pt = new Point();foreach (FormattedText formattedText in FormattedTextCollection){i++;if (i != 0){if (DeltaCollectionX != null){double deltaX = DeltaCollectionX.GetValue(i - 1);deltaXTotal += deltaX;}if (DeltaCollectionY != null){double deltaY = DeltaCollectionY.GetValue(i - 1);deltaYTotal += deltaY;}}pt.X = TextLocation.X + deltaXTotal;pt.Y = TextLocation.Y + deltaYTotal - FormattedTextCollection.FontBaseLine;dc.DrawText(formattedText, pt);}}

绘制前,需要对当前坐标做变换、旋转、剪切等操作。

后记 编写阅读器类软件的关键是建模。首先读懂标准,对标准中描述的图元做归类分析,并建立起相应的显示模型。本人做WPF开发很多年了,感觉用WPF开发这类软件并不是非常的难。我用不到两周的时间,初步完成了OFD显示开发。如果要完整实现OFD标准,还需要大量的开发,我会逐步完善该软件的功能。

转载于:https://www.cnblogs.com/yuanchenhui/p/ofdreader.html

采用WPF技术,开发OFD电子文档阅读器相关推荐

  1. OFD电子文档阅读器功能说明(采用WPF开发,永久免费)

    特别说明 ofd阅读器开发语言为c#,具有完全自主产权,没有使用第三方ofd开发包.可以根据你的需求快速定制开发.本阅读器还在开发完善阶段,如有任何问题,可以联系我.博客:https://www.cn ...

  2. 下载的电子发票打不开?ofd电子文档你可以这样做

    如今随着线上支付的普及,发票也有原来的纸质改为电子版,可在我们刚适应PDF电子发票的使用后,发票又改为专用的OFD版,于是很多人都会苦恼下载后的电子发票打不开,教你几招让ofd电子发票也像PDF一样好 ...

  3. iStylePDF安全电子文档解决方案之评标报告专家签字

    所谓评标,是指按照规定的评标标准和方法,对各投标人的投标文件进行评价比较和分析,从中选出最佳投标人的过程.目前很多地方已经实现电子化招投标,评标结束后需要出具评标报告也电子化,势必评标专家必须在评标报 ...

  4. iStylePDF安全电子文档解决方案之电子合同在线订立

    对于许多企业和政府机构而言与客户.合作方.委托人进行妥善的文件交换对他们的成功起着至关重要的作用.由于这些企业必须依靠快速并简单的信息共享于是他们开始将基于文档的业务流程放到网上以提升其运营的质量.效 ...

  5. 企业电子文档管理需要注意的点及解决措施

    随着计算机技术和网络在企业管理中的普遍应用,电子文件成了企业业务活动中最直接的记录.电子文件作为一种数字化信息,从产生.处理.传递至整理.保管.利用,形成电子档案,丰富了档案管理内容,也对档案工作人员 ...

  6. Aed电子文档与无纸化办公

    Aed电子文档与无纸化办公 一.当前阻碍无纸化办公的根本原因 十几年来人们怀着极大的热情,投入了大量资金,来研发.建设和使用计算机办公系统.然而,十几年过去了,人们看到的却是电子文档和纸质文档并行.纸 ...

  7. 亿赛通开启电子文档安全领域智能化新时代

    文档加密发展历史 数据加密产品起源于公元前2000年,他作为保障数据安全的一种方式发展至今,最早由埃及人使用特别的象形文字最为信息编码.但随着时间的推移,巴比伦.美索不达米亚和希腊文明都开始使用一些方 ...

  8. 如何制作电子文档CHM(How to gernerate chm from assembly)

    2011-09-04 01:12:09|  分类: 默认分类 |  标签:chm  assembly  build  generate |字号大中小订阅 目的  大多数情况下,开发人员编写的代码最后都 ...

  9. 企业电子文档管理系统哪个好?怎么选?

    选择一款企业电子文档管理系统(EDMS)时应该关注什么? 这完全取决于你需要实现的控制.协作和灵活性水平. 然而,有两个关键的电子文档管理系统功能是你应该要关注的.   ● 简单配置的工作流程   你 ...

最新文章

  1. Linq to SQL 资源
  2. 操作系统学习:进程、线程与Linux0.12初始化过程概述
  3. SharePoint 2007 SDK v1.5
  4. python中requests.session的妙用
  5. 牛客14605 画三角
  6. linux ipset 流量,linux中ipset命令的使用方法详解
  7. CCNA之三:RIP协议
  8. 史丹·温斯坦称傲牛熊市的秘密
  9. 1040. Airline Company
  10. asp.net 独立缓存服务器的研究
  11. 20161212 输出1到n之间所有的奇(单)数(n30000) 。
  12. iMazing比iTunes好用在哪些地方
  13. 概率论中两个独立连续随机变量X,Y,变量Z=X+Y的密度函数为X,Y的卷积与特征函数原理
  14. 黑客游戏-梦之光芒1~14攻略
  15. uva 509 RAID!(磁盘数据)
  16. MATLAB冒号用法
  17. web开发技巧-网页排版布局常见问题及解决办法
  18. 解决VSCode终端Ctrl+V无法粘贴问题
  19. 5G课程笔记--华为ICT课堂(初学)(二)
  20. flyme最新7基于android,终于来了,魅族开始基于Android 7.0版本的Flyme内测

热门文章

  1. CSS 垂直居中的正确打开方式
  2. 计算窗口Z轴位置的过程分析
  3. [汇编语言]更灵活的定位内存地址的方法
  4. 听说三年前那些月入30K的程序员都是这样开始轻松构建算法交易机器:一点python基础+一点运气
  5. ajax 提交 form表单 ,后台执行两次的问题
  6. 关于Transitions-Everywhere
  7. 详解Fiddler Everywhere安装及入门使用
  8. Redis修炼秘籍筑基篇 — 3分钟教你安装连接测试Redis
  9. 虚存的用法计算机组成原理,计算机组成原理_第8讲:虚拟存储-2015秋.pdf
  10. 关于游戏现状的一点想法