Tehnični članak

Hiperpovezave HotPDF v Delphiju: nasveti za PrintHyperlink

Hiperpovezave PDF so pripisi URI: pravokotnik, ki pokriva del površine strani in ob kliku pregledovalniku pove, naj odpre URL. Pripis in besedilo pod njim sta povsem neodvisna objekta. HotPDF-jev PrintHyperlink oboje zapakira v en klic: nariše besedilo in pravokotnik pripisa izračuna iz mer upodobljenega besedila. Ta udobnost skriva podrobnost, ki jo je vredno razumeti, preden napišete produkcijsko kodo. Prav tako to ni celotna zgodba: AddURILink klikljivo področje postavi nad vsebino, ki ste jo narisali sami, AddGoToLink pa poskrbi za notranjo navigacijo — oboje je obravnavano spodaj

Kako deluje PrintHyperlink

PrintHyperlink živi na THPDFPage in vzame štiri argumente: koordinati X in Y (v točkah, z izhodiščem v spodnjem levem kotu in Y, ki raste navzgor), niz oznake za izris in ciljni URL. Interno pokliče TextOut v trenutni barvi hiperpovezav, nato pa takoj izračuna pravokotnik pripisa iz TextWidth in TextHeight pri trenutnih merah pisave. To pomeni, da morata biti pisava in velikost nastavljeni pred klicem in se med izrisom oznake ter postavitvijo pripisa ne smeta spremeniti, saj se oboje razreši v istem klicu

Anatomija enega klica HotPDF PrintHyperlink, ki zapiše dva neodvisna objekta PDF: vidne znake oznake, izrisane s TextOut, in pravokotnik pripisa povezave URI, izračunan iz TextWidth in TextHeight
Znaki oznake in pravokotnik URI so ločeni objekti PDF, in prav zato se morata pisava in barva hiperpovezave ustaliti, preden en klic zapiše oboje

Privzeta barva je clBlue. SetRGBHyperlinkColor jo spremeni le za naslednje klice; že zapisanih pripisov za nazaj ne posodobi. Če za različne skupine povezav na isti strani potrebujete različne barve, SetRGBHyperlinkColor pokličite pred vsako skupino in ga nato ponastavite

Spodaj je minimalen dokument, ki zapiše tri povezave v dveh različnih barvah:

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

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

    // Privzeta modra za informativne povezave
    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');

    // Rdeča za akcijsko povezavo
    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);  // obnovi privzeto

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

Past s koordinatami

HotPDF uporablja izhodišče v spodnjem levem kotu z Y, ki raste navzgor, v točkah (1/72 palca). Stran A4 meri 595 x 842 pt; stran US Letter meri 612 x 792 pt. Y=750 leži blizu vrha strani A4, Y=50 pa bi bil blizu spodnjega roba. Kdor prihaja iz zaslonske grafike ali HTML, predpostavi nasprotno in prvo vrstico povezav postavi naravnost zunaj vidnega območja

Pravokotnik pripisa, ki ga izračuna PrintHyperlink, uporablja isti koordinatni sistem. Če stran pozneje zasukate, ji spremenite merilo ali velikost, ne da bi preračunali svoje vrednosti X/Y, se bosta vidno besedilo in klikljivi pravokotnik razšla. Povezava "deluje" v tem smislu, da klik nekje blizu besedila sproži URL, vroče območje pa ne ustreza več temu, kar bralec vidi. Preizkusite na dejanski velikosti strani in stopnji povečave, ki ju odpremljate, ne le na razvijalskem stroju pri 100 %

En primer, ko je razhajanje zagotovljeno: če PrintHyperlink pokličete s koordinatami, primernimi za stran A4, nato pa preklopite na ozko stran po meri, ne da bi prilagodili vrednosti X/Y, lahko pripis pristane povsem zunaj strani. Objekt pripisa je v PDF vseeno zapisan; večina pregledovalnikov ga tiho poreže, zato povezava preprosto izgine brez kakršne koli napake

Besedilo oznake proti cilju URL

Argumenta Text in Link sta neodvisna. Izrišete lahko "Prenesi PDF računa", medtem ko je cilj polno kvalificiran URL HTTPS s parametri poizvedbe. Ta ločitev je namerna; vidna oznaka naj bo berljiva za človeka, URL pa je lahko dolg ali ustvarjen dinamično

Težave nastanejo, kadar je oznaka kar sam URL, še posebej dolg. Če se URL vizualno prelomi čez dve vrstici, pravokotnik pripisa pa je bil izračunan za enovrstični niz, je klikljiva le prva vrstica. PrintHyperlink večvrstičnega toka ne obvlada; oznako ohranite dovolj kratko, da se pri trenutni velikosti pisave in širini strani zmesti v eno vrstico, uporabite kratko opisno oznako s polnim URL kot ciljem ali pa uporabite rešitev po vrsticah, prikazano v naslednjem razdelku

Pri dokumentih, ki bodo arhivirani ali razdeljeni brez dejavne internetne povezave, razmislite tudi, ali naj se URL v natisnjeni obliki pojavi nekje v telesu dokumenta in ne le kot metapodatek pripisa. Bralec, ki PDF natisne na papir, od pripisa URI nima ničesar

Kako zaobiti omejitev ene vrstice

Kadar mora oznaka povezave res zajeti več kot eno vrstico — dolg URL, natisnjen dobesedno, ali prelomljen stavek, ki naj bo klikljiv od začetka do konca — je popravek ta, da jo nehate obravnavati kot eno povezavo in jo obravnavate kot eno povezavo na vrstico. Vsak klic PrintHyperlink svoj pravokotnik izračuna iz besedila, ki ga nariše, zato več klicev z istim ciljem Link da več pravilno dimenzioniranih pripisov, ki vsi odprejo isti URL. Bralec razlike ne opazi; vsaka vrstica se odzove na klik

Primerjava prelomljene oznake hiperpovezave HotPDF, ki dobi en pripis, pokrivajoč le svojo prvo vrstico, z enim klicem PrintHyperlink na upodobljeno vrstico, kjer si vsi delijo isti ciljni URL
Pravokotnik, izračunan za eno vrstico, pusti vsako prelomljeno nadaljevanje na cedilu, klici po vrsticah pa si delijo cilj in ohranijo klikljiv celoten blok
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;

// Raba: oznako prelomite na mestih, kjer jo prelomi vaša postavitev
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');

Deljenje niza je vaša odgovornost: prelomite ga na istih mestih, kjer bi se vizualno prelomil pri trenutni pisavi in širini stolpca, za preizkus vsake kandidatne vrstice pa uporabite TextWidth. Druga možnost je, da prelomljeno besedilo narišete sami z navadnimi klici TextOut in nato čez vsako vrstico položite en pravokotnik AddURILink — to je boljša pot, kadar besedilo že ustvarja vaša lastna logika preloma, kar nas pripelje do te funkcije

AddURILink: klikljiva območja nad čimer koli, kar ste narisali

PrintHyperlink je ovoj za udobje: nariše svojo oznako in pravokotnik izpelje iz mer te oznake. AddURILink je neposredno razkrita nižja polovica:

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

Zapiše samo pripis — nobeno besedilo ni izrisano in nobena barva se ne spremeni. Rectangle se tolmači v istem koordinatnem prostoru kot vaši risalni klici, zato lahko znova uporabite prav tiste vrednosti X/Y, ki ste jih podali klicu TextOut ali klicu za sliko. Zato je to pravo orodje vselej, kadar vidna vsebina že obstaja: vroča točka na sliki, celica tabele, blok besedila, narisan prej, ali ena vrstica prelomljenega odstavka kot v zgornji rešitvi. Pripis nosi obrobo z ničelno širino, zato se vidno nič ne spremeni; klikljivo območje je natanko pravokotnik, ki ga navedete

Funkcija vrne slovar pripisa kot THPDFDictionaryObject. Večina klicateljev izid zavrže, a če ga obdržite, lahko vnose pripisa prilagodite, preden se dokument zapiše

Vgrajeni sta dve podrobnosti glede skladnosti. V načinih PDF/A je zastavica za tiskanje pripisa nastavljena tako, kot ti standardi zahtevajo. Pod PDFUACompliance mora biti parameter Description neprazen niz — postane vnos /Contents pripisa, torej tisto, kar pomožna tehnologija za povezavo naznani — klic pa sproži izjemo, namesto da bi tiho izpisal neskladno datoteko. PrintHyperlink je starejši od tega pravila in ne pripne nobenega opisa, zato za izhod PDF/UA oznako narišite s TextOut in pripis postavite z AddURILink ter smiselnim opisom

Odločitveno pravilo je preprosto: PrintHyperlink uporabite, kadar je povezava kratek kos besedila, ki ga še niste narisali; AddURILink pa, kadar klikljivo območje določa vsebina, ki jo narišete ali izmerite sami

Notranja navigacija z AddGoToLink

Zunanji URL-ji so le polovica tega, kar pripisi povezav počnejo. Druga polovica je navigacija znotraj dokumenta — kazalo, ki skoči na poglavja, navzkrižni sklici med razdelki. HotPDF to razkriva prek AddGoToLink:

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

Tri pomenske podrobnosti je vredno povedati natančno, saj nobene ni mogoče uganiti iz podpisa. TargetPageIndex se šteje od nič: prva stran dokumenta je stran 0, kar se ujema s CurrentPageNumber. Ciljna stran mora ob klicu že obstajati; če je indeks zunaj obsega, se postopek vrne, ne da bi dodal pripis — brez izjeme, brez povezave, brez opozorila. Za kazalo, ki kaže naprej, najprej ustvarite vse strani, nato pa se vrnite in dodajte povezave

YPos izbere navpični položaj na ciljni strani, v istem koordinatnem prostoru kot vaši risalni klici. Privzeta vrednost -1 (katera koli negativna vrednost) zapiše ničelno ciljno koordinato in pregledovalniku pove, naj ob pristanku na ciljni strani ohrani trenutni navpični položaj. Podajte nenegativno vrednost in pregledovalnik se pomakne tako, da bo ta položaj na vrhu okna — uporabite koordinato Y naslova, na katerega se povezujete. Povečava ostane vedno nespremenjena. Tako kot pri AddURILink mora biti Description pod PDFUACompliance neprazen in postane nadomestno besedilo povezave

HotPDF: povezano kazalo, zgrajeno z AddGoToLink, ki prikazuje skoke z od nič šteto vrednostjo TargetPageIndex s strani kazala na strani poglavij, kjer vsak naslov pristane na vrhu okna
Pravokotniki segajo čez besedilo, tako da se odzovejo cele vrstice, stalna pristajalna vrednost Y pa naslov vsakega poglavja postavi na vrh okna
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;                        // stran 0 postane stran s kazalom

    // Najprej ustvarite strani poglavij, da cilji povezav obstajajo
    for I := 0 to High(Chapters) do
    begin
      Pdf.AddPage;                       // strani 1..3
      Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
      Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
    end;

    // Vrnite se na stran 0 in izrišite vnose kazala z njihovimi povezavami
    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),    // pokrije vnos z odmikom
        I + 1,                           // šteto od nič: poglavja so strani 1..3
        780,                             // pristani z naslovom na vrhu
        AnsiString('Go to ' + Chapters[I]));
      Y := Y - 25;
    end;

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

Vsak vnos dobi pravokotnik, širši od besedila, tako da se na kazalec odzove cela vrstica, vsaka povezava pa pristane z naslovom poglavja (narisanim pri Y=780) na vrhu okna. Če pozneje vstavite stran pred poglavja, se vsak TargetPageIndex premakne za ena; indekse računajte iz svoje zanke za ustvarjanje strani, namesto da bi jih zapisali trdo

Celovit primer generiranja dokumenta

Spodnji vzorec kaže bolj resničen scenarij: generiranje kratkega poročila z razdelkom glave, besedilom telesa in nožno vrstico povezav, vse iz kode in ne iz obrazca s polji 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;

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

    // Nadomestek za odstavek telesa
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');

    // Povezave v nogi
    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;

Opazite, da je SetFont poklican pred vsako skupino besedilnih klicev. Pisava ne obstane čez AddPage, in če jo pozabite nastaviti pred PrintHyperlink na novi strani, bo pravokotnik pripisa izračunan glede na privzete mere te strani, ki se lahko razlikujejo od tega, kar pričakujete

Kje se obravnava pripisov med pregledovalniki razlikuje

Pripisi URI v PDF so definirani v ISO 32000-1 §12.6.4.7 in vsak skladen pregledovalnik bi jim moral slediti. V praksi se nekaj vedenj med pregledovalniki razlikuje. Adobe Acrobat ob prvem kliku pokaže varnostni poziv za URL-je, ki niso na seznamu zaupanja vrednih domen; mnogi brskalniki in lahki bralniki tega ne počnejo. Nekateri poslovni pregledovalniki PDF v zaklenjenih okoljih pripise URI po pravilniku povsem onemogočijo, zato klik ne stori nič, brez vidne napake. Mobilne aplikacije za PDF se razlikujejo po tem, ali povezave odprejo v spletnem pogledu znotraj aplikacije ali jih predajo sistemskemu brskalniku

Nič od tega ni napaka, ki bi jo lahko popravili na strani generiranja; gre za pravilniške odločitve pregledovalnikov. Kar lahko storite, je, da napišete oznake povezav, ki URL naredijo viden tudi v telesu dokumenta, tako da lahko bralec v omejenem okolju naslov še vedno prepiše ročno. Pripis je udobje; besedilo je zasilna rešitev

Še ena podrobnost, ki jo je vredno poznati: pripisi URI v PDF privzeto ne nosijo nobenega vidnega podčrtaja. Podčrtaj, ki ga vidite v večini pregledovalnikov, nariše pregledovalnik sam glede na vrsto pripisa in ne kak znak v toku vsebine. Če potrebujete fizičen podčrtaj, ki preživi tiskanje v neinteraktivni upodabljalnik ali pretvorbo PDF v sliko, ga izrišite izrecno z LineTo in Stroke pri ustreznem odmiku Y pod osnovnico besedila. To je ločena risalna operacija in ne nekaj, kar bi PrintHyperlink opravil namesto vas

API za hiperpovezave, prikazan tukaj, je del komponente HotPDF Delphi za Delphi in C++Builder