texjporg / uplatex

upLaTeX community edition
BSD 3-Clause "New" or "Revised" License
18 stars 0 forks source link

和文文字のコントロール・シンボルと \DeclareRobustCommand #3

Closed wtsnjp closed 6 years ago

wtsnjp commented 6 years ago

ZR さんのブログ記事で指摘されていた件です.

% uplatex, xelatex, lualatex で実行
\documentclass[autodetect-engine,a4paper]{bxjsarticle}
\usepackage{scsnowman}
% ☃のマフラーは赤色
\DeclareRobustCommand*{\☃}{\scsnowman[%
  hat,arms,buttons,snow,muffler=red,scale=1.3]}
\DeclareRobustCommand*{\!}{!}
\begin{document}
% 目次
\tableofcontents

% 本文
\section{{\TeX}とは何か}
アレ。
\section{\☃とは何か}
非アレ。
\section{\!とは何か}
非ナントカ。
\end{document}

上記の共通ソースを LuaLaTeX, XeLaTeX で実行した場合,aux ファイルには

\relax 
\@writefile{toc}{\contentsline {section}{\numberline {1}{T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX\bxjs@SE {}}とは何か}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {2}\☃とは何か}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {3}\!とは何か}{1}}

が書き込まれます.一方 upLaTeX で実行した場合は

\relax 
\@writefile{toc}{\contentsline {section}{\numberline {1}{T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX\bxjs@SE {}}とは何か}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {2}\☃  とは何か}{1}}
\@writefile{toc}{\contentsline {section}{\numberline {3}\!とは何か}{1}}

となり \☃ の後ろに余分な(無視されない)空白が入り,これが結果的に目次の表示をおかしくします.

この違いが生じる理由ですが,調べた限り XeLaTeX や LuaLaTeX が特別な処理を加えているわけではなく,例の記事に北川さんがコメントなさっているように「pTeX 系エンジンが単文字コントロール・シークエンスを特殊扱いしない」という仕様からくる挙動のようです.

wtsnjp commented 6 years ago

以下は個人的な意見です.

この問題に upLaTeX の範疇で(latex.ltx の \DeclareRobustCommand の定義を上書きするなどの方法で)特別に対策を講じることは原理上は可能だと思いますが,それがまた別の問題の原因とならない保証はありません(つい最近も texjporg/platex#55 のようなバグが発生しました).

この件についてはほとんどの場合 TeX 言語レベルでの回避はそれほど難しくないと考えられるので(例えば,和文文字のコントロール・シンボルを robust なコントロール・ワードの下請け命令に展開されるマクロにしてしまう回避策が考えられると思います)pTeX 系エンジンの仕様にしたがった upLaTeX の仕様ということにしてもいい気がしています.

aminophen commented 6 years ago

「pTeX 系エンジンが単文字コントロール・シークエンスを特殊扱いしない」という仕様

細かいですがここは “2バイト以上の単文字” が正確そうですね。また,例がたまたま☃️なだけで,この現象自体は 「 のような “記号扱いされる文字” なら何でも起きる(pLaTeX でも起きる)と思います。

あと,一連の議論で出てくる単語が混乱するので確認したいのですが,(edit: 修正)

「コントロール・ワード」が(1文字か2文字以上かにかかわらず)\catcode = 11 の文字からなる命令

「コントロール・シンボル」が \catcode = 11 以外の文字(“記号扱いされる文字”)からなる命令(これは事実上1文字命令ばかり)

で,両方合わせて「コントロール・シークエンス」 = 「制御綴」か。

aminophen commented 6 years ago

ちなみに TeX 言語レベルでの回避については,一番難しいのが「和文文字のコントロール・シンボルかどうかの判定」だろうと思うので,マクロレベルでの対処はしたくないです…。

wtsnjp commented 6 years ago

コントロール・ワード,コントロール・シンボル,コントロール・シークエンスの定義について,アミノフェンさんの認識と一致しています.

「TeX 言語レベルでの回避」というのは,私は “2バイト以上の単文字” のコントロール・シンボルを使う upLaTeX ユーザ側の話をしていました.

upLaTeX のマクロレベルで対応するのは,おそらく ad-hoc なコードにならざるを得ないと思うので私も反対です.

aminophen commented 6 years ago

(まちがって投稿ボタンを押したのでもう一回)

あと一点,よくわかっていないのですが,

「pTeX 系エンジンが単文字コントロール・シークエンスを特殊扱いしない」という仕様

から今回の挙動が直接の帰結として導けていないのですが,どういうことなのでしょうか? 実際 LuaTeX は(最近の \pdfprimitive のバグ (texjporg/tex-jp-build#29) から無縁だったように)単文字の命令を特別扱いしていませんが,それでも今回の件では問題が起きていないですよね。

aminophen commented 6 years ago

あと一点,よくわかっていないのですが,

もう少し具体的に書くと,「pTeX 系エンジンが単文字コントロール・シークエンスを特殊扱いしない」ことと「pTeX 系での以下の結果が XeTeX/LuaTeX と一致していない」ことが,私の中でまだ直接には結びついていないんですよね。

% plain TeX ソース
\def\+{A}
\def\+{A}
\def\X{\+}
\def\Y{\+}
% 記号類扱い → この時の挙動が問題
\ifx\kanjiskip\undefined
  \catcode`\+=12
  \catcode`\+=12
\else
  \catcode`\+=12
  \kcatcode`+=18
\fi
\show\X\relax\message{(\meaning\X)}
\show\Y\relax\message{(\meaning\Y)}
% 普通の文字扱い
\ifx\kanjiskip\undefined
  \catcode`\+=11
  \catcode`\+=11
\else
  \catcode`\+=11
  \kcatcode`+=17
\fi
\show\X\relax\message{(\meaning\X)}
\show\Y\relax\message{(\meaning\Y)}
\end
zr-tex8r commented 6 years ago

自分は次のように把握しています:

zr-tex8r commented 6 years ago

改めて整理しておきます。

【用語】

【問題】

zr-tex8r commented 6 years ago

ところで、最近はLaTeXレベル保護(\DeclareRobustCommand)の代わりにe-TeXのエンジンレベル保護(\protected)が使われることが多くなっているため、「仕様をどうするべきか」を考えるのであれば、後者についても留意すべきです。

エンジンレベル保護の場合、エンジンでの文字列化の仕様が(マクロを介さずに)直接絡んでくることになります。結局、「LaTeXで頑強な命令を作る」ためにエンジンレベル保護を使うのは、和文制御記号に関しては原理的に不可能ということになります。(エンジンの仕様を変えるのは非現実的だろう。)

このことを考慮に入れると、LaTeXレベルの保護(\DeclareRobustCommand)だけ対処する、というのは得策ではないと思えます。

t-tk commented 6 years ago

この通りだと思います。 和文の単文字の制御綴の取り扱いについて、upTeX では、pTeX の実装をそのまま受け継いでいるはずです。 (u)pTeX で、「トークン列の(=\string以外の)文字列化の際に、制御記号については「後に空白文字を補う」挙動が抑止される。」が出来るようになれば良いのでしょうか?

wtsnjp commented 6 years ago

ZR さんのまとめてくださった【問題】を読む限り(そして私の知識の範囲では)そのように思えます.

もし今後,pTeX 系列エンジンの「トークン列の文字列化」に関わる仕様を変更することを議論するのであれば,現状の

和文の制御記号は再トークン化した場合に完全には元に戻らない

という性質を利用(悪用?)しているケースがないかどうかを調べておいた方が良さそうです.

zr-tex8r commented 6 years ago

自分の考えとしては 「『気をつけよう!』で済ませればよい」 なんですけど、どうやらLaTeXユーザにも影響がある(つまり“気をつける”必要がある)らしいです。というのも、\protect も同じ問題を抱えているからです。

% pLaTeX文書
\documentclass[a4paper]{jsarticle}
\usepackage{otf}
\newcommand*{\☆}{\UTF{2603}}
\begin{document}
\tableofcontents
\section{{\TeX}と\protect\☆の関係}
一切ありません。
\end{document}
aminophen commented 6 years ago

LaTeXレベル保護(\DeclareRobustCommand) e-TeXのエンジンレベル保護(\protected) \protect も同じ問題を抱えている

いずれも tex.web l.5586 あたりの関数 print_cs

@<Basic printing...@>=
procedure print_cs(@!p:integer); {prints a purported control sequence}
begin if p<hash_base then {single character}
  if p>=single_base then
    if p=null_cs then
      begin print_esc("csname"); print_esc("endcsname"); print_char(" ");
      end
    else  begin print_esc(p-single_base);
      if cat_code(p-single_base)=letter then print_char(" ");
      end
  else if p<active_base then print_esc("IMPOSSIBLE.")
@.IMPOSSIBLE@>
  else print(p-active_base)
else if p>=undefined_control_sequence then print_esc("IMPOSSIBLE.")
else if (text(p)<0)or(text(p)>=str_ptr) then print_esc("NONEXISTENT.")
@.NONEXISTENT@>
else  begin print_esc(text(p));
  print_char(" ");
  end;
end;

の分岐で,最後の else に落ちるので print_char(" "); されるという「根っこ」は同じですね。

aminophen commented 6 years ago

和文のコントロールシンボル

pTeX だけを考慮したコードになっていますが,ちょっと ptex-base.ch をいじってみました。今後は upLaTeX の issue ではないと思いますので,より適切そうな texjporg/tex-jp-build#37 に飛びます。

aminophen commented 6 years ago

和文の制御記号は再トークン化した場合に完全には元に戻らない という性質を利用(悪用?)しているケースがないかどうかを調べておいた方が良さそうです.

和文の制御記号 (control symbol) の表示変更を r45950 でコミットしたので,ビルドできる方(または W32TeX [2017/12/01] 以降を使える方)は試していただき,上記のようなケースが見当たらないか調べていただけると助かります。

# abenori さんの jlreq.cls のように,従来から「制御記号の後の空白を吸収するため \ignorespaces する」というような工夫していたケースは問題ないはずだと思っています。