Apache FOP and iText for dynamic PDF generation

What is FOP

Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL formatting objects (XSL-FO) and an output independent formatter. It is a Java application that reads a formatting object (FO) tree and renders the resulting pages to a specified output. Output formats currently supported include PDF, PS, PCL, AFP, XML (area tree representation), Print, AWT and PNG, and to a lesser extent, RTF and TXT. The primary output target is PDF.

 

What is iText

iText is an ideal library for developers looking to enhance web- and other applications with dynamic PDF document generation and/or manipulation. iText is not an end-user tool. Typically you won’t use it on your Desktop as you would use Acrobat or any other PDF application. Rather, you’ll build iText into your own applications so that you can automate the PDF creation and manipulation process.

 

Which one or both?

So, both FOP and iText can generate dynamic PDF. Which one should we go for or should we use the combination of two?

 

FOP use XSL-FO to build your output template which is a lot more flexible than iText. However, in the side, iText provides better support for PDF post-process such as adding watermarks. With many pros and cons respectively in mind, it would be ideal to use the combination of two to extreme your benefit.

 

Transform XML to PDF using FOP

In most of cases, you would use the xml file to hold the main file content, which will be transformed by XSL-FO template.

 

InputSource xmlInputSource = getInputSource(xmlContent);

InputSource foInputSource = getInputSource(xslFo);

XSLTInputHandler input = new XSLTInputHandler(xmlInputSource, foInputSource);

 

How about other contents you would like to add to your PDF file, such as the date you published the file onto your website?  FOP allows you to use a Map object to hold those additional contents and you can pass them to your XSL-FO template

 

Map foParameterMap = new HashMap()

foParameterMap.put(“TITLE”, title)

foParameterMap.put("ONSITEDATE", onsitedate);

 

if (foParameterMap != null) {

    Set keySet = foParameterMap.keySet();

    Iterator iterator = keySet.iterator();

    while (iterator.hasNext()) {

        String parameterName = (String) iterator.next();

        String parameterValue = (String) foParameterMap.get(parameterName);

        input.setParameter(parameterName, parameterValue);

    }

}

 

To use those parameters passed in, you have to declare it first in your FO file

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform&quot; xmlns:fo="http://www.w3.org/1999/XSL/Format&quot; xmlns:xs="http://www.w3.org/2001/XMLSchema&quot; xmlns:fn="http://www.w3.org/2005/xpath-functions&quot; xmlns:xdt="http://www.w3.org/2005/xpath-datatypes"&gt;

    <xsl:param name="TITLE"/>

<xsl:param name="ONSITEDATE"/>

………

 

And then you can use it where is appropriate

<fo:block text-align="center" font-size="1.2em">

    <xsl:value-of select="$TITLE"/>

</fo:block>

 

You have to make sure that your FO templates comply to the standards, such as region-body must declare before region-before. To learn more about XSL-FO, you can find from http://www.w3schools.com/xslfo/xslfo_intro.asp

 

The following code will call the FOP driver to render xml file to a PDF output stream. Driver.RENDER_PDF is indicating the output format. You can have any other format that FOP support.

ByteArrayOutputStream basePdfOut = new ByteArrayOutputStream()

Driver driver = new Driver();

driver.setOutputStream(basePdfOut);

driver.setRenderer(Driver.RENDER_PDF);

input.run(driver);

 

FOP version issue

The preceding code is based on the FOP 0.20.5, which is first proper release. The latest version 0.95 has many new features and become a lot more stable. You can find the details at http://xmlgraphics.apache.org/fop/0.95/upgrading.html

 

The following code is how you write the earlier function in version 0.95

URI uri = new URI(xsltFilePath);

File xsltFile = new File(uri);

ByteArrayOutputStream basePdfOut = new ByteArrayOutputStream();

FopFactory fopFactory = FopFactory.newInstance();

FOUserAgent foUserAgent = fopFactory.newFOUserAgent();

 

// Construct fop with desired output format

Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, basePdfOut);

 

// Setup XSLT

TransformerFactory factory = TransformerFactory.newInstance();

Transformer transformer = factory.newTransformer(new StreamSource(xsltFile));

 

// Set the value of a <param> in the stylesheet

transformer.setParameter("versionParam", "2.0");

if (foParameterMap != null) {

    Set keySet = foParameterMap.keySet();

    Iterator iterator = keySet.iterator();

    while (iterator.hasNext()) {

        String parameterName = (String) iterator.next();

        String parameterValue = (String) foParameterMap.get(parameterName);

        transformer.setParameter(parameterName, parameterValue);

    }

}

 

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xmlContent.getBytes());

// Setup input for XSLT transformation

Source src = new StreamSource(byteArrayInputStream);

// Resulting SAX events (the generated FO) must be piped through to FOP

Result res = new SAXResult(fop.getDefaultHandler());

 

// Start XSLT transformation and FOP processing

transformer.transform(src, res);

 

This code works perfect to run as a stand alone application, but will cause a SAX parser exception if you run it as a web application.

SAXParseException: The prefix "x" for element "x:xmpmeta" is not bound.

 

FOP version 0.95 includes usage of Adobe XMP object. (Find more details at http://xmlgraphics.apache.org/fop/0.95/metadata.html)  It should add the following code into the FO template

<x:xmpmeta xmlns:x="adobe:ns:meta/">

after <fo:declarations> tag, but somehow the parser couldn’t parse it correctly. I have tried all the suggested options that I could find, including using SAXON parser instead. However, none of them could solve the problem eventually. If you have got a solution, please let me know. More stories about this issue could be found at http://markmail.org/message/ez27j7e6e2ymzo5k#query:ITEXT%20xmpmeta+page:1+mid:r74uahyc74xy2pfe+state:results

 

Post process with iText

The following code show how you add watermark, permission and owner password onto your PDF file.  

 

PdfReader pdfReader = new PdfReader(basePdfOut.toByteArray());

int n = pdfReader.getNumberOfPages();

Image waterMarkImage = Image.getInstance((String) itextParameterMap.get(WATER_MARK_IMAGE_PATH));

String pdfOwnerPwd = (String) itextParameterMap.get(PDF_OWNER_PASSWORD);

String pdfPermsStr = (String) itextParameterMap.get(PDF_PERMISSIONS);

int permissions = getPdfGenPermissions(pdfPermsStr);

PdfStamper stamp = new PdfStamper(pdfReader, out);

stamp.setEncryption(null, pdfOwnerPwd.getBytes(), permissions, PdfWriter.ENCRYPTION_AES_128);

int i = 0;

PdfContentByte under;

waterMarkImage.setAbsolutePosition(0, 0);

while (i < n) {

    i++;

    under = stamp.getUnderContent(i);

    under.addImage(waterMarkImage);

}

stamp.close();

 

protected static int getPdfGenPermissions(String genPermissions) {

    int pdfGenPermissions;

    String[] perms = genPermissions.split(",");

    Integer[] permIntObj = new Integer[perms.length];

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

        permIntObj[i] = new Integer(perms[i]);

    }

    pdfGenPermissions = permIntObj[0].intValue();

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

        pdfGenPermissions = pdfGenPermissions | permIntObj[i].intValue();

    }

    return pdfGenPermissions;

}

 

 

Response Header Setting

It have been found that many people have problem with the header settings to deliver the PDF file download with different browser. The following code is what I have found that compatible with IE6, IE7 and Firefox 3

 

response.setContentType("application/pdf");

response.setHeader("Cache-Control", "no-cache");

response.setHeader("Pragma", "no-cache");

response.setDateHeader("Expires", 0);          

 

 

Advertisements
This entry was posted in Dynamic File Generation. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s