LaTeX.jsとAce.jsを用いたLaTeXのリアルタイムプレビューができるエディタ

作成: 2019年02月26日

更新: 2021年02月07日

やりたいこと

markdownをリアルタイムプレビューできるエディターを作ってみた系の記事を見て興味を持った。しかし同じものを作っても面白くないのでLaTeXで同じことをやってみようと思った。
最終的に左のtextareaにLaTeXで記述すると右にLaTeXを解釈したものを表示したい。pdf化できるとなおよし。

LaTeXとは

数式や簡単な図形をテキストで表現できる言語。フォントの美しさが一部ファンを魅了している。理工系の大学生はしばしばレポートをこれで書くことを強いられる。


latex.png

Ace

Ace - The High Performance Code Editor for the Web
Aceはブラウザ上にエディターを設置できるJavaScriptライブラリです。さまざまな言語のシンタックスハイライトに対応し、入力補完までできる。
html上のエディターを置きたい場所に以下のような要素を置く。

<div id="editor" style="height: 100vh;">
</div>

そして以下のJavaScriptを書く。

<script>
        const editor = ace.edit('editor');
        editor.setTheme('ace/theme/monokai');
        editor.setFontSize(14);
        editor.getSession().setMode('ace/mode/latex');
        editor.getSession().setUseWrapMode(true);
        editor.getSession().setTabSize(4);
        editor.$blockScrolling = Infinity;
        editor.setOptions({
            enableBasicAutocompletion: true,
            enableSnippets: true,
        });
</script>

これだけでLaTeX用のエディターができる。setTheme部分を変更すればテーマをsetModeを変更すればシンタックスハイライトを変えられる。LaTeXとかいうマイナー言語のシンタックスハイライトが用意されているのでよっぽどマイナーな言語でなければ用意されている。詳しくは公式ドキュメント参照。

LaTeX.js

LaTeX.js - LaTeX to HTML5 translator
LaTeX.jsはLaTeXで書かれたものをHTMLの要素に変換し、CSSによってフォントなど適切な装飾を施すライブラリです。
これで上記のエディター上に書かれたLaTeXをプレビューできるようにする。
プレビューを表示したい場所に以下の要素を置く。

<div id="output">
</div>

そしてJavaScriptを以下のように追記する。

<script>
    const editor = ace.edit('editor');
    editor.setTheme('ace/theme/monokai');
    editor.setFontSize(14);
    editor.getSession().setMode('ace/mode/latex');
    editor.getSession().setUseWrapMode(true);
    editor.getSession().setTabSize(4);
    editor.$blockScrolling = Infinity;
    editor.setOptions({
        enableBasicAutocompletion: true,
        enableSnippets: true,
    });
    editor.getSession().on('change', function () {
        var generator = new latexjs.HtmlGenerator({
            hyphenate: false
        });
        try {
            generator = latexjs.parse(editor.getValue(), {
                generator: generator
            });
            $("#output").empty();
            output.appendChild(generator.stylesAndScripts(
                "https://cdn.jsdelivr.net/npm/latex.js@0.11.1/dist/"));
            output.appendChild(generator.domFragment());
        } catch (e) {
            if (e.name == "SyntaxError") {
                /*LaTeXのパースエラー*/
                $("#output").replaceWith('<div id="output"> <p>' + e.name + '</p><p>line ' + e.location[
                        "start"]
                    ["line"] + ' (column ' + e.location["start"]["column"] + '): ' + e.message +
                    '</p></div>');
                console.error(e);
            } else {
                /*予期せぬエラー*/
                $("#output").replaceWith('<div id="output"> <p>unexpected error' + '</p></div>');
                console.error(e);
            }
        }
    });
</script>

これでエディター上のテキストが変更されたとき、エディター上のLaTeXをHTMLへと変換しプレビューできるようになる。またエディター上のテキストをLaTeXとして解釈できなかった時のためにtry catchでエラー表示に対応している。LaTeX.jsのエラーはエラーの場所など多くの情報を含んでいるためそれをそのまま表示できる。またLaTeX.jsではなくAce.jsなどのエラーもcatchできるようにしている。このようにしないとエディターが操作できなくなったりおかしな挙動をとってしまう。

LaTeXのpdf化

これだけでは公式のPlayground以下なのでLaTeXのPDF化に挑戦してみた。
しかしPythonでLaTeXのPDF化ができるライブラリは以外にもほとんど見つからなかった。
とりあえずlatex.pyというライブラリを使ってみた。
latex — latex 0.6.4 documentation
まず以下のようなスクリプトでLaTeXのテキストをサーバーにPOSTメソッドで送る。

<script>
    var postForm = function (url, data) {
        var $form = $('<form/>', {
            'action': url,
            'method': 'post',
            'id': 'tempForm'
        });
        for (var key in data) {
            $form.append($('<input/>', {
                'type': 'hidden',
                'name': key,
                'value': data[key]
            }));
        }
        $form.append('{% csrf_token %}');
        $form.appendTo(document.body);
        $form.submit();
        $("#tempForm").remove();
    };

    function exportToPDF() {
        var tex = editor.getValue();
        postForm("/LaTeXEditor/exportToPDF", {
            "tex": tex
        });
    }
</script>

postFormメソッドで一時的にフォームを作り、そこを経由してテキストをサーバーに送信している。jQueryのpostメソッドではサーバーに遅れてもファイルのダウンロードが行われなかったのでこのようにしている。
そしてサーバー(Django)では以下のように処理する。

views.pyfrom latex import build_pdf

def exportToPDF(request):
    tex = request.POST["tex"]
    response = HttpResponse(content_type='application/pdf')
    filename = "document.pdf"
    response['Content-Disposition'] = 'attachment; filename={0}'.format(
        filename)
    try:
        pdf = build_pdf(tex)
    except:
        return HttpResponseBadRequest("build pdf error!")
    else:
        response.write(bytes(pdf))
        return response

latex.pyのbuild_pdfメソッドでLaTeXをPDFのバイナリに変換しダウンロードする。LaTeXの解釈がうまくいかずPDF化できなかったときはBadRequestを返す。
latex.pyは日本語に対応していないらしく日本語を入れるとエラーにはならないがすべて空白になってしまう。LaTeXのPDF化はPythonにこだわるのではなく別言語で行ったほうがいいかもしれない。

まとめ

LaTeXのPDF化は不完全になってしまったが、LaTeXのプレビューができるエディターを作れた。
URLは以下
https://banatech.net/latex_editor/