PDF-tiedostokoon pienentämiseksi Delphissä losLab PDF Library tarjoaa kolme sovellusliittymää (API), jotka pureutuvat kolmeen suurimpaan paisumisen lähteeseen: SubsetEmbeddedFonts kirjoittaa jokaisen upotetun TrueType-fonttiohjelman uudelleen vain niille glyyfeille, joita dokumentti todella hahmontaa, DownsampleImages vähentää sellaisten rasterikuvien tarkkuutta (resample), jotka ylittävät kohde-DPI-arvon, ja NormalizeLZWStreams korvaa vanhan LZWDecode-pakkauksen FlateDecode-pakkauksella. Kukin funktio palauttaa muuttamiensa objektien määrän, joten nolla kertoo ajon olleen tyhjä toiminto eikä hiljainen epäonnistuminen
Miksi yhdistetty PDF-tiedostoni on suurempi kuin sen lähdetiedostot?
Yhdistetty tai ohjelmallisesti luotu PDF-tiedosto on yleensä ylisuuri yhdestä kolmesta syystä: kokonaan upotetut fontit, kuvat joiden tarkkuus on huomattavasti suurempi kuin niiden näyttötarkkuus ja tietovirrat, jotka on edelleen pakattu vanhalla LZW-suodattimella. ISO 32000-1 §9.9 sallii luojan upottaa koko fonttiohjelman, ja useimmat luontiohjelmat tekevät juuri niin, koska se on turvallinen oletus. Täydellinen Arial-fontin FontFile2 vie satoja kilotavuja; kun upotat sen tusinaan lähdetiedostoja ja yhdistät ne, kuljetat mukasi tusinaa kopiota glyyfireunoista merkeille, joita kukaan ei koskaan kirjoittanut. Yhdistäminen itsessään ei luo jätettä, se vain keskittää sen yhteen tiedostoon, jossa kokonaismäärä lopulta tulee näkyviin
Kuvat ovat toinen syyllinen. Neljännessivun kehykseen asetettu 4800 pikseliä leveä skannaus siirtää noin 40 kertaa enemmän pikselidataa kuin mitä 300 DPI:n tulostusputki pystyy hyödyntämään. Kolmas syy on hiljaisempi: LZWDecode-suodatetut tietovirrat. ISO 32000-1 §7.4.4 määrittelee sekä LZWDecode- että FlateDecode-suodattimet ja panee merkille, että Flate pakkaa yleensä vähintään yhtä hyvin; käytännössä Flate-tuloste on poikkeuksetta pienempi samalla datalla, ja LZW säilyy enimmäkseen tiedostoissa, jotka ovat jossain vaiheessa historiaansa kulkeneet 1990-luvun työkalujen läpi. Tässä artikkelissa käydään läpi kolme losLab PDF Libraryn ajoa, jotka korjaavat kunkin ongelman, ja yhdistetään ne sitten yhdeksi putkeksi
Fonttien osittaminen SubsetEmbeddedFonts-toiminnolla
SubsetEmbeddedFonts kutistaa jokaisen ladatun dokumentin upotetun TrueType-fontin niihin merkkeihin, joita dokumentissa todella käytetään, eikä se tarvitse argumentteja, koska se johtaa säilytettävien merkkien luettelon suoraan sisältövirroista itsestään. Sisäisesti ajo käy läpi jokaisen sivun sisältövirran GetTextRuns-metodilla, kerää kunkin fonttiresurssin alla viitatut merkkikoodit, rakentaa säilytysluettelon ja välittää alkuperäisen fonttiohjelman Windowsin FontSub-moottorille (CreateFontPackage) osajoukon (subset) luomiseksi. Uudelleenkirjoitettu ohjelma korvaa FontFile2-virran paikoillaan, ja BaseFont-nimi saa LOSABC+-tunnisteen, eli standardin ISO 32000-1 §9.6.4 mukaisen osafonteille määritellyn kuuden suuraakkosen ja plusmerkin yhdistelmän. Tämä etuliite tekee kutsusta myös idempotentin: suorita ajo kahdesti, ja jo ositetut fontit tunnistetaan ja ohitetaan, joten sen liittäminen tiedostoja mahdollisesti uudelleen käsittelevään eräajoon on turvallista
Kaksi toteutuksen yksityiskohtaa on hyvä tietää, sillä ne selittävät API:n rajoitukset. Ensinnäkin ajo kohdistuu FontFile2-tietovirtaan, joten se kattaa upotetut TrueType-ohjelmat; Type 1- tai pelkkinä CFF-muodossa upotettuja fontteja ei käsitellä riskien välttämiseksi. Toiseksi se tukeutuu FontSub-moottoriin, mikä tekee SubsetEmbeddedFonts-toiminnosta vain Windows-yhteensopivan. Hienovaraisempi toteutusyksityiskohta: se, soveltuuko fontti käsiteltäväksi, päätetään selvittämällä todellinen viiteketju FontDescriptor → FontFile2, eikä luottamalla upotustunnisteen (embedded-flag) heuristiikkaan, sillä ladatun dokumentin fontit eivät ole koskaan käyneet läpi luontipuolen kirjanpitoa, joka asettaa tällaiset liput. Jos selvitetty tietovirta on olemassa, fontti on ehdokas; jos ei, se ohitetaan ilman virhettä
Rehellinen kompromissi: ositettu fontti sisältää vain ne glyyfit, jotka olivat mukana ositushetkellä. Jos jokin myöhempi työkalu tai oma koodisi lisää tekstiä samalla fontilla, kaikilla osajoukon ulkopuolisilla merkeillä ei ole ääriviivoja ja ne hahmontuvat puuttuvina glyyfeinä. Suorita ositus viimeisenä sisältöä muuttavana vaiheena, älä koskaan ennen muokkausvaihetta. Sama varovaisuus pätee, jos aiot myöhemmin purkaa fontin uudelleenkäyttöä varten; artikkeli tekstin, kuvien ja fonttien purkamisesta PDFlibPas-kirjastolla kattaa sen, mitä puraettu ositettu ohjelma voi ja ei voi tarjota
Miten DownsampleImages päättää, mitä kuvia kutistetaan?
DownsampleImages(MaxDPI, Quality, Filter) muuttaa vain sellaisten kuvien tarkkuutta, joita se voi varmuudella pitää ylinäytteistettyinä, käyttäen tarkoituksella varovaista DPI-arviota. PDF-kuvan XObject tallentaa pikselimitat, mutta ei luotettavaa fyysistä resoluutiota, ja lähdekuvan DPI-tunniste säilyy harvoin lataa-muokkaa-tallenna-syklin läpi. Siksi ajo arvioi suhteella SrcDPI = PixelWidth / 8.5 kysyen käytännössä: jos tämä kuva ulottuisi koko Letter-sivun leveydelle, mikä sen resoluutio olisi? Vain kuvia, joiden arvioitu arvo ylittää MaxDPI-arvon, käsitellään. Tämä painotus on tarkoituksellinen: sivulle pieneksi asetellulla kuvalla on todellinen DPI suurempi kuin arvio, joten ajo mieluummin alikäynnistyy kuin heikentää painolaatuisen aineiston laatua, jota se ei pysty mittaamaan
Quality välillä 1–100 valitsee JPEG-uudelleenkoodauksen laadun, kun taas arvo 0 säilyttää tulosteen häviöttömänä PNG-tyyppisenä Flate-muotona; Filter valitsee näytteenottimen ytimen (kernel): 0 laatikko-keskiarvolle (box average) ja 1 bilineaariselle suodatukselle. Skannatuille toimistoasiakirjoille DownsampleImages(150, 75, 1) on järkevä lähtökohta; kaikelle, mikä saatetaan tulostaa uudelleen, nosta MaxDPI-arvo 300:aan tai ohita tämä vaihe kokonaan. Kuvan tarkkuuden vähentäminen on näistä kolmesta ainoa häviöllinen vaihe, joten se kuuluu asetukseksi, jonka käyttäjäsi voivat kytkeä pois päältä
Vanhojen LZW-tietovirtojen muuntaminen NormalizeLZWStreams-toiminnolla
NormalizeLZWStreams on helppo etu: se purkaa häviöttömästi jokaisen LZWDecode-virran ja pakkaa sen uudelleen FlateDecode-pakkauksella paikoillaan palauttaen muunnettujen virtojen määrän. Se käsittelee sekä yksittäisen /Filter /LZWDecode -merkinnän että suodatinketjun taulukossa esiintyvän LZW-suodattimen, jolloin vain LZW-linkki korvataan ja loppu ketjusta säilyetään. Ennusteparametrit (Predictor, Columns, Colors, BitsPerComponent) luetaan virran DecodeParms-osiosta ja välitetään purkajalle, jotta ennustekoodattu kuvadata palautuu oikein. Koska molemmat suodattimet ovat bittitarkkoja koodekkeja, puretut tavut ovat täysin samat ennen ja jälkeen; vain säiliön pakkaus muuttuu, minkä vuoksi tämä ajo on turvallista suorittaa ehdottomasti jokaiselle tiedostolle
Dokumentissa, jossa ei ole LZW-virtoja, kutsu palauttaa yksinkertaisesti arvon 0 eikä koske mihinkään, mitä kirjaston regressiotestit testaavat nimenomaisesti: vastaluodun, vain Flate-muotoa käyttävän tiedoston on ilmoitettava nolla muunnosta. Tämä takuu tyhjästä toiminnosta on tärkeä silloin, kun ajo on osana putkea, joka käsittelee tuhansia erilaisia tiedostoja, joista osa on vuodelta 2024 ja osa vuodelta 1998
Täydellinen koon optimointiputki Delphissä
Nämä kolme ajoa yhdistyvät yhdeksi lataa-optimoi-tallenna-funktioksi, ja järjestyksellä on vähemmän merkitystä kuin voisi olettaa, koska ne toimivat toisistaan erillisillä objektityypeillä: fonteilla, kuva-XObjecteilla ja virran suodattimilla. Osittamisen suorittaminen ensin on silti siisti valinta, sillä se on ajo, jolla on muokkausjärjestyksen rajoitus
function OptimizePDF(const Src, Dst: string): Boolean;
var
Lib: TPDFlib;
Fonts, Images, Streams: Integer;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
Exit;
Fonts := Lib.SubsetEmbeddedFonts; // TrueType FontFile2 -> subset
Images := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, bilinear
Streams := Lib.NormalizeLZWStreams; // LZWDecode -> FlateDecode
Result := Lib.SaveToFile(Dst) = 1;
// Log Fonts/Images/Streams: three zeros mean the file was already lean
finally
Lib.Free;
end;
end;
Verify the pipeline the way the library verifies itself: round-trip. The v3.130 regression tests create a document, save it, reload it, run the optimization, save again, and then assert three things: the output is smaller, the returned counts match expectations, and a reload of the optimized file still parses and renders. Reproducing that create-optimize-reload loop against a sample of your own production files, and comparing extracted text before and after, is a one-hour investment that catches integration mistakes long before a customer opens a broken invoice
// Round-trip check: the optimized file must still load cleanly
Lib := TPDFlib.Create;
try
Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
Assert(Lib.GetPageCount > 0);
finally
Lib.Free;
end;
Where does the pipeline fit in a merge workflow? After the merge, not during it. Merging first and optimizing the single result means each embedded font is subsetted once against the union of all used characters, instead of per source file. If merge throughput is the bottleneck, PDFlibPas offers a byte-level fast path that avoids full object parsing, described in the article on fast PDF merging with byte reference shifting; and for inputs too large to hold fully in memory, direct-access merge and split for large PDFs covers the streaming route. Both pair naturally with a final optimization pass on the merged output
Mitä nämä kolme ajoa eivät tee
losLab PDF Libraryn optimointikolmikko jättää tarkoituksella pois kaiken, mikä muuttaa dokumentin semantiikkaa. SubsetEmbeddedFonts ei yhdistä kaksoiskappalefontteja yhdistetyistä lähteistä yhdeksi ohjelmaksi, vaan se kutistaa kunkin itsenäisesti; kaksoiskappaleiden poistaminen on erilainen ja riskialttiimpi muunnos. DownsampleImages ohittaa kuvan, jonka varovainen DPI-arvio pysyy kynnyksen alapuolella, vaikka ihminen näkisi sen olevan ylisuuri kehykseensä. Mikään ajoista ei myöskään koske dokumentin rakenteeseen, joten tuhansien orpojen objektien vuoksi paisunut tiedosto vaatii uudelleenkirjoitustyyppisen tallennuksen näiden virtatason ajojen sijaan. Näissä rajoissa fonttien osittamisen, kuvan tarkkuuden vähentämisen ja LZW-Flate-normalisoinnin yhdistelmä poistaa kolme klassista PDF-paisumisen lähdettä kullakin yhdellä ennustettavalla API-kutsulla. Nämä kolme funktiota toimitetaan osana standardia losLab PDF Library -kirjastoa Delphille, C#:lle ja VB.NET:lle yhdessä edellä mainittujen yhdistämis-, purku- ja hahmontamis-API-liittymien kanssa