在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一直以来,我都想为 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)) { // 渲染页面并转换为 Bitmap bmp.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 相同尺寸的彩色 Bitmap Bitmap 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-r pl[1] = sl[1]; //g-g pl[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 一直以来,我都想为 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)) { // 渲染页面并转换为 Bitmap bmp.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 相同尺寸的彩色 Bitmap Bitmap 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-r pl[1] = sl[1]; //g-g pl[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 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论