服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - c# 基于wpf,开发OFD电子文档阅读器

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

2022-11-04 12:06源之缘 C#

这篇文章主要介绍了c# 如何基于wpf,开发OFD电子文档阅读器,帮助大家更好的理解和学习使用c#的wpf技术,感兴趣的朋友可以了解下

前言 

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

1 产权属于自主产权

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

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

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

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

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

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

ofd阅读器程序(已集成了转图、转PDF功能)下载。

  我最近一直研究ofd标准,试图写一款阅读器,已初有成果。具有ofd转换为pdf、转为图片等特色功能。界面如下:

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

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

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

OFD标准简介

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

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

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

开发OFD阅读软件步骤

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

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

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

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

1 对OFD文件解压缩

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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类处理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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。 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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。分别处理三种图元数据。所有的绘制操作在函数

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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);
     }
   }

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

最近又对程序完善了,增加缩略图和公文索引:

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

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

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

后记

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

特别说明

ofd阅读器开发语言为c#,具有完全自主产权,没有使用第三方ofd开发包。可以根据你的需求快速定制开发。本阅读器还在开发完善阶段,如有任何问题,可以联系我QQ:13712486。

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

原文链接:https://www.cnblogs.com/yuanchenhui/p/ofdreader.html

延伸 · 阅读

精彩推荐
  • C#C#实现窗体抖动的两种方法

    C#实现窗体抖动的两种方法

    这篇文章主要为大家详细介绍了C#实现窗体抖动的两种方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    英莱特——Dream11852022-10-18
  • C#Unity实现物体左右移动效果

    Unity实现物体左右移动效果

    这篇文章主要为大家详细介绍了Unity实现物体左右移动效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    _April_10742022-08-03
  • C#C#集合Collections购物车Shopping Cart(实例讲解)

    C#集合Collections购物车Shopping Cart(实例讲解)

    下面小编就为大家分享一篇C#集合Collections购物车Shopping Cart的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    杨明波(Leo Yang)11732022-02-16
  • C#C#实现的UDP收发请求工具类实例

    C#实现的UDP收发请求工具类实例

    这篇文章主要介绍了C#实现的UDP收发请求工具类,结合具体实例形式分析了C#针对UDP请求的监听、接收、发送等相关操作技巧,需要的朋友可以参考下...

    _iorilan3852022-01-07
  • C#Unity3D实现简易五子棋源码

    Unity3D实现简易五子棋源码

    这篇文章主要为大家详细介绍了Unity3D实现简易五子棋源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    308169234882022-08-05
  • C#深入理解StringBuilder的使用方法

    深入理解StringBuilder的使用方法

    下面小编就为大家带来一篇深入理解StringBuilder的使用方法。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    编程之家5602021-11-22
  • C#C# RichTextBox制作文本编辑器

    C# RichTextBox制作文本编辑器

    这篇文章主要为大家详细介绍了C# RichTextBox制作文本编辑器的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    飞翔的月亮4102021-12-31
  • C#浅谈C# winForm 窗体闪烁的问题

    浅谈C# winForm 窗体闪烁的问题

    下面小编就为大家带来一篇浅谈C# winForm 窗体闪烁的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C#教程网8122021-12-21