Apache PDFBoxで用紙を横向きにする方法
  Java, プログラミング

Javaで簡単にPDFの作成・加工ができるライブラリー Apache PDFBox。用紙サイズのプリセットとしてPDRectangle.A4PDRectangle.A3が定義されていますが、これらの用紙はすべて縦向きになっています。

A4横などの横向き用紙にしたいときはどうすれば良いのでしょうか?

今回は、Apache PDFBoxで用紙を横向きにする方法を2つ紹介します。

はじめに基本となるA4縦のPDFを出力するサンプルプログラムを紹介します。

A4縦のPDFを出力する例
import java.io.File; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; public class PDFBoxSample { public static void main(String[] args) throws Exception { PDRectangle rectangle = PDRectangle.A4; PDPage page = new PDPage(rectangle); PDDocument doc = new PDDocument(); doc.addPage(page); PDFont font = PDType0Font.load(doc, new File("C:/Windows/Fonts/yumindb.ttf")); float fontSize = 30; PDPageContentStream contents = new PDPageContentStream(doc, page); contents.beginText(); contents.setFont(font, fontSize); contents.newLineAtOffset(10, page.getMediaBox().getHeight() - fontSize - 10); contents.showText("こんにちは"); contents.endText(); contents.close(); doc.save("output.pdf"); } }

このプログラムを実行するとoutput.pdfというファイル名でPDFが出力されます。内容は左上に「こんにちは」と表示されるだけの簡単なものです。

注意しておきたいのはテキストの表示位置を決めている以下のコードです。

contents.newLineAtOffset(10, rectangle.getHeight() - fontSize - 10);

コンピューター画面の座標系では左上が原点(0, 0)になっていることが多いですが、PDF既定の座標系は左下が原点(0, 0)となっています。

そのため、用紙の左上にテキストを表示するために用紙の高さ page.getMediaBox().getHeight() の分だけオフセット指定が必要になります。この用紙の高さの分だけずらす操作は、用紙の回転後に影響してきます…。

用紙を回転させる

Apache PDFBoxではPDPageクラスのsetRotationメソッドを使って用紙を回転させることができます。

PDF用紙を時計回りに90度回転させる
PDPage page = new PDPage(rectangle); page.setRotation(90);

この90度回転させるコードを追加した完全なプログラムは以下の通りです。

A4縦のPDFを90度回転させる例
import java.io.File; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; public class PDFBoxSample { public static void main(String[] args) throws Exception { PDRectangle rectangle = PDRectangle.A4; PDPage page = new PDPage(rectangle); //用紙を90度回転させます page.setRotation(90); PDDocument doc = new PDDocument(); doc.addPage(page); PDFont font = PDType0Font.load(doc, new File("C:/Windows/Fonts/yumindb.ttf")); float fontSize = 30; PDPageContentStream contents = new PDPageContentStream(doc, page); contents.beginText(); contents.setFont(font, fontSize); contents.newLineAtOffset(10, rectangle.getHeight() - fontSize - 10); contents.showText("こんにちは"); contents.endText(); contents.close(); doc.save("output.pdf"); } }

さて、このプログラムを実行してみると・・・。

どうでしょうか? そのまま用紙が回転しただけですね。これではA4縦が横倒しになっただけで、A4横の用紙になったとは言えません。

コンテンツも回転させる方法

PDPageクラスのsetRotationメソッドではPDF全体が回転してしまいます。この方法をA4横の用紙として使うためには、さらに「こんにちは」というテキストなどすべてのコンテンツ出力を反時計回りに回転させる必要があります。

PDPageContentStreamクラスのtransformメソッドを使うと、コンテンツの出力を反時計回りに回転させることができます。

コンテンツ出力を反時計回りに90度回転させる
PDPageContentStream contents = new PDPageContentStream(doc, page); contents.transform(new Matrix(0, 1, -1, 0, page.getMediaBox().getWidth(), 0));

プログラム全体は以下のようになりました。

A4縦のPDFを90度回転させる例
import java.io.File; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.util.Matrix; public class PDFBoxSample { public static void main(String[] args) throws Exception { PDRectangle rectangle = PDRectangle.A4; PDPage page = new PDPage(rectangle); //用紙を90度回転させます page.setRotation(90); PDDocument doc = new PDDocument(); doc.addPage(page); PDFont font = PDType0Font.load(doc, new File("C:/Windows/Fonts/yumindb.ttf")); float fontSize = 30; PDPageContentStream contents = new PDPageContentStream(doc, page); //コンテンツを反時計回りに90度回転させます contents.transform(new Matrix(0, 1, -1, 0, page.getMediaBox().getWidth(), 0)); contents.beginText(); contents.setFont(font, fontSize); contents.newLineAtOffset(10, page.getMediaBox().getHeight() - fontSize - 10); contents.showText("こんにちは"); contents.endText(); contents.close(); doc.save("output.pdf"); } }

今度こそ上手くいってくれるでしょうか?

ダメでした・・・。白紙になってしまいました。

何がおこったのでしょう?

実はこのPDFは用紙の領域外に「こんにちは」というテキストが存在しています。原因はPDPageContentStreamクラスのnewLineAtOffsetメソッドによるテキストの出力位置の決め方にあります。

PDFでは左下が原点(0, 0)になっているので、テキストの出力位置を用紙の高さの分だけずらしていたのですが、この page.getMediaBox().getHeight() は用紙を回転させても変わらず元々の用紙の高さ(長辺)を返します。用紙を横向きに回転させた場合の高さは用紙の短辺になりますよね。にもかかわらず、用紙の長辺の分だけオフセット指定をしてしまったのでテキストの出力位置が領域外になってしまったのです。

テキスト出力のオフセットも変える

テキスト出力のオフセット指定も回転に合わせる必要があります。

用紙の高さ(短辺)の分だけオフセット指定する
contents.newLineAtOffset(10, page.getMediaBox().getWidth() - fontSize - 10);

このようgetHeight()getWidth()に変えます。高さなのにWidth?と疑問に思うかもしれませんが、元々の用紙の横幅(Width)が、90度回転した後の高さ(Height)になると考えてください。

最終版
import java.io.File; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.util.Matrix; public class PDFBoxSample { public static void main(String[] args) throws Exception { PDRectangle rectangle = PDRectangle.A4; PDPage page = new PDPage(rectangle); //用紙を90度回転させます page.setRotation(90); PDDocument doc = new PDDocument(); doc.addPage(page); PDFont font = PDType0Font.load(doc, new File("C:/Windows/Fonts/yumindb.ttf")); float fontSize = 30; PDPageContentStream contents = new PDPageContentStream(doc, page); //コンテンツを反時計回りに90度回転させます contents.transform(new Matrix(0, 1, -1, 0, page.getMediaBox().getWidth(), 0)); contents.beginText(); contents.setFont(font, fontSize); //回転によって縦と横が入れ替わったことを考慮してオフセットを指定します contents.newLineAtOffset(10, page.getMediaBox().getWidth() - fontSize - 10); contents.showText("こんにちは"); contents.endText(); contents.close(); doc.save("output.pdf"); } }

今度こそ・・・。

上手くいきました!

Apache PDFBoxで用紙を回転させるためには次の3点に注意する必要があります。

  1. PDPage.setRotationで用紙全体の向きを回転させる
  2. PDPageContentStream.transformでコンテンツ出力を逆向きに回転させる
  3. コンテンツ出力の位置指定時は縦と横が入れ替わっていることに注意する

用紙サイズを定義する方法

実は横向きの用紙を使うだけであれば回転させるよりも簡単な方法があります。それはPDRectangle.A4などのプリセットを使わずに、自分で用紙サイズを設定してしまう方法です。

PDRectangleには横幅と高さを引数に持つコンストラクタPDRectangle(float width, float height)があります。これを使って簡単に独自の用紙サイズを作ることができます。ミリメートル(mm)単位ではなくポイント(pt)単位で指定することに注意してください。

A4横向きの用紙の場合は横幅が 297mm = 841.9pt、高さが 210mm = 595.3pt になります。

float MM_TO_PT = (72 / 25.4f);
PDRectangle rectangle = new PDRectangle(297 * MM_TO_PT, 210 * MM_TO_PT);

これを一番最初に提示したコードに適用します。

横向きの用紙サイズを指定する
import java.io.File; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; public class PDFBoxSample { public static void main(String[] args) throws Exception { //プリセットを使わずに自分で用紙サイズを指定します float MM_TO_PT = (72 / 25.4f); PDRectangle rectangle = new PDRectangle(297 * MM_TO_PT, 210 * MM_TO_PT); PDPage page = new PDPage(rectangle); PDDocument doc = new PDDocument(); doc.addPage(page); PDFont font = PDType0Font.load(doc, new File("C:/Windows/Fonts/yumindb.ttf")); float fontSize = 30; PDPageContentStream contents = new PDPageContentStream(doc, page); contents.beginText(); contents.setFont(font, fontSize); contents.newLineAtOffset(10, page.getMediaBox().getHeight() - fontSize - 10); contents.showText("こんにちは"); contents.endText(); contents.close(); doc.save("output.pdf"); } }

冒頭の用紙設定の部分が変わっただけで、残りのコードは元のままです。「回転したから縦と横が入れ替わって~」と考える必要もありません。

簡単にA4横のPDF出力を得ることができました。

上記では、A4のサイズ(297mm x 210mm)を数値リテラルで指定しましたが、 PDRectangle.A4プリセットのgetHeight()getWidth()を参照して、縦と横を入れ替えて新しいPDRectangleインスタンスを作る方法もあります。

A4横向きのPDRectangleを作成する
PDRectangle rectangle = new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth());

この方法のほうが分かりやすいですね。A4の実際のサイズやミリメートルとポイントの換算式を知らなくても使えます。

日本で使われているB列にも対応できる

PDRectangleでサイズを指定する方法なら、B4, B5 といったプリセットが用意されていない日本独自の用紙サイズにも対応できます。

日本独自のB列 用紙サイズ
static float MM_TO_PT = (72 / 25.4f); //縦向き static PDRectangle B0 = new PDRectangle(1000 * MM_TO_PT, 1414 * MM_TO_PT); static PDRectangle B1 = new PDRectangle( 707 * MM_TO_PT, 1000 * MM_TO_PT); static PDRectangle B2 = new PDRectangle( 500 * MM_TO_PT, 707 * MM_TO_PT); static PDRectangle B3 = new PDRectangle( 353 * MM_TO_PT, 500 * MM_TO_PT); static PDRectangle B4 = new PDRectangle( 250 * MM_TO_PT, 353 * MM_TO_PT); static PDRectangle B5 = new PDRectangle( 176 * MM_TO_PT, 250 * MM_TO_PT); static PDRectangle B6 = new PDRectangle( 125 * MM_TO_PT, 176 * MM_TO_PT); //横向き static PDRectangle B0_LANDSCAPE = new PDRectangle(1414 * MM_TO_PT, 1000 * MM_TO_PT); static PDRectangle B1_LANDSCAPE = new PDRectangle(1000 * MM_TO_PT, 707 * MM_TO_PT); static PDRectangle B2_LANDSCAPE = new PDRectangle( 707 * MM_TO_PT, 500 * MM_TO_PT); static PDRectangle B3_LANDSCAPE = new PDRectangle( 500 * MM_TO_PT, 353 * MM_TO_PT); static PDRectangle B4_LANDSCAPE = new PDRectangle( 353 * MM_TO_PT, 250 * MM_TO_PT); static PDRectangle B5_LANDSCAPE = new PDRectangle( 250 * MM_TO_PT, 176 * MM_TO_PT); static PDRectangle B6_LANDSCAPE = new PDRectangle( 176 * MM_TO_PT, 125 * MM_TO_PT);

Apache PDFBoxで用紙を横向きにする方法、独自の用紙サイズを設定する方法の紹介は以上です。