iTextSharp(C#) を使ってみました

.NET Framework の C# を使って PDF を作成する方法を調べました。

環境

  • Windows 10
  • Visual Studio 2017
  • .NET Framework 4.6.1(C#, VB.NET)
  • iTextSharp 5.5.13

iText 7 or iText 5

Download iText 7 and iText 5 | iText Developers を見ると、次の種類があるようです。

  • iText 7
    • Java
    • .NET
  • Text 5
    • Java
    • .NET / iTextSharp

Download iText 7 and iText 5 | iText Developers

iTextSharp と言われていたものは 7 になって、 iText 7 .NET という名称になったのかな。

7 と 5 はどちらが良いのかな?

iText 7 のメリット

iText 7 is a complete re-write of iText 5, allowing you to choose your adventure with add-ons, all based on a simple, modular code structure that is easy to use and well documented.

…略…

iText 7 includes pdfDebug, the first debugging tool that gives you a clear overview of your content streams and document structure as well as pdfCalligraph, allowing you to leverage advanced typography.

NuGet Gallery | iTextSharp 5.5.13

アドオンになっていたり、 PDF デバッグができたりするようです。 今回は、単純に PDF に文字列を出力できればいい感じなので、 iText 7 である必要はなさそうでした。

後から認識したことですが、 iTextSharp のクラスの構造がいまいちつかみづらい感じがありました。 iText 7 では改善されているのかな?

ダウンロード数

NuGet のパッケージのダウンロード数を見てみました。

last updated 2 months ago

…略…

49,658 downloads of latest version

NuGet Gallery | iTextSharp 5.5.13


last updated 2 months ago

…略…

4,673 downloads of latest version

NuGet Gallery | itext7 7.1.1

ダウンロード数は、 iText 5 の方が 10 倍くらい多いみたいです。

トレンド

Google Trends で検索してみました。

iTextSharp が多いみたいです。

検索結果

Google の検索結果を見てみました。

  • “itext 5”: 約 16,900 件 (0.25 秒)
  • “itext 7”: 約 22,900 件 (0.24 秒)
  • “itextsharp”: 約 427,000 件 (0.34 秒)

iText 5 .NET / iTextSharp の方が良さそうかな。

ライセンス

使う前に、 iText はライセンスについての記載をよく見かけます。

iText 5 と iText 7 が現行のサポート対象のバージョンのようです。 両方とも AGPL と商業ライセンスのもとで使うことができるようです。 (iText 5 は AGPL だけかな?)

AGPL は、適用範囲とソースコードの公開義務があるようで、条件に合うかどうか確認してから使う必要があるみたいです。

インストール

NuGet パッケージ マネージャーを使ってインストールします。 Visual Studio のメニューから [ツール]-[NuGet パッケージ マネージャー]-[パッケージ マネージャー コンソール] を選択します。 パッケージ マネージャー コンソールに次のように入力しました。

PM> Install-Package iTextSharp -Version 5.5.13

ログが流れてゆきます。



'.NETFramework,Version=v4.6.1' を対象とするプロジェクト 'ConsoleApp1' に関して、パッケージ 'iTextSharp.5.5.13' の依存関係情報の収集を試行しています
依存関係情報の収集に 1.49 sec かかりました
DependencyBehavior 'Lowest' でパッケージ 'iTextSharp.5.5.13' の依存関係の解決を試行しています
依存関係情報の解決に 0 ms かかりました
パッケージ 'iTextSharp.5.5.13' をインストールするアクションを解決しています
パッケージ 'iTextSharp.5.5.13' をインストールするアクションが解決されました
'nuget.org' からパッケージ 'iTextSharp 5.5.13' を取得しています。
  GET https://api.nuget.org/v3-flatcontainer/itextsharp/5.5.13/itextsharp.5.5.13.nupkg
  OK https://api.nuget.org/v3-flatcontainer/itextsharp/5.5.13/itextsharp.5.5.13.nupkg 700 ミリ秒
iTextSharp 5.5.13 をインストールしています。
パッケージ 'iTextSharp.5.5.13' をフォルダー 'C:\Users\user\Source\Repos\ConsoleApp1\packages' に追加しています
パッケージ 'iTextSharp.5.5.13' をフォルダー 'C:\Users\user\Source\Repos\ConsoleApp1\packages' に追加しました
パッケージ 'iTextSharp.5.5.13' を 'packages.config' に追加しました
'iTextSharp 5.5.13' が ConsoleApp1 に正常にインストールされました
NuGet の操作の実行に 4.29 sec かかりました
経過した時間: 00:00:06.4740974

インストールできたみたいです。

文字列を出力する1

存在する PDF に文字列を出力するサンプルです。 (テンプレートの PDF は上書きしません) Adding page numbers to an existing PDF | iText Developers の examples を参考にしました。

class Class1
{
    /**
     * template
     */
    private const String template = @"C:\Users\user\Desktop\app\template.pdf";

    /**
     * output
     */
    private const String output = @"C:\Users\user\Desktop\app\output.pdf";

    /**
     * Adding page numbers to an existing PDF | iText Developers
     * https://developers.itextpdf.com/examples/columntext-examples-itext5/adding-page-numbers-existing-pdf
     * 
     * ColumnText (iText 5.5.13 API)
     * http://itextsupport.com/apidocs/itext5/latest/com/itextpdf/text/pdf/ColumnText.html#showTextAligned-com.itextpdf.text.pdf.PdfContentByte-int-com.itextpdf.text.Phrase-float-float-float-int-int-
     * showTextAligned
     * 
     * PdfContentByte (iText 5.5.13 API)
     * http://itextsupport.com/apidocs/itext5/latest/com/itextpdf/text/pdf/PdfContentByte.html#showTextAligned-int-java.lang.String-float-float-float-
     * showTextAligned
     */
    public void Method1()
    {
        iTextSharp.text.FontFactory.RegisterDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
        var fs = new System.IO.FileStream(output, System.IO.FileMode.Create, System.IO.FileAccess.Write);
        using (var reader = new iTextSharp.text.pdf.PdfReader(template))
        using (var stamper = new iTextSharp.text.pdf.PdfStamper(reader, fs))
        {
            var contentByte = stamper.GetOverContent(1);
            //iTextSharp.text.pdf.ColumnText.ShowTextAligned(contentByte, iTextSharp.text.Element.ALIGN_RIGHT, new iTextSharp.text.Phrase("タイトルです"), 170.0f, 420.0f, 0.0f); // NG
            //iTextSharp.text.pdf.ColumnText.ShowTextAligned(contentByte, iTextSharp.text.Element.ALIGN_RIGHT, new iTextSharp.text.Phrase("this is title"), 170.0f, 420.0f, 0.0f); // OK
            contentByte.BeginText();
            var f = iTextSharp.text.FontFactory.GetFont("游ゴシック", iTextSharp.text.pdf.BaseFont.IDENTITY_H, iTextSharp.text.pdf.BaseFont.NOT_EMBEDDED);
            contentByte.SetFontAndSize(f.BaseFont, 66.0f);
            contentByte.ShowTextAligned(iTextSharp.text.Element.ALIGN_LEFT, "タイトルです", 170.0f, 320.0f, 0.0f);
            contentByte.EndText();
        }
    }
}

最初、 examples にあるように ColumnText.ShowTextAligned を使っていたのですが、なかなか出力できませんでした。 英数字だけを出力したらできました。 日本語はダメみたいでした。

ColumnText.ShowTextAligned は、この 1 行だけで出力が完了するようです。 が、 PdfContentByte.ShowTextAlignedPdfContentByte.BeginText();PdfContentByte.EndText(); を記述しなければいけないようなので、少し手間がかかります。

PdfContentByte.ShowTextAligned は Align を基準に用紙の外側までどこまでも文字列を出力するようです。 折り返しなしで 1 行を出力するときに使いそうです。

文字列を出力する2

存在する PDF に文字列を出力するサンプルの 2 つ目です。 (テンプレートの PDF は上書きしません) Positioning different text snippets on a page | iText Developers の examples を参考にしました。

class Class2
{
    /**
     * template
     */
    private const String template = @"C:\Users\user\Desktop\app\template.pdf";

    /**
     * output
     */
    private const String output = @"C:\Users\user\Desktop\app\output.pdf";

    /**
     * Positioning different text snippets on a page | iText Developers
     * https://developers.itextpdf.com/examples/columntext-examples-itext5/positioning-different-text-snippets-page
     * ColumnTextPhrase.java 
     */
    public void Method1()
    {
        iTextSharp.text.FontFactory.RegisterDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
        var fs = new System.IO.FileStream(output, System.IO.FileMode.Create, System.IO.FileAccess.Write);
        using (var reader = new iTextSharp.text.pdf.PdfReader(template))
        using (var stamper = new iTextSharp.text.pdf.PdfStamper(reader, fs))
        {
            var contentByte = stamper.GetOverContent(1);
            {
                var ct = new iTextSharp.text.pdf.ColumnText(contentByte);
                ct.SetSimpleColumn(775.0f, 110.0f, 170.0f, 420.0f);
                var f = iTextSharp.text.FontFactory.GetFont("游ゴシック", iTextSharp.text.pdf.BaseFont.IDENTITY_H, iTextSharp.text.pdf.BaseFont.NOT_EMBEDDED, 66.0f, 0, iTextSharp.text.BaseColor.DARK_GRAY);
                var paragraph = new iTextSharp.text.Paragraph("タイトルです", f);
                ct.AddElement(paragraph);
                ct.Go();
            }
            {
                var ct = new iTextSharp.text.pdf.ColumnText(contentByte);
                ct.SetSimpleColumn(775.0f, 100.0f, 170.0f, 290.0f);
                var f = iTextSharp.text.FontFactory.GetFont("游ゴシック", iTextSharp.text.pdf.BaseFont.IDENTITY_H, iTextSharp.text.pdf.BaseFont.NOT_EMBEDDED, 18.0f, 0, iTextSharp.text.BaseColor.DARK_GRAY);
                var paragraph = new iTextSharp.text.Paragraph("サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。サブタイトルです。", f);
                ct.AddElement(paragraph);
                ct.Go();
            }
        }
    }
}

範囲を決めて、複数行で出力する場合に使いそうです。

が、 1 行分だけの範囲を指定しても、範囲を超えて 2 行目も出力されてしまいます。 (なぜか 2 行目までで、 3 行目以降は出力されません) 1 行より小さい範囲にすると、今度は文字列全体が出力されてしまいます。

どうしても 1 行にしたい場合は PdfContentByte.ShowTextAligned を使った方がよさそうです。 その場合、横にはどこまでも伸びていくことを考慮しなければいけません。

iTextSharp.text.FontFactory.GetFont の引数の 5 番目は style のようですが、 0 だとなし、 1 だと太字、 2 だと斜体、 3 だと太字と斜体、 4 だと下線のようになっていました。 その後は試してません。 定数とかないのかな?

複数のテンプレートを組み替える

テンプレートの PDF を読み込んで、出力の PDF に設定するサンプルです。 Create booklet from A4 document | iText Developers の examples を参考にしました。

class Class3
{
    /**
     * template
     */
    private const String template = @"C:\Users\user\Desktop\app\template21.pdf";

    /**
     * template2
     */
    private const String template2 = @"C:\Users\user\Desktop\app\template22.pdf";

    /**
     * output
     */
    private const String output = @"C:\Users\user\Desktop\app\output.pdf";

    /**
     * Create booklet from A4 document | iText Developers
     * https://developers.itextpdf.com/examples/merging-pdf-documents/create-booklet-a4-document
     */
    public void Method1()
    {
        using (var reader = new iTextSharp.text.pdf.PdfReader(template))
        using (var reader2 = new iTextSharp.text.pdf.PdfReader(template2))
        // 出力 PDF の 1 ページ目のサイズと印刷の向きについて、テンプレート 1 の内容で設定する
        // 出力 PDF の 1 ページ目は Document.open の後に設定しても有効にならない?
        using (var document = new iTextSharp.text.Document(reader.GetPageSizeWithRotation(1)))
        {
            var fs = new System.IO.FileStream(output, System.IO.FileMode.Create, System.IO.FileAccess.Write);
            var writer = iTextSharp.text.pdf.PdfWriter.GetInstance(document, fs);
            document.Open();
            var contentByte = writer.DirectContent;
            {
                // テンプレート 1 の 1 ページ目の内容を取得する
                var importedPage = writer.GetImportedPage(reader, 1);
                // テンプレート 2 の 1 ページ目の内容を、出力 PDF の 1 ページ目に設定する
                // ビルドエラーのメモ
                // 重大度レベル	コード	説明	プロジェクト	ファイル	行	抑制状態
                // エラー CS0012  型 'Matrix' は、参照されていないアセンブリに定義されています。アセンブリ 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' に参照を追加する必要があります。	ConsoleApp1 C:\Users\user\Source\Repos\ConsoleApp1\ConsoleApp1\Class3.cs  42  アクティブ
                // メニューバーから [プロジェクト]-[参照の追加] を選択。[アセンブリ]-[フレームワーク] を選択。[System.Drawing] にチェック。
                contentByte.AddTemplate(importedPage, 0d, 0d);
            }
            {
                document.NewPage();
                // 出力 PDF の 2 ページ目のサイズと印刷の向きについて、テンプレート 2 の内容で設定する
                document.SetPageSize(reader2.GetPageSizeWithRotation(1));
                // テンプレート 2 の 1 ページ目の内容を取得する
                var importedPage = writer.GetImportedPage(reader2, 1);
                // テンプレート 2 の 1 ページ目の内容を、出力 PDF の 2 ページ目に設定する
                // ビルドエラーのメモ
                // 重大度レベル	コード	説明	プロジェクト	ファイル	行	抑制状態
                // エラー CS0012  型 'Matrix' は、参照されていないアセンブリに定義されています。アセンブリ 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' に参照を追加する必要があります。	ConsoleApp1 C:\Users\user\Source\Repos\ConsoleApp1\ConsoleApp1\Class3.cs  42  アクティブ
                // メニューバーから [プロジェクト]-[参照の追加] を選択。[アセンブリ]-[フレームワーク] を選択。[System.Drawing] にチェック。
                contentByte.AddTemplate(importedPage, 0d, 0d);
            }
        }
    }
}

PdfStamper は、入力と出力が 1:1 に限られてしまいますが、 PdfWriter.GetImportedPagePdfContentByte.AddTemplate を使うことで、複数のテンプレートを組み替えながら出力することができそうです。

テンプレートは 1 つの PDF ファイルに複数ページ分を定義しておいても良さそうです。 PdfWriter.GetImportedPage でページが指定できましたのでね。

パスワードを設定する

PDF 文書にパスワードを設定してみました。 PdfWriter (iText 5.5.13 API) の API を参考にしました。

class Class4
{
    /**
     * template
     */
    private const String template = @"C:\Users\user\Desktop\app\template.pdf";

    /**
      * output
      */
    private const String output = @"C:\Users\user\Desktop\app\output.pdf";

    /**
      * PdfWriter (iText 5.5.13 API)
      * http://itextsupport.com/apidocs/itext5/latest/com/itextpdf/text/pdf/PdfWriter.html#setEncryption-byte:A-byte:A-int-int-
      */
    public void Method1()
    {
        iTextSharp.text.FontFactory.RegisterDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Fonts));
        var fs = new System.IO.FileStream(output, System.IO.FileMode.Create, System.IO.FileAccess.Write);
        using (var reader = new iTextSharp.text.pdf.PdfReader(template))
        using (var stamper = new iTextSharp.text.pdf.PdfStamper(reader, fs))
        {
            stamper.SetEncryption(Encoding.Default.GetBytes("user"), Encoding.Default.GetBytes("owner"), iTextSharp.text.pdf.PdfWriter.ALLOW_ASSEMBLY + iTextSharp.text.pdf.PdfWriter.ALLOW_COPY + iTextSharp.text.pdf.PdfWriter.ALLOW_DEGRADED_PRINTING + iTextSharp.text.pdf.PdfWriter.ALLOW_FILL_IN + iTextSharp.text.pdf.PdfWriter.ALLOW_MODIFY_ANNOTATIONS + iTextSharp.text.pdf.PdfWriter.ALLOW_MODIFY_CONTENTS + iTextSharp.text.pdf.PdfWriter.ALLOW_PRINTING + iTextSharp.text.pdf.PdfWriter.ALLOW_SCREENREADERS, iTextSharp.text.pdf.PdfWriter.ENCRYPTION_AES_256);
            var contentByte = stamper.GetOverContent(1);
            contentByte.BeginText();
            var f = iTextSharp.text.FontFactory.GetFont("游ゴシック", iTextSharp.text.pdf.BaseFont.IDENTITY_H, iTextSharp.text.pdf.BaseFont.NOT_EMBEDDED);
            contentByte.SetFontAndSize(f.BaseFont, 66.0f);
            contentByte.ShowTextAligned(iTextSharp.text.Element.ALIGN_LEFT, "タイトルです", 170.0f, 320.0f, 0.0f);
            contentByte.EndText();
        }
    }
}

ここでは PdfStamper の PdfStamper.SetEncryption を使いました。 PdfWriter の PdfWriter.SetEncryption も同様に使えるようです。

A. パスワードの種類

Acrobat では、文書を開くパスワード、権限パスワード、添付ファイルを開くパスワードの 3 種類のパスワードを設定できます。

PDF 文書にパスワードを設定する方法 (Acrobat XI/DC)

PdfStamper.SetEncryption の引数の 1 つ目は userPassword 、 2 つ目は ownerPassword です。 Acrobat XI/DC のページを参考にすると、文書を開くパスワードが userPassword 、 権限パスワードが ownerPassword になりそうかな。

PdfStamper.SetEncryption の引数の 3 つ目は permissions です。 次のような定数があるようです。 int 型なので、 + 演算子で加算した値を設定するようです。

  • PdfWriter.ALLOW_ASSEMBLY
  • PdfWriter.ALLOW_COPY
  • PdfWriter.ALLOW_DEGRADED_PRINTING
  • PdfWriter.ALLOW_FILL_IN
  • PdfWriter.ALLOW_MODIFY_ANNOTATIONS
  • PdfWriter.ALLOW_MODIFY_CONTENTS
  • PdfWriter.ALLOW_PRINTING
  • PdfWriter.ALLOW_SCREENREADERS

互換性のある形式

暗号化アルゴリズムとキーサイズはバージョンによって異り、このオプションによって、暗号化レベルとキーサイズが設定されます。 PDF ファイルの受信者の Acrobat または Acrobat Reader のバージョンに対応しているものを選択します。

PDF 文書にパスワードを設定する方法 (Acrobat XI/DC)

PdfStamper.SetEncryption の引数の 4 つ目は encryptionType です。 暗号化アルゴリズムとキーサイズは次のような対応になりそうです。

  • PdfWriter.ENCRYPTION_AES_256: Acrobat X およびそれ以降
  • PdfWriter.ENCRYPTION_AES_128: Acrobat 7.0 およびそれ以降
  • PdfWriter.STANDARD_ENCRYPTION_128: Acrobat 6.0 およびそれ以降
  • PdfWriter.STANDARD_ENCRYPTION_40: Acrobat 6.0 より前?

終わり

罫線はテンプレートの PDF に定義することにしたいかな、と思いましたので書きませんでした。 PdfContentByte.MoveTo で開始位置を指定して、 PdfContentByte.LineTo で終了位置を指定することでラインが描画できそうではありました。 うーん、これを実装するのは面倒な気がします。

文字列の出力も座標を把握しなければいけないのですが、コードに直接座標を記述して、何度も実行しながら微調整しました。 素早く座標を把握する方法はあるのかな?