转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档
在 C# 中使用 P/Invoke 調用 Mupdf 函數庫顯示 PDF 文檔
一直以來,我都想為 PDF 補丁丁添加一個 PDF 渲染引擎。可是,目前并沒有可以在 .NET 框架上運行的免費 PDF 渲染引擎。經過網上的搜索,有人使用 C++/CLI 調用 XPDF 或 Mupdf,實現了不安裝 Adobe 系列軟件而渲染出 PDF 文件的功能。
Mupdf 是一個開源的 PDF 渲染引擎,使用 C 語言編寫,可編譯成能讓 C# 調用的動態鏈接庫。因此,只要編寫合適的調用代碼,就能使用該渲染引擎,將 PDF 文檔轉換為一頁一頁的圖片,或者在程序界面顯示 PDF 文檔的內容。
要使用 Mupdf 渲染 PDF 文檔,有幾個步驟:
獲取 Mupdf 動態鏈接庫
Mupdf 的源代碼沒有提供直接編譯生成動態鏈接庫的 Make 文件。幸好,從另一個基于 Mupdf 的開源項目——SumatraPDF——能編譯生成 Mupdf 動態鏈接庫。在 SumatraPDF 的源代碼網站下載源代碼和工程文件,使用 Visual C++(免費的速成版就可以了)編譯該工程,生成配置選“Release”,就能生成 Mupdf 的動態鏈接庫。
了解 Mupdf 的概念和導出函數
Mupdf 的導出函數可通過查看 Mupdf 源代碼的頭文件得到。頭文件可在 Mupdf 官方網站的 Documentation 區在線查閱。
Mupdf 最通用的函數放在頭文件“Fitz.h”里。如果只是使用 C# 函數來渲染 PDF 文檔,只使用 Fitz.h 文件中提供的結構和函數即可。在渲染 PDF 文檔時用到的結構主要有五個:
Fitz.h 文件中提供的函數均以“fz_”開頭,這些函數可用于處理上述五個結構。以上述五個結構為基礎,調用相應的函數,就能完成渲染 PDF 文檔的任務。
沒有 C 語言基礎的開發人員請注意:部分預定義處理指令——即 #define 指令,也使用“fz_”開頭,這些處理指令并不是導出函數。在使用 P/Invoke 技術調用函數庫時不能使用 #define 指令定義的替換函數。例如,fz_try、fz_catch、fz_finally 就是這類型的預定義處理指令。
為導出函數撰寫 P/Invoke 代碼
Fitz.h 提供的導出函數中,下列函數在渲染 PDF 文檔時是必須使用的。
在撰寫 P/Invoke 代碼的過程中,我們還會遇到幾個結構,“BBox”表示邊框結構,包含 x0、y0、x1 和 y1 四個整數坐標變量;“Rectangle”與“BBox”類似,但坐標變量為浮點數;“Matrix”用于渲染過程中的拉伸、平移等操作(詳見 Mupdf 代碼中的頭文件)。最后,我們得到與下列代碼類似的 P/Invoke C# 代碼。
public struct BBox {public int Left, Top, Right, Bottom; } public struct Rectangle {public float Left, Top, Right, Bottom; } public struct Matrix {public float A, B, C, D, E, F; } class NativeMethods {const string DLL = "libmupdf.dll";[DllImport (DLL, EntryPoint="fz_new_context")]public static extern IntPtr NewContext (IntPtr alloc, IntPtr locks, uint max_store);[DllImport (DLL, EntryPoint = "fz_free_context")]public static extern IntPtr FreeContext (IntPtr ctx);[DllImport (DLL, EntryPoint = "fz_open_file_w", CharSet = CharSet.Unicode)]public static extern IntPtr OpenFile (IntPtr ctx, string fileName);[DllImport (DLL, EntryPoint = "fz_open_document_with_stream")]public static extern IntPtr OpenDocumentStream (IntPtr ctx, string magic, IntPtr stm);[DllImport (DLL, EntryPoint = "fz_close")]public static extern IntPtr CloseStream (IntPtr stm);[DllImport (DLL, EntryPoint = "fz_close_document")]public static extern IntPtr CloseDocument (IntPtr doc);[DllImport (DLL, EntryPoint = "fz_count_pages")]public static extern int CountPages (IntPtr doc);[DllImport (DLL, EntryPoint = "fz_bound_page")]public static extern Rectangle BoundPage (IntPtr doc, IntPtr page);[DllImport (DLL, EntryPoint = "fz_clear_pixmap_with_value")]public static extern void ClearPixmap (IntPtr ctx, IntPtr pix, int byteValue);[DllImport (DLL, EntryPoint = "fz_find_device_colorspace")]public static extern IntPtr FindDeviceColorSpace (IntPtr ctx, string colorspace);[DllImport (DLL, EntryPoint = "fz_free_device")]public static extern void FreeDevice (IntPtr dev);[DllImport (DLL, EntryPoint = "fz_free_page")]public static extern void FreePage (IntPtr doc, IntPtr page);[DllImport (DLL, EntryPoint = "fz_load_page")]public static extern IntPtr LoadPage (IntPtr doc, int pageNumber);[DllImport (DLL, EntryPoint = "fz_new_draw_device")]public static extern IntPtr NewDrawDevice (IntPtr ctx, IntPtr pix);[DllImport (DLL, EntryPoint = "fz_new_pixmap")]public static extern IntPtr NewPixmap (IntPtr ctx, IntPtr colorspace, int width, int height);[DllImport (DLL, EntryPoint = "fz_run_page")]public static extern void RunPage (IntPtr doc, IntPtr page, IntPtr dev, Matrix transform, IntPtr cookie);[DllImport (DLL, EntryPoint = "fz_drop_pixmap")]public static extern void DropPixmap (IntPtr ctx, IntPtr pix);[DllImport (DLL, EntryPoint = "fz_pixmap_samples")]public static extern IntPtr GetSamples (IntPtr ctx, IntPtr pix);}撰寫代碼調用導出函數
在上述 P/Invoke 代碼已經準備好之后,需要撰寫代碼調用導出函數并渲染出頁面。為簡單起見,示例中并不使用類封裝結構,而是直接調用上述 P/Invoke 函數。上述函數中,名稱中包含“close”、“drop”、“free”的函數是用來釋放資源的。在實際開發過程中,應撰寫相應的類來保存對這些資源的指針引用。而且,這些類應實現 IDisposable 接口,并將釋放資源的函數放在 Dispose 方法中。在完成操作后,應調用類實例的 Dispose 方法,釋放相關的資源。
渲染頁面的流程如下,按步驟逐個調用上述的函數即可:
代碼如下所示。
static void Main (string[] args) {const uint FZ_STORE_DEFAULT = 256 << 20;IntPtr ctx = NativeMethods.NewContext (IntPtr.Zero, IntPtr.Zero, FZ_STORE_DEFAULT); // 創建上下文IntPtr stm = NativeMethods.OpenFile (ctx, "test.pdf"); // 打開 test.pdf 文件流IntPtr doc = NativeMethods.OpenDocumentStream (ctx, ".pdf", stm); // 從文件流創建文檔對象int pn = NativeMethods.CountPages (doc); // 獲取文檔的頁數for (int i = 0; i < pn; i++) { // 遍歷各頁IntPtr p = NativeMethods.LoadPage (doc, i); // 加載頁面(首頁為 0)Rectangle b = NativeMethods.BoundPage (doc, p); // 獲取頁面尺寸using (var bmp = RenderPage (ctx, doc, p, b)) { // 渲染頁面并轉換為 Bitmapbmp.Save ((i+1) + ".png"); // 將 Bitmap 保存為文件 }NativeMethods.FreePage (doc, p); // 釋放頁面所占用的資源 }NativeMethods.CloseDocument (doc); // 釋放其它資源 NativeMethods.CloseStream (stm);NativeMethods.FreeContext (ctx); }其中,RenderPage 方法用來渲染圖片,代碼如下。
static Bitmap RenderPage (IntPtr context, IntPtr document, IntPtr page, Rectangle pageBound) {Matrix ctm = new Matrix ();IntPtr pix = IntPtr.Zero;IntPtr dev = IntPtr.Zero;int width = (int)(pageBound.Right - pageBound.Left); // 獲取頁面的寬度和高度int height = (int)(pageBound.Bottom - pageBound.Top);ctm.A = ctm.D = 1; // 設置單位矩陣 (1,0,0,1,0,0)// 創建與頁面相同尺寸的繪圖畫布(Pixmap)pix = NativeMethods.NewPixmap (context, NativeMethods.FindDeviceColorSpace (context, "DeviceRGB"), width, height);// 將 Pixmap 的背景設為白色NativeMethods.ClearPixmap (context, pix, 0xFF);// 創建繪圖設備dev = NativeMethods.NewDrawDevice (context, pix);// 將頁面繪制到以 Pixmap 生成的繪圖設備上 NativeMethods.RunPage (document, page, dev, ctm, IntPtr.Zero);NativeMethods.FreeDevice (dev); // 釋放繪圖設備對應的資源dev = IntPtr.Zero;// 創建與 Pixmap 相同尺寸的彩色 BitmapBitmap bmp = new Bitmap (width, height, PixelFormat.Format24bppRgb); var imageData = bmp.LockBits (new System.Drawing.Rectangle (0, 0, width, height), ImageLockMode.ReadWrite, bmp.PixelFormat);unsafe { // 將 Pixmap 的數據轉換為 Bitmap 數據// 獲取 Pixmap 的圖像數據byte* ptrSrc = (byte*)NativeMethods.GetSamples (context, pix);byte* ptrDest = (byte*)imageData.Scan0;for (int y = 0; y < height; y++) {byte* pl = ptrDest;byte* sl = ptrSrc;for (int x = 0; x < width; x++) {// 將 Pixmap 的色彩數據轉換為 Bitmap 的格式pl[2] = sl[0]; //b-rpl[1] = sl[1]; //g-gpl[0] = sl[2]; //r-b//sl[3] 是透明通道數據,在此忽略pl += 3;sl += 4;}ptrDest += imageData.Stride;ptrSrc += width * 4;}}NativeMethods.DropPixmap (context, pix); // 釋放 Pixmap 占用的資源return bmp; }好了,渲染 PDF 文檔的代碼雛形就此完成了。
在實際項目開發中,我們還需要考慮以下幾個首要問題:
本文及源代碼項目發布在 CodeProject 網站,有興趣的同好可閱讀《Rendering PDF Documents with Mupdf and P/Invoke in C#》。
來自:http://www.cnblogs.com/pdfpatcher/archive/2012/11/25/2785154.html
轉載于:https://www.cnblogs.com/lusunqing/p/3829082.html
總結
以上是生活随笔為你收集整理的转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2007年10月14日的日记
- 下一篇: c# char unsigned_dll