使用 babel 和 lualatex 可以破坏箭头

使用 babel 和 lualatex 可以破坏箭头

我开始因为这个我不知道如何解决的奇怪错误而失去理智。我用希伯来语的乳胶做作业,并用它babel来设置它。我有一个自定义包和类来定义我的风格和命令。经过几个小时的努力让它编译而没有错误,我发现它\Longrightarrow坏了:

我尝试创建一个最小的例子来重现这个问题:

% !TeX spellcheck = he_HE
\documentclass[a4paper, 12pt]{extarticle}

\usepackage{amsmath}
\usepackage{enumitem}
\usepackage{witharrows}

\usepackage[bidi=basic, provide=*]{babel}
\babelprovide[alph=letters, Alph=letters]{hebrew}
\babelprovide{english}

\babelfont[hebrew]{rm}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\babelfont[hebrew]{sf}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\babelfont[hebrew]{tt}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\AddToHook{begindocument/end}{\selectlanguage{hebrew}}

\begin{document}
דוגמה \textit{לחישוב}:
\begin{DispWithArrows*}
A &= (a + b)^2 \Arrow{נפתח את הסוגריים בביטוי} \\
&= a^2 + 2ab + b^2
\end{DispWithArrows*}
\underline{דוגמה} \textbf{נוספת}:
\begin{align*}
A &= (a + b)^2\\
&= a^2 + 2ab + b^2
\end{align*}

\begin{otherlanguage}{english}
Some English example.
\end{otherlanguage}
\begin{enumerate}[leftmargin=*, label=\alph*.]
    \item
בדיקה ראשונה.
    \item
בדיקה שנייה.
\end{enumerate}

\[ AB = I \Longrightarrow B = A^{-1} \]
\end{document}

但在编译时,它按预期工作:

我检查了代码,试图禁用单个功能,但找不到这个问题的根源。处理的代码babel与上面示例中的代码相同。以下是我的文件的精简版本:

my-style.sty

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{my-style}[2023/03/06 My personal style and custom commands]

\RequirePackage[table]{xcolor}
\RequirePackage{tikz}
\RequirePackage{tkz-base, tkz-euclide}
\RequirePackage{pgfplots}
\RequirePackage{graphicx}
\RequirePackage[most]{tcolorbox}
\RequirePackage{wrapfig}
\RequirePackage{titlesec}
\RequirePackage{xifthen}
\RequirePackage{xparse}
\RequirePackage{geometry}
\RequirePackage{amsmath}
\RequirePackage{amsfonts}
\RequirePackage{amssymb}
\RequirePackage{amsthm}
\RequirePackage{mathrsfs}
\RequirePackage{enumitem}
\RequirePackage{moreenum}
\RequirePackage{bm}
\RequirePackage{upgreek}
\RequirePackage[euler]{textgreek}
\RequirePackage{import}
\RequirePackage{esint}
\RequirePackage{mathtools}
\RequirePackage[thicklines]{cancel}
\RequirePackage[per-mode=fraction, exponent-product=\cdot]{siunitx}
\RequirePackage[italicdiff]{physics}
\RequirePackage{nicefrac}
\RequirePackage{parskip}
\RequirePackage{setspace}
\RequirePackage{dsfont}
\RequirePackage{etoolbox}
\RequirePackage[bottom]{footmisc}
\RequirePackage{multicol}
\RequirePackage{ulem}
\RequirePackage{pgfkeys}
\RequirePackage{nicematrix}
\RequirePackage{witharrows}

% Hebrew option
\newboolean{hebrew}
\setboolean{hebrew}{false}

\DeclareOption{hebrew}{\setboolean{hebrew}{true}}
\DeclareOption*{\PackageWarning{my-style}{Unknown '\CurrentOption'}}
\ProcessOptions\relax

% Font and lanuguage settings
\RequirePackage[bidi=basic, provide=*]{babel}
\babelprovide{english}
\babelprovide[alph=letters, Alph=letters]{hebrew}
\babelfont[hebrew]{rm}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\babelfont[hebrew]{sf}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\babelfont[hebrew]{tt}[Renderer=Harfbuzz, ItalicFont=*-Regular, ItalicFeatures={FakeSlant=0.3}]{Assistant}
\ifthenelse{\boolean{hebrew}}{
    \AddToHook{begindocument/end}{\selectlanguage{hebrew}}
}{
    \AddToHook{begindocument/end}{\selectlanguage{english}}
}

% witharrows options
\WithArrowsOptions{displaystyle}
\tikzset{
    WithArrows/arrow/.append style = {
        font= \small
    }
}

\RequirePackage{hyperref}
\RequirePackage{fancyhdr}

\hypersetup{
    colorlinks,
    linkcolor=blue
}

\NewDocumentCommand{\OverSet}{ O{0cm} m m }{
    \overset{\raise #1 \hbox{\(#2\)}}{#3}
}
\NewDocumentCommand{\UnderSet}{ O{0cm} m m }{
    \underset{\lower #1 \hbox{\(#2\)}}{#3}
}

\NewDocumentCommand{\claptext}{ m }{\mathclap{\text{#1}}}

\NewDocumentCommand{\Transpose}{ s m }{
    {\IfBooleanTF{#1}{\pqty{#2}}{#2}}^{\mathrm{t}}
}
\NewDocumentCommand{\InverseTranspose}{ s m }{
    {\IfBooleanTF{#1}{\pqty{#2}}{#2}}^{-\mathrm{t}}
}
\NewDocumentCommand{\HermitianTranspose}{ s m }{
    {\IfBooleanTF{#1}{\pqty{#2}}{#2}}^{\dagger}
}
\NewDocumentCommand{\InverseHermitianTranspose}{ s m }{
    {\IfBooleanTF{#1}{\pqty{#2}}{#2}}^{-\dagger}
}

\NewDocumentCommand{\Conjugate}{ s m }{
    \IfBooleanTF{#1}{\pqty{#2}}{#2}^{\ast}
}

% Physics commands
\NewDocumentCommand{\IdentityOperator}{}{\hat{I}}

\NewDocumentCommand{\Change}{ m }{\mathop{\Delta#1}}

homework.cls

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{homework}[2022/11/01 My custom class for homework]

\LoadClass[a4paper, 12pt]{extarticle}

\RequirePackage{my-style}

\DeclareOption{hebrew}{\PassOptionsToPackage{hebrew}{my-style}}
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{extarticle}}
\ProcessOptions\relax

\geometry{left=2cm, right=2cm, top=2cm, bottom=2cm}

% HomeWork options
\NewExpandableDocumentCommand{\Course}{}{}
\NewExpandableDocumentCommand{\StudentId}{}{#########}
\NewExpandableDocumentCommand{\StudentEmail}{}{[email protected]}
\NewExpandableDocumentCommand{\HWDate}{}{\today}

\ifthenelse{\boolean{hebrew}}{
    \NewExpandableDocumentCommand{\Homework}{}{תרגיל בית}
    \NewExpandableDocumentCommand{\StudentName}{}{שם הסטודנט}
}
{
    \NewExpandableDocumentCommand{\Homework}{}{Homework assignment}
    \NewExpandableDocumentCommand{\StudentName}{}{}
}

\NewDocumentCommand{\SetCourse}{ m }{\RenewDocumentCommand{\Course}{}{#1}}
\NewDocumentCommand{\SetHomework}{ m }{\RenewDocumentCommand{\Homework}{}{#1}}
\NewDocumentCommand{\SetStudentName}{ m }{\RenewDocumentCommand{\StudentName}{}{#1}}
\NewDocumentCommand{\SetStudentId}{ m }{\RenewDocumentCommand{\StudentId}{}{#1}}
\NewDocumentCommand{\SetStudentEmail}{ m }{\RenewDocumentCommand{\StudentEmail}{}{#1}}
\NewDocumentCommand{\SetHWDate}{ m }{\RenewDocumentCommand{\HWDate}{}{#1}}

% Styling customizations
\ifthenelse{\boolean{hebrew}}{
    % Customize title
    \titlelabel{חלק \thetitle}
}{}

\NewDocumentCommand{\Appendix}{ s }{
    \IfBooleanF{#1}{\newpage}
    \appendix
    \ifthenelse{\boolean{hebrew}}{
        \renewcommand{\thesection}{\arabic{section}}

        % Customize title
        \titlelabel{נספח \thetitle\quad}
    }{}
}

% Homework functions
\newcounter{ProblemNum}
\newcounter{SubProblemNum}[ProblemNum]

\NewExpandableDocumentCommand{\TheProblemNum}{}{\arabic{ProblemNum}}
\ifthenelse{\boolean{hebrew}}{
    \NewExpandableDocumentCommand{\TheSubProblemNum}{}{\alph{SubProblemNum}}
}{
    \NewExpandableDocumentCommand{\TheSubProblemNum}{}{\arabic{SubProblemNum}}
}

\NewDocumentCommand{\Section}{ m }{
    \phantomsection
    \addcontentsline{toc}{section}{#1}
    \section*{#1}
}

\NewDocumentCommand{\Problem}{ s o }{
    \IfBooleanF{#1}{\newpage}
    \stepcounter{ProblemNum}

    \phantomsection
    \ifthenelse{\boolean{hebrew}}{
        \addcontentsline{toc}{subsection}{שאלה \TheProblemNum \IfNoValueF{#2}{\; #2}}
        \subsection*{שאלה \TheProblemNum \IfNoValueF{#2}{\; #2}}
    }{
        \addcontentsline{toc}{subsection}{Question \TheProblemNum \IfNoValueF{#2}{\; #2}}
        \subsection*{Question \TheProblemNum \IfNoValueF{#2}{\; #2}}
    }
}

\NewDocumentCommand{\Solution}{}{
    \phantomsection
    \ifthenelse{\boolean{hebrew}}{
        \addcontentsline{toc}{subsubsection}{פתרון}
        \subsubsection*{פתרון}
    }{
        \addcontentsline{toc}{subsubsection}{Solution}
        \subsubsection*{Solution}
    }
}
\NewDocumentCommand{\Part}{ o }{
    \IfNoValueTF{#1}{
        \stepcounter{SubProblemNum}
        \phantomsection
        \ifthenelse{\boolean{hebrew}}{
            \addcontentsline{toc}{subsubsection}{סעיף \texorpdfstring{\TheSubProblemNum}{\arabic{SubProblemNum}}}
            \subsubsection*{סעיף \TheSubProblemNum}
        }{
            \addcontentsline{toc}{subsubsection}{Part \texorpdfstring{\TheSubProblemNum}{\arabic{SubProblemNum}}}
            \subsubsection*{Part \TheSubProblemNum}
        }
    }{
        \phantomsection
        \ifthenelse{\boolean{hebrew}}{
            \addcontentsline{toc}{subsubsection}{סעיף #1}
            \subsubsection*{סעיף #1}
        }{
            \addcontentsline{toc}{subsubsection}{Part #1}
            \subsubsection*{Part #1}
        }
    }
}

% Theorems and proofs
\ifthenelse{\boolean{hebrew}}{
    \newtheorem{lemma}{למה}[ProblemNum]
}{
    \newtheorem{lemma}{Lemma}[ProblemNum]
}

% Lists
\newlist{QuestionParts}{enumerate}{2}
\setlist[QuestionParts, 1]{align=left, leftmargin=*, label=\alph*.}
\setlist[QuestionParts, 2]{align=right, label=\arabic*.}

\newlist{SubParts}{enumerate}{1}
\setlist[SubParts, 1]{align=left, labelsep=0pt, leftmargin=0pt, labelwidth=*, label=\uline{חלק \arabic*}}

\NewDocumentCommand{\nitem}{ o }{
    \IfNoValueTF{#1}{
        \item\mbox{}\\
    }{
        \item[#1]\mbox{}\\
    }
}

% Hyperref and fancyhdr setup
\hypersetup{
    pdfauthor={\StudentName},
    pdftitle={\Homework},
}

\pdfstringdefDisableCommands{\let\;\empty}

\setlength{\headheight}{15pt}
\ifthenelse{\boolean{hebrew}}{
    \lhead{שאלה \arabic{ProblemNum}}
}{
    \lhead{Question \arabic{ProblemNum}}
}
\chead{\Course}
\rhead{\Homework}
\lfoot{}
\cfoot{\thepage}
\rfoot{}
\pagestyle{fancy}

main.tex

% !TeX spellcheck = he_HE
\documentclass[hebrew]{homework}

\SetCourse{פיזיקה קוונטית 1}
\SetHomework{תרגיל בית 3}

\begin{document}

\tableofcontents

\import{./}{q1.tex}
\end{document}

q1.tex

% !TeX spellcheck = he_HE
\Problem[צמוד הרמיטי]
\[
\HermitianTranspose*{\hat{O}^{-1}}\HermitianTranspose{\hat{O}} = \HermitianTranspose*{\hat{O}\hat{O}^{-1}} = \HermitianTranspose{\IdentityOperator} = \IdentityOperator
\Longrightarrow \tcbhighmath{\HermitianTranspose*{\hat{O}^{-1}} = \pqty{\HermitianTranspose{\hat{O}}}^{-1}}
\]

编译main.tex生成lualatex第一张图片。正如我之前所说,我花了几个小时将这些文件与上面的示例进行比较,但找不到这个烦人的错误的来源。如果有人能弄清楚这里发生了什么以及如何解决它,我将不胜感激。

PS:我正在使用字体助手

答案1

首先,由 生成的字形\Longrigtharrow实际上不是一个字形,而是由( \Rightarrow) 和组合=而成,以延长其腿。问题是这两个字形来自不同的字体,如果没有默认字体,则无法使用它们的组合。

即使你的最小例子“不成功”,如果你仔细观察,你会发现腿部和头部实际上并没有完全重叠:

在此处输入图片描述

原因在于,您的最小示例使用了12pt字体大小,并且虽然字体存在(加载lmr12时使用的默认字体 ,使用命令时 babel 会加载该字体),但字体(箭头所在的位置)不存在,因此 LaTeX 可以缩放,但结果并不完美。fontspec\babelfontcmsy12cmsy10

关于您的主文档,在某些情况下,fontspec 会将mathrm字体定义为文档的主字体,这就是导致箭头腿看起来受到干扰的原因,它们实际上来自=助手字体。

为了抑制这种情况,可以将选项传递给 fontspec,您可以通过在第一个 之前no-math添加行来实现,或者您可以重命名文档类选项,或者您可以在 之前加载包。可能后者更可取,因为您使用的是默认字体大小。\PassOptionsToPackage{no-math}{fontspec}\babelfontunicode-mathbabel12pt

您无法通过最小示例获得类似结果的原因是,通过将选项传递hebrew给 documentclass 减速,您实际上也将其传递给了所有包。

因为您本质上已经将\RequirePackage[hebrew,bidi=basic, provide=*]{babel}babel视为hebrew主要语言,并将主要字体设置为希伯来字体。

您可以使用更短的文档产生类似的结果:

\documentclass[hebrew]{article}

\usepackage{amsmath}

\usepackage[bidi=basic, provide=*]{babel}
\babelfont[hebrew]{rm}{Assistant}

\begin{document}
\[\Longrightarrow\]
\end{document}

答案2

更新了添加解决方法的答案。

除了将no-math选项传递给fontspec(via \PassOptionsToPackage),或者确保所有加载的包都amsmath在之后加载fontspec(这很麻烦,例如tcolorbox使用选项“most”就可以做到这一点),我认为最简单的方法是

  • \makeatletter\Umathcharnumdef\std@equal\Umathcodenum`\=\relax\makeatother之后立即添加 \begin{document}

  • 或添加

      \makeatletter
      \AtBeginDocument{%
        \Umathcharnumdef\std@equal\Umathcodenum`\=\relax
      }%
      \makeatother
    

    如果您使用的 LaTeX 至少是 2020 年 10 月之前的版本,则可以在序言中的任何地方使用,或者\begin{document}对于较旧的 LaTeX,则可以在之前使用。

您不需要担心\std@minus(如果您阅读这个答案的其余部分,您会看到提到),因为fontspec(据我检查)没有什么特别的-


这只是对非常好的补充回答由@UdiFogiel 撰写,因为我的评论太长,不适合作为评论。

比较以下几种情况:

  1. 一份 pdflatex 文档:
\documentclass[12pt]{article}
\usepackage{amsmath}

\usepackage{dejavu}

\begin{document}
a $\Longrightarrow$
\thispagestyle{empty}
\showoutput
\end{document}
% Local variables:
% TeX-engine: default
% End:

然后必须放大才能目视确认轻微的不匹配 longrightarrow 与 pdflatex

并且日志包含(不要担心)它代表一个插槽,实际上是箭头所在的位置)

....\OT1/DejaVuSerif-TLF/m/n/12 a
....\glue 3.81479 plus 1.9068 minus 1.27199
....\mathon
....\OT1/cmr/m/n/12 =
....\hbox(0.0+0.0)x-1.99997
.....\kern -1.99997
....\OMS/cmsy/m/n/12 )
....\mathoff
  1. 现在我们尝试使用 luatex 和 fontspec 类似的东西:
\documentclass[12pt]{article}
\usepackage{amsmath}

\usepackage{fontspec}
\setmainfont{DejaVu Serif}

\begin{document}
a $\Longrightarrow$
\thispagestyle{empty}
\showoutput
\end{document}
% Local variables:
% TeX-engine: luatex
% End:

如果不进行缩放,就会出现不匹配的情况lonrightarrow 与 fontspec

日志内容如下:

....\TU/DejaVuSerif(0)/m/n/12 a
....\glue(\spaceskip) 3.81445 plus 1.90723 minus 1.27148
....\mathon
....\TU/DejaVuSerif(0)/m/n/12 =
....\hbox(0.0+0.0)x-1.99997, direction TLT
.....\kern-1.99997 (italic)
....\OMS/cmsy/m/n/12 )
....\mathoff
  1. 与 2. 相同,但没有amsmath
\documentclass[12pt]{article}
%\usepackage{amsmath}

\usepackage{fontspec}
\setmainfont{DejaVu Serif}

\begin{document}
a $\Longrightarrow$
\thispagestyle{empty}
\showoutput
\end{document}
% Local variables:
% TeX-engine: luatex
% End:

产生,这里我必须放大很多: lonrightarrow 带有 luatex 而不带有 amsmath

并且日志包含

....\TU/DejaVuSerif(0)/m/n/12 a
....\glue(\spaceskip) 3.81445 plus 1.90723 minus 1.27148
....\mathon
....\hbox(4.33081+0.0)x9.1388, direction TLT
.....\OT1/cmr/m/n/12 =
....\hbox(0.0+0.0)x-1.99997, direction TLT
.....\kern-1.99997 (italic)
....\OMS/cmsy/m/n/12 )
....\mathoff

请注意,在这些日志摘录中显示的“字体名称”实际上是 LaTeX“新字体选择方案(或系统?)NFSS”分配的宏名称。

因此,我们看到问题不仅仅在于其中一个,而在于和以及 Unicode 引擎fontspec的组合。fontspecamsmath

现在amsmath的定义\Longrightarrow

\renewcommand{\Longrightarrow}{%
  \DOTSB\protect\Relbar\protect\joinrel\Rightarrow}

并且的定义\Relbar

\ams@DeclareRobustCommand\Relbar{\mathrel\std@equal}

并且的定义\std@equal稍微复杂一些

\@ifundefined{Umathcode}
  {%
    \mathchardef\std@minus\mathcode`\-\relax
    \mathchardef\std@equal\mathcode`\=\relax
  }
  {%
   \Umathcharnumdef\std@minus\Umathcodenum`\-\relax
   \Umathcharnumdef\std@equal\Umathcodenum`\=\relax
  }
\@ifundefined{Umathcode}
  {%
    \AtBeginDocument{%
      \mathchardef\std@minus\mathcode`\-\relax
      \mathchardef\std@equal\mathcode`\=\relax
      }%
  }
  {%
    \AtBeginDocument{%
      \Umathcharnumdef\std@minus\Umathcodenum`\-\relax
      \Umathcharnumdef\std@equal\Umathcodenum`\=\relax
    }%
  }

它表明这是数学模式中\std@equal的别名,并坚持它在开始文档时重新进行定义。=amsmath

如果没有 amsmath,我们也会得到类似的东西,\Relbar只是后者的定义是一个扩展为的健壮宏\mathrel{=}。所以基本上它看起来和上面的东西一样。

amsmath那么为什么会有差异呢?在let's do的例子中

\makeatletter
a $\std@equal = \Longrightarrow$

那么输出是 std@equal 和 equal 不同。因此,amsmath 不遗余力地制造出\std@equal相同=的,但最终它们并不相同。如果没有 amsmath,箭头将使用\Relbar将使用的内容\mathrel{=},并且不会出现不匹配的情况。

但这里发生的事情是,\std@equal数学代码被冻结为等号,它映射到使用数学模式的“运算符”TeX 字体“系列”中的给定插槽。这样,fontspec“运算符”就会被修改以匹配文档的罗马字体。同时,数学模式中的“运算符”fontspec也会被修改为=不是使用=文本字体。但它不会修改 amsmath,\std@equal因此最终将使用文档“罗马”字体。

fontspec文件中“操作符”的修改fontspec-luatex.sty

  \DeclareSymbolFont{operators}\g_fontspec_encoding_tl\g__fontspec_mathrm_tl\mddefault\shapedefault

本次修改fontspec=

  \DeclareSymbolFont{legacymaths}{OT1}{cmr}{m}{n}
...
     \DeclareMathSymbol{=}{\mathrel}{legacymaths}{61}

然后我们可以检查数学代码=以及数学版本

> \symlegacymaths=\char"4.
l.8 \show\symlegacymaths
                      
? 
> 73400381.
l.9 \showthe\mathcode`=
                     
? 
> \temp=\Umathchar"3"04"00003D.
l.11 \show\temp
             
? 
> \std@equal=\Umathchar"3"00"00003D.
l.12 \show\std@equal
                  
? 

我曾经用过这个

\makeatletter
\show\symlegacymaths
\showthe\mathcode`=
\Umathcharnumdef\temp\Umathcodenum`=\relax
\show\temp
\show\std@equal

=通过别名来解开“Umathcode” \temp,这可以确认=现在取自“legacymaths”(编号 4),而\std@equal仍然引用“operators”(编号 0)的字形。但“operators”现在映射到文档“roman”字体(在 OP 使用的例子中,它\babelfont实际上是希伯来语的字体)。

结论:其中一个fontspec必须amsmath考虑到另一个的存在。这可能是一个已知的错误,已在fontspec 问题追踪器但我发现只有一个问题提及,amsmath并且它已经关闭且不相关。

相关内容