Flex Printing

7 12 2009

You’ve worked for months on your shiny new flex app. It has all the bells and whistles. Incredible data visualization that in just a few clicks delivers interactive graphs allowing the user to navigate to information they never thought possible. The customer is absolutely blown away. Then your phone rings a few days later. There is a problem, your flex app doesn’t print! “Print”, you say, “why on Earth would you want to do that?”

The customer now has a beautiful app that either prints out extremely pixelated and ugly or more often, not at all. And you are left scrambling trying to work around Flex’s Achilles heel, printing.

The good news is that your Flex components have the ability to render themselves as bitmaps due to the way Flash optimizes them. The bad news is that it is poorly documented process to get hi quality printing from Flex. (Otherwise I wouldn’t be writing this)

A quick outline of the process we will be using is as follows:

  • Take a hi res snapshot of some or all of your application
  • Encode the snapshot as a JPEG
  • Encode the JPEG binary data to Base64 so it can be sent as a POST variable
  • Embed that image into a PDF
  • Send the client back the PDF to print or to save

So for the first step, we need to capture a snapshot of our component. Adobe provides us with an ImageSnapshot class that does almost exactly what we want… almost. With ImageSnapshot, we can capture a component even specifying its dpi, encode it as either PNG or JPG and output it as a Base64 encoded string. The problem with ImageSnapshot is that by default it encodes as a transparent PNG… but you can’t use a PNG with alpha channel in a PDF (at least in the library I’m using, FPDF.) And if you encode as JPEG, you get an ugly black background where you had any transparent areas. And there is no way to change the background color to something more sensible (white) using ImageSnapshot.

We’ll create our own class similar to ImageCapture. I chose to call it PrintUtils. The first method we need is capture.

public static const MAX_BITMAP_DIMENSION:int = 2880;
public static function capture(target:UIComponent, dpi:uint = 72, bgColor:Number = 0xFFFFFF):BitmapData
{
	var scale:Number = dpi/72.0;
	if(scale * target.width > MAX_BITMAP_DIMENSION ||
		scale * target.height > MAX_BITMAP_DIMENSION) {
		if(target.width > target.height) {
			scale = MAX_BITMAP_DIMENSION / target.width;
		} else {
			scale = MAX_BITMAP_DIMENSION / target.height;
		}
	}	
	var bmd:BitmapData = new BitmapData(target.width * scale, 
		target.height * scale, false, bgColor);
	var m:Matrix = new Matrix();
	m.scale(scale, scale);
	bmd.draw(target, m);
	return bmd;
}

This is pretty standard, everything we’ve done here is well documented in the BitmapData class. Of note is Flash’s current limitation that the dimensions for bitmapdata can not exceed 2880 pixels. That takes care of step 1.

Steps 2 and 3 we will do together. We will create another static method to do this.

public static function encode(data:BitmapData, enc:IImageEncoder = null):String
{
	if(!enc) {
		enc = new JPEGEncoder(80);
	}
	var ba:ByteArray = enc.encode(data);
	var b64Enc:Base64Encoder = new Base64Encoder();
	b64Enc.encodeBytes(ba);
	return b64Enc.flush();
}

Again, pretty basic and everything here is in Adobe’s documentation. So we pass in the bitmap data from our capture function and optionally an encoder (PNGEncoder or JPEGEncoder) if you want other than the default JPEG at quality 80.

That is pretty much it from the Flex side. So to print our application, we’d include the PrintUtils class and make a function something like:

private function printApp():void
{
    var win:UIComponent = UIComponent(this.parentApplication);
    var bmd:BitmapData = PrintUtils.capture(win, 150); //capture at 150 dpi
    var req:URLRequest = new URLRequest('/pdf_gen.php');
    var dat:URLVariables = new URLVariables();
    //  set request variables 
    req.method = 'POST';
    dat.image_data = PrintUtils.encode(bmd);
    dat.image_type = 'JPG';
    dat.image_width = win.width;
    dat.image_height = win.height;
    dat.image_filename = 'certificationWindow.jpg';
    req.data = dat;
    // call page
    navigateToURL(req, '_blank');
}

The last two steps are to embed the image in a PDF and send back to the client. I use FPDF here because I’m using a PHP web server. But you will find libraries to do this with Python, Perl, Ruby and Java too.

    require('fpdf.php');
    if(!$_POST['image_data']) {
      die('No image data received.');
    }
    // Save encoded image
    file_put_contents('files/'.$_POST['image_filename'], base64_decode($_POST['image_data']));
    $_POST['image_height']) ? 'L' : 'P';
    $pdf = new FPDF($orientation, 'pt', 'Letter');
    $pdf->AddPage();
    //make sure image fits on a page
    $width = min((int)$_POST['image_width'], $pdf->wPt - 80);
    $pdf->Image('files/'.$_POST['image_filename'], 40, 40, $width, ($width/$_POST['image_width']) * $_POST['image_height']);
    //$pdf->Output('files/sample.pdf', 'F');
    $pdf->Output();
About these ads

Actions

Information

7 responses

8 12 2009
vatsalad

read, noted and bookmarked!

31 12 2009
Alex

hi, is it possible that you can privide me with an example? I am having problems making it working,

Thx

1 01 2010
Scott Bailey

I thought it was an example. Why don’t you be more specific about where you are having problems.

1 01 2010
Alex

I got it working :)
The problem was with this.parentApplication
It had problems passing through the functions (at least for the functions i created)
so i just put this or any other component id

Thx for the tutorial!

Happy New Year.

19 02 2010
Cyril

If component to capture is scaled (scaleX and/or scaleY != 1) its width/height are not the dimension of the image to capture.

(width is the length as the parent component sees, scaleX applies to the content).

I had to do :

width = target.width / target.scaleX
but don’t apply this scale into the matrix (scale into the matrix is to make what you capture more/less precise).

Is that correct ?

5 05 2010
JTtheGeek

Rather than all that backend stuff to export to pdf why not use PurePDF to create the pdf clientside?

14 09 2010
Anthony Mac Egan

Thanks Scott. Great tutorial.

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




Follow

Get every new post delivered to your Inbox.

%d bloggers like this: