PDF-hyperlänkar är URI-anteckningar (annotations): en rektangel som täcker en del av sidans yta som, när den klickas på, säger åt visaren att öppna en URL. Anteckningen och texten under den är helt oberoende objekt. HotPDFs PrintHyperlink buntar ihop båda till ett enda anrop, ritar texten och beräknar anteckningsrektangeln från de renderade textvärdena. Den bekvämligheten döljer en detalj som är värd att förstå innan du skriver produktionskod. Det är heller inte hela historien: AddURILink placerar ett klickbart område över innehåll du ritat själv, och AddGoToLink hanterar intern navigering — båda behandlas nedan
Hur PrintHyperlink fungerar
PrintHyperlink lever på THPDFPage och tar fyra argument: X- och Y-koordinater (i punkter, nedre vänstra ursprung, Y ökar uppåt), etikettsträngen (label) som ska ritas, och URL-målet. Internt anropar den TextOut i den aktuella hyperlänkfärgen, och beräknar sedan omedelbart anteckningsrektangeln från TextWidth och TextHeight med de aktuella typsnittsvärdena. Detta innebär att typsnitt och storlek måste ställas in före anropet, och de får inte ändras mellan ritandet av etiketten och placeringen av anteckningen, eftersom båda löses i samma anrop
Standardfärgen är clBlue. SetRGBHyperlinkColor ändrar den för efterföljande anrop endast; den uppdaterar inte retroaktivt anteckningar som redan skrivits. Om du behöver olika färger för olika länkgrupper på samma sida, anropa SetRGBHyperlinkColor före varje grupp och återställ den efteråt
Här är ett minimalt dokument som skriver tre länkar med två olika färger:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Standard blå för informationslänkar
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');
// Röd för åtgärdslänken
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); // återställ standard
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Koordinatfällan
HotPDF använder ett ursprung nere till vänster med Y som växer uppåt, i punkter (1/72 tum). En A4-sida är 595 x 842 pt; en US Letter-sida är 612 x 792 pt. Y=750 sitter nära toppen av en A4-sida, och Y=50 skulle vara nära bottenmarginalen. Den som kommer från skärmgrafik eller HTML antar motsatsen och placerar den första länkraden rakt utanför det synliga området
Anteckningsrektangeln som PrintHyperlink beräknar använder samma koordinatsystem. Om du senare roterar sidan, skalar den, eller ändrar sidstorleken utan att omberäkna dina X/Y-värden, kommer den synliga texten och den klickbara rektangeln att glida isär. Länken "fungerar" i den meningen att klicka någonstans nära texten utlöser URL:en, men hot zone:n matchar inte längre vad läsaren ser. Testa på den faktiska sidstorleken och zoomnivån du levererar, inte bara på utvecklingsmaskinen vid 100 %
Ett fall där driften är garanterad: om du anropar PrintHyperlink med koordinater lämpliga för en A4-sida och sedan byter till en anpassad smalformats-sida utan att justera X/Y-värdena, kan anteckningen hamna utanför sidan helt och hållet. Anteckningsobjektet skrivs fortfarande in i PDF:en; de flesta visare klipper (clip) det ljudlöst, så länken bara försvinner utan något fel
Etikett-text gentemot URL-mål
Text- och Link-argumenten är oberoende. Du kan rita "Download invoice PDF" medan målet är en fullständigt kvalificerad HTTPS-URL med frågeparametrar. Den separationen är avsiktlig; den synliga etiketten bör vara läsbar för människor och URL:en kan vara lång eller genereras dynamiskt
Vad som skapar problem är när etiketten är själva råa URL:en, särskilt en lång sådan. Om URL:en bryts visuellt över två rader men anteckningsrektangeln beräknades för en enkelrads-sträng, är endast den första raden klickbar. PrintHyperlink hanterar inte flerrads-flöde; håll etiketten tillräckligt kort för att passa på en rad vid den aktuella typsnittsstorleken och sidbredden, använd en kort beskrivande etikett med hela URL:en som mål, eller tillämpa arbetslösningen per rad som visas i nästa avsnitt
För dokument som kommer att arkiveras eller distribueras utan en aktiv internetanslutning, överväg också om själva URL:en bör visas i tryckt form någonstans i dokumentkroppen, inte bara som anteckningsmetadata. En läsare som skriver ut PDF:en på papper får ingenting från en URI-anteckning
Lösning kring flerradsbegränsningen
När en länketikett verkligen måste sträcka sig över mer än en rad — en lång URL utskriven bokstavligt, eller en inslagen mening som bör vara klickbar från början till slut — är fixen att sluta behandla den som en länk och behandla den som en länk per rad. Varje PrintHyperlink-anrop beräknar sin rektangel från den text den ritar, så flera anrop som delar samma Link-mål producerar flera korrekt storleksanpassade anteckningar som alla öppnar samma URL. Läsaren kan inte se skillnaden; varje rad svarar på ett klick
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;
// Användning: bryt etiketten vid de positioner där din layout omsluter den
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');
Att dela strängen är ditt ansvar: bryt den vid de positioner där den visuellt skulle brytas vid det aktuella typsnittet och kolumnbredden, genom att använda TextWidth för att testa varje kandidatrad. Alternativet är att rita den omslutna texten själv med vanliga TextOut-anrop och sedan lägga en AddURILink-rektangel över varje rad — den bättre vägen när texten redan produceras av din egen ord-ombrytningslogik (word-wrap), vilket för oss till den funktionen
AddURILink: klickbara områden över allt du ritat
PrintHyperlink är ett bekvämlighetsomslag (wrapper): det ritar sin egen etikett och härleder rektangeln från den etikettens värden. AddURILink är den lägre-nivå-halvan som exponeras direkt:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Den skriver bara anteckningen — ingen text ritas och inga färger ändras. Rectangle (Rektangeln) tolkas i samma koordinatrymd som dina ritningsanrop, så du kan återanvända de exakta X/Y-värdena du skickade till TextOut eller ett bildanrop. Det gör det till rätt verktyg när det synliga innehållet redan existerar: en bild-hotspot, en tabellcell, ett block med text ritat tidigare, eller en rad i ett omslutet stycke som i arbetslösningen ovan. Anteckningen bär en kantlinje med noll bredd, så inget synligt ändras; det klickbara området är exakt den rektangel du anger
Funktionen returnerar antecknings-dictionaryn som ett THPDFDictionaryObject. De flesta uppringare kasserar resultatet, men om du behåller det låter det dig justera anteckningens poster innan dokumentet skrivs
Två efterlevnadsdetaljer (compliance) är inbyggda. I PDF/A-lägen är anteckningens print-flagga satt som dessa standarder kräver. Under PDFUACompliance måste Description-parametern vara en icke-tom sträng — den blir anteckningens /Contents-post, vilket är vad hjälpmedel (assistive technology) tillkännager för länken — och anropet kastar ett undantag (exception) snarare än att tyst ge ut en icke-överensstämmande fil. PrintHyperlink föregår den regeln och bifogar ingen beskrivning, så för PDF/UA-utdata bör du rita etiketten med TextOut och placera anteckningen med AddURILink plus en meningsfull beskrivning
Beslutsregeln är enkel: använd PrintHyperlink när länken är en kort bit text du inte har ritat ännu; använd AddURILink när den klickbara regionen definieras av innehåll du ritar eller mäter själv
Intern navigering med AddGoToLink
Externa URL:er är bara hälften av vad länk-anteckningar gör. Den andra hälften är navigering inuti dokumentet — en innehållsförteckning som hoppar till kapitel, korsreferenser mellan avsnitt. HotPDF exponerar detta genom AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Tre semantiker är värda att uttrycka exakt, eftersom ingen går att gissa från signaturen. TargetPageIndex är noll-baserad: den första sidan i dokumentet är sida 0, vilket matchar CurrentPageNumber. Målsidan måste redan existera när du gör anropet; om indexet är utanför intervallet, returnerar proceduren utan att lägga till en anteckning — inget undantag, ingen länk, ingen varning. För en innehållsförteckning som pekar framåt, skapa alla sidor först, växla sedan tillbaka och lägg till länkarna
YPos väljer den vertikala positionen på målsidan, i samma koordinatrymd som dina ritningsanrop. Standardvärdet -1 (vilket som helst negativt värde) skriver en nollmålskoordinat (null), och säger åt visaren att behålla sin nuvarande vertikala position när den landar på målsidan. Skicka ett icke-negativt värde och visaren rullar så att den positionen sitter längst upp i fönstret — använd Y-koordinaten för rubriken du länkar till. Zoom lämnas alltid oförändrad. Som med AddURILink måste Description vara icke-tom under PDFUACompliance och blir länkens alternativa text (alternate text)
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; // sida 0 blir TOC-sidan
// Skapa kapitelsidorna först så att länkmålen existerar
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // sidor 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Växla tillbaka till sida 0 och rita TOC-posterna med deras länkar
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), // täcker posten med utfyllnad (padding)
I + 1, // noll-baserad: kapitel är sidor 1..3
780, // landa med rubriken längst upp
AnsiString('Gå till ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Varje post får en rektangel bredare än texten så att hela raden svarar på pekaren, och varje länk landar med kapitelrubriken (ritad vid Y=780) längst upp i fönstret. Om du senare infogar en sida före kapitlen, förskjuts varje TargetPageIndex med ett; beräkna index från din sid-skapande loop snarare än att hårdkoda dem
Ett komplett dokument-genererande exempel
Mönstret nedan visar ett mer realistiskt scenario: att generera en kort rapport med en rubriksektion, brödtext, och en sidfots-rad (footer) av länkar, allt från kod snarare än från ett formulär med TEdit-fält:
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;
// Sidhuvud
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Platshållare för brödtext
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'Se länkarna nedan för fullständig dokumentation.');
// Sidfotslänkar
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 80, 0, 'Länkar:');
Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Notera att SetFont anropas före varje grupp av textanrop. Typsnittet kvarstår inte över AddPage, och om du glömmer att ställa in det före PrintHyperlink på en ny sida, kommer anteckningsrektangeln att beräknas mot vad sidans standardvärden är, vilket kan skilja sig från vad du förväntar dig
Var anteckningshantering varierar mellan visare
PDF URI-anteckningar är definierade i ISO 32000-1 §12.6.4.7, och varje överensstämmande visare bör följa dem. I praktiken skiljer sig några beteenden beroende på visare. Adobe Acrobat visar en säkerhetsprompt vid det första klicket för URL:er som inte finns i listan över betrodda domäner; många webbläsare och lätta läsare gör det inte. Vissa företags-PDF-visare i nedlåsta miljöer inaktiverar URI-anteckningar helt enligt policy, så ett klick gör ingenting, utan något synligt fel. Mobila PDF-appar varierar i om de öppnar länkar inuti appens webbvy eller lämnar över till systemets webbläsare
Inget av detta är fel du kan fixa från genereringssidan; de är visares policybeslut. Vad du kan göra är att skriva länketiketter som gör URL:en synlig i dokumentkroppen också, så en läsare i en begränsad miljö fortfarande kan kopiera adressen manuellt. Anteckningen är bekvämligheten; texten är en reserv (fallback)
En ytterligare detalj värd att veta: PDF URI-anteckningar bär inte någon visuell understrykning som standard. Understrykningen du ser i de flesta visare ritas av visaren själv baserat på anteckningstypen, inte av en glyf i innehållsströmmen. Om du behöver en fysisk understrykning som överlever utskrift till en icke-interaktiv renderare eller PDF-till-bildkonvertering, rita den uttryckligen med LineTo och Stroke vid den lämpliga Y-offseten nedanför textens baslinje. Det är en separat ritningsoperation, inte något PrintHyperlink hanterar åt dig
Hyperlänks-API:et som visas här är en del av HotPDF Component för Delphi och C++Builder