Želite jedan rječnik iz 2 GB PDF-a, a alat prvo raširi cijelu tablicu unakrsnih referenci u niz dimenzioniran po prikolici /Size. PDFiumPas taj korak zamjenjuje rijetkim lijenim indeksom objekata: zadržava samo opise xref sekcija, razrješuje jedan broj objekta na zahtjev kroz ograničene prozore i kešira samo unose koje ste stvarno dodirnuli
Stari oblik ovoga koda u FPdfCompress bio je pošten ali skup. ApplyDefaultOpenAction pročitao je cijelu datoteku u jedan TBytes, a zatim alocirao gusti niz TPdfActiveXrefEntries s jednim utorom po broju objekta do /Size. U mjerilu su dva puta pošla krivo. Cijena čitanja rasla je linearno s veličinom dokumenta i kad je pozivatelj htio četiri rječnika, a gusti niz sudarao se s proračunom parsera: TPdfParserResourceBudget.Default postavlja MaxObjects na 4,000,000, pa je sasvim valjana datoteka čiji najviši broj objekta sjedi iznad toga stropa odbijena argumentom memorije umjesto argumentom ispravnosti
Zašto javni API PDFiuma ne odgovara na ovo pitanje?
Jer informacija postoji unutar PDFiuma ali nikad ne prelazi C granicu. CPDF_Parser održava tablicu unakrsnih referenci, članstvo u tokovima objekata i prednost revizija interno, a objavljena zaglavlja ne izlažu ulaznu točku koja prima broj objekta i vraća njegov sirovi pomak, generaciju, koja je revizija pobijedila ili u kojem ObjStm živi. Strana spremanja je jednako zatvorena: FPDF_SaveAsCopy i FPDF_SaveWithVersion vam predaju samo povratni poziv za slijedno pisanje. Svaka popravka na razini bajtova nad katalogom nakon izvornog spremanja zato mora biti građena u Pascal sloju, pošto PDFiumPas te strukture raščlanjuje sam umjesto da ponovno koristi DLL
Što rijetki indeks stvarno drži u memoriji?
Opise, ne unose. Za klasičnu tablicu (ISO 32000-1 §7.5.4) TPdfSparseXrefSubsection sprema prvi broj objekta, broj objekata, bajtni pomak gdje redovi unosa počinju i izmjerenu širinu unosa. Sami unosi ostaju u datoteci. Širina se mjeri iz prvog reda umjesto da se pretpostavi 20 bajtova, jer se proizvođači ne slažu oko završetaka redaka; PDFiumPas prima 18 do 64 i odbija sve izvan toga pojasa, uz svaku podsekciju čiji bi deklarirani broj odletio iza kraja toka. Za tok unakrsnih referenci (§7.5.8) sekcija drži tri širine polja /W, svaku ograničenu na 0 do 8, izravnane parove /Index i dekodirane bajtove unosa, čija se očekivana duljina računa iz /W i /Index prije nego što se jedan jedini bajt naduje
Cijeli indeks gradi Initialize iz repnog prozora najviše 1 MiB, gdje se nalazi startxref, a svako naknadno čitanje objekta koristi objektni prozor od 1 MiB. Strop sirovog toka je 64 MiB i jedan xref redak ne smije prijeći 1024 bajtova. Ako ste čitali našu napomenu o provjeri tokova objekata i unakrsnih referenci s PDFiumPasom, ista disciplina širine polja vrijedi i ovdje, samo se sada koristi da se adresira jedan unos umjesto da se revizira cijela tablica
uses
FPdfCompress;
var
Source: TFileStream;
Revision: TPdfSparseRevisionInfo;
begin
Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
{ walks startxref, the /Prev chain and the catalog only }
if ReadPdfSparseRevisionInfo(Source, Revision) then
begin
Writeln('root ', Revision.RootObjectNumber, ' ',
Revision.RootGeneration);
Writeln('max obj ', Revision.MaximumObjectNumber);
Writeln('xref str ', Revision.UsesXrefStream);
Writeln('encrypted ', Revision.HasEncrypt);
Writeln(string(Revision.CatalogDictionary));
end;
finally
Source.Free;
end;
end;
Kako jedno traženje dođe do jednog objekta?
Aritmetikom, u oba rasporeda. Klasična podsekcija ima redove fiksne širine, pa je adresa unosa početak podsekcije plus pomak objekta puta izmjerena širina; PDFiumPas zatim čita taj jedan redak, raščlanjuje deseterocifreni pomak i peterocifrenu generaciju, provjerava generaciju prema stropu 65535 iz §7.5.4 i svrstava završnu ključnu riječ kao axkDirect ili axkFree. Tok unakrsnih referenci treba jedan korak više jer su /Index podsekcije spojene u dekodiranom bajtnom nizu, pa indeks akumulira brojeve prethodnih podsekcija prije množenja zbrojenom širinom /W. Tip 1 daje pomak, tip 2 daje broj toka objekata i indeks člana, a sve ostalo postaje axkUnknown umjesto nagađanja
{ classic table, ISO 32000-1 section 7.5.4 }
EntryOffset := Subsection.EntryOffset +
Int64(ObjectNumber - Subsection.FirstObject) * Subsection.EntryWidth;
{ cross-reference stream, ISO 32000-1 section 7.5.8 }
EntryWidth := Section.Widths[0] + Section.Widths[1] + Section.Widths[2];
EntryPosition := Integer((PriorCount + ObjectNumber -
Section.IndexValues[I]) * EntryWidth);
Ništa u nijednom putu nije proporcionalno /Size. U tome je cijela poanta prepravke: vrijednost veličine prikolice nosi se naprijed kao metapodatak i koristi pri pisanju inkrementalne revizije, ali nikad ne pokreće alokaciju. Regresijski skup ovo pribija fiksturom čije drvo stranica živi na objektu 1,000,000,000 i 1,000,000,001 pod prikolicom koja deklarira /Size 1000000002. Stara gusta izvedba tu je datoteku odbijala; rijetki indeks razrješuje obje reference i čuva deklariranu veličinu u izlaznoj prikolici
Hibridne revizije, lanci /Prev i osigurači oko njih
Prednost revizija je mjesto gdje naivan lijeni indeks promaši. PDFiumPas prolazi lanac od startxref u redoslijedu od najnovije i zaustavlja traženje na prvoj sekciji koja odgovori, čime reproducira pravilo prednosti bez materijaliziranja spojene tablice. Hibridne referentne datoteke (§7.5.8.4) rukuju se unutar klasične grane: kad prikolica nosi /XRefStm, dopunska sekcija toka registrira se prije klasične sekcije koja ju je imenovala, pa se kompresirani objekti nevidljivi običnoj tablici ipak nalaze dok klasični unosi zadržavaju svoj stupanj. Starije revizije slijede se kroz /Prev
Dva osigurača vežu taj obilazak, i oba su bitna na oštećenim datotekama. Svaki posjećeni pomak bilježi se, pa /Prev koji pokazuje natrag u lanac staje umjesto da se vrti, a dubina obilaska je ograničena s MaxRecursionDepth, koji je po zadanom 1024. Zastavica šifriranja akumulira se kroz cijeli lanac umjesto da se čita samo iz najnovije prikolice, jer dokument čija zadnja prikolica izostavlja /Encrypt može biti šifriran dublje u nazad; pozivatelji koji dopisuju revizije oslanjaju se na tu zastavicu da odbiju pisanje objekata otvorenog teksta u šifriranu datoteku
Unosi tipa 2: zašto tok objekata čeka
Unos tipa 2 imenuje tok objekata, i PDFiumPas ne dira taj tok dok pozivatelj ne zatraži člana njega. Kad konačno to učini, /Type /ObjStm se potvrđuje, /N se provjerava prema proračunu objekata i /First prema stropu dekodiranih bajtova, a /N se provjerava i protiv /First jer svaki par zaglavlja treba najmanje četiri bajta. Tek tada se tok naduje, a pregled zaglavlja staje na zatraženom članu i njegovom sljedbeniku umjesto da gradi punu tablicu članova. Jedan dekodirani tok objekata zadržava se u jednom trenu, što je pravi kompromis kad se grana drveta stranica nakupi u jedan ObjStm; naš prikaz o dekodiranju tokova objekata i prediktora u Delphiju pokriva što se događa unutar toga koraka dekompresije (§7.5.7)
var
Reader: TPdfSparseDictionaryReader;
Generation: Integer;
Dict: AnsiString;
begin
{ one retained index, many generation-aware reads }
Reader := TPdfSparseDictionaryReader.Create(Source);
try
if Reader.Valid and
Reader.ReadLatestDictionary(PageObjectNumber, Generation, Dict) then
HandlePage(PageObjectNumber, Generation, Dict);
finally
Reader.Free; { Source stays yours }
end;
end;
Gdje keš prestaje davati obećanja
Indeks je snimak, i vrijedi biti prizemljen o tome. Sekcije se raščlanjuju jednom u Initialize; ako se temeljni tok poslije izmijeni, svaki keširani unos je zastario i razred to neće primijetiti. TPdfSparseDictionaryReader drži indeks za trajanje vlasništva pozivatelja nad izvorom, što je točno ono što rekurzivni obilazak drveta stranica hoće i točno ono što ne smijete raditi preko prepravke. Keš unosa je ravni niz pretraživan linearno i sprema i negativne rezultate, pa su stotine traženja jeftine a stotine tisuća nisu. ReadDictionary traži točno poklapanje generacije dok ReadLatestDictionary razrješuje aktivnu, i razlika je namjerna: razrješivanje referenci treba prvo, pregled kataloga drugo. Gdje se te granice ne mogu poštovati, okolne jedinice vraćaju se na nasljedni parser cijele datoteke umjesto da suze skup datoteka koje još rade, obrazac koji koristimo i za slijedno strujanje velikih PDF-ova na zahtjev
Regresije preko kompilatora pokrivaju isto ponašanje na sva tri alatna lanca, uključujući tvrdnju da 2 MiB izvor nikad ne vidi jedno čitanje veće od 1 MiB. Ako održavate Delphi, C++Builder ili Lazarus kod koji dotiče PDF strukturu izravno i umorili ste se od plaćanja troškova parsiranja cijele datoteke za četiri rječnika, rijetki indeks i javni šav oko njega stižu u PDFiumPas Delphi PDFium komponenti