Creating PDF with images using flex and AlivePDF

July 19th, 2009 by Andrea Franz

Today I’m working on an AIR application, and I need to create a PDF starting from a list of images. This PDF needs to have one image for each page. I used AlivePDF, a very useful ActionScript3 library for PDF generation, and I created a simple class that takes an array of image paths and creates the PDF I need.

Here the class I use:

package
{	
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.filesystem.File;
  import flash.filesystem.FileMode;
  import flash.filesystem.FileStream;
  import flash.net.URLLoader;
  import flash.net.URLLoaderDataFormat;
  import flash.net.URLRequest;
  import flash.utils.ByteArray;
 
  import org.alivepdf.pdf.PDF;	
  import org.alivepdf.display.Display;
  import org.alivepdf.images.ResizeMode;
  import org.alivepdf.layout.Layout;
  import org.alivepdf.layout.Orientation;
  import org.alivepdf.layout.Size;
  import org.alivepdf.layout.Unit;
  import org.alivepdf.saving.Method;
 
  public class PdfGenerator extends EventDispatcher
  {		  
    private var pdf:PDF;
    private var images:Array;		
    private var outFilePath:String;
    private var completedPages:int;
 
    public function PdfGenerator(images:Array, outFilePath:String)
    {
      this.outFilePath = outFilePath;
      this.images = images;
    }				
 
    public function generate():void
    {						
      this.completedPages = 0;
      this.pdf = new PDF(Orientation.LANDSCAPE, Unit.MM, Size.A4);
      this.nextPage();		
    }
 
    private function nextPage():void
    {
      this.completedPages < this.images.length ? this.loadImage(this.images[this.completedPages++]) : this.save();
    }
 
    private function loadImage(path:String):void
    {						
      var urlLoader:URLLoader = new URLLoader();			
      urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
      urlLoader.addEventListener(Event.COMPLETE, this.onImageLoadComplete);
      urlLoader.load(new URLRequest(path));						
    }
 
    private function onImageLoadComplete(event:Event):void
    {						
      this.pdf.addPage();
      this.pdf.addImageStream(ByteArray(event.target.data), 0, 0, 0, 0, 1, ResizeMode.FIT_TO_PAGE);
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, false, false, this.images.length, this.completedPages));
      this.nextPage();
    }
 
    private function save():void
    {				
      this.pdf.end();
      this.writeOutFile();												
      this.dispatchEvent(new PdfGeneratorEvent(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE));
    }
 
    private function writeOutFile():void
    {
      var fileStream:FileStream = new FileStream();								
      fileStream.open(new File(this.outFilePath), FileMode.WRITE);			
      fileStream.writeBytes(this.pdf.save(Method.LOCAL));
      fileStream.close();
    }
  }
}

Basically for each image it creates a new URLRequest and when the request is completed it adds a new page with the image. As you can see I also dispatch a custom Event called PdfGeneratorEvent. That because I need to create a PDF with a large number of images, and doing like this I can give a feedback to the user with a ProgressBar that display the generation progress.

package
{
  import flash.events.Event;
 
  public class PdfGeneratorEvent extends Event
  {			
    public static const PDF_GENERATOR_PAGE_COMPLETE:String = "PDF_GENERATOR_PAGE_COMPLETE";
    public static const PDF_GENERATOR_COMPLETE:String      = "PDF_GENERATOR_COMPLETE";			
 
    public var totalPages:int;
    public var completedPages:int;
 
    public function PdfGeneratorEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false, totalPages:int=0, completedPages:int=0)
    {			
      super(type, bubbles, cancelable);
      this.totalPages     = totalPages;
      this.completedPages = completedPages;
    }
  }
}

And here the main file that uses this class:

public function createPdf(images:Array):void
{
  var generator:PdfGenerator = new PdfGenerator(images, "/path/to/my/test.pdf");
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_COMPLETE, this.onPdfComplete);
  generator.addEventListener(PdfGeneratorEvent.PDF_GENERATOR_PAGE_COMPLETE, this.onPdfPageComplete);				
  generator.generate();				
}
 
public function onPdfPageComplete(event:PdfGeneratorEvent):void
{					
  progressBar.setProgress(event.completedPages, event.totalPages);
  progressBar.label = event.completedPages + " of " + event.totalPages;															
}
 
public function onPdfComplete(event:PdfGeneratorEvent):void
{											
  trace("completed")
}

35 Comments on “Creating PDF with images using flex and AlivePDF”

  1. danno
    8:12 am on July 20th, 2009:

    thanks for posting this. when the PDF is generated through a flash .swf on a server ( not in AIR ) , is there a way to save that file to the server instead of prompting the user to download the .pdf?

    thanks for any help, i really do appreciate it!

    rocksteady,
    danno~

  2. Andrea Franz
    9:15 am on July 20th, 2009:

    danno, you can use something like:

    pdf.save(Method.REMOTE, url, Download.INLINE);

    server side you’ll receive the pdf in the raw post data.

  3. danno
    7:54 am on July 21st, 2009:

    when i try to compile, flash is giving me this error:
    “1120: Access of undefined property Download.

  4. danno
    8:19 am on July 21st, 2009:

    ok, so i just went back up to the start of my AS and added the following :
    import org.alivepdf.saving.Download;

    and then just to be sure, went in again and just added:
    import org.alivepdf.*;

    that gets rid of the error that i put before in my last comment, but it doesn’t generate a PDF on the server. do you happen to have a very quick and simple .as & .php file you can show me that this works with for flash? thanks again for your help!

    rocksteady,
    danno~

  5. Andrea Franz
    1:41 pm on July 21st, 2009:

    Here an example:

    var pdf:PDF = new PDF();
    pdf.addPage();
    // something else
    pdf.save(Method.REMOTE, php_page_url, Download.INLINE);

    In the php file you need to read the raw post data:

    $pdf = $GLOBALS["HTTP_RAW_POST_DATA"];
    header(‘Content-Type: application/pdf’);
    header(‘Content-Length: ‘.strlen($pdf));
    header(‘Content-disposition:’inline’; filename=”file.pdf”‘);
    echo $pdf;

    Not tested but it should be something like this, obviously you need to check if that parameter is set.
    Let me know if it works.

  6. danno~
    10:53 pm on July 21st, 2009:

    i’ll try that out and see if it works. my goal is so that a pdf can be created and stored on the server, in the background, without having to open a new window/tab to load the php file to create it. and so when a user clicks a button, it sends the info to the php page, which creates the file and that’s it. i can worry about echoing success or not success back to flash. i just don’t want the end user when looking at the flash file to know that a .pdf was created on server.

    after i try this out, i’ll let ya know. thanks again!

    rocksteady,
    danno~

  7. danno
    6:51 am on July 23rd, 2009:

    nope. that doesn’t work. try it yourself and you’ll see that it :
    1) opens up a new window/tab
    2) creates a .pdf , but with the example you gave it’s formatted as such: “?file.pdf?” . easily fixable, but still an issue.
    3) it doesn’t create anything on the server.

    any other solutions you can think of?

    rocksteady,
    danno~

  8. Andrea Franz
    11:07 am on July 23rd, 2009:

    It was only an example about how to send the pdf data to the server.
    If you want to save the pdf you need to create a file:

    < ?php
    if (isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
    $data = $GLOBALS["HTTP_RAW_POST_DATA"];
    $file = fopen("test.pdf", "wb");
    fwrite($file, $data);
    fclose($file);
    echo "File saved!";
    }
    ?>

    Doing like this the pdf is saved server side.
    A new page is opened because alivepdf call navigateToURL from the save method.
    If you want you can override that method removing that call.

  9. danno
    9:03 pm on July 23rd, 2009:

    thanks andrea for your persistence in helping me with this. i’m going to post my solution so that others who happen upon this page can have my solution as well.

    after your suggestion to look at the navigateToURL in the PDF.as , i just converted it to the following:

    var myLoader:URLLoader;
    myLoader = new URLLoader();
    myLoader.dataFormat = URLLoaderDataFormat.TEXT;
    myLoader.addEventListener(Event.COMPLETE, showCompleted);
    myLoader.addEventListener(IOErrorEvent.IO_ERROR, showError);
    myLoader.load(myRequest);
    break;

    i also needed to add the correct import.flash… to the PDF.as file so that everything would work correctly with the new loaders and such.

    thanks again so much for your help , it’s working great!

    rocksteady,
    danno~

  10. Andrea Franz
    9:25 pm on July 23rd, 2009:

    Glad to help you danno, you are welcome!

  11. danno
    10:17 pm on July 23rd, 2009:

    on a separate note, but still about AlivePDF, is there a way to have a transparent gif/png on top of an image, but with the ‘transparent’ part revealing that image below it?

    thanks again!

    rocksteady,
    danno~

  12. Andrea Franz
    10:54 pm on July 23rd, 2009:

    I think it will be possible from the next release, check here:

    http://alivepdf.bytearray.org/?p=204

  13. Ashley
    4:14 pm on July 24th, 2009:

    I have a question. I am using Flex 3.0 and AlivePDF. I have generated a graph on the client that the user wants to save as PDF on the server. I tried all these scripts; but I am getting a compile error. Can anyone help or supply the correct code please?

    private function generatePDF(e:MouseEvent):void {
    Alert (“in the function now”,Message);

    myPDF = new PDF(Orientation.LANDSCAPE,Unit.INCHES,Size.A4);
    // add a background image
    myPDF.addImage (viewStack, 1 , null, null, false, ImageFormat.JPG, 100, 0, 0, 0, 0);

    // add headline
    myPDF.textStyle ( new RGBColor(0×992200));
    myPDF.setFont( FontFamily.HELVETICA, Style.BOLD );
    myPDF.setFontSize ( 18 );
    myPDF.setXY( 10, 40 );
    myPDF.addMultiCell ( 300, 1, “This is my first PDF!”);

    // add text message
    myPDF.textStyle ( new RGBColor ( 0×992200) );
    myPDF.setFont( FontFamily.HELVETICA, Style.BOLD );
    myPDF.setFontSize ( 14 );
    myPDF.setXY( 10, 50 );
    myPDF.addMultiCell ( 300, 4, comment.text );

    // save PDF to the desktop
    var FileStream f:FileStream = new FileStream();
    var file:File = File.desktopDirectory.resolvePath(“MyPDF.pdf”);
    f.open( file, FileMode.WRITE);
    var bytes:ByteArray = myPDF.save(Method.LOCAL,”",Download.ATTACHMENT,”output.pdf”);
    f.writeBytes(bytes);
    f.close();

    Alert.show(“Your file has been saved!”,”Message”);
    }
    I am getting the following error:
    -1086: Syntax error: expecting semicolon before f.

    Thanks for your help.
    Ashley

  14. Andrea Franz
    4:54 pm on July 24th, 2009:

    I think the error is where you wrote:

    var FileStream f:FileStream = new FileStream();

    try this one:

    var f:FileStream = new FileStream();

  15. danno~
    10:44 pm on July 25th, 2009:

    @ashley
    yep, that solution that andrea wrote takes care of your error. was going to respond with same thing and then saw that it was answered correctly!

    rocksteady,
    danno~

  16. Kartik
    6:51 am on August 19th, 2009:

    <![CDATA[

    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.controls.ProgressBar;
    import mx.controls.ProgressBarMode;
    import mx.formatters.*;
    import mx.controls.Alert;
    import org.alivepdf.saving.Method;
    import org.alivepdf.layout.Size;
    import org.alivepdf.layout.Unit;
    import org.alivepdf.layout.Orientation;
    import org.alivepdf.pdf.PDF;
    import flash.display.NativeWindow;
    import flash.display.NativeWindowType;
    import org.alivepdf.*;

    public var pref:Number;
    private var myPdf:PDF;
    public function generatePdf():void

    {
    myPdf = new PDF(Orientation.LANDSCAPE,Unit.MM,Size.A4);
    myPdf.addPage();

    var stream:FileStream = new FileStream();
    var file:File = File.desktopDirectory.resolvePath("Quotetry.pdf");
    stream.open(file,FileMode.WRITE);
    myPdf.addText("Some more text",10,30);

    var byteArray:ByteArray = myPdf.save(Method.LOCAL);
    stream.writeBytes(byteArray);
    stream.close();

    }

    It generates PDF in desktop. but text is not there, an empty PDF is generated…I used multicell, even thatz not working..Please help..

  17. Kartik
    6:53 am on August 19th, 2009:

    The basic purpose of this is, I’m gettin inputs from user in previous screen..It need to be printed in PDF…If some one can help me I’ll be glad !! thanks in advance..

  18. adebruin
    11:34 pm on September 2nd, 2009:

    Elegant code, worked great, thanks.

    I am using this from Flash player (i.e. not AIR) and found the following:

    - YES this does work with CS3 but you DO need flash player 10

    - I modified writeOutFile:

    private function writeOutFile():void
    {
    var f:FileReference = new FileReference();
    var bytes:ByteArray = this.pdf.save(Method.LOCAL);
    f.save(bytes, “drawing.pdf”);
    }

    - YES you do get an error in Flash UNLESS you compile specifically for flashplayer 10

    - In Flex Builder you can use Project Properties >> Flex Compiler, and set “Required Flash Player Version” to 10.0.00

    - if you use the SDK you can either:
    > add the option “-target-player=10.0.00″ to the mxmlc options line OR:
    > modify the flex_sdk_3/frameworks/flex-config.xml file to change that same option

    Note that the compiler option trumps the config file.

    then… you may get another error: in flash this function is only allowed for an interactively initiated function.

    In this case, the writeOutFile is called from the onImageLoadComplete handler and thus it fails.

    You need another button with a mouseclick handler that calls writeOutFile, in my case I have the main program create a popup which shows the progress for each image and then exposes a “Save PDF” button when all images are loaded.

  19. Andrea Franz
    10:02 am on September 3rd, 2009:

    adebruin: thank you for your comment, I used this code just for AIR but I’m sure what you wrote will be useful for many people.

  20. Keith Craigo
    5:36 pm on September 10th, 2009:

    Your code worked like a charm, very nice.

    Thank you.

    Keith

  21. Andrea Franz
    5:53 pm on September 10th, 2009:

    Thank you Keith!

  22. chinmoy
    6:48 pm on October 19th, 2009:

    Hi,
    I have recently used Alive PDF to export Flex Charts into PDF. I’m using the addImage() method and passing the id of the container(v-Box) which contains the charts. The problem is that we have to scroll down the V-Box to see all the charts, when I export to PDF I can only see the charts that are visible and not the all the charts. I hope you are getting my point. Can you suggest me what needs to be done so that all charts that are there in the V-Box are visible.

  23. AuzzieB
    1:22 am on October 26th, 2009:

    Hi Andrea,

    The code worked great, I’ve been banging my head trying to accomplish something similar using a ‘for each’ loop with my array and couldn’t get the image part to work, so thanks a million.

    My arrayCollection has other text-based categroies like name, date, age, etc. I isolated the image path of the array for the PDF, what I’m trying to do now is include some text relating to each image on the same PDF.

    Any suggestions on how to take an array of text and draw it to each PDF page?

    I’m jumping in right now, but any help would be appreciated.

    Thanks again,
    Auzzie

  24. AuzzieB
    2:39 am on October 27th, 2009:

    Got it working with the text arrays.

    Thanks again for the script, really helped me out a lot.

    Peace.

  25. Andrea Franz
    8:59 am on October 28th, 2009:

    AuzzieB, you are welcome! You already solved it, good :)
    Chinmoy: I think the problem is that it creates the image making a screenshot of the component, and so it takes only the visible part. You can expand the vbox during the pdf creation..but I didn’t try that.

  26. Abhi
    8:59 am on November 7th, 2009:

    Hey Andrea … quick question related to the way httpservice calls are handled in Flex and pdf generation using AlivePDF.

    Currently I need to perform some atcion(s) on successful generation of pdf using AlivePDF. Since I am not using AIR, I cannot use the Method.LOCAL in the save call of AlivePDF.

    I hit a struts action servlet, wherein I consume the generated byte array and save it in the database using sping-jdbc. On successful save to the database, I need to perform further set of operations (httpservice) which are fired from Flex.

    My problem is that the httpservice in Flex is an asynchronous call, so invariably my httpservice call(s) are getting executed even before the byte stream is stored in the db, resulting in error scenarios.

    I have tried invoking the httpservice in the event: ProcessingEvent.COMPLETE, even then it gets called before the saving of the pdf.

    I am trying either of the options:

    1. to propogate the alive pdf response to Flex but I get a null if I use Method.LOCAL. I do send a response from the db saving service from the Action servlet, but have not managed to propogate the same to Flex.

    2. to sequentially and synchronously call the httpservice ensuring they are called after the pdf is generated and saved.

  27. Dorothy
    10:21 pm on November 16th, 2009:

    Hello.
    I just want to add a logo to my pdf.
    I’ve tried the
    myPDF.addImage(“logo.png”,300 300)
    I’ve tried a number of variations of this. Seems to have an issue with the DisplayObject.

    From what I’ve read, addImagestream works better, but I’m not getting the syntex.

    Could someone provide code that simply allow me to put a small png in the pdf? I don’t care if it’s addImage or addImagestream.

    Thanks for any help!
    D

  28. Andrea Franz
    1:34 pm on November 24th, 2009:

    @Dorothy, in my example I use addImageStream, did you try that?
    @Abhi, do you start each call after completing the previous one?

  29. George Girton
    10:38 pm on November 30th, 2009:

    Here you go, @Dorothy, hope this helps get you going! Note that in “addImage”, the id reference to “tomato” is not in quotes. You will need to supply various bits of connection, setup, import statements, and so forth.

    </code.

  30. George Girton
    10:46 pm on November 30th, 2009:

    woops, my prior post did not appear at all on this webpage!
    It showed defining of an

    mx:Image element with id=”tomato”, source=”your_file.png”

    and then the reference

    var tPt:Point = new Point(10, 12)
    myPDF.addImage(tomato,null,tPt.x, tPt.y, pageWidth/3,pageWidth/3)

  31. theo
    9:57 am on December 16th, 2009:

    Hi, thanks a lot for your example! I followed you idea, but when I use the addImageStream method, it gives me an error:”Image format not supported for now”. I want to display jpg images.I first thought that my AlivePfd version does not suport jpg, but when I looked at the sources I saw that JPEGImage, JPEGEncoder exist. Maybe I have to do some decoding. I don’t know. Maybe you have encounted this before. Thanks

  32. Andrea Franz
    12:11 pm on December 16th, 2009:

    It could be a problem in the image header. Did you try with other jpg’s?

  33. Nils
    2:38 pm on January 6th, 2010:

    Hello Andrea!

    Is it possible to display a preview of the generated pdf by using the bytearray that is a result of the save method as source for an instance of the loader or image class? I played a little bit but had no success yet!

    Do you have any ideas?

    Thank you in advance!

    Nils

  34. patrick
    7:55 pm on January 20th, 2010:

    if you have time, can you contact me via email? i have a question you might be able to answer related to the alivepdf actionscript. thank you.

  35. Andrea Franz
    9:56 am on January 22nd, 2010:

    Nil, I think you should send it to the server and then send the output to the browser

Comments are closed.