Een gebruiker vult een PDF-formulier in een Delphi-viewer in, drukt op Print, en het vel komt naar buiten met elk veld leeg en de Letter-pagina uitgerekt om A4 te vullen. Beide gebreken zitten in de print-lus: PDFium Component schildert veldwaarden alleen in een bitmap (of bakt ze in een afgevlakte kopie), en elke pagina wil één schaalfactor die de verhouding bewaart
De formulierlaag is een tweede pass die een bitmap nodig heeft
Op Windows geeft de device-context-overload TPdf.RenderPage(DC, Left, Top, Width, Height, Rotation, Options) de HDC van de printer rechtstreeks aan FPDF_RenderPage. Die aanroep schildert de pagina-inhoud, plus markup-annotations als reAnnotations is gezet. De interactieve formulierlaag bereikt de DC nooit — hij hoort bij de form-fill-omgeving van PDFium en wordt getekend door FPDF_FFLDraw, wiens enige doel een FPDF_BITMAP is. Er is geen HDC-variant. De twee bitmap-overloads van TPdf.RenderPage draaien FPDF_RenderPageBitmap en FPDF_FFLDraw op dezelfde pixels zodra het document werd geopend met FormFill op True, de default. Met rePrinting in de opties verwijdert PDFium Component de veld-highlight vóór de form-pass en herstelt daarna de default-highlightkleur en -alpha, zodat de getinte kaders rond bewerkbare velden op het scherm blijven. rePrinting wijst ook naar de flag FPDF_PRINTING van de engine, die de uitvoer als print markeert — het geval dat ISO 32000-1 §12.5.3 (Tabel 165) aanpakt met annotation-flag bit 3: een conforme reader laat een annotation wiens Print-bit gewist is van het papier, hoe hij er op het scherm ook uitziet
Het platform beslist de rest. In de PDFium-engine tekent FPDF_RenderPage via Windows GDI naar een device context en bestaat alleen in Windows-builds, terwijl FPDF_FFLDraw naar een bitmap tekent en naar niets anders, op elk platform. Op Linux en de andere non-Windows-doelen is er geen GDI-aanroep te maken, dus de HDC-overload wordt bediend door een bitmap te renderen — formulierlaag incluis — en die op het canvas achter de handle te tekenen. De device-context-aanroep draagt veldwaarden dus mee buiten Windows en laat ze op Windows achterwege. Houd de branch expliciet in code die tussen Delphi en Lazarus wordt gedeeld: heeft het document een formulieromgeving en horen zijn waarden op papier, vraag dan op elk platform om een bitmap
Welke route hoort een printtaak te nemen?
Kies op grond van wat het papier moet dragen. Levende formulierwaarden wijzen naar de bitmaproute, vectoruitvoer naar de HDC-route, en een afgevlakte kopie geeft u beide voor de prijs van een opslag en een herlading. SetPdfPrintMode werkt op bibliotheksniveau, dus stel hem bij tussen taken in, nooit tussen pagina's
| Route | Formulierwaarden op papier | Uitvoer | Kost |
|---|---|---|---|
RenderPage(DC, …) | Nee, op Windows | Vector GDI; SetPdfPrintMode kiest EMF (de default) of een PostScript-niveau | Kleinste spool, snelste |
RenderPage naar een TBitmap, dan StretchDIBits | Ja, inclusief niet-opgeslagen bewerkingen | Raster op de pixelformaat die u vraagt | Breedte × Hoogte × 4 bytes per pagina, plus de DIB-kopie |
RenderPageBanded | Ja | Raster, één strook per keer | Eén band in het geheugen in plaats van het hele vel |
Afgevlakte kopie, dan RenderPage(DC, …) | Ja, als ingebakken pagina-inhoud | Vector | Opslaan, herladen, GenerateFormAppearances, FlattenAllPages(FLAT_PRINT) |
Een vol A4-vel op 600 dpi is ongeveer 4960 × 7016 device pixels, en op 32 bits per pixel is dat 139.197.440 bytes voor één pagina; hem naar een DIB kopiëren voor StretchDIBits verdubbelt de piek, een zware last voor een 32-bit-proces. Voor de Print-knop van een viewer blijft de bitmaproute de juiste default: een gebruiker die in een veld typt en op Print drukt verwacht die waarde op papier te zien, en banding houdt het geheugen begrensd. Documenten die de applicatie verlaten, zoals gearchiveerde afschriften, zijn beter geholpen met de afgevlakte kopie — de waarden worden gewone pagina-inhoud en de printer ontvangt vectoruitvoer
uses
System.SysUtils, System.IOUtils, PDFium, FPdfUtils; // FPdfUtils: FLAT_PRINT
function OpenFlattenedPrintCopy(Source: TPdf; out TempName: string): TPdf;
begin
TempName := TPath.Combine(TPath.GetTempPath, TPath.GetRandomFileName + '.pdf');
if not Source.SaveAs(TempName) then
raise Exception.Create('Could not write the print copy');
Result := TPdf.Create(nil);
try
Result.FileName := TempName; // FormFill blijft True (de default)
Result.Password := Source.Password; // de kopie houdt zijn versleuteling
Result.Active := True;
if not Result.Active then // een mislukte load laat TPdf inactief
raise Exception.Create('Could not reopen the print copy');
Result.GenerateFormAppearances; // /AP herbouwen vanuit de huidige /V
if not Result.FlattenAllPages(FLAT_PRINT) then
raise Exception.Create('Flattening failed');
except
Result.Free;
TFile.Delete(TempName);
raise;
end;
end;
De flatten-route heeft een ordeningsregel die zwaarder weegt dan de flag. FlattenAllPages bakt de appearance stream van elke widget (ISO 32000-1 §12.5.5) in de pagina-inhoud, en een appearance stream is precies wat de laatste producent schreef; de veldwaarde in /V speelt geen rol. Een formulier gevuld door een tool die zijn appearances nooit opnieuw genereerde vlakt af tot de oude tekst of tot niets, dus GenerateFormAppearances draait eerst: hij herbouwt tekstveld-appearances vanuit de huidige waarden en synchroniseert de checkbox- en radio-/AS-states, en hij heeft nodig dat FormFill True stond toen het document werd geopend. Doe dit alles op een kopie, want afvlakken herschrijft de pagina's van welke TPdf hij ook op draait, en een open viewer wil zijn velden bewerkbaar houden. De kopie gaat door de vector-branch van de print-routine hieronder met de formulierlaag uit; geef hem vrij en verwijder het tijdelijke bestand zodra de taak eindigt
Elke pagina passen op het afdrukbare gebied
Schaal één keer, met de kleinste van de twee factoren, en centreer het resultaat. Uitrekken naar Printer.PageWidth × Printer.PageHeight vervormt elke pagina waarvan de vorm van het papier verschilt, een Letter-pagina (612 × 792 points) op A4 of een liggende pagina op een staand vel. De printerkant is het afdrukbare gebied in device pixels (HORZRES bij VERTRES), met de marges van de driver al uitgesloten. Aan de PDF-kant melden TPdf.PageWidth en PageHeight de pagina al zoals getoond: een A4-staand blad opgeslagen met /Rotate 90 komt terug als 842 × 595 points. ISO 32000-1 §7.7.3.3 (Tabel 30) zegt dat de pagina bij tonen of printen met /Rotate geroteerd hoort te worden, PDFium past dat binnen de render-aanroep toe, en het argument Rotation van RenderPage is alleen de extra draai die een viewer toevoegt wanneer de gebruiker de weergave roteert. Geef daar de eigen PageRotation van de pagina door en de pagina draait twee keer. Wissel breedte en hoogte voor de pasvorm alleen wanneer die extra rotatie 90 of 270 graden is, hetzelfde paar regels dat thumbnail-pasvorm en zoom bij rotatie stuurt; de routine hieronder breidt de basislus voor PDF-documenten printen met PDFium Component uit met een formulierwaarde-branch en een annuleerpad dat de taak weggooit
uses
Winapi.Windows, System.SysUtils, Vcl.Graphics, Vcl.Forms, Vcl.Printers, PDFium;
procedure FitRect(PageW, PageH: Double; Swap: Boolean;
AreaW, AreaH: Integer; out X, Y, W, H: Integer);
var
T, Scale: Double;
begin
if Swap then
begin
T := PageW; PageW := PageH; PageH := T;
end;
if (PageW <= 0) or (PageH <= 0) then
begin
X := 0; Y := 0; W := AreaW; H := AreaH;
Exit;
end;
Scale := AreaW / PageW;
if AreaH / PageH < Scale then
Scale := AreaH / PageH;
W := Round(PageW * Scale);
H := Round(PageH * Scale);
X := (AreaW - W) div 2;
Y := (AreaH - H) div 2;
end;
procedure BlitBitmap(DC: HDC; Bitmap: TBitmap; X, Y: Integer);
var
InfoSize, ImageSize: DWORD;
Info: PBitmapInfo;
Bits: Pointer;
begin
GetDIBSizes(Bitmap.Handle, InfoSize, ImageSize);
Info := AllocMem(InfoSize);
try
Bits := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, Info^, Bits^);
StretchDIBits(DC, X, Y, Bitmap.Width, Bitmap.Height, 0, 0,
Bitmap.Width, Bitmap.Height, Bits, Info^, DIB_RGB_COLORS, SRCCOPY);
finally
FreeMem(Bits);
end;
finally
FreeMem(Info);
end;
end;
function PrintPages(Pdf: TPdf; FromPage, ToPage: Integer; FormLayer: Boolean;
ExtraRotation: TRotation; var Cancel: Boolean): Boolean;
const
Opts: TRenderOptions = [reAnnotations, rePrinting];
var
Page, X, Y, W, H: Integer;
Bitmap: TBitmap;
begin
Result := False;
Printer.BeginDoc;
try
for Page := FromPage to ToPage do
begin
if Page > FromPage then
Printer.NewPage;
Pdf.PageNumber := Page;
FitRect(Pdf.PageWidth, Pdf.PageHeight, ExtraRotation in [ro90, ro270],
Printer.PageWidth, Printer.PageHeight, X, Y, W, H);
if FormLayer then
begin
Bitmap := Pdf.RenderPage(0, 0, W, H, ExtraRotation, Opts);
if Bitmap <> nil then // nil bij een degenerate paginagrootte
try
BlitBitmap(Printer.Canvas.Handle, Bitmap, X, Y);
finally
Bitmap.Free;
end;
end
else
Pdf.RenderPage(Printer.Canvas.Handle, X, Y, W, H, ExtraRotation, Opts);
Application.ProcessMessages; // laat een Cancel-knop de vlag zetten
if Cancel then
Break;
end;
Result := not Cancel;
finally
if Result then
Printer.EndDoc // draagt de taak aan de spooler over
else
Printer.Abort; // annuleren of exception: er wordt niets geprint
end;
end;
Een viewer roept dit aan met Pdf.FormFill als FormLayer; de afgevlakte kopie geeft False door. EndDoc is de commit, geen opruimstap. Na BeginDoc draagt Printer.EndDoc alles wat tot dan is gerenderd als geldige taak aan de spooler over, dus een onvoorwaardelijke EndDoc in finally maakt van een geannuleerde print een halfgeprinte. De routine beslist in finally vanuit de succesvlag: EndDoc na de laatste pagina, Abort na een annulering of een exception. De lusuitgang heeft vergelijkbare zorg nodig, want Delphi en Free Pascal evalueren allebei de eindwaarde van een for-lus één keer, bij binnenkomst. ToPage of een kopieaantal op nul zetten vanuit een Cancel-handler stopt een lus die al draait niet; toets de vlag en doe Break, en bij geneste kopie- en collatie-lussen op elk niveau uitbreken. En omdat Application.ProcessMessages ook menusneltoetsen laat vuren, schakel Openen, Opslaan en Printen uit terwijl de taak draait
De raster-route binnen het geheugen houden
TPdf.RenderPageBanded rendert een pagina in horizontale stroken van een hoogte die u kiest en geeft elke strook door aan een callback. Elke strook is een plak van de pagina op ware grootte — er wordt niets herschaald — en hij draagt dezelfde veldwaarden als een bitmap van de hele pagina. Met banden van 512 rijen heeft een pagina van 4960 pixels breed ongeveer 10 MB per band nodig in plaats van 139 MB voor het vel. Ruwweg een veertiende van het geheugen. TPdfBandCallback is een method pointer die de bandindex ontvangt, de bovenoffset van de band in paginapixels en de bitmap, en True teruggeeft om door te gaan; RenderPageBanded geeft het bandaantal terug, 0 bij mislukking
type
TBandPrinter = class
private
FDC: HDC;
FLeft, FTop: Integer;
public
constructor Create(DC: HDC; Left, Top: Integer);
function PrintBand(BandIndex, BandTopY: Integer; Bitmap: TBitmap): Boolean;
end;
constructor TBandPrinter.Create(DC: HDC; Left, Top: Integer);
begin
inherited Create;
FDC := DC;
FLeft := Left;
FTop := Top;
end;
function TBandPrinter.PrintBand(BandIndex, BandTopY: Integer;
Bitmap: TBitmap): Boolean;
begin
BlitBitmap(FDC, Bitmap, FLeft, FTop + BandTopY);
Result := True; // False stopt de resterende banden
end;
// aangeroepen vanuit de paginalus in plaats van de hele-pagina-bitmap-branch
procedure PrintPageBanded(Pdf: TPdf; Page, X, Y, W, H: Integer;
ExtraRotation: TRotation);
var
Bands: TBandPrinter;
begin
Bands := TBandPrinter.Create(Printer.Canvas.Handle, X, Y);
try
if Pdf.RenderPageBanded(W, H, 512, Bands.PrintBand, ExtraRotation,
[reAnnotations, rePrinting]) = 0 then
raise Exception.CreateFmt('Page %d could not be rendered', [Page]);
finally
Bands.Free;
end;
end;
Wat hoort de host te doen met ViewerPreferences en permissiebits?
Lees ze en handel ernaar in uw eigen code; PDFium meldt ze en rendert de pagina toch. Een producent kan printhints opslaan in de dictionary /ViewerPreferences van de catalog (ISO 32000-1 §12.2, Tabel 150), en TPdf legt degenen bloot die een printdialoog vormgeven:
PrintScaling(Boolean) is False wanneer het bestand/PrintScaling /Nonezegt, wat printen op ware grootte betekent, en True voor/AppDefaultof een ontbrekende entry. De standaard wil hem gehonoreerd zelfs wanneer er geen printdialoog wordt getoondPrintPaperHandling(TPrintPaperHandling) draagt/DuplexalsphSimplex,phDuplexFlipShortEdge,phDuplexFlipLongEdgeofphUndefined.SetPdfPrintPaperHandlingDevModeschrijft hem inDEVMODE.dmDuplexen zetDM_DUPLEX; voorphUndefinedgeeft hij False terug en laat het record met rust, dus de driver-default overleeftPrintCopies(Integer) is/NumCopies, het kopieaantal voor de dialoog
Permissiebits volgen dezelfde regel. Voor de standaard security handler wijst ISO 32000-1 §7.6.3.2 (Tabel 22) bit 3 van /P toe aan printen en, voor revision 3 en latere handlers, bit 12 aan printen op volle getrouwheid; met bit 3 gezet en bit 12 gewist is printen beperkt tot een lage weergave van de appearance, mogelijk gedegradeerd. TPdf.GrantedPermissions decodeert het masker naar permPrint en permPrintHighQuality, en HasPermission toetst één waarde; de engine rendert wat u ook vraagt, dus afdwingen is het werk van de host, normaal een uitgeschakelde Print-knop. Wanneer alleen permPrint is toegekend is de lezing die bij de standaard past de bitmaproute op een bescheiden resolutie in plaats van de vector- of PostScript-uitvoer die een volledige toekenning toestaat. De bits beschrijven het document zoals geopend: een bestand dat met zijn owner password werd opengemaakt meldt volledige toegang, en UserHasPermission antwoordt alleen over de user-level-bits
uses
Winapi.Windows, System.SysUtils, Vcl.Dialogs, Vcl.Printers, PDFium;
procedure PreparePrintJob(Pdf: TPdf; Dialog: TPrintDialog;
out ActualSize, HighQuality: Boolean);
var
Device, Driver, Port: array[0..MAX_PATH] of Char;
DevModeHandle: THandle;
DevMode: PDeviceMode;
begin
if not Pdf.HasPermission(permPrint) then
raise Exception.Create('This document does not permit printing');
HighQuality := Pdf.HasPermission(permPrintHighQuality);
ActualSize := not Pdf.PrintScaling; // /PrintScaling /None
Printer.GetPrinter(Device, Driver, Port, DevModeHandle);
if DevModeHandle <> 0 then
begin
DevMode := GlobalLock(DevModeHandle);
try
SetPdfPrintPaperHandlingDevMode(DevMode^, Pdf.PrintPaperHandling);
finally
GlobalUnlock(DevModeHandle);
end;
Printer.SetPrinter(Device, Driver, Port, DevModeHandle); // de dialoog begint ermee
end;
Dialog.MinPage := 1;
Dialog.MaxPage := Pdf.PageCount;
Dialog.Options := Dialog.Options + [poPageNums];
if Pdf.PrintCopies > 1 then
Dialog.Copies := Pdf.PrintCopies;
end;
// gebruikt in plaats van FitRect wanneer ActualSize True is
procedure ActualSizeRect(Pdf: TPdf; out X, Y, W, H: Integer);
begin
// 72 points per inch aan de PDF-kant, device pixels per inch op de printer
W := Round(Pdf.PageWidth / 72 * GetDeviceCaps(Printer.Handle, LOGPIXELSX));
H := Round(Pdf.PageHeight / 72 * GetDeviceCaps(Printer.Handle, LOGPIXELSY));
X := (Printer.PageWidth - W) div 2; // negatief wanneer de pagina groter is
Y := (Printer.PageHeight - H) div 2; // dan het afdrukbare gebied: randen vallen af
end;
Will-print- en did-print-actions draaien op de echte printgrens
Printscripts op documentniveau draaien wanneer de host ze draait. De additional-actions-dictionary van de catalog (ISO 32000-1 §12.6.3, Tabel 197) definieert WP, een JavaScript-action vóór het printen, en DP, erna. PDFium Component legt beide bloot via ExecuteDocumentLifecycleAction(dlaWillPrint, ErrorMessage) en dlaDidPrint, en houdt ze buiten de render-aanroepen: thumbnails, snapshots en conversies renderen ook, en geen ervan hoort voor een documentscript als de print van een gebruiker te lijken. De volgorde moet u zelf goed neerzetten. Een will-print-script dat een datum stempelt of een veld "printed by" vult verandert formulierwaarden, dus draai hem voordat de eerste pagina wordt gerenderd en, op de flatten-route, voordat de kopie wordt opgeslagen; vuur did-print pas af nadat EndDoc de taak heeft overgedragen. De aanroep geeft False terug met een melding wanneer de formulieromgeving ontbreekt of LifecycleActionPolicy hem weigert; voor onbetrouwbare bestanden schakelt lapDisabled document-actions uit. Het bredere scripting-verhaal staat in AcroForm JavaScript draaien met host-events
Op Windows hoort een taak die formulierwaarden moet dragen op de bitmap-branch: RenderPage naar een TBitmap op gewone resoluties, RenderPageBanded op 600 dpi en hoger. Houd de device-context-overload van PDFium Component voor afgevlakte kopieën en voor documenten zonder velden, waar zijn vectoruitvoer het betere resultaat is