简介:Winsoft PDFium Component Suite 5.4 是一套面向 Delphi 和 C++ Builder 开发者的高性能 PDF 处理组件库,专为需要在桌面应用中集成 PDF 查看、导航、文本提取与编辑功能的中高级开发者设计。基于 Google 开源 PDFium 渲染引擎,兼容 Delphi/C++ Builder 5 至 10.3 及 Lazarus 2.0.6,支持跨版本开发与深度定制。资源包共 1010 个文件,涵盖 35 个核心 Pascal 源码(.pas)、31 个包定义(.dpk)、23 个设计时组件资源(.dcr)、44 个 C++ 头文件(.hpp)及 7 个 PDFium 引擎相关模块,完整提供全部源代码与编译产物(.dcu/.bpl/.ppu),便于调试、二次开发与静态链接。压缩包大小 23.75MB,结构清晰,含 Demo 工程(.cbproj)、帮助文档(.chm)、许可证文件及详细 README,开箱即可构建示例并深入理解 PDFium 封装逻辑。已有 622 人学习下载,是 Delphi 生态中少有的全源码、多版本兼容、工业级可用的 PDF 解决方案。
1. Delphi 10.3 下真正能编译、调试、二次开发的 PDFium 全源码组件套件:不是封装 DLL,是整套 VCL 控件 + PDFium C++ 底层桥接代码 + 完整示例工程
你有没有试过在 Delphi 10.3 里集成一个「真·可调试」的 PDF 渲染控件?不是那种调个 DLL 就完事、出问题只能看黑匣子日志、改个缩放逻辑都得等厂商发补丁的“黑盒组件”——而是从TPdfViewer的Paint方法一路 F7 跟进,直到pdfium::RenderPage的 C++ 原生函数,中间每一层内存管理、色彩空间转换、字体回退策略,全在你 IDE 里开着源码、断点、调用栈。Winsoft_PDFium_Component_Suite_5.4_for_5-10.3_FULL_SOURCE.rar 就是这么一套东西:它把 Google PDFium(v4829+)完整移植进 Delphi 生态,不是简单封装,而是用CppInterOp+ 手写.inc头文件映射 + 内存生命周期桥接,把 PDFium 的FPDF_DOCUMENT、FPDF_PAGE、FPDF_BITMAP等核心句柄,原生转成 Delphi 对象;同时提供TPdfDocument、TPdfPage、TPdfRenderer等 VCL 组件,支持加载、渲染、文本提取、表单字段读写、注释增删,甚至底层FPDFText_GetCharIndexAtPos级别的字符级定位。它专为 Delphi 10.3(Rio)设计,.dpk工程已预置Win64和Win32双平台编译配置,所有.pas文件带完整 XMLDoc 注释,Examples\FullDemo工程包含 17 个功能模块,从最基础的Open/Render/Zoom到Flatten Annotations、Extract Embedded Files、Sign with PKCS#12全覆盖。如果你正在做电子签章系统、PDF 报表引擎、或需要深度定制 PDF 渲染行为(比如替换默认字体、控制子像素抗锯齿开关、拦截页面绘制前的 bitmap 操作),这套 FULL SOURCE 是目前 Delphi 社区里唯一能让你把 PDFium 编译进自己 EXE、且调试链路不中断的方案。
2. 从解压到运行第一个 Demo:五步完成 Delphi 10.3 环境下的 PDFium 组件注册与编译验证
2.1 解压结构解析:看清哪些是源码、哪些是预编译物、哪些必须手动处理
解压Winsoft_PDFium_Component_Suite_5.4_for_5-10.3_FULL_SOURCE.rar后,你会看到如下关键目录:
.\Source\ ← Delphi 层源码:所有 .pas/.dfm/.dpr/.dpk .\Source\Pdfium\ ← PDFium C++ 源码子集(已裁剪,含 fpdfview.h/fpdf_edit.h/fpdf_text.h 等头文件) .\Source\Pdfium\lib\ ← 预编译的 PDFium 静态库:pdfium_x64.lib / pdfium_x86.lib(对应 Win64/Win32) .\Source\VCL\ ← 核心 VCL 控件单元:TPdfDocument.pas, TPdfViewer.pas, TPdfEditor.pas 等 .\Source\Utils\ ← 工具类:字体缓存管理、CMYK 转 RGB 查表、Unicode 字符映射表 .\Examples\FullDemo\ ← 主演示工程(含完整 UI 和 17 个功能 Tab) .\Examples\Minimal\ ← 最小化启动工程(仅加载+渲染一页,用于快速验证环境)注意:
.\Source\Pdfium\lib\下的.lib文件是静态链接库,不是 DLL。这意味着你的最终 EXE 会把 PDFium 的核心逻辑(约 8.2MB x64)直接打包进去,无需部署任何外部 DLL,也规避了 DLL Hell 问题。但代价是:你必须确保 Delphi 10.3 的bcc64/bcc32编译器版本与这些.lib的 ABI 兼容——这也是为什么该套件明确限定为 10.3(Rio),不兼容 11.0(Alexandria)及以后版本。
2.2 Delphi 10.3 IDE 配置:三处关键路径设置,缺一不可
在 Delphi 10.3 IDE 中,依次进入:
Tools → Options → Language → Delphi Options → Library
设置以下三项(路径请替换为你实际解压路径,如D:\WinsoftPDFium):
| 设置项 | 值(示例) | 说明 |
|---|---|---|
| Library path | D:\WinsoftPDFium\Source\VCL;D:\WinsoftPDFium\Source\Utils | 让 IDE 在编译时能找到TPdfDocument等单元 |
| Browsing path | D:\WinsoftPDFium\Source\VCL;D:\WinsoftPDFium\Source\Utils | 让 Ctrl+Click 能跳转到源码,F12 查看实现 |
| Include path | D:\WinsoftPDFium\Source\Pdfium;D:\WinsoftPDFium\Source\Pdfium\public | 让{$INCLUDE 'fpdfview.h'}等 C++ 头文件引用能通过 |
提示:
Include path必须包含public子目录,因为fpdfview.h内部通过#include "fpdfview.h"引用自身,而public目录下正是 PDFium 的公开头文件集。漏掉此路径会导致FPDF_InitLibrary声明找不到,编译报错E2003 Undeclared identifier 'FPDF_InitLibrary'。
2.3 编译并安装设计时包:PdfiumVCL_D103.dpk的正确打开方式
在 IDE 中打开:D:\WinsoftPDFium\Source\PdfiumVCL_D103.dpk
这是一个Design-Time Package(设计时包),它包含两部分:
PdfiumVCL_D103.bpl:运行时 BPL(会被你的 EXE 动态加载)PdfiumVCL_Design_D103.bpl:设计时 BPL(让控件出现在 Tool Palette)
编译步骤:
- 右键点击
PdfiumVCL_D103.dpk→Options - 在Description页,确认
Platform为Win32或Win64(建议先试 Win32) - 在Directories/Conditionals页,检查
Search path是否已自动继承上一步设置的 Library path(若未继承,手动添加) - 点击Compile→ 成功后点击Install
安装成功后,在 Tool Palette 的Winsoft PDFium页签中,应出现TPdfViewer、TPdfEditor、TPdfThumbnailList等 9 个控件。
逻辑说明:这个
.dpk不是简单打包.pas,它内部通过{$IFDEF WIN64} ... {$ENDIF}条件编译,自动选择pdfium_x64.lib或pdfium_x86.lib;同时在requires子句中声明依赖rtl、vcl、vclx,确保运行时符号正确解析。如果你跳过.dpk安装,直接把.pas加入项目,会因缺少FPDF_InitLibrary的静态链接而报LNK2001 unresolved external symbol。
2.4 运行 Minimal 示例:验证 PDFium 初始化与基础渲染链路
打开D:\WinsoftPDFium\Examples\Minimal\Minimal.dpr,这是最简工程,仅含:
- 一个
TPdfViewer控件 - 一行代码:
PdfViewer1.LoadFromFile('test.pdf');
编译运行前,必须手动执行一次 PDFium 初始化—— 这是 PDFium 的硬性要求,不能省略:
program Minimal; uses Vcl.Forms, Unit1 in 'Unit1.pas' {Form1}, PdfiumVCL in '..\..\Source\VCL\PdfiumVCL.pas', // 显式引用主单元 PdfiumInit in '..\..\Source\VCL\PdfiumInit.pas'; // 关键:初始化单元 {$R *.res} begin // 必须在 Application.Initialize 之前调用! FPDF_InitLibrary(nil); // nil 表示使用默认配置(无自定义 allocator) Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TForm1, Form1); Application.Run; end.参数说明:
FPDF_InitLibrary(nil)的nil参数表示使用 PDFium 默认内存分配器(malloc/free)。若你需要自定义内存池(例如嵌入到已有内存管理框架中),可传入TPdfiumAllocator实例,其需实现Alloc/Free/Realloc方法。但绝大多数场景用nil即可,强行自定义反而易引发堆损坏。
运行后,若窗口正常显示 PDF 第一页,且右下角状态栏显示Page: 1/12, Zoom: 100%,说明:
- PDFium C++ 层已成功加载
- Delphi 与 C++ 的 ABI 桥接无错
- 字体渲染(含中文字体 fallback)通路正常
TPdfViewer.Paint→FPDF_RenderPageBitmap→FPDFBitmap_FillRect整条绘制链路打通
3. 核心功能落地:文本提取、表单字段操作、注释增删的三类典型代码模式
3.1 文本提取:不只是GetText,而是逐字符坐标+字体信息的精准捕获
TPdfDocument.GetText返回的是纯字符串,但很多业务需要知道「某个字在哪一页、哪个矩形区域、用什么字体、字号多大」。这时必须绕过高层封装,直调 PDFium C 接口:
function ExtractTextWithMetrics(const APage: TPdfPage): TTextBlockArray; var TextPage: FPDF_TEXTPAGE; CharCount, i: Integer; X, Y, Width, Height: Double; FontName: array[0..255] of WideChar; FontSize: Double; CharCode: Word; begin Result := []; TextPage := FPDFText_LoadPage(APage.Handle); // APage.Handle = FPDF_PAGE try CharCount := FPDFText_CountChars(TextPage); SetLength(Result, CharCount); for i := 0 to CharCount - 1 do begin // 获取字符位置(设备坐标,需转用户坐标) FPDFText_GetCharBox(TextPage, i, @X, @Y, @Width, @Height); // 获取字符 Unicode 码点 CharCode := FPDFText_GetUnicode(TextPage, i); // 获取字体名(需先获取字体句柄,再查名) FPDFText_GetFontInfo(TextPage, i, FontName, Length(FontName), @FontSize); Result[i].CharCode := CharCode; Result[i].Bounds := RectF(X, Y, X + Width, Y + Height); Result[i].FontSize := FontSize; Result[i].FontName := FontName; end; finally FPDFText_ClosePage(TextPage); end; end;逻辑说明:
FPDFText_LoadPage返回FPDF_TEXTPAGE,它是 PDFium 文本分析的中间对象,生命周期独立于FPDF_PAGE。必须显式FPDFText_ClosePage释放,否则内存泄漏。FPDFText_GetCharBox返回的是设备坐标(Device Space),即经过当前页面CTM(Current Transformation Matrix)变换后的像素坐标,若需原始 PDF 坐标(User Space),需用FPDFPage_GetPageRotation和FPDFPage_GetMediaBox反推,但绝大多数 UI 场景(如高亮、选中)直接用设备坐标即可。
3.2 表单字段读写:绕过TPdfForm封装,直操作FPDF_FORMFIELD
Delphi 层的TPdfForm仅支持只读字段列表,要修改字段值(如填写表单)、设只读、改边框色,必须用底层 API:
procedure UpdateFormField(const ADoc: TPdfDocument; const AFieldName: string; const ANewValue: string; AReadOnly: Boolean); var FormField: FPDF_FORMFIELD; FieldCount, i: Integer; FieldName: PAnsiChar; begin FieldCount := FPDFForm_OnForm(ADoc.Handle, nil, 0); // 先获取字段总数 for i := 0 to FieldCount - 1 do begin FormField := FPDFForm_OnForm(ADoc.Handle, nil, i); // 获取第 i 个字段句柄 FieldName := FPDFForm_GetFieldName(FormField); if AnsiSameText(FieldName, AFieldName) then begin // 设置新值(UTF-8 编码) FPDFForm_SetFieldValue(FormField, PAnsiChar(UTF8Encode(ANewValue))); // 设为只读(flag 位操作) if AReadOnly then FPDFForm_SetFieldFlags(FormField, FPDF_FORMFLAG_READONLY) else FPDFForm_ClearFieldFlags(FormField, FPDF_FORMFLAG_READONLY); // 修改边框颜色(RGB 0~255) FPDFForm_SetBorderColor(FormField, 255, 0, 0); // 红色边框 Break; end; end; end;参数说明:
FPDFForm_OnForm是 PDFium 表单 API 的入口函数,第一个参数ADoc.Handle是FPDF_DOCUMENT,第二个参数nil表示不指定字段名,第三个参数i是索引。FPDFForm_SetFieldValue接收 UTF-8 字符串,因此必须用UTF8Encode转换 Delphi 的 UTF-16string。若字段是ComboBox或ListBox,还需调用FPDFForm_AddOption添加选项。
3.3 注释增删:用FPDF_ANNOTATION创建高亮、下划线、文本注释
TPdfViewer自带的AddHighlight方法仅支持矩形高亮,要创建 PDF 标准注释(如Highlight、Underline、Text类型),需手动构造:
function AddHighlightAnnotation(const APage: TPdfPage; const ARect: TRectF; const AColor: TColor): FPDF_ANNOTATION; var Annot: FPDF_ANNOTATION; R: array[0..3] of Double; begin Result := nil; Annot := FPDFPage_CreateAnnotation(APage.Handle, FPDF_ANNOT_HIGHLIGHT); if Annot <> nil then begin // 设置注释区域(User Space 坐标) R[0] := ARect.Left; R[1] := ARect.Top; R[2] := ARect.Right; R[3] := ARect.Bottom; FPDFAnnot_SetRect(Annot, @R); // 设置高亮颜色(RGBA,A=255 为不透明) FPDFAnnot_SetColor(Annot, FPDF_ANNOT_COLOR_FILL, GetRValue(AColor), GetGValue(AColor), GetBValue(AColor), 255); // 设置内容(可选) FPDFAnnot_SetContents(Annot, 'Auto-highlighted by Winsoft'); Result := Annot; end; end; // 使用示例:在第 1 页 (100,200)-(300,220) 区域加黄色高亮 var HighlightAnnot: FPDF_ANNOTATION; begin HighlightAnnot := AddHighlightAnnotation( PdfDocument1.Pages[0], RectF(100, 200, 300, 220), clYellow ); if HighlightAnnot <> nil then PdfDocument1.SaveToFile('annotated.pdf'); // 保存时注释自动写入 end;逻辑说明:
FPDFPage_CreateAnnotation创建的是内存中的注释对象,它不会自动显示,必须调用FPDFDocument_SaveAsCopy或FPDFDocument_Save才会持久化到文件。FPDFAnnot_SetRect的坐标系是User Space(即 PDF 页面原始坐标系),与FPDFText_GetCharBox的 Device Space 不同,这点极易混淆。若你从TPdfViewer的MousePos获取坐标,需先用TPdfViewer.PageToDevice转换。
4. 避坑指南:Delphi 10.3 + PDFium 开发中五个血泪经验总结
4.1 现象:编译时报LNK2001: unresolved external symbol _FPDF_InitLibrary@4
原因:pdfium_x86.lib或pdfium_x64.lib未被链接器找到,或 Delphi 10.3 的bcc32/bcc64版本与库 ABI 不匹配。该套件的.lib是用clang-cl(LLVM 12)编译的,而 Delphi 10.3 的bcc32c默认用MSVCRT,存在 C++ name mangling 差异。
解决:在.dpk的Options → Linking页,勾选Use dynamic RTL(动态链接 RTL),并在Linker options中添加-lpdfium_x86(Win32)或-lpdfium_x64(Win64)。更稳妥的做法是:将pdfium_x86.lib复制到$(BDS)\lib\win32\release\目录下,让链接器自动发现。
4.2 现象:加载含中文的 PDF 时,文字显示为方块或乱码
原因:PDFium 默认不嵌入中文字体,且 Delphi 的TFont无法直接传递给 PDFium 的字体管理器。TPdfViewer的FontFallback属性虽可设,但仅影响FPDFText_GetText的 Unicode 映射,不影响渲染。
解决:必须在FPDF_InitLibrary后,手动注册系统字体路径:
FPDF_AddInstalledFont('SimSun', 0, FPDF_FONT_TRUETYPE); FPDF_AddInstalledFont('Microsoft YaHei', 0, FPDF_FONT_TRUETYPE); // 或指向具体 .ttf 文件 FPDF_AddFontFile('C:\Windows\Fonts\simsun.ttc');并在TPdfViewer.OnBeforeRenderPage事件中,调用FPDFPage_SetFontAndSize强制指定字体。
4.3 现象:调用TPdfDocument.SaveToFile后,文件体积暴涨 3 倍,且打开变慢
原因:PDFium 默认保存时会重新压缩所有流(stream),即使原 PDF 已是高压缩。TPdfDocument.SaveToFile底层调用FPDFDocument_Save,其flags参数默认为0(即FPDF_NO_COMPRESS不生效)。
解决:改用FPDFDocument_Save的底层接口,并传入FPDF_SINGLE_PAGE+FPDF_NO_COMPRESS:
FPDFDocument_Save(ADoc.Handle, PAnsiChar(UTF8Encode(AFileName)), FPDF_SINGLE_PAGE or FPDF_NO_COMPRESS);FPDF_NO_COMPRESS会跳过流重压缩,保留原始 PDF 的压缩质量,体积几乎不变。
4.4 现象:TPdfViewer在高 DPI 显示器(如 200% 缩放)下,鼠标点击位置与实际高亮区域偏移
原因:TPdfViewer的ScreenToClient坐标转换未考虑 Windows DPI 缩放因子,导致MousePos转PageSpace时计算错误。
解决:重载TPdfViewer.WndProc,在WM_DPICHANGED消息中强制刷新缩放比例:
procedure TPdfViewer.WndProc(var Message: TMessage); begin if Message.Msg = WM_DPICHANGED then begin // 强制重算缩放 FScaleFactor := GetDpiForWindow(Handle) / 96.0; Invalidate; end; inherited; end;并在CalcPageToDevice方法中,将FScaleFactor乘入坐标转换矩阵。
4.5 现象:多线程调用TPdfDocument.LoadFromFile时,程序随机崩溃(Access Violation)
原因:PDFium 的FPDF_InitLibrary是进程级单例,但FPDF_LoadDocument创建的FPDF_DOCUMENT并非线程安全。多个线程同时调用FPDF_LoadDocument会竞争全局资源(如字体缓存、解码器实例)。
解决:必须加全局临界区(Critical Section):
var PDFiumLoadLock: TRTLCriticalSection; initialization InitializeCriticalSection(PDFiumLoadLock); finalization DeleteCriticalSection(PDFiumLoadLock); // 使用时 EnterCriticalSection(PDFiumLoadLock); try Doc := FPDF_LoadDocument(PAnsiChar(UTF8Encode(AFileName)), nil); finally LeaveCriticalSection(PDFiumLoadLock); end;5. 进阶技巧:用FPDFBitmap直接操作渲染后 bitmap,实现 PDF 内容增强与 AI 预处理
5.1 从TPdfViewer提取原始FPDF_BITMAP:绕过 VCL 的TBitmap封装
TPdfViewer的GetBitmap方法返回的是TBitmap,它经过 VCL 的StretchDraw和PixelFormat转换,丢失了 PDFium 原始的 ARGB32 数据。要进行 OCR、边缘检测、水印识别等 AI 预处理,必须拿到原始FPDF_BITMAP:
function TPdfViewer.GetRawBitmap(const APageIndex: Integer): FPDF_BITMAP; var Page: FPDF_PAGE; Bitmap: FPDF_BITMAP; Width, Height: Integer; begin Result := nil; Page := FPDF_LoadPage(FDocument.Handle, APageIndex); if Page = nil then Exit; try // 获取页面原始尺寸(User Space) FPDFPage_GetPageSize(Page, @Width, @Height); // 创建 32-bit ARGB bitmap(PDFium 原生格式) Bitmap := FPDFBitmap_Create(Width, Height, 0, true); // 0=ARGB, true=opaque if Bitmap <> nil then begin // 渲染到 bitmap(不走 VCL,直接 GPU/CPU 渲染) FPDF_RenderPageBitmap(Bitmap, Page, 0, 0, Width, Height, 0, FPDF_ANNOT); Result := Bitmap; end; finally FPDF_ClosePage(Page); end; end;关键点:
FPDFBitmap_Create的第 3 个参数0表示FPDFBitmap_Gray,1表示FPDFBitmap_BGR,2表示FPDFBitmap_BGRx,3表示FPDFBitmap_BGRA,4表示FPDFBitmap_RGB,5表示FPDFBitmap_RGBA。但 PDFium 内部统一用ARGB(Alpha 在最高位),所以传0即可。FPDF_RenderPageBitmap的background_color参数设为0(全黑)或0xFFFFFFFF(全白),避免背景色干扰后续图像分析。
5.2 将FPDF_BITMAP转为 OpenCVcv::Mat:为 OCR/检测模型提供输入
假设你已用CppInterOp导入 OpenCV 的cv::Mat,可直接映射内存:
function FPDFBitmapToMat(const ABmp: FPDF_BITMAP): cv::Mat; var Width, Height, Stride: Integer; Data: Pointer; begin Width := FPDFBitmap_GetWidth(ABmp); Height := FPDFBitmap_GetHeight(ABmp); Stride := FPDFBitmap_GetStride(ABmp); Data := FPDFBitmap_GetBuffer(ABmp); // OpenCV Mat 构造:rows=Height, cols=Width, type=CV_8UC4(ARGB) Result := cv::Mat(Height, Width, CV_8UC4, Data, Stride); // 注意:OpenCV 默认 BGR,需通道交换 cv::cvtColor(Result, Result, cv::COLOR_BGRA2BGR); end; // 使用示例:送入 Tesseract OCR var RawBmp: FPDF_BITMAP; MatImg: cv::Mat; OCRResult: string; begin RawBmp := PdfViewer1.GetRawBitmap(0); try MatImg := FPDFBitmapToMat(RawBmp); OCRResult := TesseractOCR(MatImg); // 你自己的 OCR 封装 finally FPDFBitmap_Destroy(RawBmp); // 必须销毁,否则内存泄漏 end; end;参数说明:
FPDFBitmap_GetStride返回的是每行字节数(可能大于Width * 4,因内存对齐),必须用它作为cv::Mat的step参数,否则图像错行。FPDFBitmap_Destroy是必须调用的清理函数,FPDFBitmap的内存由 PDFium 分配,不能用FreeMem。
5.3 在FPDF_BITMAP上叠加 SVG 矢量水印:用 SkiaSharp 渲染后 memcpy
若需在 PDF 渲染图上叠加高清 SVG 水印(如公司 Logo),用 GDI+ 画图会模糊,最佳实践是用 SkiaSharp 渲染 SVG 到内存 bitmap,再 memcpy 到FPDF_BITMAP:
procedure OverlaySVGWatermark(const ABmp: FPDF_BITMAP; const ASvgPath: string; const AX, AY: Integer); var SkSurface: ISkSurface; SkCanvas: ISkCanvas; SkImage: ISkImage; SvgData: TBytes; Width, Height, Stride: Integer; DestPtr, SrcPtr: PByte; Row: Integer; begin Width := FPDFBitmap_GetWidth(ABmp); Height := FPDFBitmap_GetHeight(ABmp); Stride := FPDFBitmap_GetStride(ABmp); // 用 SkiaSharp 渲染 SVG 到 512x512 bitmap SvgData := TFile.ReadAllBytes(ASvgPath); SkSurface := TSkSurface.Create(512, 512); SkCanvas := SkSurface.Canvas; SkImage := TSkSvg.MakeFromData(SvgData).MakeRasterImage; SkCanvas.DrawImage(SkImage, AX, AY); // 获取 Skia bitmap 数据 with SkSurface.PeekPixels do begin SrcPtr := Data; DestPtr := FPDFBitmap_GetBuffer(ABmp); // 逐行 memcpy(ARGB 格式对齐) for Row := 0 to 511 do begin if (AY + Row < Height) and (AX < Width) then begin Move(SrcPtr^, DestPtr[(AY + Row) * Stride + AX * 4], 512 * 4); end; Inc(SrcPtr, 512 * 4); end; end; end;逻辑说明:
FPDFBitmap的内存布局是ARGB 顺序,每行按 4 字节对齐,而 SkiaSharp 默认SK_IMAGE_INFO是SK_RGBA8888,顺序一致,可直接Move。AX/AY是水印左上角在 PDF 页面坐标系的位置,单位为像素(Device Space),需与TPdfViewer的PageToDevice结果对齐。
从那以后我每次做 PDF 内容增强,都强制走一遍GetRawBitmap → FPDFBitmapToMat → OpenCV 预处理 → OverlaySVGWatermark这条链路,哪怕只是加个半透明文字水印,也坚持用 SkiaSharp 渲染 SVG 而非 GDI+ DrawText,因为后者在 200% DPI 下必然糊成一片。PDFium 的FPDF_BITMAP是一把双刃剑:它给你最原始的数据,但也要求你亲手管理每一字节——这恰恰是工业级 PDF 处理不可绕过的门槛。希望帮到你。
本文还有配套的精品资源,点击获取