为什么添加 \cprotect\section{} 会导致 PDF 中的书签损坏?大量 hyperref 删除“\@ifnextchar”消息

为什么添加 \cprotect\section{} 会导致 PDF 中的书签损坏?大量 hyperref 删除“\@ifnextchar”消息

有一次我\cprotect在我的章节标题周围添加了内容(我需要这样做,因为其中一些标题中可能包含数学)。现在,当我打开 PDF 文件时,所有书签都丢失了,取而代之的是,我看到cpt每个书签上都有内容,但章节和小节名称没有显示。在使用之前,我得到了一个答案\usepackage[bookmarks=false]{hyperref},但当时我不知道这是什么意思。现在我注意到它会导致 PDF 书籍标记丢失,所以我不能使用这样的解决方案。

平均能量损失

\documentclass[12pt]{book}
\usepackage{cprotect}
\usepackage{hyperref}

\begin{document}

\chapter{A}
\cprotect\section{B}
\cprotect\subsection{C}
stuff

\cprotect\subsection{D}
stuff

\end{document}

使用编译lualatex

.....
Chapter 1.
(./foo3-1.cpt)

Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref)                removing `\@ifnextchar' on input line 8.

(./foo3-2.cpt)

Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref)                removing `\@ifnextchar' on input line 9.

(./foo3-3.cpt)

Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref)                removing `\@ifnextchar' on input line 12.

PDF 文件书签如下所示(Adobe PDF 阅读器)

在此处输入图片描述

删除cprotect后即可正常工作:

\documentclass[12pt]{book}
\usepackage{hyperref}
\begin{document}

\chapter{A}
\section{B}
\subsection{C}
stuff

\subsection{D}
stuff

\end{document}

给出

在此处输入图片描述

我想使用cprotect,但又不想丢失 PDF 书签。有什么办法吗?

順利2020 年 Linux 版

更新以回答评论

请编辑您的帖子以给出导致悲痛的 \section 命令的实际示例。

下面是一个在 LuaTeX 上失败的示例,除非我使用\cprotect,当我使用 时\cprotect书签会丢失。发生这种情况是因为我使用了\usepackage{Baskervaldx}我喜欢的字体

\documentclass[12pt]{book}

\usepackage{unicode-math}
\defaultfontfeatures{Scale=MatchLowercase}
\setmathfont{Asana Math}
\usepackage{Baskervaldx}

\usepackage{amsmath}
\usepackage{hyperref}

\begin{document}
\tableofcontents

\chapter{A}
\section{$\cos\left(  A+B\right)  $ and $\sin\left(  A+B\right)  $}%

\subsection{C}
stuff
\subsection{D}
stuff

\end{document}

使用 LuaLaTeX 编译可得

Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref)                removing `math shift' on input line 15.

! Improper alphabetic constant.
<to be read again>
\math@bgroup
l.15 \section{$\cos\left(  A+B\right)  $ and $\sin\left(  A+B\right)  $}
                                                                      %
?

但如果我使用cprotect它,它会编译时没有错误,但现在没有书签

\documentclass[12pt]{book}

\usepackage{unicode-math}
\defaultfontfeatures{Scale=MatchLowercase}
\setmathfont{Asana Math}
\usepackage{Baskervaldx}

\usepackage{amsmath}
\usepackage{hyperref}

\usepackage{cprotect}
\begin{document}
\tableofcontents

\chapter{A}
\cprotect\section{$\cos\left(  A+B\right)  $ and $\sin\left(  A+B\right)  $}%

\subsection{C}
stuff
\subsection{D}
stuff

\end{document}

给出

在此处输入图片描述

我有很多这样的例子。下面是另一个

 \section{ this is $\zeta$ }%

给出

Package hyperref Warning: Token not allowed in a PDF string (Unicode):
(hyperref)                removing `math shift' on input line 15.

! Improper alphabetic constant.
<to be read again>
\mitzeta
l.15 \section{ this is $\zeta$ }
                              %
?

请注意,这些都失败了,因为我正在使用字体

\usepackage{unicode-math}
\defaultfontfeatures{Scale=MatchLowercase}
\setmathfont{Asana Math}
\usepackage{Baskervaldx}

我当然可以不使用上面的字体,这样它就可以编译成功,书签也会保留在那里(但没有 Math,这对我来说没问题)。所以也许我必须这样做并使用上面的字体,我喜欢它,但保留书签更重要。所以如果没有其他解决方案,这是一个选择。

是否可以告诉 hyperref,如果它找到可以放入书签的内容,则只能对该部分进行替换.cpt,而不能对所有内容进行替换?

问题是我预处理了整个 LaTeX 文件,并在每个部分和小节周围添加了 \cprotect,以防其中有数学内容。所以现在所有书签都丢失了。

我无法逐一进行此操作,因为我有数万个这样的条目。

记录发现的错误及解决方法

这太小了,无法在评论中写下,所以我在这里添加它。

由于包的顺序错误而产生的错误。这与 luacode 无关。

这失败了

% !TEX TS-program = lualatex
\documentclass{book}

\usepackage{amsmath,mleftright}
\usepackage{unicode-math}
\usepackage{Baskervaldx}
\setmathfont{Asana Math}[Scale=MatchLowercase]
\usepackage{xcolor}
\usepackage[colorlinks,allcolors=blue,linktocpage]{hyperref}

\begin{document}

\section{Solve numerically the ODE $u''''+u=f$ using point collocation method}

test

\end{document}

使用 LuaLaTeX 编译后

t) (./foo3.out)
! Undefined control sequence.
\g__um_prime_font_cmd_tl ->\l__um_font

l.14 \section{Solve numerically the ODE $u''''+u=f$ using point collocation method}

?

解决方法是\usepackage{Baskervaldx} \setmathfont{Asana Math},所以顺序变成

\usepackage{amsmath,mleftright}
\usepackage{unicode-math}
\setmathfont{Asana Math}[Scale=MatchLowercase]
\usepackage{Baskervaldx}
\usepackage{xcolor}
\usepackage[colorlinks,allcolors=blue,linktocpage]{hyperref}

现在编译成功了。这与本节中的数学无关。以下是示例:

% !TEX TS-program = lualatex
\documentclass{book}
\usepackage{amsmath,mleftright}
\usepackage{unicode-math}
\usepackage{Baskervaldx}
\setmathfont{Asana Math}[Scale=MatchLowercase]

\usepackage{xcolor}
\usepackage[colorlinks,allcolors=blue,linktocpage]{hyperref}

\begin{document}

\section{test}

Solve $y''(x)-3 y(x) = -x^2$ over $x=0\ldots1$ with boundary conditions
$x(0)=0$ and $x(1)=0$ using piecewise linear trial functions.
\end{document}

编译时出现错误:

! Undefined control sequence.
\g__um_prime_font_cmd_tl ->\l__um_font

l.17 Solve $y''(
              x)-3 y(x) = -x^2$ over $x=0\ldots1$ with boundary conditions
?

再次更改包的顺序,错误就消失了。这就是为什么我在测试 Mico 的优秀代码时会遇到一些错误。

答案1

我认为\cprotect在当前上下文中使用构成了对宏的严重滥用。此外,正如您所发现的,它无法正常工作,因为 pdf 查看器程序的书签不再正确生成。

由于你使用的是 LuaLaTeX,我建议你采用不同的方法,,设置一个在非常早期阶段运行的 Lua 函数,即在 TeX 开始其常规处理例程之前。通过将 Lua 函数分配给 LuaTeX 的process_input_buffer预处理器回调,它可以扫描\section、和的所有实例\subsection,并\subsubsection自动识别任何和所有内联数学材料实例并将这些实例放在\texorpdfstring指令中,本质上是“净化”数学表达式以供使用hyperref书签例程。例如,

\subsection{$x^2+y^2=z^2$}

将被“动态”替换为

\subsection{\texorpdfstring{$x^2+y^2=z^2$}{x2+y2=z2}}

\section{$\cos\left(A+B\right)$ \textcolor{red}{and} $\sin\left(A+B\right)$}

将被即时替换为

\section{\texorpdfstring{$\cos\left(A+B\right)$}{cos(A+B)} 
         \textcolor{red}{and}   
         \texorpdfstring{$\sin\left(A+B\right)$}{sin(A+B)}}

以下代码提供了两个 LaTeX 实用宏和两个 Lua 函数。这两个 LaTeX 宏分别称为\texorpdfOn\texorpdfOff;它们用于激活和停用名为 的 Lua 函数fix_headers。激活后,即分配给 LuaTeX 的process_input_buffer回调后,fix_headers将检查所有输入行;每次遇到\section\subsection\subsubsection或其“带星号”变体的实例时,Lua 函数接下来会通过搜索字符 的对来检查该命令的参数是否包含内联数学材料$。如果匹配,则strip_math调用名为 的辅助 Lua 函数来生成一个或多个实例

\texorpdfstring{$<unmodified math>$}{<sanitized math>}

\section在、\subsection等参数内。

输入要求如下面所述:

  • 每个分段命令及其参数必须位于同一输入行。这绝对是最严格的要求。

  • 在任何给定的输入行中,都有最多一个\section\subsection\subsubsection或这些命令的带星号变体之一的实例。(这可能更像是一般的输入健全性检查。但是,我认为无论如何我都应该提一下。)

  • 没有包含分段指令的逐字材料实例,而分段指令又包含内联数学材料。例如,没有 的实例。(可以通过排除所有内联逐字材料和、和\verb+\subsection{$1+1=2$}+等环境的内容来放宽这一限制;如果在实践中存在这个问题,请提出新问题。或者,在到达逐字材料之前运行。稍后,退出逐字材料后,您可以再次运行。)verbatimVerbatimcomment\texorpdfOff\texorpdfOn

  • 文档中没有名为 等的命令\Xsection\xyzsection此要求主要是为了编程方便。如果需要,可以放宽此要求,而无需做太多额外工作。)

  • \chapter和的参数\chapter*不包含内联数学材料。(此要求也可以放宽,不需要太多额外工作。)

  • 字符$用于分隔节标题中的内联数学材料。(的实例\$,用于排版$ 象征本身就是允许的。

  • 无显示数学材料\section在、等论点中。特别是在、等论点中\subsection没有 的实例。$$\section\subsection

  • \frac不允许使用嵌套表达式。\frac但是可以使用非嵌套表达式。以下形式的非嵌套表达式\frac{<numer>}{<denom>}在书签中显示为(<numer>)/(<denom>)

我会祈祷这些输入要求不会太繁重。


在此处输入图片描述

% !TEX TS-program = lualatex
%% (compile twice to update the ToC and bookmarks)
\documentclass{book} % or some other suitable document class
\usepackage{luacode} % for 'luacode*' environment
\begin{luacode*}
function strip_math ( u ) 
  -- Drop the '$' delimiters:
  v = u:sub  ( 2 , -2 ) 
  -- Three types of math directives that need to be modified:
      -- directives that need to be removed, e.g, \left and \biggr
      -- directives that need to be modified, e.g., \mid and \prime
      -- all others: just remove the leading backslash (\cos,\int,\log, ...)
  -- Remove all fence-sizing instructions from the input stream:
  v = v:gsub ("\\m?left" , "" ) 
  v = v:gsub ("\\m?right", "" )
  v = v:gsub ("\\[bB]igg?[lrm]?" , "" )
  -- Replace "\frac{...}{...}" with inline-fraction notation:
  v = v:gsub ("\\frac%s-(%b{})%s-(%b{})" , "(%1)/(%2)" ) 
  -- Delete '_' and '^' characters from input stream:
  v = v:gsub ("[%_%^]" , "" )   
  -- Change '\mid' to '|'
  v = v:gsub ("\\mid" , "|" )
  -- Change \prime to '
  v = v:gsub ("\\prime" , "'" )
  -- Finally, change '\int' to 'int', '\sum` to 'sum', '\det' to 'det', etc.
  v = v:gsub ("\\(%a+)", "%1" ) 
  -- Return a "\texorpdfstring" directive:
  return "\\texorpdfstring{"..u.."}{"..v.."}"
end

function fix_headers ( s )
  s = s:gsub ( "(\\%l-section[%*]?)%s-(%b{})" ,
        function ( x , y )
        -- Set aside all instances of "\$" (if any):
        y = y:gsub ( "\\%$", "@@@@@@@@" )
        -- Apply 'strip_math' function if inline-math found: 
        y = y:gsub ( "%b$$" , strip_math )
        -- Restore instances of "\$":
        y = y:gsub ( "@@@@@@@@" , "\\$" )
        return x..y
        end )
  return s
end
\end{luacode*}
%% Define a couple of utility LaTeX macros:
\newcommand\texorpdfOn{\directlua{luatexbase.add_to_callback(
  "process_input_buffer", fix_headers , "fix_headers" )}}
\newcommand\texorpdfOff{\directlua{luatexbase.remove_from_callback(
  "process_input_buffer", "fix_headers" )}}  

\usepackage{amsmath,mleftright}
\usepackage{unicode-math}
\setmainfont{Baskerville 10 Pro} % pick a suitable text font
\setmathfont{Asana Math}[Scale=MatchLowercase] % pick a suitable math font

\usepackage{xcolor}
\usepackage[colorlinks,allcolors=blue,linktocpage]{hyperref}

\begin{document}
\texorpdfOn % Activate the Lua function 'fix_headers'

\setcounter{secnumdepth}{3} % just for this example
\setcounter{tocdepth}{3}

\tableofcontents

\chapter{AAA}
\section{$\cos\left(  A+B\right)  $ \textcolor{red}{and} $\sin\left(  A+B\right)  $}
\subsection{$\det\bigl(A\bigr)$}
\subsubsection{$\ln \mleft[x\mright]$}
\subsubsection{$x^2+y^2=z^2$}
\subsection{$\int f(x)\,dx$}
\section{\textcolor{violet}{Hello World}}
\section{$\frac{a+b}{c+d}$ or $\frac{u}{v}$}
\subsection{$1+1+1=3$, and \$1+\$1+\$1=\textdollar3}
\subsection{Solve numerically the ODE $u''''+u=f$ using\dots}
\end{document}

答案2

该问题并不取决于特定的字体,而是取决于unicode-math

使用\cprotect并不是解决方案:标题中没有任何逐字逐句的内容。

你可以逐步收集“有问题的”命令:

\documentclass[12pt]{book}

\usepackage{unicode-math}
\defaultfontfeatures{Scale=MatchLowercase}
%\setmathfont{Asana Math}
%\usepackage{Baskervaldx}

\usepackage{amsmath}
\usepackage{hyperref}

\pdfstringdefDisableCommands{%
  \def\sin{sin}\def\cos{cos}% <-- add here
  \let\left\relax
  \let\right\relax
}

\begin{document}
\tableofcontents

\chapter{A}
\section{$\cos\left(  A+B\right)  $ and $\sin\left(  A+B\right)  $}%

\subsection{C}
stuff
\subsection{D}
stuff

\end{document}

在此处输入图片描述

答案3

\cos 和 \sin 的问题可以通过使 \operator@font 更强大来解决。我在 unicode-math github 上为此开了一个问题https://github.com/wspr/unicode-math/issues/550

这解决了问题,确实如此不是意味着每个数学运算都不会出错。

\documentclass[12pt]{book}

\usepackage{unicode-math}
\setmathfont{Asana Math}
\usepackage{hyperref}
\makeatletter
\ExplSyntaxOn
\cs_set_protected:Npn \operator@font
  {
    \__um_switch_to:n {literal}
    \__um_fontswitch:n { \g__um_operator_mathfont_tl }
  }
\ExplSyntaxOff
\makeatother
\begin{document}
\tableofcontents

\chapter{A}
\section{$\cos\left(  A+B\right)  $ and $\sin\left(  A+B\right)  $}%


\end{document}

通过使用以下选项加载 hyperref 可以避免 \zeta 和类似问题psdextra

\documentclass{article}
\usepackage{unicode-math}
\usepackage[psdextra]{hyperref}

\begin{document}

\section{$\zeta$}

\end{document}

相关内容