我正在编写一个程序,让用户编写一个 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 命令,所以如果我可以做任何简单的改进,请告诉我。