En halvtreds-siders scannet kontrakt gentager det samme alfabet på hver side, men en JBIG2-encoder, der bygger én symbolordbog pr. billede, gentræner det alfabet halvtreds separate gange. HotPDF, den native Delphi- og C++Builder-PDF-komponent, kan i stedet akkumulere én delt symbolordbog på tværs af hele dokumentet og forfremme den til én dokument-niveau /JBIG2Globals-stream, så hver sides egen JBIG2-stream blot refererer symbol-ID'er i stedet for at gemme sin egen kopi af alfabetet
Dette stykke holder sig bevidst snævert og dækker kun, hvordan HotPDF bygger den side-på-tværs-deling internt — JBIG2-fundamentals, CCITT-sammenligningen og Lossless-versus-LossyLevel-afvejningerne findes allerede i følgeartiklen om native JBIG2-bilevel-komprimering i Delphi, som dette stykke antager, du har læst
Hvorfor gentager per-side-JBIG2-komprimering stadig den samme omkostning?
Svaret er, at intet bærer tilstand mellem kald. Hver gang HotPDFs encoder bygger en symbolordbog til ét billede, er den ordbog afgrænset til det ene AddImage-kald: form-matching-passet starter fra nul, hver glyf på siden klassificeres som ny, og de resulterende bitmaps arithmetic-kodes og gemmes friskt. Fodrer man den samme encoder med halvtreds sider sat i den samme skrifttype, gentager den gladeligt hele det trænings-pas halvtreds gange, fordi fra dens synspunkt er hver side et urelateret billede, der tilfældigvis ligner. Per-side UseSymbolDictionary slår allerede en flad generisk-region-kodning med en bred margin på én enkelt side, men den topper langt før det loft, en rigtig flersidet scanning efterlader på bordet
Hvordan deler HotPDF én symbolordbog på tværs af sider?
Aktivér AccumulateGlobalsAcrossPages på THPDFJBIG2Options, og HotPDF holder én symbolordbog i live i hukommelsen for hele dokumentets levetid i stedet for at kassere den efter hvert billede. Hver efterfølgende sides glyffer tjekkes mod den kørende ordbog, før noget genkodes: en form, der allerede findes, genbruges via sit symbol-ID, og kun en form ingen har set før, tilføjes og kodes ind i ordbogen. Sammenligningen genbruger den samme tolerance-logik, LossyLevel anvender på en enkelt side — en lidt støjfyldt scanning af det samme bogstav tæller stadig som et match — så akkumulatoren balloneres ikke i stilhed til én ordbogspost pr. pixel-niveau-variation af den samme glyf. Udtrækning sker først og fodrer den sammenligning: HotPDF gennemgår hver sides bitmap og trækker forbundne former ud gennem flood fill mod de sorte pixels, den samme idé som at spore blækklatter i hånden, og det er de udtrukne former, ikke rå pixelblokke, der sammenlignes med den kørende ordbog
Hvordan den delte ordbog sidder inden i en /JBIG2Globals-stream
Den akkumulerede ordbog skrives som ét symbolordbog-segment inde i /JBIG2Globals-strømmen, holdt ved et fast segmentnummer, så hver side kan pege på det samme mål. Inden i den indlejrede JBIG2-organisation, ISO 32000-1 §7.4.7 definerer, kan et tekst-region-segment navngive et andet segment som sin symbolkilde gennem referred-to-segment-feltet i segment-headeren, og det er den præcise mekanisme, HotPDF læner sig på: globals-strømmen bærer den ene store symbolordbog, og hver sides egen JBIG2-stream krymper ned til et side-info-segment plus et tekst-region-segment, hvis referred-to-liste peger tilbage på globals-segmentet. Hvad der plejede at være en selvstændig bitstream pr. side, bliver en kort liste af positioner og symbol-ID'er, og hver side bygget på denne måde refererer det identiske indirekte /JBIG2Globals-objekt frem for en kopi af det. HotPDFs egen regressionsdækning tjekker præcis det: kod et kort dokument, hvor hver side har et forskelligt glyf-layout, genindlæs det, og tæl hvor mange distinkte /JBIG2Globals-objektreferencer der dukker op i filen — ét dokument, én objektreference, uanset hvor mange sider der bidrog symboler til den
At slå akkumulering af symbolordbog på tværs af sider til
Kontakten sidder på den samme options-record dækket i følgeartiklen, og den kræver fire indstillinger, der er enige med hinanden, før akkumulering rent faktisk kobler ind
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 kombination er ikke valgfri pynt. Den eksterne encoder-grænseflade beskrevet i bilevel-komprimeringsartiklen — den man registrerer gennem RegisterJBIG2EncoderBackend til produktionsklasse-forhold — er bygget omkring per-billede-kodning, og HotPDFs egne akkumuleringsdemoer og regressionstests parrer altid AccumulateGlobalsAcrossPages med UseExternalEncoder := False. Behandl det som et hårdt krav frem for et forslag: side-på-tværs-deling er en native-encoder-funktion, og en registreret ekstern backend er simpelthen ikke en del af den vej, der bygger den delte ordbog
Hvor meget mindre bliver en flersidet scanning rent faktisk?
Det ærlige svar begynder med, hvad der ikke flyttede nålen først. En tidligere udgivelse tilføjede en indholds-adresseret cache til /JBIG2Globals-streams — et opslag nøglet af en 64-bit FNV-1a-hash af stream-bytene, så to billeder, der tilfældigvis producerede byte-identiske globals-data, kunne dele ét PDF-objekt. Målt mod rigtigt output hjalp den cache knap nok, fordi HotPDFs eksisterende hele-billede-dublet-detektion allerede kollapsede byte-identiske billeder, før cachen nogensinde fik chancen for at køre. Lektien var, at stream-niveau-deduplikering kun betaler sig, når to genuint forskellige side-billeder stadig kan dele én voksende ordbog, hvilket er, hvad ægte side-på-tværs-akkumulering leverer
For det sværere tilfælde sætter HotPDFs eget tekniske skøn den ekstra besparelse til omtrent 30 til 60 procent mindre, end stream-niveau-deduplikering alene opnår, for en typisk flersidet scanning bygget fra én gentagende skrifttype — intervallet bevæger sig med, hvor meget af dokumentets visuelle vokabular der rent faktisk gentager sig, da en side fuld af unikke diagrammer ikke giver ordbogen noget at genbruge. Behandl det som et designmål frem for en garanti for et specifikt input, og mål dine egne dokumenter frem for at stole på ét tal. JBIG2Benchmark-demoen, der følger med HotPDF, findes netop til det formål: den koder den samme flersidede scanning på fire forskellige måder og udskriver den resulterende filstørrelse for hver konfiguration, så sammenligningen kører mod din egen scan-mix 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 side-på-tværs-akkumulering rammer sine grænser
Den akkumulerede ordbog er begrænset til 4096 symboler, det samme loft den per-billede native encoder allerede håndhæver på en enkelt side. Krydser man den grænse midt i dokumentet, kaster HotPDF ikke en undtagelse eller afbryder kørslen: akkumulatoren afviser den nye glyf, og den side, der introducerede den, falder automatisk tilbage til uafhængig per-billede-kodning, så dokumentet stadig kommer korrekt ud — man mister bare side-på-tværs-besparelsen for de sider, der skubbede forbi loftet. En anden sikring holder øje med den totale størrelse frem for symbolantal: så snart den akkumulerede ordbogs kombinerede symbolbredde krydser 131071 pixels, spilder HotPDF den nuværende batch til disk og starter en frisk globals-gruppe automatisk, i stedet for at lade én struktur i hukommelsen vokse uden grænse. Ingen af grænserne kræver nogen kode på din side, da begge er automatiske fallbacks frem for undtagelser, man skal fange
PDF/A-konformitet er den ene indstilling, der slår hele mekanismen fra frem for blot at begrænse den. HotPDF erstatter i stilhed CCITT Group 4 med JBIG2, i det øjeblik PDFACompliance er ikke-tom, på hver side, uafhængigt af AccumulateGlobalsAcrossPages eller noget andet på JBIG2Options — et bevidst konformitetsvalg, ikke en fejl, men det betyder, at en arkiv-profil og side-på-tværs-symbol-deling gensidigt udelukker hinanden i dag. Uanset hvilken konfiguration man lander på, dekod hvad man skrev, før man stoler på det: indlæs filen tilbage med LoadFromFile og træk hver side gennem ExtractLoadedImage, som løser de delte globals for dig på samme måde som enhver konform læser ville, og sammenlign resultatet med dine kilde-bitmaps
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;
Side-på-tværs-ordbogsdeling rører kun bilevel-billedsiden af et dokument. Hvis den samme pipeline også udsender genererede tekstsider ved siden af scanningerne — forsider, indeks-sider, et OCR-tekstlag — angriber objektstrømme og xref-strømme den anden halvdel af filstørrelsesbudgettet ved at komprimere den dokumentstruktur, de sider tilføjer. Side-på-tværs JBIG2-globals leveres som en del af HotPDF-komponenten til Delphi og C++Builder, sammen med per-billede JBIG2-indstillingerne og resten af komprimeringspipelinen