sankantsuのブログ

技術メモ・競プロなど

SATySFi でお絵描きライブラリをつくる構想

モチベーション

スライドなどで使うような、簡単な図形を組み合せた説明図を手軽に描きたい。 Drawio のような GUI で作図するツールもあるが、テキストベースで描けると再利用しやすく管理上も便利であったりする。

TeX には PGF/Tikz という図を描くためのライブラリがある。 Tikz はかなりつくりこまれていて、いろいろな図が描けるしドキュメントもかなり徹底して書かれている。 しかし、Tikz の中身は TeX 芸の結晶であり、その副作用として少しでも書き方を誤るとたちまち解釈不能なエラーメッセージに悩まされることになる。

そこで、型検査のできる SATySFi で Tikz ライクなお絵描きライブラリをつくりたい。 SATySFi も pdf の描画命令を直接埋めこむようなプリミティブを備えているから、原理的には Tikz などと同じようにお絵描きをつくることができるはずである (一部サポートしていない pdf のプリミティブはありそうだが)。

「Tikz ライク」ってなんだ

上で「Tikz ライク」に図を描きたいといったが、これを具体化して考えたい。 考えた範囲で次のようなものが思いついた。

  • node というオブジェクト
  • style による描画方法のカスタマイズ
  • 交点計算・接線計算などの幾何計算
  • パスの変形
  • let 等による変数定義や pgffor による繰り返しなどによる構造化

node というオブジェクト

Tikz では、

  • 中身のテキスト
  • 枠線

をまとめて扱う node というオブジェクトを基本単位にして描画を行うことが多い。 基本的に図の中にあらわれるオブジェクトは node としてつくり、node どうしを相対的に配置したり枠線の形・大きさを変えたりすることで様々な図をつくることができる。

また、node 間を edge で結ぶ際には自動的に edge と node 自身の枠線が被らないように、edge のうち枠線の内側に入る部分を削って短くする処理が自動的に行われる。 この処理によって、エッジがノードの中に食いこむのではなく、枠線に触れるような形で edge を描くことができる。 地味な部分ではあるが、手軽に図を描くためには必須であると感じる。

node のようなオブジェクトベースでお絵描きを行える SATySFi のライブラリは自分の知る限りではない。

style による描画方法のカスタマイズ

Tikz では、線の太さや色・枠線の形などを style として指定することで描画されたときの見た目を変化させる機構が備わっている。 デフォルトで豊富な種類の設定項目が用意されているほか、これらを組み合わせて style を定義することもできて、この仕組みがある。 これらの仕組みによって同じようなオブジェクトに統一した style を適用したり、style の一部分の値を変更して簡単に見た目を調節したりすることができる。

Tikz の style のような方法で見た目の調節ができるような SATySFi のお絵描きライブラリは自分の知る限りではない。

交点計算・接線計算

Tikz では intersections というライブラリを使って交点計算をしたり、円の接線を簡単に描画したりできる機能がある。特に交点の取得は複雑な図を描く上でかなり重要な機能といえる。

交点の計算に関しては、yasuo-ozu さんによる xpath という実装がある。 xpath は交点の計算の他にもパスの長さの取得やパスの途中分割など便利な機能を備えている。

パスの変形

Tikz では decoration という機能によって、パスの変形を行うことができる。 例えば、例えば直線や円をつくってからギザギザの線に変更したりするといったことが可能で、これによって複雑な線や図形の描画が可能になる。

SATySFi では xpath で数式的な変換や法線方向に曲線全体をずらすような移動が xpath で実装されている。 また、複雑な線を描くライブラリとしては monaqa さんによる railway というライブラリもある。 こちらは部分的なパスをつないだり繰り返したりするのを描きやすくするほか、パス全体のスケール変換などが行えるようである。

変数定義、繰り返しなどによる構造化

Tikz では TeX のマクロを駆使して独自に let 文のような変数定義構文を用意しているほか、付属の pgffor ライブラリによる for 文が書けたりするなど汎用のプログラミング言語にみられるような構造化のための機構が用意されている。 これらは、"TeX の上で使える" という点においては特殊であるが、SATySFi であればそもそも言語として一般的な関数型言語相当の構文が使えるので、これらの構文を自分でつくる必要はない。 むしろ、この点においては言語組み込みの豊富な機能が使いやすい SATySFi のほうが Tikz よりだいぶ書き味はよくなるはずである。

目標

これまで見てきた Tikz の特徴のうち特に現状の SATySFi のエコシステムに足りていないのは、node というオブジェクトベースでのお絵描きや、組み合わせ可能な style による見た目の調整のための機構ではないかと思う。 最終的にはこれらを使いやすい形で提供するようなライブラリをつくりたい。 今回はまだかなり荒削りであるが、部分的にこれを実現するような構成を模索してみた。

現状 SATySFi 上でもっとも機能が充実したパス操作ライブラリは xpath であると思われるので、これをベースに実装する。

現状

https://gist.github.com/sankantsu/3ca29d9daa18ee6c4818c2f3285e4622

今日急いで書いたのでまだライブラリの形にすらなっていないが、上の gist に現状動作する実装がある。 組版した結果は次のようになる。

大枠のアイデア

現状の型宣言を抜き出したものを以下にのせる。 node は、

  • 参照点 (position)
  • 枠線を書く関数 (border)
  • テキストなどの中身 (content)

描画する際の線の太さ・色などのスタイルは context を通して渡されるものとする。 node 単体へのローカルなスタイルの変更は context を変換する関数などをもたせることによって実現できそうである (未実装)。

edgenode 間を結ぶ線を表す。

picture は複数の nodeedge をまとめたひとつの"絵"である。

% type declarations
type context = (|
  line-width : length;
  draw-color : color;
  inner-sep : length;
  precision : length; % precision for calculating intersections
|)

type node = Node of (|
  position : point;
  border : context -> point -> XPath.t;
  content: graphics;
  % style: ctx -> ctx
|)

type edge = Edge of (|
  from: node;
  to: node;
  shorten: point * point;
  % style: ctx -> ctx
|)

type picture = (|
  nodes: node list;
  edges: edge list;
  context: context;
|)

インターフェースの概観

\picture というインラインコマンドで picture を inline graphics に変換する。 使用例を以下に示す。

まず、start-picture ctx に適当な描画コンテキストを渡して初期化する。 ここに make-text-node text-ctxt point it などでつくった nodemake-edge from to などでつくった edge を追加していく。 ここで、text-ctx というのは SATySFi の通常の意味でのテキスト処理文脈である。

\picture(
    let text-ctx = get-initial-context 100000cm (command \math) in
    let node1 = make-text-node text-ctx (0pt, 0pt) {This is a test} in
    let node2 = make-text-node text-ctx (100pt, 30pt) {fgh} in
    let edge1 = make-edge node1 node2 in
    start-picture default-context
    |> add-node node1
    |> add-node node2
    |> add-edge edge1
);

今後

見ての通りこの構想はまだまだ未完成なところが多い。 以下にいくつか今後の課題を挙げる。

  • node の anchor (north, east など)
  • style の適用方法の整理
  • edge の先端を矢印にできるようにする
  • 円などを用いた場合に交点計算の結果がおかしい

node の形を円にしたときの描画結果が以下。 明らかに交点ではない場所に交点が計算されてしまって edge の長さがおかしくなっているように見える。 これに関しては xpath の交点計算部分のデバッグが必要そうな気がしている。

また、SATySFi 自体の部分として、図の中にテキストを埋め込みたい場合に本文と同じテキスト処理文脈をもってきたりできないかというのを思った。 現状、get-initial-context で新しい context をつくっているので本文と全然違うスタイルになってしまう可能性がある。

最後に、もしこんなのがあったら面白いとか、こういうインターフェースにしたほうが良いとか、気軽に意見やコメントをもらえたら嬉しいです。