转:在 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 的动态链接库。
- 了解该库中的相关导出函数。
- 为导出函数撰写 P/Invoke 代码。
- 撰写 C# 代码,调用 Mupdf 的导出函数。将渲染后的数据(Pixmap)转换为位图,或直接在控件的设备句柄(HDC)绘制渲染后的文档。
获取 Mupdf 动态链接库
Mupdf 的源代码没有提供直接编译生成动态链接库的 Make 文件。幸好,从另一个基于 Mupdf 的开源项目——SumatraPDF——能编译生成 Mupdf 动态链接库。在 SumatraPDF 的源代码网站下载源代码和工程文件,使用 Visual C++(免费的速成版就可以了)编译该工程,生成配置选“Release”,就能生成 Mupdf 的动态链接库。
了解 Mupdf 的概念和导出函数
Mupdf 的导出函数可通过查看 Mupdf 源代码的头文件得到。头文件可在 Mupdf 官方网站的 Documentation 区在线查阅。
Mupdf 最通用的函数放在头文件“Fitz.h”里。如果只是使用 C# 函数来渲染 PDF 文档,只使用 Fitz.h 文件中提供的结构和函数即可。在渲染 PDF 文档时用到的结构主要有五个:
- fz_context:存放渲染引擎所用的全局数据。
- fz_document:存放文档的信息。
- fz_page:存放页面的数据。
- fz_device:用于放置渲染结果的目标设备。
- fz_pixmap:存放渲染结果的画布。
Fitz.h 文件中提供的函数均以“fz_”开头,这些函数可用于处理上述五个结构。以上述五个结构为基础,调用相应的函数,就能完成渲染 PDF 文档的任务。
没有 C 语言基础的开发人员请注意:部分预定义处理指令——即 #define 指令,也使用“fz_”开头,这些处理指令并不是导出函数。在使用 P/Invoke 技术调用函数库时不能使用 #define 指令定义的替换函数。例如,fz_try、fz_catch、fz_finally 就是这类型的预定义处理指令。
为导出函数撰写 P/Invoke 代码
Fitz.h 提供的导出函数中,下列函数在渲染 PDF 文档时是必须使用的。
- fz_new_context:创建渲染文档时的上下文变量。
- fz_free_context:释放上下文变量所占用的资源。
- fz_open_file_w:打开文件流(传入的文件名变量为 Unicode)
- fz_open_document_with_stream:打开文件流对应的文档(PDF 或其它支持的文件格式)。
- fz_close_document:关闭文档。
- fz_close:关闭文件流。
- fz_count_pages:获得文档的页数。
- fz_load_page:加载文档指定的页面。
- fz_free_page:释放文档页面占用的资源。
- fz_bound_page:确定文档页面的尺寸。
- fz_new_pixmap:创建渲染页面所用的图形画纸。
- fz_clear_pixmap_with_value:清除画纸(通常用于将 PDF 文档的背景色设置为纯白色)。
- fz_new_draw_device:从画纸创建绘图设备。
- fz_find_device_colorspace:获取渲染页面所用的颜色域(彩色或灰色)。
- fz_run_page:将页面渲染到指定的设备上。
- fz_free_device:释放设备所占用的资源。
- fz_drop_pixmap:释放画纸占用的资源。
- fz_pixmap_samples:获取画纸的数据(用于将已渲染的画纸内容转换为 Bitmap)。
在撰写 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 方法,释放相关的资源。
渲染页面的流程如下,按步骤逐个调用上述的函数即可:
- 加载文档。
- 加载页面。
- 预备好绘图画纸(Pixmap)。
- 从绘图画纸创建绘图设备。
- 将页面绘制到绘图设备(即画纸)上。
- 将画纸的数据转换为 Bitmap。
- 保存 Bitmap 或将 Bitmap 绘制到程序界面。
- 释放 Bitmap 的资源。
- 释放画纸、绘图设备、页面和文档的资源。
代码如下所示。
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 文档的代码雏形就此完成了。
在实际项目开发中,我们还需要考虑以下几个首要问题:
- 处理 Mupdf 抛出的异常:捕获 AccessViolationException 异常。
- 记住释放资源:可考虑将相应的资源封装为实现 IDisposible 接口的类。
- 扩展程序的功能:可参考使用 Mupdf 的开放源代码项目,其中最著名的一个项目莫过于 SumatraPDF。
- 在64位机器上运行:可将 .NET 项目的 CPU 平台设置为 x86,强制程序用 32 位 .NET Framework 运行。
本文及源代码项目发布在 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 文档相关推荐
- springboot中使用freemarker根据flt模板导出word、pdf文档
1.导包: <!--FreeMarker --> <dependency><groupId>org.springframework.boot</groupId ...
- java调用libreoffice_JAVA实现LibreOffice转换PDF文档
首先,安装LibreOffice服务 使用.LibreOfficeAndJodconverter.java package com.jalor.LibreOffice; import java.io. ...
- pdf.js使用方法整理,web页面中pdf在线查看,web页面显示pdf文档
pdf.js 使用步骤: 一.到官网下载 pdf.js 插件并解压 (地址: http://mozilla.github.io/pdf.js/ ) 若官网无法下载,通过下面链接下载,注:作者有测试方 ...
- Android调用WPS第三方App打开PDF文档,一直停留在首页,提示正在加载文档类型
Android 7.0 以后对Uri的访问进行了限制,需要在manifest项目清单文件里面添加 provider,具体怎么写这个就不说了. 解决此问题只需添加以下代码即可: intent.addFl ...
- ios html格式转换,如何使用HTML模版和iOS中的UIPrintPageRenderer来生成PDF文档
如何使用HTML模版和iOS中的UIPrintPageRenderer来生成PDF文档 作者:GABRIEL THEODOROPOULOS,时间:2016/7/10 翻译:BigNerdCoding, ...
- php+tcpdf+表格,PHP中使用TCPDF生成PDF文档实例
实际工作中,我们要使用PHP动态的创建PDF文档,目前有许多开源的PHP创建PDF的类库,今天我给大家来介绍一款优秀的PDF库,它就是TCPDF,TCPDF是一个用于快速生成PDF文件的PHP5函数包 ...
- 如何在ASP.NET Core 中快速构建PDF文档
比如我们需要ASP.NET Core 中需要通过PDF来进行某些简单的报表开发,随着这并不难,但还是会手忙脚乱的去搜索一些资料,那么恭喜您,这篇帖子会帮助到您,我们就不会再去浪费一些宝贵的时间. 在本 ...
- 多端手机网页中直接打开PDF文档
1. 需求 接到一个需求,客户想在手机端的网页里直接能查看到 pdf 文档,一开始我的思路是加一个<a>标签让他跳转链接到 pdf 文档的地址,经过测试发现在 IOS 系统中,网页跳转之后 ...
- Java使用 PDFBox 2.0 从 PDF 文档中读取所有文本
在本教程中,我们将学习在 Java 程序中使用 PDFBox 2.0 库从 pdf 文档中读取所有文本. PDF 文档可能包含文本.嵌入图像等作为其内容.PDFBox 中的 PDFTextStripp ...
最新文章
- django之全文检索
- JAVA实现QQ聊天气泡
- 【黑金视频连载】FPGA NIOSII视频教程(11)--系统时钟实验
- insight切换窗口 source_source insight的使用方法逆天整理(1)
- Resharper4.5:增强你的.net开发
- HDFS 入门和基本操作
- birt java api_「Birt」birt api生成报表 | 学步园
- __strong、__weak 与 __unsafe_unretained区别
- 微信小程序使用腾讯地图
- 【操作系统】-- 进程同步、信号量机制(整型信号量、记录型信号量、PV操作)
- mentohust mac安装
- 华硕笔记本进入pe系统-华硕电脑从U盘启动-实测有效-转载--记录用
- 如何利用ChatGPT学习量化投资?
- Struts2+Spring+Hibernate 三大框架的合并集成
- java中什么是空指针异常以及为什么会产生空指针异常
- 用计算机娱乐教学反思,计算机教学反思
- 中国羟丙甲纤维素胶囊市场深度研究分析报告
- 服务器如何设置防火墙?
- 高群耀:移动电影院2.0四大功能实现了用户“观影社交化”
- win11合盖不休眠怎么设置?
热门文章
- Lr中脚本的迭代次数和场景运行时间的关系
- jQuery动画高级用法(上)——详解animation中的.queue()函数
- PendingIntent与Intent的区别
- 解决EXCEL统计问题的分享
- Vmware 虚拟机提示:无法打开磁盘***.vmdk 无法启动虚拟机 解决办法
- java分布式 mq_分布式系统消息中间件—RabbitMQ的使用进阶篇
- 阿里云原生多模数据库Lindorm联合东软云科技,赋能车联网数字化运营运维创新升级
- 阿里云边缘计算又获奖啦!
- 千万商家的智能决策引擎AnalyticDB
- 云原生:重新定义信息产业生态体系