Le port semblait terminé vendredi. Un visualiseur PDF Delphi déplacé vers Lazarus en deux jours : quelques changements de noms d'unités, quelques blocs {$IFDEF FPC}, et le projet compilait proprement. La campagne de tests du lundi racontait autre chose : la boîte de recherche ne retournait aucun hit pour des mots clairement visibles, et un nom de fichier avec accents refusait de s'ouvrir. Rien dans la sortie du compilateur n'avait signalé ces deux échecs, car tous deux étaient des problèmes d'encodage de chaînes, invisibles jusqu'à ce que de vraies données circulent. Porter un visualiseur construit sur PDFium Component, qui livre des éditions VCL et LCL depuis une même source, est surtout mécanique ; les parties non mécaniques sont exactement celles que couvre cet article.
Même Pascal, charges de chaîne différentes
Le string natif de Delphi est UTF-16 depuis 2009. Lazarus et Free Pascal utilisent par défaut UTF-8 dans les applications LCL. Les API textuelles du composant parlent UTF-16 via le type WString, que le build FPC aliasse vers WideString ; chaque frontière où du texte passe entre votre UI LCL et le moteur PDF est donc un point de conversion.
Les conversions sont automatiques dans les affectations simples, mais deux habitudes évitent la classe de bugs du lundi matin. Passez le texte directement sans manipulation au niveau octet : du code qui découpe un terme de recherche par offset d'octet fonctionne en Delphi et corrompt l'UTF-8 multi-octets en LCL. Testez aussi avec des données non ASCII dès le premier jour, un nom de fichier allemand, un terme de recherche avec tiret cadratin, car les données de test ASCII masquent tous les bugs d'encodage.
Une petite couche conditionnelle au lieu d'un fork
Après les douze premiers IFDEF, la tentation est de forker le code par IDE. Résistez : les différences tiennent dans un bloc de déclarations partagé, et un fork double chaque correction future. La couche conditionnelle reste aussi courte :
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // component text APIs are UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Tout ce qui suit ce bloc, gestion du document, navigation de pages, appels de rendu, compile identiquement dans les deux IDEs, parce que TPdf et TPdfView exposent la même surface dans les éditions VCL et LCL. La discipline qui maintient cela est structurelle : la logique PDF partagée vit dans des unités sans dialogues ni panneaux propres à un framework, et ce qui diffère réellement, dialogues d'impression, sélecteurs de fichiers avec conventions de plateforme, vit derrière une interface mince implémentée une fois par framework. Le bloc IFDEF ci-dessus est aussi le bon endroit pour toute divergence future, ce qui évite de disperser les conditions de compilation dans le code.
Construire le formulaire en code, pas dans deux designers
Le streaming de formulaires est l'endroit où les projets double IDE pourrissent silencieusement : un .dfm et un .lfm décrivant « le même » formulaire dérivent propriété par propriété jusqu'à ce que les deux builds se comportent différemment pour des raisons que personne ne peut comparer. Pour le cœur du visualiseur, la construction à l'exécution contourne tout le problème : une seule séquence de constructeur, versionnée comme du code ordinaire :
procedure TViewerForm.FormCreate(Sender: TObject);
begin
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.FitMode := pfmFitWidth;
if ParamCount > 0 then
begin
Pdf.FileName := ParamStr(1);
Pdf.Active := True; // opens the document; PageCount valid after this
end;
end;
La séquence compte moins que le lien : PdfView.Pdf := Pdf rattache le contrôle visuel au composant document, après quoi la navigation par PageNumber et le zoom par FitMode se comportent de la même façon en VCL et LCL. Un comportement cross-framework à connaître avant les utilisateurs : affecter Zoom manuellement remet FitMode à pfmNone dans les deux frameworks. Si votre barre d'outils propose « adapter à la largeur » comme préférence persistante, restaurez le mode d'ajustement après tout changement programmatique de zoom, sinon la préférence cesse silencieusement de coller.
Le binaire dont l'IDE ne vous a jamais parlé
Le composant encapsule le moteur PDFium, livré comme binaire de plateforme, et le chargement binaire est la source des rapports « marche dans l'IDE, échoue depuis le raccourci installé ». Trois règles couvrent presque tous les cas. L'architecture doit correspondre exactement : un exécutable 32 bits ne peut pas charger une bibliothèque pdfium 64 bits, et le message de l'OS (« module introuvable » sur certaines versions Windows) induit en erreur, car le fichier est bien là. Résolvez le chemin de bibliothèque relativement à l'exécutable, jamais au répertoire courant : les lancements IDE et shell diffèrent justement par le répertoire courant. Et signalez les échecs de chargement avant le premier document, avec un message nommant le chemin et l'architecture attendus ; un ticket « binaire PDFium 64 bits manquant à
Versionnez aussi le binaire moteur avec l'exécutable. PDFium évolue vite, et un installateur qui met à jour l'application tout en laissant une ancienne bibliothèque sur disque produit des plantages qu'aucune machine de votre bureau ne reproduit, car toutes ont la paire correspondante. Traitez la bibliothèque comme une partie de l'artefact de build : même installateur, même marque de version, même rollback.
Enregistrer les composants dans l'IDE Lazarus
La construction à l'exécution n'a pas besoin d'enregistrement design-time, ce qui est la configuration correcte la plus simple pour une application de visualisation. Si vous voulez les composants dans la palette Lazarus, installez le package et laissez son unité d'enregistrement dédiée (PDFiumLazReg) faire le travail ; cette unité est marquée design-time précisément parce qu'elle référence des interfaces d'éditeurs de propriétés de l'IDE qui ne doivent jamais être liées dans votre exécutable livré.
Le symptôme d'une erreur ici est une application qui dépend mystérieusement de packages IDE, ce qui devient un échec de déploiement sur des machines n'ayant jamais vu Lazarus.
Voix et lecteurs d'écran hors Windows
Si l'original Delphi proposait la synthèse vocale, le port hérite d'une décision de plateforme. SAPI, backend TTS habituel sous Windows, n'existe que là. Les builds Lazarus ciblant Windows gardent donc tout le comportement SAPI et compatible NVDA ; un port Windows vers Windows ne perd rien, et les utilisateurs NVDA interagissent de la même manière sous les deux IDEs.
Les cibles Linux et macOS ont besoin d'un autre backend vocal câblé aux mêmes API de lecture, ce qui plaide pour placer la voix derrière une interface dès le départ. La mécanique d'ordre de lecture et de suivi de mot est indépendante de la plateforme ; seule la couche audio change. L'article sur le lecteur accessible couvre cette mécanique en détail.
Ce qu'il faut tester avant de déclarer le port terminé
Un passage de parité qui a capturé de vraies régressions, à peu près dans l'ordre où elles surviennent : ouvrir un document dont le chemin contient des caractères non ASCII ; rechercher un terme non ASCII et confirmer la surbrillance des résultats ; tester molette, sélection par glisser et navigation clavier de pages sur chaque widget set cible, car focus et molette sont les zones LCL les plus dépendantes du widget set ; vérifier le rendu à 100 %, 150 % et 200 % de mise à l'échelle ; exécuter le build installé, pas le build IDE, sur une machine sans l'IDE, seul test qui exerce honnêtement la résolution du binaire.
Les caractéristiques de débit de rendu se transfèrent entre éditions, donc la stratégie de cache de l'article sur le cache de rendu et les performances de zoom s'applique sans changement.
FAQ
L'édition LCL est-elle complète par rapport à l'édition VCL ?
La surface cœur, TPdf, TPdfView, rendu, formulaires, extraction de texte et API d'accessibilité, est la même. Les vraies différences sont liées à la plateforme : SAPI est Windows-only, et dialogues d'impression et de fichiers suivent les conventions de chaque framework.
Pourquoi mon build Lazarus plante-t-il au démarrage alors que le build Delphi fonctionne ?
Vérifiez d'abord la résolution binaire : mismatch d'architecture entre l'exécutable et la bibliothèque pdfium, ou chemin de chargement supposant le répertoire courant de l'IDE. Les deux causent des échecs immédiats de démarrage qui ressemblent à des bugs de composant et sont des bugs de déploiement.
Puis-je garder un formulaire partagé entre les IDEs ?
Les fichiers de description de formulaire ne se transfèrent pas : .dfm et .lfm sont des formats différents avec des jeux de propriétés différents. Construire l'UI du visualiseur à l'exécution, comme montré ci-dessus, remplace deux fichiers designer par un seul chemin de code et supprime le problème de dérive.
Les éditions VCL et LCL décrites ici sont livrées ensemble comme PDFium Component, avec code source et API publiques identiques pour Delphi, C++Builder et Lazarus/FPC.