PDF-coördinaten worden weergegeven in punten, printercoördinaten in apparaateenheden, en de twee hebben niets met elkaar te maken totdat u ze opzettelijk converteert. Deze wanverhouding is de oorzaak van de meeste slechte afdrukken in Delphi-toepassingen: de code verzendt het juiste bestand, maar de pagina komt er bijgesneden, uitgerekt of blanco uit. De PDFium-component handelt de renderzijde netjes af; het loodgieterswerk voor de printer is standaard VCL. De twee passen goed samen met een bescheiden hoeveelheid code als u eenmaal begrijpt wat elke partij verwacht
Hoe de render-dan-afdrukken pijplijn werkt
De PDFium-component praat niet rechtstreeks met printers. Het patroon is: render een pagina naar een TBitmap met de door u gewenste resolutie, en draag die bitmap vervolgens over naar het canvas van de printer met StretchDIBits. TPdf.RenderPage retourneert een bitmap die eigendom is van de aanroeper, dus u beheert de pixelafmetingen. Geef [rePrinting] door in de optieset en PDFium schakelt zijn renderpad over naar een pad dat alleen-scherm-effecten, zoals LCD subpixel hinting, weglaat en de MediaBox van de pagina correct afhandelt voor de afdrukuitvoer. Laat u rePrinting weg, dan verzendt u een schermweergave naar de printer, wat er op een monitor prima uitziet, maar bij hoge-DPI-printers vaak resulteert in een zachtere uitvoer omdat de hintingbeslissingen die zijn genomen voor 96 DPI-schermen niet geschikt zijn voor afdrukken met 300 of 600 DPI
TPdf.Active is de enige poort die u moet controleren voordat u een pagina-eigenschap aanraakt. De component slikt laadfouten stilletjes in: het instellen van Active := True op een beschadigd of met een wachtwoord beveiligd bestand veroorzaakt geen uitzondering; het laat Active simpelweg op False staan. Controleer het altijd na de toewijzing. Het lezen van PageCount of PageWidth van een inactief document retourneert nul, wat resulteert in stille, niet-uitgevoerde bewerkingen (no-ops) die erg moeilijk te diagnosticeren zijn zodra ze de spooler bereiken
Een minimale afdruklus
Het eenvoudigste werkende geval laadt een bestand, opent een afdruktaak, itereert de pagina's en sluit af. Het enige lastige detail is dat Printer.NewPage niet mag worden aangeroepen vóór de eerste pagina, vandaar de flag FirstPage. De StretchDIBits-overdracht verloopt via GetDIBSizes en GetDIB om apparaatonafhankelijke bits uit de bitmap-handle te halen, waarna ze op volledige paginagrootte op het printercanvas worden getekend:
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;
Het doorgeven van Printer.PageWidth en Printer.PageHeight als de bitmapafmetingen betekent dat u rendert op de oorspronkelijke pixelgrootte van de printer, waarbij al rekening is gehouden met de DPI van het apparaat. De aanroep StretchDIBits wijst deze pixels vervolgens 1:1 toe aan de pagina. Dit geeft u de best haalbare getrouwheid zonder enige expliciete DPI-rekenkunde, maar het werkt alleen als de PDF-pagina en het fysieke papier toevallig even groot zijn. Als ze verschillen, hebt u expliciete schaling nodig
Schalen wanneer pagina- en papierformaten verschillen
Een PDF-pagina in A4 staand formaat (portrait) past niet automatisch op een US Letter-printer, en een liggende pagina (landscape) die naar een printer in staande stand wordt gestuurd, zal worden afgesneden. De standaardaanpak is om een uniforme schaalfactor te berekenen uit de verhouding tussen printerpixels en PDF-punten, en deze vervolgens toe te passen op beide afmetingen, zodat de beeldverhouding behouden blijft. Pdf.PageWidth en Pdf.PageHeight geven de huidige pagina-afmetingen in punten weer, waarbij één punt overeenkomt met 1/72 inch. Door dit te vermenigvuldigen met een doel-DPI en te delen door 72, wordt geconverteerd naar pixels in die resolutie. Neem Min van de X- en Y-verhoudingen om de grootste schaal te krijgen die nog steeds binnen het afdrukbare gebied past:
// 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;
Rederen met Dpi = 300 is geschikt voor de meeste kantoorprinters. Bij 600 DPI loopt de bitmap voor een enkele A4-pagina op tot ruwweg 34 megapixels, wat overeenkomt met ongeveer 100 MB als een 32-bits bitmap; de kwaliteitsverbetering voor gewone tekstdocumenten is minimaal, terwijl de geheugenkosten per pagina aanzienlijk zijn. Bewaar 600 DPI voor drukkerijen of zware technische lijntekeningen waar dit echt van belang is
De flag reAnnotations in het tweede codeblok staat los van rePrinting. Neem dit op als de gebruiker verwacht dat stempels, markeringen en commentaarvakken op het papier verschijnen. Laat het weg voor uitsluitend inhoudelijke uitvoer. Beide flags kunnen vrijelijk worden gecombineerd
Paginarotatie
PDFium slaat de paginarotatie in de PDF op als een /Rotate-item, toegankelijk via Pdf.PageRotation, dat een TRotation-waarde retourneert (ro0, ro90, ro180, ro270). Het coördinatensysteem van de printer keert rotaties van 90 en 270 graden om ten opzichte van het scherm. Als u de onbewerkte PageRotation-waarde rechtstreeks doorgeeft aan RenderPage zonder enige aanpassing, zullen liggende pagina's ingebed in een staand document ondersteboven worden afgedrukt door de meeste Windows-printerstuurprogramma's. De oplossing is een eenvoudige omwisseling vóór de renderaanroep: map ro90 naar ro270 en ro270 weer naar ro90, en laat ro0 en ro180 ongewijzigd
Verifieer dit gedrag op uw specifieke doelprinter voordat u software uitbrengt. Het gedrag van het stuurprogramma met betrekking tot rotatie is niet uniform bij alle leveranciers, en sommige stuurprogramma's passen hun eigen rotatiecorrectie toe op GDI-niveau. Als u dubbele rotatie ziet, verwijdert u de omwisseling; ziet u helemaal geen correctie, dan voegt u deze toe. Een document met gemengde afdrukstanden (afwisselend staande en liggende pagina's) is de snelste manier om beide faalwijzen te ontdekken tijdens het testen
Geheugenbeheer tijdens een lange afdruktaak
Elke aanroep van RenderPage reserveert een nieuwe TBitmap die eigendom is van de aanroeper en die moet worden vrijgegeven. In de bovenstaande lus handelt het try/finally Bitmap.Free-blok dit correct af voor één pagina tegelijk. Bouw geen bitmaps op over verschillende pagina's: een 300-DPI render van een document met 200 pagina's zou gigabytes verbruiken voordat de eerste pagina de spooler bereikt. Geef elke bitmap vrij voordat u doorgaat naar de volgende pagina
Het AllocMem / FreeMem-paar in het overdrachtsblok volgt dezelfde regel. GetDIBSizes vertelt u hoeveel geheugen de DIB-header en pixelgegevens nodig hebben; u reserveert, vult, tekent en geeft alles vrij, dit alles binnen de scope van één pagina. Als u een van beide blokken laat lekken, zal de afdruktaak de heap van het proces uitputten bij documenten die langer zijn dan een paar dozijn pagina's
Als u afdruktaken moet uitvoeren op een achtergrondthread, houd TPdf en alle VCL-printeraanroepen dan op dezelfde thread. TPdf zelf is thread-onveilig voor instanties die de algemene staat (global state) van de PDFium DLL delen; het veiligste model is één TPdf per thread, waarbij elke thread zijn eigen exemplaar van het bestand laadt
De API voor het renderen en documenten die hier wordt weergegeven, maakt deel uit van de PDFium-component voor Delphi en C++Builder