table/table_invoice
Create an invoice using the table formatter.
Download Java Code Switch to PHP Code Show Output
/*
* Table invoice:
* Create an invoice using the table feature
*
* Create an invoice by importing a background PDF page with the company's
* stationery header. Output the customer's address data as well as a table
* listing all items ordered including their quantities and prices. If the table
* spans several pages output a subtotal after each table instance. To retrieve
* the last row having been output in a table instance use info_table() with the
* "lastbodyrow" option. In the last table row, output the total at the end of
* the table. Output some final text directly after the table. Use info_table()
* with the "height" option to retrieve the exact end position of the table.
*
* Required software: PDFlib+PDI/PPS 9
* Required data: PDF file
*/
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[])
{
/* This is where the data files are. Adjust as necessary. */
String searchpath = "../input";
/* By default annotations are also imported. In some cases this
* requires the Noto fonts for creating annotation appearance streams.
* We therefore set the searchpath to also point to the font directory.
*/
String fontpath = "../resource/font";
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;
/* The table coordinates are fixed; only the height of the table may differ
*/
final int llx = 55, urx = 505, lly = 80;
/* The widths of the individual columns is fixed */
final int maxcol = 5;
final int c1 = 30, c2 = 200, c3 = 70, c4 = 70, c5 = 80;
/* Get the current date */
Date now = new Date();
DateFormat fulldate =
DateFormat.getDateInstance(DateFormat.LONG, Locale.US);
/* Text to output after the table */
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 = {
/* Description, Quantity, Price */
{ "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"
};
/* Used to format the prices to a maximum of to fraction digits */
BigDecimal value, roundedValue;
try {
p = new pdflib();
p.set_option("searchpath={" + searchpath + "}");
p.set_option("searchpath={" + fontpath + "}");
/* This means we must check return values of load_font() etc. */
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);
/* Open the PDF document */
stationery = p.open_pdi_document(infile, "");
if (stationery == -1)
throw new Exception("Error: " + p.get_errmsg());
/* Open the first page of the PDF */
page = p.open_pdi_page(stationery, 1, "");
if (page == -1)
throw new Exception("Error: " + p.get_errmsg());
/* Load the bold and regular styles of a font */
boldfont = p.load_font("NotoSerif-Bold", "unicode", "");
if (boldfont == -1)
throw new Exception("Error: " + p.get_errmsg());
regularfont = p.load_font("NotoSerif-Regular", "unicode", "");
if (regularfont == -1)
throw new Exception("Error: " + p.get_errmsg());
/* Start the output page */
p.begin_page_ext(pagewidth, pageheight, "");
/* Fit and close the imported PDF page */
p.fit_pdi_page(page, 0, 0, "");
p.close_pdi_page(page);
/* Output the customer's address */
y = ystart;
p.setfont(regularfont, fontsize);
for (i = 0; i < address.length; i++) {
p.fit_textline(address[i], llx, y, "");
y -= yoffset;
}
/* Print the header and the date */
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;
/* ----------------------------------------------------
* Add the first table row containing the heading cells
* ----------------------------------------------------
*/
/* Prepare the general option list for adding text line cells of the
* table header:
* Define a fixed row height, and the position of the text line to be on
* the top left with a margin of 4, for example.
* The text will be aligned on the top right or on the top left,
* respectively.
* For an exact vertical alignment of the text line and the Textflow
* which will be added later note the following:
* The height of an uppercase letter is exactly represented by the
* capheight value of the font. For this reason use the capheight in the
* font size specification. For example, a capheight of 8.5 will
* approximately result in a font size of 12 points and (along with
* "margin=4"), will sum up to an overall height of 16 points.
*/
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;
/* Add each heading cell with the option list defined above;
* in addition, supply a fixed column width
*/
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++;
/* -------------------------------------------
* Add the body cells in subsequent table rows
* -------------------------------------------
*/
/* Prepare the general option list for adding text line cells of the
* table body; it is similar to the option list defined for header cells
* but the font is set to a regular font instead
*/
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;
/* ---------------------------------------------------------------
* Add the text line cell containing the Item in the first column,
* with the options defined for table body cells above
* ---------------------------------------------------------------
*/
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());
/* --------------------------------------------------------------
* Add the Textflow cell containing the Description in the second
* column
* --------------------------------------------------------------
*/
/* Prepare the option list for adding the Textflow.
* For an exact vertical alignment of the Textflow and the text
* lines added as well note the following:
* The height of an uppercase letter is exactly represented by the
* capheight value of the font. For this reason use the capheight in
* the font size specification. For example, a capheight of 8.5 will
* approximately result in a font size of 12 points and (along with
* "margin=4"), will sum up to an overall height of 16 points.
*/
tf_opts = "font=" + regularfont +
" fontsize={capheight=" + capheight + "} leading=" + leading;
/* Prepare the option list for adding the Textflow cell
*
* The first line of the Textflow should be aligned with the
* baseline of the text lines. At the same time, the text lines
* should have the same distance from the top cell border as the
* Textflow. To avoid any space from the top add the Textflow cell
* using "fittextflow={firstlinedist=capheight}". Then add a margin
* of 4 points, the same as for the text lines.
*/
final String bodytf_opts = "fittextflow={firstlinedist=capheight}" +
" colwidth=" + c2 + " margin=" + margin;
/* Add the Textflow with the options defined above */
tf = p.add_textflow(-1, items[itemno-1][0], tf_opts);
if (tf == -1)
throw new Exception("Error: " + p.get_errmsg());
/* Add the Textflow table cell with the options defined above */
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;
/* -----------------------------------------------------------
* Add the text line cell containing the Quantity in the third
* column, with the options defined for table body cells above
* -----------------------------------------------------------
*/
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());
/* -----------------------------------------------------------
* Add the text line cell containing the Price in the third
* column, with the options defined for table body cells above
* -----------------------------------------------------------
*/
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());
/* ---------------------------------------------------------------
* Add the text line cell containing the sum with the options
* defined for table body cells above. Format them to a maximum of
* two fraction digits.
* ---------------------------------------------------------------
*/
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());
/* Calculate the overall sum */
total += sum;
} /* for */
/* Add an empty footer row containing a matchbox called "subtotal".
* It will be filled with the subtotal or total later. The matchbox
* starts in the column before last and spans two columns.
*/
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());
/* ------------------------------------
* Place the table on one or more pages
* ------------------------------------
*/
/* Loop until all of the table is placed; create new pages as long as
* more table instances need to be placed
*/
do {
/* The first row is the header row which will be repeated on each
* new page. The last row is the footer and will be repeated on each
* new page. The header row is filled with a light blue, and the
* footer row is filled with a light orange. Each odd row is filled
* with a light gray.
*/
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}}}";
/* Place the table instance */
result = p.fit_table(tbl, llx, lly, urx, y, fit_opts);
/* An error occurred or the table's fitbox is too small to keep any
* contents
*/
if (result.equals("_error"))
throw new Exception ("Couldn't place table : " +
p.get_errmsg());
/* If all rows have been placed output the total in the matchbox
* defined for the footer row. Since the matchbox cannot be supplied
* directly to fit_textline(), we retrieve the matchbox coordinates
* and fit the text accordingly.
*/
if (!result.equals("_boxfull")) {
/* Format the total to a maximum of two fraction digits */
value = new BigDecimal(total);
roundedValue = value.setScale(2, RoundingMode.HALF_UP);
String contents = "total: " + roundedValue;
/* Retrieve the coordinates of the third (upper right) corner of
* the "subtotal" matchbox. The parameter "1" indicates the
* first instance of the matchbox.
*/
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());
}
/* Start the text line at the corner coordinates retrieved
* (x2, y2) with a small margin. Right-align the text.
*/
p.setfont(boldfont, fontsize);
p.fit_textline(contents, x3 - margin, y3 - margin,
"position={right top}");
}
/* Print the subtotal for all rows in the table instance on the
* current page below the last table column before we place the
* remaining rows on the next page
*/
else if (result.equals("_boxfull")) {
/* Get the last body row output in the table instance */
double lastrow = p.info_table(tbl, "lastbodyrow");
/* Calculate the subtotal */
for (i = 0, subtotal = 0; i < lastrow - nfooters; i++) {
subtotal += Double.valueOf(items[i][1]).doubleValue() *
Double.valueOf(items[i][2]).doubleValue();
}
/* Output the subtotal in the matchbox defined for the footer
* row. Since the matchbox cannot be directly referenced we
* retrieve the matchbox coordinates and fit the text
* accordingly.
*/
/* Format the subtotal to a maximum of two fraction digits*/
value = new BigDecimal(subtotal);
roundedValue = value.setScale(2, RoundingMode.HALF_UP);
String contents = "subtotal: " + roundedValue;
/* Retrieve the coordinates of the third (upper right) corner of
* the "subtotal" matchbox. The parameter "1" indicates the
* first instance of the matchbox.
*/
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());
}
/* Start the text line at the corner coordinates retrieved in
* (x3, y3) with a small margin. Right-align the text.
*/
p.setfont(boldfont, fontsize);
p.fit_textline(contents, x3 - margin, y3 - margin,
"position={right top}");
/* Output the "Continued" remark */
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"));
/* -----------------------------------------------
* Place the closing text directly after the table
* -----------------------------------------------
*/
/* Get the table height of the current table instance */
tabheight = p.info_table(tbl, "height");
y = y - (int) tabheight - yoffset;
/* Add the closing Textflow to be placed after the table */
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());
/* Loop until all text has been fit which is indicated by the "_stop"
* return value of fit_textflow()
*/
do {
/* Place the Textflow */
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);
}
}
}