PDFium Component antaa PDF:n jakamiseen yhden metodin: ImportPages. Kaikki muu, olitpa eristämässä yhtä sivua, katkomassa tiedostoa mielivaltaisista kohdista tai seuraamassa asiakirjan omaa kirjanmerkkirakennetta, on vain erilaisia tapoja päättää mitkä sivunumerot menevät kuhunkin ulostulotiedostoon. Mekaniikka pysyy samana. Tämän ymmärtäminen varhain säästää monelta väärältä suunnalta
Miten jakosilmukka toimii
Malli on sama riippumatta siitä, miten lähdeasiakirja jaetaan. Luo uusi TPdf-instanssi, kutsu sillä CreateDocument-metodia alustamaan tyhjä PDF muistiin, tuo haluamasi sivut ImportPages-kutsulla, tallenna tulos ja aseta sitten Active arvoon False ennen seuraavaa iteraatiota. Juuri tuo viimeinen askel jää ihmisiltä usein tekemättä: CreateDocument ei sulje muistissa yhä olevaa asiakirjaa implisiittisesti, joten ulostulo täytyy tallentaa ja Active := False asettaa eksplisiittisesti ennen seuraavaa kutsua; nollaus ensin pitää tilan puhtaana ja hyvin määriteltynä. Ulompi TPdf-instanssi käytetään uudelleen kaikkien iteraatioiden ajan, mikä pitää allokointipaineen pienenä suurissa töissä
Tältä sivukohtainen jako näyttää riisuttuna olennaisimpiin osiin
procedure SplitIntoPages(Source: TPdf; const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 1 to Source.PageCount do
begin
PdfOut.CreateDocument;
// Range is a 1-based page number string; insertion point 1 = first position
if not PdfOut.ImportPages(Source, IntToStr(I), 1) then
raise Exception.CreateFmt('Failed to import page %d', [I]);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False; // reset before next CreateDocument
end;
finally
PdfOut.Free;
end;
end;
Range-parametri, joka annetaan ImportPages-metodille, käyttää samaa merkkijonomuotoa jota PDFium käyttää sisäisesti: pilkuilla erotettu lista sivunumeroita tai yhdysviivalla ilmaistuja alueita, kaikki 1-pohjaisina. '3' tuo sivun 3. '1-5' tuo sivut 1–5 järjestyksessä. '2,5,8' tuo nuo kolme sivua. Kolmas parametri on 1-pohjainen lisäyskohta kohdeasiakirjassa; arvon 1 antaminen sijoittaa tuodut sivut aina muuten tyhjän tiedoston alkuun, mikä on juuri sitä mitä tässä halutaan
Jakaminen sivualueiden mukaan
Kun kutsuja toimittaa listan kuten 1-12,13-24,25-36, se jäsennetään alku/loppu-pareiksi ja ajetaan sama silmukka, joka rakentaa jokaisesta parista alue-merkkijonon
procedure SplitByRanges(Source: TPdf; const RangeList: array of string;
const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(RangeList) do
begin
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeList[I], 1) then
raise Exception.Create('Invalid page range: ' + RangeList[I]);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Tässä validointi ennen kuin päästään ImportPages-kutsuun on tärkeää. ImportPages palauttaa False, kun alue-merkkijonossa oleva sivunumero ylittää arvon Source.PageCount, mutta se ei heitä poikkeusta eikä tuota osittaista ulostulotiedostoa, jota voisi päätellä pelkästä tiedostonimestä. Tarkista SaveAs-kutsun paluuarvo ja kirjaa epäonnistumiset erikseen; alue, joka tuottaa tyhjän ulostulotiedoston, ei näytä ilmeisen väärältä ennen kuin joku avaa sen
Jakaminen kirjanmerkkirajojen kohdalta
Kolmas lähestymistapa käyttää asiakirjan omaa rakennetta ulkopuolelta syötetyn listan sijasta. Jokaisella ylimmän tason kirjanmerkillä on kohdesivunumero; sen määrittelemä osio ulottuu kyseisestä sivusta seuraavan kirjanmerkin sivua edeltävään sivuun asti tai viimeisen merkinnän tapauksessa asiakirjan loppuun
procedure SplitByBookmarks(Source: TPdf; const OutputDir: string);
var
Bm: TBookmarks;
I, StartPage, EndPage: Integer;
PdfOut: TPdf;
RangeStr, OutFile, SafeTitle: string;
begin
Bm := Source.Bookmarks;
if Length(Bm) = 0 then
Exit;
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(Bm) do
begin
StartPage := Bm[I].PageNumber;
if I < High(Bm) then
EndPage := Bm[I + 1].PageNumber - 1
else
EndPage := Source.PageCount;
if (StartPage < 1) or (EndPage < StartPage) then
Continue;
RangeStr := Format('%d-%d', [StartPage, EndPage]);
PdfOut.CreateDocument;
if not PdfOut.ImportPages(Source, RangeStr, 1) then
begin
PdfOut.Active := False;
Continue; // skip a malformed section instead of writing an empty file
end;
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Asiakirja, jossa ei ole yhtään kirjanmerkkiä, ei ole virhetila jonka takia käyttäjälle pitäisi nostaa virhe; se tarkoittaa vain, ettei tällä jakotilalla ole mitään mihin tarttua. Tarkistus Length(Bm) = 0 hoitaa tämän hiljaa. Se mikä kannattaa nostaa esiin on tilanne, jossa kirjanmerkin sivunumero on asiakirjan rajojen ulkopuolella, mitä tapahtuu rikkinäisissä tiedostoissa joissa sisällysluetteloa ei päivitetty sivujen poistamisen jälkeen. Raja-arvotarkistus kentille StartPage ja EndPage ohittaa tällaiset merkinnät sen sijaan että syöttäisi roska-alueen ImportPages-kutsulle
Ulostulotiedostojen nimeäminen ja Active-tilan nollaus
Kirjanmerkeistä johdettujen nimien tiedostonimiturvallisuus vaatii erillistä huomiota. Kirjanmerkkien otsikot voivat sisältää merkkejä, jotka ovat sallittuja PDF-merkkijonossa mutta eivät tiedostojärjestelmän polussa. Vähintään etukeno, takakeno ja kaksoispiste kannattaa korvata ennen ulostulopolun rakentamista. Windowsissa myös *, ?, ", <, > ja | ovat kiellettyjä; yksinkertainen silmukka kiinteän merkkijoukon yli kattaa nämä ilman että tarvitaan säännöllisiä lausekkeita
Rivi Active := False jokaisen iteraation lopussa ansaitsee painotuksen, koska se on mallin ainoa epäitsestään selvä vaatimus. CreateDocument ei sulje mitään jo avattua implisiittisesti. Jos Active on edelleen True, kun CreateDocument ajetaan uudelleen, muistissa ollut asiakirja ei koskaan sulkeutunut eikä tallentunut kunnolla, eikä tuollaisessa tilassa kannata luottaa hyvin määriteltyyn käyttäytymiseen, joten tallenna ja nollaa tila eksplisiittisesti ennen seuraavan asiakirjan aloittamista. Ajattele sitä vastinparina try/finally-rakenteelle: finally lohko vapauttaa ulomman olion; Active := False nollaa sisemmän asiakirjatilan silmukan iteraatioiden välissä
Muistinkäyttö pysyy tasaisena suurissa jakotöissä tällä lähestymistavalla, koska muistissa ei pidetä koskaan enempää kuin yhtä ulostuloasiakirjaa kerrallaan. Lähdeasiakirja pysyy auki ja vain luku -tilassa koko ajan; ImportPages kopioi sivudatan uuteen asiakirjaan muuttamatta lähdettä. Jos lähde on salattu, avaa se salasanallaan ennen silmukkaa ja jokaisen ulostulotiedoston kopioidut sivut ovat salaamattomia, mikä on tavallisesti oikea käyttäytyminen, kun jaettu ulostulo menee eri vastaanottajille
Yksi lisähuomio SaveAs-metodista: se palauttaa Boolean-arvon. Ulostulohakemisto, jota ei ole olemassa, polku jossa on käyttöjärjestelmän hylkäämiä merkkejä, tai tilanne jossa levy on täynnä, saa SaveAs-kutsun palauttamaan False ilman poikkeusta. Eräajossa, joka jakaa 200-sivuisen asiakirjan 200 yksisivuiseksi tiedostoksi, hiljainen epäonnistuminen sivulla 147 on helppo ohittaa. Tarkista jokaisen kutsun paluuarvo ja vertaa onnistumisten määrää odotettuun kokonaismäärään, kun silmukka päättyy
Tässä näytetyt ImportPages- ja CreateDocument-metodit ovat osa Delphiä ja C++Builderia varten tarjottavaa PDFium Component -tuotetta