CollateDocumentsEx i PDF-biblioteket PDFlibPas for Delphi slår sammen flere åpne dokumenter til ett interleavet dokument. Den legger til GroupSize sider fra hver kilde per runde, aksepterer en sideområdeliste per kilde, og behandler et synkende område som 3-1 som en reversering av den kilden. Ett kall gjør en forsidestabel og en reversert baksidestabel om til leserekkefølge
Scenarioet bak dette API-et er hverdagslig og svært vanlig. En arkmatet skanner med enveis papirbane kjører hele stabelen med forsiden ned, deretter snur operatøren stabelen og kjører den igjen. Resultatet er to PDF-er: forsider i rekkefølge, baksider i omvendt rekkefølge. Det brukeren ønsker er én fil, side 1 forside, side 1 bakside, side 2 forside, og så videre. Denne artikkelen handler om rekkefølgeproblemet og ressursduplikasjonsfellen som ligger under det. Hvis du i stedet er opptatt av rå gjennomstrømning ved sammenslåing, se rask PDF-sammenslåing med byte-nivå ref-forskyvning; hvis inndataene er for store til å holdes i minnet i det hele tatt, se sammenslåing og splitting av gigabyte-store PDF-er med direktetilgang
Skanneren produserer to stabler, den ene baklengs
Kollasjonering er ikke sammenslåing. En sammenslåing kjeder sammen sideområder; en kollasjonering interleaver dem, og interleaving-mønsteret er en egenskap ved den fysiske enheten som produserte inndataene. Får du mønsteret feil, blir ikke filen litt feil, den blir ubrukelig: annenhver side hører til et annet ark. Tre variabler beskriver nesten alle reelle tilfeller: hvor mange kilder som er i rotasjonen, hvor mange sider som kommer fra hver kilde per runde, og om noen kilde må leses baklengs. CollateDocuments dekker de to første med en enkel array av dokumenthåndtak og et GroupSize-heltall. CollateDocumentsEx legger til det tredje ved å akseptere en semikolon-separert liste med sideområder, ett segment per kilde, hvor et tomt segment betyr alle sider for den kilden og et synkende område reverserer den. Begge funksjonene legger til på slutten av det valgte dokumentet og returnerer 1 ved suksess, 0 ved avvisning
Hvorfor multipliserer den naive kollasjoneringen filstørrelsen?
Fordi importkartet som mapper kildeobjektnumre til målobjektnumre bygges opp på nytt for hvert kopikall, og alt som er tilgjengelig fra mer enn én bit blir importert én gang per bit. Inne i PDFlibPas nullstiller TPDFDocument.CopyPagesFromDoc sin NewIndObjList ved starten av hvert kall. Den listen er det eneste minnet kopiereren har om hva den allerede har overført. Kall den én gang med et titallssideområde, og en font som deles av alle ti sidene blir bygget inn én gang. Kall den ti ganger med én side hver, og den samme fonten blir bygget inn ti ganger. Dette betyr langt mer for skann enn for tekstdokumenter, fordi en skannet side er et enkelt stort bilde-XObject og de delte objektene er de med reell vekt: en innebygd ICC-profil, en delt /DecodeParms-kjede, en stempel- eller vannmerke-form-XObject som brukes på hvert ark, OCR-tekstlagets font. Den opplagte måten å skrive en round-robin-kollasjonering på er en løkke over rundene, og den løkken er nettopp det patologiske tilfellet
// 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;
Tolv runder, to kilder, tjuefire importkart. Ingenting advarer deg. Siderekkefølgen er korrekt, hver side tegnes riktig, og det eneste symptomet er en fil som er flere ganger større enn summen av inndataene. På en 300-siders batch-jobb er multiplikatoren ikke en avrundingsfeil, den er forskjellen mellom et arkiv som passer i oppbevaringsbudsjettet og et som ikke gjør det
Importer én gang, omorganiser deretter sidetreet
Løsningen er å skille de to hensynene som den naive løkken hadde smeltet sammen. Kopiering avgjør hvilke objekter som finnes i målet; rekkefølge avgjør hvor sidene sitter i sidetreet. CollateDocumentsEx kopierer hver kilde nøyaktig én gang, i ett enkelt CopyPagesFromDoc-kall med hele kildens område, slik at hver kilde får ett importkart og delte ressurser skrives én gang. Først etter at hver kilde har landet skjer interleavingen, og den skjer utelukkende gjennom TPDFPageTree.MovePage
Sideflyttinger er gratis i den forstand som betyr noe her. ISO 32000-1 §7.7.3 definerer sidetreet som en balansert struktur av nodeordbøker hvis /Kids-arrayer inneholder indirekte referanser, med /Count som bærer bladtotalen på hver node. Å flytte en side betyr å fjerne én indirekte referanse fra én /Kids-array, sette den inn i en annen, justere begge /Count-verdier, og peke sidens /Parent om. Ingen innholdsstrøm blir rørt, ingen ressurs blir duplisert, ingen objekt blir opprettet. Sideobjektet beholder sitt objektnummer, som er grunnen til at objektnumrene forblir stabile på samme måte som i sideerstatning som bevarer objektnumre. Det er én ytterligere detalj som en naiv sideflytting gjør feil og som MovePage ikke gjør. ISO 32000-1 §7.7.3.4 lar /Resources, /MediaBox, /CropBox og /Rotate arves fra en forfedernode i stedet for å oppgis på siden. En side som arver sine ressurser fra node A og deretter flyttes under node B, arver stilltiende noe annet, eller ingenting i det hele tatt. MovePage løser derfor opp den arvede verdien og skriver den inn i sideordboken før flyttingen, slik at siden bærer sine egne attributter gjennom flyttingen
Hva gjør egentlig omorganiseringspasset?
Det kjører en seleksjonssortering mot sett-inn-på-semantikk. Den ønskede blokkrelative rekkefølgen beregnes først: gå gjennom kildene i rotasjon, ta opptil GroupSize indekser fra hver, hopp over en kilde som er uttømt, gjenta til hver side er plassert. Det gir en permutasjon over den vedlagte blokken. Å anvende den er den vanskelige delen, fordi MovePage er en innsetting, ikke en bytting, så hver flytting forskyver alt mellom den gamle og den nye posisjonen med én
Implementasjonen holder en Current-array som modellerer hvor hver vedlagte side befinner seg akkurat nå, skanner fremover fra posisjon K etter siden som hører hjemme der, utfører flyttingen, og glir deretter arrayoppføringene for å speile det flyttingen gjorde med treet. Det er O(n kvadrat) i arrayoperasjoner og null i objektkopier, som er den riktige avveiningen for denne arbeidsmengden: en 500-siders kollasjonering er en kvart million heltallsforflytninger og ikke en eneste byte duplisert bildedata. Synkende områder og gjentatte sider krever ingen spesialbehandling i dette passet fordi PLParsePageRangeList kalles med sortering deaktivert og duplikater tillatt, slik at den forespurte rekkefølgen overlever parsingen intakt
Reverserte områder og enkeltkallet for duplex-sammenslåing
Med reversering uttrykt som et område, kollapser det klassiske dobbeltpass-tilfellet med flatbed til ett enkelt kall. Forsidene vil ha sin naturlige rekkefølge og baksidene vil ha 12-1, og det tomme første segmentet foran semikolonet sier at den første kilden bidrar med alle sine sider
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;
To oppførsler i det utdraget er verdt å nevne eksplisitt. De kollasjonerte sidene legges til det valgte dokumentet, så et dokument opprettet med NewDocument bidrar med sin innledende blanke side foran dem, og du bør slette den hvis du ikke vil ha den. Og kildene kan være ujevne: med GroupSize 2 over en treside- og en femsidekilde, kommer rundene ut som A1 A2 B1 B2, deretter A3 B3 B4 når A nesten er brukt opp, deretter B5 alene, fordi en uttømt kilde ganske enkelt hoppes over i stedet for å fylles ut
Tilbakerulling, skjemafelter, og hva som ikke blir med
Hvert argument valideres før målet blir rørt. Et manglende dokumenthåndtak, det valgte dokumentet oppført som sin egen kilde, en GroupSize under én, et segmentantall som ikke stemmer med kildeantallet, et område som navngir en side kilden ikke har: alt dette returnerer 0 med målet uendret. Feil under kopiering er det vanskeligere tilfellet, og det håndteres gjennom den offentlige DeletePages i stedet for den rå PageTree.DeletePages. Grunnen er spesifikk. Kopieringen kjører med MergeFormData aktivert, så kildens skjemafelter er allerede lagt til målets /AcroForm /Fields-array når en senere kilde feiler. Å slette sidene på sidetre-nivå ville strippe widget-sidene og etterlate de feltreferansene hengende; den offentlige stien kobler fra feltet, disposisjonen og artikkeltråd-referansene sammen med sidene
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]));
Vær ærlig med brukerne dine om begrensningene. Kollasjoneringen tar med sider, deres annotasjoner og deres skjemafelter, og den slår sammen AcroForm-feltlisten, beregningsrekkefølge-arrayet og standardressursordboken. Den tar ikke med kildeoppslagslister (bokmerker): disposisjonstreet til en skannet forsidestabel er nesten alltid tomt, så ingenting går tapt i duplex-tilfellet, men hvis du kollasjonerer to redigerte dokumenter blir oppslagslistene deres liggende igjen og du må gjenoppbygge navigasjonen selv. Navngitte destinasjoner som bare fantes i kildekatalogen er i samme situasjon. Planlegg for det før du lover en kunde en tapsfri kollasjonering
PDFlibPas leverer kollasjoneringsfunksjonene sammen med resten av sin sammenstillingsflate for sider, slik at skannerarbeidsflyten, den områdebaserte utpakkingen og de store filstiene alle sitter bak én komponent i Delphi og C++Builder. Den fullstendige API-referansen og en prøveversjon finnes på produktsiden for losLab Delphi PDF-bibliotek