ソフトウェア/pukiwiki/tchart.inc.php

(916d) 更新

公開メモ

pukiwiki でタイミング図を書きたい

デジタル信号処理の記事を書くのに、

こういったタイミング図を書きたくて、 プラグインを作りました。

tchart

もともと、非常に便利なツールを熊谷正朗さんが公開しておられました。

このツールを使うと、

clock	_~_~_~_~_~_~_~_~_~_~_
data	=====X=DATA========X=====
valid	_____~~~~~~~~~~______
ready	_____________~~______

こんな風にテキストで書いたタイミング図を、

このような画像にできます。

熊谷さんのツールはポストスクリプトや gif になるのですが、 きれいにホームページ上に表示するために、 似たような文法で書かれたチャートを svg として出力するツールを javascript で自作しました。

プラグインからは javascript を呼び出しているだけなので、 サーバーへの負担はほとんどありません。

こちらで紹介しています: ソフトウェア/タイミングチャート清書サービス

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 という関数を定義する javascript コードを出力する
  • ソースを javascript の関数 tchart へ渡して表示する
LANG:php(linenumber)
<?php

$plugin_tchart_initialized = false;

function plugin_tchart_inline()
{
    $tchart_definition = <<<'EOS'
<script>(function(){var t,s,i,n={}.hasOwnProperty;t=function(){function t(t){this.segments=[],this.style=t}return t.prototype.draw=function(t,s,i,n){return this.segments.push([t,s,i,n])},t.prototype.svg=function(){var t,s,i,n,e,h,r,o,g,c,a,l,f,u,p,y,x,d,_,w,m,v,L,H,S,b,k;for(d=this.segments,i=0,e=d.length;e>i;i++)S=d[i],S[4]=0,S[5]=null;for(_=this.segments,n=0,h=_.length;h>n;n++)for(b=_[n],w=this.segments,l=0,r=w.length;r>l;l++)k=w[l],b[2]===k[0]&&b[3]===k[1]&&(b[4]+=1,k[4]+=1);for(m=this.segments,f=0,o=m.length;o>f;f++){for(b=m[f],v=this.segments,u=0,g=v.length;g>u;u++)if(k=v[u],!(k[4]<2||b[2]!==k[0]||b[3]!==k[1])){b[5]=k,k[4]=-1;break}if(!b[5])for(L=this.segments,y=0,c=L.length;c>y;y++)if(k=L[y],1===k[4]&&b[2]===k[0]&&b[3]===k[1]){b[5]=k,k[4]=-1;break}}for(p=[],H=this.segments,x=0,a=H.length;a>x;x++)if(b=H[x],!(b[4]<0))for(S=b,t=S[0],s=S[1],p.push("M"+t+","+s);S;)t===S[2]&&s===S[3]||(t===S[2]?"V"===p[p.length-1][0]?p[p.length-1]="V"+S[3]:p.push("V"+S[3]):s===S[3]?"H"===p[p.length-1][0]?p[p.length-1]="H"+S[2]:p.push("H"+S[2]):p.push("L"+S[2]+","+S[3])),t=S[2],s=S[3],S=S[5];return"<path "+this.style+' d="'+p.join("")+'" />\n'},t}(),s=function(){function s(s,i){this.config=s,this.x=s.w_caption,this.y=i,this.path=new t(s.signal_style),this.current=0,this.crosses=[],this.strings=[],this.grids=[],this.highlights=[]}return s.transitions=["          ","  - 1 4 14","  2 ~ / ~/","  3 ` _ _`","  23`~/_= ","  23`~/_=/","  23`~/_=`","  23`~/_X ","  23`~/_=X"],s.transitionLines={" ":[],"~":[[1,1]],_:[[0,0]],"=":[[1,1],[0,0]],X:[[1,0],[0,1]],"`":[[1,0]],"/":[[0,1]],1:[[1,.5]],2:[[.5,1]],3:[[.5,0]],4:[[0,.5]],"-":[[.5,.5]]},s.stateLines=[[],[.5],[1],[0],[0,1]],s.codes=":-~_=/\\X*",s.prototype.ys=function(t){return this.y+(1-t)*this.config.h_line},s.prototype.y0=function(){return this.y+this.config.h_line},s.prototype.y1=function(){return this.y},s.prototype.yz=function(){return this.y+this.config.h_line/2},s.prototype.xh=function(){return this.x+this.config.w_transient/2},s.prototype.xt=function(){return this.x+this.config.w_transient},s.prototype.xr=function(){return this.x+this.config.w_transient+this.config.w_hold},s.prototype.parse=function(t){for(var s,i;""!==t;)(s=/^\s+/.exec(t))||((s=/^\|/.exec(t))?this.grids.push([this.xh(),this.config.grid_style]):(s=/^\[/.exec(t))?(0===this.highlights.length||Array.isArray(this.highlights[this.highlights.length-1]))&&this.highlights.push(this.xh()):(s=/^\]/.exec(t))?this.highlights.length>0&&!Array.isArray(this.highlights[this.highlights.length-1])&&(this.highlights[this.highlights.length-1]=[this.highlights[this.highlights.length-1],this.xh(),this.config.highlight_style]):(i=/^([:\-~_=\/\\X*])/.exec(t))?this.addState(i[1]):(i=/^"(([^"]|"")*)"/.exec(t))?this.addString(i[1].replace(/""/g,'"').replace(/[ ]/g,"&nbsp;")):(i=/([^:\-~_=\/\\X*\|\]\[]+)/.exec(t))&&this.addString(i[1])),t=t.substr(i[0].length,t.length-i[0].length);return this.processStrings()+this.path.svg()},s.prototype.addState=function(t){var i,n;return n=s.codes.indexOf(t),i=this.drawTransition(n),n>4&&(n=4),this.drawState(n),""!==i&&this.crosses.push([this.x,i]),(0===this.current&&0!==n||0!==this.current&&0===n||0===this.crosses.length&&0===n)&&this.crosses.push([this.x,"|"]),this.current=n,this.x=this.xr()},s.prototype.addString=function(t){return this.strings.push([this.crosses.length,t.replace(/^\s+|\s+$/g,"")])},s.prototype.drawTransition=function(t){var i,n,e,h,r;for(i="",r=s.transitions[t].substr(2*this.current,2),n=e=0,h=r.length-1;h>=0?h>=e:e>=h;n=h>=0?++e:--e)i+=this.drawTransitionSub(r[n]);return i},s.prototype.drawTransitionSub=function(t){var i,n,e,h,r;for(i="",r=s.transitionLines[t],n=0,e=r.length;e>n;n++)h=r[n],this.path.draw(this.x,this.ys(h[0]),this.xt(),this.ys(h[1])),h[0]!==h[1]&&(i+=t);return i},s.prototype.drawState=function(t){var i,n,e,h,r;for(h=s.stateLines[t],r=[],i=0,n=h.length;n>i;i++)e=h[i],r.push(this.path.draw(this.xt(),this.ys(e),this.xr(),this.ys(e)));return r},s.prototype.processStrings=function(){var t,s,i,n,e,h,r,o,g,c,a,l,f,u,p,y,x,d,_,w;for(o=[],this.crosses.push([this.x,"|"]),e=this.strings,s=0,i=e.length;i>s;s++)r=e[s],d=this.ys(0),_=this.ys(1),w=this.ys(.5),c=this.crosses[r[0]-1][0],f=c+this.config.w_transient,a=c+this.config.w_transient/2,l=f+this.config.w_hold,u=this.crosses[r[0]][0],x=u+this.config.w_transient,p=u+this.config.w_transient/2,y=x+this.config.w_hold,"?"===r[1]&&(n=["M"+f+","+_+"H"+u],n.push(function(){switch(this.crosses[r[0]][1]){case"|":return"";case"XX":return"L"+p+","+w;case"/":return"H"+x;case"`":return"L"+x+","+d;case"23":return"H"+x+"L"+u+","+w+"L"+x+","+d;case"14":return"L"+x+","+w;case"1":return"L"+x+","+w+"V"+d;case"2":return"H"+x+"L"+u+","+w;case"3":return"V"+w+"L"+x+","+d;case"4":return"H"+x+"V"+w}}.call(this)),n.push("L"+u+","+d+"H"+f),n.push(function(){switch(this.crosses[r[0]-1][1]){case"|":return"";case"XX":return"L"+a+","+w;case"/":return"H"+c;case"`":return"L"+c+","+_;case"23":return"L"+c+","+w;case"14":return"H"+c+"L"+f+","+w+"L"+c+","+_;case"1":return"V"+w+"L"+c+","+_;case"2":return"H"+c+"V"+w;case"3":return"L"+c+","+w+"V{y1}";case"4":return"H"+c+"L"+xt+","+w}}.call(this)),n.push("Z"),o.push('\n<path stroke="none" d="'+n.join("")+'" '+this.config.notcare_style+"/>")),h=r[1].replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),"_<_"===r[1].substr(0,3)?(g=f,t="start",h=h.substring(6)):"_>_"===r[1].substr(0,3)?(g=u,t="end",h=h.substring(6)):(g=(a+p)/2,t="middle"),o.push('<text x="'+g+'" y="'+(this.ys(0)-1.5)+'" text-anchor="'+t+'" '+('font-size="'+this.config.h_line+'" '+this.config.signal_font+">"+h+"</text>\n"));return o.join("")},s}(),i=function(){function t(s){null==s&&(s={}),this.config={},this.setConfig(t.config),this.setConfig(s)}return t.config={scale:1,margin:10,w_caption:40,w_hold:10,w_transient:2,h_line:10,h_space:10,signal_style:'stroke-linecap="round" stroke-width="0.6" stroke="black" fill="none"',grid_style:'stroke-linecap="round" stroke-width="0.6" stroke="red" fill="none"',highlight_style:'stroke="none" fill="#ff8"',notcare_style:'fill="#ccc"',rotate:0,caption_font:'fill="black" font-family="Helvetica"',signal_font:'fill="black" font-family="Helvetica"'},t.prototype.setConfig=function(t){var s,i,e;i=[];for(s in t)n.call(t,s)&&(e=t[s],i.push(this.config[s]=e));return i},t.prototype.parse=function(t){var s,i,n,e;for(this.svg=[],this.grids=[],this.highlights=[],this.y=-1,this.x_max=this.config.w_caption,t=t.replace(/^\n+/,""),t=t.replace(/\n+$/,""),e=t.split("\n"),s=0,i=e.length;i>s;s++)n=e[s],this.parseLine(n);return this.processGrids(),this.processHighlights(),this.formatSVG(t)},t.prototype.formatSVG=function(t){var s,i,n;return i=this.config.margin,n=(this.x_max+2*i)*this.config.scale*1.3,s=(this.y+2*i)*this.config.scale*1.3,this.width=n,this.height=s,'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" \n  width="'+n+'px" height="'+s+'px" viewBox="'+-i+" "+-i+" "+(this.x_max+2*i)+" "+(this.y+2*i)+'" version="1.1">\n<![CDATA[\n'+t.replace(/\]\]\>/g,"]]&gt;")+"\n]]>\n<g>\n"+this.svg.join("\n")+"\n</g>\n</svg>"},t.prototype.parseLine=function(t){var s;if("#"!==t[0]){if("@"===t[0]){if(!(s=/^@([^\s]+)[\s]+([^\s].*)$/.exec(t)))throw new SyntaxError("Illegal Line: "+t);return void($.isNumeric(this.config[s[1]])?this.config[s[1]]=Number(s[2]):this.config[s[1]]=s[2])}if("%"===t[0]){if(!(s=/^%(-?[\d\.]+)\s+(-?[\d\.]+)\s+?(.*)$/.exec(t)))throw new SyntaxError("Illegal Line: "+t);return void this.svg.push('<text x="'+s[1]+'" y="'+s[2]+'" text-anchor="middle" '+('font-size="'+this.config.h_line+'" '+this.config.signal_font+">"+s[3]+"</text>"))}if(this.y<0?this.y=0:this.y+=this.config.h_space,t=t.replace(/\s*$/,""),""!==t){if(!(s=/^([^\s]+)[\s]+([^\s].*)$/.exec(t)))throw new SyntaxError("Illegal Line: "+t);return this.formatCaption(s[1]),this.formatTimeline(s[2]),this.y+=this.config.h_line}}},t.prototype.formatCaption=function(t){var s;return s=t.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),this.svg.push('<text x="'+(this.config.w_caption-5)+'" y="'+(this.y+this.config.h_line-1.5)+'" text-anchor="end" '+('font-size="'+this.config.h_line+'" '+this.config.caption_font+">"+s+"</text>"))},t.prototype.formatTimeline=function(t){var i,n,e,h,r,o,g,c,a,l;for(l=new s(this.config,this.y),this.svg.push(l.parse(t)),l.x>this.x_max&&(this.x_max=l.x),g=l.grids,e=0,r=g.length;r>e;e++)i=g[e],this.grids.push(i);for(c=l.highlights,a=[],h=0,o=c.length;o>h;h++)n=c[h],a.push(this.highlights.push(n));return a},t.prototype.processGrids=function(){var t,s,i,n,e,h,r;for(r=-this.config.margin/2,t=this.y+this.config.margin/2,e=this.grids,h=[],i=0,n=e.length;n>i;i++)s=e[i],h.push(this.svg.push('<path d="M'+s[0]+","+r+"V"+t+'" '+s[1]+" />"));return h},t.prototype.processHighlights=function(){var t,s,i,n,e,h,r;for(r=-this.config.margin/2,t=this.y+this.config.margin/2,e=this.highlights,h=[],i=0,n=e.length;n>i;i++)s=e[i],Array.isArray(s)?h.push(this.svg.unshift('<path d="M'+s[0]+","+r+"V"+t+"H"+s[1]+"V"+r+'Z" '+s[2]+" />")):h.push(void 0);return h},t}(),("undefined"!=typeof exports&&null!==exports?exports:this).tchart=function(t){var s;return s=new i,document.write(s.parse(t))}}).call(this);</script>
EOS;

    $result = '';

    global $plugin_tchart_initialized;
    if(!$plugin_tchart_initialized) {
      $result .= $tchart_definition;
      $plugin_tchart_initialized = true;
    }

    $args = func_get_args();
    $source = join(",", $args);
    $source = preg_replace("/,$/", "", $source);
    $source = str_replace(['\\', '<br>', '"'], ['\\\\', "\\n\\\n", '\\"'], $source);
    $result .= <<<"EOS"
<script>tchart("\
#{$source}");</script>
EOS;

    return $result;
}

?>

コメント・質問





Counter: 975 (from 2010/06/03), today: 1, yesterday: 0