【Java】PDFboxで文字をページ中央に配置したい

ちょっとしたことをしようとして変な深みにハマった例です。

背景

PDFに透かしを入れることにしました。せっかくなので、文字を斜めに傾けながらページ中央に大きく表示させたいなと考えました。

課題

文字の位置がページの中央に来ない。

原因

PDPageContentStream.setTextMatrixで回転させると、その後の計算は回転後の座標系で考えないといけないことに気づかなかった。(ずっと最初の座標系で計算してました。。。) 解説画像

対策

回転前と後のページ中央の点の移動量分、出力位置をずらして文字を出力する。

package jp.javadrive;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

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.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.util.Matrix;

public class Sample01 {

    /**
     * @param args the command line arguments
     */

    public static void main(String[] args) {
        try {
            // 空のドキュメントオブジェクトを作成します
            PDDocument document = new PDDocument();

            // 画像オブジェクトを作成する
            BufferedImage img = ImageIO.read(new File("/path/to/image/sample.tiff"));
            PDImageXObject image = LosslessFactory.createFromImage(document, img);

            // 新しいページのオブジェクトを作成します
            PDPage page = SetPageFrame(image);
            document.addPage(page);

            // 出力用のストリームを開いて画像を描写する
            PDPageContentStream contentStream = new PDPageContentStream(document, page);
            contentStream.drawImage(image, 0, 0, image.getWidth(), image.getHeight());

            // フォントを生成
            PDFont font = PDType1Font.HELVETICA_BOLD;
            float per = 0.7f;
            String msg = "Content Stream";
            float fontSize = image.getWidth() * per * 1000f / font.getStringWidth(msg);
            float fontHeight = ( font.getFontDescriptor().getCapHeight()) / 1000 * fontSize;

            float fontWidth = fontSize * font.getStringWidth(msg) / 1000f;

            // 文字出力位置の計算
            float theta = 0.1f;

            float x = image.getWidth()/2f;
            float y = image.getHeight()/2f;

            float cx = SetTextPositionX(x, y, -theta);
            float cy = SetTextPositionY(x, y, -theta);

            float dcx = cx - x;
            float dcy = cy - y;

            // 文字列の出力
            contentStream.beginText();
            Matrix rot = new Matrix();
            rot.rotate(theta);
            rot.translate(-fontWidth/2f+x+dcx, -fontHeight/2f+y+dcy);
            contentStream.setTextMatrix(rot);
            contentStream.setFont(font, fontSize);
            contentStream.showText(msg);
            contentStream.endText();

            // ストリームを閉じる
            contentStream.close();

            // ドキュメントを保存します
            document.save("sample04.pdf");
            document.close();
          }
          catch (IOException e) {
            e.printStackTrace();
          }
    }

	private static float SetTextPositionY(float x, float y, float theta) {
		// 回転後のx座標を計算
		float stry = x * (float) Math.sin(theta) + y * (float) Math.cos(theta);
		return stry;
	}

	private static float SetTextPositionX(float x,float y,float theta) {
		// 回転後のy座標を計算
		float strx = x * (float) Math.cos(theta) - y * (float) Math.sin(theta);
		return strx;
	}

	private static PDPage SetPageFrame(PDImageXObject image) {
		// TODO 自動生成されたメソッド・スタブ
        PDRectangle pageSize = new PDRectangle();
        pageSize.setLowerLeftX(0);
        pageSize.setLowerLeftY(0);
        pageSize.setUpperRightX(image.getWidth());
        pageSize.setUpperRightY(image.getHeight());
		return new PDPage(pageSize);
	}
}

参考サイト