c# 基于wpf,開發(fā)OFD電子文檔閱讀器
前言
OFD是國家標(biāo)準(zhǔn)版式文檔格式,于2016年生效。OFD文檔國家標(biāo)準(zhǔn)參見《電子文件存儲(chǔ)與交換格式版式文檔》。既然是國家標(biāo)準(zhǔn),OFD隨后肯定會(huì)首先在政務(wù)系統(tǒng)使用,并逐步推向社會(huì)各個(gè)方面。OFD是在研究當(dāng)下各類文件格式后,推出的標(biāo)準(zhǔn),有如下優(yōu)點(diǎn):
1 產(chǎn)權(quán)屬于自主產(chǎn)權(quán)
2 具有便攜性:文件小,可壓縮比率大。測試顯示生成的文件體量比PDF還要小。
3 具有開放性:易于入門,對于使用者來說更具開放性。
4 具有擴(kuò)展性:預(yù)留了可擴(kuò)展入口和自定義標(biāo)引,設(shè)置了非接觸式引用機(jī)制,為特性化提供支持。
5 呈現(xiàn)效果與設(shè)備無關(guān),在各種設(shè)備上閱讀、打印或印刷時(shí),版面固定、不跑版。
6 應(yīng)用廣泛:無論是電子商務(wù)、電子公務(wù),還是信息發(fā)布、文件交換,檔案管理等都需要版式文檔的技術(shù)支持。
關(guān)于標(biāo)準(zhǔn),我也要吐槽一下。OFD標(biāo)準(zhǔn)是國內(nèi)幾家專業(yè)的電子文檔處理公司參與起草的;標(biāo)準(zhǔn)文檔(注:以下用”標(biāo)準(zhǔn)”特指OFD標(biāo)準(zhǔn))只有126頁,在我看來,標(biāo)準(zhǔn)對技術(shù)細(xì)節(jié)的描述過于簡單,沒有一定的技術(shù)背景很難看懂。與此形成鮮明對比的是pdf標(biāo)準(zhǔn),有1000多頁。我在網(wǎng)上也沒找到文字版的標(biāo)準(zhǔn),特別不利于閱讀和參考。
ofd閱讀器程序(已集成了轉(zhuǎn)圖、轉(zhuǎn)PDF功能)下載。
我最近一直研究ofd標(biāo)準(zhǔn),試圖寫一款閱讀器,已初有成果。具有ofd轉(zhuǎn)換為pdf、轉(zhuǎn)為圖片等特色功能。界面如下:


本文就把我開發(fā)的過程做簡單介紹。
OFD標(biāo)準(zhǔn)簡介
簡而言之,OFD存儲(chǔ)是采用壓縮技術(shù),描述采用XML格式。這一點(diǎn)與微軟的word文檔(docx)格式很類似。標(biāo)準(zhǔn)可能參考了微軟的處理方式;在技術(shù)上也要實(shí)事求是,國標(biāo)這種格式不是獨(dú)創(chuàng)和領(lǐng)先的。將OFD格式文件解壓后,會(huì)看到如下目錄和文件:

文件中會(huì)包括資源文件(圖片、字體庫等)。XML會(huì)對資源存放,圖元(文字、圖像等)顯示做描述,閱讀軟件會(huì)根據(jù)這些描述呈現(xiàn)出一致的顯示效果。
開發(fā)OFD閱讀軟件步驟
國內(nèi)流行的ofd閱讀軟件應(yīng)該是福昕和數(shù)科開發(fā)的,這兩款我都用過。我還要吐槽一下:
1)福昕閱讀器幫助文檔是ofd格式,但是無法用數(shù)科的閱讀器打開。
2)有些ofd文檔中xml標(biāo)記,在標(biāo)準(zhǔn)中找不到,是某些公司獨(dú)創(chuàng)的?
這些軟件都是用C++開發(fā)的,用到了QT。同樣情況下,相比于C#,C++開發(fā)軟件難度肯定會(huì)大增。在windows平臺(tái)開發(fā)界面,WPF應(yīng)該是最好的庫了。WPF雖然出現(xiàn)十幾年了,大家好像對此還很陌生。主要現(xiàn)在是BS的天下;不是WPF不夠好,是生不逢時(shí)。
1 對OFD文件解壓縮
OFD文件其實(shí)就是壓縮文件,解壓后的文件也有目錄結(jié)構(gòu)。該模塊的功能是獲取每個(gè)文件的路徑和數(shù)據(jù)。
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頁包含三類節(jié)點(diǎn):PathObject、ImageObject,暨對應(yīng)標(biāo)準(zhǔn)中的三類圖元。需要對這三類節(jié)點(diǎn)建模。這三個(gè)類共同繼承父類PageObject。所有的圖元都有繪制區(qū)域、坐標(biāo)變換、裁剪等共性,所有這些由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 創(chuàng)建WPF顯示模型
圖像精確定位需要用到Canvas控件作為容器。繪制大量圖形需要用到輕量級繪制模型DrawingVisual。在此基礎(chǔ)上,派生了繪制基礎(chǔ)模型OfdVisual,此模型對應(yīng)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,派生自O(shè)fdVisual。分別處理三種圖元數(shù)據(jù)。所有的繪制操作在函數(shù)
public override void Show(bool visiable, bool even = false);
對應(yīng)文本,繪制函數(shù)如下:
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);
}
}
繪制前,需要對當(dāng)前坐標(biāo)做變換、旋轉(zhuǎn)、剪切等操作。
最近又對程序完善了,增加縮略圖和公文索引:



后記
編寫閱讀器類軟件的關(guān)鍵是建模。首先讀懂標(biāo)準(zhǔn),對標(biāo)準(zhǔn)中描述的圖元做歸類分析,并建立起相應(yīng)的顯示模型。本人做WPF開發(fā)很多年了,感覺用WPF開發(fā)這類軟件并不是非常的難。相比于QT,采用wpf開發(fā)有很多優(yōu)勢。如果要完整實(shí)現(xiàn)OFD標(biāo)準(zhǔn),還需要大量的開發(fā),我會(huì)逐步完善該軟件的功能。
特別說明
ofd閱讀器開發(fā)語言為c#,具有完全自主產(chǎn)權(quán),沒有使用第三方ofd開發(fā)包??梢愿鶕?jù)你的需求快速定制開發(fā)。本閱讀器還在開發(fā)完善階段,如有任何問題,可以聯(lián)系我QQ:13712486。
以上就是c# 基于wpf,開發(fā)OFD電子文檔閱讀器的詳細(xì)內(nèi)容,更多關(guān)于c# wpf開發(fā)的資料請關(guān)注腳本之家其它相關(guān)文章!
- ASP.NET C#中Application的用法教程
- 用c#獲得當(dāng)前用戶的Application Data文件夾位置
- C# WPF如何反射加載Geometry幾何圖形數(shù)據(jù)圖標(biāo)
- c# wpf如何附加依賴項(xiàng)屬性
- c# WPF中的TreeView使用詳解
- c# wpf如何使用Blend工具繪制Control樣式
- c# 基于GMap.NET實(shí)現(xiàn)電子圍欄功能(WPF版)
- c# wpf使用GMap.NET類庫,實(shí)現(xiàn)地圖軌跡回放
- c# WPF中自定義加載時(shí)實(shí)現(xiàn)帶動(dòng)畫效果的Form和FormItem
- c# WPF中CheckBox樣式的使用總結(jié)
- c# wpf如何更好的使用Application程序集資源
相關(guān)文章
C#使用Dictionary<string, string>拆分字符串與記錄log方法
這篇文章介紹了Dictionary<string, string>拆分字符串與記錄log的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
C#實(shí)現(xiàn)獲取IIS站點(diǎn)及虛擬目錄信息的方法
這篇文章主要介紹了C#實(shí)現(xiàn)獲取IIS站點(diǎn)及虛擬目錄信息的方法,可實(shí)現(xiàn)獲取IIS站點(diǎn)信息及物理路徑等功能,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10

