Home > 梅原 > FlashからPDFを生成(AlivePDF)

FlashからPDFを生成(AlivePDF)

20090701
Category:梅原 /Tags:

FlashからPDFを生成(AlivePDF)クライアントサイドでPDFを生成するAS3のオープンソースライブラリ、AlivePDF(http://www.alivepdf.org/)を試してみた。
現在の最新バージョンは、1.4.9。残念ながら日本語には対応していない。1.5で対応予定らしいがいつになるかわからない模様。日本語(マルチバイト)対応版を作っている人もいるが、まずは本家英語版で概要を知ることにした。

まずはGoogle Codeからダウンロードしてみる。
http://code.google.com/p/alivepdf/
MITライセンスなので安心して商用にも使える。

ダウンロードファイルの中には、AIR(Flex)とFlashCS3用のデモソースファイルや、ASDocも入っている。「クライアントサイドでPDFを生成する」とは言っても、Flashからは直接はファイルの書き出しはできないので、その部分はPHPなどのサーバーサイドプログラムを使ったり、AIRの機能を使ったりしているのだ。今回はPHP連携版で試した。

Get Adobe Flash player


↑のGenerateボタンを押すと生成。

◆図形や文字の乗せ方
本家alivepdf.org(http://www.alivepdf.org/)や他の作例サイトを見て、DisplayObjectを投げるとそのままキレイなPDFが生成されるのかと思ったら、そういうわけではなかった。
一応、そういう作り方もaddImage(DisplayObject)でできるけど、あくまでbitmapとして貼付けるためのものらしい。

var myPDF:PDF = new PDF( Orientation.PORTRAIT, Unit.MM, Size.A4 );
<~略~>
myPDF.lineStyle( new RGBColor ( 0x000000 ), 0);
myPDF.beginFill ( new RGBColor ( 0xFF0000 ) );
myPDF.drawCircle ( 180, 25, 15 );
<~略~>
myPDF.addText ("Dammy hogehoge",0,20);

こんな感じで、PDF用のオブジェクトを作って、その上に描画していく。
Flashの通常のDisplayObjectへの指定の仕方に似せようとしているようだが、恐らくPDFの仕様故の差異があってイマイチ使いこなしにくい、というのが率直なところ。

◆日本語を乗せてみる。
日本語は文字として乗らないのだが、日本語の入ったTextFieldをDisplayObjectとして指定すれば、一応出せるはだせる。もちろん、そのままではギザギザガタガタなので、あらかじめ拡大しておいてBitmap化すればいい。

*nihongoBaseは日本語の入ったDisplayObject
//日本語の入ったBitmapObjectを拡大して、縮小して配置。
//これにより印刷してもピクセルのガタガタが目立たない文字にしている。
nihongoBase.scaleX = nihongoBase.scaleY = 3;
myPDF.addImage(nihongoBase,15,15,465/4,200/4);
nihongoBase.scaleX = nihongoBase.scaleY = 1;

ただ、これは多用するとファイルとして重くなって、どうも上手く書き出されないことがある。Flashから送信時の問題か、PHPのサーバー側の問題か、PDF生成後の問題かまでは突き詰めてないけど、画像を切るとあっという間に表示されるのに、画像を置くと上手く行かないことがある。

やっぱ、これは緊急避難の方法なのかな。

日本語対応マルチバイト版を試してみようかな。
http://www.harhid-labo.com/p1/index.php?Flex3%20PDF作成

▼ファイル一式
http://www.mztm.jp/wp/wp-content/uploads/2009/07/umhr_alivepdf.zip

追記▼日本語pdfを作ってみた
http://www.mztm.jp/wp/2009/07/06/flashから日本語pdfを生成(alivepdfmbpdf)/

▼ActionScript AS3(FP10)

package {
	import flash.display.Sprite;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.display.SimpleButton;

	import org.alivepdf.pdf.PDF;
	import org.alivepdf.layout.Orientation;
	import org.alivepdf.layout.Size;
	import org.alivepdf.layout.Unit;
	import org.alivepdf.display.Display;
	import org.alivepdf.saving.Method;
	import org.alivepdf.fonts.FontFamily;
	import org.alivepdf.fonts.Style;
	import org.alivepdf.colors.RGBColor;

	public class Main extends Sprite {
		private var loadFiles_array:Array;
		private var baseURL:String = "";
		private var MultiLoader:MultiLoaderClass = new MultiLoaderClass("http://mztm.heteml.jp/crossdomain.xml");
		private var myPDF:PDF;
		private var tf:TextField = Create.newTextField([5,405,455,22,"Alphabet,0123456,;:[]{}!@#$%^&*(),ABCDEFGHIJK,abcdefghijk"],[["border",true],["type","input"],["background",true]]);
		private var nihongoBase:Sprite = Create.newSprite([0, 0], null, [["beginFill", [0xFFFFFF, 1]], ["drawRect", [0, 0, 465, 200]]]);
		private var para:BitmapData;
		private var bmp_obj:Bitmap;
		function Main(){
			if(stage.loaderInfo.url.indexOf("mztm.jp") > -1){
				baseURL = "http://www.mztm.jp/wp/wp-content/uploads/2009/07/";
			}else if(stage.loaderInfo.url.substr(0,5) == "file:"){
			}
			loadFiles_array = MultiLoader.setLoad([baseURL+"umhr_alivepdfimg.jpg"],onImgComp);
		}

		public function onImgComp() {
			//ロードした画像を配置
			addChild(loadFiles_array[0]);

			//日本語のエリアに文字を配置
			nihongoBase.addChild(Create.newTextField([0,0,464,199],[["border",true],["type","input"],["wordWrap",true],["defaultTextFormat",new TextFormat(null,16)],["text","日本語をいれる。AlivePDF1.4.9では日本語フォントをPDFに埋め込めません。一つの解決方法として、Bitmap化して(addImageで自動的に変換される)画像として貼付ける方法があります。通常、そのままではドットがガタガタになります。今回は、addImageに送る直前に3倍に拡大し、送った直後に正倍に戻しています。"]]));
            addChild(nihongoBase);

			//英字テキストフィールド
			addChild(tf);

			//Generateボタン
			var btn:SimpleButton = Create.newSimpleButton([(465-100)/2,440,100,22,"Generate"]);
			addChild(btn);
			btn.addEventListener( MouseEvent.CLICK,CLICK);
		}
		private function CLICK(e:MouseEvent):void{
			myPDF = new PDF( Orientation.PORTRAIT, Unit.MM, Size.A4 );
			myPDF.setDisplayMode ( Display.REAL ); 

			//ページを追加
			myPDF.addPage();

			//画像の見本。
			myPDF.addImage(loadFiles_array[0],10,100);

			//円の見本。
			myPDF.lineStyle( new RGBColor ( 0x000000 ), 0);
			myPDF.beginFill ( new RGBColor ( 0xFF0000 ) );
			myPDF.drawCircle ( 180, 25, 15 );
			myPDF.beginFill ( new RGBColor ( 0x00FF00 ) );
			myPDF.drawCircle ( 160, 35, 10 );
			myPDF.beginFill ( new RGBColor ( 0x0000FF ) );
			myPDF.drawCircle ( 150, 45, 7 );
			myPDF.endFill();

			//線の見本。
			myPDF.lineStyle( new RGBColor ( 0x000099 ), 0.2, 1 );
			myPDF.moveTo ( 10, 80 );
			myPDF.lineTo ( 190, 80 );
			myPDF.lineTo ( 10, 85 );
			myPDF.lineTo ( 190, 85 );
			myPDF.lineTo ( 10, 90 );
			myPDF.lineTo ( 190, 90 );
			myPDF.lineTo ( 10, 95 );
			myPDF.lineTo ( 190, 95 );
			myPDF.endFill();
			myPDF.end();

			//次ページを追加
			myPDF.addPage();

			//フォントの見本
			var fontFamily:Array = [FontFamily.ARIAL,FontFamily.COURIER,FontFamily.HELVETICA,FontFamily.SYMBOL,FontFamily.TIMES,FontFamily.ZAPFDINGBATS];
			var fontSize:Array = [48,36,24,12,9,6];
			var pozY:Number=0;
			for (var j:int = 0; j < fontFamily.length; j++) {
				for (var i:int = 0; i < fontSize.length; i++) {
					var txt:String = fontFamily[j] + fontSize[i] + ":" +tf.text;
					pozY += fontSize[i];
					myPDF.setFont ( fontFamily[j], Style.NORMAL);
					myPDF.setFontSize (fontSize[i]);
					myPDF.addText (txt,10,pozY*0.35+2);
				}
			}

			//1ページ目を指定して編集する。
			myPDF.gotoPage (1);
			//日本語の入ったBitmapObjectを拡大して、縮小して配置。
			//これにより印刷してもピクセルのガタガタが目立たない文字にしている。
			nihongoBase.scaleX = nihongoBase.scaleY = 3;
			myPDF.addImage(nihongoBase,15,15,465/4,200/4);
			nihongoBase.scaleX = nihongoBase.scaleY = 1;

			//generated.pdfという名前で出力
			myPDF.save( Method.REMOTE, "http://mztm.heteml.jp/umhr/pdf/create.php", "generated.pdf" );
		}
	}
}

import flash.system.Security;
import flash.net.URLRequest;
import flash.net.URLLoader;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.display.Loader;
class MultiLoaderClass{

    private var onComplete:Function = function():void{};
    private var loadNum:int;
    private var loadCompNum:int;

    public function MultiLoaderClass(_str:String = null){
        if(_str != null){
            Security.loadPolicyFile(_str);
        }
    }

    public function setLoad(__item_array:Array,_onComp:Function = null):Array{
        loadCompNum = loadNum = 0;
        onComplete = _onComp;
        var _array:Array = new Array();
        var _length:int = __item_array.length;
        for (var i:int = 0; i < _length; i++) {
            if(__item_array[i] == null){continue};
            var _extension:String = __item_array[i].substr(-4,4).toLowerCase();//拡張子を取り出す。
            if(_extension == ".xml"){
                loadNum ++;
                _array[i] = fnURLLoader(__item_array[i]);
            }else if(_extension == ".jpg" || _extension == ".png"){
                loadNum ++;
                _array[i] = fnLoader(__item_array[i]);
            }else{
                //_array[i] = null;
            }
        }
        return _array;
    }
    private function fnURLLoader(__url:String):URLLoader{
        var _loader : URLLoader = new URLLoader();
        _loader.load(new URLRequest(__url));
        _loader.addEventListener (Event.COMPLETE,completeHandler);
        _loader.addEventListener (IOErrorEvent.IO_ERROR, ioErrorHandler);
        return _loader;
    }

    private function fnLoader(__url:String):Loader{
        var _loader:Loader = new Loader();
        _loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
        _loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        _loader.load(new URLRequest(__url));
        //_loader.name = __url;
        return _loader;
    }

    private function completeHandler(event:Event = null):void {
        loadCompNum ++;
        if(loadCompNum == loadNum){
            onComplete();
        }
        //var loaderInfo:LoaderInfo=event.currentTarget as LoaderInfo;
        //var loader:Loader=loaderInfo.loader;
        //addChild(loader);
    }

    private function ioErrorHandler(event:IOErrorEvent):void {
        //event.text = "Error #2035: URL が見つかりません。 URL: file:///~~~~~";
        //event.text = "Error #2036: 読み込みが未完了です。 URL: http://~~~~~";
        //から、URLのみを取り出す。
        //trace(String(event.text).substr(String(event.text).indexOf(" URL: ")+6),"*****");
        completeHandler();
    }
}

import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.SimpleButton;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.events.KeyboardEvent;

class Create{
	public static var defaultTextFormat:TextFormat = new TextFormat();

	public static function newSimpleButton(x_y_w_h_txt:Array = null,property:Array=null,graphics:Array=null):SimpleButton{
		var upState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xCCCCCC,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
		upState.addChild(newShape([2,2],null,[["beginFill",[0xE5E5E5,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
		var overState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xBBBBBB,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
		overState.addChild(newShape([2,2],null,[["beginFill",[0xEEEEEE,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
		var downState:Sprite = newSprite([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0xAAAAAA,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
		downState.addChild(newShape([2,2],null,[["beginFill",[0xDDDDDD,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2]-4,x_y_w_h_txt[3]-4,6]]]))
		var hitTestState:Shape = newShape([x_y_w_h_txt[0],x_y_w_h_txt[1]],null,[["beginFill",[0,1]],["drawRoundRect",[0,0,x_y_w_h_txt[2],x_y_w_h_txt[3],8]]]);
		if(x_y_w_h_txt[4]){
			upState.addChild(newTextField([0,2,x_y_w_h_txt[2],x_y_w_h_txt[3]-2],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
			overState.addChild(newTextField([0,2,x_y_w_h_txt[2],x_y_w_h_txt[3]-2],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
			downState.addChild(newTextField([0,3,x_y_w_h_txt[2],x_y_w_h_txt[3]-3],[["defaultTextFormat",new TextFormat("_sans", null, null, null, null, null, null, null, "center")],["text",x_y_w_h_txt[4]]]));
		}
		var sb:SimpleButton = new SimpleButton(upState,overState,downState,hitTestState);

		return sb;
	}

	public static function newShape(x_y_w_h_sh:Array = null,property:Array=null,graphics:Array=null):Shape{
		var i:int;
		var sh:Shape;
		if(x_y_w_h_sh && x_y_w_h_sh[4]){
			sh = x_y_w_h_sh[4];
		}else{
			sh = new Shape();
		}
		if(x_y_w_h_sh){
			if (x_y_w_h_sh[0]) { sh.x = x_y_w_h_sh[0] };
			if (x_y_w_h_sh[1]) { sh.y = x_y_w_h_sh[1] };
		}
		if(property){
			for (i = 0; i < property.length; i++) {
				if(property[i] && property[i].length > 1){
					sh[property[i][0]] = property[i][1];
				}
			}
		}
		if(graphics){
			for (i = 0; i < graphics.length; i++) {
				if(graphics[i] && graphics[i].length > 1){
					sh.graphics[graphics[i][0]].apply(null, graphics[i][1]);
				}
			}

		}
		if(x_y_w_h_sh){
			if (x_y_w_h_sh[2]) { sh.width = x_y_w_h_sh[2] };
			if (x_y_w_h_sh[3]) { sh.height = x_y_w_h_sh[3] };
		}
		return sh;
	}
	public static function newSprite(x_y_w_h_sp:Array = null,property:Array=null,graphics:Array=null,addChild:DisplayObject = null):Sprite{
		var i:int;
		var sp:Sprite;
		if(x_y_w_h_sp && x_y_w_h_sp[4]){
			sp = x_y_w_h_sp[4];
		}else{
			sp = new Sprite();
		}
		if(x_y_w_h_sp){
			if (x_y_w_h_sp[0]) { sp.x = x_y_w_h_sp[0] };
			if (x_y_w_h_sp[1]) { sp.y = x_y_w_h_sp[1] };
		}
		if(property){
			for (i = 0; i < property.length; i++) {
				if(property[i] && property[i].length > 1){
					sp[property[i][0]] = property[i][1];
				}
			}
		}
		if(graphics){
			for (i = 0; i < graphics.length; i++) {
				if(graphics[i] && graphics[i].length > 1){
					sp.graphics[graphics[i][0]].apply(null, graphics[i][1]);
				}
			}

		}
		if(addChild){
			sp.addChild(addChild);
		}
		if(x_y_w_h_sp){
			if (x_y_w_h_sp[2]) { sp.width = x_y_w_h_sp[2] };
			if (x_y_w_h_sp[3]) { sp.height = x_y_w_h_sp[3] };
		}
		return sp;
	}

	public static function newTextField(x_y_w_h_txt_color_alpha:Array = null,property:Array=null,method:Array=null):TextField{
		var i:int;
		var ta:TextField = new TextField();
		ta.defaultTextFormat = defaultTextFormat;
		if(x_y_w_h_txt_color_alpha){
			if (x_y_w_h_txt_color_alpha[0]) { ta.x = x_y_w_h_txt_color_alpha[0] };
			if (x_y_w_h_txt_color_alpha[1]) { ta.y = x_y_w_h_txt_color_alpha[1] };
			if (x_y_w_h_txt_color_alpha[2]) { ta.width = x_y_w_h_txt_color_alpha[2] };
			if (x_y_w_h_txt_color_alpha[3]) { ta.height = x_y_w_h_txt_color_alpha[3] };
			if (x_y_w_h_txt_color_alpha[4]) { ta.text = x_y_w_h_txt_color_alpha[4] };
			if (x_y_w_h_txt_color_alpha[5]) { ta.textColor = x_y_w_h_txt_color_alpha[5] };
			if (x_y_w_h_txt_color_alpha[6]) { ta.alpha = x_y_w_h_txt_color_alpha[6] };
		}
		if(property){
			for (i = 0; i < property.length; i++) {
				if(property[i] && property[i].length > 1){
					ta[property[i][0]] = property[i][1];
				}
			}
		}
		if(method){
			for (i = 0; i < method.length; i++) {
				if(method[i] && method[i].length > 1){
					ta[method[i][0]].apply(null, method[i][1]);
				}
			}
		}
		return ta;
	}
}

関連記事:

Comments are closed.