PDFiumコンポーネントは、単一のメソッドであるImportPagesを通じてPDFのマージを公開します。パターンは常に同じです:空の宛先ドキュメントを作成し、各ソースファイルを開き、ImportPagesを呼び出してページをコピーし、ソースを閉じて繰り返します。ループが終了すると、SaveAsが結果をディスクに書き込みます。特別なマージモードや切り替える設定はありません。複雑さはエッジケースに存在し、警告なしに噛み付くものがいくつかあります
コアループ
必要なのは2つのTPdfインスタンスだけです。1つはCreateDocumentで空として作成された宛先ドキュメントを保持します。もう1つは各ソースファイルを順番に開きます。以下は、ファイルパスのリストを受け取り、マージされた出力を単一のパスに書き込む手順です:
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;
このコードで最初読んだときに見落としやすい点が2つあります。1つ目は、PDFiumがロードの失敗を報告する方法です。Active := Trueは例外を発生させません:ファイルが見つからない、破損している、またはパスワードで保護されている場合、PDFiumは内部でエラーをキャッチし、ActiveをFalseのままにします。10行目の明示的なチェックがないと、不良ファイルは出力に兆候を示すことなくマージから静かに脱落します。最終的なPDFは予想よりもページ数が少なくなり、どのファイルが原因であるかがわからなくなります
2つ目はInsertAtカウンターです。ImportPagesの第3引数は、最初にインポートされたページが配置される宛先内の1ベースの位置です。1から開始すると、最初のソースドキュメントが、それ以外は空のファイルの先頭に配置されます。各ソースの後、カウンターはPdfSrc.PageCountずつ進むため、次のページのバッチは最後のバッチの後に追加されます。インクリメントするのを忘れると、後続のすべてのソースが位置1のページを上書きし、リストの最後のドキュメントだけが得られることになります
選択的なページ範囲
ソースからすべてのページを取得する必要はありません。第2引数として渡される範囲文字列は、シンプルなカンマとハイフンの形式に従います:"1-3"はページ1から3を取得し、"2,4,6"は3つの特定のページを選択し、"1-"はページ1からドキュメントの最後までを意味します。範囲は単一の文字列内で組み合わせることができるため、"1-3,5,7-"はページ4と6をスキップします。ここで1つの微妙な点が重要です:数値は常に、ページが宛先のどこに行き着くかに関係なく、1から始まるソースドキュメント内のページを指します。200ページのカタログからページ40から50が必要な場合、範囲文字列は"40-50"であり、すでに宛先にあるものからの相対的な位置ではありません
// 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;
InsertAtへのインクリメントを計算するときは、ソースのページ数ではなく、実際にインポートしたページ数をカウントします。'1,3-5'を渡した場合、4つのページをインポートしたため、4だけ進めます。PdfSrc.PageCountだけ進めると、空白の宛先位置のギャップが生じ、次のソースドキュメントが意図したよりもファイルの奥に配置されます
ImportPagesが保持するものとしないもの
ImportPagesによってコピーされたページは、その視覚的なコンテンツをそのまま保持します。テキスト、ベクターグラフィックス、ラスター画像、埋め込みフォント、およびフォームXObjectはすべて、ページコンテンツストリームの一部として転送されます。コメント、ハイライト、インクストロークなどのページレベルの注釈も、ドキュメントレベルではなくページ辞書内に保存されるため転送されます
ドキュメントレベルのメタデータは別の話です。ソースのInfo辞書にあるタイトル、作成者、サブジェクト、キーワード文字列は残されません。宛先ドキュメントはCreateDocumentの後に空のメタデータで開始されるため、マージされた出力でこれらのフィールドにデータを入力する必要がある場合は、SaveAsを呼び出す前にそれらを直接PdfDestに割り当てる必要があります。TPdfのTitle、Author、Subject、Keywords、およびCreatorプロパティは、プレーンな文字列を受け取り、保存時にInfo辞書に書き込みます
インタラクティブなフォームフィールドはさらに複雑です。AcroFormのフィールド定義は、個々のページストリーム内ではなく、ドキュメントレベルの辞書に存在します。ImportPagesがフォームフィールドを含むページをコピーするとき、それらのフィールドの視覚的な外観はページコンテンツストリームにレンダリングされるため転送されますが、それらをインタラクティブにするフィールドウィジェットはAcroForm構造の一部であり、追従しません。一般的なマージでは、ソースドキュメントのテキストフィールドはインポート時の値を表示しますが、マージされたファイルでは編集できません。フィールドを記入可能なままにする必要がある場合は、インポートする前に各ソースドキュメントでフラット化します:これにより、現在の値がコンテンツストリームに焼き付けられ、インタラクティブなオーバーレイが削除され、出力に壊れたウィジェットのないクリーンな視覚的結果が得られます
暗号化されたソースファイル
パスワードで保護されたソースドキュメントは、暗号化されていないドキュメントと同じ方法で開かれますが、最初に設定する追加のプロパティが1つあります。Active := Trueを切り替える前に、PdfSrc.Passwordにパスワードを割り当てると、PDFiumは開く間にそれを使用します:
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;
間違ったパスワードは、見つからないファイルと同じように静かにActive = Falseという結果を引き起こすため、ここでも明示的なチェックが必要になります。暗号化は宛先には転送されません:保護されたソースからインポートされたページは、保護されていないコンテンツとして宛先に配置されます。マージされた出力も暗号化する必要がある場合は、SaveAsを呼び出す前にPdfDestで設定します
結果の保存
TPdfのSaveAsは、ファイルパスまたはTStreamのいずれかを受け入れます。ほとんどのマージでは、ファイルのオーバーロードが必要です:
PdfDest.SaveAs('merged-output.pdf');
オプションの第2引数は、保存モードを制御するTSaveOptionです。デフォルトのsaNoneは、ドキュメントがファイルからロードされた場合は増分更新を、新しく作成された場合は完全な書き換えを書き込みます。CreateDocumentで構築された宛先は常に新しいため、出力はコンパクトな単一リビジョンのファイルになります。第3引数であるTPdfVersionを使用すると、特定のバージョンを必要とする下流コンシューマーがいる場合にPDFバージョンヘッダーを固定できます。pvUnknownのままにすると、PDFiumがコンテンツに基づいて選択します
ここで示されているImportPagesおよびSaveAsメソッドは、DelphiおよびC++Builder用のPDFiumコンポーネントの一部です