聞こえないJavaエンジニアが適当に書き連ねていく

つらつらとメモしたり日頃の溜まっている想いを吐き出す場所です。

JavaでHTMLファイルをpdfに変換する

これは何

HTMLファイルをPDFファイルに変換する方法のメモです。

前提

htmlファイルは以下の手順で作成したものをそのまま使う想定です。

tadashi.hatenablog.com

ライブラリなど

iTextとflying-saucer-pdfが今回のpdf変換で使うライブラリです。

<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.lowagie/itext -->
<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf -->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.16</version>
</dependency>

ディレクトリ構成

outputディレクトリの下にhtmlファイルとpdfを出力する想定です。 html出力先にfontsディレクトリ及び、フォントファイルが無いとpdf変換時に日本語が出力されません。

src
  └ main
        ├  java
        │     └ sample
        │           └ Main.java
        └ resource
               └ sample.html
output
  └ fonts
        └  ipag.ttf

Javaプログラム

package sample;

import java.io.*;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.IContext;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

public class Main {

    public void execute(String... args) throws Exception {
        // テンプレートエンジンを初期化する
        final TemplateEngine engine = initializeTemplateEngine();
        // コンテキストを生成する
        final IContext ctx = makeContext(args);
        // 今回はWriter経由で結果を出力するのでWriterも初期化
        final Writer writer = new FileWriter("output/sample.html");
        // テンプレート名とコンテキストとWriterを引数としてprocessメソッドをコール
        engine.process("sample", ctx, writer);
        // Writerをクローズ
        writer.close();

        // ここからがpdf出力用
        File html = new File("output/sample.html");
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(html);
        renderer.layout();

        String outputFile = "output/report.pdf";
        try (OutputStream os = new FileOutputStream(outputFile)) {
            renderer.createPDF(os);
        }

    }

    private TemplateEngine initializeTemplateEngine() {
        // エンジンをインスタンス化
        final TemplateEngine templateEngine = new TemplateEngine();
        // テンプレート解決子をインスタンス化(今回はクラスパスからテンプレートをロードする)
        final ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        // テンプレートモードはXHTML
        resolver.setTemplateMode("XHTML");
        // クラスパスのtemplatesディレクトリ配下にテンプレートファイルを置くことにする
        resolver.setPrefix("templates/");
        // テンプレートの拡張子はhtml
        resolver.setSuffix(".html");
        // テンプレート解決子をエンジンに設定
        templateEngine.setTemplateResolver(resolver);
        return templateEngine;
    }

    private IContext makeContext(String... args) {
        final IContext ctx = new Context();
        // 変数マップにテンプレート変数を設定

        String[] values = {"aaa", "bbb"};

        ((Context) ctx).setVariable("args", values);

        SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
        Calendar cal = Calendar.getInstance();
        ((Context) ctx).setVariable("today", dateFormat.format(cal.getTime()));

        return ctx;
    }

    public static void main(String... args) throws Exception {
        new Main().execute(args);
    }
}

htmlファイル

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org" xml:lang="ja" lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Sample</title>
    <!-- スタイルシートの中でフォントを明示的に指定しないとpdf出力時に日本語が出ない -->
    <style>
        @font-face{
            font-family: "IPAGothic";
            src: url("fonts/ipag.ttf");
            -fs-pdf-font-embed: embed;
            -fs-pdf-font-encoding: Identity-H;
        }
        body{
            font-family: "IPAGothic";
        }
    </style>

</head>
<body>
<h1>Arguments</h1>
<p>
    The sample application received
    <span th:text="${#arrays.isEmpty(args)} ? 'no' : 'some'">...</span>
    arguments.
</p>

<p>今日 is: <span th:text="${today}">13 February 2011</span></p>

<ul>
    <li th:each="a : ${args}"><span th:text="${a}">...</span></li>
</ul>
</body>
</html>

出来上がったpdf

こんな感じのpdfがhtmlファイル経由で出力できます。

f:id:su_zu_ki_1010:20190222172810p:plain
出来上がったpdf

感想

JasperReportsでGUIで帳票のレイアウトを決めるのもいいけど、Thymeleafでhtmlを出力して、iTextでpdfに変換する方が可読性がいいかもしれない。