Teknisk artikel

Fletning af flere PDF-filer til ét dokument med PDFium Component

PDFium Component eksponerer PDF-fletning gennem en enkelt metode: ImportPages. Mønsteret er altid det samme: opret et tomt destinationsdokument, åbn hver kildefil, kald ImportPages for at kopiere siderne over, luk kilden, og gentag. Når løkken er færdig, skriver SaveAs resultatet til disk. Der er ingen speciel fletningstilstand, ingen konfiguration at ændre. Kompleksiteten lever i kanttilfældene, og der er et par stykker, der bider uden advarsel

Kerneløkken

To TPdf instanser er alt, hvad du behøver. Den ene holder destinationsdokumentet, oprettet tomt med CreateDocument. Den anden åbner hver kildefil på skift. Nedenfor er en procedure, der tager en liste over filstier og skriver det flettede output til en enkelt sti:

procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
  PdfDest, PdfSrc: TPdf;
  InsertAt, I: Integer;
begin
  PdfDest := TPdf.Create(nil);
  PdfSrc  := TPdf.Create(nil);
  try
    PdfDest.CreateDocument;
    InsertAt := 1;  // ImportPages uses 1-based destination position

    for I := 0 to FileList.Count - 1 do
    begin
      PdfSrc.FileName := FileList[I];
      PdfSrc.Active   := True;

      if not PdfSrc.Active then
        raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);

      PdfDest.ImportPages(
        PdfSrc,
        '1-' + IntToStr(PdfSrc.PageCount),  // full document range
        InsertAt);

      Inc(InsertAt, PdfSrc.PageCount);
      PdfSrc.Active := False;
    end;

    PdfDest.SaveAs(OutputPath);
  finally
    PdfSrc.Free;
    PdfDest.Free;
  end;
end;

To ting i denne kode er lette at overse ved første gennemlæsning. Den første er, hvordan PDFium rapporterer indlæsningsfejl. Active := True rejser aldrig en undtagelse: Hvis filen mangler, er beskadiget eller er adgangskodebeskyttet, fanger PDFium fejlen internt og efterlader Active som False. Uden den eksplicitte kontrol på linje 10 ville en dårlig fil i stilhed falde ud af fletningen uden nogen indikation i outputtet. Den endelige PDF ville have færre sider end forventet, og du ville ikke vide, hvilken fil der var synderen

Den anden er InsertAt tælleren. Det tredje argument til ImportPages er den 1-baserede position i destinationen, hvor den første importerede side lander. At starte på 1 sætter det første kildedokument i begyndelsen af en ellers tom fil. Efter hver kilde rykker tælleren frem med PdfSrc.PageCount, så den næste portion sider tilføjes efter den sidste. Glem at forøge den, og hver efterfølgende kilde overskriver sider på position 1, hvilket giver dig det sidste dokument på listen og intet andet

Selektive sideintervaller

Du behøver ikke tage hver side fra en kilde. Områdestrengen, der overføres som det andet argument, følger et simpelt komma-og-bindestreg format: "1-3" tager side 1 til 3, "2,4,6" vælger tre specifikke sider, og "1-" betyder side 1 til slutningen af dokumentet. Områder kan kombineres i en enkelt streng, så "1-3,5,7-" springer over side 4 og 6. En fiks detalje har betydning her: tallene henviser altid til sider i kildedokumentet, startende med 1, uanset hvor disse sider ender i destinationen. Hvis du vil have side 40 til 50 ud af et katalog på 200 sider, er områdestrengen "40-50", ikke en position i forhold til, hvad der allerede er i destinationen

// Extract cover plus a three-page executive summary from a long report
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active   := True;
if PdfSrc.Active then
begin
  // Page 1 is the cover; pages 3-5 are the summary
  PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
  Inc(InsertAt, 4);  // 1 cover + 3 summary pages = 4 pages added
  PdfSrc.Active := False;
end;

Når du beregner forøgelsen til InsertAt, skal du tælle de sider, du rent faktisk har importeret, ikke sideantallet for kilden. Hvis du overfører '1,3-5', importerede du 4 sider, så ryk frem med 4. At rykke frem med PdfSrc.PageCount ville efterlade et hul af tomme destinationspositioner og placere det næste kildedokument længere inde i filen end beregnet

Hvad ImportPages bevarer, og hvad den ikke gør

Sider kopieret af ImportPages bærer deres synlige indhold intakt. Tekst, vektorgrafik, rasterbilleder, indlejrede skrifttyper og form-XObjects overføres alle som en del af sidens indholdsstrømme. Annotationer på sideniveau, herunder kommentarer, fremhævelser og blækstrøg, kommer også med, fordi de gemmes i sideordbogen i stedet for på dokumentniveau

Dokumentniveau-metadata er en anden historie. Strengene for titel, forfatter, emne og nøgleord i kildens Info-ordbog bliver tilbage. Destinationsdokumentet starter med tomme metadata efter CreateDocument, så hvis det flettede output har brug for disse felter udfyldt, skal du tildele dem til PdfDest direkte før du kalder SaveAs. Egenskaberne Title, Author, Subject, Keywords og CreatorTPdf tager almindelige strenge og skriver ind i Info-ordbogen ved gem

Interaktive formularfelter er mere komplicerede. AcroForm-feltdefinitioner bor i en ordbog på dokumentniveau frem for inde i individuelle sidestrømme. Når ImportPages kopierer en side, der indeholder formularfelter, overføres disse felters visuelle udseende, fordi det gengives i sidens indholdsstrøm, men de felt-widgets, der gør dem interaktive, er en del af AcroForm-strukturen og følger ikke med. I en typisk fletning vil et tekstfelt fra et kildedokument vise den værdi, det havde på tidspunktet for importen, men det vil ikke kunne redigeres i den flettede fil. Hvis du har brug for, at felterne forbliver udfyldelige, skal du gøre dem flade i hvert kildedokument før import: Dette indbager de aktuelle værdier i indholdsstrømmen og fjerner det interaktive overlæg, hvilket giver dig et rent visuelt resultat uden ødelagte widgets i outputtet

Krypterede kildefiler

Adgangskodebeskyttede kildedokumenter åbner på samme måde som ukrypterede med en ekstra egenskab at indstille først. Tildel adgangskoden til PdfSrc.Password før du vipper Active := True, og PDFium vil bruge den under åbningen:

PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active   := True;
if not PdfSrc.Active then
  raise Exception.Create('Wrong password or file cannot be opened');

PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;

En forkert adgangskode forårsager samme tavse Active = False resultat som en manglende fil, så den eksplicitte kontrol er lige så nødvendig her. Krypteringen overføres ikke til destinationen: Sider importeret fra en beskyttet kilde lander i destinationen som ubeskyttet indhold. Hvis det flettede output også har brug for kryptering, skal du konfigurere den på PdfDest, før du kalder SaveAs

Gem resultatet

SaveAsTPdf accepterer enten en filsti eller en TStream. Til de fleste fletninger er fil-overbelastningen (overload) det, du ønsker:

PdfDest.SaveAs('merged-output.pdf');

Det valgfri andet argument er en TSaveOption, der styrer gemmetilstanden. Standarden, saNone, skriver en inkrementel opdatering, hvis dokumentet blev indlæst fra en fil, eller en komplet genskrivning, hvis det blev oprettet frisk. Da en destination bygget med CreateDocument altid er frisk, vil outputtet være en kompakt fil med en enkelt revision. Det tredje argument, TPdfVersion, lader dig fastgøre PDF-versionhovedet, når du har downstream-forbrugere, der kræver en bestemt version; at lade den stå på pvUnknown lader PDFium vælge baseret på indholdet

Metoderne ImportPages og SaveAs vist her er en del af PDFium Component til Delphi og C++Builder