Trois rastériseurs peuvent lire le même PDF et être en désaccord sur ce qu'il dit. Le moteur intégré de PDF Library for Delphi est celui qui se livre sans fichier supplémentaire et rend tout avec compétence, ce qui lui vaut la place par défaut. Cairo apporte un pipeline de transparence et d'anticrénelage différent, et c'est en général celui vers lequel on se tourne quand les masques doux ou les modes de fusion sortent mal ailleurs. PDFium porte le code de rendu de Chrome, si bien qu'une page correcte dans un navigateur est en général correcte sous PDFium aussi, au prix d'une DLL volumineuse et d'un nombre de bits qu'il exige de faire correspondre. Aucun des trois n'est correct dans l'absolu. La justesse s'apprécie document par document, et la seule manière honnête de savoir quel moteur traite bien un corpus donné est de faire passer ce corpus par chacun d'eux
Voilà l'argument pour traiter le moteur comme un choix d'exécution plutôt que de compilation. PDF Library for Delphi, la bibliothèque PDF de losLab pour Delphi et C++Builder, place les trois derrière une seule surface de rendu, si bien que la décision coûte un entier plutôt qu'une branche de code. Le reste se ramène à choisir entre eux sans danger, à confirmer quels moteurs un binaire déployé porte réellement, et à empêcher l'état de rendu d'empoisonner discrètement le travail suivant
Trois rastériseurs derrière une seule surface d'appel
La bibliothèque numérote ses moteurs. Le moteur 1 est le rendu intégré, celui par défaut, avec les options de lissage GDI+ sous Windows. Le moteur 2 est Cairo et le moteur 3 est PDFium, tous deux sélectionnés à l'exécution par SelectRenderer. Les deux moteurs externes se chargent depuis des DLL dont vous fournissez les chemins avec SetCairoFileName et SetPDFiumFileName avant de les sélectionner. Quel que soit le moteur actif, le travail passe par les mêmes appels : RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Changer de moteur déplace un seul nombre ; le reste de votre code de rendu ne s'en aperçoit jamais
Le modèle de destination va bien au-delà des bitmaps. La classe de rendu vise aussi les métafichiers (WMF, EMF, EMF+), EPS, les contextes de périphérique directs, les imprimantes et HTML5, Cairo et PDFium n'apparaissant comme destinations supplémentaires que lorsqu'ils ont été compilés. La sortie raster est l'endroit où les trois moteurs divergent le plus visiblement, et c'est donc elle que les exemples utilisent ici
Ne supposez jamais qu'un moteur existe : sondez au démarrage
Cairo et PDFium sont des fonctionnalités à compilation conditionnelle, ce qui veut dire qu'un binaire peut être construit entièrement sans eux. Dans ce cas, demander le moteur 2 ou 3 ne lève rien. SelectRenderer renvoie simplement une valeur différente de l'identifiant demandé, et un code qui ignore la valeur de retour continue de rendre avec le moteur déjà actif. La défense est une sonde de démarrage qui demande à chaque moteur de se déclarer et consigne la réponse :
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // le moteur 1 est toujours présent
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // rétablir le défaut avant le vrai travail
end;
Exécutez cette sonde une fois au démarrage et écrivez son résultat dans le journal à côté de chaque travail de rendu. La question la plus fréquente quand un client signale une différence de rendu est de savoir quels moteurs son installation possède réellement, et une réponse d'une ligne posée dans le journal la règle sans session de bureau à distance. Un effet de bord utile : si SetPDFiumFileName renvoie lui-même 0, vous savez déjà que le problème est la DLL (mauvais chemin, mauvais nombre de bits, dépendance manquante) et non un binaire compilé sans prise en charge de PDFium, car l'appel de chemin n'a rien résolu avant même que SelectRenderer ne s'exécute
Dix formats de sortie derrière un seul entier Options
Le paramètre Options des appels de rendu sélectionne l'encodage de sortie : 0 pour BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ et 9 HTML5. PNG (5) est le défaut raisonnable pour les aperçus et les images de page d'archive. JPEG (1), associé à SetJPEGQuality, est le meilleur choix pour les numérisations photographiques où la taille du fichier compte plus que la netteté des contours
Un format cache une exigence sur le flux cible. Le chemin BMP écrit d'abord les données d'image, puis revient à l'offset 0x26 pour corriger les champs de résolution de l'en-tête. Pointez cela vers un flux en avance seule, une enveloppe de compression ou une socket réseau, et l'appel échoue d'une manière qui ressemble à une faute de moteur sans en être une. Quand une cible non repositionnable est inévitable, produisez du PNG à la place, ou faites transiter le BMP par un flux mémoire et recopiez-le une fois complet
Le DPI que vous passez n'est pas le DPI que vous obtenez
Chaque appel de rendu prend un argument DPI, mais la résolution réellement obtenue est cette valeur multipliée par le facteur de rendu global. SetRenderScale démarre à 1.0, et une fois que vous le changez, le nouveau facteur s'applique en silence à tous les rendus ultérieurs de cette instance :
PDF.SetRenderScale(2.0); // chaque rendu ultérieur est doublé
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // soit 300 DPI en pratique
PDF.SetRenderScale(1.0); // réinitialiser, sinon les vignettes seront énormes
La même persistance vaut pour SetRenderCropType et le réglage de qualité JPEG. Dans un service qui produit vignettes, aperçus et images en résolution d'impression depuis une seule instance partagée, ces réglages résiduels sont ce qui se cache vraiment derrière le ticket occasionnel "les vignettes font soudain 40 Mo". Deux sorties propres : réinitialiser l'état concerné en tête de chaque opération, ou dédier une instance distincte à chaque profil de sortie pour que rien ne fuie entre eux
Régler le moteur par défaut avant d'en chercher un autre
Une part surprenante des demandes du type "il nous faut un autre moteur" se révèle être des problèmes de réglage déguisés. Le moteur intégré expose son comportement de lissage via SetGDIPlusOptions et la famille plus large SetRenderOptions, et SetGDIPlusFileName vous permet de le pointer vers un runtime GDI+ précis quand un environnement de déploiement en livre un inhabituel. Traits crénelés à bas DPI, texte flou dans les vignettes, bandes dans les dégradés : tout cela répond à ces boutons, et les tourner ne coûte rien dans l'installeur. Ajouter Cairo ou PDFium, à l'inverse, signifie livrer plus de DLL, suivre une deuxième ou troisième variante de nombre de bits, et assumer l'obligation de les mettre à jour
Une plainte sur la qualité a donc un ordre naturel des opérations. Reproduisez-la d'abord au DPI et au facteur exacts du client, puisque la moitié du temps la différence s'évapore une fois ces valeurs alignées. Essayez ensuite les options de lissage du moteur intégré. Alors seulement, mettez la page côte à côte entre moteurs, toutes les autres variables tenues constantes : rendez-la en PNG par les moteurs 1, 2 et 3 au même DPI et joignez les trois. En général deux des trois s'accordent, et cette majorité vous dit si l'exception est un document interprété différemment ou votre propre attente de référence qui est faussée. Trois images concrètes règlent un litige du type "le rendu est mauvais" bien plus vite qu'un paragraphe d'adjectifs
Une chaîne de repli qui s'explique elle-même
Une fois la sonde et la discipline d'état en place, la chaîne de repli elle-même est courte. La détection d'un échec repose sur LastRenderError, qui contient le texte du message propre au moteur pour le dernier rendu et reste vide quand le rendu a réussi :
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // intégré en premier
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium comme repli lourd
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Deux points de conception pèsent ici. La chaîne consigne pourquoi chaque bascule a eu lieu, car une ligne de journal disant "cette page est retombée sur PDFium depuis la version 3.7" est un signal de régression que vous voulez voir en tendance dans la supervision plutôt que perdu. L'ordre de repli lui-même est une politique à choisir par charge de travail. Le moteur intégré se déploie sans DLL supplémentaire, ce qui en fait le bon premier essai dans la plupart des installations, tandis que les documents chargés en groupes de transparence ou en ombrages inhabituels sont la raison habituelle pour laquelle une équipe câble un moteur alternatif. Aucun moteur n'est le plus rapide en général, et c'est tout l'intérêt du choix par appel : mesurez chacun sur un échantillon de vos documents réels à votre DPI réel, et refaites cette mesure chaque fois que les DLL de moteur ou le mélange de documents changent. C'est le corpus qui tranche à chaque fois
Au-delà des pages isolées : lots TIFF et contextes de périphérique vivants
Deux voisines des appels par page complètent la boîte à outils. RenderAsMultipageTIFFToFile rend une expression de plage de pages directement dans un TIFF multipage, la forme naturelle pour les transferts d'archivage vers des systèmes de gestion documentaire antérieurs au PDF. RenderPageToDC peint directement sur un contexte de périphérique Windows pour les contrôles d'aperçu, gouverné par son propre trio de réglages persistants (SetRenderDCOffset, SetRenderDCErasePage, plus le type de rognage) qui réclament la même discipline de réinitialisation que le facteur d'échelle. L'aperçu à l'écran et le rendu sur le chemin d'impression portent assez de pièges à eux seuls pour mériter un article dédié, lié ci-dessous
Où aller ensuite
Une habitude à emporter : comme SelectRenderer prend effet pour tous les appels ultérieurs de l'instance, une seule page récalcitrante peut être réessayée sur un autre moteur pendant que le reste du document reste sur le moteur par défaut. Pour la peinture d'aperçu, la sélection d'imprimante et la gestion de DevMode, poursuivez avec l'article sur l'aperçu avant impression et le contexte de périphérique. Quand les rendus alimentent un pipeline à fort volume sur de très gros fichiers, l'approche par handles de le guide de l'accès direct s'associe naturellement au rendu page par page via DARenderPageToFile
Le conditionnement des moteurs, les formats pris en charge et les versions d'évaluation sont détaillés sur la page produit de PDF Library for Delphi