Teknisk artikkel

HotPDF CompressDocument: kompakte font-subsetter i Delphi

HotPDF THotPDF.CompressDocument er én enkelt bryter som får BeginDoc til å produsere den minste tapsfrie PDF-en komponenten kan skrive: FlateDecode på maksimalt nivå, en cross-reference stream med object streams, font-subsetting og kompakte font-subsetter som omnummererer de beholdte glyfene bak et eksplisitt /CIDToGIDMap. EndDoc legger så dine egne innstillinger tilbake. Et testdokument på tre sider med Arial og SimSun falt fra 10.2 MB til 20 KB med identisk rendering

Hva slår CompressDocument egentlig på?

CompressDocument overstyrer seks skriverinnstillinger, pluss object stream-grenen, for ett dokument og gjenoppretter alle etterpå. Ved BeginDoc, før PDF-versjonen faller på plass, registrerer HotPDF verdiene dine og setter Compression til cmFlateDecode, CompressionLevel til clMaximum, slår på EnableFontSubsetting og CompactFontSubsetting og aktiverer UseXRefStream pluss UseObjectStreams (ISO 32000-1 §7.5.7 og §7.5.8). Object streams krever PDF 1.5, så en eldre Version heves til 1.5 når den ikke er låst. PDF/A-1 forbyr begge strukturene, så et PDF/A-1-dokument beholder sin klassiske cross-reference-tabell og får bare Flate- og fontarbeidet. Bilder blir liggende nøyaktig slik du la dem inn

Diagram over HotPDF CompressDocument-livssyklusen i Delphi: BeginDoc registrerer skriverens egne verdier, overstyrer seks innstillinger inkludert Compression og UseObjectStreams for ett dokument, og EndDoc gjenoppretter hver lånte verdi i sin ytterste finally mens CompressDocument-egenskapen selv forblir True
Seks skriverinnstillinger og object stream-grenen lånes til nøyaktig ett dokument og leveres tilbake når EndDoc kjører, så en feilet rapport etterlater aldri komponenten låst på maksimal kompresjon
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'invoice-2026-1042.pdf';
    Pdf.CompressDocument := True;    // anvendt av BeginDoc, omgjort av EndDoc
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Gjenopprettingen skjer i den ytterste finally-en i EndDoc, så et unntak halvveis i en rapport etterlater ikke en langlevd komponent låst på maksimal kompresjon for neste jobb. CompressDocument-egenskapen selv forblir True; bare de seks innstillingene den lånte, går tilbake. Versjonen håndteres med mer omtanke. HotPDF omgjør sin egen heving til 1.5 bare hvis dokumentet fortsatt ender på 1.5, så når en annen funksjon dyttet filen til 1.6 under kjøringen (et innebygd OpenType-skriftsnitt, si), blir den høyere versjonen værende, nøyaktig som den ville gjort uten kompresjon

Hvorfor er font-subsetter fortsatt store uten kompakting?

En klassisk TrueType-subset slipper omrissene du aldri tegner, men beholder hver glyf-ID der den var, og den nummereringen er det som holder den tung. Content stream-en viser CID-er som er lik de opprinnelige GID-ene, så subsettet må beholde en loca-offset og en hmtx-oppføring for hver plass opp til den høyeste glyfen den beholder, tom eller ikke. For en latinsk font er den overheaden støy. For et CJK-skriftsnitt som SimSun, hvis ideografer ligger dypt i en svært stor glyftabell, slepper to kinesiske tegn med seg tabeller dimensjonert for hele fonten. Font-subset-lukkereglene for shapede glyfer bestemmer hvilke glyfer som overlever; kompakting handler om hvor mye de overlevende koster

CompactFontSubsetting omnummererer de beholdte glyfene til et tett område som starter på null og skriver en /CIDToGIDMap-stream på CIDFont-en, som ISO 32000-1 §9.7.4.2 definerer som en tabell av tobynes GID-er indeksert etter CID. Den tabellen er hele trikset. Content streams, /W-bredde-arrayen og ToUnicode CMap beholder alle de opprinnelige CID-ene, så ingenting som allerede er skrevet, trenger å endres; bare oppslaget fra CID til glyf flytter inn i kartet. I testen som motiverte funksjonen, gikk SimSun med to tegn fra 24.8 KB fontdata til 3.1 KB

Sammenligning av en spredt HotPDF font-subset, som beholder loca- og hmtx-oppføringer for hver opprinnelig glyf-ID opp til den høyeste beholdte GID-en, mot CompactFontSubsetting-output som omnummererer beholdte glyfer tett fra null og mapper CID-er gjennom en CIDToGIDMap-stream mens content streams, /W og ToUnicode forblir uendret
Omnummerering flytter kostnaden ut av fontprogrammet og inn i én liten kart-stream — to SimSun-tegn falt fra 24.8 KB til 3.1 KB uten å røre en eneste byte av allerede skrevet innhold

Kompakting har harde grenser, og den degraderer stille i stedet for å feile. HotPDF bygger kompakte subsetter bare for Type 0 TrueType-skriftsnitt, både de satt gjennom SetFont med subsetting på og snittet registrert gjennom RegisterUnicodeTTF. Et enkelt TrueType-font finner glyfene sine gjennom cmap-en inne i fontprogrammet, noe omnummerering ville knekke, så det beholder den spredte subsetten. OpenType-CFF-snitt har heller ingen kompakt sti. En kompakt bygg som feiler, faller tilbake til den spredte subsetten i stedet for å reise. Egenskapen er av som standard, så eksisterende output forblir byteidentisk, mens under PDF/A får det registrerte Unicode-snittet alltid en kompakt subset

Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True;   // kan brukes uten CompressDocument
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;

Hvordan klemmer den pakkede skriveren filstrukturen?

Når fonter og streams er små, blir ordbøkene og cross-reference-dataene den største gjenværende kostnaden, så object stream-skriveren bak CompressDocument trimmer også de. Guiden om object streams og inkrementelle oppdateringer dekker containerformatet selv; kompresjonsstien legger fire finpuss på toppen:

  • Kompakt syntaks per ISO 32000-1 §7.2.2: et mellomrom skrives bare mellom to token som ellers ville løpt sammen som vanlige tegn, så /Type /Page blir /Type/Page
  • Cross-reference stream-felter tar enhver bredde §7.5.8.2 tillater, så en fil under 16 MB lagrer hver offset i 3 byte i stedet for 4
  • Opptil 250 objekter går i hvert object stream i stedet for de vanlige 100, med mindre du setter din egen grense gjennom ConfigureAdaptiveObjectStreamPacking
  • Når filen ikke er kryptert, pakkes Catalog-en og Info-ordboken inn i object streams også; kryptert output beholder dem på toppnivå

Den kompakte syntaksen kom med en felle verdt å kjenne hvis du utvider skriveren. Signering fyller inn signaturen etter at filen er skrevet, ved å søke i bytene etter de bokstavelige plassholderne /ByteRange ( og /Contents <, og kompakt stavemåte ville gjøre dem til /ByteRange( og /Contents<, som søket aldri finner. Signaturordbøker (Type Sig eller DocTimeStamp, FT Sig) og krypteringsordboken beholder derfor det mellomromsatte oppsettet. En relatert defekt rammet bygg før v2.766.41: hver object stream-lagring, CompressDocument inkludert, begynte med to %PDF--headerlinjer, så oppgrader hvis en streng validator flagger outputen din

Kan du komprimere en PDF som allerede er lastet?

Ja, gjennom options-overloaden CompressLoadedDocument(Options, Info), som kjører de samme tapsfrie trinnene på en eksisterende fil. Med THPDFLoadedDocumentCompressionOptions.Default fjerner den ubrukte sideressurser, fletter identiske fonter og skjemaer, subsetter innbakte fonter med kompakte subsetter på, rekomprimerer ufiltrerte, Flate-, LZW-, ASCII- og RunLength-streams med Flate når resultatet er mindre, og får neste lagring til å bruke object streams. HighRatioFlate er av som standard, og object streams hoppes over for PDF/A-1 og inkrementelle lagringer. Den parameterløse CompressLoadedDocument-overloaden er det eldre, smalere kallet som bare Flate-komprimerer ukomprimerte streams

Flyt av HotPDF CompressLoadedDocument i Delphi: kallet fjerner ubrukte sideressurser, fletter identiske fonter og skjemaer, subsetter innbakte fonter med kompakte subsetter, rekomprimerer streams med Flate bare når resultatet er mindre, og slår object streams på for neste lagring, mens signaturfelt utløser RefusedBySignaturePolicy og lar filen være urørt
Hvert trinn omskriver byte en signatur dekker, så hele dokumentet nektes med mindre du eksplisitt tillater invalidering — Info.BytesSaved summerer da bare ressurs-, font- og stream-arbeidet
var
  Doc: THotPDF;
  Options: THPDFLoadedDocumentCompressionOptions;
  Info: THPDFLoadedDocumentCompressionInfo;
begin
  Doc := THotPDF.Create(nil);
  try
    Doc.AutoLaunch := False;
    Doc.LoadFromFile('quarterly-report.pdf');
    Options := THPDFLoadedDocumentCompressionOptions.Default;
    Doc.CompressLoadedDocument(Options, Info);
    if Info.RefusedBySignaturePolicy then
      Writeln(Format('Left untouched: %d signature fields', [Info.SignatureCount]))
    else
    begin
      Writeln(Format('Compact fonts: %d, stream bytes saved: %d',
        [Info.Fonts.CompactSubsetFontCount, Info.BytesSaved]));
      Doc.SaveLoadedDocument('quarterly-report-compact.pdf');
    end;
  finally
    Doc.Free;
  end;
end;

To grenser betyr noe på den lastede stien. Hvert trinn omskriver byte en signatur dekker, så et dokument med signaturfelt nektes som helhet: kallet returnerer 0, setter RefusedBySignaturePolicy og endrer ingenting, med mindre du setter AllowSignatureInvalidation, hvoretter Info.SignaturesInvalidated forteller deg hva du ga slipp på. Kompaktingen er også mer konservativ her enn på skapelsesstien. HotPDF kompakter bare fontprogrammer utelukkende brukt av CIDFontType2-fonter med en Identity-/CIDToGIDMap, der CID er lik GID, og hopper over programmer med en eksisterende kart-stream, en /CIDSet eller fargeglyftabeller som COLR, sbix, CBDT eller SVG, fordi den kompakte gjenoppbyggingen ville slippe fargelagene. Merk også at Info.BytesSaved summerer bare ressurs-, font- og stream-trinnene; object stream-gevinsten viser seg når filen skrives

Hvilke resultater bør du forvente i praksis?

Gevinstene følger hvor mye av en fil som er ukomprimert struktur og overdimensjonert fontdata, ikke hvor mange sider den har. Tre-siders Arial- og SimSun-eksempelet krympet fra 10.2 MB til 20 KB da det ble generert med CompressDocument, og fra 10.2 MB til 19.8 KB da det ukomprimerte originalen ble lastet og kjørt gjennom CompressLoadedDocument, med identisk rendering begge veier. En PDF som allerede er kompakt, rører seg knapt: i regresjonssettet ble slike filer lagret innenfor -0.07 % til +0.06 % av original størrelse. Fototunge filer gagner lite, for ingen av stiene rører bildedata

Hvis du genererer de samme CJK-rapportene hver natt, kombiner kompakte subsetter med den vedvarende font-subset-cachen på disk slik at subsettarbeidet ikke gjentas per kjøring, og diff komprimerte outputer etter objektinnhold i stedet for byte, siden ett endret felt re-Flater en hel object stream. Full referanse for egenskaper og records ligger på HotPDF Delphi PDF-komponentens produktside