はじめに
この記事はQiitaの「Vim Advent Calendar 2021 その2」24日目の記事です.
先日,『OpenCV Advent Calendar 2021』 の20日目の記事を執筆してるときに裏で使ってたネタです.
つくったもの
Markdown中のLaTeXのコードをtex2imgでコンパイルして図にしてくれるVimスクリプト書きました.
% 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}
モチベーション
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.
また,出力ファイル名を変えたかったのでコードの冒頭にコメントで指定できるようにしました.
function! MyTex2imgRunner(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(src, tmp)
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の方が楽なので使い分けるのが良いと思います.
-
GUIの
tex2img
は自動でプリアンブルをつけてくれるが,CLIのtex2imgc
は自分でつけないといけないので注意. ↩︎