简介libharu源码包是免费开源的PDF生成库面向需要在C/C项目中自行实现PDF导出能力的开发者尤其适合在Visual Studio 2013环境下从源码编译并集成的初中级程序员可满足报表、票据与文档类PDF输出的定制需求。压缩包共100个文件、约506KB以57个.c源文件与36个.h头文件为主体涵盖文档、页面、字体、编码、图像及页面操作符等模块另含CMake配置、VS工程文件与说明文本便于直接编译或按需裁剪。已有374人学习下载。读者可获取完整的库源码与头文件接口结合hpdf_doc_create、hpdf_page_create、hpdf_page_draw_text等API实践文档、页面与文本绘制流程理解TrueType/Type1字体加载与中日韩编码处理并参考工程配置完成zlib等依赖的排错与集成为二次开发和跨平台移植打下基础。1. 服务端批量出 PDF 的另一条路几百 KB 的 C 库而不是塞一个浏览器去年接了个对账单导出的活第一版用无头浏览器渲染 HTML 再打印成 PDF镜像直接胖了 700 多兆冷启动两秒起步压测时 CPU 全耗在解析 CSS 上。后来换成 libharu pdf 这条路线一个纯 C 写的 PDF 生成库编译产物只有几百 KB跟主进程一起加载在代码里直接画页面、写文字、贴图中间不经过 HTML 这一层。这份 libharu.rar 就是它的源码包。它适合版式固定的批量输出票据、标签、对账单、检测报告不适合拿去改别人给过来的 PDF。下面按我接活儿的顺序从编译到成品校验拆一遍。2. 搞清能力边界它只写不读对象都挂在 Doc 上选型阶段最容易犯的错是把它当成“PDF 处理库”而不是“PDF 生成库”。libharu 里没有任何解析器打不开已有 PDF、读不出表单域、也没法把两个文件合并。凡是需求里出现“在客户提供的模板上填数”“把扫描件和报表拼一起”这个库就不合适硬上只会做到一半推翻。它强的地方是从零构造页面、文字、矢量线、图片、书签、链接、加密全都能在 C 层直接控制没有中间格式转换输出结果可预测。2.1 HPDF_Doc 是唯一的根Page 和 Font 都不归你释放整个库的对象模型只有三层HPDF_Doc是文档根HPDF_Page、HPDF_Font、HPDF_Image、HPDF_Outline全部由它持有生命周期跟着 Doc 走。这意味着你不需要、也不应该去单独释放一个 Page调用HPDF_Free(pdf)时这些东西一起回收。我见过有人在循环里对每页做手工释放结果堆直接损坏——这种崩溃现场很难查因为崩的位置和出错的位置隔着几百行。第二件事是错误处理模型。libharu 的所有接口返回HPDF_STATUSHPDF_OK值为 0表示成功出错不抛异常、不中断流程。你在HPDF_New时传一个回调进去错误会实时打到回调里也可以在事后用HPDF_GetError/HPDF_GetErrorDetail取最后一次错误码。这套设计和大多数 C 库一致代价是每个调用点理论上都该判返回值。实际写业务代码时不必每个都判但凡是涉及文件读写、字体加载、图片解码的调用必须判——这几类是运行时最容易失败的地方而失败后往往不报错只是默默生成一份内容缺失的 PDF。还有一点值得提前知道所有坐标和尺寸的单位都是点point1 点 1/72 英寸A4 是 595.27 × 841.89 点。原点在页面左下角y 向上为正。这个设定跟前端、跟 Canvas、跟大多数截图工具的左上角原点完全相反是后面排版出错的最大来源。2.2 构建先确认 zlib 和 libpng 有没有被探测到拿到源码包先看根目录有没有CMakeLists.txt。有就走 CMake没有就是更早的 Makefile 体系那就得手工改makefile里的编译器和依赖路径不建议折腾直接换包里另一份构建脚本更省事。主线版本走 CMake两个外部依赖是可选的zlib 决定能不能压缩流libpng 决定能不能读 PNG缺了前者文件会明显变大缺了后者所有 PNG 加载接口在链接期就找不到。JPEG 是内置解码的不依赖 libjpeg。# 依赖zlib 负责流压缩libpng 负责 PNG 解码 sudo apt-get install -y build-essential cmake zlib1g-dev libpng-dev cmake -S . -B build \ -DCMAKE_BUILD_TYPERelease \ -DLIBHPDF_SHAREDON \ -DLIBHPDF_STATICOFF \ -DLIBHPDF_EXAMPLESON cmake --build build -j$(nproc) cmake --install build --prefix $PWD/install-DLIBHPDF_SHAREDON出动态库服务端常驻进程用这个多个模块共享一份-DLIBHPDF_STATICON出静态库适合要单文件分发或者要塞进移动端的情况但注意静态链接时许可证虽然宽松仍要随产品附上声明。-DLIBHPDF_EXAMPLESON会把官方示例一起编出来这些示例价值很高——它是唯一能对照“正确调用顺序”的参考比翻头文件快得多。--prefix指定安装目录装完是include/hpdf.h加lib/libhpdf.*的结构。不同版本的开关名可能略有出入configure 阶段的输出里能看到实际探测结果重点确认三行zlib 找到了没有、libpng 找到了没有、共享库还是静态库。这三行看错后面所有问题都是连锁的。装完先验一下 UTF 编码支持有没有编进去这一步能省掉后面半小时的乱码排查# 有输出说明 UTF-8 编码器可用没有输出就是构建时被裁掉了 nm -D install/lib/libhpdf.so | grep -i UTFEncoding2.3 二十行跑通第一份 PDF不要一上来就写业务先跑通最小闭环建文档、加一页、写一行字、存盘。#include stdio.h #include hpdf.h /* 所有 API 都不抛异常出错走这个回调 */ static void on_error(HPDF_STATUS err_no, HPDF_STATUS detail_no, void *user_data) { (void)user_data; fprintf(stderr, [libharu] err0x%04X detail%u\n, (unsigned)err_no, (unsigned)detail_no); } int main(void) { HPDF_Doc pdf HPDF_New(on_error, NULL); if (!pdf) { fprintf(stderr, HPDF_New failed\n); return 1; } HPDF_SetCompressionMode(pdf, HPDF_COMP_ALL); /* 文本流和图片流一起压 */ HPDF_Page page HPDF_AddPage(pdf); HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT); HPDF_Font font HPDF_GetFont(pdf, Helvetica, NULL); HPDF_Page_SetFontAndSize(page, font, 18); HPDF_Page_BeginText(page); HPDF_Page_MoveTextPos(page, 60, 760); /* 原点在左下角 */ HPDF_Page_ShowText(page, first page from libharu); HPDF_Page_EndText(page); if (HPDF_SaveToFile(pdf, out.pdf) ! HPDF_OK) fprintf(stderr, save failed: %u\n, (unsigned)HPDF_GetError(pdf)); HPDF_Free(pdf); /* Page/Font/Image 随 Doc 一起回收 */ return 0; }编译命令里参数顺序有讲究静态链接时库的顺序错了就是一堆 undefined referencegcc -O2 main.c \ -I$PWD/install/include \ -L$PWD/install/lib \ -lhpdf -lz -lpng -lm \ -o mkpdf ./mkpdf ls -l out.pdf-I指向hpdf.h所在目录-L指向库目录。-lhpdf必须放在源文件之后这是链接器的单遍扫描规则决定的它从左往右处理遇到-lhpdf时还不知道有哪些未解析符号就会丢掉这个库。-lz -lpng是 libharu 的传递依赖静态链接时必须显式列出动态链接时通常可以省但写上更稳。-lm是数学库部分平台上的字体度量代码会用到。文字部分用BeginText/MoveTextPos/ShowText/EndText这套不要漏EndText——漏了之后 PDF 结构里文本对象不闭合某些阅读器会直接渲染空白而 libharu 本身不认为这是错误。这也是为什么我建议第一版就跑通再往上加功能出问题时能立刻分清是“基础链路坏了”还是“新加的功能坏了”。3. 中文不乱码字体嵌入、编码器与折行度量英文跑通之后下一个必然撞上的墙是中文。这一段的坑密度最高因为它同时涉及三个独立的东西字体里有没有字形、字符串编码对不对、PDF 里的编码器声明得对不对。三者任意一个错现象都是“中文不见了”但排查方向完全不同。热词里“libharu pdf 中文乱码”常年挂着说明这不是个别现象。3.1 内置字体只有 14 款字形表里根本没有汉字HPDF_GetFont(pdf, Helvetica, NULL)取的是 PDF 标准的 14 款基础字体Helvetica、Times、Courier 三族各四种字形加 Symbol 和 ZapfDingbats。这些字体在 PDF 里只写名字不嵌文件由阅读器用本机字体替代渲染好处是文件极小坏处是编码只有单字节的 WinAnsi、MacRoman 这类字符集覆盖到 Latin-1 就没了。你把一段 UTF-8 中文喂进去libharu 会按单字节逐个取字形取不到就是空白或者问号。所以中文这件事只有一条路加载一个包含汉字的 TrueType 字体文件并嵌入。不要试图用“把中文字符串转成 GBK 再塞进内置字体”——编码器对不上结果只会更乱。挑字体时看两点覆盖范围要够GB18030 常用字全覆盖别拿只做了 6763 个字的精简版去排人名地名文件别太大一个全量中文字体动辄十几兆靠子集嵌入能把最终 PDF 压到几百 KB。3.2 编码器 嵌入字体顺序不能反正确的调用顺序是新建 Doc → 打开 UTF 编码支持 → 设置当前编码器为 UTF-8 → 加载 TTF → 写文本。前三步必须在加载任何字体、写入任何文本之前完成。/* 1) 打开 UTF 编码支持必须在加载字体之前 */ HPDF_UseUTFEncodings(pdf); HPDF_SetCurrentEncoder(pdf, UTF-8); /* 2) 嵌入字体第三个参数 HPDF_TRUE 表示做子集嵌入 */ HPDF_Font cn HPDF_LoadTTFontFromFile2(pdf, fonts/zh.ttf, 0, HPDF_TRUE); if (!cn) { fprintf(stderr, font load failed: 0x%04X\n, (unsigned)HPDF_GetError(pdf)); return 2; } /* 3) 写中文传进去的必须是 UTF-8 字节序列 */ HPDF_Page_SetFontAndSize(page, cn, 12); HPDF_Page_BeginText(page); HPDF_Page_MoveTextPos(page, 60, 700); HPDF_Page_ShowText(page, 对账单 2024 年 03 月); HPDF_Page_EndText(page);HPDF_LoadTTFontFromFile2的第三个参数是字体集合里的索引普通.ttf传 0 就行.ttc这种一个文件装多套字形的才需要换索引。第四个参数HPDF_TRUE是子集嵌入只把页面里实际用到的字形打进 PDF——这个开关千万别图省事关掉关掉之后每个 PDF 都会把整份字体塞进去十几兆起步。HPDF_TRUE的代价是生成时要做一次字形收集速度慢一点但换来的是几百 KB 的成品。如果链接时提示找不到HPDF_UseUTFEncodings说明构建时把 UTF 编码支持裁掉了回到 2.2 节重新检查编译开关别想着绕过去——没有这个函数中文在这条路线上基本走不通。顺带说一个跨平台的坑字体文件路径里带中文在 Windows 上大概率加载失败。libharu 内部用的是窄字符fopen走系统 ANSI 代码页路径里的中文会被截断或转义失败。解决办法有两个把字体放到纯英文路径下或者自己用宽字符接口读进内存再调HPDF_LoadTTFontFromMem传字节数组和长度——后者更稳但要自己管这块内存的生命周期必须在HPDF_Free之后才能释放。3.3 折行得自己算TextWidth是唯一可信的尺子本文还有配套的精品资源点击获取