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.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");
/* 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());
}
catch (IOException e) {
System.err.println("IOException occurred: " + e.getMessage());
}
catch (Exception e) {
System.err.println("Exception occurred: " + e.getMessage());
}
finally {
if (p != null) {
p.delete();
}
}
}
/**
* 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,
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.18 $");
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;
}
}