Tehnični članak

HotPDF CompressDocument: kompaktni podnabori pisav

HotPDF THotPDF.CompressDocument je eno samo stikalo, ki BeginDoc prepriča, da ustvari najmanjši brezizgubni PDF, ki ga komponenta zna zapisati: FlateDecode na najvišji ravni, tok navzkrižnih referenc s tokovi objektov, podnaborjenje pisav in kompaktne podnabore pisav, ki ohranjene glife znova oštevilčijo za eksplicitnim /CIDToGIDMap. EndDoc nato vrne vaše lastne nastavitve. Tristranska testna datoteka z Arial in SimSun je s 10.2 MB padla na 20 KB z identičnim izrisom

Kaj CompressDocument sploh vklopi?

CompressDocument za en dokument preglaši šest nastavitev pisalca, plus zgornjo mejo tokov objektov, in jih potem vse povrne. Ob BeginDoc, preden se različica PDF ustali, HotPDF zabeleži vaše vrednosti in nastavi Compression na cmFlateDecode, CompressionLevel na clMaximum, vklopi EnableFontSubsetting in CompactFontSubsetting ter omogoči UseXRefStream in UseObjectStreams (ISO 32000-1 §7.5.7 in §7.5.8). Tokovi objektov potrebujejo PDF 1.5, zato se starejša Version dvigne na 1.5, kadar ni zaklenjena. PDF/A-1 obe strukturi prepove, zato dokument PDF/A-1 obdrži klasično tabelo navzkrižnih referenc in dobi samo delo Flate in pisav. Slike ostanejo točno take, kot ste jih vdelali

Diagram življenjskega cikla HotPDF CompressDocument v Delphiju: BeginDoc zabeleži lastne vrednosti pisalca, za en dokument preglaši šest nastavitev, vključno s Compression in UseObjectStreams, EndDoc pa povrne vsako izposojeno vrednost v svojem najbolj zunanjem finally, medtem ko lastnost CompressDocument sama ostane True
Šest nastavitev pisalca in zgornja meja tokov objektov sta izposojeni za točno en dokument in vrnjeni, ko teče EndDoc, zato odpovedano poročilo komponente nikoli ne zapusti obtičane na maksimalnem stiskanju
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.AutoLaunch := False;
    Pdf.FileName := 'invoice-2026-1042.pdf';
    Pdf.CompressDocument := True;    // uveljavi BeginDoc, razveljavi EndDoc
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Povrnitev se zgodi v najbolj zunanjem finally od EndDoc, zato izjema na polovici poročila ne pusti dolgožive komponente obtičane na maksimalnem stiskanju za naslednje opravilo. Lastnost CompressDocument sama ostane True; vrnejo se samo šest nastavitev, ki jih je izposodila. Z različico se ravna skrbneje. HotPDF razveljavi lastno dviganje na 1.5 le, če dokument še vedno konča na 1.5, ko pa druga zmožnost potisne datoteko na 1.6 med tekom (recimo vdelana pisava OpenType), višja različica ostane, točno tako kot bi ostala brez stiskanja

Zakaj so podnabori pisav brez kompakcije še vedno veliki?

Klasičen podnabor TrueType odvrže orise, ki jih nikoli ne narišete, vsak ID glife pa pusti, kjer je bil, in prav to oštevilčenje ga drži težkega. Tok vsebine prikaže CID, ki so enaki izvirnim GID, zato mora podnabor obdržati odmik loca in vnos hmtx za vsako režo do najvišje ohranjene glife, prazna ali ne. Za latinično pisavo je ta preglavica šum. Za pisavo CJK, kot je SimSun, katere ideogrami sedijo globoko v zelo veliki tabeli glifov, pa dva kitajska znaka vlečeta za sabo tabele, dimenzionirane za celo pisavo. Pravila zaprtja podnabora pisav za oblikovane glife odločajo, kateri glifi preživijo; kompakcija gre za to, koliko stanejo preživelci

CompactFontSubsetting ohranjene glife znova oštevilči v gosto območje, ki se začne z ničlo, in zapiše tok /CIDToGIDMap na CIDFont, ki ga ISO 32000-1 §9.7.4.2 definira kot tabelo dvo-bajtnih GID, indeksirano po CID. Ta tabela je cel trik. Tokovi vsebine, polje širin /W in CMap ToUnicode vsi obdržijo izvirne CID, zato nič od že zapisanega ne mora spremeniti; v mapo se preseli samo iskanje od CID do glife. V preizkusu, ki je motiviral zmožnost, je SimSun z dvema znakoma šel od 24.8 KB podatkov pisave na 3.1 KB

Primerjava redkega podnabora pisav HotPDF, ki obdrži vnose loca in hmtx za vsak izvirni ID glife do najvišjega ohranjenega GID, z izhodom CompactFontSubsetting, ki ohranjene glife gost oštevilči od nič in preslika CID skozi tok CIDToGIDMap, medtem ko tokovi vsebine, /W in ToUnicode ostanejo nespremenjeni
Znova oštevilčenje preseli strošek iz programa pisave v en majhen tok mape — dva znaka SimSun sta padla z 24.8 KB na 3.1 KB, ne da bi se dotaknila enega bajta že zapisane vsebine

Kompakcija ima trde meje in tiho degradira, namesto da bi odpovedala. HotPDF gradi kompaktne podnabore samo za pisave Type 0 TrueType, tako tiste, nastavljene skozi SetFont z vklopljenim podnaborjenjem, kot pisavo, registrirano skozi RegisterUnicodeTTF. Preprosta pisava TrueType najde svoje glife skozi cmap znotraj programa pisave, kar bi znova oštevilčenje polomilo, zato obdrži redek podnabor. Tudi pisave OpenType-CFF nimajo kompaktne poti. Kompaktna izgradnja, ki odpove, pade nazaj na redek podnabor, namesto da bi sprožila izjemo. Lastnost je privzeto izklopljena, zato obstoječi izhod ostane bajtno enak, pod PDF/A pa registrirana pisava Unicode vedno dobi kompakten podnabor

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

Kako zapakirani pisalec stisne strukturo datoteke?

Ko so pisave in tokovi enkrat majhni, slovarji in podatki navzkrižnih referenc postanejo največji preostali strošek, zato jih izreže tudi pisalec tokov objektov za CompressDocument. Vodnik po tokovih objektov in priraščajočih posodobitvah pokrije sam format vsebnika; pot stiskanja doda štiri izboljšave nanj:

  • Kompaktna skladnja po ISO 32000-1 §7.2.2: presledek se zapiše le med dvema žetonoma, ki bi sicer stekla skupaj kot navadna znaka, zato /Type /Page postane /Type/Page
  • Polja toka navzkrižnih referenc vzamejo katero koli širino, ki jo §7.5.8.2 dovoli, zato datoteka pod 16 MB shrani vsak odmik v 3 bajte namesto v 4
  • Do 250 objektov gre v vsak tok objektov namesto običajnih 100, razen če nastavite lastno mejo skozi ConfigureAdaptiveObjectStreamPacking
  • Ko datoteka ni šifrirana, se Catalog in slovar Info zapakirata v tokove objektov; šifriran izhod ju pusti na vrhnji ravni

Kompaktna skladnja je prišla s pastjo, vredno vedenja, če pisalec razširjate. Podpisovanje dopolni podpis, potem ko je datoteka zapisana, z iskanjem bajtov za dobesednimi ogradejkami /ByteRange ( in /Contents <, kompaktna črkovanja pa bi ta spremenila v /ByteRange( in /Contents<, kjer iskanje nikoli ne najde ničesar. Slovarji podpisov (Type Sig ali DocTimeStamp, FT Sig) in šifrirni slovar zato obdržijo razmaknjeno postavitev. Sorodna napaka je prizadela verzije pred v2.766.41: vsako shranjevanje s tokovi objektov, CompressDocument vključno, se je začelo z dvema vrsticama glave %PDF-, zato nadgradite, če strog validator označi vaš izhod

Ali lahko stisnete PDF, ki je že naložen?

Da, skozi preobremenitev z možnostmi CompressLoadedDocument(Options, Info), ki na obstoječi datoteki požene iste brezizgubne korake. Z THPDFLoadedDocumentCompressionOptions.Default odstrani neuporabljene vire strani, zlije identične pisave in obrazce, podnabori vdelane pisave z vklopljenimi kompaktnimi podnabori, znova stisne nestisnjene tokove, Flate, LZW, ASCII in RunLength s Flate, kadar je rezultat manjši, in pripravi naslednje shranjevanje na tokove objektov. HighRatioFlate je privzeto izklopljen, tokovi objektov pa se preskočijo za PDF/A-1 in priraščajoča shranjevanja. Preobremenitev CompressLoadedDocument brez parametrov je starejši, ožji klic, ki le s Flate stisne nestisnjene tokove

Potek HotPDF CompressLoadedDocument v Delphiju: klic odstrani neuporabljene vire strani, zlije identične pisave in obrazce, podnabori vdelane pisave s kompaktnimi podnabori, znova stisne tokove s Flate samo, kadar je rezultat manjši, in vklopi tokove objektov za naslednje shranjevanje, medtem ko polja podpisov sprožijo RefusedBySignaturePolicy in pustijo datoteko nedotaknjeno
Vsak korak prepiše bajte, ki jih pokriva podpis, zato je cel dokument odklonjen, razen če izrecno dovolite neveljavnost — Info.BytesSaved sešteje potem samo delo virov, pisav in tokov
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;

Na naloženi poti sta pomembni dve meji. Vsak korak prepiše bajte, ki jih pokriva podpis, zato je dokument s podpisnimi polji odklonjen v celoti: klic vrne 0, nastavi RefusedBySignaturePolicy in ničesar ne spremeni, razen če nastavite AllowSignatureInvalidation, po čemer vam Info.SignaturesInvalidated pove, česa ste se odpovedali. Kompakcija je tukaj tudi bolj previdna kot na poti ustvarjanja. HotPDF kompakta samo programe pisav, ki jih izključno uporabljajo pisave CIDFontType2 z Identity /CIDToGIDMap, kjer je CID enak GID, in preskoči programe z obstoječim tokom mape, /CIDSet ali tabelami barvnih glifov, kot so COLR, sbix, CBDT ali SVG, ker bi kompaktna ponovna izgradnja odvrgla barvne plasti. Upoštevajte tudi, da Info.BytesSaved sešteva samo korake virov, pisav in tokov; dobiček tokov objektov se pokaže, ko je datoteka zapisana

Kakšne rezultate smete pričakovati v praksi?

Dobički sledijo temu, koliko datoteke je nestisnjena struktura in preveliki podatki pisav, ne temu, koliko ima strani. Tristranski vzorec z Arial in SimSun se je skrčil z 10.2 MB na 20 KB, ko je bil generiran z CompressDocument, in z 10.2 MB na 19.8 KB, ko je bil nestisnjeni izvirnik naložen in pognan skozi CompressLoadedDocument, z identičnim izrisom na obeh načinih. PDF, ki je že kompakten, se komaj premakne: v regresijski množici so take datoteke shranile znotraj -0.07 % do +0.06 % svoje izvirne velikosti. Datoteke, polne fotografij, malo pridobijo, ker nobena pot ne dotakne podatkov slik

Če vsako noč generirate ista poročila CJK, združite kompaktne podnabore z vztrajnim predpomnilnikom podnaborov pisav na disku, da se podnaborjensko delo ne ponavlja ob vsakem poganjanju, in primerejte stisnjene izhode po vsebini objektov, ne po bajtih, ker eno spremenjeno polje znova s Flate stisne cel tok objektov. Popolne reference lastnosti in zapisov so na strani izdelka HotPDF Delphi PDF component