PDF hyperlinks zijn URI-annotaties: een rechthoek die een bepaald paginagebied bedekt en, wanneer erop wordt geklikt, de viewer vertelt om een URL te openen. De annotatie en de tekst eronder zijn volledig onafhankelijke objecten. De PrintHyperlink van HotPDF bundelt beide in één aanroep, door de tekst te tekenen en de annotatierechthoek te berekenen uit de gerenderde tekststatistieken. Dat gemak verbergt een detail dat de moeite waard is om te begrijpen voordat u productiecode schrijft. Het is ook niet het hele verhaal: AddURILink plaatst een klikbaar gebied over inhoud die u zelf heeft getekend, en AddGoToLink handelt interne navigatie af — beide worden hieronder behandeld
Hoe PrintHyperlink werkt
PrintHyperlink bevindt zich op THPDFPage en neemt vier argumenten: X- en Y-coördinaten (in punten, oorsprong linksonder, Y neemt naar boven toe toe), de te tekenen labeltekenreeks (label string) en het URL-doel. Intern roept het TextOut aan in de huidige hyperlinkkleur en berekent het vervolgens onmiddellijk de annotatierechthoek uit TextWidth en TextHeight met de huidige lettertypestatistieken. Dit betekent dat het lettertype en de grootte vóór de aanroep moeten worden ingesteld, en deze mogen niet veranderen tussen het tekenen van het label en het plaatsen van de annotatie, omdat beide in dezelfde aanroep worden opgelost
De standaardkleur is clBlue. SetRGBHyperlinkColor wijzigt dit alleen voor volgende aanroepen; het werkt geen annotaties met terugwerkende kracht bij die al zijn geschreven. Als u verschillende kleuren nodig hebt voor verschillende linkgroepen op dezelfde pagina, roep dan SetRGBHyperlinkColor aan vóór elke groep en stel deze daarna opnieuw in
Hier is een minimaal document dat drie links met twee verschillende kleuren schrijft:
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;
De coördinatenvalkuil
HotPDF gebruikt een oorsprong linksonder waarbij Y naar boven groeit, in punten (1/72 inch). Een A4-pagina is 595 x 842 pt; een US Letter-pagina is 612 x 792 pt. Y=750 bevindt zich bovenaan een A4-pagina en Y=50 zou in de buurt van de ondermarge liggen. Iedereen die uit beeldschermgrafische weergaven of HTML komt, gaat uit van het tegenovergestelde en plaatst de eerste linklijn rechtstreeks buiten het zichtbare gebied
De annotatierechthoek die door PrintHyperlink wordt berekend, gebruikt hetzelfde coördinatensysteem. Als u de pagina later roteert, schaalt of de paginagrootte wijzigt zonder uw X/Y-waarden opnieuw te berekenen, zullen de zichtbare tekst en de klikbare rechthoek uit elkaar drijven. De link "werkt" in de zin dat klikken ergens in de buurt van de tekst de URL activeert, maar de actieve zone (hot zone) komt niet meer overeen met wat de lezer ziet. Test op de daadwerkelijke paginagrootte en het zoomniveau dat u distribueert, niet alleen op de ontwikkelmachine op 100%
Eén geval waarbij het afdrijven gegarandeerd is: als u PrintHyperlink aanroept met coördinaten die geschikt zijn voor een A4-pagina en vervolgens overschakelt naar een aangepaste pagina in smal formaat zonder de X/Y-waarden aan te passen, kan de annotatie volledig buiten de pagina terechtkomen. Het annotatieobject wordt nog steeds in de PDF geschreven; de meeste viewers knippen het stilzwijgend bij, zodat de link eenvoudigweg verdwijnt zonder enige foutmelding
Labeltekst versus URL-doel
De argumenten Text en Link zijn onafhankelijk. U kunt "Download factuur PDF" tekenen terwijl het doel een volledig gekwalificeerde HTTPS-URL met queryparameters is. Die scheiding is opzettelijk; het zichtbare label moet menselijk leesbaar zijn en de URL kan lang zijn of dynamisch worden gegenereerd
Wat problemen veroorzaakt, is wanneer het label de onbewerkte URL zelf is, vooral een lange. Als de URL visueel over twee regels loopt, maar de annotatierechthoek is berekend voor een reeks van één regel, is alleen de eerste regel klikbaar. PrintHyperlink gaat niet om met multi-line flow; houd het label kort genoeg zodat het op één regel past bij de huidige lettergrootte en paginabreedte, gebruik een kort beschrijvend label met de volledige URL als doel, of pas de per-regel tijdelijke oplossing (workaround) toe die in de volgende sectie wordt getoond
Voor documenten die worden gearchiveerd of gedistribueerd zonder actieve internetverbinding, kunt u ook overwegen of de URL zelf ergens in de tekst van het document moet verschijnen in gedrukte vorm, en niet alleen als metadata van een annotatie. Een lezer die de PDF op papier afdrukt, heeft niets aan een URI-annotatie
De beperking voor meerdere regels omzeilen
Wanneer een linklabel daadwerkelijk meer dan één regel moet beslaan — een lange URL letterlijk afgedrukt, of een gewikkelde zin (wrapped sentence) die van begin tot eind klikbaar zou moeten zijn — is de oplossing om te stoppen met het te behandelen als één link en het te behandelen als één link per regel. Elke PrintHyperlink-aanroep berekent zijn rechthoek op basis van de tekst die wordt getekend, dus verschillende aanroepen die hetzelfde Link-doel delen, produceren meerdere correct gedimensioneerde annotaties die allemaal dezelfde URL openen. De lezer merkt het verschil niet; elke regel reageert op een 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');
Het splitsen van de tekenreeks is uw verantwoordelijkheid: breek deze af op dezelfde posities waar deze visueel zou omwikkelen met het huidige lettertype en kolombreedte, met behulp van TextWidth om elke kandidaatregel te testen. Het alternatief is om de gewikkelde tekst zelf te tekenen met gewone TextOut-aanroepen en vervolgens één AddURILink-rechthoek over elke regel te leggen — de betere route wanneer de tekst al wordt geproduceerd door uw eigen logica voor woordterugloop (word-wrap logic), wat ons bij die functie brengt
AddURILink: klikbare gebieden over alles wat u hebt getekend
PrintHyperlink is een gemaksomhulsel (convenience wrapper): het tekent zijn eigen label en leidt de rechthoek af uit de statistieken van dat label. AddURILink is de helft op een lager niveau die rechtstreeks wordt blootgesteld:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Het schrijft alleen de annotatie — er wordt geen tekst getekend en er veranderen geen kleuren. De Rectangle wordt geïnterpreteerd in dezelfde coördinatenruimte als uw tekenaanroepen, dus u kunt exact dezelfde X/Y-waarden hergebruiken die u hebt doorgegeven aan TextOut of een aanroep voor een afbeelding. Dat maakt het het juiste hulpmiddel wanneer de zichtbare inhoud al bestaat: een hotspot op een afbeelding, een tabelcel, een eerder getekend tekstblok of één regel van een omwikkelde paragraaf, zoals in de bovenstaande oplossing. De annotatie heeft een rand van nul breedte, dus er verandert niets zichtbaars; het klikbare gebied is exact de rechthoek die u opgeeft
De functie retourneert de annotatiewoordenboek (annotation dictionary) als een THPDFDictionaryObject. De meeste aanroepers negeren het resultaat, maar door het te bewaren kunt u de vermeldingen van de annotatie aanpassen voordat het document wordt geschreven
Er zijn twee conformiteitsdetails ingebouwd. In PDF/A-modi wordt de afdrukvlag van de annotatie ingesteld zoals die standaarden vereisen. Onder PDFUACompliance moet de Description-parameter een niet-lege tekenreeks zijn — het wordt de /Contents-invoer van de annotatie, wat is wat ondersteunende technologie (assistive technology) aankondigt voor de link — en de aanroep werpt een uitzondering (exception) op in plaats van stilzwijgend een niet-conform bestand uit te zenden. PrintHyperlink stamt van vóór die regel en voegt geen beschrijving toe, dus voor PDF/UA-uitvoer tekent u het label met TextOut en plaatst u de annotatie met AddURILink plus een betekenisvolle beschrijving
De beslissingsregel is eenvoudig: gebruik PrintHyperlink wanneer de link een kort stukje tekst is dat u nog niet hebt getekend; gebruik AddURILink wanneer het klikbare gebied wordt gedefinieerd door inhoud die u zelf tekent of meet
Interne navigatie met AddGoToLink
Externe URL's zijn slechts de helft van wat link-annotaties doen. De andere helft is navigatie binnen het document — een inhoudsopgave die naar hoofdstukken springt, kruisverwijzingen tussen secties. HotPDF stelt dit bloot via AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Drie betekenissen zijn de moeite waard om nauwkeurig te formuleren, aangezien geen ervan te raden is uit de signatuur. TargetPageIndex is op nul gebaseerd (zero-based): de eerste pagina van het document is pagina 0, overeenkomend met CurrentPageNumber. De doelpagina moet al bestaan wanneer u de aanroep doet; als de index buiten bereik is, keert de procedure terug zonder een annotatie toe te voegen — geen uitzondering, geen link, geen waarschuwing. Voor een inhoudsopgave die vooruit wijst, maakt u eerst alle pagina's aan, schakelt u dan terug en voegt u de links toe
YPos selecteert de verticale positie op de doelpagina, in dezelfde coördinatenruimte als uw tekenaanroepen. De standaardwaarde van -1 (elke negatieve waarde) schrijft een nul-bestemmingscoördinaat (null destination coordinate), wat de viewer vertelt zijn huidige verticale positie te behouden wanneer deze op de doelpagina landt. Geef een niet-negatieve waarde door en de viewer scrolt zodanig dat die positie zich bovenaan het venster bevindt — gebruik de Y-coördinaat van de koptekst waarnaar u linkt. Zoom blijft altijd ongewijzigd. Net als bij AddURILink moet Description niet-leeg zijn onder PDFUACompliance en wordt het de alternatieve tekst van de link
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;
Elke invoer krijgt een rechthoek die breder is dan de tekst, zodat de hele rij reageert op de aanwijzer, en elke link landt met de hoofdstuk-koptekst (getekend op Y=780) bovenaan het venster. Als u later een pagina vóór de hoofdstukken invoegt, verschuift elke TargetPageIndex met één; bereken indexen uit uw pagina-creatie lus in plaats van ze hard te coderen
Een compleet voorbeeld van documentgeneratie
Het onderstaande patroon toont een realistischer scenario: het genereren van een kort rapport met een koptekstsectie (header section), hoofdtekst en een voettekstrij (footer row) met links, dit alles vanuit code in plaats van uit een formulier met TEdit-velden:
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;
Merk op dat SetFont wordt aangeroepen vóór elke groep van tekstaanroepen. Het lettertype blijft niet behouden tijdens AddPage, en als u vergeet het in te stellen vóór PrintHyperlink op een nieuwe pagina, wordt de annotatierechthoek berekend op basis van de standaardstatistieken van de pagina, wat kan afwijken van wat u verwacht
Waar de omgang met annotaties per viewer verschilt
PDF URI-annotaties zijn gedefinieerd in ISO 32000-1 §12.6.4.7, en elke conforme viewer zou ze moeten volgen. In de praktijk verschillen enkele gedragingen per viewer. Adobe Acrobat toont een beveiligingsprompt bij de eerste klik voor URL's die niet in de lijst met vertrouwde domeinen staan; veel browsers en lichtgewicht lezers doen dat niet. Sommige zakelijke PDF-viewers in afgeschermde omgevingen (locked-down environments) schakelen URI-annotaties in hun geheel uit op basis van beleid, dus een klik doet niets, zonder zichtbare foutmelding. Mobiele PDF-apps variëren in de mate waarin ze links openen binnen de webweergave (web view) van de app of deze overdragen aan de systeembrowser
Geen van deze zijn bugs die u vanaf de generatiekant kunt oplossen; het zijn beleidsbeslissingen van de viewer. Wat u wel kunt doen, is linklabels schrijven die de URL ook zichtbaar maken in de tekst van het document, zodat een lezer in een beperkte omgeving het adres nog steeds handmatig kan kopiëren. De annotatie is het gemak; de tekst is de terugvaloptie (fallback)
Nog een detail dat de moeite waard is om te weten: PDF URI-annotaties dragen standaard geen visuele onderstreping (underline). De onderstreping die u in de meeste viewers ziet, wordt door de viewer zelf getekend op basis van het type annotatie, niet door een glyph (teken) in de content stream. Als u een fysieke onderstreping nodig hebt die afdrukken naar een niet-interactieve renderer of PDF-naar-afbeelding-conversie overleeft, teken deze dan expliciet met LineTo en Stroke op de juiste Y-offset (Y offset) onder de tekstbasislijn. Dat is een afzonderlijke tekenbewerking, niet iets dat PrintHyperlink voor u afhandelt
De hyperlink-API die hier wordt getoond, maakt deel uit van de HotPDF-component voor Delphi en C++Builder