TeX Alchemist Online

TeX のこと,フォントのこと,Mac のこと

品詞記号出力マクロを設計しよう

この記事は TeX & LaTeX Advent Calendar 2021 の5日目の記事です。4日目は h20y6m さんでした。6日目も自分の記事です。


今回は,英和辞典とか単語帳とかでよく見かける,次のような品詞記号を出力するマクロを設計してみましょう。

f:id:doraTeX:20211205150502p:plain

要は単に「文字を角丸長方形で囲む」だけなのですが,気をつけるべき点が結構あります。

okumacro パッケージの \keytop を使う方法

最もシンプルな手としては,okumacro パッケージの \keytop を使う手があります。

【入力】

% upLaTeX + dvipdfmx
\documentclass[dvipdfmx]{jlreq}
\usepackage{okumacro}
\begin{document}
essential \keytop{} 本質的☃な
\end{document}

【出力】

f:id:doraTeX:20211205151016p:plain

とりあえずそれらしくなりましたが,ちょっとイマイチですね。\keytop では,昔ながらの picture 環境を用いて四分円を描画しています。今時ならば,TikZ を用いてカッコよく描画しましょう。

TikZ を用いた描画

【入力】

\documentclass[dvipdfmx]{jlreq}
\usepackage{tikz}

\newcommand{\品詞}[1]{%
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\small]
    \node[draw=red, rectangle, rounded corners=1.5pt,
          text=red,
          fill=red!20!white,
          line width=.4pt, inner sep=1pt, outer sep=0pt
          ] (A) {#1};
  \end{tikzpicture}%
}

\begin{document}
essential \品詞{} 本質的☃な
\end{document}

【出力】

f:id:doraTeX:20211205151618p:plain

とりあえずいい感じになりました。TeX & LaTeX Advent Calendar 20213日目の記事にあったように,baseline=(A.base) によってノードのベースラインを外側のベースラインと合わせています。

また,中身が伸びてくると,横方向にいい感じに広がってゆきます。

f:id:doraTeX:20211205152425p:plain

不満点の解決(その1)~中身が欧文文字の場合~

現状だと,中身に欧文文字を入れたときにちょっと小さい気がします。

f:id:doraTeX:20211205153733p:plain

そこで,minimum width を指定しましょう。\Cwd1zw に相当する長さになります。また,ノードの中に \vphantom{あ} を仕込んで,和文文字分同等の高さを確保します。

【入力】

\documentclass[dvipdfmx]{jlreq}
\usepackage{tikz}

\newcommand{\品詞}[1]{%
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\small]
    \node[draw=red, rectangle, rounded corners=1.5pt,
          text=red,
          fill=red!20!white,
          line width=.4pt, inner sep=1pt, outer sep=0pt,
          minimum width=\Cwd+1pt,
          ] (A) {\vphantom{}#1};
  \end{tikzpicture}%
}

\begin{document}
essence \品詞{U} 本質☃
\end{document}

【出力】

f:id:doraTeX:20211205154646p:plain

これで和文文字の場合と同様の正方形型の出力となりました。

不満点の解決(その2)~色指定オプションを設ける~

現状では,色が固定されてしまっていますので,ユーザーが色を指定できるオプションを設けましょう。xkeyval パッケージを使うと便利です。

qiita.com

【入力】

\documentclass[dvipdfmx]{jlreq}
\usepackage{tikz}
\usepackage{xkeyval}

\makeatletter
\define@cmdkeys{品詞}{draw,text,fill}

%% デフォルト値
\def\cmdKV@品詞@draw{red}
\def\cmdKV@品詞@fill{red!20!white}
\def\cmdKV@品詞@text{red}

\newcommand{\品詞}[2][]{%
  \begingroup  
  \setkeys{品詞}{#1}%
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\small]
    \node[draw=\cmdKV@品詞@draw, rectangle, rounded corners=1.5pt,
          text=\cmdKV@品詞@text,
          fill=\cmdKV@品詞@fill,
          line width=.4pt, inner sep=1pt, outer sep=0pt,
          minimum width=\Cwd+1pt,
          ] (A) {\vphantom{}#2};
  \end{tikzpicture}%
  \endgroup
}
\makeatother

\begin{document}
essence \品詞{} 本質☃\par
essential \品詞[draw=blue, fill=blue!20!white, text=blue]{} 本質的☃な
\end{document}

【出力】

f:id:doraTeX:20211205163043p:plain

不満点の解決(その3)~周囲の文字サイズに応じて相似拡大されるように~

現状の定義では,品詞記号の文字サイズが \small 固定だったり,rounded corners などのパラメータ値が具体的なポイント数指定となっていたりと,周囲の文字サイズが変化することを想定していません。たとえば,次のように \Huge をかぶせると,品詞記号だけサイズ変更されません。

【入力】

\Huge
essential \品詞{} 本質的☃な

【出力】 f:id:doraTeX:20211205174128p:plain

そこで,現在の文字サイズを取得し,それに対する相対値で各種パラメータの値を動的に決定するようにしましょう。

現在の文字サイズは \f@size で取得できますので,それを基準にパラメータの数値を相対的に指定しましょう。

\dimexpr では長さに対して小数点を含む係数をかけられないので,\pgfmathparse の結果を格納して利用しています。

【入力】

\documentclass[dvipdfmx]{jlreq}
\usepackage{tikz}
\usepackage{xkeyval}

\makeatletter
\define@cmdkeys{品詞}{draw,text,fill}

%% デフォルト値
\def\cmdKV@品詞@draw{red}
\def\cmdKV@品詞@fill{red!20!white}
\def\cmdKV@品詞@text{red}

\newcommand{\品詞}[2][]{%
  \begingroup
  \setkeys{品詞}{#1}%
  \pgfmathparse{0.9*\f@size}\edef\品詞@fontsize{\pgfmathresult}%
  \pgfmathparse{0.15*\f@size}\edef\品詞@roundedcorners{\pgfmathresult}%
  \pgfmathparse{0.04*\f@size}\edef\品詞@linewidth{\pgfmathresult}%
  \pgfmathparse{0.1*\f@size}\edef\品詞@innersep{\pgfmathresult}%
  \pgfmathparse{1.1*\f@size}\edef\品詞@minimumwidth{\pgfmathresult}%
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\fontsize{\品詞@fontsize pt}{\baselineskip}\selectfont]
    \node[draw=\cmdKV@品詞@draw, rectangle, rounded corners=\品詞@roundedcorners pt,
          text=\cmdKV@品詞@text,
          fill=\cmdKV@品詞@fill,
          line width=\品詞@linewidth pt,
          inner sep=\品詞@innersep pt, outer sep=0pt,
          minimum width=\品詞@minimumwidth pt,
          ] (A) {\vphantom{}#2};
  \end{tikzpicture}%
  \endgroup
}
\makeatother

\begin{document}
\parindent=0pt
essence \品詞{} 本質☃\par
\Huge
essential \品詞{} 本質的☃な
\end{document}

【出力】 f:id:doraTeX:20211205183145p:plain

これで周囲の文字サイズに応じて適切な相似拡大がかかるようになりました。

不満点の解決(その4)~適切な字間空白が入るように~

現状だと,品詞記号の前後に \kanjiskip\xkanjiskip の挿入がなされないため,前後のスペースの入り方が不適切です。

【入力】

\fboxsep=0pt
\framebox[5cm][s]{名詞の\品詞{C}と\品詞{U}の違い}

【出力】 f:id:doraTeX:20211205164214p:plain

これは,以前のこちらの記事で論じた問題です。

doratex.hatenablog.jp

この問題を解決するには,現在ならばエンジンを問わずに使えるBXghostパッケージが便利です。\jghostguarded{...} で囲んでおけば,和文文字扱いされて,前後に適切なスペースが入ります。

upLaTeX + dvipdfmx + jsarticle.cls の場合

upLaTeX + dvipdfmx + jsarticle.cls の場合のサンプルです。

【入力】

% upLaTeX + dvipdfmx
\documentclass[dvipdfmx,uplatex]{jsarticle}
\usepackage{tikz}
\usepackage{xkeyval}
\usepackage{bxghost}

\makeatletter
\define@cmdkeys{品詞}{draw,text,fill}

%% デフォルト値
\def\cmdKV@品詞@draw{red}
\def\cmdKV@品詞@fill{red!20!white}
\def\cmdKV@品詞@text{red}

\newcommand{\品詞}[2][]{%
  \jghostguarded{% 前後に和文ゴースト挿入
  \begingroup
  \setkeys{品詞}{#1}%
  \pgfmathparse{0.9*\f@size}\edef\品詞@fontsize{\pgfmathresult}%
  \pgfmathparse{0.15*\f@size}\edef\品詞@roundedcorners{\pgfmathresult}%
  \pgfmathparse{0.04*\f@size}\edef\品詞@linewidth{\pgfmathresult}%
  \pgfmathparse{0.1*\f@size}\edef\品詞@innersep{\pgfmathresult}%
  \pgfmathparse{1.1*\f@size}\edef\品詞@minimumwidth{\pgfmathresult}%
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\fontsize{\品詞@fontsize pt}{\baselineskip}\selectfont]
    \node[draw=\cmdKV@品詞@draw, rectangle, rounded corners=\品詞@roundedcorners pt,
          text=\cmdKV@品詞@text,
          fill=\cmdKV@品詞@fill,
          line width=\品詞@linewidth pt,
          inner sep=\品詞@innersep pt, outer sep=0pt,
          minimum width=\品詞@minimumwidth pt,
          ] (A) {\vphantom{}#2};
  \end{tikzpicture}%
  \endgroup
  }%
}
\makeatother

\begin{document}
\fboxsep=0pt
\framebox[5cm][s]{名詞の\品詞{C}と\品詞{U}の違い}\par
\end{document}

【出力】

f:id:doraTeX:20211205165444p:plain

LuaLaTeX + jlreq.cls の場合

LuaLaTeX + jlreq.cls の場合も同様に上手くいきます。

% lualatex
\documentclass{jlreq}
\usepackage{tikz}
\usepackage{xkeyval}
\usepackage{bxghost}

(以下同じ)

upLaTeX + jlreq.cls の場合

この項目は古い情報です。現在では,BXghostパッケージのアップデートにより,upLaTeX + jlreq.cls の場合も自動的にうまく対応されるようになりました。解決策の技術的アイデアの記録として,古い記述も残しておきます。

BXghostパッケージは,全角スペースを用いてゴースト挿入を実現していますが,これは upLaTeX + jlreq.cls の場合には,JFM の違いによりそのままでは機能しません。そこで,TeX Forum の本田さんのアイデアに基づき,全角スペース出力部分を upTeX 標準の upjisr-h.tfm を使うように改変しておけばOKです。

% upLaTeX + dvipdfmx
\documentclass[dvipdfmx]{jlreq}
\usepackage{tikz}
\usepackage{xkeyval}
\usepackage{bxghost}

\makeatletter
\DeclareFontFamily{JY2}{jismin}{}
\DeclareFontFamily{JT2}{jismin}{}
\DeclareFontShape{JY2}{jismin}{m}{n}{<->s*[0.92469]upjisr-h}{}
\DeclareFontShape{JT2}{jismin}{m}{n}{<->s*[0.92469]upjisr-v}{}
\def\usejismin{\usekanji{\k@encoding}{jismin}{m}{n}}

% bxghost.sty の内部命令 \bxqgg@jghostguarded@a の定義を改変し,全角スペース出力部分だけ upjisr-h.tfm に変更
\edef\bxqgg@jghostguarded@a#1{%
  \bgroup
    \noexpand\usejismin
    \bxqgg@fwsp\bxqgg@kern@m@ne@zw
  \egroup
  #1%
  \bgroup
    \noexpand\usejismin
    \bxqgg@kern@m@ne@zw\bxqgg@fwsp
  \egroup
}

(以下同じ)

不満点の解決(その5)~upLaTeX + dvipdfmx での縦組対応~

現状のものだと,upLaTeX + dvipdfmx で縦組するとひどいことになります(LuaLaTeXの場合は現状のままで問題ありません)。これは,TikZ のバックエンドにある pgf の dvipdfmx への対応が完全ではないからでしょう。

upLaTeX + jlreq.cls の場合

f:id:doraTeX:20220413231310p:plain:w80

そこで,TikZを使う部分を横組にすることで,「縦組の中でのTikZ」自体を避けるようにするとよいでしょう。(u)pTeX の独自プリミティブである \ifydir を用いて挙動を場合分けする手が使えます。

【入力】

% uplatex + jlreq.cls + 縦組 + dvipdfmx
\documentclass[dvipdfmx,tate]{jlreq}
\usepackage{tikz}
\usepackage{xkeyval}
\usepackage{bxghost}

\makeatletter
\define@cmdkeys{品詞}{draw,text,fill}

%% デフォルト値
\def\cmdKV@品詞@draw{red}
\def\cmdKV@品詞@fill{red!20!white}
\def\cmdKV@品詞@text{red}

\newcommand{\品詞}[2][]{%
  \jghostguarded{% 前後に和文ゴースト挿入
  \begingroup
  \setkeys{品詞}{#1}%
  \pgfmathparse{0.9*\f@size}\edef\品詞@fontsize{\pgfmathresult}%
  \pgfmathparse{0.15*\f@size}\edef\品詞@roundedcorners{\pgfmathresult}%
  \pgfmathparse{0.04*\f@size}\edef\品詞@linewidth{\pgfmathresult}%
  \pgfmathparse{0.1*\f@size}\edef\品詞@innersep{\pgfmathresult}%
  \pgfmathparse{1.1*\f@size}\edef\品詞@minimumwidth{\pgfmathresult}%
  \ifydir
    \def\品詞@node{\vphantom{}#2}%
  \else
    \def\品詞@node{\mbox{\tate\vphantom{}#2}}%
  \fi
  \mbox{\yoko
  \begin{tikzpicture}[baseline=(A.base),font=\sffamily\fontsize{\品詞@fontsize pt}{\baselineskip}\selectfont]
    \node[draw=\cmdKV@品詞@draw, rectangle, rounded corners=\品詞@roundedcorners pt,
          text=\cmdKV@品詞@text,
          fill=\cmdKV@品詞@fill,
          line width=\品詞@linewidth pt,
          inner sep=\品詞@innersep pt, outer sep=0pt,
          minimum width=\品詞@minimumwidth pt,
          ] (A) {\品詞@node};
  \end{tikzpicture}}%
  \endgroup
  }%
}
\makeatother

\begin{document}

これは\品詞{}と\品詞{形容}です。

\end{document}

【出力】

f:id:doraTeX:20220413231619p:plain:w50

完成版

以上で完成した命令を Overleaf に置いておきます。

LuaLaTeX + jlreq.cls の場合

www.overleaf.com

upLaTeX + jlreq.cls の場合

www.overleaf.com