PDFBoxを使って既存のPDF文書に線を引く


PDFBox を使って既存のPDF文書に対して線を書き込みたいというニッチなニーズがあったので、その方法を紹介します。例えば、行政に提出する様式で、申請内容に応じて関係のない部分に取り消し線を引くといったケースを想定しています。

確認環境

  • PDFBox 2.0.15
  • AdoptOpenJDK 11.0.3+7
  • macOS 10.14.6

実現方法

  • PDPageContentStream を初期化するときに AppendMode.APPEND を指定してあげるのがポイント
    • デフォルトだと AppendMode.OVERWRITE なので、元の内容がクリアされて真っ白な背景になってしまう
  • PDF の座標系はページの左下が (0, 0) で右上に向かって大きくなる
    • 描画位置の特定方法は後述
void line() throws IOException {
    try (InputStream is = getInputStream("sample.pdf");
         PDDocument doc = PDDocument.load(is)) {

        PDPage page = doc.getPage(0);
        PDPageContentStream contents = new PDPageContentStream(doc, page, AppendMode.APPEND, false);

        contents.moveTo(290f, 808f); // 始点座標
        contents.lineTo(365f, 808f); // 終点座標
        contents.setLineWidth(1f);   // 線の太さ
        contents.stroke();           // 描画

        contents.moveTo(290f, 805f); // 始点座標
        contents.lineTo(365f, 805f); // 終点座標
        contents.setLineWidth(1f);   // 線の太さ
        contents.stroke();           // 描画

        contents.close();
        doc.save("/tmp/output.pdf");
    }
}

描画位置の特定

適切なツールを使うとかもっと賢い方法がありそうですが、分からなかったので泥臭く自分で座標系を描画してあげることにします。

void grid() throws IOException {
    try (InputStream is = getInputStream("sample.pdf");
         PDDocument doc = PDDocument.load(is)) {
        PDPage page = doc.getPage(0);

        // ページ左下から右上に向かって格子線を引く
        PDPageContentStream contents = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
        PDRectangle bbox = page.getBBox();
        for (float x = 0; x < bbox.getWidth(); x += 10) {
            contents.moveTo(bbox.getLowerLeftX() + x, bbox.getLowerLeftY());
            contents.lineTo(bbox.getLowerLeftX() + x, bbox.getUpperRightY());
            contents.setLineWidth((x % 100 == 0) ? 2f : 0.5f);
            contents.stroke();
        }
        for (float y = 0; y < bbox.getHeight(); y += 10) {
            contents.moveTo(bbox.getLowerLeftX(), bbox.getLowerLeftY() + y);
            contents.lineTo(bbox.getUpperRightX(), bbox.getLowerLeftY() + y);
            contents.setLineWidth((y % 100 == 0) ? 2f : 0.5f);
            contents.stroke();
        }
        contents.close();

        doc.save("/tmp/output.pdf");
    }
}

あとは左下から目盛りを数えてやれば、描画したい位置の大まかな座標を特定できるというわけです。

もっと良い方法があれば、ぜひ教えて下さい