Technical Article

PDF peržiūros programa Lazarus ir Free Pascal su PDFium

Delphi ir Lazarus kompiliuoja tą patį Object Pascal kodą, ir būtent šis išorinis panašumas daro peržiūros programos perkėlimą tarp jų apgaulingą. Abi įrankių grandinės išsiskiria trijose PDF darbui svarbiose vietose: gimtasis string tipas Delphi yra UTF-16, o LCL programoje – UTF-8; VCL ir LCL yra skirtingos vizualinės sistemos su savo valdikliais, dialogais ir formų srautinio perdavimo formatais; be to, Delphi dvejetainis failas yra skirtas Windows, o FPC dvejetainis failas gali būti skirtas Linux arba macOS. Nė vienas iš šių skirtumų nepasirodo kompiliavimo metu. Peržiūros programa, sukurta naudojant „PDFium Component“ (kuris pateikia VCL ir LCL versijas iš vieno šaltinio medžio), bus sėkmingai sukompiliuota Lazarus aplinkoje po kelių modulių pavadinimų pakeitimų ir keleto {$IFDEF FPC} blokų. Tačiau problemos kyla vėliau, kai tikrieji duomenys ir realus diegimas atskleidžia prielaidas, kurias tyliai darė Delphi versija.

Keturios iš šių prielaidų atima daugiausiai laiko: teksto kodavimas vartotojo sąsajos paribyje, pagunda išlaikyti dvi formos kopijas, būdas, kaip vykdymo metu nustatomas variklio dvejetainis failas, ir momentas, kai teksto pavertimas kalba (TTS) praranda platformą, kai nelieka SAPI. Kiekvieną iš jų lengva išspręsti, jei žinote apie tai iš anksto, ir brangu ieškoti klaidų, jei nežinote.

Tas pats Pascal, skirtingi eilučių duomenys

Delphi gimtasis string tipas yra UTF-16 nuo 2009 metų. Lazarus ir Free Pascal pagal nutylėjimą LCL programose naudoja UTF-8. Komponento teksto API sąsajos naudoja UTF-16 per tipą WString, kurį FPC versija susieja su WideString, todėl kiekviena riba, kurioje tekstas pereina iš LCL sąsajos į PDF variklį, yra konvertavimo taškas.

Konvertavimas vyksta automatiškai paprasto priskyrimo metu, ir daugeliui kodo dalių apie tai galvoti nereikia. Du įpročiai padeda išvengti kodavimo klaidų. Perduokite tekstą tiesiogiai, neatlikdami baitų lygio manipuliacijų: kodas, kuris skaido paieškos terminą pagal baitų poslinkį, veikia Delphi aplinkoje, kur vienas Char yra vienas UTF-16 vienetas, tačiau LCL aplinkoje sugadina kelių baitų UTF-8 simbolius. Taip pat nuo pat pirmo paleidimo atlikite testus su ne ASCII duomenimis. Vokiškas failo pavadinimas, kirilicos paieškos terminas, autoriaus vardas su diakritiniais ženklais dokumento metaduomenyse: tik ASCII testavimo duomenys paslepia visus kodavimo defektus, nes ASCII yra vienintelė sritis, kurioje UTF-8 ir UTF-16 sutampa baitas po baito. Klaida egzistuoja visą laiką, tačiau ASCII tiesiog daro ją nematomą, kol klientas Miunchene neatidaro failo, kurio niekada nebandėte.

Vienas sąlyginis blokas, o ne atskira atšaka kiekvienai IDE

Po pirmųjų dešimties IFDEF direktyvų kodo bazė pradeta atrodyti kaip du projektai vienoje saugykloje, ir kyla pagunda sukurti atskirą atšaką kiekvienai IDE. Tai netinkamas žingsnis. Tikrieji skirtumai telpa į vieną bendrą deklaracijos bloką, o atšakų kūrimas padvigubina kiekvieno klaidos taisymo išlaidas. Išlaikykite šį sąlyginį sluoksnį tokį mažą:

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

Viskas po šiuo bloku kompiliuojama vienodai abiejose IDE. Dokumentų tvarkymas, puslapių navigacija, atvaizdavimo iškvietimai: TPdf ir TPdfView pateikia tą pačią sąsają VCL ir LCL versijose, todėl didžioji peržiūros programos dalis niekada nemato kompiliatoriaus sąlygų. Tokios struktūros išlaikymas yra struktūrinė drausmė, o ne gudri schema. Bendra PDF logika gyvena moduliuose, kurie nenaudoja jokių konkrečiai sistemai būdingų dialogų ar skydelių. Keli dalykai, kurie iš tikrųjų skiriasi, pavyzdžiui, spausdinimo dialogai ir failų parinkikliai su savo platformos konvencijomis, slepiasi po plona sąsaja, įgyvendinta po vieną kartą kiekvienai sistemai. IFDEF blokas tampa vienintele vieta, kur leidžiama atsirasti būsimiems platformos skirtumams, užuot išbarsčius kompiliatoriaus direktyvas per keturiasdešimt modulių.

Formos kūrimas kode, o ne dviejuose dizaineriuose

Formų srautinis perdavimas (form streaming) yra ta vieta, kur projektai su dviem IDE tyliai sugenda. Failai .dfm ir .lfm, kurie turėtų aprašyti tą pačią formą, skiriasi savybė po savybės, kol abi versijos pradeda elgtis skirtingai dėl priežasčių, kurių niekas negali palyginti, nes failai netgi nėra to paties formato. Formos kūrimas vykdymo metu leidžia išvengti šios problemos. Yra viena konstruktoriaus seka, valdoma versijų kontrolės kaip paprastas kodas, ir ji vienodai skaitoma abiejose platformose:

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;

Tiksli šių priskyrimų tvarka yra mažiau svarbi nei viena eilutė, kuri atlieka tikrąjį darbą. PdfView.Pdf := Pdf susieja vizualinį valdiklį su dokumento komponentu, ir nuo to momento puslapio navigacija per PageNumber ir pritaikymas per FitMode veikia vienodai tiek VCL, tiek LCL. Verta žinoti vieną kelių sistemų ypatumą, kol vartotojas nepranešė apie tai kaip apie klaidą: rankinis Zoom priskyrimas abiejose sistemose sugrąžina FitMode į pfmNone. Taigi, jei jūsų įrankių juosta traktuoja „pritaikyti plotį“ kaip išliekantį nustatymą, turite iš naujo priskirti pritaikymo režimą po bet kokio programinio mastelio keitimo, kitaip nustatymas tyliai nustos galioti pirmą kartą programiškai pakeitus mastelį.

Dvejetainis failas, apie kurį IDE niekada neįspėjo

Komponentas apgaubia PDFium variklį, kuris pateikiamas kaip gimtoji platformos biblioteka, ir šis dvejetainis failas yra beveik kiekvieno pranešimo „veikia IDE, bet neveikia paleidus iš nuorodos“ priežastis. Trys taisyklės paaiškina daugumą šių atvejų. Bitingumas turi visiškai sutapti. 32 bitų vykdomasis failas negali įkelti 64 bitų pdfium bibliotekos, o OS grąžinamas pranešimas („modulis nerastas“ kai kuriose Windows versijose) klaidina, nes failas yra tiesiog šalia vykdomojo failo. Bibliotekos kelią nustatykite santykinai pagal vykdomąjį failą, o ne pagal darbinį katalogą; paleidimas iš IDE ir paleidimas iš konsolės skiriasi būtent šiuo aspektu, zodėl klaida slepiasi kūrimo metu. Ir pagaukite nesėkmingą įkėlimą prieš atidarant pirmąjį dokumentą, tada praneškite apie tai nurodydami tikėtiną kelią ir architektūrą. Pagalbos užklausa „nerasta 64 bitų PDFium biblioteka kelio adresu <path>“ išsprendžiama per kelias minutes. Užklausa „peržiūros programa sugenda paleidžiant“ virsta savaitės bendravimu pirmyn ir atgal.

Kartu naudokite tos pačios versijos variklio dvejetainį failą ir vykdomąjį failą. PDFium keičiasi greitai, o diegimo programa, kuri atnaujina programą, bet palieka pasenusią biblioteką diske, sukelia gedimus, kurių niekas jūsų biure negali atkurti dėl paprastos priežasties – kiekvienas kompiuteris jūsų biure turi atitinkančią porą. Laikykite biblioteką kūrimo artefakto dalimi, naudojančia tą pačią diegimo programą, tą patį versijos spaudą ir tą patį grąžinimo kelią kaip ir vykdomasis failas, kurį ji įkelia.

Komponentų registravimas Lazarus IDE

Kuriant formą vykdymo metu, jokios registracijos dizaino metu nereikia, ir tai yra švariausias nustatymas peržiūros programai, kuri kuria savo UI kode. Jei norite matyti komponentus Lazarus paletėje, įdiekite paketą ir leiskite tai atlikti specialiam registracijos moduliui PDFiumLazReg failo keliu Lib/FPC/PDFiumLaz.lpk. Šis modulis yra pažymėtas kaip skirtas dizaino metui (design-time) tikslingai: jis nurodo IDE savybių redagavimo sąsajas, kurios niekada neturėtų būti susietos su jūsų platinamu vykdomuoju failu.

Padarius klaidą, programa nepaaiškinamai priklausys nuo IDE paketų, o tai pasireikš kaip diegimo nesėkmė pirmame kliento kompiuteryje, kuriame niekada nebuvo įdiegtas Lazarus.

Kalba ir ekrano skaitytuvai už Windows ribų

Teksto pavertimas kalba yra ta funkcija, kurioje kelių platformų istorija sugriūva, ir tai nutinka operacinėje sistemoje, o ne komponente. SAPI – įprasta TTS posistemė Windows aplinkoje – egzistuoja tik Windows sistemoje. Lazarus versija, skirta Windows, išlaiko pilną SAPI išvestį ir tą patį su NVDA suderinamą elgesį, kurį turėjo Delphi originalas, todėl perkėlimas iš Windows į Windows čia nieko nepraranda, o NVDA vartotojas negali atskirti šių dviejų versijų.

Linux arba macOS atveju situacija yra kitokia. Nėra SAPI, kurį būtų galima iškviesti, todėl garso išvestis turi būti nukreipta į gimtąją kalbos paslaugą, o skaitymo API virš jos išlieka nepakeistos. Šis padalijimas yra argumentas už tai, kad kalba būtų paslėpta po sąsaja nuo pat pirmojo kodo įrašo: skaitymo tvarkos analizė ir žodžių sekimo žymeklis yra nepriklausomi nuo platformos ir perkeliami nepakeisti, ir tik plonas sluoksnis, kuris iš tikrųjų gamina garsą, turi keistis priklausomai nuo platformos. Straipsnyje apie prieinamą skaitytuvą ši sistema nagrinėjama išsamiau.

Pariteto kontrolinis sąrašas prieš baigiant perkėlimą

Šis patikrinimas padėjo aptikti realių regresijų, kurios čia pateikiamos apytiksle jų atsiradimo tvarka. Atidarykite dokumentą, kurio kelyje yra ne ASCII simbolių. Ieškokite termino su ne ASCII simboliais ir įsitikinkite, kad paieškos rezultatai paryškinami ten, kur turėtų. Išbandykite pelės ratuko slinkimą, vilkimą ir puslapių navigaciją klaviatūra kiekviename jūsų platinamame valdiklių rinkinyje, nes fokuso valdymas ir ratuko elgesys yra labiausiai nuo valdiklių rinkinio priklausantys LCL aspektai. Patikrinkite atvaizdavimą esant 100%, 150% ir 200% ekrano masteliui. Paskiausiai paleiskite įdiegtą versiją, o ne IDE versiją, kompiuteryje, kuriame niekada nebuvo įdiegta IDE, nes tai yra vienintelis testas, kuris sąžiningai patikrina dvejetainių failų įkėlimą. Visi kiti testai gali praeiti sėkmingai, o šis tyliai suges.

Atvaizdavimo našumas išlieka nepakitęs abiejose versijose, todėl talpyklos naudojimo principai iš straipsnio apie atvaizdavimo talpyklą ir mastelio keitimo našumą yra taikomi LCL peržiūros programai lygiai taip pat, kaip parašyta VCL versijai.

Dėl šių ypatumų LCL versija netampa prastesnė. Pagrindinė sąsaja yra identiška abiejose pusėse: TPdf, TPdfView, atvaizdavimas, formos, teksto išgavimas ir prieinamumo API elgiasi vienodai, nepriklausomai nuo to, kuri IDE jas sukompiliavo. Kiekvienas skirtumas, kurį verta stebėti, yra susijęs su platforma, o ne su pačia programos versija. SAPI balsas yra prieinamas tik Windows aplinkoje, dialogai seka kiekvienos sistemos konvencijas, o dvejetainis failas turi atitikti architektūrą, į kurią jis įkeliamas. Teisingai sutvarkykite kodavimo ribas, formos kūrimą vykdymo metu bei dvejetainio failo įkėlimą, ir likęs perkėlimo darbas bus tik mechaninė užduotis, kurią kompiliatorius jau atliko už jus.

Čia aprašytos VCL ir LCL versijos platinamos kartu kaip „PDFium Component“ su šaltinio kodu ir identiškomis viešosiomis API sąsajomis, skirtomis Delphi, C++Builder ir Lazarus/FPC.