Ustvarjanje poročila se zvede na postavitev treh elementov na stran in njihovo medsebojno uskladitev: besedila na določenih koordinatah, pisav, ki se na strežniku izrišejo enako kot na vašem namizju, in slik ustrezne velikosti. Vse ostalo, kar počne knjižnica za poročila, je organizirano okoli teh treh elementov. Knjižnica HotPDF, losLabovo orodje za ustvarjanje PDF-jev v Delphi in C++Builderju, ponuja vsakega od njih kot neposreden klic na objektu strani, edina resnična ovira pa je koordinatni sistem, ki deluje ravno obratno od tistega na platnu VCL (canvas), ki ste ga vajeni. Najprej razčistite to usmeritev in preostalo delo z razporeditvijo vam ne bo več povzročalo preglavic.
Postavitev besedila in izhodišče spodaj levo
Skoraj vsako prvo poročilo je obrnjeno na glavo. Naslov se pojavi blizu spodnjega roba, vsaka naslednja vrstica pa se vzpenja proti vrhu. Nič ne deluje napačno. Uporabniški prostor PDF, definiran v standardu ISO 32000-1 §8.3, postavlja izhodišče v spodnji levi kot, pri čemer Y narašča navzgor, kar je zrcalna slika platna GDI, kjer Y narašča navdol iz zgornjega levega kota. Pet minut, ki jih porabite, da se sprijaznite s tem, vam prihrani preureditev celotne postavitve.
Osrednji klic objekta strani je TextOut(X, Y, Angle, Text). Koordinati X in Y določata položaj besedila v točkah od spodnjega levega kota, parameter Angle pa ga zasuče v stopinjah, kar omogoča preprost izris diagonalnega žiga DRAFT ali COPY. Trik, ki omogoča uporabo intuicije iz VCL, je izraziti koordinato Y kot višino strani, od katere odštejete želeno razdaljo od vrha:
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;
Obe stanji (stateful behavior) v zgornjem primeru sta odgovorni za večino hroščev, ki se pojavijo šele na drugi strani. Metoda AddPage preusmeri CurrentPage na stran, ki jo je pravkar ustvarila, zato sklic na prejimšjo stran ne bo več izrisoval tam, kjer pričakujete. Tudi izbira pisave poteka na ravni posamezne strani in ne celotnega dokumenta. Če izpustite klic SetFont po klicu AddPage, bo prvi TextOut na novi strani uporabil privzeto pisavo te strani in ne krepke pisave naslova, ki ste jo nastavili prej. Varno pravilo je, da začetek nove strani in ponovno nastavitev stanja besedila obravnavate kot en neločljiv korak v zanki poročila.
Pisave, ki obstajajo na strežniku in ne le na vašem namizju
Večina težav s pisavami so dejansko težave z namestitvijo (deployment). Na vašem razvojnem računalniku je nameščena pisava podjetja, zato je poročilo videti pravilno in ga oddate. Produkcijski strežnik pa poganja opravilo pod sistemskim računom, ki te pisave nima nameščene, zato upodabljalnik tiho uporabi nadomestno pisavo, ki jo najde, prva novica o tem pa je pritožba stranke, da se je glava dopisa spremenila. Rešitev je, da ne zaupate sistemskemu imeniku pisav, temveč naložite pisavo iz datoteke, ki jo vaš namestitveni program odloži na disk. Registracijski klic Unicode v HotPDF sprejme pot do datoteke in stori natanko 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 ✓'));
Metoda TextOut neposredno sprejme tip WideString, kar je pomembnejše, kot se zdi na prvi pogled. Ime stranke z naglasom, nemška ulica, poljsko mesto: to niso izjemni primeri, temveč običajna vsebina tabel, in vsi gredo skozi isti klic kot trdo kodirane oznake ASCII, če le registrirana pisava vsebuje te znake (glife). Z vgrajenimi pisavami je povezana ena omejitev različice: dokument mora biti PDF 1.5 or novejši. Če vas druga zahteva veže na starejšo različico, bo to tisto, kar bo tiho odpovedalo. Pisave, ki se berejo od desne proti levi, kot sta arabščina in hebrejščina, potrebujejo dejansko oblikovanje (shaping) in ne le preprosto iskanje znakov, kar ima lasten postopek; oglejte si naš članek o oblikovanju besedila s kompleksnimi pisavami v HotPDF.
Kadar nobena nameščena pisava ne more izraziti tega, kar potrebujete (na primer znaki MICR na čeku ali lastniški nabor simbolov), vrzel zapolnijo pisave Type 3. Vsak glif definirate kot majhen tok vsebine prek RegisterType3Font in AddType3Glyph. To je specializiran del API-ja, ki ga boste redko potrebovali, vendar je veliko bolj eleganten kot razprševanje stotih majhnih sličic simbolov po strani.
Slike: srednji argumenti so širina in višina, ne vogal
Delo s slikami je razdeljeno na dva koraka in njuno ločevanje je ključnega pomena. Metoda AddImage sprejme TBitmap ali TJPEGImage, jo enkrat vgradi in vrne indeks. Slike PNG je treba pred tem dekodirati v bitmap. Metoda ShowImage nato izriše ta indeks kjerkoli in kolikorkrat želite. Vrstni red argumentov pri ShowImage je del, ki ga je vredno prebrati zanesljivo počasi:
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;
Številki za položajem predstavljata širino in višino. To niso koordinate nasprotnega vogala, zadnji argument pa je kot zasuka v stopinjah. Če podpis preberete kot polje X1/Y1/X2/Y2, se bo logotip velikosti 120 x 40, postavljen na (50, 700), raztegnil od tam do (120, 40) in se razprostrl čez večino strani. Rezultat to napako takoj razkrije, čeprav je izvorna koda videti povsem logična. Lastnost KeepImageAspectRatio je privzeto nastavljena na True, logo okvir z napačnimi razmerji sliko obreže ali doda robove, namesto da bi jo popačil; na False jo preklopite le, če jo želite namerno raztegniti.
Ločitev med registracijo in postavitvijo se obrestuje pri večjih dokumentih. Ker AddImage vgradi slikovne pike le enkrat, vsak klic ShowImage s tem indeksom pa kaže na isti vgrajeni objekt, mesto klica AddImage določa končno velikost datoteke. Če jo pokličete znotraj zanke strani za 500-stranski dokument, bo isti logotip vgrajen 500-krat. Pokličite jo enkrat pred zanko, shranite indeks in logotip bo shranjen le enkrat. Preprost slovar, indeksiran s potjo do vira, zadostuje za zagotovilo, da se vsaka slika registrira natanko enkrat.
Izbira kodeka je drugi vzvod za uravnavanje velikosti. Fotografska vsebina, skenirane priloge in podobno sodijo v format JPEG: metodi AddImage posredujte icJpeg in zmanjšajte lastnost JpegQuality na približno 85, saj se ta začne pri 100, razlika pri 85 pa je na tiskani strani neopazna. Enobarvne grafike, kot so logotipi, diagrami in risbe, sodijo v icFlate, kjer je stiskanje brez izgub že samo po sebi učinkovito, JPEG pa bi okoli ostrih robov ustvaril opazne šume. Obdelava, ki na vsako stran doda eno fotografijo polne kakovosti, lahko datoteka napihne v gigabajte; enaka vsebina pri JPEG 85 pa zavzame le desetino te velikosti, česar bralec ne bo opazil.
Črte, okvirji in senčenje s potmi
Vodoravna črta pod glavo tabele in sivo polje za skupnimi zneski ne potrebujeta biti sliki. Narišite jih kot vektorje in ostali bodo ostri pri vsaki povečavi, natisnjeni bodo jasno in datoteki ne bodo dodali skoraj ničesar. HotPDF sledi enakemu modelu kot tokovi surovih vsebin PDF: zgradi pot in nato pokliče operaterja, ki jo izriše.
// 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;
Vrstni red ni izbiren: nastavite stanje barvanja, zgradite pot in nato pokličite Stroke ali Fill. Pot, ki jo zgradite, a je nikoli ne pobarvate, ne prispeva ničesar k strani, kar je skoraj vedno razlog, da se črta "ne prikaže". Metoda SetRGBFillColor sprejme eno vrednost TColor, zato lahko uporabite znane konstante VCL, kot sta clNavy in clBlack, metoda Rectangle pa uporablja enake argumente za širino in višino kot postavitev slik in ne dveh vogalov. Opozorilo glede tankih črt: vse, kar je pod polovico točke, je na zaslonu lahko videti elegantno, na pisarniškem tiskalniku z ločljivostjo 600 dpi pa lahko izgine, zato je 0.75pt primerna spodnja meja za črte, ki morajo preživeti tiskanje.
Stranitev (paginacija) glede na dejanske in ne vzorčne podatke
Podrobnost, ki jo je treba urediti pred dokončno razporeditvijo: številčni stolpci morajo biti poravnani po desnem robu. To storite tako, da izmerite izrisano širino vsake vrednosti in jo postavite nazaj od meje stolpca, ne pa da niz dopolnjujete z vodilnimi presledki. Dopolnjevanje s presledki deluje le pri pisavi s fiksno širino znakov (monospace), nihče pa finančnega poročila ne oblikuje v takšni pisavi. Vrednosti najprej pošljite skozi sistemske funkcije Delphi, kot je FormatFloat, da bo ločilo tisočic, katerega širino merite, enako tistemu, ki ga bo dejansko prikazal uporabnikov lokalni sistem.
Nevarnost pri stranišču (stranitvi) je v tem, da jo napišete za testni nabor podatkov, kjer se deset kratkih vrstic prilega eni strani in se zanka nikoli ne preklopi. V produkciji pa prejmete stranko, katere ime podjetja obsega 140 znakov, in izpisek s 4.000 postavkami, kjer se mora zanka vsakič pravilno prelomiti. Vzorec, ki deluje, je en sam kazalec Y, ki se premika navzdol, ko odštevate višino vsake vrstice, in preverjanje, ki začne novo stran v trenutku, ko bi kazalec prečkal spodnji rob. Premikanje navzdol pomeni zmanjševanje vrednosti Y, kar je edino mesto, kjer spodnje levo izhodišče ostaja protislovno. Vse to obdržite v eni rutini, ki ponovno izda SetFont in izriše glavo na novi strani, pa se hrošči z napačnim številom strani ne bodo pojavili. Ko morajo enaka poročila izpolnjevati arhivske standarde ali standarde dostopnosti, so odločitve, ki jih sprejmete tukaj (katere pisave vgraditi, ali je izhod označen in katere barvne prostore uporabiti), ravno tiste, ki jih ti standardi nadzorujejo; vodnik po HotPDF za PDF/A, PDF/X in PDF/UA je vsekakor vredno prebrati, preden predlogo zaklenete.
Vsak prikazan klic (določanje položaja besedila, registracija pisav, vgradnja slik in risanje poti) je del komponente HotPDF za Delphi in C++Builder, katere referenca dokumentira celoten izhodni API skupaj s funkcijami za obrazce, šifriranje in podpisovanje.