PDFlibPas convertit les métafichiers améliorés en véritable contenu de page PDF enregistrement par enregistrement, au lieu de les rastériser, ce qui garde un graphique importé ou un dessin CAD net à n'importe quel zoom. Ce convertisseur fait environ 6500 lignes et il avait été écrit contre la VCL, si bien que lorsque la bibliothèque a gagné une cible Free Pascal, il a été classé comme non portable et remplacé par un stub. Ce classement était faux, et la manière dont il était faux constitue une leçon utile sur la façon d'auditer une dépendance avant de décider de réécrire autour
La surface VCL réelle de ces 6500 lignes s'est révélée minuscule : une classe bitmap utilisée pour son format de pixels, sa sauvegarde en flux, son handle, son canvas et ses scanlines ; une classe métafichier utilisée pour sa largeur, sa hauteur et son handle ; et le type couleur avec deux constantes. Chacun de ces éléments était déjà fourni par l'unité graphique de la bibliothèque elle-même, qui existe précisément pour que la compilation sans VCL ait des équivalents. Le convertisseur n'était pas du tout bloqué par la VCL. Il était bloqué par l'unité Windows de Free Pascal
Couper sur l'axe dont le code dépend réellement
Le changement n'était donc pas une réimplémentation. C'était un conditionnel : de « compiler le stub quand on construit sans la VCL » à « compiler le stub quand on ne construit pas pour Windows ». C'est le bon axe, et énoncer pourquoi rend la différence évidente. Un métafichier amélioré est un conteneur Windows. Le convertisseur est un analyseur d'enregistrements GDI Windows de bout en bout. Que l'application hôte utilise la VCL, un autre jeu de widgets ou aucun jeu de widgets du tout n'a rien à voir avec la possibilité d'interpréter ces enregistrements ; que la cible soit Windows a tout à y voir
Les conséquences du choix du bon axe tombent gratuitement. Les compilations C++Builder, qui annulent la définition du symbole de plateforme Windows dans cette bibliothèque, gardent le stub lançant une exception et se comportent exactement comme avant. macOS garde le stub, à juste titre, car il n'y a aucun enregistrement GDI à analyser là-bas. Les compilations Delphi VCL ne sont pas touchées. Et une compilation Windows avec un jeu de widgets non VCL gagne l'import EMF vectoriel comme effet secondaire, sans que personne n'ait eu à l'implémenter. Un conditionnel aligné sur la vraie dépendance transforme le travail de plateforme en changement d'une ligne ; un conditionnel aligné sur la mauvaise en fait une réécriture qui ne sera jamais planifiée
L'écart Free Pascal était des déclarations, pas de la logique
Ce qui manquait réellement, ce sont les déclarations Win32 que l'unité Windows de Delphi fournit et que celle de Free Pascal ne fournit pas. Les regrouper dans une seule unité de compatibilité plutôt que d'éparpiller des conditionnels dans le convertisseur a gardé l'analyseur lisible. La liste est instructive, car elle montre à quel point la couverture des en-têtes est inégale entre les deux RTL : 113 constantes de types d'enregistrements de métafichier, deux drapeaux de sortie de texte étendue, trois constantes de mode de remplissage dégradé, un type pointeur de table de handles, des alias pour les enregistrements de sommets et de primitives de dégradé, et trois types d'enregistrements que Free Pascal ne déclare pas du tout, couvrant la fusion alpha, le blitting transparent et le mode de gestion des couleurs
Rien de tout cela n'est intéressant individuellement. Tout doit être juste avant que l'analyseur ne compile, et une unité de compatibilité est la maison naturelle, car on peut la comparer par diff à la documentation des en-têtes en tant qu'unité
Celle qui dessine silencieusement la mauvaise image
Deux de ces déclarations ne se contentent pas d'être absentes : elles sont présentes et fausses pour cet usage, et c'est la partie à retenir même si vous ne touchez jamais à un métafichier
Free Pascal déclare l'enregistrement de création de brosse avec la structure de brosse d'exécution intégrée, et l'enregistrement de stylo étendu avec la structure de stylo d'exécution intégrée. Ces deux structures d'exécution déclarent leur membre hatch comme un entier de la taille d'un pointeur, car dans un appel GDI vivant ce membre peut porter un handle. Un métafichier, en revanche, stocke toujours la forme 32 bits, car la disposition des enregistrements fait partie du format de fichier sérialisé et ne change pas avec le nombre de bits du processus
Sur les compilations 32 bits, les deux coïncident et rien ne se passe. Sous Win64, le membre de la taille d'un pointeur fait huit octets là où le fichier en a quatre, donc chaque champ après le membre hatch est lu à partir du mauvais décalage. Pas d'exception, pas d'erreur d'analyse, pas d'avertissement. Le métafichier se rend simplement mal : des couleurs issues des mauvais octets, des largeurs de stylo issues des mauvais octets, et une image qui ressemble à un bug de rendu plutôt qu'à un bug de disposition de structure. Delphi livre explicitement des variantes 32 bits des deux structures précisément pour cette raison, et l'unité de compatibilité les redéclare de la même manière
// Faux sous Win64 : Hatch est de la taille d'un pointeur, le fichier stocke 32 bits,
// et chaque champ suivant se décale de quatre octets sans aucune erreur
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 octets dans un processus 64 bits
end;
// Juste : la disposition sérialisée, largeur fixe quel que soit le nombre de bits
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // toujours 4 octets, tel que stocké dans le métafichier
end;
La règle générale : toute structure qui apparaît à la fois comme argument d'API d'exécution et comme disposition de champs sérialisée exige deux déclarations, et la sérialisée doit utiliser des types à largeur fixe partout. Des membres de la taille d'un pointeur dans un format de fichier sont toujours un bug qui attend une compilation 64 bits
Les différences de signature vont dans un wrapper, pas sur chaque site d'appel
Les différences restantes étaient de simples incompatibilités de signature, et la manière de les absorber est un wrapper de transfert plutôt qu'un conditionnel sur chacun des sites d'appel. La fonction de combinaison de transformations prend des pointeurs sous Free Pascal là où Delphi prend des paramètres par référence, donc le wrapper prend des références et passe des adresses. Il copie aussi d'abord les deux arguments sources dans des locaux, car le convertisseur a des sites d'appel où la matrice de destination est simultanément l'une des sources, et passer deux fois la même adresse à une fonction qui écrit pendant qu'elle lit produit une transformation subtilement fausse d'une manière qui n'apparaît que sur du contenu pivoté
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Copier d'abord : les appelants passent légitimement Dest comme A ou B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Les types rectangle et point sont l'autre cas. Free Pascal traite les enregistrements rectangle et point du métafichier comme des types distincts de ceux des graphiques généraux, donc huit sites d'affectation ont exigé un cast explicite entre des enregistrements de disposition identique. Les deux compilateurs acceptent la forme castée, donc ces sites ne portent aucun conditionnel, ce qui vaut bien un peu de laideur
Ce que cela change pour un déploiement Free Pascal
L'import EMF vectoriel fonctionne sous Windows avec Free Pascal, produisant le même contenu de page que la compilation Delphi : des chemins comme chemins, des dégradés comme contenu de motif, du texte comme texte. Hors Windows, la voie raster reste la réponse, et c'est une limite du format plutôt que du portage. L'état de coordonnées et de clipping que le convertisseur alimente est décrit dans l'article sur le suivi du CTM et du clipping du flux de contenu, et les primitives vectorielles qu'il émet sont couvertes dans graphiques vectoriels, shaders et dégradés
Si vous auditez votre propre base de code pour la même opportunité, l'exercice utile est celui qui a lancé tout ceci : listez les membres que vous utilisez réellement du cadre dont vous pensez dépendre. La réponse est souvent bien plus courte que ce que la liste des imports suggère, et la vraie contrainte se trouve généralement ailleurs entièrement. Les voies d'import basées sur le contexte de périphérique sont décrites en général dans l'article sur l'aperçu avant impression et le contexte de périphérique, et la couverture des plateformes et des chaînes d'outils est listée sur la page produit de la losLab PDF Developer Library