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")
}

Mask problem with flex canvas and drag & drop

July 12th, 2009 by Andrea Franz

Some weeks ago a spent a lot of time trying to fix a bug inside an AIR application I was working on.
I wrote a custom component that has an image as child. This image is draggable, and the container should act as a mask. The problem was that when I started dragging the picture, the picture went outside its container, like the following example.

Try dragging the red square outside the white one:

This movie requires Flash Player 9

I fixed this bug just setting the scrollRect property to the container:

This movie requires Flash Player 9

Only when I realized how to fix the bug I found another guy with the same problem and the same solution.

Here the code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application width="500" height="300" xmlns:mx="http://www.adobe.com/2006/mxml" 
  layout="vertical" verticalAlign="middle" horizontalAlign="center"
  applicationComplete="init()">
  <mx:Script>
    <![CDATA[												
      public function init():void
      {				
        this.container.scrollRect = new Rectangle(0, 0, this.container.width, this.container.height);																
        this.draggableCanvas.addEventListener(MouseEvent.MOUSE_UP, this.mouseUpHandler);				
        this.draggableCanvas.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownHandler);
      }			
 
      private function mouseDownHandler(event:MouseEvent):void
      {
        this.draggableCanvas.startDrag();
      }
 
      private function mouseUpHandler(event:MouseEvent):void
      {
        this.draggableCanvas.stopDrag();
        }
    ]]>
  </mx:Script>
  <mx:Canvas id="container" width="300" height="250" backgroundColor="white" horizontalScrollPolicy="off" verticalScrollPolicy="off">
    <mx:Canvas id="draggableCanvas" x="50" y="25" width="200" height="200" backgroundColor="#AA0000">			
    </mx:Canvas>
  </mx:Canvas>
</mx:Application>

2 things I like in prototype 1.6.1

March 31st, 2009 by Andrea Franz

The stable version of prototype is 1.6.0.3. I just fiddled around with the edge version (1.6.1_rc2) and I found 2 new features I really like. The first one is the Element#clone method. At last I can easily clone an element, or make a deep clone of it passing true as first argument:

<div id="genres">
  <div class="genre">
    <select name="genre[]">
      <option>Your favorite genre...</option>
      <option>Death Metal</option>
      <option>Black Metal</option>
      <option>Speed Metal</option>
    </select>
  </div>
</div>
<a id="add-genre" href="#">Add</a>
document.observe("dom:loaded", function() {
  $("add-genre").observe("click", function(event) {
    event.stop();
    var genres = $("genres"); 
    genres.insert(genres.down(".genre").clone(true));
  });
});

Just click the Add link to add a new the select tag.

Another cool new feature is the Element Storage. You can store any kind of data inside a element, and retrieve it when you need it.

Here an example:

<div id="items">
  <div class="item" id="item-1">
    <h1>Item 1</h1>
    <a href="#" class="save">Save</a>
  </div>
  <div class="item" id="item-2">
    <h1>Item 2</h1>
    <a href="#" class="save">Save</a>
  </div>
</div>
document.observe("dom:loaded", function() {
  var item = $("item-1");
  var data = item.retrieve("data", {});
  data.url = "http://cowboys.from.hell/items/1";			
 
  $("items").select("a.save").invoke("observe", "click", function(event) {
    alert(event.target.up(".item").retrieve("data").url);
  });
});

Just click the first save link and an alert will display the url I previously stored inside the first item element.

Given what I’m seeing I’m pretty excited about the upcoming version of prototype.