首页 / 资讯中心 / 文章详情

libharu 2.3.0 编译与集成实战:从源码到中文 PDF 生成

libharu 2.3.0 编译与集成实战:从源码到中文 PDF 生成 ★ FEATURED ARTICLE
简介本资源为libharu 2.3.0开源PDF写入库的完整编译包面向需要在C/C项目中嵌入PDF生成能力的开发者尤其适合追求轻量依赖、希望快速集成静态库或动态库的技术人员。libharu本身仅依赖libpng与zlib体积小巧、调用简洁虽原生对Unicode支持有限但通过增加Unicode转GBK函数并适当修改读写代码即可实现中英文与简繁体混合输出。压缩包共361个文件约2.66MB涵盖59个c源文件、53个h头文件、26个vcxproj工程、25个exe示例、20个pdf输出、8个lib库文件及2个dll动态库等并附有afm、ttf字体资源与readme说明。作者已完整编译静态库与dll共四个版本推荐直接使用dll版本无需额外链接其他静态库除全部官方实例外还额外提供一个中文输出实例便于对照调试。目前已有1607人学习下载适合需要快速验证PDF生成效果或研究库源码的开发者参考。1. 完美编译 libharu 2.3.0从源码到可用的 PDF 写入库手上接到一个需求要在 C 服务端直接生成 PDF 报表不依赖浏览器渲染也不想引入体积庞大的商业 SDK。翻了一圈开源方案libharu 2.3.0 是最对路的选择——纯 C 写的轻量级 PDF 写入库编译出来就一个静态库加几个头文件嵌进现有工程几乎没有负担。但真动手编译时事情没那么顺zlib 和 libpng 的依赖顺序、CMake 与 Makefile 两套构建系统的差异、Windows 下 MSVC 和 MinGW 的符号导出问题每一个都能卡住半天。这篇笔记就把 libharu 2.3.0 从拉源码到跑通示例的完整路径拆开讲清楚包括参数怎么设、报错怎么查、哪些坑我亲自踩过。适合需要在 C/C 项目里集成 PDF 生成能力、又不想被商业库绑死的工程师。2. 编译前的环境准备与依赖决策2.1 为什么 libharu 2.3.0 值得单独编译而不是找预编译包很多人第一反应是搜现成的二进制包省事。但 libharu 2.3.0 这个版本比较特殊它发布于 2022 年左右很多 Linux 发行版仓库里带的还是更老的 2.2.x 或 2.3.0 之前的快照API 有差异。更关键的是预编译包通常不带调试符号一旦在 PDF 生成过程中遇到段错误或者字体嵌入异常你连堆栈都看不到只能靠 printf 硬猜这就是典型的黑匣子困境。自己编译有三个实际好处。第一可以精确控制依赖版本zlib 用哪个版本、libpng 开不开全由你定避免运行时动态库版本冲突。第二能拿到完整的调试符号配合 gdb 或 Visual Studio 调试器可以直接定位到 libharu 内部函数。第三可以根据项目需要裁剪功能比如不需要 PNG 图片嵌入就关掉 libpng 依赖编译出来的库体积能小一圈。libharu 2.3.0 的核心依赖只有两个zlib 用于流压缩libpng 用于 PNG 图片处理。如果你确定生成的 PDF 里不嵌 PNGlibpng 可以不要。但 zlib 基本是必须的因为 PDF 的内容流默认要经过 Flate 压缩不压缩的话文件体积会大得离谱。2.2 Linux 下用 CMake 编译 libharu 2.3.0 的最小命令先确认系统里有没有 CMake 和基础编译工具链。我一般在 Ubuntu 22.04 或 CentOS 8 上操作命令大同小异。# 检查工具链版本CMake 要求 3.10 以上 cmake --version gcc --version # 安装 zlib 和 libpng 的开发包Ubuntu/Debian sudo apt-get install zlib1g-dev libpng-dev # 如果是 CentOS/RHEL 系 # sudo yum install zlib-devel libpng-devel依赖装好后拉源码。libharu 的官方仓库在 GitHub 上直接 clone 下来切到 2.3.0 标签。git clone https://github.com/libharu/libharu.git cd libharu git checkout v2.3.0 # 创建构建目录保持源码目录干净 mkdir build cd build # 配置 CMake指定安装路径和构建类型 cmake .. \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_INSTALL_PREFIX/usr/local/libharu \ -DLIBHARU_BUILD_SAMPLESON \ -DLIBHARU_BUILD_STATICON \ -DLIBHARU_BUILD_SHAREDOFF # 开始编译-j 后面跟 CPU 核心数 make -j$(nproc) # 安装到指定目录 sudo make install这段命令里几个参数值得展开说。CMAKE_BUILD_TYPERelease会开启 -O2 优化并去掉断言生产环境用这个。如果你要调试 libharu 内部逻辑改成Debug并加上-DCMAKE_C_FLAGS-g -O0。LIBHARU_BUILD_SAMPLESON会顺便编译官方示例这些示例是验证编译是否成功的最好材料。LIBHARU_BUILD_STATICON和LIBHARU_BUILD_SHAREDOFF表示只出静态库如果你的项目是动态链接方式把这两个值对调。编译完成后在 build/src 目录下能看到libharu.a在 build 目录下能找到示例可执行文件。先跑一个官方示例验证# 进入示例输出目录运行文本编码示例 ./demo/text_demo # 如果生成了 text_demo.pdf说明编译链路通了 ls -lh text_demo.pdf2.3 Windows 下 MSVC 与 MinGW 的编译差异Windows 上编译 libharu 2.3.0 有两条路MSVC 和 MinGW。我两个都试过踩的坑不一样。MSVC 路线需要用 CMake 生成 Visual Studio 工程文件。前提是系统里已经装好 zlib 和 libpng 的 Windows 版本或者用 vcpkg 统一管理依赖。# 用 vcpkg 安装依赖推荐省去手动配路径的麻烦 vcpkg install zlib:x64-windows libpng:x64-windows # 生成 VS 工程注意指定 vcpkg 工具链文件 cmake .. \ -G Visual Studio 17 2022 \ -A x64 \ -DCMAKE_TOOLCHAIN_FILE[vcpkg根目录]/scripts/buildsystems/vcpkg.cmake \ -DLIBHARU_BUILD_SAMPLESON # 用 MSBuild 编译 msbuild libharu.sln /p:ConfigurationRelease /p:Platformx64MinGW 路线更接近 Linux 体验用 MSYS2 环境最省事。# 在 MSYS2 MINGW64 终端里操作 pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake \ mingw-w64-x86_64-zlib mingw-w64-x86_64-libpng git clone https://github.com/libharu/libharu.git cd libharu git checkout v2.3.0 mkdir build cd build cmake .. -G MinGW Makefiles \ -DCMAKE_BUILD_TYPERelease \ -DLIBHARU_BUILD_SAMPLESON mingw32-make -j8MSVC 和 MinGW 最大的差异在符号导出和运行时库。MSVC 默认用动态运行时库/MDMinGW 默认用静态运行时库如果你的主工程和 libharu 的运行时库选项不一致链接时会报一堆LNK2019或undefined reference。解决办法是统一要么两边都用静态运行时要么都用动态。我一般倾向于静态部署时少带几个 DLL。注意Windows 下如果同时装了多个版本的 zlibCMake 的 find_package 可能找到错误的那个。在 CMake 配置输出里搜ZLIB_LIBRARY和PNG_LIBRARY确认路径指向你期望的版本。3. 把 libharu 集成进自己的工程链接与第一个 PDF3.1 静态库链接时必须带上的系统库编译出 libharu.a 只是第一步把它链接到自己的可执行文件时还需要带上它依赖的系统库。Linux 下典型的链接命令长这样g -o myapp main.cpp \ -I/usr/local/libharu/include \ -L/usr/local/libharu/lib \ -lharu -lz -lpng -lm这里-lz是 zlib-lpng是 libpng-lm是数学库。顺序不能乱-lharu必须放在依赖它的目标文件之后-lz和-lpng放在-lharu之后。如果链接时报undefined reference to deflate之类的错误八成是-lz的位置不对或者根本没加。Windows MSVC 下在项目属性里配置附加包含目录指向 libharu 的 include附加库目录指向 lib 文件所在位置附加依赖项里填haru.lib;zlib.lib;libpng.lib。如果用 CMake 管理主工程更推荐用find_package或者直接把 libharu 作为子目录加进来。3.2 用 libharu 生成第一个带中文的 PDFlibharu 默认只支持英文和拉丁字符直接写中文会输出乱码或者空白。要支持中文必须嵌入中文字体并设置正确的编码。下面是一个最小可运行示例生成一个包含中文文本的 PDF。#include hpdf.h #include cstdio // 错误处理回调libharu 出错时会调用 void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *user_data) { printf(ERROR: error_no0x%04X, detail_no%u\n, (unsigned)error_no, (unsigned)detail_no); } int main() { // 创建 PDF 文档对象 HPDF_Doc pdf HPDF_New(error_handler, NULL); if (!pdf) { printf(无法创建 PDF 对象\n); return 1; } // 设置压缩模式减小文件体积 HPDF_SetCompressionMode(pdf, HPDF_COMP_ALL); // 加载中文字体这里用系统里的思源黑体 // 字体文件路径根据实际系统调整 const char *font_path /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc; HPDF_Font font HPDF_GetFont(pdf, font_path, UTF-8); if (!font) { printf(字体加载失败检查路径是否正确\n); HPDF_Free(pdf); return 1; } // 添加一页 A4 尺寸的页面 HPDF_Page page HPDF_AddPage(pdf); HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT); // 设置字体和字号 HPDF_Page_SetFontAndSize(page, font, 14); // 在坐标 (50, 750) 处写中文文本 HPDF_Page_BeginText(page); HPDF_Page_TextOut(page, 50, 750, 你好libharu 2.3.0 编译成功); HPDF_Page_EndText(page); // 保存文件 HPDF_SaveToFile(pdf, hello_cn.pdf); // 释放资源 HPDF_Free(pdf); printf(PDF 生成完毕\n); return 0; }编译这个示例g -o hello_cn hello_cn.cpp \ -I/usr/local/libharu/include \ -L/usr/local/libharu/lib \ -lharu -lz -lpng -lm ./hello_cn代码逻辑说明HPDF_New创建文档对象传入错误回调函数任何内部错误都会触发这个回调方便定位问题。HPDF_SetCompressionMode开启全部压缩包括文本流和图像流。HPDF_GetFont加载字体文件第三个参数指定编码为 UTF-8这样传入的中文字符串才能被正确解析。HPDF_Page_TextOut的坐标原点在页面左下角单位是点1 点约等于 1/72 英寸A4 纸高度约 842 点所以 750 的位置靠近顶部。参数说明字体路径必须指向实际存在的字体文件Windows 下可以用C:/Windows/Fonts/msyh.ttc微软雅黑。如果字体文件是 TTC 格式字体集合libharu 2.3.0 能自动识别第一个字体。如果加载失败先检查文件路径有没有中文或空格再确认 libharu 编译时有没有开启字体支持。注意嵌入中文字体后 PDF 文件会明显变大因为字体子集被嵌入了。如果只需要少量汉字可以考虑用HPDF_UseCNTFonts加载内置的中文编码字体但那个依赖 libharu 编译时的编码支持选项不是所有构建都带。4. 编译与集成中的避坑排查4.1 找不到 zlib 或 libpngCMake 配置阶段的路径问题现象CMake 配置时输出Could NOT find ZLIB或Could NOT find PNG即使系统里已经装了开发包。原因CMake 的 find_package 模块在默认路径里搜索如果 zlib 装在非标准路径比如自己编译到 /opt/zlibCMake 找不到。Windows 下 vcpkg 安装的库也需要通过工具链文件告诉 CMake。解决手动指定路径。在 cmake 命令里加-DZLIB_ROOT/opt/zlib和-DPNG_ROOT/opt/libpng。如果还不行直接指定库文件和头文件路径-DZLIB_LIBRARY/opt/zlib/lib/libz.so -DZLIB_INCLUDE_DIR/opt/zlib/include。Windows 下确认 vcpkg 工具链文件路径写对了路径里不要有中文。4.2 链接报错 undefined reference todeflate库顺序与依赖传递现象编译主工程时链接阶段报大量undefined reference to deflate、undefined reference to png_create_read_struct。原因libharu 静态库依赖 zlib 和 libpng但链接器在处理静态库时是从左到右扫描的。如果-lharu在-lz前面链接器先处理 haru发现需要 deflate 符号但此时 zlib 还没被扫描到就报未定义。解决调整链接顺序把-lharu放在最前面-lz和-lpng放后面。如果依赖关系复杂可以用-Wl,--start-group和-Wl,--end-group把一组库包起来让链接器反复扫描。CMake 里用target_link_libraries时注意 PRIVATE 和 PUBLIC 的区别libharu 的依赖应该用 PUBLIC 传递出去。4.3 中文显示为方框或乱码字体嵌入与编码设置现象PDF 生成成功但中文部分显示为空白、方框或者乱码字符。原因三种可能。一是没有嵌入中文字体libharu 默认的 Helvetica 字体不含汉字。二是字体加载时编码参数写错了比如写成了GBK但实际传入的是 UTF-8 字符串。三是字体文件本身是 TTC 集合libharu 取错了子字体。解决确认HPDF_GetFont的第三个参数是UTF-8并且源文件保存为 UTF-8 编码。用fc-list :langzh命令在 Linux 下查找可用的中文字体文件。如果用的是 TTC 文件试试用 fontforge 或者 Python 的 fontTools 把它拆成 TTF 再加载。Windows 下微软雅黑是msyh.ttc宋体是simsun.ttc一般都能直接用。4.4 Windows 下 LNK2019 无法解析的外部符号现象MSVC 链接时大量LNK2019提示__imp_HPDF_New之类的符号找不到。原因libharu 编译时用的是动态运行时库/MD而主工程用的是静态运行时库/MT或者反过来。运行时库不匹配导致符号修饰名不一致。解决统一运行时库选项。在 CMake 里设置set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:Debug)强制静态运行时。或者检查 libharu 编译时的CMAKE_C_FLAGS里有没有/MD或/MT和主工程保持一致。另一个可能是 libharu 编译成了 64 位而主工程是 32 位用dumpbin /headers haru.lib确认机器类型。4.5 生成的 PDF 文件异常大压缩未开启或字体全量嵌入现象一个只有几行文字的 PDF 文件超过 10MB。原因HPDF_SetCompressionMode没有调用或者调用时参数传了HPDF_COMP_NONE。另一个原因是中文字体被全量嵌入一个完整的中文字体文件动辄十几 MB。解决确保调用HPDF_SetCompressionMode(pdf, HPDF_COMP_ALL)。对于字体libharu 2.3.0 默认会做子集化只嵌入用到的字符但如果字体文件本身有问题或者编码设置不对子集化可能失效。检查方法是用pdffonts命令查看 PDF 里嵌入的字体信息如果显示embyes且subno说明是全量嵌入。这种情况下换一个字体文件试试或者用 fontTools 手动做子集化后再嵌入。5. 进阶用 libharu 做批量报表与性能调优5.1 批量生成时的内存复用与对象池单次生成 PDF 没什么性能压力但如果要在一个循环里生成几百上千份报表每次都HPDF_New和HPDF_Free会频繁申请释放内存效率不高。我的做法是复用字体对象和页面模板。// 预加载字体避免每次生成都重新解析字体文件 HPDF_Font font HPDF_GetFont(pdf, font_path, UTF-8); // 批量生成时每份 PDF 单独创建文档对象但字体可以跨文档复用吗 // 不行libharu 的字体对象绑定在文档上。但可以把字体文件读进内存 // 用 HPDF_LoadTTFontFromMemory 避免重复磁盘 IO。 FILE *fp fopen(font_path, rb); fseek(fp, 0, SEEK_END); long font_size ftell(fp); fseek(fp, 0, SEEK_SET); void *font_data malloc(font_size); fread(font_data, 1, font_size, fp); fclose(fp); // 每次生成新 PDF 时从内存加载字体 HPDF_Doc pdf HPDF_New(error_handler, NULL); HPDF_Font font HPDF_LoadTTFontFromMemory(pdf, font_data, font_size, HPDF_TRUE, UTF-8);HPDF_LoadTTFontFromMemory的第四个参数HPDF_TRUE表示嵌入字体第五个参数指定编码。这样字体文件只从磁盘读一次后续都在内存里操作批量生成时能省不少时间。5.2 用 valgrind 和 gdb 定位 libharu 内部崩溃libharu 是 C 库内存管理全靠手动用错了很容易段错误。我习惯在开发阶段用 valgrind 跑一遍。# 编译时带调试符号 g -g -O0 -o hello_cn hello_cn.cpp -I... -L... -lharu -lz -lpng -lm # 用 valgrind 检查内存问题 valgrind --leak-checkfull --show-leak-kindsall ./hello_cn如果 valgrind 报Invalid read或Invalid write重点看是不是在HPDF_Free之后还引用了页面或字体对象。libharu 的对象生命周期规则是文档对象释放时所有附属的页面、字体、图像对象都会被自动释放不需要手动 free。但如果你在HPDF_Free之后还拿着HPDF_Page指针去操作就会崩溃。gdb 调试时在error_handler里下断点libharu 内部出错时会先触发这个回调此时用bt看调用栈能直接定位到是哪个 API 调用出了问题。5.3 验证 PDF 是否符合规范的三个命令生成 PDF 后怎么确认文件是合法的、内容是对的我常用三个命令行工具。工具用途典型命令pdfinfo查看 PDF 元信息页数、尺寸、创建者pdfinfo output.pdfpdffonts检查字体嵌入情况pdffonts output.pdfpdftotext提取文本内容验证中文是否正确pdftotext output.pdf -pdfinfo输出里重点看Pages和Page size是否符合预期。pdffonts输出里emb列如果是yes表示字体已嵌入sub列如果是yes表示做了子集化。pdftotext把 PDF 里的文本提取出来打印到终端如果中文能正常显示说明编码和字体都没问题。如果提取出来是乱码但 PDF 阅读器里显示正常那可能是 ToUnicode 映射没写好这种情况在 libharu 2.3.0 里偶尔出现换一个字体文件通常能解决。我现在的习惯是每次改完 PDF 生成逻辑先跑一遍pdftotext确认文本层没问题再用pdfinfo看页数和尺寸最后用 valgrind 扫一遍内存。这三步走完基本能拦住九成以上的低级问题。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站