Delphi 10.3 全源码 PDFium 组件实战:VCL 中 PDF 显示、表单填写与注释保存

发布时间:2026/11/2 4:35:32
Delphi 10.3 全源码 PDFium 组件实战:VCL 中 PDF 显示、表单填写与注释保存 简介Winsoft PDFium Component Suite 5.4 是面向 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 包工程及 44 个 hpp 头文件为主体另含 res/dcr 资源、dfm/lfm 窗体、dll 与 pdfium 运行库及示例工程完整覆盖从源码到编译产物的组件结构。资源附带全部源代码便于读者研读渲染封装与接口设计按包工程组织方式二次编译或裁剪功能并借助示例快速验证文本提取与编辑流程。目前已有 623 人学习下载可作为 Delphi 平台 PDF 组件选型与源码研究的实用参考。1. 拿到一份 Delphi 10.3 的 PDFium 全源码包先别急着编译上周有个做桌面端的朋友甩过来一个压缩包文件名长得像乱码Winsoft_PDFium_Component_Suite_5.4_for_5-10.3_FULL_SOURCE.rar。他原话是「客户要在 Delphi 10.3 的 ERP 里直接看 PDF、填表单、盖电子章我找了一圈要么是壳要么是试用版这个带 FULL SOURCE你帮我看看能不能用」。这其实是很多 Delphi 老项目组的真实处境——VCL 界面跑了十几年业务逻辑全在 Pascal 里突然要加 PDF 渲染和编辑用外部进程调 Adobe 太丑用 WebView 嵌 PDF.js 又要处理一堆兼容性最省事的路径就是找一个能直接拖到 Form 上的原生组件。这份资源就是干这个的Winsoft 的 PDFium Component Suite版本 5.4覆盖 Delphi 5 到 10.3 的源码全量包。PDFium 是 Chromium 里那个 PDF 渲染引擎Google 维护渲染质量和 Chrome 一致Winsoft 做的事是把它包成 Delphi 的 VCL 组件让你在 Object Inspector 里设几个属性就能显示 PDF、提取文字、做注释、填 AcroForm 字段。关键词里的「delphi 10.3」「PDF 编辑组件」说的就是它。适合谁手上是 Delphi 5~10.3 的老项目、需要离线渲染、不想引入浏览器内核、又要求能改源码排查问题的团队。不适合谁纯 FMX 跨平台或者已经上 11/12 的新项目这套的 DCU 和包结构是按老版本 IDE 组织的硬上会翻车。2. 拆包看结构源码、DCU 与 PDFium 动态库怎么摆2.1 压缩包里到底有什么解压之后不要急着双击.dpr先按目录扫一遍。这类全源码包的典型结构是「组件源码 预编译 DCU 原生 DLL Demo」四类东西混在一起分不清就会在编译期和运行期各踩一次坑。我一般先跑一遍文件清单把扩展名归类目录/文件类型内容用途注意点Source/下.pas组件包装单元如 PDFium 主控件、注释、表单类可编译、可改依赖顺序有讲究Source/下.inc版本条件编译开关区分 Delphi 5~10.3改错一个开关整包编不过Lib/或Dcu/预编译.dcu免编译直接用必须和 IDE 版本严格对应Bin/或根目录pdfium.dll32/64 位运行时渲染核心位数必须和宿主进程一致Demos/示例工程验证环境先跑 Demo 再动自己的工程这里第一个认知要立住组件源码和 PDFium 原生库是两层东西。.pas编出来的是 Delphi 侧的包装真正解析 PDF 字节流的是pdfium.dll。所以「编译通过」不等于「能显示」DLL 没放对位置、位数不匹配表现是控件一片空白或者一创建就抛异常而不是编译错误。很多人卡在这一步以为是源码有问题其实是运行期加载失败。2.2 版本条件编译5.4 覆盖 5~10.3 的开关在哪全源码包为了兼容十几个 Delphi 版本.inc里一定有一堆{$IFDEF VERxxx}。Delphi 10.3 的符号是VER330Rio10.2 是VER320再往前 7 是VER150。打开主.inc先确认两件事当前分支是否已经包含VER330以及字符集相关开关UNICODE是否按 10.3 的默认走。老包常见的翻车点是作者在 10.3 上没测全某个{$IFDEF}分支里引用了 10.3 已废弃的单元。// 典型的版本开关片段位置在各 .inc 文件顶部 {$IFDEF VER330} // Delphi 10.3 Rio {$DEFINE PDFIUM_DELPHI_10_3} {$DEFINE PDFIUM_UNICODE} {$ENDIF} {$IFDEF PDFIUM_DELPHI_10_3} // 10.3 下 System.AnsiStrings 的引用方式与老版本不同 uses System.SysUtils, System.Classes, System.AnsiStrings; {$ELSE} uses SysUtils, Classes; {$ENDIF}这段的逻辑是先用 IDE 自带的VER330符号判定版本再派生出包内自己的语义开关后面的单元统一用语义开关做条件编译避免到处写VERxxx。参数上你只需要关心一处——如果你的 10.3 装了更新补丁导致某些 RTL 单元行为变化优先改语义开关而不是动VER330判定否则会影响整个包的版本识别。改完.inc记得全量重编Build 而非 Compile增量编译对条件编译的感知并不可靠这是血泪经验。2.3 安装组件包的正确顺序不要一个个.pas往工程里拖。正确做法是先编运行时包Runtime Package再编设计期包Design Package最后在 IDE 里 Install。顺序错了的典型现象是设计期能看到控件页签但一拖到 Form 上就报「Class not found」。# 以命令行编译为例先运行时包后设计期包 # 32 位 Debug 先跑通再切 Release / 64 位 dcc32 -B -Q ^ -USource;Source\Core;Source\Forms ^ -ISource ^ -NULib\Dcu\Win32 ^ PDFium_Runtime.dpk dcc32 -B -Q ^ -USource;Source\Core;Source\Forms ^ -ISource ^ -LULib\Dcu\Win32 ^ PDFium_Design.dpk-B强制全量编译-U是单元搜索路径-I是 include 路径.inc靠它找-NU指定 DCU 输出目录-LU让设计期包链接到刚编出来的运行时包。参数里最容易漏的是-I漏了就会报找不到.inc而错误信息往往指向一个莫名其妙的标识符未定义排查方向会被带偏。编完把Bin下的pdfium.dll拷到 Demo 的 exe 同目录先跑官方 Demo 确认渲染正常再动自己的工程——这一步是分界线Demo 不通就别往下走。3. 把 PDF 显示到 VCL 窗体从加载到翻页的最小闭环3.1 控件初始化与文档加载最小可用闭环就四步放控件、设 DLL 路径、Load 文档、设当前页。Winsoft 这套的控件命名各家版本略有差异但属性语义稳定一个Document或FileName入口负责加载PageNumber/PageCount管翻页Zoom管缩放。下面这段是通用写法落到你手上的具体类名按源码里的声明微调。procedure TFormMain.FormCreate(Sender: TObject); begin // 1) 指定原生库位置避免依赖系统 PATH PDFView1.LibraryPath : ExtractFilePath(ParamStr(0)) pdfium.dll; // 2) 加载文档失败要拿到错误码而不是只看布尔值 if not PDFView1.LoadFromFile(D:\data\contract.pdf) then begin ShowMessage(Format(加载失败错误码 %d, [PDFView1.LastErrorCode])); Exit; end; // 3) 初始视图参数 PDFView1.Zoom : 100; // 百分比100 为原始尺寸 PDFView1.PageNumber : 1; // 1-based注意不是 0 PDFView1.FitMode : fmFitWidth; // 按宽度适配长文档常用 end;逻辑说明LibraryPath显式指定 DLL 是必须的靠系统目录搜索在客户机上十有八九找不到LoadFromFile返回布尔只是「有没有抛异常」的粗粒度结果真正定位问题要看LastErrorCodePDFium 的错误码能区分「文件损坏」「加密」「不支持的滤镜」几类。参数上PageNumber是 1-based这点和很多 PDF 库的 0-based 不一样翻页逻辑里for i : 0 to PageCount - 1再PageNumber : i 1是常见写法写反了会越界。FitMode建议默认按宽度因为合同、报表类文档横向溢出比纵向溢出更烦人。3.2 翻页、缩放与渲染性能的三个参数渲染性能在 VCL 里主要受三件事影响是否每次翻页都重新解析、位图缓存策略、以及高 DPI 下的缩放重绘。PDFium 本身有页面缓存但包装层如果每次PageNumber赋值都触发一次完整LoadPage翻页就会明显卡顿。实操里我一般把「预加载相邻页」打开并把缩放做成离散档位75/100/125/150避免鼠标滚轮连续改Zoom导致高频重绘。procedure TFormMain.NextPage; begin if PDFView1.PageNumber PDFView1.PageCount then begin PDFView1.PageNumber : PDFView1.PageNumber 1; // 预渲染下一页减少翻页白屏 if PDFView1.PageNumber PDFView1.PageCount then PDFView1.PreloadPage(PDFView1.PageNumber 1); end; end; procedure TFormMain.SetZoomStep(AIndex: Integer); const ZOOMS: array[0..4] of Integer (75, 100, 125, 150, 200); begin if (AIndex Low(ZOOMS)) and (AIndex High(ZOOMS)) then PDFView1.Zoom : ZOOMS[AIndex]; // 离散档位避免连续重绘 end;PreloadPage是这套组件里比较实用的一个口子代价是内存——一份 200 页的扫描件全预加载能吃掉几百 MB所以只预加载相邻一页是平衡点。Zoom用离散档位而不是绑定到 TrackBar 的连续值是因为 PDFium 每次缩放都要重新栅格化连续值会让 UI 线程排队。如果你的场景是「大屏看图纸、要平滑缩放」那就得把渲染放到后台线程VCL 控件只在主线程贴位图这个改造量不小先评估值不值。3.3 文字提取与搜索高亮只显示不够很多业务要「在 PDF 里搜关键词并高亮」。PDFium 的文字提取是按页返回文本块包装层一般提供GetText(PageIndex)或逐字符回调。搜索高亮的实现路径是提取当前页文本 → 定位匹配区间 → 用注释或矩形覆盖层画高亮。注意 PDF 里的文字坐标是页面坐标系点1/72 英寸和屏幕像素不是一回事要经过当前Zoom和 DPI 换算。function TFormMain.HighlightKeyword(const AKeyword: string): Integer; var PageText: string; PosIdx: Integer; begin Result : 0; PageText : PDFView1.GetPageText(PDFView1.PageNumber - 1); // 0-based 取文本 PosIdx : Pos(AKeyword, PageText); while PosIdx 0 do begin // 用文本区间建高亮注释坐标换算由组件内部按当前 Zoom 处理 PDFView1.AddTextHighlight(PDFView1.PageNumber - 1, PosIdx - 1, Length(AKeyword)); Inc(Result); PosIdx : PosEx(AKeyword, PageText, PosIdx Length(AKeyword)); end; end;这里有个容易忽略的边界GetPageText的索引是 0-based而PageNumber是 1-based两个体系混用是这套组件最容易写错的地方我见过有人在循环里减了两次 1结果永远搜不到第一页。另外扫描件图片型 PDF提取出来是空串这不是组件坏了是 PDFium 不做 OCR要文字得先过 OCR 引擎。高亮注释在保存时会写进 PDF 的注释层如果客户要求「高亮不能改原文件」那就得用覆盖层画矩形而不是真注释这是需求层面的选择动手前先问清楚。4. 表单填写与注释保存编辑能力的边界在哪4.1 AcroForm 字段读写PDF 表单分两类AcroForm标准表单域有字段名和 XFAAdobe 私有复杂。PDFium 对 AcroForm 支持较好对 XFA 基本无能为力——这是选型时必须先确认的客户给的模板如果是 XFA这套组件填不了别硬扛。AcroForm 的读写路径是枚举字段 → 按字段名设值 → 保存。procedure TFormMain.FillForm; var I: Integer; Field: TPDFFormField; begin if not PDFView1.HasAcroForm then begin ShowMessage(该文档不含标准表单域); Exit; end; for I : 0 to PDFView1.FormFieldCount - 1 do begin Field : PDFView1.FormField[I]; // 按字段名匹配业务数据字段名区分大小写 if Field.Name BuyerName then Field.Value : 某公司 else if Field.Name SignDate then Field.Value : FormatDateTime(yyyy-mm-dd, Now); end; PDFView1.SaveToFile(D:\data\contract_filled.pdf); end;逻辑上先判HasAcroForm因为很多「看起来像表单」的 PDF 其实是画上去的线框没有字段直接遍历会拿到 0 个字段然后静默失败。Field.Name区分大小写且可能带层级前缀如form1[0].BuyerName实际项目里我一般先导出全部字段名做成映射表而不是在代码里硬编码。SaveToFile默认是另存原文件不动这点符合大多数合规要求如果要原地保存先写临时文件再替换避免保存中途崩溃把原件写坏。4.2 注释、印章与增量保存注释和印章是「编辑」里最常被问的两件事。注释高亮、批注、便签走 PDFium 的注释 API印章本质是往页面内容流里贴一张图或一段矢量。这里的关键概念是增量保存incremental savePDF 支持在原文件末尾追加变更而不是全量重写好处是快、且保留原始签名坏处是文件会越来越大多次保存后体积膨胀明显。procedure TFormMain.AddStamp(const AImagePath: string; AX, AY: Double); begin // 坐标单位为 PDF 点原点在页面左下角 PDFView1.AddImageStamp( PDFView1.PageNumber - 1, // 0-based 页索引 AImagePath, AX, AY, // 左下角坐标 120, 60 // 宽高单位点 ); // 增量保存保留原内容追加变更 PDFView1.SaveToFile(D:\data\stamped.pdf, True); end;参数里AX, AY是页面坐标系左下角原点和屏幕坐标左上角原点Y 轴方向相反直接拿鼠标坐标往里塞会上下颠倒这是新手最常见的翻车点。SaveToFile第二个参数True表示增量保存适合「盖个章就完事」的场景如果要做「批量改几十页再存」增量保存会让文件膨胀到不可接受这时应该走全量重写传False代价是原始数字签名会失效——有签名的合同千万别用全量重写这是合规红线。4.3 加密文档与权限位带密码的 PDF 分两种打开密码user password和权限密码owner password。PDFium 能处理前者加载时传密码即可后者限制的是「能不能打印、能不能复制、能不能改」即使能打开编辑操作也会被拒绝。包装层一般有Password属性和Permissions只读属性。PDFView1.Password : userpwd; if PDFView1.LoadFromFile(D:\data\secure.pdf) then begin // 检查权限位别等用户点了保存才报错 if not (pAllowModify in PDFView1.Permissions) then btnSave.Enabled : False; end;权限位要在加载后立刻检查并反映到 UI 上而不是等用户操作完再弹错误——这是体验问题也是很多交付项目被挑刺的地方。另外注意权限密码在 PDF 规范里是「君子协定」技术上能绕过所以如果你的业务真的要求「不可篡改」光靠 PDF 权限位不够得配合数字签名或服务端校验。5. 避坑清单这份全源码包最容易翻车的五个地方5.1 现象编译通过运行时控件一片空白原因pdfium.dll没找到或位数不匹配。32 位宿主进程加载 64 位 DLL 会直接失败但异常可能被包装层吞掉表现为空白而非报错。解决用 Dependency Walker 或 Process Monitor 看加载路径确认 exe 同目录下的 DLL 位数与编译目标一致LibraryPath显式指定绝对路径别依赖 PATH。5.2 现象设计期能拖控件运行期报「Class not found」原因设计期包和运行时包的 DCU 版本不一致或者只装了设计期包没编运行时包。解决删掉Lib\Dcu下所有旧 DCU按「运行时包 → 设计期包 → IDE Install」顺序全量重编检查 IDE 的 Library Path 里是否残留了其他版本的组件路径路径顺序会决定加载哪个 DCU。5.3 现象中文路径下加载 PDF 失败英文路径正常原因老版本包装层内部用了 ANSI 版文件 API10.3 默认 Unicode 字符串传进去被截断。解决确认.inc里PDFIUM_UNICODE已定义如果源码里确实有AnsiString转换改成WideString或先LoadFromStream再解析绕开路径编码问题。这是 Delphi 老组件在 10.x 上的通病不是这份包独有。5.4 现象翻页越来越卡内存持续上涨原因每次翻页都新建页面对象却没释放或者预加载开了但没设上限。解决确认PreloadPage只预加载相邻页在PageNumber变更前后用任务管理器看私有工作集如果单调上涨就是泄漏检查包装层是否在OnPageChanged里重复LoadPage。PDFium 的页面对象需要显式ClosePage包装层漏掉是常见 bug源码在手就直接补。5.5 现象保存后的 PDF 在别的阅读器里打不开原因增量保存时追加的内容流偏移计算错误或者保存过程中异常中断留下半截文件。解决保存一律先写临时文件成功后原子替换跨阅读器验证至少用两个不同引擎比如系统自带的和浏览器内置的各开一次如果只有增量保存出问题切全量重写对比能快速定位是增量逻辑还是内容流本身的问题。6. 进阶把渲染搬出 UI 线程以及一份可复用的验证清单到这一步基本功能都能跑了真正决定这套组件能不能上生产的是两件事大文档下的 UI 响应以及交付前的验证覆盖。先说线程。VCL 控件只能在主线程碰但 PDFium 的渲染本身可以在后台线程做——思路是后台线程用 PDFium 的文档对象渲染到位图主线程只负责把位图贴到控件上。包装层如果没暴露线程安全的渲染接口就得自己包一层注意每个线程要有独立的文档实例PDFium 的文档对象不是线程安全的共享会随机崩溃。type TRenderTask class(TThread) private FDocPath: string; FPageIndex: Integer; FBitmap: TBitmap; protected procedure Execute; override; public constructor Create(const APath: string; APage: Integer); property Bitmap: TBitmap read FBitmap; end; procedure TRenderTask.Execute; var Doc: TPDFDocument; begin // 后台线程独立打开文档不复用 UI 线程的实例 Doc : TPDFDocument.Create; try Doc.LoadFromFile(FDocPath); FBitmap : Doc.RenderPage(FPageIndex, 150); // 150 DPI finally Doc.Free; end; end;这段的关键约束是「独立实例」和「DPI 显式指定」。RenderPage的 DPI 参数决定位图分辨率屏幕上 96 DPI 够用但要打印或导出就得 150~300提前想清楚用途再定事后改会牵动整个缓存策略。线程里创建的TBitmap跨线程传给主线程时要Synchronize或走消息队列直接赋值是未定义行为。再说验证清单。这套组件涉及「Delphi 包装层 原生 DLL PDF 规范」三层任何一层出问题表现都相似所以交付前我固定跑一遍下面这张表比事后救火省事得多验证项方法通过标准DLL 加载换一台没装开发环境的机器跑 exe不报缺库控件正常创建位数匹配32/64 位各编一次两个目标都能加载对应 DLL中文路径文件放在含中文和空格的目录正常加载、正常保存加密文档带 user password 和 owner password 各一份前者能开后者权限位正确反映表单往返填值保存后用另一阅读器打开字段值保留、字段名不变大文档200 页以上扫描件连续翻页内存稳定、翻页无明显卡顿增量保存连续保存 10 次文件可打开体积增长在预期内这张表里「换机器验证」和「另一阅读器打开」是最容易被跳过、也最容易暴露问题的两项——开发机上什么都有客户机上什么都没有这个落差就是翻车的温床。从那以后我每次交付带原生库的 Delphi 组件都强制在一台干净虚拟机上走一遍完整清单宁可多花半小时也不想半夜被电话叫起来查「为什么客户那边打不开」。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询