转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档
在 C# 中使用 P/Invoke 調(diào)用 Mupdf 函數(shù)庫顯示 PDF 文檔
一直以來,我都想為 PDF 補(bǔ)丁丁添加一個 PDF 渲染引擎。可是,目前并沒有可以在 .NET 框架上運(yùn)行的免費(fèi) PDF 渲染引擎。經(jīng)過網(wǎng)上的搜索,有人使用 C++/CLI 調(diào)用 XPDF 或 Mupdf,實(shí)現(xiàn)了不安裝 Adobe 系列軟件而渲染出 PDF 文件的功能。
Mupdf 是一個開源的 PDF 渲染引擎,使用 C 語言編寫,可編譯成能讓 C# 調(diào)用的動態(tài)鏈接庫。因此,只要編寫合適的調(diào)用代碼,就能使用該渲染引擎,將 PDF 文檔轉(zhuǎn)換為一頁一頁的圖片,或者在程序界面顯示 PDF 文檔的內(nèi)容。
要使用 Mupdf 渲染 PDF 文檔,有幾個步驟:
獲取 Mupdf 動態(tài)鏈接庫
Mupdf 的源代碼沒有提供直接編譯生成動態(tài)鏈接庫的 Make 文件。幸好,從另一個基于 Mupdf 的開源項(xiàng)目——SumatraPDF——能編譯生成 Mupdf 動態(tài)鏈接庫。在 SumatraPDF 的源代碼網(wǎng)站下載源代碼和工程文件,使用 Visual C++(免費(fèi)的速成版就可以了)編譯該工程,生成配置選“Release”,就能生成 Mupdf 的動態(tài)鏈接庫。
了解 Mupdf 的概念和導(dǎo)出函數(shù)
Mupdf 的導(dǎo)出函數(shù)可通過查看 Mupdf 源代碼的頭文件得到。頭文件可在 Mupdf 官方網(wǎng)站的 Documentation 區(qū)在線查閱。
Mupdf 最通用的函數(shù)放在頭文件“Fitz.h”里。如果只是使用 C# 函數(shù)來渲染 PDF 文檔,只使用 Fitz.h 文件中提供的結(jié)構(gòu)和函數(shù)即可。在渲染 PDF 文檔時用到的結(jié)構(gòu)主要有五個:
Fitz.h 文件中提供的函數(shù)均以“fz_”開頭,這些函數(shù)可用于處理上述五個結(jié)構(gòu)。以上述五個結(jié)構(gòu)為基礎(chǔ),調(diào)用相應(yīng)的函數(shù),就能完成渲染 PDF 文檔的任務(wù)。
沒有 C 語言基礎(chǔ)的開發(fā)人員請注意:部分預(yù)定義處理指令——即 #define 指令,也使用“fz_”開頭,這些處理指令并不是導(dǎo)出函數(shù)。在使用 P/Invoke 技術(shù)調(diào)用函數(shù)庫時不能使用 #define 指令定義的替換函數(shù)。例如,fz_try、fz_catch、fz_finally 就是這類型的預(yù)定義處理指令。
為導(dǎo)出函數(shù)撰寫 P/Invoke 代碼
Fitz.h 提供的導(dǎo)出函數(shù)中,下列函數(shù)在渲染 PDF 文檔時是必須使用的。
在撰寫 P/Invoke 代碼的過程中,我們還會遇到幾個結(jié)構(gòu),“BBox”表示邊框結(jié)構(gòu),包含 x0、y0、x1 和 y1 四個整數(shù)坐標(biāo)變量;“Rectangle”與“BBox”類似,但坐標(biāo)變量為浮點(diǎn)數(shù);“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);}撰寫代碼調(diào)用導(dǎo)出函數(shù)
在上述 P/Invoke 代碼已經(jīng)準(zhǔn)備好之后,需要撰寫代碼調(diào)用導(dǎo)出函數(shù)并渲染出頁面。為簡單起見,示例中并不使用類封裝結(jié)構(gòu),而是直接調(diào)用上述 P/Invoke 函數(shù)。上述函數(shù)中,名稱中包含“close”、“drop”、“free”的函數(shù)是用來釋放資源的。在實(shí)際開發(fā)過程中,應(yīng)撰寫相應(yīng)的類來保存對這些資源的指針引用。而且,這些類應(yīng)實(shí)現(xiàn) IDisposable 接口,并將釋放資源的函數(shù)放在 Dispose 方法中。在完成操作后,應(yīng)調(diào)用類實(shí)例的 Dispose 方法,釋放相關(guān)的資源。
渲染頁面的流程如下,按步驟逐個調(diào)用上述的函數(shù)即可:
代碼如下所示。
static void Main (string[] args) {const uint FZ_STORE_DEFAULT = 256 << 20;IntPtr ctx = NativeMethods.NewContext (IntPtr.Zero, IntPtr.Zero, FZ_STORE_DEFAULT); // 創(chuàng)建上下文IntPtr stm = NativeMethods.OpenFile (ctx, "test.pdf"); // 打開 test.pdf 文件流IntPtr doc = NativeMethods.OpenDocumentStream (ctx, ".pdf", stm); // 從文件流創(chuàng)建文檔對象int pn = NativeMethods.CountPages (doc); // 獲取文檔的頁數(shù)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)) { // 渲染頁面并轉(zhuǎn)換為 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; // 設(shè)置單位矩陣 (1,0,0,1,0,0)// 創(chuàng)建與頁面相同尺寸的繪圖畫布(Pixmap)pix = NativeMethods.NewPixmap (context, NativeMethods.FindDeviceColorSpace (context, "DeviceRGB"), width, height);// 將 Pixmap 的背景設(shè)為白色NativeMethods.ClearPixmap (context, pix, 0xFF);// 創(chuàng)建繪圖設(shè)備dev = NativeMethods.NewDrawDevice (context, pix);// 將頁面繪制到以 Pixmap 生成的繪圖設(shè)備上 NativeMethods.RunPage (document, page, dev, ctm, IntPtr.Zero);NativeMethods.FreeDevice (dev); // 釋放繪圖設(shè)備對應(yīng)的資源dev = IntPtr.Zero;// 創(chuàng)建與 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 的數(shù)據(jù)轉(zhuǎn)換為 Bitmap 數(shù)據(jù)// 獲取 Pixmap 的圖像數(shù)據(jù)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 的色彩數(shù)據(jù)轉(zhuǎn)換為 Bitmap 的格式pl[2] = sl[0]; //b-rpl[1] = sl[1]; //g-gpl[0] = sl[2]; //r-b//sl[3] 是透明通道數(shù)據(jù),在此忽略pl += 3;sl += 4;}ptrDest += imageData.Stride;ptrSrc += width * 4;}}NativeMethods.DropPixmap (context, pix); // 釋放 Pixmap 占用的資源return bmp; }好了,渲染 PDF 文檔的代碼雛形就此完成了。
在實(shí)際項(xiàng)目開發(fā)中,我們還需要考慮以下幾個首要問題:
本文及源代碼項(xiàng)目發(fā)布在 CodeProject 網(wǎng)站,有興趣的同好可閱讀《Rendering PDF Documents with Mupdf and P/Invoke in C#》。
來自:http://www.cnblogs.com/pdfpatcher/archive/2012/11/25/2785154.html
轉(zhuǎn)載于:https://www.cnblogs.com/lusunqing/p/3829082.html
總結(jié)
以上是生活随笔為你收集整理的转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2007年10月14日的日记
- 下一篇: 2年