Tekninen artikkeli

PDF-katseluohjelma Lazarukselle ja Free Pascalille PDFiumilla

Delphi ja Lazarus kääntävät samaa Object Pascalia, ja juuri tuo pinnan samankaltaisuus tekee katseluohjelman siirtämisestä niiden välillä petollista. Työkaluketjut eroavat toisistaan kolmessa PDF-työn kannalta olennaisessa kohdassa: natiivi string-tyyppi on UTF-16 Delphissä ja UTF-8 LCL-sovelluksessa; VCL ja LCL ovat eri visuaalisia kehyksiä, joilla on omat kontrollinsa, dialoginsa ja lomakkeiden tallennusmuotonsa; ja Delphi-binääri kohdistuu Windowsiin, kun taas FPC-binääri voi olla matkalla Linuxiin tai macOS:ään. Mikään näistä eroista ei paljastu käännösaikana. PDFium Componentin päälle rakennettu katseluohjelma — komponentti toimitetaan VCL- ja LCL-editioina yhdestä lähdekoodipuusta — kääntyy puhtaasti Lazaruksen alla kourallisen yksikkönimien vaihtoja ja muutaman {$IFDEF FPC}-lohkon jälkeen. Viat tulevat esiin myöhemmin, kun oikea data ja oikea käyttöönotto paljastavat ne oletukset, joita Delphi-käännös oli hiljaa tehnyt

Suurimman osan menetetystä ajasta selittää neljä näistä oletuksista: tekstin koodaus käyttöliittymän rajalla, kiusaus ylläpitää lomakkeesta kahta kopiota, tapa jolla natiivi moottoribinääri ratkeaa ajonaikaisesti ja hetki, jolloin tekstistä puheeksi -ominaisuudelta loppuu alusta SAPI:n kadotessa. Kunkin käsittely on halpaa, jos sen tulemisen tietää, ja jäljittäminen kallista, jos ei tiedä

Sama Pascal, erilaiset merkkijonojen hyötykuormat

Delphin natiivi string on ollut UTF-16 vuodesta 2009 lähtien. Lazarus ja Free Pascal käyttävät oletuksena UTF-8:aa LCL-sovelluksissa. Komponentin tekstiä käsittelevät API:t puhuvat UTF-16:ta WString-tyypin kautta, jonka FPC-käännös aliasoi tyypiksi WideString, joten jokainen raja, jolla teksti liikkuu LCL-käyttöliittymäsi ja PDF-moottorin välillä, on muunnospiste

Muunnokset tapahtuvat automaattisesti suoraviivaisissa sijoituksissa, eikä useimpien ohjelmien tarvitse koskaan miettiä niitä. Kaksi tapaa pitää koodausviat loitolla. Välitä teksti sellaisenaan ilman tavutason käsittelyä: koodi, joka pilkkoo hakutermin tavusiirtymän mukaan, toimii Delphissä, jossa yksi Char on yksi UTF-16-yksikkö, mutta korruptoi monitavuisen UTF-8:n LCL:ssä. Ja testaa ei-ASCII-datalla ensimmäisestä ajosta lähtien. Saksalainen tiedostonimi, kyrillinen hakutermi, aksentoitu tekijänimi asiakirjan metadatassa: puhtaasti ASCII-testidata kätkee jokaisen koodausvian, koska ASCII on se ainoa alue, jossa UTF-8 ja UTF-16 vastaavat toisiaan merkkiä kohden tavu kerrallaan. Vika on todellinen koko ajan; ASCII vain pitää sen näkymättömänä, kunnes asiakas Münchenissä avaa tiedoston, jota et koskaan kokeillut

Kaavio UTF-16- ja UTF-8-merkkijonon muunnosrajoista LCL-katselimen käyttöliittymän ja PDFium-komponentin välillä Lazaruksessa
Komponentin tekstirajapinnat puhuvat UTF-16:a WStringin kautta, joten UTF-8-merkkijonoja kantava LCL-käyttöliittymä kohtaa muunnospisteen jokaisella rajalla, ja tavuoffseteilla viipalointi tai pelkkiä ASCII-merkkejä sisältävä testidata ovat paikat, joissa merkistöviat piileskelevät

Yksi ehtolohko, ei haarautumista per IDE

Ensimmäisen tusinan IFDEF:n jälkeen koodikanta alkaa tuntua kahdelta projektilta samassa arkistossa, ja sen haarauttaminen per IDE näyttää houkuttelevalta. Se on väärä liike. Todelliset erovat supistuvat yhdeksi jaetuksi esittelylohkoksi, ja haarautuminen kaksinkertaistaisi jokaisen vikakorjauksen hinnan siitä eteenpäin. Pidä ehtokerros näin pienenä:

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

type
  WString = WideString;   // komponentin teksti-API:t ovat UTF-16
  TBytes  = array of Byte;
{$ELSE}
uses
  Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}

Kaikki tuon lohkon alla oleva kääntyy identtisesti molemmissa IDE:ssä. Asiakirjojen käsittely, sivulla liikkuminen, renderöintikutsut: TPdf ja TPdfView tarjoavat saman rajapinnan VCL- ja LCL-editioissa, joten katseluohjelmasta suurin osa ei koskaan kohtaa kääntäjädirektiiviä. Sen pitäminen sellaisenaan on rakenteellista kuria eikä nokkela temppu. Yhteinen PDF-logiikka elää yksiköissä, jotka eivät vedä mukaansa kehyskohtaisia dialogeja tai paneeleja. Se harvojen asioiden joukko, jotka aidosti eroavat — kuten tulostusdialogit ja alustakäytäntöjensä mukaiset tiedostovalitsimet — kätkeytyy ohuen rajapinnan taakse, joka toteutetaan kerran per kehys. IFDEF-lohkosta tulee se yksi paikka, johon tulevat alustaerot saavat laskeutua, sen sijaan että kääntäjädirektiivit vuotaisivat neljänkymmenen yksikön yli

Rakenna lomake koodissa, ei kahdessa suunnittelijassa

Lomakkeiden tallennus on kohta, jossa kahden IDE:n projektit mätänevät hiljalleen. .dfm ja .lfm, jotka väittävät kuvaavansa samaa lomaketta, ajautuvat erilleen ominaisuus kerrallaan, kunnes molemmat käännökset käyttäytyvät eri tavalla syistä, joita kukaan ei pysty vertailemaan, koska kyseiset kaksi tiedostoa eivät edes ole samassa muodossa. Katseluohjelman rakentaminen ajonaikaisesti kiertää koko ongelman. Siinä on yksi konstruktorisekvenssi, versionhallittuna tavallisena koodina, ja se näyttää samalta molemmilla alustoilla:

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;   // avaa asiakirjan; PageCount on käytössä tämän jälkeen
  end;
end;

Noiden sijoitusten tarkalla järjestyksellä on vähemmän merkitystä kuin sillä yhdellä rivillä, joka tekee varsinaisen työn. PdfView.Pdf := Pdf sitoo visuaalisen kontrollin asiakirjakomponenttiin, ja siitä lähtien sivulla liikkuminen PageNumber-ominaisuuden kautta ja sovituskäyttäytyminen FitMode-ominaisuuden kautta toimivat identtisesti VCL:n ja LCL:n alla. Yksi kehyksienvälinen erikoisuus on hyvä tietää ennen kuin käyttäjä raportoi sen vikana: Zoom-arvon asettaminen käsin palauttaa FitModen arvoon pfmNone molemmissa kehyksissä. Jos työkalupalkkisi siis kohtelee "sovita leveyteen" -asetusta pysyvänä mieltymyksenä, sinun on asetettava sovitustila uudelleen jokaisen ohjelmallisen zoomauksen jälkeen, tai asetus lakkaa hiljaa pätemästä ensimmäisellä kerralla, kun koodi koskee zoomaustasoon

Binääri, josta IDE ei koskaan varoittanut

Komponentti kietoo ympärilleen PDFium-moottorin, joka toimitetaan natiivina alustabinäärinä, ja juuri tuo binääri on lähde lähes jokaiseen "toimii IDE:ssä, kaatuu asennetusta pikakuvakkeesta" -raporttiin. Kolme sääntöä selittää suurimman osan niistä. Bittisyyden on täsmättävä tarkalleen. 32-bittinen suoritettava tiedosto ei voi ladata 64-bittistä pdfium-kirjastoa, ja käyttöjärjestelmän palauttama viesti ("moduulia ei löydy" joissakin Windows-versioissa) johdattaa aktiivisesti harhaan, koska tiedosto makaa aivan suoritettavan tiedoston vieressä. Ratkaise kirjaston polku suoritettavaan tiedostoon suhteutettuna, älä koskaan työhakemistoon; IDE-käynnistys ja komentorivikäynnistys eroavat juuri tässä suhteessa, minkä vuoksi vika piilottelee kehityksen aikana. Ja ota epäonnistunut lataus kiinni ennen ensimmäisen asiakirjan avaamista ja raportoi se odotettu polku ja arkkitehtuuri erikseen mainiten. Tukipyyntö, jossa lukee "PDFium 64-bit binary missing at <path>", sulkeutuu minuuteissa. Se, jossa lukee "katseluohjelma kaatuu käynnistyksessä", muuttuu viikon edestakaiseksi selittelyksi

Versioi moottoribinääri suoritettavan tiedoston rinnalla samalla kertaa. PDFium kehittyy nopeasti, ja asennusohjelma, joka päivittää sovelluksen mutta jättää vanhentuneen kirjaston levylle, tuottaa kaatumisia, joita kukaan toimistossanne ei pysty toistamaan, yksinkertaisesti siksi, että jokaisella koneella toimistossanne sattuu olemaan sopiva pari. Kohtele kirjastoa osana käännösartefaktia, samalla asennusohjelmalla, samalla versioleimalla ja samalla paluupolulla kuin se suoritettava tiedosto, jonka se lataa

Kaavio kolmesta PDFium-natiivibinäärin lataussäännöstä Lazarus- tai Delphi-PDF-katselimen suoritettavalle tiedostolle
Kolme sääntöä kattaa useimmat IDE:ssä toimii-asennettuna kaatuu -raportit: suoritettavan tiedoston ja PDFium-kirjaston on jaettava sama bittisyys, kirjastopolku ratkeaa suoritettavasta tiedostosta eikä työhakemistosta, ja epäonnistunut lataus saadaan kiinni odotettu polku kirjoitettuna esiin

Komponenttien rekisteröinti Lazarus IDE:ssä

Ajonaikainen rakentaminen ei tarvitse lainkaan suunnitteluaikaisia rekisteröintejä, mikä on puhtain asetelma katseluohjelmalle, joka rakentaa oman käyttöliittymänsä koodissa. Kun haluat komponentit Lazarus-paletille suunnitteluaikaista työskentelyä varten, asenna paketti ja anna sen omalle rekisteröintiyksikölle, PDFiumLazReg tiedostossa Lib/FPC/PDFiumLaz.lpk, hoitaa asia. Tuo yksikkö on merkitty tarkoituksella suunnitteluaikaiseksi: se viittaa IDE:n ominaisuuseditorien rajapintoihin, joita ei saa koskaan linkittää toimitettavaan suoritettavaan tiedostoon

Jos tämä menee pieleen, oireena on sovellus, joka riippuu käsittämättömästi IDE-paketeista, mikä näkyy käyttöönotto-ongelmana ensimmäisellä asiakaskoneella, johon Lazarusta ei ole koskaan asennettu

Puhe ja ruudunlukijat Windowsin ulkopuolella

Tekstistä puheeksi on se yksi ominaisuus, jossa alustarajat hajoavat, ja ne hajoavat käyttöjärjestelmässä, eivät komponentissa. SAPI, tavallinen TTS-tausta Windowsissa, on olemassa vain Windowsissa. Lazarus-käännös, joka kohdistuu yhä Windowsiin, säilyttää täyden SAPI-tulosteen ja saman NVDA-yhteensopivan käyttäytymisen kuin Delphi-alkuperäinenkin, joten Windowsista Windowsiin -siirto ei menetä tässä mitään, eikä NVDA-käyttäjä erota näitä kahta käännöstä toisistaan

Linux- tai macOS-kohde on eri juttu. SAPIa ei ole kutsuttavana, joten äänilähtö on kytkettävä uudelleen natiiviin puhepalveluun, kun taas sen yläpuolella olevat lukemisrajapinnat pysyvät paikoillaan. Juuri tämä kahtiajako on peruste sille, miksi puhe kannattaa pistää rajapinnan taakse ensimmäisestä commitista lähtien: lukujärjestysanalyysi ja sanoja seuraava kohdistin ovat alustaneutraaleja ja siirtyvät koskemattomina, ja vain se ohut kerros, joka oikeasti tuottaa äänen, on muutettava alustakohtaisesti. Artikkeli saavutettavasta lukijasta käsittelee tuota lukukoneistoa perusteellisesti

Yhtäpitävyystarkistuslista ennen kuin julistat siirron valmiiksi

Seuraava läpikäynti on saanut kiinni oikeita regressioita; kohdat on lueteltu suunnilleen siinä järjestyksessä, jossa viat tapaavat tulla esiin. Avaa asiakirja, jonka polku sisältää ei-ASCII-merkkejä. Hae termi, jossa on ei-ASCII-merkkejä, ja varmista, että osumat korostuvat siellä, missä niiden pitääkin. Kokeile hiiren rullan vieritystä, raahaamalla valitsemista ja näppäimistöllä sivulta toiselle siirtymistä jokaisessa toimittamassasi widget-sarjassa, koska kohdistuksen käsittely ja rullan käyttäytyminen ovat LCL:n widget-sarjasta eniten riippuvia nurkkia. Tarkista renderöinti 100 prosentin, 150 prosentin ja 200 prosentin näytön skaalauksella. Aja lopuksi asennettu käännös, ei IDE-käännös, koneella, jolla ei ole koskaan ollut IDE:tä, koska vain se testi koettelee binäärin ratkaisun rehellisesti. Kaikki muu voi mennä läpi, vaikka toi yksi epäonnistuu hiljaisesti

Renderöinnin läpimeno siirtyy editioiden välillä muuttumattomana, joten artikkelissa renderöintivälimuistista ja zoomauksen suorituskyvystä esitetty lähestymistapa pätee LCL-katseluohjelmaan täsmälleen siinä muodossa kuin se on kirjoitettu VCL-katseluohjelmalle

Mikään tästä ei tee LCL-editiosta toisenluokkaista. Ydinrajapinta on identtinen molemmin puolin: TPdf, TPdfView, renderöinti, lomakkeet, tekstin poiminta ja saavutettavuusrajapinnat käyttäytyvät samalla tavalla riippumatta siitä, millä IDE:llä ne on käännetty. Jokainen seurannan arvoinen ero on alustasidonnainen, ei editiosidonnainen. SAPI-puhe on vain Windowsissa, dialogit noudattavat kunkin kehyksen käytäntöjä, ja binäärin on vastattava sitä arkkitehtuuria, johon se ladataan. Kun koodausrajat, ajonaikainen lomake ja binäärin ratkaisu ovat kunnossa, loput siirrosta on sitä mekaanista työtä, jonka kääntäjä jo hoiti puolestasi

PDFium Component -kaavio puherajapinnan jaosta, joka siirtää TTS-tulosteen alustakohtaisen moottorin taakse Lazarus-PDF-katselimessa
Lukujärjestysanalyysi ja sanaseurannan kohdistin pysyvät alustariippumattomina, kun taas yksi ohut puhurajapinta ratkeaa SAPI:ksi Windowsissa ja natiiveiksi puhepalveluiksi Linuxilla ja macOS:llä

Tässä kuvatut VCL- ja LCL-editiot toimitetaan yhdessä PDFium Component -pakettina, lähdekoodeineen ja identtisine julkisine API:neineen Delphille, C++Builderille ja Lazarus/FPC:lle