简介:面向 Delphi 与 C++ Builder 开发者的 PDF 处理组件套件,基于开源 PDFium 渲染引擎,可在 Delphi/C++ Builder 5 至 10.3 及 Lazarus 2.0.6 环境中实现 PDF 查看、页面导航、文本提取与内容编辑等功能,适合需要在桌面应用中集成 PDF 能力的初中级开发者。压缩包共 1010 个文件,约 23.75MB,以 671 个 dcu 编译单元、35 个 pas 源码、31 个 dpk 包工程及 13 个 dfm 窗体文件为主体,另含 hpp、obj、res、bpl 等编译与资源文件,并附带 pdfium 动态库、示例工程与说明文档,版本内含全部源代码,便于二次开发与调试。已有 623 人学习下载。套件覆盖从底层渲染到上层控件封装的完整链路,读者可据此快速在自有项目中嵌入 PDF 阅读与编辑模块,并借助源码理解组件内部实现与扩展方式。
1. 拿到一个 Delphi 10.3 的 PDF 组件源码包,先别急着编译
如果你正在用 Delphi 10.3 做桌面项目,客户又提了“要能打开 PDF、能翻页、能填表单、最好还能改两笔”的需求,那这个 Winsoft PDFium Component Suite 5.4 的完整源码包,大概率就是你要找的东西。它不是某个成品 PDF 阅读器,而是一套把 PDFium 引擎封装成 Delphi 原生 VCL 组件的源码工程——你拿到的是组件本身的实现代码,可以编进 IDE、拖到窗体上、按自己的业务逻辑去调。适合两类人:一类是想在现有 Delphi 项目里快速塞进 PDF 预览与编辑能力的一线开发,另一类是想搞清楚“Delphi 怎么和 C++ 写的 PDF 引擎对接”的熟手。关键词 delphi 10.3、Winsoft_PDFium、PDF 编辑组件,说的就是这套东西的定位:RAD 环境下的 PDF 能力封装。
这套源码包的价值不在“开箱即用的 exe”,而在“你能改”。PDF 阅读、页面渲染、文本提取、表单域读写、页面增删旋转、注释与书签这些能力,通常都以组件属性和方法的形式暴露出来,底层通过 DLL 边界调用 PDFium。你改的是 Delphi 侧的封装层,引擎本身不用碰。下面按“它是什么 → 怎么跑起来 → 核心封装怎么写的 → 怎么改”这条线拆,中间会穿插我踩过的坑。
2. 工程结构与运行链路:从包安装到窗体上的一次渲染
2.1 源码包里的几层东西
这类组件套件的源码包,目录结构基本是固定的几块,拿到后先按这个思路对号入座,别一上来就双击 dpr:
| 目录/文件类型 | 作用 | 你要不要动 |
|---|---|---|
| 组件包工程(.dpk) | 把各单元注册成 IDE 组件页 | 一般不动,编译安装用 |
| 核心封装单元(.pas) | 对 PDFium 的接口声明与类封装 | 二次开发主战场 |
| 演示工程(demo) | 最小可运行示例 | 先跑通它 |
| 运行时依赖(DLL) | PDFium 动态库,32/64 位各一份 | 按目标平台放对 |
| 资源与图标 | 组件图标、字符串资源 | 基本不动 |
血泪经验第一条:先确认 DLL 的位数和你编译的目标平台一致。Delphi 10.3 默认可能编 32 位,而包里如果只放了 64 位 DLL,运行时会直接报“找不到入口点”或加载失败,这个报错很玄学,看起来像代码问题,其实是位数不匹配。
2.2 安装组件包的标准步骤
在 IDE 里走一遍,顺序别乱:
# 1. 打开组件包工程(在 IDE 中 File > Open,选 .dpk) # 2. Project > Build 编译包 # 3. Component > Install Packages,把刚编出来的包勾上 # 4. 新建一个 VCL 窗体应用,组件页应出现对应组件编译前先在Tools > Options > Environment Options里确认库路径包含源码单元所在目录,否则uses里的单元找不到。安装成功后,组件会出现在工具面板的独立页签里,拖一个到窗体上,属性面板能看到 PDF 文件路径、当前页、缩放模式这些。
2.3 最小可运行示例:加载并渲染第一页
演示工程通常长这样,核心就几行,但每行都有讲究:
procedure TForm1.LoadPdf(const AFileName: string); begin // 指定 PDF 文件,组件内部会触发文档解析 PdfView1.FileName := AFileName; // 打开文档,失败会抛异常或返回 False,视封装而定 if not PdfView1.Open then begin ShowMessage('打开失败,检查文件路径与 DLL 是否就位'); Exit; end; // 渲染当前页到组件画布 PdfView1.ActivePage := 0; PdfView1.Render; end;逻辑说明:FileName只是记录路径,真正解析发生在Open;ActivePage是零基页码,别传 1 进去然后纳闷为什么跳页;Render才是把页面画到控件上。参数上,缩放一般有Zoom或ZoomMode(适应宽度/适应页面/自定义百分比),初次跑通建议先用“适应宽度”,避免大页面渲染超出可视区以为没画出来。
提示:如果
Open成功但界面一片空白,先查Render有没有被调用,再查控件尺寸是否为 0——窗体还没显示时尺寸是 0,渲染出来自然是空的。
3. 核心封装拆解:Delphi 怎么和 PDFium 对话
3.1 接口声明层:把 C 函数翻译成 Delphi 声明
PDFium 是 C 接口的动态库,Delphi 侧第一件事是把函数原型声明出来。这一层通常在专门的单元里,用external绑定 DLL:
// 文档加载:路径、密码、返回文档句柄 function FPDF_LoadDocument(file_path: PAnsiChar; password: PAnsiChar): Pointer; cdecl; external PDFIUM_DLL; // 获取页数 function FPDF_GetPageCount(document: Pointer): Integer; cdecl; external PDFIUM_DLL; // 加载指定页(零基) function FPDF_LoadPage(document: Pointer; page_index: Integer): Pointer; cdecl; external PDFIUM_DLL;逻辑说明:cdecl调用约定必须和 DLL 导出一致,写成stdcall会在调用瞬间崩,且崩得莫名其妙;字符串统一用PAnsiChar,Delphi 的string是 Unicode,传进去前要AnsiString转换,否则路径里带中文就翻车。参数上page_index零基,password传nil表示无密码,加密文档要在这里给口令。
3.2 对象封装层:句柄生命周期管理
裸句柄用起来容易漏释放,封装层一般用一个类持有文档句柄,在Destroy里统一关:
type TPdfDocument = class private FHandle: Pointer; FPageCount: Integer; public constructor Create(const AFile: string; const APassword: string = ''); destructor Destroy; override; property PageCount: Integer read FPageCount; end; constructor TPdfDocument.Create(const AFile, APassword: string); var LFile, LPwd: AnsiString; begin inherited Create; LFile := AnsiString(AFile); if APassword = '' then FHandle := FPDF_LoadDocument(PAnsiChar(LFile), nil) else begin LPwd := AnsiString(APassword); FHandle := FPDF_LoadDocument(PAnsiChar(LFile), PAnsiChar(LPwd)); end; if FHandle = nil then raise Exception.Create('PDF 加载失败:' + AFile); FPageCount := FPDF_GetPageCount(FHandle); end; destructor TPdfDocument.Destroy; begin if FHandle <> nil then FPDF_CloseDocument(FHandle); // 必须关,否则内存持续涨 inherited; end;逻辑说明:AnsiString局部变量保证转换后的缓冲区在调用期间有效,直接PAnsiChar(AnsiString(AFile))在某些编译器版本下会拿到临时对象,属于踩坑高发点。Destroy里关文档是硬要求,PDFium 的文档句柄不释放,反复打开关闭会稳定吃内存。参数上密码为空走nil分支,避免传空串指针。
3.3 渲染层:位图、矩阵与 DPI
渲染不是“调个函数就出图”,要准备位图缓冲和变换矩阵:
// 按目标宽高创建位图,再渲染页面到该位图 FBitmap := TBitmap.Create; FBitmap.PixelFormat := pf32bit; FBitmap.SetSize(TargetW, TargetH); // 初始化渲染矩阵:缩放 + 平移 FPDF_BITMAP_InitMatrix(matrix); FPDF_BITMAP_SetMatrixScale(matrix, ScaleX, ScaleY); FPDF_RenderPageBitmap(FBitmapHandle, PageHandle, 0, 0, TargetW, TargetH, 0, Flags);逻辑说明:ScaleX/ScaleY决定清晰度,按屏幕 DPI 和缩放比例算,1.0 是原始尺寸,高分屏下给 1.5~2.0 才不糊;Flags控制是否抗锯齿、是否渲染注释,按需组合。位图必须是 32 位,PDFium 输出 BGRA,转成 Delphi 的pf32bit后颜色通道顺序要对,否则红蓝互换,这个错很常见。
3.4 编辑能力:表单域与页面操作
“PDF 编辑组件”里的编辑,多数场景指表单域读写和页面级操作,不是像 Word 那样随便改文字:
// 遍历表单域,按类型读写 for I := 0 to Form.FieldCount - 1 do begin case Form.Fields[I].FieldType of ftText: Form.Fields[I].Value := '填充值'; ftCheckBox: Form.Fields[I].Checked := True; end; end; // 页面旋转与删除,改完必须保存到新文件 Doc.Pages[0].Rotation := 90; Doc.DeletePage(2); Doc.SaveAs('output.pdf');逻辑说明:表单域类型判断不能省,往复选框写字符串会静默失败;页面操作后要SaveAs到新路径,直接覆盖原文件在部分封装里会因文件占用失败。参数上旋转角度一般只接受 90 的倍数。
4. 避坑与排查:五个真实会卡住你的点
4.1 现象:编译通过,运行时提示找不到 DLL
原因:DLL 不在 exe 同目录,或位数不匹配。解决:把对应位数的 PDFium DLL 放到输出目录,32 位工程配 32 位库,别混用;用依赖查看工具确认导出函数名对得上。
4.2 现象:中文路径打开失败,英文路径正常
原因:PAnsiChar转换时用了系统默认代码页,中文丢失。解决:统一走AnsiString显式转换,或确认封装层是否提供了宽字符版本接口,优先用宽字符。
4.3 现象:连续打开多个 PDF 后内存暴涨
原因:文档句柄或页面句柄没释放。解决:每个FPDF_LoadPage对应一个FPDF_ClosePage,文档在对象销毁时关;用任务管理器盯内存曲线验证。
4.4 现象:渲染出来红蓝颠倒或全黑
原因:位图通道顺序或矩阵未初始化。解决:确认pf32bit且通道按 BGRA 解释;矩阵先Init再设缩放,别直接改未初始化结构。
4.5 现象:表单填写后保存,再打开值没了
原因:只改了内存对象没触发保存,或保存到了只读目录。解决:改完显式调保存接口并检查返回值,输出路径选可写目录,保存后重新加载验证。
5. 二次开发:把组件改成贴合业务的形态
真正落地时,很少直接用演示工程,通常要做三件事:裁剪、扩展、验证。
裁剪上,如果只需要预览不需要编辑,可以把表单与页面操作相关单元从uses里摘掉,减小体积;但注意摘之前确认没有隐式依赖,Delphi 的单元依赖有时藏得深,编不过再逐个加回,比一次删干净稳妥。
扩展上,最常见的改造是加“缩略图侧栏”和“文本搜索”。缩略图本质是对每页用低分辨率矩阵渲染一次,缓存成TBitmap列表;搜索则调 PDFium 的文本查找接口,拿到页码和矩形区域后高亮。下面是一个缩略图生成的骨架:
procedure TForm1.BuildThumbnails(const ADoc: TPdfDocument); var I: Integer; LThumb: TBitmap; begin for I := 0 to ADoc.PageCount - 1 do begin LThumb := RenderPageToBitmap(ADoc, I, 120, 160); // 小尺寸 ImageList1.Add(LThumb, nil); // 存入列表供列表控件用 end; end;参数上缩略图宽高按侧栏宽度定,别用全尺寸渲染再缩小,那样又慢又费内存。验证方法很直接:拿一份 100 页以上的文档,看生成耗时和内存峰值,超过预期就改成懒加载——滚到哪页渲染哪页。
还有一个我常做的技巧:把文档对象包一层业务类,对外只暴露“打开、跳页、填表、导出”四个方法,内部句柄和异常全吞掉转成业务错误码。这样上层界面代码不碰任何 PDFium 细节,换引擎版本时改动面极小。从那以后我每次接 PDF 相关需求,都强制先把这层薄封装写出来再动界面,后悔药少吃很多。希望帮到你。
本文还有配套的精品资源,点击获取