Za smanjenje veličine PDF datoteke u Delphiju, losLab PDF Library nudi tri API-ja koji napadaju tri najveća izvora prevelike veličine datoteke: SubsetEmbeddedFonts prepisuje svaki ugrađeni program TrueType fonta smanjujući ga samo na glifove koje dokument stvarno prikazuje, DownsampleImages smanjuje uzorkovanje rasterskih slika koje premašuju ciljani DPI, a NormalizeLZWStreams zamjenjuje zastarjelu LZWDecode kompresiju s FlateDecode. Svaki od njih vraća broj objekata koje je promijenio, pa nula govori da je prolaz bio prazna operacija (no-op), a ne tihi neuspjeh
Zašto je moj spojeni PDF veći od svojih izvornih datoteka?
Spojeni ili programski generirani PDF obično je prevelik iz jednog od tri razloga: potpuno ugrađenih fontova, slika uzorkovanih daleko iznad njihove rezolucije prikaza i tokova koji su još uvijek komprimirani starim LZW filtrom. ISO 32000-1 §9.9 dopušta proizvođaču ugradnju cjelovitog programa fonta, i većina proizvođača radi upravo to jer je to sigurna zadana postavka. Cjelovita Arial FontFile2 datoteka teži stotinama kilobajta; ugradite je u desetak izvornih datoteka, spojite ih, i prenosite desetak kopija obrisa glifova za znakove koje nitko nije upisao. Spajanje samo po sebi ne stvara otpad, ono ga samo koncentrira u jednu datoteku u kojoj ukupni iznos konačno postaje vidljiv
Slike su drugi krivac. Skenirani dokument širine 4800 piksela smješten u okvir od četvrtine stranice prenosi otprilike 40 puta više slikovnih podataka nego što cjevovod za ispis od 300 DPI može iskoristiti. Treći je tiši: tokovi filtrirani s LZWDecode. ISO 32000-1 §7.4.4 specificira i LZWDecode i FlateDecode, te napominje da Flate obično komprimira barem jednako dobro; u praktičnom radu Flate izlaz je dosljedno manji na istim podacima, a LZW preživljava uglavnom u datotekama koje su u nekom trenutku svoje povijesti prošle kroz alate iz 1990-ih. Ostatak ovog članka prolazi kroz tri prolaza losLab PDF Libraryja koji rješavaju svaki problem, a zatim ih kombinira u jedan cjevovod
Izdvajanje podskupa fontova s SubsetEmbeddedFonts
SubsetEmbeddedFonts smanjuje svaki ugrađeni TrueType font u učitanom dokumentu na znakove koje dokument stvarno koristi, i ne treba nikakve argumente jer popis znakova za zadržavanje izvodi iz samih tokova sadržaja. Interno, ovaj prolaz prolazi kroz tok sadržaja svake stranice s GetTextRuns, prikuplja kodove znakova referencirane pod svakim resursom fonta, gradi popis za zadržavanje i predaje izvorni program fonta Windows FontSub mehanizmu (CreateFontPackage) radi proizvodnje podskupa. Prepisani program zamjenjuje tok FontFile2 na licu mjesta, a naziv BaseFont dobiva oznaku LOSABC+, konvenciju od šest velikih slova i znaka plus koju ISO 32000-1 §9.6.4 definira za podskupove fontova. Taj je prefiks također ono što poziv čini idempotentnim: pokrenite prolaz dvaput i već izdvojeni podskupovi fontova bit će prepoznati i preskočeni, pa je njihovo povezivanje u serijski posao koji može ponovno posjetiti datoteke sigurno
var
Lib: TPDFlib;
Fonts: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('merged-report.pdf', '') = 1 then
begin
Fonts := Lib.SubsetEmbeddedFonts;
// Fonts = number of FontFile2 programs rewritten;
// 0 means nothing embedded, or everything already subsetted
Lib.SaveToFile('merged-report-subset.pdf');
end;
finally
Lib.Free;
end;
end;
Dva detalja implementacije vrijedi znati jer objašnjavaju granice API-ja. Prvo, prolaz cilja na FontFile2, tako da pokriva ugrađene TrueType programe; fontovi ugrađeni kao Type 1 or bare CFF ostaju netaknuti kako se ne bi riskiralo. Drugo, oslanja se na FontSub, što čini SubsetEmbeddedFonts dostupnim samo za Windows. Suptilnija točka iz implementacije: kvalificira li se font odlučuje se stvarnim razrješavanjem referentnog lanca FontDescriptor → FontFile2, a ne povjerenjem u heuristiku zastavice ugradnje, jer fontovi u učitanom dokumentu nikada nisu prošli kroz knjigovodstvo na strani stvaranja koje postavlja takve zastavice. Ako razriješeni tok postoji, font je kandidat; ako ne, preskače se bez pogreške
Iskreni kompromis: podskup fonta sadrži samo glifove prisutne u trenutku izdvajanja podskupa. Ako nizvodni alat, ili vaš vlastiti kod, kasnije doda tekst u tom istom fontu, bilo koji znak izvan podskupa neće imati obris i prikazat će se kao nedostajući glif. Izdvajajte podskupove kao posljednji korak promjene sadržaja, nikada prije faze uređivanja. Isti oprez vrijedi i ako planirate kasnije ponovno izvući font radi ponovne uporabe; članak o izdvajanju teksta, slika i fontova pomoću PDFlibPas pokriva što vam izvučeni program podskupa može, a što ne može dati
Kako DownsampleImages odlučuje koje slike treba smanjiti?
DownsampleImages(MaxDPI, Quality, Filter) ponovno uzorkuje samo one slike koje može sa sigurnošću nazvati preuzorkovanim, koristeći namjerno konzervativnu procjenu DPI-ja. PDF slikovni XObject pohranjuje dimenzije piksela, ali ne i pouzdanu fizičku rezoluciju, a bilo koja oznaka DPI iz izvorne slike rijetko preživi ciklus učitavanja-uređivanja-spremanja. Stoga prolaz procjenjuje SrcDPI = PixelWidth / 8.5, zapravo pitajući: ako bi ova slika obuhvaćala punu širinu Letter stranice, kolika bi bila njezina rezolucija? Dotiču se samo slike čija procjena premašuje MaxDPI. Pristranost je namjerna: slika postavljena mala na stranici ima stvarni DPI viši od procjene, pa se prolaz radije nedovoljno aktivira nego da pogorša kvalitetu ispisa resursa koji ne može izmjeriti
Quality od 1 do 100 odabire kvalitetu ponovnog kodiranja JPEG-a, dok 0 zadržava izlaz kao Flate bez gubitaka u stilu PNG-a; Filter bira jezgru ponovnog uzorkovanja, 0 za prosjek kutije (box average) i 1 za bilinearno. Za skeniranu uredsku dokumentaciju, DownsampleImages(150, 75, 1) je razumna početna točka; za sve što se može ponovno tiskati, podignite MaxDPI na 300 ili u potpunosti preskočite prolaz. Smanjivanje uzorkovanja je jedini korak s gubicima od ova tri, pa mu je mjesto iza postavke koju vaši korisnici mogu isključiti
Pretvaranje starih LZW tokova s NormalizeLZWStreams
NormalizeLZWStreams je besplatni dobitak: bez gubitaka dekomprimira svaki LZWDecode tok i ponovno ga komprimira s FlateDecode, na licu mjesta, vraćajući broj pretvorenih tokova. Rukuje i pojedinačnim unosom /Filter /LZWDecode i LZW-om koji se pojavljuje unutar polja lanca filtara, gdje se zamjenjuje samo LZW karika, a ostatak lanca se čuva. Parametri prediktora (Predictor, Columns, Colors, BitsPerComponent) čitaju se iz DecodeParms toka i prosljeđuju dekompresoru, tako da se slikovni podaci kodirani prediktorom ispravno vraćaju. Budući da su oba filtra bit-točni kodeci, dekodirani bajtovi su identični prije i poslije; mijenja se samo kompresija spremnika, zbog čega je ovaj prolaz sigurno pokrenuti bezuvjetno na svakoj datoteci
Na dokumentu bez LZW tokova poziv jednostavno vraća 0 i ne dira ništa, što regresijski paket knjižnice izričito provjerava: svježe stvorena datoteka koja sadrži samo Flate mora prijaviti nula pretvorbi. To jamstvo prazne operacije (no-op) važno je kada se prolaz nalazi u cjevovodu koji obrađuje tisuće heterogenih datoteka, neke iz 2024. a neke iz 1998
Kompletni cjevovod za optimizaciju veličine u Delphiju
Tri prolaza spajaju se u jednu funkciju učitaj-optimiziraj-spremi, a redoslijed je manje važan nego što biste očekivali jer djeluju na nepovezane vrste objekata: fontove, slikovne XObjecte i filtre tokova. Pokretanje izdvajanja podskupova prvo je i dalje uredan izbor, jer je to prolaz s ograničenjem redoslijeda uređivanja
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;
Provjerite cjevovod na način na koji knjižnica provjerava samu sebe: povratno putovanje (round-trip). Regresijski testovi verzije v3.130 stvaraju dokument, spremaju ga, ponovno učitavaju, pokreću optimizaciju, ponovno spremaju, a zatim potvrđuju tri stvari: izlaz je manji, vraćeni brojevi odgovaraju očekivanjima, a ponovno učitavanje optimizirane datoteke i dalje je analizira i renderira. Reprodukcija te petlje stvori-optimiziraj-ponovno_učitaj na uzorku vlastitih produkcijskih datoteka i usporedba izdvojenog teksta prije i poslije investicija je od jednog sata koja hvata integracijske pogreške mnogo prije nego što kupac otvori neispravan račun
// 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;
Gdje se cjevovod uklapa u tijek rada spajanja? Nakon spajanja, ne tijekom njega. Prvo spajanje i optimizacija jedinstvenog rezultata znači da se svaki ugrađeni font jednom izdvoji u podskup u odnosu na uniju svih korištenih znakova, umjesto po izvornoj datoteci. Ako je propusnost spajanja usko grlo, PDFlibPas nudi brzi put na razini bajtova koji izbjegava potpunu analizu objekta, opisan u članku o brzom spajanju PDF-a s pomicanjem referenci bajtova; a za ulaze prevelike da bi se u potpunosti držali u memoriji, spajanje i dijeljenje s izravnim pristupom za velike PDF-ove pokriva put strujanja. Oba se prirodno uparuju s konačnim prolazom optimizacije na spojenom izlazu
Što ova tri prolaza neće učiniti
Optimizacijski trojac losLab PDF Libraryja namjerno isključuje sve što mijenja semantiku dokumenta. SubsetEmbeddedFonts ne objedinjuje duplicirane fontove iz spojenih izvora u jedan program, već smanjuje svaki neovisno; deduplikacija je drugačija, rizičnija transformacija. DownsampleImages će zaobići sliku čija konzervativna procjena DPI-ja ostaje ispod praga čak i kada bi čovjek mogao reći da je prevelika za svoj okvir. I niti jedan od prolaza ne dira strukturu dokumenta, pa datoteka napuhnuta tisućama napuštenih objekata zahtijeva spremanje u stilu prepisivanja, a ne ove prolaze na razini toka. Unutar tih granica, kombinacija izdvajanja podskupa fontova, smanjenja uzorkovanja slika i normalizacije LZW u Flate uklanja tri klasična izvora napuhavanja PDF-a s po jednim predvidljivim pozivom API-ja. Tri funkcije dolaze kao dio losLab PDF Libraryja za Delphi, C# i VB.NET, zajedno s API-jima za spajanje, izdvajanje i renderiranje o kojima se raspravljalo gore