Tehnički članak

HotPDF Delphi hiperveze: Savjeti za PrintHyperlink anotacije

PDF hiperveze su URI anotacije: pravokutnik koji pokriva neko područje stranice koji, kada se klikne, govori pregledniku da otvori URL. Anotacija i tekst ispod nje potpuno su neovisni objekti. HotPDF-ov PrintHyperlink spaja oboje u jedan poziv, crtajući tekst i računajući pravokutnik anotacije iz metrike iscrtanog teksta. Ta pogodnost skriva detalj koji vrijedi razumjeti prije nego što napišete produkcijski kod. To ujedno nije cijela priča: AddURILink postavlja klikabilno područje preko sadržaja koji ste sami nacrtali, a AddGoToLink pokriva internu navigaciju — oboje slijedi u nastavku

Kako PrintHyperlink radi

PrintHyperlink nalazi se na THPDFPage i prima četiri argumenta: X i Y koordinate (u točkama, ishodiste dolje lijevo, Y se povećava prema gore), niz znakova (label string) koji se iscrtava i ciljani URL. Interno on poziva TextOut u trenutnoj boji hiperveze, a zatim odmah računa pravokutnik anotacije iz TextWidth i TextHeight pri trenutnoj metrici fonta. To znači da se font i veličina moraju postaviti prije poziva i ne smiju se mijenjati između crtanja oznake i postavljanja anotacije, jer se oboje rješava u istom pozivu

Anatomija jednog HotPDF PrintHyperlink poziva koji zapisuje dva neovisna PDF objekta: vidljive glifove oznake nacrtane TextOut-om i pravokutnik napomene URI poveznice izračunat iz TextWidth i TextHeight
Glifovi natpisa i URI pravokutnik odvojeni su PDF objekti, pa se font i boja hiperveze moraju odlučiti prije nego jedan poziv upiše oboje

Zadana boja je clBlue. SetRGBHyperlinkColor mijenja je samo za naknadne pozive; on ne ažurira retroaktivno već napisane anotacije. Ako su vam potrebne različite boje za različite grupe veza na istoj stranici, pozovite SetRGBHyperlinkColor prije svake grupe i ponovno ga postavite (resetirajte) nakon toga

Ovdje je minimalan dokument koji ispisuje tri veze s dvije različite boje:

procedure CreateLinkedReport(const FileName: string);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.BeginDoc;

    Pdf.CurrentPage.SetFont('Arial', [], 11);

    // Zadana plava boja za informativne poveznice
    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');

    // Crvena za akcijsku poveznicu
    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);  // vrati na zadano

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Zamka s koordinatama

HotPDF koristi ishodište u donjem lijevom kutu s Y koji raste prema gore, u točkama (1/72 inča). A4 stranica je 595 x 842 točaka (pt); US Letter stranica je 612 x 792 točaka. Y=750 nalazi se blizu vrha A4 stranice, a Y=50 bi bilo blizu donje margine. Svatko tko dolazi iz svijeta grafike za ekrane ili HTML-a pretpostavlja suprotno te postavlja prvu liniju veze ravno izvan vidljivog područja

Pravokutnik anotacije koji PrintHyperlink izračunava koristi isti koordinatni sustav. Ako kasnije rotirate stranicu, promijenite joj veličinu (scale) ili veličinu stranice bez ponovnog izračunavanja vaših X/Y vrijednosti, vidljivi tekst i pravokutnik na koji se može kliknuti odvojit će se jedno od drugog (drift apart). Veza "radi" u smislu da klik negdje blizu teksta pokreće URL, ali aktivna zona (hot zone) više ne odgovara onome što čitatelj vidi. Testirajte na stvarnoj veličini stranice i razini zumiranja koju isporučujete, a ne samo na razvojnom stroju na 100%

Jedan slučaj gdje je odvajanje (drift) zajamčeno: ako pozovete PrintHyperlink s koordinatama primjerenim za A4 stranicu, a zatim se prebacite na prilagođenu stranicu uskog formata bez prilagodbe X/Y vrijednosti, anotacija može završiti potpuno izvan stranice. Objekt anotacije i dalje se zapisuje u PDF; većina preglednika ga tiho odreže (clip), pa veza jednostavno nestane bez ikakve pogreške

Tekst oznake u odnosu na ciljani URL

Argumenti Text i Link su neovisni. Možete nacrtati "Preuzmi PDF računa" dok je cilj potpuno kvalificiran HTTPS URL s parametrima upita (query parameters). To razdvajanje je namjerno; vidljiva oznaka bi trebala biti čitljiva ljudima, a URL može biti dug ili dinamički generiran

Ono što stvara probleme jest kada je oznaka sam sirovi URL, posebno onaj dugački. Ako se URL vizualno prelama preko dvije linije (wraps), ali je pravokutnik anotacije izračunat za jednolinijski niz, samo se na prvu liniju može kliknuti. PrintHyperlink ne upravlja višelinijskim tokom (multi-line flow); neka oznaka bude dovoljno kratka da stane na jednu liniju pri trenutnoj veličini fonta i širini stranice, ili pak upotrijebite kratku opisnu oznaku s punim URL-om kao ciljem

Za dokumente koji će biti arhivirani ili distribuirani bez aktivne internetske veze, također razmotrite treba li se sam URL pojaviti u tiskanom obliku negdje u tijelu dokumenta, a ne samo kao metapodaci anotacije. Čitatelj koji ispisuje PDF na papir ne dobiva ništa od URI anotacije

Zaobilaženje ograničenja višerednog teksta

Kada oznaka veze doista mora obuhvatiti više od jednog retka — dugi URL ispisan doslovno ili prelomljena rečenica koja treba biti klikabilna s kraja na kraj — rješenje je prestati je tretirati kao jednu vezu i tretirati je kao jednu vezu po retku. Svaki poziv PrintHyperlink izračunava svoj pravokutnik iz teksta koji crta, pa nekoliko poziva koji dijele isti Link cilj proizvodi nekoliko ispravno dimenzioniranih anotacija koje sve otvaraju isti URL. Čitatelj ne vidi razliku; svaki redak reagira na klik

Usporedba prelomljene HotPDF oznake poveznice koja dobiva jednu napomenu koja pokriva samo njen prvi red nasuprot jednom PrintHyperlink pozivu po renderiranom retku koji dijele isti URL cilj
Pravokutnik izračunat za jednu liniju napušta svaki omotani nastavak, dok pozivi po liniji dijele cilj i drže cijeli blok klikabilnim
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;

// Upotreba: prelomite oznaku na mjestima gdje vaš izgled to čini
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');

Dijeljenje niza vaša je odgovornost: prelomite ga na istim mjestima na kojima bi se vizualno prelomio pri trenutnom fontu i širini stupca, uz TextWidth za provjeru svakog kandidata za redak. Alternativa je da prelomljeni tekst sami iscrtate običnim TextOut pozivima, a zatim preko svakog retka položite po jedan AddURILink pravokutnik — bolji put kada tekst ionako proizvodi vaša vlastita logika prelamanja, što nas dovodi upravo do te funkcije

AddURILink: klikabilna područja preko svega što ste nacrtali

PrintHyperlink je omotač radi praktičnosti: sam crta svoju oznaku i pravokutnik izvodi iz metrike te oznake. AddURILink je niža polovica izložena izravno:

function AddURILink(Rectangle: TRect; const URL: AnsiString;
  const Description: AnsiString = ''): THPDFDictionaryObject;

Ona zapisuje samo anotaciju — nikakav se tekst ne crta i nijedna boja se ne mijenja. Rectangle se tumači u istom koordinatnom prostoru kao i vaši pozivi za crtanje, pa možete ponovno upotrijebiti točno one X/Y vrijednosti koje ste predali pozivu TextOut ili pozivu za sliku. To je čini pravim alatom kad god vidljivi sadržaj već postoji: aktivna zona na slici, ćelija tablice, blok teksta nacrtan ranije ili jedan redak prelomljenog odlomka kao u gornjem zaobilaznom rješenju. Anotacija nosi rub nulte širine, pa se vizualno ništa ne mijenja; klikabilno područje točno je onaj pravokutnik koji zadate

Funkcija vraća rječnik anotacije kao THPDFDictionaryObject. Većina pozivatelja odbaci rezultat, ali ako ga zadržite, možete prilagoditi unose anotacije prije nego što se dokument zapiše

Dva detalja o usklađenosti ugrađena su unutra. U PDF/A načinima zastavica za ispis anotacije postavlja se onako kako ti standardi traže. Pod PDFUACompliance parametar Description mora biti neprazan niz — on postaje /Contents unos anotacije, a to je ono što pomoćna tehnologija izgovara za tu vezu — i poziv podiže iznimku umjesto da tiho ispiše neusklađenu datoteku. PrintHyperlink je stariji od tog pravila i ne prilaže nikakav opis, pa za PDF/UA izlaz oznaku nacrtajte pozivom TextOut, a anotaciju postavite pozivom AddURILink uz smislen opis

Pravilo odlučivanja je jednostavno: uzmite PrintHyperlink kada je veza kratak komad teksta koji još niste nacrtali; uzmite AddURILink kada klikabilno područje definira sadržaj koji sami crtate ili mjerite

Interna navigacija pomoću AddGoToLink

Vanjski URL-ovi su tek polovica onoga što anotacije veza rade. Druga je polovica navigacija unutar dokumenta — sadržaj koji skače na poglavlja, unakrsne reference između odjeljaka. HotPDF to izlaže kroz AddGoToLink:

procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
  YPos: Single = -1; const Description: AnsiString = '');

Tri semantike vrijedi navesti precizno, jer se nijedna ne da pogoditi iz potpisa. TargetPageIndex broji od nule: prva stranica dokumenta je stranica 0, u skladu s CurrentPageNumber. Ciljna stranica mora već postojati u trenutku poziva; ako je indeks izvan raspona, procedura se vraća bez dodavanja anotacije — nema iznimke, nema veze, nema upozorenja. Za sadržaj koji pokazuje prema naprijed prvo stvorite sve stranice, pa se vratite natrag i dodajte veze

YPos bira okomiti položaj na ciljnoj stranici, u istom koordinatnom prostoru kao i vaši pozivi za crtanje. Zadana vrijednost -1 (bilo koja negativna vrijednost) zapisuje praznu koordinatu odredišta, čime preglednik zadržava trenutni okomiti položaj kada sleti na ciljnu stranicu. Predajte nenegativnu vrijednost i preglednik će pomaknuti prikaz tako da taj položaj završi na vrhu prozora — upotrijebite Y koordinatu naslova na koji vežete. Zumiranje uvijek ostaje nepromijenjeno. Kao i kod AddURILink, Description mora biti neprazan pod PDFUACompliance i postaje alternativni tekst veze

HotPDF: povezani sadržaj izgrađen s AddGoToLink koji prikazuje TargetPageIndex skokove od nula iz stranice sadržaja na stranice poglavlja gdje svaki naslov dospije na vrh prozora
Pravokutnici se protežu ispod teksta da reagiraju cijeli redovi, a fiksno Y slijetanje svaki naslov poglavlja postavlja na vrh prozora
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;                        // stranica 0 postaje stranica sadržaja

    // Prvo stvorite stranice poglavlja kako bi ciljevi poveznica postojali
    for I := 0 to High(Chapters) do
    begin
      Pdf.AddPage;                       // stranice 1..3
      Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
      Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
    end;

    // Vratite se na stranicu 0 i nacrtajte unose sadržaja s njihovim poveznicama
    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),    // pokriva unos s dodatnim razmakom
        I + 1,                           // indeksirano od nule: poglavlja su stranice 1..3
        780,                             // pozicionirano tako da je naslov na vrhu
        AnsiString('Go to ' + Chapters[I]));
      Y := Y - 25;
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Svaki unos dobiva pravokutnik širi od teksta pa cijeli redak reagira na pokazivač, a svaka veza sleti tako da naslov poglavlja (nacrtan na Y=780) bude na vrhu prozora. Ako kasnije umetnete stranicu ispred poglavlja, svaki TargetPageIndex pomiče se za jedan; indekse računajte iz svoje petlje za stvaranje stranica umjesto da ih upisujete fiksno

Potpuni primjer generiranja dokumenta

Uzorak u nastavku prikazuje realističniji scenarij: generiranje kratkog izvješća s odjeljkom zaglavlja, tekstom tijela i redom poveznica u podnožju, sve iz koda umjesto s obrasca s TEdit poljima:

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;

    // Zaglavlje
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));

    // Rezervirano mjesto za odlomak tijela
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');

    // Poveznice u podnožju
    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;

Imajte na umu da se SetFont poziva prije svake grupe tekstualnih poziva. Font se ne zadržava preko AddPage, i ako ga zaboravite postaviti prije PrintHyperlink na novoj stranici, pravokutnik anotacije izračunat će se u odnosu na bilo kakve zadane metrike stranice, koje se mogu razlikovati od onoga što očekujete

Gdje se rukovanje anotacijama razlikuje ovisno o pregledniku

PDF URI anotacije definirane su u ISO 32000-1 §12.6.4.7, i svaki usklađeni preglednik trebao bi ih slijediti. U praksi, nekoliko ponašanja razlikuje se ovisno o pregledniku. Adobe Acrobat prikazuje sigurnosni upit (security prompt) pri prvom kliku na URL-ove koji nisu na popisu pouzdanih domena; mnogi web preglednici i lagani (lightweight) čitači to ne čine. Neki poslovni (enterprise) preglednici PDF-a u zaključanim (locked-down) okruženjima u potpunosti onemogućuju URI anotacije prema politici, pa klik ne radi ništa, bez vidljive pogreške. Mobilne PDF aplikacije razlikuju se u tome hoće li otvoriti veze unutar vlastitog web prikaza ili ih proslijediti pregledniku sustava

Ništa od navedenog nisu greške koje možete popraviti sa strane generiranja; to su odluke politika preglednika. Ono što možete učiniti je napisati oznake veza koje URL čine vidljivim i u tijelu dokumenta, tako da čitatelj u ograničenom okruženju još uvijek može ručno kopirati adresu. Anotacija predstavlja pogodnost; tekst je opcija u nuždi (fallback)

Još jedan detalj koji vrijedi znati: PDF URI anotacije po defaultu ne nose vizualno podcrtavanje. Podvlaku koju vidite u većini preglednika crta sam preglednik na temelju vrste anotacije, a ne glifa u struji sadržaja. Ako trebate fizičko podcrtavanje koje preživljava ispis na neinteraktivni mehanizam za iscrtavanje (renderer) ili konverziju iz PDF-a u sliku, iscrtajte ga eksplicitno uz LineTo i Stroke na odgovarajućem Y odmaku ispod osnovne linije teksta. To je zasebna operacija crtanja, a ne nešto što PrintHyperlink radi umjesto vas

Ovdje prikazani hipertekstualni API dio je HotPDF Delphi komponente za Delphi i C++Builder