Thursday 8 March 2012

Printing in Flex, with runtime images

Printing in Flex is a topic that comes up from time to time, and is met by a shudder from most people. Whilst it is nice to have some direct printing capability within the Flash Runtime and API set, it is far from friendly to work with.

FlexPrintJob provides a nice abstraction on top of the core Flash Printing API, allowing Flex visual components to be printed directly. This can make life easier when it comes to complex layouts and content, as it is not necessary to work with low-level drawing APIs. However, do note that Flex components need to be added to the display list, otherwise certain dependent resources will not be available that are required by FlexPrintJob to function correctly.

The general premise is to call FlexPrintJob.start() which begins the print job, and presents the end user with a print dialog. This method is synchronous, and processing will halt until the user has made a selection (do note, however, that the Flash API places a 15 second maximum time between calling FlexPrintJob methods, and I experienced timeout issues when spending too much time working with an Acrobat Distiller browse dialog). After this point, FlexPrintJob.addObject() is called for each page in the print job. At its conclusion, FlexPrintJob.send() is called to pass the job to the native print spooler.

The problem I encountered recently involved the printing of images, which did not make use of embedded content. I'll demonstrate with a brief example. The printable UI may look like:

PrintUI.mxml
<s:Group>
   <s:Image id="image" />
</s:Group>

PrintMediator.as
[Inject]
public var printUI: PrintUI;

private var _imageData: ByteArray;

override protected function onRegister(  ): void
{
   addContextListener( ApplicationEvent.PRINT, onPrint, ApplicationEvent );
}

private function onPrint( event: ApplicationEvent ): void
{
   var flexPrintJob: FlexPrintJob = new FlexPrintJob(  );
   flexPrintJob.start();
   flexPrintJob.printBitmap = false;

   printUI.image.source = _imageData;

   flexPrintJob.addObject( printUI );

   flexPrintJob.send(  );
}

So, what happens? Ideally, the printed output should contain a representation of the image contained in the ByteArray. Unfortunately, we'll get a blank page. Why is this? It comes down to the way that Image handles a ByteArray. Whilst the data is available client-side, it still needs to be validated by the image parser. Consequently, when the .source setter is called, the value is stored in the Image instance, but it is not processed until a later stage - one which is not in the current call-stack. Consequently, by the time processing has reached the FlexPrintJob.addObject() method, the Image data has not been converted into its visual representation.

Fortunately (although not simply), the Image API provides events to track the progress of loading and parsing. Thus, problem is resolved by listening for the FlexEvent.READY event, which is fired when the image has been processed and displayed. The downside to this is that a simple synchronous function (like the one presented above), must become asynchronous, as indicated below:

PrintMediator.as
[Inject]
public var printUI: PrintUI;

private var _imageData: ByteArray;

private var _flexPrintJob: FlexPrintJob;

override protected function onRegister(  ): void
{
   addContextListener( ApplicationEvent.PRINT, onPrint, ApplicationEvent );
   printUI.image.addEventListener( FlexEvent.READY, onImageReady );
}

private function onPrint( event: ApplicationEvent ): void
{
   var flexPrintJob: FlexPrintJob = new FlexPrintJob(  );

   _flexPrintJob.start();
   _flexPrintJob.printBitmap = false;

   printUI.image.source = imageData;
}

private function onImageReady( event: FlexEvent ): void
{
   _flexPrintJob.addObject( printUI );

   _flexPrintJob.send(  );
}
Obviously these examples have been simplified for the purposes of display.

3 comments:

  1. Thanks for sharing this, it helped a lot. My friend was just telling me about image-flex, and I really wasn't sure what it was or how to do it. I'm loving it so far though.

    ReplyDelete
  2. Thanks so much for this. I was able to implement multiple images loading across multi-page print jobs. Accomplished by registering the images in an array as the source is set. When the 'ready' event fires you can determine the image that loaded by accessing the event.target.source or id, name etc. and matching it to images you have registered in an array. When all images in the array have fired back as 'ready' then add your object to the print job and send.

    ReplyDelete
  3. Glad to be able to help. Thanks for adding some new info.

    ReplyDelete