BASKET
Search
PDFlib

pdfvt/starter_pdfvt2s

Download Java Code     Show Output      Show Input Files

/* $Id: starter_pdfvt2s.java,v 1.20 2016/12/21 17:20:44 tm Exp $ */


package com.pdflib.cookbook.pdflib.pdfvt;


import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.io.StringWriter;

import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.text.DateFormat;

import java.text.DecimalFormat;

import java.text.NumberFormat;

import java.util.Date;

import java.util.Calendar;

import java.util.HashMap;

import java.util.Locale;

import java.util.Random;


import com.pdflib.pdflib;

import com.pdflib.PDFlibException;


/**

 * Starter sample for PDF/VT-2s

 *

 * This is a variation of the starter_pdfvt2 sample program. It wraps all the

 * data into a PDF/VT-2s-compliant MIME package, according to the

 * ISO 16612-2:2010 specification, Annex A, "Use of multipart MIME for

 * streamed generation of PDF/VT data".

 *

 * For demonstration purposes the MIME package is stored in the file system.

 * In a real-world application it could be streamed directly to the

 * consuming output device.

 *

 * Experiments showed that various tools that can be used to inspect MIME

 * packages, like WinZip or Mozilla Thunderbird, do not handle MIME parts

 * correctly that have content transfer encoding set to "binary". The tools

 * performed line-end conversion on the binary data and rendered the files

 * unusable. Therefore this sample program writes the MIME parts by default

 * with content transfer encoding "base64". To produce MIME parts with

 * content transfer encoding "binary", set the variable "USE_BASE64_ENC" to

 * false. Content transfer encoding "binary" is the encoding that is

 * recommended by ISO 16612-2:2010.

 *

 * For performing the base64 encoding some code from the Apache Commons

 * library is included (see below).

 *

 * Required software: PDFlib+PDI/PPS 9

 * Required data: PDF/X-4p input documents, fonts

 */

public class starter_pdfvt2s {


    /**

     * Stationery document

     */

    private static final String STATIONERY = "stationery_pdfx4p.pdf";


    /**

     * Name of the referenced ICC profile. Must be the same for all PDF/VT-2

     * documents in the MIME package.

     */

    private static final String ICC_PROFILE_FILENAME = "ISOcoated_v2_eci.icc";


    /**

     * Nodenamelist for the document part hierarchy in the PDF/VT-2 files. Must

     * be the same for all PDF/VT-2 files in the MIME package.

     */

    private static final String NODENAMELIST = "root recipient";

   

    /**

     * Output file name for PDF/VT-2s MIME package.

     */

    private static final String MIME_FILE_NAME = "starter_pdfvt2s.uue";


    /**

     * Character set for writing the MIME headers.

     */

    private static final String MIME_HEADER_CHARSET = "US-ASCII";


    /*

     * Some constants for producing the MIME headers.

     */

    private static final String PDFVT_STREAM_VERSION_HEADER_FIELD =

                                        "X-PDFVT-Stream-version";

    private static final String CONTENT_TYPE_HEADER_FIELD = "Content-Type";

    private static final String CONTENT_DISPOSITION_HEADER_FIELD =

                                        "Content-Disposition";

    private static final String CONTENT_TRANSFER_ENCODING_HEADER_FIELD =

                                        "Content-Transfer-Encoding";

    private static final String MIME_VERSION_HEADER_FIELD = "MIME-Version";

    private static final String BOUNDARY_MARKER = "--";


    /**

     * If set to true, use content transfer encoding "base64", otherwise

     * use "binary". The latter is recommended by ISO 16612-2:2010.

     */

    private static final boolean USE_BASE64_ENC = true;

   

    /**

     * Where to find the input files.

     */

    private static final String INPUT_DIR = "../input";


    /**

     * Number of PDF/VT-2 files in the PDF/VT-2s stream.

     */

    private static final int PDFVT2_COUNT = 3;

   

    /**

     * Map of file names to hash values for adding unique identifiers to the

     * Reference XObjects.

     */

    static HashMap<String, String> filenameXidMap =

                                new HashMap<String, String>();

   

    /**

     * Digest object used for the computation of unique ids for the referenced

     * files.

     */

    static MessageDigest digest;

   

    public static void main(String argv[])

            throws UnsupportedEncodingException, NoSuchAlgorithmException {

        pdflib p = null;

        digest = MessageDigest.getInstance("MD5");

        int exitcode = 0;


        /* This is where font/image/PDF input files live. Adjust as necessary. */

        String searchpath = INPUT_DIR;


        try {

            p = new pdflib();

           

            /* This means we must check return values of load_font() etc. */

            p.set_option("errorpolicy=return");

           

            p.set_option("searchpath={" + searchpath + "}");


            /*

             * The MIME package is written to a file, but it could be streamed

             * to a socket as well.

             */

            OutputStream mime_file = new FileOutputStream(MIME_FILE_NAME);

            make_file(mime_file, p);

            mime_file.close();

        }

        catch (FileNotFoundException e) {

            System.err.println("Unable to open file: " + e.getMessage());

            exitcode = 1;

        }

        catch (IOException e) {

            System.err.println("IOException occurred: " + e.getMessage());

            exitcode = 1;

        }

        catch (Exception e) {

            System.err.println("Exception occurred: " + e.getMessage());

            exitcode = 1;

        }

        finally {

            if (p != null) {

                p.delete();

            }

            System.exit(exitcode);

        }

    }


    /**

     * Produce the MIME package and write it to the given OutputStream.

     *

     * @param mime_package

     *            the OutputStream for the MIME package

     * @param p

     *           

     * @throws Exception

     * @throws PDFlibException

     */

    private static void make_file(OutputStream mime_package, pdflib p)

        throws Exception, PDFlibException {

        final String main_boundary = new_boundary();


        write_header_field(mime_package, MIME_VERSION_HEADER_FIELD, "1.0");

        write_header_field(mime_package, PDFVT_STREAM_VERSION_HEADER_FIELD, "1");

        write_header_field(mime_package, CONTENT_TYPE_HEADER_FIELD,

            "multipart/mixed; boundary=\"" + main_boundary + "\"");


        crlf(mime_package);


        /*

         * Write the ICC profile.

         */

        begin_part(mime_package, main_boundary);

        write_resource_part(mime_package, "application/vnd.iccprofile",

            ICC_PROFILE_FILENAME);


        /*

         * Write the stationery document.

         */

        begin_part(mime_package, main_boundary);

        write_resource_part(mime_package, "application/pdf", STATIONERY);


        /*

         * Write the sales rep PDF documents.

         */

        int i;

        for (i = 0; i < salesrepnames.length; i++) {

            begin_part(mime_package, main_boundary);

            write_resource_part(mime_package, "application/pdf",

                get_salesrep_filename(i));

        }


        /*

         * Write several PDF/VT-2 documents to the PDF/VT-2s stream, that

         * reference the ICC profile and the sales rep documents. They need to

         * be written with content disposition "inline".

         *

         * ISO 16612-2:2010 requires that these PDF/VT-2 documents have the

         * same nodenamelist and are prepared for the same characterized

         * printing condition. This must be ensured by the application, as

         * this is outside of the scope of PDFlib.

         */

        for (i = 1; i <= PDFVT2_COUNT; i++) {

            begin_part(mime_package, main_boundary);

   

            write_header_field(mime_package, CONTENT_TYPE_HEADER_FIELD,

                "application/pdf");

           

            /*

             * The PDF/VT-2 files containing content to be directly interpreted

             * are identifed by the names "pdfvt2_direct_<n>.pdf", with <n>

             * in the range 1 to PDFVT2_COUNT.

             */

            write_header_field(mime_package, CONTENT_DISPOSITION_HEADER_FIELD,

                "inline; filename=pdfvt2_direct_" + i + ".pdf");

           

            write_content_transfer_encoding_header_field(mime_package);

            crlf(mime_package);

           

            /*

             * Write each PDF/VT-2 document with a unique Subject entry in

             * the document information dictionary, and vary the number of

             * pages.

             */

            write_main_file(mime_package, p, "PDF/VT-2 document #" + i, i * 10);

        }


        end_multipart(mime_package, main_boundary);

    }


    /**

     * Write the header field "Content-Transfer-Encoding", depending on the

     * setting of the USE_BASE64_ENCODING setting.

     *

     * @param os        the OutputStream to write to

     *

     * @throws UnsupportedEncodingException

     * @throws IOException

     */

    private static void write_content_transfer_encoding_header_field(

            OutputStream os)

        throws UnsupportedEncodingException, IOException {

        write_header_field(os,

            CONTENT_TRANSFER_ENCODING_HEADER_FIELD,

                        USE_BASE64_ENC ? "base64" : "binary");

    }


    /**

     * Write a resource file as a MIME part to the MIME package.

     *

     * The file is written as an attachment with content transfer encoding

     * "binary".

     *

     * The file is expected to exist in the directory named INPUT_DIR.

     *

     * @param os

     *            the OutputStream to write to

     * @param content_type

     *            value for the "Content-Type" MIME header field

     * @param filename

     *            name of attachment file

     *           

     * @throws Exception

     */

    private static void write_resource_part(OutputStream os,

        String content_type, String filename) throws Exception {

        write_header_field(os, CONTENT_TYPE_HEADER_FIELD, content_type);

        write_header_field(os, CONTENT_DISPOSITION_HEADER_FIELD,

            "attachment; filename=" + filename);

        write_content_transfer_encoding_header_field(os);

        crlf(os);


        copy(os, filename);

    }


    private static void copy(OutputStream os, String filename)

            throws IOException, FileNotFoundException, Exception {

        if (USE_BASE64_ENC) {

            copyBase64(os, filename);

        }

        else {

            copyBinary(os, filename);

        }

    }

   

    /**

     * For staying Java 1.4 compliant, we convert bytes to hex digits directly.

     */

    private final static String HEX_DIGITS = "0123456789abcdef";

   

    /**

     * Copy the given file in binary form to the output stream, and create

     * a hash of the file contents which is stored in filenameXidMap.

     *

     * @param os

     *            target OutputStream of copy operation

     * @param filename

     *            file to copy from INPUT_DIR to the output stream

     * @throws FileNotFoundException

     * @throws Exception

     *

     * @throws NoSuchAlgorithmException

     */

    private static void copyBinary(OutputStream os, String filename)

        throws IOException, FileNotFoundException, Exception {

        /*

         * Perform check that a file with the same name was not yet copied

         * to the output stream. This is considered a fatal error, as it is

         * not clear which file should be referenced by the name.

         */

        if (filenameXidMap.containsKey(filename)) {

            throw new Exception("Duplicate input filename detected");

        }

       

        FileInputStream infile = new FileInputStream(new File(INPUT_DIR, filename));

        final byte buffer[] = new byte[1024];

        int bytes_read;


        digest.reset();

       

        while ((bytes_read = infile.read(buffer)) != -1) {

            digest.update(buffer);

            os.write(buffer, 0, bytes_read);

        }

       

        infile.close();

       

        byte hash[] = digest.digest();

       

        /*

         * Convert byte array to a string that looks like a

         * "uuid-schemed URI as defined in RFC 4122". This is the form that is

         * recommended in ISO 16612-2:2010,

         * "6.7.2 Unique identification of an XObject".

         */

        StringWriter uuidString = new StringWriter();

        uuidString.write("uuid:");

        for (int i = 0; i < hash.length; i += 1) {

            int hi_nibble = (int) (hash[i] & 0xf0) >> 4;

            uuidString.write(HEX_DIGITS.substring(hi_nibble, hi_nibble + 1));

            int lo_nibble = (int) hash[i] & 0xf;

            uuidString.write(HEX_DIGITS.substring(lo_nibble, lo_nibble + 1));

            if (i == 3 || i == 5 || i == 7 || i == 9)

            {

                uuidString.write("-");

            }

        }

       

        filenameXidMap.put(filename, uuidString.toString());

    }

   

    /**

     * Copy the given file in binary form to the output stream, and create

     * a hash of the file contents which is stored in filenameXidMap.

     *

     * @param os

     *            target OutputStream of copy operation

     * @param filename

     *            file to copy from INPUT_DIR to the output stream

     * @throws Exception

     *

     * @throws NoSuchAlgorithmException

     */

    private static void copyBase64(OutputStream os, String filename)

                throws Exception {

        /*

         * Perform check that a file with the same name was not yet copied

         * to the output stream. This is considered a fatal error, as it is

         * not clear which file should be referenced by the name.

         */

        if (filenameXidMap.containsKey(filename)) {

            throw new Exception("Duplicate input filename detected");

        }

       

        FileInputStream infile = new FileInputStream(new File(INPUT_DIR, filename));

        final byte buffer[] = new byte[1024];

        int bytes_read;

        ByteArrayOutputStream intermediate = new ByteArrayOutputStream();

       

        digest.reset();

       

        while ((bytes_read = infile.read(buffer)) != -1) {

            digest.update(buffer);

            intermediate.write(buffer, 0, bytes_read);

        }

       

        infile.close();

       

        os.write(encodeBase64Chunked(intermediate.toByteArray()));

       

        byte hash[] = digest.digest();

       

        /*

         * Convert byte array to a string that looks like a

         * "uuid-schemed URI as defined in RFC 4122". This is the form that is

         * recommended in ISO 16612-2:2010,

         * "6.7.2 Unique identification of an XObject".

         */

        StringWriter uuidString = new StringWriter();

        uuidString.write("uuid:");

        for (int i = 0; i < hash.length; i += 1) {

            int hi_nibble = (int) (hash[i] & 0xf0) >> 4;

            uuidString.write(HEX_DIGITS.substring(hi_nibble, hi_nibble + 1));

            int lo_nibble = (int) hash[i] & 0xf;

            uuidString.write(HEX_DIGITS.substring(lo_nibble, lo_nibble + 1));

            if (i == 3 || i == 5 || i == 7 || i == 9)

            {

                uuidString.write("-");

            }

        }

       

        filenameXidMap.put(filename, uuidString.toString());

    }


    /**

     * Write the MIME marker for the beginning of a MIME part.

     *

     * @param mime_package

     *            OutputStream for MIME package

     * @param boundary

     *            boundary string

     *

     * @throws UnsupportedEncodingException

     * @throws IOException

     */

    private static void begin_part(OutputStream mime_package, String boundary)

        throws UnsupportedEncodingException, IOException {

        crlf(mime_package);

        mime_package.write(BOUNDARY_MARKER.getBytes(MIME_HEADER_CHARSET));

        mime_package.write(boundary.getBytes(MIME_HEADER_CHARSET));

        crlf(mime_package);

    }


    /**

     * Write the final MIME marker for the end of the MIME multipart package.

     *

     * @param mime_package

     *            OutputStream for MIME package

     * @param boundary

     *            boundary string

     *

     * @throws UnsupportedEncodingException

     * @throws IOException

     */

    private static void end_multipart(OutputStream mime_package, String boundary)

        throws UnsupportedEncodingException, IOException {

        crlf(mime_package);

        mime_package.write(BOUNDARY_MARKER.getBytes(MIME_HEADER_CHARSET));

        mime_package.write(boundary.getBytes(MIME_HEADER_CHARSET));

        mime_package.write(BOUNDARY_MARKER.getBytes(MIME_HEADER_CHARSET));

        crlf(mime_package);

    }


    /**

     * Write a single MIME header field.

     *

     * @param os

     *            OutputStream for MIME package

     * @param name

     *            field name

     * @param value

     *            value of the field

     *

     * @throws UnsupportedEncodingException

     * @throws IOException

     */

    private static void write_header_field(OutputStream os, String name,

        String value) throws UnsupportedEncodingException, IOException {

        os.write(name.getBytes(MIME_HEADER_CHARSET));

        os.write(": ".getBytes(MIME_HEADER_CHARSET));

        os.write(value.getBytes(MIME_HEADER_CHARSET));

        crlf(os);

    }


    /**

     * Write a CRLF sequence to the output stream.

     *

     * @param os

     *            the OutputStream

     *

     * @throws IOException

     * @throws UnsupportedEncodingException

     */

    private static void crlf(OutputStream os) throws IOException,

        UnsupportedEncodingException {

        os.write("\r\n".getBytes(MIME_HEADER_CHARSET));

    }


    /**

     * Characters for generating a pseudo-random MIME boundary string.

     */

    private static final String BOUNDARY_CHARS =

        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";


    /**

     * Length of the pseudo-random MIME boundary string.

     */

    private static final int BOUNDARY_LENGTH = 20;


    /**

     * Return a new boundary string.

     */

    private static String new_boundary() {

        final int charset_size = BOUNDARY_CHARS.length();

        int i;


        StringWriter result = new StringWriter();


        result.write("----------");

        for (i = 0; i < BOUNDARY_LENGTH - 1; i += 1) {

            final int index = get_random(charset_size);

            result.write(BOUNDARY_CHARS.substring(index, index + 1));

        }


        return result.toString();

    }

   

    final static Random random = new Random();


    static class articledata_s {

        articledata_s(String name, double price) {

            this.name = name;

            this.price = price;

        }


        String name;

        double price;

    };


    static class addressdata_s {

        addressdata_s(String firstname, String lastname, String flat,

                String street, String city) {

            this.firstname = firstname;

            this.lastname = lastname;

            this.flat = flat;

            this.street = street;

            this.city = city;

        }


        String firstname;

        String lastname;

        String flat;

        String street;

        String city;

    };


    private static final String[] salesrepnames = {

        "Charles Ragner", "Hugo Baldwin",

        "Katie Blomock", "Ernie Bastel", "Lucy Irwin", "Bob Montagnier",

        "Chuck Hope", "Pierre Richard"

    };

   

    private static final String[] headers = {

        "ITEM", "DESCRIPTION", "QUANTITY", "PRICE", "AMOUNT"

    };


    private static final String[] alignments = {

        "right", "left", "right", "right", "right"

    };

   

    static final int MATRIXROWS = 32;

    static final int MATRIXDATASIZE = 4 * MATRIXROWS;


    /**

     * Write a PDF/VT-2s file to the MIME stream.

     *

     * @param os

     *            the OutputStream for the MIME package

     * @param p

     *            the pdflib object

     * @param subject

     *            value for the "Subject" field in the document information

     *            dictionary

     * @param records

     *            number of records to generate

     *           

     * @throws Exception

     * @throws IOException

     * @throws PDFlibException

     */

    private static void write_main_file(OutputStream os,

            pdflib p, String subject, int records)

            throws PDFlibException, IOException, Exception {

        int i;

        int record;

        int barcodeimage;

        String fontname = "DejaVuSerif";

        final String title = "Starter PDF/VT-2s";


        double left = 55;

        double right = 530;

        double bottom = 822;


        double fontsize = 12, leading, x, y;

        String buf;

        String optlist;

        String fontoptions;


        String closingtext =

            "Terms of payment: <save fillcolor={cmyk 0 1 1 0}>30 days net<restore>. "

                + "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.";


        articledata_s articledata[] = {

            new articledata_s("Super Kite", 20),

            new articledata_s("Turbo Flyer", 40),

            new articledata_s("Giga Trash", 180),

            new articledata_s("Bare Bone Kit", 50),

            new articledata_s("Nitty Gritty", 20),

            new articledata_s("Pretty Dark Flyer", 75),

            new articledata_s("Large Hadron Glider", 85),

            new articledata_s("Flying Bat", 25),

            new articledata_s("Simple Dimple", 40),

            new articledata_s("Mega Sail", 95),

            new articledata_s("Tiny Tin", 25),

            new articledata_s("Monster Duck", 275),

            new articledata_s("Free Gift", 0)

        };


        addressdata_s addressdata[] = {

            new addressdata_s("Edith", "Poulard", "Suite C", "Main Street",

                    "New York"),

            new addressdata_s("Max", "Huber", "", "Lipton Avenue",

                    "Albuquerque"),

            new addressdata_s("Herbert", "Pakard", "App. 29", "Easel",

                    "Duckberg"),

            new addressdata_s("Charles", "Fever", "Office 3", "Scenic Drive",

                    "Los Angeles"),

            new addressdata_s("D.", "Milliband", "", "Old Harbour", "Westland"),

            new addressdata_s("Lizzy", "Tin", "Workshop", "Ford", "Detroit"),

            new addressdata_s("Patrick", "Black", "Backside",

                    "Woolworth Street", "Clover")

        };


        int dpm = 0, cip4_root, cip4_metadata;


        leading = fontsize + 2;


        /*

         * Produce document in memory for writing in chunks to the OutputStream.

         * ISO 16612-2:2010 requires that the same nodenamelist value is used

         * for all PDF/VT-2 files in the PDF/VT-2s stream. This must be ensured

         * by the application and is outside of the scope of PDFlib.

         */

        if (p.begin_document("",

                "pdfvt=PDF/VT-2 pdfx=PDF/X-5pg usestransparency=true "

                + "nodenamelist={" + NODENAMELIST + "} "

                + "recordlevel=1") == -1) {

            throw new Exception("Error: " + p.get_errmsg());

        }


        p.set_info("Creator", "PDFlib Cookbook");

        p.set_info("Title", title + " $Revision: 1.20 $");

        p.set_info("Subject", subject);


        fontoptions = "fontname=" + fontname + " fontsize=" + fontsize

            + " embedding encoding=unicode";

       

        /*

         * Define output intent profile. ISO 16612-2:2010 requires that the same

         * output intent is used for all PDF/VT-2 files in the PDF/VT-2s stream.

         * This must be ensured by the application and is outside of the scope

         * of PDFlib.

         */

        if (p.load_iccprofile(ICC_PROFILE_FILENAME,

                "usage=outputintent urls={http://www.color.org}") == -1) {

            System.err.print("Error: " + p.get_errmsg() + "\n");

            System.err.print("Please install the ICC profile package from "

                    + "www.pdflib.com to run the PDF/VT-2 starter sample.\n");

            throw new Exception("ICC profile not found");

        }


        /*

         * -----------------------------------

         * Load company stationery as background (used

         * on first page for each recipient) by reference and

         * construct proxy for it

         *

         * For demonstration purposes we assume that there is a specific

         * PDF/VT environment context for stationery documents, and

         * therefore pass the string "Stationery Environment Context" for

         * the PDF/VT environment context.

         * -----------------------------------

         */

        final int proxy_stationery = make_proxy(p, STATIONERY,

                                        "Proxy for stationery",

                                        "Stationery Environment Context");

        if (proxy_stationery == -1)

        {

            throw new Exception("Error: " + p.get_errmsg());

        }


        /*

         * -----------------------------------

         * Preload PDF images of all local sales reps (used on first page

         * for each recipient) by reference and construct proxy for it

         * -----------------------------------

         */

        final int proxy_salesrepimage[] = new int[salesrepnames.length];

        for (i = 0; i < salesrepnames.length; i++) {

            final String description = "Proxy for sales rep image " + i;

            final String salesrepfilename = get_salesrep_filename(i);

           

            /*

             * For demonstration purposes we assume that theres a separate

             * PDF/VT environment context for the sales rep image

             * documents.

             */

            proxy_salesrepimage[i] =

                make_proxy(p, salesrepfilename, description,

                                "Sales Rep Image Environment Context");


            if (proxy_salesrepimage[i] == -1) {

                throw new Exception("Proxy error: " + p.get_errmsg());

            }

        }


        final int ARTICLECOUNT = articledata.length;

        final int ADDRESSCOUNT = addressdata.length;

        final int COLUMNCOUNT = headers.length;

        final int SALESREPS = salesrepnames.length;

       

        /*

         * -----------------------------------

         * Construct DPM metadata for the DPart

         * root node

         * -----------------------------------

         */

        dpm = p.poca_new("containertype=dict usage=dpm");

        cip4_root = p.poca_new("containertype=dict usage=dpm");

        cip4_metadata = p.poca_new("containertype=dict usage=dpm");


        optlist = "type=dict key=CIP4_Root value=" + cip4_root;

        p.poca_insert(dpm, optlist);


        optlist = "type=dict key=CIP4_Metadata value=" + cip4_metadata;

        p.poca_insert(cip4_root, optlist);


        p.poca_insert(cip4_metadata,

                "type=string key=CIP4_Conformance value=base");

        p.poca_insert(cip4_metadata,

                "type=string key=CIP4_Creator value=starter_pdfvt2");

        p.poca_insert(cip4_metadata,

                "type=string key=CIP4_JobID value={Kraxi Systems invoice}");


        /* Create root node in the DPart hierarchy and add DPM metadata */

        optlist = "dpm=" + dpm;

        p.begin_dpart(optlist);


        p.poca_delete(dpm, "recursive=true");


        /*

         * If base64 encoding is switched on, buffer the output for being able

         * to encode the whole output as base64 at the end.

         */

        ByteArrayOutputStream bos = null;

        OutputStream intermediate;

        if (USE_BASE64_ENC)

        {

            intermediate = bos = new ByteArrayOutputStream();

        }

        else

        {

            intermediate = os;

        }

        intermediate.write(p.get_buffer());

       

        DecimalFormat zip_code_format = new DecimalFormat("00000");

       

        NumberFormat priceFormat = NumberFormat.getInstance(Locale.US);

        priceFormat.setMaximumFractionDigits(2);

        priceFormat.setMinimumFractionDigits(2);

       

        for (record = 0; record < records; record++) {

            byte datamatrix[] = new byte[MATRIXDATASIZE];

            int cip4_recipient, cip4_contact, cip4_person;

            String firstname, lastname, result;

            int pagecount=0;

            int item;


            firstname = addressdata[get_random(ADDRESSCOUNT)].firstname;

            lastname = addressdata[get_random(ADDRESSCOUNT)].lastname;


            /*

             * -----------------------------------

             * Construct DPM metadata for the next

             * DPart node (i.e. the page)

             * -----------------------------------

             */

            dpm = p.poca_new("containertype=dict usage=dpm");

            cip4_root = p.poca_new("containertype=dict usage=dpm");

            cip4_recipient = p.poca_new("containertype=dict usage=dpm");

            cip4_contact = p.poca_new("containertype=dict usage=dpm");

            cip4_person = p.poca_new("containertype=dict usage=dpm");


            optlist = "type=dict key=CIP4_Root value=" + cip4_root;

            p.poca_insert(dpm, optlist);


            optlist = "type=dict key=CIP4_Recipient value="

                    + cip4_recipient;

            p.poca_insert(cip4_root, optlist);


            optlist = "type=string key=CIP4_UniqueID value={ID_" + record

                    + "}";

            p.poca_insert(cip4_recipient, optlist);


            optlist = "type=dict key=CIP4_Contact value=" + cip4_contact;

            p.poca_insert(cip4_recipient, optlist);


            optlist = "type=dict key=CIP4_Person value=" + cip4_person;

            p.poca_insert(cip4_contact, optlist);


            optlist = "type=string key=CIP4_Firstname value={" + firstname

                    + "}";

            p.poca_insert(cip4_person, optlist);


            optlist = "type=string key=CIP4_Lastname value={" + lastname

                    + "}";

            p.poca_insert(cip4_person, optlist);


            /*

             * Create a new node in the document part hierarchy and add DPM

             * metadata

             */

            optlist = "dpm=" + dpm;

            p.begin_dpart(optlist);


            p.poca_delete(dpm, "recursive=true");


            /* -----------------------------------

             * Create and place table with article list

             * -----------------------------------

             */

            /* ---------- Header row */

            int row = 1, col;

            int tbl = -1;


            for (col=1; col <= COLUMNCOUNT; col++)

            {

                optlist = "fittextline={position={" + alignments[col-1]

                    + " center} " + fontoptions + "} margin=2";

                tbl = p.add_table_cell(tbl, col, row, headers[col-1],

                                        optlist);

            }

            row++;


            /* ---------- Data rows: one for each article */

            double total = 0;


            /* -----------------------------------

             * Print variable-length article list

             * -----------------------------------

             */

            for (i = 0, item = 0; i < ARTICLECOUNT; i++) {

                int quantity = get_random(9) + 1;

                double sum;


                if ((get_random(2) % 2) != 0)

                    continue;


                col = 1;


                item++;

                sum = articledata[i].price * quantity;


                /* column 1: ITEM */

                buf = "" + item;

                optlist = "fittextline={position={" + alignments[col-1]

                        + " center} " + fontoptions

                        + "} colwidth=5% margin=2";

                tbl = p.add_table_cell(tbl, col++, row, buf, optlist);


                /* column 2: DESCRIPTION */

                optlist = "fittextline={position={" + alignments[col-1]

                        + " center} " + fontoptions

                        + "} colwidth=50% margin=2";

                tbl = p.add_table_cell(tbl, col++, row,

                        articledata[i].name, optlist);


                /* column 3: QUANTITY */

                buf = "" + quantity;

                optlist = "fittextline={position={" + alignments[col-1]

                        + " center} " + fontoptions + "} margin=2";

                tbl = p.add_table_cell(tbl, col++, row, buf, optlist);


                /* column 4: PRICE */

                buf = priceFormat.format(articledata[i].price);

                optlist = "fittextline={position={" + alignments[col-1]

                        + " center} " + fontoptions + "} margin=2";

                tbl = p.add_table_cell(tbl, col++, row, buf, optlist);


                /* column 5: AMOUNT */

                buf = priceFormat.format(sum);

                optlist = "fittextline={position={" + alignments[col-1]

                        + " center} " + fontoptions + "} margin=2";

                tbl = p.add_table_cell(tbl, col++, row, buf, optlist);


                total += sum;

                row++;

            }


            /* ---------- Print total in the rightmost column */

            buf = priceFormat.format(total);

            optlist = "fittextline={position={" + alignments[COLUMNCOUNT-1]

                    + " center} " + fontoptions + "} margin=2";

            tbl = p.add_table_cell(tbl, COLUMNCOUNT, row++, buf, optlist);



            /* ---------- Footer row with terms of payment */

            optlist = fontoptions + " alignment=justify leading=120%";

            int tf = p.create_textflow(closingtext, optlist);


            optlist = "rowheight=1 margin=2 margintop=" + 2*fontsize

                    + " textflow=" + tf + " colspan=" + COLUMNCOUNT;

            tbl = p.add_table_cell(tbl, 1, row++, "", optlist);



            /* ----- Place the table instance(s), creating pages as required */

            do {

                double top;


                p.begin_page_ext(0, 0,

                        "topdown=true width=a4.width height=a4.height");


                if (++pagecount == 1)

                {

                    /*

                     * -----------------------------------

                     * Place company stationery / proxy (template) as

                     * background on the page

                     * -----------------------------------

                     */

                    p.fit_image(proxy_stationery, 0, 842, "");


                    /* -----------------------------------

                     * Place name and image of local sales rep on first page

                     * for each recipient

                     * -----------------------------------

                     */

                    y = 177;

                    x = 455;


                    optlist = "fontname=" + fontname

                            + " encoding=winansi embedding fontsize=9";

                    p.fit_textline("Local sales rep:", x, y, optlist);

                    p.fit_textline(salesrepnames[record % SALESREPS],

                            x, y+9, optlist);


                    y = 280;

                   

                    /* Place the proxy on the page */

                    p.fit_image(

                        proxy_salesrepimage[record % salesrepnames.length],

                        x, y, "boxsize={90 90} fitmethod=meet");



                    /* -----------------------------------

                     * Address of recipient

                     * -----------------------------------

                     */

                    y = 170;


                    optlist = "fontname=" + fontname

                            + " encoding=winansi embedding fontsize="

                            + fontsize;

                    buf = firstname + " " + lastname;

                    p.fit_textline(buf, left, y, optlist);


                    y += leading;

                    p.fit_textline(addressdata[get_random(ADDRESSCOUNT)].flat,

                            left, y, optlist);


                    y += leading;

                    buf = get_random(999) + " "

                        + addressdata[get_random(ADDRESSCOUNT)].street;

                    p.fit_textline(buf, left, y, optlist);


                    y += leading;

                    buf = zip_code_format.format(get_random(99999))

                        + " " + addressdata[get_random(ADDRESSCOUNT)].city;

                    p.fit_textline(buf, left, y, optlist);



                    /*

                     * -----------------------------------

                     * Individual barcode image for each recipient

                     * -----------------------------------

                     */

                    create_datamatrix(datamatrix, record);

                    p.create_pvf("barcode", datamatrix, "");


                    /*

                     * The "mask" option helps us achieve GTS_Encapsulated

                     * status

                     */

                    barcodeimage = p.load_image("raw", "barcode",

                            "bpc=1 components=1 width=32 height=32 invert "

                                    + "pdfvt={scope=singleuse} mask");

                    if (barcodeimage == -1) {

                        throw new Exception("Error: " + p.get_errmsg());

                    }


                    p.fit_image(barcodeimage, 280.0, 200.0, "scale=1.5");

                    p.close_image(barcodeimage);

                    p.delete_pvf("barcode");



                    /* -----------------------------------

                     * Print header and date

                     * -----------------------------------

                     */

                    y = 300;

                    buf = "INVOICE "

                            + Calendar.getInstance().get(Calendar.YEAR)

                            + "-" + (record+1);

                    optlist = "fontname=" + fontname

                            + " encoding=winansi embedding fontsize="

                            + fontsize;

                    p.fit_textline(buf, left, y, optlist);

                   

                    buf = DateFormat.getDateInstance(DateFormat.LONG,

                                    Locale.US).format(new Date());

                    optlist = "fontname=" + fontname

                            + " encoding=winansi fontsize=" + fontsize

                            + " embedding position {100 0}";

                    p.fit_textline(buf, right, y, optlist);


                    top = y + 2*leading;

                }

                else

                {

                    top = 50;

                }


                /*

                 * Place the table on the page.

                 * Shade every other row, except the footer row.

                 */

                result = p.fit_table(tbl,

                        left, bottom, right, top,

                        "header=1 "

                        + "fill={{area=rowodd fillcolor={gray 0.9}} "

                            + "{area=rowlast fillcolor={gray 1}}} "

                        + "rowheightdefault=auto colwidthdefault=auto");


                if (result.equals("_error")) {

                    throw new Exception("Couldn't place table: "

                                            + p.get_errmsg());

                }


                p.end_page_ext("");

            } while (result.equals("_boxfull"));


            p.delete_table(tbl, "");


            /* Close node in the document part hierarchy */

            p.end_dpart("");

           

            intermediate.write(p.get_buffer());

        }


        /* Close root node in the document part hierarchy */

        p.end_dpart("");


        p.end_document("");

       

        intermediate.write(p.get_buffer());

       

        /*

         * For base64 encoding, the whole output was only buffered up to now.

         */

        if (USE_BASE64_ENC) {

            os.write(encodeBase64Chunked(bos.toByteArray()));

            bos.close();

        }

    }


    /**

     * Generate filename for the referenced PDF files containing the sales rep

     * images.

     *

     * @param i

     *            numer of sales rep

     *

     * @return generated filename

     */

    private static String get_salesrep_filename(int i) {

        return "sales_rep" + i + ".pdf";

    }


    /**

     * Load page 1 of the specified PDF and use it as reference for

     * a proxy which consists of a transparent crossed-out rectangle

     * of the same size.

     *

     * @throws Exception

     */

    static int

    make_proxy(pdflib p, String targetname, String description, String environment) throws Exception

    {

        String optlist;

        int proxy;

        double linewidth = 2;

        double width, height;

        double x1, y1, x2, y2, x3, y3, x4, y4;

        int gstate;

       

        /*

         * We use the hashes that were computed from the file contents in

         * the "copy" function for specifying unique identifiers for the

         * XObjects.

         */

        final String unique_id = filenameXidMap.get(targetname);

        if (unique_id == null) {

            throw new Exception(

                "Internal error: no unique id found for filename \""

                + targetname + "\"");

        }

       

        /* Create the template which will serve as proxy. The referenced

         * page (the target) is attached to the proxy.

         * The width and height parameters will be set in PDF_end_template_ext()

         * after we queried the size of the target page.

         * The "transparencygroup" option is provided to achieve GTS_Encapsulated

         * status.

         *

         * As the referenced files are referenced from all PDF/VT-2 files

         * in the PDF/VT-2s stream, the right setting for the scope is "stream"

         * here. Specifying "scope=stream" implies that the "environment"

         * option must be specified as well.

         */

        optlist = "reference={filename=" + targetname

            + " pagenumber=1} "

            + "pdfvt={scope=stream environment={" + environment

                                            + "} xid={" + unique_id + "}} "

            + "transparencygroup={colorspace=devicecmyk isolated=true}";

        proxy = p.begin_template_ext(0, 0, optlist);


        if (proxy == -1)

        {

            return proxy;

        }


        /* Determine the coordinates of the target; we use it for

         * dimensioning the proxy appropriately.

         */

        x1 = p.info_image(proxy, "targetx1", "");

        y1 = p.info_image(proxy, "targety1", "");

        x2 = p.info_image(proxy, "targetx2", "");

        y2 = p.info_image(proxy, "targety2", "");

        x3 = p.info_image(proxy, "targetx3", "");

        y3 = p.info_image(proxy, "targety3", "");

        x4 = p.info_image(proxy, "targetx4", "");

        y4 = p.info_image(proxy, "targety4", "");


        width = x2 - x1;

        height = y4 - y1;


        /* Draw a transparent crossed-out rectangle to visualize the proxy.

         * Attention: if we use the exact corner points, one half of the

         * linewidth would end up outside the template, and therefore be

         * clipped.

         */

        p.setlinewidth(linewidth);

        p.set_graphics_option("dasharray={10 5}");


        /* Make the dashed crossed-out rectangle transparent so that the proxy

         * does not obscure the underlying page contents.

         */

        gstate = p.create_gstate("opacitystroke=0.25 opacityfill=0.25");

        p.set_gstate(gstate);


        p.moveto(x1 + linewidth / 2, y1 + linewidth / 2);

        p.lineto(x2 - linewidth / 2, y2 + linewidth / 2);

        p.lineto(x3 - linewidth / 2, y3 - linewidth / 2);

        p.lineto(x4 + linewidth / 2, y4 - linewidth / 2);

        p.lineto(x1 + linewidth / 2, y1 + linewidth / 2);

        p.lineto(x3 - linewidth / 2, y3 - linewidth / 2);

        p.moveto(x2 - linewidth / 2, y2 + linewidth / 2);

        p.lineto(x4 + linewidth / 2, y4 - linewidth / 2);

        p.stroke();


        double fontsize = width > 550 ? 24.0 : 48.0;

        optlist = "fontname=LuciduxSans-Oblique encoding=winansi embedding "

                + "fontsize=" + fontsize + " fitmethod=auto position=center "

                + "boxsize={" + width + " " + height + "}";

        p.fit_textline(description, 0, 0, optlist);


        /* Make the proxy template the same size as the target page */

        p.end_template_ext(width, height);


        return proxy;

    }

   

    /**

     * Get a pseudo random number between 0 and n-1

     */

    static int get_random(int n) {

        return random.nextInt(n);

    }


    /**

     * Simulate a datamatrix barcode

     */

    static void create_datamatrix(byte datamatrix[], int record) {

        int i;


        for (i = 0; i < MATRIXROWS; i++) {

            datamatrix[4 * i + 0] = (byte) ((0xA3 + 1 * record + 17 * i) % 0xFF);

            datamatrix[4 * i + 1] = (byte) ((0xA2 + 3 * record + 11 * i) % 0xFF);

            datamatrix[4 * i + 2] = (byte) ((0xA0 + 5 * record + 7 * i) % 0xFF);

            datamatrix[4 * i + 3] = (byte) ((0x71 + 7 * record + 9 * i) % 0xFF);

        }

        for (i = 0; i < MATRIXROWS; i++) {

            datamatrix[4 * i + 0] |= 0x80;

            datamatrix[4 * i + 2] |= 0x80;

            if ((i % 2) != 0)

                datamatrix[4 * i + 3] |= 0x01;

            else

                datamatrix[4 * i + 3] &= 0xFE;

        }

        for (i = 0; i < 4; i++) {

            datamatrix[4 * (MATRIXROWS / 2 - 1) + i] = (byte) 0xFF;

            datamatrix[4 * (MATRIXROWS - 1) + i] = (byte) 0xFF;

        }

    }

   

    /*

     * The rest of the class is taken in slightly modified form from class

     * org.apache.commons.codec.binary.Base64 from the Apache Commons project.

     *

     * Copyright 2001-2004 The Apache Software Foundation.

     *

     * Licensed under the Apache License, Version 2.0 (the "License");

     * you may not use this file except in compliance with the License.

     * You may obtain a copy of the License at

     *

     *      http://www.apache.org/licenses/LICENSE-2.0

     *

     * Unless required by applicable law or agreed to in writing, software

     * distributed under the License is distributed on an "AS IS" BASIS,

     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

     * See the License for the specific language governing permissions and

     * limitations under the License.

     */

   

    /**

     * Chunk size per RFC 2045 section 6.8.

     *

     * <p>The {@value} character limit does not count the trailing CRLF, but counts

     * all other characters, including any equal signs.</p>

     *

     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>

     */

    private static final int CHUNK_SIZE = 76;


    /**

     * Chunk separator per RFC 2045 section 2.1.

     *

     * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>

     */

    private static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();


    /**

     * The base length.

     */

    private static final int BASELENGTH = 255;


    /**

     * Lookup length.

     */

    private static final int LOOKUPLENGTH = 64;


    /**

     * Used to calculate the number of bits in a byte.

     */

    private static final int EIGHTBIT = 8;


    /**

     * Used when encoding something which has fewer than 24 bits.

     */

    private static final int SIXTEENBIT = 16;


    /**

     * Used to determine how many bits data contains.

     */

    private static final int TWENTYFOURBITGROUP = 24;


    /**

     * Used to test the sign of a byte.

     */

    private static final int SIGN = -128;


    /**

     * Byte used to pad output.

     */

    private static final byte PAD = (byte) '=';


    // Create arrays to hold the base64 characters and a

    // lookup for base64 chars

    private static byte[] base64Alphabet = new byte[BASELENGTH];

    private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];


    // Populating the lookup and character arrays

    static {

        for (int i = 0; i < BASELENGTH; i++) {

            base64Alphabet[i] = (byte) -1;

        }

        for (int i = 'Z'; i >= 'A'; i--) {

            base64Alphabet[i] = (byte) (i - 'A');

        }

        for (int i = 'z'; i >= 'a'; i--) {

            base64Alphabet[i] = (byte) (i - 'a' + 26);

        }

        for (int i = '9'; i >= '0'; i--) {

            base64Alphabet[i] = (byte) (i - '0' + 52);

        }


        base64Alphabet['+'] = 62;

        base64Alphabet['/'] = 63;


        for (int i = 0; i <= 25; i++) {

            lookUpBase64Alphabet[i] = (byte) ('A' + i);

        }


        for (int i = 26, j = 0; i <= 51; i++, j++) {

            lookUpBase64Alphabet[i] = (byte) ('a' + j);

        }


        for (int i = 52, j = 0; i <= 61; i++, j++) {

            lookUpBase64Alphabet[i] = (byte) ('0' + j);

        }


        lookUpBase64Alphabet[62] = (byte) '+';

        lookUpBase64Alphabet[63] = (byte) '/';

    }


    /**

     * Encodes binary data using the base64 algorithm and chunks

     * the encoded output into 76 character blocks

     *

     * @param binaryData binary data to encode

     * @return Base64 characters chunked in 76 character blocks

     */

    private static byte[] encodeBase64Chunked(byte[] binaryData) {

        return encodeBase64(binaryData, true);

    }

   

    /**

     * Encodes binary data using the base64 algorithm, optionally

     * chunking the output into 76 character blocks.

     *

     * @param binaryData Array containing binary data to encode.

     * @param isChunked if isChunked is true this encoder will chunk

     *                  the base64 output into 76 character blocks

     * @return Base64-encoded data.

     */

    private static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {

        int lengthDataBits = binaryData.length * EIGHTBIT;

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;

        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;

        byte encodedData[] = null;

        int encodedDataLength = 0;

        int nbrChunks = 0;


        if (fewerThan24bits != 0) {

            //data not divisible by 24 bit

            encodedDataLength = (numberTriplets + 1) * 4;

        } else {

            // 16 or 8 bit

            encodedDataLength = numberTriplets * 4;

        }


        // If the output is to be "chunked" into 76 character sections,

        // for compliance with RFC 2045 MIME, then it is important to

        // allow for extra length to account for the separator(s)

        if (isChunked) {


            nbrChunks =

                (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE));

            encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length;

        }


        encodedData = new byte[encodedDataLength];


        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;


        int encodedIndex = 0;

        int dataIndex = 0;

        int i = 0;

        int nextSeparatorIndex = CHUNK_SIZE;

        int chunksSoFar = 0;


        //log.debug("number of triplets = " + numberTriplets);

        for (i = 0; i < numberTriplets; i++) {

            dataIndex = i * 3;

            b1 = binaryData[dataIndex];

            b2 = binaryData[dataIndex + 1];

            b3 = binaryData[dataIndex + 2];


            //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);


            l = (byte) (b2 & 0x0f);

            k = (byte) (b1 & 0x03);


            byte val1 =

                ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);

            byte val2 =

                ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);

            byte val3 =

                ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);


            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];

            //log.debug( "val2 = " + val2 );

            //log.debug( "k4   = " + (k<<4) );

            //log.debug(  "vak  = " + (val2 | (k<<4)) );

            encodedData[encodedIndex + 1] =

                lookUpBase64Alphabet[val2 | (k << 4)];

            encodedData[encodedIndex + 2] =

                lookUpBase64Alphabet[(l << 2) | val3];

            encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];


            encodedIndex += 4;


            // If we are chunking, let's put a chunk separator down.

            if (isChunked) {

                // this assumes that CHUNK_SIZE % 4 == 0

                if (encodedIndex == nextSeparatorIndex) {

                    System.arraycopy(

                        CHUNK_SEPARATOR,

                        0,

                        encodedData,

                        encodedIndex,

                        CHUNK_SEPARATOR.length);

                    chunksSoFar++;

                    nextSeparatorIndex =

                        (CHUNK_SIZE * (chunksSoFar + 1)) +

                        (chunksSoFar * CHUNK_SEPARATOR.length);

                    encodedIndex += CHUNK_SEPARATOR.length;

                }

            }

        }


        // form integral number of 6-bit groups

        dataIndex = i * 3;


        if (fewerThan24bits == EIGHTBIT) {

            b1 = binaryData[dataIndex];

            k = (byte) (b1 & 0x03);

            //log.debug("b1=" + b1);

            //log.debug("b1<<2 = " + (b1>>2) );

            byte val1 =

                ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);

            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];

            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];

            encodedData[encodedIndex + 2] = PAD;

            encodedData[encodedIndex + 3] = PAD;

        } else if (fewerThan24bits == SIXTEENBIT) {


            b1 = binaryData[dataIndex];

            b2 = binaryData[dataIndex + 1];

            l = (byte) (b2 & 0x0f);

            k = (byte) (b1 & 0x03);


            byte val1 =

                ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);

            byte val2 =

                ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);


            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];

            encodedData[encodedIndex + 1] =

                lookUpBase64Alphabet[val2 | (k << 4)];

            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];

            encodedData[encodedIndex + 3] = PAD;

        }


        if (isChunked) {

            // we also add a separator to the end of the final chunk.

            if (chunksSoFar < nbrChunks) {

                System.arraycopy(

                    CHUNK_SEPARATOR,

                    0,

                    encodedData,

                    encodedDataLength - CHUNK_SEPARATOR.length,

                    CHUNK_SEPARATOR.length);

            }

        }


        return encodedData;

    }

}