PDFium ima reputaciju (reputation) kao mehanizam preglednika (viewer engine), renderer iza Chromeove PDF kartice, stoga je prva stvar koju treba razjasniti to da PDFium Component također može izgraditi dokument koji nikada prije nije postojao. Strana za autorizaciju (authoring side) omata (wraps) PDFium-ov API objekta stranice: napravite prazan dokument, dodajte stranice s eksplicitnim (explicit) dimenzijama, a zatim ispustite tekst, vektorske staze (paths) i slike na svaku stranicu po koordinatama koje odaberete. Ne postoji jezik za opis stranice (page description language) koji treba naučiti i nema upravljačkog programa za ispis (print driver) u petlji. Vi pozivate metode, biblioteka sastavlja (assembles) PDF objekte, a SaveAs (Spremi kao) serijalizira rezultat
Ono što ne dobivate je mehanizam za raspored (layout engine). To je dovoljno važno (matters enough) da se kaže unaprijed (up front) jer oblikuje (shapes) svaki primjer u nastavku. PDFium Component postavlja sadržaj tamo gdje mu kažete, u apsolutnim koordinatama i nigdje drugdje. Neće omotati (wrap) odlomak, prebacivati tekst preko prijeloma stranice (page break) ili izračunati (compute) tablicu iz redaka i stupaca. To je vaš posao. Ako ste stigli očekujući nešto što prepravlja (reflows) prozu na način na koji to radi procesor teksta (word processor), kalibrirajte se (calibrate) sada: ovo je precizan API za smještaj (placement) niske razine (low-level), bliži crtanju na platnu nego slaganju dokumenta (typesetting a document). Za generirane račune (invoices), certifikate, oznake (labels) i stranice s izvješćima, na kojima već znate gdje svaki element pripada, ta je preciznost točno ono što želite
Minimum koji proizvodi datoteku
Tri poziva stoje između praznog TPdf-a i spremljenog PDF-a: izradite dokument, dodajte stranicu, zapišite ga (write it out). Sve ostalo je sadržaj koji slojevito slažete (layer in) između
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 sapliće (trips) ljude koji su vidjeli starije isječke (snippets): nakon CreateDocument ne dodjeljujete Pdf.Active := True. Svojstvo Active izvještava postoji li ručka dokumenta (document handle), a CreateDocument je već stvorio jednu, tako da je svojstvo True u trenutku kada se taj poziv vrati. Njeno ponovno postavljanje je u najboljem slučaju neoperativno (no-op), au najgorem slučaju zavaravajuće (misleading) za sljedećeg čitatelja. Active zarađuje na izlasku (earns its keep on the way out): dodjeljivanje (assigning) False oslobađa temeljni dokument (underlying document) prije Free, što je čist redoslijed rušenja (teardown order). Tretirajte CreateDocument i otvaranje datoteke (file-loading open) kao međusobno isključive (mutually exclusive). Biblioteka odbija (refuses) stvoriti novi dokument na TPdf koji već ima otvoren dokument, pa ponovna upotreba znači najprije zatvaranje trenutnog dokumenta
Koordinate počinju u donjem lijevom kutu
Drugi argumentni par u AddText, kao i svakom pozivu za postavljanje, je točka u PDF korisničkom prostoru. Ishodište (origin) se nalazi u donjem lijevom kutu stranice, X teče (runs) desno, a Y teče (runs) gore. Jedna jedinica (unit) je jedna točka, 1/72 inča, tako da je A4 stranica veličine 595 x 842 jedinice, a US Letter 612 x 792. Taj Y okrenut prema gore najčešći je izvor zabune (confusion) "moj je tekst izvan stranice" jer zaslon i bitmapa koordinate stavljaju u ishodište na vrh (top) gdje Y raste prema dolje. Na stranici visokoj (tall) 842 točke, naslov (heading) blizu vrha iznosi oko Y 780, a ne Y 60. Kada pokretanje (run) završi (lands) negdje neočekivano, visina stranice minus vaš Y gotovo je uvijek onaj broj koji ste zapravo mislili
AddPage preuzima položaj umetanja (insertion position) kao svoj prvi argument, izražen temeljen na jednom (one-based), s 0 kao prikladnom kraticom (shorthand) "početak dokumenta". Proslijedite (pass) 0 ili 1 za prvu stranicu i stranica se umeće na početak (front); proslijedite (pass) vrijednost koja odgovara broju koji dodajete na kraj. Novododana stranica ujedno postaje i trenutna (current) stranica, na koju ciljaju sljedeći (subsequent) pozivi za crtanje, tako da nakon dodavanja nema zasebnog (separate) koraka "odaberi ovu stranicu (select this page)". Ako dodate nekoliko stranica te kasnije morate ponovno crtati na nekoj od ranijih (earlier), postavite PageNumber za pomicanje kursora; dok stranice popunjavate redom po kojem ih kreirate, možete ih ostaviti na miru (alone)
Pisanje teksta, te pravilo za fontove koje tiho (silently) grize (bites)
Potpis AddText nosi (carries) sve što jednom izvođenju treba: niz (string), ime fonta, veličina u točkama, X i Y sidro (anchor), zatim po izboru i boju, alfa bajt (alpha byte) za prozirnost (transparency), i kut rotacije (rotation angle) 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 (alpha byte) se kreće od $00 (nevidljivo) do $FF (neprozirno (opaque)), što je razlog zašto oznaka nacrta (draft stamp) postaje vodeni žig (watermark), a ne puni blok (solid block): $30 je otprilike devetnaest posto neprozirnosti (opacity), dovoljno za čitanje. Kut okreće tok u smjeru suprotnom od kazaljke na satu (counterclockwise) oko sidra, tako da 45 stupnjeva daje klasičan (classic) pečat iz kuta u kut (corner-to-corner stamp). Ništa od ovoga ne treba zasebnu značajku (feature) vodenog žiga (watermark). Vodeni žig samo je velik, poluproziran (semi-transparent), zakrenuti (rotated) poziv AddText, a njegovo crtanje prije ili poslije tijela (body) odlučuje nalazi li se on iza ili povrh (on top) sadržaja
Fontovi zaslužuju (deserve) opreznu rečenicu, jer je način kvara (failure mode) tih (quiet). Kada proslijedite ime fonta, PDFium Component od operacijskog sustava (operating system) traži TrueType podatke tog fonta i ugrađuje (embeds) ih u dokument, zbog čega se datoteka izgrađena (built) na vašem stroju renderira identično na onoj koja nikada nije imala instaliran takav font. Kvaka (catch) je u tome što se događa kada se ime ne razrješava (resolve): tipfeler (typo) ili lice (face) koje jednostavno nije prisutno na stroju za građenje (build machine). Nema iznimke (exception). Biblioteka se oslanja na stvaranje tekstualnog objekta (text object) koji nosi naziv samo kao oznaku (label), bez ičega ugrađenog (embedded) te ostavlja gledatelju da to zamijeni (substitute) za ono što on smatra bliskim (close). Tekst se pojavljuje u vašim testovima, izgleda uvjerljivo (plausible), te pomiče mjernu podatke (metrics) ili glifove u onom trenutku kada se datoteka otvori negdje s instaliranim različitim fontovima. Koristite imena za koja znate da su prisutna na stroju koji ih stvara, tretirajte popis (list) fontova kao ovisnost o postavljanju (deployment dependency) i otvorite primjer (sample) u pregledniku (viewer) na čistom (clean) sustavu prije nego što povjerujete u taj izlaz
Vektorski oblici: izgradite put (path), a zatim ga uredite (commit it)
Linije, pravokutnici i ispunjena područja prolaze kroz put (path). Otvarate ga (open) s pomoću CreatePath, koji istovremeno (at once) postavlja početnu (start) točku i sva stiliziranja, način ispune (fill mode), boje ispune (fill) i poteza (stroke) s vlastitim alfa bajtovima (alpha bytes), širinu poteza (stroke width), kapice crte (line caps) i spojeve (joins). Zatim ga produljujete (extend) s LineTo, BezierTo i ClosePath, a na kraju AddPath predaje (commits) gotovu putanju na stranicu. Korak s obvezivanjem (commit step) je lako zaboraviti te se ništa neće proizvesti ukoliko ga preskočite
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 (overloads) pokrivaju (cover) uobičajene slučajeve (common cases). Oblik (form) s četiri koordinate zauzima (takes) X, Y, širinu i visinu i daje vam pravokutnik (rectangle) poravnat po osi u jednom pozivu, što je ono za čime posežete kako biste nacrtali pravilo (rule), granicu ćelije (cell border) ili ispunjeni panel na pozadini (filled background panel). Oblik s dvije koordinate postavlja samo početnu točku (start point), a vi sami iscrtavate ostatak obrisa s LineTo i BezierTo. Način ispune (fill mode) kontrolira kako se boje preklapajuća područja (overlapping regions): fmWinding (neprozirno namotavanje (nonzero winding)) odgovara većini punih (solid) oblika, fmAlternate (parno-neparno (even-odd)) rješava (handles) izreze (cutouts) i konture (outlines) koje se same sijeku (self-intersecting), a fmNone ostavlja stazu sa samo potezima (stroked-only path) i bez ispune, a što je upravo ono što divider iznad koristi
Tablice su putevi i tekst, ručno sastavljeni (assembled by hand)
Budući da ne postoji (there is no) primitiva tablice (table primitive), tablica je petlja. Vi odlučujete o X odmacima stupca (column) i visini retka (row), zapisujete svaku ćeliju sa AddText te crtate pravila (rules) sa stazama (paths) pravokutnika. Aritmetika je vaša, ali je obična (plain) i nakon što je napisana generalizira se na bilo koju mrežu (grid) 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;
Uočite da Y pada prema dolje (stepping downward) za visinu reda u svakom prolazu, i to opet iz istog razloga - jer je 'gore' pozitivno (up is positive). To je ujedno i mjesto gdje se pokazuje nedostatak (absence) mjerenja teksta (text measurement): ništa ne zaustavlja predugo (long) nazivno ime predmeta (item name) da ne pređe u susjedni (next) stupac, jer biblioteka ne zna (does not know) koliko je širok (wide) niz koji ste vi kreirali (rendered). Kod ispisa fiksnog formata (fixed-format output) u kojima vi kontrolirate te podatke, obilato dimenzionirate stupce (size columns generously) i idete dalje. Za istinski (genuinely) promjenjiv sadržaj, vi ćete ograničiti (constrain) unos (inputs) ili sami izmjeriti širinu glifova (glyph widths) prije nego što ih smjestite, a što je točka u kojoj posvećena (dedicated) biblioteka s kompozicijama počinje vraćati samu sebe (pay for itself)
Slike i višestruke stranice
Sadržaj rastera dolazi kroz pomoćnike za slike. AddPicture uzima učitani TPicture i postavlja ga na točku, s opcijskom (optional) širinom (width) i visinom (height) za skaliranje (scale); AddImage izravno prihvaća putanju datoteke ili TBitmap, a AddJpegImage struji JPEG bajtove bez povratnog puta kroz bitmapu. Kao i kod svega drugoga, koordinate postavljanja su donji lijevi (lower-left) kut slike u korisničkom prostoru (user space), a širina i visina su veličina na stranici (on-page size) u točkama, a ne dimenzije piksela (pixel dimensions) u izvoru
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;
Dokument (document) s više stranica, obrazac (pattern) je u jednoj stranici, i to u petlji. Svaka AddPage dodaje stranicu te ju time čini i trenutačnom, pa će tijelo i podnožje koje sljedeće nacrtate (draw next) sletjeti na stranicu koju ste upravo dodali. Unutar ove petlje (loop) nećete morati ponovno odrediti PageNumber, jer se dodavanjem stranice pokazivač već prebacio tamo; trebat će vam samo PageNumber kada se vraćate (go back) na stranicu po nekakvom izvanrednom poretku kreiranja (out of creation order). Na kraju pozovite SaveAs, i to samo jednom nakon što ste popunili (filled) zadnju stranicu. Ukoliko (If) vam je umjesto obične (plain) datoteke potreban arhivski (archival) profil, isti će objekt dokumenta otkriti (exposes) SaveAsPdfA kao i razne (other) varijante sukladnosti (conformance variants), stoga i odabir izlaznog (output) standarda postaje potpuno novi poziv za spremanjem (save call), a ne neki novi građevinski put (build path)
Gdje se ovo uklapa
Iskreno (honest) uokvirivanje (framing) leži u tome da je API za izradu u programu (authoring) PDFium Component vjeran, tanak sloj (thin layer) iznad modela PDFium objekta stranice: pravo stvaranje (creation) dokumenta, pravi (real) ugrađeni (embedded) fontovi te istinski (real) vektorski i rasterski sadržaj (vector and raster content), serijalizirani do datoteke koja je u skladu (conforming) sa standardima (standards). To nije i niti ne (does not) namjerava biti "presvlačenje" motora za dokumetiranje (reflowing document engine). Linija razdvajanja (dividing line) je tekstualni izgled (text layout). Ako je vaš ispis (output) u obliku predložaka, faktura (invoices), potvrda (certificates), natpisa (labels), te kontrolnih (dashboards) ploča namještenih na fiksnu (fixed) mrežu, model (model) apsolutne-koordinate bit će trenutan (direct) i brz (fast), a kod ostaje čitljiv. Međutim, (If) ukoliko vaš proizvod mora biti napisana proza u dugom obliku a koja mora sama stvoriti (wrap) te dodati još listova (paginate), onda ćete na vrhu takvih poziva morati ponovo izgraditi cijeli sistem oko razmještaja stranica (layout engine), a to je pogrešan (wrong) alat za ovakav jedan zadatak (job). Najbolja odluka kreće od razaznavanja (Knowing) na kojoj se strani te granice nalazite
Metode kreiranja (creation methods) koje su ovdje opisane (described here) jesu dio komponente PDFium Component (PDFium Component) za Delphi, a koja objedinjuje ovu putanju (path) kod stvaranja skupa (pairs) s opcijom o iscrtavanju (rendering) te onom o izdvajanju teksta (text-extraction features) po čemu je zapravo PDFium puno poznatiji (better known for)