libharu 2.3.0编译与集成:跨平台PDF生成及中文字体实战

发布时间:2026/9/8 6:36:38
libharu 2.3.0编译与集成:跨平台PDF生成及中文字体实战 简介libharu2.3.0开源PDF写入库的完整编译成果面向需要轻量级PDF生成能力的C/C开发者尤其适合在文档生成、报表导出等场景中快速集成PDF输出。该库仅依赖libpng与zlib结构简洁针对原生Unicode支持不足的问题通过增加Unicode转GBK函数并调整部分读写代码即可实现中英文、简繁体混合文本的正确写入。资源共361个文件涵盖c/h源码、Visual Studio工程文件、lib与dll库、可执行示例、生成的PDF及PNG预览等整体约2.66MB。除静态库与DLL动态库四个编译版本外还编译了全部官方实例并额外添加一个中文输出Demo推荐直接使用DLL版本以省去额外静态库链接。已有1605人浏览学习适合需要对PDF生成库进行二次开发或希望获得中文完整支持的开发者参考。 如果你接手过那种“没有浏览器但必须生成 PDF 对账单”的嵌入式项目或者想在 C/C 服务端里直接输出票据、报告、条码标签八成会碰到 libharu 这个名字。它是一个纯 C 写的开源 PDF 写入库2.3.0 是 2018 年发布的版本——虽然这几年官方仓库基本处于“静默维护”状态但因为这个库代码量小、依赖少、跨平台依然是很多工控、医疗、物流、报表场景里的常客。很多你见过的 PDF 编辑器和转换工具后端能力里就有它的一份贡献。这篇文章不打算只讲“装好之后怎么用”就完事而是把 2.3.0 从源码编译、跨平台集成、中文字体处理、常见坑位整体过一遍目标就一个让你拿到这份记录之后不用再重复我踩过的那些坑。1. 项目背景与技术选型思考1.1 这个库到底适合解决什么问题libharu 的核心能力是“写入 PDF”不是“读取 PDF”也不是“渲染 PDF”。它能做的是我在代码里直接创建页面、写文字、画线、填色、贴图片、建书签最后产出一个符合 PDF 规范的文档。和几类常见方案放在一起看会更清楚方案语言/依赖擅长场景不适合的点libharuC依赖 zlib/libpng快速生成 PDF、报表、票据、图形化文档没有 PDF 渲染引擎不支持 HTML 转 PDFPDFiumCPDF 解析、渲染、注释生成和编辑 API 偏底层上手成本高PoDoFoCPDF 内容操作模板多、编译配置复杂比较“重”wkhtmltopdfC / Qt WebEngineHTML 模板转 PDF部署体积大依赖跨平台组件多headless ChromeChrome 浏览器核心网页截图、打印成 PDF需要独立的浏览器进程资源占用高所以如果你和我一样是在嵌入式设备、工控机或者后端服务里生成 PDFlibharu 的优势非常明显编译简单、体积小、API 稳定、对资源受限环境友好。它不擅长 HTML 转 PDF 那种“所见即所得”的排版但如果你的版式是可控的、手写代码能表达的它反而是最省事的选择。1.2 版本 2.3.0 有什么特殊之处选择 2.3.0 不是因为它新而是因为它“稳”。这一版发布后libharu 官方仓库基本进入慢速维护状态之后很长一段时间都没有大的功能变更这也意味着 API 冻结不会出现今天写的代码明天因为升级就崩掉的情况。很多 Linux 发行版内置的 libharu 其实就是基于这版打的包社区里的使用经验、坑位记录也大多集中在这版上。“完美编译”这件事的关键点在于2.3.0 是一个比较老的版本代码本身很规矩但现代工具链、新版本 zlib/libpng、Windows 平台下 CMake 工程都需要我们额外处理。我把这套流程拆开讲透后面你换到任何平台都能少走弯路。2. 依赖解析与工具链准备2.1 三层依赖关系要理清libharu 自身其实很克制核心逻辑不依赖外部库。但它支持嵌入 PNG 图片这部分依赖 libpnglibpng 又依赖 zlib。所以完整依赖链是这样的libharu负责 PDF 对象、页面、字体、图形等核心逻辑libpng解析 PNG 图片数据zlib提供压缩能力用于压缩 PDF 内部流如果你的应用场景不需要 PNG理论上可以关闭相关功能但实际编译时几乎所有人都会把所有依赖装齐省得后面要加图片功能时又重新折腾。另外如果需要做编码转换可能还会用到 libiconv。不过在现代 Linux 上glibc 已经内置 iconv 接口Windows 上则可以用 Win32 API 里的 MultiByteToWideChar/WideCharToMultiByte 替代也不是必须依赖。2.2 按目标平台准备工具链编译之前先把工具链准备好平台工具链需要提前装的依赖Linux x86/ARMgcc、make、pkg-configzlib1g-dev、libpng-devWindows MSVCVisual Studio CMake通过 vcpkg 或现成的 zlib/libpng 静态库Windows MinGWMSYS2 或 Qt 自带工具链mingw-w64-x86_64-zlib、libpng嵌入式 Linux交叉编译工具链目标平台对应的 zlib/libpng 源码包装依赖时建议多用系统包管理。比如 Ubuntu 上执行sudo apt-get install -y zlib1g-dev libpng-dev如果是在内网离线环境没有系统包那就提前把 zlib 和 libpng 源码也准备好交叉编译时统一打进前缀目录这个后面会提到。3. 四套编译方案实测3.1 Linux 下用 autotools 一条龙Linux 上的常规路径最顺因为 2.3.0 自带 configure 脚本。我实际编译的步骤tar xf libharu-2.3.0.tar.gz cd libharu-2.3.0 ./configure --prefix/opt/libharu make -j$(nproc) make install这里有一个容易忽略的点configure 脚本会生成hpdf_config.h这个头文件会决定某些宏定义是否开启例如是否有 PNG 支持、是否启用 libiconv 等。如果你的系统里装了多版本 libpngconfigure 可能因为 pkg-config 路径不对找不到库这时先手动确认pkg-config --exists libpng echo libpng ok pkg-config --cflags --libs libpng如果 pkg-config 找不到试试安装pkg-config工具或者通过环境变量补路径export PKG_CONFIG_PATH/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH编译完成后/opt/libharu/include下会有hpdf.h/opt/libharu/lib下会有libhpdf.a或libhpdf.so。这一步基本没有坑属于“顺利到让人怀疑是不是遗漏了什么”的那种顺利。3.2 Windows 下用 MSVC 编译Windows 上稍微麻烦一点。libharu 2.3.0 自带的 win32 工程文件年代比较久直接打开不一定能在新版 Visual Studio 里生成所以我建议用 CMake 从源码生成 VS 工程。mkdir build cd build cmake .. -G Visual Studio 17 2022 -A x64 ^ -DCMAKE_PREFIX_PATHC:/deps ^ -DCMAKE_INSTALL_PREFIXC:/libharu ^ -DBUILD_SHARED_LIBSOFF cmake --build . --config Release cmake --install .CMAKE_PREFIX_PATH指向 zlib 和 libpng 的安装位置。如果直接使用 vcpkg 安装依赖也可以让 CMake 通过 toolchain 文件自动找到cmake .. -G Visual Studio 17 2022 -A x64 -DCMAKE_TOOLCHAIN_FILEC:/vcpkg/scripts/buildsystems/vcpkg.cmake -DBUILD_SHARED_LIBSOFF有一点要提醒2.3.0 的 CMake 对 DLL 导出符号的处理不算完善直接用BUILD_SHARED_LIBSON可能会遇到部分HPDF_*函数没有导出、链接时找不到符号的问题。如果项目不强制要求 DLL优先编成静态库省心很多。3.3 Windows 下用 MinGW 编译如果你习惯用 Qt 自带的 MinGW 工具链或者用 MSYS2流程也简单。以 MSYS2 为例pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-zlib mingw-w64-x86_64-libpng mkdir build cd build cmake -G MinGW Makefiles -DCMAKE_INSTALL_PREFIXC:/libharu .. mingw32-make mingw32-make install这里有个容易踩的小坑如果同时装了多个 MinGW 工具链CMake 可能选错编译器导致链接时出现“file format not recognized”之类的报错。解决方案是在PATH环境变量里把目标编译器的 bin 目录放在最前面或者在 CMake 命令里显式指定编译器cmake -G MinGW Makefiles \ -DCMAKE_C_COMPILERC:/Qt/Tools/mingw1310/bin/gcc.exe \ -DCMAKE_CXX_COMPILERC:/Qt/Tools/mingw1310/bin/g.exe \ ..3.4 嵌入式交叉编译与静态库很多嵌入式项目要用 libharu常规做法是交叉编译。以 ARM Linux 和arm-linux-gnueabihf-gcc为例./configure \ --hostarm-linux-gnueabihf \ --prefix/opt/libharu-arm \ CCarm-linux-gnueabihf-gcc \ CXXarm-linux-gnueabihf-g \ CPPFLAGS-I/path/to/zlib/include \ LDFLAGS-L/path/to/zlib/lib注意交叉编译时zlib 和 libpng 也需要先用同一套交叉编译器编出来不能直接拿主板上的 .so 来链接。如果目标板没有动态库或者不想在部署时额外放依赖就改成全静态编译./configure \ --hostarm-linux-gnueabihf \ --enable-static \ --disable-shared \ LDFLAGS-static -L/path/to/zlib/lib make -j$(nproc)编译完检查一下产物file src/.libs/libhpdf.a确认架构正确再往工程里集成这一步能省出不少调试时间。4. 中文字体与编码的实战细节4.1 字体文件要显式加载libharu 默认自带一组 PDF 基础字体例如 Helvetica、Times、Courier只支持 Latin 字符。想显示中文必须把系统里的中文字体文件加载进去。常见做法const char *font_path /usr/share/fonts/truetype/wqy/wqy-microhei.ttc; HPDF_Font font HPDF_GetFont( pdf, HPDF_LoadTTFontFromFile2(pdf, font_path, 0, HPDF_TRUE), UniGB-UCS2-H );HPDF_LoadTTFontFromFile2的第三个参数是字体索引面对.ttcTrueType Collection文件时可以选第几个字体第四个参数是是否嵌入字体。这里建议永远传HPDF_TRUE不然换一台没有同样字体的机器PDF 里的中文就可能变成方框或空白。Windows 下直接加载系统字体也是可以的比如C:/Windows/Fonts/simhei.ttf C:/Windows/Fonts/simsun.ttc路径里尽量用正斜杠避免转义和平台差异。4.2 简体中文的编码姿势这是 libharu 中文乱码的重灾区。很多人直接往HPDF_Page_ShowText里塞 UTF-8 字节流结果生成的 PDF 打开全是“□□□□”。原因是 libharu 的文本接口本质是按字节流工作的它会根据当前字体关联的编码来解释这些字节。对于简体中文最稳妥的搭配是字体编码使用GBK-EUC传入的字节流必须是 GBK 编码如果你的业务逻辑内部全是 UTF-8 字符串用 iconv 转成 GBK 再传给 libharu#include iconv.h #include string.h static size_t utf8_to_gbk(const char *in, char *out, size_t out_sz) { iconv_t cd iconv_open(GBK, UTF-8); if (cd (iconv_t)-1) { return 0; } char *src (char *)in; char *dst out; size_t in_len strlen(in); size_t ret iconv(cd, src, in_len, dst, out_sz); iconv_close(cd); if (ret (size_t)-1) { return 0; } return (size_t)(dst - out); }在 Linux 的 glibc 环境下直接调用iconv不需要额外链接如果是 Windows 的 MSVC没有 iconv 可用就换成 Win32 的MultiByteToWideChar先转成 UTF-16再用WideCharToMultiByte转 GBK逻辑一样。为什么不推荐UniGB-UCS2-H 因为ShowText系列接口以空字符作为字符串终止判断而 UCS-2BE 编码里 ASCII 字符的高字节是0x00很容易被当成结束符导致文本被截断。虽然有些 hack 能绕过去但远不如直接用 GBK 字节流省心。4.3 多页与页码处理生成多页 PDF 时页脚“第 x / y 页”里的总页数 y 往往要等所有页面创建完才知道。我的习惯是两段式处理先创建完全部页面再通过HPDF_GetPageByIndex拿到每页对象补写页脚。int total HPDF_GetPageCount(pdf); for (int i 0; i total; i) { HPDF_Page page HPDF_GetPageByIndex(pdf, i, 0); // 这里复用字体写上页脚 HPDF_Page_BeginText(page); HPDF_Page_SetFontAndSize(page, cn_font, 10); char footer[64]; snprintf(footer, sizeof(footer), 第 %d / %d 页, i 1, total); char gbk_footer[128]; utf8_to_gbk(footer, gbk_footer, sizeof(gbk_footer)); HPDF_Page_TextOut(page, 280, 40, gbk_footer); HPDF_Page_EndText(page); }这样就不需要提前猜总页数也避免了“生成一半发现页数变了”的问题。5. 最小可运行示例与产物验证5.1 完整代码解读下面是一个能跑通的完整示例创建三页 A4 页面每页写一行正文最后补页脚#include hpdf.h #include iconv.h #include stdio.h #include string.h static size_t utf8_to_gbk(const char *in, char *out, size_t out_sz) { iconv_t cd iconv_open(GBK, UTF-8); if (cd (iconv_t)-1) { return 0; } char *src (char *)in; char *dst out; size_t in_len strlen(in); size_t ret iconv(cd, src, in_len, dst, out_sz); iconv_close(cd); return (ret (size_t)-1) ? 0 : (size_t)(dst - out); } int main(void) { HPDF_Doc pdf HPDF_New(NULL, NULL); if (!pdf) { fprintf(stderr, create pdf handle failed.\n); return 1; } HPDF_SetCompressionMode(pdf, HPDF_COMP_ALL); HPDF_UseCNSEncodings(pdf); HPDF_UseCNTFonts(pdf); const char *font_path /usr/share/fonts/truetype/wqy/wqy-microhei.ttc; HPDF_Font cn_font HPDF_GetFont( pdf, HPDF_LoadTTFontFromFile2(pdf, font_path, 0, HPDF_TRUE), GBK-EUC ); for (int i 0; i 3; i) { HPDF_Page page HPDF_AddPage(pdf); HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT); HPDF_Page_SetFontAndSize(page, cn_font, 14); char line[64]; snprintf(line, sizeof(line), 第 %d 页正文内容, i 1); char gbk_line[128]; utf8_to_gbk(line, gbk_line, sizeof(gbk_line)); HPDF_Page_BeginText(page); HPDF_Page_TextOut(page, 72, 700, gbk_line); HPDF_Page_EndText(page); } int total HPDF_GetPageCount(pdf); for (int i 0; i total; i) { HPDF_Page page HPDF_GetPageByIndex(pdf, i, 0); HPDF_Page_SetFontAndSize(page, cn_font, 10); char footer[64]; snprintf(footer, sizeof(footer), 第 %d / %d 页, i 1, total); char gbk_footer[128]; utf8_to_gbk(footer, gbk_footer, sizeof(gbk_footer)); HPDF_Page_BeginText(page); HPDF_Page_TextOut(page, 280, 40, gbk_footer); HPDF_Page_EndText(page); } HPDF_SaveToFile(pdf, demo.pdf); HPDF_Free(pdf); return 0; }注意HPDF_UseCNSEncodings和HPDF_UseCNTFonts要放在加载中文字体之前这样 libharu 才识别简体中文编码和字体列表。如果你用的是繁体中文项目对应调用HPDF_UseCNSEncodings和HPDF_UseCNTFonts这组 API 同样适用。5.2 编译链接与验证命令Linux 下链接gcc demo.c -I/opt/libharu/include -L/opt/libharu/lib \ -lhpdf -lpng -lz -o demo如果是在 Windows 的 MSVC 环境cl demo.c /I C:\libharu\include /link \ /LIBPATH:C:\libharu\lib libhpdf_static.lib zlib.lib libpng16.lib生成demo.pdf后强烈建议用工具验证一下文件结构而不是直接拿肉眼去看qpdf --check demo.pdf pdfinfo demo.pdf pdffonts demo.pdfpdffonts能看出字体有没有真正嵌入。如果字体列表里显示的是空名称或者 not embedded基本就是字体加载参数或路径有问题。6. 编译和运行中的常见坑6.1 编译阶段问题清单报错/现象可能原因解决方法configure 提示找不到 libpng缺少 libpng-dev或 pkg-config 路径不对安装依赖或设置PKG_CONFIG_PATHCMake 找不到 zlib.h依赖库路径没加入到 CMake 搜索范围设置CMAKE_INCLUDE_PATH、CMAKE_LIBRARY_PATHWindows 链接出现 unresolved external deflatezlib 没有参与链接确保 zlib.lib 被链接且库格式和工具链匹配DLL 编出来后HPDF_*符号缺失2.3.0 的 CMake 导出符号配置不完善改为静态库-DBUILD_SHARED_LIBSOFF文件格式 not recognizedMinGW 工具链选错或混用了 MSVC 库用同一套编译器统一编依赖和 libharu6.2 运行阶段问题清单现象原因处理方式中文全是方框编码不匹配或字体未嵌入确认使用GBK-EUC且传入的是 GBK 字节流中文字体在别的机器上丢失HPDF_LoadTTFontFromFile2的嵌入参数传了HPDF_FALSE改为传HPDF_TRUE文本被截断使用了UniGB-UCS2-H字节流里的0x00被当成结束符改用 GBK 编码或自己处理 UCS-2 长度问题加载 PNG 时崩溃个别 PNG 灰度位数、色深不是 libharu 喜欢的格式先用工具转成标准 8bit RGB/RGBA PNG生成的 PDF 在某些软件里显示异常缺少压缩、页面属性没设置完整使用qpdf --check检查结构并补全页面尺寸等属性错误回调为空导致进程异常HPDF_New传了 NULL 错误处理函数出错时没有回调建议传入一个日志回调函数便于定位问题6.3 一些值得养成的小习惯第一每次编译完先file查看库文件的架构尤其交叉编译时这一步能立刻发现工具链不匹配的问题。第二链接时把依赖顺序排好静态库链接时-lhpdf -lpng -lz的顺序不能乱否则可能出现 undefined reference。第三生成 PDF 后跑一遍qpdf --check以低成本方式确认文档结构正常。7. 在自己的工程中集成时的取舍7.1 集成方式怎么选libharu 的集成方式大概有三种源码直接纳入、编译成动态库、使用系统包管理。嵌入式项目或对交付体积敏感的项目我建议源码级集成把 libharu 源码放进工程树里一起编译最后静态链接。这样运行时不需要额外传 .so/.dll目标机上的依赖问题最少。服务端项目则可以考虑编译成独立动态库由多个模块共享。Windows 上建议用静态库编译避免 DLL 导出符号遗漏的问题。Linux 上如果系统包里的版本和代码兼容直接用apt install libharp-dev之类的包管理方式也不是不行但要注意版本锁定避免升级后行为变化。7.2 几点个人经验我实际用 libharu 时绝大多数业务场景只用到不到二十个 APIHPDF_New、HPDF_Free、HPDF_AddPage、HPDF_Page_SetSize、HPDF_Page_SetFontAndSize、HPDF_Page_TextOut、HPDF_Page_BeginText、HPDF_Page_EndText、HPDF_LoadTTFontFromFile2、HPDF_Page_DrawImage这些。它最大的价值就是“稳”和“准”。在确定版式的场景里它不会给你发挥空间但也正因如此生成结果的确定性很高。如果你需要的是从 HTML 模板生成漂亮排版的 PDF那就应该选 wkhtmltopdf 或者 headless Chrome别在 libharu 里硬做复杂排版。不同项目的边界不一样认清工具的能力边界比把某个库用到极致更重要。本文还有配套的精品资源点击获取

相关新闻