チュートリアル

簡単にライブラリーを使用するためのチュートリアルコレクション

チュートリアル»Java»Spire.XLS for Java»セル»Java:Excel の行、列、セルを書式付きでコピーする方法
2026-10-13

C# で PDF をフラット化する方法:フォームフィールド、注釈、レイヤー

C# と Spire.PDF で PDF をフラット化 -- フォームフィールド、注釈、レイヤーが固定ページコンテンツに変換される

PDF のフラット化(Flatten) とは、ドキュメント内のインタラクティブオブジェクトを固定ページコンテンツに変換する処理です。入力済みのフォームフィールドは入力コントロールを失い、その値はテキストとしてページに残ります。レビューアーのコメントはページ外観の一部となり、オプションコンテンツレイヤーは現在表示されているコンテンツから各ページを再構築することで削除されます。フラット化後のドキュメントは、個々の PDF ビューアーのインタラクティブ機能に依存しなくなります。ただし、ページコンテンツ自体は PDF エディタで編集可能です。

本ガイドでは、Spire.PDF for .NET を使用して C# で PDF をフラット化 する方法を解説します。フォーム全体の一括フラット化、指定フィールドのみのフラット化、全ページの注釈フラット化、SVG を経由したレイヤーのフラット化を取り上げ、最後に検証手順を示します。フラット化されたファイルは元のファイルとほぼ同じ外観を持つため、差異は構造的であり視覚的ではありません。

クイックナビゲーション


1. Spire.PDF for .NET のインストール

Spire.PDF for .NET は NuGet パッケージとして提供されています。.NET CLI でインストールします。

dotnet add package Spire.PDF

本記事の例は Spire.PDF for .NET 12.9.18 で動作確認しています。

Spire.PDF for .NET のダウンロードから手動でプロジェクトに追加することも可能です。

フラット化に関わる名前空間は以下の 3 つです。

名前空間 含まれるクラス
Spire.Pdf PdfDocument、PdfPageBase、FileFormat
Spire.Pdf.Widget、Spire.Pdf.Fields PdfFormWidget および PdfField -- フォーム関連
Spire.Pdf.Interactive.Annotations PdfAnnotation、PdfAnnotationCollection -- 注釈関連、Flatten() を含む

2. PDF フラット化の役割

フラット化はインタラクティブオブジェクトの外観をページコンテンツに押し込み、オブジェクト自体を削除します。目的は可視外観を保ちつつインタラクティブ性を除去することです。だからこそ本ガイドでは、スクリーンショットを比較するのではなく、保存されたファイルに残っているオブジェクト数をカウントして結果を検証します。

オブジェクトの種類ごとに呼び出し方が異なります。

オブジェクト フラット化方法
フォームフィールド(テキストボックス、チェックボックス、リストボックス、コンボボックス) PdfForm.IsFlatten でフォーム全体、PdfField.Flatten で個別フィールド
注釈(コメント、ハイライト、スタンプ、フリーテキスト) 各注釈に PdfAnnotation.Flatten() を呼び出し
レイヤー(オプションコンテンツグループ) PDF → SVG → PDF 変換で各ページを可視コンテンツから再構築

3. PDF フォーム全体をフラット化

Form.IsFlatten を true に設定するだけで、ドキュメント内の全フィールドが一度の処理で変換されます。

using Spire.Pdf;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");

// すべてのフィールドとその現在の値がページコンテンツになります。
pdf.Form.IsFlatten = true;

pdf.SaveToFile("FlattenedForm.pdf");
pdf.Close();

ここで使用した登録フォーム(5 つのフィールド、うち 3 つ入力済み)では、フィールド数が 5 から 0 に減少します。入力値は選択可能なテキストとしてページに残ります。

登録フォームのフラット化前後 -- ページの見た目は変わらず、5 つのインタラクティブフィールドが消滅

4. 指定した PDF フォームフィールドをフラット化

より一般的な要件は、入力完了済みのフィールドのみをロックすることです。完成したセクションを読み取り専用にしつつ、残りのフォームは入力可能なまま残します。フィールドには PdfFormWidget の FieldsWidget コレクションを経由してアクセスし、各フィールドの Flatten はブール型プロパティです。

using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");

PdfFormWidget formWidget = pdf.Form as PdfFormWidget;

foreach (PdfField field in formWidget.FieldsWidget)
{
    // 確定済みフィールドをフラット化し、残りは入力可能なままにする。
    if (field.Name == "full_name" || field.Name == "email" || field.Name == "department")
    {
        field.Flatten = true;
    }
}

pdf.SaveToFile("PartiallyFlattenedForm.pdf");
pdf.Close();

サンプルフォームでは 3 つのフィールドがフラット化され、start_date と manager は編集可能なまま残ります。フィールド数は 5 から 2 に減少します。フィルタ条件はフォームが定義するフィールド名に依存します。FieldsWidget からフィールド名を読み取り、正確に照合してください。

部分フラット化後の登録フォーム -- 3 つのフィールドがページテキストになり、入社日と上長は入力可能なまま

フラット化とフィールド削除の違い。 フラット化はフィールドの可視外観(入力された値を含む)を保持し、インタラクティブ性のみを削除します。フォームフィールドの削除はオブジェクトと外観を一緒に消去します。削除が真の目的であれば、PDF からフォームフィールドを削除を参照してください。

5. PDF 注釈とコメントをフラット化

注釈はレビューアーがページ上に残すオブジェクトです。テキストコメント、ハイライト、スタンプ、フリーテキストボックスがあります。Spire.PDF for .NET では、インタラクティブ注釈モデルを通じてフラット化します。各ページに PdfAnnotationCollection を作成し、各注釈に Flatten() を呼び出します。

using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("ReviewedDocument.pdf");

foreach (PdfPageBase page in pdf.Pages)
{
    if (page.Annotations.Count == 0) continue;

    PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);

    // Flatten() がコレクションから注釈を削除するため、逆順で走査する。
    for (int i = annotations.Count - 1; i >= 0; i--)
    {
        annotations[i].Flatten();
    }
}

pdf.SaveToFile("FlattenedAnnotations.pdf");
pdf.Close();

本ガイドで使用した契約書レビューページには 3 つの注釈(フリーテキストメモ、ゴムスタンプ、条項上の黄色いハイライト)があります。ループ終了後、注釈数は 3 から 0 になり、3 つのマークはすべてページ上に描画されたまま残ります。メモはボックスと色を保持し、スタンプは境界を保持し、ハイライトはテキストの背後に残ります。消えるのはインタラクティブオブジェクトのみです。ハイライトが保持されるのは、その四点座標がメモリ上のページではなく保存済みページから読み取られるためです。

契約書レビューページのフラット化前後 -- スタンプ、メモ、ハイライトはページに残り、注釈オブジェクトは消滅

ループ内の 2 つの詳細に注意してください。コレクションを逆順で走査します。Flatten() は各注釈をフラット化すると同時にコレクションから削除するため、逆順ループにより各項目を正確に 1 回ずつ処理します。Flatten() は Spire.Pdf.Interactive.Annotations.PdfAnnotation に宣言されており、これが注釈コレクションの作成元となるモデルです。

6. SVG 経由で PDF レイヤーをフラット化

オプションコンテンツレイヤーは、現在表示されているコンテンツから各ページを再構築することで削除します。各ページを SVG に変換し、SVG から PDF を再構築します。このラウンドトリップは各ページを再描画し、レイヤー構造を削除しつつテキストはテキスト、グラフィックはベクターとして保持します。そのため、画像にレンダリングしたページとは異なり、結果は引き続き選択、検索、スケーリングが可能です。

レイヤーを含むドキュメントを 1 ページずつ変換します。

using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Spire.Pdf;

// 実行ごとに新しい一時フォルダで作業する。
string workDir = Path.Combine(Path.GetTempPath(),
    "flatten_svg_" + Guid.NewGuid().ToString("N").Substring(0, 8));
Directory.CreateDirectory(workDir);

try
{
    // ステップ 1 - 各ページを個別の SVG ファイルとして保存。複数ページのドキュメントは
    // page_1.svg、page_2.svg、... を生成。単一ページのドキュメントは page.svg を生成。
    PdfDocument source = new PdfDocument();
    source.LoadFromFile("LayeredDocument.pdf");
    int pageCount = source.Pages.Count;
    source.SaveToFile(Path.Combine(workDir, "page.svg"), FileFormat.SVG);
    source.Close();

    // ページ番号順にファイルをソート。単一ページのドキュメントは page.svg を生成。
    string[] svgFiles = Directory.GetFiles(workDir, "*.svg")
        .OrderBy(f =>
        {
            Match m = Regex.Match(Path.GetFileNameWithoutExtension(f), @"\d+$");
            return m.Success ? int.Parse(m.Value) : 0;
        })
        .ToArray();

    // ページごとに 1 つの SVG -- 下のマージステップでこの数を使用。
    if (svgFiles.Length != pageCount)
    {
        throw new IOException(
            $"SVG 変換が {pageCount} ページに対して {svgFiles.Length} 個のファイルを生成しました。");
    }

    // ステップ 2 - 各 SVG を読み込み直し、1 ページの PDF として保存。
    string[] pageFiles = new string[svgFiles.Length];
    for (int i = 0; i < svgFiles.Length; i++)
    {
        PdfDocument page = new PdfDocument();
        page.LoadFromSvg(svgFiles[i]);
        pageFiles[i] = Path.Combine(workDir, "page_" + (i + 1) + ".pdf");
        page.SaveToFile(pageFiles[i], FileFormat.PDF);
        page.Close();
    }

    // ステップ 3 - 1 ページの PDF をページ順に結合して 1 つのドキュメントにする。
    PdfDocument.MergeFiles(pageFiles, "FlattenedLayers.pdf");
}
finally
{
    Directory.Delete(workDir, true);
}

ファイル名はページ数に依存します。2 ページのレイヤー付きドキュメントを page.svg に保存すると page_1.svg と page_2.svg が生成され、1 ページのドキュメントは番号なしの page.svg を生成します。サンプルドキュメントでは、ラウンドトリップが両ページを元のページサイズで順序通りに保持し、各レイヤーに描画されたテキストを保持し、レイヤー数は 4 から 0 に減少します。同じワークフローを 1 ページのドキュメントに適用すると 1 ページが返されます。

SVG で再構築されたレイヤー付きドキュメント -- 同じ 2 ページと同じコンテンツで、レイヤー構造が消滅

テキストとベクターグラフィックはラウンドトリップで損なわれないため、再構築されたページは元のページと同様に選択、検索、スケーリングが可能で、各レイヤーに描画されたコンテンツはページに残ります。

7. 印刷前に PDF をフラット化

ファイルをプリンターや印刷業者、制御できないビューアーに送付する場合、印刷前に PDF をフラット化します。フォームフィールド、注釈、レイヤーが通常のページコンテンツになれば、印刷内容はビューアーのインタラクティブ機能ではなくファイル自体によって決定されます。

別途コードは不要です。ドキュメントにフィールドがあればフォーム系の呼び出しを、コメントがあれば注釈ループを、レイヤーの可視性が印刷内容に影響する可能性があれば SVG ワークフローを使用し、送付前に以下の検証ステップで出力を確認します。

8. フラット化された PDF の検証

フラット化されたファイルは元のファイルと同じ外観を持つため、レンダリングを確認するのではなく、保存されたファイル内のオブジェクト数をカウントして結果を確認します。

using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;

PdfDocument result = new PdfDocument();
result.LoadFromFile("FlattenedForm.pdf");

int fields = 0;
PdfFormWidget form = result.Form as PdfFormWidget;
if (form != null)
{
    foreach (PdfField field in form.FieldsWidget) fields++;
}

int annotations = 0;
foreach (PdfPageBase page in result.Pages)
{
    annotations += page.Annotations.Count;
}

Console.WriteLine($"pages={result.Pages.Count} fields={fields} "
                + $"annotations={annotations} layers={result.Layers.Count}");
result.Close();

PdfFormWidget.FieldsWidget でフィールド数をカウントします。サンプルファイルでの出力は以下の通りです。

ファイル ページ数 フィールド 注釈 レイヤー
FillableForm.pdf(入力) 1 5 0 0
FlattenedForm.pdf 1 0 0 0
PartiallyFlattenedForm.pdf 1 2 0 0
ReviewedDocument.pdf(入力) 1 0 3 0
FlattenedAnnotations.pdf 1 0 0 0
LayeredDocument.pdf(入力) 2 0 0 4
FlattenedLayers.pdf 2 0 0 0

続いて視覚結果を確認します。ビューアーでファイルを開き、クリックやトグルができないことを確認し、ページ数とページサイズを元のファイルと比較し、テキストを選択または抽出して実際のテキストのままであることを確認します。

9. 全体をまとめる

上記のスニペットはそれぞれ 1 つの課題を解決します。ここではフォームフィールドと注釈を一度の処理でフラット化する単一のメソッドとして統合します。

using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;

/// <summary>
/// PDF のフォームフィールドと注釈を一度の処理でフラット化する。
/// </summary>
public static void FlattenDocument(string inputFile, string outputFile)
{
    PdfDocument pdf = new PdfDocument();
    pdf.LoadFromFile(inputFile);

    // 1. フォームフィールド: 1 つのフラグでドキュメント内の全フィールドをカバー。
    if (pdf.Form != null)
    {
        pdf.Form.IsFlatten = true;
    }

    // 2. 注釈: Flatten() が各注釈を削除するため逆順で走査。
    //    注釈のないページはスキップ。
    foreach (PdfPageBase page in pdf.Pages)
    {
        if (page.Annotations.Count == 0) continue;

        PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);
        for (int i = annotations.Count - 1; i >= 0; i--)
        {
            annotations[i].Flatten();
        }
    }

    pdf.SaveToFile(outputFile);
    pdf.Close();
}

ガード条件によりループは注釈を持つページのみを処理するため、フォームフィールドのみのドキュメントにも同じメソッドが使えます。このメソッドで 5 フィールドのフォームは 0 フィールドに、契約書レビューページは注釈 3 から 0 になります。レイヤー付きドキュメントは前節の SVG ワークフローで処理します。

10. よくある質問

フラット化とフォームフィールド削除の違いは何ですか?

フラット化はフィールドの可視外観(入力された値を含む)を保持し、インタラクティブ性のみを削除します。フォームフィールドの削除はフィールドオブジェクトと外観を一緒に消去します。

一部のフォームフィールドだけをフラット化できますか?

はい。pdf.Form を PdfFormWidget にキャストし、FieldsWidget を走査して、指定フィールドに Flatten = true を設定します。残りのフィールドは入力可能なまま残ります。

フラット化後もテキストは選択・検索可能ですか?

はい。フラット化されたテキストフィールドは値をページのテキストレイヤーに保持し、SVG ラウンドトリップも再描画したテキストを保持するため、値は引き続き選択、コピー、検索が可能です。

フラット化は可逆ですか?

フラット化はフィールドと注釈をページに書き込むため、後で編集する可能性がある場合は元の PDF ファイルを保管してください。

印刷前にフラット化は必要ですか?

常に必要というわけではありませんが、ビューアーへの依存をなくせます。フラット化により、可視コンテンツがインタラクティブフィールド、注釈、レイヤーの可視性設定に依存しなくなるため、異なる PDF ビューアー間でより一貫した出力が得られます。

11. まとめ

C# での PDF フラット化は、オブジェクトの種類ごとに 1 回の呼び出しで実行できます。

  • pdf.Form.IsFlatten = true でドキュメント内の全フィールドをフラット化。PdfField.Flatten で指定フィールドをフラット化。
  • PdfAnnotationCollection.Create(page) と各注釈への Flatten() を逆順で呼び出し、ページ上の全コメント、ハイライト、スタンプをフラット化。
  • レイヤーは各ページを SVG として保存し、読み込み直して PdfDocument.MergeFiles でマージすることでフラット化。

いずれの方法でもページ外観は保持されるため、スクリーンショットの比較ではなく、保存されたファイルに残っているオブジェクト数をカウントして検証してください。

実際のビジネス環境で Spire.PDF for .NET をテストする必要がある場合は、一時ライセンスの申請が可能です。

延伸読書

Read 7 times