PDF hipersaitai yra URI anotacijos: stačiakampis, dengiantis tam tikrą puslapio sritį ir spustelėtas nurodantis peržiūros programai atverti URL. Anotacija ir po ja esantis tekstas yra visiškai nepriklausomi objektai. HotPDF PrintHyperlink abu suriša į vieną iškvietimą: nupiešia tekstą ir apskaičiuoja anotacijos stačiakampį iš atvaizduoto teksto matmenų. Tas patogumas slepia smulkmeną, kurią verta suprasti prieš rašant gamybinį kodą. Tai taip pat ne visa istorija: AddURILink uždeda spustelimą sritį ant turinio, kurį nupiešėte patys, o AddGoToLink tvarko vidinę navigaciją — abu aptariami toliau
Kaip veikia PrintHyperlink
PrintHyperlink gyvena THPDFPage klasėje ir priima keturis argumentus: X ir Y koordinates (taškais, pradžia apatiniame kairiajame kampe, Y didėja į viršų), piešiamą etiketės eilutę ir URL tikslą. Viduje jis iškviečia TextOut dabartine hipersaito spalva, o tada iškart apskaičiuoja anotacijos stačiakampį iš TextWidth ir TextHeight pagal dabartinius šrifto matmenis. Vadinasi, šriftą ir dydį reikia nustatyti prieš iškvietimą, ir jie negali keistis tarp etiketės piešimo ir anotacijos padėjimo, nes abu išsprendžiami tame pačiame iškvietime
Numatytoji spalva yra clBlue. SetRGBHyperlinkColor ją pakeičia tik vėlesniems iškvietimams; jau įrašytų anotacijų jis atgaline data neatnaujina. Jei tame pačiame puslapyje jums reikia skirtingų spalvų skirtingoms nuorodų grupėms, iškvieskite SetRGBHyperlinkColor prieš kiekvieną grupę ir po jos atstatykite
Štai minimalus dokumentas, rašantis tris nuorodas dviem skirtingomis spalvomis:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// Numatytoji mėlyna informacinėms nuorodoms
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');
// Raudona veiksmo nuorodai
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); // atstatome numatytąją
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Koordinačių spąstai
HotPDF naudoja pradžią apatiniame kairiajame kampe su Y, augančiu į viršų, ir matuoja taškais (1/72 colio). A4 puslapis yra 595 x 842 pt; US Letter puslapis yra 612 x 792 pt. Y=750 yra netoli A4 puslapio viršaus, o Y=50 būtų netoli apatinės paraštės. Kiekvienas, atėjęs iš ekrano grafikos ar HTML, mano priešingai ir pirmąją nuorodų eilutę pastato tiesiai už matomos srities
Anotacijos stačiakampis, kurį apskaičiuoja PrintHyperlink, naudoja tą pačią koordinačių sistemą. Jei vėliau puslapį pasuksite, pakeisite jo mastelį ar dydį neperskaičiavę savo X ir Y reikšmių, matomas tekstas ir spustelimas stačiakampis nutols vienas nuo kito. Nuoroda „veikia“ ta prasme, kad spustelėjus kažkur netoli teksto suveikia URL, tačiau karštoji zona nebeatitinka to, ką skaitytojas mato. Testuokite su tuo puslapio dydžiu ir mastelio lygiu, kurį iš tikrųjų platinate, o ne vien kūrėjo mašinoje ties 100 procentų
Vienas atvejis, kai nutolimas garantuotas: jei PrintHyperlink iškviesite su A4 puslapiui tinkamomis koordinatėmis, o paskui persijungsite į savą siauro formato puslapį nepakoregavę X ir Y reikšmių, anotacija gali atsidurti visiškai už puslapio ribų. Anotacijos objektas vis tiek įrašomas į PDF; dauguma peržiūros programų jį tyliai nukerpa, todėl nuoroda tiesiog dingsta be jokios klaidos
Etiketės tekstas prieš URL tikslą
Argumentai Text ir Link yra nepriklausomi. Galite nupiešti „Download invoice PDF“, kai tikslas yra pilnai kvalifikuotas HTTPS URL su užklausos parametrais. Tas atskyrimas yra tyčinis; matoma etiketė turi būti skaitoma žmogui, o URL gali būti ilgas arba generuojamas dinamiškai
Bėdų kyla tada, kai etiketė yra pats neapdorotas URL, ypač ilgas. Jei URL vizualiai persineša per dvi eilutes, o anotacijos stačiakampis buvo apskaičiuotas vienos eilutės eilutei, spustelima bus tik pirmoji eilutė. PrintHyperlink kelių eilučių srauto netvarko; laikykite etiketę pakankamai trumpą, kad ji tilptų į vieną eilutę esant dabartiniam šrifto dydžiui ir puslapio pločiui, naudokite trumpą aprašomąją etiketę su pilnu URL kaip tikslu arba taikykite kitame skyriuje parodytą kiekvienos eilutės apėjimą
Dokumentams, kurie bus archyvuojami ar platinami be aktyvaus interneto ryšio, taip pat pasvarstykite, ar pats URL neturėtų kur nors dokumento tekste pasirodyti spausdinta forma, o ne vien kaip anotacijos metaduomenys. Skaitytojas, spausdinantis PDF ant popieriaus, iš URI anotacijos negauna nieko
Kelių eilučių apribojimo apėjimas
Kai nuorodos etiketė iš tikrųjų privalo apimti daugiau nei vieną eilutę — ilgas pažodžiui atspausdintas URL arba persineštas sakinys, kuris turi būti spustelimas nuo pradžios iki galo — sprendimas yra nustoti laikyti ją viena nuoroda ir laikyti ją po vieną nuorodą kiekvienai eilutei. Kiekvienas PrintHyperlink iškvietimas savo stačiakampį apskaičiuoja iš to teksto, kurį nupiešia, todėl keli iškvietimai su tuo pačiu Link tikslu pagamina kelias teisingo dydžio anotacijas, kurios visos atveria tą patį URL. Skaitytojas skirtumo nepastebi; kiekviena eilutė reaguoja į spustelėjimą
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;
// Naudojimas: laužykite etiketę tose vietose, kur ją laužo jūsų išdėstymas
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');
Eilutės skaidymas yra jūsų atsakomybė: laužykite ją tose pačiose vietose, kur ji vizualiai persineštų esant dabartiniam šriftui ir stulpelio pločiui, o kiekvieną kandidatinę eilutę tikrinkite su TextWidth. Alternatyva yra persineštą tekstą nupiešti patiems paprastais TextOut iškvietimais, o tada ant kiekvienos eilutės uždėti po vieną AddURILink stačiakampį — geresnis kelias, kai tekstą jau gamina jūsų pačių eilučių laužymo logika, o tai mus veda prie tos funkcijos
AddURILink: spustelimos sritys ant bet ko, ką nupiešėte
PrintHyperlink yra patogumo apvalkalas: jis pats nupiešia savo etiketę ir iš tos etiketės matmenų išveda stačiakampį. AddURILink yra tiesiogiai atskleista žemesnio lygio pusė:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
Jis rašo tik anotaciją — jokio teksto nepiešia ir jokių spalvų nekeičia. Rectangle aiškinamas toje pačioje koordinačių erdvėje kaip ir jūsų piešimo iškvietimai, todėl galite pakartotinai panaudoti tas pačias X ir Y reikšmes, kurias perdavėte TextOut ar paveikslėlio iškvietimui. Tai daro jį tinkamu įrankiu visada, kai matomas turinys jau egzistuoja: paveikslėlio karštajai sričiai, lentelės langeliui, anksčiau nupieštam teksto blokui ar vienai persinešto pastraipos eilutei, kaip aukščiau aprašytame apėjime. Anotacija neša nulinio pločio rėmelį, todėl niekas matomo nepasikeičia; spustelima sritis yra būtent jūsų nurodytas stačiakampis
Funkcija grąžina anotacijos žodyną kaip THPDFDictionaryObject. Dauguma iškvietėjų rezultatą atmeta, tačiau jį pasilikus galima pakoreguoti anotacijos įrašus prieš įrašant dokumentą
Dvi atitikties smulkmenos yra įtaisytos. PDF/A režimuose anotacijos spausdinimo požymis nustatomas taip, kaip reikalauja tie standartai. Esant PDFUACompliance, parametras Description privalo būti netuščia eilutė — ji tampa anotacijos /Contents įrašu, kurį pagalbinės technologijos ir paskelbia apie nuorodą — o iškvietimas meta išimtį, užuot tyliai išleidęs standarto neatitinkantį failą. PrintHyperlink yra senesnis už tą taisyklę ir jokio aprašymo neprideda, todėl PDF/UA išvestyje etiketę pieškite su TextOut, o anotaciją dėkite su AddURILink ir prasmingu aprašymu
Sprendimo taisyklė paprasta: naudokite PrintHyperlink, kai nuoroda yra trumpas teksto gabalas, kurio dar nenupiešėte; naudokite AddURILink, kai spustelimą sritį apibrėžia turinys, kurį piešiate ar matuojate patys
Vidinė navigacija su AddGoToLink
Išoriniai URL yra tik pusė to, ką daro nuorodų anotacijos. Kita pusė yra navigacija dokumento viduje — turinys, šokantis prie skyrių, kryžminės nuorodos tarp dalių. HotPDF tai atskleidžia per AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
Vertos tikslaus įvardijimo trys semantikos, nes nė vienos negalima atspėti iš parašo. TargetPageIndex skaičiuojamas nuo nulio: pirmasis dokumento puslapis yra puslapis 0, kaip ir CurrentPageNumber. Tikslinis puslapis iškvietimo metu privalo jau egzistuoti; jei indeksas yra už ribų, procedūra grįžta nepridėjusi anotacijos — jokios išimties, jokios nuorodos, jokio įspėjimo. Turiniui, rodančiam į priekį, pirma sukurkite visus puslapius, o tada grįžkite atgal ir pridėkite nuorodas
YPos parenka vertikalią padėtį tiksliniame puslapyje toje pačioje koordinačių erdvėje kaip ir jūsų piešimo iškvietimai. Numatytoji reikšmė -1 (bet kokia neigiama reikšmė) įrašo tuščią paskirties koordinatę, nurodančią peržiūros programai atkeliavus į tikslinį puslapį išlaikyti dabartinę vertikalią padėtį. Perduokite neneigiamą reikšmę, ir peržiūros programa slinks taip, kad ta padėtis atsidurtų lango viršuje; naudokite antraštės, į kurią rodote, Y koordinatę. Mastelis visada paliekamas nepakeistas. Kaip ir su AddURILink, esant PDFUACompliance Description privalo būti netuščias ir tampa nuorodos alternatyviuoju tekstu
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; // puslapis 0 tampa turinio puslapiu
// Pirma sukuriame skyrių puslapius, kad nuorodų tikslai egzistuotų
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // puslapiai 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// Grįžtame į puslapį 0 ir piešiame turinio įrašus su jų nuorodomis
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), // dengia įrašą su atsarga
I + 1, // nuo nulio: skyriai yra puslapiai 1..3
780, // nusileidžia su antrašte viršuje
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Kiekvienas įrašas gauna už tekstą platesnį stačiakampį, todėl į žymeklį reaguoja visa eilutė, o kiekviena nuoroda nusileidžia taip, kad skyriaus antraštė (nupiešta ties Y=780) būtų lango viršuje. Jei vėliau prieš skyrius įterpsite puslapį, kiekvienas TargetPageIndex pasislinks vienetu; indeksus skaičiuokite iš savo puslapių kūrimo ciklo, o ne rašykite juos kietai
Pilnas dokumento generavimo pavyzdys
Žemiau pateiktas modelis rodo tikroviškesnį scenarijų: trumpos ataskaitos su antraštės sekcija, tekstu ir poraštės nuorodų eilute generavimą vien iš kodo, o ne iš formos su TEdit laukais:
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;
// Antraštė
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// Teksto pastraipos vietaženklis
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// Poraštės nuorodos
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;
Atkreipkite dėmesį, kad SetFont kviečiamas prieš kiekvieną teksto iškvietimų grupę. Šriftas neišlieka per AddPage, ir jei pamiršite jį nustatyti prieš PrintHyperlink naujame puslapyje, anotacijos stačiakampis bus apskaičiuotas pagal to puslapio numatytuosius matmenis, kurie gali skirtis nuo tų, kurių tikitės
Kur anotacijų apdorojimas skiriasi tarp peržiūros programų
PDF URI anotacijos apibrėžtos ISO 32000-1 §12.6.4.7, ir kiekviena standartą atitinkanti peržiūros programa turėtų jų laikytis. Praktikoje kelios elgsenos priklauso nuo programos. Adobe Acrobat pirmą kartą spustelėjus URL, kurio nėra patikimų domenų sąraše, parodo saugumo užklausą; daugelis naršyklių ir lengvųjų skaityklių to nedaro. Kai kurios įmonių PDF peržiūros programos suvaržytose aplinkose URI anotacijas visiškai išjungia pagal politiką, todėl spustelėjimas nedaro nieko ir jokios matomos klaidos nerodo. Mobiliosios PDF programėlės skiriasi tuo, ar nuorodas atveria programėlės viduje esančioje interneto peržiūroje, ar perduoda sistemos naršyklei
Nė viena iš šių elgsenų nėra klaida, kurią galėtumėte pataisyti generavimo pusėje; tai peržiūros programų politikos sprendimai. Ką galite padaryti, tai rašyti nuorodų etiketes taip, kad URL būtų matomas ir dokumento tekste, ir tada skaitytojas suvaržytoje aplinkoje vis tiek galės adresą nusikopijuoti ranka. Anotacija yra patogumas; tekstas yra atsarga
Dar viena smulkmena, kurią verta žinoti: PDF URI anotacijos pagal nutylėjimą neneša jokio matomo pabraukimo. Pabraukimą, kurį matote daugumoje peržiūros programų, piešia pati programa pagal anotacijos tipą, o ne glifas turinio sraute. Jei jums reikia fizinio pabraukimo, išliekančio spausdinant į neinteraktyvų atvaizdiklį ar konvertuojant PDF į paveikslėlį, nupieškite jį atvirai su LineTo ir Stroke tinkamu Y poslinkiu žemiau teksto bazinės linijos. Tai atskira piešimo operacija, o ne kažkas, ką PrintHyperlink padaro už jus
Čia parodytas hipersaitų API yra HotPDF Delphi komponento, skirto Delphi ir C++Builder, dalis