Generovanie reportu sa v zásade scvrkáva na umiestnenie troch vecí na stránku a zabezpečenie toho, aby sa zhodovali na svojich pozíciách: text na známych súradniciach, písma (fonty), ktoré sa vykreslia rovnako na serveri aj na vašom počítači, a obrázky so správnou veľkosťou. Všetko ostatné, čo knižnica na tvorbu reportov robí, sa točí okolo tejto trojice. HotPDF, knižnica na generovanie PDF od losLab pre Delphi a C++Builder, vám poskytuje každú z týchto možností ako priame volanie nad objektom stránky. Jediným skutočným úskalím je súradnicový systém na pozadí, ktorý funguje opačne ako plátno (canvas) VCL, na ktoré ste zvyknutí. Keď si ujasníte túto orientáciu, prestanete bojovať so zvyškom rozvrhnutia stránky.
Umiestnenie textu a počiatok vľavo dole
Takmer každý prvý report vyzerá ako obrátený hore nohami. Názov skončí blízko spodného okraja a každý riadok pod ním stúpa smerom nahor. Nejde o žiadnu chybu. Používateľský priestor PDF, definovaný v norme ISO 32000-1 §8.3, umiestňuje počiatok súradnicovej sústavy do ľavého dolného rohu, pričom os Y rastie smerom nahor. To je zrkadlovým obrazom plátna GDI, kde os Y rastie nadol od ľavého horného rohu. Päť minút strávených pochopením tohto princípu vám ušetrí rozvrhnutie, ktoré by ste inak museli prepisovať, keď by čísla prestali dávať zmysel.
Hlavným volaním objektu stránky je TextOut(X, Y, Angle, Text). Súradnice X a Y určujú pozíciu textu v bodoch (points) od ľavého dolného rohu a parameter Angle ho otáča v stupňoch, vďaka čomu môžete bez špeciálnej podpory vykresliť napríklad diagonálnu pečiatku DRAFT alebo COPY. Trik, ktorý umožní využiť vašu intuíciu z prostredia VCL, spočíva v vyjadrení osi Y ako výšky stránky mínus požadovaná vzdialenosť od horného okraja:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-0001.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE'); // 50pt from top of Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // rotated stamp
Pdf.AddPage; // CurrentPage now points here
Pdf.CurrentPage.SetFont('Arial', [], 10); // font state does not carry over
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Dve stavové správania v tomto ukážkovom kóde sú zodpovedné za väčšinu chýb, ktoré sa prejavia až na druhej stránke. Volanie AddPage presmeruje CurrentPage na novovytvorenú stránku, takže predtým uložená referencia na stránku už nevykresľuje tam, kde očakávate. Výber písma je tiež špecifický pre každú stránku, nie pre celý dokument. Ak po AddPage vynecháte SetFont, prvý TextOut na novej stránke sa vráti k predvolenému písmu, a nie k tučnému nadpisu, ktorý ste nastavili pred tromi stránkami. Bezpečným pravidlom je považovať kroky „začať novú stránku“ a „opätovne nastaviť stav textu“ za jeden neoddeliteľný krok v cykle generovania reportu.
Písma, ktoré existujú na serveri, nie iba na vašom počítači
Väčšina problémov s písmami sú v skutočnosti skryté problémy s nasadením (deploymentom). Na vašom vývojárskom počítači je nainštalované firemné písmo, takže report vyzerá na obrazovke správne a odošle sa do produkcie. Produkčný server však spúšťa úlohu pod servisným účtom, ktorý toto písmo nikdy nemal nainštalované, vykresľovač ho ticho nahradí nejakým dostupným písmom a prvý, kto si to všimne, je zákazník pýtajúci sa, prečo sa zmenil hlavičkový papier. Cesta von spočíva v tom, že prestanete dôverovať systémovému adresáru písem operačného systému a načítate písmo priamo zo súboru, ktorý váš inštalátor uloží na disk. Volanie na registráciu Unicode v HotPDF prevezme cestu k súboru a urobí presne to:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź - Ünïcode test ✓'));
Metóda TextOut priamo prijíma typ WideString, čo je dôležitejšie, než sa na prvý pohľad zdá. Meno zákazníka s diakritikou, nemecká ulica alebo poľské mesto nie sú výnimočné prípady, ale bežný obsah zákazníckych tabuliek. Prechádzajú rovnakým volaním ako pevne zakódované ASCII popisky, pokiaľ registrované písmo skutočne obsahuje potrebné glyfy. S vloženými písmami sa však spája jedno obmedzenie verzie: dokument musí byť vo verzii PDF 1.5 oder novšej. Ak vás iná požiadavka núti použiť staršiu verziu, táto funkcia ticho zlyhá. Písma písané sprava doľava, ako napríklad arabčina alebo hebrejčina, si vyžadujú skutočné tvarovanie textu a nie len priame vyhľadávanie glyfov, čo má vlastné spracovanie. Prečítajte si náš článok o tvarovaní textu pre komplexné písma v HotPDF.
Ak žiadne z nainštalovaných písiem nedokáže zobraziť to, čo potrebujete (napríklad znaky MICR na šekoch alebo špecifická sada symbolov), riešením sú písma typu Type 3. Každý glyf definujete ako malý prúd obsahu pomocou metód RegisterType3Font and AddType3Glyph. Ide o špecializovanú časť API, ktorú nebudete používať často, no je to oveľa čistejšie riešenie, než rozmiestňovať po stránke stovky malých rastrových obrázkov so symbolmi.
Obrázky: stredné argumenty sú šírka a výška, nie roh
Práca s obrázkami sa delí na dva kroky a ich oddelenie je kľúčové. Metóda AddImage prevezme objekt TBitmap alebo TJPEGImage, raz ho vloží a vráti index. Grafika vo formáte PNG musí byť pred importom dekódovaná na bitmapu. Metóda ShowImage potom vykreslí tento index kdekoľvek a akokoľvek často potrebujete. Poradie argumentov pri ShowImage je miesto, pri ktorom sa oplatí na chvíľu zastaviť:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decode PNG to a bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless for flat-color art
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle): not (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
Dve čísla nasledujúce po pozícii predstavujú šírku a výšku. Nie sú to súradnice protiľahlého rohu a posledným argumentom je uhol otočenia v stupňoch. Ak by ste signatúru čítali ako súradnice obdĺžnika X1/Y1/X2/Y2, logo s rozmermi 120x40 logo umiestnené na pozícii (50, 700) by sa namiesto toho roztiahlo od tohto bodu až po (120, 40), čím by pokrylo väčšinu stránky. Výsledok túto chybu rýchlo odhalí, no zdrojový kód vyzerá na prvý pohľad logicky, čo môže viesť k zbytočne stratenému času. Vlastnosť KeepImageAspectRatio je predvolene nastavená na True, takže rámček s nesprávnymi pomermi strán obrázok radšej oreže (letterbox) namiesto toho, aby ho deformoval. Na False ju prepnite iba vtedy, ak naozaj chcete obrázok roztiahnuť.
Rozdelenie na registráciu a umiestnenie sa oplatí najmä pri veľkých dávkach. Keďže metóda AddImage vloží pixely iba raz a každé volanie ShowImage s týmto indexom odkazuje na rovnaký vložený objekt, miesto volania AddImage priamo rozhoduje o veľkosti výsledného súboru. Ak ju zavoláte vo vnútri cyklu stránok pre 500-stranový výpis, rovnaké logo sa vloží 500-krát. Zavolajte ju raz pred cyklom, uložte si index a logo sa uloží iba raz. Malý slovník (dictionary) indexovaný cestou k súboru stačí na to, aby sa zabezpečilo, že každý jedinečný obrázok sa zaregistruje práve raz.
Voľba kodeku je ďalším nástrojom na ovplyvnenie veľkosti súboru. Fotografický obsah, naskenované prílohy a podobne patria do JPEG: odovzdajte hodnotu icJpeg metóde AddImage a znížte vlastnosť JpegQuality na približne 85. Táto vlastnosť začína na hodnote 100, pričom rozdiel pri nastavení 85 je na vytlačenej stránke nepostrehnuteľný. Jednoduchá grafika, ako sú logá, grafy a čiarové výkresy, patrí do formátu icFlate, kde je bezstratová kompresia už sama o sebe efektívna a JPEG by okolo ostrých hrán vytvoril nežiaduce artefakty. Dávka výpisov, ktorá by na každú stránku vložila jednu fotografiu v plnej kvalite, by mohla narásť do gigabajtov. Rovnaký obsah s kompresiou JPEG 85 dosiahne približne desatinu tejto veľkosti bez toho, aby si čitateľ všimol rozdiel.
Čiary, obdĺžniky a tieňovanie pomocou základných ciest
Vodorovná čiara pod hlavičkou tabuľky a sivý rámček za celkovou sumou nemusia byť obrázky. Vykreslite ich ako vektory a zostanú ostré pri akomkoľvek priblížení, vytlačia sa bez rozmazania a do súboru nepridajú takmer žiadne dáta navyše. HotPDF nasleduje rovnaký model, aký používajú priame toky obsahu v PDF: vytvorte cestu (path) a potom zavolajte operátor, ktorý ju vykreslí.
// Horizontal rule under the table header
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Shaded totals box: X, Y, width, height
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
Poradie krokov je pevne dané: nastavte stav kreslenia, zostavte cestu a potom zavolajte Stroke alebo Fill. Cesta, ktorú vytvoríte, ale nevykreslíte, nepridá na stránku nič, čo býva najčastejším dôvodom, prečo sa čiara nezobrazuje. Metóda SetRGBFillColor prijíma jednu hodnotu TColor, takže môžete použiť známe konštanty z VCL ako clNavy and clBlack. Metóda Rectangle používa rovnaké argumenty pre šírku a výšku ako umiestnenie obrázka, a nie súradnice dvoch rohov. Ešte jedno upozornenie k tenkým čiaram: čokoľvek pod pol bodu (0.5pt) môže na monitore vyzerať elegantne, no na kancelárskej tlačiarni s rozlíšením 600 dpi úplne zmizne. Preto je 0.75pt rozumnou spodnou hranicou pre akúkoľvek čiaru, ktorá má byť vytlačená.
Stránkovanie na základe reálnych dát, nie testovacích vzoriek
Jeden detail, ktorý je potrebné vyriešiť pred definitívnym návrhom rozvrhnutia: stĺpce s číselnými údajmi by mali byť zarovnané k pravému okraju. Správny spôsob, ako to urobiť, je zmerať šírku vykresleného textu pre každú hodnotu a posunúť pozíciu smerom späť od hranice stĺpca, namiesto dopĺňania reťazca medzerami na začiatku. Zarovnanie pomocou medzier funguje iba pri neproporcionálnom písme (monospace) a finančné reporty nikto nenavrhuje s takýmto písmom. Najskôr naformátujte hodnoty pomocou funkcií Delphi zohľadňujúcich národné prostredie (napr. FormatFloat), aby oddeľovač tisícov, ktorého šírku meriate, bol rovnaký, aký sa skutočne zobrazí v prostredí zákazníka.
Nebezpečenstvo pri stránkovaní spočíva v tom, že ho navrhnete pre demo dáta, kde sa desať krátkych riadkov zmestí na jednu stránku a cyklus sa nikdy nemusí zalomiť. V produkcii však narazíte na zákazníka, ktorého názov spoločnosti má 140 znakov, a výpis so 4 000 položkami. Cyklus zalomenia musí zakaždým zafungovať správne. Vzorec, ktorý sa osvedčil, využíva jediný kurzor Y, ktorý sa posúva nadol odčítaním výšky každého riadku, a kontrolu, ktorá vytvorí novú stránku vo chvíli, keď by kurzor prekročil dolný okraj. Smerom nadol tu znamená klesanie hodnoty Y, čo je jediné miesto, kde zostáva počiatok vľavo dole protichodný. Všetky tieto operácie udržiavajte v jednej rutine, ktorá tiež znova nastaví SetFont a vykreslí hlavičku na novej stránke, čím zabránite chybám s posunom o jednu stránku. Ak musia rovnaké reporty spĺňať aj archívne pravidlá alebo pravidlá prístupnosti, rozhodnutia, ktoré urobíte práve tu (ktoré písma vložíte, či je výstup štruktúrovaný tagmi, aké farebné priestory použijete), sú presne tie, na ktoré tieto štandardy dohliadajú. Preto je vhodné prečítať si príručke pre HotPDF PDF/A, PDF/X a PDF/UA ešte pred definitívnym dokončením šablóny.
Všetky predstavené volania – umiestnenie textu, registrácia písiem, vkladanie obrázkov a kreslenie ciest – sú súčasťou komponentu HotPDF Component pre Delphi a C++Builder, ktorého dokumentácia popisuje kompletné výstupné API spolu s funkciami pre formuláre, šifrovanie a podpisovanie.