PhantomJS で HTML のスライドを PDF にし SlideShare にあげる方法
本文
最近は HTML 形式のプレゼンテーションツールも多くなってきました。
しかし、そうしたツールで作ったスライドはそのままでは SlideShare にあげることが出来ません。
もちろん HTML なのでサーバに上げて URL を共有することもできますが、
やはり、「終わったら SlideShare で共有」ってのは割と恒例になってるし、一元管理できるし、色々便利です。
方法としては、 HTML を PDF とかに直せれば良いのですが、
「見た目そのまま」で PDF 化するのは結構難しいです。
一番実直な方法は一枚づつスクリーンキャプチャを取る方法ですが、面倒です。
自分も色々試したんですが、なかなかうまくいかず。
しかし、 @t_wada さんが同じことに取り組んでおり、ヒントをもらったところ、成功したので紹介します。
今回使った方法は PhantomJS を使ってキャプチャ取得を自動で行い、
PDF 化して SlideShare にそのままの見た目であげられるようにする方法です。
この方法で色々と便利になると思います。眠ってる HTML スライドがあるかたも、貴重な資料の共有手段の一つとして是非。
ちなみに以下で使用した環境は Mac です。
流れ
PhantomJS
PhantomJS はヘッドレスブラウザツールです。
WebKit のエンジンが入っており、云々。
PhantomJS のインストール
PhantomJS のインストールには、 Homebrew で入れる方法と、ソースからビルドする方法があります。
Homebrew で入れると Qt のビルドから始まり時間がかかるそうです。
そこで今回は Qt のビルド済みバイナリを入れて、 PhantomJS はソースからビルドしました。
(Qt のバイナリを入れてから Homebrew すればいけるのかもと後で気づきました。今度ためそう。)
Qt のインストール
PhantomJS を入れるためには Xcode と Qt が必要です。
Xcode は入ってますが、 Qt は入ってないので入れます。
下記ページの中にある
http://qt.nokia.com/downloads/qt-for-open-source-cpp-development-on-mac-os-x
これを入れます。
Cocoa: Mac binary package for Mac OS X 10.5 - 10.6 (32-bit and 64-bit)
http://get.qt.nokia.com/qt/source/qt-mac-opensource-4.7.4.dmg (211 MB, includes build and interface tools)
PhantomJS のビルド
PhantomJS のビルドは、下記の通り。
$ git clone git://github.com/ariya/phantomjs.git && cd phantomjs $ qmake -spec macx-g++ && make
すると、
phantomjs/bin/phantomjs.app/Contents/MacOS/phantomjs
にバイナリができます。
これをパスの通ったところに移すなり、シンボリックリンクを貼るなりします。
$ ln -s ./bin/phantomjs /usr/local/bin/phantomjs
これで、 PhantomJS コマンドが使えるようになります。
PhantomJS の実行
$ phantomjs Usage: phantomjs [options] script.[js|coffee] [script argument [script argument ...]] Options: --load-images=[yes|no] Load all inlined images (default is 'yes'). --load-plugins=[yes|no] Load all plugins (i.e. 'Flash', 'Silverlight', ...) (default is 'no'). --proxy=address:port Set the network proxy. --upload-file fileId=/file/path Upload a file by creating a '<input type="file" id="foo" />' and calling phantom.setFormInputFile(document.getElementById('foo'), 'fileId').
動いてるようです。
とりあえず example を実行してみるとこんな感じ。
$ cd example $ phantom hello.js Hello, world!
単純に、仮想ブラウザを通して実行した結果が出力されます。
ラスタライズ
ラスタライズは、仮想ブラウザで実行した結果のスクリーンショットを保存するような機能です。
オプションでは PNG と PDF のフォーマットが選べます。
ならここで PDF にすればいいと思うんですが、 PDF の方はスクショではなく、
ブラウザの印刷プレビュー時の形式(print css 適応のやつ)での保存になるので、
HTML 見た目そのままにしたい場合は、一旦 PNG にします。
phantom.js についてる example の rasterize.js あたりがほぼそのまま使えるんですが、
今回、自分は下記のようなファイルを作成しました。
var page = new WebPage(), address, output, size, width, height, paperwidth, paperheight; address = phantom.args[0]; output = phantom.args[1]; width = phantom.args[2]; height = phantom.args[3]; paperwidth = phantom.args[4]; paperheight = phantom.args[5]; page.viewportSize = { width: width, height: height }; page.paperSize = { width: paperwidth, height: paperheight, border: '0px' } page.open(address, function (status) { console.log(status); if (status !== 'success') { console.log('Unable to load the address!'); } else { window.setTimeout(function () { page.render(output); phantom.exit(); }, 200); } });
これを rasterize.js で保存して、直で実行する場合のコマンドは以下です。
引数は
- URI
- スライドのアドレス
- output
- 出力ファイル名
- windowwidth
- ブラウザの Window サイズ幅
- windowheight
- ブラウザの Window サイズ縦
- viewportwidth
- 出力の横サイズ(cm)
- viewporthheight
- 出力の縦サイズ(cm)
$ phantomjs rasterize.js URI output windowwidth windowheight viewportwidth viewporthheight
対象のスライドはローカルでサーバを立ち上げていて http://localhost:3000/slide.html#slide-0 だったとします。
自分が使ったのはこれ。
サイズはこちらを参考にしました。
$ phantomjs rasterize.js http://localhost:3000/slide.html#slide-0 slide-0.png 1366 768 48.77cm 17.43cm
これで一枚の PNG ができます。
パラメータを引数指定できるようにしてるのは、色々試したいからです。
決まってれば直で rasterize.js に書いても良いですが rasterize.js を直接いじるよりは、
これを呼び出す Make などを書く方がいいかもしれません。
自分は Cake (CoffeeScript で書く Make) でタスクを書いてやりました。
注意点としては、沢山のページを同時にラスタライズしようとすると、うまくいかない場合があります。
その辺もうまいこと工夫してみてください。
sam2p
sam2p はフォーマット変換ツールです。
これで生成した PNG を PDF に変換します。
sam2p のインストール
http://code.google.com/p/sam2p/downloads/list ここにバイナリがあるんですが、
Mac 用はなく、他のディストリ版はやはり動きませんでした。
ソースからビルドします。
ソースはこれ
http://sam2p.googlecode.com/files/sam2p-0.49.tar.gz
$ ./configure --enable-lzw --enable-gif $ . make
なんか最後の方色々怪しげなメッセージができつつも sam2p バイナリが完成。
しかし実行してもエラーがでました。
$ sam2p-0.49/sam2p example.png example.pdf This is sam2p 0.49. Available Loaders: PS PDF JAI PNG JPEG TIFF PNM BMP GIF LBM XPM PCX TGA. Available Appliers: XWD Meta Empty BMP PNG TIFF6 TIFF6-JAI JPEG-JAI JPEG PNM GIF89a+LZW XPM PSL1C PSL23+PDF PSL2+PDF-JAI P-TrOpBb. sh: png22pnm: command not found sh: pngtopnm: command not found sam2p: Error: Filter::PipeE: system() failed: (png22pnm -rgba /var/folders/qg/...
png22pnm, pngtopnm がないとのこと。
http://code.google.com/p/pdfsizeopt/wiki/InstallationInstructions によると
pngtopnm は png22pnm の代理らしいです。
pngtopnm: free (this is an replacement if you can't install png22pnm)
で、さがしてみたら pnp22pnm の max 版バイナリがありました。
http://code.google.com/p/pdfsizeopt/downloads/list
この中の png22pnm-darwin です。
png22pnm-darwin を npg22pnm にリネームして
chmod +x して /usr/local/bin にシンボリックリンクしました。
再度 sam2p を実行したら成功!
pdftk
pdftk(pdf tool kit) は PDF 関連の機能が詰まった便利ツールです。
これを用いて、バラバラの PDF を一つのファイルに連結します。
pdftk のインストール
http://www.pdflabs.com/docs/install-pdftk/ に Macintosh OS X Snow Leopard Installer
がありました、すごい!
dmg を落としてきて普通にインストール。
pdftk コマンドが使えるようになります。
PDF の連結
pdftk cat で連結します。
$ pdftk slide-0.pdf slide-1.pdf .... cat output slide.pdf
これで一つに連結されます。
コマンドのまとめ
$ phantomjs rasterize.js http://localhost:3000/slide.html#slide-0 slide-0.png 1366 768 48.77cm 17.43cm $ sam2p slide-0.png sliede-0.pdf $ pdftk slide-0.pdf slide-1.pdf .... cat output slide.pdf
Cakefile
Rake でもいいですが Cake でタスクを書いてみました。
まだ coffeescript は初心者なので、あまり色々な機能は使えていないと思います。
CoffeeScript は賛否有るけど、少なくともこういうツールをさくっと書きたい場面では手軽で良いと思います。
サーバはローカルで立てている前提です。。
PNG の生成で一旦確認したいので、二つのタスクに分けています。
また、ラスタライズは同時にたくさん起動するとうまくいかないページがあったので、少しインターバルを入れています。
使い方は -p でページ数を指定するだけです。
その都合からスライドのハッシュは連番にしています。
option '-p', '--page [page]' task 'png', 'build png', (options) -> page = options.page command = 'phantomjs' script = 'rasterize.js' uri = '' width = 1366 height = 768 paperwidth = '48.77cm' paperheight = '17.43cm' commands = [] for i in [0..page] uri = "http://localhost:3000/slide.html#slide-#{i}" output = uri.split('#')[1] + '.png' commands.push "#{command} #{script} #{uri} #{output} #{width} #{height} #{paperwidth} #{paperheight}" build = (c) -> exec c.shift() , (err, stdout, stderr)-> throw err if err log stdout + stderr if c.length isnt 0 setTimeout () -> build(c) , 1000 build(commands) option '-p', '--page [page]' task 'pdf', 'build pdf', (options) -> slides = [] page = options.page for i in [0..page] exec "echo 'sam2p slide-#{i}.png sliede-#{i}.pdf'" , (err, stdout, stderr)-> throw err if err log stdout + stderr slides.push "slide-#{i}.pdf" slides = slides.join(' ') commands = "pdftk #{slides} cat output slide.pdf" exec commands, (err, stdout, stderr)-> throw err if err log stdout + stderr
実行はこんなかんじ。
$ cake -p 30 png $ cake -p 30 pdf
我ながら恥ずかしいくらい汚いw もうちょっときれいに書きたいですw
あとスーパーpreってまだ coffeescript に対応していないんですね。
いずれ対応される事を見越して coffeescript でマークアップしておきます。
完成
これで完成です。
サンプルとして以下のファイルと、変換結果の slideshare にアップしたものを貼っておきます。
HTML:
PDF: http://www.slideshare.net/Jxck/nodefest2011live
HTML: HTML5 Presentation
SlideShare: http://www.slideshare.net/Jxck/test-it-9845589
雑感
PhantomJS に読ませた時点で、若干フォントなどが変わったりは有りますが、
まあ資料として見る分には特に問題ないでしょう。
もしくはその辺の CSS を修正すればいいだけです。
もとのスライドのスタイルの作り方によっては、うまくいかなかったりすることがあります。
うまくいかないページだけ取り直しながら、とにかく各ページの PNG が揃えばあとはこっちのものです。
(そこだけスクショでもいいかも)
結構面倒な手順を踏みましたが、ここまで全部コマンドラインから出来るため、
一度環境ができれば、あとは好きなように自動化できると思います。
画像ではなく文字をコピペしたいなどの場合は、別途 OCR をかませればいいでしょう。
また方法自体が、数ある HTML スライドツールのどれを使うかに依存しないあたりがうれしいですね。自作ツールでもなんでも使えます。
自分はこれで、 KeyNote や PTT から、いよいよ HTML ベースでのプレゼンに置き換えていくつもりです。
また、ツールが HTML なので JS や CSS3 の機能をつかったプレゼンが存分にでき、image, iframe も張れるのでスライドから他リソースの引用も HTML の流儀にそって行えるし、
その上で PDF にもできるのは自分にとっては大きいです。
ちなみに iframe を使用した場合は、 PhantomJS のラスタライズでメモリを食うので、そのページだけ別でやるとかが必要かもしれません。
もしくは画像にしておくとか。自分はそうしたページは、なんとなく iframe を消してリンクだけ書いておく感じでやりました。
ちなみに最近自分は deck.js というツールを使っています。
軽くてごてごてしてない、プラグインが書ける、ブラウザでの印刷用 CSS も入ってる、などが気に入ってます。
長くなりました。多少面倒かもしれませんが、是非ためしてみてください。
また他にもいい方法があったら(きっと出てくるはず)教えてください。