Skip to content

ci(xeCJK): 3.10.5 的 CTAN announcement text 中有的 \ 变成了 ¥ #1066

Description

@muzimuzhi

xeCJK 的 CHANGELOG.md 是中文的,发布到 CTAN 时则翻译为英文,且按修改类别分为 bug fixes、new features 等。

xeCJK 3.10.5 的 CTAN announcement text,部分 \ 变成了 ¥。之前的 announcement text 无此问题。

Announcement text:
This release fixes several bugs related to CJK glue handling, brace-group 
consumption, hyperlink anchors, and underline/strikethrough decorations, and 
adds a new transparent wrapper variant for brace-delimited argument forwarding.

BUG FIXES

- `\CJKecglue` inserted at source-space positions now uses a portable code path, 
preventing its shrink component from being frozen inside the fixed-width segment 
boxes produced by `ulem` (#1037).
- Before boundary detection, category-code-4 `&` in the parameter copy is 
replaced with `\scan_stop:`, fixing compilation errors when commands like 
`\colorbox` receive arguments containing `&` inside `\halign` (#1043).
- `\sbox` now uses a dedicated adapter: prefixes such as `\global` must 
immediately precede `\setbox`, but assignments placed in the `cmd/sbox/before` 
hook consumed the prefix, causing `\global\sbox` to silently degrade to a local 
assignment (#1029).
- Only the single triggering left brace is now absorbed instead of swallowing 
the entire group and re-emitting it, fixing the `Improper alphabetic constant` 
error when `中文¥¥` appears in `tabular` (#1038).
- Inline anchor outputs `¥hyper@anchor`, `¥Hy@raisedlink`, and 
`¥__hyp_target_raise:n` are registered as transparent captures so that 
`¥hypertarget`, `¥phantomsection`, `¥MakeLinkTarget`, unnumbered heading 
anchors, and manually wrapped anchors no longer mask the actual boundaries on 
either side; the path via `¥hyper@anchorstart` (e.g. `¥pdfbookmark`) is not yet 
covered (#1047).
- The stream capture for `l3doc` is moved from the internal `¥__codedoc_meta:n` 
up to the public `¥meta`, so that `¥CJKecglue` on both sides is evaluated using 
the font metrics outside the command (#1046).
[...]

PS:我订阅了 ctan-ann.ctan.org mailing list,所以看到了 xecjk 3.10.5 的 announcement text。

Activity

  1. github-actions commented on Aug 9, 2026

    @github-actions
    Contributor

    🐛 Bug 分析 #1066

    项目 结果
    根因 release-notes.md 中的反斜杠是正确的,但 Claude 生成 announcement.md 后没有校验关键字符,导致部分 LaTeX 命令中的 \ 被写成字面 ¥,随后原样上传到 CTAN。
    严重程度 🟢 low
    适合后续自动实现 ✅ 是

    📋 详细分析

    复现路径:

    1. 运行 release-ctan-upload.yml 的 prepare-announcement,由 scripts/extract-changes.py 生成 release-notes.md。
    2. Claude 按工作流提示翻译并写入 announcement.md。
    3. 工作流当前只检查文件非空,随后 l3build upload 直接提交。
    4. CTAN 页面中可观察到 中文¥¥、¥hyper@anchor 等字面内容。

    代码定位:

    问题原因:发布流水线把 LLM 输出当作可直接上传的最终文本;对 LaTeX 命令中的反斜杠没有做逐字校验,也没有比较源条目与译文中的命令记号。因而翻译模型的字符替换可进入 CTAN 说明。建议后续在上传前拒绝包含异常 ¥ 的命令记号,或对源文本中的命令集合做一致性检查。


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

  2. Liam0205 commented on Aug 9, 2026

    @Liam0205
    Contributor

    @muzimuzhi 目测和经典编码问题有关系。🤣

  3. Liam0205 commented on Aug 9, 2026

    @Liam0205
    Contributor

    @muzimuzhi llm 生成的是正确的。可以确认是编码问题。不太确定出在 CTAN 那一侧还是 upload 步骤。之后查查看。

    This release fixes several bugs related to CJK glue handling, brace-group consumption, hyperlink anchors, and underline/strikethrough decorations, and adds a new transparent wrapper variant for brace-delimited argument forwarding.
    
    BUG FIXES
    
    - \CJKecglue inserted at source-space positions now uses a portable code path, preventing its shrink component from being frozen inside the fixed-width segment boxes produced by ulem (#1037).
    - Before boundary detection, category-code-4 & in the parameter copy is replaced with \scan_stop:, fixing compilation errors when commands like \colorbox receive arguments containing & inside \halign (#1043).
    - \sbox now uses a dedicated adapter: prefixes such as \global must immediately precede \setbox, but assignments placed in the cmd/sbox/before hook consumed the prefix, causing \global\sbox to silently degrade to a local assignment (#1029).
    - Only the single triggering left brace is now absorbed instead of swallowing the entire group and re-emitting it, fixing the Improper alphabetic constant error when 中文\\ appears in tabular (#1038).
    - Inline anchor outputs \hyper@anchor, \Hy@raisedlink, and \__hyp_target_raise:n are registered as transparent captures so that \hypertarget, \phantomsection, \MakeLinkTarget, unnumbered heading anchors, and manually wrapped anchors no longer mask the actual boundaries on either side; the path via \hyper@anchorstart (e.g. `\pdfbookmark`) is not yet covered (#1047).
    - The stream capture for l3doc is moved from the internal \__codedoc_meta:n up to the public \meta, so that \CJKecglue on both sides is evaluated using the font metrics outside the command (#1046).
    - Period-style decorations now save end-of-segment state per nesting level, preventing an inner command from overwriting the right end of an outer one (#1012).
    - Body text now always enters ulem arguments as literal tokens: only "math followed by a trailing source space" is reformatted, and reformatting first assembles then expands once, preventing \CJKecglue to the right of Latin words from being trapped in a fixed-width decoration segment box and losing its shrink component (#1026).
    - The user manual now explains that a nested segment inside line-style commands cannot break across lines, distinguishes line-style from symbol-style commands, and provides alternative approaches; a regression guard for the \UL@onin path is added using "ability to line-break" as the observable (#1057).
    - Wavy underlines and diagonal strikethroughs now use a quarter-em drawing unit that scales with font size and ordinary \leaders; the minus-sign variant insets by half a period at each end to prevent adjacent commands from merging (#1012).
    - Decoration content is wrapped in an empty ActualText marker, and LaTeX tagging is suspended during decoration box construction, preventing glyphs used by wavy underlines and diagonal strikethroughs from appearing in PDF text extraction results (#1017).
    
    NEW FEATURES
    
    - A new transparent wrapper variant is added that forwards arguments delimited by braces, for use by target functions that consume their argument as \hbox:n content (#1047).
    
    Source code: https://github.com/CTeX-org/ctex-kit
    Issue tracker: https://github.com/CTeX-org/ctex-kit/issues
    
  4. Liam0205 commented on Aug 9, 2026

    @Liam0205
    Contributor

    @Liam0205 从日志看,也没啥问题。

    https://github.com/CTeX-org/ctex-kit/actions/runs/31242377357/job/93065475564

    那么大概率是 CTAN 自己的问题了。

  5. changed the title [-]xeCJK: 3.10.5 的 CTAN announcement text 中有的 `\` 变成了 `¥`[/-] [+]ci(xeCJK): 3.10.5 的 CTAN announcement text 中有的 `\` 变成了 `¥`[/+] on Aug 9, 2026
  6. myhsia commented on Aug 9, 2026

    @myhsia
    Member

    @Liam0205 给 CTAN 写封信 我觉得可能也需要同时抄送webmaster🫠

  7. Liam0205 commented on Aug 10, 2026

    @Liam0205
    Contributor

    谢谢报告,问题属实。我把整条链路复现了一遍:我们投出去的内容是正确的,¥ 由 CTAN 侧产生。

    不是我们这边的问题

    逐环节查过:

    1. 生成的 announcement.md 正确。 workflow artifact 里第 9 行是 `\hyper@anchor`、
    `\Hy@raisedlink`,都是正常反斜线。

    2. l3build 的转义正确。 l3build-upload.lua:329-332 把 \ 转义成 \\ 写进 curl 的
    config 文件(form-string="...")。我实测了 curl config 的还原规则:

    config 里的反斜线数 curl 实际发出
    2 1
    4 2

    正好是 l3build 期待的语义,既没多转义也没少转义。

    3. curl 实际发出的字节正确。 用同一份 announcement.md 跑 l3build 的转义逻辑,再让
    curl 把 config POST 到本地 socket 捕获请求体:

    --- CJKecglue ---   '- `\CJKecglue` inserted at source-space positions'
    --- 中文 ---        'error when `中文\\` appears in `tabular` (#1038).'
    --- hyper@anchor ---'- Inline anchor outputs `\hyper@anchor`, `\Hy@raisedlink`,'
    
    请求体里 U+00A5 (¥) 出现次数: 0
    请求体里反斜线出现次数: 26
    

    一个 ¥ 都没有,替换发生在 CTAN 接收之后。

    另外确认 CTAN 页面上存的是真的 U+00A5 字符,不是浏览器渲染问题:中 U+4E2D、
    文 U+6587、¥ U+00A5、¥ U+00A5。

    触发条件:CJK 与反斜线同行

    ¥ 这个字符是线索 —— JIS X 0201 把 0x5C 这个字节位定义为日元号而非反斜线。

    按行看 3.10.5 的损坏分布:

    行 含 CJK 反斜线数 结果
    5–7 否 1/3/5 正常
    8 是 2 ¥
    9 起 否 8/3/1… 全部 ¥

    我一开始以为规律是「遇到 CJK 后切换编码判定,此后所有反斜线都坏」。但翻了 xeCJK 在
    ctan-ann 上的历史归档(共 63 条),3.10.2 那条否证了这个说法:它第 1 行就有 CJK
    (em-dash (破折号)),第 21、22 行有 \setlanguage、\verb、\meta 共 6 个反斜线,
    全部完好、0 个 ¥。

    两份样本的真正差别在这里:

    • 3.10.2(未损坏):CJK 与反斜线从不在同一行;
    • 3.10.5(损坏):第 8 行 CJK 与反斜线同行,损坏正是从这一行开始。

    所以更贴合证据的假设是:当某一行同时含 CJK 字符和反斜线时触发,此后到文末的反斜线
    都按 ¥ 处理。3.10.2 之所以没事,是因为它的 CJK 行恰好没有反斜线 —— 它其实没有测试到
    这个条件。

    后半句「此后全坏」是已观察到的现象;至于是行级状态还是一次性切换后不再恢复,我没有更多
    样本可以区分,标注为推测。

    关于「以前没出现」

    补充一点背景:xeCJK 此前投递过 3.10.1/3.10.2/3.10.3/3.10.4,每次都带 announcement,
    所以不是首次启用这条流程。真正的区别是这次的 announcement 里第一次出现了「同一行里
    CJK 与反斜线共存」—— 这条讲的正是 中文\\ 在 tabular 里报
    Improper alphabetic constant(#1038),代码示例躲不开中文字符。

    接下来

    我倾向报给 CTAN 团队,因为问题在他们的处理环节,而且不是 xeCJK 独有 —— 任何在
    announcement 里让 CJK 与反斜线同行的包都会踩到,对中文 TeX 包是普遍问题。

    我们这边的规避办法是让两者不同行(例如把 中文\\ 那句改写,或者把 CJK 示例单独成行),
    但那是替上游的 bug 买单,还会削弱说明力。所以想先报上去,看答复再定。

    如果你在 ctan-ann 邮件列表上,能否顺便确认一件事:邮件原文(而非 web 归档页)里是
    ¥ 还是 \?如果邮件正常、只有 web 归档坏了,问题范围会小很多,也更容易定位到具体是哪
    一步做了这个替换。

  8. Liam0205 commented on Aug 10, 2026

    @Liam0205
    Contributor

    确认上游问题。

  9. muzimuzhi commented on Aug 10, 2026

    @muzimuzhi
    MemberAuthor

    如果你在 ctan-ann 邮件列表上,能否顺便确认一件事:邮件原文(而非 web 归档页)里是
    ¥ 还是 \?如果邮件正常、只有 web 归档坏了,问题范围会小很多,也更容易定位到具体是哪
    一步做了这个替换。

    (也许已经知道/不需要以下信息了)

    我收到的 ctan-ann 邮件原文和 ctan-ann archive 上,都是坏的。邮件原文 Content-Type 字段包含 charset="iso-2022-jp"

    From: CTAN Announcements via ctan-ann via ctan-ann <ctan-ann@ctan.org>
    Reply-To: ctan@ctan.org, CTAN Announcements via ctan-ann <erik@ctan.org>
    Content-Type: text/plain; format="flowed"; charset="iso-2022-jp"
    Content-Transfer-Encoding: 7bit
    

    CTAN 收到上传后,处理该上传的 CTAN 成员会给上传者发一封确认邮件(例如 pgf 3.1.12 的上传收到的),没准那时就已经编码错误了。

  10. Liam0205 commented on Aug 10, 2026

    @Liam0205
    Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    github_actionsPull requests that update GitHub Actions code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions