雾月
雾月
这家伙很懒,什么也没写!

注册于 4年前

回答
219
文章
2
关注者
23

封面和目录应当设置 nomarginpar
\fancyhfoffset 只有 fancyhdr 定义的 pagestyle 才有用,必须用 \fancypagestyle 重新定义 plain 样式。
每次修改 geometry 都要重新设置 \fancyheadoffset\fancyfootoffset,否则页眉页脚的位置可能不对。

mod.tex

使用 positioning 库可以以相对位置排布 node,容易排出第二行。之后使用使用第二行的宽度与第三行单个 node 的宽度可以计算出第三行 node 之间的间距,然后用和第二行一样的方法即可。

ans-133-tikz.jpg

字体为 霞鹜文楷:https://github.com/lxgw/LxgwWenKai

\documentclass[tikz,border=5pt]{standalone}
\usetikzlibrary{positioning,decorations.pathreplacing,shapes.misc,arrows.meta}
\usepackage{ctex}
\setCJKmainfont{LXGW WenKai} % https://github.com/lxgw/LxgwWenKai
\begin{document}

\definecolor{col1}{HTML}{4faabc}
\definecolor{col2}{HTML}{e26a6f}
\definecolor{col3}{HTML}{4eab93}
\definecolor{col4}{HTML}{b85f84}

\makeatletter
\newcommand\tvtext[2]{\pgfmathparse{#1}\vbox to\pgfmathresult\p@{\vss\hsize=1em#2\vss}}
\newcommand\getsepoffixedwidth[5]{%
  \begingroup
  \pgf@process{\path#1;}\pgf@xa=\pgf@x
  \pgf@process{\path#2;}\pgf@xb=\pgf@x
  \pgf@process{\path#3;}\pgf@xc=\pgf@x
  \pgfmathparse{(\pgf@xb-\pgf@xa-(#4)*2*(\pgf@xc-\pgf@xa))/(#4-1)}%
  \edef\x{\endgroup\def\noexpand#5{\pgfmathresult pt}}\x}
\makeatother

\begin{tikzpicture}[
  l0style/.style={draw=#1,thick,rectangle,rounded corners=3pt},
  l1style/.style={l0style=#1,text width=76pt,align=center},
  l2style/.style={l0style=#1,text width=76pt,align=center},
  l3style/.style={l0style=#1,text height=76pt,inner xsep=5pt},
  llstyle/.style={draw=#1,thick,-Latex},
]
% level 1
\node(l1) [l1style=col1] at (0,0) {电磁学};
% level 2 node 2
\node(l2-2) [below left=7pt and 3pt of l1.south, l2style=col2] {基础:实验定律};
\node(l2-1) [left=6pt of l2-2.west, l2style=col1] {方法:归纳法};
\node(l2-3) [right=6pt of l2-2.east, l2style=col3] {核心:四大定理};
\node(l2-4) [right=6pt of l2-3.east, l2style=col4] {线索:相互作用};

\def\TV#1{\tvtext{76pt}{\linespread{1}\selectfont \noindent#1}}
\node(l3-1) [below right=15pt and 0pt of l2-1.south west, l3style=col1]{\TV{现象到本质}};
\getsepoffixedwidth{(l2-1.west)}{(l2-4.east)}{(l3-1.center)}{13}{\sep}
\node(l3-2) [right=\sep of l3-1, l3style=col1]{\TV{特殊到一般}};
\node(l3-3) [right=\sep of l3-2, l3style=col2]{\TV{库伦定律}};
\node(l3-4) [right=\sep of l3-3, l3style=col2]{\TV{安培定律}};
\node(l3-5) [right=\sep of l3-4, l3style=col2]{\TV{电磁感应定律}};
\node(l3-6) [right=\sep of l3-5, l3style=col3]{\TV{电场高斯定理}};
\node(l3-7) [right=\sep of l3-6, l3style=col3]{\TV{电场环路定理}};
\node(l3-8) [right=\sep of l3-7, l3style=col3]{\TV{磁场高斯定理}};
\node(l3-9) [right=\sep of l3-8, l3style=col3]{\TV{磁场环路定理}};
\node(l3-10) [right=\sep of l3-9, l3style=col4]{\TV{物质与电场}};
\node(l3-11) [right=\sep of l3-10, l3style=col4]{\TV{物质与磁场}};
\node(l3-12) [right=\sep of l3-11, l3style=col4]{\TV{电场与磁场}};
\node(l3-13) [right=\sep of l3-12, l3style=col4]{\TV{磁场与电场}};

\path[decorate,decoration={brace,amplitude=7pt},draw=col1,thick] (l2-1.north) -- (l2-4.north);
\begin{scope}[shorten >=-.4pt]
\foreach \i in {1,2} \path[llstyle=col1](l2-1.south)--(l3-\i.north);
\foreach \i in {3,...,5} \path[llstyle=col2](l2-2.south)--(l3-\i.north);
\foreach \i in {6,...,9} \path[llstyle=col3](l2-3.south)--(l3-\i.north);
\foreach \i in {10,...,13} \path[llstyle=col4](l2-4.south)--(l3-\i.north);
\end{scope}
\end{tikzpicture}

\end{document}

\foo 1 2 {} 得到的是 *1, 2 *,而不是 *1,2*

TeX 宏的参数有两种,一种是没有分隔符的参数(或定界符,undelimited parameter),另一种是有分隔符的参数(delimited parameter)。参数的分隔符就是两个参数之间的记号,是标记参数结束的一些符号。

TeX 在开始读取 undelimited parameter 时,会忽略空格,之后如果发现第一个记号是 begin-group character,则认为这个参数是由一对括号包裹(可以认为 { 标记参数的开始位置,} 标记结束位置,它们不会在最终的参数里出现),否则这个参数就是遇到的第一个非空格记号。
读取 delimited parameter 时,会忽略空格,这个参数由指定的定界符来标记结束的位置(定界符必须与定义时的完全匹配),并且 {} 需要正确嵌套,并且如果结果是一对正确嵌套的组,则最外层的括号不会出现在实参中。在这两种情况中,{ 可以是任意 catcode 为 1 的字符(即 begin-group character),} 可以是任意 catcode 为 2 的字符(即 end-group character)。

macro parameter(catcode=6 的字符或被 let 为这些字符的控制序列,一般是 #) + begin-group character 可以看成是一个特殊的定界符,只能放在最末尾。在匹配时,begin-group character 必须和定义时使用的字符完全一致。
如假定 [{ 的 catcode 都为 1,则 \def\foo#1#[...} 在使用时也必须是 \foo aaa[...,不能是 \foo aaa{...

了解这些之后,答案就很清楚了。\def\foo#1#2#{...},第一个参数是 undelimited parameter,第二个参数由 { 定界,#11#2 分别是 2 和空。

需要注意的是,TeX 在读取一个控制词(如 \a\foo 是控制词,\; 是控制符,它们都是控制序列)会忽略它后面的空格,所以 \foo␣␣ 等于 \foo␣ 等于 \foo。如前所述,macro parameter 可以是字符,也可以是控制序列,如

\let\pp#
\def\foo#1\pp2{\pp1,#2}
\def\foo#1#2{#1,#2}

两种完全一致。
空格只比较 catcode,不比较 charcode,TeX 在读取输入文本时,所有的 catcode=10 的字符都会变成 charcode=32, catcode=10 的记号。

更详细的内容可以参考 The TeXBook 第 20 章。

l3draw 相当于 pgf 中最基本的部分。tikz 的作用是把绘图代码转换为 pgf 宏。本问题相当于是说重新实现 tikz

pgf 的一个 node 有形状、锚点、文字,还可以被保存。这些代码是很多的。

如果只要实现文字旋转和指定到某一位置,用 l3draw 提供的几个 transform 命令即可,文字使用 box 或 coffin 皆可。要指定锚点则需要自己计算文字的尺寸,然后再根据它来计算平移变换需要设置的向量。

\documentclass{article}
\usepackage{l3draw}
\usepackage{tikz}
\begin{document}

\ExplSyntaxOn

\draw_begin:
\draw_path_moveto:n { 0cm , 0cm }
\draw_path_use_clear:n { }

\group_begin:
\draw_transform_rotate:n { 30 }
\draw_transform_shift:n { 1cm , 3cm }
\hbox_set:Nn \l_tmpa_box { A }
\draw_box_use:N \l_tmpa_box
\group_end:

\draw_end:

\ExplSyntaxOff


\tikz{
  \path (0,0);
  \node[rotate=30,inner sep=0pt,outer sep=0pt,anchor=south west] at (1,3) {A};
}

\end{document}

可以参考 pgfmanual 或者相关笔记:https://ask.latexstudio.net/ask/article/175.html ,pgf 的 node 在该笔记的第二十五章。

时间是从开始编译时到完全展开此函数时用的时间,这个整数除以 2^16 得到以秒为单位的时间。在不同的位置完全展开它,可以得到不同的值。

“CPU 时间”和“等待用户输入的时间”就是字面意思——执行此程序花费了 CPU 多少时间、等待用户输入花费了多少时间,程序在 IO 的过程 CPU 会去干别的事,IO 完成后再由操作系统把 CPU 叫回来继续执行。而且,在目前的桌面操作系统下,CPU 并不总是一直执行一个程序,而是一会执行这个程序一会执行那个。所以 CPU 时间并不是你感知到的程序执行的时间(real time)。

TeX 除了可以从文件读入,也支持从终端读入,比如

\ior_get_term:nN { Please~input:~ } \l_tmpa_tl

就会在终端中显示 Please input: ,等待用户输入,再把输入的内容保存到 \l_tmpa_tl 中。

并不是所有 x 中的 # 都需要双写。直接用 primitive 实现的一般不需要双写。但是即使这样也有例外情况,这应该是 LaTeX3 的函数命名不一致导致的,由于 \cs_new:Npx 这些函数使用广泛,目前看来不太可能再修改它们的名称了。目前我记得的不需要双写的 #x 参数有 \cs_new...\cs_(g)set... 系列以及 \iow_shipout_x:Nn。所有的 \exp_.. 中的 x 都需要双写(包括 \cs_generate_variant:Nn\prg_generate_conditional_variant:Nnn 生成的变体)。
需要双写的原因是定义了一个临时的宏,见后文。

# 作为引用参数的标识时,它应该仅写一次(随嵌套次数而倍增),当 # 作为它自己时,应该双写。
需要双写的 # 可以使用 \exp_not:n 包裹起来,这样就不要双写。如可以 \use:x { \exp_not:n { \def\tmp#1{#1} } }

\cs_new:Npx 相当于 \long\edef。这里的 x 其实是 e。

定义函数时,\edef\test#1#2{#1,#2},括号里的 #1 #2 是引用第一、二个参数,应该把它们看成一个整体作为 1 个记号,## 也应当看成一个记号。\test 实际上是:两个参数的宏,它展开为 <参数1>,<参数2>
\edef\test#1{\def\noexpand\foo##1#1##2{##1 ##2 #1 \def\foo####1{####1##2}}}\test 是有一个参数的宏,它展开为 \def\foo#1<参数1>#2{#1 #2 <参数1> \def\foo##1{##1#2}}

\use:x 实际上是 \def\use:x#1{\edef\temp{#1}\temp},当写 \use:x{?#1?} 时,\use:x 展开为 \edef\temp{?#1?}\temp,此时在定义 \temp 时就会出错,因为它没有参数,但它的替换文本中却引用了参数,所以必须双写 \use:x{?##1?},此时 ## 表示 #,而不是引用参数的标识。

\use:e 则不是这样,它使用 \expanded primitive 实现,没有定义一个临时的宏,# 直接就是作为它自己。

还是那句话,能使用 e 就使用 e 而非 x

1.
你的理解是对的,定义函数时遵循这个规则可以在需要展开其参数时不至于有太大的开销。
如果需要展开 nofe 参数之后的参数,如 \foo:Nno\foo:Nne\foo:Nff\foo:NnV\foo:no,只有使用 \expanded primitive(\tex_expanded:D)才是最快的。而所谓的“optimized function”则总可以找到一个不用 \expanded 实现且比 \expanded 快的方法。(“最快”与“更快”都是从一般的情况来说的)

就是说,使用 \expanded 实现的函数都认为是需要更慢的处理。

在终端中执行 latexdef -b \ExplSyntaxOn -a \ExplSyntaxOff \exp_args:NVo 可以查看 \exp_args:NVo 的定义,(目前)它是用 \expanded 实现的,但可以找到一个不用 \expanded (且比它快)的实现:

\cs_gset:Npn \exp_args:NVo #1#2#3
  {
    \exp_after:wN #1
    \exp_after:wN { \exp:w \exp_after:wN
      \__exp_eval_register:N \exp_after:wN #2 \exp_after:wN }
    \exp_after:wN { #3 }
  }

\exp_args:NNVV\exp_args:NcVV 也是如此。但由于它们目前都是使用 \expanded 实现的,因此才把它们归类到需要更慢的处理的函数上。

2.
last_unbraced 就是最后一个参数展开的结果的最外层不自动添加 {}。比如

\tl_set:Nn \foo { ab }
\exp_args:No \faa { \foo }          % 结果是 \faa { ab }
\exp_last_unbraced:No \faa { \foo } % 结果是 \faa ab

如果展开的结果中有括号,也不会移除括号。另外,Nc 参数的结果总是不自动添加 {}

3.
这些是用来定义 \exp_args:...\exp_last_unbraced:... 的。已经明确表示应当仅用于 l3expan 这个模块中,当然想在其它地方用也不是不可以。
\::: 作为分割符,前面的是展开方式,后面的是参数。
比如 \::n \::f \::o \::e \::e_unbraced \::: 就是 \::: 后面的那一项移除可能的括号,接着对随后的那些项依次进行指定的展开。(\.._unbraced 只能是最后一个)
好处是不受 TeX 的函数最多 9 个参数的限制,坏处是甚至可能比 \expanded 更慢。

把图片名分别保存到 seq 里即可。

\seq_new:N \g__my_chapter_oimage_seq % odd 
\seq_new:N \g__my_chapter_eimage_seq % even 
\seq_new:N \g__my_part_oimage_seq % odd 
\seq_new:N \g__my_part_eimage_seq % even 

\keys_define:nn { my/titleimage }
  {
    chapteroddimage  .code:n = 
      \seq_gset_from_clist:Nn \g__my_chapter_oimage_seq {#1} ,
    chapterevenimage .code:n = 
      \seq_gset_from_clist:Nn \g__my_chapter_eimage_seq {#1} ,
    partoddimage     .code:n = 
      \seq_gset_from_clist:Nn \g__my_part_oimage_seq    {#1} ,
    partevenimage    .code:n = 
      \seq_gset_from_clist:Nn \g__my_part_eimage_seq    {#1} ,
  }
\cs_new:Npn \__my_title_image:Nn #1#2 % image seq, number
  { \seq_item:Nn #1 { \int_mod:nn {#2} { \seq_count:N #1 } + 1 } }
\cs_new:Npn \my@chapterimagename
  {
    \int_if_odd:nTF { \value{page} } 
      { \__my_title_image:Nn \g__my_chapter_oimage_seq { \value{chapter} } }
      { \__my_title_image:Nn \g__my_chapter_eimage_seq { \value{chapter} } }
  }
\cs_new:Npn \my@partimagename
  {
    \int_if_odd:nTF { \value{page} }
      { \__my_title_image:Nn \g__my_part_oimage_seq { \value{part} } }
      { \__my_title_image:Nn \g__my_part_eimage_seq { \value{part} } }
  }
\cs_new_protected:Npn \titleimage #1 
  { \keys_set:nn { my/titleimage } {#1} }

使用时

\titleimage{
  chapteroddimage={inner_pics/songodd, inner_pics/yinghuaodd},
  chapterevenimage={inner_pics/songeven, inner_pics/yinghuaeven},
  ...
}

\includegraphics{\my@chapterimagename} % 会自动改变
\includegraphics{\my@partimagename}    % 会自动改变

放到文档类中时,不要用 my 作为模块名,改成你自己的名字。

1.
忽略类别码(catcode)是指在比较两个 token list 时,先把它们 \detokenize,然后再按字典序比较,(也就是不比较它们的类别码,因为经过 \detokenize 后,同一字符的类别码都相同,控制序列也没有了),即使用 \str_if_eq:...,反之,则使用 \tl_if_eq:...

\str_set:Nn \a { ~ \\ $ }
\tl_set:Nn  \b { ~ \\ $ }
\tl_set:Nx  \c { \c_space_tl \c_backslash_str \c_backslash_str \c_dollar_str }

\a\c 的值完全相同:即,它们的长度相同且每一对字符的 (catcode,charcode) 完全相同,也就是 \tl_if_eq:NNTF \a \c 返回 true 分支。
\b\c 的长度不同,即 \tl_if_eq:NNTF \b \c 返回 false 分支。但对 \b 的值使用 \detokenize\tl_to_str:n)之后,就变成了 \c(的值),也就是 \str_if_eq:NNTF \b \c 返回 true 分支。

prop 中 key 的比较就是使用 \str_if_eq:...
“忽略类别码”只有在比较 token list 时才有意义。

(在 (u)pTeX 中应该是 kcatcode 而非 catcode。)

2.
\prop_set_from_keyval:Nn 是使用 \keyval_parse:nnn 实现的。
\tl_trim_spaces:n 移除首尾空格,但在此之后,如果结果是一个正确嵌套的组(如 {a } 是,{a}{b} 不是),并不会继续移除外层的 {}。而 \keyval_parse:nnn 在分别解析 key 和 value 时则会继续移除外层的 {}

{~a~}~ = ~bc~ ,  % key 是 " a ",value 是 "bc"
~a~ = {~bc~}~ ,  % key 是 "a",value 是 " bc "
{ a= } = {b=c} , % key 是 "a=",value 是 "b=c"
{ a, } = {b,} ,  % key 是 "a,",value 是 "b,"
a =,             % key 是 "a",value 是 ""
a,               % key 是 "a",没有 value
这样,key 和 value 中可以包含空格、逗号和等号

如上例。\keyval_parse:nnn 并不会修改 key 和 value 的类别码。

3.
某些情况下可能会改变 =, 的类别码为 13(active),这样解析键值时就会出错,比如 keyval 宏包。
\keyval_parse:nnn 专门处理了这种情况。方法是递归。
先以 active , 分割出 n 项,然后对这 n 项分别以 other , 分割,由此得到 key=val。再对 = 施以类似的操作,由此得到每个 key 与 对应的 value。

代码中的多出的长度正好是 \@pnumwidth。这是因为没有正确设置目录中章的页码。
改为

\titlecontents{chapter}[0mm]
  {\addvspace{0em}}
  {\contentsmargin{0pt}\boxedd}
  {\contentsmargin{0pt}\boxeddd}
  {}
  [\addvspace{0em}]

即可。

load:该键只能作为宏包选项(或文档类选项)被设置;
preamble:该键只能在导言区设置;
general:该键只要有定义就能设置。

使用了 load 的键,只在使用 \SetKeys 时才能正确处理。使用 l3keys2e 宏包的设置键的命令不会正确处理。

使用了 preamble 的键,可以在导言区以及 begindocument/before 钩子(以及 etoolbox\AtEndPreamble)中被设置,无法在其它位置设置。

general,清除 loadgeneral 的效果,这是默认的情形。

设置 .usage:n 时,只是把键保存到 \l_keys_usage_load_prop\l_keys_usage_preamble_prop 中,等到设置键时,再根据键是否在它们中而决定是否能被设置(实际上,在加载宏包、在导言区之后,会重新定义这两个 prop 里的键,使得它们无法再被设置)。

下例中,有两个位置出错,因为它们在不正确的位置设置了某些键。
\DeclareKeys\SetKeys 由 LaTeX2e 内核提供,模块名是可选参数。

\documentclass{article}
\begin{filecontents}[force]{mypkg.sty}
\DeclareKeys[mypkg]{
  faa.store=\mypkgfaa,
  faa.usage=load,
  fbb.store=\mypkgfbb,
  fbb.usage=preamble,
  fcc.store=\mypkgfcc,
}
\ProcessKeyOptions[mypkg]
\end{filecontents}

\usepackage[faa=1,fbb=1,fcc=1]{mypkg}

\SetKeys[mypkg]{faa=2,fbb=2,fcc=2} % 出错:faa=2

\begin{document}
\SetKeys[mypkg]{fbb=3,fcc=3} % 出错:fbb=3

hello.
aa: \mypkgfaa,
bb: \mypkgfbb,
cc: \mypkgfcc.

\ExplSyntaxOn
\prop_show:N \l_keys_usage_preamble_prop
\ExplSyntaxOff

\end{document}

TeX 在获取命令的参数时,参数的 catcode 已经固定了,多个空格合并为一个,行结束符(一般是换行符)转为空格。所以再使用修改 catcode 的命令(比如 \obeyspaces)已经无效了。

要么用环境,且不能用 b 参数,要么原样获取参数,然后重新扫描(使用 \scantokens\tl_rescan:nn 及类似命令)。

\documentclass{article}
\begin{document}

\ExplSyntaxOn
\NewDocumentCommand{\test}{ O{\linewidth} +v }
  {
    \coffin_clear:N \l_tmpa_coffin
    \vcoffin_set:Nnn \l_tmpa_coffin {#1} 
      { % 注意如果 \parindent>0pt 这里会有缩进
        \small \ttfamily
        \obeyspaces
        \obeylines 
        \scantokens{#2}
        \ignorespacesafterend
      }
    \coffin_typeset:Nnnnn \l_tmpa_coffin {H}{l}{0pt}{0pt}
  }
\ExplSyntaxOff

\noindent % 移除段首缩进
\test{ha   ha
hahahha
}

\end{document}

当然也还有其它办法,不过比这个略微复杂。

PS:使用 varwidth 还能有不一样的效果:

\documentclass{article}
\usepackage{varwidth}
\begin{document}

\ExplSyntaxOn
\NewDocumentCommand{\testa}{ O{\linewidth} +v }
  {
    \coffin_clear:N \l_tmpa_coffin
    \hcoffin_set:Nn \l_tmpa_coffin 
      { 
        \varwidth [b] { \dim_eval:n {#1} } 
        \dim_set:Nn \parindent { \c_zero_dim }
        \small \ttfamily
        \obeyspaces
        \obeylines 
        \scantokens{#2}
        \endvarwidth 
      }
%    \coffin_typeset:Nnnnn \l_tmpa_coffin {H}{l}{0pt}{0pt}
    \coffin_display_handles:Nn \l_tmpa_coffin { blue }
  }
\NewDocumentCommand{\testb}{ O{\linewidth} +v }
  {
    \coffin_clear:N \l_tmpa_coffin
    \vcoffin_set:Nnn \l_tmpa_coffin {#1} 
      {
        \dim_set:Nn \parindent { \c_zero_dim }
        \small \ttfamily
        \obeyspaces
        \obeylines 
        \scantokens{#2}
        \ignorespacesafterend
      }
%    \coffin_typeset:Nnnnn \l_tmpa_coffin {H}{l}{0pt}{0pt}
    \coffin_display_handles:Nn \l_tmpa_coffin { red }
  }
\ExplSyntaxOff

\noindent
\testa{ha   ha
hahahha
}

\vspace{1cm}

\noindent
\testb{ha   ha
hahahha
}

\end{document}

image.png

tabularray 不支持这么写。只能把结果保存到 tl 里,然后用 expand 键。

\documentclass{ctexart}
\usepackage{tabularray}
\SetTblrInner[tblr]{%
    cells={c},
    hlines,
    vlines,
}
\begin{document}
\ExplSyntaxOn

\tl_clear:N \tableval 
\seq_set_split:Nnn \l_my_a_seq { , }{ a,b,c,d,ef }
\int_step_inline:nnn { 1 } { \seq_count:N \l_my_a_seq }
  {
    \int_compare:nNnTF {#1} < { \seq_count:N \l_my_a_seq }
      {
        \tl_put_right:Nn \tableval { #1 & }
      }
      {
        \tl_put_right:Nn \tableval  { #1 \\ }
      }
  }
\int_step_inline:nnn { 1 } { \seq_count:N \l_my_a_seq } 
  {
    \int_compare:nNnTF {#1} < { \seq_count:N \l_my_a_seq }
      {
        \tl_put_right:Nx \tableval { \seq_item:Nn \l_my_a_seq {#1} & }
      }
      {
        \tl_put_right:Nx \tableval { \seq_item:Nn \l_my_a_seq {#1} }
      }
  }
\ExplSyntaxOff

\begin{tblr}[expand=\tableval]{}
\tableval
\end{tblr} 
\end{document}

  1. 使用临时变量时,特别是需要用到 \.._put_left:Nn\.._put_right:Nn 等命令时,要注意先清空变量;
  2. 使用 \dim_compare:nNn.. 时,第 1、3 个参数必须是具体的长度(或长度表达式);
如此得到的 \l_tmpb_clist 为:{11.5632pt,34.68959pt,23.12639pt}

本例中,在使用 \clist_sort:Nn 之前,你可以用 \clist_show:N \l_tmpb_clist 看看是不是你想的那个结果:

\clist_set:Nn \l_tmpa_clist { ha, hahaha, haha }

\clist_clear:N \l_tmpb_clist
\clist_map_inline:Nn \l_tmpa_clist
  {
    \clist_put_right:Nn \l_tmpb_clist
      {
        \hcoffin_set:Nn \l_tmpa_coffin {#1}
        \dim_use:N \coffin_wd:N \l_tmpa_coffin
      }
  }
\clist_show:N \l_tmpb_clist

为什么结果不是 {11.5632pt,34.68959pt,23.12639pt} 而是

The comma list \l_tmpb_clist contains the items (without outer braces):
>  {\hcoffin_set:Nn \l_tmpa_coffin {ha}\dim_use:N \coffin_wd:N \l_tmpa_coffin}
>  {\hcoffin_set:Nn \l_tmpa_coffin {hahaha}\dim_use:N \coffin_wd:N \l_tmpa_coffin }
>  {\hcoffin_set:Nn \l_tmpa_coffin {haha}\dim_use:N \coffin_wd:N \l_tmpa_coffin }

因为你用的是 \clist_put_right:Nn,而
image.png
要把长度保存到 \l_tmpb_clist 里,而不是 \hcoffin_se...,可以这样:

\clist_set:Nn \l_tmpa_clist { ha, hahaha, haha }

\clist_clear:N \l_tmpb_clist
\clist_map_inline:Nn \l_tmpa_clist
  {
    \hcoffin_set:Nn \l_tmpa_coffin {#1}
    \clist_put_right:Nx \l_tmpb_clist
      { \dim_use:N \coffin_wd:N \l_tmpa_coffin }
  }

\clist_sort:Nn \l_tmpb_clist
  {
    \dim_compare:nNnTF {#1} > {#2}
      { \sort_return_same:    }
      { \sort_return_swapped: }
  }

\documentclass{article}
% \usepackage{amsmath}
% \usepackage{unicode-math}
\begin{document}

\def\cdotss{\cleaders\hbox to 0.5em{\hfil$\cdotp$\hfil}\hskip 3.1em\relax}

\begin{equation}
    9 \div 2 = 4 \cdotss 1
\end{equation}

\end{document}

image.png
间距自己改改就行了,0.5em 是点之间的间距,省略号的总长为 3.1em,3.1÷0.5=6···0.1,所以总共6个点,多余的 0.1em 作为空白,对半放在省略号的两边。

发布
问题