Techninis straipsnis

JBIG2 koduoklio posistemės ir Free Pascal linkeris

PDFlibPas gali koduoti dviejų lygių (bilevel) paveikslėlius kaip JBIG2 per du skirtingus posistemius. Vienas yra savasis Object Pascal MMR koduoklis, esantis visada. Kitas yra išorinis simbolių žodyno koduoklis, kuris nuskaitytą tekstą sudengia gerokai mažesnį, ir jis yra pasirinktinis: projektas turi susieti posistemio modulį, kad jis apskritai egzistuotų. Ta išskirtis yra dažniausio šios savybės netikėtumo šaltinis, todėl verta pasakyti pirmiausia: DefaultJBIG2EncodeOptions pagal numatymą prašo išorinio koduoklio, o kai posistemio modulis nesurištas, tas prašymas tyloje grįžta į Pascal MMR kelią

Delphi ir C++Builder aplinkose išorinis posistemis yra paruoštų statinių objektų rinkinys. Free Pascal aplinkoje jis turėjo tapti DLL, o kelias iki tos išvados yra linkerio istorija, naudinga kiekvienam, kas yra bandęs susieti C++ objektus į Free Pascal programą

Registracija yra sutartis

Posistemio modulis užsiregistruoja iš savo inicializacijos sekcijos, iškviesdamas RegisterJBIG2EncoderBackend. Iškviečiantieji jo prašo arba per parinkčių bitą PDF_JBIG2_OPTION_EXTERNAL_ENCODER, kurio reikšmė yra 4, arba per išplėstinių paveikslėlių įėjimo taškų parametrą UseExternalEncoder. Bibliotekos skėtis sąmoningai nepatraukia posistemio modulio, nes didelio objektų rinkinio nešiojimas turėtų būti kiekvieno projekto sprendimas; C++Builder medyje, pavyzdžiui, jį aiškiai įtraukia projektai, kurie jo nori

Iškviečiančiųjų pasekmė tokia, kad išorinio koduoklio prašymas yra pageidavimas, o ne garantija, ir versija, pamiršusi modulį, duoda didesnius failus, o ne klaidą. Jeigu išvesties dydis pakankamai svarbus, kad prašytumėte geresnio koduoklio, jis pakankamai svarbus ir tam, kad patikrintumėte, ar jį gavote

PDFlibPas JBIG2 kodavimo prašymo eiga, kurioje išorinio koduoklio pageidavimas be posistemio modulio tyloje grįžta į savąjį Pascal MMR kelią
Išorinio simbolių žodyno koduoklio prašymas yra pageidavimas: susietas — išvestis mažėja; nesurištas — Pascal MMR kelias tyloje dirba su didesniais failais
uses
  PDFlibrary,
{$IFDEF FPC}
  PDFlibJBIG2EncDLL;    // dinaminis posistemis Free Pascal
{$ELSE}
  PDFlibJBIG2EncC;      // statinis objektų rinkinys Delphi / C++Builder
{$ENDIF}

var
  Pdf: TPDFlib;
  ImageId: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;
    Pdf.NewPage;
    // Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
    // BlackDotSize, LossyLevel
    ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
      0, 1, 1, 0, 0, 0);
    if ImageId = 0 then
      raise Exception.Create('JBIG2 encoding failed');
    Pdf.SaveToFile('archive.pdf');
  finally
    Pdf.Free;
  end;
end;

Modulio sukompiliavimas buvo dvi eilutės. Simboliai buvo tas darbas

Kad pats posistemio modulis sukompiliuotų Free Pascal aplinkoje, užteko lygiai dviejų pakeitimų: nustatyti asemblerio dialektą ir pakeisti įrašu paremtą formatų nustatymų konstruktorių globaliu numatytuoju kintamuoju. Tai sąžiningas atspindys, kiek perkeliamas nesudėtingas Pascal tarp dviejų kompiliatorių

Simbolių pusė buvo tikras darbas. Objektų rinkinys nurodo 176 C simbolius. Iš jų 128 jau turėjo Pascal įgyvendinimus modulio viduje ir jiems teko tik pridėti eksporto vardus, nes Delphi naudoja funkcijos vardą kaip simbolio vardą, o Free Pascal reikalauja aiškios viešojo vardo deklaracijos. Dvidešimt septyni buvo bendri su JPEG 2000 kodeku ir juos teko eksportuoti tiksliai iš vienos vietos, nes dukart apibrėžti jie sugadina bet kurią programą, kuri susieja abu. Likę 21 buvo platformos ir C vykdymo laiko įrašai — šešiolika Win32 failų funkcijų ir saujelė standartinės bibliotekos iškvietimų — ir jie atiteko naujam suderinamumo moduliui

Nė viena iš tų dalių nėra sąvokos atžvilgiu sunki, ir visos jos reikalingos, kad linkeris apskritai pamėgintų. Linkeris yra ta vieta, kur tas darbas sustojo

Trys susiejimo keliai, trys aklavietės

Vidinis Free Pascal linkeris negali skaityti objektinių failų, nes jie pagaminti kompiliatoriaus, skleidžiančio asociatyviąsias COMDAT sekcijas, ir vidinis linkeris praneša, kad jų nepalaiko. Tai kategoriškas atsisakymas, o ne įspėjimas

Pereiti prie išorinio linkerio atrodė kaip atsakymas. Kartu su Free Pascal teikiamas binutils linkeris tuoj pat stringa taikydamas sekcijų šiukšlių surinkimą šiam archyvui, o ta vėliavėlė yra fiksuoto parametrų rinkinio, kurį Free Pascal perduoda 64 bitų Windows paskirčiai, dalis, todėl jos negalima pašalinti iš komandinės eilutės; dokumentuoti jos slopinimo jungikliai šiuo keliu nepaisomi. Gerokai naujesnio binutils pateikimas nepavyksta kitaip: jis apskritai negali apdoroti Free Pascal susiejimo scenarijaus — be scenarijaus duoda tuščią išvestį, o su juo — sieną perkeliamųjų įrašų klaidų

Aptikta pakeliui riba verta žinoti, net jeigu linkerio problemos niekada nepatirsite. Išorinis linkeris objektinių failų kelius aiškina santykiu su vykdomojo failo išvesties katalogu, o ne su šaltinių medžiu, todėl santykinė include-object direktyva veikia tik tada, kai išvesties katalogas atsitiktinai sutampa su kompiliavimo darbo katalogu. Biblioteka negali to manyti apie vartotojo projektą, o tai savaime yra priežastis teikti pirmenybę susietai bibliotekai, o ne laisviems objektams

Trys nesėkmingi linkerio keliai C++ JBIG2 koduoklio objektams Free Pascal aplinkoje ir DLL, atverianti du paprastus C įėjimo taškus, kurie juos išsprendė
COMDAT sekcijos įveikia vidinį linkerį, o abu išoriniai linkeriai neveikia, todėl C++ koduoklis išsiunčiamas kaip viena DLL, kurią dinamiškai susieja posistemio modulis

Kodėl kitas C++ kompiliatorius nepadeda

Akivaizdi tolesnė idėja — perstatyti C++ pusę kompiliatoriumi, kurio objektus Free Pascal moka skaityti. Tai taip pat neveikia, ir priežastis yra esminė, o ne jungiklių reikalas. Minimalus C++ vertimo vienetas su šablonu (template), sukompiliuotas išjungus visas kodo generavimo savybes, vis tiek skleidžia silpnus išorinius simbolius, nes šablonų ir inline instancijos juos duoda savaime. Free Pascal tą simbolių klasę atmeta kategoriškai. Ir atvirkščia kryptimi nepavyksta: pagrindinio srauto C++ linkeris negali vartoti kito kompiliatoriaus objektų dėl to paties COMDAT sekcijų apdorojimo

Taigi C++ kodo negalima pristatyti Free Pascal kaip objektų jokiu turimu keliu. Jį galima pristatyti kaip DLL, kas ir nutiko: koduoklis ir jo paveikslėlių apdorojimo priklausomybė pastatyti į vieną biblioteką, atveriančią du paprastus C įėjimo taškus, o Free Pascal posistemio modulis juos susieja dinamiškai ir užsiregistruoja lygiai taip, kaip statinis posistemis. Delphi ir C++Builder kelias apskritai nepaliestas, ir tai teisingas rezultatas; perkeliamumo problema vienoje įrankių grandinėje neturi sudrumsti tos, kuri jau veikia

Polarumas — vienintelis dalykas, kuris jus įkąs

Tarp Windows dviejų lygių bitmap ir JBIG2 koduoklio yra konvencijų nesutapimas, kurio neužfiksuos jokia tipų sistema. Vieno bito pikseliui nepriklausomo nuo įrenginio bitmap eilutėje nustatytas bitas reiškia baltą. Koduoklyje nustatytas bitas reiškia juodą. Perdavę eilutes nepakeistas gausite visiškai teisėtą JBIG2 srautą — jūsų puslapio fotografinį negatyvą

Vieno bito DIB ir JBIG2 polarumo konvencijos, kuriose nustatytas bitas eilutėje yra baltas, o koduoklyje juodas; ištaisyta apverčiant kiekvieną baitą
Tie patys baitai, priešinga prasmė: neapvertus kiekvieno baito koduoklis duoda teisėtą fotografinio negatyvo JBIG2 srautą
// Vieno bito DIB: nustatytas bitas reiškia baltą. JBIG2 koduoklis:
// nustatytas bitas reiškia juodą. Apverskite kiekvieną baitą kelyje į vidų
for I := 0 to RowBytes - 1 do
  Row[I] := Row[I] xor $FF;

Patikros metodas toks pat svarbus kaip pataisymas. Suglaudintų srautų ilgių palyginimas nieko nepasako, nes negatyvas glaudinasi iki panašaus dydžio. Žvilgsnis į puslapį įrodo tik tai, kad jis ne akivaizdžiai apverstas. Patikimas tikrinimas — atvaizduoti abiejų kodavimo kelių, savojo Pascal ir išorinio, išvestį į PNG ir palyginti baitas po baito: abu koduokliai toje pačioje šaltinio atvaizde yra besauliai, todėl bet kas, išskyrus tikslų sutapimą, yra vieno iš jų klaida. Tas palyginimas dabar yra nuolatinis regresijos testas, ir toks teiginys verta kurti kaskart, kai dvi realizacijos turi sutapti tiksliai

Kurį posistemį naudoti

Bendro pobūdžio dviejų lygių turiniui — rastruotiems pusamongiams, linijinei grafikai, maišytai grafikai — savasis Pascal MMR koduoklis pakanka ir neturi diegimo kaštų. Nuskaitytam tekstui, kuris yra tas atvejis, kuriam JBIG2 buvo projektuotas, išorinis simbolių žodyno koduoklis yra tai, kur gyvena dydžio sumažėjimas, nes jis pasikartojančias glifo formas iškrauna į žodyną, o ne užkoduja iš naujo kiekvieną pasirodymą. Jeigu gaminate nuskaitytų dokumentų archyvus, tas skirtumas pakankamai didelis, kad pakeistų saugojimo planavimą

Pradinis klausimas — kaip dviejų lygių atvaizdas apskritai pagaminamas — dydžiui yra lygiai taip pat svarbus; sritimis paremtą vienspalvį atvaizdavimą aprašo vienspalvio regiono atvaizdavimo straipsnis, o viso dokumento dydžio strategiją — PDF failo dydžio optimizacija ir šriftų poaibių sudarymas. Nuskaitymo rinkiniams su pasikartojančiais puslapiais dedubliavimas dažnai nugalą gerensesnį glaudinimą — tai suvokiamasis atvaizdų dedubliavimas. Įrankių grandinės ir posistemio prieinamumas pagal platformą išvardytas losLab PDF Developer Library produkto puslapyje