Delphi en Lazarus compileren dezelfde Object Pascal, en dat oppervlaktegelijkenis is precies wat het overzetten van een viewer ertussen bedrieglijk maakt. De twee toolchains wijken af op drie plaatsen die er voor PDF-werk toe doen: het native string-type is UTF-16 in Delphi en UTF-8 in een LCL-applicatie; VCL en LCL zijn verschillende visuele frameworks met hun eigen controls, dialogen en form-streaming-formaten; en een Delphi-binair richt zich op Windows terwijl een FPC-binair mogelijk op Linux of macOS af gaat. Geen van die verschillen verschijnt op compileertijd. Een viewer gebouwd op PDFium Component, dat VCL- en LCL-edities levert vanuit één bronboom, compileert schoon onder Lazarus na een handvol unit-naam-swaps en een paar {$IFDEF FPC}-blokken. De storingen komen later, wanneer echte data en een echte uitrol de aannames blootleggen die de Delphi-build geruisloos maakte
Vier van die aannames verklaren de meeste verloren tijd: tekstencodering op de UI-grens, de verleiding om twee kopieën van het formulier te onderhouden, de manier waarop een native engine-binair tijdens runtime wordt opgelost, en het moment waarop tekst-naar-spraak zonder platform komt te zitten zodra SAPI verdwijnt. Elk is goedkoop te hanteren als u weet dat het eraan komt en duur om op te sporen als u dat niet doet
Dezelfde Pascal, verschillende string-payloads
Het native string van Delphi is al UTF-16 sinds 2009. Lazarus en Free Pascal standaard op UTF-8 in LCL-applicaties. De tekstgerichte API's van de component spreken UTF-16 via het type WString, dat de FPC-build aliasseert naar WideString, dus elke grens waar tekst tussen uw LCL-UI en de PDF-engine oversteekt is een conversiepunt
De conversies gebeuren automatisch in eenvoudige toewijzingen, en de meeste code hoeft er nooit over na te denken. Twee gewoontes houden de encoderings-bugs eruit. Voer tekst recht door zonder manipulatie op byteniveau: code die een zoekterm op byte-offset snijdt werkt in Delphi, waar één Char één UTF-16-eenheid is, en corrumpeert multi-byte UTF-8 in de LCL. En test vanaf de eerste run met niet-ASCII-data. Een Duitse bestandsnaam, een Cyrillische zoekterm, een geaccentueerde auteursnaam in de documentmetadata: zuiver ASCII-testdata verbergt elk encoderingsdefect, want ASCII is het enige bereik waar UTF-8 en UTF-16 byte-voor-teken overeenkomen. De bug is de hele tijd echt; ASCII houdt hem alleen onzichtbaar tot een klant in München een bestand opent dat u nooit probeerde
Eén conditioneel blok, geen fork per IDE
Na de eerste dozijn IFDEFs begint de codebase aan te voelen als twee projecten in één repository, en haar forken per IDE ziet er verleidelijk uit. Het is de verkeerde zet. De werkelijke verschillen krimpen ineen tot één gedeeld declaratieblok, en een fork verdubbelt de kosten van elke bugfix vanaf dat moment. Houd de conditionele laag zo klein:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // de tekst-API's van het component zijn UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Alles onder dat blok compileert identiek in beide IDE's. Documentverwerking, paginanavigatie, rendering-aanroepen: TPdf en TPdfView tonen hetzelfde oppervlak in de VCL- en LCL-edities, dus het gros van de viewer ziet nooit een compilerconditie. Dat zo houden is een structurele discipline eerder dan een slimme truc. Gedeelde PDF-logica leeft in units die geen framework-specifieke dialogen of panelen binnenhalen. De handvol dingen die echt verschillen, zoals afdrukdialogen en bestandskiezers met hun platformconventies, verbergen zich achter een dunne interface die één keer per framework wordt geïmplementeerd. Het IFDEF-blok wordt de enige plaats waar toekomstige platformdivergentie nog mag landen, in plaats van compilerdirectieven over veertig units te laten lekken
Bouw het formulier in code, niet in twee designers
Form-streaming is waar dual-IDE-projecten geruisloos wegrotten. Een .dfm en een .lfm die beweren hetzelfde formulier te beschrijven drijven eigenschap voor eigenschap uit elkaar tot de twee builds zich om redenen verschillend gedragen die niemand kan diffen, omdat de twee bestanden niet eens hetzelfde formaat hebben. De viewer tijdens runtime construeren omzeilt het hele probleem. Er is één constructorsequentie, onder versiebeheer als gewone code, en hij leest hetzelfde op beide platforms:
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; // opent het document; PageCount is hierna geldig
end;
end;
De exacte volgorde van die toewijzingen doet er minder toe dan de regel die het echte werk doet. PdfView.Pdf := Pdf bindt het visuele control aan de documentcomponent, en vanaf dat punt reageert paginanavigatie via PageNumber en fit-gedrag via FitMode identiek onder VCL en LCL. Eén cross-framework-eigenaardigheid is de moeite waard om te kennen voordat een gebruiker hem als bug meldt: Zoom met de hand toewijzen klapt FitMode terug op pfmNone op beide frameworks. Dus als uw werkbalk "fit width" als een kleefvoorkeur behandelt, moet u de fit-modus na elke programmatische zoom opnieuw toewijzen, anders stopt de voorkeur geruisloos met kleven de eerste keer dat code het zoomniveau aanraakt
De binair waar de IDE u nooit voor waarschuwde
De component wikkelt de PDFium-engine, die levert als een native platformbinair, en die binair is de bron van nagenoeg elk "werkt in de IDE, faalt vanuit de geïnstalleerde snelkoppeling"-rapport. Drie regels verklaren de meeste ervan. Bitness moet exact overeenkomen. Een 32-bit uitvoerbaar bestand kan geen 64-bit pdfium-bibliotheek laden, en het bericht dat de OS teruggeeft ("module not found" op sommige Windows-versies) misleidt actief, want het bestand staat gewoon daar naast het uitvoerbare bestand. Los het bibliotheekpad op ten opzichte van het uitvoerbare bestand, nooit de werkdirectory; een IDE-start en een shell-start verschillen precies op dat punt, vandaar dat de bug zich tijdens ontwikkeling verbergt. En vang een mislukte load op voordat het eerste document opent, en meld hem dan met het verwachte pad en de architectuur uitgespeld. Een supportticket dat leest "PDFium 64-bit binary missing at <path>" sluit in minuten. Eentje dat leest "viewer crashes on startup" wordt een week heen-en-weer
Versieer de engine-binair naast het uitvoerbare bestand terwijl u toch bezig bent. PDFium beweegt snel, en een installer die de applicatie bijwerkt maar een verouderde bibliotheek op schijf laat staan produceert crashes die niemand op uw kantoor kan reproduceren, om de simpele reden dat elke machine op uw kantoor toevallig het bijbehorende paar vasthoudt. Behandel de bibliotheek als onderdeel van het build-artifact, met dezelfde installer, dezelfde versiestempel en hetzelfde rollback-pad als het uitvoerbare bestand dat hij laadt
Componenten registreren in de Lazarus IDE
Runtime-constructie heeft helemaal geen registratie op ontwerptijd nodig, wat de schoonste opzet is voor een viewer die zijn eigen UI in code bouwt. Wanneer u de componenten wel op het Lazarus-palet wilt voor ontwerptijdwerk, installeer dan het pakket en laat zijn toegewijde registratie-unit, PDFiumLazReg in Lib/FPC/PDFiumLaz.lpk, het afhandelen. Die unit is opzettelijk als ontwerptijd gemarkeerd: hij verwijst naar IDE-property-editor-interfaces die nooit in uw uit te leveren uitvoerbare bestand mogen linken
Krijg dit verkeerd en het symptoom is een applicatie die onverklaarbaar op IDE-pakketten leunt, wat opduikt als een uitrolfalen op de eerste klantmachine waar Lazarus nooit op geïnstalleerd is
Spraak en schermlezers buiten Windows
Tekst-naar-spraak is het enige feature waar het cross-platform-verhaal breekt, en het breekt op het besturingssysteem, niet op de component. SAPI, de gebruikelijke TTS-backend op Windows, bestaat alleen op Windows. Een Lazarus-build die nog steeds op Windows richt behoudt volledige SAPI-uitvoer en hetzelfde NVDA-compatibele gedrag dat het Delphi-origineel had, dus een Windows-naar-Windows-port verliest hier niets, en een NVDA-gebruiker kan de twee builds niet uit elkaar halen
Een Linux- of macOS-doel is een ander verhaal. Er is geen SAPI om aan te roepen, dus de audio-uitvoer moet worden omgeleid naar een native spraakservice terwijl de lees-API's erboven op hun plaats blijven. Die splitsing is het argument om spraak vanaf de eerste commit achter een interface te zetten: de leesvolgorde-analyse en de woord-volgcursor zijn platformneutraal en dragen ongewijzigd over, en alleen de dunne laag die werkelijk geluid produceert moet per platform veranderen. Het artikel over de toegankelijke lezer behandelt die leesmachinerie in diepte
Een pariteitschecklist voordat u de port klaar noemt
De volgende ronde heeft echte regressies gevangen, ruwweg in de volgorde waarin de storingen de neiging hebben op te duiken. Open een document waarvan het pad niet-ASCII-tekens bevat. Zoek naar een term met niet-ASCII-tekens en bevestig dat de treffers markeren waar ze horen. Oefen muiswiel-scroll, sleep-selectie en paginanavigatie via het toetsenbord op elke widgetset die u levert, want focushandhaving en wielgedrag zijn de meest widgetset-afhankelijke hoeken van de LCL. Controleer rendering op 100%, 150% en 200% weergaveschaling. Draai tot slot de geïnstalleerde build, niet de IDE-build, op een machine waar de IDE nooit op gestaan heeft, want dat is de enige test die binaire resolutie eerlijk oefent. Al het andere kan slagen terwijl die ene geruisloos faalt
Rendering-doorvoer draagt ongewijzigd over tussen de twee edities, dus de caching-aanpak uit het artikel over rendercache en zoomprestaties is op de LCL-viewer precies zo van toepassing als geschreven voor de VCL-versie
Niets hiervan maakt de LCL-editie een mindere. Het kernoppervlak is identiek aan beide kanten: TPdf, TPdfView, rendering, formulieren, tekstextractie en de toegankelijkheids-API's gedragen zich hetzelfde ongeacht welke IDE ze compileerde. Elk verschil dat de moeite waard is om bij te houden is platformgebonden eerder dan editiegebonden. SAPI-spraak is Windows-only, dialogen volgen de conventies van elk framework, en de binair moet overeenkomen met de architectuur waarin hij wordt geladen. Krijg de encoderingsgrenzen, het runtime-formulier en de binaire resolutie goed, en de rest van de port is het mechanische werk dat de compiler al voor u heeft afgehandeld
De VCL- en LCL-edities die hier worden beschreven leveren samen als PDFium Component, met broncode en identieke publieke API's voor Delphi, C++Builder en Lazarus/FPC