Želite jedan rečnik iz 2 GB PDF-a i alat prvo razvija celu tablicu unakrsnih referenci u niz dimenzionisan trailerovim /Size. PDFiumPas zamenjuje taj korak retkim lenjim indeksom objekata: zadržava samo deskriptore xref sekcija, razrešava jedan broj objekta na zahtev kroz ograničene prozore, i kešira samo unose koje ste zaista dotakli
Stari oblik ovog koda u FPdfCompress bio je pošten ali skup. ApplyDefaultOpenAction je čitao ceo fajl u jedan TBytes, pa dodeljivao gust TPdfActiveXrefEntries niz sa jednim slotom po broju objekta do /Size. Dve stvari su pošle naopako na razmeri. Cena čitanja je rasla linearno sa veličinom dokumenta i kad je pozivalac hteo samo četiri rečnika, a gusti niz se sudarao sa budžetom parsera: TPdfParserResourceBudget.Default postavlja MaxObjects na 4.000.000, pa je savršeno validan fajl čiji najviši broj objekta sedi iznad tog plafona bio odbijen na memorijskom argumentu a ne na ispravnosti
Zašto javni API PDFium-a ne odgovara na ovo pitanje?
Zato što informacija postoji unutar PDFium-a ali nikad ne prelazi C granicu. CPDF_Parser održava tablicu unakrsnih referenci, članstvo u objektnim tokovima i prednost revizije interno, a objavljeni headeri ne izlažu ulaznu tačku koja prima broj objekta i vraća njegov sirovi ofset, njegovu generaciju, koja je revizija pobedila ili u kom ObjStm živi. Strana čuvanja je jednako zatvorena: FPDF_SaveAsCopy i FPDF_SaveWithVersion vam daju samo sekvencijalni write callback. Svaka zakrpa kataloga na nivou bajtova posle nativnog čuvanja zato mora biti izgrađena u Pascal sloju, po čemu PDFiumPas sam raščlanjuje ove strukture umesto da ponovo koristi DLL
Šta retki indeks zaista drži u memoriji?
Deskriptore, ne unose. Za klasičnu tablicu (ISO 32000-1 §7.5.4) TPdfSparseXrefSubsection čuva prvi broj objekta, broj objekata, bajt ofset gde redovi unosa počinju i izmerenu širinu unosa. Unosi sami ostaju u fajlu. Širina se meri iz prvog reda umesto da se pretpostavi 20 bajtova, jer se proizvođači ne slažu oko završetaka linija; PDFiumPas prihvata 18 do 64 i odbija bilo šta van tog opsega, zajedno sa svakom podsekcijom čija bi deklarisana količina pretrčala kraj toka. Za tok unakrsnih referenci (§7.5.8) sekcija drži tri širine polja /W, svaku ograničenu na 0 do 8, spljoštene parove /Index, i dekodirane bajtove unosa, čija se očekivana dužina računa iz /W i /Index pre nego što se jedan jedini bajt razvije
Celi indeks gradi Initialize iz repnog prozora od najviše 1 MiB, gde se nalazi startxref, i svako naredno čitanje objekta koristi objektni prozor od 1 MiB. Plafon sirovog toka je 64 MiB i jedna xref linija ne sme preći 1024 bajta. Ako ste čitali našu belešku o validaciji objektnih i tokova unakrsnih referenci sa PDFiumPas-om, ista disciplina širine polja važi ovde, samo se sada koristi da adresuje jedan unos umesto da audituje celu tablicu
uses
FPdfCompress;
var
Source: TFileStream;
Revision: TPdfSparseRevisionInfo;
begin
Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
{ prelazi samo startxref, lanac /Prev i katalog }
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 ofset objekta puta izmerena širina; PDFiumPas zatim čita taj jedan red, raščlanjuje desetocifreni ofset i petocifrenu generaciju, proverava generaciju protiv plafona 65535 iz §7.5.4, i klasifikuje završnu ključnu reč kao axkDirect ili axkFree. Tok unakrsnih referenci treba još jedan korak jer su /Index podsekcije nadovezane u dekodiranom bajt nizu, pa indeks akumulira količine prethodnih podsekcija pre množenja zbirom širina /W. Tip 1 daje ofset, tip 2 daje broj objektnog toka i indeks člana, a sve ostalo postaje axkUnknown umesto nagađanja
{ klasična tablica, ISO 32000-1 odeljak 7.5.4 }
EntryOffset := Subsection.EntryOffset +
Int64(ObjectNumber - Subsection.FirstObject) * Subsection.EntryWidth;
{ tok unakrsnih referenci, ISO 32000-1 odeljak 7.5.8 }
EntryWidth := Section.Widths[0] + Section.Widths[1] + Section.Widths[2];
EntryPosition := Integer((PriorCount + ObjectNumber -
Section.IndexValues[I]) * EntryWidth);
Ništa u bilo kojem putu nije proporcionalno /Size. U tome je poenta prepisivanja: vrednost veličine trailer-a se prenosi dalje kao metapodatak i koristi se pri upisu inkrementalne revizije, ali nikad ne pokreće dodelu memorije. Regresiona suita ovo pribija fiksturom čije drvo stranica živi na objektu 1.000.000.000 i 1.000.000.001 pod trailerom koji deklariše /Size 1000000002. Stara gusta implementacija je odbila taj fajl; retki indeks razrešava obe reference i čuva deklarisanu veličinu u izlaznom traileru
Hibridne revizije, lanci /Prev i stražari oko njih
Prednost revizije je mesto gde naivan lenji indeks greši. PDFiumPas prelazi lanac od startxref redosledom od najnovije i zaustavlja traženje na prvoj sekciji koja odgovori, što reprodukuje pravilo prednosti bez materijalizacije spojene tablice. Hibridno-referencirani fajlovi (§7.5.8.4) se rukuju unutar klasične grane: kad trailer nosi /XRefStm, dopunska sekcija toka se registruje pre klasične sekcije koja ju je imenovala, pa se komprimovani objekti nevidljivi običnoj tablici ipak nalaze dok klasični unosi zadržavaju svoj stav. Starije revizije se zatim prate kroz /Prev
Dva stražara ograničavaju taj obilazak, i oba su važna na oštećenim fajlovima. Svaki posjećeni ofset se beleži, pa /Prev koji ukazuje nazad u lanac terminira umesto da vrti u krug, a dubina obilaska je ograničena sa MaxRecursionDepth, koja po podrazumevanom iznosi 1024. Flag šifrovanja se akumulira kroz ceo lanac umesto da se čita samo iz najnovijeg trailer-a, jer dokument čiji poslednji trailer izostavlja /Encrypt može i dalje biti šifrovan dalje unazad; pozivaoci koji dodaju revizije oslanjaju se na taj flag da odbiju upis otvorenog teksta u šifrovani fajl
Tip-2 unosi: zašto objektni tok čeka
Tip-2 unos imenuje objektni tok, i PDFiumPas ne dodiruje taj tok dok pozivalac ne zatraži člana njega. Kad konačno dodirne, /Type /ObjStm se verifikuje, /N se proverava protiv budžeta objekata i /First protiv plafona dekodiranih bajtova, i /N se proverava protiv /First pošto svaki par headera treba najmanje četiri bajta. Tek tada se tok razvija, i skeniranje headera se zaustavlja na zatraženom članu i njegovom nasledniku umesto da gradi punu tablicu članova. Jedan dekodirani objektni tok se zadržava u datom trenutku, što je pravi kompromis kad se grana drveta stranica grupiše u jedan ObjStm; naš tekst o dekodiranju objektnih tokova i prediktora u Delphi-ju pokriva šta se dešava unutar tog koraka razvijanja (§7.5.7)
var
Reader: TPdfSparseDictionaryReader;
Generation: Integer;
Dict: AnsiString;
begin
{ jedan zadržani indeks, mnogo čitanja svesnih generacije }
Reader := TPdfSparseDictionaryReader.Create(Source);
try
if Reader.Valid and
Reader.ReadLatestDictionary(PageObjectNumber, Generation, Dict) then
HandlePage(PageObjectNumber, Generation, Dict);
finally
Reader.Free; { Source ostaje vaš }
end;
end;
Gde keš prestaje davati obećanja
Indeks je snimak, i vredi biti direktan oko toga. Sekcije se raščlanjuju jednom u Initialize; ako se tok ispod kasnije izmeni, svaki keširani unos je zastareo i klasa to neće primetiti. TPdfSparseDictionaryReader drži indeks tokom životnog veka izvora u vlasništvu pozivaoca, što je tačno ono što rekurzivan obilazak drveta stranica želi i tačno ono što ne smete raditi preko prepisivanja. Keš unosa je ravan niz pretraživan linearno i čuva i negativne rezultate, pa je nekoliko stotina traženja jeftino a nekoliko stotina hiljada nije. ReadDictionary zahteva tačno poklapanje generacije dok ReadLatestDictionary razrešava aktivnu, i razlika je namerna: razrešavanje referenci treba prvo, inspekcija kataloga drugo. Gde se ove granice ne mogu poštovati, okružne jedinice se vraćaju na nasleđeni parser celog fajla umesto da suze skup fajlova koji još rade, obrazac koji koristimo i za strujanje velikih PDF-ova na zahtev
Unakrsno-kompajlerske regresije 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 direktno dodiruje PDF strukturu i umorili ste se od plaćanja troškova parsiranja celog fajla za četiri rečnika, retki indeks i javni šav oko njega isporučuju se u PDFiumPas Delphi PDFium komponenti