Hyperlinkurile din PDF sunt adnotări URI: un dreptunghi care acoperă o zonă de pagină și care, la clic, îi spune vizualizatorului să deschidă o adresă. Adnotarea și textul de sub ea sunt obiecte complet independente. Metoda PrintHyperlink din HotPDF le împachetează pe amândouă într-un singur apel, desenând textul și calculând dreptunghiul adnotării din metricile textului randat. Acea comoditate ascunde un detaliu pe care merită să îl înțelegeți înainte de a scrie cod de producție. Și nu este toată povestea: AddURILink plasează o zonă clicabilă peste conținut desenat de dumneavoastră, iar AddGoToLink se ocupă de navigarea internă — ambele tratate mai jos
Cum funcționează PrintHyperlink
PrintHyperlink stă pe THPDFPage și primește patru argumente: coordonatele X și Y (în puncte, cu originea în stânga jos și Y crescând în sus), șirul de etichetă de desenat și adresa țintă. Pe plan intern apelează TextOut cu culoarea curentă de hyperlink, apoi calculează imediat dreptunghiul adnotării din TextWidth și TextHeight, la metricile curente ale fontului. Asta înseamnă că fontul și dimensiunea trebuie setate înainte de apel și nu trebuie să se schimbe între desenarea etichetei și plasarea adnotării, pentru că amândouă se rezolvă în același apel
Culoarea implicită este clBlue. SetRGBHyperlinkColor o schimbă doar pentru apelurile următoare; nu actualizează retroactiv adnotările deja scrise. Dacă aveți nevoie de culori diferite pentru grupuri de legături diferite pe aceeași pagină, apelați SetRGBHyperlinkColor înaintea fiecărui grup și resetați-o după
Iată un document minimal care scrie trei legături cu două culori diferite:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Albastru implicit pentru legăturile informative
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');
// Roșu pentru legătura de acțiune
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); // revine la valoarea implicită
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Capcana coordonatelor
HotPDF folosește o origine în stânga jos, cu Y crescând în sus, în puncte (1/72 dintr-un inch). O pagină A4 are 595 x 842 pt; o pagină US Letter are 612 x 792 pt. Y=750 stă lângă partea de sus a unei pagini A4, iar Y=50 ar fi lângă marginea de jos. Oricine vine dinspre grafica de ecran sau dinspre HTML presupune inversul și plasează primul rând de legături direct în afara zonei vizibile
Dreptunghiul de adnotare pe care îl calculează PrintHyperlink folosește același sistem de coordonate. Dacă ulterior rotiți pagina, o scalați sau îi schimbați dimensiunea fără să recalculați valorile X/Y, textul vizibil și dreptunghiul clicabil se vor îndepărta unul de altul. Legătura „funcționează”, în sensul că un clic undeva lângă text declanșează adresa, dar zona activă nu mai corespunde cu ce vede cititorul. Testați pe dimensiunea de pagină și pe nivelul de mărire cu care livrați, nu doar pe mașina de dezvoltare la 100%
Un caz în care abaterea este garantată: dacă apelați PrintHyperlink cu coordonate potrivite pentru o pagină A4 și apoi treceți la o pagină cu format îngust personalizat fără să ajustați valorile X/Y, adnotarea poate ajunge complet în afara paginii. Obiectul de adnotare tot este scris în PDF; majoritatea vizualizatoarelor îl decupează în tăcere, deci legătura pur și simplu dispare, fără nicio eroare
Textul etichetei față de adresa țintă
Argumentele Text și Link sunt independente. Puteți desena „Descărcați factura PDF” în timp ce ținta este o adresă HTTPS complet calificată, cu parametri de interogare. Acea separare este intenționată; eticheta vizibilă ar trebui să fie lizibilă de om, iar adresa poate fi lungă sau generată dinamic
Ce creează probleme este atunci când eticheta este chiar adresa brută, mai ales una lungă. Dacă adresa se împarte vizual pe două rânduri, dar dreptunghiul de adnotare a fost calculat pentru un șir de un singur rând, doar primul rând este clicabil. PrintHyperlink nu tratează fluxul pe mai multe rânduri; păstrați eticheta destul de scurtă încât să încapă pe un rând la dimensiunea curentă a fontului și la lățimea paginii, folosiți o etichetă scurtă și descriptivă cu adresa completă drept țintă sau aplicați soluția pe rânduri arătată în secțiunea următoare
Pentru documentele care vor fi arhivate sau distribuite fără o conexiune activă la internet, cântăriți și dacă adresa în sine ar trebui să apară tipărită undeva în corpul documentului, nu doar ca metadate de adnotare. Un cititor care tipărește PDF-ul pe hârtie nu obține nimic dintr-o adnotare URI
Ocolirea limitării la un singur rând
Când o etichetă de legătură chiar trebuie să se întindă pe mai mult de un rând — o adresă lungă tipărită ca atare sau o frază împărțită care ar trebui să fie clicabilă de la un capăt la altul — corectura este să nu o mai tratați ca pe o singură legătură, ci ca pe o legătură pentru fiecare rând. Fiecare apel PrintHyperlink își calculează dreptunghiul din textul pe care îl desenează, așa că mai multe apeluri care împart aceeași țintă Link produc mai multe adnotări dimensionate corect, care deschid toate aceeași adresă. Cititorul nu poate simți diferența; fiecare rând răspunde la clic
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;
// Utilizare: împărțiți eticheta acolo unde aranjamentul o rupe pe rânduri
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');
Împărțirea șirului este responsabilitatea dumneavoastră: rupeți-l în aceleași poziții în care s-ar rupe vizual la fontul și lățimea de coloană curente, folosind TextWidth pentru a testa fiecare rând candidat. Alternativa este să desenați singur textul împărțit cu simple apeluri TextOut și apoi să așezați câte un dreptunghi AddURILink peste fiecare rând — calea mai bună atunci când textul este deja produs de propria dumneavoastră logică de împărțire pe rânduri, ceea ce ne aduce la acea funcție
AddURILink: zone clicabile peste orice ați desenat
PrintHyperlink este un înveliș de comoditate: își desenează propria etichetă și derivă dreptunghiul din metricile acelei etichete. AddURILink este jumătatea de nivel jos, expusă direct:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Ea scrie doar adnotarea — nu se desenează niciun text și nu se schimbă nicio culoare. Rectangle este interpretat în același spațiu de coordonate ca apelurile dumneavoastră de desenare, deci puteți refolosi exact valorile X/Y pe care le-ați transmis lui TextOut sau unui apel de imagine. Asta o face instrumentul potrivit ori de câte ori conținutul vizibil există deja: o zonă activă pe o imagine, o celulă de tabel, un bloc de text desenat mai devreme sau un rând dintr-un paragraf împărțit, ca în soluția de mai sus. Adnotarea poartă o bordură cu lățime zero, deci nimic vizibil nu se schimbă; regiunea clicabilă este exact dreptunghiul pe care îl indicați
Funcția returnează dicționarul adnotării ca THPDFDictionaryObject. Majoritatea apelanților aruncă rezultatul, dar păstrarea lui vă lasă să ajustați intrările adnotării înainte ca documentul să fie scris
Două detalii de conformitate sunt integrate. În modurile PDF/A, indicatorul de tipărire al adnotării este setat așa cum cer acele standarde. Sub PDFUACompliance, parametrul Description trebuie să fie un șir nevid — el devine intrarea /Contents a adnotării, adică exact ce anunță tehnologia asistivă pentru legătură — iar apelul ridică o excepție în loc să emită în tăcere un fișier neconform. PrintHyperlink este anterior acelei reguli și nu atașează nicio descriere, deci pentru ieșire PDF/UA desenați eticheta cu TextOut și plasați adnotarea cu AddURILink, cu o descriere cu sens
Regula de decizie este simplă: folosiți PrintHyperlink atunci când legătura este o bucată scurtă de text pe care nu ați desenat-o încă; folosiți AddURILink atunci când regiunea clicabilă este definită de conținut pe care îl desenați sau îl măsurați chiar dumneavoastră
Navigare internă cu AddGoToLink
Adresele externe sunt doar jumătate din ce fac adnotările de legătură. Cealaltă jumătate este navigarea în interiorul documentului — un cuprins care sare la capitole, referințe încrucișate între secțiuni. HotPDF expune asta prin AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Merită enunțate precis trei semantici, de vreme ce niciuna nu se ghicește din semnătură. TargetPageIndex pornește de la zero: prima pagină a documentului este pagina 0, în acord cu CurrentPageNumber. Pagina țintă trebuie să existe deja când faceți apelul; dacă indexul este în afara intervalului, procedura se întoarce fără să adauge vreo adnotare — nicio excepție, nicio legătură, niciun avertisment. Pentru un cuprins care arată înainte, creați întâi toate paginile, apoi reveniți și adăugați legăturile
YPos alege poziția verticală de pe pagina țintă, în același spațiu de coordonate ca apelurile dumneavoastră de desenare. Valoarea implicită -1 (orice valoare negativă) scrie o coordonată de destinație nulă, spunându-i vizualizatorului să își păstreze poziția verticală curentă când ajunge pe pagina țintă. Transmiteți o valoare nenegativă și vizualizatorul derulează astfel încât acea poziție să stea în partea de sus a ferestrei — folosiți coordonata Y a titlului spre care faceți legătura. Nivelul de mărire rămâne întotdeauna neschimbat. Ca și la AddURILink, Description trebuie să fie nevid sub PDFUACompliance și devine textul alternativ al legăturii
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; // pagina 0 devine pagina de cuprins
// Creați întâi paginile de capitol, ca țintele legăturilor să existe
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // paginile 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Reveniți la pagina 0 și desenați intrările de cuprins cu legăturile lor
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), // acoperă intrarea cu spațiu în plus
I + 1, // pornit de la zero: capitolele sunt paginile 1..3
780, // aterizează cu titlul în partea de sus
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Fiecare intrare primește un dreptunghi mai lat decât textul, ca tot rândul să răspundă la indicator, iar fiecare legătură aterizează cu titlul de capitol (desenat la Y=780) în partea de sus a ferestrei. Dacă inserați ulterior o pagină înaintea capitolelor, fiecare TargetPageIndex se deplasează cu unu; calculați indicii din bucla dumneavoastră de creare a paginilor, în loc să îi scrieți fix în cod
Un exemplu complet de generare a unui document
Tiparul de mai jos arată un scenariu mai realist: generarea unui raport scurt cu o secțiune de antet, text de corp și un rând de legături în subsol, totul din cod, nu dintr-un formular cu câmpuri 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;
// Antet
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Substituent pentru paragraful de corp
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Legături în subsol
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;
Observați că SetFont este apelat înaintea fiecărui grup de apeluri de text. Fontul nu persistă peste AddPage, iar dacă uitați să îl setați înainte de PrintHyperlink pe o pagină nouă, dreptunghiul de adnotare va fi calculat față de metricile implicite ale paginii, care pot să difere de ce vă așteptați
Unde diferă tratarea adnotărilor de la un vizualizator la altul
Adnotările URI din PDF sunt definite în ISO 32000-1 §12.6.4.7, iar orice vizualizator conform ar trebui să le respecte. În practică, câteva comportamente diferă de la un vizualizator la altul. Adobe Acrobat arată o solicitare de securitate la primul clic pentru adresele care nu sunt în lista de domenii de încredere; multe browsere și cititoare ușoare nu o fac. Unele vizualizatoare PDF de întreprindere, în medii restricționate, dezactivează complet adnotările URI prin politică, deci un clic nu face nimic, fără vreo eroare vizibilă. Aplicațiile PDF mobile diferă între ele prin faptul că deschid legăturile în vizualizatorul web al aplicației sau le predau browserului sistemului
Niciuna dintre acestea nu este un defect pe care să îl puteți repara din partea de generare; sunt decizii de politică ale vizualizatoarelor. Ce puteți face este să scrieți etichete de legătură care fac adresa vizibilă și în corpul documentului, astfel încât un cititor dintr-un mediu restricționat să poată copia adresa manual. Adnotarea este comoditatea; textul este soluția de rezervă
Încă un detaliu pe care merită să îl știți: adnotările URI din PDF nu poartă implicit nicio subliniere vizuală. Sublinierea pe care o vedeți în majoritatea vizualizatoarelor este desenată de vizualizatorul însuși, pe baza tipului de adnotare, nu de o glifă din fluxul de conținut. Dacă aveți nevoie de o subliniere fizică ce supraviețuiește tipăririi către un randor neinteractiv sau conversiei din PDF în imagine, desenați-o explicit cu LineTo și Stroke, la decalajul Y potrivit sub linia de bază a textului. Aceea este o operație de desenare separată, nu ceva de care se ocupă PrintHyperlink în locul dumneavoastră
API-ul de hyperlinkuri prezentat aici face parte din HotPDF Delphi Component pentru Delphi și C++Builder