PDF-koordinater är i punkter, skrivarkoordinater är i enhetsenheter (device units), och de två har ingenting med varandra att göra tills du konverterar dem avsiktligt. Den obalansen är roten till de flesta dåliga utskrifter i Delphi-applikationer: koden skickar rätt fil men sidan kommer ut beskuren, utdragen eller tom. PDFium Component hanterar renderingssidan snyggt; skrivar-rördragningen är standard VCL. De två passar ihop med en blygsam mängd kod när du väl förstår vad varje sida förväntar sig
Hur rendera-sedan-skriv-ut-pipelinen fungerar
PDFium Component pratar inte direkt med skrivare. Mönstret är: rendera en sida till en TBitmap med den upplösning du vill ha, och överför sedan den bitmappen till skrivarens arbetsyta med StretchDIBits. TPdf.RenderPage returnerar en bitmapp som ägs av anroparen, så du styr pixeldimensionerna. Skicka med [rePrinting] i uppsättningen av alternativ och PDFium växlar sin renderingsväg till en som utelämnar effekter endast för skärmar, såsom subpixel-hinting för LCD, och hanterar sidans MediaBox korrekt för utskrift. Utelämna rePrinting och det du skickar till skrivaren är en skärmrendering, vilket ser bra ut på en bildskärm men tenderar att ge ett suddigare resultat på högupplösta skrivare eftersom hinting-besluten som fattas för 96 DPI-skärmar inte passar utskrifter på 300 eller 600 DPI
TPdf.Active är den enda grinden att kontrollera innan du rör någon sidegenskap. Komponenten sväljer inläsningsfel tyst: att sätta Active := True på en skadad eller lösenordsskyddad fil kastar inget undantag; det lämnar helt enkelt Active som False. Kontrollera den alltid efter tilldelningen. Att läsa PageCount eller PageWidth på ett inaktivt dokument returnerar noll, vilket ger tysta ingen-operationer som är mycket svåra att diagnostisera när de väl når utskriftshanteraren
En minimal utskriftsslinga
Det enklaste fungerande fallet laddar en fil, öppnar ett utskriftsjobb, itererar sidor och stänger. Den enda knepiga detaljen är att Printer.NewPage inte får anropas före den första sidan, därav flaggan FirstPage. StretchDIBits-överföringen går via GetDIBSizes och GetDIB för att dra enhetsoberoende bitar från bitmapp-handtaget, och målar dem sedan på skrivarens arbetsyta med full sidstorlek:
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;
Att skicka in Printer.PageWidth och Printer.PageHeight som bitmapp-dimensioner innebär att du renderar i skrivarens inbyggda pixelstorlek, vilket redan tar hänsyn till enhetens DPI. StretchDIBits-anropet mappar sedan dessa pixlar 1:1 på sidan. Detta ger dig bästa möjliga trohet utan någon explicit DPI-aritmetik, men det fungerar bara när PDF-sidan och det fysiska papperet råkar ha samma storlek. När de skiljer sig åt behöver du explicit skalning
Skalning när sid- och pappersstorlekar skiljer sig åt
En PDF-sida i stående A4-format passar inte automatiskt i en US Letter-skrivare, och en liggande sida som matas in i en stående skrivare kommer att beskäras. Standardmetoden är att beräkna en enhetlig skalfaktor från förhållandet mellan skrivarpixlar och PDF-punkter, och sedan tillämpa den på båda dimensionerna så att bildförhållandet bevaras. Pdf.PageWidth och Pdf.PageHeight exponerar de aktuella sidmåtten i punkter, där en punkt är 1/72 tum. Att multiplicera med en mål-DPI och dividera med 72 konverterar till pixlar vid den upplösningen. Använd Min av X- och Y-förhållandena för att få den största skalan som fortfarande ryms inom det utskrivbara området:
// 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;
Att rendera med Dpi = 300 passar de flesta kontorsskrivare. Vid 600 DPI blir bitmappen för en enda A4-sida ungefär 34 megapixel, vilket är cirka 100 MB som en 32-bitars bitmapp; kvalitetsvinsten för vanliga textdokument är minimal och minneskostnaden per sida är betydande. Spara 600 DPI för tryckerier eller vektortunga tekniska ritningar där det verkligen spelar roll
Flaggan reAnnotations i det andra kodblocket är oberoende av rePrinting. Inkludera den när användaren förväntar sig att stämplar, markeringar och kommentarsrutor ska synas på papperet. Utelämna den för endast innehåll. Båda flaggorna kan kombineras fritt
Sidrotation
PDFium lagrar sidrotation i PDF:en som en /Rotate-post, åtkomlig via Pdf.PageRotation, som returnerar ett TRotation-värde (ro0, ro90, ro180, ro270). Skrivarens koordinatsystem inverterar 90 och 270 graders rotationer jämfört med skärmen. Om du skickar det råa PageRotation-värdet direkt till RenderPage utan någon justering, kommer liggande sidor som är inbäddade i ett stående dokument att skrivas ut upp och ner på de flesta Windows-skrivardrivrutiner. Lösningen är ett enkelt byte före renderingsanropet: mappa ro90 till ro270 och ro270 tillbaka till ro90, med ro0 och ro180 oförändrade
Verifiera detta beteende på just din målskrivare innan du levererar. Drivrutinsbeteende kring rotation är inte enhetligt hos olika leverantörer, och vissa drivrutiner tillämpar sin egen rotationskorrigering på GDI-nivå. Om du ser dubbel rotation, ta bort bytet; om du inte ser någon korrigering alls, lägg till det. Ett dokument med blandad orientering med omväxlande stående och liggande sidor är det snabbaste sättet att fånga upp båda fellägena under testning
Minneshantering under ett långt utskriftsjobb
Varje anrop till RenderPage allokerar en ny TBitmap som anroparen äger och måste frigöra. I loopen ovan hanterar blocket try/finally Bitmap.Free detta korrekt för en sida i taget. Ackumulera inte bitmappar över sidor: en 300-DPI-rendering av ett 200-sidigt dokument skulle förbruka gigabyte innan den första sidan når utskriftshanteraren. Frigör varje bitmapp innan du går vidare till nästa sida
Paret AllocMem / FreeMem inuti överföringsblocket följer samma regel. GetDIBSizes talar om för dig hur mycket minne DIB-huvudet och pixeldata behöver; du allokerar, fyller, målar och frigör allt inom ramen för en enda sida. Att låta något av blocken läcka kommer att få utskriftsjobbet att tömma processheapen på dokument som är längre än några dussin sidor
Om du behöver köra utskriftsjobb på en bakgrundstråd, behåll TPdf och alla VCL-skrivaranrop på samma tråd. TPdf i sig är inte trådsäkert över instanser som delar PDFium-DLL:ens globala tillstånd; den säkraste modellen är en TPdf per tråd, där varje laddar sin egen kopia av filen
Renderings- och dokument-API:et som visas här är en del av PDFium Component för Delphi och C++Builder