十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

podofo 0.9.5 在 VS2013 x86 环境下的编译集成与 PDF 处理实践

podofo 0.9.5 在 VS2013 x86 环境下的编译集成与 PDF 处理实践 简介一套已编译好的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 团队维护的功能强但是接口风格偏底层而且官方预编译包基本只给 x64mupdf 倒是轻量可它的 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 StudioVS2013 Update 5Update 5 修正了大量 C 编译器和 STL 的问题必装CMake2.8.12 到 3.9 之间均可CMake 3.10 仍然支持 VS2013 生成器但有少量废弃警告zlib1.2.8或 1.2.11处理 PDF 内嵌的 FlateDecode 压缩流FreeType2.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_TOOLSETv120之类容易撞墙。方案 C使用官方网站提供的预编译依赖。zlib 官网有老版本二进制freetype 官网也提供 Windows 安装包但安装包默认 x64x86 版本要找历史链接。我最终采用的是折中路线zlib 用 nmake 从源码编译freetype 用 CMake VS2013 编译jpeg 和 png 直接省略。因为我的场景只是生成 PDF 和做文本提取不涉及图片解码所需的 JPEG/PNG 支持。如果你也需要图片处理再把这俩加上。提示所有依赖库的运行时库设置必须与后续编译 podofo、以及使用 podofo 的最终项目保持一致。/MDRelease 动态和/MDdDebug 动态是最常见的配置千万不要在依赖库里用/MT否则链接时会出现LNK2038运行时库不匹配的错误。3. 从 CMake 到 VS2013完整编译流程与踩坑记录3.1 CMake 生成项目文件的参数细节我习惯在 podofo 源码目录的同级建一个build-x86目录保持源码干净cmake .. -G Visual Studio 12 2013 -A Win32 ^ -DPODOFO_BUILD_SHAREDOFF ^ -DPODOFO_BUILD_STATICON ^ -DPODOFO_HAVE_JPEG_LIBOFF ^ -DPODOFO_HAVE_PNG_LIBOFF ^ -DPODOFO_HAVE_TIFF_LIBOFF ^ -DPODOFO_HAVE_LUAOFF ^ -DPODOFO_HAVE_OPENSSLOFF ^ -DFREETYPE_INCLUDE_PATH_FT2BUILDG:/libs/freetype-2.5.5/include ^ -DFREETYPE_INCLUDE_PATH_FTHEADERG:/libs/freetype-2.5.5/include ^ -DFREETYPE_LIBRARYG:/libs/freetype-2.5.5/build-x86/Release/freetype.lib ^ -DZLIB_INCLUDE_DIRG:/libs/zlib-1.2.8 ^ -DZLIB_LIBRARYG:/libs/zlib-1.2.8/build-x86/Release/zlib.lib有几个参数值得单独讲清楚PODOFO_BUILD_SHAREDOFF和PODOFO_BUILD_STATICON是为了生成静态库。在工业软件场景里静态库链接最省心省去部署 DLL 到客户机器上的麻烦。-A Win32是 CMake 3.x 里指定 x86 平台的方式老一点的 CMake 2.8 用的是-DCMAKE_GENERATOR_PLATFORMWin32。指定错了会生成 x64 工程后面白白浪费时间。关掉 TinyXML 相关的选项其实 podofo 0.9.5 没有 TinyXML 依赖但有个PODOFO_HAVE_TINXML相关的配置我默认不打开即可影响不大。3.2 编译顺序与配置对齐生成出来的.sln里包含多个项目一个是 podofo 主库另外还有若干工具如podofoimpose、podofocrop、podofobox等。一般来说直接编译ALL_BUILDRelease 和 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::vectorVS2013 的标准库对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. 实战功能扩展不只是生成 PDF5.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_castPdfTextField*(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后面接手的人照着做就能省掉两三天的摸索时间这笔账怎么算都划算。本文还有配套的精品资源点击获取
返回列表