ソフトウェア/pukiwiki/tchart.inc.php の履歴(No.2)
更新pukiwiki でタイミング図を書きたい†
デジタル信号処理の記事を書くのに、
&tchart( clock _~_~_~_~_~_~_~_~_~_~_ data =====X=DATA========X===== valid ~~~~~~~~~~_ ready ________~~_ );
こういったタイミング図を書きたくて、 プラグインにしてみました。
tchart†
非常に便利なツールを熊谷正朗さんが公開しておられます。
- タイミングチャート清書ツール tchart
http://www.mech.tohoku-gakuin.ac.jp/rde/contents/library/tchart/indexframe.html
このツールを使うと、
clock _~_~_~_~_~_~_~_~_~_~_ data =====X=DATA========X===== valid _____~~~~~~~~~~______ ready _____________~~______
こんな風にテキストで書いたタイミング図を、
&tchart( clock _~_~_~_~_~_~_~_~_~_~_ data =====X=DATA========X===== valid ~~~~~~~~~~_ ready ________~~_ );
このように画像化できます。
ツール自体の出力はポストスクリプトになるのですが、 そのままだとホームページ上に表示できません。 本当なら tchart 自体を svg を出力する形に手直しをすれば良いのですが、 ここでは手抜きをして、ポストスクリプトファイルを svg に変換するコードを別途書きました。
svg ファイルを作るまでの処理†
gohstscript と pdf2svg がインストールされていれば、 以下のようにしてポストスクリプトから SVG を作成できます。
LANG:console $ tchart chart.tc chart.eps $ ps2pdf chart.eps chart.pdf $ pdf2svg chart.pdf chart.svg
処理の流れ†
- pukiwiki.php を変更して tchart プラグインに複数行にわたる引数を与えられるようにする
- tchart を呼び出して eps を作成
- ps2pdf で pdf に変換 https://packages.debian.org/cgi-bin/search_contents.pl?word=ps2pdf
- pdf2svg で svg に変換 https://packages.debian.org/search?keywords=pdf2svg
- ruby で書いたプログラムで svg の余白をトリミングする
- html に直接埋め込んで表示する
- 変換結果の svg は次回の表示のためにキャッシュする
tchart ソースから svg を作り、余白をトリミングするスクリプト†
ruby で書きました。tchart ソースを与えると標準出力に SVG コードを出力します。
/usr/local/bin/tchart-svg.rb
LANG:ruby(linenumber)
#!/usr/bin/ruby
require "digest/md5"
require "tempfile"
source = readlines.join('')
##################################################### SVG を作る
file = ''
temp = Tempfile.open(['tchart', '.tc'])
file = temp.path.sub(/\..+/, '')
temp.write source+"\n"
temp.close
system("/usr/local/bin/tchart.pl #{file}.tc #{file}.eps") or exit
system("/usr/bin/ps2pdf #{file}.eps #{file}.pdf") or exit
system("/usr/bin/pdf2svg #{file}.pdf #{file}.svg") or exit
lines = ''
open("#{file}.svg") do |svg|
lines = svg.readlines
end
system("/bin/rm #{file}.tc #{file}.eps #{file}.pdf #{file}.svg")
##################################################### トリミングする
surface = lines.find_index {|line| line =~ /<g id="surface1"/ }
exit -1 unless surface
exit -1 unless lines[1] =~ /viewBox="0 0 (\d+) (\d+)"/
viewx = $1.to_f
viewy = $2.to_f
exit -1 unless lines[surface+1] =~ /d="M (\d+) (\d+) L (\d+) (\d+) L (\d+) (\d+) /
offsetx = 0.1 * $1.to_f
offsety = 0.1 * $2.to_f
boundx = 0.1 * $5.to_f - offsetx
boundy = - 0.1 * $6.to_f + offsety
lines.delete_at(surface+1)
lines[surface] = "<g id=\"surface1\" transform=\"matrix(1,0,0,1,#{-offsetx},#{offsety-viewy})\">\n"
lines[1].sub!(/(?<=width=")\d+(?=pt")/, boundx.to_s)
lines[1].sub!(/(?<=height=")\d+(?=pt")/, boundy.to_s)
lines[1].sub!(/(?<=viewBox\="0 0 )\d+ \d+(?=")/, "#{boundx} #{boundy}")
##################################################### グリフ名を変える
svg = lines.join('')
key = Digest::MD5.hexdigest(svg)[0,6]
##################################################### 出力する
print svg.gsub(/\bglyph/, key)
処理内容は以下のようになっています:
- 標準入力へ渡された tchart のソースコードをテンポラリファイルに書き出し、 外部ツールを使って svg に変換する
- 作成された svg には A4 いっぱいの余白が付いてしまうので、 四角で囲まれた範囲外の余白を取り除く
- 1つの HTML に複数の SVG を貼るとき、グリフ名が重複すると結果がおかしくなってしまうため、 SVG 中のグリフ名を他と重ならないように付け替えます
pukiwiki.php の変更†
標準ではプラグインに渡す引数に改行文字を入れられないので、 tchart プラグインに限って改行入りの引数を渡せるように変更します。
具体的には、改行文字を <br> という文字に変更しておき、 プラグイン側で改行文字に復元します。
LANG:php
// $body = get_source($base);
$lines = get_source($base);
for($i=0;$i<count($lines);$i++)
if (preg_match('/\&tchart\(/', $lines[$i]) )
while(!preg_match('/\&tchart\(.*?\)\;/', $lines[$i]) && ($i+1<count($lines)))
array_splice($lines, $i, 2, chop($lines[$i]) . '<br>' . $lines[$i+1] );
$body = convert_html($lines);
plugin/tchart.inc.php†
- 引数を連結して1つの文字列にする
- <br> を改行に戻す
- キャッシュされたファイルが無いか探す
- 無ければソースを tchart-svg.rb へ渡して結果をキャッシュする
- あればキャッシュを読み出す
- そのまま SVG コードを返す
LANG:php(linenumber)
<?php
// 画像ファイルのキャッシュ場所
define('TCHART_DIR', 'tchart');
function tchart($source)
{
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("file", "/dev/null", "a") // stderr
);
$process = proc_open('/usr/local/bin/tchart-svg.rb', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], $source);
fclose($pipes[0]);
$result = stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
}
return $result;
}
function tchart_cached($source)
{
$result = '';
$image_base = sha1($source);
$image_dir = TCHART_DIR . "/{$image_base[0]}/{$image_base[1]}";
$image_svg = "{$image_dir}/{$image_base}.svg";
if(!file_exists($image_svg)) {
$svg = tchart($source);
if (mb_substr($svg[0],0,4)=="<svg") {
if(!file_exists($image_dir)){
mkdir($image_dir, 0770, true);
}
$fh = fopen($image_svg, "w");
fwrite($fh, join("", $svg));
fclose($fh);
}
} else {
$svg = file($image_svg);
}
return $svg;
}
function plugin_tchart_inline()
{
$args = func_get_args();
$source = join(",", $args);
$source = preg_replace('/<br>/', "\n", $source);
return tchart_cached($source);
}
?>
コメント・質問†
Counter: 4523 (from 2010/06/03),
today: 1,
yesterday: 2