請求書のヘッダー部分は別途 PDF として用意し、請求書を作成する時に PDFlib+PDI のインポート機能で取り込んでいます。注文された商品の一覧を作成するのにテーブルを使用しています。また各ページの最後に subtotal を記載するため、テーブルを配置する前に PDF_info_table() の lastbodyrow キーワードで配置される行数を確認してから PDF_fit_table() を行なっています。
/*
* テーブルを使った請求書の作成:
* テーブルを使い請求書を PDF で作成します。
*
* 請求書のヘッダー部分は別途 PDF として用意し、請求書を作成する時に PDFlib+PDI の
* インポート機能で取り込んでいます。注文された商品の一覧を作成するのにテーブルを
* 使用しています。また各ページの最後に subtotal を記載するため、テーブルを配置する前に
* PDF_info_table() の lastbodyrow キーワードで配置される行数を確認してから
* PDF_fit_table() を行なっています。
*
* 必要な製品 : PDFlib+PDI/PPS 9
* 必要なデータ: PDF ファイル
*/
package com.pdflib.cookbook.pdflib.table;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import com.pdflib.pdflib;
import com.pdflib.PDFlibException;
public class table_invoice
{
public static void main (String argv[])
{
/* 必要に応じてデータファイルがあるフォルダのパスを指定する */
String searchpath = "../input";
String outfile = "table_invoice.pdf";
String title = "Table Invoice";
pdflib p = null;
String infile = "stationery.pdf";
int exitcode = 0;
int row, col, tf=-1, tbl=-1;
int i, y, stationery, itemno, page, regularfont, boldfont;
String tf_opts;
double sum = 0, total = 0, subtotal = 0, tabheight = 0;
String result;
final double pagewidth = 595, pageheight = 842;
final double fontsize = 12;
final double capheight = 8.5;
final double rowheight = 16;
final int margin = 4;
final String leading = "120%";
final int ystart = (int) pageheight - 170;
final int yoffset = 15;
final int ycontinued = 40;
final int nfooters = 1, nheaders = 1;
/* テーブルの座標は固定される。テーブルの高さのみ異なる場合がある */
final int llx = 55, urx = 505, lly = 80;
/* 個々の列の幅は固定される */
final int maxcol = 5;
final int c1 = 30, c2 = 200, c3 = 70, c4 = 70, c5 = 80;
/* 現在の日付を取得する */
Date now = new Date();
DateFormat fulldate =
DateFormat.getDateInstance(DateFormat.LONG, Locale.US);
/* テーブルの後に出力するテキスト */
final String closingtext =
"Terms of payment: 30 days net. " +
"90 days warranty starting at the day of sale. " +
"This warranty covers defects in workmanship only. " +
"Kraxi Systems, Inc. will, at its option, repair or replace the " +
"product under the warranty. This warranty is not transferable. " +
"No returns or exchanges will be accepted for wet products.";
final String[][] items = {
/* 説明, 数量, 価格 */
{ "Long Distance Glider; price includes volume discount of 20% for " +
"more than 10 items ordered",
"11", "15.96"},
{ "Turbo Flyer", "5", "39.95"},
{ "Giga Trash", "1", "179.95"},
{ "Bare Bone Kit", "3", "49.95"},
{ "Nitty Gritty", "10", "19.95"},
{ "Pretty Dark Flyer", "1", "74.95"},
{ "Free Gift", "2", "29.95"},
{ "Giant Wing", "2", "29.95"},
{ "Cone Head Rocket; price includes volume discount of 30% for " +
"more than 20 items ordered",
"25", "6.97"},
{ "Super Dart", "2", "29.95"},
{ "German Bi-Plane", "6", "9.95"},
{ "Turbo Glider; price includes volume discount of 20% for " +
"more than 10 items ordered",
"11", "15.96"},
{ "Red Baron", "5", "39.95"},
{ "Mega Rocket", "1", "179.95"},
{ "Kit the Kat", "3", "49.95"},
{ "Red Wing", "10", "19.95"},
{ "Dark Rider", "1", "74.95"},
{ "Speedy Gift", "2", "29.95"},
{ "Giant Bingo", "2", "29.95"},
{ "Ready Rocket; price includes volume discount of 30% for " +
"more than 20 items ordered",
"25", "6.97"},
};
final String [] address = {
"John Q. Doe", "255 Customer Lane", "Suite B",
"12345 User Town", "Everland"
};
BigDecimal value, roundedValue;
try {
p = new pdflib();
p.set_option("searchpath={" + searchpath + "}");
/* load_font() 等でエラーが起きた場合、戻り値をチェックする */
p.set_option("errorpolicy=return");
if (p.begin_document(outfile, "") == -1)
throw new Exception("Error: " + p.get_errmsg());
p.set_info("Creator", "PDFlib Cookbook");
p.set_info("Title", title);
/* インポートしたPDFドキュメントを開く*/
stationery = p.open_pdi_document(infile, "");
if (stationery == -1)
throw new Exception("Error: " + p.get_errmsg());
/* インポートしたPDFの1ページ目を開く */
page = p.open_pdi_page(stationery, 1, "");
if (page == -1)
throw new Exception("Error: " + p.get_errmsg());
/* ボールドとレギュラースタイルのフォントをロードする*/
boldfont = p.load_font("Helvetica-Bold", "unicode", "");
if (boldfont == -1)
throw new Exception("Error: " + p.get_errmsg());
regularfont = p.load_font("Helvetica", "unicode", "");
if (regularfont == -1)
throw new Exception("Error: " + p.get_errmsg());
/* 出力ページを始める*/
p.begin_page_ext(pagewidth, pageheight, "");
/* インポートしたPDFページを配置してクローズする */
p.fit_pdi_page(page, 0, 0, "");
p.close_pdi_page(page);
/* 顧客のアドレスを出力する */
y = ystart;
p.setfont(regularfont, fontsize);
for (i = 0; i < address.length; i++) {
p.fit_textline(address[i], llx, y, "");
y -= yoffset;
}
/* ヘッダーと日付を出力する */
y -= 3 * yoffset;
p.setfont(boldfont, fontsize);
p.fit_textline("INVOICE", llx, y, "position {left top}");
p.fit_textline(fulldate.format(now), urx, y, "position {right top}");
y -= 3 * yoffset;
/* ----------------------------------------------------
* ヘッダーセルを含む最初のテーブル行を追加する
* ----------------------------------------------------
*/
/* テーブルヘッダーのテキスト行セルを追加するための、一般的なオプション
* リストを準備する。行の高さとテキストの位置を指定する。
* テキストはそれぞれ右上か左上に配置される。
*
* テキストラインとテキストフローの縦方向の位置については下記に注意する。
* 大文字の高さは、フォントのキャップハイトの値で正確に表される。
* そのため、キャップハイト値を使用してフォントサイズを指定する。
* 例えば、キャップハイトを8.5にするとフォントサイズは約12ポイントとなり、
* マージン4も加えると、行全体の高さが16ポイントになる。
*/
final String head_opts_right = "fittextline={position={right top} " +
" font=" + boldfont + " fontsize={capheight=" + capheight + "}} " +
" rowheight=" + rowheight + " margin=" + margin;
final String head_opts_left = "fittextline={position={left top} " +
" font=" + boldfont + " fontsize={capheight=" + capheight + "}} " +
" rowheight=" + rowheight + " margin=" + margin;
col = 1; row = 1;
/* 上記で定義したオプションリストを使用して、各ヘッダーセルを追加する。
* さらに列幅を指定する。
*/
tbl = p.add_table_cell(tbl, col++, row, "ITEM",
head_opts_right + " colwidth=" + c1);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
tbl = p.add_table_cell(tbl, col++, row, "DESCRIPTION",
head_opts_left + " colwidth=" + c1);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
tbl = p.add_table_cell(tbl, col++, row, "QUANTITY",
head_opts_right + " colwidth=" + c3);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
tbl = p.add_table_cell(tbl, col++, row, "PRICE",
head_opts_right + " colwidth=" + c4);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
tbl = p.add_table_cell(tbl, col++, row, "SUM",
head_opts_right + " colwidth=" + c5);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
row++;
/* -------------------------------------------
* 後続のテーブル行にボディセルを追加する
* -------------------------------------------
*/
/* テーブル本体に、テキスト行を追加するための一般的なオプションリストを指定する。
* ヘッダーセル用に定義したオプションリストと似ているが、
* ここでは、フォントはレギュラーフォントを使用する。
*/
final String body_opts = "fittextline={position={right top} " +
" font=" + regularfont +
" fontsize={capheight=" + capheight + "}} " +
" rowheight=" + rowheight + " margin=" + margin;
for (itemno = 1; itemno <= items.length; itemno++, row++) {
col = 1;
/* ---------------------------------------------------------------
* 1番目の列(ITEM)にテキストラインセルを、上記のボディセルで
* 定義したオプションと共に追加する。
* ---------------------------------------------------------------
*/
tbl = p.add_table_cell(tbl, col++, row, String.valueOf(itemno),
body_opts);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
/* --------------------------------------------------------------
* 2番目の列(DESCRIPTION)にテキストフローセルを追加する
* --------------------------------------------------------------
*/
/* テキストフローを追加するためのオプションリストを準備する。
*
* テキストラインとテキストフローの縦方向の位置については下記に注意する。
* 大文字の高さは、フォントのキャップハイトの値で正確に表される。
* そのため、キャップハイト値を使用してフォントサイズを指定する。
* 例えば、キャップハイトを8.5にするとフォントサイズは約12ポイントとなり
* マージン4も加えると、行全体の高さが16ポイントになる。
*/
tf_opts = "font=" + regularfont +
" fontsize={capheight=" + capheight + "} leading=" + leading;
/* テキストフローセルを追加するためのオプションリストを準備する
*
* テキストフローの1行目はテキストラインのベースラインに揃える。
* 同時に、テキストラインはセルの上部の境界線から、テキストフローと
* 同じ距離にある必要がある。
* 上端に余白を生じさせないために、テキストフローセルを追加する際に
* fittextflow={firstlinedist=capheight} を使用する。そしてテキスト
* ラインと同じく、マージン4ポイントを追加する。
*
*/
final String bodytf_opts = "fittextflow={firstlinedist=capheight}" +
" colwidth=" + c2 + " margin=" + margin;
/* 上記で定義したオプションを使用してテキストフローを追加する */
tf = p.add_textflow(-1, items[itemno-1][0], tf_opts);
if (tf == -1)
throw new Exception("Error: " + p.get_errmsg());
/* 上記で定義したオプションを使用してテーブルセルを追加する */
tbl = p.add_table_cell(tbl, col++, row, "",
bodytf_opts + " textflow=" + tf);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
tf = -1;
/* -----------------------------------------------------------
* 3番目の列(Quantity)にテキストラインセルを、上記のボディセルで
* 定義したオプションと共に追加する。
* -----------------------------------------------------------
*/
tbl = p.add_table_cell(tbl, col++, row, items[itemno-1][1],
body_opts);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
/* -----------------------------------------------------------
* 4番目の列(Price)にテキストラインセルを、上記のボディセルで
* 定義したオプションと共に追加する。
* -----------------------------------------------------------
*/
tbl = p.add_table_cell(tbl, col++, row, items[itemno-1][2],
body_opts);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
/* ---------------------------------------------------------------
* 5番目の列(SUM)にテキストラインセルを、上記のボディセルで定義
* したオプションと共に追加する。
* 小数点以下を2桁でフォーマットする
* ---------------------------------------------------------------
*/
sum = Double.valueOf(items[itemno-1][1]).doubleValue() *
Double.valueOf(items[itemno-1][2]).doubleValue();
value = new BigDecimal(sum);
roundedValue = value.setScale(2, RoundingMode.HALF_UP);
tbl = p.add_table_cell(tbl, col, row,
roundedValue.toString(), body_opts);
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
/* 総計を計算する */
total += sum;
}
/* マッチボックスと呼ばれる空のフッター(subtotal)を追加する。
* ここに小計または総計を追加する。
* マッチボックス2つの列(4列目、5列目)にまたがっている。
*/
final String footer_opts =
"rowheight=" + rowheight + " colspan=2 margin =" + margin +
" matchbox={name=subtotal}";
tbl = p.add_table_cell(tbl, maxcol-1, row, "", footer_opts + "");
if (tbl == -1)
throw new Exception("Error adding cell: " + p.get_errmsg());
/* ------------------------------------
* テーブルを複数のページに配置する
* ------------------------------------
*/
/* 全てのテーブルが配置されるまで繰り返す。
* 配置するテーブルインスタンスの分だけ、新しいページを作成する
*/
do {
/* 最初の行はヘッダー行で新しいページ毎に繰り返される。
* 最後の行はフッター行で同様に新しいページごとに繰り返される。
* ヘッダー行は水色で塗りつぶし、フッター行はオレンジで塗りつぶす。
* 奇数行はグレーで塗りつぶす
*/
final String fit_opts =
"header=" + nheaders + " footer=" + nfooters +
" fill={{area=rowodd fillcolor={gray 0.9}} " +
"{area=header fillcolor={rgb 0.90 0.90 0.98}} " +
"{area=footer fillcolor={rgb 0.98 0.92 0.84}}}";
/* テーブルインスタンスを配置する */
result = p.fit_table(tbl, llx, lly, urx, y, fit_opts);
/* エラーが発生したか、もしくはテーブルのフィットボックスが小さすぎて
* コンテンツが入りきらない場合
*/
if (result.equals("_error"))
throw new Exception ("Couldn't place table : " +
p.get_errmsg());
/* もし全ての行が配置されている場合はフッター行に定義されている
* マッチボックスに合計を出力する。マッチボックスをfit_textline()に
* 直接定義することはできないため、マッチボックスの座標を取得し、
* テキストを合わせる。
*/
if (!result.equals("_boxfull")) {
/* 総計(total)を小数点以下を2桁でフォーマットする */
value = new BigDecimal(total);
roundedValue = value.setScale(2, RoundingMode.HALF_UP);
String contents = "total: " + roundedValue;
/* 「subtotal」マッチボックスの3番目の(右上)の角の座標を取得する。
* パラメータ 1 はマッチボックスの最初のインスタンスを示している。
*/
double x3 = 0, y3 = 0;
if ((int) p.info_matchbox("subtotal", 1, "exists") == 1) {
x3 = p.info_matchbox("subtotal", 1, "x3");
y3 = p.info_matchbox("subtotal", 1, "y3");
}
else {
throw new Exception("Error: " + p.get_errmsg());
}
/* 取得したコーナー座標 (x3, y3)になマージン幅を加え
* テキスト行を開始する。テキストは右揃えにする。
*/
p.setfont(boldfont, fontsize);
p.fit_textline(contents, x3 - margin, y3 - margin,
"position={right top}");
}
/* 次のページに残りの行を配置する前に、最後のテーブル列の下にある
* 現在のページのテーブルインスタンスの全ての行の小計を出力する。
*/
else if (result.equals("_boxfull")) {
/* テーブルインスタンスの最後の本文行の行数を取得する */
double lastrow = p.info_table(tbl, "lastbodyrow");
/* 小計を計算する */
for (i = 0, subtotal = 0; i < lastrow - nfooters; i++) {
subtotal += Double.valueOf(items[i][1]).doubleValue() *
Double.valueOf(items[i][2]).doubleValue();
}
/* フッター行に定義されたマッチボックスに小計を出力する。
* マッチボックスは直接参照することができないため、マッチボックスの
* 座標を取得し、テキストを合わせる。
*/
/* 小計(subtotal)を小数点以下を2桁でフォーマットする */
value = new BigDecimal(subtotal);
roundedValue = value.setScale(2, RoundingMode.HALF_UP);
String contents = "subtotal: " + roundedValue;
/* 「subtotal」マッチボックスの3番目の(右上)の角の座標を取得する。
* パラメータの 1 はマッチボックスの最初のインスタンスを示している。
*/
double x3 = 0, y3 = 0;
if ((int) p.info_matchbox("subtotal", 1, "exists") == 1) {
x3 = p.info_matchbox("subtotal", 1, "x3");
y3 = p.info_matchbox("subtotal", 1, "y3");
}
else {
throw new Exception("Error: " + p.get_errmsg());
}
/* 取得したコーナー座標 (x3, y3)にマージン幅を加え
* テキスト行を開始する。テキストは右揃えにする。
*/
p.setfont(boldfont, fontsize);
p.fit_textline(contents, x3 - margin, y3 - margin,
"position={right top}");
/*「Continued」を出力する */
p.setfont(regularfont, fontsize);
p.fit_textline("-- Continued --", urx, ycontinued,
"position {right top}");
p.end_page_ext("");
p.begin_page_ext(pagewidth, pageheight, "");
y = ystart;
}
} while (result.equals("_boxfull"));
/* -----------------------------------------------
* テーブルの後にテキストを配置する
* -----------------------------------------------
*/
/* 現在のテーブルインスタンスのテーブルの高さを取得する */
tabheight = p.info_table(tbl, "height");
y = y - (int) tabheight - yoffset;
/* テーブルの後に配置するテキストフローを追加する */
tf_opts = "font=" + regularfont + " fontsize=" + fontsize +
" leading=" + leading + " alignment=justify";
tf = p.add_textflow(-1, closingtext, tf_opts);
if (tf == -1)
throw new Exception("Error: " + p.get_errmsg());
/*
* 全てのテキストを配置し、fit_textflow()の戻り値が「_stop」に
* なるまでループする
*/
do {
/* テキストフローを配置する */
result = p.fit_textflow(tf, llx, lly, urx, y, "");
if (result.equals("_error"))
throw new Exception ("Couldn't place table : " +
p.get_errmsg());
if (result.equals("_boxfull") || result.equals("_boxempty")) {
p.setfont(regularfont, fontsize);
p.fit_textline("-- Continued --", urx, ycontinued,
"position {right top}");
p.end_page_ext("");
p.begin_page_ext(pagewidth, pageheight, "");
y = ystart;
}
} while (!result.equals("_stop"));
p.end_page_ext("");
p.end_document("");
p.close_pdi_document(stationery);
} catch (PDFlibException e) {
System.err.println("PDFlib exception occurred:");
System.err.println("[" + e.get_errnum() + "] " + e.get_apiname() +
": " + e.get_errmsg());
exitcode = 1;
} catch (Exception e) {
System.err.println(e.toString());
exitcode = 1;
} finally {
if (p != null) {
p.delete();
}
System.exit(exitcode);
}
}
}