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

前言 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

时间: 2024-10-08 21:56:35

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

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

特别说明 ofd阅读器开发语言为c#,具有完全自主产权,没有使用第三方ofd开发包.可以根据你的需求快速定制开发.本阅读器还在开发完善阶段,如有任何问题,可以联系我QQ:13712486.博客:https://www.cnblogs.com/yuanchenhui/ 软件下载地址:ofd阅读器(全新开发,永久免费).zip 1 主界面 2 文件信息指示:    无电子印章和签名                       有电子印章和签名     文本属性:指示是否包含 电子印章.签名.如果包含

怎么拆分PDF格式电子文档,拆分PDF文档需要使用什么软件!

PDF格式虽说很受大众用户的喜爱,但是因为这种文件比较特殊的原因,所以我们在使用这种文件的时候需要借助到专业的编辑软件,今天我们就一起来学习一下怎么将PDF格式文件进行拆分吧! 1.打开百度,搜索软件关键词"迅捷PDF编辑器",在搜索结果中找到对应的软件后,下载并安装到电脑中准备使用. 2.打开软件,在左上角文件处点击"打开".我们将需要进行编辑拆分的pdf文件添加到软件中. 3.想要编辑pdf文件的话,可以点击"编辑内容",单击文本出现蓝色的编

一个基于Windows Vista speech API5 3以及WPF技术的语音识别代码

本人小试牛刀,试验了一下用c#.net3.0 WPF技术开发了一个语音识别程序, windows.cs using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input

一个基于Windows Vista speech API5 3以及WPF技术的语音朗读代码

闲暇无事,利用window SDK 与vs2008,基于Windows Vista speech API5.3以及WPF技术开发了一套语音朗读的代码, 牛刀小试, using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Docume

基于ASP.NET WPF技术及MVP模式实战太平人寿客户管理项目开发(Repository模式)

亲爱的网友,我这里有套课程想和大家分享,如果对这个课程有兴趣的,可以加我的QQ2059055336和我联系. 课程背景 本课程是教授使用WPF.ADO.NET.MVVM技术来实现太平人寿保险有限公司保险客户管理系统,是学习WPF开发中的一门主打课程之一. WPF是一个框架,它供程序员开发出媲美Mac程序的酷炫界面. Blend是一种工具,可以在美工板上绘制形状.路径和控件,然后修改其外观和行为,从而直观地设计应用程序 Repository\MVVM\MVP设计模式是WPF常用的系统架构 Auto

采用WPF开发截图程序,so easy!

原文:采用WPF开发截图程序,so easy! 前言  QQ.微信截图功能已很强大了,似乎没必要在开发一个截图程序了.但是有时QQ热键就是被占用,不能快速的开启截屏:有时,天天挂着QQ,领导也不乐意.既然是程序员,就要自己开发截屏工具,功能随心所欲,岂不快哉. 再强调一点:工具就是生产力!没有掌握WPF之前,我是不会开发这么一个程序的,如果采用MFC.winform框架,工作量是相当的大,开发出来的效果肯定也比较low.本人用WPF,花了一天的功夫,开发了这个小程序.程序的定位就功能简单,平时工

软件工程导论第一周作业(你认为一些军事方面的软件系统采用什么样的开发模型比较合适?)

4.你认为一些军事方面的软件系统采用什么样的开发模型比较合适? 我认为军事方面的软件系统采用瀑布型的开发模式比较合适,原因如下: 首先,我觉得军事方面的开发是一项要求严格,不允许有严重差错甚至细微差错也最好不要出现的项目,因为军事是和国家的安全与政治息息相关的,正因为如此,军事方面的开发要求参与人员在设计时候更严谨,在项目从开始到结束都不能出现多次更改,反复修正的情况,应当一个阶段一个阶段的经过深思熟虑后再进行,并且要求最终生产出来的产品是优质的,而瀑布模型的显著特征就是阶段间具有顺序性和依赖性

软件开发十三种文档格式

在项目开发过程中,应该按要求编写好十三种文档,文档编制要求具有针对性.精确性.清晰性.完整性.灵活性.可追溯性. ◇ 可行性分析报告:说明该软件开发项目的实现在技术上.经济上和社会因素上的可行性,评述为了合理地达到开发目标可供选择的各种可能实施方案,说明并论证所选定实施方案的理由. ◇ 项目开发计划:为软件项目实施方案制订出具体计划,应该包括各部分工作的负责人员.开发的进度.开发经费的预算.所需的硬件及软件资源等. ◇ 软件需求说明书(软件规格说明书):对所开发软件的功能.性能.用户界面及运行环

程序员如何开发独立电商系统?

当社会发展进入"互联网+"时代,传统的PC电商已经不能满足电商运营者的需求,打造独立的移动电商系统是必然的趋势. 随着移动电商热度的不断增加,许多的商家是开始慢慢的加入到移动电商的行列当中,那么你们知道设计开发移动电商系统的吗?延誉电商为您答疑解惑! 1.规划阶段 前规划阶段的主要任务是进行建立数据库的必要性及可行性分析,确定数据库系统在组织中和信息系统中的地位,以及各个数据库之间的联系.规划工作完成后应写出详尽的可行性分析报告和数据库系统规划纲要.可行性分析报告的主要内容包括信息范围