日記(日記とは言っていない)

https://zenn.dev/23prime に移行しました。

<LaTeX> LaTeX 執筆環境のメモ

LaTeX 関連のツールに関してのメモです.

主に
- Latexmk
- YaTeX + RefTeX
- SyncTeX
- パッケージの追加
等について.

Latexmk

LaTeX 関連のコマンドをいい感じにまとめてくれてるやつです.

BiBTeX とか,何回もタイプセットしなきゃいけないのをまとめて自動的にやってくれたり,勝手に PDF を生成してくれたりします.

TeXLive なんかには既に入ってると思います.

参考:Latexmk - TeX Wiki

参考:latexmk で楽々 TeX タイプセットの薦め(& biblatex+biberで先進的な参考文献処理) - konn-san.com

Usage

latexmk [<options>] hoge.tex

Options

  • -pv : PDF を生成して,それをビューアで開く.
  • -pvc : -pv してさらに hoge.tex が更新されたら勝手に再コンパイル
  • -c : 中間ファイル(.dvi .pdf 除く)の削除を行う.
  • -C : 中間ファイル(.dvi .pdf 含む)の削除を行う.

Settings

~./latexmkrc

#!/usr/bin/env perl
$latex         = 'uplatex -synctex=1 -halt-on-error -shell-escape';
$latex_silent  = 'uplatex -synctex=1 -halt-on-error -interaction=batchmode -shell-escape';
$bibtex        = 'upbibtex';
$dvipdf        = 'dvipdfmx %O -o %D %S';
$makeindex     = 'mendex %O -o %D %S';
$max_repeat    = 5;
$pdf_mode      = 3;
$pdf_previewer = "evince";
$pvc_view_file_via_temporary = 0;

基本的には使うコマンドを指定するだけなので,好きなものを書きます.

なお, uplatex は,platexUnicode 化です.

\documentclass[uplatex]{jsarticle}

とか書くと使えます.こちら が参考になります.

uplatex のオプションについて.

  • -halt-on-error -interaction=batchmode により,途中のエラーを無視したり,-synctex=1 で後述の SyncTeX を有効にしたりしてます.

  • -shell-escapeminted 環境を使う時に付けろって言われたので付けてます.

YaTeX + RefTeX

LaTeX 執筆に不可欠な Emacs のモードです.

色々と便利なコマンドを提供してくれたり,括弧の自動補完とかしてくれます.

Settings

init.el

;; YaTeX + RefTeX
(setq auto-mode-alist
      (cons (cons "\\.tex$" 'yatex-mode) auto-mode-alist))
(setq auto-mode-alist
      (cons (cons "\\.sty$" 'yatex-mode) auto-mode-alist))
(autoload 'yatex-mode "yatex" "Yet Another LaTeX mode" t)
(setq YaTeX-open-lines 0)
(setq YaTeX-kanji-code nil)
(setq tex-command "latexmk -pvc")
(setq dviprint-from-format "-p %b")
(setq dviprint-to-format "-l %e")
(setq dviprint-command-format "dvips %f %t %s | lpr")
(add-hook 'yatex-mode-hook'(lambda ()(setq auto-fill-function nil)))
(add-hook 'latex-mode-hook 'turn-on-reftex)
(add-hook 'yatex-mode-hook 'turn-on-reftex)

(setq tex-command "latexmk -pvc")C-c C-t j から latexmk -pvc を呼べるようにしてます.

(個人的に)よく使うコマンド

  • C-c C-t j : コンパイルtex-command で,デフォルトでは platex してくれる)
  • C-c C-t d : PDF の生成(デフォルトでは platex + dvipdfmx してくれる)
    • ↑これらは実行時に .tex の Save も行う.
  • C-c C-b SPC : \begin{hoge} \end{hoge} を作ってくれる
  • C-c [ : bib から \cite を挿入

TeXrm

LaTeX の中間ファイルたちをまとめて片付けるために書いたスクリプトです.

※普通に考えたらちゃんとした人が作ってる latexmk -c の方がいい.

texrm

#!/bin/bash

ARG=$1

# Extensions of target files.
targets=(
    *.dvi
    *.aux
    *.log
    *.out
    *.nav
    *.toc
    *.snm
    *.vrb
    *.loa
    texput*
    *.fdb_latexmk
    *.fls
    *.gz
)

# Help.
function usage {
    cat <<EOF
Usage:
    texrm [<options>]

Options:
    -a, --all    : Remove all files under home directory.
    -y, --yes    : Not ask whether to remove.
    -e, --except : Exclude some files by extension.
    -v, --version: TeXrm version.
    -h, --help   : Help for TeXrm.
EOF
}

function fsort {
org_ifs=$IFS # Define the delimiter temporarily.
IFS=$'\n'
files=($(echo "${files[*]}" | sort -n)) # Sort array of files for lex-order.
IFS=$org_ifs
}

# Function to ask remove or not.
function askremove {
    for j in ${files[@]}; do # Indicate target files.
        echo "${j}"
    done
    echo "Remove these files? [Y/n]"
    read ANS
    case $ANS in
        "" | Y | y | yes | Yes | YES )
            for i in ${files[@]}; do
                rm -rf "${i}"
            done
            echo "Removed!" ;;
        * )
            echo "Canceled." ;;
    esac
    exit 0
}

# Make array of target files.
files=()


for i in ${targets[@]}; do
    case $ARG in
        -a | --all )
            files+=(`find ./ -name "${i}"`) ;; # All files under current directory.
        * )
            files+=(`find -maxdepth 1 -name "${i}"`) ;; # Only current directory.
    esac
done

# Execute.
case $ARG in
    -h | --help )
        usage
        exit 0 ;;
    -v | --version )
        echo "TeXrm: Version 1.0.0"
        exit 0 ;;
    * )
        if [ "${#files[@]}" -eq 0 ]; # Check; files exist or not.
        then
            echo "No such files."
            exit 0
        else
            case $ARG in
                "" | -a | --all )
                    fsort
                    askremove 
                    exit 0 ;;
                -Y | -y | -yes | -Yes | -YES ) # Not ask to remove or not
                    fsort
                    for j in ${files[@]}; do
                        echo "${j}"
                    done
                    for i in ${files[@]}; do
                        rm -rf "${i}"
                    done
                    echo "These files have been Removed!"
                    exit 0 ;;
                -e | --except ) # Specify extension don't remove.
                    echo "Please enter the extension to exclude."
                    read EXT
                    files=()
                    for i in ${targets[@]}; do
                        if [[ $i != *.$EXT ]]; then
                            files+=(`find -maxdepth 1 -name "${i}"`)
                        else
                            :
                        fi
                    done
                    fsort
                    askremove
                    exit 0 ;;
                * )
                    echo "TeXrm: Error; Bad Option." 1>&2
                    echo ""
                    echo "Use:"
                    echo "    texrm -h"
                    exit 1
            esac
        fi
esac

一応 Emacs から使えるようにしておきます.

init.el

(defun texrm ()
  (interactive)
  (message "TeXrm -- Done!")
  (shell-command-to-string "texrm -y"))
(add-hook 'yatex-mode-hook
          '(lambda ()
             (define-key YaTeX-mode-map (kbd "C-c d") 'texrm)))

SyncTeX

TeX のソースファイルと PDF を相互にジャンプできるようにするやつです.

Usage

タイプセットコマンドに -synctex=1 とか書くだけで使えます(Latexmk の設定に書いてあげてます).

PDF から飛ぶときは,Ctrl + 左クリック でいけます.

詳しくは SyncTeX - TeX Wiki で.

Settings

とはいえ,エディタやビューアごとにスクリプトが必要です.

Emacs と Evince をジャンプできるようにします.

Settings for Evince

path の通った場所/fwdevince

参考:Evince/fwdevince/Python - TeX Wiki

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from gi.repository import GLib, Gio
import dbus
import argparse
import pathlib
import traceback
import urllib.parse

class EvinceForwardSearch:
    def parse_args(self):
        parser = argparse.ArgumentParser(description='Forward search with Evince')
        parser.add_argument('pdf', nargs=1, help='PDF file')
        parser.add_argument('line', nargs=1, type=int, help='Line')
        parser.add_argument('tex', nargs=1, help='TeX file')
        return parser.parse_args()

    def run(self):
        args = self.parse_args()
        pdf = str(pathlib.Path(args.pdf[0]).resolve()).replace(" ", "%20")
        line = int(args.line[0])
        tex = str(pathlib.Path(args.tex[0]).resolve().parent / pathlib.Path('./') / pathlib.Path(args.tex[0]).resolve().name)

        try:
            bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
            daemon = Gio.DBusProxy.new_sync(bus, 0, None, 'org.gnome.evince.Daemon', '/org/gnome/evince/Daemon', 'org.gnome.evince.Daemon', None)
            dbus_name = daemon.call_sync('FindDocument', GLib.Variant('(sb)', ('file://' + urllib.parse.quote(pdf, safe="%/:=&?~#+!$,;'@()*[]"), True)), Gio.DBusSignalFlags.NONE, -1, None)
            dbus_name = dbus_name.unpack()[0]
            window = Gio.DBusProxy.new_sync(bus, 0, None, dbus_name, '/org/gnome/evince/Window/0', 'org.gnome.evince.Window', None)
            window.call_sync('SyncView', GLib.Variant('(s(ii)u)', (tex, (line, 1), 0)), Gio.DBusSignalFlags.NONE, -1, None)
        except Exception:
            traceback.print_exc()

class EvinceInverseSearch:
    def parse_args(self):
        parser = argparse.ArgumentParser(description='Inverse search with Evince')
        parser.add_argument('pdf', nargs=1, help='PDF file')
        parser.add_argument('editor', nargs=1, help='Editor command')
        return parser.parse_args()

    def run(self):
        args = self.parse_args()
        pdf = str(pathlib.Path(args.pdf[0]).resolve())
        editor = args.editor[0]
        import dbus.mainloop.glib
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        a = EvinceWindowProxy('file://' + pdf, editor, True)
        GLib.MainLoop().run()

class EvinceWindowProxy:
    """A Dbus proxy for an Evince Window."""
    daemon = None
    bus = None

    RUNNING = range(2)
    CLOSED = range(2)
    EV_DAEMON_PATH = '/org/gnome/evince/Daemon'
    EV_DAEMON_NAME = 'org.gnome.evince.Daemon'
    EV_DAEMON_IFACE = 'org.gnome.evince.Daemon'

    EVINCE_PATH = '/org/gnome/evince/Evince'
    EVINCE_IFACE = 'org.gnome.evince.Application'

    EV_WINDOW_IFACE = 'org.gnome.evince.Window'

    def __init__(self, uri, editor, spawn=False, logger=None):
        self._log = logger
        self.uri = uri.replace(" ", "%20")
        self.editor = editor
        self.status = self.CLOSED
        self.source_handler = None
        self.dbus_name = ''
        self._handler = None
        try:
            if EvinceWindowProxy.bus is None:
                EvinceWindowProxy.bus = dbus.SessionBus()

            if EvinceWindowProxy.daemon is None:
                EvinceWindowProxy.daemon = EvinceWindowProxy.bus.get_object(self.EV_DAEMON_NAME,
                    self.EV_DAEMON_PATH,
                    follow_name_owner_changes=True)
            EvinceWindowProxy.bus.add_signal_receiver(self._on_doc_loaded,
                signal_name='DocumentLoaded',
                dbus_interface=self.EV_WINDOW_IFACE,
                sender_keyword='sender')
            self._get_dbus_name(False)
        except dbus.DBusException:
            traceback.print_exc()
            if self._log:
                self._log.debug('Could not connect to the Evince Daemon')

    def _on_doc_loaded(self, uri, **keyargs):
        if uri == self.uri and self._handler is None:
            self.handle_find_document_reply(keyargs['sender'])

    def _get_dbus_name(self, spawn):
        EvinceWindowProxy.daemon.FindDocument(self.uri, spawn,
                     reply_handler=self.handle_find_document_reply,
                     error_handler=self.handle_find_document_error,
                     dbus_interface = self.EV_DAEMON_IFACE)

    def handle_find_document_error(self, error):
        if self._log:
            self._log.debug('FindDocument DBus call has failed')

    def handle_find_document_reply(self, evince_name):
        if self._handler is not None:
            handler = self._handler
        else:
            handler = self.handle_get_window_list_reply
        if evince_name != '':
            self.dbus_name = evince_name
            self.status = self.RUNNING
            self.evince = EvinceWindowProxy.bus.get_object(self.dbus_name, self.EVINCE_PATH)
            self.evince.GetWindowList(dbus_interface = self.EVINCE_IFACE,
                          reply_handler = handler,
                          error_handler = self.handle_get_window_list_error)

    def handle_get_window_list_error (self, e):
        if self._log:
            self._log.debug("GetWindowList DBus call has failed")

    def handle_get_window_list_reply (self, window_list):
        if len(window_list) > 0:
            window_obj = EvinceWindowProxy.bus.get_object(self.dbus_name, window_list[0])
            self.window = dbus.Interface(window_obj,self.EV_WINDOW_IFACE)
            self.window.connect_to_signal("SyncSource", self.on_sync_source)
        else:
            #That should never happen.
            if self._log:
                self._log.debug("GetWindowList returned empty list")

    def on_sync_source(self, input_file, source_link, timestamp):
        import subprocess
        import re
        print(input_file + ':' + str(source_link[0]))
        input_file = input_file.replace("%20", " ")
        input_file = urllib.parse.unquote(input_file)
        print(type(input_file), input_file)
        cmd = re.sub("%f", input_file.replace('file://', ''), self.editor)
        cmd = re.sub("%l", str(source_link[0]), cmd)
        print(cmd)
        subprocess.run(cmd, shell=True)
        if self.source_handler is not None:
            self.source_handler(input_file, source_link, timestamp)

if __name__ == '__main__':
    import sys
    cmd = pathlib.Path(sys.argv[0]).name
    if cmd == 'fwdevince' or cmd == 'evince_forward_search':
        EvinceForwardSearch().run()
    elif cmd == 'invevince' or cmd == 'bwdevince' or cmd == 'evince_inverse_search' or cmd == 'evince_backward_search':
        EvinceInverseSearch().run()
    else:
        sys.stderr.write("rename 'fwdevince' or 'invevince'\n")
        sys.exit(1)
Settings for Emacs

init.el

参考:SyncTeXの設定。 Emacs(YaTeX) + Evince - うぶつん

;; SyncTeX
(defun evince-forward-search ()
  (interactive)
  (let* ((ctf (buffer-name))
         (mtf)
         (pf)
         (ln (format "%d" (line-number-at-pos)))
         (cmd "fwdevince")
         (args))
    (if (YaTeX-main-file-p)
        (setq mtf (buffer-name))
      (progn
        (if (equal YaTeX-parent-file nil)
            (save-excursion
              (YaTeX-visit-main t)))
        (setq mtf YaTeX-parent-file)))
    (setq pf (concat (car (split-string mtf "\\.")) ".pdf"))
    (setq args (concat pf " " ln " " ctf))
    (message (concat cmd " " args))
    (process-kill-without-query
     (start-process-shell-command "fwdevince" nil cmd args))))

(add-hook 'yatex-mode-hook
          '(lambda ()
             (define-key YaTeX-mode-map (kbd "C-c e") 'evince-forward-search)))

(require 'dbus)

(defun un-urlify (fname-or-url)
  "A trivial function that replaces a prefix of file:/// with just /."
  (if (string= (substring fname-or-url 0 8) "file:///")
      (substring fname-or-url 7)
    fname-or-url))

(defun th-evince-sync (file linecol &rest ignored)
  (let* ((fname (un-urlify file))
 ;        (buf (find-buffer-visiting fname))
         (buf (find-file fname))
         (line (car linecol))
         (col (cadr linecol)))
    (if (null buf)
        (message "[Synctex]: %s is not opened..." fname)
      (switch-to-buffer buf)
      (goto-line (car linecol))
      (unless (= col -1)
        (move-to-column col)))))

(defvar *dbus-evince-signal* nil)

(defun enable-evince-sync ()
  (require 'dbus)
  (when (and
         (eq window-system 'x)
         (fboundp 'dbus-register-signal))
    (unless *dbus-evince-signal*
      (setf *dbus-evince-signal*
            (dbus-register-signal
             :session nil "/org/gnome/evince/Window/0"
             "org.gnome.evince.Window" "SyncSource"
             'th-evince-sync)))))

(add-hook 'yatex-mode-hook 'enable-evince-sync)

パッケージの追加について

インストールされていないパッケージを使おうとすると,

! LaTeX Error: File `hoge.sty' not found.

とか言われます.そんなときは,hoge.sty を探してきたり作ったりして,

/usr/share/texlive/texmf-dist/tex/latex/

あたりに置いてあげて,

sudo mktexlsr

とか打ってあげればよいです.

参考:各種パッケージの利用 - TeX Wiki