![XeTeX 文本布局对空格非常敏感](https://linux22.com/image/358320/XeTeX%20%E6%96%87%E6%9C%AC%E5%B8%83%E5%B1%80%E5%AF%B9%E7%A9%BA%E6%A0%BC%E9%9D%9E%E5%B8%B8%E6%95%8F%E6%84%9F.png)
我在脚本中有一个文档需要复杂的文本布局我相信这应该可以在 XeTeX 中工作。但我得到了令人惊讶的结果:
\documentclass{article}
\usepackage{fontspec}
\tracinglostchars=2 % https://tex.stackexchange.com/a/41235/48
\def\testtext{R ಶ್ರೀವತ್ಸ \quad Rಶ್ರೀವತ್ಸ}
\begin{document}
\fontspec{Arial Unicode MS} \testtext
\fontspec{Noto Sans Kannada} \testtext
\fontspec{Noto Serif Kannada} \testtext
\fontspec{Kedage} \testtext
\end{document}
编译后xelatex
得到:
对于那些看不懂脚本的人来说,左边的内容(输入的R ಶ್ರೀವತ್ಸ
R 后面有一个空格)是正确的,而右边的内容(输入的文本相同,但是 R 后面没有空格)则不正确。
我理解输出中的“方框”:这是因为所选的卡纳达语字体中没有 R 字符。(由于 ,在终端中打印了一条有关此内容的消息\tracinglostchars=2
。)
问题:为什么省略空格会导致输出错误?即使没有空格,如何才能使程序正常运行?
据我了解,在 XeTeX 中,文本布局(又称文本渲染,又称文本整形)由 HarfBuzz 库提供,许多其他应用程序都使用该库,应该能够很好地处理此文本。在 LuaTeX 中,他们试图避免系统依赖性,并希望自己实现一切(使用 Lua 代码),这可能低估了文本布局的复杂性,而且无论如何,LuaTeX 目前完全不支持除 Devanagari 和 Malayalam 之外的任何印度语脚本。因此,这就是lualatex
上述文件生成的内容:
(至少我明白它始终是错误的!)
编辑:感谢@cfr 在下面给出的回答,我知道我应该怎么做才能解决实际问题:在加载字体时指定脚本(例如\fontspec{Noto Sans Kannada}[Script=Kannada]
或她回答中的更好方法)。因此,可以解决这个问题;唯一剩下的问题是:这是怎么回事?
值得一提的是,这里有一个可以重现该问题的最小纯 XeTeX 文件(使用xetex
而不是进行编译xelatex
):
\font\notosansnone="Noto Sans Kannada"
% \font\notosanskndt="Noto Sans Kannada:script=knd2"
\font\notosansknda="Noto Sans Kannada:script=knda"
\def\testtext{R ಶ್ರೀ Rಶ್ರೀ}
{\notosansnone \testtext} (No script)
% {\notosanskndt \testtext} (knd2)
{\notosansknda \testtext} (knda)
\bye
答案1
我没有第一种或最后一种字体。但是,Polyglossia 对我来说工作正常。(我认为它可能也适用于正确的字体配置,但我这样做是因为这可能是你最终想要的。)
\documentclass{article}
\usepackage{polyglossia}
\setmainlanguage{kannada}
\setotherlanguage[variant=british]{english}
\newfontfamily\kannadafont{Noto Serif Kannada}[Script=Kannada]
\newfontfamily\kannadafontsf{Noto Sans Kannada}[Script=Kannada]
\tracinglostchars=2 % https://tex.stackexchange.com/a/41235/48
\def\testtext{R ಶ್ರೀವತ್ಸ \quad Rಶ್ರೀವತ್ಸ}
\begin{document}
% \fontspec{Arial Unicode MS} \testtext
\testtext
\sffamily \testtext
% \fontspec{Kedage} \testtext
\end{document}
答案2
(分享我对这一切的理解。)
解决方案
一、问题的解决方案:
- 作为@cfr 的回答指出,我应该使用
[Script=Kannada]
这种字体,如fontspec
和polyglossia
手册中所述。当使用它时,一切都按预期工作:无论有没有空格,整个文本都会按照卡纳达语脚本的需要进行渲染。 - 此外,我们实际上不希望在卡纳达语脚本中呈现非卡纳达语字符(例如 R):
R
必须将不同脚本的字符标记为不同的语言或至少不同的字体(请参阅下文了解如何执行此操作)。
那么,这是 XeTeX 或它使用的某个库中的错误吗?不,我认为这是用户错误。不过,当单词之间有空格时(无需指定脚本),一切都可以正常工作,这一事实可能使这种用户错误更有可能发生。
解释
如何解释这种因空间不同而产生的行为差异(到底是怎么回事)?这种行为可以在 XeTeX 中改变吗?我发现了以下内容。
XeTeX 用于文本布局的库,即哈夫巴兹(在 Firefox、Chrome、LibreOffice 等中使用,请参阅Harfbuzz 是什么?) 附带了一个名为的命令行程序hb-view
,可以使用字体和文本字符串来调用它。使用它我得到了以下输出:
hb-view NotoSansKannada-Regular.ttf "ಶ್ರೀ"
与--script=knda
:hb-view NotoSansKannada-Regular.ttf " ಶ್ರೀ"
与--script=knda
:hb-view NotoSansKannada-Regular.ttf "Rಶ್ರೀ"
与--script=knda
hb-view NotoSansKannada-Regular.ttf "R ಶ್ರೀ"
与--script=knda
这表明,如果任何一个第一个非空格字符来自正确的脚本,或者脚本已明确指定。
因此,XeTeX 中看到的行为(“Rಶ್ರೀ”和“R ಶ್ರೀ”之间的区别”)可以通过以下方式解释@Ulrike Fischer指出XeTeX 伴侣:
XeTeX 的方法如下:
排版过程收集字符(单词)的运行,这些字符的宽度通过系统库的 API 获取 […] 以确定宽度,
XeTeX 段落是单词节点分隔胶水。
因此,XeTeX 的排版引擎放置的是单词而不是字形,后者由字体渲染引擎绘制。
(上面的“系统库”和“字体渲染引擎”现在是 HarfBuzz(感谢哈立德·霍斯尼);他们以前是ICU。)所以
使用“Rಶ್ರೀವತ್ಸ”,XeTeX 要求 HarfBuzz 将整个字符串渲染为一个单元,但失败了(如上面的 hb-view 实验所示),因为它既不是以所需脚本中的字符开头,我们也没有正确指定脚本,而
使用“R ಶ್ರೀವತ್ಸ”,XeTeX 分别向 HarfBuzz 询问两个单词,在这种情况下,第二个单词被正确呈现(即使我们没有指定脚本),因为它以正确脚本中的字符开头。
但最好不要依赖这种猜测,而是明确指定脚本。
使用两个脚本
为了让两个脚本都能顺利运行,我们应该指定 R 等字符属于不同的语言。我们可以通过将 改为 来实现这一点。\textenglish{R}ಶ್ರೀವತ್ಸ
但是Rಶ್ರೀವತ್ಸ
,如果我们不想更改输入,可以使用ucharclasses
包裹。
由于某种原因,我无法让它工作,所以我只能手动操作(参考示例texdoc xetex
和一个邮政来自的作者ucharclasses
,并将 255 更改为 4095,例如这个答案):
\documentclass{article}
\usepackage{fontspec}
\usepackage{polyglossia}
\newfontfamily\kannadafont{Noto Serif Kannada}[Script=Kannada]
\newfontfamily\englishfont{Georgia}
\setdefaultlanguage{kannada}
\setotherlanguage{english}
\XeTeXinterchartokenstate = 1 % Enable the character classes functionality
\newXeTeXintercharclass \CharEnglish
\XeTeXcharclass `R = \CharEnglish
\XeTeXinterchartoks 0 \CharEnglish = {\selectlanguage{english}}
\XeTeXinterchartoks 4095 \CharEnglish = {\selectlanguage{english}}
\XeTeXinterchartoks \CharEnglish 0 = {\selectlanguage{kannada}}
\XeTeXinterchartoks \CharEnglish 4095 = {\selectlanguage{kannada}}
\begin{document}
R ಶ್ರೀವತ್ಸ \quad Rಶ್ರೀವತ್ಸ
\end{document}
每次我们在英文字符(仅限R
上方)与单词边界(4095)或常规(未指定为英文)字符(0)之间移动时,语言都会发生变化。
对于我的原始文档,为了处理所有英文字符,我编写了一个循环来执行相当于
\XeTeXcharclass `R = \CharEnglish
对于字母表中的每个大写字母和小写字母:
\newcount\tmpchar
\tmpchar = `A
\loop
\ifnum \tmpchar < `[ % [ comes just after Z
\XeTeXcharclass \tmpchar = \CharEnglish
\XeTeXcharclass \lccode \tmpchar = \CharEnglish
\advance \tmpchar by 1
\repeat