PDFium ima reputaciju mehanizma za pregledavanje (viewer engine), odnosno renderera koji stoji iza Chromeove PDF kartice, stoga najprije treba razjasniti da PDFium VCL također može izgraditi dokument koji nikada prije nije postojao. Strana za kreiranje dokumenata omotava PDFium-ov API za objekte stranice (page-object API): stvarate prazan dokument, dodajete stranice s eksplicitnim dimenzijama te postavljate tekst, vektorske putanje i slike na svaku stranicu na koordinatama koje sami odaberete. Nema jezika za opis stranice koji morate učiti niti upravljačkog programa za pisač. Pozivate metode, knjižnica sastavlja PDF objekte, a SaveAs serijalizira rezultat.
Ono što ne dobivate je mehanizam za raspoređivanje sadržaja (layout engine). To je dovoljno važno naglasiti odmah na početku jer definira svaki primjer u nastavku. PDFium VCL postavlja sadržaj isključivo tamo gdje mu kažete, u apsolutnim koordinatama, i nigdje drugdje. Neće sam prelomiti odlomak, prebaciti tekst na sljedeću stranicu niti izračunati tablicu iz redaka i stupaca. To je vaš zadatak. Ako ste došli očekujući nešto što prelama tekst poput procesora teksta (word processor), prilagodite svoja očekivanja: ovo je precizan API niske razine za pozicioniranje, sličniji crtanju na platnu nego slaganju teksta u dokumentu. Za generirane račune, certifikate, naljepnice i stranice izvještaja gdje već znate gdje svaki element pripada, ta preciznost je upravo ono što želite.
Minimum potreban za stvaranje datoteke
Tri poziva dijele prazan TPdf od spremljenog PDF-a: stvaranje dokumenta, dodavanje stranice i zapisivanje na disk. Sve ostalo je sadržaj koji umećete u međuvremenu.
uses
Vcl.Graphics, // for clBlack and TColor
PDFium; // TPdf lives here
procedure CreateBlankPdf(const FileName: string);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // empty in-memory document
Pdf.AddPage(0, 595, 842); // A4 portrait, in points
Pdf.AddText('First page', 'Arial', 18, 50, 780);
Pdf.SaveAs(FileName); // serialize to disk
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Jedan detalj može zbuniti one koji su vidjeli starije isječke koda: ne dodjeljujete Pdf.Active := True nakon poziva CreateDocument. Svojstvo Active prijavljuje postoji li pokazivač (handle) dokumenta, a CreateDocument ga je već stvorio, pa je to svojstvo True čim se taj poziv vrati. Ponovno postavljanje u najboljem slučaju ne radi ništa, a u najgorem zbunjuje sljedećeg čitatelja koda. Svojstvo Active opravdava svoje postojanje na odlasku: dodjeljivanje vrijednosti False oslobađa dokument prije poziva Free, što je ispravan redoslijed zatvaranja. Tretirajte CreateDocument i otvaranje datoteke kao međusobno isključive radnje. Knjižnica odbija stvoriti novi dokument na TPdf objektu koji već ima otvorenu datoteku, pa ponovna upotreba zahtijeva najprije zatvaranje trenutnog dokumenta.
Koordinate počinju u donjem lijevom kutu
Drugi par argumenata u metodi AddText, kao i u svakom pozivu za pozicioniranje, predstavlja točku u korisničkom prostoru PDF-a. Ishodište se nalazi u donjem lijevom kutu stranice, X ide udesno, a Y ide prema gore. Jedna jedinica je jedna točka (point), odnosno 1/72 inča, pa stranica A4 formata ima 595 × 842 jedinice, a US Letter 612 × 792. Ta os Y koja raste prema gore najčešći je izvor zabune u kojoj programeri misle da im je tekst pobjegao sa stranice, jer koordinate zaslona i bitmapa postavljaju ishodište na vrh, pri čemu Y raste prema dolje. Na stranici visine 842 točke, naslov pri vrhu nalazi se oko Y = 780, a ne Y = 60. Kada tekst završi na neočekivanom mjestu, visina stranice minus vaš Y gotovo je uvijek broj koji ste zapravo namjeravali upotrijebiti.
Metoda AddPage prima poziciju umetanja kao svoj prvi argument (indeksiran od 1), pri čemu je 0 prikladna kratica za "početak dokumenta". Proslijedite 0 ili 1 za prvu stranicu i stranica će biti umetnuta na početak; proslijedite vrijednost koja odgovara trenutnom broju stranica kako biste je dodali na kraj. Novododana stranica također postaje trenutna stranica, ona na koju se odnose sljedeći pozivi za crtanje, tako da nema potrebe za zasebnim korakom odabira stranice nakon njezina dodavanja. Ako dodate više stranica i kasnije trebate pisati po nekoj ranijoj, postavite svojstvo PageNumber kako biste pomaknuli kursor; dok stranice punite redom kako ih stvarate, možete ga ostaviti na miru.
Pisanje teksta i pravilo o fontovima koje tiho stvara probleme
Deklaracija metode AddText sadrži sve što je potrebno za jedan ispis teksta: niz znakova, naziv fonta, veličinu u točkama, sidro X i Y, te opcionalno boju, bajt alfa kanala za prozirnost i kut rotacije u stupnjevima.
procedure WriteHeader(Pdf: TPdf; const Title, Author: string);
begin
// Title in black, default opacity, no rotation
Pdf.AddText(Title, 'Arial', 20, 50, 780);
// A lighter byline 24 points below it
Pdf.AddText('By ' + Author, 'Arial', 11, 50, 756, clGray);
// A faint diagonal draft stamp across the page
Pdf.AddText('DRAFT', 'Arial', 64, 180, 380, clGray, $30, 45.0);
end;
Alfa bajt se kreće od $00 (nevidljivo) do $FF (neprozirno), što pretvara oznaku nacrta u vodeni žig umjesto u čvrsti blok: $30 je otprilike devetnaest posto neprozirnosti, dovoljno da se tekst ispod može čitati. Kut rotira ispis u smjeru suprotnom od kazaljke na satu oko njegovog sidra, pa 45 stupnjeva daje klasičan dijagonalni pečat od kuta do kuta. Za ovo nije potrebna nikakva posebna značajka vodenog žiga. Vodeni žig je jednostavno veliki, poluprozirni, rotirani poziv AddText, a to hoćete li ga nacrtati prije ili poslije glavnog sadržaja određuje sjeda li iza ili ispred teksta.
Fontovi zaslužuju pažnju jer je način zakazivanja vrlo tih. Kada proslijedite naziv fonta, PDFium VCL traži od operacijskog sustava TrueType podatke tog fonta i ugrađuje ih u dokument, zbog čega se datoteka stvorena na vašem računalu prikazuje identično i na računalu koje taj font uopće nema instaliran. Problem nastaje kada se naziv ne može razriješiti: zbog tipfelera ili fonta koji jednostavno nije prisutan na računalu na kojem se gradi dokument. Nema nikakve iznimke. Knjižnica se vraća na stvaranje tekstualnog objekta koji nosi naziv samo kao oznaku, bez ugrađenih podataka, i prepušta pregledniku da zamijeni font nečim što smatra sličnim. Tekst se pojavljuje u vašim testovima, izgleda u redu, a mijenja metriku ili glifove onog trenutka kada se datoteka otvori na sustavu s drugačijim fontovima. Koristite nazive za koje znate da su prisutni na računalu koje generira dokument, tretirajte popis fontova kao ovisnost pri isporuci i otvorite uzorak u pregledniku na čistom sustavu prije nego što povjerujete izlazu.
Vektorski oblici: izgradite putanju, a zatim je primijenite
Linije, pravokutnici i ispunjene regije idu kroz putanju. Otvarate je pomoću CreatePath, koja postavlja početnu točku i sve stilove odjednom: način ispune, boje ispune i obrub s njihovim alfa bajtovima, širinu obruba, završetke linija i spojeve. Zatim je proširujete s LineTo, BezierTo i ClosePath, i na kraju AddPath primjenjuje gotovu putanju na stranicu. Korak primjene (commit) lako je zaboraviti, a ako ga preskočite, ništa se neće nacrtati.
procedure DrawDivider(Pdf: TPdf; X, Y, Width: Single);
begin
// A thin horizontal rule. The rectangle overload sets a box directly:
// X, Y, Width, Height, then fill mode and colors.
Pdf.CreatePath(X, Y, Width, 0.5, fmNone, clBlack, $FF,
True, clBlack, $FF, 1.0);
Pdf.AddPath;
end;
procedure DrawTriangle(Pdf: TPdf);
begin
// Point overload: start at the first vertex, line to the rest, close.
Pdf.CreatePath(200, 300, fmWinding, clBlue, $80, True, clNavy, $FF, 2.0);
Pdf.LineTo(300, 300);
Pdf.LineTo(250, 400);
Pdf.ClosePath;
Pdf.AddPath; // nothing is drawn until this runs
end;
Dva preopterećenja pokrivaju uobičajene slučajeve. Oblik s četiri koordinate prima X, Y, širinu i visinu te vam daje pravokutnik poravnat s osima u jednom pozivu, što koristite za crtanje linija, obruba ćelija ili obojanih pozadina. Oblik s dvije koordinate postavlja samo početnu točku, a ostatak konture crtate sami pomoću LineTo i BezierTo. Način ispune kontrolira kako se oslikavaju regije koje se preklapaju: fmWinding odgovara većini čvrstih oblika, fmAlternate (par-nepar) upravlja izrezima i konturama koje se same sijeku, dok fmNone ostavlja samo iscrtanu liniju bez ispune, što koristi gornja linija razgraničenja.
Tablice su putanje i tekst, sastavljeni ručno
Budući da ne postoji gotov element tablice, tablica je zapravo petlja. Vi određujete pomake X za stupce i visinu retka, pišete svaku ćeliju pomoću AddText i crtate linije pravokutnim putanjama. Izračun je na vama, ali je jednostavan i jednom napisan može se primijeniti na bilo koju mrežu koju trebate.
procedure DrawTable(Pdf: TPdf; Left, Top: Double);
const
ColX: array[0..2] of Double = (0, 110, 210); // column offsets
RowH = 20;
var
Y: Double;
Row: Integer;
begin
// Header row
Pdf.AddText('Item', 'Arial', 10, Left + ColX[0], Top);
Pdf.AddText('Qty', 'Arial', 10, Left + ColX[1], Top);
Pdf.AddText('Price', 'Arial', 10, Left + ColX[2], Top);
// Rule under the header
Pdf.CreatePath(Left, Top - 5, 260, 0.5, fmNone, clBlack, $FF);
Pdf.AddPath;
// Data rows, stepping Y downward each iteration
Y := Top;
for Row := 1 to 3 do
begin
Y := Y - RowH;
Pdf.AddText('Item ' + IntToStr(Row), 'Arial', 9, Left + ColX[0], Y);
Pdf.AddText(IntToStr(Row * 2), 'Arial', 9, Left + ColX[1], Y);
Pdf.AddText('$' + IntToStr(Row * 10) + '.00', 'Arial', 9, Left + ColX[2], Y);
end;
end;
Primijetite da se u svakom prolazu Y smanjuje za visinu retka, opet zato što je smjer prema gore pozitivan. Ovdje se također vidi nedostatak mjerenja teksta: ništa ne sprječava dugi naziv stavke da se prelije u sljedeći stupac, jer knjižnica ne zna koliko je širok vaš iscrtani niz znakova. Za izlaze fiksnog formata gdje kontrolirate podatke, stupce dimenzionirate velikodušno i nastavljate dalje. Za uistinu varijabilni sadržaj, ili ograničavate unose ili sami mjerite širine glifova prije nego što ih postavite, što je trenutak u kojem se namjenska knjižnica za slaganje teksta počinje isplatiti.
Slike i više stranica
Rasterski sadržaj dolazi preko pomoćnih funkcija za slike. AddPicture prima učitanu sliku TPicture i postavlja je na točku, uz opcionalnu širinu i visinu za skaliranje; AddImage prihvaća putanju datoteke ili izravno TBitmap, a AddJpegImage šalje JPEG bajtove izravno iz toka bez pretvorbe u bitmapu. Kao i sa svime ostalim, koordinate postavljanja predstavljaju donji lijevi kut slike u korisničkom prostoru, a širina i visina su veličina na stranici u točkama, a ne dimenzije piksela izvora.
procedure CreateMultiPageReport(const FileName: string; PageCount: Integer);
var
Pdf: TPdf;
P: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument;
for P := 1 to PageCount do
begin
Pdf.AddPage(P, 595, 842); // append; the new page becomes current
Pdf.AddText('Page ' + IntToStr(P) + ' of ' + IntToStr(PageCount),
'Arial', 10, 50, 30); // footer near the bottom edge
// ... draw this page's body here ...
end;
Pdf.SaveAs(FileName);
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Višestrani dokument je zapravo petlja uzorka za jednu stranicu. Svaki poziv AddPage dodaje stranicu i čini je trenutnom, tako da sadržaj i podnožje (footer) koje sljedeće nacrtate završe na stranicu koju ste upravo dodali. Ne mijenjate svojstvo PageNumber unutar ove petlje jer je dodavanje stranice već pomaknulo kursor na nju; PageNumber trebate samo ako se vraćate na neku stranicu izvan redoslijeda stvaranja. Pozovite SaveAs jednom na kraju, nakon što je zadnja stranica popunjena. Ako trebate arhivski profil (PDF/A) umjesto obične datoteke, isti objekt dokumenta izlaže SaveAsPdfA i druge varijante usklađenosti, pa je odabir izlaznog standarda samo drugačiji poziv za spremanje, a ne drugačiji proces gradnje.
Gdje se ovo uklapa
Iskreno rečeno, API za stvaranje dokumenata u PDFium VCL-u vjerna je i tanka poveznica s PDFium-ovim modelom objekata stranice: stvarno stvaranje dokumenata, ugrađeni fontovi, stvarni vektorski i rasterski sadržaj, serijalizirani u datoteku usklađenu sa standardima. To nije, niti se pretvara da jest, mehanizam za automatski prijelom dokumenta. Linija razgraničenja je izgled teksta. Ako je vaš izlaz predložak – računi, certifikati, naljepnice, nadzorne ploče iscrtane u fiksnoj mreži – model apsolutnih koordinata je izravan i brz, a kod ostaje čitljiv. Ako je vaš izlaz dugi tekst koji se mora sam prelamati i paginirati, morat ćete izgraditi vlastiti mehanizam rasporeda na temelju ovih poziva, a to je pogrešan alat za taj posao. Znati na kojoj ste strani te linije većina je odluke.
Metode stvaranja dokumenata opisane ovdje dio su komponente PDFium VCL Component za Delphi, koja povezuje ovaj put stvaranja s renderiranjem i značajkama ekstrakcije teksta po kojima je PDFium poznatiji.