Hypertextové odkazy PDF jsou anotace URI: obdélník pokrývající určitou oblast stránky, který po kliknutí řekne prohlížeči, aby otevřel URL. Anotace a text pod ní jsou zcela nezávislé objekty. Funkce PrintHyperlink z HotPDF je spojuje do jednoho volání, nakreslí text a vypočítá obdélník anotace na základě metriky vykresleného textu. Toto pohodlí však skrývá detail, kterému byste měli rozumět před psaním produkčního kódu. A to není celý příběh: AddURILink umístí klikatelnou oblast přes obsah, který jste sami nakreslili, a AddGoToLink zajišťuje interní navigaci – obojí je rozebráno níže
Jak PrintHyperlink funguje
Funkce PrintHyperlink se nachází na objektu THPDFPage a přijímá čtyři argumenty: souřadnice X a Y (v bodech, počátek je vlevo dole, Y roste nahoru), textový řetězec štítku, který se má vykreslit, a cílové URL. Interně volá TextOut v aktuální barvě odkazu a poté okamžitě vypočítá obdélník anotace z TextWidth a TextHeight při aktuálních metrikách písma. To znamená, že písmo a velikost musí být nastaveny před voláním a nesmí se měnit mezi kreslením štítku a umístěním anotace, protože obojí se řeší ve stejném volání
Výchozí barva je clBlue. SetRGBHyperlinkColor ji změní pouze pro následná volání; neupravuje zpětně již zapsané anotace. Pokud potřebujete různé barvy pro různé skupiny odkazů na stejné stránce, volejte SetRGBHyperlinkColor před každou skupinou a poté ji obnovte
Zde je minimální dokument, který zapisuje tři odkazy ve dvou různých barvách:
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;
Past se souřadnicemi
HotPDF používá souřadnicový systém s počátkem vlevo dole, kde Y roste nahoru, v bodech (1/72 palce). Stránka A4 má 595 x 842 pt; stránka US Letter má 612 x 792 pt. Y=750 se nachází blízko horního okraje stránky A4 a Y=50 by bylo blízko spodního okraje. Každý, kdo přichází z grafiky na obrazovce nebo z HTML, předpokládá opak a umístí první řádek odkazu přímo mimo viditelnou oblast
Obdélník anotace, který PrintHyperlink vypočítá, používá stejný souřadnicový systém. Pokud později stránku otočíte, změníte její měřítko nebo upravíte její velikost, aniž byste přepočítali hodnoty X/Y, viditelný text a klikatelný obdélník se rozejdou. Odkaz „funguje“ v tom smyslu, že kliknutí někam do blízkosti textu spustí URL, ale aktivní zóna už neodpovídá tomu, co čtenář vidí. Testujte na skutečné velikosti stránky a při úrovni přiblížení, které dodáváte, nejen na vývojovém počítači při 100 %
Jeden případ, kdy je odchylka zaručena: pokud zavoláte PrintHyperlink se souřadnicemi vhodnými pro stránku A4 a poté přejdete na vlastní úzký formát stránky, aniž byste upravili hodnoty X/Y, anotace se může ocitnout zcela mimo stránku. Objekt anotace je stále zapsán do PDF; většina prohlížečů jej potichu ořízne, takže odkaz jednoduše zmizí bez jakékoliv chyby
Text štítku versus cílové URL
Argumenty Text a Link jsou nezávislé. Můžete vykreslit „Stáhnout fakturu PDF“, přičemž cílem je plně kvalifikované URL HTTPS s parametry dotazu. Toto oddělení je záměrné; viditelný štítek by měl být čitelný pro člověka a URL může být dlouhé nebo dynamicky generované
K problémům dochází, když je štítkem samotné čisté URL, obzvláště dlouhé. Pokud se URL vizuálně zalomí přes dva řádky, ale obdélník anotace byl vypočten pro jednořádkový řetězec, je klikatelný pouze první řádek. PrintHyperlink nepodporuje víceřádkový tok; udržujte štítek dostatečně krátký, aby se vešel na jeden řádek při aktuální velikosti písma a šířce stránky, použijte krátký popisný štítek s plným URL jako cílem, nebo aplikujte řešení pro zalamování po řádcích ukázané v další části
U dokumentů, které se budou archivovat nebo distribuovat bez aktivního připojení k internetu, zvažte také, zda by se URL nemělo objevit ve vytištěné podobě někde v těle dokumentu, a ne pouze jako metadata anotace. Čtenář, který si PDF vytiskne na papír, z URI anotace nic nezíská
Řešení omezení více řádků
Když se štítek odkazu skutečně musí roztáhnout na více než jeden řádek – dlouhé URL vytištěné doslovně nebo zalomená věta, která by měla být klikatelná od konce do konce – řešením je přestat k němu přistupovat jako k jednomu odkazu a brát jej jako jeden odkaz na každý řádek. Každé volání PrintHyperlink vypočítá svůj obdélník z textu, který nakreslí, takže několik volání sdílejících stejný cíl Link vytvoří několik správně dimenzovaných anotací, které všechny otevřou stejné URL. Čtenář nepozná rozdíl; každý řádek reaguje na kliknutí
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');
Rozdělení řetězce je vaší odpovědností: zalomte ho na stejných pozicích, kde by se vizuálně zalomil při aktuálním písmu a šířce sloupce, pomocí TextWidth pro otestování každého možného řádku. Alternativou je nakreslit zalomený text sami pomocí čistých volání TextOut a poté položit jeden obdélník AddURILink přes každý řádek – což je lepší způsob, když text je již vyprodukován vaší vlastní logikou zalamování slov, což nás přivádí k této funkci
AddURILink: klikatelné oblasti nad čímkoliv, co jste nakreslili
PrintHyperlink je pohodlný obal (wrapper): nakreslí svůj vlastní štítek a odvodí obdélník z metrik tohoto štítku. AddURILink je nízkoúrovňová polovina, která je zpřístupněna napřímo:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Zapíše pouze anotaci – nevykresluje se žádný text a nedochází ke změně barev. Parametr Rectangle (obdélník) je interpretován ve stejném prostoru souřadnic jako vaše volání pro kreslení, takže můžete znovu použít přesně ty hodnoty X/Y, které jste předali funkci TextOut nebo volání obrázku. To z ní dělá správný nástroj vždy, když viditelný obsah již existuje: aktivní oblast obrázku, buňka tabulky, blok textu nakreslený dříve nebo jeden řádek zalomeného odstavce, jak bylo ukázáno v předchozím řešení. Anotace má ohraničení o šířce nula, takže se vizuálně nic nezmění; klikatelnou oblastí je přesně vámi zadaný obdélník
Funkce vrací slovník anotace jako THPDFDictionaryObject. Většina volajících tento výsledek zahodí, ale jeho uchování vám umožní upravit položky anotace předtím, než se dokument zapíše
Vestavěné jsou dva detaily pro shodu. V režimech PDF/A je příznak tisku anotace nastaven tak, jak to tyto standardy vyžadují. V rámci PDFUACompliance musí být parametr Description (Popis) neprázdný řetězec – stane se z něj položka /Contents anotace, což je to, co pro odkaz ohlašují asistenční technologie – a volání vyvolá výjimku, místo aby potichu vygenerovalo nevyhovující soubor. Funkce PrintHyperlink tomuto pravidlu předchází a žádný popis nepřipojuje, proto pro výstup PDF/UA nakreslete štítek pomocí TextOut a umístěte anotaci s funkcí AddURILink doplněnou o smysluplný popis
Pravidlo pro rozhodování je jednoduché: použijte PrintHyperlink, když je odkazem krátký úsek textu, který jste ještě nenakreslili; použijte AddURILink, když je klikatelná oblast definována obsahem, který si sami nakreslíte nebo změříte
Interní navigace pomocí AddGoToLink
Externí URL adresy tvoří pouze polovinu toho, co anotace odkazů dělají. Druhou polovinou je navigace uvnitř dokumentu – obsah, který přeskočí na kapitoly, nebo křížové odkazy mezi oddíly. HotPDF to zpřístupňuje prostřednictvím funkce AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Za přesné uvedení stojí tři sémantická pravidla, jelikož žádné z nich nelze uhodnout ze signatury funkce. Indexování TargetPageIndex začíná od nuly: první stránka dokumentu je stránka 0, což odpovídá vlastnosti CurrentPageNumber. Cílová stránka již musí v okamžiku volání existovat; pokud je index mimo rozsah, procedura skončí bez přidání anotace – neohlásí se výjimka, odkaz nevznikne a neobjeví se varování. U obsahu odkazujícího vpřed nejprve vytvořte všechny stránky a následně se vraťte zpět pro doplnění odkazů
Parametr YPos vybírá vertikální pozici na cílové stránce ve stejném souřadnicovém prostoru jako u vašeho kreslení. Výchozí hodnota -1 (libovolná záporná hodnota) zapíše nulovou cílovou souřadnici a tím prohlížeči sděluje, že si má při dopadu na cílovou stránku zachovat aktuální vertikální pozici. Po zadání nezáporné hodnoty prohlížeč odskroluje tak, že tato pozice bude sedět na horním okraji okna – využijte pro ni souřadnici Y nadpisu, na který se odkazujete. Přiblížení (zoom) zůstává vždy nezměněno. Obdobně jako u funkce AddURILink musí být v režimu PDFUACompliance parametr Description neprázdný a bude využit jako alternativní text odkazu
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;
Každá položka získá obdélník širší než text, aby na ukazatel reagoval celý řádek, a každý odkaz se usadí tak, aby byl nadpis kapitoly (nakreslený na Y=780) v horní části okna. Pokud později vložíte před kapitoly další stránku, každý parametr TargetPageIndex se posune o jedna; indexy vypočítejte pomocí smyčky tvořící stránky místo jejich pevného zadání
Kompletní příklad vygenerování dokumentu
Níže uvedený vzor demonstruje realističtější scénář: generování krátkého reportu s hlavičkou, textem těla a patičkovým řádkem s odkazy, a to vše čistě z kódu spíše než z formuláře s poli TEdit:
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;
Všimněte si, že SetFont se volá před každou skupinou volání pro psaní textu. Zvolené písmo přes funkci AddPage nepřetrvá, a pokud ho před voláním PrintHyperlink na nové stránce opomenete nastavit, rámeček anotace bude počítán proti jakýmkoliv výchozím metrikám dané stránky, které se mohou lišit od toho, co očekáváte
Kde se zpracování anotací napříč prohlížeči liší
Anotace s odkazy na URI v PDF definuje norma ISO 32000-1 §12.6.4.7 a každý vyhovující prohlížeč by ji měl dodržovat. V praxi se však chování některých prohlížečů liší. Aplikace Adobe Acrobat vykazuje při prvním kliknutí bezpečnostní výzvu ohledně adres, které nejsou na seznamu důvěryhodných domén; u mnoha prohlížečů i odlehčených čteček se tomu tak neděje. Některé firemní prohlížeče PDF v chráněných prostředích kompletně zakazují podle vnitřní politiky anotace s URI adresami, tudíž kliknutí nic nespustí bez jakéhokoliv viditelného upozornění. U mobilních PDF aplikací navíc záleží, jestli odkaz otevřou ve vlastním prohlížeči v aplikaci nebo jej předají do systémového prohlížeče zařízení
Žádná ze jmenovaných vlastností nepatří mezi chyby, které lze řešit na straně generování obsahu; jedná se o rozhodnutí a zásady platné na úrovni prohlížeče. Co však můžete udělat, je zapsat text pro odkazy tak, aby bylo URL adresy zřetelné samotném těle dokumentu a čtenář si ho mohl v rámci restriktivního prostředí zkopírovat manuálně. Samotná anotace slouží pro pohodlí; text funguje coby záloha
Dále byste měli vědět: URI anotace v PDF nemají ve svém výchozím nastavení žádné podtržení. To podtržení, které vídáte ve většině prohlížečů, vykresluje prohlížeč sám o sobě podle konkrétního typu anotace, a není to tedy vykresleno samostatným znakem v obsahovém proudu dat (content stream). Potřebujete-li vytvořit natvrdo aplikované podtržení přežívající také v neinteraktivním prostředí po vyrenderování při převodu z PDF na obrázek nebo po vytištění, nakreslete si je dodatečně skrze volání LineTo a Stroke na příslušné vzdálenosti podél základní řady pod textem. Jedná se tak o separátní operaci vykreslení a nikoliv jen něco, co si automaticky zpracuje příkaz PrintHyperlink
Zde představené rozhraní pro hypervazby je součástí komponenty HotPDF Component pro Delphi a C++Builder