Teknisk artikkel

PDF-viser for Lazarus og Free Pascal med PDFium

Delphi og Lazarus kompilerer den samme Object Pascal, og den overflatiske likheten er nettopp det som gjør det bedragersk å portere en fremviser mellom dem. De to verktøykjedene divergerer på tre steder som betyr noe for PDF-arbeid: den native string-typen er UTF-16 i Delphi og UTF-8 i en LCL-applikasjon; VCL og LCL er forskjellige visuelle rammeverk med sine egne kontroller, dialoger og skjema-strømmeformater; og en Delphi-binærfil retter seg mot Windows mens en FPC-binærfil kan være på vei mot Linux eller macOS. Ingen av disse forskjellene viser seg ved kompileringstidspunktet. En fremviser bygget på PDFium Component, som leverer VCL- og LCL-utgaver fra ett enkelt kildetre, vil kompilere rent under Lazarus etter en håndfull enhetsnavn-bytter og noen få {$IFDEF FPC}-blokker. Feilene ankommer senere, når ekte data og en ekte utrulling avslører antagelsene Delphi-builden stille gjorde

Fire av disse antagelsene står for mesteparten av den tapte tiden: tekstkoding ved UI-grensen, fristelsen til å vedlikeholde to kopier av skjemaet, måten en native motor-binærfil løses opp ved kjøretid på, og øyeblikket tekst-til-tale går tom for plattform når SAPI er borte. Hver av dem er billig å håndtere hvis du vet den kommer, og dyr å jage etter hvis du ikke gjør det

Samme Pascal, forskjellige strenglaster

Delphis native string har vært UTF-16 siden 2009. Lazarus og Free Pascal bruker UTF-8 som standard i LCL-applikasjoner. Komponentens tekstvendte API-er snakker UTF-16 gjennom WString-typen, som FPC-builden aliaser til WideString, så hver grense hvor tekst krysser mellom LCL-UI-et ditt og PDF-motoren, er et konverteringspunkt

Konverteringene skjer automatisk i grei tilordninger, og det meste av koden trenger aldri å tenke på dem. To vaner holder kodingsfeilene unna. Send tekst rett gjennom uten manipulasjon på bytenivå: kode som skjærer opp et søkeord etter byteforskyvning, fungerer i Delphi, hvor én Char er én UTF-16-enhet, og ødelegger multi-byte UTF-8 i LCL. Og test med ikke-ASCII-data fra første kjøring. Et tysk filnavn, et kyrillisk søkeord, et forfatternavn med aksenttegn i dokumentmetadataen: rent ASCII-testdata skjuler hver eneste kodingsfeil, fordi ASCII er det ene området hvor UTF-8 og UTF-16 er enige byte for tegn. Feilen er reell hele tiden; ASCII holder den bare usynlig helt til en kunde i München åpner en fil du aldri prøvde

Diagram over UTF-16- og UTF-8 strengkonverteringsgrensene mellom et LCL-viser-UI og PDFium-komponenten i Lazarus
Komponentens tekst-API-er snakker UTF-16 gjennom WString, så et LCL-UI med UTF-8-strenger møter et konverteringspunkt ved hver grense, og byte-offset-utskjæring eller kun-ASCII-testdata er der kodingsfeilene gjemmer seg

Én betinget blokk, ikke en fork per IDE

Etter det første dusinet IFDEF-er begynner kodebasen å føles som to prosjekter i ett repository, og å forke den per IDE ser fristende ut. Det er feil grep. De genuine forskjellene faller sammen til én delt deklarasjonsblokk, og en fork dobler kostnaden av hver feilfiks fra da av. Hold det betingede laget så lite som dette:

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

type
  WString = WideString;   // komponentens tekst-API-er er UTF-16
  TBytes  = array of Byte;
{$ELSE}
uses
  Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}

Alt under den blokken kompilerer identisk i begge IDE-ene. Dokumenthåndtering, sidenavigering, rendringskall: TPdf og TPdfView eksponerer den samme overflaten i VCL- og LCL-utgavene, så hoveddelen av fremviseren ser aldri en kompilatorbetingelse. Å holde det slik er strukturell disiplin fremfor et smart triks. Delt PDF-logikk bor i enheter som ikke drar inn noen rammeverk-spesifikke dialoger eller paneler. Den håndfullen ting som genuint skiller seg, som utskriftsdialoger og filvelgere med sine plattformkonvensjoner, gjemmer seg bak et tynt grensesnitt implementert én gang per rammeverk. IFDEF-blokken blir det ene stedet fremtidig plattformdivergens har lov til å lande, i stedet for å lekke kompilatordirektiver over førti enheter

Bygg skjemaet i kode, ikke i to designere

Skjema-strømming er der dobbelt-IDE-prosjekter stille råtner. En .dfm og en .lfm som hevder å beskrive det samme skjemaet, driver fra hverandre egenskap for egenskap helt til de to buildene oppfører seg forskjellig av grunner ingen kan diffe, fordi de to filene ikke engang er i samme format. Å konstruere fremviseren ved kjøretid går utenom hele problemet. Det finnes én konstruktørsekvens, versjonskontrollert som vanlig kode, og den leses likt på begge plattformer:

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;   // åpner dokumentet; PageCount gyldig etter dette
  end;
end;

Den eksakte rekkefølgen til de tilordningene betyr mindre enn den ene linjen som gjør det reelle arbeidet. PdfView.Pdf := Pdf binder det visuelle kontrollelementet til dokumentkomponenten, og fra det punktet svarer sidenavigering gjennom PageNumber og tilpasningsatferd gjennom FitMode identisk under VCL og LCL. Ett kvirk på tvers av rammeverk er verdt å kjenne til før en bruker rapporterer det som en feil: å tilordne Zoom for hånd smeller FitMode tilbake til pfmNone på begge rammeverk. Så hvis verktøylinjen din behandler "tilpass bredde" som en klebrig preferanse, må du tilordne tilpasningsmodusen på nytt etter enhver programmatisk zoom, ellers slutter preferansen stille å henge ved første gang koden rører zoom-nivået

Binærfilen IDE-en aldri advarte deg om

Komponenten pakker inn PDFium-motoren, som leveres som en native plattform-binærfil, og den binærfilen er kilden til nesten hver eneste "fungerer i IDE-en, feiler fra den installerte snarveien"-rapport. Tre regler står for de fleste av dem. Bit-bredden må matche eksakt. En 32-bits kjørbar fil kan ikke laste et 64-bits pdfium-bibliotek, og meldingen OS-et gir tilbake ("modul ikke funnet" på enkelte Windows-versjoner) villeder aktivt, fordi filen sitter rett der ved siden av den kjørbare filen. Løs bibliotekstien relativt til den kjørbare filen, aldri arbeidsmappen; en IDE-oppstart og en skalloppstart skiller seg akkurat på det punktet, og det er derfor feilen gjemmer seg under utvikling. Og fang en mislykket lasting før det første dokumentet åpnes, og rapporter den så med forventet sti og arkitektur stavet ut. En supportsak som lyder "PDFium 64-bits binærfil mangler ved <sti>" lukkes på minutter. Én som lyder "fremviser krasjer ved oppstart" blir til en uke med frem og tilbake

Versjonér motor-binærfilen sammen med den kjørbare filen mens du først er i gang. PDFium beveger seg raskt, og et installasjonsprogram som oppdaterer applikasjonen, men lar et foreldet bibliotek ligge igjen på disk, produserer krasj ingen på kontoret ditt kan gjenskape, av den enkle grunn at hver maskin på kontoret ditt tilfeldigvis holder det matchende paret. Behandle biblioteket som en del av byggeartefakten, med samme installasjonsprogram, samme versjonsstempel og samme tilbakerullingssti som den kjørbare filen den lastes inn i

Diagram over de tre PDFium native binærlastereglene for en Lazarus- eller Delphi PDF-viser-kjørbar
Tre regler dekker fleste rapporter om det fungerer i IDE-en men feiler installert: den kjørbare filen og PDFium-biblioteket må dele én bitthet, bibliotekstien løses fra den kjørbare filen i stedet for arbeidsmappen, og en feilet innlasting fanges med den forventede stien stavet ut

Å registrere komponenter i Lazarus-IDE-en

Kjøretidskonstruksjon trenger ingen designtidsregistrering i det hele tatt, noe som er det reneste oppsettet for en fremviser som bygger sitt eget UI i kode. Når du faktisk vil ha komponentene på Lazarus-paletten for designtidsarbeid, installer pakken og la dens dedikerte registreringsenhet, PDFiumLazReg i Lib/FPC/PDFiumLaz.lpk, ta seg av det. Den enheten er merket designtid med hensikt: den refererer IDE-egenskapsredigerer-grensesnitt som aldri må lenkes inn i den kjørbare filen du sender ut

Gjør dette feil, og symptomet er en applikasjon som uforklarlig avhenger av IDE-pakker, noe som viser seg som en utrullingsfeil på den første kundemaskinen som aldri har hatt Lazarus installert

Tale og skjermlesere utenfor Windows

Tekst-til-tale er den ene funksjonen hvor plattformuavhengigheten bryter sammen, og den bryter sammen ved operativsystemet, ikke ved komponenten. SAPI, den vanlige TTS-bakenden på Windows, eksisterer bare på Windows. En Lazarus-build som fortsatt retter seg mot Windows, beholder full SAPI-utdata og den samme NVDA-kompatible atferden Delphi-originalen hadde, så en Windows-til-Windows-portering mister ingenting her, og en NVDA-bruker kan ikke skille de to buildene fra hverandre

Et Linux- eller macOS-mål er en annen sak. Det finnes ingen SAPI å kalle, så lydutdataen må kobles om til en native taletjeneste mens lese-API-ene over den blir stående. Det skillet er argumentet for å plassere tale bak et grensesnitt fra første commit: analysen av leserekkefølge og ordsporingsmarkøren er plattformnøytrale og bæres over urørt, og bare det tynne laget som faktisk produserer lyd, må endres per plattform. Artikkelen om tilgjengelig leser dekker det lesemaskineriet i dybden

En paritetssjekkliste før du erklærer porteringen ferdig

Følgende gjennomgang har fanget reelle regresjoner, listet omtrent i den rekkefølgen feil har en tendens til å dukke opp i. Åpne et dokument hvis sti inneholder ikke-ASCII-tegn. Søk etter et begrep med ikke-ASCII-tegn og bekreft at treffene uthever der de skal. Utøv musehjul-rulling, dra-utvalg og tastaturs sidenavigering på hvert kontrollelement-sett du sender, fordi fokushåndtering og hjulatferd er de mest kontrollelement-sett-avhengige hjørnene av LCL-en. Sjekk rendring ved 100 %, 150 % og 200 % skjermskalering. Til slutt, kjør den installerte builden, ikke IDE-builden, på en maskin som aldri har hatt IDE-en på seg, fordi det er den eneste testen som utøver binærfilløsing ærlig. Alt annet kan bestå mens akkurat den stille feiler

Rendringsgjennomstrømning bæres over mellom de to utgavene uendret, så bufringstilnærmingen fra artikkelen om rendringsbuffer og zoom-ytelse gjelder for LCL-fremviseren nøyaktig slik den er skrevet for VCL-en

Ingenting av dette gjør LCL-utgaven til en dårligere en. Kjerneoverflaten er identisk på begge sider: TPdf, TPdfView, rendring, skjemaer, tekstuthenting, og tilgjengelighets-API-ene oppfører seg likt uansett hvilken IDE som kompilerte dem. Hver forskjell verdt å spore, er plattformbundet fremfor utgavebundet. SAPI-tale er bare for Windows, dialoger følger hvert rammeverks konvensjoner, og binærfilen må matche arkitekturen den lastes inn i. Få kodingsgrensene, kjøretidsskjemaet og binærfilløsingen riktig, og resten av porteringen er det mekaniske arbeidet kompilatoren allerede har tatt seg av for deg

PDFium Component-diagram over talegrensesnitt-splittingen som flytter TTS-utdata bak en per-plattform motor i en Lazarus PDF-viser
Leserekkefølge-analyse og den ordsporende markøren forblir plattformnøytrale mens ett tynt talegrensesnitt løses til SAPI på Windows og til opprinnelige tjenester på Linux og macOS

VCL- og LCL-utgavene beskrevet her, leveres sammen som PDFium Component, med kildekode og identiske offentlige API-er for Delphi, C++Builder og Lazarus/FPC