Skup podataka su redovi i kolone; PDF stranica je prazna koordinatna mreža bez ikakvog pojma ni o jednom ni o drugom. Premošćavanje tog jaza je čitav posao ovde. U HotPDF-u ne postoji poziv DrawTable koji prima skup podataka i pruža vam formatiranu mrežu. Ono što dobijate umesto toga su primitivi od kojih je mreža sazdana: TextOut za postavljanje niske u tačku, SetFont za izbor njenog pisma, Rectangle i Fill za senčenje trake, i MoveTo / LineTo / Stroke za crtanje linija. Radan izvoznik tabela je disciplina pretvaranja razmišljanja u redovima i kolonama u eksplicitne x i y koordinate, a zatim održavanje tih koordinata poštenim kada podaci pređu dno stranice
Primer koji sledi izveštava o zapisima kupaca, ali ništa u kodu za crtanje ne zna niti mari odakle redovi dolaze. Original je koristio zastareli TTable; FireDAC upit, skup podataka u memoriji, ili obični niz zapisa napaja iste rutine nepromenjene. Ono što je bitno je da možete proći kroz podatke jedan red po jedan i pročitati četiri tekstualna polja iz svakog. Držite iscrtavanje odvojeno od izvora podataka i možete promeniti bilo koju stranu bez ometanja druge
Geometrija kolona dolazi prva
Pre nego što se nacrta i jedan znak, odlučite gde svaka kolona živi. Ovde tabela ima četiri kolone, pa joj trebaju četiri leve ivice i poznata desna margina. Tvrdo kodiranje magičnog broja pri svakom pozivu TextOut, kako brzi primeri obično rade, je upravo ono što kasnije čini proširivanje tabele bolnim. Imenujte ivice jednom, u tačkama od koordinatnog početka u donjem levom uglu, i svaki poziv za crtanje se poziva na njih po imenu:
const
ColNo = 70; // leva ivica kolone "No."
ColName = 110; // naziv kompanije
ColAddr = 300; // adresa ulice
ColCity = 480; // grad
RowLeft = 50; // okvir tabele: leva linija
RowRight = 570; // okvir tabele: desna linija
RowStep = 20; // vertikalno rastojanje između baznih linija
procedure PrintRow(Page: THPDFPage; Y: Single;
const ANo, AName, AAddr, ACity: string; Shaded: boolean);
begin
if Shaded then
begin
// Osenčena traka iza reda. Rectangle prima 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;
Dva detalja ovde opravdavaju svoje postojanje. Osenčena traka se crta prva, a zatim tekst preko nje, jer je redosled crtanja z-red u PDF-u: popunite pravougaonik posle teksta i zatrpaćete red. A naizmenično senčenje nije ukras radi ukrasa. Na gustom izveštaju to je najjeftiniji način da se spreči da oko sklizne na pogrešan red, i zato petlja kasnije okreće bulovu vrednost na svakom redu i prosleđuje je direktno u Shaded
Pozicije kolona iznad su fiksne, što je pošteno za izveštaj čiju šemu kontrolišete. Kada su podaci promenljivi, merite umesto da nagađate. HotPDF izlaže merenje širine teksta na objektu stranice, tako da produkciona verzija PrintRow može uzeti najdužu očekivanu vrednost u svakoj koloni, izmeriti je jednom pri izabranoj veličini fonta, i izvesti leve ivice iz tih širina plus razmak. Oblik rutine se ne menja; menja se samo izvor konstanti
Zaglavlje, linije, i jedno mesto koje njima upravlja
Tabela koja se prelije preko stranice i nastavi na sledećoj bez naslova kolona je nečitljiva. Rešenje je tretirati zaglavlje kao nešto što ponovo crtate, ne kao nešto što crtate jednom. Stavite naslove kolona i horizontalne linije koje ih uokviruju u jednu rutinu, i pozovite tu rutinu i na početku i ponovo svaki put kada otvorite novu stranicu. Pošto zaglavlje i telo dele iste konstante kolona, one se poravnavaju samom konstrukcijom
procedure DrawHeader(Page: THPDFPage; var Y: Single; PageNo: Integer);
begin
// Levo: oznaka izvora i broj stranice. Desno: vreme generisanja.
Page.SetFont('Arial', [fsItalic], 10);
Page.TextOut(RowLeft, Y, 0, 'customer.db Page ' + IntToStr(PageNo));
Page.TextOut(ColCity, Y, 0, DateTimeToStr(Now));
// Dve horizontalne linije koje uokviruju naslove kolona.
Page.MoveTo(RowLeft, Y + 15);
Page.LineTo(RowRight, Y + 15);
Page.MoveTo(RowLeft, Y + 45);
Page.LineTo(RowRight, Y + 45);
Page.Stroke;
// Naslovi kolona, u debljem pismu tako da se čitaju kao naslovi.
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; // pomeri se preko uokvirenog zaglavlja pre prvog reda tela
end;
Primetite da DrawHeader prima Y po referenci i pomera ga napred. Pozivalac nikada ne mora da pamti koliko je zaglavlje visoko; rutina koja ga crta je rutina koja to zna. To pravilo jednog vlasništva je ono što sprečava da se raspored razjedini kada kasnije dodate logotip ili sažetak filtera u traku zaglavlja. Petlja tela ostaje ravnodušna. Ona samo nastavlja da crta redove odakle god Y trenutno pokazuje
Same linije su razlika između liste i tabele. Vertikalni razdvajači kolona su ista ideja primenjena na x osu: MoveTo / LineTo / Stroke na svakoj ivici kolone, koji se protežu od gornje linije do dna poslednjeg reda na stranici. Primer se drži horizontalnih linija radi čitljivosti, ali je produkcioni korak mehanički čim konstante kolona postoje
Petlja kursora upravlja prelomom stranice
Crtanje je laka polovina. Polovina koja razdvaja igračku od izveštaja je paginacija: znanje, pre nego što nacrtate red, da li još stane, i započinjanje nove stranice sa novim zaglavljem kada ne stane. Ta odluka pripada tačno jednom mestu, petlji koja prolazi kroz podatke, i nigde drugde
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;
// Naslov izveštaja, jednom, na vrhu prve stranice.
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
// Nema više mesta? Otvorite novu stranicu i ponovite zaglavlje tamo.
if Y < 60 then
begin
Pdf.AddPage;
Page := Pdf.CurrentPage; // AddPage pomera CurrentPage napred
Inc(PageNo);
Y := 760;
DrawHeader(Page, Y, PageNo);
end;
Shaded := not Shaded;
Page.SetFont('Arial', [], 10); // SetFont se mora ponovo izdati na svakoj novoj stranici
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;
Dve koordinatne činjenice pokreću celu petlju. PDF meri y naviše od donjeg levog ugla, pa redovi koračaju naniže niz stranicu oduzimanjem RowStep od Y svaki put, a test za punu stranicu se aktivira kada Y padne ispod donje margine, a ne iznad neke gornje. Pogrešite smer i vaš prvi red će se odštampati van donje ivice dok petlja misli da ima celu stranicu prostora
Druga činjenica uhvati skoro svakog jednom. AddPage pravi novu stranicu i ponovo usmerava CurrentPage na nju, ali ne prenosi ništa: ni font, ni boju popune, ni poziciju. Zato se Page ponovo čita iz CurrentPage posle svakog AddPage, i zato se SetFont ponovo izdaje pre redova tela. Preskočite ponovno čitanje i nastavljate da crtate na stranicu koju ste upravo napustili; preskočite font i nova stranica se iscrtava u kojem god podrazumevanom obliku pregledač koristi kao rezervni
Slučajevi koji lome izvoznik tabela
Većina grešaka u tabelama se ne pojavljuje na srećnom putu od nekoliko desetina urednih redova. One žive na ivicama, a ivice je jeftino testirati kada jednom znate gde su
- Prazni skupovi podataka. Petlja preko nula redova proizvodi stranicu sa zaglavljem i ničim ispod njega, što bar izgleda namerno. Prazna stranica bez zaglavlja izgleda kao greška. Odlučite šta želite pre isporuke
- Red koji sleti tačno na granicu. Generišite izveštaj čiji poslednji red sedi jedan korak iznad margine, zatim onaj čiji naredni red je jedan korak ispod nje. Paginacija sa greškom za jedan se krije sve dok podaci nisu tačno pogrešne dužine
- Predugačke vrednosti. Naziv kompanije širi od svoje kolone će se preliti u sledeću. Izmerite polje i odlučite se za politiku: prelomite u drugi red, isecite, ili skratite sa tri tačke. Ćutanje nije politika
- Null polja. Čitanje null vrednosti direktno u
TextOutmože se pojaviti kao doslovan tekstNullili kao prazno, u zavisnosti od toga kako je konvertujete. Izaberite iscrtavanje namerno umesto da konverzija varijante bira umesto vas
Provucite rezultat kroz više od jednog pregledača pre nego što proglasite da je gotovo. Zamena fonta i isecanje se različito ponašaju kroz različite rendere, a tabela koja izgleda uredno u jednom PDF čitaču može pokazati neporavnatu kolonu ili isečen grad u drugom. Potvrdite da ponovljeno zaglavlje, senčenje redova, i margine prežive prelaz, i da brojevi stranica ostanu neprekidni nakon što podaci pređu granicu
Ručno crtanje mreže umesto oslanjanja na vizuelni dizajner izveštaja je više koda, i tu razmenu vredi jasno imenovati: vi posedujete svaku koordinatu, što je tačno ono što želite za paketne poslove na serverskoj strani, fakture, i revizorske izvoze koji moraju identično da se iscrtaju na svakoj mašini, i tačno onaj režijski trošak koji biste radije izbegli za jednokratni interni spisak. Za ono prvo, kontrola se isplati prvi put kada izveštaj mora da izgleda isto u produkciji kao što je izgledao na vašem stolu
Linije i osenčene trake iznad se oslanjaju na iste vektorske i primitive boje pokrivene u vodiču kroz crtanje na platnu, ako želite da pozivi Rectangle, MoveTo, i LineTo budu obrađeni prvo za sebe. Primitivi za crtanje korišćeni ovde su deo HotPDF Delphi Component-a za Delphi i C++Builder