2012/11

Phantom.jsとimageMagickで.html to .png to .pdf

年末のプレゼンに向けてこんなの作ってみた。 テスト一切書いてなかったり、Cakefileが微妙だったり、デザインがやっつけだったりまだまだ改善の余地ありですが、一旦完成。

  georgeOsdDev/SlideBase

express + socket.IOでスライドショーを共有するみたいな感じです。
  Jxck/SlideStream の劣化版といえばそれまでですが、 js,coffeescriptの勉強という意味で以下の分厚い本を読みつつ1から自分で作ってみた。


webサーバはexpressでadminログインしたプレゼンターの操作をsocket.IOでブロードキャストで基本的なアイデアや作りは冒頭申したとおり、  Jxck/SlideStream まんまなんですが、
以下の構成パッケージで、socket.IOとexpressでセッション共有する際にJxckさんのブログのままだと動かなかったのでcookieのとりかたは pxsta's Memo さんを参考にcookieのパースが必要みたい。

package.json


              "dependencies": {
                "express": "3.0.0",
                "ejs": ">= 0.0.1",
                "connect": "2.6.0",
                "socket.io": "0.9.10",
                "connect-redis": "~1.4.5",
                "cookie": "0.0.5",
                "underscore": "~1.4.2"
              }
              

expressサーバ側


              app.use util.parallel(
                express.methodOverride(), 
                express.session
                  store: sessionStore
                  secret: config.session.secret
                  maxAge: false
                  cookie:
                    httpOnly: false
                )
              

socket.IOサーバ側


              io.set 'authorization', (handshakeData, callback) ->
                if handshakeData.headers.cookie
                  singedCookie = slideBaseUtil.parseCookie decodeURIComponent(handshakeData.headers.cookie)
                  cookie = slideBaseUtil.parseSignedCookies singedCookie
                  sessionID = cookie['connect.sid']
                  store.get sessionID ,(err,session) ->
                    if err
                      log "err", err
                      callback err.message, false
                    else if !session
                      callback "session not found", false
                    else
                      log "session found"
                      handshakeData.cookie = cookie
                      handshakeData.sessionID = sessionID
                      handshakeData.session = session
                      callback null, true
                else
                  callback "cookie not found", true
              

slideBaseUtilの中身はこんな感じ


              connect = require 'connect'
              cookie  = require 'cookie'
              
              parseCookie: (target) ->
                cookie.parse target
              parseSignedCookies: (singedCookie) ->
                connect.utils.parseSignedCookies singedCookie, config.session.secret
              

クライアントサイドでプラグインみたいに別ファイルのいろんなところからsocketを使えるようにするには

              execEmit: (name,data) ->
                console.log "execEmit "+name
                obj =
                  name:'plugin'
                  plugin:
                    name:name
                    data:data
                $('body').trigger 'execEmit', obj
              socket?.on 'connect', ->
                $('body').on 'execEmit', (event,obj) ->
                  socket.emit obj.name, obj
              

みたいにbodyにイベント作ってそれを外から呼ぶみたいにしてみた。
あとはサーバ側にもpluginで受けたらそのままブロードキャストするようにかいて


              socket.on 'plugin', (data) ->
                socket.broadcast.volatile.emit "plugin", data
              

クライアントサイドはpluginを受けたらpluginとして登録してある関数を呼ぶみたいな感じにしておけば未定義のものが仮に送られてきても大丈夫みたいな感じ。


              socket.on 'plugin', (obj) ->
                if not obj.plugin or not obj.plugin.name then return false
                if not isEnableServerPush obj.plugin.name then return false
                func = sbClient.plugins[obj.plugin.name].get("callback") || {}
                func(obj.data)
              

さて、タイトルにあるとおり感動したのはPhantom.js。
このやり方もJxckさんの ブログ を参考にしたのですが、チョット違うのはPhantom.jsからクリックイベントを呼んでページ遷移させるとこと、sam2pとpdftkの代わりに imageMagick っていうツールを使いました。imageMagickを使うと、複数pngから一発で1枚のpdfに変換することができました。

Phantom.jsからクリックイベントを呼ぶには


              page.includeJs 'http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js',->
                offset = page.evaluate ->
                  $('#next').offset()
                page.sendEvent 'click', offset.left+1, offset.top+1
              
みたいな感じで、叩きたいボタンや場所のoffsetを取得してpage.sendEvent('イベント',top,left)を呼べばOK。これでページ送りができますが、それぞれ非同期でやってる?ようなので、この直後にpage.render(outout)を呼んじゃうとまだクリックされてなかったりするのでsetTimeoutで微妙な調整がいるかも。sendEventの完了タイミングとれたりすると便利なのに。
さて、imageMagickインストールはhomebrew経由でカンタン。
              brew install imagemagick
              
これでこんな感じでいくつかコマンドが使えるようになります。
              22:26:33:oshidatakeharu@george-on-mba.local:/usr/local/Cellar/imagemagick/6.7.7-6/bin:master$ ll
              total 304
              -r-xr-xr-x  1 oshidatakeharu  admin  1290 Nov 11 12:08 Magick++-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1256 Nov 11 12:08 Magick-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1264 Nov 11 12:08 MagickCore-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1269 Nov 11 12:08 MagickWand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1251 Nov 11 12:08 Wand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 animate
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 compare
              -r-xr-xr-x  1 oshidatakeharu  admin  9204 Nov 11 12:08 composite
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 conjure
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 convert
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 display
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 identify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 import
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 mogrify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 montage
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 stream
              
このうちconvertっていうのがファイル変換してくれるやつで、
              convert src/*.png dest/output.pdf
              
とかあるいは
              convert -append 1.png 2.png output.pdf
              
みたいな感じでpngが複数ページ構成のpdfファイルに1つにまとまります。
カンタンでイイね。
あーなんか断片的でまとまりのないエントリーですみません。