Nabor podatkov sestavljajo vrstice in stolpci, stran PDF pa je prazna koordinatna mreža brez kakršne koli tovrstne predstave. Premostitev tega razkoraka je glavna naloga tega članka. V knjižnici HotPDF ni klica DrawTable, ki bi sprejel nabor podatkov in vrnil oblikovano mrežo. Namesto tega prejmete osnovne gradnike mreže: metodo TextOut za postavitev niza na točko, SetFont za izbiro pisave, Rectangle in Fill za senčenje pasov ter MoveTo / LineTo / Stroke za izris črt. Delujoč izvoznik tabel zahteva prenos razmišljanja o vrsticah in stolpcih v eksplicitne koordinate X in Y ter ohranjanje njihove pravilnosti, ko podatki presegajo spodnji rob strani.
Spodnji primer izpisuje podatke o strankah, vendar koda za risanje ne ve in je ne zanima, od kod vrstice izvirajo. Prvotni primer je uporabljal zastarel TTable, vendar pa poizvedba FireDAC, nabor podatkov v pomnilniku (in-memory dataset) ali preprosto polje zapisov napajajo enake rutine brez sprememb. Pomembno je le to, da se lahko premikate po podatkih vrstico za vrstico in iz vsake preberete štiri besedilna polja. Ločite upodabljanje od vira podatkov in lahko spreminjate katero koli stran, ne da bi motili drugo.
Najprej geometrija stolpcev
Preden izrišete en sam znak, določite položaj vsakega stolpca. Tabela v tem primeru ima štiri stolpce, zato potrebuje štiri leve robove in znan desni rob. Trdo kodiranje poljubnih številk ob vsakem klicu TextOut (kot se to pogosto dela v enostavnih primerih) je natanko tisto, kar oteži kasnejše širjenje tabele. Poimenujte robove enkrat (v točkah od spodnjega levega izhodišča) in vsak risarski klic se bo nanje skliceval po imenu:
const
ColNo = 70; // left edge of the "No." column
ColName = 110; // company name
ColAddr = 300; // street address
ColCity = 480; // city
RowLeft = 50; // table frame: left rule
RowRight = 570; // table frame: right rule
RowStep = 20; // vertical distance between baselines
procedure PrintRow(Page: THPDFPage; Y: Single;
const ANo, AName, AAddr, ACity: string; Shaded: boolean);
begin
if Shaded then
begin
// A shaded band behind the row. Rectangle takes X, Y, Width, Height.
Page.SetRGBFillColor($00FFF3DD);
Page.Rectangle(RowLeft, Y - 4, RowRight - RowLeft, RowStep);
Page.Fill;
Page.SetRGBFillColor(clBlack);
end;
Page.TextOut(ColNo, Y, 0, ANo);
Page.TextOut(ColName, Y, 0, AName);
Page.TextOut(ColAddr, Y, 0, AAddr);
Page.TextOut(ColCity, Y, 0, ACity);
end;
Tukaj sta pomembni dve podrobnosti. Senčen pas se izriše najprej, nato pa besedilo na njem, saj je vrstni red risanja v PDF-ju enak vrstnemu redu plasti (z-order): če pravokotnik pobarvate po izrisu besedila, boste vrstico prekrili. Izmenično senčenje pa ni samo sebi namen. V zgoščenem poročilu je to najlažji način, da bralcu preprečite drsenje oči v napačno vrstico, zato zanka kasneje obrne logično vrednost pri vsaki vrstici in jo posreduje parametru Shaded.
Položaji stolpcev zgoraj so fiksni, kar je primerno za poročilo z znano shemo. Ko so podatki spremenljivi, jih raje izmerite, kot da ugibate. HotPDF omogoča merjenje širine besedila na objektu strani, zato lahko produkcijska različica metode PrintRow sprejme najdaljšo pričakovano vrednost v vsakem stolpcu, jo enkrat izmeri pri izbrani velikosti pisave in izpelje leve robove iz teh širin z dodanim razmikom. Zgradba same rutine se ne spremeni, spremeni se le izvor konstant.
Glava, črte in eno mesto, ki jih nadzoruje
Tabela, ki se prelije na naslednjo stran brez ponovljenih oznak stolpcev, je neberljiva. Rešitev je v tem, da glavo obravnavate kot nekaj, kar rišete znova, in ne le enkrat. Naslove stolpcev in vodoravne črte, ki jih uokvirjajo, postavite v eno rutino ter jo pokličite tako na začetku kot ob vsakem odpiranju nove strani. Ker glava in telo tabele souporabljata enake konstante stolpcev, se bosta samodejno poravnala.
procedure DrawHeader(Page: THPDFPage; var Y: Single; PageNo: Integer);
begin
// Left: source label and page number. Right: generation time.
Page.SetFont('Arial', [fsItalic], 10);
Page.TextOut(RowLeft, Y, 0, 'customer.db Page ' + IntToStr(PageNo));
Page.TextOut(ColCity, Y, 0, DateTimeToStr(Now));
// Two horizontal rules that box the column titles.
Page.MoveTo(RowLeft, Y + 15);
Page.LineTo(RowRight, Y + 15);
Page.MoveTo(RowLeft, Y + 45);
Page.LineTo(RowRight, Y + 45);
Page.Stroke;
// The column titles, in a heavier face so they read as headings.
Page.SetFont('Times New Roman', [fsBold], 12);
Page.SetRGBFillColor(clNavy);
PrintRow(Page, Y + 25, 'No.', 'Company', 'Address', 'City', False);
Page.SetRGBFillColor(clBlack);
Y := Y + RowStep + 45; // advance past the boxed header before the first body row
end;
Upoštevajte, da DrawHeader sprejme Y kot referenčni parameter (var) in ga premakne naprej. Klicatelju si ni treba zapomniti višine glave; rutina, ki jo izriše, je tista, ki to ve. To pravilo enega lastništva preprečuje razhajanje postavitve, če kasneje v glavo dodate logotip ali povzetek filtrov. Zanka telesa tabele se s tem ne obremenjuje. Preprosto riše vrstice od tam, kamor trenutno kaže Y.
Črte same po sebi ločijo seznam od tabele. Navpične ločnice stolpcev so ista ideja, preslikana na os X: klici MoveTo / LineTo / Stroke na vsakem robu stolpca, ki potekajo od zgornje črte do dna zadnje vrstice na strani. Ta primer se zaradi berljivosti omejuje na vodoravne črte, vendar je produkcijski korak povsem enak, ko so konstante enkrat določene.
Zanka kazalca nadzoruje prelom strani
Risanje je lažji del. Del, ki loči preprosto igračo od pravega poročila, pa je stranitev (paginacija): pred izrisom vrstice morate vedeti, ali se še prilega strani, in začeti novo stran s svežo glavo, ko se ne. Ta odločitev sodi na eno samo mesto – v zanko, ki se sprehaja po podatkih, in nikamor drugam.
var
Pdf: THotPDF;
Page: THPDFPage;
Y: Single;
PageNo: Integer;
Shaded: boolean;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'CustomerReport.pdf';
Pdf.BeginDoc;
Page := Pdf.CurrentPage;
// Report title, once, at the top of the first page.
Page.SetFont('Arial', [fsBold], 24);
Page.TextOut(200, 800, 0, 'Customer Report');
PageNo := 1;
Y := 760;
DrawHeader(Page, Y, PageNo);
Shaded := False;
CustomerTable.First;
while not CustomerTable.Eof do
begin
// Out of room? Open a new page and repeat the header there.
if Y < 60 then
begin
Pdf.AddPage;
Page := Pdf.CurrentPage; // AddPage moves CurrentPage forward
Inc(PageNo);
Y := 760;
DrawHeader(Page, Y, PageNo);
end;
Shaded := not Shaded;
Page.SetFont('Arial', [], 10); // SetFont must be reissued on every new page
PrintRow(Page, Y,
VarToStr(CustomerTable['CustNo']),
VarToStr(CustomerTable['Company']),
VarToStr(CustomerTable['Addr1']),
VarToStr(CustomerTable['City']),
Shaded);
Y := Y - RowStep;
CustomerTable.Next;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Celotno zanko usmerjata dve koordinatni dejstvi. PDF meri koordinato Y navzgor od spodnjega levega kota, zato se vrstice pomikajo navzdol po strani z odštevanjem vrednosti RowStep od Y pri vsakem koraku, preizkus polne strani pa se sproži, ko se Y spusti pod spodnjo mejo in ne nad zgornjo. Če zgrešite smer, se bo prva vrstica izrisala izven spodnjega roba, medtem ko bo zanka verjela, da ima še celo stran prostora.
Drugo dejstvo pa skoraj vsakogar vsaj enkrat preseneti. Metoda AddPage ustvari novo stran in nanjo preusmeri CurrentPage, vendar ne prenese nastavitev: ne pisave, ne barve polnila, ne položaja. Zato je treba Page ponovno prebrati iz CurrentPage po vsakem klicu AddPage ter znova nastaviti pisavo pred izrisom vrstic telesa tabele. Če izpustite ponovno branje strani, boste nadaljevali z risanjem na stran, ki ste jo pravkar zapustili; če izpustite nastavitev pisave, se bo nova stran izrisala v privzetih nastavitvah pregledovalnika.
Primeri, ki zrušijo izvoznik tabel
Večina hroščev pri tabelah se ne pojavi na idealni poti z nekaj desetinami urejenih vrstic. Pojavijo se na robovih, ki pa jih je enostavno preizkusiti, ko veste, kje so.
- Prazni nabori podatkov. Zanka nad nič vrsticami ustvari stran z glavo in ničemer pod njo, kar je vsaj videti namerno. Popolnoma prazna stran brez glave pa deluje kot napaka. Pred izdajo se odločite, kaj želite prikazati.
- Vrstica, ki pristane natanko na meji. Ustvarite poročilo, pri katerem zadnja vrstica stoji en korak nad robom, nato ja takšnega, kjer je naslednja vrstica en korak pod njim. Napake pri prelomih strani zaradi odstopanj za eno vrstico ostanejo skrite, dokler podatki niso natanko napačne dolžine.
- Predolge vrednosti. Ime podjetja, ki je širše od svojega stolpca, se bo prelilo v naslednjega. Izmerite polje in se odločite za pravilo: prelom v drugo vrstico, obrezovanje ali krajšanje s tripičjem. Ignoriranje težave ni rešitev.
- Prazna polja (Null). Branje vrednosti null neposredno v metodo
TextOutse lahko prikaže kot dobesedno besediloNullali kot praznina, odvisno od načina pretvorbe. Izberite način izrisa zavestno, namesto da prepustite odločitev pretvorbi tipa variant.
Preden delo označite kot končano, pošljite rezultat skozi več kot en pregledovalnik. Zamenjava pisav in obrezovanje se med upodabljalniki razlikujeta, tabela, ki je v enem bralniku videti poravnana, pa lahko v drugem prikaže zamaknjen stolpec ali obrezano ime mesta. Potrdite, da ponovljena glava, senčenje vrstic in robovi preživijo selitev ter da številčenje strani ostane neprekinjeno po prelomu.
Ročni izris mreže namesto uporabe vizualnega oblikovalca poročil zahteva več kode, in kompromis je vredno jasno opredeliti: nadzorujete vsako koordinato, kar je natanko tisto, kar želite pri strežniških serijskih opravilih, računih in revizijskih izvozih, ki se morajo na vsakem stroju izrisati identično, in natanko tisti režijski strošek, ki se mu želite izogniti pri enkratnih internih izpisih. Pri prvih se popoln nadzor obrestuje takoj, ko mora poročilo v produkciji izgledati povsem enako kot na vaši mizi.
Črte in osenčeni pasovi zgoraj slonijo na enakih vektorskih in barvnih osnovah, ki so opisane v vodniku po risanju na platno, če želite najprej spoznati samostojno delovanje klicov Rectangle, MoveTo in LineTo. Risarski gradniki, uporabljeni tukaj, so del komponente HotPDF za Delphi in C++Builder.