Repository navigation
xeCJK:命令边界处的间距应由实际输出字符类别决定(长期追踪) #992
Description
Activity
- addedwishlistLong-term feature wishLong-term feature wish
on Jul 18, 2026 - pinned this issue
on Jul 18, 2026 命令边界状态表1
- 每格依次表示源码空格
00、10、01、11;第一位是命令左侧,第二位是右侧,1表示有空格。 - 每个编号分为
xCJKecglue=false、true两行;候选和直接输入使用相同的选项值。 - ✅ 表示该选项值下,默认间距和可区分间距(
CJKecglue=5pt、CJKglue=1pt)都与直接输入相同;❌ 表示至少一种设置不同;— 表示尚未测试。 - 当前绿色单元均已纳入回归测试;
command-boundary01执行 1668 个普通命令断言,command-boundary-math01执行 5504 次公式比较,均全部通过。 - 「中·西·中」表示命令输出西文或数字、两侧是中文;「西·中·西」表示命令输出中文、两侧是西文;「中·中·中」表示命令和两侧都输出中文。
- 四个公式列都以直接
$x$为比较基准;两个混合方向分别检查公式左、右边界,避免一侧多出的间距与另一侧缺少的间距互相抵消。
编号 命令 xCJKecglue中·西·中 西·中·西 中·中·中 中·公式·中 中·公式·西 西·公式·西 西·公式·中 MWE 排版对照(5pt/1pt) 说明 1 普通展开宏( \newcommand)false ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件1 普通展开宏( \newcommand)true ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件2 显式分组 {...}false ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件命令输出中文时,右侧源码空格也能正确处理。回归测试: command-boundary01、command-boundary02。2 显式分组 {...}true ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件命令输出中文时,右侧源码空格也能正确处理。回归测试: command-boundary01、command-boundary02。3 \textbffalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件3 \textbftrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件4 \textitfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件4 \textittrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件5 \textttfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件5 \texttttrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件6 \textcolorfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件颜色切换不改变可见边界。回归测试: command-boundary01、fntef-color01、color-ecglue01。6 \textcolortrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件颜色切换不改变可见边界。回归测试: command-boundary01、fntef-color01、color-ecglue01。7 \mboxfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件命令结束时读取盒子实际排出的首尾类别。回归测试: command-boundary01、command-boundary02。 命令内公式四种周边文字均与直接$x$一致;原编号 28 已并入本行。7 \mboxtrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件PR #1005 重放 Default 类 marker 时同步外层列表的 spacefactor;嵌套盒子和中西混合内容的四种源码空格组合均与直接输入一致。回归测试:command-boundary01、command-boundary02。 命令内公式四种周边文字均与直接$x$一致;原编号 28 已并入本行。8 \makeboxfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \mbox;另测带可选参数的\makebox,以便发现 LaTeX 内核接口变化。8 \makeboxtrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \mbox;另测带可选参数的\makebox,以便发现 LaTeX 内核接口变化。9 \fboxfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \mbox;回归测试另含带可选参数的\framebox。9 \fboxtrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件PR #1005 对 box 注册策略的同一修复也覆盖嵌套 \fbox;四种源码空格组合均与直接输入一致。10 \colorboxfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件先收集 \colorbox直接排出的多个节点,再按实际内容恢复边界。colorbox-measure01另检查 #995 中的离线测量。10 \colorboxtrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件先收集 \colorbox直接排出的多个节点,再按实际内容恢复边界。colorbox-measure01另检查 #995 中的离线测量。11 \CJKunderlinefalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件恢复的左侧间距排在下划线装饰范围之外。回归测试覆盖 ulem 与 xeCJKfntef 的双向嵌套。 11 \CJKunderlinetrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件恢复的左侧间距排在下划线装饰范围之外。回归测试覆盖 ulem 与 xeCJKfntef 的双向嵌套。 12 \CJKunderdotfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \CJKunderline;回归测试含\uline与\CJKunderdot的双向嵌套。12 \CJKunderdottrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \CJKunderline;回归测试含\uline与\CJKunderdot的双向嵌套。13 \CJKsoutfalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \CJKunderline;回归测试含与\CJKunderline的嵌套。13 \CJKsouttrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \CJKunderline;回归测试含与\CJKunderline的嵌套。14 \ref(未加载 hyperref)false ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在 \@setref和\real@setref执行期间观察实际输出。14 \ref(未加载 hyperref)true ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在 \@setref和\real@setref执行期间观察实际输出。15 内容后随 \null(LaTeX 内核\ref使用的结构)false ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件只在零尺寸尾盒确实遮住边界标记时恢复状态。若显式 \hskip与词间空格无法区分,可在前面加入\kern0pt。15 内容后随 \null(LaTeX 内核\ref使用的结构)true ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件PR #1005 只移动由实际 marker 证明的有限节点序列。有源码空格时,7pt glue 按直接输入换成 CJKecglue;没有源码空格时,显式 7pt glue 原样保留。回归测试:command-boundary01、command-boundary02。16 \hyperref[...]{...}false ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件在整个 PDF 注释范围内观察实际首尾内容。回归测试: command-boundary01、hyperref-ecglue01。16 \hyperref[...]{...}true ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件在整个 PDF 注释范围内观察实际首尾内容。回归测试: command-boundary01、hyperref-ecglue01。17 \hreffalse ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \hyperref。17 \hreftrue ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅普通:默认 · 5pt/1pt
公式:默认 · 5pt/1pt
普通 show-case 源文件
公式 false 源文件 · 公式 true 源文件处理方法同 \hyperref。18 \ref(已加载 hyperref)false ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件由 \hyperref的处理覆盖;回归测试:ref-ecglue02。18 \ref(已加载 hyperref)true ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件由 \hyperref的处理覆盖;回归测试:ref-ecglue02。19 \pageref(数字标签,已加载 hyperref)false ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件回归测试: command-boundary01。19 \pageref(数字标签,已加载 hyperref)true ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件回归测试: command-boundary01。20 \eqref(西文标签,已加载 hyperref)false ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件根据两端固定的圆括号按西文类别恢复边界。回归测试: command-boundary01。20 \eqref(西文标签,已加载 hyperref)true ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件根据两端固定的圆括号按西文类别恢复边界。回归测试: command-boundary01。21 \url/\nolinkurl(西文内容)false ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在整个 URL 排版期间观察实际输出。回归测试: url-ecglue01、command-boundary01。21 \url/\nolinkurl(西文内容)true ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在整个 URL 排版期间观察实际输出。回归测试: url-ecglue01、command-boundary01。22 \HD@target(hypdoc 的不可见锚点)false ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在锚点前保存边界状态,锚点结束后原样恢复。回归测试: hypdoc-ecglue01。22 \HD@target(hypdoc 的不可见锚点)true ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件在锚点前保存边界状态,锚点结束后原样恢复。回归测试: hypdoc-ecglue01。23 \meta(hypdoc)false ✅✅✅✅— ✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件保留参数盒子内部的排版,同时根据外层尖括号按西文类别恢复边界。 23 \meta(hypdoc)true ✅✅✅✅— ✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件保留参数盒子内部的排版,同时根据外层尖括号按西文类别恢复边界。 24 \cs(hypdoc)false ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件将开头反斜线按西文类别处理,末尾类别取自实际输出。回归测试: command-boundary01。24 \cs(hypdoc)true ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件将开头反斜线按西文类别处理,末尾类别取自实际输出。回归测试: command-boundary01。25 \verbfalse ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件观察 \verb从开始扫描到结束的完整输出;\verb*和\MakeShortVerb定义的简写形式使用相同入口。25 \verbtrue ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件观察 \verb从开始扫描到结束的完整输出;\verb*和\MakeShortVerb定义的简写形式使用相同入口。26 \blx@pagetracker(biblatex 页码跟踪)false ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件把页码跟踪产生的写出节点作为不可见内容处理。测试替身只覆盖跟踪节点位于条目文字之前的情况。 26 \blx@pagetracker(biblatex 页码跟踪)true ✅✅✅✅✅✅✅✅✅✅✅✅— — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件把页码跟踪产生的写出节点作为不可见内容处理。测试替身只覆盖跟踪节点位于条目文字之前的情况。 27 跨 \hbox的显式\hskip(前一个盒子以中文结尾,#996)false — — ✅———— — — — #996 MWE 
普通 show-case 源文件待检查状态不会越过盒子误处理下一处显式间距。这里只测试了 00;无法与词间空格区分的显式\hskip仍可用\kern0pt保护。27 跨 \hbox的显式\hskip(前一个盒子以中文结尾,#996)true — — ✅———— — — — #996 MWE 
普通 show-case 源文件DELTA=0.0pt;稳定回归由boundary-crossbox01固化。28 \mbox{\vrule ...}(标尺盒子,#998)false ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件把盒子末尾直接排出的标尺按西文类别处理。直接输入 \vrule不触发字符类别转换;如需保留该行为,可用原语\hbox{\vrule ...}。28 \mbox{\vrule ...}(标尺盒子,#998)true ✅✅✅✅— — — — — — 普通:默认 · 5pt/1pt 
普通 show-case 源文件把盒子末尾直接排出的标尺按西文类别处理。直接输入 \vrule不触发字符类别转换;如需保留该行为,可用原语\hbox{\vrule ...}。29 \unit/\qty/\num(siunitx,#1000)false ✅✅✅✅— — — — — — #1000 MWE 
普通 show-case 源文件将这些命令的首尾按西文类别处理,并兼容 siunitx v2 的 \si、\SI。\ang的比较基准尚未确定,不在本行范围内。29 \unit/\qty/\num(siunitx,#1000)true ✅✅✅✅— — — — — — #1000 MWE 
普通 show-case 源文件默认间距和可区分间距各有 36/36 项通过;稳定回归由 siunitx-ecglue01固化。30 \(x\)false — — — ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅公式:默认 · 5pt/1pt 
公式 false 源文件 · 公式 true 源文件与直接 $x$使用相同的公式边界语义。30 \(x\)true — — — ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅公式:默认 · 5pt/1pt 
公式 false 源文件 · 公式 true 源文件与直接 $x$使用相同的公式边界语义。31 \ensuremath{x}false — — — ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅公式:默认 · 5pt/1pt 
公式 false 源文件 · 公式 true 源文件与直接 $x$和\(x\)使用相同的公式边界语义。31 \ensuremath{x}true — — — ✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅公式:默认 · 5pt/1pt 
公式 false 源文件 · 公式 true 源文件与直接 $x$和\(x\)使用相同的公式边界语义。测试方法
每项测试都把命令包装的内容与去掉包装后的相同可见内容比较。二者分别放入受限水平盒子测量,并扣除各自单独排版时的固有宽度差,剩余差值只来自左右边界。
每格同时检查默认间距和可区分间距。默认词间空格可能与
CJKecglue等宽,而默认CJKglue的自然宽度可能为零,因此只测默认设置会漏掉节点类型错误。节点结构另由xeCJK/testfiles/中的回归测试检查。普通命令和公式矩阵都使用具体的可编译入口:例如 core false/default、core true/default、math false/default 和 math true/default。每行 MWE 单元格列出该场景所需的默认/5pt/1pt 驱动,不再用目录、README 或“同上”代替。
更新表格
- 新增或修复一个场景后,同时运行默认间距和可区分间距测试。
- 更新对应行的标记、说明和排版图片。
- 修复合并后,从合并提交重新验证,再修改本评论;未合并结果只写在相应 PR 中。
- 无法实现的组合保留 ❌,并说明原因和可用的规避方法。
matrix-verdicts.tsv保留为初始审计快照,只有明确替换整张表的基线时才更新。
Footnotes
-
本表只记录已经合并到
master并重新验证的结果。2026-07-18 的初始审计基于master4628cb44(xeCJK v3.10.3、ctex v2.6.3)。PR fix(xecjk): unify command-boundary spacing recovery #999 合并为cb6b2f73后,第 1—26 行从该提交重新测试,280 个可表达组合全部通过。PR fix(xecjk): close the #996/#998/#1000 boundary-capture gaps #1001 合并为10500b33后,当时编号 27—30 的各行从该提交重新测试;当时的矩阵只覆盖xCJKecglue=false。2026-07-21 又从master10500b33检查xCJKecglue=true:排除已转交 xeCJK:需要明确行内公式与中文相邻时的间距规则 #1002 的行内公式后,默认间距通过 318/320 项,可区分间距通过 312/320 项。PR fix(xecjk): 恢复 xCJKecglue 命令右边界状态 #1005 合并为8007e4df后,从该提交重新运行 16 个驱动;普通命令在xCJKecglue=false/true与默认/可区分间距四种配置下均为 320/320 通过,第 7、9、15 行据此更新。初始逐项差值保存在matrix-verdicts.tsv。 PR fix(xeCJK): 修正命令内行内公式的边界间距 #1009 合并后的master为1b77613b;从该提交重新解包 xeCJK 后,四套公式矩阵均为 272/272 通过,17 张公式 show-case 也全部一致。原编号 28 的\mbox{$x$}已并入编号 7,后续编号随之递补。 ↩
- 每格依次表示源码空格
Note
本评论记录 2026-07-18 基于
master4628cb44所做的阶段性分析。随后合并的 PR #999 采用了更完整的捕获和注册机制(capture/register),取代了下文分析阶段设想的实现。当前结果以命令边界状态表为准。阶段性分析:从逐命令修补转向统一处理
对状态表前 26 行进行节点检查后,当时发现的 132 个不一致组合大致可以归入四类。这说明问题虽然涉及许多命令,但底层原因并没有同样繁多;继续为每个命令分别保存和恢复状态,既容易重复代码,也很难保证左右边界的处理一致。
1. 源码空格遮住边界标记
部分场景中,边界标记仍在当前列表内,但它后面已经出现了源码空格形成的 glue(可伸缩间距)节点。从命令边界进入西文的旧恢复路径(Boundary→Default)只检查
\lastkern,因此找不到被 glue 节点遮住的标记;当时从命令边界进入中文的路径(Boundary→CJK)则已经包含相应检查。这是两个方向处理不对称造成的共同缺口。典型节点形状为:
中文 → 标记用 kern 节点 → 源码空格产生的 glue 节点 → 西文2. 盒子或数学节点遮住边界标记
\mbox、\makebox、\fbox、\colorbox等命令会把内容放进盒子。普通的\lastkern或\lastskip无法检查盒子内部,也无法越过盒子找到先前的边界标记。因此,仅在命令入口保存状态还不够,命令结束时还要根据实际输出重建左右边界。3. 特殊操作节点遮住边界标记
TeX 使用 whatsit 节点记录写出文件、切换语言等不直接排出文字的特殊操作。
biblatex页码跟踪和部分\verb路径会留下这类节点。它们与盒子问题相似:实际文字没有改变,但末尾节点已经不再是边界标记。过去的定点补丁通常只防止插入多余间距,没有完整恢复本应存在的间距。这里不能采用“遇到任意 whatsit 都继续向前查找”的通用规则,因为无法证明所有 whatsit 都能安全忽略。最终实现需要在已知命令的完整作用范围内观察输出。
4. 命令本身改变了排版语义
\eqref、\meta、\cs等命令可能增加括号、数学节点、反斜线或字体变化。此时命令输出与参数文本并不相同,不能只根据参数内容判断边界类别,而应观察实际排出的首尾内容。数学公式的比较基准还有额外问题,目前由 #1002 继续讨论。#999 采用的最终方案
上述分析确认了“应建立统一处理层”的方向。分析阶段曾设想从列表末尾逐个取出节点,找到边界标记后再按原顺序恢复这些节点;最终实现没有采用这种方法。PR #999 按命令产生的节点形式建立了五种注册策略:
box:处理末尾留下单个盒子的命令;wrapped-box:收集会直接写出多个节点的盒子命令;stream:观察命令执行期间直接写入当前列表的内容;transparent:用于不应改变可见边界的命令;post-transparent:处理命令结束后才能观察到的零尺寸盒子。
运行期间,XeTeX 的字符类别转换(interchar)会报告实际出现的首、尾类别;命令结束后,框架据此恢复左右边界。这样,命令名称只决定采用哪种注册策略,间距判断仍由实际输出决定。PR #1001 又补齐了跨盒显式 glue、盒内数学内容或标尺(rule),以及 siunitx 命令等后续缺口。
这套实现仍有一个无法消除的限制:参数完全相同的源码空格和显式
\hskip会产生结构与参数都相同的 glue 节点,TeX 无法可靠判断其来源。需要保留这类显式间距时,可以在前面加入\kern0pt。初始矩阵、节点探针和逐项差值仍保存在
gh-assets/issues/992/,作为这次阶段性分析的复现材料。历史上关于这类问题有过几次讨论。结论大致是:
技术上实现一个 case 不难。但是有很多的 edge case,所以很难保证完备性。因此,暂时不去实现;同时在 xeCJK 层面,中文与西文之间建议手工加上空格。
因为这样的讨论和结论,在相当长时间内,xeCJK 对这类问题都是「回避」的。即是说,知道它们存在,但总是保持现状留待以后。大约 2026-04,接入 Claude Code 之后,我开始试着逐个解决最常见的 cases。这些工作确实解决了一系列问题,但也确实引入了一系列新的问题——所谓按下葫芦起了瓢。这即是 xeCJK 在 3.10.0 -- 3.10.2 上类似问题的反复踩坑经历。
但踩坑不应该掉到坑里,而应该踩出价值。上一评论的工作试图对此类问题进一步分类,以期能找到相对完备的解法。或者说,至少对于常见的问题,我们能有统一的解决模板。发现新的问题之后,只需要将对应的命令注册进去即可自动解决。
接下来将试着做一做这个方案的原型。
Reacted by Mingyu Hsia and Explorer#992 的状态表还没有系统检查行内公式。目前只记录了几个内部使用数学模式的命令,例如
\eqref、\mbox{$x$}和 siunitx 命令;下面两类常见写法仍未覆盖:- 直接在中文旁边使用
$x$、\(x\)或\ensuremath{x}; - 在已有命令中放入公式,例如
\textbf{$x$}、\textcolor{red}{$x$}或\fbox{$x$}。
这些场景的实测结果和待解决问题记录在 #1002。现在还不能直接把结果填入状态表,因为需要先确定比较基准:去掉外层命令后,应当与直接输入的
$x$相同,还是应当按西文字母x的边界处理?目前这两种写法本身就会产生不同间距。#1002 明确预期行为后,再补充相应的状态表项目和回归测试。
Reacted by Mingyu Hsia- 直接在中文旁边使用
- added 5 commits that reference this issue
on Jul 21, 2026 - added 6 commits that reference this issue
on Jul 29, 2026


Note
本 issue 取代 #491,长期跟踪 xeCJK 在命令边界处的中西文及行内公式间距。#491 中的历史讨论、定点修复和图片仍保留作参考。
问题
命令夹在中文、西文、数字或行内公式之间时,边界间距应由命令实际排出的内容决定,并与去掉外层命令后的直接输入一致。
比较时需要覆盖命令左右有无源码空格的四种组合,即
00、10、01和11。判定不能只看总宽度:一侧多出的间距可能与另一侧缺少的间距互相抵消;默认词间空格也可能恰好与CJKecglue等宽。因此测试同时使用左右混合方向、可区分间距和节点/断行回归。当前状态
逐命令结果记录在命令边界状态表中。每个编号分别列出
xCJKecglue=false和xCJKecglue=true;候选内容与直接输入使用相同的选项值。xCJKecglue=false/true与默认/5pt/1pt 可区分间距下检查;四种配置均为 320/320 通过。command-boundary01执行 1668 个普通命令断言,command-boundary-math01执行 5504 次公式比较;当前 xeCJK 标准回归为 109/109 通过。\mbox{$x$}已并入编号 7\mbox的四个公式列,后续编号随之递补;\(x\)和\ensuremath{x}分别列为编号 30、31。master1b77613b重新验证。状态表中的 MWE 均指向具体、可直接编译的
.tex文件,不以目录、README 或“同上”代替。公式矩阵的四个入口为:xCJKecglue=false、默认间距;xCJKecglue=false、5pt/1pt;xCJKecglue=true、默认间距;xCJKecglue=true、5pt/1pt。xCJKecglue=<glue>会同时把CJKecglue设为该<glue>,并启用xCJKecglue。因此不为这一简写复制整张矩阵,只用独立回归测试检查它与显式设置CJKecglue=<glue>, xCJKecglue=true的结果相同。CJKspace是另一项独立设置,不与xCJKecglue做全组合。状态表只反映已经合并到
master并重新验证的实现。未合并修复的测试结果应留在相应 PR 中,不能提前把红叉改成绿勾。已知限制
TeX 的 glue 节点不记录它来自源码空格还是显式
\hskip。两者参数完全相同时,xeCJK 无法可靠区分;需要保留显式 glue 时,可以在它前面加入\kern0pt,阻止边界恢复逻辑把它当作源码空格处理。当前表格证明已经登记的命令与直接输入一致,不表示 xeCJK 可以推断任意用户宏的输出。新的命令仍需先确定其可见内容和比较基准,再选择相应的 capture/register 策略。
维护方式
发现新的命令或边界组合时:
xCJKecglue=false和xCJKecglue=true;00、10、01、11四种源码空格组合;master复验后,更新状态表、具体 MWE、截图和回归测试。当所有已知且可以实现的组合都有回归测试,无法实现的组合也都有机制说明、支持边界和可用的规避方法时,可以关闭本 issue。