Műszaki cikk

PDF-megjelenítő Lazarus és Free Pascal környezethez PDFium-mal

A Delphi és a Lazarus ugyanazt az Object Pascalt fordítja, és pontosan ez a felszíni hasonlóság teszi megtévesztővé egy nézegető portolását közöttük. A két eszközlánc három olyan ponton tér el, amely számít a PDF-munkánál: a natív string típus Delphiben UTF-16, egy LCL-alkalmazásban UTF-8; a VCL és az LCL különböző vizuális keretrendszerek a saját vezérlőikkel, párbeszédablakaikkal és form-streamelési formátumaikkal; és egy Delphi-bináris Windowst céloz, míg egy FPC-bináris Linux vagy macOS felé is indulhat. Ezek közül a különbségek közül egyik sem mutatkozik meg fordítási időben. Egy PDFium Component-re épített nézegető, amely egyetlen forráskódfából szállítja a VCL- és LCL-kiadásokat, tisztán fog fordulni Lazarus alatt néhány egységnév-csere és néhány {$IFDEF FPC} blokk után. A hibák később érkeznek, amikor valós adatok és egy valós telepítés felfedik azokat a feltételezéseket, amelyeket a Delphi-build csendben tett

Ezek közül négy feltételezés teszi ki az elveszett idő nagy részét: a szövegkódolás a felhasználói felület határán, a kísértés, hogy két példányt tartsunk fenn az űrlapból, ahogyan egy natív motor-bináris futásidőben feloldódik, és az a pillanat, amikor a szövegfelolvasás kifogy a platformból, amint a SAPI eltűnik. Mindegyiket olcsó kezelni, ha tudod, hogy jön, és drága utólag felkutatni, ha nem

Ugyanaz a Pascal, más karakterlánc-terhelés

A Delphi natív string-je 2009 óta UTF-16. A Lazarus és a Free Pascal alapértelmezésben UTF-8-at használ LCL-alkalmazásokban. A komponens szöveg felé forduló API-jai a WString típuson keresztül UTF-16-ot beszélnek, amelyet az FPC-build a WideString-re aliasol, így minden határ, ahol a szöveg átlép az LCL felhasználói felületed és a PDF-motor között, egy átalakítási pont

Az átalakítások automatikusan megtörténnek az egyszerű hozzárendelésekben, és a legtöbb kódnak sosem kell rájuk gondolnia. Két szokás tartja kívül a kódolási hibákat. Add tovább a szöveget közvetlenül, bájtszintű manipuláció nélkül: az a kód, amely bájteltolás szerint szeletel egy keresési kifejezést, Delphiben működik, ahol egy Char egy UTF-16 egység, és megrongálja a többbájtos UTF-8-at az LCL-ben. És már az első futtatástól kezdve tesztelj nem-ASCII adatokkal. Egy német fájlnév, egy cirill keresési kifejezés, egy ékezetes szerzőnév a dokumentum metaadataiban: a tiszta ASCII tesztadat minden kódolási hibát elrejt, mert az ASCII az az egyetlen tartomány, ahol az UTF-8 és az UTF-16 karakterenként bájtra bájt egyezik. A hiba végig valódi; az ASCII csak láthatatlanul tartja, amíg egy müncheni ügyfél meg nem nyit egy fájlt, amit sosem próbáltál ki

Diagram UTF-16 és UTF-8 karakterlánckonverziós határokról egy LCL megjelenítő UI és a PDFium komponens között Lazarusban
A komponens szöveg-API-i UTF-16-ot beszélnek WStringen át, így egy UTF-8 karakterláncokat tartó LCL UI minden határon konverziós pontba ütközik, és a bájtoffset-alapú szeletelés vagy a csak ASCII tesztadat az, ahol a kódolási hibák rejtőznek

Egy feltételes blokk, nem egy fork IDE-nként

Az első tucat IFDEF után a kódbázis kezd úgy érezni, mintha két projekt viselné egyetlen repository ruháját, és az IDE-nkénti forkolás csábítónak tűnik. Ez a rossz lépés. A valódi különbségek egyetlen közös deklarációs blokkba omlanak össze, és egy fork attól kezdve megduplázza minden hibajavítás költségét. Tartsd ilyen kicsin a feltételes réteget:

{$IFDEF FPC}
uses
  LCLType, Forms, Graphics, Controls;

type
  WString = WideString;   // a komponens szöveges API-jai UTF-16 kódolásúak
  TBytes  = array of Byte;
{$ELSE}
uses
  Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}

Minden, ami ez alatt a blokk alatt van, azonosan fordul mindkét IDE-ben. Dokumentumkezelés, oldalnavigáció, renderelési hívások: a TPdf és a TPdfView ugyanazt a felületet kínálja a VCL- és LCL-kiadásokban, így a nézegető nagy része sosem lát fordítási feltételt. Ennek megtartása strukturális fegyelem kérdése, nem egy ügyes trükké. A közös PDF-logika olyan egységekben él, amelyek nem húznak be keretrendszer-specifikus párbeszédablakokat vagy paneleket. Az a maroknyi dolog, ami valóban eltér, mint például a nyomtatási párbeszédablakok és a fájlválasztók a saját platformkonvencióikkal, egy vékony interfész mögé bújik, amelyet keretrendszerenként egyszer implementálnak. Az IFDEF blokk lesz az egyetlen hely, ahol a jövőbeli platformeltérés landolhat, ahelyett hogy negyven egységen keresztül szivárogtatná a fordítási direktívákat

Építsd fel az űrlapot kódban, ne két designerben

A form-streamelés az, ahol a kettős IDE-s projektek csendben rothadnak. Egy .dfm és egy .lfm, amelyek állítólag ugyanazt az űrlapot írják le, tulajdonságonként elsodródnak egymástól, amíg a két build olyan okokból viselkedik másképp, amelyeket senki sem tud diffelni, mert a két fájl még csak nem is ugyanabban a formátumban van. A nézegető futásidejű felépítése megkerüli az egész problémát. Van egy konstruktorszekvencia, közönséges kódként verziókezelve, és ugyanúgy olvasható mindkét platformon:

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;   // megnyitja a dokumentumot; a PageCount ezután érvényes
  end;
end;

Ezeknek a hozzárendeléseknek a pontos sorrendje kevésbé számít, mint az az egy sor, amely a valódi munkát végzi. A PdfView.Pdf := Pdf köti a vizuális vezérlőt a dokumentumkomponenshez, és ettől a ponttól kezdve az oldalnavigáció a PageNumber-en keresztül és az illesztési viselkedés a FitMode-on keresztül azonosan válaszol VCL és LCL alatt is. Egy keretrendszerek közötti furcsaságot érdemes tudni, mielőtt egy felhasználó hibaként jelentené: a Zoom kézi hozzárendelése mindkét keretrendszeren visszaugrasztja a FitMode-ot pfmNone-ra. Így ha az eszköztárad a „szélességhez illesztést" tartós beállításként kezeli, minden programozott zoom után újra hozzá kell rendelned az illesztési módot, különben a beállítás csendben megszűnik ragadni, amint a kód először hozzáér a zoomszinthez

A bináris, amiről az IDE sosem figyelmeztetett

A komponens a PDFium motort csomagolja be, amely natív platform-binárisként érkezik, és ez a bináris szinte minden „az IDE-ben működik, a telepített parancsikonból elbukik" jelentés forrása. Három szabály teszi ki ezek nagy részét. A bitszélességnek pontosan egyeznie kell. Egy 32 bites futtatható nem tud betölteni egy 64 bites pdfium-könyvtárat, és az üzenet, amit az OS visszaad (bizonyos Windows-verziókon „module not found"), aktívan félrevezet, mert a fájl pontosan ott ül a futtatható mellett. Oldd fel a könyvtár elérési útját a futtatóhoz képest, soha ne a munkakönyvtárhoz képest; egy IDE-indítás és egy héjból indítás pontosan ezen a ponton tér el, és ezért rejtőzik el a hiba fejlesztés közben. És fogd el a sikertelen betöltést, mielőtt az első dokumentum megnyílik, majd jelentsd ki a várt elérési úttal és architektúrával kiírva. Egy support-jegy, amely úgy szól, hogy „PDFium 64 bites bináris hiányzik itt: <path>", perceken belül lezárul. Az, amelyik úgy szól, hogy „a nézegető induláskor összeomlik", egy hétnyi oda-vissza levelezéssé válik

Ha már itt tartunk, verziózd a motor-binárist a futtatható mellett. A PDFium gyorsan mozog, és egy telepítő, amely frissíti az alkalmazást, de egy elavult könyvtárat hagy a lemezen, olyan összeomlásokat produkál, amelyeket senki az irodádban nem tud reprodukálni, egyszerűen azért, mert az irodádban lévő minden gép történetesen az egyező párost tartja. Kezeld a könyvtárat a build-artifact részeként, ugyanazzal a telepítővel, ugyanazzal a verziójelzéssel és ugyanazzal a visszaállítási útvonallal, mint a futtathatót, amit betölt

Diagram a három PDFium natív bináris betöltési szabályról egy Lazarus vagy Delphi PDF megjelenítő végrehajtható fájlhoz
Három szabály fedi le a legtöbb az-IDE-ben-működik-telepítve-bukik jelentést: a futtatható állománynak és a PDFium könyvtárnak azonos bitszélességűnek kell lennie, a könyvtárútvonal a futtatható állományból oldódik fel, nem a munkakönyvtárból, és egy megbukott betöltés elkapásra kerül a várt útvonal kiírásával

Komponensek regisztrálása a Lazarus IDE-ben

A futásidejű felépítés egyáltalán nem igényel tervezésidejű regisztrációt, ami a legtisztább beállítás egy olyan nézegetőhöz, amely kódban építi fel a saját felhasználói felületét. Amikor mégis a Lazarus paletta komponenseit szeretnéd tervezésidejű munkához, telepítsd a csomagot, és hagyd, hogy a dedikált regisztrációs egysége, a PDFiumLazReg a Lib/FPC/PDFiumLaz.lpk-ban, elvégezze. Az az egység szándékosan tervezésidejűnek van megjelölve: olyan IDE tulajdonságszerkesztő-interfészekre hivatkozik, amelyeknek soha nem szabad belinkelődniük a szállított futtathatóba

Ha ezt elrontod, a tünet egy olyan alkalmazás lesz, amely megmagyarázhatatlanul IDE-csomagoktól függ, ami az első olyan ügyfélgépen jelentkezik telepítési hibaként, amelyre sosem volt telepítve a Lazarus

Beszéd és képernyőolvasók Windowson kívül

A szövegfelolvasás az az egy funkció, ahol a platformfüggetlen történet megtörik, és az operációs rendszernél törik meg, nem a komponensnél. A SAPI, a szokásos TTS-háttér Windowson, csak Windowson létezik. Egy Lazarus-build, amely még mindig Windowst céloz, megtartja a teljes SAPI-kimenetet és ugyanazt az NVDA-kompatibilis viselkedést, ami a Delphi-eredetinek volt, így egy Windows-ról-Windowsra portolás itt semmit sem veszít, és egy NVDA-felhasználó nem tudja megkülönböztetni a két buildet

Egy Linux- vagy macOS-cél más eset. Nincs SAPI, amit hívni lehetne, így a hangkimenetet át kell drótozni egy natív beszédszolgáltatásra, miközben a fölötte lévő olvasási API-k a helyükön maradnak. Ez a megosztás az érv amellett, hogy a beszédet már az első commit-tól kezdve egy interfész mögé tedd: az olvasási-sorrend elemzés és a szókövető kurzor platformfüggetlen, és érintetlenül átvihető, és csak annak a vékony rétegnek kell platformonként változnia, amely ténylegesen hangot állít elő. A akadálymentes felolvasó cikk mélyrehatóan lefedi ezt az olvasási gépezetet

Egy paritási ellenőrzőlista, mielőtt kész portolásnak nyilvánítod

A következő átfutás valódi regressziókat fogott ki, nagyjából abban a sorrendben felsorolva, ahogyan a hibák felszínre szoktak kerülni. Nyiss meg egy dokumentumot, amelynek elérési útja nem-ASCII karaktereket tartalmaz. Keress egy nem-ASCII karaktereket tartalmazó kifejezésre, és győződj meg róla, hogy a találatok ott emelődnek ki, ahol kellene. Gyakorold az egérgörgős görgetést, a húzásos kijelölést és a billentyűzetes oldalnavigációt minden szállított widget-készleten, mert a fókuszkezelés és a görgő-viselkedés az LCL legwidget-készlet-függőbb sarkai. Ellenőrizd a renderelést 100%-os, 150%-os és 200%-os megjelenítési méretezésnél. Végül futtasd a telepített buildet, ne az IDE-buildet, egy olyan gépen, amelyre sosem volt telepítve az IDE, mert ez az egyetlen teszt, amely becsületesen gyakorolja a bináris feloldást. Minden más átmehet, miközben ez az egy csendben elbukik

A renderelési átviteli sebesség változatlanul átvihető a két kiadás között, így a renderelési gyorsítótár és zoom-teljesítmény cikk gyorsítótárazási megközelítése pontosan úgy alkalmazható az LCL-nézegetőre, ahogyan azt a VCL-nézegetőhöz írták

Mindettől az LCL-kiadás nem lesz alacsonyabb rendű. A magfelület mindkét oldalon azonos: a TPdf, a TPdfView, a renderelés, az űrlapok, a szövegkinyerés és az akadálymentesítési API-k ugyanúgy viselkednek, függetlenül attól, melyik IDE fordította őket. Minden nyomon követésre érdemes különbség platformhoz kötött, nem kiadáshoz kötött. A SAPI-beszéd csak Windowson érhető el, a párbeszédablakok az adott keretrendszer konvencióit követik, és a binárisnak illeszkednie kell ahhoz az architektúrához, amelybe betöltik. Ha jól kezeled a kódolási határokat, a futásidejű űrlapot és a bináris feloldást, a portolás többi része az a mechanikus munka, amit a fordító már elvégzett helyetted

PDFium Component diagram a beszédfelület felosztásáról, amely TTS kimenetet platformonkénti motor mögé helyez egy Lazarus PDF megjelenítőben
Az olvasásirend-elemzés és a szókövető kurzor platformsemleges marad, miközben egyetlen vékony beszédfelület Windowson SAPI-ra, Linuxon és macOS-en natív beszédszolgáltatásokra oldódik fel

Az itt leírt VCL- és LCL-kiadások együtt szállítva érkeznek PDFium Component néven, forráskóddal és azonos nyilvános API-kkal Delphihez, C++Builderhez és Lazarus/FPC-hez