JasperReportsでPDFを作成する

JavaでPDFを作成するJasperReportsの使い方について解説します。少し古いライブラリですが企業がバックについてますし、継続的にメンテナンスはされているようです。

JasperReportでPDFを作成するには、

  1. JasperReport Studioでテンプレートを作成する
  2. Javaプログラムでデータを流し込んでPDFを作成する

と言う手順を踏みます。JasperReport StudioではGUIからText、Imageといったパーツを配置しますが、裏ではjrxml形式のファイルを吐き出しています。このjrxmlをコンパイルした.jasperという拡張子のファイルをJavaのプログラムから読み込み、PDFを作成するというのが通常の手順です。

PDF出力のサンプルコードは次のようになります。大抵の場合はこれで事足りると思います。

// 入力パス、出力パス
Path in = ...
Path out = ...

// .jasperを読み込む
//
// .jrxmlを直接読み込むこともできる
//
// JasperReport report = 
//   JasperCompileManager.compileReport(in.toAbsolutePath().toString());
JasperReport report = 
  (JasperReport) JRLoader.loadObject(in.toAbsolutePath().toFile());

// パラメータ、データソースの準備
Map<String, Object> parameters = new HashMap<>();
Collection<Map<String, ?>> source = new ArrayList<>();
...

// パラメータ、データソースの流し込み
// ここでは JRMapCollectionDataSource を利用しているが、他にも JRBeanCollectionDataSource などがある。
// データソースが空の時は空の JRMapCollectionDataSource ではなく JREmptyDataSource を利用する。
// 空の JRMapCollectionDataSourc を利用すると JasperReports 6.7.0 では真っ白なPDFが作成され、エラーも発生しない問題があった。
JasperPrint print = JasperFillManager.fillReport(report, parameters, new JRMapCollectionDataSource(source));
// 書き出し
JasperExportManager.exportReportToPdfFile(print, out.toAbsolutePath().toString());

テンプレートへ流し込むデータには、「パラメータ」と「データソース」の2種類があります。データソースはDetailセクションに流し込む繰り返し構造のデータで、CSVファイルをイメージすると良いです。パラメータは主にDetailセクション以外で使われる埋め込みデータです。

テンプレートの作成

まずは空のテンプレートを見ていきましょう。

次の画像はJasperReports Studioで作った空のテンプレートファイルです。テンプレートはTitle, Page Header, Column Header, Detail1, Column Footer, Page Footer, Summaryのセクションからなっています。Title, Page Headerなどはそのままの意味で、タイトルやヘッダーを表示するところです。Column Header, Detail1, Column Footerがデータを流し込んで、テーブルとして表示する場所です。

f:id:tototoshi:20190302214117p:plain

jrxmlの方も見てみましょう。Title,Page,Header...といった構造がjrxmlにも反映されていることがわかります。

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport
        xmlns="http://jasperreports.sourceforge.net/jasperreports"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
        name="Blank_A4"
        pageWidth="595"
        pageHeight="842"
        columnWidth="555"
        leftMargin="20"
        rightMargin="20"
        topMargin="20"
        bottomMargin="20"
        uuid="86d21441-0199-44fa-8d1e-8b7170bc739f">
    <queryString>
        <![CDATA[]]>
    </queryString>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
                <!-- bandタグで各セクションの高さを指定できる -->
        <band height="79" splitType="Stretch"/>
    </title>
    <pageHeader>
        <band height="35" splitType="Stretch"/>
    </pageHeader>
    <columnHeader>
        <band height="61" splitType="Stretch"/>
    </columnHeader>
    <detail>
        <band height="125" splitType="Stretch"/>
    </detail>
    <columnFooter>
        <band height="45" splitType="Stretch"/>
    </columnFooter>
    <pageFooter>
        <band height="54" splitType="Stretch"/>
    </pageFooter>
    <summary>
        <band height="42" splitType="Stretch"/>
    </summary>
</jasperReport>

各セクションは必須ではなく、不要であったら削除しても構いません。例えば何枚かに渡るレポートを作りたい時、最初の1枚は表紙になるかもしれません。その時はTitleセクションだけにして大丈夫です。

文字の埋め込み

雰囲気を掴むためにjrxmlを直接編集してみます。

文字の埋め込みは staticText タグを使います。

<title>
    <band height="79" splitType="Stretch">
        <staticText>
            <!-- reportElementタグで表示場所などを指定できる -->
            <reportElement x="0" y="0" width="100" height="50"/>
            <text><![CDATA[Invoice]]></text>
        </staticText>
    </band>
</title>

f:id:tototoshi:20190302214050p:plain

独自のフォントを埋め込む場合には設定の追加が必要です。特に日本語を利用する場合は必須です。今回はIPAフォントを利用します。

まず、クラスパス上に jasperreports_extension.properties というファイルを作成し、フォントの読み込みのための設定と、フォント情報を記述したxmlファイルへのパス(fonts/fonts.xml)を指定します。

net.sf.jasperreports.extension.registry.factory.fonts=net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory
net.sf.jasperreports.extension.simple.font.families.font=fonts/fonts.xml

fonts.xmlは次のように記述します。

<?xml version="1.0" encoding="UTF-8"?>
<fontFamilies>
    <fontFamily name="IPAexゴシック">
        <normal>fonts/ipaexg.ttf</normal>
        <pdfEncoding>Identity-H</pdfEncoding>
        <pdfEmbedded>true</pdfEmbedded>
    </fontFamily>
</fontFamilies>

その上で、jrxmlに textElement タグでフォントの名前を指定します。これで日本語フォントが利用可能になります。

<title>
    <band height="79" splitType="Stretch">
        <staticText>
            <!-- reportElementタグで表示場所などを指定できる -->
            <reportElement x="0" y="0" width="100" height="50"/>
            <textElement>
                <font fontName="IPAexゴシック" size="18"/>
            </textElement>
            <text><![CDATA[請求書]]></text>
        </staticText>
    </band>
</title>

f:id:tototoshi:20190302214028p:plain

なおフォントの使用にあたってはライセンスに注意してください。

パラメータの埋め込み

パラメータの埋め込みは textField タグを使います。textFieldExpression として $P{パラメータ名} を指定します。パラメータの名前はXMLの先頭で parameter タグで宣言しておきます。

<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.6.0.final using JasperReports Library version 6.6.0  -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4_1" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="f7adae20-db6a-4207-ae6a-75eb4a16e40e">
    <parameter name="title"/>
    <queryString>
        <![CDATA[]]>
    </queryString>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="79" splitType="Stretch">
            <textField>
                <reportElement x="0" y="0" width="100" height="50"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$P{title}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    <pageHeader>
        <band height="35" splitType="Stretch"/>
    </pageHeader>
    <columnHeader>
        <band height="61" splitType="Stretch"/>
    </columnHeader>
    <detail>
        <band height="125" splitType="Stretch"/>
    </detail>
    <columnFooter>
        <band height="45" splitType="Stretch"/>
    </columnFooter>
    <pageFooter>
        <band height="54" splitType="Stretch"/>
    </pageFooter>
    <summary>
        <band height="42" splitType="Stretch"/>
    </summary>
</jasperReport>

テーブルの作成

次のようなテーブルを作成してみましょう。

f:id:tototoshi:20190302214004p:plain

columnHeaderに見出しの内容が、detailにテーブルの一行分の内容が入ります。

<columnHeader>{ここに見出しの内容が入る}</columnHeader>
<detail>{ここにテーブルの一行分の内容が入る}</detail>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.6.0.final using JasperReports Library version 6.6.0  -->
<jasperReport
        xmlns="http://jasperreports.sourceforge.net/jasperreports"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
        name="Blank_A4"
        pageWidth="595"
        pageHeight="842"
        columnWidth="555"
        leftMargin="20"
        rightMargin="20"
        topMargin="20"
        bottomMargin="20"
        uuid="86d21441-0199-44fa-8d1e-8b7170bc739f">
    <queryString>
        <![CDATA[]]>
    </queryString>
    <field name="fruit"/>
    <field name="price"/>
    <columnHeader>
        <band height="30" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[商品]]></text>
            </staticText>
            <staticText>
                <reportElement x="100" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[価格]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="30" splitType="Stretch">
            <textField isBlankWhenNull="true">
                <reportElement x="0" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{fruit}]]></textFieldExpression>
            </textField>
            <textField isBlankWhenNull="true">
                <reportElement x="100" y="0" width="100" height="30"/>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

テーブルに埋め込むデータは「パラメータ」ではなく主に「データソース」です。データソースはコレクション形式のデータで、そのコレクション内の一つの要素がテーブル一行分のデータとなります。コレクション内の要素はその中にいくつかのフィールドを持つことになります。 今回の例の場合、データソースは「fruit」、「price」というフィールドを持つ要素のコレクションです。

データソースの内容を埋め込むには、$F{フィールドの名前} という記法を使います。フィールドの名前は field タグであらかじめ宣言しておく必要があります。

これをレンダリングするJavaプログラムは次のようになります。

Path in = ...;
Path out = ...;

JasperReport report = (JasperReport) JRLoader.loadObject(in.toAbsolutePath().toFile());

Map<String, Object> parameters = new HashMap<>();
Collection<Map<String, ?>> source = new ArrayList<>();

Map<String, Object> row1 = new HashMap<>();
row1.put("fruit", "りんご");
row1.put("price", "100");
source.add(row1);
Map<String, Object> row2 = new HashMap<>();
row2.put("fruit", "みかん");
row2.put("price", "50");
source.add(row2);
Map<String, Object> row3 = new HashMap<>();
row3.put("fruit", "バナナ");
row3.put("price", "30");
source.add(row3);

JasperPrint print = JasperFillManager.fillReport(report, parameters, new JRMapCollectionDataSource(source));
JasperExportManager.exportReportToPdfFile(print, out.toAbsolutePath().toString());

罫線や背景色の調整

reportElement タグで背景色をつけたり、 box タグで罫線を引いたりすることができます。

    <columnHeader>
        <band height="30" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="0" width="100" height="30" mode="Opaque" backcolor="rgba(200, 200, 200, 0.5)"/>
                <box>
                    <topPen lineWidth="1"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[商品]]></text>
            </staticText>
            <staticText>
                <reportElement x="100" y="0" width="100" height="30" mode="Opaque" backcolor="rgba(200, 200, 200, 0.5)"/>
                <box>
                    <topPen lineWidth="1"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <text><![CDATA[価格]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="30" splitType="Stretch">
            <textField isBlankWhenNull="true">
                <reportElement x="0" y="0" width="100" height="30"/>
                <box>
                    <topPen lineWidth="0"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{fruit}]]></textFieldExpression>
            </textField>
            <textField isBlankWhenNull="true">
                <reportElement x="100" y="0" width="100" height="30"/>
                <box>
                    <topPen lineWidth="0"/>
                    <leftPen lineWidth="1"/>
                    <bottomPen lineWidth="1"/>
                    <rightPen lineWidth="1"/>
                </box>
                <textElement verticalAlignment="Middle" textAlignment="Center">
                    <font fontName="IPAexゴシック" size="14"/>
                </textElement>
                <textFieldExpression><![CDATA[$F{price}]]></textFieldExpression>
            </textField>
        </band>
    </detail>

f:id:tototoshi:20190302213926p:plain

とまあここまでくるとJasperReports StudioのGUIも使いながらデザインするのが楽だと思います。

画像、線、矩形表示などのタグもありますが、基本的な考えは変わらないので割愛します。 より詳しくはドキュメントのJasperReports Library Ultimate Guideを参照してください。

https://community.jaspersoft.com/documentation

JasperReportsはテーブルが1つのPDF、例えば見積書や請求書のような書類を作成するのに特化しているようにも思いましたが、SubReportという機能を利用することでより複雑なレイアウトにも対応できそうです。