Measuring programming progress by lines of code is like measuring aircraft building progress by weight.
In general, the process of printing from web applications works a little differently compared to printing from the desktop. Web applications have good reason for not allowing direct access to a user’s printer: malicious websites could immediately start printing their fliers on your home or corporate printer, offering you anything from pizza delivery to adult entertainment. That’s why you can’t write a program in JavaScript that would automatically detect all available printers and send them print jobs. That’s why the user is forced to manually select the printer via the web browser’s pop-up dialog window.
Existing Flash Player bugs add more issues for Flex developers; for example, the Print dialog might not report all features of the available printer, and setting such parameters as tray selection or paper size might not be possible. To put it simply, you may not have complete control over the user’s printer from an application running in Flash Player. You may need to adjust your reports to standard printer settings.
Adobe had a product called FlashPaper that tried to mitigate these limitations by adding ActionScript 2 objects to a special control with complete access to the printer. In 2008, however, Adobe discontinued FlashPaper, apparently promoting printing PDF documents using Acrobat instead.
The process of printing from Flash Player consists of starting a single-threaded print job and adding dynamically created pages to it (i.e., the data that comes from a database). Unfortunately, Flash Player’s virtual machine AVM2 ActionScript timeout is 15 seconds. Accordingly, for both Flex and AIR, the interval between the following commands shouldn’t be more than 15 seconds:
PrintJob.start()
and the first
PrintJob.addPage()
PrintJob.addPage()
and the next
PrintJob.addPage()
PrintJob.addPage()
and PrintJob.send()
If, at each of these commands, printing the specified page always completed in 15 seconds or less, your application will be able to print a multipage document, although somewhat slowly. If any of the intervals spans more than 15 seconds, however, your print job will receive a runtime exception, which turns direct printing from Flash Player into an unpleasant experience, if application developers don’t handle exceptions properly. Plus, if the internal thread that started the print job failed, it may be automatically closed and unable to be recovered properly.
You can read more about handling printing errors in the Adobe document “Flash Player and Air tasks and system printing”.
You may think that setTimeout()
can
help break the 15-second barrier for printing, but it can’t. Printing has to
be handled by the same internal AVM2 thread (apparently a bug), and with
setTimeout()
, you are in fact spawning a
new one. The issue with printing long documents is demonstrated in Example 11-1. The PrintJob
starts and the method finishPrinting()
is called in the same thread and
works fine. If you instead comment out the call to finishPrinting()
and uncomment the method setTimeout()
, this printing job will fail: the
addPage()
will throw an exception,
because it runs in a thread different than PrintJob
.
Imagine that a timeout was initiated not by calling the function
setTimeout()
, but rather by Flash Player
during printing of a multipage document because one of the addPage()
calls took longer than 15 seconds. In
this case, addPage()
would be called on a
different internal thread than PrintJob.start()
and the
addPage()
operation would fail, even though Flash Player
should’ve known how to process a such situation properly.
Example 11-1. PrintTimeout.mxml—an illustration of printing failure
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"> <mx:Button label="Print Me" click="printMe()"/> <mx:Script> <![CDATA[ private function printMe() :void { var pj:PrintJob = new PrintJob(); pj.start(); // setTimeout(function() :void { finishPrinting(pj);}, 1); finishPrinting(pj); } private function finishPrinting(pj:PrintJob): void { pj.addPage(this); pj.send(); } ]]> </mx:Script> </mx:WindowedApplication>
Example 11-1 just prints
itself, addPage(this)
, but if it had to
print, say, a slow-rendered DataGrid
with
a couple of thousand rows, the chances are high that such a program would
time out before the printing job was finished.
There is a bigger problem than the technical restrictions mentioned so
far, and it is in the very approach to printing via the PrintJob
API. The process of programming reports
in ActionScript comes down to creating snapshots of components displayed on
the users’ monitors and sending them to the printer. Because screen
resolution differs from printer resolution, however, application developers
pursing this method need to create separate layouts just for printing, which
is time-consuming and challenging.
That’s why you should consider creating and printing your reports as PDF files. Besides, it neatly reinforces this book’s philosophy: minimize the amount of code that business application developers have to write. In this chapter, you’ll learn how to create XDP-enabled Flex components that will allow application developers to generate PDF documents on the client side with minimal coding.
PDF generation is supported by Adobe LiveCycle and LCDS, as well as
other server-side products. Suppose that you have a Flex or AIR window
with a number of UI controls, and you want to create a PDF out of it. One
option is to create a snapshot of the Flex component or container using
the class mx.graphics.ImageSnapshot
and
its function captureImage()
, which can
scale the image to a specific resolution and encode it into a specified
image format. You can send an instance of this class via RemoteObject
to the server with LCDS installed.
LCDS then creates a PDF document (or merges it with a PDF form) and
includes the new image received from the client.
The problem with this approach is that the resulting PDF will not be
searchable. For instance, if a Flex Panel
has text fields, you won’t be able to find
the text of these fields in Acrobat Reader if the Panel
is embedded as a bitmap.
Such PDFs have limitations on resolution as well (to create a PDF with resolution 300 dpi, you’d need to create a multimegabyte image). Also, printed materials often use different CSS and metrics from the screen ones. You don’t want to print, say, a background gradient that looks fine on the monitor, but bad on paper.
To embed forms into PDF documents, Adobe uses the XDP format. If you
purchase an LCDS license, you’ll have the option to use it. You can design
forms in Acrobat Designer and export the data from your Flex view, and
LCDS will merge the data and the form on the server. On the Java side,
LCDS adds several JARs in the lib
directory of your web application, which makes the XFAHelper
Java class available for your
server-side PDF generation.
After generating the PDF, the server-side program can be:
Placed as a ByteArray
in
HTTPSession
object
Saved as a file on the server for future use
Streamed back to the client marked as a MIME type application/pdf
Saved in a DBMS field as a binary large object (BLOB)
Depending on the business requirements, the server-side PDF generation might not be feasible. You might have just disconnected the AIR application, or the server software may not have any of the technologies supporting PDF creation installed. If the Flex UI is truly dynamic, that might change the number of displayed components based on some business criteria; developing an additional UI in Acrobat Designer just for printing can in these ways become either impossible or time-consuming. The LCDS Developer Guide describes this process in the document called “Using the PDF Generation Feature”.
Adobe has published an article describing the process of creating PDF documents using templates.
In general, for server-side PDF generation from Adobe Flex applications, you have to do the following:
Use Adobe LiveCycle Designer ES, which provides tools for creating interactive forms and personalized documents (see http://www.adobe.com/products/livecycle/designer/). This software comes with Acrobat Professional or can be purchased separately, and is well documented, but it requires someone to create the XDP form and the data model and establish a process of synchronizing the Flex application with the server-side LiveCycle.
Initiate the server-side PDF generation from your Flex application seamlessly.
Although this process provides guaranteed quality and predictable results, it also requires the double effort of developing XDP forms for printing and Flex forms for displaying. Besides, LiveCycle Designer is another piece of software that application developers in your organization may not be familiar with.
LCDS generation with merging data and forms produces good printing
quality with LCDS. The Flex client sends data as XML to the server along
with the name of the form file (template) to be used for merging, as shown
in Example 11-2. In this case, the LCDS layer
just needs to process it with the XDPXFAHelper
class and return it as a PDF stream
to the browser for displaying.
The ActionScript class FormRenderer
sends generated XDP to the server
and opens a web browser’s window to display the PDF when it arrives from
the server.
Example 11-2. Class FormRenderer.as
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); } }
You also need an XDP file with the data and presentation. If you don’t have LiveCycle Designer, you can make the XDP file programmatically, ensuring that it matches the printer’s paper size and corporate stationery. XDP documents are XML objects, which are easily processed in Flex using E4X syntax, for example:
Declare a variable of type XML, and initialize it with the required XDP template deployed on the server. A fragment of the 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>
Select a Flex UI container or component that you are going to
print (a Panel
, a DataGrid
, and so on).
Query the object from step 2, get its XDP attributes and children, and create the XML preparing this object for printing. Attach the XML to the template as a page.
Because original Flex components don’t know how to represent themselves in the XDP format, you’ll need to teach them to do so. This becomes the next task in enhancing Flex components.
For example, each UI component can implement some interface (e.g.,
IXdpObject
with the only getter,
xmlContent()
) that allows it to return
its own XDP content or, in the case of containers, to traverse the list of
its child components for their XDP content. For example, a new panel
component (PanelXdp
) may have the
following structure:
public class PanelXdp extends Panel implements IXdpObject{ public function get xmlContent():Object{ // The code to return representation of the panel // in the XDP format goes here } }
Repeat the process of attaching XML to the XDP template using E4X until all print pages are ready. This method of printing from Flex requires less effort for reporting and creation of dynamic layouts. It might also provide better printing quality and searchability within the printed document.
Example 11-3 is the server-side part written as a
Java ServerSide Page. It uncompresses the XDP stream received from the
client, creates the PDF using XDPXFAHelper
, turns it into an array of bytes,
and sends it back to the client as the MIME type "application/pdf"
.
Example 11-3. Render.jsp
<%@ page language="java" import="java.io.*, java.util.*, javax.xml.parsers.*, org.w3c.dom.Document, flex.messaging.*, flex.acrobat.pdf.XDPXFAHelper, flex.messaging.util.UUIDUtils, org.w3c.dom.Document " %><%! private static void _log(Object o){ System.out.println(""+o); } private String getParam(HttpServletRequest request, String name, String defVal) throws Exception{ String val = request.getParameter(name); return (val!=null && val.length()>0)?val:defVal; } private String getParam(HttpServletRequest request, String name) throws Exception{ return getParam(request, name, null); } private void processRenderRequest(HttpServletRequest request, HttpServletResponse response) throws Exception{ String data = getParam(request, "document"); String template = getParam(request, "template"); // Security hole, check path _log("template="+template); _log("data="+data); template = FlexContext.getServletContext().getRealPath(template); _log("template real="+template); // You must have LCDS license to use XDPXFAHelper XDPXFAHelper helper = new XDPXFAHelper(); helper.open(template); // Import XFA dataset if( data!=null ){ _log("data.length="+data.length()); ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes("UTF-8")); DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document dataset = builder.parse(bais); helper.importDataset(dataset); } else _log("data="+null); byte[] content = helper.saveToByteArray(); _log("content="+content); helper.close(); ServletOutputStream out3 = response.getOutputStream(); response.setContentType("application/pdf"); response.setContentLength(content.length); out3.write(content); } %><% _log(""); _log("--------------------------------------------"); _log("render.jsp"); processRenderRequest(request, response); %>
The WebORB PDF Generator from Midnight Coders allows you to either create XML printing templates on the server or generate them in Flex clients. To use this solution, you have to install the WebORB Server. For more details, visit http://www.themidnightcoders.com/products/pdf-generator/overview.html.
Now we’ll take a look at how to generate a PDF on the Flex side.
18.227.46.229