Teknisk artikkel

Å dele JBIG2-symbolordbøker på tvers av sider i Delphi

En femti-siders skannet kontrakt gjentar det samme alfabetet på hver side, men en JBIG2-koder som bygger én symbolordbok per bilde, trener opp det alfabetet på nytt femti separate ganger. HotPDF, den native Delphi- og C++Builder-PDF-komponenten, kan i stedet akkumulere én delt symbolordbok gjennom hele dokumentet og forfremme den til én enkelt dokumentnivå-/JBIG2Globals-strøm, slik at hver sides egen JBIG2-strøm bare refererer symbol-ID-er i stedet for å lagre sin egen kopi av alfabetet

Denne artikkelen holder seg bevisst smal og dekker bare hvordan HotPDF bygger den delingen på tvers av sider internt — JBIG2-grunnlaget, CCITT-sammenligningen, og Lossless-versus-LossyLevel-avveiningene hører allerede hjemme i følgeartikkelen om nativ JBIG2-binærnivå-komprimering i Delphi, som denne artikkelen forutsetter at du har lest

Hvorfor gjentar per-side JBIG2-komprimering fortsatt den samme kostnaden?

Svaret er at ingenting bærer tilstand mellom kall. Hver gang HotPDFs koder bygger en symbolordbok for ett bilde, er den ordboken avgrenset til det ene AddImage-kallet: form-matchings-passeringen starter fra null, hver glyf på siden klassifiseres som ny, og de resulterende bitkartene aritmetisk-kodes og lagres på nytt. Mat den samme koderen med femti sider satt i samme skrifttype, og den gjentar villig hele den treningspasseringen femti ganger, fordi fra dens synspunkt er hver side et urelatert bilde som tilfeldigvis ser likt ut. Per-side UseSymbolDictionary slår allerede en flat generisk-region-koding med god margin på én enkelt side, men den flater ut godt før taket et ekte flersides-skann etterlater på bordet

Hvordan deler HotPDF én enkelt symbolordbok på tvers av sider?

Aktiver AccumulateGlobalsAcrossPagesTHPDFJBIG2Options, og HotPDF holder én symbolordbok i live i minnet gjennom hele dokumentets levetid i stedet for å forkaste den etter hvert bilde. Hver etterfølgende sides glyfer sjekkes mot den løpende ordboken før noe blir re-kodet: en form som allerede finnes, gjenbrukes ved sin symbol-ID, og bare en form ingen har sett før, blir lagt til og kodet inn i ordboken. Sammenligningen gjenbruker den samme toleranselogikken LossyLevel anvender på én enkelt side — en litt støyete skanning av den samme bokstaven teller fortsatt som en match — slik at akkumulatoren ikke stille sveller til én ordbokoppføring per piksel-nivå-variasjon av den samme glyfen. Uttrekking skjer først og mater den sammenligningen: HotPDF går gjennom hver sides bitkart og trekker ut sammenhengende former gjennom flood fill mot de svarte pikslene, den samme idéen som å spore blekkflekker for hånd, og det er de uttrukkede formene, ikke rå pikselblokker, som sammenlignes mot den løpende ordboken

Hvordan den delte ordboken sitter inne i en /JBIG2Globals-strøm

Den akkumulerte ordboken skrives som ett symbolordbok-segment inne i /JBIG2Globals-strømmen, holdt ved et fast segmentnummer slik at hver side kan peke på det samme målet. Inne i den innebygde JBIG2-organiseringen ISO 32000-1 §7.4.7 definerer, kan et tekstregion-segment navngi et annet segment som sin symbolkilde gjennom referert-til-segment-feltet i segmentheaderen, og det er den nøyaktige mekanismen HotPDF lener seg på: globals-strømmen bærer den ene store symbolordboken, og hver sides egen JBIG2-strøm krymper ned til et side-info-segment pluss et tekstregion-segment hvis referert-til-liste peker tilbake på globals-segmentet. Det som pleide å være en selvstendig bitstrøm per side, blir en kort liste med posisjoner og symbol-ID-er, og hver side bygget på denne måten refererer til det identiske indirekte /JBIG2Globals-objektet i stedet for en kopi av det. HotPDFs egen regresjonsdekning sjekker nøyaktig det: kod et kort dokument der hver side har et forskjellig glyf-layout, last det inn på nytt, og tell hvor mange distinkte /JBIG2Globals-objektreferanser som dukker opp i filen — ett dokument, én objektreferanse, uansett hvor mange sider som bidro med symboler til den

Å skru på akkumulering av symbolordbok på tvers av sider

Bryteren sitter på den samme opsjons-recorden dekket i følgeartikkelen, og den krever at fire innstillinger er enige med hverandre før akkumulering faktisk trer i kraft

var
  Pdf: THotPDF;
  Bmp: TBitmap;
  PageIdx, ImgIdx: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.JBIG2Options.Lossless := True;
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.UseGlobalSegments := True;
    Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := True;  // opt-in, default False
    Pdf.JBIG2Options.UseExternalEncoder := False;            // accumulation needs the native path
    Pdf.JBIG2Options.UseNativeArithmeticFallback := True;
    Pdf.BeginDoc;
    for PageIdx := 0 to ScannedPages.Count - 1 do
    begin
      if PageIdx > 0 then
        Pdf.AddPage;
      Bmp := ScannedPages[PageIdx];             // 1-bit TBitmap for this page
      ImgIdx := Pdf.AddImage(Bmp, icJBIG2);
      Pdf.CurrentPage.ShowImage(ImgIdx, 0, 0, Bmp.Width, Bmp.Height, 0);
    end;
    Pdf.EndDoc;                                  // the shared /JBIG2Globals stream is finalized here
  finally
    Pdf.Free;
  end;
end;

Den koblingen er ikke valgfri pynt. Den eksterne koder-sømmen beskrevet i binærnivå-komprimeringsartikkelen — den man registrerer gjennom RegisterJBIG2EncoderBackend for produksjonsklasse-komprimeringsforhold — er bygget rundt per-bilde-koding, og HotPDFs egne akkumulerings-demoer og regresjonstester parer alltid AccumulateGlobalsAcrossPages med UseExternalEncoder := False. Behandle det som et hardt krav snarere enn et forslag: deling på tvers av sider er en nativ-koder-funksjon, og en registrert ekstern backend er rett og slett ikke en del av veien som bygger den delte ordboken

Hvor mye mindre blir et flersides-skann faktisk?

Det ærlige svaret starter med hva som ikke flyttet nålen først. En tidligere utgivelse la til en innholdsadressert buffer for /JBIG2Globals-strømmer — et oppslag nøkkelsatt på en 64-bit FNV-1a-hash av strømbytene, slik at to bilder som tilfeldigvis produserte byte-identiske globals-data, kunne dele ett PDF-objekt. Målt mot ekte utdata hjalp den bufferen knapt, fordi HotPDFs eksisterende hele-bilde-duplikatdeteksjon allerede kollapset byte-identiske bilder før bufferen noensinne fikk sjansen til å kjøre. Lærdommen var at strøm-nivå-deduplisering bare lønner seg når to genuint forskjellige sidebilder likevel kan dele én voksende ordbok, noe ekte akkumulering på tvers av sider leverer

For det vanskeligere tilfellet setter HotPDFs egen ingeniør-anslag den ekstra besparelsen til omtrent 30 til 60 prosent mindre enn strøm-nivå-deduplisering alene oppnår, for et typisk flersides-skann bygget fra én gjentakende skrifttype — området beveger seg med hvor mye av dokumentets visuelle ordforråd som faktisk gjentar seg, ettersom en side full av unike diagrammer ikke gir ordboken noe å gjenbruke. Behandle det som et designmål snarere enn en garanti for noe spesifikt inndata, og mål dine egne dokumenter i stedet for å stole på ett enkelt tall. JBIG2Benchmark-demoen som følger med HotPDF, finnes nettopp for det formålet: den koder det samme flersides-skannet på fire forskjellige måter og skriver ut den resulterende filstørrelsen for hver konfigurasjon, slik at sammenligningen kjører mot din egen skann-miks i stedet for en syntetisk en

procedure RunScenario(const Title: string; AccumulateGlobals: Boolean);
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.JBIG2Options.Lossless := True;
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.UseGlobalSegments := True;
    Pdf.JBIG2Options.AccumulateGlobalsAcrossPages := AccumulateGlobals;
    Pdf.JBIG2Options.UseExternalEncoder := not AccumulateGlobals;
    // ... encode the same three-page scan here, then compare file sizes.
  finally
    Pdf.Free;
  end;
end;

begin
  RunScenario('Per-image lossless baseline', False);
  RunScenario('Cross-page accumulated globals', True);
end.

Hvor akkumulering på tvers av sider når sine grenser

Den akkumulerte ordboken er begrenset til 4096 symboler, det samme taket den per-bilde-native koderen allerede håndhever på én enkelt side. Krysser man den grensen midt i dokumentet, kaster ikke HotPDF et unntak eller avbryter kjøringen: akkumulatoren avviser den nye glyfen, og siden som introduserte den, faller automatisk tilbake til uavhengig per-bilde-koding, slik at dokumentet fortsatt kommer ut korrekt — man slutter bare å få besparelsen på tvers av sider for hvilke som helst sider som presset forbi taket. En annen sikringsmekanisme følger med på total størrelse i stedet for symbolantall: så snart den akkumulerte ordbokens kombinerte symbolbredde krysser 131071 piksler, søler HotPDF automatisk den gjeldende batchen til disk og starter en ny globals-gruppe, i stedet for å la én struktur i minnet vokse uten grense. Ingen av grensene trenger noen kode på din side, ettersom begge er automatiske reserveløsninger snarere enn unntak man må fange

PDF/A-konformitet er den ene innstillingen som skrur av hele mekanismen i stedet for bare å begrense den. HotPDF erstatter stille CCITT Group 4 for JBIG2 i det øyeblikket PDFACompliance er ikke-tom, på hver eneste side, uavhengig av AccumulateGlobalsAcrossPages eller noe annet på JBIG2Options — et bevisst konformitetsvalg, ikke en bug, men det betyr at en arkivprofil og deling av symboler på tvers av sider er gjensidig utelukkende i dag. Uansett hvilken konfigurasjon man lander på, avkod det man skrev før man stoler på det: last filen inn igjen med LoadFromFile og trekk hver side gjennom ExtractLoadedImage, som løser den delte globals-en for deg på samme måte som enhver konform leser ville gjort, og sammenlign resultatet mot kildebitkartene

var
  Loaded: THotPDF;
  PageBmp: TBitmap;
  PageIdx: Integer;
begin
  Loaded := THotPDF.Create(nil);
  try
    Loaded.LoadFromFile('scanned-contract.pdf');
    for PageIdx := 0 to Loaded.PagesCount - 1 do
    begin
      PageBmp := Loaded.ExtractLoadedImage(PageIdx);   // resolves the shared globals for you
      try
        // Compare PageBmp against the source bitmap for this page.
      finally
        PageBmp.Free;
      end;
    end;
  finally
    Loaded.Free;
  end;
end;

Deling av ordbok på tvers av sider rører bare den binærnivå-bilde-siden av et dokument. Hvis den samme pipelinen også genererer tekstsider ved siden av skannene — omslagsark, indekssider, et OCR-tekstlag — angriper objektstrømmer og xref-strømmer den andre halvparten av filstørrelsesbudsjettet ved å komprimere dokumentstrukturen de sidene legger til. JBIG2-globals på tvers av sider følger med som en del av HotPDF-komponenten for Delphi og C++Builder, sammen med per-bilde JBIG2-opsjonene og resten av komprimeringspipelinen