ARTICLE DETAIL

资讯详情

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

libharu 2.3.0 跨平台编译指南:用 CMake 搞定 PDF 生成库

libharu 2.3.0 跨平台编译指南:用 CMake 搞定 PDF 生成库 简介libharu 2.3.0 是一款轻量级开源 PDF 读写库仅依赖 libpng 和 zlib编译与集成成本非常低面向需要快速生成 PDF 的 C/C 开发者尤其适合希望解决中英文、简繁体混合输出问题的场景。作者已完美编译静态库和 dll 动态库共四个版本推荐使用 dll 版无需链接额外静态库即可直接集成到项目中。资源压缩包共 361 个文件大小仅 2.66MB以 59 个 c 源码、53 个 h 头文件、26 个 vcxproj 工程文件、25 个可执行示例、20 个生成的 PDF 及 16 个 png 图片为主另外包含 8 个 lib 和 2 个 dll 库文件工程结构清晰便于对照学习。除官方全部实例外作者还额外添加了一个中文输出示例通过 Unicode 转 GBK 函数弥补原库对 Unicode 支持不足的短板使中英文、简繁体混合排版输出更加完善。目前已有 1605 人学习下载适合嵌入式或桌面端需要轻量 PDF 生成能力的开发者快速上手。1. 为什么非要自己编译 libharu1.1 官方仓库里没有省事儿的安装包你翻遍 libharu 的 GitHub Release 页面会发现 2.3.0 这个版本只提供源代码包预编译的 .lib、.dll、.so 一律没有。原因倒也不难理解一个用 ANSI C 写的老牌 PDF 库依赖又涉及 zlib、libpng 这类周边组件想要在发布时覆盖所有平台和编译器组合维护成本远大于收益。所以官方把“适配什么编译器、选静态还是动态、开不开启 PNG 支持”这个选择权直接交还给了使用者。对一个需要落地到具体项目的开发者来说这反而是一种自由。你不需要为了某个库里绑定的二进制版本被迫升级编译器也不需要担心官方预编译包是用什么奇怪的参数做出来的所有东西都在自己掌控之下。1.2 2.3.0 的构建系统变化真正值得庆幸的是2.3.0 把构建系统从老旧的 autotools 迁移到了 CMake。以前在 Windows 上想编译 libharu要打开 Visual Studio 手动维护一份 .vcproj还得自己改一堆类库路径稍有不慎就是几十个 LNK 错误。换到 CMake 之后配置、编译、安装三个命令就能完成整条链路思路也清晰很多。你要做的第一件事其实是理解这个库比你想的更“老实”它只是把哪些功能打开、链接到什么依赖这些决定交给你而已。1.3 libharu 和其他 PDF 生成方案的取舍我见过有人问“为什么不用 iText 或者 Chromium 打印让浏览器生成 PDF”我的看法是iText 是 JVM 生态的重量级方案为一个几十行的工具引入 Java 运行时光太贵Chromium 保证“所见即所得”的同时也带来了几百 MB 的体积和进程管理问题。libharu 的价值在于“够用”你不需要解析 PDF不考虑复杂排版引擎只需要把数据写进去、保存成文件它自带的字体、绘制图形、图片嵌入接口就足够应付绝大多数业务报表了。2. 编译前的准备工作2.1 工具链与版本选择编译 libharu 2.3.0 对工具链的要求并不苛刻我实测过几套组合操作系统推荐工具链注意事项WindowsVisual Studio 2019 / 2022CMake 3.16 以上建议 x64 架构Linuxgcc 7.x 以上需要 zlib1g-dev 和 libpng-dev 时自行安装macOSXcode Command Line Tools可直接使用系统 clang配 Ninja 体验更好32 位工具链虽然历史原因存在但现代环境我建议直接按 x64 编省得天坑遍地。尤其 Windows 下混用 32 位库和 64 位程序链接时的“无法解析的外部符号”劝退过不少新手。2.2 依赖库处理zlib 与 libpnglibharu 本身不是“零依赖”。它做对象压缩时要用到 zlib嵌入 PNG 图片时需要 libpng。理解这一点编译时你就有两条路。第一条路不装任何依赖编译时把 zlib、libpng 相关选项全部关掉。代价就是生成的 PDF 不压缩PNG 图片也嵌不进去对绝大多数纯文本报告来说其实无所谓。我早期做设备端程序时就是这么干的库体积小部署链条也短。第二条路提前编好 zlib 和 libpng然后用 CMAKE_PREFIX_PATH 指给 CMake 查找。这种方式的优点是可以拿到压缩后的 PDF体积通常能减少 50% 以上但你要为三个依赖的编译和版本一致性负责。如果项目里还在用 OpenSSL、curl 等其他依赖压缩的库zlib 的版本冲突会让你多花不少时间。2.3 获取源码与目录结构从 GitHub Release 页面下载 libharu-2.3.0.tar.gz解压后你会看到 include、src、test、cmake 几个目录。src 下是按功能拆分的 C 源文件include 里是公开头文件 hpdf.h。下载的时候可以顺手验证一下哈希值避免源文件不完整导致莫名其妙的编译行为。源码包本身只有不到 2 MB在动辄几十 MB 的大型开源库里算是非常精简的了。3. 三平台编译实操3.1 Windows Visual Studio 完整流程我经常推荐 Windows 用户走“源码目录外构建”的方式不要把生成的文件混进源码树。打开 Developer PowerShell执行cmake -S . -B build -G Visual Studio 17 2022 -A x64 -DCMAKE_INSTALL_PREFIXD:/libs/libharu -DLIBHPDF_ENABLE_SHAREDON -DLIBHPDF_ENABLE_ZLIBOFF -DLIBHPDF_ENABLE_LIBPNGOFF这里的 -A x64 指定生成 64 位工程-DLIBHPDF_ENABLE_SHAREDON 表示生成动态链接库。如果不想临时找 zlib把 ZLIB 和 LIBPNG 两个开关关掉库本身也能正常编出来只是输出 PDF 没有压缩。接下来编译安装cmake --build build --config Release cmake --install build编译完成后build/src/Release 下会生成 hpdf.dll 和 hpdf.libinclude 目录里的头文件会被安装到 D:/libs/libharu/include。这里有个细节值得说输出文件名不是 libharu而是 hpdf这是因为库本身一直沿用自己的 C 接口前缀很多人在链接时才反应过来。3.2 Linux / macOS 一条命令Linux 上如果包管理工具已经装好了 zlib1g-dev 和 libpng-dev那么保持默认开启依赖直接构建就行cmake -S . -B build -DCMAKE_BUILD_TYPERelease cmake --build build -j sudo cmake --install buildmacOS 同样如此。懒人方案就是先用系统包把 zlib、libpng 装齐再让 CMake 自动探测。需要注意macOS 上 CMake 默认生成的构建系统可能是 Unix Makefiles也可以直接指定 Ninjacmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPERelease cmake --build buildNinja 的并行编译速度确实更快尤其是 C 文件较多、又开满 CPU 核时体感很明显。3.3 CMake 参数逐条解析很多人在编译时卡住是因为没搞懂这些 LIBHPDF 开头的开关到底在控制什么。我把 2.3.0 里最常用的一组选项整理成了表选项默认值作用LIBHPDF_ENABLE_SHAREDON生成动态库。Windows 下对应 hpdf.dllLinux/macOS 下对应 libhpdf.so 或 dylibLIBHPDF_ENABLE_ZLIBON开启 zlib 压缩。关闭后 PDF 体积会大但少一个外部依赖LIBHPDF_ENABLE_LIBPNGON启用 PNG 图片嵌入。不解析图片的话建议关掉CMAKE_BUILD_TYPE空Release / Debug仅对单配置生成器生效CMAKE_INSTALL_PREFIX系统默认安装目录Windows 下务必改成自己方便找的路径从这张表也能看出libharu 的设计思路是“尽量可裁剪”。嵌入式项目往往只保留最核心的文本绘制能力把压缩和图片都关掉库体积能控制在百 KB 级别。需要提一句的是如果你想生成 Debug 版用来调试崩溃问题代码里不要开优化同时记得给主程序和库使用同样的运行时配置不然调试器里一堆“源码与符号不匹配”的提示会很难受。4. 把 libharu 集成到自己的项目4.1 写一个最小生成 PDF 示例编译通过只是开始真正跑起来才算数。我习惯用一个 20 行的最小示例验证整条链路是否通畅#include stdio.h #include stdlib.h #include hpdf.h static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *user_data) { (void)user_data; fprintf(stderr, libharu error: 0x%04X detail 0x%04X\n, (unsigned int)error_no, (unsigned int)detail_no); exit(EXIT_FAILURE); } int main(void) { HPDF_Doc pdf HPDF_New(error_handler, NULL); if (!pdf) return 1; HPDF_Page page HPDF_AddPage(pdf); HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_LANDSCAPE); HPDF_Font font HPDF_GetFont(pdf, Helvetica, NULL); HPDF_Page_SetFontAndSize(page, font, 14); HPDF_Page_BeginText(page); HPDF_Page_TextOut(page, 60, 60, hello libharu 2.3.0); HPDF_Page_EndText(page); HPDF_SaveToFile(pdf, hello.pdf); HPDF_Free(pdf); return 0; }这里用到了 PDF 标准自带的基础字体 Helvetica不需要嵌入任何字体文件所以代码很短。HPDF_New 里传入的错误回调函数不能省库在出错时会调用它输出错误码否则出现异常你只能看到一片黑盒。保存成功后用任意 PDF 阅读器打开能看到横版 A4 页面的左下角有一行文本。如果你的项目是 C 工程直接在 .cpp 里包含 hpdf.h 也没问题因为库本身就具备 C 和 C 的兼容性。担心头文件路径找不到的话只要把 libharu 安装目录下的 include 加进编译器头文件搜索路径即可。4.2 CMake 工程集成在自己的项目里有两种接法。第一种是用 find_package 找已安装的库find_package(HPDF CONFIG REQUIRED) target_link_libraries(demo PRIVATE HPDF::HPDF)前提是 CMake 能找到 libharu 的 config 文件Windows 下需通过 -DCMAKE_PREFIX_PATHD:/libs/libharu 指定安装根目录。第二种是把 libharu 源码直接放进工程用 add_subdirectory 编进去好处是依赖关系完全内聚跨机器构建不容易缺东西。实际项目里我反而推荐第二种省掉一堆“为什么我本机行同事那儿不行”的问题。不过第二种方式有个隐藏代价libharu 的 CMake 构建里会生成它的测试程序如果你不需要记得在 add_subdirectory 之前设置 BUILD_TESTINGOFF否则会拖慢整个工程的首轮构建速度。4.3 中文与字体嵌入libharu 的内置字体只有西文字体想输出中文必须自己提供 TTF 或 TTC 字体文件。做法是用 HPDF_UseFont 加载外部字体再通过 HPDF_GetFont 加上编码参数取到字体对象HPDF_Font font HPDF_GetFont(pdf, HPDF_LoadTTFontFromFile(pdf, simsun.ttc, 0), UTF-8);一个容易踩的坑是即使你加载了中文字体页面文本绘制时的默认编码如果还是 WinAnsiEncoding中文会变成乱码。务必在取字体时明确传 “UTF-8”。此外字体文件必须随程序一起分发否则换台机器就没字可用了。对多数报表场景我建议只在服务端保留一个 10 MB 以内的开源字体比如思源黑体的子集版效果比系统字体稳定得多。如果还不放心可以先用HPDF_GetEncoder(pdf, UTF-8)检查当前编码是否存在避免传了一个不存在的编码名导致返回 NULL。实际项目里我遇到中文文字能定位但显示为方块的怪问题十有八九就是字体文件没加载成功或者字体里不含对应字形。5. 我在编译过程中踩过的坑5.1 链接时找不到符号最常见的症状是“error LNK2019: unresolved external symbol HPDF_New”。原因无非三种忘了链接 hpdf.lib链接的是动态库的 import lib但程序字符集或调用约定不匹配头文件路径指到了旧版本。排查时先确认 hpdf.lib 确实在链接器的附加依赖里再看编译出来的库文件是不是 Release 版因为 Debug 版符号名和 Release 版一致但混用不同构建配置的库也常常引发诡异错误。Linux 下如果提示 undefined reference to HPDF_New通常是在链接命令里把 -lhpdf 写错了位置。GCC 处理库依赖是从右往左的源文件必须放在左侧库放在右侧例如gcc main.c -o app -lhpdf。如果你用了 pkg-config 自动拼接基本不会遇到这种低级问题。5.2 运行库不一致导致报错Windows 下用 MSVC 编译 libharu 时如果主工程用的是静态运行时 /MT而链接的依赖库是用 /MD 编的就会出现 “LNK2038 runtime library mismatch”。这个问题全称叫“运行库不一致”解决方法是保持主项目和依赖库的 MultiThreaded DLL 属性一致或者在编译 zlib 时也用对应开关统一。手动改 libharu 源码里的编译选项没有意义根因一定在 zlib 的导出符号上。对于这个坑我现在的习惯是项目里如果统一用 /MT那外部依赖库全部重新自己编译一次不要指望网上能下到正好匹配版本的二进制包。宁可多花一点时间也不要让问题留到客户机器上才爆发。5.3 动态库运行时丢失即使编译链接全通过运行时如果提示“找不到 hpdf.dll”问题就出在搜索路径上。把 hpdf.dll 复制到可执行文件旁边是最直接的办法也可以用 CMake 的 POST_BUILD 命令自动拷贝。我之前的做法是把 DLL 放到 exe 同目录的同时设置 PATH 环境变量不生效就干脆不走 PATH因为生产环境里还要考虑部署机器的隔离性。另外 Windows 下如果用了静态链接方式也就是链接 hpdf.lib 且把宏 HPDF_DLL 的定义去掉那就不需要带 hpdf.dll。判断方法很简单编译出来的库目录里有没有 hpdf.dll没有就说明你拿到的是静态库运行时只依赖其他系统 DLL。5.4 版本混用与依赖冲突libharu 2.3.0 对 zlib 的 ABI 要求非常稳但 libpng 的大版本必须和编译时一致。如果系统里同时存在多个 libpng 版本CMake 误找到旧版本的概率很高。遇到类似问题我会在 CMAKE_PREFIX_PATH 里精确指向自己编译的依赖目录必要时再加一条-DZLIB_INCLUDE_DIR、-DLIBPNG_INCLUDE_DIR手动指定头文件位置。宁可多敲两个参数也不要让 CMake 从系统目录里猜。Linux 上尤其容易踩这个坑因为包管理器会自动帮你更新 libpng 到新版本但旧版本的安装残留不会自动清理。CMake 可能先搜到了老版本的 .so 文件等到运行时加载到新版本库符号不一致瞬间崩掉。最保险的做法是在干净的构建目录里重新执行 cmake每次都加上明确的依赖路径参数。最后再分享一个偏方如果你只是想在脚本里快速试一下 libharu 的 APILinux 上编译完后可以直接把 hpdf.so 拷进系统库目录写 C 代码时用-lhpdf链接。偶尔用它做个小工具比每次都要维护一份完整 CMake 工程省心得多。我这两年在多个项目里反复编译 libharu 2.3.0体会是它在源码层面其实很轻只要把依赖关系和构建类型提前想清楚整条链路就再也不会卡住你了。本文还有配套的精品资源点击获取
返回列表