问号 ? 与感叹号 !,是极少数拥有上下颠倒搭档的标点——西班牙语的 ¿ 与 ¡。在 LaTeX 里,只要在符号后面敲一个反引号,用合字就能得到它们;而鲜为人知的是,这个合字在 Knuth 最初的 OT1 编码和 T1 里同样有效。OT1 为此付出的代价是:在正文里敲 <,出来的却是 ¡。本页将结合实测,理清 ¿ ¡ 的输入方式、< > 为何变形、? ! 如何加宽其后的空白、法语在符号之前留出的空隙,以及数学模式里 ! 表示阶乘这件事(\! 根本不是感叹号)。
打出西班牙语的 ¿ ¡——反引号合字与 \textquestiondown
在 ? 后面紧接一个反引号就得到 ¿,在 ! 后面紧接就得到 ¡。 这是字体连字,既不需要宏包也不需要命令。照下面的代码例子写,便得到「¿Como estas?」,反引号不会出现在输出里。若想写得显式,还有 \textquestiondown 与 \textexclamdown,实测宽度与连字完全一致——T1 下 ¿ 为 4.72107pt,¡ 为 2.7771pt。这两个倒置符号本身是西班牙皇家学院在 18 世纪提出的,目的是让读者在读到句末之前就知道疑问从哪里开始。所以只有收尾符号是不完整的:漏掉开头符号,与其说是排版瑕疵,不如说更接近拼写错误。
连字与命令的关系,在两种编码里方向正好相反。OT1 中 ot1enc.def 把 \textquestiondown 定义为连字本身(见下面的代码),也就是说命令去调用连字:连字才是本体,命令只是别名。T1 则反过来:t1enc.def 声明 \DeclareTextSymbol{\textquestiondown}{T1}{190},并把 \textexclamdown 放在 189 号,直接指向字体中实际存在的字符位置。两种编码的结果一致,实务上任选即可;但 \textquestiondown 更便于搜索替换,别人读到时也能一眼看出意图。在 UTF-8 源码里直接写 ¿ ¡ 同样可行——现代 LaTeX 默认 UTF-8 输入,在 T1 下原样通过。
\usepackage[T1]{fontenc}
\usepackage[spanish]{babel}
% ...
?`Como estas? !`Que bien!
\textquestiondown Como estas? \textexclamdown Que bien!
¿Como estas? ¡Que bien! % direct UTF-8 also works% ot1enc.def -- in OT1 the COMMAND is defined as the ligature
\DeclareTextCommand{\textexclamdown}{OT1}{!`}
\DeclareTextCommand{\textquestiondown}{OT1}{?`}
% t1enc.def -- in T1 they are real slots in the font
\DeclareTextSymbol{\textexclamdown}{T1}{189}
\DeclareTextSymbol{\textquestiondown}{T1}{190}在 OT1 下 < 与 > 会变成 ¡ ¿——倒置符号占着的位置
因为 OT1 字体里根本没有 < 和 > 的字形,那两个码位坐着的是 ¡ 和 ¿。 实际试一下:不写 \usepackage[T1]{fontenc} 就编译 OT1 angle: <a> and 2<3 and x>y.,再用 pdftotext 抽取,返回的是 OT1 angle: ¡a¿ and 2¡3 and x¿y.——没有任何错误或警告。同一份源码加载 T1 后,<a> 依然是 <a>。也就是说,这是一类「不会报错的事故」:只要在正文里写下比较运算符或尖括号,它就会悄悄变成别的字符。
有三种修法,优先次序也照此排列。第一,加上 \usepackage[T1]{fontenc}——T1 有真正的 < > 字形,顺带还能让带重音字母的单词正确断字,所以排西文最终总归需要它。第二,用数学模式写 $<$ $>$——作为比较运算符,语义上也更正确。第三,使用 \textless \textgreater——适合需要显式声明为符号的场合。反过来说,这个现象也是一个提示:如果正文中会出现尖括号,那就又多了一条加载 T1 的理由。
? 与 ! 之后的空白会变宽——它们如何被判为句末
? 与 ! 带有和句点相同的空格因子 3000,会把随后的空白切换成句末空白。实测:wow! Next 为 47.2107pt,而写明词间距的 wow!\ Next 是 46.09985pt,差值 1.11085pt 就是句末的额外空白。也就是说,用 ? 或 ! 结句,无需任何操作就能得到与句点相同的宽度。反过来说,当感叹号位于句中时也会插入同样的空白。遇到 Wow! he said 这类写法,或 Yahoo! 这样的专名,请写明词间距(Yahoo!\ Inc.),或者用 \frenchspacing 干脆关掉句末空白。
法语在 ? ! 之前留空——babel 插入的量实测为 1.66626pt
法语正字法要求在 ? ! ; : 之前留空,加载带 [french] 的 babel 后,无论你是否敲了空格,它都会自动补上。 具体数量在 french.ldf 里写得清清楚楚:\FBthinspace 是 \hskip .5\fontdimen2\font——也就是词间距的一半,10pt 时为 1.66626pt。实测:Quoi 为 21.10596pt,Quoi! 为 25.54932pt,差 4.44336pt;减去 T1 下 ! 的字宽 2.7771pt,正好剩 1.66626pt。冒号则另作处理:\FBcolonspace 是 \space,即整个词间距 3.33252pt。实测 Non 为 18.05115pt,Non: 为 24.16077pt,减去 : 的字宽后恰好剩 3.33252pt。
实务结论很清楚。法语稿件里,不要自己敲符号前的空格。 babel 会插入,而且它插入的是不可断行的空白,还能顺带避免行首只掉下一个 ? 的事故。手工敲空格会让空白翻倍,还会多出一个可能断行的位置。反过来,法语之外的语言不在 ? ! 之前留空——英语、德语、西班牙语都紧贴前一个词。多语言文档中,把这种差异交给 babel 的语言切换最为稳妥。
\usepackage[french]{babel}
% ...
% babel adds the space itself; typing one is unnecessary
Vraiment? Voici: ceci; cela! % prints as: Vraiment ? Voici : ceci ; cela !数学模式里的 ! 是阶乘——而 \! 不是感叹号
数学模式下的 ! 两侧完全不插入空白。正如 fontmath.ltx 中的 \DeclareMathSymbol{!}{\mathclose}{operators}{"21} 所声明,! 属于闭合符号(\mathclose),会紧贴前一个符号。实测:$ab$ 为 9.57755pt,$a!b$ 为 12.35533pt——差 2.77778pt,正是 ! 的字宽。两侧连一个 scaled point 的空白都没有。 阶乘 n! 之所以排得严丝合缝就是这个缘故;$n!!$ 也只是照样再加一个字形(11.55792pt),所以双阶乘直接写就行。? 同样被声明为 \mathclose。
这里有一个因名字相似而设下的陷阱。\! 不是感叹号,而是负细空格(negative thin space)。 实测:$ab$ 的 9.57755pt 在 $a\!b$ 中变为 7.91092pt,缩短了 1.66663pt。它与 \,(正细空格)方向正好相反,是用来把符号收紧的微调命令。若只是想在公式里打一个感叹号,请不加反斜杠直接写 !。由于 \! 常见于 \int\!\int 这类收紧积分号的写法,想写阶乘却敲成 \!,就会出现一种难以察觉的坏法:符号消失,前后还被挤到一起。
\[
\binom{n}{k} = \frac{n!}{k!\,(n-k)!}, \qquad (2n)!! = 2^n n!
\]
% \! is a NEGATIVE thin space, not an exclamation mark:
\[ \int\!\!\int_D f(x,y)\,dx\,dy \]感叹疑问号 ‽ 与 !! ??——想把符号叠起来时
!! 与 ?? 直接照原样敲即可:不需要任何命令,也不会触发连字。 而那个把「疑问与惊讶」合成一个字的符号——感叹疑问号 ‽——出人意料地就藏在 LaTeX 的标准机制里。加载 textcomp 后即可使用的 TS1 编码 \textinterrobang 就是它,声明于 ts1enc.def 的第 148 号(倒置版 \textinterrobangdown 在 149 号)。实际编译后交给 pdftotext,确实能抽出 ‽。它的宽度为 4.72107pt,与 ? 相同。
感叹疑问号由 Martin K. Speckter 于 1962 年提出,他当时经营着一家美国广告代理公司;这个符号源自文案的诉求——想用一个字写出「带着惊讶的提问」——一度还铸过专用铅字、做过打字机字球。它最终没能成为通用标点,但作为 U+203D 留在了 Unicode 里,也通过 TS1 留在 LaTeX 中。老实说,实务中几乎用不上它;学术文章里叠写 !! ?? 同理,惯常的建议是去加强句子,而不是加强符号。不过,知道真需要时有专用命令可用,总归是好事。
输入速查——敲什么、出什么
| 输入 | 输出 | 注意事项 |
|---|---|---|
? | ? | 西文问号;sfcode 为 3000,其后的空白变为句末空白 |
?` | ¿ | 连字;OT1 与 T1 下均有效,反引号不会输出 |
!` | ¡ | 连字;输出与 \textexclamdown 相同 |
\textquestiondown | ¿ | 显式命令;在 T1 下是第 190 号的真实字形 |
\textexclamdown | ¡ | 显式命令;在 T1 下是第 189 号的真实字形 |
\textinterrobang | ‽ | 需要 textcomp(TS1);第 148 号 |
$n!$ | n! | 阶乘;! 属 \mathclose,两侧不加空白 |
\! | (负空白) | 不是感叹号;在数学模式中收紧 1.66663pt 的微调命令 |
? ! | ? ! | 日文全角符号;其后不要再敲全角空格 |
日文的 ? ! 是全角——其后不要敲空格
日文使用全角的 ? !,其后不要再敲全角空格。upLaTeX 或 LuaLaTeX + luatexja 这类支持日文的引擎会把它们当作约物处理,自动预留其后的留白。W3C 的《日语文本布局需求(JLReq)》同样规定:用于句末的问号、感叹号占全角字宽,其后跟一个全角留白——但若紧接着是右括号类,则不留白。若你自己再补一个全角空格,就会与自动留白叠加,空出两倍。行末出现 ? 时多余留白的处理,多数文档类与宏包也会代劳。把约物的留白交给引擎——这就是日文一侧的原则。
\documentclass{ltjsarticle}
\begin{document}
本当ですか? はい、できました! 次に進みましょう。
\end{document}- 以日文为主的稿件统一用全角
?!。 混入半角?!会让语气相同的句子在行末与词间呈现出不同的样子。 - 西文引文、代码与专名保留半角。 引自原文的符号与日文正文的方针分开处理。
- 西班牙语要检查开头符号。 只写收尾的
?!而漏掉¿¡,是语言错误而非体例瑕疵。 - 法语绝不要手工插入空格。 交给带
[french]的babel,并把「符号前不敲空格」定为团队规约。 - 仍用 OT1 的稿件要怀疑尖括号。 正文里的
<>会悄悄变成 ¡ ¿,先确认有没有\usepackage[T1]{fontenc}。