Le ticket tenait en trois lignes : « L'aperçu montre le formulaire centré. La page imprimée est décalée vers le haut et la gauche, et la bordure est rognée sur deux côtés. » Le code de rendu n'avait rien d'anormal. L'aperçu dessinait la page par rapport à la feuille de papier, tandis que l'origine du device context de l'imprimante se trouve au coin de la zone imprimable — et l'imprimante laser concernée ne pouvait pas atteindre les 4,2 mm extérieurs de la feuille. Toute fonction d'impression Delphi finit par rencontrer ce décalage, et le bon moment pour le traiter est avant que le premier client imprime un formulaire bordé. losLab PDF Library (PDFlibPas) couvre tout le chemin avec des appels de rendu vers device context, une couche de configuration d'imprimante virtuelle et des bitmaps d'aperçu générés depuis les métriques propres de l'imprimante.
La géométrie du papier n'est pas la géométrie imprimable
Trois rectangles décrivent toute cible d'impression, et les confondre produit exactement le ticket ci-dessus. Le rectangle papier est la feuille physique. Le rectangle imprimable est la région que le moteur d'impression peut atteindre. Le décalage entre leurs origines est la marge matérielle ; il varie selon le modèle d'imprimante et parfois selon le bac. La couche d'impression de la bibliothèque modélise les trois : la classe sous-jacente TPLPrinter expose PageWidth et PageHeight pour la zone imprimable, FullPageWidth et FullPageHeight pour la feuille, et PrintOffsetX et PrintOffsetY pour l'écart, le tout en pixels de périphérique à la résolution rapportée par GetDPI. Un aperçu honnête réduit les mêmes trois rectangles à la résolution écran au lieu de peindre la page dans le rectangle disponible du contrôle.
Aperçu écran via RenderPageToDC
Pour un contrôle d'aperçu à l'écran, RenderPageToDC(DPI, Page, DC) dessine une page du document chargé directement sur n'importe quel device context GDI — canvas de TPaintBox, bitmap hors écran ou DC de métafichier. L'argument DPI définit le zoom : 96 approxime une vue 100 % sur un écran classique, et le doubler double la taille rendue.
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// these three are sticky library state, not per-call parameters:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
Le piège est que le chemin de rendu DC est piloté par l'état persistant de la bibliothèque plutôt que par des paramètres par appel. SetRenderDCOffset, SetRenderDCErasePage et SetRenderCropType persistent chacun jusqu'à modification ; une boucle de miniatures exécutée après que l'utilisateur a ajusté la vue zoomée hérite donc de l'offset ou du recadrage laissé par le chemin précédent — et le symptôme est un aperçu qui dérive seulement dans certaines séquences de navigation, pénible à reproduire. Définir tout l'état pertinent au début du gestionnaire de peinture, comme ci-dessus, ne coûte rien et supprime toute cette classe de bugs. Un second multiplicateur se cache à proximité : la résolution de sortie effective est l'échelle de rendu multipliée par l'argument DPI, et SetRenderScale vaut 1.0 par défaut mais persiste une fois modifié ; une fonction d'export qui l'a ajusté redimensionne discrètement tous les aperçus suivants.
Les visualiseurs à défilement et repeints partiels disposent d'une variante dédiée : RenderPageToDCClip prend une spécification de clip en plus du device context, de sorte que l'invalidation d'une bande de la fenêtre ne repeint que cette bande au lieu de rerasteriser toute la page. À fort zoom sur des pages grand format, c'est la différence entre un visualiseur qui suit la barre de défilement et un visualiseur qui traîne derrière elle.
Un job d'impression qui correspond à l'aperçu
Côté impression, le travail passe par une imprimante virtuelle : NewCustomPrinter clone une imprimante système dans une configuration privée à la bibliothèque, et SetupPrinter ajuste ce clone — papier avec le réglage 1 (une constante DMPAPER_*) et orientation avec le réglage 11 — sans toucher au DevMode global de la machine. Un service peut imprimer des étiquettes A4 pendant que l'imprimante par défaut de l'hôte reste en Letter, sans rien restaurer ensuite.
var
Pdf: TPDFlib;
Virt: WideString;
Opt: Integer;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile('report.pdf', '') <> 1 then
raise Exception.Create('load failed');
Virt := Pdf.NewCustomPrinter(Pdf.GetDefaultPrinterName);
Pdf.SetupPrinter(Virt, 1, 9); // setting 1 = paper, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // setting 11 = orientation, 1 = portrait
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // fit to paper, auto-rotate + center
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
PrintOptions mérite une lecture attentive : il renvoie un handle d'options qui doit être passé à PrintDocument ou PrintPages. Ce n'est pas un état ambiant. Construire les options puis oublier de passer le handle est un échec silencieux — le job imprime avec les valeurs par défaut, et personne ne remarque avant qu'une politique d'ajustement à la page était attendue et qu'une grande page sorte rognée. L'argument de mise à l'échelle porte la politique : aucune mise à l'échelle préserve l'exactitude dimensionnelle pour les formulaires mesurés à la règle, l'ajustement à la page redimensionne tout, et la réduction des grandes pages n'intervient que lorsqu'une page dépasse la zone imprimable — généralement le bon défaut pour des ensembles de documents mixtes. Le flag auto-rotation et centrage gère les pages paysage sans chemin de code séparé.
Les applications qui pilotent déjà un TPrinter via le flux de dialogue VCL peuvent le remettre directement : PrintDocumentToPrinterObject et PrintPagesToPrinterObject acceptent l'instance TPrinter configurée, ce qui conserve la boîte de dialogue d'impression standard comme surface de configuration utilisateur pendant que la bibliothèque rend les pages. Les deux approches se mélangent mal dans un même chemin de code — choisissez l'imprimante virtuelle pour les services sans surveillance et la route TPrinter pour les applications interactives, et le contrat géométrique reste unique.
Les environnements sans surveillance disposent aussi d'une sortie sans périphérique physique : les appels d'impression par plage de pages ont des variantes print-to-file, réponse pratique pour tester en régression la géométrie d'impression sur un serveur de build sans file de pilotes. Rendez le même document avec les mêmes options vers un artefact fichier à chaque build, et une régression de géométrie devient un diff plutôt qu'un rapport client.
Bitmaps d'aperçu avec les métriques propres de l'imprimante
Un aperçu rendu à 96 DPI avec une taille de page supposée répond à la mauvaise question. GetPrintPreviewBitmapToString construit l'aperçu avec la même imprimante personnalisée et le même handle d'options que le job final ; la taille du papier, l'orientation, la politique d'échelle, la rotation et l'offset matériel participent donc tous — ce qui revient est ce que la feuille montrera.
procedure ShowPrinterTruePreview(Pdf: TPDFlib; const Virt: WideString; Opt: Integer);
var
Data: AnsiString;
Strm: TMemoryStream;
Bmp: TBitmap;
begin
Data := Pdf.GetPrintPreviewBitmapToString(Virt, 1, Opt, 1200, 0);
Strm := TMemoryStream.Create;
try
Strm.WriteBuffer(PAnsiChar(Data)^, Length(Data));
Strm.Position := 0;
Bmp := TBitmap.Create;
try
Bmp.LoadFromStream(Strm);
PreviewImage.Picture.Assign(Bmp);
finally
Bmp.Free;
end;
finally
Strm.Free;
end;
end;
L'argument MaxDimension plafonne le grand côté du bitmap : 1200 pixels restent nettement assez précis pour une boîte de dialogue d'aperçu et gardent la mémoire modeste même pour des plans d'ingénierie au format E, où un rendu pleine résolution aux 600 DPI de l'imprimante atteindrait des gigaoctets.
Mémoriser les choix d'imprimante de l'utilisateur
Les boîtes de dialogue d'impression qui oublient leurs réglages entre sessions génèrent leurs propres tickets de support. La paire DevMode — GetPrinterDevModeToString et SetPrinterDevModeFromString — sérialise toute la configuration pilote d'une imprimante dans une chaîne opaque que vous pouvez stocker dans les préférences utilisateur et restaurer à la session suivante, y compris les options spécifiques au pilote qu'aucune API générique ne modélise. Persistez l'imprimante par nom depuis GetPrinterNames, jamais par index de liste : l'ordre des index change à chaque ajout ou retrait d'imprimante, et GetDefaultPrinterName couvre le repli lorsque le périphérique mémorisé a disparu.
Le choix de bac complète l'histoire de persistance : GetPrinterBins rapporte les sources papier exposées par un pilote, ce qui compte pour les workflows à papier à en-tête où la première page vient du bac à en-tête et les suivantes du stock ordinaire — une politique que les utilisateurs s'attendent à voir mémorisée avec le reste.
Questions qui reviennent dans les projets d'impression
Pourquoi la page imprimée est-elle décalée par rapport à mon aperçu ?
Presque toujours à cause de la marge matérielle : l'origine du DC d'imprimante est le coin de la zone imprimable, pas celui du papier. Modélisez explicitement l'offset dans l'aperçu, ou générez les aperçus avec GetPrintPreviewBitmapToString afin que la géométrie de l'imprimante y soit intégrée.
Comment imprimer une sélection de pages comme 2-5 et 12 ?
PrintPages accepte une chaîne de plage — passez le nom de l'imprimante virtuelle, '2-5,12' et le handle d'options. La même syntaxe de plage pilote les variantes print-to-file.
L'aperçu et le job d'impression peuvent-ils utiliser des moteurs de rendu différents ?
Ils le peuvent, mais ils ne devraient pas : la sélection de moteur s'applique aux destinations écran comme imprimante, et mélanger les moteurs réintroduit exactement la dérive de fidélité qu'un aperçu fidèle à l'imprimante élimine. Les compromis entre les moteurs intégré, Cairo et PDFium sont pesés dans le rendu PDF multi-moteur en Delphi.
Les documents trop grands pour être chargés confortablement avant impression peuvent être ouverts par le chemin Direct Access décrit dans la fusion, la scission et l'accès direct de grands PDF, qui rend les pages vers un device context depuis un handle de fichier sans construire l'arbre du document. La référence complète de l'API d'impression se trouve sur la page produit losLab PDF Library pour Delphi.