December 24, 2021

Markdown文書にTeX2imgのコードを埋め込んでVimから呼び出せるようにした

ダークパワーを得た

はじめに

この記事はQiitaの「Vim Advent Calendar 2021 その2」24日目の記事です.

先日,『OpenCV Advent Calendar 2021』 の20日目の記事を執筆してるときに裏で使ってたネタです.

つくったもの

Markdown中のLaTeXのコードをtex2imgでコンパイルして図にしてくれるVimスクリプト書きました.

build

% vim-runner-tex2img/example.svg
\begin{tikzpicture}[scale=2.0, every node/.style={scale=2.0}]
    \tikzstyle{mynode}=[draw, circle, line width=1.5pt, minimum size=0.75cm]
    \node [mynode] (a) at (0, 0) {$a$};
    \node [mynode,fill=blue!20] (c) at (2, 0) {$c$};
    \node [mynode] (b) at (4, 0) {$b$};
    \draw [-latex, line width=1.0pt] (c) -- (a);
    \draw [-latex, line width=1.0pt] (c) -- (b);
\end{tikzpicture}

example

モチベーション

Hugoでブログを書くとき,簡単な図を描いて記事内に入れたいことがあります. 図を描くためにInkscapeなどの外部ツールを起動するのは面倒臭いのでテキストエディタ上で作業が完結してほしいですよね. コードで図を描く方法としてLaTeXのパッケージであるTikZがありますが図と一緒に.texファイルが増殖するのはちょっと嫌です.

なので記事のMarkdownファイルの中にTikZのコードを埋め込んでおいてVimのコマンドでコンパイルできるようにしてみました.

つかったもの

Neovim

v0.4.4

TikZ/PGF

TikZについて:

PGF (Portable Graphics Format) は,Till Tantau(Beamerのオリジナルの開発者)によって作成され,現在は Henri Menke, Christian Feuersänger などのメンバーによって開発・メンテナンスされている TeX 用の描画パッケージです。 PGF のフロントエンドとして一般的には TikZ (TikZ ist kein Zeichenprogramm = “TikZ is not a drawing program”) を使用します。 Beamer が基礎としている描画エンジンも PGF です。

https://texwiki.texjp.org/?TikZ

TikZはLaTeXのパッケージとして提供されていて,LaTeXのコード中に描画コマンドを記述することで図を描くことができます. 下記マニュアルで図の例を見ていただければわかると思いますが,かなり高機能で何でも描けます.

https://pgf-tikz.github.io/pgf/pgfmanual.pdf

文法に慣れるまでは大変ですが,オブジェクト同士のサイズや位置のスナップのような本質じゃないレイアウト調整に頭を使わなくてよくなるのは嬉しいですね.

TeX2img

TeXのコードから画像を作るアプリケーションです. 数式やTikZの画像化に使われることが多いですが,TeX文書ならなんでも画像化できます. また,CLIもあるのでヘッドレス実行が可能です.

https://texwiki.texjp.org/?TeX2img

SVG出力をするには別途環境構築が必要なので方法を別記事にまとめています

https://blog.eqseqs.work/2021/06/01/214757/

vim-markdown-runner

Markdown内のコードブロックに書かれたコードを実行するプラグインです.

https://github.com/dbridges/vim-markdown-runner

やったこと

vim-markdown-runnerの g:markdown_runners という変数に言語名と関数の対応付けをする辞書が入ってるので, 言語がtexの場合にTeX2imgを実行してくれる関数を登録しました.

TeX2imgを実行するrunnerは以下のようになります. やってることはMarkdownから取得されたコード src に必要なプリアンブルを付けて適当な名前で保存し, tex2imgcを実行してるだけです1. また,出力ファイル名を変えたかったのでコードの冒頭にコメントで指定できるようにしました.

functionMyTex2imgRunner(src)
    let tmp = tempname() . ".tex"
    let src = a:src

    " コードの最初にコメントとして挿入されているファイル名を取得する
    " 無ければ`_output.svg`が標準のファイル名になる
    let matched = matchlist(src[0], "^\%\\s*\\(.*\.\\(svg\\|pdf\\|png\\)\\)")
    if len(matched) >= 2
        let out = matched[1]
    else
        let out = "_output.svg"
    endif

    " tex2imgに突っ込むコードを生成する
    let joined_src = join(src[1:], "\n")
    let src = split(
            \ "\\documentclass[fleqn,papersize,dvipdfmx]{jsarticle}\n"
            \. "\\usepackage{tikz,graphicx}\n"
            \. "\\usepackage{amsmath,amssymb}\n"
            \. "\\usepackage{color}\n"
            \. "\\pagestyle{empty}\n"
            \. "\\usetikzlibrary{calc,positioning,arrows,shapes,automata}\n"
            \. "\\begin{document}\n"
            \. joined_src
            \. "\n\\end{document}\n""\n")

    call writefile(srctmp)
    let res = system("tex2imgc /transparent /keep-page-size /embed-source /quiet /margins=8 " . tmp . " " . out)
    call delete(tmp)
    return res
endfunction

autocmd FileType markdown nnoremap <buffer> <Leader>r :MarkdownRunner<CR>
autocmd FileType markdown nnoremap <buffer> <Leader>R :MarkdownRunnerInsert<CR>

" vim-markdown-runnerに登録する
if !exists("g:markdown_runners")
    let g:markdown_runners = {
                \ ''getenv('SHELL'),
                \ 'go'function("markdown_runner#RunGoBlock"),
                \ 'js''node',
                \ 'javascript''node',
                \ 'vim'function("markdown_runner#RunVimBlock"),
                \ 'tex2img'function('MyTex2imgRunner'),
                \ 'tex'function('MyTex2imgRunner')
                \ }
endif

自分はdein.vimでプラギン管理をしてるのでTomlファイルの中にhookとして記述しています:

[[plugins]]

repo = 'dbridges/vim-markdown-runner'
hook_add = '''
function! MyTex2imgRunner(src)
    ...
'''

図とコードが常に文書中に共存してて邪魔なときはHTMLのコメントかHugoのShortcodeを使って隠すと良いです. 自分は次のようなShortcodeで隠しています:

{{ if .Inner }}{{ end }}

実行中にエディタが固まるのとエラーメッセージが文字化けで読めないのが残念ですがとりあえず動きます. そのうちプラグイン化するのでそのとき出来れば直します.

TikZで描くのが面倒な図はどうする?

前回の記事の信号の例の図なんかはVScodeのdrawioのプラグインでサッッッッと描いてました. エディタ上に図形描画のGUIが出てくるやつです.最早vim関係ないですね.

https://github.com/hediet/vscode-drawio

SVGファイルにdrawioの情報を埋め込んでくれるのでファイルが増殖しないのが嬉しいポイントです.Gitとも相性が良いです. 数式のレンダリングが弱めなのとスタイルの一括修正とかはTikZの方が楽なので使い分けるのが良いと思います.


  1. GUIのtex2imgは自動でプリアンブルをつけてくれるが,CLIのtex2imgcは自分でつけないといけないので注意. ↩︎

© eqs 2021