一直以来,我都想为 PDF 补丁丁添加一个 PDF 渲染引擎。可是,目前并没有可以在 .NET 框架上运行的免费 PDF 渲染引擎。经过网上的搜索,有人使用 C++/CLI 调用 XPDF 或 Mupdf,实现了不安装 Adobe 系列软件而渲染出 PDF 文件的功能。

Mupdf 是一个开源的 PDF 渲染引擎,使用 C 语言编写,可编译成能让 C# 调用的动态链接库。因此,只要编写合适的调用代码,就能使用该渲染引擎,将 PDF 文档转换为一页一页的图片,或者在程序界面显示 PDF 文档的内容。

要使用 Mupdf 渲染 PDF 文档,有几个步骤:

  1. 获取 Mupdf 的动态链接库。
  2. 了解该库中的相关导出函数。
  3. 为导出函数撰写 P/Invoke 代码。
  4. 撰写 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 文档时用到的结构主要有五个:

  1. fz_context:存放渲染引擎所用的全局数据。
  2. fz_document:存放文档的信息。
  3. fz_page:存放页面的数据。
  4. fz_device:用于放置渲染结果的目标设备。
  5. fz_pixmap:存放渲染结果的画布。

Fitz.h 文件中提供的函数均以“fz_”开头,这些函数可用于处理上述五个结构。以上述五个结构为基础,调用相应的函数,就能完成渲染 PDF 文档的任务。

没有 C 语言基础的开发人员请注意:部分预定义处理指令——即 #define 指令,也使用“fz_”开头,这些处理指令并不是导出函数。在使用 P/Invoke 技术调用函数库时不能使用 #define 指令定义的替换函数。例如,fz_try、fz_catch、fz_finally 就是这类型的预定义处理指令。

为导出函数撰写 P/Invoke 代码

Fitz.h 提供的导出函数中,下列函数在渲染 PDF 文档时是必须使用的。

  1. fz_new_context:创建渲染文档时的上下文变量。
  2. fz_free_context:释放上下文变量所占用的资源。
  3. fz_open_file_w:打开文件流(传入的文件名变量为 Unicode)
  4. fz_open_document_with_stream:打开文件流对应的文档(PDF 或其它支持的文件格式)。
  5. fz_close_document:关闭文档。
  6. fz_close:关闭文件流。
  7. fz_count_pages:获得文档的页数。
  8. fz_load_page:加载文档指定的页面。
  9. fz_free_page:释放文档页面占用的资源。
  10. fz_bound_page:确定文档页面的尺寸。
  11. fz_new_pixmap:创建渲染页面所用的图形画纸。
  12. fz_clear_pixmap_with_value:清除画纸(通常用于将 PDF 文档的背景色设置为纯白色)。
  13. fz_new_draw_device:从画纸创建绘图设备。
  14. fz_find_device_colorspace:获取渲染页面所用的颜色域(彩色或灰色)。
  15. fz_run_page:将页面渲染到指定的设备上。
  16. fz_free_device:释放设备所占用的资源。
  17. fz_drop_pixmap:释放画纸占用的资源。
  18. 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 方法,释放相关的资源。

渲染页面的流程如下,按步骤逐个调用上述的函数即可:

  1. 加载文档。
  2. 加载页面。
  3. 预备好绘图画纸(Pixmap)。
  4. 从绘图画纸创建绘图设备。
  5. 将页面绘制到绘图设备(即画纸)上。
  6. 将画纸的数据转换为 Bitmap。
  7. 保存 Bitmap 或将 Bitmap 绘制到程序界面。
  8. 释放 Bitmap 的资源。
  9. 释放画纸、绘图设备、页面和文档的资源。

代码如下所示。

static void Main (string[] args) {
const uint FZ_STORE_DEFAULT = << ;
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 = ; 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+) + ".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,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 (, ,
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 = ; y < height; y++) {
byte* pl = ptrDest;
byte* sl = ptrSrc;
for (int x = ; x < width; x++) {
// 将 Pixmap 的色彩数据转换为 Bitmap 的格式
pl[] = sl[]; //b-r
pl[] = sl[]; //g-g
pl[] = sl[]; //r-b
//sl[3] 是透明通道数据,在此忽略
pl += ;
sl += ;
}
ptrDest += imageData.Stride;
ptrSrc += width * ;
}
}
NativeMethods.DropPixmap (context, pix); // 释放 Pixmap 占用的资源
return bmp;
}

好了,渲染 PDF 文档的代码雏形就此完成了。

在实际项目开发中,我们还需要考虑以下几个首要问题:

  1. 处理 Mupdf 抛出的异常:捕获 AccessViolationException 异常。
  2. 记住释放资源:可考虑将相应的资源封装为实现 IDisposible 接口的类。
  3. 扩展程序的功能:可参考使用 Mupdf 的开放源代码项目,其中最著名的一个项目莫过于 SumatraPDF。
  4. 在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

转:在 C# 中使用 P/Invoke 调用 Mupdf 函数库显示 PDF 文档的更多相关文章

  1. 如何突出显示PDF文档中的一些重要文本信息

    PDF文档中如果存在着太多的文字时,阅读者会容易遗漏很多重要的信息.但如果,文档中存在着一些特殊标记的文字时,比如标黄.标红文本时,很多人都会给予特别关注. 因此,当大家在使用pdfFactory专业 ...

  2. 如何在ASP.NET Core 中快速构建PDF文档

    比如我们需要ASP.NET Core 中需要通过PDF来进行某些简单的报表开发,随着这并不难,但还是会手忙脚乱的去搜索一些资料,那么恭喜您,这篇帖子会帮助到您,我们就不会再去浪费一些宝贵的时间. 在本 ...

  3. 在 C# 中通过 P/Invoke 调用Win32 DLL

    在 C# 中通过 P/Invoke 调用Win32 DLL 发布日期 : 1/13/2005 | 更新日期 : 1/13/2005 Jason Clark 下载本文的代码: NET0307.exe ( ...

  4. Android中使用POI加载与显示word文档

    最近打算实现一个功能:在Android中加载显示Word文档,当然这里不是使用外部程序打开.查看一些资料后,打算采用poi实现,确定了以下实现思路: 将ftp中的word文档下载到本地. 调用poi将 ...

  5. F2833x 调用DSP函数库实现复数的FFT的方法

    转载自:http://blog.csdn.net/aeecren/article/details/67644363:个人觉得写的很详细,值得一看 在数字信号处理中,FFT变换是经常使用到的,在DSP中 ...

  6. C#通过调用WinApi打印PDF文档类,服务器PDF打印、IIS PDF打印

    其他网站下载来的类,可以用于Winform.Asp.Net,用于服务器端PDF或其他文件打印. 直接上代码: using System; using System.Collections.Generi ...

  7. 下载网页中的 pdf 各种姿势,教你如何 carry 各种网页上的 pdf 文档。

    关联词: PDF 下载 FLASH 网页 HTML 报告 内嵌 浏览器 文档 FlexPaperViewer swfobject. 这个需求是最近帮一个妹子处理一下各大高校网站里的 PDF 文档下载, ...

  8. 如何使用免费PDF控件从PDF文档中提取文本和图片

             如何使用免费PDF控件从PDF文档中提取文本和图片 概要 现在手头的项目有一个需求是从PDF文档中提取文本和图片,我以前也使用过像iTextSharp, PDFBox 这些免费的PD ...

  9. 将w3cplus网站中的文章页面提取并导出为pdf文档

    最近在看一些关于CSS3方面的知识,主要是平时看到网页中有很多用CSS3实现的很炫的效果,所以就打算系统的学习一下.在网上找到很多的文章,但都没有一个好的整理性,比较凌乱.昨天看到w3cplus网站中 ...

随机推荐

  1. ss搭建

    aliyun ecs ,hongkong , t5 , 1M, 1.卸载阿里云盾监控 wget http://update.aegis.aliyun.com/download/uninstall.sh ...

  2. java直接生成zip压缩文件精简代码(跳过txt文件)

    /** * @param args */ public static void main(String[] args) throws Exception{ ZipOutputStream zos = ...

  3. Win10 高分屏软件界面字体模糊问题解决

    只需要将自定义缩放值改为 124% 即可

  4. 【CF613D】Kingdom and its Cities 虚树+树形DP

    [CF613D]Kingdom and its Cities 题意:给你一棵树,每次询问给出k个关键点,问做多干掉多少个非关键点才能使得所有关键点两两不连通. $n,\sum k\le 10^5$ 题 ...

  5. Namespace declaration statement has to be the very first statement in the script-去除bom头

    今天准备测试小程序的签名加密,但是刚引入官方的“加密数据解密算法”文件到项目里,然后为每个文件添加命名空间的时候,不管怎么加都报“Namespace declaration statement has ...

  6. [Codeforces Round #221 (Div. 1)][D. Tree and Queries]

    题目链接:375D - Tree and Queries 题目大意:给你一个有n个点的树,每个点都有其对应的颜色,给出m次询问(v,k),问v的子树中有多少种颜色至少出现k次 题解:先对所有的询问进行 ...

  7. 接口自动化测试 (三)request.post

    上一节介绍了  requests.get()  方法的基本使用,本节介绍  requests.post()  方法的使用: 本文目录: 一.方法定义 二.post方法简单使用 1.带数据的post 2 ...

  8. CTextUI 文本控件 显示数字方法

    得将数字变成字符串才行 m_ptxtCurrentcharUI->SetText(util::int32ToCString(txtLength)); 或 String.valueOf(x) 或 ...

  9. 关于Could not load driverClass ${jdbc.driverClassName}问题解决方案

    在spring与mybatis3整合时一直遇到Could not load driverClass ${jdbc.driverClassName}报错如果将 ${jdbc.driverClassNam ...

  10. MySQL数据库(增删查改)

    创建一个表:create table user( uid varchar(10) , pwd int(10) ); 学生表: create table student( sno varchar(20) ...