我想知道一种在 PDFLaTeX(XeLaTeX 不可用)中在命令名称(宏名称)中使用西里尔字符的有效方法。
我发现,如果我将源代码保留为 Windows-1251 8 位字符编码,我就可以定义名称中带有西里尔字母的命令,如下所示:
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[cp1251]{inputenc}
\usepackage[russian]{babel}
\catcode`\л=11\relax
\catcode`\п=11\relax
\catcode`\к=11\relax
\newcommand{\лк}{«}
\newcommand{\пк}{»}
\begin{document}
\лк Золотой век\пк\ распространения славянской письменности относится ко времени царствования в Болгарии царя Симеона Великого.
\end{document}
我的第一个问题是:这个解决方案有多“稳健”?
另外,我想知道如何将所有西里尔字母的类别代码设置为 11,而无需逐一进行设置。
更新:我发现,如果我使用hyperref
此解决方案的包并在文档中创建部分,则生成的 PDF 文档大纲将在设置 catcode 11 的字母位置出现错误的字符。我想知道该如何处理这个问题。
更新 2:我发现有一个“非官方”的软件包russlh
正是采用了这种方法,但它的最新更新显然是在 2003 年,而且它似乎不适用于的最新版本hyperref
。
我的第二个问题是:如果我使用 UTF-8 作为源,我可以使用西里尔字母命令吗?
简单地重新编码示例文件并使用utf8
而不是cp1251
不起作用:
! Missing number, treated as zero.
<to be read again>
\protect
l.7 \catcode`\л
=11\relax
有一个相关问题带有 utf-8/特殊字符的命令名称,但我不太明白答案的这部分:
由于 pdfTeX 处理输入的方式,pdfTeX 的
\√
语法将无法工作(尽管理论上可以实现这一点)。
答案1
使用 UTF-8 等多字节编码,您想要做的事情基本上是不可能的(或者只能以非常脆弱的方式实现)。
它似乎使用 8 位编码(如 Windows-1251),但一个简单的示例将显示可能出现的问题:
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[cp1251]{inputenc}
\usepackage[russian]{babel}
\catcode`\ё=11\relax
\begin{document}
ё
\end{document}
(这应该以 Windows-1251 编码保存)。如果我运行它,输出是
原因很简单:在 Windows-1251(cp1251
LaTeX 术语)中,该字符的ё
编码为0xB8
,但 T2A 编码字体ҷ
在该位置有 (见下表)。通过将字节的 catcode 设置为 11,TeX 会将其解释为“在当前字体中0xB8
的位置打印字形”。0xB8
的工作inputenc
确实是0xB8
通过扩展使字节活跃\char"BC
(字符在 T2A 编码字体中所在的插槽ё
)。
答案2
作为替代方案,我建议定义一命令(带有 ascii 名称),以西里尔字母为参数,并使用 \detokenize 构建命令名:
\documentclass{article}
\usepackage[T2A]{fontenc}
\usepackage[russian]{babel}
\newcommand\cyrcmd[1]{\csname cyrcmd\detokenize{#1}\endcsname}
\makeatletter
\@namedef{cyrcmd\detokenize{лк}}{«}
\@namedef{cyrcmd\detokenize{пк}}{»}
%% more commands
\makeatother
\usepackage{hyperref}
\begin{document}
\tableofcontents
\section{\cyrcmd{лк}Золотой век\cyrcmd{пк}}
\cyrcmd{лк}Золотой век\cyrcmd{пк} распространения славянской письменности относится ко времени царствования в Болгарии царя Симеона Великого.
\end{document}
答案3
[这本质上是一个扩展的评论,因为虽然我曾经研究过这里列出的选项,但我实际上并没有实施其中任何一个。]
我相信你无法使用inputenc
传统的 TeX 引擎来做到这一点。
拉丁字母对于 TeX 来说是特殊的,因为它们本质上必须以 ASCII 编码才能使 TeX 工作(TeX 源代码中有一个段,在读取 ASCII 符号后,会将其从主机编码逐字翻译为 ASCII,以防 TeX 要在 EBCDIC 系统或类似的系统上运行,因为 TeX 原语需要像pt
或 这样的关键字,plus
或者to
将它们与文字 ASCII 码进行比较,类别就见鬼了),和所有 TeX 字体都将拉丁字母放置在与其 ASCII 代码点相等的字形编号处。这是因为 TeX 将“字母”(类别代码 11)字符视为具有内部 TeX 代码点的字符n(在现代 ASCII 兼容系统上,这通常意味着主机代码点n) 在段落中作为输出字形编号的指令n来自当前文本字体。这意味着内部 TeX 代码点(“输入编码”)和当前字体中的字形编号(“字体编码”)必须匹配。这也意味着 单个 TeX 字体中的字符数不得超过 2 8 = 256 个。
LaTeX 解决这个问题的常用方法有点儿像 hack 变通方法。首先,每个非 ASCII 代码点都被声明为“活动” (类别代码 13),也就是说,当 TeX 看到字符n和n> 128,则指示将其视为单字符宏(与遇到时产生不可中断空格的方式相同~
:有两行latex.ltx
写着\catcode`\~=13 \def~{\nobreakspace{}}
);这是通过中的\inputencoding
和完成的。其次,当设置输入编码时(当加载时),这些宏被定义为通过调用从(\@inpenc@loop
inputenc.sty
inputenc
\DeclareInputText
inputenc.sty
例如在 Windows-1251 文档中,字符 168(西里尔大写字母 IO)被定义为扩展为\CYRYO
)cp1251.def
。(扩展实际上是\IeC{\CYRA}
,但那这里并不重要。)这些众所周知的宏名构成了LaTeX 内部字符表示(LICR)。第三,当设置字体编码时(当babel
正在加载或被指示切换语言时,或者当您\fontencoding
明确更改时),该编码中存在的字符的 LICR 命令被定义为通过调用\DeclareTextSymbol
从latex.ltx
(例如在 T2A 字体中,\CYRYO
定义为发出字符 156 t2aenc.def
)。(实际设置更复杂,但这在这里并不重要,请ltoutenc.dtx
参阅来源2e了解详情。
UTF-8 等多字节编码更加复杂,因为编码的前导字节被定义为消耗输入流中所需数量的标记作为无界宏参数,然后查找并发出相应的 LICR 代码。这就是为什么当你忘记分隔作为未分隔参数传递给宏的单个 UTF-8 编码西里尔字母时,你会得到这么多有趣的错误的原因(例如*工作\textit ы
时失败\textit g
):西里尔字母不是一个单一的 TeX 标记,而且传递给命令的第一个标记会吃掉命令代码的任意部分,以尝试获取尾随字节。
为了能够在 TeX 命令中使用多个西里尔字母,它们需要转换为声明为“字母”(类别代码 11)的内部 TeX 代码点;这就是 TeX 扫描器处理反斜杠字符(或任何“转义”,类别代码 0,字符)的方式。所以我看到了几种实现你想要的方法,它们都相当不稳定:
- 使用单字节编码,如 Windows-1251、IBM 866 或(历史上最流行的)KOI8-R;避免将
inputenc
它们定义为字母;使用已经在必要编码中的字体(据我所知,现代 LaTeX 安装中没有)或通过定义一个按必要顺序组装不兼容字体的字符虚拟字体和字体编码; - 使用单字节编码或 UTF-8;避免
inputenc
使用(显然晦涩难懂的)编码TeX通过重新生成 LaTeX 格式并传递选项-enc
来扩展initex
;向 encTeX 解释您想要的输入编码,它将相应地调整 TeX 内核的输入翻译机制,并将该翻译的结果定义为字母;然后破解 LaTeX 内核以识别该翻译的结果; - 使用 UTF-8(仅使用);避免
inputenc
使用硬连线使用 UTF-8 的 TeX 引擎,例如特克斯或者LuaTeX;可能需要再次破解 LaTeX 内核,但我对这些引擎不太了解。
如果这听起来很模糊,那是因为它确实如此。我不知道如何详细地做到这一点,除非你有数百页的旧 TeX 文档,或者需要与真正古老的 TeX 安装保持兼容,否则移植你的文档(可能以编程方式使用足够好的解析器,如塑料甚至一堆好的正则表达式)可能更容易。