latexdiff tips

3行で

  • tex で書いた文書の差分を可視化したいなら、overleaf でも使える latexdiff が便利
  • latexdiff の出力がコンパイルできないときは option オプションを調整するとよい
  • 差分をハイライトする方法を作ったので紹介する

論文の再提出で差分をハイライトしたPDFを提出せよ。と言われて色々工夫が必要だったのでメモを残す。同じように困る人もいそうなので。

latexdiff と overleaf

latex で書いた文章の差分を可視化するツールとして latexdiff というコマンドがある。削除した部分を赤字かつ取り消し線で表示し、追加した部分を青字して表示するというような事ができる。出力は tex ファイルとして出てくるので、latexコンパイルして表示する。

overleaf はオンラインで共著者と共同編集しながら tex を書いてコンパイルまでできる Web サービス。自分が学部生のころはこんな便利なものはなかったが、今はデファクトスタンダードのようだ。

local に tex 環境を用意しなくても overleaf で事足りるので、可能なら latexdiff も overleaf 内で完結したい。overleaf 公式もやり方を紹介していて、これに従えばほぼ問題なく使える。

この記事で紹介されている方法は、latexmkrc というlatexコンパイル時に読まれる設定ファイルを書き換えることで、コンパイル対象を latexdiff が生成した main-d.tex ファイルに変更している。この main-d.tex ファイルが正しくコンパイルできるなら何も問題はない。しかし時々 latexdiff はコンパイルできない tex を吐き出すので、なにも表示されず中間ファイルである main-d.tex も見ることができず、手詰まりになってしまう。

latexdiff のオプション

latexdiff はものすごい高機能なソフトウェアで、起動オプションもたくさんある。なるべくシンプルな diff を出すようにオプションを指定すると無事コンパイルできることがある。経験的にわかったおすすめオプションは以下のように -t CFONT だけつける。

latexdiff -t CFONT main_prev.tex main.tex > main-d.tex; pdflatex %O  main-d

完全に想像で書くけれど、マークアップ入れ子構造が壊れやすいようなオプションがあり、複雑な変更のdiffを出そうとしたときにうまく動かないように見える。上のおすすめオプションは比較的入れ子構造が壊れにくいのだろう。

diff をハイライトする

さて本題である。削除部分は表示せず、追加部分をハイライトするという diff を PDF にしたい。先に解決策を書こう。まずは、latexmkrc の中身。

$pdflatex = "latexdiff --no-del --graphics-markup=0 -p latexdiff_preamble.tex main_prev.tex main.tex > main-d.tex; pdflatex %O main-d"

--no-del は削除部分を表示しないオプション --graphics-markup=0 は追加した図表が青い枠で囲まれるのを防いでいる -p オプションで読み込んでいる latexdiff_preamble.tex は以下のような、tex ファイルの断片だ。このファイルも overleaf の中に作成しておく。

\RequirePackage{xcolor}
\providecommand{\DIFadd}[1]{{\protect\color[HTML]{1C2833} #1}}
\providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}}

\providecommand{\DIFaddbegin}{}
\providecommand{\DIFaddend}{}
\providecommand{\DIFdelbegin}{}
\providecommand{\DIFdelend}{}
\providecommand{\DIFmodbegin}{}
\providecommand{\DIFmodend}{}

\providecommand{\DIFaddFL}[1]{\DIFadd{#1}}
\providecommand{\DIFdelFL}[1]{\DIFdel{#1}}
\providecommand{\DIFaddbeginFL}{}
\providecommand{\DIFaddendFL}{}
\providecommand{\DIFdelbeginFL}{}
\providecommand{\DIFdelendFL}{}

この -p オプションで、main-d.tex にどんなマークアップが適応されるか事細かに制御できる。詳細はマニュアルを見てほしい。簡単に説明すると、\DIF で始まるコマンドが変更箇所に差し込まれるので、そのコマンドの定義を latexdiff_preamble.tex に書いて渡している。ほとんどのコマンドは何もしない様になっていて、DIFadd と DIFdel だけが定義されている。削除部分は--no-delオプションで消してしまうので実質的には意味がない。DIFadd で追加部分が #1C2833 というかなり濃い灰色のテキストになるように指定している。

この latexmkrc で変更されたコンパイルが無事成功すると、ほぼ完成原稿と変わらない PDF が生成される。追加された箇所は濃い灰色なので目を凝らさないと他の文と見分けられないだろう。

さらにハイライトをこのファイルに施す。以下のような pythonスクリプトを使う。ChatGPT に作ってもらったものを少し改変している。

import sys

import fitz  # PyMuPDF


def main():
    # PDFを再度読み込み(赤文字のまま、ハイライトのみ追加)
    input_pdf_path = sys.argv[1]
    output_pdf_path = sys.argv[2]
    target_color = 0x1C2833
    doc = fitz.open(input_pdf_path)

    for page in doc:
        blocks = page.get_text("dict")["blocks"]
        for block in blocks:
            for line in block.get("lines", []):
                for span in line.get("spans", []):
                    if span["color"] == target_color:
                        rect = fitz.Rect(span["bbox"])
                        # 黄色でハイライトのみ追加(文字の色は変更しない)
                        page.add_highlight_annot(rect)

    # 保存
    doc.save(output_pdf_path)
    doc.close()


if __name__ == "__main__":
    main()

別案

別案として latexdiff_preamble.tex の冒頭を次のように書き換える手があり得る。

\RequirePackage{soul}
\providecommand{\DIFadd}[1]{\hl{#1}}

最初に紹介した方法のような回りくどいことをせずに直接的に追加部分をハイライトしてしまう方法だ。これでうまくいくならシンプルで良いのだが、差分が複雑だと大量にエラーのある main-d.tex が出力されてしまう。少なくとも私の環境ではうまくいかなかった。