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開發的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C#實現Base64編碼與解碼及規則
- c# 用ICSharpCode組件壓縮文件
- Java中InputSteam怎麼轉String
- C#實現多文件打包壓縮(.Net Core)
- 基於c# Task自己動手寫個異步IO函數