HotPDF:n THotPDF.CompressDocument on yksi kytkin, joka saa BeginDocin tuottamaan pienimmän häviöttömän PDF:n, jonka komponentti osaa kirjoittaa: FlateDecode korkeimmalla tasolla, ristiviittausstreami objektistreameilla, fonttisubsetting ja tiiviit fonttiosajoukot, jotka numeroivat säästetyt glyyfit uudelleen eksplisiittisen /CIDToGIDMapin taakse. EndDoc laittaa sitten omat asetuksesi takaisin. Kolmisivuinen Arial- ja SimSun-testiasiakirja putosi arvosta 10.2 MB arvoon 20 KB identtisellä renderöinnillä
Mitä CompressDocument oikeasti kytkee päälle?
CompressDocument ohittaa kuusi kirjoittajan asetusta sekä objektistreamikaton yhden asiakirjan ajaksi ja palauttaa ne kaikki sen jälkeen. BeginDocissa, ennen kuin PDF-versio vakiintuu, HotPDF kirjaa arvosi ja asettaa Compressionin arvoon cmFlateDecode, CompressionLevelin arvoon clMaximum, kytkee päälle EnableFontSubsettingin ja CompactFontSubsettingin sekä ottaa käyttöön UseXRefStreamin ja UseObjectStreamsin (ISO 32000-1 §7.5.7 ja §7.5.8). Objektistreamit tarvitsevat PDF 1.5:n, joten vanhempi Version nostetaan arvoon 1.5, jos se ei ole lukittu. PDF/A-1 kieltää molemmat rakenteet, joten PDF/A-1-asiakirja pitää klassisen ristiviittaustaulukkonsa ja saa vain Flate- ja fonttityön. Kuvat jätetään täsmälleen sellaisiksi kuin upotit ne
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'invoice-2026-1042.pdf';
Pdf.CompressDocument := True; // BeginDoc ottaa käyttöön, EndDoc kumoaa
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, 'Invoice 2026-1042');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Palautus tapahtuu EndDocin uloimmassa finallyssä, joten poikkeus raportin puolivälissä ei jätä pitkäikäistä komponenttia jumiin maksimipakkaukseen seuraavaa työtä varten. CompressDocument-ominaisuus itsessään pysyy Truena; vain kuusi sen lainaamaa asetusta menevät takaisin. Versioon suhtaudutaan huolellisemmin. HotPDF kumoaa oman nostonsa arvoon 1.5 vain, jos asiakirja yhä päättyy arvoon 1.5, joten kun toinen ominaisuus työnsi tiedoston arvoon 1.6 ajon aikana (esimerkiksi upotettu OpenType-fontti), korkeampi versio jää, täsmälleen niin kuin se jäisi ilman pakkaustakin
Miksi fonttiosajoukot ovat yhä suuria ilman tiivistystä?
Klassinen TrueType-osajoukko pudottaa ääriviivat, joita et koskaan piirrä, mutta pitää jokaisen glyyfi-ID:n paikallaan, ja juuri tuo numerointi pitää sen raskaana. Sisältöstreami näyttää CIDit, jotka vastaavat alkuperäisiä GIDejä, joten osajoukon on pidettävä loca-offset ja hmtx-merkintä jokaisesta slotista korkeimpaan säästämäänsä glyyfiin asti, tyhjä tai ei. Latinalaiselle fontille se ylikustannus on melua. CJK-fontille kuten SimSun, jonka ideografit istuvat syvällä hyvin suuressa glyyfitaulukossa, kaksi kiinalaista merkkiä raahaa mukanaan koko fontin kokoisia taulukoita. Kirjoituksen fonttiosajoukon sulkemissäännöt muodostetuille glyyfeille päättävät, mitkä glyyfit selviävät; tiivistys on siitä, paljonko selviytyjät maksavat
CompactFontSubsetting numeroo säästetyt glyyfit uudelleen tiheäksi alueeksi nollasta alkaen ja kirjoittaa /CIDToGIDMap-streamin CIDFontille, jonka ISO 32000-1 §9.7.4.2 määrittelee kaksitavuisten GIDien taulukoksi, jonka CID indeksoi. Tuo taulukko on koko temppu. Sisältöstreamit, /W-leveystaulukko ja ToUnicode-CMap pitävät kaikki alkuperäiset CIDit, joten mikään jo kirjoitettu ei joudu muuttumaan; vain haku CID:stä glyyfiin siirtyy mappiin. Testissä, joka innoitti ominaisuuden, SimSun kahdella merkillä meni arvosta 24.8 KB fonttidataa arvoon 3.1 KB
Tiivistyksellä on jäykät rajat, ja se heikkenee hiljaa sen sijaan, että epäonnistuisi. HotPDF rakentaa tiiviit osajoukot vain Type 0 TrueType-fonteille, sekä niille, jotka asetetaan funktiolla SetFont subsetting päällä, että funktiolla RegisterUnicodeTTF rekisteröidylle fontille. Yksinkertainen TrueType-fontti löytää glyyfinsä fonttiohjelman sisäisen cmapin kautta, jonka uudellenumerointi rikkoisi, joten se pitää harvan osajoukon. OpenType-CFF-fonteilla ei ole tiivistyspolkuakaan. Epäonnistuva tiivis rakennus putoaa takaisin harvaan osajoukkoon poikkeuksen sijaan. Ominaisuus on oletukselta pois päältä, joten olemassa oleva tuloste pysyy tavuiltaan samana, kun taas PDF/A:n alla rekisteröity Unicode-fontti saa aina tiiviin osajoukon
Pdf.EnableFontSubsetting := True;
Pdf.CompactFontSubsetting := True; // toimii myös ilman CompressDocumentia
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('SimSun', [], 12);
Pdf.CurrentPage.TextOut(40, 40, 0, WideString('Total: '#$4E2D#$6587));
Pdf.EndDoc;
Miten pakattu kirjoittaja puristaa tiedostorakenteen?
Kun fontit ja streamit ovat pieniä, sanakirjoista ja ristiviittausdatasta tulee suurin jäljelle jäävä kustannus, joten CompressDocumentin takana oleva objektistreamikirjoittaja trimmaa myös niitä. Kirjoitus objektistreamit ja inkrementaaliset päivitykset kattaa konttimuodon itsensä; pakkauspolku lisää päälle neljä hienosäätöä:
- Tiivis syntaksi ISO 32000-1 §7.2.2:n mukaan: välilyönti kirjoitetaan vain kahden tokenin väliin, jotka muuten juoksisivat yhteen tavallisina merkkeinä, joten
/Type /Pagemuuttuu muodoksi/Type/Page - Ristiviittausstreamin kentät ottavat minkä tahansa leveyden, jonka §7.5.8.2 sallii, joten alle 16 MB:n tiedosto tallettaa jokaisen offsetin 3 tavuun 4:n sijaan
- Jopa 250 objektia menee jokaiseen objektistreamiin tavallisen 100 sijaan, ellei aseta omaa kattoaan funktiolla
ConfigureAdaptiveObjectStreamPacking - Kun tiedosto ei ole salattu, myös Catalog ja Info-sanakirja pakataan objektistreameihin; salattu tuloste pitää ne ylimmällä tasolla
Tiivis syntaksi tuli ansan mukanaan, joka on syytä tuntea, jos laajennat kirjoittajaa. Allekirjoitus täyttää allekirjoituksen tiedoston kirjoittamisen jälkeen etsimällä tavuista litteraalit paikanpitäjät /ByteRange ( ja /Contents <, ja tiivis kirjoitusasu kääntäisi ne muotoihin /ByteRange( ja /Contents<, joita haku ei koskaan löydä. Allekirjoitussanakirjat (Type Sig tai DocTimeStamp, FT Sig) ja salausosanakirja pitävät siksi välilyönnillisen asettelun. Asiaan liittyvä vika vaikutti versioihin ennen v2.766.41: jokainen objektistreamitallennus, CompressDocument mukaan lukien, alkoi kahdella %PDF--otsikkorivillä, joten päivitä, jos tiukka validaattori liputtaa tulosteesi
Voitko pakata PDF:n, joka on jo ladattu?
Kyllä, optio-overloadin CompressLoadedDocument(Options, Info) kautta, joka ajaa samat häviöttömät vaiheet olemassa olevalle tiedostolle. Arvolla THPDFLoadedDocumentCompressionOptions.Default se poistaa käyttämättömät sivuresurssit, yhdistää identtiset fontit ja formit, tekee upotetuista fonteista osajoukot tiivit osajoukot päällä, pakkaa uudelleen suodattamattomat, Flate-, LZW-, ASCII- ja RunLength-streamit Flatella, kun tulos on pienempi, ja saa seuraavan tallennuksen käyttämään objektistreameja. HighRatioFlate on oletukselta pois, ja objektistreamit ohitetaan PDF/A-1:llä ja inkrementaalisissa tallennuksissa. Parametriton CompressLoadedDocument-overload on vanhempi, kapeampi kutsu, joka vain Flate-pakkaa pakaamattomat streamit
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;
Kaksi rajaa merkitsee ladatulla polulla. Jokainen vaihe kirjoittaa uudelleen tavuja, joita allekirjoitus kattaa, joten allekirjoituskenttiä sisältävä asiakirja torjutaan kokonaisuutena: kutsu palauttaa arvon 0, asettaa lipun RefusedBySignaturePolicy eikä muuta mitään, ellet aseta AllowSignatureInvalidationia, jonka jälkeen Info.SignaturesInvalidated kertoo, mistä luopuit. Tiivistys on täällä myös varovaisempi kuin luontipolulla. HotPDF tiivistää vain fonttiohjelmat, joita käyttävät yksinomaan CIDFontType2-fontit Identity-/CIDToGIDMapilla, jossa CID vastaa GIDiä, ja ohittaa ohjelmat, joilla on jo olemassa oleva mapistream, /CIDSet tai väriglyyfitaulukot kuten COLR, sbix, CBDT tai SVG, koska tiivis uudelleenrakennus pudottaisi värikerrokset. Huomaa myös, että Info.BytesSaved laskee yhteen vain resurssi-, fontti- ja streamivaiheet; objektistreamin hyöty ilmenee, kun tiedosto kirjoitetaan
Mitä tuloksia kannattaa odottaa käytännössä?
Hyödyt seuraavat sitä, kuinka suuri osa tiedostosta on pakaamatonta rakennetta ja ylikokoista fonttidataa, eivät sivumäärää. Kolmisivuinen Arial- ja SimSun-näyte kutistui arvosta 10.2 MB arvoon 20 KB, kun se generoitiin arvolla CompressDocument, ja arvosta 10.2 MB arvoon 19.8 KB, kun pakaamaton alkuperä ladattiin ja ajettiin läpi funktiolla CompressLoadedDocument, identtisellä renderöinnillä molemmin puolin. Jo tiivis PDF tuskin liikahtaa: regressiojoukossa tällaiset tiedostot säästivät alueella -0.07 % – +0.06 % alkuperäisestä koostaan. Valokuvapainotteiset tiedostot hyötyvät vähän, koska kumpikaan polku ei kosketa kuvadataa
Jos generoit samat CJK-raportit joka yö, yhdistä tiiviit osajoukot pysyvään fonttiosajoukovälimuistiin levyltä, jotta subsetting-työtä ei toisteta jokaista ajoa kohden, ja diffaa pakatut tulosteet objektisisällön eikä tavujen perusteella, sillä yksi muuttunut kenttä pakkaa koko objektistreamin uudelleen. Täysi ominaisuus- ja tietuereferenssi on HotPDF Delphi PDF component -tuotesivulla