Tekninen artikkeli

PDF-katseluohjelma Lazarukselle ja Free Pascalille PDFiumilla

Delphi ja Lazarus kääntävät (compile) samaa Object Pascalia, ja tuo pintatason samankaltaisuus (surface similarity) on juuri se, mikä tekee katseluohjelman (viewer) siirtämisestä (porting) niiden välillä petollista (deceptive). Nämä kaksi työkaluohjelmistoa (toolchains) erkanevat (diverge) kolmessa paikassa, joilla on väliä PDF-työssä: natiivi string-tyyppi on UTF-16 Delphissä ja UTF-8 LCL-sovelluksessa; VCL ja LCL ovat erilaisia visuaalisia kehyksiä (visual frameworks), joilla on omat kontrollinsa (controls), dialoginsa ja lomakkeiden striimausformaattinsa (form-streaming formats); ja Delphi-binääri (binary) on suunnattu (targets) Windowsille, kun taas FPC-binääri saattaa olla matkalla (headed for) Linuxiin tai macOS:ään. Mikään noista (None of those) eroista (differences) ei näy (shows up) käännösaikana (at compile time). Katseluohjelma, joka on rakennettu (built on) PDFium Component -komponentin päälle, joka (which) toimittaa (ships) VCL- ja LCL-versiot (editions) yhdestä lähdekoodipuusta (source tree), kääntyy puhtaasti (compile clean) Lazaruksen alla kourallisen (handful of) yksikönnimivaihtojen (unit-name swaps) ja muutaman {$IFDEF FPC} -lohkon jälkeen. Viat (failures) saapuvat myöhemmin, kun todellinen (real) data ja todellinen käyttöönotto (deployment) paljastavat oletukset (assumptions), joita (the) Delphi-koontiversio (build) teki hiljaa (quietly making)

Neljä noista oletuksista (assumptions) selittää (account for) suurimman osan menetetystä ajasta: tekstin koodaus (text encoding) käyttöliittymän rajalla (UI boundary), kiusaus (temptation) ylläpitää kahta kopiota lomakkeesta, tapa (way), jolla natiivi moottoribinääri (engine binary) ratkaistaan (resolves) ajonaikana (at runtime), ja hetki, jolloin teksti puheeksi -ominaisuudelta (text-to-speech) loppuu alusta (runs out of platform) SAPI:n poistuttua (is gone). Jokainen niistä on halpa käsitellä, jos tiedät sen olevan tulossa (know it is coming), ja kallis jäljittää (chase down), jos et

Sama Pascal, erilaiset merkkijonojen (string) hyötykuormat (payloads)

Delphin natiivi (native) string on ollut UTF-16 vuodesta 2009 lähtien. Lazarus ja Free Pascal käyttävät oletuksena (default to) UTF-8-muotoa LCL-sovelluksissa. Komponentin tekstiin suunnatut API:t puhuvat UTF-16-muotoa WString-tyypin (type) kautta, jonka FPC-koontiversio aliaskytkee (aliases to) WideString-tyypiksi, joten jokainen raja (boundary), jossa teksti risteää (crosses) LCL-käyttöliittymäsi (UI) ja PDF-moottorin välillä, on muunnospiste (conversion point)

Muunnokset tapahtuvat automaattisesti suoraviivaisissa (straightforward) sijoituksissa (assignments), ja useimman koodin ei koskaan tarvitse miettiä niitä (think about them). Kaksi tapaa (habits) pitävät koodausbugit loitolla. Siirrä (Pass) teksti suoraan (straight through) ilman tavutason (byte-level) manipulaatiota (manipulation): koodi, joka leikkaa (slices) hakusanaa (search term) tavusiirtymän (byte offset) mukaan, toimii Delphissä, jossa yksi Char on yksi UTF-16-yksikkö, ja korruptoi monitavuisen (multi-byte) UTF-8:n LCL:ssä. Ja testaa muulla kuin ASCII-datalla (non-ASCII data) ensimmäisestä ajosta lähtien. Saksalainen tiedostonimi, kyrillinen hakusana (search term), aksenttimerkkejä (accented) sisältävä tekijän (author) nimi asiakirjan metadatassa: pelkkä (pure) ASCII-testidata piilottaa (hides) jokaisen koodausvian (encoding defect), koska ASCII on se yksi (one) alue (range), jossa UTF-8 ja UTF-16 vastaavat (agree) tavua per merkki (byte for character). Bugi on todellinen (real) koko ajan; ASCII pitää sen vain näkymättömänä (invisible), kunnes (until) asiakas Münchenissä avaa tiedoston, jota et koskaan kokeillut

Yksi ehtolohko (conditional block), ei forkkia per IDE

Ensimmäisen tusinan IFDEF:n jälkeen koodipohja (codebase) alkaa tuntua (feel like) kahdelta projektilta yhden arkiston (repository) sisällä, ja sen forkkivalinta (forking it) per IDE näyttää (looks) houkuttelevalta (tempting). Se on väärä (wrong) siirto (move). Aidot erot (genuine differences) romahtavat (collapse) yhdeksi jaetuksi (shared) määrittelylohkoksi (declaration block), ja (and a) forkki kaksinkertaistaa (doubles) jokaisen bugikorjauksen hinnan siitä eteenpäin (from then on). Pidä ehdollinen taso (conditional layer) näin pienenä:

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

Kaikki tuon lohkon alapuolella kääntyy (compiles) identtisesti molemmissa IDE:issä. Asiakirjan (Document) käsittely, sivulla (page) navigointi (navigation), renderöintikutsut (rendering calls): TPdf ja TPdfView paljastavat (expose) saman pinnan (surface) VCL- ja LCL-versioissa (editions), joten (so) suurin osa katseluohjelmasta ei koskaan näe (sees) kääntäjän (compiler) ehtoa (condition). Sen pitäminen sellaisena on rakenteellinen (structural) kurinalaisuus (discipline) ennemmin kuin nokkela (clever) temppu (trick). Jaettu (Shared) PDF-logiikka elää yksiköissä (units), jotka eivät (pull in no) vedä mukaansa kehyskohtaisia (framework-specific) dialogeja tai paneeleja. Se kourallinen asioita, jotka aidosti eroavat (genuinely differ), kuten tulostusdialogit (print dialogs) ja tiedostovalitsimet (file pickers) alustakonventioineen (platform conventions), piiloutuvat ohuen (thin) rajapinnan (interface) taakse, joka on toteutettu kerran per kehys. IFDEF-lohkosta tulee se yksi paikka (single place), jonne tulevan (future) alustaerojen (platform divergence) annetaan laskeutua (land), sen sijaan että kääntäjädirektiivejä (compiler directives) vuotaisi (leaking) neljänkymmenen (forty) yksikön yli (across)

Rakenna lomake (form) koodissa (in code), ei (not in) kahdessa suunnitteluohjelmassa (designers)

Lomakkeen suoratoisto (Form streaming) on se kohta, missä (where) kahden IDE:n (dual-IDE) projektit mätänevät (rot) hiljaa. Yksi .dfm ja .lfm, jotka (that) väittävät (claim) kuvaavansa (to describe) saman lomakkeen (same form), ajautuvat erilleen (drift apart) ominaisuus kerrallaan, kunnes (until) nämä kaksi koontiversiota (builds) käyttäytyvät (behave) eri tavalla syistä (for reasons), joita kukaan ei osaa (nobody can) verrata (diff), koska (because) kyseiset kaksi tiedostoa (the two files) eivät edes (not even) ole samassa muodossa (same format). Katseluohjelman (viewer) rakentaminen ajonaikana (at runtime) kiertää (sidesteps) koko ongelman (whole problem). On yksi (There is one) konstruktorisekvenssi (constructor sequence), jota versionhallitaan (version-controlled) tavallisena koodina, ja (and it) se lukee (reads) samalla tavalla (the same) molemmilla alustoilla (on both platforms):

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;

Noiden (those) sijoitusten (assignments) tarkalla (exact) järjestyksellä (order) on vähemmän merkitystä kuin (less than) sillä yhdellä rivillä, joka tekee todellisen työn. PdfView.Pdf := Pdf sitoo (binds) visuaalisen (visual) kontrollin (control) asiakirjakomponenttiin (document component), ja siitä hetkestä lähtien (from that point) sivulla navigointi (page navigation) komennon PageNumber kautta ja (and) sovituskäyttäytyminen (fit behavior) komennon FitMode kautta vastaavat identtisesti (identically) VCL:n ja LCL:n alla. Yksi kehysten (cross-framework) välinen (quirk) on (is) tietämisen (knowing) arvoinen (worth) ennen kuin (before a) käyttäjä raportoi sen bugina: Zoom-arvon (Zoom) sijoittaminen (assigning) käsin (by hand) napsauttaa (snaps) FitMode-tilan (FitMode) takaisin arvoon pfmNone molemmissa kehyksissä. Joten (So) jos työkalurivisi (toolbar) käsittelee "sovita leveyteen" (fit width) tarttuvana mieltymyksenä (sticky preference), sinun (you) on sijoitettava (reassign) sovitustila (fit mode) uudelleen (reassign) minkä tahansa ohjelmallisen zoomaus-komennon (programmatic zoom) jälkeen, tai mieltymys lakkaa (stops) hiljaa tarttumasta (sticking) heti kun koodi koskee (touches) zoomaustasoon (zoom level) ensimmäisen kerran

Binääri (binary), josta IDE ei koskaan (never) varoittanut (warned) sinua

Komponentti käärii (wraps) sisäänsä PDFium-moottorin (engine), joka toimitetaan (ships) natiivina alustabinäärinä (native platform binary), ja tuo binääri on (is the) lähde lähes jokaiselle (nearly every) "toimii IDE:ssä, epäonnistuu asennetusta pikakuvakkeesta" (works in the IDE, fails from the installed shortcut) -raportille. Kolme (Three) sääntöä selittävät (account for) suurimman osan (most of) niistä. Bittisyyden (Bitness) on täsmättävä tarkalleen (exactly). 32-bittinen (32-bit) suoritettava (executable) tiedosto ei voi (cannot) ladata 64-bittistä pdfium-kirjastoa (library), ja (and the) viesti, jonka OS ojentaa takaisin ("moduulia ei löydy" joissakin Windows-versioissa), johtaa aktiivisesti harhaan (misleads), koska tiedosto (the file) istuu (sitting) aivan siinä (right there) suoritettavan tiedoston vieressä. Ratkaise (Resolve the) kirjaston (library) polku suhteessa suoritettavaan tiedostoon (executable), älä koskaan työhakemistoon (working directory); IDE-käynnistys (IDE launch) ja kuorikäynnistys (shell launch) eroavat (differ) toisistaan juuri tässä (precisely that) suhteessa (point), minkä (which is) vuoksi (why the) bugi piiloutuu (hides) kehityksen aikana. Ja pyydystä (catch) epäonnistunut (failed) lataus ennen kuin (before the) ensimmäinen (first) asiakirja (document) avautuu, ja (then) raportoi (report it) siitä odotetun polun ja arkkitehtuurin (architecture) kanssa selkeästi (spelled out). Tukipyyntö (support ticket), jossa lukee (reads) "PDFium 64-bit binary missing at <path>" sulkeutuu (closes in) minuuteissa. Se (One that), joka lukee "katseluohjelma kaatuu (crashes on) käynnistyksessä", muuttuu (turns into a) viikon pituiseksi (week of) edestakaisin vääntämiseksi (back-and-forth)

Versioi (Version) moottorin binääri (engine binary) suoritettavan tiedoston (executable) rinnalla samalla kun (while you are) teet (at it) sitä. PDFium liikkuu nopeasti (moves fast), ja asennusohjelma (installer), joka päivittää sovelluksen (application) mutta jättää vanhentuneen (stale) kirjaston levylle (on disk), tuottaa kaatumisia (crashes), joita kukaan (nobody in your) toimistossasi ei pysty (can) toistamaan (reproduce), yksinkertaisesta (simple) syystä (reason), että toimistosi jokaisella koneella (machine in your) sattuu olemaan (happens to hold) sopiva (matching) pari (pair). Käsittele kirjastoa (library) osana (part of the) käännösartifaktia (build artifact), samalla asennusohjelmalla (installer), samalla versioleimalla (version stamp) ja samalla peruutuspolulla (rollback path) kuin suoritettavaa (executable) tiedostoa, jota se (it) lataa

Komponenttien rekisteröinti Lazarus IDE:ssä

Ajonaikainen (Runtime) rakentaminen ei tarvitse minkäänlaista (whatsoever) suunnitteluaikaista (design-time) rekisteröintiä (registration), mikä on puhtain (cleanest) asetelma katseluohjelmalle (viewer), joka rakentaa oman käyttöliittymänsä (UI in) koodilla. Kun haluat komponentit (components) Lazarus-paletille suunnitteluaikaista (design-time) työtä varten, asenna (install the) paketti (package) ja anna (let its) sen omistetun (dedicated) rekisteröintiyksikön (registration unit), PDFiumLazReg (joka sijaitsee Lib/FPC/PDFiumLaz.lpk), käsitellä se (handle it). Tuo yksikkö on (is) merkitty (marked) tarkoituksella (on purpose) suunnitteluaikaiseksi (design-time): se viittaa IDE-ominaisuuseditorin rajapintoihin, joita ei koskaan saa linkittää toimitettavaan (shipping) suoritettavaan (executable) tiedostoosi

Tee (Get) tämä (this) väärin (wrong) ja oireena (symptom is an) on sovellus, joka (that inexplicably) riippuu selittämättömästi IDE-paketeista (IDE packages), mikä (which shows) näkyy (up as a) käyttöönotto-ongelmana (deployment failure on the) ensimmäisellä asiakkaan (customer) koneella, johon ei ole (has never) koskaan asennettu Lazarusta

Puhe (Speech) ja ruudunlukijat (screen readers off) Windowsin ulkopuolella (Windows)

Teksti puheeksi (Text-to-speech) on se yksi (one) ominaisuus (feature), jossa alustojen (cross-platform) välinen (story) tarina (story breaks) katkeaa, ja se (and it breaks) katkeaa käyttöjärjestelmässä (operating system), ei (not the) komponentissa. SAPI, tavallinen (usual) TTS-taustaohjelma Windowsissa, on olemassa (exists only on) vain Windowsissa. Lazarus-koontiversio, joka (that still) on edelleen (still) kohdistettu (targets) Windowsille, pitää (keeps) täyden (full) SAPI-tulosteen ja saman (same) NVDA-yhteensopivan (NVDA-compatible) käyttäytymisen (behavior) kuin (the) Delphi-alkuperäiselläkin (original) oli, joten (so a) Windows-to-Windows-siirto (port loses) ei menetä tässä (here) mitään, eikä (and an) NVDA-käyttäjä (user) erota (cannot tell) näitä kahta (two) koontiversiota (builds) toisistaan (apart)

Linux- tai macOS-kohde (target) on (is a) eri (different) asia (matter). Kutsuttavaa SAPIa ei ole (no SAPI to call), joten (so the) äänilähtö (audio output) on (has to be) johdotettava uudelleen (rewired to a) natiiviin (native) puhepalveluun (speech service) samalla kun sen (while the) yläpuolella (above it) olevat luku-API:t (reading APIs) pysyvät paikoillaan (stay put). Tuo kahtiajako (split) on (is the) argumentti (argument for) puheen (speech) asettamiselle rajapinnan (interface) taakse (behind an interface from the) ensimmäisestä (first) commitista (commit) lähtien: lukemisjärjestyksen analyysi (reading-order analysis) ja sanaa seuraava kohdistin (word-tracking cursor) ovat alustaneutraaleja (platform-neutral) ja siirtyvät (carry over) koskemattomina (untouched), ja vain ohut (thin) kerros (layer), joka todella (actually) tuottaa äänen (produces sound), on (has to) muututtava alustakohtaisesti (per platform). Artikkeli pdfium-component-accessible-reader-delphi.html (saavutettava lukija, accessible reader) käsittelee (covers that) tuota lukukoneistoa (reading machinery) syvällisesti (in depth)

Tasavertaisuuden (parity) tarkistuslista (checklist) ennen kuin (before you) kutsut (call the) porttausta (port) valmiiksi (done)

Seuraava ajo (pass) on (has) saanut kiinni todellisia regressioita (regressions), jotka on lueteltu karkeasti (roughly in the) siinä (order) järjestyksessä (order), jossa viat (failures) pyrkivät nousemaan (tend to surface) pintaan (surface). Avaa asiakirja, jonka (whose) polku sisältää muita kuin (non-ASCII) ASCII-merkkejä (characters). Etsi (Search for a) termiä, jossa on (with) ei-ASCII-merkkejä, ja (and confirm the) vahvista, että osumat (hits) korostuvat (highlight where) siellä, missä niiden pitäisikin (should). Kokeile (Exercise) hiiren pyörän (mouse-wheel) vieritystä (scroll), vedä valintaa (drag selection) ja näppäimistön (keyboard) sivulla (page) navigointia (navigation on each) jokaisessa toimittamassasi (you ship) widget-sarjassa (widget set), koska kohdistuksen (focus) käsittely (handling) ja (and) pyörän (wheel) käyttäytyminen (behavior are the) ovat LCL:n widget-sarjasta (widget-set-dependent) riippuvaisimpia (most) nurkkia (corners). Tarkista (Check) renderöinti 100 %, 150 % ja (and) 200 % näytön skaalauksella (display scaling). Lopuksi (Last), aja asennettua koontiversiota (installed build), ei (not the) IDE-koontiversiota, koneella (machine), jolla ei ole (has never) koskaan (had the) ollut IDE:tä (IDE on it), koska (because that is the) se on ainoa (only test) testi, joka koettelee binääriresoluutiota rehellisesti (honestly). Kaikki (Everything) muu (else) voi mennä läpi (can pass), kun (while) tuo yksi epäonnistuu hiljaa (quietly fails)

Renderöinnin läpimeno (Rendering throughput) siirtyy (carries over between the) sellaisenaan (unchanged) kahden version (editions) välillä (between the), joten artikkeli pdfium-component-render-cache-zoom-performance.html (renderöintivälimuisti ja zoomaus-suorituskyky, render cache and zoom performance) lähestymistapa (approach from the) pätee LCL-katseluohjelmaan (viewer) täsmälleen (exactly) niin (as) kuin se on (written for the) kirjoitettu VCL-katseluohjelmalle (VCL one)

Mikään näistä (None of this) ei tee (makes the) LCL-versiosta (LCL edition a) heikompaa (lesser one). Ydinpinta (core surface is) on (identical on) identtinen molemmilla (both) puolilla (sides): TPdf, TPdfView, renderöinti (rendering), lomakkeet (forms), tekstin louhinta (text extraction) ja (and the) saavutettavuus-API:t (accessibility APIs) käyttäytyvät samalla tavalla riippumatta (regardless of) siitä, mikä IDE (IDE compiled) ne käänsi (compiled them). Jokainen seuraamisen (tracking is) arvoinen ero (difference worth) on sidottu alustaan (platform-bound rather than) ennemmin kuin (rather than) versioon (edition-bound). SAPI-puhe on (is) vain Windowsille (Windows-only), dialogit (dialogs) noudattavat (follow each) kunkin kehyksen (framework's) käytäntöjä (conventions), ja binäärin (binary has to) on (match the) vastattava sitä (architecture) arkkitehtuuria, johon se ladataan (loaded into). Hoida (Get the) koodausrajat (encoding boundaries), ajonaikainen lomake (runtime form) ja binäärin (binary resolution) ratkaiseminen (resolution) kuntoon (right), ja (and the rest of the) loppu (rest of the) porttaus (port is the) on (is the) mekaanista (mechanical) työtä (work), jonka (the compiler) kääntäjä hoiti jo (already handled) puolestasi (for you)

Tässä kuvatut (described here ship) VCL- (VCL and LCL editions) ja LCL-versiot (editions) toimitetaan yhdessä (together as) nimellä PDFium Component, ja niissä (with source code) on (with) lähdekoodi sekä identtiset julkiset API:t Delphille, C++Builderille ja Lazarukselle/FPC:lle