当初の目標 EditToHeaderToFooter

  • 数式機能 ── 数式を簡単に扱える環境の構築 → Latex との連携
  • 字下げ書式 ── ウィキコードの可読性の向上

    目標 EditToHeaderToFooter

  • 工学数学サイト
    • Wiki システムの導入 ── 楽をするため。
      • 数式機能 ── 数式を扱うため → Latex。
      • コード機能 ── コードを扱うため → codehighlight。
      • 字下げ書式 ── ウィキコードの可読性向上のため。

やっちゃったこと EditToHeaderToFooter

改造 EditToHeaderToFooter

  • pukiwiki.ini.php の適当な場所にて、 #code(diff){{ + $indent_format = 0; }}
    • $indent_format は天突きと字下げを表すフラグ。
      • ##$indent_format = 0# ⇔ 天突き書式。(デフォルト)
      • ##$indent_format = 1# ⇔ 字下げ書式。
  • convert_html.phpfunction parse 入り口直後にて、 #code(diff){{ function parse(& $lines) { + global $indent_format; $this->last = & $this; $matches = array(); + $indent = ""; }}
    • $indent = ""; は字下げした深さを格納する変数。複数行プラグインで利用。
  • convert_html.phpfunction parsewhile (!empty($lines))直後にて、 #code(diff){{{{ while (! empty($lines)) { $line = array_shift($lines); + // Indent extension + if ($indent_format) + { + preg_match('/^(\s*)(.*)$/', $line, $matches); + $indent = strlen($matches[1]); + $line = $matches[2] ; + } + + if (rtrim($line) == '#indent') { + $indent_format = 1; + continue; + } + if (rtrim($line) == '#noindent') { + $indent_format = 0; + continue; + } // Escape comments if (substr($line, 0, 2) == '//') continue; }}}}
    • $line = array_shift($lines); でラインを切り出すため、この後に記述。
    • Escape commentsなど全てが字下げの影響を受けるため、これらの前に記述。
    • if (rtrim($line) == '#indent') {...} は字下げ書式へ切り替える疑似プラグイン
    • if (rtrim($line) == '#noindent') {...} は天突き書式へ切り替える疑似プラグイン

      課題 EditToHeaderToFooter

  • #indent」と「#noindent」による文脈の切り替えは良くない。
    • 互換性を捨て、字下げ書式に限定すべきか?

H整形の導入 EditToHeaderToFooter

方針 EditToHeaderToFooter

  • 字下げ書式では、行頭の空白は無視されるため、従来の空白整形済みテキストに代わる書式が必要。
  • 先頭が「^」で始まる「H整形/Hat整形」を導入。
    • 正規表現で行頭を示す「^」、また、Wikiで引用を表す「>」や「<」からの連想。
  • 以降では便宜上、空白で始まる整形済みテキストの書式を「S整形/Space整形」で区別。

仕様 EditToHeaderToFooter

  • 行頭が「^」の行は整形済み書式と見なす。基本仕様はS整形に同じ。
  • S整形とH整形の混用は無効。段が一旦切られる。
  • 天突き書式の場合、「^」の左に空白があるとSと見なされ、「^」が残る。

改造 EditToHeaderToFooter

  • convert_html.php で、S整形のクラス class Preコピー。場所は任意だが、流れ的にS整形の直後で、 #code(diff){{{{ ! // '^ 'Hat-beginning sentence ! // '^ 'Hat-beginning sentence ! // '^ 'Hat-beginning sentence ! class HPre extends Element // for Indent extension { ! function HPre(& $root, $text) { parent::Element(); $this->elements[] = htmlspecialchars(substr($text, 1)); }
function canContain(& $obj) { ! return is_a($obj, 'HPre'); }
function & insert(& $obj) { $this->elements[] = $obj->elements[0]; return $this; }
function toString() { return $this->wrap(join("\n", $this->elements), 'pre'); } } }}}}
  • 'HPre' はH整形の識別キーであり、クラス名と一致する必要がある。
  • 同ファイルの class Bodyfunction parsewhile (! empty($lines)) 中にて、Preに関する分岐をコピーし、 #code(diff){{{{ // Pre - if ($head == ' ' || $head == "\t") { + if (!$indent_format && ($head == ' ' || $head == "\t")) { $this->last = & $this->last->add(new Pre($this, $line)); continue; } + // HPre + if ($head == '^') { + $this->last = & $this->last->add(new HPre($this, $line)); + continue; + } }}}}
    • !$indent_format && は字下げ書式でS整形を無効化する条件。
    • if ($head == '^')はH整形への分岐。

P表の改造 EditToHeaderToFooter

方針 EditToHeaderToFooter

  • 便宜上、行頭が「|」の表を「P表/Pipe表」、行頭が「,」の表を「C表/Comma表」で区別する。
  • P表では、縦方向の関連性が強いので、空白による整列はコードの可読性向上に必要。
  • P表では、行方向のセル連結は右方向(>)でも良いが、左方向の連結も欲しい。
    • コードは左から右、上から下に向かって読むもの、列方向と同様、結合記号より先にセル内容を記述すべき。
  • ~」は列方向のセル連結と、ヘッダ(th)の2つの意味がある上、空白による整列を許すと構文が衝突する。
    • >」からの連想で、左向き連結に「<」を、上向き連結に「^」を導入。
    • 「*」などからの連想で、ヘッダに「^」を導入。

      仕様 EditToHeaderToFooter

  • セル内の先頭と末尾の空白を無視。
  • >」のみのセルは右のセルに連結
  • ^」のみのセルは上のセルに連結
  • <」のみのセルは左のセルに連結(互換仕様)
  • ~」のみのセルは上のセルに連結(互換仕様)
  • *」で始まるセルはヘッダ(th)。
  • ~」で始まる非空白セルはヘッダ(th)。(互換仕様)

改造 EditToHeaderToFooter

  • convert_html.phpclass TableCell にて、 #code(diff){{{{ class TableCell extends Element { var $tag = 'td'; // {td|th} + var $colleft = 0; // ==0: colspan to right by '>'; ==1: colspan to left by '<'; var $colspan = 1; var $rowspan = 1; var $style; // is array('width'=>, 'align'=>...); }}}}
    • $colleftは行結合方向を表す変数。
      • ##$colleft = 0# ⇔ 左に連結。
      • ##$colleft = 1# ⇔ 右に連結。
  • 同クラスのfunction TableCell にて、 #code(diff){{{{ function TableCell($text, $is_template = FALSE) { parent::Element(); $this->style = $matches = array(); + $text = trim($text); }}}}
    • trim でセル先頭と末尾の空白を除去。
  • 同関数 セル結合の分岐にて、 #code(diff){{{{ if ($text == '>') { + $this->colleft = 0; $this->colspan = 0; + } else if ($text == '<') { + $this->colleft = 1; + $this->colspan = 0; - } else if ($text == '~') { + } else if ($text == '^' || $text == '~') { $this->rowspan = 0; - } else if (substr($text, 0, 1) == '~') { + } else if (substr($text, 0, 1) == '*' || substr($text, 0, 1) == '~') { $this->tag = 'th'; $text = substr($text, 1); } }}}}
    • $this->colleft の追加は連結向きの指定
    • if ($text == '<') は左向き連結。
    • $text == '^' || は上向き連結。
    • substr($text, 0, 1) == '*' || はヘッダ。
  • 同関数 セル結合ループにて、右向き連結のオリジナルに対して変更し、さらに左向き連結用にコピー #code(diff){{{{ ! // Set colspan and style, from right to left. + $stylerow = NULL; + foreach (array_keys($this->elements) as $nrow) { + $row = & $this->elements[$nrow]; + if ($this->types[$nrow] == 'c') + $stylerow = & $row; + $colspan = 1; ! foreach (array_reverse(array_keys($row)) as $ncol) { ! if ($row[$ncol]->colspan == 0 && $row[$ncol]->colleft == 1) { + ++$colspan; + continue; + } + if ($row[$ncol]->colspan > 0 && $row[$ncol]->$colspan < $colspan) { + $row[$ncol]->colspan = $colspan; + } + if ($stylerow !== NULL) { + $row[$ncol]->setStyle($stylerow[$ncol]->style); + // Inherits column style + while (--$colspan) ! $row[$ncol + $colspan]->setStyle($stylerow[$ncol]->style); + } + $colspan = 1; + } + } - // Set colspan and style + // Set colspan and style, from left to right. $stylerow = NULL; foreach (array_keys($this->elements) as $nrow) { $row = & $this->elements[$nrow]; if ($this->types[$nrow] == 'c') $stylerow = & $row; $colspan = 1; foreach (array_keys($row) as $ncol) { - if ($row[$ncol]->colspan == 0) { + if ($row[$ncol]->colspan == 0 && $row[$ncol]->colleft == 0) { ++$colspan; continue; } + if ($row[$ncol]->colspan > 0 && $row[$ncol]->colspan < $colspan) { $row[$ncol]->colspan = $colspan; + } if ($stylerow !== NULL) { $row[$ncol]->setStyle($stylerow[$ncol]->style); // Inherits column style while (--$colspan) $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style); } $colspan = 1; } } }}}}
    • 見やすくするため、コピーの差分を「!」で示した。
    • 内側ループ foreach (array_reverse(array_keys($row)) as $ncol)array_reverse でスキャン向きを反転する仕組み。
    • $row[$ncol + $colspan]->setStyle($stylerow[$ncol]->style); の符号 + は連結方向の影響。

リスト系書式のスキーン変更 EditToHeaderToFooter

方針 EditToHeaderToFooter

  • リストの左マージンや階層間間隔はSkinとして指定すべき。
  • タグ中にstyle属性を直に指定されると、外部CSSが優先順位で負けてしまうので都合が悪い。

改造 EditToHeaderToFooter

  • convert_html.phpclass ListContainer にて、 #code(diff){{{{ class ListContainer extends Element { var $tag; var $tag2; var $level; - var $style; - var $margin; - var $left_margin; }}}}
  • 同クラスの function ListContainer にて、 #code(diff){{{{ function ListContainer($tag, $tag2, $head, $text) { parent::Element(); - $var_margin = '_' . $tag . '_margin'; - $var_left_margin = '_' . $tag . '_left_margin'; - global $$ var_margin, $$var_left_margin; - - $this->margin = $$var_margin; - $this->left_margin = $$var_left_margin; }}}}
  • 同クラスの function setParent にて、 #code(diff){{{{ function setParent(& $parent) { global $_list_pad_str; parent::setParent($parent); $step = $this->level; if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) $step -= $parent->parent->level; $margin = $this->margin * $step; if ($step == $this->level) $margin += $this->left_margin; - - $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin); } }}}}
  • 同クラスの function toString にて、 #code(diff){{{{ function toString() { - return $this->wrap(parent::toString(), $this->tag, $this->style); + return $this->wrap(parent::toString(), $this->tag, ' class="list'.$this->level.'"'); } }}}}
    • 階層毎に異なるスタイルを指定する場合は、従来のまま .list1.list2.list3 を利用。
    • 全階層に共通のスタイルを指定する場合は、タグ名を利用すれば良い。そのスタイルを上書きするスタイルは削除済み。
  • default.ini.php にて、 #code(diff){{{{ -///////////////////////////////////////////////// -// リスト構造の左マージン -$_ul_left_margin = 0; // リストと画面左端との間隔(px) -$_ul_margin = 16; // リストの階層間の間隔(px) -$_ol_left_margin = 0; // リストと画面左端との間隔(px) -$_ol_margin = 16; // リストの階層間の間隔(px) -$_dl_left_margin = 0; // リストと画面左端との間隔(px) -$_dl_margin = 16; // リストの階層間の間隔(px) -$_list_pad_str = ' class="list%d" style="padding-left:%dpx;margin-left:%dpx"'; }}}}

数式プラグイン eq の追加 EditToHeaderToFooter

方針 EditToHeaderToFooter

  • 基本仕様はwikihouse.commath2.inc.phpに従う。
    • ただし、ハッシュの衝突が心配のため、数式の同定は生のTeXコードで比較。
    • プラグインの同時呼出しで作業ファイルが衝突するため、全てハッシュ名を用いる。
    • LaTeX、dvipngにより数式画像生成後に、Imagemagicのconvertで画像処理を施す。
  • 数式の質は、それなりに拘る。少なくとも処理速度などよりは優先。

仕様 EditToHeaderToFooter

  • パスを通した platex、dvipngとconvertが必要。
  • 引数をそのままLaTeXに渡し、最終的にpng画像に変換して出力。
    • ブロックプラグインの場合は、align環境(\begin(align) 〜 \end(align))に渡される。
    • インラインプラグインの場合は、ディスプレイ数式モードに変更されるテキスト数式モード($ \displaystyle 〜 $)に渡される。
  • 全ファイルはファイル名は数式に基づくハッシュキーを使用。
  • pukiwiki://eq/{ハッシュキー}.eq に生のTeX式を保存し、数式の同定に使用。ハッシュキーを同定には使わない。
  • pukiwiki://eq/{ハッシュキー}.png に数式画像を出力。
    • ブラウザのズーム対策として、画像を高解像度で出力し、IMGタグのwidthheightで縮小表示。
  • インライン数式の垂直揃えは中心揃え(vertical-align: middle;)。分数線が画像中心に来るようにTeX側で調節する。
    • dviには位置決めのために数式に枠を付けて出力。convertにより枠を除去。
  • 数式がコンパイルでき無い場合、TeXのエラーを出力。

改造 EditToHeaderToFooter

  • pukiwiki://plugin/eq.inc.php を作成して、 #code(php){{{{ <?php function plugin_eq_convert() { $arg = rtrim(join(func_get_args(), ','), ','); // 末尾の「,」は波引数が空の場合に現れ、不要物。 return '<div class="eq">'.plugin_eq_core($arg, 'convert').'</div>'; } function plugin_eq_inline() { $arg = rtrim(join(func_get_args(), ','), ','); // 末尾の「,」は波引数が空の場合に現れ、不要物。 return '<span class="eq">'.plugin_eq_core($arg, 'inline').'</span>'; } function plugin_eq_core($arg, $mode) { $eq_dir = './eq/'; // 数式と数式画像のパス $err_msg_end_phrase = 'No pages of output.'.PHP_EOL; // エラー終了を示す文字列(platexの出力依存) $prefix_convert = 'eq-nc-'; //  ブロック用ファイル衝突を避けるための接頭語 $prefix_inline = 'eq-ni-'; // インライン用ファイル衝突を避けるための接頭語 $tex_frame_convert = 'convert.tex'; //  ブロック用フレームファイル名 $tex_frame_inline = 'inline.tex' ; // インライン用フレームファイル名 $eq_replace_phrase = '\input{eq}' ; // フレームファイルで数式を埋め込む部を示す文字列 $key = ($mode=='inline') ? $prefix_inline.md5($arg) : $prefix_convert.md5($arg); $eq = $key.'.eq'; // 照合用引数出力(保存ファイル) $img = $key.'.png'; // 数式の画像出力(保存ファイル) $old_dir = getcwd(); chdir($eq_dir); if (!file_exists($img) || $arg != file_get_contents($eq)) { // 一時ファイル $tex = $key.'.tex'; $std = $key.'.std.log'; // Tex の標準出力、エラー出力用 $dvi = $key.'.dvi'; $aux = $key.'.aux'; $log = $key.'.log'; $box = $key.'.box.png'; $face = $key.'.face.png'; $edge = $key.'.edge.png'; $bold = $key.'.bold.png'; flush(); // Timeout 対策 $tex_code = file_get_contents(($mode=='inline') ? $tex_frame_inline : $tex_frame_convert); $tex_code = str_replace($eq_replace_phrase, $arg, $tex_code); file_put_contents($eq ,$arg ); file_put_contents($tex ,$tex_code); exec('platex -halt-on-error '.$tex.' > '.$std); flush(); // Timeout 対策 if (!file_exists($dvi)) // Tex Error { $err_flag = false; $err_code = "Tex Error:\n\n"; if ($log_code = fopen($std, 'r')) { while (!feof($log_code)) { $tex_msg = fgets($log_code); if ($tex_msg[0] == '!' ) {$err_flag = true ;} else if ($tex_msg == $err_msg_end_phrase) {$err_flag = false;} if ($err_flag) {$err_code .= $tex_msg;} } fclose($log_code); } chdir($old_dir); return '<pre class=eq_error_title>'.$arg.'</pre><pre class=eq_error_message>'.$err_code.'</pre>'; } exec('dvipng -D 720 -T tight --gamma 2.5 -o '.$box.' '.$dvi); if ($mode=='inline') { list($width, $height) = getimagesize($box); exec('convert '.$box.' -crop '.($width-16).'x'.($height-16).'+8+8 -bordercolor #FFFFFF -threshold 50% '.$face); } else { exec('convert '.$box.' -bordercolor #FFFFFF -border 16x16 -threshold 50% '.$face); } exec('convert '.$face.' -edge 1 -negate '.$edge); exec('composite -compose multiply '.$face.' '.$edge.' '.$bold); exec('convert '.$bold.' -black-threshold 90% -antialias -blur 1 - | convert - -transparent #FFFFFF -antialias '.$img); flush(); // Timeout 対策 unlink($tex ); unlink($std ); unlink($dvi ); unlink($aux ); unlink($log ); unlink($box ); unlink($face); unlink($edge); unlink($bold); } list($width, $height) = getimagesize($img); chdir($old_dir); return '<img widht='.round($width/7).' height='.round($height/7).' src="'.$eq_dir.$img.'" alt="eq: '.$arg.'" title="'.$arg.'">'; } ?> }}}}
    • 17行目は数式のパスを指定。数式のキャッシュとみなせるため、cache/と同じレベルにeq/を作った。
    • 26行目は数式のハッシュキー。プラグインの種別と数式のmd5ハッシュに依存。
    • 34〜98行目は変換処理。数式が変換済みの場合は分岐が成立せず、処理が省かれる。
    • 47行目、55行目と88行目の flash(); はタイムアウト対策。これらが無いと、大量の数式を変換するとPHPが落ちる。
    • 48、49行目はTeXコードの雛形の読み込みと数式の組み込み(=置換)。52行目で数式のTeXファイルを出力。
    • 56〜76行目はTeXがエラーのときの処理。エラーの場合、ログを解析し、エラーの部分をだけ出力。
      • 67行目と68行目はそれぞれエラー部の開始と終了の検出。これが環境依存のため、変更が必要かもしれない。
    • 77〜86行目は画像処理。インラインプラグインでは位置決め枠の除去のため、ブロックプラグインより処理が多い。
    • 99行目、102行目は数式画像のサイズを取得し、表示すべきサイズを算出し、出力している。
      • 数式画像の表示サイズは、77行目のdvipngの解像度 -D 720 と102行目の $width/7$height/7 の分母で調節可能。
  • pukiwiki://eq/convert.tex を作成して、 #code(tex){{{{ \documentclass[12pt]{jarticle} \usepackage{amsmath,amssymb,bm} \usepackage{pukiwiki} \pagestyle{empty} \begin{document} \thispagestyle{empty} \begin{align*} \input{eq} \end{align*} \end{document} }}}}
  • -

    ----

  • 複数行eq書式

EditToHeaderToFooter

  • -
  • 複数行インラインプラグイン
    • convert_html / function & Factory_span L164 L246 L466 L889 L939 L965 L1041 L1096 L1166
  • エスケープシーケンス
    • make_link L59 L285
  • Code 行番号ズレ
    • codehighlight L83
  • インラインcode
    • code.inc.php L71 L101
  • Code pukiwiki indent 対応
    • codehighlight L169 L208
    • code/line.pukiwiki.php
  • C 拡張
    • code/keyword.c.php
  • diff拡張
    • code/line.diff.php
  • パンミミ
    • topicpath.inc.php L32
  • ページの先頭と末尾へのリンクの移動
    • default.ini.php / 特殊シンボル
    • edit.inc.php L98 L121
  • フッとノートスキーン変更
    • html.php L128

これからやること EditToHeaderToFooter

  • 編集画面のスキーン変更
    • 編集関連ボタンの上下配置
    • テキストエリアの寸法の相対指定

タッチしたファイル EditToHeaderToFooter

つづく
fileline.pukiwiki.php 460件 [詳細] fileline.diff.php 449件 [詳細] fileinline.tex 439件 [詳細] fileconvert_html.php 472件 [詳細] fileeq.inc.php 508件 [詳細] fileconvert.tex 423件 [詳細] filecodehighlight.php 438件 [詳細] filecode.inc.php 476件 [詳細]
    初基 一覧 検索 最新 バックアップ リンク元   ヘルプ   最終更新のRSS