Les coordonnées PDF sont en points, les coordonnées d'impression sont en unités de périphérique, et les deux n'ont rien en commun tant que vous ne les convertissez pas explicitement. Cette incompatibilité est à l'origine de la plupart des résultats d'impression défectueux dans les applications Delphi : le code envoie le bon fichier mais la page sort rognée, étirée ou blanche. PDFium VCL gère proprement le côté rendu ; la mécanique d'impression est du VCL standard. Les deux s'assemblent avec un code modeste une fois que vous comprenez ce qu'attend chaque côté.
Fonctionnement du pipeline restitution-puis-impression
PDFium VCL ne communique pas directement avec les imprimantes. Le schéma est le suivant : restituez une page dans un TBitmap à la résolution souhaitée, puis transférez ce bitmap sur le canvas de l'imprimante avec StretchDIBits. TPdf.RenderPage retourne un bitmap dont l'appelant est propriétaire, vous contrôlez donc les dimensions en pixels. Passez [rePrinting] dans l'ensemble d'options et PDFium bascule vers un chemin de rendu qui omet les effets réservés à l'écran tels que le sous-pixel hinting LCD, et gère correctement la MediaBox de la page pour l'impression. Sans rePrinting, ce que vous envoyez à l'imprimante est un rendu écran : acceptable sur un moniteur, mais tendant à produire une sortie plus floue sur les imprimantes haute résolution, car les décisions de hinting optimisées pour 96 DPI ne conviennent pas à 300 ou 600 DPI.
TPdf.Active est la seule condition à vérifier avant de toucher à une propriété de page. Le composant avale silencieusement les erreurs de chargement : affecter Active := True sur un fichier endommagé ou protégé par un mot de passe ne lève pas d'exception, mais laisse simplement Active à False. Vérifiez-le toujours après l'affectation. Lire PageCount ou PageWidth sur un document inactif retourne zéro, ce qui produit des opérations silencieuses très difficiles à diagnostiquer une fois qu'elles atteignent le spouleur.
Une boucle d'impression minimale
Le cas de base le plus simple charge un fichier, ouvre un travail d'impression, itère les pages et ferme. Le seul détail délicat est que Printer.NewPage ne doit pas être appelé avant la première page, d'où le drapeau FirstPage. Le transfert StretchDIBits passe par GetDIBSizes et GetDIB pour extraire les bits indépendants du périphérique depuis le handle de bitmap, puis les peint sur le canvas de l'imprimante à la taille complète de la page :
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;
Passer Printer.PageWidth et Printer.PageHeight comme dimensions du bitmap signifie que vous restituez à la taille de pixel native de l'imprimante, qui tient déjà compte de la résolution du périphérique. L'appel StretchDIBits mappe ensuite ces pixels 1:1 sur la page. Cela vous offre la meilleure fidélité possible sans arithmétique DPI explicite, mais ne fonctionne que lorsque la page PDF et le papier physique ont la même taille. Lorsqu'ils diffèrent, vous avez besoin d'une mise à l'échelle explicite.
Mise à l'échelle quand les tailles de page et de papier diffèrent
Une page PDF en A4 portrait ne s'adapte pas automatiquement à une imprimante US Letter, et une page en paysage envoyée à une imprimante en orientation portrait sera rognée. L'approche standard consiste à calculer un facteur d'échelle uniforme à partir du ratio pixels d'imprimante / points PDF, puis à l'appliquer aux deux dimensions pour préserver le rapport d'aspect. Pdf.PageWidth et Pdf.PageHeight exposent les dimensions de la page courante en points, où un point vaut 1/72 pouce. Multiplier par une résolution cible en DPI et diviser par 72 convertit en pixels à cette résolution. Prenez le Min des ratios X et Y pour obtenir la plus grande échelle qui tient encore dans la zone imprimable :
// 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;
Restituer à Dpi = 300 convient à la plupart des imprimantes de bureau. À 600 DPI, le bitmap d'une seule page A4 représente environ 34 mégapixels, soit environ 100 Mo en bitmap 32 bits ; le gain de qualité pour les documents de texte ordinaires est minime et le coût mémoire par page est significatif. Réservez 600 DPI aux reprographies ou aux dessins techniques avec beaucoup de vecteurs où cela apporte réellement un bénéfice.
Le drapeau reAnnotations dans le deuxième bloc de code est indépendant de rePrinting. Incluez-le lorsque l'utilisateur souhaite voir les tampons, surlignages et zones de commentaires sur papier. Omettez-le pour une sortie contenu uniquement. Les deux drapeaux peuvent être combinés librement.
Rotation de page
PDFium stocke la rotation de page dans le PDF sous forme d'entrée /Rotate, accessible via Pdf.PageRotation, qui retourne une valeur TRotation (ro0, ro90, ro180, ro270). Le système de coordonnées de l'imprimante inverse les rotations de 90 et 270 degrés par rapport à l'écran. Si vous passez la valeur brute de PageRotation directement à RenderPage sans ajustement, les pages en paysage intégrées dans un document en portrait s'imprimeront à l'envers sur la plupart des pilotes d'imprimante Windows. La correction est un simple échange avant l'appel de rendu : mappez ro90 vers ro270 et ro270 vers ro90, en laissant ro0 et ro180 inchangés.
Vérifiez ce comportement sur votre imprimante cible spécifique avant de livrer. Le comportement des pilotes en matière de rotation n'est pas uniforme entre les fabricants, et certains pilotes appliquent leur propre correction de rotation au niveau GDI. Si vous observez une double rotation, supprimez l'échange ; si vous ne voyez aucune correction, ajoutez-le. Un document à orientation mixte avec alternance de pages portrait et paysage est le moyen le plus rapide de détecter l'un ou l'autre défaut lors des tests.
Gestion de la mémoire sur un long travail d'impression
Chaque appel à RenderPage alloue un nouveau TBitmap dont l'appelant est propriétaire et qu'il doit libérer. Dans la boucle ci-dessus, le bloc try/finally Bitmap.Free gère cela correctement page par page. N'accumulez pas de bitmaps entre les pages : un rendu à 300 DPI d'un document de 200 pages consommerait des gigaoctets avant que la première page n'atteigne le spouleur. Libérez chaque bitmap avant de passer à la page suivante.
La paire AllocMem / FreeMem à l'intérieur du bloc de transfert suit la même règle. GetDIBSizes indique la quantité de mémoire nécessaire pour l'en-tête DIB et les données pixel ; vous allouez, remplissez, peignez et libérez le tout dans la portée d'une seule page. Laisser fuir l'un ou l'autre bloc causera l'épuisement du tas du processus sur des documents de plus de quelques dizaines de pages.
Si vous avez besoin d'exécuter des travaux d'impression sur un thread d'arrière-plan, conservez TPdf et tous les appels d'imprimante VCL sur le même thread. TPdf lui-même n'est pas thread-safe entre les instances partageant l'état global de la DLL PDFium ; le modèle le plus sûr est un TPdf par thread, chacun chargeant sa propre copie du fichier.
L'API de rendu et de document présentée ici fait partie du composant PDFium VCL pour Delphi et C++Builder.