简介:一套已编译好的Podofo 0.9.5库,目标平台是VS2013下的x86,面向需要在Windows系统中读写PDF文档的C++开发者。该库围绕PDF文档的解析、生成与操作提供完整接口,常用于配合zlib压缩库与freetype字体库使用,在VS2013的x86配置下可被工程直接引用,省去从源码构建Podofo的步骤。压缩包内共有113个文件,其中107个头文件定义了PDF文档、页面、字体、加密、过滤器等核心类,两个lib文件用于静态链接,两个dll文件用于运行时动态调用,两个pdb文件则提供调试符号,发布包整体约6.74MB,非常适合快速部署。从下载数据看,已有316人学习使用,证明该版本在VS2013环境中经过实际检验。基于这套库,开发者能够完成PDF元数据提取、页面渲染、文本内容抽取以及内容修改等处理任务,也可以将调试符号随工程保存,方便定位崩溃位置,是Windows平台开展PDF功能开发的一个省时选择。
1. 为什么还在折腾 podofo 0.9.5 + VS2013 这个老组合
1.1 老项目的无奈与选择
说实话,2024年还在写 VS2013 的 C++ 项目,听起来确实有点穿越。但如果你是接手老系统的维护开发,或者是给某些工业设备、银行柜面、医疗仪器做配套工具,大概率会遇到这种场景:客户的生产环境只部署了 VS2013 的运行时,系统架构锁定 x86,代码仓库里还有一堆只能在老工具链下编译的第三方库。这时候你要往系统里加一个 PDF 解析、生成或填表的功能,选择就不多了。
在我接触过的 PDF 开源库里,大致有这么几条路:Poppler 对 Windows 的支持一直不算友好,编译依赖的 glib、cairo 一整套下来能把人折腾疯;PDFium 是 Chromium 团队维护的,功能强但是接口风格偏底层,而且官方预编译包基本只给 x64;mupdf 倒是轻量,可它的 API 变化太激进,从 1.x 到 2.x 接口大变,老项目要适配得改不少代码。相比之下,PoDoFo 算是最务实的选择:纯 C++ 编写、接口稳定、依赖可控,0.9.x 系列对 VS2013 的支持相当友好,x86 和 x64 都能顺利编译。
1.2 为什么是 0.9.5 而不是新版本
PoDoFo 在 0.9.x 之后经历了较长的停滞期,直到 2019 年前后才开始活跃迭代 0.10.x。但新版本有一个绕不开的问题:它对编译器的要求提高了,VS2013 的 C++ 标准支持程度已经喂不饱新代码——比如std::unique_ptr、nullptr这些在 VS2013 里虽然能用,但某些模板特化和 constexpr 的写法在新版 PoDoFo 里会直接编译报错。
而 0.9.5 恰好是最后一个对老编译器极其宽容的版本。它的 CMake 配置简单到几乎没有门槛,依赖库选老版本就能直接过编译。此外,很多工业软件用的还是 MFC 或者 Win32 界面框架,进程模型默认 32 位,为了不打破既有模块的内存布局和 COM 交互方式,x86 是唯一选项。所以podofo-0.9.5-vs2013-x86 这个组合不是情怀,是刚需。下面我就把这套库从拉源码到跑通示例的完整过程记录下来,包括我实际踩过的坑。
2. 编译前的准备:工具链和依赖库,一个都不能缺
2.1 基础工具清单
编译 podofo 0.9.5 之前,先把工具备齐:
| 工具 | 版本/说明 | 备注 |
|---|---|---|
| Visual Studio | VS2013 Update 5 | Update 5 修正了大量 C++ 编译器和 STL 的问题,必装 |
| CMake | 2.8.12 到 3.9 之间均可 | CMake 3.10+ 仍然支持 VS2013 生成器,但有少量废弃警告 |
| zlib | 1.2.8(或 1.2.11) | 处理 PDF 内嵌的 FlateDecode 压缩流 |
| FreeType | 2.5.5 | 字体字形渲染核心依赖 |
| libjpeg / libpng | 可选,但有就开着 | 支持 PDF 内嵌图片的解码/编码 |
| OpenSSL(可选) | 1.0.1 或 1.0.2 | 支持 PDF 加密/签名功能,但不是必须 |
注意一个细节:zlib 建议用 1.2.8 而不是 1.2.13。不是说新版不能用,而是新版 zlib 对 CMake 的最低版本有要求,如果你手里只有 VS2013 内置的 CMake 模块,老版本 zlib 可以直接用源码包里的win32/Makefile.msc通过 nmake 编译,不依赖 CMake 也完全没问题。
2.2 x86 依赖库的获取与预编译策略
依赖库的构建方式有三种,我分别试过,说下感受:
- 方案 A:自己用 VS2013 逐个编译 zlib、freetype、jpeg、png。最稳妥,但是费时。freetype 在 VS2013 下有少量源文件会出现
for each关键字冲突或std::remove_if的判断谓词问题,需要打小补丁。 - 方案 B:用 vcpkg 以
x86-windowstriplet 安装老版本。vcpkg 虽然能指定老版本,但需要 checkout 历史 commit,操作麻烦不说,新版 vcpkg 默认已经不支持 VS2013 工具集(需要设置VCPKG_PLATFORM_TOOLSET=v120之类),容易撞墙。 - 方案 C:使用官方网站提供的预编译依赖。zlib 官网有老版本二进制,freetype 官网也提供 Windows 安装包,但安装包默认 x64,x86 版本要找历史链接。
我最终采用的是折中路线:zlib 用 nmake 从源码编译,freetype 用 CMake + VS2013 编译,jpeg 和 png 直接省略。因为我的场景只是生成 PDF 和做文本提取,不涉及图片解码所需的 JPEG/PNG 支持。如果你也需要图片处理,再把这俩加上。
提示:所有依赖库的运行时库设置必须与后续编译 podofo、以及使用 podofo 的最终项目保持一致。
/MD(Release 动态)和/MDd(Debug 动态)是最常见的配置,千万不要在依赖库里用/MT,否则链接时会出现LNK2038运行时库不匹配的错误。
3. 从 CMake 到 VS2013:完整编译流程与踩坑记录
3.1 CMake 生成项目文件的参数细节
我习惯在 podofo 源码目录的同级建一个build-x86目录,保持源码干净:
cmake .. -G "Visual Studio 12 2013" -A Win32 ^ -DPODOFO_BUILD_SHARED=OFF ^ -DPODOFO_BUILD_STATIC=ON ^ -DPODOFO_HAVE_JPEG_LIB=OFF ^ -DPODOFO_HAVE_PNG_LIB=OFF ^ -DPODOFO_HAVE_TIFF_LIB=OFF ^ -DPODOFO_HAVE_LUA=OFF ^ -DPODOFO_HAVE_OPENSSL=OFF ^ -DFREETYPE_INCLUDE_PATH_FT2BUILD=G:/libs/freetype-2.5.5/include ^ -DFREETYPE_INCLUDE_PATH_FTHEADER=G:/libs/freetype-2.5.5/include ^ -DFREETYPE_LIBRARY=G:/libs/freetype-2.5.5/build-x86/Release/freetype.lib ^ -DZLIB_INCLUDE_DIR=G:/libs/zlib-1.2.8 ^ -DZLIB_LIBRARY=G:/libs/zlib-1.2.8/build-x86/Release/zlib.lib有几个参数值得单独讲清楚:
PODOFO_BUILD_SHARED=OFF和PODOFO_BUILD_STATIC=ON是为了生成静态库。在工业软件场景里,静态库链接最省心,省去部署 DLL 到客户机器上的麻烦。-A Win32是 CMake 3.x 里指定 x86 平台的方式,老一点的 CMake 2.8 用的是-DCMAKE_GENERATOR_PLATFORM=Win32。指定错了会生成 x64 工程,后面白白浪费时间。- 关掉 TinyXML 相关的选项?其实 podofo 0.9.5 没有 TinyXML 依赖,但有个
PODOFO_HAVE_TINXML相关的配置,我默认不打开即可,影响不大。
3.2 编译顺序与配置对齐
生成出来的.sln里包含多个项目,一个是 podofo 主库,另外还有若干工具,如podofoimpose、podofocrop、podofobox等。一般来说直接编译ALL_BUILD,Release 和 Debug 各生成一遍。生成时间不长,大概两三分钟,主要是模板实例化耗时。
编译完成后在build-x86/src/Release下会得到podofo.lib(静态库)和若干工具 exe。Debug 版在build-x86/src/Debug下得到podofod.lib,命名上带了个d,注意区分。
注意:如果你在 Release 下编译通过,Debug 下报了一堆
_ITERATOR_DEBUG_LEVEL不匹配的错误,十有八九是依赖库只编译了 Release 版本,Debug 找不到对应的 lib。解决方法是把 zlib 和 freetype 的 Debug 版本也编译出来,或者干脆 Debug 版本也链接 Release 依赖库,同时把 podofo 的 Debug 配置里的_ITERATOR_DEBUG_LEVEL手动调成 0,但我不推荐后者,检测越界时会漏报。
3.3 我踩过的三个编译坑
坑一:freetype 的ftmodule.h生成失败
在编译 freetype 2.5.5 时,CMake 会尝试通过make命令生成头文件,Windows 上如果没有安装 GNU make,这一步直接卡住。解决办法是先运行cmake -P cmake_install.cmake?不对,正确做法是在 CMake 配置 freetype 时打开CMAKE_DISABLE_FIND_PACKAGE_HarfBuzz,同时在系统 PATH 里加上 GnuWin32 的 make,或者直接修改 freetype 源码里的CMakeLists.txt,把那一步生成逻辑跳过。我是用 GnuWin32 搞定的。
坑二:podofo 源码里的PdfVec迭代器兼容问题
0.9.5 在 VS2013 下有个已知的小毛病:某些模板函数的返回值类型推断有歧义,会报error C2440: 'return' : cannot convert。定位到src/base/PdfVec.h文件里,重载operator[]的 const 版本,把返回类型由const T&改为const T&?其实这不是返回类型问题,而是PdfVec底层用了std::vector,VS2013 的标准库对const_reference的处理和 GCC 有差异。我直接修改了PdfVec.h,给相关的迭代器操作增加了一个显式类型转换,问题解决。这个补丁在网络上可以搜到,叫 "podofo 0.9.5 VS2013 patch",或者自行搜索。
坑三:静态库符号冲突
如果 podofo 和另一个静态库都链接了 zlib,最终 exe 链接时会出现重复符号。处理办法有两个:要么用/FORCE:MULTIPLE强行忽略(不推荐,运行期行为不确定);要么在使用 podofo 的项目里不显式链接 zlib,让 podofo.lib 内部的符号外部化。实际上 podofo 编译时如果选择不静态包含 zlib,那 podofo.lib 里只有对 zlib 函数的引用符号,最终链接时你仍需提供 zlib.lib。关键点是把 zlib.lib 放在 podofo.lib 之后,链接器按顺序解析符号,避免二义性。
4. 在 VS2013 项目里集成 podofo 库:配置与最小示例
4.1 项目配置速查表
拿到编译好的 lib 和头文件后,在你自己的 VS2013 工程里这样配置:
- C/C++ -> 常规 -> 附加包含目录: 添加
G:/podofo-0.9.5/src和G:/podofo-0.9.5/build-x86/src(因为生成的头文件podofo_config.h在 build 目录)。 - 链接器 -> 常规 -> 附加库目录: 添加
G:/podofo-0.9.5/build-x86/src/Release。 - 链接器 -> 输入 -> 附加依赖项: 填写
podofo.lib、freetype.lib、zlib.lib。 - C/C++ -> 预处理器 -> 预处理器定义: 添加
PODOFO_STATIC,这个宏控制导入导出,静态库场景必须加。
注意,podofo 0.9.5 的某些头文件对 ANSI 和 Unicode 字符集敏感。如果你的项目用了 Unicode 字符集,而 podofo 用的是多字节字符集,可能存在隐式转换警告,但编译通常能过。我建议在预处理定义里显式加上_MBCS,并在项目属性里把字符集统一成"使用多字节字符集",彻底消除这类警告。
4.2 最小可用的 PDF 生成代码
配置好之后,跑通一个最简单的 PDF 生成示例,确认整套链路没问题。下面这段代码在 Release x86 下编译运行正常:
#include <podofo/podofo.h> using namespace PoDoFo; int main() { PdfMemDocument document; document.CreateEmptyDocument(); PdfPage* page = document.CreatePage(PdfPage::CreateStandardPageSize(PdfPageSize::A4)); if (!page) return -1; PdfPainter painter; painter.SetPage(page); painter.SetColor(0.2, 0.4, 0.8); painter.DrawText(50, 750, "Hello PoDoFo VS2013 x86"); painter.FinishDrawing(); document.Save("test.pdf"); return 0; }这个例子涵盖了三个核心流程:创建文档、创建页面、绘制文本。其中PdfPage::CreateStandardPageSize可以直接拿到 A4 纸张尺寸,不需要手动传点数。需要注意的是PdfPainter在构造和SetPage之间不需要特殊初始化,但如果要绘制中文文本,必须加载支持中文的 TTF 字体,并且用PdfFontCache设置嵌入子集,否则输出 PDF 的中文会显示为乱码或不显示:
// 加载系统中文字体 PdfFont* font = document.GetFontCache()->GetFont("Microsoft YaHei", false, true); painter.SetFont(font);GetFont的第二个参数false表示不嵌入完整字体文件,第三个参数true表示嵌入子集。对于只包含少量中文的 PDF,子集嵌入能把文件体积控制在几十 KB。
4.3 我踩过的第四个坑:动态链接与 C 运行时冲突
使用 podofo 静态库时,最容易被忽略的是 C 运行时的线程本地变量问题。如果你的主程序用/MT编译,而 podofo 库是用/MD编译的,链接会报LIBCMT.lib和MSVCRT.lib冲突。反过来,主程序用/MD、podofo 用/MT也一样。最稳妥的做法是统一用/MD,因为 VS2013 的/MD运行时会相应地在目标机器上依赖msvcr120.dll,而一般客户的机器上这套运行库通常已经装好。
5. 实战功能扩展:不只是生成 PDF
5.1 读取和解析已有 PDF 的文本
搞定了编译和基础生成,更大的需求其实是读取 PDF,比如从已有的电子发票、报告里提取文本。podofo 0.9.5 里做文本提取不如新版那么自动化,没有现成的"提取全部文本"方法,需要通过内容流解析器逐段读取:
PdfMemDocument doc; if (doc.Load("existing.pdf") != PdfErrorCode::ePdfError_Ok) { return -1; } PdfContentsTokenizer tokenizer(page); const char* token; EPdfContentsType type; while (tokenizer.ReadNext(type, token)) { if (type == ePdfContentsType_Text) { // token 是以 Tj/TJ 形式显示的文本内容 PdfString str(token); // 处理字符串,注意 PDF 可能用十六进制编码表示 } }这个方案的局限在于:如果 PDF 里是扫描图片,或者文本被打散成单个字符的独立绘制指令,提取结果会非常碎片化。podofo 本质上是面向 PDF 结构操作的库,文本提取只是附带能力。真要高效提取文本,还是要配合 OCR 引擎或者专门的文本抽取层。但在结构化 PDF(程序生成的报告、表单、票据)上,用这个代码提取文本是靠谱的。
5.2 表单填写的实现思路
另一个高频需求是 PDF 表单填写。0.9.5 通过PdfAcroForm和PdfField系列类来支持:
PdfAcroForm* form = doc.GetAcroForm(); if (!form) return -1; PdfFieldIterator it = form->GetFieldIterator(); while (it) { PdfField* field = *it; if (field->GetFieldType() == ePdfFieldType_TextField) { PdfTextField* textField = static_cast<PdfTextField*>(field); textField->SetText("fill value"); } it.Next(); }对于 PDF 表单填写,有个关键点是PdfAcroForm在加载时默认不会初始化所有字段的缓存,如果你直接调用GetFieldIterator返回空,多半是 PDF 的表单字典结构比较特殊。建议在 Load 之后先调用doc.GetAcroForm()->ForcePageInsertion()?其实不是,更稳妥的是调用form->GetFieldTreeRoot()来遍历字段树。这个遍历函数在 0.9.5 版本里稳定可用,只是名字比较隐藏。另外,填写完成后保存时,记得用document.Save(..., PdfSaveOptions::eSaveOptions_Incremental)增量保存,避免对整个文件做全量重写,保留原始 PDF 结构完整性,同时减小写入负担。
5.3 与图像数据交互的特殊情况
我的项目里还遇到一个需求:把 C++ 内存中的位图数据直接填入 PDF 页面当作图片。这个问题在 podofo 0.9.5 里稍微绕一点。库本身支持PdfImage类,可以通过SetImageData接收像素数据,但要求数据格式是 RGB 原始数组。例子:
PdfImage image(&doc); image.SetImageData(width, height, colorSpace, bitsPerComponent, rawDataBuffer); page->GetResources()->AddResource("Img1", &image); painter.DrawImage(50, 50, &image, scaleX, scaleY);这里的rawDataBuffer如果是 BITMAPINFO 的bits指针,需要注意 Windows 位图的扫描行是自底向上、每行按 4 字节对齐的,和 PDF 期望的自顶向下行序正好相反。我就在这里吃了亏,花了半天才定位到是行序颠倒导致图片上下翻转。处理办法是先把位图逐行反转后再传给 podofo,或者让上位机直接提供自顶向下的 RGB 数据。
6. 关于这套库的维护性,我说点实在的
podofo 0.9.5 不是完美的库,它的加密模块在 VS2013 x86 下如果打开 OpenSSL 支持,链接时会多出不少依赖,有时候还会因为 OpenSSL 1.0.x 的 EVP 接口在 VS2013 下的一些兼容性问题导致 PDF 加密功能崩溃。我建议在非必要场景下关闭 OpenSSL 支持,因为大部分项目的需求只是解析和生成非加密 PDF,就算客户给的 PDF 是加密的,也可以要求对方提供解密后的版本。加密支持虽然听起来很有价值,但在老工具链下是纯粹的风险面。
再就是 0.9.5 的线程安全性。这个版本大量使用静态局部变量做缓存(尤其是PdfFontCache),在多线程下同时加载字体文件会引发数据竞争。如果项目的架构是多线程任务同时处理 PDF,我的建议是每线程用独立的PdfMemDocument实例,不要让多个线程共享同一个PdfFontCache,或者加一个全局锁保护字体初始化过程。实测下来,线程内独立实例的方式最可靠,性能损失也可以接受。
如果你只是临时用一下、没有长期维护计划,也可以考虑直接把 podofo 0.9.5 的源码加入你的工程一起编译,而不是编译成独立库,省去 include/lib 路径配置这一堆事。缺点是你的工程里会多出一坨 pdf 相关的源文件,构建时间变长,而且代码结构上不够干净,遇到升级维护会比较头疼。综合考虑,我还是推荐独立编译库的方式,一次配置,到处链接。
还有一个细节:如果你团队里不同成员机器上装的 VS2013 版本不同(比如有人没装 Update 5),编译 podofo 的时候可能出现 STL 头文件的版本错位,导致莫名奇妙的编译失败。建议把 VS2013 Update 5 作为团队标准环境写进项目文档,避免后续无意义的排查。
最后,把我实际编译好的库目录结构展示一下,供你参考:
G:/podofo-0.9.5/ build-x86/ src/ Release/podofo.lib Debug/podofod.lib src/ (源码与公开头文件) libs/ freetype-2.5.5/ build-x86/Release/freetype.lib zlib-1.2.8/ build-x86/Release/zlib.lib我的经验是,这套组合只要一次编译通过,后面能用很多年——老系统最怕的不是库老,而是比你更老的同事离职后没人知道怎么搭环境。把这篇过程整理成团队 wiki,后面接手的人照着做就能省掉两三天的摸索时间,这笔账怎么算都划算。
本文还有配套的精品资源,点击获取