% mermaid.sty -- Embed Mermaid diagrams in LaTeX.
%
% Copyright (C) 2026 Ryoya Ando (https://ryoya9826.github.io/)
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3c
% of this license or (at your option) any later version.
% The latest version of this license is in
%   https://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2008/05/04 or later.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{mermaid}[2026/10/22 v1.1 Mermaid via Mermaid CLI / PDF]
\RequirePackage{shellesc}
\RequirePackage{graphicx}
\RequirePackage{adjustbox}
\RequirePackage{fancyvrb}
\RequirePackage{kvoptions}
\SetupKeyvalOptions{family=mermaid,prefix=mermaid@,setkeys=\kvsetkeys}
\DeclareStringOption[]{Renderer}
\ProcessKeyvalOptions*

\newread\mermaid@login
\newwrite\mermaid@optsout
\def\mermaid@cmd{}
\def\mermaid@xargs{}
\AtEndOfPackage{%
  \edef\mermaid@renderer@nonempty{\mermaid@Renderer}%
  \ifx\mermaid@renderer@nonempty\@empty
  \else
    \let\mermaid@cmd\mermaid@Renderer
  \fi
}
\newcommand{\MermaidRendererOptions}[1]{\def\mermaid@xargs{#1}}
% auto: Mermaid CLI 11 以前のときだけ -f を付ける（-f は Mermaid CLI 12 で廃止）
\def\mermaid@pdffit{auto}
\newcommand{\MermaidPdfFit}{\def\mermaid@pdffit{-f}}
\newcommand{\MermaidNoPdfFit}{\def\mermaid@pdffit{}}
% シェル変数 D に出力ディレクトリ（-output-directory 未指定時は .）を設定する前置き
\def\mermaid@shoutdir{D=$TEXMF_OUTPUT_DIRECTORY; [ -n "$D" ] || D=.;}
% `mmdc --version` の出力からメジャーバージョンを取り出す sed スクリプト
\edef\mermaid@sedmajor{\detokenize{s/^ *v*\([0-9][0-9]*\)\.[0-9][0-9]*\.[0-9][0-9]*.*$/\1/p}}
\def\mermaid@graphicsopts{}
\newcommand{\MermaidGraphicsOpts}[1]{\def\mermaid@graphicsopts{#1}}
\def\mermaid@boxopts{max width=0.9\linewidth,center}
\newcommand{\MermaidAdjustBoxOpts}[1]{\def\mermaid@boxopts{#1}}
\def\mermaid@resolverenderer{%
  \ifx\mermaid@cmd\@empty
    \def\mermaid@resolvedrenderer{mmdc}%
  \else
    \let\mermaid@resolvedrenderer\mermaid@cmd
  \fi
}
\def\mermaid@log#1{\typeout{[mermaid] #1}}
\def\mermaid@mkdir{\ShellEscape{\mermaid@shoutdir\space mkdir -p "$D/mermaid"}}
\def\mermaid@logcwd{%
  \edef\mermaid@cwdfile{mermaid/mermaid-cwd-\the\c@mermaid@diag.txt}%
  \ShellEscape{\mermaid@shoutdir\space pwd > "$D/\mermaid@cwdfile"}%
  \openin\mermaid@login=\mermaid@cwdfile\relax
  \ifeof\mermaid@login
    \mermaid@log{cwd: (unavailable)}%
  \else
    \read\mermaid@login to \mermaid@cwdline
    \closein\mermaid@login
    \mermaid@log{cwd: \mermaid@cwdline}%
  \fi
}
% pdfTeX / LuaTeX の PDF 出力以外（DVI 出力と XeTeX）かどうか
\newif\ifmermaid@needabs
\def\mermaid@checkneedabs{%
  \mermaid@needabstrue
  \ifdefined\XeTeXrevision\else
    \ifdefined\outputmode
      \ifnum\outputmode>\z@ \mermaid@needabsfalse \fi
    \else
      \ifdefined\pdfoutput
        \ifnum\pdfoutput>\z@ \mermaid@needabsfalse \fi
      \fi
    \fi
  \fi
}
% DVI 出力や XeTeX で -output-directory を指定したときは，\includegraphics に絶対パスを渡す．
% XeTeX の画像読み込み，extractbb（BoundingBox の取得），dvipdfmx は出力ディレクトリを
% 探さないため，相対パスでは出力ディレクトリ内の図を見つけられない
\let\mermaid@includeprefix\@empty
\newif\ifmermaid@prefixready
\def\mermaid@setincludeprefix{%
  \ifmermaid@prefixready\else
    \global\mermaid@prefixreadytrue
    \mermaid@checkneedabs
    \ifmermaid@needabs
      \edef\mermaid@outdirfile{mermaid/\jobname-mermaid-outdir.txt}%
      \ShellEscape{\mermaid@shoutdir\space if [ "$D" != . ]; then cd "$D" && pwd; fi > "$D/\mermaid@outdirfile"}%
      \begingroup
        \endlinechar=\m@ne
        \openin\mermaid@login=\mermaid@outdirfile\relax
        \ifeof\mermaid@login\else
          % パスに _ や ~ が含まれても壊れないよう，\readline で文字として読む
          \readline\mermaid@login to \mermaid@outdirline
          \closein\mermaid@login
          \ifx\mermaid@outdirline\@empty\else
            \xdef\mermaid@includeprefix{\mermaid@outdirline/}%
          \fi
        \fi
      \endgroup
      \ifx\mermaid@includeprefix\@empty\else
        \mermaid@log{-output-directory without direct PDF output: including diagrams by absolute path (\mermaid@includeprefix)}%
      \fi
    \fi
  \fi
}
% 絶対パスで取り込むときは，BoundingBox を書いた .xbb を図の隣に用意しておく．
% dvipdfmx.def はパスを引用符で囲まずに extractbb を呼ぶため，空白を含むパスで失敗する．
% .xbb には作成日時が入るので，PDF より古いときだけ作り直す（latexmk の収束を妨げないため）
\def\mermaid@makexbb{%
  \ifx\mermaid@includeprefix\@empty\else
    \ShellEscape{\mermaid@shoutdir\space O="$D/\mermaid@base.pdf"; X="$D/\mermaid@base.xbb"; [ "$X" -nt "$O" ] || extractbb -O "$O" > "$X"}%
  \fi
}
% #1: TeX から見た入力パス，#2: シェルに渡す入力パス，#3: TeX から見た出力パス
% TeX は -output-directory 配下のファイルも見つけるが，シェルは作業ディレクトリで
% 動くため，mermaid/ 配下のパスには "$D/" を前置して渡す
\def\mermaid@run#1#2#3{%
  \mermaid@resolverenderer
  \edef\mermaid@infile{#1}%
  \edef\mermaid@outfile{#3}%
  \edef\mermaid@base{mermaid/\jobname-mermaid-\the\c@mermaid@diag}%
  \edef\mermaid@stdout{\mermaid@base.out}%
  \edef\mermaid@stderr{\mermaid@base.err}%
  \mermaid@mkdir
  \mermaid@log{----------}%
  \mermaid@log{diagram \the\c@mermaid@diag: #1 -> #3}%
  \mermaid@logcwd
  \mermaid@log{renderer (resolved): \mermaid@resolvedrenderer}%
  \mermaid@log{pdf-fit: \ifx\mermaid@pdffit\@empty off\else\mermaid@pdffit\fi}%
  \ifx\mermaid@xargs\@empty\else
    \mermaid@log{CLI extra args: \mermaid@xargs}%
  \fi
  % 前回描画時のソースとオプションを比べるための見出し部分を書き出す
  \immediate\openout\mermaid@optsout=\mermaid@base.opts\relax
  \immediate\write\mermaid@optsout{renderer: \expandafter\detokenize\expandafter{\mermaid@resolvedrenderer}}%
  \immediate\write\mermaid@optsout{pdffit: \expandafter\detokenize\expandafter{\mermaid@pdffit}}%
  \immediate\write\mermaid@optsout{args: \expandafter\detokenize\expandafter{\mermaid@xargs}}%
  \immediate\write\mermaid@optsout{}%
  \immediate\closeout\mermaid@optsout
  % 内容（ソースとオプション）が前回描画時と同じならスキップする．Mermaid CLI の PDF は
  % 毎回バイト列が変わるため，不要な再描画は latexmk の収束を妨げる
  \edef\mermaid@shcmd{%
    \mermaid@shoutdir\space
    I="#2"; O="$D/\mermaid@outfile"; S="$D/\mermaid@base.stamp"; N="$S.new";
    cat "$D/\mermaid@base.opts" "$I" > "$N" 2>/dev/null;
    if [ -f "$O" ] && cmp -s "$N" "$S"; then
      rm -f "$N";
      echo "renderer skipped: source and options unchanged since last rendering" > "$D/\mermaid@stdout";
    else
      F="\expandafter\detokenize\expandafter{\mermaid@pdffit}";
      if [ "$F" = auto ]; then
        F=; M=$(\expandafter\detokenize\expandafter{\mermaid@resolvedrenderer} --version 2>/dev/null | sed -n '\mermaid@sedmajor' | head -n 1);
        if [ -n "$M" ] && [ "$M" -lt 12 ]; then F=-f; fi;
        echo "pdf-fit: auto -> '$F' (Mermaid CLI major version: '$M')" > "$D/\mermaid@stdout";
      else
        : > "$D/\mermaid@stdout";
      fi;
      rm -f "$O" "$S";
      \expandafter\detokenize\expandafter{\mermaid@resolvedrenderer} $F \expandafter\detokenize\expandafter{\mermaid@xargs} -i "$I" -o "$O" >>"$D/\mermaid@stdout" 2>"$D/\mermaid@stderr" && [ -f "$O" ] && mv -f "$N" "$S";
    fi
  }%
  \mermaid@log{full shell command: \mermaid@shcmd}%
  \ifcsname pdffilesize\endcsname
    \IfFileExists{\mermaid@infile}{%
      \mermaid@log{.mmd size (bytes): \pdffilesize{\mermaid@infile}}%
    }{%
      \mermaid@log{.mmd size (bytes): (missing)}%
    }%
  \else
    \mermaid@log{.mmd size (bytes): unavailable (no pdffilesize on this engine)}%
  \fi
  \ifcsname pdfresettimer\endcsname\pdfresettimer\fi
  \immediate\write18{\mermaid@shcmd}%
  \ifcsname pdfelapsedtime\endcsname
    \mermaid@log{timing: \the\pdfelapsedtime\space ms (pdfelapsedtime)}%
  \else
    \mermaid@log{timing: (pdfelapsedtime not available on this engine)}%
  \fi
  \IfFileExists{\mermaid@outfile}{%
    \mermaid@log{renderer subprocess: finished (parsed as success)}%
    \ifcsname pdffilesize\endcsname
      \mermaid@log{output PDF size (bytes): \pdffilesize{\mermaid@outfile}}%
    \else
      \mermaid@log{output PDF size (bytes): unavailable}%
    \fi
    \mermaid@log{PDF header check: OK}%
    \mermaid@log{----------}%
  }{%
    \mermaid@log{expected output PDF missing after write18 (exit status not checked)}%
  }%
}
\newcount\c@mermaid@diag
\c@mermaid@diag=\z@
\def\mermaid@env@scanopt{%
  \@ifnextchar[%]
    {\mermaid@env@gotopt}%
    {\mermaid@env@startenv{}}%
}
\def\mermaid@env@gotopt[#1]{\mermaid@env@startenv{#1}}
\def\mermaid@env@startenv#1{%
  \def\mermaid@envgraphicsopts{#1}%
  \global\advance\c@mermaid@diag\@ne\relax
  \edef\mermaid@in{mermaid/\jobname-mermaid-\the\c@mermaid@diag.mmd}%
  \edef\mermaid@out{mermaid/\jobname-mermaid-\the\c@mermaid@diag.pdf}%
  \mermaid@mkdir
  \VerbatimOut{\mermaid@in}%
}
\newenvironment{mermaid}{%
  \catcode`\^^M=\active
  \mermaid@env@scanopt
}{%
  \endVerbatimOut
  \mermaid@run{\mermaid@in}{$D/\mermaid@in}{\mermaid@out}%
  \IfFileExists{\mermaid@out}{%
    \mermaid@setincludeprefix
    \mermaid@makexbb
    \begin{center}%
      \bgroup
      \ifx\mermaid@graphicsopts\@empty
        \ifx\mermaid@envgraphicsopts\@empty
          \def\mermaid@graphicsoptsmerged{}%
        \else
          \def\mermaid@graphicsoptsmerged{\mermaid@envgraphicsopts}%
        \fi
      \else
        \ifx\mermaid@envgraphicsopts\@empty
          \def\mermaid@graphicsoptsmerged{\mermaid@graphicsopts}%
        \else
          \def\mermaid@graphicsoptsmerged{\mermaid@graphicsopts,\mermaid@envgraphicsopts}%
        \fi
      \fi
      \edef\mermaid@tmp{%
        \egroup
        \noexpand\adjustbox{\mermaid@boxopts}{%
          \noexpand\includegraphics[\mermaid@graphicsoptsmerged]{\mermaid@includeprefix\mermaid@out}%
        }%
      }%
      \mermaid@tmp
    \end{center}%
  }{%
    \PackageError{mermaid}{%
      Output PDF was not produced. Check mermaid/\jobname-mermaid-\the\c@mermaid@diag.err for mmdc messages; %
      ensure -shell-escape and Mermaid CLI.%
    }{}%
  }%
}

% New command: \mermaidfile{<filename>}
\newcommand{\mermaidfile}[2][]{%
  \begingroup
    \edef\mermaid@in{#2}%
    \def\mermaid@envgraphicsopts{#1}%
    \global\advance\c@mermaid@diag\@ne\relax
    \edef\mermaid@out{mermaid/\jobname-mermaid-\the\c@mermaid@diag.pdf}%
    % 外部ファイルは作業ディレクトリ基準のパスなので，そのままシェルに渡す
    \mermaid@run{\mermaid@in}{\detokenize{#2}}{\mermaid@out}%
    \IfFileExists{\mermaid@out}{%
      \mermaid@setincludeprefix
      \mermaid@makexbb
      \begin{center}%
        \bgroup
        \ifx\mermaid@graphicsopts\@empty
          \ifx\mermaid@envgraphicsopts\@empty
            \def\mermaid@graphicsoptsmerged{}%
          \else
            \def\mermaid@graphicsoptsmerged{\mermaid@envgraphicsopts}%
          \fi
        \else
          \ifx\mermaid@envgraphicsopts\@empty
            \def\mermaid@graphicsoptsmerged{\mermaid@graphicsopts}%
          \else
            \def\mermaid@graphicsoptsmerged{\mermaid@graphicsopts,\mermaid@envgraphicsopts}%
          \fi
        \fi
        \edef\mermaid@tmp{%
          \egroup
          \noexpand\adjustbox{\mermaid@boxopts}{%
            \noexpand\includegraphics[\mermaid@graphicsoptsmerged]{\mermaid@includeprefix\mermaid@out}%
          }%
        }%
        \mermaid@tmp
      \end{center}%
    }{%
      \PackageError{mermaid}{%
        Output PDF was not produced. Check mermaid/\jobname-mermaid-\the\c@mermaid@diag.err for mmdc messages; %
        ensure -shell-escape and Mermaid CLI.%
      }{}%
    }%
  \endgroup
}
\endinput