[初心者のJava] iText7を試してみる

ということで、JavaでPDFファイルを作ってみるの初回として、とりあえずベタ打ちテキストをベタ出力するだけのプログラムを作ってみました。

iText7はオープンソースのPDFライブラリで、オープンソース開発が条件 (AGPL) のオープンソース版とプロプライエタリソフトウエアを開発できるコマーシャルライセンス版があります。

当面は学習ということでオープンソース版で試してみることにしました。

作成したプログラムはGitHubに置いてあります。

まずは手始めにチュートリアルを参考にしてベタ打ちテキストのベタ出力を行うプログラムを作ってみました。

チュートリアルはJump-Start tutorialとBuilding BlocksのPdfFont examplesを参考にしています。

日本語テキストを2カラムでベタ出力する

最初のサンプルはE01.javaとして保存してあります。

日本語テキストを出力する際にまずやらなければならないのがフォントの準備です。

PDFの基本14フォントは日本語出力をサポートしていないので、日本語出力が可能なフォントを設定する必要があります。

とりあえず今回はNoto Sans CJK JPフォントを使用してみました。Noto Sansフォントはオープンフォントライセンスで再配布が許可されていますので、プログラムと一緒にGitHubに置いてあります。

また、テキストデータもパブリックドメインなものが必要なので、日本国憲法全文のテキストファイルを使用しています。

まず、iText7でフォントを使用するためのPdfFontオブジェクトを作成します。

PdfFont mainfont = PdfFontFactory.createFont(NOTOCJK, PdfEncodings.IDENTITY_H);

NOTOCJKにはフォントデータへのパスを文字列として指定しています。また、作成するフォントのエンコーディングとしてIDENTITY_H (とりあえずのエンコーディングで横書き) を指定しています。

縦書きの場合にはIDENTITY_Vが使用できるのですが、縦書きはちょっといろいろあるので今回は試していません。

次に出力先のPdfDocumentオブジェクトを生成します。

PdfDocument pdf = new PdfDocument(new PdfWriter(dest));

destには出力先ファイル名の文字列を指定します。PdfWriterを指定してPdfDocumentを生成しているので、PDFファイル作成用のPdfDocumentオブジェクトが生成されます。

次に実際のPDFドキュメントを生成します。

まず、新規ドキュメントを生成します。

PageSize ps = PageSize.A4;
Document document = new Document(pdf, ps);

この処理によってPdfDocumentオブジェクトにA4サイズの新規ドキュメントが生成されます。

ページサイズは後で使うので変数にしています。

次にカラムのサイズを計算してカラムを表現するRectangleオブジェクトを生成します。

float offSet = 36;
float columnSpace = 18;
float columnWidth = (ps.getWidth() - offSet * 2 - columnSpace) / 2;

float columnHeight = ps.getHeight() - offSet * 2;

Rectangle[] columns = {new Rectangle(offSet, offSet, columnWidth, columnHeight),

  new Rectangle(offSet + columnWidth + columnSpace, offSet, columnWidth, 

    columnHeight)};

ざっと見ていただくとわかるとおり、余白を36pts、段間を18pts取って、それぞれのカラムの原点位置とサイズを指定したRectangleオブジェクトを生成しています。

次にRendererを設定します。

document.setRenderer(new ColumnDocumentRenderer(document, columns));

ColumnDocumentRendererは段組み文書への描画を行うオブジェクトだそうで、先ほど作成したカラムを指定するRectangleオブジェクトの配列を引数として受け取っています。

文書中にカラムを配置して、カラムに描画するという考え方が一般的だと思っていたので、カラム形状に描画を行うという考え方が少し新鮮です。

続いてテキストを読み込みます。

String article = new String(Files.readAllBytes(Paths.get(SOURCE_TXT)),
  StandardCharsets.UTF_8);

とりあえずテキストファイルを一括で読み込み、1個のStringオブジェクトを生成しています。

最後に読み込んだテキストを保持するParagraphオブジェクトを生成してDocumentに追加します。

Paragraph p = new Paragraph("")
  .setFont(mainfont)
  .setFontSize(10)

  .add(article);

document.add(p);

document.close();

明示的な描画処理は指示せずにDocumentオブジェクトにParagraphオブジェクトを追加するだけとなります。

描画処理の詳細は確認していませんが、DocumentオブジェクトにParagraphオブジェクトを追加した時点、あるいはDocumentオブジェクトをクローズした時点でRendererオブジェクトが実行されて描画されるものと思います。

E01クラスを実行するとresult/e01/result.pdfが生成されます。

生成されたresult.pdfを見てみましょう。

基本的には問題なく日本語のPDFファイルとなっていますが、少しばかり気になるところがあります。

第9条のあたりを見てみると以下のようになっています。


第1項の4行目、第2項の2行目ともに読点 (、) が行頭に来ています。

iText7は日本語の禁則ルールを知らないため、このように禁則処理がされません。

次に禁則処理を実装していこうと思います。

禁則処理の導入

まず、禁則処理を導入します。

禁則処理を導入したサンプルはE02.javaとして公開しています。

禁則処理を導入するにはiText7のTextオブジェクトに用意されているSplitCharactersプロパティを使用します。

Textオブジェクトを描画する際にSplitCharactersプロパティに設定されているオブジェクトのisSplitCharacterメソッドが呼ばれます。

isSplitCharacterメソッドは引数で与えられたグリフ列 (GlyphLine) と文字位置から指定された文字とその前後の文字を参照して指定された文字位置の後ろで開業していいかどうかを判定します。

まずSplitCharactersプロパティを使用するためにTextオブジェクトを生成します。

 Text text = new Text(article);

最初の例ではParagraphオブジェクトに文字列をそのまま追加していましたが、今回は禁則処理を導入するために文字列からTextオブジェクトを生成し、TextオブジェクトをParagraphオブジェクトに追加する必要があります。

次に禁則処理をTextオブジェクトに追加します。

text.setSplitCharacters(
  (glyphLine, glyphPos)->{
    if (gyoumatsuKinsoku.indexOf(glyphLine.get(glyphPos).getUnicode()) >= 0)

      return false;

    if (glyphPos < glyphLine.size() - 1) {

      if (gyoutouKinsoku.indexOf(glyphLine.get(glyphPos + 1).getUnicode()) >= 0) {

        return false;

      }

    }

    return true;

  });

Lambda式を使っているので少し見にくいのですが、3行目でglyphPosで指定された位置の文字を取得し、gyoumatsuKinsokuという文字列 (行末禁則文字の文字列) にその文字が含まれているかを検査します。

含まれていた場合には行末にはおけない文字なのでfalseを返してこの文字の直後での開業を禁止しています。

次は行頭禁則の確認です。行頭禁則の場合、禁則文字の直前での改行が禁止されるので、1文字先読みを行って、行と禁則文字ではないかを確認します。

まず、5行目で最後の文字ではないことを確認し、指定された位置の次の文字位置にある文字がgyoutouKinsoku文字列に含まれているかを確認して、含まれていた場合には改行を禁止するfalseを返しています。

最後、禁則にかからないことが確認されればtrueを返します。

なお、gyoumatsuKinsokuおよびgyoutouKinsoku文字列はE02.javaのコードを参照してください。

最後にParagraphを生成するところでTextオブジェクトを追加して終了です。

Paragraph p = new Paragraph("")
  .setFont(mainfont)
  .setFontSize(10)

  .add(text);

以上で生成されたPDF (result/e02/result.pdf) の第9条部分は以下のようになります。


第1項の4行目、第2項の2行目ともに読点の前の文字が行送りされ、その影響で第2項の3行目も行送りがされて行頭に句読点が来ることがなくなりました。

その代わり行末の位置が不ぞろいになってしまいました。

次は行末位置の不ぞろいを修正したいと思います。

ジャスティフィケーション

行末をそろえるジャスティフィケーションを導入しようと思います。

ジャスティフィケーションを導入したサンプルはE03.javaになります。

ジャスティフィケーション自体はとても簡単に指定できます。

Paragraph p = new Paragraph("")
  .setFont(mainfont)
  .setFontSize(10)

  .add(text);

p.setProperty(Property.TEXT_ALIGNMENT, TextAlignment.JUSTIFIED);

document.add(p);

ParagraphオブジェクトにProperty.TEXT_ALIGNMENTをプロパティとしてTextAlignment.JUSTIFIEDを設定するだけです。

result/e03/result.pdfに結果がありますので見てみましょう。


上のように行末がそろった文章となりました。

以上、ベタ打ちテキストのベタ出力をやってみました。

すべてのページが同じ段組みで作られている文書の場合、非常に簡単にPDFを作れることがわかりました。

次はもう少し凝ったことをやってみたいと思います。


0コメント

  • 1000 / 1000