A PDF-hiperhivatkozások URI-jegyzetek: olyan téglalapok, amelyek egy oldalterületet fednek le, és kattintásra megkérik a megjelenítőt, hogy nyisson meg egy URL-t. A jegyzet és az alatta lévő szöveg teljesen független objektum. A HotPDF PrintHyperlink hívása mindkettőt egyetlen hívásba köti: kirajzolja a szöveget, és a kirajzolt szöveg méretadataiból számítja ki a jegyzet téglalapját. Ez a kényelem olyan részletet rejt, amelyet érdemes megérteni, mielőtt éles kódot írna. Ráadásul nem ez a teljes kép: az AddURILink a saját kezűleg rajzolt tartalom fölé helyez kattintható területet, az AddGoToLink pedig a dokumentumon belüli navigációt kezeli — mindkettőről lentebb lesz szó
Hogyan működik a PrintHyperlink?
A PrintHyperlink a THPDFPage osztályon él, és négy argumentumot vár: X és Y koordinátát (pontban, bal alsó origóval, felfelé növekvő Y értékkel), a kirajzolandó címkeszöveget és az URL-célt. Belül a TextOut hívást használja az aktuális hiperhivatkozás-színnel, majd azonnal kiszámítja a jegyzet téglalapját a TextWidth és a TextHeight értékéből az aktuális betűkészlet-méretadatok mellett. Ez azt jelenti, hogy a betűkészletet és a méretet a hívás előtt be kell állítani, és nem szabad megváltoztatni a címke kirajzolása és a jegyzet elhelyezése között, mert mindkettő ugyanabban a hívásban dől el
Az alapértelmezett szín a clBlue. A SetRGBHyperlinkColor csak a későbbi hívásokra változtatja meg; a már kiírt jegyzeteket visszamenőleg nem frissíti. Ha ugyanazon az oldalon különböző hivatkozáscsoportokhoz eltérő színek kellenek, hívja meg a SetRGBHyperlinkColor metódust minden csoport előtt, és utána állítsa vissza
Íme egy minimális dokumentum, amely három hivatkozást ír két különböző színnel:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Alapértelmezett kék a tájékoztató hivatkozásokhoz
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');
// Piros a műveleti hivatkozáshoz
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); // az alapértelmezés visszaállítása
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
A koordinátacsapda
A HotPDF bal alsó origót használ felfelé növekvő Y értékkel, pontban (1/72 hüvelyk). Az A4-es oldal 595 x 842 pt; a US Letter oldal 612 x 792 pt. Az Y=750 az A4-es oldal teteje közelében ül, az Y=50 pedig az alsó margó közelében lenne. Aki képernyőgrafika vagy HTML felől érkezik, az ellenkezőjét feltételezi, és az első hivatkozássort egyenesen a látható területen kívülre teszi
Az a jegyzettéglalap, amelyet a PrintHyperlink kiszámít, ugyanezt a koordináta-rendszert használja. Ha később elforgatja az oldalt, átméretezi, vagy megváltoztatja az oldalméretet anélkül, hogy újraszámolná az X/Y értékeket, a látható szöveg és a kattintható téglalap elcsúszik egymástól. A hivatkozás „működik” abban az értelemben, hogy a szöveg közelébe kattintva elindul az URL, de a forró zóna már nem esik egybe azzal, amit az olvasó lát. Azon az oldalméreten és nagyítási szinten teszteljen, amelyet ki is szállít, ne csak a fejlesztői gépen 100%-on
Egy eset, ahol az elcsúszás garantált: ha A4-es oldalhoz illő koordinátákkal hívja a PrintHyperlink metódust, majd egyedi, keskeny formátumú oldalra vált az X/Y értékek igazítása nélkül, a jegyzet teljesen az oldalon kívülre kerülhet. A jegyzetobjektum ettől még beíródik a PDF-be; a legtöbb megjelenítő némán levágja, tehát a hivatkozás egyszerűen eltűnik, minden hibaüzenet nélkül
Címkeszöveg kontra URL-cél
A Text és a Link argumentum független egymástól. Kirajzolhatja azt, hogy „Download invoice PDF”, miközben a cél egy teljes, lekérdezési paraméterekkel ellátott HTTPS URL. Ez a szétválasztás szándékos; a látható címke legyen ember számára olvasható, az URL pedig lehet hosszú vagy dinamikusan előállított
A gond akkor keletkezik, amikor a címke maga a nyers URL, különösen egy hosszú. Ha az URL vizuálisan két sorra tördelődik, de a jegyzettéglalap egysoros karakterláncra lett kiszámítva, csak az első sor kattintható. A PrintHyperlink nem kezeli a többsoros folyást; tartsa a címkét elég rövidre ahhoz, hogy az aktuális betűméreten és oldalszélességen egy sorban elférjen, használjon rövid, leíró címkét a teljes URL-lel célként, vagy alkalmazza a következő szakaszban bemutatott soronkénti megkerülést
Olyan dokumentumoknál, amelyeket archiválnak vagy aktív internetkapcsolat nélkül terjesztenek, azt is fontolja meg, hogy magának az URL-nek meg kellene-e jelennie nyomtatott formában valahol a dokumentum törzsében, nem csak jegyzetadatként. Az az olvasó, aki papírra nyomtatja a PDF-et, semmit sem kap egy URI-jegyzettől
A többsoros korlát megkerülése
Ha egy hivatkozáscímkének valóban egynél több sort kell átfognia — egy szó szerint kinyomtatott hosszú URL vagy egy tördelt mondat, amelynek elejétől a végéig kattinthatónak kell lennie —, a megoldás az, hogy megszűnik egyetlen hivatkozásként kezelni, és soronként egy hivatkozásként kezeli. Minden PrintHyperlink hívás abból a szövegből számítja ki a téglalapját, amelyet kirajzol, tehát több hívás, amely ugyanazon a Link célon osztozik, több, helyesen méretezett jegyzetet állít elő, és mindegyik ugyanazt az URL-t nyitja meg. Az olvasó nem érzékel különbséget; minden sor válaszol a kattintásra
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;
// Használat: ott törje meg a címkét, ahol az elrendezése tördeli
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');
A karakterlánc felbontása az Ön felelőssége: ugyanazokon a helyeken törje meg, ahol az aktuális betűkészletnél és hasábszélességnél vizuálisan tördelődne, és a TextWidth hívással próbálja ki az egyes jelölt sorokat. Az alternatíva az, hogy a tördelt szöveget maga rajzolja ki egyszerű TextOut hívásokkal, majd soronként egy AddURILink téglalapot fektet rá — ez a jobb út, ha a szöveget már a saját sortördelési logikája állítja elő, ami el is vezet ahhoz a függvényhez
AddURILink: kattintható területek bármi fölött, amit kirajzolt
A PrintHyperlink kényelmi burkoló: kirajzolja a saját címkéjét, és annak méretadataiból vezeti le a téglalapot. Az AddURILink az alacsonyabb szintű fele, közvetlenül kitéve:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Csak a jegyzetet írja ki — nem rajzol szöveget, és nem változtat színt. A Rectangle ugyanabban a koordinátatérben értendő, mint a rajzolási hívásai, tehát pontosan azokat az X/Y értékeket használhatja újra, amelyeket a TextOut hívásnak vagy egy képhívásnak adott át. Ettől lesz ez a megfelelő eszköz, valahányszor a látható tartalom már létezik: képre helyezett forró terület, táblázatcella, korábban kirajzolt szövegblokk vagy egy tördelt bekezdés egyik sora, ahogy a fenti megkerülésben. A jegyzet nulla szélességű szegélyt hordoz, tehát semmi látható nem változik; a kattintható terület pontosan az a téglalap, amelyet megad
A függvény THPDFDictionaryObject típusban adja vissza a jegyzetszótárt. A legtöbb hívó eldobja az eredményt, de ha megtartja, a dokumentum kiírása előtt módosíthatja a jegyzet bejegyzéseit
Két megfelelőségi részlet be van építve. PDF/A módokban a jegyzet nyomtatási jelzője úgy áll be, ahogy azok a szabványok megkövetelik. PDFUACompliance mellett a Description paraméter nem lehet üres karakterlánc — ebből lesz a jegyzet /Contents bejegyzése, amelyet a segítő technológia bemond a hivatkozáshoz —, és a hívás kivételt vált ki ahelyett, hogy némán nem megfelelő fájlt bocsátana ki. A PrintHyperlink megelőzi ezt a szabályt, és nem csatol leírást, tehát PDF/UA kimenethez rajzolja ki a címkét TextOut hívással, és az AddURILink hívással plusz értelmes leírással helyezze el a jegyzetet
A döntési szabály egyszerű: a PrintHyperlink hívást akkor használja, ha a hivatkozás rövid szövegdarab, amelyet még nem rajzolt ki; az AddURILink hívást akkor, ha a kattintható területet olyan tartalom határozza meg, amelyet maga rajzol vagy mér ki
Belső navigáció az AddGoToLink hívással
A külső URL-ek csak a felét adják annak, amit a hivatkozásjegyzetek tudnak. A másik fele a dokumentumon belüli navigáció — fejezetekre ugró tartalomjegyzék, szakaszok közti kereszthivatkozások. A HotPDF ezt az AddGoToLink hívással teszi elérhetővé:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Három jelentésbeli tudnivalót érdemes pontosan kimondani, mert az aláírásból egyik sem kitalálható. A TargetPageIndex nullaalapú: a dokumentum első oldala a 0. oldal, összhangban a CurrentPageNumber tulajdonsággal. A céloldalnak már léteznie kell a hívás pillanatában; ha az index a tartományon kívülre esik, az eljárás jegyzet hozzáadása nélkül tér vissza — nincs kivétel, nincs hivatkozás, nincs figyelmeztetés. Az előremutató tartalomjegyzékhez hozza létre előbb az összes oldalt, majd váltson vissza, és adja hozzá a hivatkozásokat
Az YPos a céloldalon belüli függőleges pozíciót választja ki, ugyanabban a koordinátatérben, mint a rajzolási hívásai. A -1 alapérték (bármely negatív érték) üres célkoordinátát ír ki, jelezve a megjelenítőnek, hogy tartsa meg az aktuális függőleges pozícióját, amikor a céloldalra érkezik. Adjon át nemnegatív értéket, és a megjelenítő úgy görget, hogy az a pozíció az ablak tetejére kerüljön — használja annak a címsornak az Y koordinátáját, amelyre hivatkozik. A nagyítás mindig érintetlen marad. Az AddURILink hívásához hasonlóan a Description sem lehet üres PDFUACompliance mellett, és ebből lesz a hivatkozás alternatív szövege
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; // a 0. oldalból lesz a tartalomjegyzék
// Előbb hozza létre a fejezetoldalakat, hogy a hivatkozáscélok létezzenek
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // 1..3. oldal
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Váltson vissza a 0. oldalra, és rajzolja ki a bejegyzéseket a hivatkozásaikkal
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), // a bejegyzést fedi, ráhagyással
I + 1, // nullaalapú: a fejezetek az 1..3. oldalak
780, // a címsor az ablak tetejére érkezzen
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Minden bejegyzés a szövegnél szélesebb téglalapot kap, hogy az egész sor válaszoljon a mutatóra, és minden hivatkozás úgy érkezik, hogy a fejezetcímsor (amely az Y=780 helyre van rajzolva) az ablak tetején áll. Ha később beszúr egy oldalt a fejezetek elé, minden TargetPageIndex eggyel eltolódik; az indexeket az oldal-létrehozási ciklusából számítsa, ne írja be őket fixen
Teljes dokumentumgenerálási példa
Az alábbi minta életszerűbb helyzetet mutat: rövid jelentés előállítását fejlécszakasszal, törzsszöveggel és hivatkozásokból álló láblécsorral, mindezt kódból, nem TEdit mezőkkel teli űrlapról:
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;
// Fejléc
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Törzsbekezdés helykitöltője
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Lábléchivatkozások
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;
Figyelje meg, hogy a SetFont hívás minden szöveghívás-csoport előtt szerepel. A betűkészlet nem marad meg az AddPage hívásán át, és ha elfelejti beállítani a PrintHyperlink előtt egy új oldalon, a jegyzettéglalap az oldal alapértelmezett méretadatai alapján számítódik ki, amelyek eltérhetnek attól, amire számít
Hol tér el a jegyzetkezelés a megjelenítők között?
A PDF URI-jegyzeteit az ISO 32000-1 §12.6.4.7 határozza meg, és minden megfelelő megjelenítőnek követnie kellene őket. A gyakorlatban néhány viselkedés megjelenítőnként eltér. Az Adobe Acrobat biztonsági kérdést mutat az első kattintáskor azoknál az URL-eknél, amelyek nincsenek a megbízható tartományok listáján; sok böngésző és könnyűsúlyú olvasó nem. Egyes vállalati PDF-megjelenítők zárolt környezetekben irányelv alapján teljesen letiltják az URI-jegyzeteket, tehát a kattintás nem csinál semmit, látható hiba nélkül. A mobilos PDF-alkalmazások abban is eltérnek, hogy az alkalmazás saját webnézetében nyitják-e meg a hivatkozásokat, vagy átadják a rendszerböngészőnek
Ezek egyike sem olyan hiba, amelyet a generálás oldaláról javíthat; ezek megjelenítői irányelvi döntések. Amit tehet: olyan hivatkozáscímkéket írjon, amelyek az URL-t a dokumentum törzsében is láthatóvá teszik, hogy a korlátozott környezetben lévő olvasó továbbra is kézzel másolhassa ki a címet. A jegyzet a kényelem; a szöveg a tartalék
Egy további tudnivaló: a PDF URI-jegyzetei alapból semmilyen látható aláhúzást nem hordoznak. Azt az aláhúzást, amelyet a legtöbb megjelenítőben lát, maga a megjelenítő rajzolja a jegyzet típusa alapján, nem egy jelalak a tartalomfolyamban. Ha olyan fizikai aláhúzásra van szüksége, amely túléli a nem interaktív rajzolóra való nyomtatást vagy a PDF-ből képpé alakítást, rajzolja ki kifejezetten a LineTo és a Stroke hívással a szöveg alapvonala alatti megfelelő Y eltolásnál. Ez külön rajzolási művelet, nem olyasmi, amit a PrintHyperlink elintéz Ön helyett
Az itt bemutatott hiperhivatkozás-API a HotPDF Delphi Component része, Delphihez és C++Builderhez