クライアントサイドで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連携版で試した。
↑のGenerateボタンを押すと生成。
◆図形や文字の乗せ方
本家alivepdf.org(http://www.alivepdf.org/)や他の作例サイトを見て、DisplayObjectを投げるとそのままキレイなPDFが生成されるのかと思ったら、そういうわけではなかった。
一応、そういう作り方もaddImage(DisplayObject)でできるけど、あくまでbitmapとして貼付けるためのものらしい。
1 2 3 4 5 6 7 |
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化すればいい。
1 2 3 4 5 6 |
*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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
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; } } |