〜 LaTeXなしでmarkdownをpdfに変換 〜
当解説文と関連のrubyスクリプトは、kpdf.zipに含まれている。
markdownの原稿から、LaTeXを用いずに簡単にpdfファイルを生成するのが目的。
rubyのライブラリkramdownを利用すると、markdownの原稿をhtmlやLaTeXのソースに変換できる。
加えて、kramdown ver 1.3 以降では、prawn(やはりrubyのライブラリ)を経由して、直接pdfに変換することができるようになった。
しかし、そのままだと一部の欧文フォント以外の文字を扱うことができない。そこで、kramdownの派生クラスを設けて、日本語等のフォントを扱えるようにした。
kpdf.rbは、次の環境で作成・テストした。
ruby 2.1.5 on MS-Windows7 | ruby 1.9.3 on CentOS6.2
kramdown 1.5.0
prawn 1.3.0
prawn-table 0.2.1
kramdown, prawn, prawn-table は、rubyのgemでインストール可能。
gem install kramdown [enter]
コマンドラインで上のように入力すると、kramdownがインストールされる。
kpdf.rbとsetup.rbが同じディレクトリにある状態で次のように入力。
ruby setup.rb [enter]
こうすると、kpdfをどのディレクトリからでも利用できるようになる。
unset.rbを実行すれば、kpdf.rbが消去される。
なお、このようにインストールしなくても、kpdf.rbがカレントディレクトリにある場合、rubyスクリプトの最初の方で次のように書くと、kpdfを利用できる。
require "./kpdf"
後述のサンプルスクリプトでは、日本語フォントとしてipaフォントを用いている。
ipaフォントの入手元は次のとおり。
サンプルでは ipam.ttf(明朝)、ipag.ttf(ゴシック)を利用。
なお、当方のWebにあるrptパッケージを導入している場合は、既にこれらのipaフォントがw32texディレクトリの下にあるのでダウンロードする必要はない。
まず、kramdownが予め指定しているフォントについて記す。pdf生成時に次の欧文フォントが用いられる。
root(全般のフォント): Times-Roman
header(見出しのフォント): Helvetica
codeblock(複数行のソースコードなど): Courier
codespan(行内の部分的ソースコードなど): Courier
この他、表については prawn-table にお任せになっている。Helvetica というフォントが使われる。
こうしたフォントのままだと日本語が扱えないので、フォントを変更する。
変更するための手続きをスクリプトの形で示すと次のとおり。
−−−−−−−− ここから
require "kpdf"
kpdf = Kramdown::kpdf
kpdf.dir = "C:/usr/font" # フォントの所在ディレクトリ
kpdf.root = kpdf.table = "ipam.ttf"
kpdf.header = kpdf.codeblock = kpdf.codespan = "ipag.ttf"
−−−−−−−− ここまで
kpdf.rbでは、利用者が指定するフォントの情報を保持するため、KpdfOptというクラスを定義している。
kpdf.rbをrequireすれば、このKpdfOptクラスのオブジェクトが暗黙のうちに生成される。そのオブジェクトは、Kramdownというモジュールの直下に所属する。
「kpdf = Kramdown::kpdf」とすれば、変数kpdfにKpdfOptオブジェクトが代入される。
フォントの指定方法は、サンプルで示したとおり「kpdf.root = ……」のように行う。
kpdf.root や kpdf.header に “C:/usr/font/ipam.ttf” のようにフルパス名を指定すると、kpdf.dirの値にかかわらず、そのフルパス名が用いられる。そうでなければ kpdf.dir と組み合わせてフルパス名が生成される。
kpdf.root や kpdf.header は、読み書き両用で参照と代入の両方が可能。
kpdf.tableには、表を作成する時のフォントを指定する。
日本語を扱えるようにフォントを設定した後は、次の2行でpdfファイルを作成できる。
変数mkd_strにmarkdownの原稿が代入されているものとする。
−−−−−−−− ここから
pdf_str = Kramdown::Document.new(mkd_str).to_kpdf
File.open("test.pdf", "wb") {|ff| ff.write pdf_str}
−−−−−−−− ここまで
「to_kpdf」を「to_pdf」に書き換えれば、kramdownの本来のpdf変換が行われる。その場合、日本語文字は ‘_’ になってしまう。
kramdownでは、Kramdownというモジュールの下にConverterというモジュールがあり、その下でPdfというクラスが定義されている。このクラス(Kramdown::Converter::Pdf)がpdf変換を担っている。
Pdfクラスの中に、フォントに関わるメソッドがいくつかある。kpdfではそれらを書き換えて、日本語等のフォントを扱えるようにした。
ただし、Pdfクラスそのものを書き換えるのではなく、その派生クラスを設ける形で対応する。派生クラスがKpdfである。
KpdfはPdfとほぼ同じだが、次のメソッドのみが異なる。
root_options
header_options
codeblock_options
codespan_options
render_table
document_options
上のメソッドは、その名前からして何を行うものか推測できると思うが、補足を少々。
root_optionsとかheader_optionsは、いずれもHash値を返す。それがオプションとして適用される。
render_tableは、prawnを利用して表を作成するメソッド。
prawnの主役オブジェクトは、Prawn::Document.new(……) によって生成されるが、kramdownでは、この主役オブジェクトを@pdfというクラス変数に代入している。
表を作る時は、@pdf.table(……) とする。render_tableの中でこれが記述されている。
Kpdfクラスでは、表を作成する前にフォントをユーザーが指定したものに変更し、表の作成が終わったら、フォントをHelveticaに戻す。
document_optionsというメソッドは、フォントとは関係ない。しかし、pdf作成に大きな影響を持つので、Kpdfクラスで指定できるようにしている。
これについては次項で述べる。
ここで、pdfファイルを作成するrubyスクリプト全体を掲げる。
kpdf.rbは、カレントディレクトリにあるものとする。
日本語フォントの ipam.ttf, ipag.ttf は、C:/usr/font というディレクトリの下にあるものと仮定。
−−−−−−−− ここから
# encoding: utf-8
require "./kpdf"
kpdf = Kramdown::kpdf
kpdf.dir = "C:/usr/font"
kpdf.root = kpdf.table = "ipam.ttf"
kpdf.header = kpdf.codeblock = kpdf.codespan = "ipag.ttf"
mkd_str = <<EOS
# kramdownによるpdfの生成\n
kramdown ver 1.3 以降では、\
prawn(ruby用ライブラリ)を利用して\
pdfを書き出せるようになった。\n
LaTeX文書を書き出した上で、別のコマンドを使ってpdfファイルを作成する、\
そんな手順を踏まなくてもpdfを生成できる。\n
# 表の作成\n
|名称|電話番号|
|---|---|
|天気予報|177|
|時刻|117|\n
# 箇条書き\n
1. 日本
- 東京
- 大阪府
2. アメリカ合衆国
- テキサス州
- カリフォルニア州\n
EOS
pdf_str = Kramdown::Document.new(mkd_str).to_kpdf
File.open("test.pdf", "wb") {|ff| ff.write pdf_str}
−−−−−−−− ここまで
rubyスクリプトの文字エンコードは utf-8 にしておくのが無難。kramdownもprawnも、utf-8で書かれている。もちろんkpdf.rbもutf-8。
ただ、MS-Windows環境下でテストしてみると、Windows-31Jでも問題なくpdfを生成できる。
kramdownで定義されている document_options() はHash値を返す。それは、Prawn::Document.new(……) の引数となる。
例えば次のものがある。
pdfのページサイズ :page_size=>’A4’
縦長・横長 :page_layout=>:portrait(縦)|:landscape(横)
四方のページ余白 :margin=>mm2pt(20) (ミリメートル→ポイント)
作成者等の情報 :info=>{:Creator=>’kramdown PDF converter’, :CreationDate=>Time.now}
圧縮 :compress=>true|false
最適化 :optimize_objects=>true|false
先に kpdf.root や kpdf.header に触れたが、kpdf.dopt というのもある。これにHash値を代入すると、それがdocument_optionsに反映される。
kpdf.doptの値がそのままdocument_optionsになるわけではない。kramdownの既定値にkpdf.doptを上書き・結合したものがdocument_optionsになる。
kpdf.dopt の初期値は {}、つまり空のHashである。
kramdownにおけるdocument_optionsの既定値を知りたければ、kpdf.dopt_default を確認されたい。
ページ余白のところに出てくる mm2pt() は、ミリメートルをポイントに変換するメソッド。Prawn::Measurementsで定義されている。
ちなみに、ミリメートルの数値が変数mmに入っているとき、「pt = mm*(72/25.4)」とすれば、ポイントの値が変数ptにセットされる。
:CreationDate=>Time.now は、pdfの作成日時を現在日時にするための指定。
これら mm2pt() や Time.now は、kpdf.doptに値を代入する時点で計算されるよりも、pdf生成時に計算される方が望ましい。特にTime.nowはそうである。
そこで、kpdf.doptに値を代入する時は、:margin=>”mm2pt(20)” とか :CreationDate=>”Time.now” のように文字列の形にして代入する。
こうしておくと、pdf生成時にその文字列がeval()で表価される。派生クラスのKpdfでは、そんなふうに動作するようdocument_optionsメソッドを再定義している。
:marginは、上下左右の四方の余白を指定するものだが、:top_margin, :bottom_margin, :left_margin, :right_margin を別々に指定することもできる。
その場合、:margin=>nil とすれば、:marginがHashから消去されて、pdf作成時には用いられない(Kpdfの仕様)。
rubyスクリプトにおいて、pdf作成終了後に kpdf.dopt2 の値を見ると、実際にどのようなdocument_optionsが適用されたのかを知ることができる。
kpdf.dopt2が返すのは、やはりHash値である。
prawnのdocument_optionsをはじめとする各種使用ノウハウについては、次のサイトを参考にさせていただいた。
rubyスクリプト中に「kpdf.root = ……」のように書いてフォント情報をセットできるわけだが、そうした情報をファイルから読み込むことができる。次のようにする。
kpdf = Kramdown::kpdf
kpdf.read("font_info.txt")
font_info.txtの中身は、例えば次のようなもの。
−−−−−−−− ここから
dir C:/usr/font
root ipaexm.ttf
header ipaexg.ttf
codeblock ipaexg.ttf
codespan ipaexg.ttf
table ipaexm.ttf
size 12
dopt {:margin=>'mm2pt(15)'}
−−−−−−−− ここまで
各行頭のdirやrootの後には、半角空白やタブコードを1つ以上書く。
sizeとdoptは、eval()で各々数値とHash値に変換される。
すべての項目を書く必要はない。書かれていない項目は変更されない。
変数hsにHash値を代入して、kpdf.set_info(hs) とすれば、そのHash値のとおりにフォント情報等が設定される。
「kpdf.set_info({:dir=>’C:/usr/font’, :root=>’ipam.ttf’, ……})」のように書く。
引数を省略すると、kramdownのフォントに関する既定値がセットされる。また、:dirはnilになり、:doptは空のHashになる。
kramdownのフォントに関する既定値は、kpdf.font_default() で知ることができる。
現在のフォント情報等を得るには、kpdf.get_info() を用いる。
kramdownは、markdownの原稿中に書かれた数式をhtmlやLaTeX向けに変換する機能を持つ。
しかし、prawn経由でpdfを生成した時は、数式が変換されずTeX記法のままpdfに書き出される。
もしかすると数式を正しく扱うための方法があるのかもしれないが、私には分からなかった。
脚注もpdf生成時には正しく扱われない。他にも使えない要素があると思う(未検証)。
完全なpdf作成を目的にするなら、やはりLaTeX経由がお勧め。
ただ、こった文書にする必要はなく、かつ、処理時間の抑制を重視するのであれば、kramdown&prawnも採用の価値ありと見る。
当方のWebにあるrptパッケージを導入している場合は、暗黙のうちにipaフォントが指定されるようになっている。rubyスクリプト中に指定のための記述を書く必要はない。
rpt導入時のipaフォントの所在ディレクトリは次のとおり。
/rpt/w32tex/share/texmf-dist/fonts/truetype/ipa
このディレクトリの下には、ipaexm.ttf, ipaexg.ttf もある。これらは、和文文字(仮名や漢字など)は固定幅、欧文文字は文字幅に合わせた変動幅を基本とした実装。
それに対し、これまで取り上げてきた ipam.ttf, ipag.ttf は、和文文字と欧文文字の両方とも固定幅。過去のシステムとの互換性を気にするなら、この両方とも固定幅の方がいいらしい。
ipaexフォントのダウンロード元は次のとおり。
〜 以上 〜