Hatena::ブログ(Diary)

エンジニアのソフトウェア的愛情 このページをアンテナに追加 RSSフィード Twitter

2015-04-12

海老で PDF を釣る

単票を作るために、RubyPDF を生成するライブラリ Prawn を試してみました。


以下、Prawn を使って PDF ができるまでの様子です。


Prawn を導入する

RubyGem で提供されているので、それインストールします。

$ gem install prawn

PDF ファイルを作成してみます。

require 'prawn'

Prawn::Document.generate('sample.pdf', page_size: 'A4') do |pdf|
  pdf.text 'Hello, Prawn!'
end

これで sample.pdf というファイル名の A4 サイズの PDF ファイルが作成されます。


直接ファイルを生成するのではなく、レンダリングしたデータを生成することもできます。

次の例では、Prawn::Document#renderレンダリングしたデータを IO.write でファイルに保存しています。

require 'prawn'

pdf = Prawn::Document.new page_size: 'A4')
pdf.text 'Hello, Prawn!'
IO.write('sample2.pdf', pdf.render)

文字列の出力位置を変更します。

text の代わりに draw_text を使います。

pdf.draw_text 'Hello, Prawn', at: [100, 100]

Prawn では印字範囲の左下が原点となり、そこから [ 右方向の距離, 上方向の距離 ] という形で位置を指定します(第一象限の (x, y) を指定する形です)。単位はポイント(72分の1インチ)です。


表示領域を指定し、その領域内の配置を指定することもできます。

そのばあいは text_box を使います。

次の例では、右から 100 ポイント、下から 100 ポイント、幅 100 ポイント、高さ 100 ポイントの範囲に、縦横とも中央寄せで出力します。

pdf.text_box 'Hello, Prawn!', at: [100, 100], width: 100, height: 20, align: :center, valign: :center

高さ (height) を指定するばあい、指定した位置から下方向の位置になるので注意が必要です


f:id:E_Mattsan:20150412103335p:image


また「ポイントの計算が面倒臭い」というばあいのために、メートル法、ヤード法を使うための手段が用意されています。

prawn/measurement_extensions を使うと、.mm.cm といったメソッドが使えるようになります。

require 'prawn'
require 'prawn/measurement_extensions'

Prawn::Document.generate('sample.pdf', page_size: 'A4') do |pdf|
  pdf.draw_text 'Hello, Prawn', at: [100.mm, 100.mm]
end

標準では日本語フォントを持っていないので、日本語を表示させるばあいはフォントファイルを読み込ませる必要があります。

IPAフォントなどを用意します。

たとえば明朝体フォント ipaexm.ttf を設定するばあい次のように記述します。

# フォントの読み込みと設定
pdf.font('ipaexm.ttf')

あるいは

# フォントファルの読み込み
pdf.font_families.update('ipa-mincho' => {normal: {file: 'ipaexm.ttf'}})
# フォントの設定
pdf.font('ipa-mincho')

細かな設定は Prawn のドキュメントを参照してみてください。



捺印欄を作ってみる

これらを踏まえて。スタンプラリーの台紙 捺印欄のある書類を作成してみます。

f:id:E_Mattsan:20150412100026p:image


require 'prawn'
require 'prawn/measurement_extensions'

# ファイル名 sample.pdf 、 用紙サイズ A4 、マージン を上 15mm 、右 15mm 、下 20mm 、左 25mm に設定した PDF ファイルを作る
# マージンの数字の並び(上右下左の並び)は CSS と同じ、また値が1つ、2つ、3つの場合の動作も CSS と同じ
Prawn::Document.generate('sample.pdf', page_size: 'A4', margin: [15.mm, 15.mm, 20.mm, 25.mm]) do |pdf|

  # 捺印欄の位置と幅高さ
  # 印字領域の右から 9cm 、上から 5cm の位置、幅 9cm 、高さ 2.5cm の領域に設定
  pdf.bounding_box([pdf.bounds.width - 90.mm, pdf.bounds.height - 50.mm], width: 90.mm, height: 25.mm) do
    # このブロック内では捺印欄の左下が原点

    # 太線の幅を指定
    pdf.line_width(1)

    # 枠を書く(bounding_box で指定した領域を覆う線を描く)
    pdf.stroke_bounds

    # 技術本部と技術部を分ける線
    pdf.horizontal_line(0.mm, 90.mm, at: 20.mm) # 横線:左から 0mm〜90mm までの横線を、高さ 20mm の位置に描く
    pdf.vertical_line(0.mm, 25.mm, at: 30.mm)   # 縦線:高さ 0mm〜25mm までの縦線を、左から 30mm の位置に描く

    # ここまでの線を描画
    pdf.stroke

    # 細線の幅を指定
    pdf.line_width(0.2)

    # 役職を分ける線
    pdf.horizontal_line(0.mm, 90.mm, at: 15.mm)
    pdf.vertical_line(0.cm, 20.mm, at: 15.mm)
    pdf.vertical_line(0.cm, 20.mm, at: 45.mm)
    pdf.vertical_line(0.cm, 20.mm, at: 60.mm)
    pdf.vertical_line(0.cm, 20.mm, at: 75.mm)

    # ここまでの線を描画
    pdf.stroke

    # フォントファルの読み込み
    pdf.font_families.update('ipa-mincho' => {normal: {file: 'fonts/ipaexm.ttf'}})

    # フォントの設定
    pdf.font('ipa-mincho')

    # フォントサイズ指定
    pdf.font_size(8)

    # 部門名
    pdf.bounding_box([0.mm, 25.mm], width: 90.mm, height: 5.mm) do
      pdf.text_box('技術本部', at: [0.mm, 5.mm], width: 30.mm, height: 5.mm, align: :center, valign: :center)
      pdf.text_box('技術部', at: [30.mm, 5.mm], width: 60.mm, height: 5.mm, align: :center, valign: :center)
    end

    # 役職名
    pdf.bounding_box([0.mm, 20.mm], width: 90.mm, height: 5.mm) do
      %w(本部長 副本部長 部長 課長 係長 担当).each_with_index do |pos, i|
      pdf.text_box(pos, at: [15.mm * i, 5.mm], width: 15.mm, height: 5.mm, align: :center, valign: :center)
      end
    end
  end
end


Ruby on Rails のレスポンスで PDF を返す

Prawn::Document.newPrawn::Document#render を使うことで Rails のレンスポンスとして生成した PDF を返すことができます。


  def show
    respond_to do |format|
      format.pdf do
        pdf = Prawn::Document.new(page_size: 'A4', margin: [15.mm, 15.mm, 20.mm, 25.mm])
        # PDF を構築(省略)
        send_data pdf.render, filename: "sample.pdf", type: "application/pdf", disposition: 'inline'
      end
    end
  end


いつか読むはずっと読まない:がんばれカミナリ竜

ブロントサウルスはアパトサウルスとは別属だったらしいという記事。

歩けば雷鳴が轟くごときだっただろうという想像から、雷竜〜ブロントサウルスという名称がつけられ、広く知られていたもののアパトサウルスと同属と判断され先名権により姿を消していた名称。

ここにきて復活するとは思いませんでした。


雷竜といえば、この書籍。

今回調べてみて、入手困難になっていることを知り、少なからずショックを受けました。


そしてグールド博士の最後のエッセイ集。日本語訳版がでてすぐに購入したのですが、これでおしまいかと思うと、もったいない感じがしてしまい、いまだに読めずにいます。

2015-03-30

RailsとHamlでSVG

ふつうに

ブラウザがインラインのSVG表示に対応していれば、HamlSVGを書くとそのまま表示してくれます。

app/controllers/circles_controller.rb

class CirclesController < ApplicationController
  def index
    @circles = 30.times.map { {x: rand(500), y: rand(500), r: rand(100)} }
  end
end

app/views/circles/index.html.haml

%svg{'xmlns' => 'http://www.w3.org/2000/svg', 'xmlns:xlink' => 'http://www.w3.org/1999/xlink', 'viewBox' => '0 0 500 500', 'width' => '100%', 'height' => '100%'}
  - @circles.each do |circle|
    %circle{cx: circle[:x], cy: circle[:y], r: circle[:r], stroke: 'red', fill: 'none'}

Rails を起動して http://localhost:3000/circles を開くとこんな感じ。

f:id:E_Mattsan:20150330225649p:image


フォーマットにSVGを指定して

フォーマットを指定して、http://localhost:3000/circles.svg とアクセスしたい場合には、いくつか設定が必要になります。


MIME タイプを設定する

config/initializers/mime_types.rb に SVG を設定します。

次の1行を追加します。

Mime::Type.register 'image/svg+xml', :svg

Haml のフォーマットを設定する

Haml のフォーマットを XHTML にします。これをしておかないとXMLのDOCTYPE宣言が出力されません。

具体的には config/initializers/haml.rb を作成して次の内容を記述します。

Haml::Template.options[:format] = :xhtml

ビューを書く

拡張子SVGにしたHamlを書きます。

内容はインラインで書いたものにDOCTYPE宣言を追加したものになります。

app/views/circles/index.svg.haml

!!! XML
%svg{'xmlns' => 'http://www.w3.org/2000/svg', 'xmlns:xlink' => 'http://www.w3.org/1999/xlink', 'viewBox' => '0 0 500 500', 'width' => '100%', 'height' => '100%'}
  - @circles.each do |circle|
    %circle{cx: circle[:x], cy: circle[:y], r: circle[:r], stroke: 'red', fill: 'none'}

img タグで表示する

MIME タイプを指定しているので、img タグを使っても表示することができます。

index.html.haml を次のように書き換えて http://localhost:3000/circles にアクセスすると通常の画像と同じように表示されます。

app/views/circles/index.html.haml

= image_tag circles_path(format: :svg)

2015-03-07

有限オートマトン Finite Automaton はPrologで書くと簡単だった件

引き続き、アンダースタンディング コンピュテーションを読んでいます。


先日はいろんな言語でDFAを書いてみたわけですが、よくよくコードを読み直してみると、Prologのばあいはもっと簡単にDFAを書けることに気がつきました。

というか、Prologのコードは、ほとんどルールそのものだった、というお話。


決定性有限オートマトン: Deterministic Finite Automaton (DFA)

DFAでは、ある状態である入力があったときの遷移先が1つに決定しています。

次のような状態遷移表で表されるDFAを書いてみます。

状態\入力ab
121
223
333

DFAのコード。

rule(1, 0'a, 2). rule(1, 0'b, 1).
rule(2, 0'a, 2). rule(2, 0'b, 3).
rule(3, 0'a, 3). rule(3, 0'b, 3).

dfa(State, [], State).
dfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  dfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  dfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

DFAをテストするコード。開始状態は 1 、受理状態は 3 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [3], "a"),
  test(1, [3], "baa"),
  test(1, [3], "baba").

実行結果。

$ swipl -s dfa.prolog -g 'main, halt'
a => false
baa => false
baba => true

非決定性有限オートマトン: Nondeterministic Finite Automaton (NFA)

NFAでは遷移先が1つに決まりません。そのため、各々の遷移先に対する挙動を考慮しなければなりません。

状態\入力ab
111 or 2
233
344

…なのですが。Prologにはバックトラックというしくみが言語自体に組み込まれています。これによって、複数の遷移先があるばあいでも各々の挙動を網羅することができるようになっています。


ja.wikipedia.org に解説がありますので、詳しくはこちらへ:


NFAのコード。

実際のところ、rule の内容を変更し、名前を dfa から nfa に変えただけで、処理自体は DFA と同じです。

rule(1, 0'a, 1). rule(1, 0'b, 1). rule(1, 0'b, 2).
rule(2, 0'a, 3). rule(2, 0'b, 3).
rule(3, 0'a, 4). rule(3, 0'b, 4).

nfa(State, [], State).
nfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  nfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  nfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

ちょっと挙動を確認してみます。

$ swipl -s nfa.prolog
?- nfa(1, "b", R).
R = 1 ;
R = 2 ;
false.

swipl -s nfa.prolog でファイルを読み込んでいます。

プロンプトが表示されるので nfa(1, "b", R). と入力します。

実行結果として R = 1 と表示され、一旦入力待ちになります。ここでセミコロンを入力すると別の解を求めるため実行を再開します。

別の結果として R = 2 と表示されます。これらはそれぞれ、状態1のときに入力Bがあったばあいの結果(遷移先の状態)の状態1と状態2に対応します。

2つ目の解を表示したあと再び入力待ちになっているのでセミコロンを入力すると、これ以上解がないので false. を表示して処理を終了します。


NFAをテストするコード。開始状態は 1 、受理状態は 4 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [4], "bab"),
  test(1, [4], "bbbbb"),
  test(1, [4], "bbabb").

実行結果。

$ swipl -s nfa.prolog -g 'main, halt'
bab => true
bbbbb => true
bbabb => false

自由移動のある NFA

入力がなくても自発的に遷移する自由移動があるばあいを考えます。

下記の状態遷移表ではεで書いた列が自由移動です。遷移先が - となっている部分は、そのような遷移のルールがないことを表しています。

状態\入力aε
1-2 or 4
23-
32-
45-
56-
64-

状態1は自由移動で、つまり入力がなくても自発的に、状態2か状態4に遷移します。


自由移動のあるNFAのコード。

入力を必要としない rule/2 と、 rule/2 を利用して入力をとらずに遷移するコードを追加しています。

rule(1, 2).
rule(1, 4).
rule(2, 0'a, 3).
rule(3, 0'a, 2).
rule(4, 0'a, 5).
rule(5, 0'a, 6).
rule(6, 0'a, 4).

nfa(State, [], State).
nfa(StartState, CS, LastState) :-
  rule(StartState, NextState),
  nfa(NextState, CS, LastState).
nfa(StartState, [C|CS], LastState) :-
  rule(StartState, C, NextState),
  nfa(NextState, CS, LastState).

isAccepts(StartState, AcceptStates, String, true) :-
  nfa(StartState, String, LastState),
  member(LastState, AcceptStates).
isAccepts(_, _, _, false).

ちょっと挙動を確認してみます。

?- nfa(1, "", R).
R = 1 ;
R = 2 ;
R = 4 ;
false.

状態1のばあい、入力がなくても状態2か状態4に遷移することがわかります。遷移せず状態1のままという場合もあるので、このばあいは状態1,2,4のいずれかが取りうる値となります。


自由移動のあるNFAをテストするコード。開始状態は 1 、受理状態は 2 か 4 です。

test(StartState, AcceptStates, String) :-
  isAccepts(StartState, AcceptStates, String, Result),
  format("~s => ~p~n", [String, Result]).

main :-
  test(1, [2, 4], "aa"),
  test(1, [2, 4], "aaa"),
  test(1, [2, 4], "aaaa"),
  test(1, [2, 4], "aaaaa").

実行結果。

$ swipl -s nfa.prolog -g 'main, halt'
aa => true
aaa => true
aaaa => true
aaaaa => false

いつか読むはずっと読まない:人工知能つながりということで…

久々に小説を読んでいます。これがなかなか進みません。小説を読む体力が低下しているようです。

2015-02-22

修練がてら DFA をいろんな言語で書いてみた

アンダースタンディング コンピュテーションを読んでいます。


その「3章 最も単純なコンピュータ」の「3.1 決定性有限オートマトン」に登場する決定性有限オートマトンDFA)のRubyでの実装を他の言語でも実装してみました。


実装

GitHubに収めました。

Ruby写経https://github.com/mattsan/dfa-samples/blob/master/dfa.rb
CoffeeScripthttps://github.com/mattsan/dfa-samples/blob/master/dfa.coffee
C++https://github.com/mattsan/dfa-samples/blob/master/dfa.cpp
Haskellhttps://github.com/mattsan/dfa-samples/blob/master/dfa.hs
Io languagehttps://github.com/mattsan/dfa-samples/blob/master/dfa.io
Prologhttps://github.com/mattsan/dfa-samples/blob/master/dfa.prolog
Erlanghttps://github.com/mattsan/dfa-samples/blob/master/dfa.erl
C++ Template Meta Programminghttps://github.com/mattsan/dfa-samples/blob/master/dfa_template.cpp
SQLhttps://github.com/mattsan/dfa-samples/blob/master/dfa.sql

実行結果

実装の詳細は GitHub を見ていただくとして。

実行の手順と結果だけ載せておきます。

実行環境は Mac Yosemite です。


Ruby
$ ruby dfa.rb
a => false
baa => false
baba => true

CoffeeScript
$ coffee dfa.coffee
a => false
baa => false
baba => true

C++
$ g++ --std=c++11 -o dfa dfa.cpp
$ ./dfa
a => false
baa => false
baba => true

Haskell
$ ghc --make dfa
$ ./dfa
a => False
baa => False
baba => True

Io language
$ io dfa.io
a => false
baa => false
baba => true

Prolog
swipl -s dfa.prolog -g 'main'
a => false
baa => false
baba => true

Erlang
$ erlc dfa.erl
$ erl -noshell -s dfa start -s init stop
a => false
baa => false
baba => true

C++ Template Meta Programming
$ g++ --std=c++11 -c dfa_template.cpp 
dfa_template.cpp:107:1: error: static_assert failed "'a' was not accepted"
dfa_template.cpp:110:1: error: static_assert failed "'baa' was not accepted"
dfa_template.cpp:114:1: error: static_assert failed "'baba' was accepted"

static_assert を利用して結果を表示しているので error と出ていますが、実行エラー(コンパイルエラー)なわけではないです。

後ろのメッセージの方を見てやってください。


SQL
$ psql some-database < dfa.sql
CREATE TABLE
INSERT 0 6
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 3
 source | accepted 
--------+----------
 baa    | f
 a      | f
 baba   | t
(3 rows)

DROP TABLE
DROP TABLE
DROP TABLE

今回の収穫

2015-01-31

Re:VIEWでドキュメントを書いた・コネタ:画像をインラインで挿入する

間が空いてしまいましたが。前回の続き

画像の挿入の話。


md2review を使って Markdown から Re:VIEW の形式に変換した場合、画像のタグは次のように変換されます。


これが、

![とある画像](images/picture.png)

こうなる。

//image[picture][とある画像]{
//}

このように Re:VIEW の形式ではタグに改行が入るため、このままではたとえば画像を表の中に配置するということができません。


実は Re:VEIW 形式には画像をインラインで挿入する方法があります。

インラインで挿入する場合は次のように記述します。

@<icon>{picture} 

この場合は画像にキャプションを付けることができませんが、インラインですのでもともとキャプションが不要な場合が多く、問題にはならないかと思います。



これらを踏まえて。

キャプションが使われないのならば、その部分をインラインか否かの識別に利用して、たとえば inline と書かれていたらインラインに変換するように変換してみます。

たとえば sed を使うとこんな感じ。


$ sed -E 's/\!\[inline\]\(images\/(.+)\.[^.]+\)/@<icon>{\1}/g' source.md | md2review > source.re

Markdown でこのように書くと、

インラインの ![inline](images/picture.png) 画像

このように変換されます。

インラインの @<icon>{picture} 画像