制作一个不影响排版的命令,并且可以在几乎任何地方插入

制作一个不影响排版的命令,并且可以在几乎任何地方插入

我正在编写一个程序,让用户编写一个 LaTeX 字符串,然后将其写入 tex 文件,运行 latex 将其转换为 DVI,然后渲染 DVI。我还希望用户能够任意拆分字符串,以允许在程序中单独操作每个子字符串。如果需要,这种拆分需要能够拆分得像每个单独的字形一样细。我这样做的想法是\special在整个 tex 文件中添加命令,然后我的 DVI 阅读器将使用这些命令来确定哪些字符对应哪些部分。我已经基本设置好了,但我很难以编程方式插入命令\special,同时仍能编译并且不影响排版。

举个例子,假设用户想要渲染a_b^c = \sqrt{d},并且每个字形需要位于不同的部分。(为简单起见,假设平方根是一个字形,尽管事实并非如此。)第一个想法是这样的:

\newcommand{sectiona}[1]{\special{section#1}}
\sectiona{0}a \sectiona{1}_b \sectiona{2}^c \sectiona{3}= \sectiona{4}\sqrt \sectiona{5}d

有两个问题:首先,c 的位置稍微靠右。在命令之前添加命令似乎有点^混乱。其次,它实际上无法编译,因为命令\sqrt只是将 a 视为\special其参数。

第二个想法是这样的:

\newcommand{sectiona}[1]{\special{section#1}}
\sectiona{0}a _\sectiona{1}b ^\sectiona{2}c \sectiona{3}= \sectiona{4}\sqrt \sectiona{5}d

这仍然无法编译。除了\sqrt参数不正确之外,_^现在也同样如此。

第三个想法是这样的:

\newcommand{sectionb}[2]{{\special{section#1}#2}}
\sectionb{0}a _\sectionb{1}b ^\sectionb{2}c \sectionb{3}= \sectionb{4}\sqrt \sectionb{5}d

通过让命令吃掉后面的标记,下标和上标可以完美地工作。但是\sqrt仍然不起作用,因为使用新命令,a}直接跟在 后面\sqrt。此外,= 周围的间距也乱了。

当然,最好的解决方案是这样的:

\newcommand{sectiona}[1]{\special{section#1}}
\newcommand{sectionb}[2]{{\special{section#1}#2}}
\sectiona{0}a _\sectionb{1}b ^\sectionb{2}c \sectiona{3}= \sectiona{4}\sqrt \sectionb{5}d

但是,我的程序无法知道节之前的内容是否需要参数,节之后的内容是否需要参数,因此这种方法似乎不可行。也许有某种 TeX 方法可以解决这个问题,但我不知道它是什么。

这个网站上有一个类似的问题这里,但那里给出的解决方案存在上述问题,即它不能很好地处理下标、上标和平方根。我需要一个可以插入 tex 字符串中几乎任何位置的解决方案。

答案1

您只需要插入适当的分组。

\def\foo#1{\special{section#1}}
$$
{\foo{1}a}_{\foo{2}b}^{\foo{2}c}\foo{3}=\foo{4}\sqrt{\foo{5}d}
$$

$$
a_b^c=\sqrt d
$$
\bye

显示差异

如果无法进行手动分组,则可能的解决方法是使用 catcode 来区分字符和标记,并使用\mathcode区分关系运算符,例如=来自变量a、、b...。一个例子(\mathcode 使用纯 TeX 不容易进行这样的比较,因此这里没有显示,但使用 LaTeX3 界面可以进行算术运算):

\def\tag#1{\special{section#1}}
\def\tagroup#1#2{{\special{section#1}#2}}
\def\bar#1#2{\ifcat\noexpand#2\relax\tag{#1}#2\else\tagroup{#1}#2\fi}
$$
\bar{1}a_\bar{2}b^\bar{2}c\foo{3}=\bar{4}\sqrt\bar{5}d
$$

答案2

根据 LdBeth 的回答,我终于知道了需要查找哪些内容才能更彻底地解决这个问题。这是我目前使用 LaTeX3 的解决方案,它几乎适用于我关心的所有情况:

\ExplSyntaxOn

\newcommand{\sectiona}[1]{\special{section#1}}
\newcommand{\sectionb}[2]{{\special{section#1}#2}}

% Mathcodes are hex numbers of the form 0xXYZZ, where X is the class.  This
% extracts the X value from a character.
\newcommand{\getmathclass}[1]{
    \int_eval:n{ ( \char_value_mathcode:n{`#1} - 2048) / 4096 }}

\newcommand\sectionc[2]{
% If the next token is a group, we should always treat it like a group.
\if_int_compare:w \tl_count:n{#2} > 1
    \sectionb{#1}{#2}
\else
    % If the next token is a command, we shouldn't eat it.
    \tl_if_head_eq_catcode:nNTF {#2} \relax
    {
        % Without \expandafter, a command might see the \fi below as an
        % argument.
        \sectiona{#1}\expandafter#2
    }
    {
        % If the next token is a binary infix (2) or relation (3), we shouldn't
        % eat it, because it messes with the spacing.
        \if_int_compare:w \getmathclass{#2} = 2
            \sectiona{#1}\expandafter#2
        \else \if_int_compare:w \getmathclass{#2} = 3
            \sectiona{#1}\expandafter#2
        % Hopefully at this point the next token just a character, use it.
        \else
            \sectionb{#1}#2
        \fi \fi
    }
\fi
}

\ExplSyntaxOff

这是我编写的一大堆测试:

\[
    a_b^c = \sqrt d
\]

\[
    \sectionc{1}a_\sectionc{2}b^\sectionc{2}c\sectionc{3}
        =\sectionc{4}\sqrt\sectionc{5}d
\]

\[
    a^{\sqrt b}
\]

\[
    \sectionc{1}a^{\sectionc{2}\sqrt \sectionc{3}b}
\]

\[
    \int_{-1}^1\sqrt{\frac{1 - x^2}{\frac{1}{4}}} \,\mathrm{d}x = \frac{\tau}{2}
\]

% Sections 9 and 16 catch the fraction line
\[
    \sectionc{1}\int_\sectionc{2}{-1}^\sectionc{3}1
    \sectionc{4}\sqrt{\sectionc{5}\frac
        {1 \sectionc{6} - \sectionc{7}x^\sectionc{8}2\sectionc{9}}
        {\sectionc{10}\frac{1}{4}}}
    \sectionc{11}\,\mathrm{d}\sectionc{12}x \sectionc{13}=
    \sectionc{14}\frac{\sectionc{15}\tau\sectionc{16}}{\sectionc{17}2}
\]

\[
    \sqrt[n]{c}
\]

\[
    \sectionc{1}\sqrt[\sectionc{2}n]{\sectionc{3}c}
\]

\[
    a + b
\]

\[
    \sectionc{1}a \sectionc{2}+ \sectionc{3}b
\]

这些测试中的每对线都是相同的方程式,一个没有部分,另一个有部分。我不会在这里放输出,但每对的可见输出是相同的。

这确实仍有几个小问题,但我愿意解决这些问题:

  • 您仍然不能将节放在下标/上标之前。不过,这可以通过编程来解决。
  • 您不能将部分放在可选参数的 [ 之前(例如\sqrt\sectionc{1}[n]{a}失败)。老实说,我看不出有什么办法可以解决这个问题,解决办法就是将部分放在 [ 之后,而不是之前。这是我的用户必须处理的问题,但它足够小,我愿意忍受它。

我确信它会在更复杂的情况下崩溃,但我并不打算在复杂的情况下使用这个功能,所以我对它现在这样就没意见了。

我不太习惯编写这种复杂的 LaTeX 命令,所以如果我可以做任何简单的改进,请告诉我。

相关内容