Apache PDFBox でテーブルを表示する

はじめに

前回は PDFBox による文章の Box 表示を見ました。

blog1.mammb.com

PDFBox が提供する PDF 操作の API は、低レベルな操作に限定されており、文章の折返しなども自身で処理する必要がありました。

テーブルの表示も同じく専用の API などは用意されておらず、線画を組み合わせて自身で描画する必要があります。


PDFBox での矩形描画

テーブルのセルを描画するには、単に四角形を描画することになります。

addRect(x, y, width, height) が用意されているため、以下のように四角形を描画できます。

float x = 10;
float y = 10;
float width  = 100;
float height = 100;

content.addRect(x, y, width, height);
content.stroke();

この場合、x, y は、四角形の左下の座標となるので注意が必要です。

または、moveTo(), lineTo(), stroke() を組み合わせて四辺を描画します。

content.moveTo(x, y);
content.lineTo(x + width, y);
content.lineTo(x + width, y + height);
content.lineTo(x , y + height);
content.lineTo(x , y);
content.stroke();


PDFBox でのテーブル描画

単純に線画とテキストの表示を組み合わせて以下のようにテーブルを描画することができます。

try (PDDocument doc = new PDDocument()) {

    PDPage page = new PDPage(PDRectangle.A4);
    doc.addPage(page);

    try (PDPageContentStream content = new PDPageContentStream(doc, page)) {
        String[][] data =
            {{"1", "first",  "One"},
             {"2", "second", "Two"},
             {"3", "third",  "Three"}};
        drawTable(page, content, data, 700, 100);
    }

    doc.save("build/example4.pdf");

} catch (IOException e) {
    throw new RuntimeException(e);
}

テーブルの描画は以下のようになります。

public static void drawTable(PDPage page, PDPageContentStream content,
        String[][] data, float posY, float margin) throws IOException {

    final PDFont font = PDType1Font.HELVETICA_BOLD;
    float fontSize = 12;
    float fontHeight = font.getFontDescriptor()
            .getFontBoundingBox().getHeight() / 1000 * fontSize;

    final int rows = data.length;
    final int cols = data[0].length;
    final float rowHeight = fontHeight * 1.5f;
    final float tableWidth = page.getMediaBox().getWidth() - margin * 2;
    final float tableHeight = rowHeight * rows;
    final float colWidth = tableWidth / cols;

    float y = posY;
    for (int i = 0; i <= rows; i++) {
        content.moveTo(margin, y);
        content.lineTo(margin + tableWidth, y);
        content.stroke();
        y -= rowHeight;
    }

    float x = margin;
    for (int i = 0; i <= cols; i++) {
        content.moveTo(x, posY);
        content.lineTo(x, posY - tableHeight);
        content.stroke();
        x += colWidth;
    }

    content.setFont(PDType1Font.HELVETICA_BOLD, fontSize);
    final float cellMargin = 5;
    float textX = margin + cellMargin;
    float textY = posY - fontHeight;
    for (final String[] rowData : data) {
        for (String text : rowData) {
            content.beginText();
            content.newLineAtOffset(textX, textY);
            content.showText(text);
            content.endText();
            textX += colWidth;
        }
        textY -= rowHeight;
        textX = margin + cellMargin;
    }
}

以下のような PDF が出力されます。

f:id:Naotsugu:20200329182452p:plain


セルテキストの Box 表示

上記は、単純に線画とテキスト表示を行っているため、長いテキストがあると以下のような表示になってしまいます。

f:id:Naotsugu:20200329182831p:plain

セル幅に収まるかどうかを font.getStringWidth(text) で調べつつ、折返しを行い、行の高さを調整しなければなりません。

これらは自作してもいいですが、easytable を使うこともできます。


easytable によるテーブル表示

依存に以下を追加します。

implementation 'com.github.vandeseer:easytable:0.6.4'

easytable によるテーブル作成は以下のようになります。

private static void drawPage(PDDocument doc, PDPage page) {

    try (PDPageContentStream content = new PDPageContentStream(doc, page)) {
        Table table = Table.builder()
                .font(PDType1Font.HELVETICA_BOLD).fontSize(12)
                .addColumnsOfWidth(120, 120, 120)
                .addRow(Row.builder()
                        .add(TextCell.builder().text("1").borderWidth(1).build())
                        .add(TextCell.builder().text("first long long long text").borderWidth(1).build())
                        .add(TextCell.builder().text("One").borderWidth(1).build())
                        .build())
                .addRow(Row.builder()
                        .add(TextCell.builder().text("2").borderWidth(1).build())
                        .add(TextCell.builder().text("second").borderWidth(1).build())
                        .add(TextCell.builder().text("Two").borderWidth(1).build())
                        .build())
                .addRow(Row.builder()
                        .add(TextCell.builder().text("3").borderWidth(1).build())
                        .add(TextCell.builder().text("third").borderWidth(1).build())
                        .add(TextCell.builder().text("Three").borderWidth(1).build())
                        .build())
                .build();

        TableDrawer tableDrawer = TableDrawer.builder()
                .contentStream(content)
                .startX(100)
                .startY(700)
                .table(table)
                .build();

        tableDrawer.draw();

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

出力結果は以下のようになります。

f:id:Naotsugu:20200329190704p:plain

長いテキストが折り返し表示されました。


まとめ

PDFBox でテーブルを表示する方法について見ました。

easytable を使うことでセル内の折返しなど、細かなことを気にせずにテーブル描画ができます。

easytable を使わずとも、そう難しいことでもないので、ユーティリティを作ってしまうのも良いかもしれません。




PDF構造解説

PDF構造解説

  • 作者:John Whitington
  • 発売日: 2012/05/25
  • メディア: 単行本(ソフトカバー)