c# 基于wpf,开发OFD电子文档阅读器

编辑: admin 分类: c#语言 发布时间: 2021-11-25 来源:互联网
目录
  • 前言 
  • OFD标准简介
  • 开发OFD阅读软件步骤
    • 1 对OFD文件解压缩
    • 2 找到需要展示的page
    • 3 创建WPF显示模型
  • 最近又对程序完善了,增加缩略图和公文索引:
    • 后记
      • 特别说明

        前言 

        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);
                【本文出处:国外高防服务器 复制请保留原URL】    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。

        以上就是c# 基于wpf,开发OFD电子文档阅读器的详细内容,更多关于c# wpf开发的资料请关注海外IDC网其它相关文章!

        【文章出处:国内服务器】