请考虑以下 Plain TeX 手稿
\catcode9=12\relax% ASCII 9 is tab
.\ \ .\par%
.
.%
\bye
无法辨别,但手稿第三行的点后面有一个制表符。为了确认,以下是该文件的字节内容(每个字节由两位十六进制数表示):
09
您可以在第二行看到以十六进制数表示的制表符。
本稿排版如下:
. .
. .
观察第一行上的点之间似乎有两个空格,但第二行上的点之间只有一个空格。
不过,我希望第二行上的点之间也有两个空格:一个用于制表符,一个用于行尾的回车符。
解释这一点的一种方法是 egreg 在这个答案(针对另一个问题),其中说:
TeX 每次读取一条记录(输入文件中的一行,或多或少),并丢弃记录结束符以及紧接在记录结束符之前的所有空格或制表符
这意味着 TeX 引擎的“胃”从未见过制表符,这解释了排版结果。
然而,就制表符而言,TeXbook(第 20 次印刷,Addison-Wesley 1991)并不支持此引述。事实上,TeXbook 如此描述同一处理阶段(第 46 页):
TeX 删除
<space>
输入行最右端的所有字符(数字 32)。
注意数字 32 的规范,它是 ASCII 空间。
输入例程的 TeX 源代码(即input_ln
,在 的第 31 节第 16 页中定义texdoc tex
)也对尾随非常讲究。空格:
行尾的空白被删除;因此,要么
last=first
(这种情况下该行完全是空白的)要么buffer[last − 1]≠"␣"
。
那么为什么第二行排版的点之间只有一个空格?
顺便说一下,如果将字体改为cmtt
,例如:
\font\myfont=cmtt14\myfont%
\catcode9=12%
.\ \ .\par%
.
.%
\bye
然而,两条线上两个点之间的距离是相同的,正如 Steven B. Segletes 的实验显示,该选项卡不被引擎的“胃”所看到。
答案1
埃格雷格“民间传说”的一些代码发现回答。
TeX 会删除输入行末尾的“空白”。这一操作在很早的阶段完成,在读取行之后,在考虑类别代码之前,输入字符会被标记化。
最初,这些“空白”只是空格,但 TeX Live 或 MiKTeX 等 TeX 发行版将它们扩展为包含制表符(水平制表符)。代码片段显示了 TeX 和 pdfTeX 的行为。未显示的是 XeTeX 和 LuaTeX,它们也会删除输入行末尾的空格和制表符。
代码片段来自 TeX Live(2016)。
texk/web2c/tex.web
:@ The |input_ln| function brings the next line of input from the specified file [...] Trailing blanks are removed from the line; [...] @p function input_ln(var f:alpha_file;@!bypass_eoln:boolean):boolean; {inputs the next line or returns |false|} var last_nonblank:0..buf_size; {|last| with trailing blanks removed} begin if bypass_eoln then if not eof(f) then get(f); {input the first character of the line into |f^|} last:=first; {cf.\ Matthew 19\thinspace:\thinspace30} if eof(f) then input_ln:=false else begin last_nonblank:=first; while not eoln(f) do begin if last>=max_buf_stack then begin max_buf_stack:=last+1; if max_buf_stack=buf_size then @<Report overflow of the input buffer, and abort@>; end; buffer[last]:=xord[f^]; get(f); incr(last); if buffer[last-1]<>" " then last_nonblank:=last; end; last:=last_nonblank; input_ln:=true; end; end;
原始的 TeX 只会删除输入行末尾的空格。但是,Pascal 版本
input_ln
将被更高效的 C 版本覆盖,请参阅下一个代码片段。texk/web2c/tex.ch
是以下的变更文件tex.web
:@x [3.31] l.933 - Do `input_ln' in C. @p function input_ln(var f:alpha_file;@!bypass_eoln:boolean):boolean; [...] end; @y We define |input_ln| in C, for efficiency. [...] @z
texk/web2c/lib/texmfmp.c
:/* Read a line of input as efficiently as possible while still looking like Pascal. We set `last' to `first' and return `false' if we get to eof. Otherwise, we return `true' and set last = first + length(line except trailing whitespace). */ #ifndef XeTeX /* for XeTeX, we have a replacement function in XeTeX_ext.c */ boolean input_line (FILE *f) { [...] /* Trim trailing whitespace. */ while (last > first && ISBLANK (buffer[last - 1])) --last; [...] }
texk/kpathsea/c-ctype.h
:#ifndef isblank #define isblank(c) ((c) == ' ' || (c) == '\t') #endif #define ISBLANK(c) (isascii (c) && isblank ((unsigned char)c))
isblank
测试空间和制表符,因此两个都在输入行的末尾被删除。texk/web2c/ChangeLog
:Thu Oct 16 20:39:27 1997 Olaf Weber <...> * `tex.ch`: [...] Also, various changes for e-TeX (small rearrangements, introduces Init..Tini, remove tabs and trailing blanks). From Peter Breitenlohner <...>.
这个变化由来已久,可以追溯到上个世纪二十年前。
答案2
我使用的文件是
\catcode9=12
.
. %
.
\bye
3号线有.<tab>
,4号线有.<tab>%
,5号线有.<tab><space>
> hexdump tabs.tex
0000000 5c 63 61 74 63 6f 64 65 39 3d 31 32 0a 0a 2e 09
0000010 0a 2e 09 25 0a 2e 09 20 0a 5c 62 79 65 0a
000001e
排版如下pdftex
:
只有<tab>
后面的%
保留,因为 TeX Live 实现的 TeX 会删除尾随空格和行中的标签,无论其 catcode 如何。我试图找到参考资料,但显然这应该被视为民间传说。
前两个句点之间的空格由行尾添加。
2019 年更新
随着 TeX Live 2019 版的发布,TeX 引擎不再删除尾随的制表符,而只删除空格,您得到的输出是
答案3
修改后的答案
我发现,即使我将 TAB 复制并粘贴到 TeXworks 中,编辑器本身也会将我的原始答案转换为空格。因此,我使用了另一个我知道会保留文件中的 TAB 字符的编辑器,结果显示键盘 TAB 的行为与^^I
“TeX TAB”几乎一样。
如果 TAB 不在行尾,则“键盘 TAB”和“TeX TAB”的行为相同。但是,如果 TAB 在行尾,则“键盘 TAB”将被视为空格,而“TeX TAB”仍将被视为空格,但 TeX 会对其进行重新定义。
结论:
键盘 TABS 和 TeX-TAB(
^^I
)似乎被视为相同,除了输入行的末尾。键盘 TABS 在行末被删除(David 所说的),而
^^I
TABS 则不会。使用 TeX TAB
^^I
在代码中表示 TABS,因为否则编辑器很容易在您的键盘 TABS 上进行自动转换。
MWE(警告:将此 MWE 复制/粘贴到您的编辑器中可能会导致制表符转换为空格):
With the TAB as defined by \TeX\par
% TWO EXPLICIT SPACES
x\ \ x\par%
% THE FOLLOWING PUTS AN EMPTY GROUP AFTER THE "KEYBOARD-TAB"; RESULT = 2 SPACES
x {}
x\par%
% THE FOLLOWING TRAILS WITH A KEYBOARD TAB (WHAT THE OP TRIED); RESULT = 1 SPACE
x
x\par
% THE FOLLOWING TRAILS WITH A "TeX-TAB"; RESULT = 1 SPACE
x^^I
x\par
With the TAB as catcode 12:\par
\catcode`\^^I=12 %
% TWO EXPLICIT SPACES
x\ \ x\par%
% THE FOLLOWING PUTS AN EMPTY GROUP AFTER THE "KEYBOARD-TAB"; RESULT = TAB GLYPH + SPACE
x {}
x\par%
% THE FOLLOWING TRAILS WITH A KEYBOARD TAB (WHAT THE OP TRIED); RESULT = 1 SPACE
x
x\par
% THE FOLLOWING TRAILS WITH A "TeX-TAB"; RESULT = TAB GLYPH PLUS SPACE
x^^I
x\par
\catcode`\^^I=\active %
\def^^I{\space}
With the TAB as an active space
% TWO EXPLICIT SPACES
x\ \ x\par%
% THE FOLLOWING PUTS AN EMPTY GROUP AFTER THE "KEYBOARD-TAB"; RESULT = 2 SPACES
x {}
x\par%
% THE FOLLOWING TRAILS WITH A KEYBOARD TAB (WHAT THE OP TRIED); RESULT = 1 SPACE
x
x\par
% THE FOLLOWING TRAILS WITH A "TeX-TAB"; RESULT = 2 SPACES
x^^I
x\par
\catcode`\^^I=\active %
\def^^I{Q}
With the TAB as an active Q
% TWO EXPLICIT SPACES
x\ \ x\par%
% THE FOLLOWING PUTS AN EMPTY GROUP AFTER THE "KEYBOARD-TAB"; RESULT = Q + SPACE
x {}
x\par%
% THE FOLLOWING TRAILS WITH A KEYBOARD TAB (WHAT THE OP TRIED); RESULT = 1 SPACE
x
x\par
% THE FOLLOWING TRAILS WITH A "TeX-TAB"; RESULT = Q + SPACE
x^^I
x\par
\bye
原始答案(被我的编辑欺骗了)
原始答案被删除了,因为当我将 TAB 粘贴到输入文件时,编辑器欺骗了我……编辑器自动转换为空格。
我应该注意到 TeXbook 在第 8、45、369-370 和 391 页讨论了 TAB 字符,正如我在对 OP 的评论中所指出的那样。
答案4
该行为已被确认为一个错误戴维·福克斯以及 TeX Live 的卡尔·贝利告诉我这个问题会在 web2c 中得到修复。朱利安·吉尔贝追踪到这个漏洞的来源与 Heiko Oberdiek 在他的回答。
以下是 Fuchs 先生对此错误的看法。
[...] 是的,这似乎是移植错误;正确的输出由两行组成:“点空格空格点”,“点 PSI 空格点”。(大写 PSI 字符位于 cmr10 的位置 9,制表符在 ascii 中当然是 9,输入文件的第一行设置了 catcode 以使制表符可以排版。)
一个微妙之处是,正如在卷 B 中明确规定的,TeX 根本不接受以下的任何字符
<space>
,包括<tab>
;所以纯粹的?/极简主义的?/基本的?/名义上的?TeX 在这个输入上只是立即抱怨制表符:! 文本行包含无效字符。l.3.^^?
但是模块 23 非常明确地说明了“获取最宽松的字符集”的正确方法,每个端口通常都会这样做,并且正是在这种背景下提出了上述“正确行为”的主张。
最后,有人可能会想知道为什么 TeX 会删除尾随空格而不删除其他字符,包括制表符。答案是它最初没有删除任何字符。但是我们想要支持的系统(特别是 IBM 的 OS360 和 VM/CMS)对文本文件有固定记录约定。因此,通常,将 .tex 文件移动到 IBM 大型机会导致系统将每行填充为 80 个字符,并添加尾随空格字符。我们希望确保往返此类系统的任何往返都不会创建一个输入文件,该文件有可能在平台之间发生神秘的行为变化(例如,在某些逐字模式下),而最好的解决方案是让 TeX 始终忽略尾随空格字符,这样它就不会受到它们自动出现的影响。但没有理由删除任何其他字符,包括制表符。