Skip to content

xeCJK:siunitx 的 \SIrange、\qtyrange 和 \numrange 左侧缺少中西文间距 #1092

Closed

Description

@Fenyutanchan

与已有问题的关系

这是 #992 命令边界追踪中的一个未覆盖场景,与 #1000 的机制相同。
#1000 已由 #1001 修复,但该修复注册的是 \unit、\qty、\num、\si 和 \SI,未包含区间命令。
本报告不依赖任何自定义宏包。

复现环境

  • TeX Live 2026,XeLaTeX。
  • LaTeX2e:2026-06-01。
  • xeCJK:2026-08-21 v3.10.6。
  • siunitx:2026-09-08 v3.5.12。
  • 中文字体:TeX Live 自带的 FandolSong-Regular.otf。

2026-09-22 核对的仓库 master 为 4f78ab2。
其中 xeCJK/xeCJK.dtx 最近一次修改为 7023032,注册函数仍未包含这三个区间命令。
下述编译测试使用本机安装的上述版本;没有把源码检查表述为重新构建整个 master 后的测试。

最小复现

运行 xelatex -interaction=nonstopmode -halt-on-error range-mwe.tex:

\documentclass{article}
\usepackage{xeCJK}
\setCJKmainfont{FandolSong-Regular.otf}
\usepackage{siunitx}
\listfiles
\sisetup{range-phrase={--},range-units=single}
\setlength{\parindent}{0pt}
\begin{document}
对照:长度约为 \SI{30}{\metre},后接中文。\par
旧命令:长度约为 \SIrange{30}{70}{\metre},后接中文。\par
新命令:长度约为 \qtyrange{30}{70}{\metre},后接中文。\par
纯数字:取值约为 \numrange{30}{70},后接中文。\par
无空格:中文\SIrange{30}{70}{\metre}中文。\par
有空格:中文 \SIrange{30}{70}{\metre} 中文。\par
\end{document}

预期:区间输出左侧与汉字之间保留正常的 CJKecglue,与单值 \SI 的边界行为一致。
实际:\SIrange、\qtyrange 和 \numrange 的左侧间距丢失;在源码中手动输入普通空格不能解决。
单值 \SI 的对照行正常。

修复前:
Image

临时规避方式

在导言区、加载 xeCJK 和 siunitx 后添加:

\xeCJKsetup{
  experiment/boundary-register={command=\SIrange,strategy=stream,mode=default},
  experiment/boundary-register={command=\qtyrange,strategy=stream,mode=default},
  experiment/boundary-register={command=\numrange,strategy=stream,mode=default}
}

建议修复

在 xeCJK/xeCJK.dtx 的 \@@_boundary_register_siunitx: 中,为三个区间命令补充与 \SI 相同的固定西文首尾边界注册,并检查命令是否存在。
同时扩展 siunitx-ecglue01.lvt 的回归覆盖。
区间命令是独立入口,不能依赖已有的 \qty 或 \SI 钩子自动覆盖。

建议将本 issue 与 #992 关联;修复合并并复验后,再更新长期追踪表的覆盖范围。
列表、乘积和角度命令的行为尚未在本报告中验证,不将它们列为已经确认的同一故障。

修复后:
Image

本 issue 报告由 [Codex : gpt-6-astra] 生成,本人确认 MWE 真实有效,本人未确认建议修复策略是否有效。

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    🐛 Bug 分析 #1092

    项目 结果
    根因 siunitx 兼容适配器只为单值命令注册固定 Default 首尾的 stream capture,遗漏了三个区间命令。
    严重程度 🟡 medium
    适合后续自动实现 ✅ 是

    📋 详细分析

    复现路径:

    1. 使用 Issue 中的 XeLaTeX 最小示例,加载 xeCJK 和 siunitx。
    2. 比较 \SI、\SIrange、\qtyrange、\numrange 前后紧邻中文时的间距。
    3. 单值命令能够保留 CJKecglue,三个区间命令的左侧间距丢失;导言区手动注册这三个命令后可规避问题。

    代码定位:

    问题原因:

    siunitx 的这些命令会进入数学模式排版,数学节点会遮住 xeCJK 用于边界恢复的 marker。已注册的命令在入口和出口通过固定 default stream capture 报告两端为西文类别,从而重建中文与命令之间的 CJKecglue。\SIrange、\qtyrange 和 \numrange 是独立入口,不会自动经过 \SI 或 \qty 的 hook;由于没有对应注册,左侧 marker 无法被恢复,普通源码空格也不能等价替代该边界恢复过程。

    Issue 提供的 experiment/boundary-register 临时规避方式能够直接恢复这三个命令的间距,与源码遗漏注册的根因相互印证。建议在 \@@_boundary_register_siunitx: 中为三个区间命令增加与 \SI 相同的 default stream 注册,并用命令存在性检查保持对不同 siunitx 版本的兼容;同时扩展 siunitx-ecglue01.lvt 的回归矩阵。该修改范围集中、逻辑明确,预计为小规模改动。

    本分析基于提交 4f78ab2a0ebd07604ac2372c4c8eb11d92ef7b73 的源码检查及 Issue 提供的复现信息,未修改仓库或重建整个 master。


    由 codex / gpt-5.6-sol 生成;本流程只分析,不自动修改代码。

  2. Liam0205 commented on Sep 22, 2026

    @Liam0205
    Contributor

    @github-actions[bot] 笑死,AI 和 AI 在对话。

    这个其实就是 experiment/boundary-register 的预期场景。很高兴你找到了它。

    此类问题可能无法穷举,暂时不打算「发现一处,修改一处」。待集中维护期再处理此类问题。

  3. self-assigned this
    on Sep 22, 2026
  4. Fenyutanchan commented on Sep 23, 2026

    @Fenyutanchan
    Author

    @github-actions[bot] 笑死,AI 和 AI 在对话。

    AI 好啊,要我之前就只能写个 MWE 了,我可不大会整模板😂

    这个其实就是 experiment/boundary-register 的预期场景。很高兴你找到了它。

    好好好,这也不是我找的,AI 好啊!

    此类问题可能无法穷举,暂时不打算「发现一处,修改一处」。待集中维护期再处理此类问题。

    了解了,我现在就用那个注册函数绕过去吧。

    谢谢回复!

  5. Explorer-cc commented on Sep 23, 2026

    @Explorer-cc
    Contributor

    xeCJK 文档的源码里也有大量在命令左侧的 ecglue 丢失的 edge case,都是暂时用「~」代替的,可以到时一起改动...

  6. added theissue type on Sep 27, 2026
  7. Liam0205 commented on Oct 1, 2026

    @Liam0205
    Contributor

    已复现,根因已定位

    感谢报告。问题确实存在,而且范围比 issue 里列出的三个命令更大:xeCJK 只给 siunitx 的 \unit、\qty、\num 和旧名 \si、\SI 注册了命令边界处理(#1000)。其余排版命令左侧紧接汉字时,都缺少 \CJKecglue,右侧正常。

    siunitx 各命令两侧的间距

    绿色是汉字与 siunitx 输出之间实际排出的间距,红色竖条表示此处没有间距。\SI、\qty 两侧都是 3.33pt;\SIrange、\qtyrange、\numrange、\SIlist、\qtylist、\numlist、\qtyproduct、\numproduct、\complexnum、\complexqty、\duration、\ang 左侧都是 0pt,右侧 3.33pt。仓库 master(v3.10.7 开发版)与 TeX Live 安装的 v3.10.1 结果相同。

    根因:siunitx 在命令内部用 $...$ 排版数字和单位。直接在源码写 中$30$ 时,xeCJK 在汉字之后看到下一个记号是 $,会在公式前补上 \CJKecglue。汉字之后是 \numrange 这样的宏时,xeCJK 只留下一对边界标记(kern),等 siunitx 在宏内部进入数学模式,\mathon 节点就把标记盖住,间距再也补不上。节点列表可以直接看到这一点:

    中$30$               中  \glue 3.33  \mathon 3 0 \mathoff
    中\numrange{30}{70}  中  \kern -0.00017 \kern 0.00017  \mathon 3 0 \mathoff ...
    

    已注册的命令由 stream capture 在命令入口按西文(Default)首类别补上间距;其余命令都是独立的顶层入口,不会经过 \qty、\num 的命令钩子,所以没有这一步。

    ctex-kit/xeCJK/xeCJK.dtx

    Lines 13587 to 13599 in 4a6e133

    % \begin{macrocode}
    \cs_new_protected:Npn \@@_boundary_register_siunitx:
    {
    \@@_boundary_register_stream:nn { unit } { default }
    \@@_boundary_register_stream:nn { qty } { default }
    \@@_boundary_register_stream:nn { num } { default }
    \cs_if_exist:NT \si
    { \@@_boundary_register_stream:nn { si } { default } }
    \cs_if_exist:NT \SI
    { \@@_boundary_register_stream:nn { SI } { default } }
    }
    \@@_package_hook:nn { siunitx }
    { \@@_boundary_register_siunitx: }

    修复方向:把 siunitx 输出必然以数字、单位或括号开始和结束的排版命令都注册为固定 Default 首尾的 stream capture,与 \SI 相同,并先检查命令是否存在。用 issue 里的 experiment/boundary-register 写法注册这些命令后,宽度矩阵在四种源码空格、中文与西文上下文中都与直接输入一致,range-units=bracket、中文 range-phrase 等选项下也一致。\ang 在 #1000 时因为“不确定应与哪种直接输入比较”而暂不注册。现在实测两种候选基准结果相同:文本 中30°文 与公式 中$30^{\circ}$文 两侧都有 \CJKecglue,所以一并注册。\tablenum 用于表格单元格对齐,输出带 fill glue 的盒子,不属于行内数字,不在这次范围内。接下来我会开分支修复,并扩展 siunitx-ecglue01 的回归覆盖。

    注意:修复合入后,xeCJK 会内建处理这些命令。按照 experiment/boundary-register 的接口约定,对已由 xeCJK 内建处理的命令再做用户注册会报错(boundary-register-conflict),所以升级到包含修复的版本后,请删掉导言区里这三条临时注册。

    测量方法与复现文件

    宽度矩阵:命令写法与去掉命令后的同样可见内容(例如 中\numrange{30}{70}文 对 中30–70文)比较,CJKecglue 设为 5pt,四种源码空格 00/10/01/11。未注册的命令在中文上下文里每格差 −5pt,西文上下文为 0。

    文件都在 gh-assets 分支的 issues/1092/:

    • issue1092-mwe.tex:issue 原 MWE,补上列表、乘积、复数、时长四行。
    • issue1092-confirm.tex:上图的源文件,左右间距由盒子宽度差算出。
    • issue1092-matrix.tex:宽度矩阵。
    • issue1092-nodes.tex:上面的节点列表。

    测试环境:XeLaTeX,TeX Live 2026,siunitx 2026-05-15 v3.5.5。

  8. added 7 commits that reference this issue on Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions