CollateDocumentsEx u PDFlibPas Delphi PDF biblioteci spaja nekoliko otvorenih dokumenata u jedan isprepleteni dokument. Dodaje GroupSize stranica iz svakog izvora po krugu, prihvaća popis raspona stranica po izvoru i tretira silazni raspon poput 3-1 kao obrat tog izvora. Jednim pozivom prednji snop i obrnuti stražnji snop postaju redoslijed čitanja
Scenarij iza tog API-ja je svakodnevan i vrlo čest. Uređaj za skeniranje s jednostranim putom papira propušta cijeli snop licem prema dolje, a zatim operater okrene snop i pokrene ga ponovno. Rezultat su dva PDF-a: prednje stranice po redu, stražnje u obrnutom redoslijedu. Korisnik želi jednu datoteku, stranicu 1 prednju, stranicu 1 stražnju, stranicu 2 prednju i tako dalje. Ovaj članak bavi se problemom redoslijeda i zamkom dupliciranja resursa koja se krije ispod njega. Ako vas zanima sirova propusnost spajanja, pogledajte brzo spajanje PDF-a pomicanjem referenci na razini bajtova; ako su ulazi preveliki da stanu u memoriju, pogledajte spajanje i dijeljenje PDF-ova od nekoliko gigabajta uz izravan pristup
Skener proizvodi dva snopa, jedan unatrag
Slaganje nije isto što i spajanje. Spajanje ulančava raspone stranica; slaganje ih isprepliće, a obrazac isprepletanja je svojstvo fizičkog uređaja koji je proizveo ulaz. Pogrešite li obrazac, datoteka nije malo pogrešna, nego je neupotrebljiva: svaka druga stranica pripada drugom listu. Tri varijable opisuju gotovo svaki stvarni slučaj: koliko izvora sudjeluje u rotaciji, koliko stranica dolazi iz svakog izvora po krugu i treba li neki izvor čitati unatrag. CollateDocuments pokriva prve dvije jednostavnim nizom rukovatelja dokumentima i cijelim brojem GroupSize. CollateDocumentsEx dodaje treću prihvaćajući popis raspona stranica odvojen točkom-zarezom, jedan segment po izvoru, gdje prazan segment znači sve stranice tog izvora, a silazni raspon ga obrne. Obje funkcije dodaju na kraj trenutno odabranog dokumenta i vraćaju 1 pri uspjehu, 0 pri svakom odbijanju
Zašto naivno slaganje umnožava veličinu datoteke?
Zato što se mapa uvoza koja preslikava brojeve objekata izvora u brojeve objekata odredišta ponovno gradi pri svakom pozivu kopiranja, pa se sve dostupno iz više od jednog dijela uvozi jednom po dijelu. Unutar PDFlibPas-a, TPDFDocument.CopyPagesFromDoc resetira svoj NewIndObjList na početku svakog poziva. Ta lista jedina je memorija koju kopirač ima o onome što je već prenio. Pozovite je jednom s rasponom od deset stranica i font zajednički svim deset stranica ugrađuje se jednom. Pozovite je deset puta s po jednom stranicom i isti taj font ugrađuje se deset puta. Ovo je puno bitnije za skenove nego za tekstualne dokumente, jer je skenirana stranica jedan veliki slikovni XObject, a zajednički objekti su upravo oni sa stvarnom težinom: ugrađeni ICC profil, zajednički lanac /DecodeParms, žig ili vodeni pečat form XObjecta primijenjen na svaki list, font sloja OCR teksta. Očiti način pisanja round-robin slaganja je petlja preko krugova, a upravo je ta petlja patološki slučaj
// Do not do this. Each CopyPageRanges call rebuilds the import map,
// so anything the two sources share internally is imported once per
// round instead of once per source.
var
RoundIndex: Integer;
begin
for RoundIndex := 1 to 12 do
begin
PDF.CopyPageRanges(Fronts, IntToStr(RoundIndex));
PDF.CopyPageRanges(Backs, IntToStr(13 - RoundIndex));
end;
end;
Dvanaest krugova, dva izvora, dvadeset i četiri mape uvoza. Ništa ne upozorava. Redoslijed stranica je ispravan, svaka se stranica renderira, a jedini je simptom datoteka nekoliko puta veća od zbroja svojih ulaza. Kod obradbe od 300 stranica množitelj nije zaokruživanje pogreške, nego razlika između arhive koja stane u proračun čuvanja i one koja ne stane
Uvezi jednom, zatim presložiti stablo stranica
Rješenje je razdvojiti dvije brige koje je naivna petlja stopila u jedno. Kopiranje odlučuje koji objekti postoje u odredištu; slaganje odlučuje gdje stranice sjede u stablu stranica. CollateDocumentsEx kopira svaki izvor točno jednom, u jednom pozivu CopyPagesFromDoc s cijelim rasponom tog izvora, tako da svaki izvor dobiva jednu mapu uvoza, a zajednički se resursi zapisuju jednom. Tek nakon što je svaki izvor stigao, dolazi do isprepletanja, i ono se odvija u cijelosti kroz TPDFPageTree.MovePage
Pomicanje stranica je besplatno u smislu koji je ovdje bitan. ISO 32000-1 §7.7.3 definira stablo stranica kao uravnoteženu strukturu čvornih rječnika čiji nizovi /Kids sadrže neizravne reference, pri čemu /Count nosi ukupan broj listova na svakom čvoru. Premještanje stranice znači ukloniti jednu neizravnu referencu iz jednog niza /Kids, umetnuti je u drugi, prilagoditi obje vrijednosti /Count i preusmjeriti /Parent stranice. Nijedan se sadržajni tok ne dira, nijedan se resurs ne duplicira, nijedan se objekt ne stvara. Objekt stranice zadržava svoj broj objekta, što je i razlog zašto brojevi objekata ostaju stabilni na isti način kao u zamjeni stranica koja čuva brojeve objekata. Postoji još jedan detalj koji naivno pomicanje stranice pogrešno rješava, a MovePage ne. ISO 32000-1 §7.7.3.4 dopušta da se /Resources, /MediaBox, /CropBox i /Rotate naslijede od predačkog čvora umjesto da budu navedeni na stranici. Stranica koja nasljeđuje svoje resurse od čvora A, a zatim se premjesti pod čvor B, tiho nasljeđuje nešto drugo, ili ništa. MovePage stoga razrješava naslijeđenu vrijednost i zapisuje je u rječnik stranice prije premještanja, tako da stranica nosi svoja vlastita svojstva kroz premještaj
Što zapravo radi prolaz preslagivanja?
Provodi selection sort nasuprot semantici umetanja-na-poziciju. Željeni redoslijed relativan bloku izračunava se prvo: prolazi se kroz izvore u rotaciji, uzima se do GroupSize indeksa iz svakog, preskače se izvor koji je iscrpljen, ponavlja se dok svaka stranica ne bude smještena. To daje permutaciju nad dodanim blokom. Primjena je nezgodan dio, jer je MovePage umetanje, a ne zamjena, pa svaki pomak pomiče sve između stare i nove pozicije za jedno
Implementacija čuva niz Current koji modelira gdje se svaka dodana stranica trenutno nalazi, pretražuje unaprijed od pozicije K za stranicu koja pripada na K, izdaje pomak, zatim klizi unose niza da odraze što je pomak učinio stablu. To je O(n na kvadrat) u operacijama nizova, a nula u kopijama objekata, što je ispravan kompromis za ovo opterećenje: slaganje od 500 stranica je četvrt milijuna preraspoređivanja cijelih brojeva i ni jedan bajt dupliciranih slikovnih podataka. Silazni rasponi i ponovljene stranice ne trebaju posebno rukovanje u ovom prolazu jer se PLParsePageRangeList poziva s isključenim sortiranjem i dopuštenim duplikatima, tako da traženi redoslijed preživi analizu netaknut
Obrnuti rasponi i duplex spajanje jednim pozivom
Kad se obrat izrazi kao raspon, slučaj dvostrukog prolaza kroz flatbed skener sažima se u jedan poziv. Prednje stranice žele svoj prirodni redoslijed, a stražnje žele 12-1, a prazan prvi segment prije točke-zareza kaže da prvi izvor doprinosi svim svojim stranicama
var
PDF: TPDFlib;
Target, Fronts, Backs: Integer;
begin
PDF := TPDFlib.Create;
try
Target := PDF.NewDocument;
if PDF.LoadFromFile('fronts.pdf', '') <> 1 then
Exit;
Fronts := PDF.SelectedDocument;
if PDF.LoadFromFile('backs.pdf', '') <> 1 then
Exit;
Backs := PDF.SelectedDocument;
PDF.SelectDocument(Target);
// fronts 1..12 in order, backs scanned in reverse: F1 B12 F2 B11 ...
if PDF.CollateDocumentsEx([Fronts, Backs], ';12-1', 1) = 1 then
PDF.SaveToFile('duplex.pdf');
finally
PDF.Free;
end;
end;
Vrijedi izričito naglasiti dva ponašanja u tom isječku. Složene stranice dodaju se odabranom dokumentu, pa dokument stvoren pomoću NewDocument ispred njih doprinosi svojom početnom praznom stranicom, koju treba obrisati ako je ne želite. A izvori mogu biti neujednačeni: s GroupSize 2 nad izvorom od tri stranice i izvorom od pet stranica, krugovi ispadaju A1 A2 B1 B2, zatim A3 B3 B4 kad je A gotovo iscrpljen, pa naposljetku B5 sam, jer se iscrpljeni izvor jednostavno preskoči umjesto da se popunjava
Vraćanje unatrag, polja obrasca i ono što ne stiže s time
Svaki se argument provjerava prije nego se odredište uopće dotakne. Rukovatelj dokumenta koji nedostaje, odabrani dokument naveden kao vlastiti izvor, GroupSize manji od jedan, broj segmenata koji se ne poklapa s brojem izvora, raspon koji imenuje stranicu koju izvor nema: sve se to vraća kao 0 s nepromijenjenim odredištem. Neuspjeh tijekom kopiranja je teži slučaj, i rješava se kroz javni DeletePages, a ne kroz sirovi PageTree.DeletePages. Razlog je konkretan. Kopiranje se izvodi s uključenim MergeFormData, pa su polja obrasca izvora već dodana u niz /AcroForm /Fields odredišta do trenutka kad kasniji izvor zakaže. Brisanje stranica na razini stabla stranica uklonilo bi widget stranice i ostavilo te reference polja visećima; javni put odvezuje referencu polja, obrisa i lanaca članaka uz stranice
if PDF.CollateDocumentsEx([Fronts, Backs], ';12-1', 1) = 0 then
// Nothing was appended and the target is byte-identical to before.
// 412 is the copy failure; 0 means the arguments were rejected
// during validation, before any page was touched.
Log(Format('collate rejected, LastErrorCode=%d', [PDF.LastErrorCode]));
Budite iskreni s korisnicima o granicama. Slaganje nosi stranice, njihove napomene i njihova polja obrasca, i spaja popis polja AcroForm, niz redoslijeda izračuna i rječnik zadanih resursa. Ne nosi izvorne knjižne oznake: stablo obrisa skeniranog prednjeg snopa gotovo je uvijek prazno, pa se u duplex slučaju ništa ne gubi, ali ako slažete dva autorizirana dokumenta, njihovi obrisi ostaju iza, a navigaciju morate ponovno izgraditi sami. Imenovana odredišta koja su postojala samo u katalogu izvora u istom su položaju. Planirajte to prije nego korisniku obećate bezgubitno slaganje
PDFlibPas isporučuje funkcije slaganja zajedno s ostatkom svog sučelja za sastavljanje stranica, pa scenarij skenera, ekstrakcija temeljena na rasponima i putanje za velike datoteke svi sjede iza jedne komponente u Delphiju i C++Builderu. Potpuna referenca API-ja i probna verzija dostupni su na stranici proizvoda losLab Delphi PDF library