Techninis straipsnis

HotPDF Delphi hipersaitai: PrintHyperlink ir anotacijos

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

Vieno HotPDF PrintHyperlink iškvietimo anatomija, rašanti du nepriklausomus PDF objektus: TextOut nupieštus matomus etiketės glifus ir URI nuorodos anotacijos stačiakampį, apskaičiuotą iš TextWidth ir TextHeight
Etiketės glifai ir URI stačiakampis yra atskiri PDF objektai, todėl šriftas ir hipersaito spalva turi nusistovėti dar prieš vieną iškvietimą, rašantį abu

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ą

Persineštos HotPDF hipersaito etiketės palyginimas: viena anotacija, dengianti tik pirmąją eilutę, prieš vieną PrintHyperlink iškvietimą kiekvienai atvaizduotai eilutei su tuo pačiu URL tikslu
Vienai eilutei apskaičiuotas stačiakampis palieka nuošalyje kiekvieną persineštą tęsinį, o iškvietimai kiekvienai eilutei dalijasi tikslu ir išlaiko visą bloką spustelimą
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

HotPDF: su AddGoToLink sukurtas nuorodomis susietas turinys, rodantis nuo nulio skaičiuojamus TargetPageIndex šuolius nuo turinio puslapio prie skyrių puslapių, kur kiekviena antraštė atsiduria lango viršuje
Stačiakampiai tęsiasi už teksto ribų, kad reaguotų visos eilutės, o fiksuota nusileidimo Y kiekvieną skyriaus antraštę pastato lango viršuje
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