PDF failo dydžiui Delphi aplinkoje sumažinti losLab PDF Library siūlo tris API, kurios puola tris didžiausius išsipūtimo šaltinius: SubsetEmbeddedFonts perrašo kiekvieną įterptą TrueType šrifto programą, palikdamas tik tuos glifus, kuriuos dokumentas iš tikrųjų atvaizduoja, DownsampleImages persampuoja taškinius paveikslėlius, viršijančius tikslinį DPI, o NormalizeLZWStreams pakeičia senovinį LZWDecode glaudinimą į FlateDecode. Kiekviena grąžina pakeistų objektų skaičių, todėl nulis pasako, kad praėjimas nieko nerado, o ne kad jis tyliai žlugo
Kodėl sujungtas PDF yra didesnis už savo šaltinio failus?
Sujungtas arba programiškai sugeneruotas PDF paprastai būna per didelis dėl vienos iš trijų priežasčių: pilnai įterptų šriftų, paveikslėlių, sampuotų kur kas aukščiau nei jų rodymo raiška, ir srautų, vis dar suglaudintų senovine LZW filtracija. ISO 32000-1 §9.9 leidžia gamintojui įterpti visą šrifto programą, ir dauguma gamintojų būtent taip ir daro, nes tai saugus numatytasis pasirinkimas. Pilna Arial FontFile2 užima šimtus kilobaitų; įterpkite ją į tuziną šaltinio failų, sujunkite juos ir nešiositės tuziną glifų kontūrų kopijų simboliams, kurių niekas neįvedė. Pats sujungimas švaistymo nesukuria, jis tik sutelkia jį į vieną failą, kuriame bendra suma pagaliau tampa matoma
Paveikslėliai yra antrasis kaltininkas. 4800 pikselių pločio skenuotė, įdėta į ketvirtadalio puslapio rėmelį, atgabena maždaug 40 kartų daugiau pikselių duomenų, nei gali panaudoti 300 DPI spausdinimo grandinė. Trečiasis kaltininkas tylesnis: srautai, filtruoti LZWDecode. ISO 32000-1 §7.4.4 apibrėžia ir LZWDecode, ir FlateDecode bei pažymi, kad Flate paprastai glaudina bent jau taip pat gerai; praktikoje Flate išvestis su tais pačiais duomenimis nuosekliai būna mažesnė, o LZW daugiausia išlieka failuose, kurie kada nors savo istorijoje perėjo per dešimtojo dešimtmečio įrankius. Likusi šio straipsnio dalis pereina per tris losLab PDF Library praėjimus, taisančius kiekvieną problemą, o tada sujungia juos į vieną grandinę
Šriftų poaibiai su SubsetEmbeddedFonts
SubsetEmbeddedFonts sutraukia kiekvieną įkelto dokumento įterptą TrueType šriftą iki tų simbolių, kuriuos dokumentas iš tikrųjų naudoja, ir jam nereikia jokių argumentų, nes paliekamų glifų sąrašą jis išveda iš pačių turinio srautų. Viduje praėjimas su GetTextRuns apeina kiekvieno puslapio turinio srautą, surenka simbolių kodus, minimus prie kiekvieno šrifto ištekliaus, sudaro paliekamų glifų sąrašą ir perduoda pradinę šrifto programą Windows FontSub varikliui (CreateFontPackage), kad šis pagamintų poaibį. Perrašyta programa pakeičia FontFile2 srautą vietoje, o BaseFont vardas gauna LOSABC+ žymą, tai yra šešių didžiųjų raidžių ir pliuso konvenciją, kurią ISO 32000-1 §9.6.4 apibrėžia poaibiniams šriftams. Būtent tas priešdėlis daro iškvietimą idempotentinį: paleidus praėjimą du kartus, jau poaibiniai šriftai atpažįstami ir praleidžiami, todėl jį saugu įpinti į paketinį darbą, kuris gali grįžti prie tų pačių failų
var
Lib: TPDFlib;
Fonts: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('merged-report.pdf', '') = 1 then
begin
Fonts := Lib.SubsetEmbeddedFonts;
// Fonts = perrašytų FontFile2 programų skaičius;
// 0 reiškia, kad nieko neįterpta arba viskas jau poaibinta
Lib.SaveToFile('merged-report-subset.pdf');
end;
finally
Lib.Free;
end;
end;
Dvi įgyvendinimo smulkmenos vertos dėmesio, nes jos paaiškina šio API ribas. Pirma, praėjimas taikosi į FontFile2, taigi apima įterptas TrueType programas; šriftai, įterpti kaip Type 1 ar plikas CFF, paliekami neliesti, o ne rizikuojami. Antra, jis remiasi FontSub, todėl SubsetEmbeddedFonts veikia tik Windows aplinkoje. Subtilesnis dalykas iš įgyvendinimo: ar šriftas tinka, sprendžiama iš tikrųjų išsprendžiant FontDescriptor → FontFile2 nuorodų grandinę, o ne pasitikint įterpimo požymio euristika, nes įkelto dokumento šriftai niekada nepraėjo kūrimo pusės apskaitos, kuri tokius požymius nustato. Jei išspręstas srautas egzistuoja, šriftas yra kandidatas; jei ne, jis praleidžiamas be klaidos
Sąžininga kaina tokia: poaibinis šriftas turi tik tuos glifus, kurie egzistavo poaibinimo metu. Jei vėliau koks nors tolesnis įrankis ar jūsų pačių kodas prideda teksto tuo pačiu šriftu, bet kuris už poaibio ribų esantis simbolis neturės kontūro ir bus atvaizduotas kaip trūkstamas glifas. Poaibinkite kaip paskutinį turinį keičiantį žingsnį, niekada ne prieš redagavimo etapą. Ta pati atsarga galioja, jei planuojate šriftą vėliau ištraukti pakartotiniam naudojimui; straipsnis apie teksto, paveikslėlių ir šriftų ištraukimą su PDF Library for Delphi aptaria, ką ištraukta poaibinė programa gali ir ko negali jums duoti
Kaip DownsampleImages nusprendžia, kuriuos paveikslėlius mažinti?
DownsampleImages(MaxDPI, Quality, Filter) persampuoja tik tuos paveikslėlius, kuriuos gali užtikrintai pavadinti per daug sampuotais, ir naudoja sąmoningai konservatyvų DPI įvertį. PDF paveikslėlio XObject saugo pikselių matmenis, bet jokios patikimos fizinės raiškos, o bet kokia DPI žyma iš pradinio paveikslėlio retai išgyvena įkėlimo, redagavimo ir įrašymo ciklą. Todėl praėjimas įvertina SrcDPI = PixelWidth / 8.5, iš esmės klausdamas: jei šis paveikslėlis apimtų visą Letter puslapio plotį, kokia būtų jo raiška? Liečiami tik tie paveikslėliai, kurių įvertis viršija MaxDPI. Šis nuokrypis yra tyčinis: puslapyje nedideliu formatu įdėtas paveikslėlis turi tikrą DPI, didesnį už įvertį, todėl praėjimas verčiau suveikia per retai, nei sugadina spausdinimo kokybės medžiagą, kurios negali išmatuoti
Quality nuo 1 iki 100 parenka JPEG perkodavimo kokybę, o 0 palieka išvestį kaip nenuostolingą PNG tipo Flate; Filter parenka persampavimo branduolį: 0 reiškia dėžutės vidurkį, 1 – tiesinę interpoliaciją. Skenuotiems biuro dokumentams DownsampleImages(150, 75, 1) yra protingas atspirties taškas; visam kam, kas gali būti spausdinama iš naujo, pakelkite MaxDPI iki 300 arba praėjimą praleiskite visai. Raiškos mažinimas yra vienintelis nuostolingas žingsnis iš trijų, todėl jo vieta yra už nustatymo, kurį jūsų naudotojai gali išjungti
Senų LZW srautų vertimas su NormalizeLZWStreams
NormalizeLZWStreams yra nemokamas laimėjimas: jis nenuostolingai išglaudina kiekvieną LZWDecode srautą ir vietoje perglaudina jį FlateDecode, grąžindamas konvertuotų srautų skaičių. Jis susitvarko ir su vienu /Filter /LZWDecode įrašu, ir su LZW, esančiu filtrų grandinės masyve, kur pakeičiama tik LZW grandis, o likusi grandinė išsaugoma. Prediktoriaus parametrai (Predictor, Columns, Colors, BitsPerComponent) nuskaitomi iš srauto DecodeParms ir perduodami išglaudinimo varikliui, todėl prediktoriumi koduoti paveikslėlio duomenys apsuka ratą teisingai. Kadangi abu filtrai yra bitas į bitą tikslūs kodekai, iškoduoti baitai prieš ir po yra identiški; keičiasi tik konteinerio glaudinimas, ir būtent todėl šį praėjimą saugu leisti besąlygiškai kiekvienam failui
Dokumentui be LZW srautų iškvietimas tiesiog grąžina 0 ir nieko neliečia, o bibliotekos regresijos rinkinys tai tikrina atvirai: ką tik sukurtas vien Flate failas privalo pranešti nulį konversijų. Ta nieko nedarymo garantija svarbi, kai praėjimas sėdi grandinėje, apdorojančioje tūkstančius nevienalyčių failų, vienus iš 2024, kitus iš 1998 metų
Pilna dydžio optimizavimo grandinė Delphi aplinkoje
Trys praėjimai susijungia į vieną įkėlimo, optimizavimo ir įrašymo funkciją, o tvarka svarbi mažiau, nei galėtumėte tikėtis, nes jie dirba su nepersidengiančiais objektų tipais: šriftais, paveikslėlių XObject ir srautų filtrais. Poaibinimą vis dėlto tvarkingiau leisti pirmą, nes būtent jis turi redagavimo tvarkos apribojimą
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 -> poaibis
Images := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, tiesinis
Streams := Lib.NormalizeLZWStreams; // LZWDecode -> FlateDecode
Result := Lib.SaveToFile(Dst) = 1;
// Registruokite Fonts/Images/Streams: trys nuliai reiškia, kad failas jau buvo lieknas
finally
Lib.Free;
end;
end;
Grandinę tikrinkite taip, kaip save tikrina pati biblioteka: apsukdami ratą. v3.130 regresijos testai sukuria dokumentą, jį įrašo, vėl įkelia, paleidžia optimizavimą, įrašo dar kartą, o tada tvirtina tris dalykus: kad išvestis mažesnė, kad grąžinti skaičiai atitinka lūkesčius ir kad optimizuoto failo pakartotinis įkėlimas vis dar išanalizuojamas bei atvaizduojamas. To sukūrimo, optimizavimo ir pakartotinio įkėlimo ciklo atkartojimas su jūsų pačių gamybinių failų imtimi, palyginant ištrauktą tekstą prieš ir po, yra vienos valandos investicija, sugaunanti integracijos klaidas gerokai anksčiau, nei klientas atsivers sugadintą sąskaitą
// Rato patikra: optimizuotas failas privalo įkeltis švariai
Lib := TPDFlib.Create;
try
Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
Assert(Lib.GetPageCount > 0);
finally
Lib.Free;
end;
Kur ši grandinė įsiterpia į sujungimo darbo eigą? Po sujungimo, ne jo metu. Sujungus pirma ir optimizavus vieną rezultatą, kiekvienas įterptas šriftas poaibinamas vieną kartą pagal visų panaudotų simbolių sąjungą, o ne kiekvienam šaltinio failui atskirai. Jei siaura vieta yra sujungimo našumas, PDF Library for Delphi siūlo baitų lygio greitąjį kelią, išvengiantį pilno objektų analizavimo, aprašytą straipsnyje apie greitą PDF sujungimą su baitinių nuorodų postūmiu; o įvestims, per didelėms, kad tilptų atmintyje, tiesioginės prieigos sujungimas ir skaidymas dideliems PDF aptaria srautinį kelią. Abu natūraliai dera su baigiamuoju optimizavimo praėjimu ant sujungtos išvesties
Ko šie trys praėjimai nedarys
losLab PDF Library optimizavimo trejetas sąmoningai neapima nieko, kas keistų dokumento semantiką. SubsetEmbeddedFonts nesuvienija pasikartojančių šriftų iš sujungtų šaltinių į vieną programą, jis kiekvieną sutraukia atskirai; dublikatų šalinimas yra kita, rizikingesnė transformacija. DownsampleImages praleis paveikslėlį, kurio konservatyvus DPI įvertis lieka žemiau slenksčio, net kai žmogus matytų, kad jis per didelis savo rėmeliui. Ir nė vienas praėjimas neliečia dokumento struktūros, todėl failui, išpūstam tūkstančių našlaičių objektų, reikia perrašančio įrašymo, o ne šių srautų lygio praėjimų. Šiose ribose šriftų poaibinimo, paveikslėlių raiškos mažinimo ir LZW keitimo į Flate derinys pašalina tris klasikinius PDF išsipūtimo šaltinius po vieną nuspėjamą API iškvietimą kiekvienam. Šios trys funkcijos pateikiamos kaip losLab PDF Library, skirtos Delphi, C# ir VB.NET, dalis kartu su aukščiau aptartais sujungimo, ištraukimo ir atvaizdavimo API