PDF 超链接是 URI 注释,实际是覆盖在页面区域上的矩形。用户点击该区域后,阅读器会打开指定 URL。注释与显示文本是独立对象。PrintHyperlink 在一次调用里帮你把文字和注释矩形一起完成,省去手工对齐,但不是唯一方式。它更适合只绘制短链接文本的场景,而要为已有内容加热点或做文内跳转时,请改用 AddURILink 或 AddGoToLink
PrintHyperlink 如何工作
PrintHyperlink 挂在 THPDFPage 上,参数为 X 与 Y(点单位,原点在左下角,Y 向上)、标签文本和 URL。它先按当前超链接颜色调用 TextOut,再用同一次调用里的 TextWidth 与 TextHeight 计算矩形。这意味着字体、字号和颜色必须在调用前就设置好,且文本绘制与注释生成之间不能变化参数
默认颜色是 clBlue,SetRGBHyperlinkColor 只影响后续调用,不会回改旧注释。需要不同颜色分组时,在每组前设置颜色并在之后恢复
以下是一个最小例子,演示三条不同颜色链接
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Default blue for informational links
Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
// Red for the action link
Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/buy-hotpdf-fastspring.html');
Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue); // restore default
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
坐标陷阱
HotPDF 使用左下角原点、Y 向上的点坐标。A4 是 595 x 842 点,US Letter 是 612 x 792 点。若你把坐标按 A4 计算后直接用于窄页,Y=750 一类的值会跑到页边缘外面,或直接不可见。更重要的是:旋转、缩放、切换页面尺寸后如果不重算 X/Y,文本和注释可能脱节,点中区域在视觉上不对齐
这类问题最早在重试发布阶段出现,因为保存后的 PDF 表面上看起来“能打开”,只是点击落点和显示文本不一致。请按实际页面尺寸和缩放级别验证,而不是只在开发机 100% 缩放下过测
PrintHyperlink 的注释矩形和绘图共用同一坐标体系。稍后你如果旋转页面、缩放显示或改了页边界尺寸而没有同步更新 X/Y,文字位置和点击区会错位。可点区域仍可能触发正确 URL,但点中的不再是读者看到的同一处内容
标签文本与 URL 目标
Text 与 Link 不关联。你可以显示“下载发票 PDF”这种友好文案,而目标却是完整 HTTPS URL。反过来把原始长 URL 当文案会让可读性和可点击区域都变差
如果长 URL 自动换行,但 PrintHyperlink 按单行文本计算矩形,通常只有第一行可点击。应对办法是保持标签在一行,或改用后文的“逐行覆盖”方式
对于长地址,建议避免把完整 URL 作为标签文本。可以保留“下载 PDF 文档”这类短说明作为显示文案,再把真实地址作为目标参数传入
如果这份文档需要归档或离线流转,也应在正文里保留可见 URL 文本作为后备。打印成纸质件时,注释元数据本身不可见,读者至少能看到地址去手动打开
多行情况下的解决方案
当链接文案确实需要多行时,把它按显示行拆成多个 PrintHyperlink 调用,每行共用同一个 Link。这样每段都会有正确矩形,且全部行都可点击
procedure PrintWrappedHyperlink(Page: THPDFPage; X, TopY, LineStep: Single;
const Lines: array of AnsiString; const Link: AnsiString);
var
I: Integer;
begin
for I := 0 to High(Lines) do
Page.PrintHyperlink(X, TopY - I * LineStep, Lines[I], Link);
end;
// Usage: break the label at the positions where your layout wraps it
Pdf.CurrentPage.SetFont('Arial', [], 10);
PrintWrappedHyperlink(Pdf.CurrentPage, 50, 400, 14,
['https://www.loslab.com/en-us/pdf-library/',
'delphi-pdf-component.html'],
'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
拆分行时按真实可见宽度计算换行点。另一种等价方式是自己先用 TextOut 画好每行,再用 AddURILink 套在每行上,这在你已有排版输出时更直观
AddURILink:为已绘制内容加交互区域
PrintHyperlink 更适合“顺带画文本”,它会同步根据标签文本绘制区域。你已手动绘制过内容、要给图片、表格或段落已有文本添加点击范围时,通常改用 AddURILink
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
它只写注释,不会改文字或颜色。矩形坐标与其他绘图调用使用同一坐标体系,所以可以复用 TextOut 或图片绘制时的 X/Y。这样每个交互区域与现有内容天然对齐
函数返回值是 THPDFDictionaryObject,多数调用者可以忽略,但在需要时保留引用后可在入稿前继续补充字典项
PDF/A 会按标准写入注释打印标记;而 PDFUACompliance 模式下 Description 不能为空,它会作为注释的 /Contents 参与读屏提示。缺少描述会触发异常,不可静默通过
要输出 PDF/UA 时通常建议先用 TextOut 画出标签,再用 AddURILink 并传入可读描述。如果目标就是短纯文本,优先选择 PrintHyperlink,否则直接用 AddURILink 定义区域
AddGoToLink 与文内导航
AddGoToLink 用于文内跳转
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
TargetPageIndex 是 0 基准,首页是 0,和 CurrentPageNumber 一致。目标页必须先存在;索引越界时不抛异常也不写注释,直接返回。先生成所有目标页再回到目录页加链接通常更安全
YPos 在目标页上设置进入位置,负值表示保持当前纵向位置。非负值会把目标 Y 对齐到窗口顶部。方向和缩放不会改动。PDFUACompliance 下 Description 同样不能为空
procedure BuildLinkedTOC(const FileName: string);
const
Chapters: array[0..2] of string =
('Introduction', 'Installation', 'API Reference');
var
Pdf: THotPDF;
I, Y: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc; // page 0 becomes the TOC page
// Create the chapter pages first so the link targets exist
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // pages 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Switch back to page 0 and draw the TOC entries with their links
Pdf.CurrentPageNumber := 0;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Contents');
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := 720;
for I := 0 to High(Chapters) do
begin
Pdf.CurrentPage.TextOut(70, Y, 0, Chapters[I]);
Pdf.CurrentPage.AddGoToLink(
Rect(70, Y + 14, 300, Y - 3), // covers the entry with padding
I + 1, // zero-based: chapters are pages 1..3
780, // land with the heading at the top
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
每行链接常用比行文字稍宽的矩形,使整行可点。若目录前置了空白页,后续 TargetPageIndex 会整体偏移,建议用创建页序列时的索引直接派生,而不是硬编码
完整文档生成示例
下面给出一个更贴近生产的例子:一个含页首、正文和底部链接区的报表模板
procedure GenerateProductSheet(
const FileName, ProductName, ProductURL, SupportURL: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Compression := cmFlateDecode;
Pdf.BeginDoc;
// Header
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Body paragraph placeholder
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Footer links
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
同一组文字前先设置一次 SetFont,因为字体不会在 AddPage 间自动保留。漏掉这一步会导致新页的注释矩形按默认指标计算,和预期错位
不同阅读器上的差异
ISO 32000-1 §12.6.4.7 规定了 URI 注释,但不同阅读器执行细节会不同。Adobe Acrobat 对第一次打开非受信任域 URL 会有安全提示,而一些轻量客户端不显示。企业环境可能会直接禁用 URI 注释。移动端可能在应用内浏览,也可能跳到系统浏览器
这些不能在生成端兜底。可用文字副本把 URL 可见化,保留阅读器策略导致点击失效时的降级路径。视觉下划线多数是阅读器在显示层补画的;如需打印或转图像后仍显示下划线,可在文本下方额外用 LineTo 与 Stroke 绘制一条
本文涉及的链接 API 归属于 HotPDF Component(Delphi 与 C++Builder),含加载、编辑、加密与签名 API 的完整能力同样在该组件里