Technischer Artikel

Aufteilen von PDF-Dokumenten in mehrere Dateien mit der PDFium-Komponente

Das Aufteilen eines PDFs erfordert keine speziellen Extraktionsbefehle in der PDFium-API. Stattdessen bauen Sie es aus denselben Werkzeugen auf, die zum Zusammenführen verwendet werden: Erstellen Sie ein neues Dokument und importieren Sie die Seiten, die Sie behalten möchten. Diese Symmetrie hält die API klein, hat aber Leistungsauswirkungen, wenn man naiv damit umgeht. Hier erfahren Sie, wie Sie typische Aufteilungsaufgaben strukturieren, ohne den Speicher oder die Festplatte zu strapazieren

Grundlegendes Zerkleinern von Seiten

Die einfachste Form des Aufteilens besteht darin, jede Seite eines Quelldokuments als eigene Datei zu speichern. TPdf.ImportPages erledigt die Arbeit. Es benötigt ein Quelldokument, einen Seitenbereichs-String und den 1-basierten Zielindex. Das Zieldokument beginnt leer über CreateDocument, empfängt eine Seite und speichert. Das Versehen, das in diesem Muster oft gemacht wird, betrifft die Wiederverwendung des Ziels

procedure BurstPdf(const SourcePath: string; const OutDir: string);
var
  PdfSrc, PdfDest: TPdf;
  I: Integer;
begin
  PdfSrc := TPdf.Create(nil);
  PdfDest := TPdf.Create(nil);
  try
    PdfSrc.FileName := SourcePath;
    PdfSrc.Active := True;
    if not PdfSrc.Active then
      raise Exception.Create('Could not open source PDF');

    for I := 1 to PdfSrc.PageCount do
    begin
      PdfDest.CreateDocument;  // clears any previous content
      PdfDest.ImportPages(PdfSrc, IntToStr(I), 1);
      PdfDest.SaveAs(IncludeTrailingPathDelimiter(OutDir) +
                     'Page_' + IntToStr(I) + '.pdf');
    end;
  finally
    PdfSrc.Free;
    PdfDest.Free;
  end;
end;

Die Neuzuweisung von PdfDest innerhalb der Schleife mit CreateDocument stellt sicher, dass jede Datei ohne Reste der vorherigen Seite neu beginnt. Der Bereichsstring ist einfach die Seitennummer, die über IntToStr in einen String umgewandelt wird

Aufteilen in Stapel

Manchmal benötigen Sie keine einzelnen Seiten, sondern Dokumentenabschnitte – z. B. die Aufteilung eines 1000-seitigen Berichts in 100-seitige Blöcke zum E-Mail-Versand. Die Logik ist dieselbe, aber der Bereichsstring verwendet die Syntax "Start-Ende", und die äußere Schleife rückt abschnittsweise vorwärts

procedure SplitIntoChunks(const SourcePath: string; ChunkSize: Integer);
var
  PdfSrc, PdfDest: TPdf;
  StartPage, EndPage, PartNum: Integer;
  RangeStr: string;
begin
  // initialization omitted for brevity...
  PartNum := 1;
  StartPage := 1;

  while StartPage <= PdfSrc.PageCount do
  begin
    EndPage := Min(StartPage + ChunkSize - 1, PdfSrc.PageCount);
    RangeStr := Format('%d-%d', [StartPage, EndPage]);

    PdfDest.CreateDocument;
    PdfDest.ImportPages(PdfSrc, RangeStr, 1);
    PdfDest.SaveAs(Format('Part_%d.pdf', [PartNum]));

    Inc(StartPage, ChunkSize);
    Inc(PartNum);
  end;
end;

Durch die Verwendung der Min-Funktion aus System.Math verhindern wir, dass der Bereichsstring eine Endseite anfordert, die über das Ende des Quelldokuments hinausgeht, was bei PDFium normalerweise stillschweigend ignoriert wird, aber sicherheitshalber explizit festgezurrt werden sollte

Das Problem der Dateiaufblähung

Beim Aufteilen ist Ihnen vielleicht etwas Beunruhigendes aufgefallen: Eine 10 MB große Quelldatei, die in zehn Stücke zerlegt wird, könnte zehn Dateien ergeben, die jeweils 8 MB groß sind. Wenn man sie addiert, nimmt die Ausgabe weitaus mehr Speicherplatz ein als das Original. Das ist kein Bug in PDFium, sondern eine Folge davon, wie PDFs Ressourcenstrukturierung betreiben

Wenn ein PDF eine eingebettete 5-MB-Schriftart oder ein 2-MB-Logo hat, das im Seitenkopf verwendet wird, existiert diese Ressource in der Quelldatei genau einmal. Wenn ImportPages eine Seite in ein neues Dokument zieht, muss es alle Ressourcen einbringen, die diese Seite benötigt, um zu rendern. Wenn Sie Seite 1 als page1.pdf speichern, gehen die Schriftart und das Logo mit ihr. Wenn Sie Seite 2 als page2.pdf speichern, gehen dieselbe Schriftart und dasselbe Logo auch dorthin mit. PDFium versucht nicht, die internen Verweise zwischen separaten Dateien als intelligentere, gemeinsam genutzte Ressource umzuschreiben – jede aufgeteilte Datei muss ein eigenständiges, gültiges PDF sein, weshalb die Duplizierung unvermeidbar ist. Je ressourcenlastiger die Quelle, desto stärker fällt die Dateigrößenaufblähung beim Aufteilen auf

Lesezeichen und Metadaten werden nicht übertragen

ImportPages verlagert (kopiert) Inhaltsströme – Text, Bilder, Formularzeichnungen und Anmerkungen. Es überträgt keine Strukturen auf Dokumentebene. Das hat zur Folge, dass, wenn Ihr Quelldokument Inhaltsverzeichnis-Lesezeichen oder ein ausgefülltes Info-Wörterbuch (Titel, Autor) aufweist, diese verworfen werden

Wenn Ihre aufgeteilten Dokumente die Quell-Metadaten erfordern, müssen Sie diese nach CreateDocument für jede Datei explizit kopieren:

PdfDest.CreateDocument;
PdfDest.Title   := PdfSrc.Title;
PdfDest.Author  := PdfSrc.Author;
PdfDest.Subject := PdfSrc.Subject;
PdfDest.ImportPages(PdfSrc, RangeStr, 1);
PdfDest.SaveAs(...);

Lesezeichen können nicht übertragen werden, ohne die Baumknoten manuell zu durchlaufen und an neue Seitenziele im Zieldokument zuzuordnen, was außerhalb des Geltungsbereichs von ImportPages liegt

Aufteilen verschlüsselter Dokumente

PDFium öffnet verschlüsselte Dateien stillschweigend nicht, es sei denn, Sie geben das Passwort im Voraus an. Da das Aufteilen ein reiner Lesevorgang am Quelldokument ist, müssen Sie lediglich PdfSrc.Password zuweisen, bevor Sie es öffnen

PdfSrc.Password := 'my-secret-password';
PdfSrc.FileName := 'encrypted.pdf';
PdfSrc.Active := True;

Der Exportvorgang überträgt die Verschlüsselung nicht. Das von PdfDest.CreateDocument erstellte Zieldokument ist standardmäßig völlig unverschlüsselt. Wenn die aufgeteilten Stücke den Sicherheitsschutz der Quelle beibehalten müssen, müssen Sie PdfDest.Password (und die berechtigungsbezogenen Eigenschaften) festlegen, bevor Sie bei jeder Schleifeniteration PdfDest.SaveAs aufrufen

Threads und Speichernutzung

Wenn Sie viele Dokumente in einem Hintergrund-Thread aufteilen, instanziieren Sie TPdf innerhalb dieses Threads. Ein TPdf ist nicht darauf ausgelegt, zwischen Threads geteilt zu werden, während es aktiv Dateien verarbeitet. Das Aufteilen benötigt keine Benutzeroberfläche und kann sicher in TTask oder einer Threads-Schleife ausgeführt werden, sofern jeder Thread sein eigenes Quell- und Ziel-TPdf-Paar aufbaut und abbaut

Die hier gezeigten APIs ImportPages, CreateDocument und SaveAs sind Teil der PDFium-Komponente für Delphi und C++Builder