![虚假空间](https://linux22.com/image/266894/%E8%99%9A%E5%81%87%E7%A9%BA%E9%97%B4.png)
我正在尝试编写一个\spaced
宏:它以字符串作为参数,分解该字符串,然后根据给定的 重新组合它,将每个标记与其他标记分开\spreadratio
。以下是宏以及 MWE:
\catcode`\@=11
\font\sc="Latin Modern Roman Caps:mapping=tex-text,+onum"
\chardef\box@temp@la=0
\dimendef\dimen@temp@la=0
\dimendef\dimen@temp@lb=2
\toksdef\toks@temp@ga=1
% \boxit is only aimed to highlight the problem.
% If you wanna see the MWE without boxing just comment this
% definition and uncomment the following one.
\def\boxit#1{\vbox{\hrule\hbox{\vrule#1\vrule}\hrule}}
%\def\boxit#1{#1}
\newcount\spreadratio \spreadratio=100
% The interface of \spaced is the following:
%
% \spaced[<number>]{<token list>}
%
% where the optional parameter is the amount of \spreadratio
% other than the predefined one.
\def\spaced{\futurelet\firsttok\@spaced}
\def\@spaced{%
\if\firsttok[\let\next\@paramspaced
\else\let\next\@simplespaced\fi
\next}
\def\@paramspaced[#1]#2{{\spreadratio=#1\dosp@ced{#2}}}
\def\@simplespaced#1{\dosp@ced{#1}}
\def\dosp@ced#1{{\setbox\box@temp@la\hbox{#1}%
% the size of the \hbox is changed according
% to the value of \spreadratio
\dimen@temp@la\wd\box@temp@la \dimen@temp@lb\dimen@temp@la
\divide\dimen@temp@lb by\@m
\multiply\dimen@temp@lb by\spreadratio
\advance\dimen@temp@la by\dimen@temp@lb
\global\toks@temp@ga={}%
\@split{#1}%
\setbox\box@temp@la\hbox to\dimen@temp@la{\the\toks@temp@ga}%
\box\box@temp@la}}
\def\@split#1{\@@split#1\end}
\def\@@split#1{%
\ifx#1\end
\global\toks@temp@ga=\expandafter{\the\toks@temp@ga\unskip}%
\let\next\relax
\else
\global\toks@temp@ga=\expandafter{\the\toks@temp@ga#1\hfil}%
\let\next\@@split\fi
\next}
\catcode`\@=12
\noindent
\boxit{The music}\par\noindent
\boxit{\spaced{The music of Steve Reich}}\par\noindent
\boxit{\spaced[300]{The music of Steve Reich}}\par\noindent
\boxit{{\sc The music of Steve Reich}}\par\noindent
\boxit{\spaced{\sc The music of Steve Reich}}\par\noindent
\boxit{\spaced[300]{\sc The music of Steve Reich}}\par\noindent
% Note that here are no spaces between '\it' and the word 'The'.
\boxit{{\it{}The music of Steve Reich}}\par\noindent
\boxit{\spaced{\it{}The music of Steve Reich}}\par\noindent
\boxit{\spaced[300]{\it{}The music of Steve Reich}}\par\noindent
\boxit{\spaced{The music of Steve Reich}}\par\noindent
\end
结果如下。
如您所见(注意单词 'the' 之前的空格),当强制参数的第一个标记是宏(就像\sc
or 一样\it
)时,\spaced
会在第二个标记之前添加一种虚假空格。
我无法想象那个空间从何而来,而且我也无法消除它。
我的错在哪里?
答案1
你的拆分例程不区分字母和命令,因此如果你尝试存储在中\spaced{\sc A b}
的最终标记列表\toks@temp@ga
\sc \hfil A\hfil b\hfil \unskip
\showthe\toks@temp@ga
正如您在之后添加到宏中看到的\let\next\relax
。
当然,分裂程序也会占用空间。
不同的策略是使用 XeTeX 功能。
\catcode`\@=11
\font\rm="Latin Modern Roman:mapping=tex-text,+onum" \rm
\font\sc="Latin Modern Roman Caps:mapping=tex-text,+onum"
\def\spaced{\futurelet\next\@spaced}
\def\@spaced{%
\ifx\next[%
\expandafter\@spacedopt
\else
\expandafter\@spacednonopt
\fi}
\def\@spacednonopt#1{#1}
\def\@spacedopt[#1]#2{\begingroup\@addtofont{letterspace=#1}#2\endgroup}
\def\@getfontname{\expandafter\@removequotes\fontname\font}
\def\@removequotes"#1"{#1}
\def\@addtofont#1{%
\begingroup\edef\x{\endgroup
\font\noexpand\@temp="\expandafter\@removequotes\fontname\font;#1;"%
\noexpand\@temp}\x}
\catcode`@=12
\spaced[10]{The music of Bach} This is not spaced
\sc\spaced[10]{The music of Bach} This is not spaced
\bye
答案2
@egreg 说:
你的拆分例程不区分字母和命令,因此如果你尝试存储在中
\spaced{\sc A b}
的最终标记列表\toks@temp@ga
\sc \hfil A\hfil b\hfil \unskip
\showthe\toks@temp@ga
正如您在之后添加到宏中看到的\let\next\relax
。
好吧,根据这个解释(以及我对宏参数化的担忧),我按照以下方式更改了拆分例程
\def\@@split#1{%
\ifx#1\end
\global\toks@temp@ga=\expandafter{\the\toks@temp@ga\unskip}%
\let\next\relax
\else
% here the routine should dinstiguish
% between letters and commands
%
\ifcat\noexpand#1\relax
\global\toks@temp@ga=\expandafter{\the\toks@temp@ga#1}%
\let\next\@@split
%
% %%%
\else
\global\toks@temp@ga=\expandafter{\the\toks@temp@ga#1\hfil}%
\let\next\@@split\fi\fi
\next}
它似乎工作正常,但如果 egreg 没有建议这个系统肯定有问题。
这仅仅是一种不安全感,还是代码中隐藏着一个不易察觉的错误?