Using AlivePDF to print from AIR Javascript via ActionScript3 – part 1

See also Part 2 of this article.

As has been blogged in many places, printing from AIR is very limited and problematic. It seemed to me that a way around the lack of printing support would be to have the ability to create PDF files client-side, as opposed to sending all the data to be printed to the server then getting a PDF back again. Knowing that it was possible to invoke ActionScript classes from HTML/Javascript AIR apps, eg this post by Ray Camden and using that post as a start, I began exploring the possibilities.

This post covers what I found along the way. In summary:

  1. It is possible to create PDF files completely client-side from Javascript AIR apps via ActionsScript. A “proof of concept” is at the end of this post.
  2. You can use the free Flex SDK to develop the SWF file you need to invoke the required ActionScript classes.
  3. You can set up Eclipse to do that (ie you dont need FlexBuilder or be restricted to command line compiling)
  4. There are lots of other AS libraries out there that can be used using the techniques discussed in this post.

Until I started this process I had never really looked at ActionScript so the following is very much from the perspective of a newbie!

Basic AS info

First, read Ray’s post as a starting point.

Download the Flex 4 SDK from here. Unzip it somewhere (in my case D:\flex_sdk_4.1).

The AS library you need in your AIR app is contained in a SWF file as you can see in Ray’s post.¬† There are 2 ways to compile your SWF file. As Ray says, the Adobe docs suggest using the acompc command line compiler, which outputs a SWC / Zip file that you can extract the SWF from. A simpler way is to use the amxmlc command line compiler instead which outputs directly to the SWF file. Both of these compilers are in the Flex SDK /bin folder.

Ray’s example assumes you are creating an AS package from scratch. What I wanted to do was to use an existing library and extend it. Before I discuss the command line compiler settings, I need to digress into talking about the libraries I found.

AS PDF options

I found two AS libraries that create PDF files client-side РAlivePDF and purePDF. The latter interested me particularly because it is a port of iText java library which I was already familiar with. However, as discussed in my forum post here, I was unable to get purePDF to work  so I switched to AlivePDF which was more successful. It is highly likely that my problems with purePDF we of my own making. However I was unable to get any help to work out what, if anything, I was doing wrong.

Extending an AS library

The basic method is that you create a new AS package that imports the classes that you need from the library. The AS class library is in a SWC file (ie is compiled) and the Flex / AS compiler imports the classes that you need from that file.

So, the easiest way I found to do this (before I found out how to use Eclipse) is as follows. I include this info here to help explain the process.

What I did was to take the AS code from this example and converted it into the following package.

package {
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;

    import org.alivepdf.pdf.PDF;
    import org.alivepdf.saving.Method;
    import org.alivepdf.fonts. * ;
    import org.alivepdf.pages.Page;
    import org.alivepdf.display.Display;
    import org.alivepdf.layout. * ;

    import mx.utils.UIDUtil;

    import flash.filesystem.FileStream;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;

    import flash.utils.ByteArray;

    // You need to extend the Flex class Sprite for AIR to be able to run the code
    public class aliveDemo extends Sprite {
        private
        var pdf: PDF;
        private
        var file: File;

        // Removed the image to simplify the testing
        //[Embed( source="/assets/o-png24.png", mimeType="application/octet-stream" )]
        //private var pngBytes:Class;
        public function aliveDemo() {}

        public function generate(): String {
            var pdf: PDF = new PDF(Orientation.PORTRAIT, Unit.MM, Size.A4);
            pdf.setDisplayMode(Display.FULL_PAGE, Layout.SINGLE_PAGE);

            var newPage: Page = new Page(Orientation.PORTRAIT, Unit.MM, Size.A4);
            pdf.addPage(newPage);

            // The following line turned out not to work as the API had changed since the original example was posted
            // pdf.setFont(FontFamily.ARIAL , Style.NORMAL, 12);
            // The correct way to do it now is:
            var myCoreFont: IFont = new CoreFont(FontFamily.HELVETICA);
            pdf.setFont(myCoreFont, 20);

            pdf.addText("This is a sample text", 5, 15);
            pdf.drawCircle(25, 35, 15);

            pdf.addPage();
            pdf.writeText(12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nPraesent vel lectus lorem. Phasellus convallis, tortor a venenatis mattis, erat mi euismod tellus, in fermentum sapien nibh sit amet urna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent tellus libero, lacinia ac egestas eget, interdum quis purus. Donec ut nisl metus, sit amet viverra turpis. Mauris ultrices dapibus lacus non ultrices. Cras elementum luctus mauris, vitae eleifend diam accumsan ut. Aliquam erat volutpat. Suspendisse placerat nibh in libero tincidunt a elementum mi vehicula. Donec lobortis magna vel nibh mollis tempor. Maecenas et elit nunc. Nam non auctor orci. Aliquam vel velit vel mi adipiscing semper in ac orci. Vestibulum commodo sem eget tortor lobortis semper. Ut sit amet sapien non velit rutrum egestas sollicitudin in elit. Fusce laoreet leo a sem mattis iaculis");

            // Removed this image for testing
            // pdf.addImageStream( new pngBytes() as ByteArray );
            // Save to file
            var fs: FileStream = new FileStream();
            file = new File("C:/CFusionMX7/wwwroot/Sites/TestAS/aliveDemo.pdf"); // Set your own path here!

            fs.open(file, FileMode.WRITE);
            var bytes: ByteArray = pdf.save(Method.LOCAL);
            fs.writeBytes(bytes);
            fs.close();

            return "End of generate";
        }

    }
}

I saved this directly to the Flex SDK /bin folder as AliveDemo.as (remember I was in quick and dirty test mode at this stage).

Then, to compile it:

  1. Create a folder in the /bin folder called alivepdflib
  2. Download the AlivePDF package (AlivePDF 0.1.5 RC.zip) and extract it
  3. Copy the AlivePDF.swc file from \AlivePDF 0.1.5 RC\Sources\bin into the alivepdflib folder created in step 1
  4. From the command line in the Flex SDK /bin folder, execute amxmlc -library-path+=alivepdflib aliveDemo.as

What that does is to create a file called aliveDemo.swf in the \bin folder by linking whatever SWC files are found in the alivepdflib folder and importing those classes into my aliveDemo.as class.

Then, I created an AIR app in Ecplise and set the HTML as follows:

<html>
    
    <head>
        <title>
            Test alivePDF
        </title>
        <script type="text/javascript" src="lib/air/AIRAliases.js"></script>
        <!--- Drop the created SWF file in the /lib folder --->
        <script src="lib/aliveDemo.swf" type="application/x-shockwave-flash"></script>
        <script>
            // Instantiate the class
            var ASlib = new window.runtime.aliveDemo();
            // Call the generate method
            var result = ASlib.generate();
            // Display the message to show we completed OK
            air.trace(result)
            // The aliveDemo.pdf will be located where ever you saved it to in aliveDemo.as
        </script>
    </head>
    
    <body>
    </body>

</html>

I copied the aliveDemo.swf file into the /bin folder in my AIR app, then ran the app and hey presto! a pdf file!!

The next step was to make a javascript wrapper so I could actually create the PDF on demand. Before that though, I set up the Flex SDK in Eclipse.

Setting up Flex 4 SDK in Eclipse

Using this very useful post as a basis, I made the following adjustments for the AIR environment:

  1. I did not bother creating the application.mxml file because the amxmlc command line compiler is actually a batch file that appends the correct xml data for compiling for AIR.
  2. In the Eclipse builder configuration section, I used the following settings instead of the ones in the post:
    1. Location= D:\flex_sdk_4.1\bin\amxmlc.bat (ie use YOUR path to the Flex SDK)
    2. Working directory=${workspace_loc:/JSPDF} (browse to YOUR project folder, mine was called JSPDF)
    3. Arguments= -library-path+=libs src/alivePDFWrapper.as -output C:/CFusionMX7/wwwroot/Sites/TestAS/lib/alivePDFWrapper.swf (set the path of the -output parameter to YOUR AIR project so that the SWF file is saved there for you)
    4. I didnt set the suggested Build Options check boxes since I didnt want the compiler to run automatically. To build, you highlight the .as file you need to compile and use CTRL+B (in windows)
  3. Copy the AlivePDF.swc file into the /libs folder in your project
  4. Copy the alivePDFWrapper.as file from Part 2 of this post to the /src folder of your project

The javascript wrapper

While it is possible to call the ActionScript methods directly, class instatiation gets tricky because the AS classes are “data types” that Javascript cant know about. My solution is to create an AS class that acts as a wrapper exposing the required methods but using data types that Javascript can handle. Since this wrapper and it’s associated JS class is the “solution” (well, so far anyway) to my problem of creating PDF files from Javascript, I have posted this section separately for those who dont care about all the above background. See Part 2