Technical Article

PDF čitač za Lazarus i Free Pascal pomoću PDFium-a

Delphi i Lazarus kompajliraju isti Object Pascal, i ta prividna sličnost je upravo ono što portovanje čitača između njih čini varljivim. Ova dva lanca alata se razlikuju na tri mesta koja su važna za rad sa PDF-om: izvorni tip string je UTF-16 u Delphi-ju i UTF-8 u LCL aplikaciji; VCL i LCL su različiti vizuelni okviri sa sopstvenim kontrolama, dijalozima i formatima toka formi; a Delphi binarna datoteka cilja Windows dok FPC binarna datoteka može biti namenjena Linux-u ili macOS-u. Nijedna od tih razlika se ne vidi u vreme kompajliranja. Čitač napravljen na PDFium komponenti, koja isporučuje VCL i LCL izdanja iz jednog stabla izvornog koda, kompajliraće se čisto pod Lazarus-om nakon nekoliko zamena imena jedinica i nekoliko {$IFDEF FPC} blokova. Problemi nastaju kasnije, kada stvarni podaci i stvarna implementacija razotkriju pretpostavke koje je Delphi build tiho pravio.

Četiri od tih pretpostavki čine većinu izgubljenog vremena: kodiranje teksta na granici korisničkog interfejsa, iskušenje da se održavaju dve kopije forme, način na koji se binarna datoteka izvornog motora razrešava u vreme izvršavanja, i trenutak kada tekst u govor (TTS) ostane bez platforme kada SAPI više nije dostupan. Svaku je lako rešiti ako znate da dolazi, a skupo pratiti ako ne znate.

Isti Pascal, različiti stringovi

Delphi-jev izvorni string je UTF-16 od 2009. godine. Lazarus i Free Pascal podrazumevaju UTF-8 u LCL aplikacijama. API-ji komponente koji rade sa tekstom govore UTF-16 kroz tip WString, koji FPC build alijasuje u WideString, tako da je svaka granica gde tekst prelazi između vašeg LCL korisničkog interfejsa i PDF motora tačka konverzije.

Konverzije se dešavaju automatski u jednostavnim dodelama vrednosti i većina koda nikada ne mora da razmišlja o njima. Dve navike sprečavaju greške u kodiranju. Prenosite tekst direktno bez manipulacije na nivou bajtova: kod koji deli termin za pretragu prema pomeraju bajta radi u Delphi-ju, gde je jedan Char jedna UTF-16 jedinica, a kvari višebajtni UTF-8 u LCL-u. Takođe, testirajte sa ne-ASCII podacima od prvog pokretanja. Nemačko ime datoteke, ćirilični termin za pretragu, akcentovano ime autora u metapodacima dokumenta: čisti ASCII test podaci skrivaju svaku grešku u kodiranju, jer je ASCII jedini opseg gde se UTF-8 i UTF-16 slažu bajt po karakter. Greška je stvarna sve vreme; ASCII je samo drži nevidljivom dok kupac u Minhenu ne otvori datoteku koju nikada niste probali.

Jedan uslovni blok, a ne grananje po razvojnom okruženju

Nakon prvih desetak uslovnih blokova (IFDEF), kodna baza počinje da izgleda kao dva projekta koja nose jedno skladište, a grananje po razvojnom okruženju (IDE) izgleda primamljivo. To je pogrešan potez. Stvarne razlike se svode na jedan zajednički blok deklaracije, a grananje duplira cenu svakog ispravljanja greške od tog trenutka nadalje. Zadržite uslovni sloj ovako malim:

{$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}

Sve ispod tog bloka kompajlira se identično u oba okruženja. Rukovanje dokumentima, navigacija po stranicama, pozivi iscrtavanja: TPdf i TPdfView izlažu istu površinu u VCL i LCL izdanjima, tako da najveći deo čitača nikada ne vidi uslovne blokove kompajlera. Održavanje tog stanja je strukturna disciplina, a ne pametan trik. Zajednička PDF logika živi u jedinicama koje ne povlače dijaloge ili panele specifične za okvir. Nekoliko stvari koje se zaista razlikuju, kao što su dijalozi za štampanje i birači datoteka sa svojim konvencijama platforme, kriju se iza tankog interfejsa implementiranog jednom po okviru. IFDEF blok postaje jedino mesto gde je dozvoljeno da se spuste buduće razlike u platformama, umesto da uslovne direktive cure kroz četrdeset jedinica.

Izgradite formu u kodu, a ne u dva dizajnera

Form streaming (tokovi formi) je mesto gde projekti sa dvostrukim IDE-om tiho propadaju. Datoteke .dfm i .lfm koje tvrde da opisuju istu formu udaljavaju se svojstvo po svojstvo sve dok se dva build-a ne ponašaju različito iz razloga koje niko ne može da uporedi (diff), jer te dve datoteke čak nisu ni u istom formatu. Izgradnja čitača u vreme izvršavanja (runtime) potpuno zaobilazi ovaj problem. Postoji jedan redosled konstruktora, kontrolisan verzijom kao običan kod, i čita se isto na obe platforme:

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;

Tačan redosled tih dodela vrednosti je manje važan od jedne linije koja radi pravi posao. PdfView.Pdf := Pdf povezuje vizuelnu kontrolu sa komponentu dokumenta, i od te tačke navigacija stranicama kroz PageNumber i ponašanje uklapanja kroz FitMode reaguju identično pod VCL-om i LCL-om. Jednu neobičnost između okvira vredi znati pre nego što je korisnik slično prijavi kao grešku: ručna dodela Zoom vraća FitMode nazad na pfmNone na oba okvira. Dakle, ako vaša linija alatki tretira „uklopi širinu“ kao lepljivu opciju, morate ponovo dodeliti režim uklapanja nakon bilo kog programskog zumiranja, inače opcija tiho prestaje da važi čim kod dodirne nivo zumiranja.

Binarna datoteka o kojoj vas razvojno okruženje nikada nije upozorilo

Komponenta obmotava PDFium motor, koji se isporučuje kao izvorna binarna datoteka platforme, i ta binarna datoteka je izvor skoro svakog izveštaja „radi u IDE-u, ne uspeva sa instalirane prečice“. Tri pravila objašnjavaju većinu njih. Bitnost mora da se poklapa tačno. 32-bitna izvršna datoteka ne može da učita 64-bitnu pdfium biblioteku, a poruka koju operativni sistem vraća („modul nije pronađen“ na nekim verzijama Windows-a) aktivno zavarava, jer datoteka stoji odmah pored izvršne datoteke. Razrešite putanju biblioteke relativno u odnosu na izvršnu datoteku, nikada u odnosu na radni direktorijum; pokretanje iz IDE-a i pokretanje iz ljuske se razlikuju upravo u toj tački, zbog čega se greška krije tokom razvoja. Uhvatite neuspešno učitavanje pre otvaranja prvog dokumenta, a zatim ga prijavite sa jasno navedenom očekivanom putanjom i arhitekturom. Prijava kvara koja glasi „Nedostaje PDFium 64-bitna binarna datoteka na “ zatvara se za nekoliko minuta. Ona koja glasi „čitač se ruši pri pokretanju“ pretvara se u nedelju dana dopisivanja.

Verziju binarne datoteke motora uskladite sa izvršnom datotekom. PDFium se brzo razvija, a instalater koji ažurira aplikaciju ali ostavlja zastarelu biblioteku na disku proizvodi rušenja koja niko u vašoj kancelariji ne može da reprodukuje, iz jednostavnog razloga što svaka mašina u vašoj kancelariji ima odgovarajući par. Tretirajte biblioteku kao deo build artefakta, sa istim instalaterom, istom oznakom verzije i istom putanjom za vraćanje unazad kao i izvršna datoteka koju učitava.

Registracija komponenti u Lazarus IDE-u

Izgradnja u vreme izvršavanja (runtime) ne zahteva nikakvu registraciju u vreme dizajna, što je najčistije podešavanje za čitač koji gradi sopstveni korisnički interfejs u kodu. Kada želite komponente na Lazarus paleti za rad u vreme dizajna, installirajte paket i pustite da njegova namenska registraciona jedinica, PDFiumLazReg u Lib/FPC/PDFiumLaz.lpk, rukuje time. Ta jedinica je označena za vreme dizajna sa razlogom: ona referencira interfejse editora svojstava IDE-a koji se nikada ne smeju povezati sa vašom izvršnom datotekom koja se isporučuje.

Pogrešite ovde i simptom je aplikacija koja neobjašnjivo zavisi od IDE paketa, što se manifestuje kao neuspeh instalacije na prvoj korisničkoj mašini na kojoj Lazarus nikada nije bio instaliran.

Govor i čitači ekrana van Windows-a

Tekst u govor (TTS) je jedina funkcija gde se priča o više platformi prekida, i to na nivou operativnog sistema, a ne komponente. SAPI, uobičajeni TTS pozadinski sistem na Windows-u, postoji samo na Windows-u. Lazarus build koji i dalje cilja Windows zadržava puni SAPI izlaz i isto ponašanje kompatibilno sa NVDA koje je imao originalni Delphi, tako da port sa Windows-a na Windows ovde ništa ne gubi, a korisnik NVDA ne može da razlikuje ova dva build-a.

Ciljanje Linux-a ili macOS-a je druga stvar. Nema SAPI-ja koji se može pozvati, tako da audio izlaz mora biti preusmeren na izvornu govornu uslugu dok API-ji za čitanje iznad nje ostaju nepromenjeni. Ta podela je razlog za postavljanje govora iza interfejsa od prvog commit-a: analiza redosleda čitanja i kursor za praćenje reči su neutralni u odnosu na platformu i prenose se netaknuti, a samo se tanak sloj koji zapravo proizvodi zvuk mora promeniti po platformi. Članak o pristupačnom čitaču detaljno pokriva tu mašineriju čitanja.

Lista provere pariteta pre nego što proglasite portovanje završenim

Sledeći prolaz je uhvatio stvarne regresije, navedene otprilike redosledom kojim se problemi obično pojavljuju. Otvorite dokument čija putanja sadrži ne-ASCII karaktere. Potražite termin sa ne-ASCII karakterima i potvrdite da se pogoci ističu tamo gde bi trebalo. Isprobajte skrolovanje točkićem miša, selekciju prevlačenjem i navigaciju stranicama tastaturom na svakom skupu kontrola koji isporučujete, jer su rukovanje fokusom i ponašanje točkića uglovi LCL-a koji najviše zavise od skupa kontrola. Proverite iscrtavanje na 100%, 150% i 200% skaliranja ekrana. Na kraju, pokrenite instaliranu verziju, a ne IDE verziju, na mašini koja nikada nije imala IDE na sebi, jer je to jedini test koji iskreno proverava binarne datoteke. Sve ostalo može proći dok to tiho ne uspe.

Brzina iscrtavanja se prenosi između dva izdanja nepromenjena, tako da se pristup keširanju iz članka o keširanju iscrtavanja i performansama zumiranja primenjuje na LCL čitač tačno onako kako je napisan za VCL čitač.

Ništa od ovoga ne čini LCL izdanje lošijim. Jezgro interfejsa je identično na obe strane: TPdf, TPdfView, iscrtavanje, obrasci, ekstrakcija teksta i API-ji za pristupačnost ponašaju se isto bez obzira na to koji ih je IDE kompajlirao. Svaka razlika vredna praćenja vezana je za platformu, a ne za izdanje. SAPI govor je samo za Windows, dijalozi prate konvencije svakog okvira, a binarna datoteka mora da odgovara arhitekturi u koju se učitava. Rešite ispravno granice kodiranja, formu u vreme izvršavanja i binarne datoteke, a ostatak portovanja je mehanički posao koji je kompajler već obavio za vas.

VCL i LCL izdanja opisana ovde isporučuju se zajedno kao PDFium Component, sa izvornim kodom i identičnim javnim API-jima za Delphi, C++Builder i Lazarus/FPC.