Welcome!

Anatole Tartakovsky

Subscribe to Anatole Tartakovsky: eMailAlertsEmail Alerts
Get Anatole Tartakovsky via: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Clear Toolkit Magazine

Clear Toolkit: Article

Creating PDF Documents from Flex Applications

A little different approach for PDF creation from Flex

There are three ways of creating PDF documents from Adobe Flex applications: (1) by using the forms/LiveCycle designer - this process is well documented and requires someone to create XDP form (an Acrobat XML Data Package file), the data model and establish a process of synchronizing Flex application with LiveCycle, (2) by treating PDF printing as one of the features of your Flex application, seamlessly providing printing functionality (with or without LiveCycle Designer), and (3) by using Snapshots available in LCDS, which uses bitmaps to represent Flash content

While the first process provides guaranteed quality and predictable results, it also requires a double effort of developing XDP and Flex forms and introduces the model and communications that can be foreign to the application domain.

The third type of printing is good for screen-only PDFs due to low DPI used by the screen snapshots. Flex 3 should provide control over the DPI resolution.

The second process is more applicable when the document structure is dynamic (like the end-user customizable report or form). It often produces better printing quality with the current version of LiveCycle Data Services ES (formerly Flex Data Services) Flex. The idea is to send the server not the data for merging with the form, but already merged compressed document in XDP format containing both data and formatting.  In this case LCDS layer just needs to process it with XDPXFAHelper class and returns it as a PDF stream to the browser for displaying.
The next code sample is the server side part written as JavaServer Page (createPDF.jsp). Note, that you need to have a commercial license of LCDS as its Express version does not support PDF generation.

<%@ page language="java"
import="java.io.IOException,
javax.servlet.http.HttpServletRequest,
flex.acrobat.pdf.XDPXFAHelper,
java.util.zip.Inflater,
java.util.zip.InflaterInputStream"
%>
<jsp:directive.page import="java.io.InputStream"/>
<jsp:directive.page import="java.io.ByteArrayInputStream"/>
<jsp:directive.page import="java.io.FileInputStream"/>
<jsp:directive.page import="java.io.FileOutputStream"/>
<%
//Load compressed XDP stream (received from client)
//into a byte array
byte[] result = new byte[5000000];
int resultLength =0 ;
try {
Inflater decompresser = new Inflater(  );
byte[] ba = new byte[500000];
ServletInputStream si = request.getInputStream();
int k = 0;
int i = 0;
while (true ) {
k = si.read(ba, i, 500000);
i +=k;
if (k<=0) break;
}
try {//Uncompress data into result
decompresser.setInput(ba);
resultLength  = decompresser.inflate(result);
} catch (Exception e) {
System.out.println(e.getMessage());
}
} catch (IOException e) {
throw new ServletException(e);
}

//The next line won’t work unless you have a commercial
// LCDS. It’s not available in Express edition
XDPXFAHelper helper = new XDPXFAHelper();

try { //load the input into PDF convertor
helper.openFromByteArray( result );
} catch (Exception e) {
e.printStackTrace();
}

// Save new PDF as a byte array
byte[] bytes = helper.saveToByteArray();

//Send PDF to the client(GZIP compression can be
// provided by an external filter)
ServletOutputStream out2 = response.getOutputStream();
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
out2.write(bytes);
out2.flush();
out2.close();
%>

On the client (Flex) side, you need a few simple classes. First you would need an ActionScript class that sends generated XDP to the server and opens the browser page for displaying the incoming PDF:

import flash.net.*;
import flash.utils.ByteArray;

public class FormRenderer
{
public static function openPdf(xdp:String,
target:String="_blank"):void
{
var req:URLRequest = new URLRequest("/createPDF.jsp");                req.method = URLRequestMethod.POST;
var ba :ByteArray = new ByteArray();;
ba.writeMultiByte(xdp, "iso-8859-1");
ba.compress();
ba.position = 0;
req.data = ba;
navigateToURL(req, target);
}
}
}

Now all we need is an XDP file with the data and presentation. While it is feasible to make an XDP file programmatically we usually recommend to start with blank templates made in LiveCycle Designer (comes with Acrobat Professional) matching the printer’s paper size/corporate stationary. XDP documents are XML objects, and EcmaScript for XML (e4x) makes the XDP building process in Flex a breeze. For example:

1.    Declare a variable of type XML, and using e4x syntax read the XDP template that matches the document and the paper size. A fragment of XDP template may look like this:
<?xml version="1.0" encoding="UTF-8"?>
<?xfa generator="AdobeLiveCycleDesigner_V8.0" APIVersion="2.5.6290.0"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" timeStamp="2007-01-25T10:40:38Z" uuid="784f469b-2fd0-4555-a905-6a2d173d0ee1">
<template xmlns="http://www.xfa.org/schema/xfa-template/2.5/">
<subform name="form1" layout="tb" restoreState="auto" locale="en_US">
<pageSet>
<pageArea name="Page1" id="Page1">
<contentArea x="0.25in" y="0.25in" w="8in" h="10.5in"/>
<medium stock="letter" short="8.5in" long="11in"/>
<?templateDesigner expand 1?></pageArea>
<?templateDesigner expand 1?></pageSet>
<subform w="8in" h="10.5in" name="YourPageAttachedHere"/>
<proto/>
<desc>
<text name="version">8.0.1291.1.339988.308172</text>
</desc>
</subform>
</template>


2.    Select a Flex container or control that you are going to print, i.e. a Panel, a DataGrid et al.

3.    Query the object from step 2, get its attributes and children and create the XML preparing this object for printing. Attach the XML to the template as a page. If you have your own presentation framework, all components can implement your own interface (the code below shows IXdpContent with the only getter function xmlContent) that allows to traverse the list of elements on the screen for their XDP content. You can also have a processor that would provide the XML for standard classes. Here is implementation of generic DataGridItemRenderer to illustrate how you can prepare for printing just one datagrid cell:
package printing
{
import com.theriabook.controls.superGridClasses.DataGridItemRenderer;

public class DataGridItemRendererXdp extends DataGridItemRenderer
implements IXdpElement
{
public  function get xmlContent():Object
{
var o:XML = <draw  x={convert(x)} w={convert(width)}
h={convert(height)}>
<ui>
<textEdit>
</textEdit>
</ui>
<value><text>{text}</text></value>
<font typeface={getStyle("fontFamily")}
size={(getStyle("fontSize")*4/5)+"pt"}
weight={getStyle("fontWeight")}
posture={getStyle("fontStyle")}
underline={getStyle("textDecoration")=="normal"?1:0}
>
<fill>
<color value={rgb(getStyle("color"))}/>
</fill>
</font>
<margin topInset="0.5mm" bottomInset="0.5mm" leftInset="0.5mm" rightInset="0.5mm"/>

<border>
<edge presence="hidden"/>
<edge presence="visible"/>
<edge presence="hidden"/>
<edge presence="hidden"/>
</border>
</draw>;

return o;
}
private function convert(val:Number) : String {
return val/96 + "in"; //convert 96 dpi to inches
}
private function rgb(val:Number) : String {

return (val >> 16 & 255) + "," + (val >> 8 & 255) + "," + (val  & 255);
}

}
}

4.    Repeat the process of attaching XML to the XDP template using E4X until all print pages are ready.

In my opinion, this method of printing from Flex requires less efforts for reporting. It might also provide better printing quality and searchability within printed document.

More Stories By Anatole Tartakovsky

Anatole Tartakovsky is a Managing Principal of Farata Systems. He's responsible for creation of frameworks and reusable components. Anatole authored number of books and articles on AJAX, XML, Internet and client-server technologies. He holds an MS in mathematics. You can reach him at atartakovsky@faratasystems.com

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.