Tehnički članak

HotPDF na Free Pascalu: Deflate, AES i granice kodeka

HotPDF se kompajlira i izvodi pod Free Pascalom 3.2.2 s Lazarusom, i pošten sažetak tog prijenosa su dvije rečenice. Stvaranje dokumenata, učitavanje, spremanje, kompresija, dekompresija, šifriranje i dešifriranje sve rade na isključivo Pascal pozadinama, pa Lazarus aplikacija može proizvoditi i trošiti stvarni PDF bez ikakve C ovisnosti. Neobavezni izvorni kodeci slika ne rade, jer prethodno izgrađeni Win64 objekti koriste COFF varijantu koju nijedan Free Pascal povezivač ne može konzumirati, pa na tom alatnom lancu ulazne točke prelaze u začepčene verzije koje zaključavaju greškom

Karta mogućnosti HotPDF na Free Pascalu: radne Pascal pozadine deflate, AES i dokumenata uz začepčene kodece slika koji zaključavaju greškom
Značajke dokumenata, kompresije i šifriranja rade na isključivo Pascal pozadinama, dok se izvorni kodeci slika rješavaju u začepčene verzije koje zaključavaju greškom

Doći od "kompajlira se" do "radi" uzelo je određen skup ispravaka, i svaki od njih je zamka koja će naći svaku drugu Delphi bazu kôda koja se premješta na Free Pascal. Vrijedi ih zapisati u redoslijedu u kojem su boljele

Zašto jedinica koja se kompajlira ne dokazuje ništa?

Zato što Pascal jedinica može referencirati simbol koji nikad neće učiniti ništa korisno i i dalje zadovoljiti kompajler. U trenutku kada su se svih 113 knjižičnih jedinica izgradilo čisto pod Free Pascalom, rukovatelji arhivskih spremnika stvarno su radili, provjereno probnim testom koji je otvorio CBZ i pretvorio ga u PDF. Ravnanje XFA obrazaca uopće nije radilo, jer ravnanje mora napuhati komprimirani tok paketa /XFA a ulazna točka deflatea bila je još začepčena verzija. Ništa u izlazu izgradnje nije razlikovalo ta dva slučaja

Pravilo koje je iz toga izašlo kratko je. Prije nego napišete u napomeni o izdanju da značajka radi na novom alatnom lancu, napišite probu u vremenu izvođenja koja značajku vježba od početka do kraja na tom alatnom lancu. Pokrivenost kompajliranjem preduvjet je, nikad dokaz. Šira slika onoga što prijenos pokriva je u napomenama o Win64 podršci za Free Pascal i Lazarus

Iznimka unutar cdecl začepčene verzije ne dospijeva do pozivatelja

Ovo zaslužuje vlastito poglavlje jer je simptom tako varljiv. Jedinice začepčenih verzija izlažu C ulazne točke onako kako bi to učinila statička biblioteka, pa začepčena verzija izgleda ovako

// Izgleda razumno. Nije.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
  public name 'inflate';
begin
  raise ENotSupportedException.Create('codec unavailable');
end;

Na Free Pascalu za Win64 ta se iznimka ne širi do pozivatelja. Nema rukovatelja try..except koji je vidi, jer odmotavanje preko cdecl granice deklarirane ovako ne nosi Pascalov okvir iznimke; proces završava s izlaznim kôdom 217. Sa strane aplikacije nema pogreške, nema poruke i nema retka u dnevniku, samo program koji nestane. To je strogo gore od pogrešnog odgovora, jer se pogrešan odgovor može obraditi

Zašto iznimka bačena unutar cdecl začepčene verzije završava Free Pascal proces s izlaznim kodom 217 i kako ograničavanje Pascal ulazne točke to ispravlja
Pascalov okvir iznimke ne može se odmotati preko cdecl granice, pa proces tiho umire; ispravak ograničava prije nego se začepčena verzija uopće dođe

Primamljiv ispravak je učiniti da začepčena verzija vrati kôd kvara, i za inflate to je ispravno jer zlib ima dobro definiran povrat pogreške. Općenito je pogrešno: začepčena verzija za jpeg_read_header koja vraća nulu govori pozivatelju da nastavi sa strukturom koju nitko nije inicijalizirao. Trajni ispravak jest ograničiti na Pascal ulaznoj točki umjesto unutar začepčene verzije u obliku C, koristeći onu konvenciju kvara koju taj API već ima

function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
  // Odbijte prije nego se uopće dođe do začepčene verzije, s vlastitom
  // konvencijom kvara ovog API-ja umjesto iznimkom preko cdecl
  Bitmap := nil;
  Result := False;
  Exit;
{$ENDIF}
  Result := DecodeJPEGNative(Data, Bitmap);
end;

paszlib nije zlib, a razlika su dvije klase dokumenata

Pascal implementacija deflatea dostupna na Free Pascalu rukuje s dva uokvirenja: zlib omotom i sirovim deflateom. Ne rukuje gzip uokvirenjem, koje zlib bira kroz vrijednosti windowBits od 16 do 31, i ne rukuje načinom automatske detekcije koji biraju vrijednosti 32 do 47. HotPDF treba oboje. Sigurni put SVG uvoza traži 31, a učitavač ima ljestvicu povratka koja traži 47 kada je uokvirenje toka dvosmisleno. Preskočite bilo koje i cijela obitelj dokumenata prestaje se otvarati, s dekodirajućom pogreškom koja upućuje na tok, a ne na nedostajuće uokvirenje

Pokrivenost windowBits paszliba u odnosu na zlib: rasponi gzip uokvirenja i automatske detekcije nedostaju za SVG uvoz u HotPDF i povratak učitavača
paszlib rukuje zlib omotom i sirovim deflateom, ali HotPDF treba i windowBits 31 i 47, pa shim mora sam opskrbiti gzip uokvirenje

Postoji druga, oštrija nespojivost. Zapis z_stream koji deklarira paszlib nema isti memorijski raspored kao C-ov: njegovo polje msg kratak je niz umjesto pokazivača, a total_in i total_out su 64-bitni gdje C ABI ima strojne riječi. Pozivni zapis dakle ne može se prosljediti ravno. Radni aranžman jest držati stanje paszliba iza pokazivača state koji javni zapis već rezervira, i kopirati javna polja unutra i van oko svakog poziva. Gzip CRC i osmobajtni završetak duljine obračunaju se u tom istom shim sloju, koji je prirodno mjesto za njih budući da on već posjeduje odluku o uokvirenju

Prosljeđivanje dinamičkog polja netipiziranom var parametru

Ovo je greška koja najvjerojatnije upravo sjedi u vašem kôdu. Kada dinamičko polje prosljedite netipiziranom var parametru, ono što primatelj dobije jest adresa varijable polja, a to je adresa pokazivača, ne adresa korisnog tereta. Čitanje u nju dakle prepisuje samu varijablu i sve što sjedi uz nju

var
  FBuffer: TBytes;
begin
  SetLength(FBuffer, 65536);

  // Pogrešno: predaje adresu varijable FBuffer
  FStream.Read(FBuffer, Length(FBuffer));

  // Ispravno: predaje adresu prvog bajta korisnog tereta
  FStream.Read(FBuffer[0], Length(FBuffer));
end;

Na Delphiju pogrešan oblik često se čini da radi, jer ono što oštećuje jest susjedni stogovni utor koji poslije ništa ne čita. Na Free Pascalu isti redak na prvoj uporabi izaziva segmentacijsku pogrešku. Ono što to čini tako teškim za uočiti okom jest to da statička polja nemaju takav problem, jer je statička varijabla polja vlastiti korisni teret, pa su oba pravopisa ispravna u istoj datoteci ovisno o deklaraciji nekoliko stotina redaka dalje

ZIP spremnici bez System.Zip

Free Pascal nema ekvivalent RTL zip jedinice, a dostupna alternativa ima i drugačiju API površinu i bez podrške za starije šifriranje koje stariji formati spremnika i dalje koriste, pa se mali čitač unutar knjižnice pokazao kraćim od prilagođavanja njoj. Dvije pojedinosti formata koštale su vremena i lako ih je pogriješiti

Prva je bajt provjere zaglavlja šifriranja. Njegov dvanaesti bajt uobičajeno je gornji bajt CRC-a, ali kada je postavljena zastavica bita 3 opće namjene, što znači da veličine žive u pratećem opisniku podataka i da CRC još nije poznat, bajt provjere dolazi iz gornjeg bajta vremena izmjene umjesto toga. Implementirajte samo CRC oblik i svaki arhiv napisan u načinu strujanja odbit će ispravnu lozinku. Druga je dodatno polje ZIP64: njegova tri 64-bitna polja pojavljuju se u fiksnom redoslijedu ali se pišu samo kada je odgovarajuće 32-bitno polje zasićeno, pa ih čitanje po fiksnim pomacima radi na arhivima koje ste testirali i pada na sljedećem. Raščlanite ih pozicijski u odnosu na to koja su 32-bitna polja zasićena

Jedna pogodnost vrijedna znanja: Free Pascal tok dekompresije uzima drugi argument konstruktora koji preskače zlib zaglavlje, što je točno ono što ZIP unosi trebaju jer spremaju sirovi deflate. Taj put uopće ne dira knjižični zlib shim, pa na njega ne utječe nedostajuća C pozadina

Prozirnost znakova u boji pod LCL-om

Čitanje alfa kanala rasteriziranog znaka u boji jedina je grafička pojedinost bez izravne pretvorbe. LCL PNG klasa nema pristupnika scanlinea koji izlaže alfu, a dodjeljivanje PNG-a bitmapa odbacuje je, pa znak emocije u boji stiže potpuno neproziran i kompozitira s crnim okvirom iza sebe. Radni put jest sučeljska slika: stvorite je iz PNG-a, zatim čitajte piksele kroz pristupnik boje, pazeći da su njegove komponente 16-bitne i da trebaju pomak dolje za osam da postanu bajtovi. Ta površina također koristi prirodni redoslijed redaka od vrha prema dolje, pa inverzija Height - 1 - Y koju VCL scanline kôd treba mora se ukloniti, a ne portirati

Dvije napomene o sustavu izgradnje prije prijave greške

Potpuna ponovna izgradnja povremeno ne uspijeva s nedefiniranim simbolom čije ime završava sufiksom $crc i heksadecimalnom vrijednošću. Taj se sufiks računa iz tipova parametara, i ne uspijeva se podudariti kada jedna izgradnja kompajlira jedinicu u odnosu na dvije različite verzije sučelja u istom prolazu. Ponovno pokretanje izgradnje to čisti; potpis nije pogrešan

Drugo, Free Pascal 3.2.2 nema anonimne metode, pa gdje god je knjižnica koristila zatvorenje za povezivanje paralelnog cjevovoda Free Pascal izgradnja uzima deterministički serijski povratak umjesto toga. Izlaz je identičan, propusnost nije; ako ovisite o paralelnom prikazivanju stranica, to je razlog da za sada ostanete na Delphiju, a dizajn cjevovoda opisan je u članku o paralelnom cjevovodu prikazivanja. Stanje kodeka slika drugo je mjesto gdje izbor alatnog lanca mijenja mogućnosti, a ne samo brzinu, pa Lazarus raspoređivanje treba planirati formate slika u skladu s tim; trenutna matrica po alatnom lancu je na stranici proizvoda HotPDF Delphi PDF component