A PDF koordináták pontokban (points) vannak, a nyomtató koordinátái eszközelemekben (device units), és a kettőnek semmi köze egymáshoz, amíg ön szándékosan át nem számítja őket. Ez az eltérés a gyökere a legtöbb rossz nyomtatási kimenetnek a Delphi alkalmazásokban: a kód a megfelelő fájlt küldi, de az oldal levágva, megnyújtva vagy üresen jön ki. A PDFium Component tisztán kezeli a renderelési oldalt; a nyomtató bekötése (plumbing) szabványos VCL. A kettő szerény mennyiségű kóddal illeszkedik egymáshoz, amint megérti, mit vár el mindkét oldal
Hogyan működik a renderelés-aztán-nyomtatás csővezeték (pipeline)
A PDFium Component nem beszélget közvetlenül a nyomtatókkal. A minta a következő: rendereljen egy oldalt egy TBitmap-be a kívánt felbontáson, majd vigye át azt a bittérképet (bitmap) a nyomtató vásznára (canvas) a StretchDIBits segítségével. A TPdf.RenderPage egy hívó által birtokolt (caller-owned) bittérképet ad vissza, így ön irányítja a pixelméreteket. Adja át a [rePrinting] értéket a beállításhalmazban (options set), és a PDFium olyan renderelési útvonalra vált, amely kihagyja a csak képernyőre vonatkozó effekteket, mint például az LCD alpixel hinting, és helyesen kezeli az oldal MediaBox-át a nyomtatási kimenethez. Hagyja ki a rePrinting-et, és amit a nyomtatónak küld, az egy képernyős renderelés lesz, ami jól mutat egy monitoron, de hajlamos lágyabb (softer) kimenetet produkálni a magas DPI-s nyomtatókon, mivel a 96 DPI-s képernyőkre hozott hinting döntések nem felelnek meg a 300 vagy 600 DPI-s nyomtatásnak
A TPdf.Active az egyetlen kapu, amelyet ellenőrizni kell, mielőtt bármilyen oldaltulajdonsághoz nyúlna. A komponens csendben elnyeli a betöltési hibákat: az Active := True beállítása egy sérült vagy jelszóval védett fájlon nem vet fel kivételt (exception); egyszerűen False értéken hagyja az Active-ot. A hozzárendelés (assignment) után mindig ellenőrizze. A PageCount vagy a PageWidth olvasása egy inaktív dokumentumon nullát ad vissza, ami csendes semmittevéseket (no-ops) eredményez, amiket nagyon nehéz diagnosztizálni, amint elérik a sorkezelőt (spooler)
Egy minimális nyomtatási hurok
A legegyszerűbb működő eset betölt egy fájlt, megnyit egy nyomtatási feladatot, végigiterál (iterates) az oldalakon, és bezár. Az egyetlen trükkös részlet az, hogy a Printer.NewPage-et nem szabad az első oldal előtt meghívni, innen a FirstPage jelző (flag). A StretchDIBits átvitel (transfer) a GetDIBSizes-en és a GetDIB-en megy keresztül, hogy eszközfüggetlen (device-independent) biteket húzzon ki a bittérkép leíróból (bitmap handle), majd a teljes oldalméretben ráfesse azokat a nyomtató vásznára:
procedure PrintPdfFile(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Bitmap: TBitmap;
InfoHeaderSize, ImageSize: DWORD;
InfoHeader: PBitmapInfo;
Image: Pointer;
FirstPage: Boolean;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
Exit; // load failed silently; bail out
Printer.Title := Pdf.Title;
Printer.BeginDoc;
try
FirstPage := True;
for I := 1 to Pdf.PageCount do
begin
if FirstPage then
FirstPage := False
else
Printer.NewPage;
Pdf.PageNumber := I;
// Render at printer resolution; rePrinting adjusts the render path
Bitmap := Pdf.RenderPage(
0, 0,
Printer.PageWidth,
Printer.PageHeight,
ro0,
[rePrinting]
);
try
GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
InfoHeader := AllocMem(InfoHeaderSize);
try
Image := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
0, 0, Printer.PageWidth, Printer.PageHeight,
0, 0, Bitmap.Width, Bitmap.Height,
Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(InfoHeader);
end;
finally
Bitmap.Free;
end;
end;
finally
Printer.EndDoc;
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
A Printer.PageWidth és a Printer.PageHeight átadása bittérkép méretként azt jelenti, hogy a nyomtató natív pixelméretén renderel, ami már figyelembe veszi az eszköz DPI-jét. A StretchDIBits hívás ezután ezeket a pixeleket 1:1 arányban leképezi (maps) az oldalra. Ez a legjobb elérhető hűséget (fidelity) adja explicit DPI aritmetika nélkül, de csak akkor működik, ha a PDF oldal és a fizikai papír véletlenül azonos méretű. Ha eltérnek, explicit skálázásra van szüksége
Skálázás, ha az oldal és a papír mérete eltér
Egy álló (portrait) A4-es PDF oldal nem illeszkedik automatikusan egy US Letter nyomtatóhoz, és egy fekvő (landscape) oldal, amelyet egy álló tájolású (portrait-oriented) nyomtatóba táplálnak be, le lesz vágva (clip). A szabványos megközelítés az, hogy egy egységes léptéktényezőt (scale factor) számolunk a nyomtató pixelek és a PDF pontok (points) arányából, majd ezt alkalmazzuk mindkét dimenzióra, hogy a képarány megmaradjon. A Pdf.PageWidth és a Pdf.PageHeight pontokban (points) teszi közzé az aktuális oldalméreteket, ahol egy pont (point) az 1/72 hüvelyk (inch). Egy cél (target) DPI-vel megszorozva és 72-vel elosztva képpontokká alakul azon a felbontáson. Vegye az X és Y arányok Min értékét, hogy megkapja a legnagyobb léptéket, amely még belefér a nyomtatható területre:
// Fit PDF page to printable area, preserving aspect ratio
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // target render resolution
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Clamp to 1.0 for shrink-to-fit only (no enlargement)
if Scale > 1.0 then Scale := 1.0;
DestWidth := Round(Pdf.PageWidth * Dpi / 72 * Scale);
DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);
Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
[rePrinting, reAnnotations]);
// ... transfer with StretchDIBits as above
end;
A Dpi = 300 felbontáson történő renderelés a legtöbb irodai nyomtatónak megfelel. 600 DPI-nél egyetlen A4-es oldal bittérképe nagyjából 34 megapixelig fut, ami egy 32 bites bittérképként körülbelül 100 MB; a minőségnövekedés (gain in quality) közönséges szöveges dokumentumok esetén minimális, a memóriaköltség oldalanként viszont jelentős. Tartsa meg a 600 DPI-t a nyomdák (print shops) vagy a vektor-nehéz (vector-heavy) műszaki rajzok számára, ahol ez valóban számít
A második kódblokkban szereplő reAnnotations jelző (flag) független a rePrinting-től. Foglalja bele, ha a felhasználó elvárja, hogy a bélyegzők (stamps), a kiemelések (highlights) és a megjegyzésdobozok (comment boxes) megjelenjenek a papíron. Hagyja ki, ha csak tartalmi (content-only) kimenetre van szüksége. A két jelző szabadon kombinálható
Oldal elforgatása (Page rotation)
A PDFium a PDF-ben az oldal elforgatását (rotation) egy /Rotate bejegyzésként tárolja, amely a Pdf.PageRotation-ön keresztül érhető el, ez pedig egy TRotation értéket ad vissza (ro0, ro90, ro180, ro270). A nyomtató koordinátarendszere a képernyőhöz képest megfordítja a 90 és 270 fokos elforgatásokat. Ha a nyers (raw) PageRotation értéket közvetlenül a RenderPage-nek adja át mindenféle korrekció (adjustment) nélkül, akkor egy álló (portrait) dokumentumba ágyazott fekvő (landscape) oldalak a legtöbb Windows nyomtató-illesztőprogramon (printer drivers) fejjel lefelé fognak nyomtatódni. A javítás egy egyszerű csere (swap) a renderelési hívás előtt: képezze le az ro90-et ro270-re, az ro270-et pedig vissza ro90-re, miközben az ro0-t és ro180-at változatlanul hagyja
Ellenőrizze ezt a viselkedést az ön konkrét célnyomtatóján (target printer), mielőtt kiadná (shipping). Az illesztőprogramok (driver) elforgatás körüli viselkedése nem egységes a gyártók között, és egyes illesztőprogramok a saját elforgatási korrekciójukat alkalmazzák a GDI szintjén. Ha dupla elforgatást lát, távolítsa el a cserét (swap); ha egyáltalán nem lát korrekciót, adja hozzá. Egy vegyes tájolású (mixed-orientation), álló és fekvő oldalakat váltakozva tartalmazó dokumentum a leggyorsabb módja bármelyik hibamód (failure mode) elcsípésének a tesztelés során
Memóriakezelés (Memory management) egy hosszú nyomtatási feladat során
Minden RenderPage hívás egy új TBitmap-et foglal le, amelyet a hívó birtokol, és neki kell felszabadítania (free). A fenti hurokban a try/finally Bitmap.Free blokk ezt helyesen kezeli egyszerre egy oldalon. Ne halmozza fel a bittérképeket az oldalakon keresztül: egy 200 oldalas dokumentum 300 DPI-s renderelése gigabájtokat emésztene fel, mielőtt az első oldal elérné a sorkezelőt (spooler). Szabadítson fel minden bittérképet, mielőtt továbblépne a következő oldalra
Az átviteli blokkon (transfer block) belüli AllocMem / FreeMem páros ugyanazt a szabályt követi. A GetDIBSizes megmondja, hogy mennyi memóriára van szüksége a DIB fejlécnek és a pixeladatoknak; ön lefoglalja, kitölti, ráfesti és felszabadítja mindezt egyetlen oldal hatókörén (scope) belül. Ha bármelyik blokkot is szivárogni hagyja (leak), az a nyomtatási feladat számára kimeríti a folyamat kupacát (process heap) a néhány tucat oldalnál hosszabb dokumentumok esetében
Ha háttérszálon (background thread) kell futtatnia nyomtatási feladatokat, tartsa a TPdf-et és az összes VCL nyomtató hívást ugyanazon a szálon. A TPdf önmagában nem szálbiztos (thread-safe) a PDFium DLL globális állapotán osztozó példányokon (instances) keresztül; a legbiztonságosabb modell egy TPdf szálanként, mindegyik betöltve a fájl saját másolatát
Az itt bemutatott renderelési és dokumentum API a Delphihez és C++Builderhez készült PDFium Component része