PDFlib

高度なPDFアプリケーションの開発を支援する定番プログラムライブラリー Supported by インフォテック株式会社

PDFlib サンプル集(クックブック)

本サンプルプログラムは、PDF 文書生成ライブラリーの実装である PDFlib の基本的な機能を実際のプログラムで紹介したものです。

本サイトでダウンロードした PDFlib は、一部機能の制限を除き、評価版として無償でお使いいただけます。

テーブルを使った請求書の作成

テーブルを使い請求書を PDF で作成します。

請求書のヘッダー部分は別途 PDF として用意し、請求書を作成する時に PDFlib+PDI のインポート機能で取り込んでいます。注文された商品の一覧を作成するのにテーブルを使用しています。また各ページの最後に subtotal を記載するため、テーブルを配置する前に PDF_info_table() の lastbodyrow キーワードで配置される行数を確認してから PDF_fit_table() を行なっています。


/*
 * 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";
    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 + "}");

        /* 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("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, 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") || 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, 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);
        }
    }
}
(Sep 18, 2013 - May 23, 2019)