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")
}
July 15th, 2009 by Andrea Franz
In the iPhone game I’m working on I need to display a different label every time I start a new game level. Each one of these labels has a random string taken from an array of words, and each one of these strings has different size. So every time I create a new label I set the maximum font size and then I decrement it until the string size measures less than the iPhone screen. I do that using the sizeWithFont method of the NSString class:
- (int) calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName {
int fontSize = 120; // it seems to be the biggest font we can use
while (--fontSize > 0) {
CGSize size = [string sizeWithFont:[UIFont fontWithName:usedFontName size:fontSize]];
if (size.width <= 480 && size.height <= 360)
break;
}
return fontSize;
}
And here a custom scene that you can use to test it:
MyScene.h
#import "cocos2d.h"
@interface MyScene : Scene {
Label *label;
NSArray *strings;
NSString *fontName;
int stringIndex;
}
- (void) nextLabel;
- (int) calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName;
- (void) removeCurrentLabel;
- (void) createNextLabel;
- (void) animateLabel;
@property (nonatomic, retain) NSArray *strings;
@property (nonatomic, retain) NSString *fontName;
@end
MyScene.m
#import "MyScene.h"
@implementation MyScene
@synthesize strings;
@synthesize fontName;
- (id) init {
if (self = [super init]) {
[self setFontName:@"Marker Felt"];
[self setStrings:[NSArray arrayWithObjects:
@"Lorem ipsum",
@"Lorem ipsum dolor",
@"Lorem ipsum dolor sit amet",
@"Lorem ipsum dolor sit amet, consectetur adipisicing elit",
@"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
nil]];
stringIndex = 0;
}
return self;
}
- (void) onEnter {
[super onEnter];
[self nextLabel];
}
- (void) nextLabel {
[self removeCurrentLabel];
[self createNextLabel];
stringIndex++;
}
- (void) removeCurrentLabel {
if (label) [self removeChild:label cleanup:YES];
label = nil;
}
- (void) createNextLabel {
NSString *labelString = [strings objectAtIndex:(stringIndex % [strings count])];
int fontSize = [self calculateFontSizeForString:labelString fontName:fontName];
label = [Label labelWithString:labelString dimensions: CGSizeMake(0, 0) alignment: UITextAlignmentCenter fontName:fontName fontSize:fontSize];
[label setPosition:ccp(240, 160)];
[label setScale:0.5];
[self addChild:label];
[self animateLabel];
}
- (void) animateLabel {
IntervalAction *scale = [ScaleTo actionWithDuration:0.5 scale:1];
id delay = [DelayTime actionWithDuration:1];
id notify = [CallFunc actionWithTarget:self selector:@selector(nextLabel)];
Sequence *sequence = [Sequence actions:scale, delay, notify, nil];
[label runAction:sequence];
}
- (int) calculateFontSizeForString:(NSString*)string fontName:(NSString*)usedFontName {
int fontSize = 120; // it seems to be the biggest font we can use
while (--fontSize > 0) {
CGSize size = [string sizeWithFont:[UIFont fontWithName:usedFontName size:fontSize]];
if (size.width <= 480 && size.height <= 360)
break;
}
return fontSize;
}
- (void) dealloc {
[strings retain];
[fontName retain];
[super dealloc];
}
@end
FitFontTestAppDelegate.m
#import "FitFontTestAppDelegate.h"
@implementation FitFontTestAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setUserInteractionEnabled:YES];
[window setMultipleTouchEnabled:YES];
[[Director sharedDirector] setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
[[Director sharedDirector] attachInWindow:window];
[window makeKeyAndVisible];
MyScene *scene = [MyScene node];
[[Director sharedDirector] runWithScene:scene];
}
- (void)dealloc {
[window release];
[super dealloc];
}
@end
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>