c# 基于wpf,开发OFD电子文档阅读器
作者:源之缘 发布时间:2023-09-08 00:10:02
前言
OFD是国家标准版式文档格式,于2016年生效。OFD文档国家标准参见《电子文件存储与交换格式版式文档》。既然是国家标准,OFD随后肯定会首先在政务系统使用,并逐步推向社会各个方面。OFD是在研究当下各类文件格式后,推出的标准,有如下优点:
1 产权属于自主产权
2 具有便携性:文件小,可压缩比率大。测试显示生成的文件体量比PDF还要小。
3 具有开放性:易于入门,对于使用者来说更具开放性。
4 具有扩展性:预留了可扩展入口和自定义标引,设置了非接触式引用机制,为特性化提供支持。
5 呈现效果与设备无关,在各种设备上阅读、打印或印刷时,版面固定、不跑版。
6 应用广泛:无论是电子商务、电子公务,还是信息发布、文件交换,档案管理等都需要版式文档的技术支持。
关于标准,我也要吐槽一下。OFD标准是国内几家专业的电子文档处理公司参与起草的;标准文档(注:以下用”标准”特指OFD标准)只有126页,在我看来,标准对技术细节的描述过于简单,没有一定的技术背景很难看懂。与此形成鲜明对比的是pdf标准,有1000多页。我在网上也没找到文字版的标准,特别不利于阅读和参考。
ofd阅读器程序(已集成了转图、转PDF功能)下载。
我最近一直研究ofd标准,试图写一款阅读器,已初有成果。具有ofd转换为pdf、转为图片等特色功能。界面如下:
本文就把我开发的过程做简单介绍。
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页包含三类节点: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开发这类软件并不是非常的难。相比于QT,采用wpf开发有很多优势。如果要完整实现OFD标准,还需要大量的开发,我会逐步完善该软件的功能。
特别说明
ofd阅读器开发语言为c#,具有完全自主产权,没有使用第三方ofd开发包。可以根据你的需求快速定制开发。本阅读器还在开发完善阶段,如有任何问题,可以联系我QQ:13712486。
来源:https://www.cnblogs.com/yuanchenhui/p/ofdreader.html


猜你喜欢
- 微信支付最近公司要在微信公众号上做一个活动预报名,活动的门票等需要在微信中支付。微信支付前的准备微信支付需要一个微信支付商务号(https:
- 1.对包名的判断,异常则说明不存在:try {PackageManager pm = getPackageManager();pm.getP
- 本文,将介绍如何通过Java后端程序代码在PDF中创建工具提示。添加工具提示后,当鼠标悬停在页面上的元素时,将显示工具提示内容。导入jar包
- 今天无意中发现一个圆形进度,想想自己实现一个,如下图:基本思路是这样的:1.首先绘制一个实心圆2.绘制一个白色实心的正方形,遮住实心圆3.在
- sqlite是啥?1、一种轻型数据库2、关系型数据库3、占用资源很低,几百K内存,适合嵌入式设备4、支持windows、linux、unix
- 一、安装activeMQLinux环境ActiveMQ部署方法:https://www.aspxhome.com/article/16232
- 一、背景我们都知道 http 协议只能浏览器单方面向服务器发起请求获得响应,服务器不能主动向浏览器推送消息。想要实现浏览器的主动推送有两种主
- 文章描述这个效果可能很多人都在抖音看到过,即把一个短视频,转成数字、字母等乱码组成的形式进行播放。开发环境.NET Framework版本:
- 先来看看效果:一、添加依赖库的步骤1.项目的gradle文件内的做以下改动allprojects { repositories
- 前言不久之前,部门进行了一次代码评审。代码整体比较简单,该吹B的地方都已经吹过了,无非是些if else的老问题而已。当翻到一段定时任务的一
- 一、前言程序中经常会用到TabControl控件,默认的控件样式很普通。而且样式或功能不一定符合我们的要求。比如:我们需要TabContro
- 肝了两天,重新整理了下时间工具类,以后我就以该时间工具类进行项目开发了,后会不定期更新功能,也欢迎留言需求,让工具类不断的完善。常量介绍相关
- 一、在学习枚举之前,首先来听听枚举的优点。1、枚举能够使代码更加清晰,它允许使用描述性的名称表示整数值。2、枚举使代码更易于维护,有助于确保
- @MapperScan包扫描的坑在使用通用mapper执行查询时,由于不太注意顺手就导了spring的包:import org.mybati
- 本文实例讲述了Java泛型与数据库应用。分享给大家供大家参考,具体如下:一 点睛BaseDao定义了基本的数据库增删查改, 之后可以继承该泛
- 如题,有时候看见一个布局写上几百行看上去会非常吃力麻烦,这时候抽取控件样式很有必要了, Android Studio提供了抽取Style样式
- 前言MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供
- 1. 简单说明嗨,大家好!今天给大家分享的是Mybatis-plus 插件的分页机制,说起分页机制,相信我们程序员都不陌生,今天,我就给大家
- 实现方案:我们直接参考实例代码:private String pattern = "((http|ftp
- 前言研究表明,Java堆中对象占据最大比重的就是字符串对象,所以弄清楚字符串知识很重要,本文主要重点聊聊字符串常量池。Java中的字符串常量