Apache PDFBoxで用紙を横向きにする方法

Java で簡単に PDF の作成 加工ができるライブラリー Apache PDFBox。用紙サイズのプリセットとして PDRectangle.A4 PDRectangle.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 になります。

A4横向きの用紙サイズを自分で作る
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 で用紙を横向きにする方法 独自の用紙サイズを設定する方法の紹介は以上です。

この記事を共有しませんか?