Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xeCJK: 更新到 LaTeX 2020 和 fontspec v2.7h 之后找不到 Fandol 字体 #482

Closed
stone-zeng opened this issue Feb 4, 2020 · 6 comments
Assignees

Comments

@stone-zeng
Copy link
Member

最小示例:

% xelatex 编译
\documentclass{article}
\usepackage[LoadFandol=false]{xeCJK}
\setCJKmainfont[Extension=.otf,BoldFont=FandolSong-Regular]{FandolSong-Regular}
\setmainfont[Scale=MatchLowercase]{texgyretermes-regular.otf}
\begin{document}
ABCabc 测试
\end{document}

报错为

mktextfm: Running mf-nowin -progname=mf \mode:=ljfour; mag:=1; ; nonstopmode; input OT
This is METAFONT, Version 2.7182818 (TeX Live 2019) (preloaded base=mf)


kpathsea: Running mktexmf OT
! I can't find file `OT'.
<*> \mode:=ljfour; mag:=1; ; nonstopmode; input OT
                                                  
Please type another input file name
! Emergency stop.
<*> \mode:=ljfour; mag:=1; ; nonstopmode; input OT
                                                  
Transcript written on mfput.log.
grep: OT.log: No such file or directory
mktextfm: `mf-nowin -progname=mf \mode:=ljfour; mag:=1; ; nonstopmode; input OT' failed to make OT.tfm.
kpathsea: Appending font creation commands to missfont.log.


! Package fontspec Error: The font "FandolSong-Regular" cannot be found.

For immediate help type H <return>.
 ...                                              
                                                  
l.6 \begin
          {document}
?

有以下特征:

  • \setCJKmainfont 要用 Extension=.otf
  • \setCJKmainfont 更换为 \setCJKsansfont 则不报错
  • \setCJKmainfont 要在 \setmainfont 之前
  • \setmainfont 要使用 Scale=MatchLowercase

可能原因:

版本信息:

  • TeX Live 2019
  • LaTeX2e 2020-02-02
  • fontspec 2020/02/03 v2.7h
  • xeCJK 2019/06/02 v3.7.4
  • macOS Catalina 10.15.2
@muzimuzhi
Copy link
Contributor

muzimuzhi commented Feb 4, 2020

  • 解析选项 Scale=MatchLowercase 时,会调用 \__fontspec_calc_scale:n,后者的定义中包含 \fontfamily { \familydefault }
  • xeCJK 把 \xeCJK@fontfamily {#1} patch 进了 \fontfamily,经过几层展开,会得到 \xeCJK_family_if_exist:nTF

    ctex-kit/xeCJK/xeCJK.dtx

    Lines 6742 to 6752 in dacf770

    \prg_new_protected_conditional:Npnn \xeCJK_family_if_exist:n #1 { T , F , TF }
    {
    \prop_get:NnNTF \g_@@_family_name_prop
    {#1} \l_@@_fontspec_family_tl
    { \prg_return_true: }
    {
    \cs_if_exist_use:cTF { \@@_family_csname:n {#1} }
    { \prg_return_true: }
    { \prg_return_false: }
    }
    }
  • \setCJKmainfont\setmainfont 之后时,\g__xeCJK_family_name_prop 包含映射 {rm} => {...},于是继续执行 \xeCJK_family_if_exist:nTF 中 true 的部分,不报错
  • \setCJKmainfont\setmainfont 之前时,\g__xeCJK_family_name_prop 是一个空的 prop。因为 \xeCJK/family/rm 有定义(由 \__xeCJK_gset_family_cs:x 定义),于是执行 \xeCJK/family/rm
  • \xeCJK/family/rm 在调用 \fontspec_gset_family:Nnn 时,有一些 fontspec 内部宏的定义不对(至少包含 \__fontspec_fontname_wrap:n\l__fontspec_extension_tl),导致 \__fontspec_primitive_font_set:Nnn 的展开结果不符合 xetex 中 \font 的语法,导致错误

在例子

\setCJKmainfont[Extension=.otf]{FandolSong-Regular}
\setmainfont[Scale=MatchLowercase]{texgyretermes-regular.otf}

中,\__fontspec_fontname_wrap:n 的定义不对,导致\__fontspec_primitive_font_set:Nnn 的展开结果是(实际字号是 10pt - 2sp,此处作了简化)

\font \l__fontspec_test_font ="FandolSong.otf/OT" at 10pt

正确的展开结果应是

\font \l__fontspec_test_font ="[FandolSong.otf]/OT" at 10pt

在另一个例子

\setCJKmainfont{FandolSong}
\setmonofont{texgyrecursor-regular.otf}[Scale=MatchLowercase]

中,\l__fontspec_extension_tl 的定义不对,导致\__fontspec_primitive_font_set:Nnn 的展开结果是(实际字号是 10pt - 2sp,此处作了简化)

\font \l__fontspec_test_font ="FandolSong.otf/OT" at 10pt

正确的展开结果应是

\font \l__fontspec_test_font ="FandolSong/OT" at 10pt

可能的方案

各点为并列关系(都没什么依据,只是我的猜测)

  • \__xeCJK_gset_family_cs:x 内初始化一些 fontspec 的内部宏,似乎不行
  • 优先考虑,提前把映射关系存入 \g__xeCJK_family_name_prop

@muzimuzhi
Copy link
Contributor

muzimuzhi commented Feb 5, 2020

另一个角度

尝试知,单独降级 latex2e 到 2019-10-01,问题消失。fontspec 的版本(2.7h/2.7g)与此问题无关。texlive 默认储存一层备份,可通过 tlmgr restore latex 52731 降级(以测试和验证,可能需要手动重新生成 xelatex.fmt 文件)。

latex2e 2020-02-02 改了不少 nfss 的内容,要慢慢看了。

@zepinglee
Copy link
Member

主要的影响是 ci 过不去

@muzimuzhi
Copy link
Contributor

muzimuzhi commented Feb 5, 2020

找到具体原因了:latex2e 2020-02-02 修改了 \usefont 的定义,导致 xecjk 对 \fontfamily 的 patch 失效。

首先,有定义

\DeclareRobustCommand\fontfamily[1]{\edef\f@family{#1}}

然后,在 latex3/latex2e@69f6705#diff-ba08b740b88cf76da18c9c76eb8b8b15 中,\usefont 的定义从

\DeclareRobustCommand\usefont[4]{\fontencoding{#1}%
  \fontfamily{#2}%
  \fontseries{#3}%
  \fontshape{#4}\selectfont
  \ignorespaces}

改为了

\DeclareRobustCommand\usefont[4]{\fontencoding{#1}%
  \edef\f@family{#2}%
  \edef\f@series{#3}%
  \edef\f@shape{#4}\selectfont
  \ignorespaces}

仅 revert \usefont 里 font family 相关的修改,就能修复问题

\documentclass{article}
\usepackage[LoadFandol=false]{xeCJK}

\makeatletter
\DeclareRobustCommand\usefont[4]{\fontencoding{#1}%
  \fontfamily{#2}% used in latex2e 2019-10-01
%  \edef\f@family{#2}% used in latex2e 2020-02-02
  \edef\f@series{#3}%
  \edef\f@shape{#4}\selectfont
  \ignorespaces}
\makeatother

% right: "[FandolSong-Regular.otf]/OT:language=dflt;"
% wrong: "FandolSong-Regular.otf/OT:language=dflt;"
%\setCJKmainfont[Extension=.otf]{FandolSong-Regular}

% right: "FandolSong/OT:language=dflt;"
% wrong: "FandolSong.otf/OT:language=dflt;"
\setCJKmainfont{FandolSong}

\setmainfont[Scale=MatchLowercase]{texgyretermes-regular.otf}

\begin{document}
中文
\end{document}

个人建议报给 latex2e,看能不能改回来。如果结论是需要在 xecjk 这头处理,patch \usefont 是一个方案。(也是我瞎想的)

@muzimuzhi
Copy link
Contributor

muzimuzhi commented Feb 5, 2020

  • \normalfont 包含 \usefont,见
    \DeclareRobustCommand\normalfont
                     {\usefont\encodingdefault
                              \familydefault
                              \seriesdefault
                              \shapedefault
                      \relax}
    \let\reset@font\normalfont
  • \setCJKmainfont[Extension=.otf]{FandolSong-Regular} 会展开到
    \xeCJK_set_family:nnn {rm} {Extension=.otf} {FandolSong-Regular}
    \normalfont
    其中 \xeCJK_set_family:nnn 的展开结果是定义了 \xeCJK/family/rm
  • latex2e 2020-02-02 对 \usefont 定义的修改,导致上述的 \normalfont 没有执行 \xeCJK@fontfamily {\defaultfamily}。而 \xeCJK@fontfamily {\defaultfamily} 会展开到 \xeCJK_switch_family:x { \CJKfamilydefault }
  • \xeCJK_switch_family:x {#1} 的功能是(以下记 #1 是完全展开 (x-type expansion) 后的 #1),
    1. 如果 key #1 在 key-value 列表 \g__xeCJK_family_name_prop 中,就设置 \l_xeCJK_family_tl#1、设置 \CJK@family 为列表中与 key #1 对应的 value
    2. 如果 key #1 不在上述 key-value 列表中,但命令 \xeCJK/family/#1 有定义,就执行 \xeCJK/family/#1,利用 fontspec 的 \fontspec_gset_family:Nnn 获取字族名,然后把映射关系 #1 -> <字族名> 加入该 key-value 列表。然后跳到第 i 步继续。
    3. 如果 key #1 不在上述 key-value 列表中,且命令 \xeCJK/family/#1 无定义,就报 font not found 的错
  • 这样,在 latex2e 2019-10-01 中,\setCJKmainfont[Extension=.otf]{FandolSong-Regular} 展开结束后,\g__xeCJK_family_name_prop 中已经有了 key rm。但在 latex2e 2020-02-02 中,相同的一句 \setCJKmainfont 展开结束后 \g__xeCJK_family_name_prop 仍然是空列表,这导致了 xeCJK: 更新到 LaTeX 2020 和 fontspec v2.7h 之后找不到 Fandol 字体 #482 (comment) 中描述的情况,即在解析选项 Scale=MatchLowercase 时才第一次执行 \xecjk@fontfamily{\defaultfamily},但这时 fontspec 的内部宏储存的是字体 texgyretermes 的信息,当临近的中英两个字体信息以不同方式给出时,就会在导致语法错误的 \font 使用,引擎唤起 metafont 程序。

以上把之前几条回复提供的信息串了起来,基本梳理清楚了为什么 latex2e 2020-02-02 对 \usefont 的修改会导致当前 issue 报告的错误。以下是解决方案部分:

从 latex2e 团队在 latex3/latex2e#265 下的回复看,1)不建议依赖 \usefont 的定义方式,2)相当不建议 patch \fontfamily

我对 nfss 和 xecjk 的了解都还不够清楚,只是把自己知道的发出来。

@qinglee
Copy link
Member

qinglee commented Feb 7, 2020

这个问题的根源是 fontspec 包没有完全初始化相关变量,导致在嵌套定义字体时,相关变量发生了混淆。这里把变量 \l__fontspec_external_bool 初始化为 false,就可以避免问题。

\ExplSyntaxOn

\tl_put_right:Nn \__fontspec_init:
  { \bool_set_false:N \l__fontspec_external_bool }

\ExplSyntaxOff

LaTeX2e 2020-02-02 对 NFSS 的修改会令 xeCJK 中对中西字体族的匹配失效,但可以使用新提供的 \@[rm|sf|tt]familyhook 来更优雅的解决,\normalfont 就只好手动打补丁,或者请他们也提供 \defaultfamilyhook

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants