PDFlib Cookbook

cookbook

table/table_invoice

Download Java Code        Switch to PHP Code      Show Output PDF

/* $Id: table_invoice.java,v 1.8 2013/03/28 07:50:06 stm Exp $
 * 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.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";
    String outfile = "table_invoice.pdf";
    String title = "Table Invoice";
    
    pdflib p = null;

    String infile = "stationery.pdf";
    
    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 + "}");

        /* 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 + " $Revision: 1.8 $");

        /* 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("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());
        
        /* 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, BigDecimal.ROUND_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") || result.equals("_boxempty"))
                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, BigDecimal.ROUND_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, BigDecimal.ROUND_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.print("PDFlib exception occurred:\n");
            System.err.print("[" + e.get_errnum() + "] " + e.get_apiname() +
                ": " + e.get_errmsg() + "\n");
        } catch (Exception e) {
            System.err.println(e.toString());
        } finally {
            if (p != null) {
                p.delete();
            }
        }
    }
}