A PDFium a Chrome PDF lapjának mögöttes megjelenítő motorjaként (viewer engine) híresült el, ezért az első dolog, amit tisztázni kell, hogy a PDFium Component olyan dokumentumokat is képes megépíteni, amelyek korábban sosem léteztek. A szerkesztői oldal (authoring side) becsomagolja a PDFium oldal-objektum (page-object) API-ját: készít egy üres dokumentumot, explicit méretekkel rendelkező oldalakat ad hozzá, és szöveget, vektoros útvonalakat (vector paths) és képeket dob az egyes oldalakra az ön által választott koordinátákon. Nincs megtanulandó oldalleíró nyelv (page description language), és nincs nyomtató-illesztőprogram (print driver) sem a folyamatban (in the loop). Ön meghívja a metódusokat, a könyvtár összerakja a PDF objektumokat, a SaveAs pedig sorosítja (serializes) az eredményt
Amit viszont nem kap, az egy elrendezési motor (layout engine). Ezt azért fontos már elöljáróban leszögezni, mert az alábbiakban minden példát ez formál. A PDFium Component oda helyezi a tartalmat, ahová ön mondja neki, abszolút koordinátákban, és sehovamáshova. Nem fog áttördelni (wrap) egy bekezdést, nem folyatja át a szöveget egy oldaltörésen, és nem számít ki egy táblázatot sorokból és oszlopokból. Ezek az ön feladatai. Ha úgy érkezett, hogy valami olyasmit vár, ami úgy tördeli újra a prózát (reflows prose), mint egy szövegszerkesztő, kalibrálja magát most: ez egy precíz, alacsony szintű elhelyezési API, ami közelebb áll egy vászonra (canvas) rajzoláshoz, mint egy dokumentum szedéséhez (typesetting). A generált számlák, tanúsítványok, címkék és jelentésoldalak esetében, ahol már tudja, hogy melyik elem hová tartozik, pontosan erre a precizitásra van szüksége
A minimum, amely fájlt eredményez
Három hívás áll egy üres TPdf és egy mentett PDF között: hozza létre a dokumentumot, adjon hozzá egy oldalt, majd írja ki. Minden más az a tartalom, amit a kettő közé rétegez
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;
Egy részlet gyakran megtréfálja azokat az embereket, akik láttak már régebbi kódrészleteket: a CreateDocument után nem rendeli hozzá a Pdf.Active := True értéket. Az Active tulajdonság arról tesz jelentést, hogy létezik-e dokumentumleíró (document handle), és a CreateDocument már létrehozott egyet, így a tulajdonság (property) True abban a pillanatban, ahogy a hívás visszatér. Újbóli beállítása a legjobb esetben is hatástalan (no-op), a legrosszabb esetben pedig félrevezető a következő olvasó számára. Az Active kifelé menet szolgálja meg az árát: a False érték hozzárendelése a Free előtt felszabadítja a mögöttes dokumentumot, ami a tiszta lebontási sorrend (teardown order). Kezelje a CreateDocument-et és a fájlbetöltő megnyitást kölcsönösen kizáróként (mutually exclusive). A könyvtár nem hajlandó új dokumentumot létrehozni egy olyan TPdf-en, amelynek már van egy nyitott dokumentuma, így az újrahasználat azt jelenti, hogy először be kell zárni az aktuális dokumentumot
A koordináták a bal alsó sarokból indulnak
Az AddText (és minden elhelyezési hívás) második argumentumpárja egy pont a PDF felhasználói térben (user space). Az origó az oldal bal alsó sarkában található, az X jobbra fut, az Y pedig felfelé (up). Egy egység egy pont (point), azaz a hüvelyk (inch) 1/72-ed része, tehát egy A4-es oldal 595-ször 842 egység, a US Letter pedig 612-ször 792. Ez a felfelé mutató Y a "a szövegem lelóg az oldalról" zavarodottság leggyakoribb forrása, mivel a képernyő és a bittérkép koordináták az origót a tetejére teszik, és az Y lefelé növekszik. Egy 842 pont magas oldalon egy a tetejéhez közeli címsor (heading) az Y 780 környékén helyezkedik el, nem pedig az Y 60-nál. Amikor egy beillesztés (run) valahol váratlanul landol, szinte mindig az oldalmagasság mínusz az ön Y értéke az a szám, amire valójában gondolt
Az AddPage első argumentumaként egy beillesztési pozíciót (insertion position) vesz fel, egy-alapúan (one-based) kifejezve, ahol a 0 kényelmes "dokumentum kezdete" rövidítés. Adjon át 0-t vagy 1-et az első oldalhoz, és az oldal az elejére szúródik be; adjon át a már hozzáfűzött darabszámmal megegyező értéket ahhoz, hogy a végéhez adja hozzá. Az újonnan hozzáadott oldal egyben aktuális oldallá is válik, ami a későbbi rajzoló hívások célpontja lesz, így a hozzáadás után nincs külön "válassza ezt az oldalt" lépés. Ha több oldalt ad hozzá, és később vissza kell rajzolnia egy korábbira, állítsa be a PageNumber-t a kurzor mozgatásához; amíg az oldalakat a létrehozásuk sorrendjében tölti meg, békén hagyhatja azt
Szövegírás, és a betűtípus-szabály, amely csendben csap le
Az AddText szignatúrája mindent tartalmaz, amire egy egyszeri beillesztésnek (single run) szüksége van: a sztringet (string), a betűtípus nevét, a méretet pontokban, az X és Y horgonyt (anchor), majd az opcionális színt, az átlátszóságot (transparency) szabályozó alfa bájtot, valamint a forgatási szöget (rotation angle) fokban kifejezve
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;
Az alfa bájt a $00-tól (láthatatlan) az $FF-ig (átlátszatlan) terjed, és ez az, ami a vázlat (draft) bélyegzőt vízjellé teszi egy szilárd (solid) blokk helyett: a $30 nagyjából tizenkilenc százalékos átlátszatlanságot (opacity) jelent, ami elég ahhoz, hogy át lehessen olvasni rajta. A szög az óramutató járásával ellentétes irányba (counterclockwise) forgatja el a feliratot (run) a horgonya (anchor) körül, így a 45 fok a klasszikus, saroktól sarokig érő bélyegzőt adja. Ehhez nincs szükség külön vízjel funkcióra. A vízjel csupán egy nagy, félig átlátszó, elforgatott AddText hívás, és az, hogy a törzs (body) előtt vagy után rajzolja ki, határozza meg, hogy a tartalom mögött vagy azon rajta fog-e elhelyezkedni
A betűtípusok (fonts) megérdemelnek egy óvatos mondatot, mert a hibamód csendes. Amikor megad egy betűtípusnevet, a PDFium Component lekéri az operációs rendszertől az adott betűtípus TrueType adatait, és beágyazza (embeds) a dokumentumba – ez az oka annak, hogy az ön gépén készített fájl pontosan ugyanúgy renderelődik egy olyanon is, amelyre soha nem telepítették az adott betűtípust. A bökkenő az, hogy mi történik, ha a nevet nem sikerül feloldani: egy elgépelés, vagy egy olyan betűcsalád (face), amely egyszerűen nincs jelen az építő (build) gépen. Nincs kivétel (exception). A könyvtár visszalép egy olyan szöveges objektum létrehozására, amely a nevet csak címkeként (label) hordozza, mindenféle beágyazás nélkül, és a megjelenítőre (viewer) bízza a helyettesítést bármivel, amit közelinek ítél meg. A szöveg megjelenik a tesztjeiben, hihetőnek tűnik (looks plausible), aztán eltolja a metrikákat vagy a glifákat (glyphs) abban a pillanatban, ahogy a fájlt valahol olyan gépen nyitják meg, ahol más betűtípusok vannak telepítve. Használjon olyan neveket, amelyekről tudja, hogy jelen vannak a generáló gépen, kezelje a betűtípus-listát (font list) telepítési függőségként (deployment dependency), és nyisson meg egy mintát egy tiszta rendszer megjelenítőjében (viewer), mielőtt megbízna a kimenetben
Vektoros alakzatok: építsen egy útvonalat, majd véglegesítse
A vonalak, téglalapok és a kitöltött régiók egy útvonalon (path) haladnak keresztül. Egy ilyet a CreatePath segítségével nyithat meg, amely egyszerre állítja be a kezdőpontot és az összes stílust, a kitöltési módot (fill mode), a kitöltési és körvonalszíneket (fill and stroke colors) a saját alfa bájtjaikkal, a körvonal vastagságát (stroke width), a vonalvégeket (line caps) és a csatlakozásokat (joins). Ezután kibővíti (extend) a LineTo, a BezierTo és a ClosePath segítségével, végül pedig az AddPath véglegesíti (commits) a befejezett útvonalat az oldalra. Ezt a véglegesítési lépést könnyű elfelejteni, és ha kihagyja, nem fog eredményezni semmit
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;
Két túlterhelés (overloads) lefedi a gyakori eseteket. A négykoordinátás forma az X-et, Y-t, a szélességet és a magasságot kéri, és egyetlen hívással egy tengelyhez igazított (axis-aligned) téglalapot ad, amihez akkor nyúlunk, ha egy vonalat (rule), cellaszegélyt vagy egy kitöltött háttérpanelt (filled background panel) szeretnénk rajzolni. A kétkoordinátás forma csak egy kezdőpontot (start point) állít be, a körvonal többi részét pedig ön rajzolja (trace) meg a LineTo és a BezierTo segítségével. A kitöltési mód szabályozza, hogy az egymást átfedő (overlapping) régiók hogyan legyenek kifestve: az fmWinding (nem nulla körüljárási / nonzero winding) a legtöbb szilárd (solid) alakzathoz megfelel, az fmAlternate (páros-páratlan / even-odd) kezeli a kivágásokat és az önmagukat metsző (self-intersecting) körvonalakat, az fmNone pedig kitöltés nélküli, csak körvonalazott (stroked-only) útvonalat hagy, amit a fenti elválasztó (divider) is használ
A táblázatok útvonalak és szövegek, kézzel összeállítva
Mivel nincs táblázat-primitíva, a táblázat valójában egy ciklus (loop). Ön dönti el az oszlop X eltolásait (offsets) és a sor magasságát, minden cellát az AddText segítségével ír meg, és a vonalakat (rules) téglalap-útvonalakkal (rectangle paths) rajzolja meg. Az aritmetika az öné, de pofonegyszerű (plain), és miután megírta, bármilyen rácsra (grid) általánosítható, amire csak szüksége van
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;
Vegye észre, hogy az Y minden lépésnél a sormagassággal lefelé lépked, ismét azért, mert a felfelé az pozitív. Itt mutatkozik meg a szövegmérés (text measurement) hiánya is: semmi sem akadályozza meg, hogy egy hosszú tételnév (item name) átnyúljon (overrunning) a következő oszlopba, mivel a könyvtár nem tudja, milyen szélesre sikerült a sztring (string) renderelése. Olyan rögzített formátumú (fixed-format) kimenet esetén, ahol ön ellenőrzi az adatokat, az oszlopokat nagyvonalúan (generously) méretezi, majd továbblép. Igazán változó (variable) tartalomnál vagy korlátozza (constrain) a bemeneteket, vagy ön maga méri le a glifák szélességét az elhelyezésük előtt – ez az a pont, ahol egy dedikált szedőkönyvtár (composition library) elkezdi visszahozni az árát
Képek és több oldal
A raszteres tartalom (raster content) a képes segédfüggvényeken keresztül érkezik. Az AddPicture egy betöltött TPicture-t fogad, és elhelyezi azt egy ponton, opcionális szélességgel és magassággal a méretezéshez (scale); az AddImage közvetlenül fogad el egy fájlelérési utat (file path) vagy egy TBitmap-et, az AddJpegImage pedig JPEG bájtokat továbbít (streams) egy bittérképen keresztüli körút (round trip) nélkül. Mint minden másnál, az elhelyezési koordináták a felhasználói térben (user space) lévő kép bal alsó sarkát jelentik, a szélesség és a magasság pedig az oldalon lévő méret (on-page size) pontokban, nem pedig a forrás pixelmérete (pixel dimensions)
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;
Egy többoldalas dokumentum valójában az egyoldalas minta egy ciklusban (loop). Minden AddPage hozzáfűz egy oldalt és aktuálissá teszi azt, így a törzs (body) és a lábléc (footer), amit a következőkben rajzol, az imént hozzáadott oldalon landol. Ebben a ciklusban nem rendeli újra hozzá (reassign) a PageNumber-t, mert az oldal hozzáadása már oda helyezte a kurzort; a PageNumber-re csak akkor van szüksége, ha egy oldalra a létrehozási sorrenden kívül (out of creation order) megy vissza. Hívja meg a SaveAs-t egyszer a végén, miután az utolsó oldalt is feltöltötte. Ha egy egyszerű fájl helyett egy archiválási profilra (archival profile) van szüksége, ugyanaz a dokumentum objektum teszi közzé a SaveAsPdfA és a többi megfelelőségi (conformance) változatot is, így a kimeneti szabvány (output standard) kiválasztása egy másik mentési hívást (save call) jelent, nem pedig egy másik építési utat (build path)
Hová illik mindez
Az őszinte megközelítés (honest framing) az, hogy a PDFium Component szerkesztő API-ja (authoring API) egy hű, vékony réteg a PDFium oldal-objektum modellje felett: valódi dokumentumkészítés (document creation), valódi beágyazott betűtípusok (embedded fonts), valódi vektoros és raszteres tartalom, melyek egy szabványoknak megfelelő fájlba lesznek sorosítva (serialized). Ez nem egy újra-tördelő dokumentummotor (reflowing document engine), és nem is tesz úgy, mintha az lenne. A választóvonal (dividing line) a szövegelrendezés (text layout). Ha a kimenete sablonozott (templated), rögzített rácsra (fixed grid) renderelt számlák, tanúsítványok, címkék, műszerfalak (dashboards), akkor az abszolút koordinátájú modell közvetlen és gyors, a kód pedig olvasható marad. Ha a kimenete egy hosszú formátumú próza (long-form prose), amelynek saját magát kell áttördelnie és több oldalra bontania (wrap and paginate), akkor ön egy elrendezési motort (layout engine) fog újjáépíteni ezeken a hívásokon felül, és ehhez ez nem a megfelelő eszköz. Annak tudata, hogy e vonal melyik oldalán áll, a döntés legnagyobb részét kiteszi
Az itt leírt létrehozási metódusok a Delphihez készült PDFium Component részét képezik, amely párosítja ezt a szerkesztési (authoring) utat azokkal a renderelési és szövegkinyerési funkciókkal, amelyekről a PDFium sokkal ismertebb