Create a large number of invoices in a single PDF and make use of the following PDF/VT-2 features:
create a document part (DPart) hierarchy
assign PDF/VT scope attributes to images and imported PDF pages
add document part metadata (DPM) to the DPart root node and all page nodes
use proxy/reference pairs for imported PDF pages for the letterhead and photographic images. The referenced PDFs are PDF/X-4p themselves.
Since transparency is used (for the dashed rectangles in the proxies) we supply the required options to achieve GTS_Encapsulated status:
"transparencygroup" for the proxy templates
"mask" for the barcode image (use "renderingintent" for color images)
Required software: PDFlib 8 VT Edition
Download Java Code Show Output Show Input Files Switch to PHP Code
*
* Starter sample for PDF/VT-2
* Create a large number of invoices in a single PDF and make use of
* the following PDF/VT-2 features:
* - create a document part (DPart) hierarchy
* - assign PDF/VT scope attributes to images and imported PDF pages
* - add document part metadata (DPM) to the DPart root node and all page nodes
* - use proxy/reference pairs for imported PDF pages for the letterhead
* and photographic images. The referenced PDFs are PDF/X-4p themselves.
* - Since transparency is used (for the dashed rectangles in the proxies)
* we supply the required options to achieve GTS_Encapsulated status:
* - "transparencygroup" for the proxy templates
* - "mask" for the barcode image (use "renderingintent" for color images)
*
* Required software: PDFlib 8 VT Edition
* Required data: PDF/X-4p input documents, fonts
*/
package com.pdflib.cookbook.pdflib.pdfvt;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import com.pdflib.pdflib;
import com.pdflib.PDFlibException;
public class starter_pdfvt2 {
final static Random random = new Random();
static class articledata_s {
articledata_s(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
String name;
double price;
int quantity;
};
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;
};
static final int MATRIXROWS = 32;
static final int MATRIXDATASIZE = 4 * MATRIXROWS;
public static void main(String argv[]) {
int MAXRECORD = 100;
int i;
int record;
int barcodeimage;
String stationeryfilename = "stationery_pdfx4p.pdf";
String fontname = "DejaVuSerif";
String title = "Starter PDF/VT-2";
/* This is where font/image/PDF input files live. Adjust as necessary. */
String searchpath = "../input";
String outfile = "starter_pdfvt2.pdf";
double left = 55;
double right = 530;
double fontsize = 12, leading, x, y;
double sum, total;
String buf;
String optlist;
String baseopt = "encoding=winansi embedding "
+ "ruler ={ 30 45 275 375 475} "
+ "tabalignment={right left right right right} "
+ "hortabmethod=ruler fontsize=12 ";
int textflow;
String closingtext = "Terms of payment: <fillcolor={cmyk 0 1 1 0}>30 days net. "
+ "<fillcolor={gray 0}>90 days warranty starting at the day of sale. "
+ "This warranty covers defects in workmanship only. "
+ "<fontname=DejaVuSerif embedding encoding=winansi>Kraxi Systems, Inc. "
+ "<resetfont>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, 2),
new articledata_s("Turbo Flyer", 40, 5),
new articledata_s("Giga Trash", 180, 1),
new articledata_s("Bare Bone Kit", 50, 3),
new articledata_s("Nitty Gritty", 20, 10),
new articledata_s("Pretty Dark Flyer", 75, 1),
new articledata_s("Large Hadron Glider", 85, 1),
new articledata_s("Flying Bat", 25, 1),
new articledata_s("Simple Dimple", 40, 1),
new articledata_s("Mega Sail", 95, 1),
new articledata_s("Tiny Tin", 25, 1),
new articledata_s("Monster Duck", 275, 1),
new articledata_s("Free Gift", 0, 1)
};
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")
};
String[] salesrepnames = { "Charles Ragner", "Hugo Baldwin",
"Katie Blomock", "Ernie Bastel", "Lucy Irwin", "Bob Montagnier",
"Chuck Hope", "Pierre Richard" };
int dpm = 0, cip4_root, cip4_metadata;
leading = fontsize + 2;
pdflib p = null;
try {
p = new pdflib();
if (p.begin_document(outfile,
"pdfvt=PDF/VT-2 pdfx=PDF/X-5pg usestransparency=true "
+ "nodenamelist={root recipient} recordlevel=1") == -1) {
throw new Exception("Error: " + p.get_errmsg());
}
p.set_parameter("SearchPath", searchpath);
p.set_info("Creator", "PDFlib Cookbook");
p.set_info("Title", title + " $Revision: 1.5 $");
/* Define output intent profile */
if (p.load_iccprofile("ISOcoated.icc",
"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");
p.delete();
System.exit(2);
}
/*
* -----------------------------------
* Load company stationery as background (used
* on first page for each recipient) by reference and
* construct proxy for it
* -----------------------------------
*/
int proxy_stationery = make_proxy(p, stationeryfilename,
"Proxy for stationery");
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
* -----------------------------------
*/
int proxy_salesrepimage[] = new int[salesrepnames.length];
for (i = 0; i < salesrepnames.length; i++) {
String description = "Proxy for sales rep image " + i;
String salesrepfilename = "sales_rep" + i + ".pdf";
proxy_salesrepimage[i] = make_proxy(p, salesrepfilename, description);
if (proxy_salesrepimage[i] == -1) {
throw new Exception("Proxy error: " + p.get_errmsg());
}
}
final int ARTICLECOUNT = articledata.length;
final int ADDRESSCOUNT = addressdata.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);
p.poca_delete(cip4_root);
p.poca_delete(cip4_metadata);
DecimalFormat zip_code_format = new DecimalFormat("00000");
for (record = 0; record < MAXRECORD; record++) {
byte datamatrix[] = new byte[MATRIXDATASIZE];
int item;
int cip4_recipient, cip4_contact, cip4_person;
String firstname, lastname;
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);
p.poca_delete(cip4_root);
p.poca_delete(cip4_recipient);
p.poca_delete(cip4_contact);
p.poca_delete(cip4_person);
/*
* Establish coordinates with the origin in the upper left
* corner.
*/
p.begin_page_ext(0, 0,
"topdown width=a4.width height=a4.height");
/*
* -----------------------------------
* Place company stationery / proxy (template) as background
* on the page
* -----------------------------------
*/
p.fit_image(proxy_stationery, 0, 842, "");
/*
* -----------------------------------
* Place name and image proxy (template) of local sales
* rep on the page
* -----------------------------------
*/
y = 177;
x = 455;
buf = "Local sales rep:";
optlist = "fontname=" + fontname
+ " encoding=winansi embedding fontsize=9";
p.fit_textline(buf, x, y, optlist);
p.fit_textline(salesrepnames[record % salesrepnames.length], 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 2011-" + (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);
/* Print the invoice header line */
y = 370;
buf = "\tITEM\tDESCRIPTION\tQUANTITY\tPRICE\tAMOUNT";
optlist = baseopt + " fontname=" + fontname;
textflow = p.create_textflow(buf, optlist);
if (textflow == -1) {
throw new Exception("Error: " + p.get_errmsg());
}
p.fit_textflow(textflow, left, y - leading, right, y, "");
p.delete_textflow(textflow);
/*
* -----------------------------------
* Print variable-length article list
* -----------------------------------
*/
y += 2 * leading;
total = 0;
optlist = baseopt + " fontname=" + fontname;
final NumberFormat priceFormat = NumberFormat.getInstance(Locale.US);
priceFormat.setMaximumFractionDigits(2);
priceFormat.setMinimumFractionDigits(2);
for (i = 0, item = 0; i < ARTICLECOUNT; i++) {
int quantity = get_random(9) + 1;
if ((get_random(2) % 2) != 0)
continue;
item++;
sum = articledata[i].price * quantity;
buf = "\t" + item + "\t" + articledata[i].name + "\t"
+ quantity + "\t"
+ priceFormat.format(articledata[i].price) + "\t"
+ priceFormat.format(sum);
textflow = p.create_textflow(buf, optlist);
if (textflow == -1) {
throw new Exception("Error: " + p.get_errmsg());
}
p.fit_textflow(textflow, left, y - leading, right, y, "");
p.delete_textflow(textflow);
y += leading;
total += sum;
}
y += leading;
buf = priceFormat.format(total);
optlist = "fontname=" + fontname
+ " encoding=winansi embedding " + "fontsize="
+ fontsize + " position {100 0}";
p.fit_textline(buf, right, y, optlist);
/*
* -----------------------------------
* Constant closing text
* -----------------------------------
*/
y += 5 * leading;
optlist = baseopt + " fontname=" + fontname
+ " alignment=justify leading=120%";
textflow = p.create_textflow(closingtext, optlist);
if (textflow == -1) {
throw new Exception("Error: " + p.get_errmsg());
}
p.fit_textflow(textflow, left, y + 6 * leading, right, y, "");
p.delete_textflow(textflow);
p.end_page_ext("");
/* Close node in the document part hierarchy */
p.end_dpart("");
}
/* Close root node in the document part hierarchy */
p.end_dpart("");
p.end_document("");
}
catch (PDFlibException e) {
System.err.println(
"PDFlib exception occurred in starter_pdfvt1 sample:");
System.err.println("[" + e.get_errnum() + "] " + e.get_apiname()
+ ": " + e.get_errmsg());
}
catch (Exception e) {
System.err.println(e.getMessage());
}
finally {
if (p != null) {
p.delete();
}
}
}
/* -------------------------------
* 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.
*/
static int
make_proxy(pdflib p, String targetname, String description) throws PDFlibException
{
String optlist;
int proxy;
double linewidth = 2;
double width, height;
double x1, y1, x2, y2, x3, y3, x4, y4;
int gstate;
/* 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.
* You can add "pdfvt={xid={uuid:...} }" to optlist if you can
* generate unique IDs.
*/
optlist = "reference={filename=" + targetname
+ " pagenumber=1} pdfvt={scope=file} "
+ "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.setdashpattern("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;
}
}
}