Delphi a Lazarus kompilujú rovnaký Object Pascal, no práve táto povrchná podobnosť robí prenos prehliadača medzi nimi zradným. Tieto dva nástroje sa rozchádzajú v troch dôležitých oblastiach pre prácu s PDF: natívny typ string je v Delphi kódovaný ako UTF-16, zatiaľ čo v aplikáciách LCL ako UTF-8; VCL a LCL sú odlišné vizuálne rámce s vlastnými ovládacími prvkami, dialógmi a formátmi ukladania formulárov; a binárny súbor z Delphi smeruje na Windows, zatiaľ čo výstup z FPC môže smerovať na Linux alebo macOS. Žiadny z týchto rozdielov sa neprejaví pri kompilácii. Prehliadač postavený na komponente PDFium Component, ktorý sa dodáva vo verziách pre VCL aj LCL z jediného zdrojového kódu, sa v prostredí Lazarus skompiluje bez chýb len po niekoľkých zmenách názvov jednotiek a pridaní zopár blokov {$IFDEF FPC}.
Zlyhania sa objavia neskôr, keď reálne dáta a nasadenie odhalia predpoklady, s ktorými zostavenie v Delphi potichu počítalo. Štyri z týchto predpokladov sú zodpovedné za väčšinu strateného času: kódovanie textu na hranici používateľského rozhrania, pokušenie udržiavať dve kópie formulára, spôsob načítania natívnej knižnice jadra počas behu a okamih, kedy hlasový syntetizátor (text-to-speech) stratí podporu platformy po odchode zo SAPI. Každý z nich sa dá vyriešiť jednoducho, ak o ňom viete vopred, no jeho dodatočné hľadanie býva nákladné.
Rovnaký Pascal, rôzny obsah reťazcov
Natívny typ string v Delphi je od verzie 2009 kódovaný ako UTF-16. Lazarus a Free Pascal predvolene používajú UTF-8 v aplikáciách LCL. Textové API komponentu komunikuje v UTF-16 prostredníctvom typu WString (ktorý má v zostavení pre FPC alias WideString), takže každé rozhranie, kde text prechádza medzi vaším LCL používateľským rozhraním a jadrom PDF, vyžaduje konverziu.
Tieto konverzie prebiehajú automaticky pri priamych priradeniach a väčšina kódu na ne nemusí vôbec myslieť. Dva návyky vám pomôžu vyhnúť sa chybám v kódovaní. Odovzdávajte text priamo bez manipulácie na úrovni bajtov: kód, ktorý rozdeľuje hľadaný výraz podľa bajtového posunu, funguje v Delphi, kde jeden znak (Char) predstavuje jednu jednotku UTF-16, ale v LCL poškodí viacbajtové UTF-8.
A testujte s dátami mimo znakovej sady ASCII už od prvého spustenia. Nemecký názov súboru, hľadaný výraz v azbuke alebo meno autora s diakritikou v metadátach dokumentu: testovacie dáta iba v ASCII skryjú každú chybu kódovania, pretože ASCII je jediný rozsah, kde sa UTF-8 a UTF-16 zhodujú bajt po bajte. Chyba existuje po celý čas; čisté ASCII ju len maskuje, kým zákazník v Mníchove neotvorí súbor, ktorý ste nikdy testovali.
Jeden podmienený blok, nie samostatná vetva pre každé IDE
Po prvých desiatich podmienkach IFDEF môže kód začať pôsobiť ako dva projekty v jednom repozitári a rozdelenie (fork) pre každé IDE sa zdá lákavé. Je to však nesprávny krok. Skutočné rozdiely sa dajú zhrnúť do jedného zdieľaného bloku deklarácií, pričom rozvetvenie kódu by zdvojnásobilo náklady na každú opravu chyby. Udržujte podmienenú vrstvu takto minimalistickú:
{$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}
Všetko pod týmto blokom sa kompiluje identicky v oboch IDE. Práca s dokumentom, navigácia po stránkach, volania vykresľovania: TPdf a TPdfView poskytujú rovnaké rozhranie vo verziách pre VCL aj LCL, takže väčšina prehliadača vôbec nevidí podmienky kompilátora. Udržať to takto je otázkou štrukturálnej disciplíny, nie zložitého triku.
Zdieľaná logika PDF žije v jednotkách (units), ktoré neobsahujú žiadne dialógy ani panely špecifické pre konkrétny vizuálny framework. Zopár vecí, ktoré sa skutočne líšia, ako sú tlačové dialógy a výber súborov s ich platformovými špecifikami, sa skrýva za tenkým rozhraním implementovaným raz pre každý framework. Blok IFDEF sa stáva jediným miestom, kde sa povoľujú budúce odchýlky platforiem, namiesto toho, aby smernice kompilátora prenikali do desiatok jednotiek.
Zostavte formulár v kóde, nie v dvoch vizuálnych návrhoch
Ukladanie formulárov (streaming) je miestom, kde projekty pre dve IDE potichu chátrajú. Súbory .dfm a .lfm, ktoré majú popisovať rovnaký formulár, sa začnú vlastnosť po vlastnosti líšiť, až kým sa obe verzie nesprávajú odlišne z dôvodov, ktoré nikto nedokáže porovnať, keďže súbory sú v odlišných formátoch. Vytvorenie prehliadača za behu programu (runtime) tento problém úplne obchádza. Existuje len jedna sekvencia konštruktora, spravovaná vo verzovacom systéme ako bežný kód, ktorá vyzerá na oboch platformách rovnako:
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;
Presné poradie týchto priradení je menej dôležité ako ten jeden riadok, ktorý vykonáva skutočnú práci. PdfView.Pdf := Pdf prepojí vizuálny ovládací prvok s komponentom dokumentu a od tohto momentu navigácia po stránkach pomocou PageNumber a prispôsobenie zobrazenia pomocou FitMode fungujú pod VCL aj LCL identicky.
Predtým, než to používateľ nahlási ako chybu, je užitočné vedieť o jednom správaní medzi frameworkami: manuálne priradenie hodnoty Zoom vráti FitMode späť na pfmNone v oboch prípadoch. Ak teda váš panel nástrojov berie voľbu „prispôsobiť šírke“ ako trvalé nastavenie, musíte po každom programovom priblížení znova priradiť režim prispôsobenia, inak sa toto nastavenie prestane uplatňovať pri prvej zmene úrovne priblíženia.
Binárny súbor, pred ktorým vás IDE nevarovalo
Komponent obaľuje jadro PDFium, ktoré sa dodáva ako natívny binárny súbor platformy. Práve tento súbor je zdrojom takmer všetkých hlásení typu „funguje v IDE, ale zlyhá zo zástupcu po inštalácii“. Tri pravidlá vysvetľujú väčšinu z nich. Bitová architektúra (bitness) sa musí presne zhodovať. 32-bitový spustiteľný súbor nemôže načítať 64-bitovú knižnicu PDFium a správa, ktorú vráti operačný systém (napr. „modul nebol nájdený“ v niektorých verziách Windows), je zavádzajúca, pretože súbor sa nachádza hneď vedľa spustiteľného súboru. Cestu ku knižnici určujte relatívne k spustiteľnému súboru, nikdy nie k pracovnému adresáru. Spustenie z IDE a spustenie zo systému sa líšia práve v tomto bode, a preto sa táto chyba počas vývoja skryje. A zachyťte zlyhanie načítania ešte pred otvorením prvého dokumentu, potom ho nahláste s jasne špecifikovanou očakávanou cestou a architektúrou. Požiadavka na podporu s textom „Chýba 64-bitový binárny súbor PDFium na adrese <path>“ sa vyrieši za pár minút. Tá s textom „prehliadač padá pri štarte“ sa natiahne na týždeň komunikácie.
Zároveň spravujte verziu binárneho súboru jadra spolu so spúšťacím súborom. PDFium sa vyvíja rýchlo a inštalátor, ktorý aktualizuje aplikáciu, ale nechá na disku zastaranú knižnicu, spôsobí pády aplikácie, ktoré nikto vo vašej kancelárii nedokáže reprodukovať jednoducho preto, že každý počítač u vás má zhodou okolností správnu dvojicu. Pristupujte ku knižnici ako k súčasti zostavenia (build artifact) s rovnakým inštalátorom, rovnakým označením verzie a rovnakou cestou pre návrat k predchádzajúcej verzii (rollback) ako pri spúšťacom súbore.
Registrácia komponentov v Lazarus IDE
Konštrukcia počas behu programu nevyžaduje žiadnu registráciu pre návrh (design-time), čo je najčistejšie riešenie pre prehliadač, ktorý zostavuje svoje rozhranie v kóde. Ak predsa len chcete komponenty v palete Lazarusu na vizuálny návrh, nainštalujte balík a nechajte to na jeho dedikovanú registračnú jednotku PDFiumLazReg v Lib/FPC/PDFiumLaz.lpk. Táto jednotka je zámerne označená ako design-time: odkazuje na rozhrania editorov vlastností IDE, ktoré sa nikdy nesmú prepojiť do vášho výsledného spustiteľného súboru.
Ak to urobíte nesprávne, príznakom bude aplikácia, ktorá nevysvetliteľne závisí od balíkov IDE, čo sa prejaví ako zlyhanie nasadenia na prvom klientskom počítači, na ktorom nebol nikdy nainštalovaný Lazarus.
Hlas a čítačky obrazovky mimo Windows
Syntéza reči (text-to-speech) je tá funkcia, kde sa príbeh o multiplatformovosti láme, pričom k tomu dochádza na úrovni operačného systému, nie komponentu. SAPI, bežné rozhranie pre TTS vo Windowse, existuje iba v tomto systéme. Zostavenie v Lazaruse, ktoré stále cielite na Windows, si zachováva plný výstup SAPI a rovnaké správanie kompatibilné s čítačkou NVDA, aké mala pôvodná verzia v Delphi. Port z Windows na Windows tu nič nestráca a používateľ NVDA nerozozná rozdiel medzi oboma verziami.
Linuxový alebo macOS cieľ je však iná záležitosť. Neexistuje tu žiadne SAPI, takže zvukový výstup sa musí presmerovať na natívnu hlasovú službu systému, zatiaľ čo čítacie API nad ním zostáva nezmenené. Toto rozdelenie je dôvodom, prečo dať hlasové služby za spoločné rozhranie hneď pri prvom commite: analýza poradia čítania a kurzor sledujúci slová sú nezávislé od platformy a dajú sa preniesť bez zmien, pričom sa mení iba tenká vrstva, ktorá skutočne generuje zvuk pre konkrétnu platformu. Článok o prístupnej čítačke sa zaoberá týmto mechanizmom čítania podrobnejšie.
Kontrolný zoznam pred prehlásením portu za dokončený
Nasledujúci testovací postup už zachytil skutočné regresie, pričom sú zoradené približne v poradí, v akom sa chyby zvyčajne objavujú. Otvorte dokument, ktorého cesta obsahuje znaky mimo ASCII. Vyhľadajte výraz so znakmi mimo ASCII a overte, či sa nájdené zhody zvýraznia tam, kde majú. Vyskúšajte posúvanie kolieskom myši, výber ťahaním a navigáciu po stránkach pomocou klávesnice na každej sade komponentov (widget set), ktorú dodávate, pretože spracovanie fokusu a správanie kolieska myši sú časti LCL, ktoré najviac závisia od sady widgetov. Skontrolujte vykresľovanie pri mierkach zobrazenia 100 %, 150 % a 200 %. Nakoniec spustite nainštalovanú verziu (nie tú spustenú z IDE) na počítači, kde nikdy nebolo inštalované IDE, pretože to je jediný test, ktorý naozaj preverí vyhľadávanie binárnych súborov. Všetko ostatné môže prejsť, zatiaľ čo tento bod potichu zlyhá.
Rýchlosť vykresľovania zostáva medzi oboma verziami nezmenená, takže prístup k ukladaniu do vyrovnávacej pamäte z článku o vyrovnávacej pamäti a výkone približovania platí pre prehliadač LCL rovnako, ako bol napísaný pre VCL.
Nič z toho nerobí verziu pre LCL menejcennou. Základné rozhranie je na oboch stranách rovnaké: TPdf, TPdfView, vykresľovanie, formuláre, extrakcia textu a API pre prístupnosť sa správajú rovnako bez ohlladu na to, ktoré IDE ich skompilovalo. Každý rozdiel, ktorý stojí za to sledovať, je viazaný na platformu, nie na samotnú edíciu komponentu. Syntéza reči SAPI je len pre Windows, dialógy sledujú konvencie konkrétneho frameworku a binárny súbor musí zodpovedať architektúre, do ktorej sa načítava. Zvládnite kódovanie znakov na hraniciach, dynamické zostavenie formulára a hľadanie knižnice jadra a zvyšok portu je len mechanická práca, ktorú za vás kompilátor už urobil.
Edície pre VCL a LCL popísané v tomto článku sa dodávajú spoločne ako PDFium Component, a to vrátane zdrojových kódov a identických verejných API pre Delphi, C++Builder a Lazarus/FPC.