HotPDF, le composant PDF natif pour Delphi et C++Builder, importe les métafichiers Windows EMF et WMF en interprétant directement chaque enregistrement GDI en opérateurs PDF plutôt qu'en aplatissant le fichier en bitmap : les remplissages en dégradé deviennent des motifs d'ombrage axial PDF, les brosses de hachures deviennent des motifs de mosaïque PDF, et un verrou centralisé sur l'état du chemin bloque les enregistrements malformés qui pourraient corrompre la sortie. Tout graphique qu'un TChart, une surface GDI+, ou un simple TCanvas peut exporter sous forme de métafichier amélioré est candidat à ce chemin, et la différence saute aux yeux dès qu'on zoome sur la page ou qu'on l'envoie vers une imprimante haute résolution
L'alternative que la plupart des développeurs Delphi adoptent par défaut consiste à rastériser le métafichier en bitmap avant de le déposer sur la page, et le coût n'apparaît que plus tard : un diagramme en barres net à l'écran devient visiblement pixellisé dès que le PDF est imprimé à 600 DPI ou projeté sur un écran de salle de réunion, et une zone CAO remplie de hachures s'effondre en un simple rectangle gris uni si le style de remplissage n'est pas conservé. Lire le métafichier comme un programme plutôt que comme une image évite ces deux problèmes, et c'est le chemin le plus difficile à implémenter correctement, ce qui justifie de connaître les pièges ci-dessous avant qu'un rapport ne parte en production
Pourquoi interpréter un métafichier plutôt que l'aplatir en bitmap ?
HotPDF maintient l'import EMF et WMF sur la voie vectorielle car un métafichier Windows est une séquence enregistrée d'appels de dessin GDI, pas une image, et rejouer ces appels sous forme d'opérateurs de chemin, de texte et d'ombrage PDF est ce qui permet au résultat de s'adapter à l'échelle comme le reste de la page. THPDFPage.ShowMetafile et son équivalent ShowMetafileEx sont les points d'entrée qu'une application appelle, et tous deux transmettent le métafichier à THPDFWmf, la classe qui parcourt chaque enregistrement GDI et le traduit. La distinction n'est pas absolue, et HotPDF ne prétend pas le contraire : un enregistrement de métafichier qui est réellement une donnée raster, un blit bitmap StretchDIBits par exemple, est intégré comme un véritable XObject Image PDF via AddImage et ShowImage, la même paire d'appels que traverse toute autre image de la page, plutôt que forcé dans des opérateurs de chemin incapables d'exprimer une photographie. Les lignes, remplissages et textes restent vectoriels ; les pixels qui étaient déjà des pixels dans la source restent des pixels dans la sortie. L'appel le plus simple ne nécessite rien de plus que le métafichier chargé :
var
Pdf: THotPDF;
Chart: TMetafile;
begin
Pdf := THotPDF.Create(nil);
Chart := TMetafile.Create;
try
Chart.LoadFromFile('quarterly-revenue.emf'); // exported from TChart or GDI+
Pdf.FileName := 'quarterly-report.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafile(Chart);
Pdf.EndDoc;
finally
Chart.Free;
Pdf.Free;
end;
end;
Comment l'interpréteur convertit-il les coordonnées GDI en espace de page PDF ?
HotPDF répond à cela par une seule passe sur le flux d'enregistrements propre au métafichier plutôt qu'en réimplémentant GDI. THPDFWmf.Analyse lit l'en-tête du métafichier via l'appel Win32 GetEnhMetaFileHeader, réinitialise son état de dessin interne, et appelle EnumEnhMetafile, la même API d'énumération qu'utiliserait une visionneuse de métafichiers, de sorte que chaque enregistrement EMR_* atteint THPDFWmf.ExecuteRecord dans l'ordre où il a été enregistré à l'origine. GDI exprime les coordonnées de haut en bas dans des unités device ou logiques choisies par le propre mode de mappage du métafichier ; une page PDF est de bas en haut en points d'espace utilisateur, le système de coordonnées couvert dans le modèle de dessin de canevas de HotPDF pour les chemins et remplissages. Chaque gestionnaire d'enregistrement résout cette discordance via ScaleX et ScaleY, qui appellent ProjectX et ProjectY pour rejouer la propre formule fenêtre-vers-fenêtre-d'affichage de GDI pour les modes de mappage anisotrope et isotrope, de sorte qu'une forme enregistrée à cinq unités logiques de large atterrit à la largeur correcte en points PDF, quelles que soient les étendues de fenêtre et de fenêtre d'affichage définies par l'application source
Comment un remplissage en dégradé GDI devient-il un motif d'ombrage PDF ?
Un enregistrement EMR_GRADIENTFILL devient un véritable motif d'ombrage axial PDF de Type 2 (ISO 32000-1 §8.7.4.5) chaque fois que GDI l'a enregistré dans l'un des deux modes rectangle. THPDFWmf.VEMRGradientFill lit la disposition propre de l'enregistrement directement depuis le buffer d'octets brut, en suivant la structure MS-EMF §2.3.1.6 : un tableau de sommets de coins RVBA 16 bits, suivi d'une liste de rectangles référençant chacun deux de ces sommets. Pour GRADIENT_FILL_RECT_H, les couleurs balaient de gauche à droite le long de la ligne médiane horizontale du rectangle ; pour GRADIENT_FILL_RECT_V, elles balaient de haut en bas le long de la ligne médiane verticale. Dans les deux cas, les deux couleurs des coins et les coordonnées projetées du rectangle passent directement dans THotPDF.RegisterAxialGradient, qui renvoie un nom de motif, et la page dessine le rectangle et le remplit via ce motif (SetFillPattern) au lieu d'un simple appel SetRGBFillColor, si bien qu'un en-tête à bandes de type tableur ou la zone de tracé en dégradé d'un graphique conserve son fondu au lieu de s'effondrer en une couleur moyenne unique
Le mode triangle de Gouraud est la lacune honnêtement assumée. Lorsque le champ ulMode de l'enregistrement indique GRADIENT_FILL_TRIANGLE, VEMRGradientFill le reconnaît, journalise que le mode triangle n'est pas encore implémenté, et ignore le rectangle plutôt que de tenter une approximation à deux couleurs. L'interpolation par sommet et par pixel sur un maillage triangulaire arbitraire ne se réduit pas à un ombrage axial ou radial à deux arrêts de couleur, et l'exprimer correctement signifierait émettre un ombrage de maillage PDF de Type 4 ou Type 5, la même famille d'ombrages que le moteur de rendu de page de HotPDF laisse également non peinte lors de la relecture d'un PDF. Deux chemins de code sans rapport aboutissent à la même limite : les ombrages de maillage constituent la lacune aussi bien côté écriture que côté lecture, et un diagramme source utilisant des triangles de Gouraud pour un dégradé radial lisse retombe sur la dernière brosse pleine utilisée, pas sur une approximation rendue
Les brosses de hachures deviennent des motifs de mosaïque, pas un gris aplati
Une brosse à hachures GDI conserve sa texture dans le PDF car THPDFWmf.SetBrushColor vérifie si CurrentBrush.lbStyle vaut BS_HATCHED avant même de retomber sur un remplissage uni, en aiguillant ce cas vers SetHatchBrushPattern. Cette méthode écrit un flux de contenu PDF de 8 par 8 unités composé d'opérateurs de trait, m, l, et S, choisis selon le style de hachure GDI : un simple trait horizontal ou vertical pour HS_HORIZONTAL et HS_VERTICAL, trois diagonales parallèles pour HS_FDIAGONAL et HS_BDIAGONAL, et les combinaisons horizontal-plus-vertical ou double-diagonale pour HS_CROSS et HS_DIAGCROSS. THotPDF.RegisterTilingPattern enregistre ce flux de contenu comme un motif de mosaïque coloré (PaintType 1, ISO 32000-1 §8.7.3.1) avec un XStep et un YStep de 8 unités, et la page remplit via SetFillPattern de la même manière qu'un ombrage axial. Un plan d'architecture CAO ou un dessin technique qui s'appuie sur des remplissages à hachures pour distinguer les matériaux conserve ce langage visuel dans le PDF au lieu de perdre chaque zone dans un gris identique
Toutes les brosses ne bénéficient pas de ce traitement, et la lacune mérite d'être connue avant qu'un import CAO ne parte en production. EMR_CREATEDIBPATTERNBRUSHPT, l'enregistrement pour une brosse à motif d'image bitmap personnalisée plutôt que l'un des six styles de hachure standard de GDI, ne fait qu'enregistrer son handle afin que les enregistrements SELECTOBJECT et DELETEOBJECT ultérieurs restent cohérents ; HotPDF n'expose pas encore de pipeline de ressource Pattern PDF pour des images de mosaïque arbitraires, si bien que sélectionner cette brosse retombe sur un remplissage de couleur unie plutôt que sur la texture source. Si un remplissage apparaît aplati là où l'original utilisait clairement une texture d'image répétée, la brosse source est presque certainement un motif DIB personnalisé plutôt qu'une hachure standard, et c'est le seul cas qui mérite d'être vérifié manuellement en premier. Configurer un import pour un dessin de ce type passe toujours par le même objet d'options :
var
Pdf: THotPDF;
Drawing: TMetafile;
Options: THPDFEmfOptions;
begin
Pdf := THotPDF.Create(nil);
Drawing := TMetafile.Create;
Options := THPDFEmfOptions.Create;
try
Drawing.LoadFromFile('floor-plan.emf');
Options.Assign(Pdf.EmfOptions); // start from the document-wide defaults
Options.Redraw := False; // interpret the original EMF bytes, no GDI re-record pass
Options.ShowNullBrush := True; // keep explicitly unfilled CAD regions visible
Options.UseFrame := True; // clip output to the frame the EMF header declares
Pdf.FileName := 'floor-plan.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
Pdf.EndDoc;
finally
Options.Free;
Drawing.Free;
Pdf.Free;
end;
end;
Qu'est-ce qui empêche un métafichier malformé de corrompre la page ?
La réponse de HotPDF est un verrou unique en tête de ExecuteRecord plutôt qu'une vérification défensive répétée dans chacun de ses quelque quatre-vingts gestionnaires d'enregistrement. Un support de chemin GDI, ouvert par EMR_BEGINPATH et fermé par EMR_ENDPATH ou EMR_ABORTPATH, est suivi par une propriété privée PathContinue adossée au champ FPathContinue. Tant que ce support est ouvert, ExecuteRecord ne laisse passer que les enregistrements de construction de chemin, les variantes move, line, polyline, polygon, polybezier et polydraw, plus CLOSEFIGURE et un petit ensemble d'enregistrements de transformation et d'état de contexte de périphérique tels que SETWORLDTRANSFORM, SAVEDC, et RESTOREDC. Tout autre type d'enregistrement atteignant ExecuteRecord pendant que le support est ouvert, un EXTTEXTOUT égaré ou un blit bitmap par exemple, est écarté de façon centralisée par un simple Exit dès son arrivée
Ce verrou existe parce qu'un support de chemin dans un métafichier écrit à la main, généré par un outil, ou simplement corrompu, n'est pas garanti de ne contenir que ce qu'un fichier bien formé placerait entre ses enregistrements d'ouverture et de fermeture. Un enregistrement de sortie texte atterrissant entre EMR_BEGINPATH et EMR_ENDPATH polluerait, sans ce verrou, soit la géométrie du chemin en cours de construction, soit émettrait un opérateur d'affichage de texte PDF au milieu d'une séquence censée être une construction de chemin pure, et ces deux modes de défaillance sont du genre à ne se manifester que sur une entrée malformée provenant d'un outil tiers, pas sur ce qu'une suite de tests normale a l'habitude de couvrir. Centraliser la vérification dans ExecuteRecord signifie que les gestionnaires VEMR* individuels n'ont pas chacun besoin de se protéger contre un appel au mauvais moment ; le verrou en décide une seule fois, avant la répartition, plutôt que quatre-vingts fois après
Placer un graphique vectoriel à côté de texte et d'images sur une même page
Une page de rapport ne contient rarement qu'un seul graphique, et ShowMetafile se compose avec les autres opérateurs de page de HotPDF exactement comme n'importe quel autre appel de dessin. Un titre dessiné avec TextOut, un diagramme en barres à hachures importé comme EMF, et un logo placé avec ShowImage peuvent tous atterrir sur la même page dans le même flux de contenu, chacun conservant sa fidélité native, le schéma de composition couvert dans le guide de HotPDF pour la mise en page du texte, des polices et des images dans un rapport :
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart); // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);
L'interpréteur EMF et WMF, les motifs d'ombrage axial qu'il enregistre pour les remplissages en dégradé, et le mappage en motifs de mosaïque pour les brosses de hachures décrits ici sont tous fournis dans le cadre du composant HotPDF standard pour Delphi et C++Builder, une bibliothèque VCL native sans aucune dépendance à une DLL externe pour tout ceci