Delphi i Lazarus kompilują ten sam Object Pascal, i to powierzchowne podobieństwo jest dokładnie tym, co czyni port przeglądarki między nimi zwodniczym. Oba zestawy narzędzi rozjeżdżają się w trzech miejscach istotnych dla pracy z PDF: natywny typ string to UTF-16 w Delphi, a UTF-8 w aplikacji LCL; VCL i LCL to różne frameworki wizualne z własnymi kontrolkami, oknami dialogowymi i formatami strumieniowania formularzy; a plik binarny Delphi celuje w Windows, podczas gdy binarka FPC może być przeznaczona dla Linuksa albo macOS. Żadna z tych różnic nie ujawnia się w czasie kompilacji. Przeglądarka zbudowana na PDFium Component, który dostarcza edycje VCL i LCL z jednego drzewa źródeł, skompiluje się czysto pod Lazarusem po garści zamian nazw jednostek i kilku blokach {$IFDEF FPC}. Awarie przychodzą później, gdy prawdziwe dane i prawdziwe wdrożenie ujawniają założenia, które build Delphi po cichu przyjmował
Cztery z tych założeń odpowiadają za większość straconego czasu: kodowanie tekstu na granicy UI, pokusę utrzymywania dwóch kopii formularza, sposób, w jaki natywna binarka silnika rozwiązuje się w czasie działania, oraz moment, w którym zamiana tekstu na mowę traci platformę, gdy SAPI znika. Każde z nich jest tanie w obsłudze, jeśli wiesz, że nadchodzi, i drogie w tropieniu, jeśli nie wiesz
Ten sam Pascal, różne ładunki łańcuchów
Natywny string w Delphi jest UTF-16 od 2009 roku. Lazarus i Free Pascal domyślnie używają UTF-8 w aplikacjach LCL. API komponentu zwrócone ku tekstowi mówi UTF-16 przez typ WString, który build FPC aliasuje do WideString, więc każda granica, na której tekst przechodzi między twoim UI LCL a silnikiem PDF, jest punktem konwersji
Konwersje zachodzą automatycznie w prostych przypisaniach, i większość kodu nigdy nie musi o nich myśleć. Dwa nawyki trzymają błędy kodowania z daleka. Przekazuj tekst wprost, bez manipulacji na poziomie bajtów: kod, który wycina frazę wyszukiwania po przesunięciu bajtowym, działa w Delphi, gdzie jeden Char to jedna jednostka UTF-16, a psuje wielobajtowe UTF-8 w LCL. I testuj danymi spoza ASCII od pierwszego uruchomienia. Niemiecka nazwa pliku, cyrylicka fraza wyszukiwania, nazwisko autora z akcentem w metadanych dokumentu: dane testowe czystego ASCII ukrywają każdy defekt kodowania, bo ASCII to jedyny zakres, w którym UTF-8 i UTF-16 zgadzają się bajt za znak. Błąd jest realny przez cały czas; ASCII po prostu trzyma go niewidocznym, aż klient w Monachium otworzy plik, którego nigdy nie próbowałeś
Jeden blok warunkowy, nie fork na każde IDE
Po pierwszym tuzinie IFDEF-ów baza kodu zaczyna wyglądać jak dwa projekty noszące jedno repozytorium, a rozwidlenie jej na IDE zaczyna kusić. To zły ruch. Prawdziwe różnice zwijają się do jednego wspólnego bloku deklaracji, a fork podwaja koszt każdej poprawki błędu od tego momentu. Trzymaj warstwę warunkową tak małą:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // API tekstowe komponentu są UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Wszystko poniżej tego bloku kompiluje się identycznie w obu IDE. Obsługa dokumentu, nawigacja po stronach, wywołania renderujące: TPdf i TPdfView udostępniają tę samą powierzchnię w edycjach VCL i LCL, więc większość przeglądarki nigdy nie widzi warunku kompilatora. Utrzymanie tego stanu to dyscyplina strukturalna, a nie sprytna sztuczka. Wspólna logika PDF mieszka w jednostkach, które nie ściągają żadnych dialogów ani paneli specyficznych dla frameworka. Garść rzeczy, które naprawdę się różnią, jak dialogi druku i wybór plików z ich konwencjami platformowymi, chowa się za cienkim interfejsem zaimplementowanym raz na framework. Blok IFDEF staje się jedynym miejscem, w którym wolno wylądować przyszłej rozbieżności platformowej, zamiast przeciekać dyrektywami kompilatora przez czterdzieści jednostek
Zbuduj formularz w kodzie, nie w dwóch projektantach
Strumieniowanie formularzy to miejsce, w którym projekty na dwa IDE po cichu gniją. .dfm i .lfm, które twierdzą, że opisują ten sam formularz, rozjeżdżają się właściwość po właściwości, aż dwa buildy zachowują się różnie z powodów, których nikt nie potrafi zdiffować, bo te dwa pliki nie są nawet w tym samym formacie. Konstruowanie przeglądarki w czasie działania omija cały ten problem. Jest jedna sekwencja konstruktora, wersjonowana jak zwykły kod, i czyta się tak samo na obu platformach:
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; // otwiera dokument; PageCount jest ważne dopiero po tym
end;
end;
Dokładna kolejność tych przypisań ma mniejsze znaczenie niż jedna linia, która wykonuje prawdziwą pracę. PdfView.Pdf := Pdf wiąże kontrolkę wizualną z komponentem dokumentu, i od tego momentu nawigacja po stronach przez PageNumber oraz zachowanie dopasowania przez FitMode reagują identycznie pod VCL i LCL. Jedną osobliwość międzyframeworkową warto znać, zanim zgłosi ją użytkownik jako błąd: ręczne przypisanie Zoom zatrzaskuje FitMode z powrotem na pfmNone w obu frameworkach. Więc jeśli twój pasek narzędzi traktuje "dopasowanie do szerokości" jako trwałą preferencję, musisz ponownie przypisać tryb dopasowania po każdym programowym powiększeniu, albo preferencja po cichu przestanie się trzymać przy pierwszym dotknięciu poziomu powiększenia przez kod
Binarka, przed którą IDE nigdy cię nie ostrzegło
Komponent owija silnik PDFium, który dostarczany jest jako natywna binarka platformowa, i ta binarka jest źródłem niemal każdego zgłoszenia typu "działa w IDE, zawodzi z zainstalowanego skrótu." Trzy zasady odpowiadają za większość z nich. Bitowość musi się dokładnie zgadzać. 32-bitowy plik wykonywalny nie potrafi wczytać 64-bitowej biblioteki pdfium, a komunikat, który zwraca system ("nie znaleziono modułu" na niektórych wersjach Windows) aktywnie wprowadza w błąd, bo plik leży dokładnie obok pliku wykonywalnego. Rozwiązuj ścieżkę biblioteki względem pliku wykonywalnego, nigdy względem katalogu roboczego; uruchomienie z IDE i uruchomienie z powłoki różnią się dokładnie na tym punkcie, i dlatego błąd ukrywa się podczas rozwoju. I łap nieudane wczytanie, zanim otworzy się pierwszy dokument, a potem zgłaszaj to z podaną wprost oczekiwaną ścieżką i architekturą. Zgłoszenie wsparcia, które mówi "brak binarki PDFium 64-bit pod <path>," zamyka się w minuty. Takie, które mówi "przeglądarka wywala się przy starcie," zamienia się w tydzień korespondencji
Skoro już przy tym jesteś, wersjonuj binarkę silnika razem z plikiem wykonywalnym. PDFium rozwija się szybko, a instalator, który aktualizuje aplikację, ale zostawia na dysku nieaktualną bibliotekę, produkuje awarie, których nikt w twoim biurze nie potrafi odtworzyć, z prostego powodu, że każda maszyna w biurze akurat trzyma pasującą parę. Traktuj bibliotekę jako część artefaktu buildu, z tym samym instalatorem, tym samym znacznikiem wersji i tą samą ścieżką wycofania co plik wykonywalny, który ją wczytuje
Rejestrowanie komponentów w IDE Lazarus
Konstrukcja w czasie działania nie potrzebuje żadnej rejestracji projektowej, co jest najczystszym ustawieniem dla przeglądarki budującej własny UI w kodzie. Gdy naprawdę chcesz mieć komponenty na palecie Lazarusa do pracy projektowej, zainstaluj pakiet i pozwól, by zajęła się tym jego dedykowana jednostka rejestracyjna, PDFiumLazReg w Lib/FPC/PDFiumLaz.lpk. Ta jednostka jest oznaczona jako projektowa celowo: odwołuje się do interfejsów edytorów właściwości IDE, które nigdy nie mogą trafić do twojego wysyłanego pliku wykonywalnego
Zrób to źle, a objawem będzie aplikacja niewytłumaczalnie zależna od pakietów IDE, co ujawnia się jako awaria wdrożenia na pierwszej maszynie klienta, która nigdy nie miała zainstalowanego Lazarusa
Mowa i czytniki ekranu poza Windows
Zamiana tekstu na mowę to jedna funkcja, w której historia międzyplatformowości się łamie, i łamie się na poziomie systemu operacyjnego, nie komponentu. SAPI, zwykły backend TTS na Windows, istnieje tylko na Windows. Build Lazarusa wciąż celujący w Windows zachowuje pełne wyjście SAPI i to samo zachowanie zgodne z NVDA, jakie miał oryginał w Delphi, więc port Windows-na-Windows nic tu nie traci, a użytkownik NVDA nie odróżni tych dwóch buildów
Cel w postaci Linuksa albo macOS to inna sprawa. Nie ma SAPI do wywołania, więc wyjście audio trzeba przełączyć na natywną usługę mowy, podczas gdy API czytania nad nim pozostaje bez zmian. Ten podział jest argumentem za umieszczeniem mowy za interfejsem już od pierwszego commita: analiza kolejności czytania i kursor śledzący słowa są neutralne platformowo i przenoszą się bez zmian, a tylko cienka warstwa, która faktycznie produkuje dźwięk, musi się zmieniać na platformę. Artykuł o dostępnej przeglądarce omawia tę mechanikę czytania szczegółowo
Lista kontrolna parytetu, zanim uznasz port za skończony
Poniższe przejście wyłapało prawdziwe regresje, wypisane w przybliżeniu w kolejności, w jakiej awarie zwykle się ujawniają. Otwórz dokument, którego ścieżka zawiera znaki spoza ASCII. Wyszukaj frazę ze znakami spoza ASCII i potwierdź, że trafienia podświetlają się tam, gdzie powinny. Przetestuj przewijanie kółkiem myszy, zaznaczanie przez przeciąganie i nawigację po stronach klawiaturą na każdym zestawie widżetów, który wysyłasz, bo obsługa fokusu i zachowanie kółka to najbardziej zależne od zestawu widżetów zakątki LCL. Sprawdź renderowanie przy skalowaniu wyświetlacza 100%, 150% i 200%. Na koniec uruchom zainstalowany build, nie build z IDE, na maszynie, która nigdy nie miała IDE, bo to jedyny test, który uczciwie ćwiczy rozwiązywanie binarki. Wszystko inne może przejść, podczas gdy właśnie ten po cichu zawiedzie
Przepustowość renderowania przenosi się między dwiema edycjami bez zmian, więc podejście do cache'owania z artykułu o cache renderowania i wydajności powiększania stosuje się do przeglądarki LCL dokładnie tak, jak zostało napisane dla VCL
Nic z tego nie czyni edycji LCL gorszą. Rdzenna powierzchnia jest identyczna po obu stronach: TPdf, TPdfView, renderowanie, formularze, wyodrębnianie tekstu i API dostępności zachowują się tak samo bez względu na to, które IDE je skompilowało. Każda różnica warta śledzenia jest przywiązana do platformy, a nie do edycji. Mowa SAPI jest tylko dla Windows, okna dialogowe podążają za konwencjami każdego frameworka, a binarka musi pasować do architektury, w którą jest wczytywana. Zrób dobrze granice kodowania, formularz w czasie działania i rozwiązywanie binarki, a reszta portu to mechaniczna praca, którą kompilator już wykonał za ciebie
Opisane tutaj edycje VCL i LCL są dostarczane razem jako PDFium Component, z kodem źródłowym i identycznymi publicznymi API dla Delphi, C++Builder i Lazarus/FPC