PDF-hyperlinks er URI-annoteringer: et rektangel, der dækker et sideområde, og som ved klik fortæller fremviseren, at den skal åbne en URL. Annoteringen og teksten under den er fuldstændigt uafhængige objekter. HotPDF's PrintHyperlink samler begge i ét kald, tegner teksten og beregner annoteringsrektanglet ud fra de renderede tekstmålinger (text metrics). Den bekvemmelighed skjuler en detalje, der er værd at forstå, før du skriver produktionskode. Det er heller ikke hele historien: AddURILink placerer et klikbart område over indhold, du selv har tegnet, og AddGoToLink håndterer intern navigation — begge dækkes nedenfor
Sådan fungerer PrintHyperlink
PrintHyperlink lever på THPDFPage og tager fire argumenter: X- og Y-koordinater (i punkter, nederst til venstre som origo, Y stiger opad), etiketstrengen (label string) der skal tegnes, og URL-målet. Internt kalder det TextOut i den aktuelle hyperlink-farve, hvorefter det straks beregner annoteringsrektanglet ud fra TextWidth og TextHeight ved de aktuelle skrifttypemålinger (font metrics). Det betyder, at skrifttype og størrelse skal indstilles før kaldet, og de må ikke ændre sig mellem tegning af etiketten og placering af annoteringen, fordi begge løses i det samme kald
Standardfarven er clBlue. SetRGBHyperlinkColor ændrer det kun for efterfølgende kald; det opdaterer ikke med tilbagevirkende kraft annoteringer, der allerede er skrevet. Hvis du har brug for forskellige farver til forskellige linkgrupper på den samme side, skal du kalde SetRGBHyperlinkColor før hver gruppe og nulstille den bagefter
Her er et minimalt dokument, der skriver tre links med to forskellige farver:
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;
Koordinatfælden (The coordinate trap)
HotPDF bruger en origo nederst til venstre med Y voksende opad, i punkter (1/72 tomme). En A4-side er 595 x 842 pt; en US Letter-side er 612 x 792 pt. Y=750 sidder nær toppen af en A4-side, og Y=50 ville være nær den nederste margen. Enhver, der kommer fra skærmgrafik eller HTML, antager det modsatte og placerer den første linklinje direkte uden for det synlige område
Annoteringsrektanglet, som PrintHyperlink beregner, bruger det samme koordinatsystem. Hvis du senere roterer siden, skalerer den eller ændrer sidestørrelsen uden at genberegne dine X/Y-værdier, vil den synlige tekst og det klikbare rektangel drive fra hinanden. Linket "virker" i den forstand, at klik et sted i nærheden af teksten udløser URL'en, men den varme zone (hot zone) stemmer ikke længere overens med det, læseren ser. Test på den faktiske sidestørrelse og det zoomniveau, du leverer, ikke kun på udviklingsmaskinen ved 100 %
Ét tilfælde, hvor driften er garanteret: Hvis du kalder PrintHyperlink med koordinater, der passer til en A4-side, og derefter skifter til en brugerdefineret smalformat-side uden at justere X/Y-værdierne, kan annoteringen ende med at være helt væk fra siden. Annoteringsobjektet skrives stadig ind i PDF'en; de fleste fremvisere (viewers) klipper det i stilhed, så linket forsvinder simpelthen uden nogen fejl
Etikettekst (Label text) over for URL-mål
Text- og Link-argumenterne er uafhængige. Du kan tegne "Download invoice PDF", mens målet er en fuldt kvalificeret HTTPS-URL med forespørgselsparametre (query parameters). Den adskillelse er bevidst; den synlige etiket bør være læsbar for mennesker, og URL'en kan være lang eller genereret dynamisk
Det, der skaber problemer, er, når etiketten er selve den rå URL, især en lang en. Hvis URL'en ombrydes (wraps) visuelt over to linjer, men annoteringsrektanglet blev beregnet til en enkeltlinje-streng, er kun den første linje klikbar. PrintHyperlink håndterer ikke multi-linje flow; hold etiketten kort nok til at passe på én linje ved den aktuelle skrifttypestørrelse og sidebredde, brug en kort beskrivende etiket med den fulde URL som målet, eller anvend pr.-linje-løsningen vist i næste sektion
For dokumenter, der vil blive arkiveret eller distribueret uden en aktiv internetforbindelse, bør du også overveje, om selve URL'en skal fremstå i trykt form et sted i dokumentets brødtekst, ikke kun som annoterings-metadata. En læser, der udskriver PDF'en på papir, får intet ud af en URI-annotering
Løsning af multi-linje begrænsningen
Når en link-etiket virkelig er nødt til at spænde over mere end én linje — en lang URL trykt ordret (verbatim), eller en ombrudt sætning, der skal være klikbar fra ende til anden — er løsningen at holde op med at behandle det som ét link og behandle det som ét link pr. linje. Hvert PrintHyperlink-kald beregner dets rektangel fra den tekst, det tegner, så adskillige kald, der deler det samme Link-mål, producerer flere korrekt dimensionerede annoteringer, der alle åbner den samme URL. Læseren kan ikke kende forskel; hver linje reagerer på et klik
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');
At dele strengen er dit ansvar: bryd den ved de samme positioner, hvor den visuelt ville ombrydes ved den aktuelle skrifttype og kolonnebredde, brug TextWidth til at teste hver kandidatlinje. Alternativet er selv at tegne den ombrudte tekst med almindelige TextOut-kald og derefter lægge ét AddURILink-rektangel over hver linje — den bedre rute, når teksten allerede er produceret af din egen ordombrydnings-logik (word-wrap logic), hvilket bringer os til den funktion
AddURILink: klikbare områder over alt, hvad du har tegnet
PrintHyperlink er en bekvemmeligheds-wrapper (convenience wrapper): den tegner sin egen etiket og udleder rektanglet fra den etikets målinger (metrics). AddURILink er lavniveau-halvdelen, der eksponeres direkte:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Den skriver kun annoteringen — ingen tekst tegnes, og ingen farve ændres. Rectangle fortolkes i det samme koordinatrum som dine tegnekald, så du kan genbruge de nøjagtige X/Y-værdier, du videregav til TextOut eller et billedkald. Det gør det til det rigtige værktøj, når som helst det synlige indhold allerede eksisterer: et billed-hotspot (image hotspot), en tabelcelle, en tekstblok tegnet tidligere, eller én linje af et ombrudt afsnit som i løsningen ovenfor. Annoteringen bærer en nuls-bredde ramme (zero-width border), så intet synligt ændres; det klikbare område er præcis det rektangel, du angiver
Funktionen returnerer annoteringsordbogen som et THPDFDictionaryObject. De fleste kaldere (callers) kasserer resultatet, men hvis du beholder det, kan du justere annoteringens poster (entries), før dokumentet skrives
To overholdelsesdetaljer (compliance details) er indbygget. I PDF/A-tilstande indstilles annoteringens udskrivningsflag (print flag), som disse standarder kræver. Under PDFUACompliance skal Description-parameteren være en ikke-tom streng — den bliver til annoteringens /Contents-post, hvilket er det, hjælpeteknologi (assistive technology) annoncerer for linket — og kaldet kaster en undtagelse (exception) i stedet for i stilhed at udsende en ikke-overholdende fil. PrintHyperlink er ældre end den regel og vedhæfter ingen beskrivelse, så for PDF/UA-output tegnes etiketten med TextOut, og annoteringen placeres med AddURILink plus en meningsfuld beskrivelse
Beslutningsreglen er enkel: brug PrintHyperlink, når linket er et kort stykke tekst, du endnu ikke har tegnet; brug AddURILink, når det klikbare område er defineret af indhold, du selv tegner eller måler
Intern navigation med AddGoToLink
Eksterne URL'er er kun halvdelen af, hvad link-annoteringer gør. Den anden halvdel er navigation inde i dokumentet — en indholdsfortegnelse (TOC), der springer til kapitler, krydsreferencer (cross-references) mellem sektioner. HotPDF afslører dette gennem AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Tre semantikker er værd at angive præcist, da ingen kan gættes ud fra signaturen. TargetPageIndex er nul-baseret: den første side i dokumentet er side 0, svarende til CurrentPageNumber. Målsiden skal allerede eksistere, når du foretager kaldet; hvis indekset er uden for rækkevidde, returnerer proceduren uden at tilføje en annotering — ingen undtagelse (exception), intet link, ingen advarsel. For en indholdsfortegnelse, der peger fremad, skal du oprette alle siderne først, derefter skifte tilbage og tilføje linkene
YPos vælger den vertikale position på målsiden i det samme koordinatrum som dine tegnekald. Standarden på -1 (enhver negativ værdi) skriver en null-destinationskoordinat, der fortæller fremviseren, at den skal beholde sin nuværende vertikale position, når den lander på målsiden. Videregiv en ikke-negativ værdi, og fremviseren scroller, så den position sidder øverst i vinduet — brug Y-koordinaten for den overskrift, du linker til. Zoom efterlades altid uændret. Som med AddURILink skal Description være ikke-tom under PDFUACompliance og bliver linkets alternative tekst
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;
Hver post (entry) får et rektangel, der er bredere end teksten, så hele rækken reagerer på markøren, og hvert link lander med kapitel-overskriften (tegnet ved Y=780) øverst i vinduet. Hvis du senere indsætter en side før kapitlerne, forskydes hvert TargetPageIndex med én; beregn indekser fra din sideoprettelsesløkke (page-creation loop) i stedet for at hardkode dem
Et komplet dokument-genererings eksempel
Mønsteret nedenfor viser et mere realistisk scenarie: generering af en kort rapport med en overskriftssektion (header section), brødtekst (body text) og en fodnoterække (footer row) med links, alt fra kode i stedet for fra en formular med TEdit-felter:
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;
Bemærk, at SetFont kaldes før hver gruppe af tekstkald. Skrifttypen fastholdes ikke på tværs af AddPage, og hvis du glemmer at indstille den før PrintHyperlink på en ny side, vil annoteringsrektanglet blive beregnet op mod, hvad sidens standardmålinger (default metrics) end er, hvilket kan afvige fra det, du forventer
Hvor annoteringshåndtering varierer på tværs af fremvisere (viewers)
PDF URI-annoteringer er defineret i ISO 32000-1 §12.6.4.7, og enhver overholdende (conforming) fremviser bør følge dem. I praksis er der et par adfærdsmønstre, der adskiller sig fra fremviser til fremviser. Adobe Acrobat viser en sikkerhedsprompt ved første klik for URL'er, der ikke er på listen over betroede domæner; mange browsere og letvægtslæsere gør ikke. Nogle virksomheds-PDF-fremvisere i aflåste (locked-down) miljøer deaktiverer URI-annoteringer fuldstændigt af politik, så et klik gør ingenting, uden synlig fejl. Mobile PDF-apps varierer i, om de åbner links inde i appens webvisning (web view) eller overdrager det til systembrowseren
Ingen af disse er fejl, du kan rette fra genereringssiden (generation side); det er fremviser-politikbeslutninger. Det, du kan gøre, er at skrive linketiketter, der gør URL'en synlig i selve dokumentets brødtekst, så en læser i et begrænset miljø stadig kan kopiere adressen manuelt. Annoteringen er bekvemmeligheden; teksten er fallbacken
Endnu en detalje, der er værd at kende: PDF URI-annoteringer bærer som standard ingen visuel understregning. Den understregning, du ser i de fleste fremvisere, tegnes af fremviseren selv baseret på annoteringstypen, ikke af en glyf i indholdsstrømmen. Hvis du har brug for en fysisk understregning, der overlever udskrivning til en ikke-interaktiv renderer eller PDF-til-billede-konvertering, skal du tegne den eksplicit med LineTo og Stroke ved den passende Y-forskydning (Y offset) under tekstens grundlinje (text baseline). Det er en separat tegneoperation, ikke noget PrintHyperlink håndterer for dig
Hyperlink-API'et, der vises her, er en del af HotPDF-komponenten til Delphi og C++Builder