Brisanje strani iz PDF ne izbriše njenih pisav, slik ali vsebinskih tokov. losLab PDF Library jih povrne z zbiralnikom mark-sweep, ki prehodi graf objektov naprej od korenov trailerja in odstrani vsak posredni objekt, do katerega nič ne kaže. Zažene se ob polnem shranjevanju, privzeto je izklopljen in vrne število odstranjenih objektov
Zakaj brisanje strani PDF ne zmanjša datoteke?
Ker je brisanje strani urejanje referenc, ne pa operacija shranjevanja. DeletePages(StartPage, PageCount) odklopi objekte strani iz drevesa strani in popravi vnose orisa, ki so kazali nanje. Česar pa ne more storiti, je odločiti, da so program pisave, vsebinski tok in slikovni XObject, ki so ga te strani uporabljale, zdaj mrtvi, ker v trenutku brisanja nič v datoteki ne zabeleži, kdo drug morda še vedno kaže nanje. Ti objekti ostanejo v seznamu objektov dokumenta, polno shranjevanje pa jih vse zapiše nazaj. Rezultat je pritožba, s katero se začne večina teh podpornih niti: stranka izbriše devetdeset odstotkov strani, shrani, datoteka pa se zmanjša za dva odstotka. Še huje, uhajanje se kopiči. Nalaganje, brisanje, shranjevanje, ponovno nalaganje, ponovno brisanje, ponovno shranjevanje — datoteka monotono raste, medtem ko število strani pada. To je drugačna težava od tiste, ki jo rešujeta podnabor pisav in zmanjšanje ločljivosti slik, ki naredita žive objekte manjše. Tu objekti niso preveliki. Preprosto niso več del dokumenta
Korenski nabor je trailer, ne drevo strani
Graf objektov PDF nima polja za povratno referenco. Format ne definira niti štetja referenc niti seznama povratnih kazalcev, ključi /Parent, ki sicer obstajajo, pa pripadajo določenim strukturam, kot je drevo strani, ne pa grafu objektov kot celoti. Nič v posrednem objektu ne pove, kdo kaže nanj, zato ima vprašanje »ali kdo še vedno uporablja objekt 47« natanko en odgovor: prehodi naprej od znanega korena in poglej, ali prideš do njega. Zato je zbiralnik v losLab PDF Library mark-sweep zbiralnik, ne pa shema s štetjem referenc
Koreni izhajajo iz trailerja datoteke (ISO 32000-1 §7.5.5). Nosijo jih trije ključi: /Root, katalog dokumenta iz §7.7.2, na katerem visijo drevo strani, imena, oris, AcroForm in metapodatki; /Info, informacijski slovar dokumenta; in /Encrypt, slovar šifriranja. Preostala dva ključa trailerja sta vabi. /ID je polje dveh bajtnih nizov, /Prev pa je celoštevilski bajtni odmik na prejšnji razdelek navzkrižnih referenc. Nobeden od njiju ni posredna referenca, zato nobeden ne prispeva korena. losLab PDF Library v vrsto uvrsti celoten slovar trailerja namesto treh poimenovanih ključev, kar nič ne stane in ohrani pri življenju vsako zasebno razširitev trailerja
Sam prehod je iterativen, ne rekurziven. Ko prehod naleti na posredno referenco, zabeleži le številko objekta in generacijo, označi ustrezno mesto in jo potisne v vrsto FIFO namesto takojšnjega razreševanja, kar drži globoka drevesa strani in dolge verige orisa stran od klicnega sklada in prepreči, da bi bil isti objekt dekodiran dvakrat. Neposredni slovarji, polja in slovarji tokov gredo v drugo vrsto, varovano z naborom obiskanih, ker resnični dokumenti vsebujejo prave cikle: /Parent strani kaže nazaj na svoje vozlišče v drevesu strani, elementi orisa pa se verižijo prek /Prev in /Next v obe smeri. Številke generacij so del ujemanja, ne okrasek. Referenca se razreši le, ko se strinjata tako številka objekta kot generacija; referenca na številko, ki obstaja pri drugi generaciji, se obravnava kot ničelni objekt, ki ga zahteva specifikacija, nikoli kot živa povezava
Kako omogočite zbiranje smeti ob shranjevanju?
Zbiranje smeti je izbirno in spada v zapis možnosti shranjevanja. Privzeto je False, ker je zbiralnik destruktiven prehod čez graf objektov in nobena knjižnica ne sme tiho izbrisati objektov, ki jih klicatelj nikoli ni prosil za pregled
var
Pdf: TPDFlib;
Opt: TPDFlibSaveOptions;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile('report-500pages.pdf', '') <> 1 then
Exit;
Pdf.DeletePages(11, 490); // keep the first ten pages
FillChar(Opt, SizeOf(Opt), 0);
Opt.CompressContent := True;
Opt.CompressFonts := True;
Opt.OptimizeContentStreams := True;
Opt.PackObjectStreams := True;
Opt.GarbageCollect := True; // drop everything the pages left behind
Pdf.SaveToFileOptions('report-10pages.pdf', Opt);
finally
Pdf.Free;
end;
end;
Do istega zbiralnika vodita še dve drugi vstopni točki. SetGarbageCollect(1) nastavi zastavico na izbranem dokumentu, tako da jo navaden SaveToFile upošteva, GarbageCollectObjects pa prehod izvede takoj in vrne število odstranjenih osirotelih posrednih objektov. Takojšnjo obliko uporabite, kadar želite število za beleženje ali preverjanje, in vredno jo je preveriti, ker negativna vrnjena vrednost ni število
var
Removed: Integer;
begin
Pdf.DeletePages(11, 490);
Removed := Pdf.GarbageCollectObjects;
if Removed < 0 then
// The graph could not be fully decoded. Nothing was swept and the
// document is unchanged; save it without GC or reject the input.
LogWarning('object graph incomplete, GC skipped')
else
LogInfo(Format('reclaimed %d orphaned objects', [Removed]));
end;
Ta pot ob napaki je pomembnejša, kot se zdi na prvi pogled. Objekti se dekodirajo leno, objekt, ki še ni bil dekodiran, pa ne razkrije nobene reference. Če bi zbiralnik obravnaval nedekodljiv objekt kot prazno vozlišče, bi pomedel vse, kar je dosegljivo le skozenj. Zato prehod ob dotiku vsakega objekta prisili dekodiranje, ena sama napaka dekodiranja pa prekine celoten prehod z negativnim rezultatom in dokument pusti bajtno enak. Pometanje grafa, ki ga razumete le delno, je način, kako zbiralnik poškodovano datoteko spremeni v uničeno
Kaj podre naiven zbiralnik PDF?
Dve podrobnosti, obe pa odpovesta tiho, ne pa glasno. Prva so tokovi objektov. Od PDF 1.5 dalje lahko objekt brez toka živi stisnjen znotraj vsebnika /ObjStm (§7.5.7), njegov vnos navzkrižne reference pa je vnos tipa 2, ki poimenuje vsebnik ter indeks znotraj njega. Stisnjen objekt je zato dosegljiv le skozi svoj vsebnik. Označite člana, pometite vsebnik, ker ga nič ni referenciralo kot objekt dokumenta, in zapisali ste datoteko, katere xref kaže v objekt, ki ne obstaja več. Vsebnik je strukturno shranjevanje, ne podatki dokumenta, zato se nikoli ne pojavi kot povezava v grafu objektov, ki ga prehajate. losLab PDF Library to obravnava tako, da vsakega preživelega stisnjenega člana odklopi iz izvornega vsebnika, preden vsebniki izginejo, nato pa shranjevanje preživele znova zapakira v sveže tokove objektov. Druga podrobnost je, na kaj se objekt toka dejansko sklicuje. Bajti niso del grafa. Vsebinski tok, ki riše besedilo z /F1 12 Tf, poimenuje pisavo z imenom vira, to ime pa se razreši prek slovarja /Resources strani, zato povezava dosegljivosti poteka stran → /Resources → /Font → objekt pisave, nikoli skozi vsebino toka. Edine reference, ki jih objekt toka prispeva, prihajajo iz njegovega slovarja, kjer so /Length, /Filter in /DecodeParms vsi lahko posredni. Zbiralnik, ki razčlenjuje bajte toka v iskanju referenc, opravlja drago delo zaman; zbiralnik, ki preskoči slovarje tokov, izgubi objekt dolžine in pokvari datoteko
Kaj se zgodi s sproščenimi številkami objektov
Postanejo prosti vnosi in v istem shranjevanju se ne uporabijo znova. Pometanje prehodi seznam objektov v padajočem vrstnem redu, tako da brisanja ostanejo indeksno stabilna, indeks za iskanje na koncu obnovi enkrat namesto po vsaki odstranitvi, za vsak odstranjen objekt pa zabeleži številko na seznamu prostih z generacijo, povečano za ena, natanko tako, kot §7.5.4 predpisuje za vnos, ki se lahko kasneje ponovno uporabi. Generacija, ki je že pri 65535, tam ostane, kar to številko trajno upokoji. Številke objektov se namerno ne strnejo. Po zbiranju datoteka ohrani luknje: objekt 12 je lahko prost, medtem ko sta 13 in 14 v uporabi, trailer /Size pa še vedno poroča najvišjo številko plus ena, ne pa preživelega števila. To je zakonito in normalno. Preštevilčenje bi prihranilo peščico bajtov v tabeli navzkrižnih referenc in bi zahtevalo prepis vsake reference v dokumentu, kar je vrsta spremembe, ki tiho izniči vse, kar zunaj hrani številke objektov. Velikost, ki jo dobite nazaj, izvira iz teles objektov, ne iz tabele xref
Kdaj zbiralnika ne smete zagnati
Nikoli ob prirastni posodobitvi. Zbiralnik je omejen na polna shranjevanja in zastavica se preprosto ne prebere, ko se dokumentu dodaja, ta omejitev pa ni ovira, ki bi jo bilo treba obiti. Prirastna posodobitev (§7.5.6) pusti izvirne bajte nedotaknjene in doda nov razdelek navzkrižnih referenc, verižen s prejšnjim prek /Prev. Vsaka prejšnja revizija še vedno kaže na objekte, na katere je vedno kazala, zato je objekt, ki je nedosegljiv v trenutni reviziji, zelo dosegljiv v starejši. Njegovo brisanje bi podrlo vsako revizijo razen zadnje, mehanika tega pa je obravnavana v članku o prirastnih posodobitvah in shranjevanjih v načinu dodajanja. Enak razlog izključuje zbiranje smeti na podpisanem dokumentu, ker polni prepis, ki zbiranje sploh omogoči, sam izniči podpis
Vredno je tudi jasno povedati, kaj zbiranje ni. Ni sanitizator. Zbiralnik odstrani objekte, na katere nič ne kaže; nima mnenja o tem, ali je bila njihova vsebina občutljiva, objekt, na katerega se še vedno sklicuje, pa ostane, kar je bil. Če je cilj narediti informacije neobnovljive in ne datoteke manjše, je graf objektov napačen sloj, prava pa sta redakcija na ravni ukazov in sanitizacija dokumenta. Oba se dobro sestavljata v tem vrstnem redu: najprej redigirajte in sanitizirajte, nato zbirajte, tako da objekti, ki jih redakcija odklopi, dejansko zapustijo datoteko. Enako parjenje obstaja v API-ju za čiščenje virov, kjer možnost zbiranja smeti povzroči, da čiščenje nato izvede zbiranje in odstranjene sirote poroča v OrphanObjectsRemoved
Še ena navada, vredna sprejetja. Beležite vrnjeno vrednost GarbageCollectObjects v katerikoli paketni opravili, ki izvaja vaša brisanja strani, in jo opazujte skozi nekaj tednov resničnih dokumentov. Ničla na datoteki, ki ste jo pravkar prepolovili, pomeni, da nekaj višje v verigi še vedno drži referenco, ki je niste pričakovali, običajno vnos v drevesu imen, cilj orisa ali polje AcroForm, ki je preživelo stran, na katero je bilo pripeto. Zbiralnik je najcenejši razhroščevalnik dosegljivosti, ki ga boste kdaj imeli, ker odgovori na vprašanje, na katerega sam format PDF noče odgovoriti
Zbiralnik smeti, zapis možnosti shranjevanja in API za čiščenje virov, opisani tukaj, so del losLab PDF Library za Delphi in C++Builder, katerega stran izdelka nosi celoten referenčni opis cevovoda shranjevanja, vključno z medsebojnim delovanjem zbiranja, pakiranja tokov objektov in linearizacije