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

前言

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。

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

(0)

相关推荐

  • c# WPF中通过双击编辑DataGrid中Cell的示例(附源码)

    背景 在很多的时候我们需要编辑DataGrid中每一个Cell,编辑后保存数据,原生的WPF中的DataGrid并没有提供这样的功能,今天通过一个具体的例子来实现这一个功能,在这个例子中DataGrid中的数据类型可能是多种多样的,有枚举.浮点类型.布尔类型.DateTime类型,每一种不同的类型需要双击以后呈现不同的效果,本文通过使用Xceed.Wpf.DataGrid这个动态控件库来实现这个功能,当前使用的Dll版本是2.5.0.0,不同的版本可能实现上面有差别,这个在使用的时候需要特别注意

  • C# WPF使用AForge类库操作USB摄像头拍照并保存

    项目中用到 USB 摄像头,需要根据情况进行图像抓拍,查了半天资料,比较多的是使用 WPFMediaKit 和 AForge . 但是由于项目要求不显示 USB 摄像头拍摄的画面,最终确定使用 AForge 解决. 下面用一个测试程序记录一下. 一.无预览拍照 首先建立一个 WPF 项目,我的就叫 AForgeTest,你们随意就好: 然后在 NuGet 包管理器中安装 AForge 库: 我只安装了图中打勾的几个库,这个根据自己项目需要安装就好. 不过用 USB 摄像头拍照必须安装: AFor

  • c# WPF中自定义加载时实现带动画效果的Form和FormItem

    背景 今天我们来谈一下我们自定义的一组WPF控件Form和FormItem,然后看一下如何自定义一组完整地组合WPF控件,在我们很多界面显示的时候我们需要同时显示文本.图片并且我们需要将这些按照特定的顺序整齐的排列在一起,这样的操作当然通过定义Grid和StackPanel然后组合在一起当然也是可以的,我们的这一组控件就是将这个过程组合到一个Form和FormItem中间去,从而达到这样的效果,我们首先来看看这组控件实现的效果. 一 动画效果 看了这个效果之后我们来看看怎么来使用Form和For

  • 在C# WPF下自定义滚动条ScrollViewer样式的操作

    一.实现对ScrollViewer样式的自定义主要包括: 1.滚动条宽度设置 2.滚动条颜色 3.滚动条圆角 4.滚动条拉动时的效果mouseover 二.实现效果: 三.实现方法 1.创建资源字典( ResourceDictionary)文件 由于style代码比较多,之间在控件文件中加载style比较混乱,也不利于其它窗口复用,这里单独创建了ScrollViewDictionary.xaml文件代码如下: <ResourceDictionary xmlns="http://schema

  • c# WPF中CheckBox样式的使用总结

    背景 很多时候我们使用WPF开发界面的时候经常会用到各种空间,很多时候我们需要去自定义控件的样式来替换默认的样式,今天通过两个方法来替换WPF中的CheckBox样式,透过这两个例子我们可以掌握基本的WPF样式的开发如何定义ControlTemplate以及使用附加属性来为我们的控件增加新的样式. 常规使用 我们在使用CheckBox的时候,原始的样式有时不能满足我们的需求,这是我们就需要更改其模板,比如我们常用的一种,在播放器中"播放"."暂停"按钮,其实这也是一

  • C# WPF 自定义按钮的方法

    本文介绍WPF一种自定义按钮的方法. 实现效果 使用图片做按钮背景: 自定义鼠标进入时效果: 自定义按压效果: 自定义禁用效果 实现效果如下图所示: 实现步骤 创建CustomButton.cs,继承自Button: 创建一个资源文件ButtonStyles.xaml: 在资源文件中设计按钮的Style: 在CustomButton.cs中添加Style中需要的依赖属性: 在程序中添加资源并引用(为了方便在不同的程序中引用自定义按钮,自定义按钮放在独立的类库中,应用程序中进行资源合并即可). 示

  • C# WPF Image控件的绑定方法

    在我们平时的开发中会经常用到Image控件,通过设置Image控件的Source属性,我们可以加载图片,设置Image的source属性时可以使用相对路径也可以使用绝对路径,一般情况下建议使用绝对路径,类似于下面的形式Source="/Demo;Component/Images/Test.jpg"其中Demo表示工程的名称,后面表示具体哪个文件夹下面的哪个图片资源,在程序中,我们甚至可以为Image控件设置X:Name属性,在后台代码中动态去改变Image的Source,但我个人认为这

  • c# WPF设置软件界面背景为MediaElement并播放视频

    在我们的常见的软件界面设计中我们经常会设置软件的背景为SolidColorBrush或者LinerColorBrush.RadialGradientBrush 等一系列的颜色画刷为背景,有时我们也会使用ImageBrush添加图片来作为界面的背景,另外常用的还有DrawingBrush以及今天需要进行总结的VisualBrush,这些我们都是比较容易实现的,那么我们如果想将软件的界面设计成一个动画或者干脆播放一段视频作为背景,这个对于整个软件的效果又是一个巨大的提升. 首先我们来看看backgr

  • c# WPF如何实现滚动显示的TextBlock

    在我们使用TextBlock进行数据显示时,经常会遇到这样一种情况就是TextBlock的文字内容太多,如果全部显示的话会占据大量的界面,这是我们就会只让其显示一部分,另外的一部分就让其随着时间的推移去滚动进行显示,但是WPF默认提供的TextBlock是不具备这种功能的,那么怎么去实现呢? 其实个人认为思路还是比较清楚的,就是自己定义一个UserControl,然后将WPF简单的元素进行组合,最终实现一个自定义控件,所以我们顺着这个思路就很容易去实现了,我们知道Canvas这个控件可以通过设置

  • C# WPF 通过委托实现多窗口间的传值的方法

    在使用WPF开发的时候就不免会遇到需要两个窗口间进行传值操作,当然多窗口间传值的方法有很多种,本文介绍的是使用委托实现多窗口间的传值. 在上代码之前呢,先简单介绍一下什么是C#中的委托(如果只想了解如何传值可以略过这部分)在网络上有很多对于委托的介绍和讲解,经过我的学习和总结加上了一点我自己的理解,我认为委托是一种类似于C语言的指针,但是它指向的是方法而不是变量.如果把委托看作一个变量,那么这个变量里存着的就是你目标方法的地址,调用委托约等于调用你的目标方法.(个人理解欢迎指正交流) 以下正文:

随机推荐