% \iffalse meta-comment
% !TEX program  = XeLaTeX
%<*internal>
\iffalse
%</internal>
%<*readme>
xeCJK
=====

`xeCJK` is a package written for XeLaTeX which allows users to typeset
CJK scripts easily.

 - Different default fonts for CJK and other characters.
 - Spaces automatically ignored between CJK characters.
 - Special effects on full-width CJK punctuation.
 - Automatic adjustment of the space between CJK and other characters.

Contributing
------------

This package is a part of the [ctex-kit](https://github.com/CTeX-org/ctex-kit) project.

Issues and pull requests are welcome.

Copyright and Licence
---------------------

    Copyright (C) 2007--2010 by Wenchang Sun <sunwch@nankai.edu.cn>
    Copyright (C) 2009--2022 by Leo Liu <leoliu.pku@gmail.com>
    Copyright (C) 2012--2022 by Qing Lee <sobenlee@gmail.com>
    Copyright (C) 2026 by Liam Huang <liamhuang0205@gmail.com>
    ----------------------------------------------------------------------

    This work may be distributed and/or modified under the
    conditions of the LaTeX Project Public License, either
    version 1.3c of this license or (at your option) any later
    version. This version of this license is in
       http://www.latex-project.org/lppl/lppl-1-3c.txt
    and the latest version of this license is in
       http://www.latex-project.org/lppl.txt
    and version 1.3 or later is part of all distributions of
    LaTeX version 2005/12/01 or later.

    This work has the LPPL maintenance status "maintained".

    The Current Maintainers of this work are Leo Liu, Qing Lee, and Liam Huang.

    This package consists of the files xeCJK.dtx,
                                       full-stop.map,
                                       fullwidth-stop.map,
                                       han-simp.map,
                                       han-trad.map,
                 and the derived files xeCJK.pdf,
                                       xeCJK.sty,
                                       xeCJK.cfg,
                                       xeCJK.ins,
                                       xeCJKfntef.sty,
                                       xeCJK-listings.sty,
                                       xunicode-addon.sty,
                                       xunicode-extra.def,
                                       xeCJK-example-autofake.tex,
                                       xeCJK-example-fallback.tex,
                                       xeCJK-example-subCJKblock.tex,
                                       xeCJK-example-CJKecglue.tex,
                                       xeCJK-example-boundary-register.tex,
                                       xeCJK-example-checksingle.tex,
                                       xeCJK-example-CJKfntef.tex,
                                       xeCJK-example-punctstyle.tex,
                                       xeCJK-example-verbatim.tex,
                                       xeCJK-example-CM.tex,
                                       xeCJK-example-listings.tex,
                                       xeCJK-example-mathblock.tex,
                                       xunicode-symbols.tex,
                                       xunicode-commands.tex,
                                       xunicode-combine-marks.tex,
                                       xunicode-symbols.pdf,
                                       full-stop.tec,
                                       fullwidth-stop.tec,
                                       han-simp.tec,
                                       han-trad.tec, and
                                       README.md (this file).
%</readme>
%<*internal>
\fi
\begingroup
  \def\temp{LaTeX2e}
\expandafter\endgroup\ifx\temp\fmtname\else
\csname fi\endcsname
%</internal>
%<*install>

\input ctxdocstrip %

\preamble

    Copyright (C) 2007--2010 by Wenchang Sun <sunwch@nankai.edu.cn>
    Copyright (C) 2009--2022 by Leo Liu <leoliu.pku@gmail.com>
    Copyright (C) 2012--2022 by Qing Lee <sobenlee@gmail.com>
    Copyright (C) 2026 by Liam Huang <liamhuang0205@gmail.com>
----------------------------------------------------------------------

    This work may be distributed and/or modified under the
    conditions of the LaTeX Project Public License, either
    version 1.3c of this license or (at your option) any later
    version. This version of this license is in
       http://www.latex-project.org/lppl/lppl-1-3c.txt
    and the latest version of this license is in
       http://www.latex-project.org/lppl.txt
    and version 1.3 or later is part of all distributions of
    LaTeX version 2005/12/01 or later.

    This work has the LPPL maintenance status "maintained".

    The Current Maintainers of this work are Leo Liu, Qing Lee, and Liam Huang.

----------------------------------------------------------------------

\endpreamble
\postamble

    This package consists of the files xeCJK.dtx,
                                       full-stop.map,
                                       fullwidth-stop.map,
                                       han-simp.map,
                                       han-trad.map,
                 and the derived files xeCJK.pdf,
                                       xeCJK.sty,
                                       xeCJK.cfg,
                                       xeCJK.ins,
                                       xeCJKfntef.sty,
                                       xeCJK-listings.sty,
                                       xunicode-addon.sty,
                                       xunicode-extra.def,
                                       xeCJK-example-autofake.tex,
                                       xeCJK-example-fallback.tex,
                                       xeCJK-example-subCJKblock.tex,
                                       xeCJK-example-CJKecglue.tex,
                                       xeCJK-example-boundary-register.tex,
                                       xeCJK-example-checksingle.tex,
                                       xeCJK-example-CJKfntef.tex,
                                       xeCJK-example-punctstyle.tex,
                                       xeCJK-example-verbatim.tex,
                                       xeCJK-example-CM.tex,
                                       xeCJK-example-listings.tex,
                                       xeCJK-example-mathblock.tex,
                                       xunicode-symbols.tex,
                                       xunicode-commands.tex,
                                       xunicode-combine-marks.tex,
                                       xunicode-symbols.pdf,
                                       full-stop.tec,
                                       fullwidth-stop.tec,
                                       han-simp.tec,
                                       han-trad.tec, and
                                       README.md.
\endpostamble

\generate
  {
%</install>
%<*internal>
    \usedir{source/xelatex/xecjk}
    \file{xeCJK.ins}                      {\from{\jobname.dtx}{install}}
%</internal>
%<*install>
    \usedir{tex/xelatex/xecjk}
    \file{xeCJK.sty}                      {\from{\jobname.dtx}{package}}
    \file{xeCJKfntef.sty}                 {\from{\jobname.dtx}{fntef}}
    \file{xeCJK-listings.sty}             {\from{\jobname.dtx}{listings}}
    \file{xunicode-addon.sty}             {\from{\jobname.dtx}{xunicode}}
    \file{xunicode-extra.def}             {\from{\jobname.dtx}{xunextra}}
    \usepreamble\emptypreamble
    \usepostamble\emptypostamble
    \usedir{tex/xelatex/xecjk/config}
    \file{xeCJK.cfg}                      {\from{\jobname.dtx}{config}}
    \usedir{doc/xelatex/xecjk/example}
    \file{xeCJK-example-autofake.tex}     {\from{\jobname.dtx}{ex-autofake}}
    \file{xeCJK-example-fallback.tex}     {\from{\jobname.dtx}{ex-fallback}}
    \file{xeCJK-example-subCJKblock.tex}  {\from{\jobname.dtx}{ex-block}}
    \file{xeCJK-example-CJKecglue.tex}    {\from{\jobname.dtx}{ex-ecglue}}
    \file{xeCJK-example-boundary-register.tex}
                                               {\from{\jobname.dtx}{ex-boundary-register}}
    \file{xeCJK-example-checksingle.tex}  {\from{\jobname.dtx}{ex-single}}
    \file{xeCJK-example-CJKfntef.tex}     {\from{\jobname.dtx}{ex-fntef}}
    \file{xeCJK-example-punctstyle.tex}   {\from{\jobname.dtx}{ex-punctstyle}}
    \file{xeCJK-example-verbatim.tex}     {\from{\jobname.dtx}{ex-verb}}
    \file{xeCJK-example-CM.tex}           {\from{\jobname.dtx}{ex-cm}}
    \file{xeCJK-example-listings.tex}     {\from{\jobname.dtx}{ex-listings}}
    \file{xeCJK-example-mathblock.tex}    {\from{\jobname.dtx}{ex-mathblock}}
    \file{xunicode-symbols.tex}           {\from{\jobname.dtx}{xunicode-symbols}}
    \nopreamble\nopostamble
    \usedir{doc/xelatex/xecjk}
    \file{README.md}                      {\from{\jobname.dtx}{readme}}
  }

\endbatchfile
%</install>
%<*internal>
\fi
%</internal>
%<*package|config|fntef|listings|xunicode|xunextra>
%<!(config|xunextra)>\NeedsTeXFormat{LaTeX2e}[2026/06/01]
%<!(config|xunextra)>\RequirePackage{expl3}
%<+!driver>\GetIdInfo$Id: xeCJK.dtx 7023032e 2026-08-21 08:33:37 +0800 Liam <liamhuang0205@gmail.com> $
%<package>  {Typesetting CJK scripts with XeLaTeX}
%<config>  {Configuration file for xeCJK package}
%<fntef>  {xeCJK font effect}
%<listings>  {xeCJK patch file for listings}
%<xunicode>  {addon file for xunicode}
%<xunextra>  {extra definition for xunicode}
%<package>\ProvidesExplPackage{\ExplFileName}
%<config>\ProvidesExplFile{\ExplFileName.cfg}
%<fntef>\ProvidesExplPackage{xeCJKfntef}
%<listings>\ProvidesExplPackage{xeCJK-listings}
%<xunicode>\ProvidesExplPackage{xunicode-addon}
%<xunextra>\ProvidesExplFile{xunicode-extra.def}
%<!driver>  {\ExplFileDate}{3.10.6}{\ExplFileDescription}
%</package|config|fntef|listings|xunicode|xunextra>
%<*driver>
\documentclass{ctxdoc}
\usepackage{xeCJKfntef}
\xeCJKDeclareSubCJKBlock{SP} { "2E3A , "301C , "30A0 , "FF65 }
\xeCJKDeclareSubCJKBlock{Ext-B} { "20000 -> "2A6DF }
\xeCJKDeclareSubCJKBlock{Hangul}
  { "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF }
\xeCJKsetup{PoZheHaoLigature}
\setCJKmainfont{Noto Serif CJK SC}[
  Language   = Chinese Simplified,
  ItalicFont = LXGWWenKaiGBLite-Regular.ttf
]
\setCJKsansfont{Noto Sans CJK SC}[Language=Chinese Simplified]
\setCJKmonofont{LXGWZhuqueFangsong-Regular.ttf}[Language=Chinese Simplified]
\setCJKmainfont[SP, Language=Chinese Simplified]{Noto Serif CJK SC}
\IfFontExistsTF{SimSun-ExtB}
  {\setCJKmainfont[Ext-B]{SimSun-ExtB}}
  {\IfFontExistsTF{HanaMinB}
    {\setCJKmainfont[Ext-B]{HanaMinB}}{}}
\setCJKmainfont[Hangul, Script=Hangul, Language=Korean]{Noto Serif CJK KR}
\usepackage{menukeys, docext}
\newcommand\PSKeyVal[2]{\item[\keys{#1}]\val{#2}：}
\newcommand\tokslink[1]{\hyperlink{#1}{\ding{51}}}
\newcommand\ghissue[1]{%
  \href{https://github.com/CTeX-org/ctex-kit/issues/#1}{\##1}}
\AtBeginDocument{\DeleteShortVerb{\"}}
\ExplSyntaxOn
\box_new:N \l__codedoc_mdcode_tmpa_box
\box_new:N \l__codedoc_mdcode_tmpb_box
\fp_new:N  \l__codedoc_mdcode_scale_fp
\dim_new:N \l__codedoc_mdcode_depth_dim
\DeclareDocumentCommand \mdcode { s m }
  {
    \mode_leave_vertical:
    \hbox_set:Nn \l__codedoc_mdcode_tmpb_box { \menu {} }
    \fp_set:Nn \l__codedoc_mdcode_scale_fp
      {
        \dim_to_decimal:n { \ccwd } /
        \dim_to_decimal:n { \box_ht_plus_dp:N \l__codedoc_mdcode_tmpb_box }
      }
    % \box_scale:Nnn 以基线为轴按比例缩放，menukeys 的盒子深度占比大于汉字，
    % 缩放后深度仍然偏大，外边框看上去比周围文字低，因此要把盒子上移。
    %
    % 上移的基准不能取汉字的声明深度：汉字字形的墨迹会伸到基线以下，声明深度只是
    % 字体给出的度量值，两者并不相等。实测正文字号下墨迹比基线低 1.80pt，而声明
    % 深度为 1.59pt，只差 0.21pt——按声明深度上移会让边框底边高过汉字底边，看起来
    % 偏高。这里按实测的墨迹与声明深度之比 1.131 折算出墨迹深度，边框底边即与汉字
    % 墨迹底边齐平；该比例在 \small、正常、\large 三种字号下实测一致。
    \hbox_set:Nn \l__codedoc_mdcode_tmpb_box { 汉 }
    \dim_set:Nn \l__codedoc_mdcode_depth_dim
      { 1.131 \box_dp:N \l__codedoc_mdcode_tmpb_box }
    \tl_set_rescan:Nnn \l__codedoc_mdcode_rescan_tl
      {
        \char_gset_active_eq:NN \- \textminus
        \char_set_catcode_active:N \-
      } { #2 }
    \clist_map_inline:Nn \l__codedoc_mdcode_rescan_tl
      {
        \hbox_set:Nn \l__codedoc_mdcode_tmpa_box
          { \bool_if:NTF #1 { \menu { \texttt { ##1 } } } { \menu { ##1 } } }
        \box_scale:Nnn \l__codedoc_mdcode_tmpa_box
          { \l__codedoc_mdcode_scale_fp } { \l__codedoc_mdcode_scale_fp }
        \box_move_up:nn
          {
            \box_dp:N \l__codedoc_mdcode_tmpa_box
              - \l__codedoc_mdcode_depth_dim
          }
          { \box_use_drop:N \l__codedoc_mdcode_tmpa_box }
      }
  }
\NewDocumentCommand \PrintPunctList { O{7} m m }
  {
    \par
    \begingroup
    \CJKfontspec[Language=Chinese ~ Simplified]{Noto ~ Serif ~ CJK ~ SC}
    \tl_clear:N \l_tmpa_tl
    \int_zero:N \l_tmpa_int
    \tl_set:Ne \l_tmpb_tl { \tl_to_str:n { c__xeCJK_#2_chars_clist } }
    \int_set:Nn \l_tmpb_int { \clist_count:c { \l_tmpb_tl } }
    \clist_map_inline:cn { \l_tmpb_tl }
      {
        \int_incr:N \l_tmpa_int
        \tl_put_right:Ne \l_tmpa_tl
          {
            \use_none:n ##1 & \tex_char:D ##1 \scan_stop:
            \int_compare:nNnF \l_tmpa_int = \l_tmpb_int
              {
                \int_compare:nNnTF { \int_mod:nn \l_tmpa_int {#1} } = \c_zero_int
                  { \exp_not:N \\ \scan_stop: } { & }
              }
          }
      }
    \noindent\hfill\linespread{1}\selectfont
    \begin{tabular}{|*{#1}{>{\footnotesize\ttfamily U+}c|c|}}
      \tl_use:N \l_tmpa_tl
    \end{tabular}\hfill\null
    \endgroup
    \par
  }
\ExplSyntaxOff
\begin{document}
  \DocInput{\jobname.dtx}
  \IndexLayout
  \PrintChanges
  \PrintIndex
\end{document}
%</driver>
% \fi
%
% \changes{v3.1.0}{2012/11/13}{放弃对 \tn{outer} 宏的特殊处理。}
% \changes{v3.1.1}{2012/12/07}{不再依赖 \pkg{xpatch} 宏包。}
% \changes{v3.2.2}{2013/06/01}{修正某些重音不能正确显示的问题。}
% \changes{v3.2.3}{2013/06/07}{提供四个 TECkit 映射文件用于句号转换和简繁互换。}
% \changes{v3.2.4}{2013/07/02}{遵循 \LaTeXiii{} 变量需要预先声明的原则。}
% \changes{v3.2.6}{2013/07/29}{\texttt{case} 类函数的用法与 \LaTeXiii{} 同步。}
% \changes{v3.3.2}{2015/05/15}{随 Unicode 7.0 更新简繁汉字映射。}
% \changes{v3.3.3}{2015/09/25}{更新 \LaTeXiii{} 代码。}
% \changes{v3.5.0}{2017/07/19}{常数 \cs{c_minus_one} 已过时。}
% \changes{v3.5.0}{2017/07/22}{使用 \texttt{lazy} 函数对 Boolean 表达式
% 进行最小化运算（\LaTeXiii{} 2017/07/19）。}
% \changes{v3.6.0}{2018/01/13}{同步 \LaTeXiii{} 2017/12/16。}
% \changes{v3.6.1}{2018/02/27}{减少 \texttt{bool} 运算。}
% \changes{v3.7.2}{2019/03/23}{同步 \LaTeXiii{} 2019/03/05。}
% \changes{v3.8.0}{2020/02/09}{兼容 \LaTeXe\ 2020/02/02 对 \pkg{NFSS} 的修改。}
% \changes{v3.8.0}{2020/02/09}{清理过时的兼容性补丁代码。}
% \changes{v3.8.3}{2020/04/07}{删除 \texttt{\_nopar}。}
% \changes{v3.10.0}{2026/04/29}{提升版本号至 v3.10.0。}
% \changes{v3.10.0}{2026/05/04}{文档字体从 Source Han Serif 统一为
%   Noto Serif CJK（\#686）。}
% \changes{v3.10.2}{2026/07/05}{文档字体启用 \opt{Language} 与
%   \opt{PoZheHaoLigature}，破折号按全角字形合字输出（\#382）。}
% \changes{v3.10.2}{2026/07/05}{文档等宽字体改用朱雀仿宋，意大利体改用
%   霞鹜文楷 GB Lite（\#908）。}
% \changes{v3.10.0}{2026/05/13}{修复 \tn{textcolor} 包裹 \pkg{ulem}
%   类下划线命令时 CJK 字间距异常的问题（\#830）。}
% \changes{v3.10.1}{2026/06/29}{提升版本号至 v3.10.1。}
% \changes{v3.10.2}{2026/07/03}{提升版本号至 v3.10.2。}
% \changes{v3.10.3}{2026/07/12}{提升版本号至 v3.10.3。}
% \changes{v3.10.4}{2026/07/18}{提升版本号至 v3.10.4。}
%
% \GetFileId{xeCJK.sty}
%
% \title{\bfseries\pkg{xeCJK} 宏包}
% \author{\href{http://www.ctex.org}{CTEX.ORG}}
% \date{\ctexkitbuilddate\qquad\fileversion\thanks{\ctexkitrev{\ExplFileVersion}.}}
% \maketitle
%
% \tableofcontents
% \vspace{\baselineskip}
%
% \begin{documentation}
%
% \section{简介}
%
% \pkg{xeCJK} 是一个 \XeLaTeX 宏包，用于排版中日韩（CJK）文字。主要功能：
% \begin{enumerate}
% \item 分别设置 CJK 和英文字体；
% \item 自动忽略 CJK 文字间的空格而保留其他空格，允许在非标点汉字和英文
% 字母 (a -- z, A -- Z) 间断行；
% \item 提供多种标点处理方式： 全角式、半角式、开明式、行末半角式和 CCT 式；
% \item 自动调整中英文间空白。
% \end{enumerate}
%
% \pkg{xeCJK} 使用了 \XeTeX 的一些最新特性，需要 \XeTeX{} 0.9995.0 (2009/06/29) 以
% 后的版本。\pkg{xeCJK} 依赖 \LaTeXiii{} 项目的宏包套件
% \package{l3kernel} 和 \package{l3packages}。
% \pkg{xeCJK} 还需要通过 \package{fontspec} 宏包来调用系统字体。
% \pkg{xeCJK} 会自动根据需要载入这些宏包。
%
% \pkg{xeCJK} 的原始作者是孙文昌，2009 年 5 月起宏包被收入 \ctexkit\ 项目，
% 陆续由刘海洋\footnote{\email{leoliu.pku@gmail.com}}、
% 李清\footnote{\email{sobenlee@gmail.com}}及
% 黄晨成\footnote{\email{liamhuang0205@gmail.com}}维护。
% 目前活跃的维护者是黄晨成。
%
% \section{基本用法}
%
% 与其他 \LaTeX{} 宏包一样，引入 \pkg{xeCJK} 宏包只要在导言区使用
% \begin{frameverb}
%    \usepackage{xeCJK}
% \end{frameverb}
% 在引入 \pkg{xeCJK} 宏包之后，只要设置 CJK 文字的字体，就可以在文档中使用中日
% 韩文字了。
%
% 可以在各种文档类中使用 \pkg{xeCJK} 宏包，最简单的示例是：
% \begin{ctexexam}
%   \documentclass{article}
%   \usepackage{xeCJK}
%   \setCJKmainfont{SimSun}
%
%   \begin{document}
%   中文 \LaTeX 示例。
%   \end{document}
% \end{ctexexam}
% 上述示例设置了中文字体 SimSun（宋体）。运行此示例要求系统安装了设置的字体，
% 源文件用 UTF-8 编码保存，使用 \XeLaTeX{} 编译。
%
% \pkg{xeCJK} 只提供了字体和标点控制等基本 CJK 语言支持。对于中文文档，可以使
% 用更为高层的 \package{ctex} 宏包或文档类，它将自动调用 \pkg{xeCJK} 并设置好中文
% 字体，同时提供了进一步的本地化支持。详细内容参看 \package{ctex} 宏包套件的说明。
%
% \pkg{xeCJK} 提供了大量选项，可以在宏包调用时作为宏包选项或用 \tn{xeCJKsetup}
% 命令进行设置，详见 \ref{subsec:opts}~节。除了 \tn{setCJKmainfont} 命令，
% \pkg{xeCJK} 还提供了许多其他命令设置和选择中文字体，详见
% \ref{subsec:fontset}~节。其他更详细的功能也都将在下面详细说明。在本文档所在的
% 文件夹的 |example| 目录下面也有一些例子可以参考。
%
% \section{用户手册}
%
% \subsection{宏包选项}
% \label{subsec:opts}
%
% \pkg{xeCJK} 以 \meta{key}| = |\meta{val} 的形式提供宏包选项，用户可以在调用宏包
% 的时候直接设置这些选项，也可以在调用宏包之后使用 \tn{xeCJKsetup} 来设置这些选
% 项。\pkg{xeCJK} 内部调用 \pkg{fontspec} 宏包，可以在调用 \pkg{xeCJK} 的时候，
% 使用它的宏包选项。\pkg{xeCJK} 会将 \pkg{fontspec} 的选项传递给它。
%
% \begin{function}{\xeCJKsetup}
%   \begin{syntax}
%     \tn{xeCJKsetup} \{\meta{key_1} = \meta{val_1}, \meta{key_2} = \meta{val_2}, ...\}
%   \end{syntax}
%   其中 \meta{key_1}, \meta{key_2} 是设置选项，而 \meta{val_1}, \meta{val_2} 则是对应选项的
%   设置内容。多个选项可以在一个语句中完成设置。例如
%   \begin{ctexexam}
%   \usepackage[PunctStyle=kaiming]{xeCJK}
%   \end{ctexexam}
%   等价于
%   \begin{ctexexam}
%   \usepackage{xeCJK}
%   ......
%   \xeCJKsetup{PunctStyle=kaiming}
%   \end{ctexexam}
% \end{function}
%
% 带有 \exptarget\expstar{} 或者~\rexptarget\rexpstar{} 标记的选项或命令
% 只能在导言区中使用，其中~\rexpstar{} 还表示这个选项或命令只
% 影响随后定义的 CJK 字体。其余不带特殊标记的选项或命令，如果没有特别说明，
% 则可以在导言区或正文中使用。\textbf{粗体}表示 \pkg{xeCJK} 的默认设置。
%
% \begin{function}[EXP,added=2012-11-22]{LocalConfig}
%   \begin{syntax}
%     LocalConfig = \Arg{\TTF|name}
%   \end{syntax}
%   是否使用本地配置文件 \texttt{xeCJK-\meta{name}.cfg}。\meta{name} 可以是不包含
%   空格的任意使文件名合法的字符串。如果设置为 |true|，则使用的是 \texttt{xeCJK.cfg}；
%   设置为 |false| 则不载入配置文件。可以把将要在下文介绍到的对 \pkg{xeCJK} 的一些
%   设置（例如设置常用 CJK 字体、修改字符范围和定义新的标点输出格式等）保存到文件
%   \texttt{xeCJK-\meta{name}.cfg}。然后把这个文件放在本地的 |TDS| 目录下的适当
%   位置。使用 \TeX~Live 的用户，可以新建下列目录，然后再把
%   \texttt{xeCJK-\meta{name}.cfg} 放在里面：
%   \begin{frameverb}
%   texlive/texmf-local/tex/xelatex/xecjk
%   \end{frameverb}
%   最后还需要在命令行下执行 |mktexlsr|，刷新文件名数据库以便 \TeX 系统能够找到它。
% \end{function}
%
% 请注意，\pkg{xeCJK} 宏包中只有上述 |LocalConfig| 选项需要在调用 \pkg{xeCJK} 时
% 设置，而不能通过 \tn{xeCJKsetup} 来设置。
%
% \begin{function}{xeCJKactive}
%   \begin{syntax}
%     xeCJKactive = \meta{\TTF}
%   \end{syntax}
%   打开/关闭对中文的特殊处理。事实上，这个选项会打开/关闭 \XeTeX 的整个字符类机制，依赖
%   这个机制的宏包都会受到影响。
% \end{function}
%
% \begin{function}{CJKspace}
%   \begin{syntax}
%     CJKspace = \meta{\TFF}
%   \end{syntax}
%   缺省状态下，\pkg{xeCJK} 会忽略 CJK 文字之间的空格，使用这一选项来保留它们之间的空格。
% \end{function}
%
% \begin{function}[EXP,updated=2016-05-04]{CJKmath}
%   \begin{syntax}
%     CJKmath = \meta{\TFF}
%   \end{syntax}
%   是否支持在数学环境中直接输入 CJK 字符。使用这个选项后，可以直接在数学环境中
%   输出 CJK 字符。\pkg{url} 宏包将一个 URL 放在一个特殊的数学环境中排版，所以如果在
%   \tn{path} 等命令的路径参数中含有汉字，则需要启用这个选项，路径中的汉字才能显示。
% \end{function}
%
% \begin{function}{CJKglue}
%   \begin{syntax}
%     CJKglue = \{\tn{hskip} 0pt plus 0.08\tn{baselineskip}\}
%   \end{syntax}
%   设置 CJK 文字之间插入的 |glue|，上边是 \pkg{xeCJK} 的默认值。一般来说，除非有
%   特殊需要（例如，改变文字间距等），否则不需要设置这个选项，使用默认值即可。如果要设置
%   这个选项，为了行末的对齐，设置的 |glue| 最好有一定的弹性。
% \end{function}
%
% \begin{function}{CJKecglue}
%   \begin{syntax}
%     CJKecglue = \Arg{glue}
%   \end{syntax}
%   设置 CJK 文字与西文、CJK 文字与行内数学公式之间的间距，默认值是一个空格。使用这个
%   选项设置的 \meta{glue} 最好也要有一定的弹性。请注意，这里设置的 \meta{glue} 只影响
%   \pkg{xeCJK} 根据需要自动添加的空白，源文件中直接输入的 CJK 文字与西文之间的空格不
%   受影响（直接输出）。有时候 \pkg{xeCJK} 可能不能正确地调整间距，需要手动加空格。
% \end{function}
%
% \begin{function}{xCJKecglue}
%   \begin{syntax}
%     xCJKecglue = \Arg{\TFF|glue}
%   \end{syntax}
%   缺省状态下，\pkg{xeCJK} 保留源文件中直接输入在 CJK 文字与西文或行内
%   数学公式之间的空格；这些空格使用普通词间空格。设为 |true| 后，
%   \pkg{xeCJK} 会将它们替换为 |CJKecglue|。也可以直接把这个选项设置为
%   一段 \meta{glue}；这等价于先用该值设置 |CJKecglue|，再设置
%   |xCJKecglue=true|。
% \end{function}
%
% \changes{v3.10.4}{2026/07/21}{明确 \opt{xCJKecglue} 同时控制行内
%   数学公式旁的源码空格（\#1002）。}
% \changes{v3.10.4}{2026/07/20}{在用户手册说明 \TeX{} 无法区分源码空格
%   与某些显式 glue，并给出前置零宽 \tn{kern} 的处理方法（\#992）。}
% \changes{v3.10.4}{2026/07/20}{补充 Boundary→CJK 方向的说明：某些
%   显式 glue 也可能被替换为 |CJKglue|（\#996）。}
% 注意：\TeX{} 的节点列表只保存 glue 的数值，不保存它来自源码空格还是
% 显式 \tn{hskip}。在 \tn{mbox}、\tn{textcolor} 等命令或分组之后，如果
% 显式写出的 glue 与当前词间空格自然宽度相同，并且带有 shrink，
% \pkg{xeCJK} 就无法判断它的来源，可能按空格恢复规则将其替换为
% |CJKecglue| 或 |CJKglue|。自然宽度不同或没有 shrink 的显式 glue
% 不受影响。如果必须原样保留前一种 glue，请在它前面加零宽
% ~|\kern0pt|，也可以改用不同的自然宽度或去掉 shrink。这样检查会停在
% 这枚 kern，不再继续向前寻找 xeCJK 写入的 marker。
%
% \begin{function}[updated=2013-06-26]{CheckSingle}
%   \begin{syntax}
%     CheckSingle = \meta{\TFF}
%   \end{syntax}
%   是否避免单个 CJK 文字单独占一个段落的最后一行。需要说明的是，这个选项只有在
%   段末的最后一个字是 CJK 文字或者标点符号，并且倒数第二和第三个字都是文字才能
%   正确处理孤字的问题。如果这倒数三个字有作为控制序列的参数的情况，那么一般
%   来说也不能正确处理。
% \end{function}
%
% \begin{function}[added=2015-04-08]{WidowPenalty}
%   \begin{syntax}
%     WidowPenalty = \Arg{penalty|(10000)}
%   \end{syntax}
%   使用 \texttt{CheckSingle} 选项后，设置段末三个汉字之间的 penalty。
%   初始值为 \num{10000}；也即，禁止在它们之间折行。
% \end{function}
%
% \begin{function}[added=2012-12-06]{PlainEquation}
%   \begin{syntax}
%     PlainEquation = \meta{\TFF}
%   \end{syntax}
%   如果使用了 |$$...$$| 的形式来输入行间数学公式，就需要启用本选项，以便
%   |CheckSingle| 选项能够正确识别。推荐使用~|\[...\]| 的形式来输入行间数学公式。
% \end{function}
%
% \begin{function}[added=2012-12-04]{NewLineCS,NewLineCS+,NewLineCS-}
%   \begin{syntax}
%     NewLineCS = \{ \tn{par} \tn{[} \}
%   \end{syntax}
%   设置造成断行的控制序列，以便 |CheckSingle| 选项能够正确识别。
%   以上是 \pkg{xeCJK} 的初始设置。
% \end{function}
%
% \begin{function}[added=2012-12-04]{EnvCS,EnvCS+,EnvCS-}
%   \begin{syntax}
%     EnvCS = \{ \tn{begin} \tn{end} \}
%   \end{syntax}
%   设置 \LaTeX 环境开始和结束的控制序列，以便 |CheckSingle| 选项能够正确识别。
%   以上是 \pkg{xeCJK} 的初始设置。
% \end{function}
%
% \begin{function}[updated=2012-12-06]{InlineEnv,InlineEnv+,InlineEnv-}
%   \begin{syntax}
%     InlineEnv = \{\meta{env_1}, \meta{env_2}, \meta{env_3}, ...\}
%   \end{syntax}
%   使用 |CheckSingle| 选项时，\pkg{xeCJK} 会将 CJK 文字后接着的 \LaTeX{} 环境
%   起止点视为可断行点。如果有某些特殊的 \LaTeX 环境没有造成断行，可以使用该选项声明，
%   以便 |CheckSingle| 能正确识别。
% \end{function}
%
% \begin{function}{AutoFallBack}
%   \begin{syntax}
%     AutoFallBack = \meta{\TFF}
%   \end{syntax}
%   当文档中有个别生僻字时，可以使用这个选项，自动使用预先设置好的后备字体来输出这些生僻
%   字。后备字体的设置方法将在 \ref{subsec:fontset}~节中介绍。
% \end{function}
%
% \begin{function}[rEXP]{AutoFakeBold}
%   \begin{syntax}
%     AutoFakeBold = \Arg{\TFF|数字}
%   \end{syntax}
%   全局设定当没有声明对应的粗体时，是否使用^^A
%   \textbf{\CJKfontspec[AutoFakeBold]{Noto Serif CJK SC}伪粗体}；
%   当输入的是数字时，将使用伪粗体，并将使用输入的数字作为伪粗体的默认粗细程度。
% \end{function}
%
% \begin{function}[rEXP]{AutoFakeSlant}
%   \begin{syntax}
%     AutoFakeSlant = \Arg{\TFF|数字}
%   \end{syntax}
%   全局设定当没有声明对应的斜体时，是否使用^^A
%   \textit{\CJKfontspec[AutoFakeSlant]{Noto Serif CJK SC}伪斜体}；
%   当输入的是数字时，将使用伪斜体，并将使用输入的数字作为伪斜体的默认倾斜程度。
% 倾斜程度的取值范围是 $[-0.999, 0.999]$。
% \end{function}
%
% \begin{function}[rEXP]{EmboldenFactor}
%   \begin{syntax}
%     EmboldenFactor = \Arg{数字|(4)}
%   \end{syntax}
%   设置伪粗体的默认粗细程度。
% \end{function}
%
% \begin{function}[rEXP]{SlantFactor}
%   \begin{syntax}
%     SlantFactor = \Arg{数字|(0.167)}
%   \end{syntax}
%   设置伪斜体的倾斜程度，范围是 $[-0.999, 0.999]$。
% \end{function}
%
% \begin{function}[updated=2012-11-10]{PunctStyle}
%   \begin{syntax}
%     PunctStyle = \Arg{(quanjiao)|banjiao|kaiming|hangmobanjiao|CCT|plain|...}
%   \end{syntax}
%   设置标点处理格式。\pkg{xeCJK} 中预先定义好的格式为
%   \begin{optdesc}
%     \item[quanjiao] 全角式：所有标点占一个汉字宽度；左标点后接右标点时保留
%       字体的自然空白，其余相邻标点在字形边界允许时压缩到约 1.5 汉字宽度；
%     \item[banjiao]  半角式：所有标点占半个汉字宽度；
%     \item[kaiming]  开明式：句末点号用全角，其他半角；
%     \item[hangmobanjiao] 行末半角式：所有标点占一个汉字宽度，行首行末对齐；
%     \item[CCT]   CCT 格式：所有标点符号的宽度略小于一个汉字宽度；
%     \item[plain] 原样（不调整标点间距）。
%   \end{optdesc}
%   可以使用 \ref{subsec:punctstyle} 中介绍的 \tn{xeCJKDeclarePunctStyle} 定义新的标点
%   格式。
% \end{function}
%
% \begin{function}[added=2018-01-24]{PunctFamily}
%   \begin{syntax}
%     PunctFamily = \Arg{\keys[emph=1,delimiter=\textbar]{false,family}}
%   \end{syntax}
%   默认情况下，CJK 标点符号的字体与 CJK 正文一致，\opt{PunctFamily} 用于单独对标点符号设置字体。
%   \meta{family} 需要使用随后说明的 \tn{setCJKfamilyfont} 或 \tn{newCJKfontfamily}
%   预先定义。\opt{false} 表示取消本选项的作用，让标点符号字体与正文一致。
% \end{function}
%
% \begin{function}[EXP]{KaiMingPunct,KaiMingPunct+,KaiMingPunct-}
%   \begin{syntax}
%     KaiMingPunct = \Arg{(． 。？ ！)}
%   \end{syntax}
%   设置开明（|kaiming|）标点处理格式时的句末点号，|KaiMingPunct| 后带的~\mdcode*{+,-}
%   分别表示从已有的开明句末点号中增加或减少标点。
% \end{function}
%
% \begin{function}[EXP]{LongPunct,LongPunct+,LongPunct-}
%   \begin{syntax}
%     LongPunct = \Arg{(— ⸺ ‥ … )}
%   \end{syntax}
%   设置长标点，例如破折号~\mdcode{——} 与省略号~\mdcode{……}，允许在长标点前后
%   断行，但是禁止在它们之间断行。同时属于 |NoBreakLongPunct|
%   的长标点则禁止在其前面断行。长标点与其他标点相邻时，
%   断点两侧还须满足通常的标点禁则：全角左标点不悬于行尾，
%   全角右标点不落于行首。
% \end{function}
%
% \begin{function}[EXP]{NoBreakLongPunct,NoBreakLongPunct+,NoBreakLongPunct-}
%   \begin{syntax}
%     NoBreakLongPunct = \Arg{(‥ … )}
%   \end{syntax}
%   设置禁止在其前面断行的长标点。属于 |NoBreakLongPunct| 的字符必须同时属于
%   |LongPunct|。与普通长标点不同，\pkg{xeCJK} 禁止在这类标点前面断行，但仍
%   允许在其后面断行，并禁止在相同长标点之间断行。缺省值包含省略号
%   ~\mdcode{‥} 和~\mdcode{…}。
% \end{function}
%
% \begin{function}[EXP]{MiddlePunct,MiddlePunct+,MiddlePunct-}
%   \begin{syntax}
%     MiddlePunct = \Arg{(– — ⸺ · · ･ 〜゠～)}
%   \end{syntax}
%   设置居中显示的标点，例如间隔号~\mdcode{·}。对于在 CJK 文字之间的居中标点，
%   \pkg{xeCJK} 会根据不同的标点处理格式，调整居中标点与前后文字之间的空白，保证
%   其确实居中。对于行末出现的居中标点，允许在其后面断行，但禁止在它前面断行。
% \end{function}
%
% \begin{function}[added=2026-07-04]{PoZheHaoLigature}
%   \begin{syntax}
%     PoZheHaoLigature = \meta{\TFF}
%   \end{syntax}
%   一些设计优良的 OpenType 字体（如思源宋体、思源黑体）提供了破折号合字功能：
%   连续的两个 \texttt{U+2014} 会被替换为一个两倍字宽的字形。
%   缺省状态下，\pkg{xeCJK} 在破折号之间插入的标点处理代码会阻断这一功能。
%   启用这一选项后，\pkg{xeCJK} 把 \texttt{U+2014} 和 \texttt{U+2015} 归入
%   |PoZheHao| 字符类，它们之间不插入任何内容，从而恢复字体的合字功能。
%   这一选项需要字体的配合（一般还需启用 |fwid| 或 |locl| 等 OpenType 特性
%   得到全角字形），对不支持破折号合字的字体（多数中文字库）不要启用它，
%   否则连续破折号中间可能出现空隙。由于标点度量按字体缓存，
%   这一选项建议在导言区或使用相应字体排版破折号之前设置。
%   例如：
% \begin{ctexexam}
%   \setCJKmainfont{Noto Serif CJK SC}[RawFeature=+fwid]
%   \xeCJKsetup{PoZheHaoLigature}
% \end{ctexexam}
% \end{function}
%
% \begin{function}[added=2026-07-05]{LatinPunct}
%   \begin{syntax}
%     LatinPunct = \meta{\TFF}
%   \end{syntax}
%   Unicode 中的弯引号（|U+2018|、|U+2019|、|U+201C|、|U+201D|）、
%   间隔号（|U+00B7|）与省略号（|U+2025|、|U+2026|、|U+2027|）等码位
%   是中西文共用的。缺省状态下，\pkg{xeCJK} 把它们归入全角标点，
%   用中文字体输出全角字形；在以西文为主的文档中，
%   夹在英文单词中间的撇号（如 |Children’s| 的 |U+2019|）会因此变成
%   突兀的全角形式。启用这一选项后，这些共用标点改归入西文半角标点类
%   （|HalfLeft| / |HalfRight|），使用当前西文字体输出，
%   也不再参与标点压缩。这一选项缺省未启用；|LatinPunct| 等价于
%   |LatinPunct = true|。破折号（|U+2014|）的归属不受此选项影响，
%   见 \opt{PoZheHaoLigature}。
%   例如：
% \begin{ctexexam}
%   \xeCJKsetup{LatinPunct}   % 共用标点使用西文字体
% \end{ctexexam}
%   这一选项可以在导言区或正文中随时切换，
%   作用范围是当前 \TeX{} 分组。
% \end{function}
%
% \begin{function}[added=2026-07-12]{CJLineBreak}
%   \begin{syntax}
%     CJLineBreak = \Arg{(normal)|strict}
%   \end{syntax}
%   设置 Unicode |Line_Break=CJ| 的日文小假名等字符是否采用严格行首禁则。
%   |normal| 将它们作为普通 CJK 文字处理，保持历史默认行为；|strict| 保持
%   同样的字距，但在这些字符前插入 penalty \num{10000}，防止它们出现在行首。
%   例如 |きゃ| 和 |きゃっ| 中的小假名前都不可断行。该选项可在分组内局部
%   切换；调用 \tn{xeCJKResetCharClass} 后仍保留当前设置。
% \end{function}
%
% \begin{function}[EXP]{PunctWidth}
%   \begin{syntax}
%     PunctWidth = \Arg{length}
%   \end{syntax}
%   缺省状态下，\pkg{xeCJK} 会根据所选择的标点处理格式自动计算标点所占的宽度，如果对缺
%   省设置不满意，可以通过这一选项来改变它。为了使得标点所占的宽度能够适应字体大小的变化，
%   这里设置的 |length| 的单位最好用 |em| 等相对距离单位，而不建议使用诸如 |pt| 之类的
%   绝对距离单位。这里的设置可用于除了 |plain| 以外的所有标点处理格式。同时，这里的
%   设置对所有的 CJK 标点都生效，如果只要设置部分标点，请使用 \ref{subsec:punct}~节的
%   \tn{xeCJKsetwidth}。
% \end{function}
%
% \begin{function}[EXP,added=2013-08-22]{PunctBoundWidth}
%   \begin{syntax}
%     PunctBoundWidth = \Arg{length}
%   \end{syntax}
%   与以上选项类似，但设置的是标点符号出现在行首/尾时的宽度。
% \end{function}
%
% \begin{function}{AllowBreakBetweenPuncts}
%   \begin{syntax}
%     AllowBreakBetweenPuncts = \meta{\TFF}
%   \end{syntax}
%   缺省状态下，\pkg{xeCJK} 禁止在相邻 CJK 右标点和 CJK 左标点之间换行，可以使用
%   这一选项改变这一设置。
% \end{function}
%
% \begin{function}[updated=2016-05-13]{RubberPunctSkip}
%   \begin{syntax}
%     RubberPunctSkip = \meta{\TTF|plus|minus}
%   \end{syntax}
%   缺省状态下，标点符号前/后的间距有一定的弹性。可以伸长到原始边界宽度，可以收缩到
%   标点另一侧的边界宽度。将本选项设置为 \texttt{plus}，将只允许伸长；设置为
%   \texttt{minus} 只允许收缩。设置为 \texttt{false} 将禁用这一特性，
%   从而使得前/后的间距为固定值。
% \end{function}
%
% \begin{function}[added=2012-12-02]{CheckFullRight}
%   \begin{syntax}
%     CheckFullRight = \meta{\TFF}
%   \end{syntax}
%   某些控制序列要求不能在它的前面断行。但是在缺省状态下，单个全角右标点的后面总是
%   可以断行的。因此当这些控制序列出现在全角右标点后面时，可能会出现意料之外的断行。
%   此时可以使用这个选项来避免这个情况。
% \end{function}
%
% \begin{function}[added=2012-12-02]{NoBreakCS,NoBreakCS+,NoBreakCS-}
%   \begin{syntax}
%     NoBreakCS = \{ \tn{footnote} \tn{footnotemark} \tn{nobreak} \}
%   \end{syntax}
%   设置不能在全角右标点后断行的控制序列。以上是 \pkg{xeCJK} 的默认设置。如果这些
%   控制序列在文档中只出现少量几次，也可以不必使用 |CheckFullRight| 选项，而是手工
%   在这些控制序列前面加上 \ref{subsec:others}~节介绍的 \tn{xeCJKnobreak}。
% \end{function}
%
% \begin{function}[added=2026-05-03]{experiment/halfright-prebreakpenalty}
%   \begin{syntax}
%     experiment/halfright-prebreakpenalty = \meta{\TFF}
%   \end{syntax}
%   这是一个实验性选项。缺省状态下，\pkg{xeCJK} 不会禁止在
%   CJK 文字或全角右标点后面紧跟的半角右标点（如
%   ~\mdcode{., {,}, ;, :, !, ?, ), ]}
%   等）之前换行。例如~\mdcode{）.} 中，\mdcode{.} 有可能被排在下一行的行首。
%   打开此选项后，\pkg{xeCJK} 会在这类位置插入禁则（penalty \num{10000}），
%   防止半角右标点出现在行首。
%
%   此选项的效果类似于 \pkg{LuaTeX-ja} 中的 |prebreakpenalty| 机制，
%   但受限于 \XeTeX{} interchar 机制，粒度为字符类级别而非逐字符级别。
%   例如：
%   \begin{verbatim}
%   \xeCJKsetup{experiment/halfright-prebreakpenalty}
%   \end{verbatim}
% \end{function}
%
% \begin{function}[added=2026-05-17]{experiment/punct-measure-fix}
%   \begin{syntax}
%     experiment/punct-measure-fix = \meta{\TFF}
%   \end{syntax}
%   这是一个实验性选项。缺省状态下，\LaTeX{} 的 \cs{para_end:} 在执行
%   \cs{tex_par:D} 之前会通过 \tn{unskip} 移除水平列表末尾的 glue。如果
%   段末恰好是全角标点，其标点补偿 glue 也会被移除，导致 \pkg{tabularray}
%   等使用 \tn{par} 结束测量段落的宏包得到不正确的宽度。
%
%   打开此选项后，\pkg{xeCJK} 会在 |para/end| 钩子中插入一个
%   \tn{kern}，补偿被 \tn{unskip} 移除的标点 glue 的自然宽度，确保
%   测量宽度与排版宽度一致。
%
%   \textbf{注意：}此修复涉及一个权衡。\tn{kern} 只保留了原 glue 的
%   自然宽度，丢弃了弹性分量（stretch/shrink）。对于段末最后一行，
%   \tn{parfillskip} 提供的 |0pt plus 1fil| 拉伸量会吸收所有剩余空间，
%   因此弹性丢失在绝大多数情况下无视觉影响。但在 CheckSingle 等
%   强制 overfull 的极端场景下，最后一行的 overfull 报告值可能增大。
%
%   使用示例：
%   \begin{verbatim}
%   \xeCJKsetup{experiment/punct-measure-fix}
%   \end{verbatim}
% \end{function}
%
% \begin{function}[added=2026-07-22]{experiment/boundary-register}
%   \begin{syntax}
%     experiment/boundary-register =
%   \  \{
%   \    command  = \meta{control sequence},
%   \    strategy = \meta{strategy}, mode = \meta{mode}
%   \  \}
%   \end{syntax}
%   这是一个面向熟悉 \TeX{} 节点和命令实现的用户的实验性接口。它把
%   \pkg{xeCJK} 尚未处理的命令接入命令边界框架，使命令包装前后的可见内容
%   尽量具有相同的中西文间距。一个键值只注册一个命令；需要注册多个命令时，
%   可以重复使用本选项。
%
%   |command| 必须是一个控制序列。命令名含有~\mdcode*{@} 时，用户应在声明外使用
%   \tn{makeatletter} 和 \tn{makeatother}；注册公开的 \LaTeX3 命令时，
%   应使用 \tn{ExplSyntaxOn} 和 \tn{ExplSyntaxOff} 让~\mdcode*{\_} 和~\mdcode*{:} 成为命令名
%   的一部分。这个接口不接受不带反斜线的命令名字符串。
%
%   |strategy| 可以取以下值：
%   \begin{keyval}
%     \item[\keys{box}]
%       命令结束时在当前列表末尾留下一个水平盒子。
%     \item[\keys{wrapped-box}]
%       命令会直接写出多个节点，并且能够在 \pkg{xeCJK} 打开的水平盒子中
%       完整执行。
%     \item[\keys{stream}]
%       命令的可见内容直接写入当前列表。
%     \item[\keys{transparent}]
%       命令没有可见输出，只留下 PDF 锚点等特殊节点。
%     \item[\keys{post-transparent}]
%       命令结束时留下零尺寸水平盒子，只能在命令执行后恢复边界状态。
%   \end{keyval}
% \end{function}
% 下面的五个命令分别给出每种策略最简单的典型形式。实际命令只有在节点
% 结构与相应示例一致时，才应采用同一种策略。
% \begin{verbatim}
% \newcommand\mybox[1]{\hbox{#1}}
% \newcommand\mywrapped[1]{\kern0pt #1\kern0pt}
% \newcommand\mystream[1]{\special{my-stream}#1}
% \newcommand\myanchor{\special{my-anchor}}
% \newcommand\myzero{\hbox{}}
% \xeCJKsetup
%   {
%     experiment/boundary-register = { command = \mybox,
%       strategy = box },
%     experiment/boundary-register = { command = \mywrapped,
%       strategy = wrapped-box },
%     experiment/boundary-register = { command = \mystream,
%       strategy = stream },
%     experiment/boundary-register = { command = \myanchor,
%       strategy = transparent },
%     experiment/boundary-register = { command = \myzero,
%       strategy = post-transparent }
%   }
% \end{verbatim}
%
% |box|、|wrapped-box| 和 |stream| 可以使用 |mode|。省略时默认为
% |auto|，即采用运行时观察到的实际首、尾类别。|default| 把两端固定视为
% Default；|first-default| 只把左端固定视为 Default，并且只适用于
% |stream|。|transparent| 和 |post-transparent| 不接受 |mode|。
%
% 用户声明在导言区收集，到导言区末尾统一注册；即使声明写在分组内，也会
% 对整篇文档生效。目标命令可以在普通的 \tn{AtBeginDocument} 中定义；
% 如果它到正文开始以后才定义，注册不会生效并会给出错误。已经由
% \pkg{xeCJK} 处理的命令和重复声明也会报错，本接口不提供反注册或覆盖
% 内建注册的功能。命令 hook 安装完成后，如果其他代码重新定义了目标命令，
% 新定义可能不再包含这里登记的处理。
%
% 并非所有命令都适合通用注册。\tn{verb}、\tn{url} 等使用特殊参数读取
% 方式的命令可能需要专用适配；公开命令如果只负责读取参数，应当注册真正
% 排版内容的内部命令。能够作为 |command| 读入的控制序列，也不一定能由
% \LaTeX{} 通用命令 hook 完整包住。通用注册不会分析命令参数；如果行内
% 公式位于可见内容的开头或结尾，|auto| 未必能观察到公式边界。
%
% 特别地，\emph{命令本身就是一条赋值语句时不能用通用注册}。\tn{global}、
% \tn{long} 这类前缀在 \TeX{} 中是“等待下一个赋值”的状态，而 |before|
% 钩子的代码插在该赋值之前执行；钩子里任何赋值都会把前缀消耗掉，调用方写的
% \tn{global} 便静默失效，且不产生任何错误或警告。\tn{sbox} 属于这一类，
% 本包为它使用专用适配器，把暂停观察移到盒子内部，使 \tn{global} 始终紧邻
% \tn{setbox}（\#1029）。用户注册自定义的“保存盒子”类命令时应同样处理。
%
% 顺带说明一个与本包无关的既有限制：\tn{global}\tn{savebox} 本来就不能跨
% 分组生效。这是因为 \tn{savebox} 是 robust 命令，\tn{global} 在它自己的
% \tn{@ifnextchar} 前瞻阶段就被消耗，前缀根本到不了内部入口；不加载本包的
% 原版 \LaTeX{} 亦然。需要全局保存盒子时应使用 \tn{global}\tn{sbox} 或
% \tn{global}\tn{setbox}。
% \tn{mbox} 等现有命令另有参数适配代码，用户注册不会自动取得这部分能力。
%
% \emph{注册的层级也会影响结果}。框架在进入命令时就把 \tn{CJKecglue}、
% \tn{CJKglue} 和词间空格排入临时盒并读成数值，缓存的度量因此取决于进入
% 命令那一刻生效的字体。如果目标命令的定义里包含字体切换，注册就必须包住
% 最外层的那次切换：注册在切换内侧时，左边界会用切换后的字体度量，而右边界
% 在命令结束、字体已经恢复之后求值，两侧数值不一致。这种写法在纯西文和
% 纯中文文档里都看不出问题，只有中西文边界两侧都出现时才暴露。因此，若公开
% 命令形如~|\newcommand\mycmd[1]{\texttt{...#1...}}|，应当注册这个公开命令，
% 而不是它内部负责排版参数的辅助命令（\#1046）。
%
% 注册后应当把命令与“去掉命令包装后的相同可见内容”比较，并分别检查左右
% 边界、四种源码空格以及 \opt{xCJKecglue} 的两个取值。随包提供的
% \pkg{xeCJK-example-boundary-register.tex} 用明显不同的
% \tn{CJKecglue} 和 \tn{CJKglue} 展示了这种检查方法。其中公式行还会
% 显示上述参数适配限制；方框不等宽时，说明不能只靠所选通用策略处理该
% 命令。
%
% \begin{function}[updated=2013-11-16]{Verb}
%   \begin{syntax}
%     Verb = \meta{\TF|(env)|env+}
%   \end{syntax}
%   \texttt{true} 表示在 \tn{verb} 命令或 \texttt{verbatim} 环境里不自动调整中英文
%   之间的间距。\texttt{env} 选项在 \texttt{verbatim} 环境里自动计算中西文间距和中文
%   之间的间距，以便于保持代码的对齐；\texttt{env} 选项不调整 \tn{verb} 里的间距，^^A
%   \texttt{env+} 选项还将正文里设置的间距应用到 \tn{verb} 里。^^A
%   这个选项对使用到 \tn{verbatim@font} 命令的情形均有效，更一般的情况可以使用
%   \ref{subsec:others}~节介绍的 \tn{xeCJKVerbAddon}。\texttt{false} 表示不作任何
%   处理。以上选项的值除 \texttt{false} 外，都禁止在汉字之间和汉字与西文之间自动换行。
% \end{function}
%
% \begin{function}[rEXP,added=2014-03-01]{LoadFandol}
%   \begin{syntax}
%     LoadFandol = \meta{\TTF}
%   \end{syntax}
%   当没有在导言区设置 CJK 字体时，是否使用 Fandol 字体。如果启用这个选项，需要
%   安装 \package{Fandol} 字体系列。
% \end{function}
%
% \subsection{字体设置与选择}
% \label{subsec:fontset}
%
% \begin{function}[EXP,updated=2016-11-18]{\setCJKmainfont}
%   \begin{syntax}
%     \tn{setCJKmainfont} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKmainfont} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   设置正文罗马族的 CJK 字体，影响 \tn{rmfamily} 和 \tn{textrm} 的字体。后面两个
%   参数继承自 \pkg{fontspec} 宏包， \meta{font features} 表示字体属性选项，
%   \meta{font name} 是字体名。字体名可以是字体族名，也可以是字体的文件名，查
%   找字体名见 \ref{subsubsec:fontsearch}~节；可用的字体属性选项参见
%   \pkg{fontspec} 宏包的文档。需要说明的是 \pkg{xeCJK} 修改了 |AutoFakeBold|
%   和 |AutoFakeSlant| 选项，以便配合全局伪粗体和伪斜体的设定。
%
%   出于兼容性考虑，字体属性可选项可以放在字体名称前面，也可以放在后面。
%   如果可选项放在后面，字体名称与可选项之间不要有空格或者换行。
% \end{function}
%
% \begin{function}[label = ]{AutoFakeBold,AutoFakeSlant}
%   \begin{syntax}
%     AutoFakeBold  = \Arg{\TF|数字}
%     AutoFakeSlant = \Arg{\TF|数字}
%   \end{syntax}
%   局部设置当前字体族的伪粗和伪斜属性。如果没有在局部给出这些选项，将使用全局设定。
% \end{function}
%
% \begin{function}[added=2013-06-07]{Mapping}
%   \begin{syntax}
%     Mapping = \Arg{fullwidth-stop|full-stop|han-trad|han-simp|...}
%   \end{syntax}
%   \pkg{xeCJK} 提供了以上四个 \href{http://scripts.sil.org/teckit}{TECKit} 映射
%   文件，可以在设置字体的时候通过 \texttt{Mapping} 选项来使用它们。其中
%   \texttt{fullwidth-stop} 用于将正常句号~\mdcode{。}
%   转换成全角实心句号~\mdcode{．}，
%   \texttt{full-stop} 的作用相反。\texttt{han-trad} 用于将简体中文转换成繁体中文，
%   \texttt{han-simp} 的作用相反。需要注意的是，简繁互换都是简单机械的字字对译，
%   不能做到完全准确，使用时要小心。例如简体的“发挥”和“头发”被转换成繁体的
%   “發揮”和“頭發”，显然后者应作“頭髮”。也可以根据实际需要，制作新的映射文件，
%   请参考 TECKit 的文档。
% \end{function}
%
% \begin{function}[EXP,updated=2016-11-18]{\setCJKsansfont}
%   \begin{syntax}
%     \tn{setCJKsansfont} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKsansfont} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   设置正文无衬线族的 CJK 字体，影响 \tn{sffamily} 和 \tn{textsf} 的字体。
% \end{function}
%
% \begin{function}[EXP,updated=2016-11-18]{\setCJKmonofont}
%   \begin{syntax}
%     \tn{setCJKmonofont} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKmonofont} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   设置正文等宽族的 CJK 字体，影响 \tn{ttfamily} 和 \tn{texttt} 的字体。
% \end{function}
%
% \begin{function}[EXP,updated=2016-11-18]{\setCJKfamilyfont}
%   \begin{syntax}
%     \tn{setCJKfamilyfont} \Arg{family} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKfamilyfont} \Arg{family} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   声明新的 CJK 字体族 \meta{family} 并指定字体。
% \end{function}
%
% \begin{function}[updated=2012-10-27]{\CJKfamily}
%   \begin{syntax}
%     \tn{CJKfamily}   \Arg{family}
%     \tn{CJKfamily} + \Arg{family}
%     \tn{CJKfamily} - \Arg{family}
%   \end{syntax}
%   用于在文档中切换 |CJK| 字体族，\meta{family} 必须预先声明。\tn{CJKfamily} 仅对
%   CJK 字符类有效，\tn{CJKfamily}|+| 对所有字符类均有效，\tn{CJKfamily}|-| 对非 CJK 字
%   符类有效。当 \tn{CJKfamily}|+| 和 \tn{CJKfamily}|-| 的参数为空时，则使用当前的 |CJK| 字体族。
% \end{function}
%
% \begin{function}[EXP,updated=2016-11-18]{\newCJKfontfamily}
%   \begin{syntax}
%     \tn{newCJKfontfamily} \oarg{family} \cs{\meta{font-switch}} \Arg{font name} \oarg{font features} 或\\
%     \tn{newCJKfontfamily} \oarg{family} \cs{\meta{font-switch}} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   声明新的 CJK 字体族 \meta{family} 并指定字体，并定义 \cs{\meta{font-switch}}，在
%   文档中可以使用它来切换 CJK 字体族。可以不必指定 \meta{family}，这时候 \meta{family}
%   将等于 \meta{font-switch}。
% \end{function}
%
%   事实上，\tn{newCJKfontfamily} 是 \tn{setCJKfamilyfont} 和
%   \tn{CJKfamily} 的合并。例如
%   \begin{ctexexam}
%   \newCJKfontfamily[song]\songti{SimSun}
%   \end{ctexexam}
%   等价于
%   \begin{ctexexam}
%   \setCJKfamilyfont{song}{SimSun}
%   \newcommand*{\songti}{\CJKfamily{song}}
%   \end{ctexexam}
%
% \begin{function}[updated=2016-11-18]{\CJKfontspec}
%   \begin{syntax}
%     \tn{CJKfontspec} \Arg{font name} \oarg{font features} 或\\
%     \tn{CJKfontspec} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   在文档中定义新的 CJK 字体族，并马上使用它。
% \end{function}
%
% \begin{function}[rEXP]{\defaultCJKfontfeatures}
%   \begin{syntax}
%     \tn{defaultCJKfontfeatures} \Arg{font features}
%   \end{syntax}
%   全局设置 CJK 字体族的默认选项。例如，使用
%   \begin{ctexexam}
%   \defaultCJKfontfeatures{Scale=0.962216}
%   \end{ctexexam}
%   可以将全部 CJK 字体缩小为 |0.962216|。\pkg{xeCJK} 宏包的初始化设置是
%   \begin{frameverb}
%   \defaultCJKfontfeatures{Script=CJK}
%   \end{frameverb}
% \end{function}
%
% \begin{function}[updated=2013-06-30]{\addCJKfontfeatures}
%   \begin{syntax}
%     \tn{addCJKfontfeatures}   \Arg{font features}
%     \tn{addCJKfontfeatures} * \Arg{font features}
%     \tn{addCJKfontfeatures}   \oarg{block_1, block_2, ...} \Arg{font features}
%     \tn{addCJKfontfeatures} * \oarg{block_1, block_2, ...} \Arg{font features}
%   \end{syntax}
%   临时增加当前使用的 CJK 字体的选项。第一条命令，仅对当前 CJK 主分区字体有效；
%   第二条对主分区和其他分区的字体都有效；第三条仅对可选参数中指定的分区有效；
%   第四条对主分区和可选参数中指定的分区有效。例如，使用
%   \begin{ctexexam}
%   \addCJKfontfeatures{Scale=1.1}
%   \end{ctexexam}
%   可以将文档中当前使用的 CJK 主分区字体放大为 |1.1|。
% \end{function}
%
% \begin{function}{\CJKrmdefault}
%   保存 \tn{textrm} 和 \tn{rmfamily} 所使用的 CJK 字体族，默认值是 |rm|。
% \end{function}
%
% \begin{function}{\CJKsfdefault}
%   保存 \tn{textsf} 和 \tn{sffamily} 所使用的 CJK 字体族，默认值是 |sf|。
% \end{function}
%
% \begin{function}{\CJKttdefault}
%   保存 \tn{texttt} 和 \tn{ttfamily} 所使用的 CJK 字体族，默认值是 |tt|。
% \end{function}
%
% \begin{function}[updated=2013-01-01]{\CJKfamilydefault}
%   保存 \tn{textnormal} 和 \tn{normalfont} 所使用的 CJK 字体族。类似西文字体的 \tn{familydefault}。
%   初始值是 \tn{CJKrmdefault}。如果没有在导言区中修改它，\pkg{xeCJK} 会在导言区
%   结束的时候根据西文字体的情况自动更新 \tn{CJKfamilydefault}。因此，在导言区里使用
%   \begin{frameverb}
%   \renewcommand\familydefault{\sfdefault}
%   \end{frameverb}
%   就可以将全文的 CJK 和西文默认字体都改为无衬线字体族。
% \end{function}
%
% \begin{function}[EXP,updated=2016-11-18]{\setCJKmathfont}
%   \begin{syntax}
%     \tn{setCJKmathfont} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKmathfont} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   设置数学公式中的 CJK 字体族。如果使用了 |CJKmath| 选项，但是没有使用
%   \tn{setCJKmathfont} 设置数学公式中的 CJK 字体，那么将使用 \tn{CJKfamilydefault}
%   作为数学公式中的 CJK 字体。
% \end{function}
%
% \begin{function}[EXP, label=, updated=2016-11-18]{\setCJKfallbackfamilyfont}
%   \begin{syntax}
%     \tn{setCJKfallbackfamilyfont} \Arg{family} \Arg{font name} \oarg{font features} 或\\
%     \tn{setCJKfallbackfamilyfont} \Arg{family} \oarg{font features} \Arg{font name}
%   \end{syntax}
%   设置 CJK 字体族 \meta{family} 的备用字体。例如，使用
%   \begin{ctexexam}
%   \setCJKmainfont{SimSun}
%   \setCJKfallbackfamilyfont{\CJKrmdefault}{SimSun-ExtB}
%   \end{ctexexam}
%   可以将 |SimSun-ExtB| 作为 |SimSun| 的备用字体。
% \end{function}
%
% \begin{function}{FallBack}
%   \begin{syntax}
%     FallBack = \{\oarg{font features} \Arg{font name}\}
%   \end{syntax}
%   \pkg{xeCJK} 在 \meta{font features} 里增加了 |FallBack| 这个选项。用来在声明主
%   字体的时候，同时设置备用字体。例如，上面的例子等价于：
%   \begin{ctexexam}
%   \setCJKmainfont[FallBack=SimSun-ExtB]{SimSun}
%   \end{ctexexam}
%   如果 |FallBack| 的值为空，将设置的是备用字体。例如，
%   \begin{ctexexam}
%   \setCJKmainfont[FallBack,AutoFakeBold,Scale=.97]{SimSun-ExtB}
%   \end{ctexexam}
%   等价于
%   \begin{ctexexam}
%   \setCJKfallbackfamilyfont{\CJKrmdefault}[AutoFakeBold,Scale=.97]{SimSun-ExtB}
%   \end{ctexexam}
% \end{function}
%
% \begin{function}[EXP,updated=2013-06-30]{\setCJKfallbackfamilyfont}
%   \begin{syntax}
%     \tn{setCJKfallbackfamilyfont} \Arg{family}
%        \  \{
%        \    \{\oarg{font features_1} \Arg{font name_1}\} ,
%        \    \{\oarg{font features_2} \Arg{font name_2}\} ,
%        \     ......
%        \  \}\oarg{common font features} 或\\
%     \tn{setCJKfallbackfamilyfont} \Arg{family} \oarg{common font features}
%        \  \{
%        \    \{\oarg{font features_1} \Arg{font name_1}\} ,
%        \    \{\oarg{font features_2} \Arg{font name_2}\} ,
%        \     ......
%        \  \}
%   \end{syntax}
%   \tn{setCJKfallbackfamilyfont} 还可以用于设置多层的备用字体。例如，使用
%   \begin{ctexexam}
%   \setCJKmainfont[AutoFakeBold,AutoFakeSlant]{KaiTi_GB2312}
%   \setCJKfallbackfamilyfont{\CJKrmdefault}[AutoFakeSlant]
%     { [BoldFont=SimHei]{SimSun} ,
%       [AutoFakeBold]   {SimSun-ExtB} }
%   \end{ctexexam}
%   之后，就设置了 |SimSun| 是 |KaiTi_GB2312| 的备用字体，而 |SimSun-ExtB| 是
%   |SimSun| 的备用字体。若当前字体族缺字，并没有备用字体，则尝试使用
%   \tn{CJKfamilydefault} 的备用字体。
% \end{function}
%
% \subsubsection{\XeTeX 的字体名查找}
% \label{subsubsec:fontsearch}
%
% 由于在 \pkg{fontspec} 宏包文档中缺少关于如何查看 \XeTeX{} 可用字体名的说明，
% 这里略作说明。
%
% \XeTeX{} 通常使用 fontconfig 库查找和调用字体，因此，可以用 |fc-list| 命令显
% 示可用的字体。在命令行（Windows 的“命令提示符”，Linux 的 Console）下运行以
% 下命令：
% \begin{frameverb}
%   fc-list > fontlist.txt
% \end{frameverb}
% 可以将系统中所有安装的字体列表存入 \file{fontlist.txt} 文件中（可能很长）。
%
% |fc-list| 命令列出的信息很多，而且在安装字体较多的 Windows 系统上的输出将非
% 常庞大，如其中可能包含：
% \begin{frameverb}
%   Times New Roman:style=cursiva,kurzíva,kursiv,Πλάγια,Italic,
%     Kursivoitu,Italique,Dőlt,Corsivo,Cursief,kursywa,Itálico,Курсив,
%     İtalik,Poševno,nghiêng,Etzana
%   Times New Roman:style=Negreta cursiva,tučné kurzíva,fed kursiv,
%     Fett Kursiv,Έντονα Πλάγια,Bold Italic,Negrita Cursiva,
%     Lihavoitu Kursivoi,Gras Italique,Félkövér dőlt,Grassetto Corsivo,
%     Vet Cursief,Halvfet Kursiv,Pogrubiona kursywa,Negrito Itálico,
%     Полужирный Курсив,Tučná kurzíva,Fet Kursiv,Kalın İtalik,
%     Krepko poševno,nghiêng đậm,Lodi etzana
%   Times New Roman:style=Negreta,tučné,fed,Fett,Έντονα,Bold,Negrita,
%     Lihavoitu,Gras,Félkövér,Grassetto,Vet,Halvfet,Pogrubiona,Negrito,
%     Полужирный,Fet,Kalın,Krepko,đậm,Lodia
%   Times New Roman:style=Normal,obyčejné,Standard,Κανονικά,Regular,
%     Normaali,Normál,Normale,Standaard,Normalny,Обычный,Normálne,Navadno,
%     thường,Arrunta
%   宋体,SimSun:style=Regular
%   黑体,SimHei:style=Normal,obyčejné,Standard,Κανονικά,Regular,Normaali,
%     Normál,Normale,Standaard,Normalny,Обычный,Normálne,Navadno,Arrunta
% \end{frameverb}
% 在 \pkg{fontspec} 或 \pkg{xeCJK} 中使用的字体族名是上面列表中冒号前的部分。
% 例如可以使用
% \begin{ctexexam}
%   \setmainfont{Times New Roman}
%   \setCJKmainfont{SimSun} % 或者 \setCJKmainfont{宋体}
% \end{ctexexam}
% 来设置字体。
%
% 为了方便起见，|fc-list| 命令也可以加上各种选项控制输出格式，例如，如果只要列出
% 所有的中文字体的字体族名，可以用命令：
% \begin{frameverb}
%   fc-list -f "%{family}\n" :lang=zh  > zhfont.txt
% \end{frameverb}
% 这样就把字体列表保存在文件 \file{zhfont.txt} 中\footnote{由于汉字编码原因，
% Windows 下总需要把字体列表输出到文件中以防止乱码。}。这样列出的字体列表就比较
% 简明易用，如 Windows 下预装的中文字体：
% \begin{frameverb}
%   Arial Unicode MS
%   FangSong,仿宋
%   KaiTi,楷体
%   Microsoft YaHei,微软雅黑
%   MingLiU,細明體
%   NSimSun,新宋体
%   PMingLiU,新細明體
%   SimHei,黑体
%   SimSun,宋体
% \end{frameverb}
% 要列出日文和韩文的字体，可以把~|:lang=zh| 选项中的 |zh| 改成 |ja| 或 |ko|。
%
% \pkg{fontspec} 和 \pkg{xeCJK} 也可以使用字体的文件名访问字体。例如 Windows
% 下的宋体也可以使用命令：
% \begin{frameverb}
%   \setCJKmainfont{simsun.ttc}
% \end{frameverb}
% 来设置。设置字体文件名的相关选项和语法在 \pkg{fontspec} 宏包手册中叙述甚详，
% 这里不再赘述。有个别字体名不规范的中文字体，\pkg{xeCJK} 宏包可能无法正确地通
% 过字体名访问，那么也可以使用这种方式设置。
%
% \subsection{CJK 分区字体设置}
% \label{subsec:block}
%
% 众所周知，CJK 文字数量极其庞大，单一的字体不可能涵盖所有的 CJK 文字。\pkg{xeCJK} 可
% 以在同一 CJK 字体族下，自动使用不同的字体输出 CJK 字符范围内不同区块里的文字。首先要
% 声明 CJK 子分区。
%
% \begin{function}[EXP]{\xeCJKDeclareSubCJKBlock}
%   \begin{syntax}
%     \tn{xeCJKDeclareSubCJKBlock}   \Arg{block} \Arg{block range}
%     \tn{xeCJKDeclareSubCJKBlock} * \Arg{block} \Arg{block range}
%   \end{syntax}
%   其中 \meta{block range} 是逗号列表，可以是 CJK 字符的 |Unicode| 范围，也可以是单个字符
%   的 |Unicode|。例如
% \end{function}
%   \begin{ctexexam}
%   { `中 -> `文 , "3400 -> "4DBF , "5000 -> "7000 , `汉 , `字 , "3500 }
%   \end{ctexexam}
%   的形式。需要注意的是，这里设置的 \meta{block range} 除非确实需要（例如某些特殊字体使用
%   了 |Unicode| 中的私人使用区的情况），否则不要超出源代码中预设的
%   \hyperlink{CJKcharclass}{CJK 文字范围}。使用
%   \begin{ctexexam}
%   \xeCJKDeclareSubCJKBlock{SPUA}{ "E400 -> "E4DA , "E500 -> "E5E8 , "E600 -> "E6CE }
%   \xeCJKDeclareSubCJKBlock{Ext-B}{ "20000 -> "2A6DF }
%   \end{ctexexam}
%   会声明 |SPUA| 和 |Ext-B| 这两个子分区；同时，这将会在 \ref{subsec:fontset}~节当中介绍的
%   CJK 字体设置命令的 \meta{font features} 里新建 |SPUA| 和 |Ext-B| 两个选项。
%   新建的这两个选项，其使用方法与 \ref{subsec:fontset} 介绍的 |FallBack| 类似——可以
%   通过它们来设置字体。
%
%   例如，可以使用
%   \begin{ctexexam}
%   \setCJKmainfont[SPUA=SunmanPUA,Ext-B=SimSun-ExtB]{SimSun}
%   \end{ctexexam}
%   设置文档的主字体是 |SimSun|，|SPUA| 分区的字体是 |SunmanPUA|，同时设置 |Ext-B| 分区
%   的字体是 |SimSun-ExtB|。
%
%   \tn{xeCJKDeclareSubCJKBlock} 应该在声明所有的 CJK 字体族之前使用。如果有某个 CJK 字
%   体族没有设置 \meta{block} 选项，将使用 \tn{CJKfamilydefault} 的 \meta{block} 选项
%   作为该 CJK 字体族的 \meta{block} 选项。如果希望在使用某 CJK 字体族时，不在 CJK 主
%   分区与 \meta{block} 之间切换字体，可以使用 \meta{block}|=*| 选项。带星号的命令除了
%   设置 CJK 子分区以外，还重置标点符号所属的字符类。
%
% \begin{function}{\xeCJKCancelSubCJKBlock}
%   \begin{syntax}
%     \tn{xeCJKCancelSubCJKBlock}   \Arg{block_1, block_2, ...}
%     \tn{xeCJKCancelSubCJKBlock} * \Arg{block_1, block_2, ...}
%   \end{syntax}
%   在文档中取消对 |CJK| 分区的声明。带星号的命令还重置标点符号所属的字符类。
% \end{function}
%
% \begin{function}{\xeCJKRestoreSubCJKBlock}
%   \begin{syntax}
%     \tn{xeCJKRestoreSubCJKBlock}   \Arg{block_1, block_2, ...}
%     \tn{xeCJKRestoreSubCJKBlock} * \Arg{block_1, block_2, ...}
%   \end{syntax}
%   在文档中恢复对 |CJK| 分区的声明。带星号的命令还重置标点符号所属的字符类。
% \end{function}
%
% \subsection{设置 CJK 字符范围}
%
% \begin{function}[EXP]{\xeCJKDeclareCharClass}
%   \begin{syntax}
%     \tn{xeCJKDeclareCharClass}   \Arg{class} \Arg{class range}
%     \tn{xeCJKDeclareCharClass} * \Arg{class} \Arg{class range}
%   \end{syntax}
%   \meta{class range} 的格式和 \ref{subsec:block}~节的 \meta{block range} 相同。
%   \meta{class} 的有效值见源代码（第 \ref{sec:xeCJK-class-set}~节）。\pkg{xeCJK} 已
%   经支持 |Unicode| 中所有 CJK 文字和标点。一般来说，不要轻易改变字符类别。带星号的
%   命令除了设置字符类别以外，为了确保标点处理的正确性，还重置标点符号所属的字符类。
% \end{function}
%
% \begin{function}[EXP]{\xeCJKResetCharClass}
%   用于恢复 \pkg{xeCJK} 对各个字符类别的初始化设置。
% \end{function}
%
% \begin{function}[EXP]{\xeCJKResetPunctClass}
%   用于重置标点符号所属的字符类。
% \end{function}
%
% \begin{function}{\normalspacedchars}
%   \begin{syntax}
%     \tn{normalspacedchars} \Arg{char list}
%   \end{syntax}
%   在 \meta{char list} 中出现的字符两端不自动添加空格，
%   初始设置是~\mdcode{/}、\mdcode{\textbackslash}~和~\mdcode{-} (|U+002D|)。
% \end{function}
%
% \subsection{标点符号的处理}
%
% \pkg{xeCJK} 对标点符号的输出宽度的调整是通过调整其左边或右边的空白宽度来实现的。
% 按照目前的处理方式，对于位于左边的标点符号（如左引号），\pkg{xeCJK} 只能调整它
% 左边的空白；对于位于右边的标点符号（如右引号），\pkg{xeCJK} 只能调整它右边的空
% 白；对于居中的标点符号，则调整其左右空白，以保证其居中。对于标点符号的相关设置，
% 只能在导言区中进行。
%
% \subsubsection{设置特定标点符号的宽度和间距}
% \label{subsec:punct}
%
% 这里的设置可用于除 |plain| 以外的所有标点处理格式。
%
% \begin{function}[EXP,updated=2013-08-22]{\xeCJKsetwidth}
%   \begin{syntax}
%     \tn{xeCJKsetwidth}   \Arg{标点列表} \Arg{length}
%     \tn{xeCJKsetwidth} * \Arg{标点列表} \Arg{length}
%   \end{syntax}
%   \meta{标点列表} 可以是单个标点，也可以是多个标点。例如，
%   \begin{ctexexam}
%   \xeCJKsetwidth{。？}{0.7em}
%   \end{ctexexam}
%   将设置句号和问号所占的宽度为 |0.7em|。带星号的命令，设置标点符号出现在行首/尾
%   时的宽度。
% \end{function}
%
% \begin{function}[EXP]{\xeCJKsetkern}
%   \begin{syntax}
%     \tn{xeCJKsetkern} \Arg{前标点} \Arg{后标点} \Arg{length}
%   \end{syntax}
%   \pkg{xeCJK} 会根据选定的标点处理格式自动调整相邻的前后两个 |CJK| 标点符号的
%   空白宽度。如果需要对个别情况进行特殊调整，可以使用这个命令。例如，
%   \begin{ctexexam}
%   \xeCJKsetkern{：}{“}{0.3em}
%   \end{ctexexam}
%   将设置冒号与左双引号之间的空白宽度为 |0.3em|。
% \end{function}
%
% \subsubsection{定义标点符号处理格式}
% \label{subsec:punctstyle}
%
% \begin{function}[EXP,updated=2013-08-22]{\xeCJKDeclarePunctStyle}
%   \begin{syntax}
%     \tn{xeCJKDeclarePunctStyle} \Arg{style} \Arg{options}
%   \end{syntax}
%   定义新的标点符号处理格式，已经存在的同名格式将被覆盖。可以设置的选项将在下面介绍。
% \end{function}
%
% \begin{function}[EXP,updated=2013-08-22]{\xeCJKEditPunctStyle}
%   \begin{syntax}
%     \tn{xeCJKEditPunctStyle} \Arg{style} \Arg{options}
%   \end{syntax}
%   修改已有的标点符号处理格式。
% \end{function}
%
% 下面是可以设置的标点符号格式选项。其中左边一栏是选项名称，中间是选项的输入值类
% 型，右边则是相关说明。某些选项之间是互斥的，具有优先级关系。要使下一级的选项有
% 效，则需要先禁用上一级的设置：对于 \meta{boolean} 类型的选项，将其设置为 |false|，
% 对于 \meta{length} 类型的选项，将其设置为 \tn{maxdimen}，而对于 \meta{real} 类型
% 的选项，将其设置为 |nan|。
%
% \changes{v3.10.3}{2026/07/13}
% {新增 \texttt{enabled-left-right-kerning} 标点格式选项（\#975）。}
% \begin{keyval}
%   \PSKeyVal{enabled-global-setting}{\TTF}
%     是否使用 \tn{xeCJKsetup} 的 |PunctWidth|、|PunctBoundWidth| 选项和
%     \tn{xeCJKsetwidth}、\tn{xeCJKsetkern} 的设置。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{fixed-punct-width}{length}
%     设置单个标点符号的宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{fixed-punct-ratio}{real}
%     设置单个标点符号的输出宽度与实际宽度的比例。默认值是 |1.0|。
%   \PSKeyVal{mixed-punct-width}{length}
%     设置句末标点符号的宽度。其中句末标点符号通过 \tn{xeCJKsetup} 的 |KaiMingPunct|
%     来设置。默认值是与 |fixed-punct-width| 选项的值相同。
%   \PSKeyVal{mixed-punct-ratio}{real}
%     设置句末标点符号的宽度比例。默认值是与 |fixed-punct-ratio| 选项的值相同。
%   \PSKeyVal{middle-punct-width}{length}
%     设置居中标点符号的宽度。其中居中标点符号通过 \tn{xeCJKsetup} 的 |MiddlePunct|
%     来设置。默认值是与 |fixed-punct-width| 选项的值相同。
%   \PSKeyVal{middle-punct-ratio}{real}
%     设置居中标点符号的宽度比例。默认值是与 |fixed-punct-ratio| 选项的值相同。
% \end{keyval}
%
% 以上几个选项设置的是标点的固定宽度或比例，\pkg{xeCJK} 会根据设定的选项计算标点符号
% 左/右的空白宽度。下面的选项设置的是标点符号左/右的空白宽度或比例，因此不同标点符号
% 的宽度可能会不同。为了使下面的选项生效，需要先禁用上面的相应选项。优先级自上而下。
%
% \begin{keyval}
%   \PSKeyVal{fixed-margin-width}{length}
%     设置标点的左/右空白宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{fixed-margin-ratio}{real}
%     设置标点的左/右空白宽度与当前字体中该标点的相应实际边界宽度的比例。默认值是~|1.0|。
%   \PSKeyVal{mixed-margin-width}{length}
%     设置句末标点的左/右空白宽度。默认值是与 |fixed-margin-width| 的值相同。
%   \PSKeyVal{mixed-margin-ratio}{real}
%     设置句末标点的左/右空白宽度的比例。默认值是与 |fixed-margin-ratio| 的值相同。
%   \PSKeyVal{middle-margin-width}{length}
%     设置居中标点的两边空白宽度。默认值是与 |fixed-margin-width| 的值相同。
%   \PSKeyVal{middle-margin-ratio}{real}
%     设置居中标点的两边空白宽度之和与两边实际两边边界宽度之和的比例。
%     默认值是与 |fixed-margin-ratio| 的值相同。
% \end{keyval}
%
% 下面选项设置标点符号出现在行首或者行尾时的宽度或比例。
%
% \begin{keyval}
%   \PSKeyVal{bound-punct-width}{length}
%   设置标点符号出现在行首/尾时的宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{bound-punct-ratio}{real}
%   设置标点符号出现在行首/尾时的输出宽度与实际宽度的比例。默认值是 |nan|。
%   \PSKeyVal{bound-margin-width}{length}
%   设置标点符号出现在行首/尾时的左/右空白宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{bound-margin-ratio}{real}
%   设置标点符号出现在行首/尾时的左/右空白宽度与相应实际边界宽度的比例。默认值是 |0|。
%   \PSKeyVal{enabled-hanging}{\TFF}
%   当以上选项的计算结果得到的宽度小于标点符号的实际边界宽度时，是否允许标点符号
%   悬挂出页面边界。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{add-min-bound-to-margin}{\TFF}
%     是否在以上计算结果的基础上再加上标点的左右实际边界宽度中的最小值。这个选项
%     对居中的标点无效。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{optimize-margin}{\TFF}
%     使用以上设置空白宽度或比例的选项时，最终输出的标点符号左/右的空白宽度可能大
%     于原来的实际边界宽度。若此时本选项被设置为 |true|，则使用原来的实际边界宽度。
%     而使用 |fixed-punct-width| 选项计算得出的左/右宽度可能小于该标点的另一侧宽
%     度，若此时本选项被启用，则使用该标点的另一侧宽度。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{margin-minimum}{length}
%     指定标点符号左/右的最小空白宽度。当经过以上选项设置的空白宽度小于这个选项的值
%     时，则使用这个选项的值。默认值是 |0pt|。
% \end{keyval}
%
% 下面的选项处理的是前后相邻的两个标点符号之间的空白宽度。这些选项是互斥的，优先级
% 自上而下。
%
% \begin{keyval}
%   \PSKeyVal{enabled-kerning}{\TTF}
%     是否调整前后相邻的两个标点之间的空白宽度。如果设置为 |false|，则每个标点都按
%     原来的输出宽度输出。
%   \PSKeyVal{enabled-left-right-kerning}{\TTF}
%     是否压缩左标点后紧跟右标点时二者之间的空白。
%     预设值 |quanjiao| 将它设为 |false|，保留字体的自然空白。
%     通过 \tn{xeCJKsetkern} 显式设置的字符对不受此选项影响。
%   \PSKeyVal{min-bound-to-kerning}{\TFF}
%     是否使用当前字体中前面标点实际左右边界的最小值与后面标点实际左右边界的最小值
%     中的最大值作为两个标点之间的空白宽度。
%   \PSKeyVal{kerning-total-width}{length}
%     设置两个标点的总共宽度。此时 \pkg{xeCJK} 会自动计算两个标点之间的空白宽度。
%     默认值是 \tn{maxdimen}。
%   \PSKeyVal{kerning-total-ratio}{real}
%     设置两个标点的总共输出宽度与实际宽度的比例。默认值是 |0.75|。
%   \PSKeyVal{same-align-margin}{length}
%     前后两个标点位于同侧时，它们之间的空白宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{same-align-ratio}{real}
%     前后两个标点位于同侧时，它们之间的空白宽度与实际输出宽度的比例。默认值是~|nan|。
%   \PSKeyVal{different-align-margin}{length}
%     前后两个标点位于异侧时，它们之间的空白宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{different-align-ratio}{real}
%     前后两个标点位于异侧时，它们之间的空白宽度与实际输出宽度的比例。默认值是~|nan|。
%   \PSKeyVal{kerning-margin-width}{length}
%     设置前后两个标点之间的空白宽度。默认值是 \tn{maxdimen}。
%   \PSKeyVal{kerning-margin-ratio}{real}
%     设置前后两个标点之间的空白宽度与实际输出空白的比例。默认值是 |1.0|。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{optimize-kerning}{\TFF}
%     以上选项计算的标点间空白宽度，可能小于 |min-bound-to-kerning| 选项的结果。
%     当出现这一情况时，若此选项被设置为 |true|，则使用该选项的空白宽度。
% \end{keyval}
%
% \begin{keyval}
%   \PSKeyVal{kerning-margin-minimum}{length}
%     指定两个标点之间的最小空白宽度。当经过以上选项设置的空白宽度小于这个选项的值
%     时，则使用这个选项的值。默认值是 |0pt|。
% \end{keyval}
%
% 事实上，\pkg{xeCJK} 的默认设置就相当于中文全角（|quanjiao|）格式。可以使用上面
% 说明的选项定义新的标点处理格式。例如，使用
% \begin{ctexexam}
%   \xeCJKDeclarePunctStyle { mine }
%     {
%       fixed-punct-ratio       = nan ,
%       fixed-margin-width      = 0 pt ,
%       mixed-margin-width      = \maxdimen ,
%       mixed-margin-ratio      = 0.5 ,
%       middle-margin-width     = \maxdimen ,
%       middle-margin-ratio     = 0.5 ,
%       add-min-bound-to-margin = true ,
%       bound-punct-width       = 0 em ,
%       enabled-hanging         = true ,
%       min-bound-to-kerning    = true ,
%       kerning-margin-minimum  = 0.1 em
%     }
% \end{ctexexam}
% 就定义了一个名为 |mine| 的标点处理格式。可以在导言区通过
% \begin{frameverb}
%   \xeCJKsetup{PunctStyle=mine}
% \end{frameverb}
% 在文档中使用这个格式。它的意义是：使用标点符号的实际左右边界中的最小值作为其左/右
% 空白的宽度，对于句末标点和居中标点，再加上实际边界空白的一半；当标点出现在行首或
% 行尾时宽度为零，允许悬挂出页面边界；使用相邻两个标点的实际边界中的较小值作为它们
% 之间的空白宽度，并且最小的空白宽度是 |0.1em|。再例如，使用
% \begin{ctexexam}
%   \xeCJKEditPunctStyle { hangmobanjiao } { enabled-global-setting = false }
% \end{ctexexam}
% 将使得 \tn{xeCJKsetkern} 等的设置对 |hangmobanjiao| 这一格式无效。
%
% \subsection{\pkg{xeCJKfntef} 用法说明}
%
% \pkg{xeCJK} 包含有一个子宏包 \pkg{xeCJKfntef}，可以用它来实现\CJKunderdot{给汉字加点}%
% 以及\CJKunderline{可正确断行的下划线}等效果。它是 \pkg{CJKfntef} 宏包在 \XeLaTeX 下的替换版本，基本用法完全一致。
%
% \pkg{xeCJKfntef} 基于 \package{ulem} 宏包，除了兼容 \pkg{ulem} 定义的一些命令外，
% 还进行了一些扩充：
%
% \begin{function}[updated=2014-11-04]
%  {\CJKunderline,\CJKunderdblline,\CJKunderwave,\CJKsout,\CJKxout}
%   \begin{syntax}
%     \tn{CJKunderline} [*] [-] \oarg{选项} \Arg{内容}
%   \end{syntax}
%   \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=6]
%     \CJKunderline{虚室生白，吉祥止止}\\
%     \CJKunderdblline{虚室生白，吉祥止止}\\
%     \CJKunderwave{虚室生白，吉祥止止}\\
%     \CJKsout{虚室生白，吉祥止止}\\
%     \CJKxout{虚室生白，吉祥止止}
%   \end{SideBySideExample}
% \end{function}
%
% \csappto{NoHighlight@Attributes}{\catcode37=14\relax}
%
% \begin{Example}[frame=single,numbers=left,gobble=4]
%   \CJKunderline-{南朝}\CJKunderline-{梁}\CJKunderline-{劉勰}%
%   \CJKunderwave-{文心雕龍}\CJKunderwave-{養氣}\\
%   \CJKunderline*[thickness=1pt, hidden=true]{瞻彼阕者，虚室生白，吉祥止止}
% \end{Example}
%
% \begin{function}[updated=2014-11-04]{\CJKunderdot}
%   \begin{syntax}
%     \tn{CJKunderdot} \oarg{选项} \Arg{内容}
%   \end{syntax}
%   在汉字下加点，可以和上述下划线命令嵌套使用，嵌套后仍然可以正常断行。例如\smallskip
%
%   \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=6]
%     \CJKunderline{虚室生白，\CJKunderdot{吉祥}止止}\\
%     \CJKunderdot{虚室生白，\CJKunderline{吉祥}止止}
%   \end{SideBySideExample}
%   下划线之间的嵌套则有断行方面的限制，详见本节末尾“嵌套使用时的断行”。
% \end{function}
%
% \bigskip
%
% 对上述六种对象，\pkg{xeCJKfntef} 提供了一些选项，设置点或线的位置和颜色。可以用
% \tn{xeCJKsetup} 预先统一设置它们，也可以在使用时特别设置。
%
% \begin{function}[added=2014-11-04]{skip}
%   \begin{syntax}
%     \tn{xeCJKsetup} \{ underline/skip = \meta{\TTF} \}
%     \tn{xeCJKsetup} \{ underline = \{ skip = \meta{\TTF} , ... \} \}
%   \end{syntax}
%   默认情况下，下划线会自动跳过中文标点符号，可以设置本选项为 \texttt{false}，
%   禁用这一功能。相应下划线命令后加上 |*| 号，具有相同的效果。
% \end{function}
%
% \begin{function}{subtract}
%   设置本选项为 \texttt{true}，使得下划线的首尾减少一定距离，避免前后的下划线
%   连在一起，适用于古籍标点整理中的专名号和书名号。在相应下划线命令后加上~|-|
%   号，具有相同的效果。
% \end{function}
%
% \begin{function}{hidden}
%   设置本选项为 \texttt{true}，将隐藏文本内容，只画下划线。
% \end{function}
%
% \begin{function}{format}
%   \begin{syntax}
%     \tn{xeCJKsetup} \{ underline/format = \tn{color}\{red\} \}
%     \tn{xeCJKsetup} \{ underwave = \{ format = \tn{color}\{red\}, ... \} \}
%   \end{syntax}
%   设置线或点的格式，比如颜色。
% \end{function}
%
% \begin{function}[added=2016-06-03]{textformat}
%   设置下划线或点的正文的格式。例如：\smallskip
%   \begin{Example}[frame=single,numbers=left,gobble=6]
%     \CJKunderline[textformat=\color{blue}]{虚室生白，吉祥止止}\\
%     \CJKunderdot[textformat=\bfseries, format=\color{red}]{虚室生白，吉祥止止}
%   \end{Example}
% \end{function}
%
% \begin{function}{symbol}
%   设置 \tn{CJKunderwave} 或 \tn{CJKunderdot} 的符号。
% \end{function}
%
% 默认波浪线由 \pkg{l3draw} 绘制，每个全角字符约容纳四个完整周期，符号的宽度、
% 振幅和粗细都会随字号缩放。默认斜删除线也使用相同比例的矢量图案，不再依赖
% 数学字体。两种默认图案都用普通 \tn{leaders} 排列，使同一行中被正文片段和
% 中文字间距分开的图案保持相同相位。命令后的减号会让装饰在正文左右各内缩半个
% 周期，因此相邻命令之间会留下一个完整周期的断口。也可以用本选项换成其他波浪
% 符号。例如，下面改用 \texttt{lasy} 字体中的波浪符号：
%
% \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=4]
%   % \usepackage{fix-cm}
%   \xeCJKsetup{%
%     underwave/symbol=
%       \fontsize{0.5em}{0pt}%
%       \fontencoding{U}\fontfamily{lasy}\selectfont
%       \char 58\relax}
%   \footnotesize
%   \CJKunderwave{瞻彼阕者，虚室生白，吉祥止止}
% \end{SideBySideExample}
%
% \begin{function}{thickness}
%   设置 \tn{CJKunderline}、\tn{CJKunderdblline} 和 \tn{CJKsout} 的线的厚度。
%   初始值是 \tn{ULthickness}。
% \end{function}
%
% \begin{function}{depth}
%   设置线或点的深度（基线到线或点的顶部的距离）。初始值都是 \texttt{0.2em}。
% \end{function}
%
% \begin{function}{boxdepth}
%   \tn{CJKunderdot} 可能会影响到行距，可以设置本选项进行调整。如果不希望
%   \tn{CJKunderdot} 影响行距，可以将本选项设置为 \texttt{0pt}。
% \end{function}
%
% \begin{function}{sep}
%   设置 \tn{CJKunderdot} 与 \tn{CJKunderline}、\tn{CJKunderdblline} 或
%   \tn{CJKunderwave} 嵌套使用时，点与线或者线与点的距离。
% \end{function}
%
% \begin{function}{gap}
%   设置 \tn{CJKunderdblline} 的两条线之间的距离。初始值是 \texttt{1.1pt}。
% \end{function}
%
% \begin{function}{height}
%   设置删除线 \tn{CJKsout} 的高度（线的中心到基线的距离）。初始值是 \texttt{0.35em}。
% \end{function}
% 例如，我们可以设置 \tn{CJKsout} 的厚度和颜色，让它具有类似高亮的效果：\smallskip
%
% \begin{Example}[frame=single,numbers=left]
%   \CJKsout*[thickness=2.5ex, format=\color{yellow}]{瞻彼阕者，虚室生白，吉祥止止}
% \end{Example}
%
% \medskip
%
% 此外还有允许自定义下划线和符号的 \tn{CJKunderanyline} 和 \tn{CJKunderanysymbol}。
%
% \begin{function}[updated=2014-11-07]{\CJKunderanyline}
%   \begin{syntax}
%     \tn{CJKunderanyline} [*] [-] \oarg{选项} \Arg{深度} \Arg{下划内容} \Arg{文本内容}
%   \end{syntax}
%   \pkg{xeCJKfntef} 先将 \meta{下划内容} 放进一个盒子（\tn{xeCJKfntefbox}）里，然后
%   向下移动 \meta{深度} 给定的距离，再用于填充。可用的 \meta{选项} 是 \texttt{textformat}、
%   \texttt{skip}、\texttt{hidden}、\texttt{subtract}、\texttt{sep} 和
%   \texttt{boxdepth}。选项 \texttt{sep} 和 \texttt{boxdepth} 的初始值是空，表示
%   禁用该选项的功能。可以在 \tn{xeCJKsetup} 中通过对象 \texttt{ulem} 来设置。
% \end{function}
%
% 例如，高亮效果也可以如下实现：\smallskip
%
% \begin{Example}[frame=single,numbers=left,gobble=4]
%   \CJKunderanyline*{0.5ex}{\color{yellow}\rule{1.5pt}{2.5ex}}{虚室生白，吉祥止止}
% \end{Example}
%
% \begin{function}[updated=2014-11-04]{\CJKunderanysymbol}
%   \begin{syntax}
%     \tn{CJKunderanysymbol} \oarg{选项} \Arg{深度} \Arg{符号} \Arg{文本内容}
%   \end{syntax}
%   \pkg{xeCJKfntef} 将 \meta{符号} 放进一个盒子（\tn{xeCJKfntefbox}）里。
%   \meta{深度} 参数用于设置盒子顶部的深度（基线到盒子顶部的距离）。
%   可用的 \meta{选项} 是 \texttt{textformat}、\texttt{sep} 和 \texttt{boxdepth}，意义与
%   \tn{CJKunderdot} 的相同。
% \end{function}
%
% 例如，给汉字加三角形，可以如下设置：\smallskip
%
% \begin{Example}[frame=single,numbers=left,gobble=4]
%   \CJKunderanysymbol[sep=0.1em]{0.2em}{\tiny$\triangle$}
%     {瞻彼阕者，虚室生白，\CJKunderline{吉祥止止}}
% \end{Example}
%
% \begin{function}[updated=2014-11-07]{\xeCJKfntefon}
%   \begin{syntax}
%     \tn{xeCJKfntefon} [*] [-] \oarg{选项}
%   \end{syntax}
%   功能与用法 \pkg{ulem} 宏包的 \tn{ULon} 相同，扩展了可选参数符号~\mdcode*{*} 和~\mdcode*{-}，
%   可用的 \meta{选项} 是 \texttt{textformat}、 \texttt{skip}、\texttt{hidden} 和
%   \texttt{subtract}。 这四个选项对 \pkg{ulem} 宏包定义的 \tn{uline} 等命令也有效，需要在
%   \tn{xeCJKsetup} 中通过对象 \texttt{ulem} 来设置。例如\smallskip
%
%   \begin{Example}[frame=single,numbers=left,gobble=6]
%     \xeCJKsetup{ulem={textformat=\bfseries\color{red}, skip=true}}
%     \uline{虚室生白，吉祥止止}
%   \end{Example}
% \end{function}
%
% \medskip
%
% \pkg{xeCJKfntef} 的下划线和删除线命令内部使用 \package{ulem} 宏包的
% \tn{hbox} 机制排版文字。举例来说，|汉字 \CJKsout{文字} 汉字| 中
% \tn{CJKsout} 前后的源码空格会被 \pkg{xeCJK} 自动处理为 CJK 文字间距
% （\texttt{CJKglue}）；间距方面的效果与 |汉字 文字 汉字| 一致。
% 关于 \pkg{xeCJKfntef} 命令与颜色命令的嵌套组合等更多细节，
% 请参见“已知问题和兼容性”一节中关于“CJK 文字与命令交互时的间距”的说明。
%
% \medskip
%
% \subsubsection{嵌套使用时的断行}
% \label{subsubsec:fntef-nest-linebreak}
%
% 按装饰的绘制方式，\pkg{xeCJKfntef} 的命令可以分成两类，它们在嵌套时的断行
% 能力不同：
%
% \begin{itemize}
%   \item 线型命令：\tn{CJKunderline}、\tn{CJKunderdblline}、\tn{CJKunderwave}、
%     \tn{CJKsout}、\tn{CJKxout} 和 \tn{CJKunderanyline}。它们借助 \pkg{ulem}
%     扫描正文并沿基线画出连续的线条。
%   \item 符号型命令：\tn{CJKunderdot} 和 \tn{CJKunderanysymbol}。它们逐字放置
%     独立的点或符号，不需要 \pkg{ulem} 的扫描过程。
% \end{itemize}
%
% 单独使用时，以上命令都可以正常断行。符号型命令与任何命令嵌套，也都保持正常
% 断行。
%
% 但是\emph{两个线型命令相互嵌套时，被嵌套的那一段无法断行}。原因是 \pkg{ulem}
% 只允许最外层的线型命令启动扫描：内层命令会复用外层已经打开的扫描过程，它的
% 正文被整段放进一个固定宽度的盒子，盒子内部不再留有断点，因此 \TeX{} 只能让
% 这一段溢出到页面右边，并给出 \texttt{Overfull \tn{hbox}} 警告。嵌套的先后
% 顺序不影响这一结果，原生的 \tn{uline} 等 \pkg{ulem} 命令与本宏包的线型命令
% 相互嵌套时同样如此。
%
% 这是 \pkg{ulem} 的扫描机制决定的，\pkg{xeCJKfntef} 目前没有绕开它的办法。
% 需要在较长的正文上同时得到两种装饰时，可以改用下面的写法：
%
% \begin{itemize}
%   \item 其中一种装饰改用符号型命令，例如把内层的 \tn{CJKunderwave} 换成
%     \tn{CJKunderdot}；
%   \item 按语义把正文分成若干段，每段只用一个线型命令，段与段之间保留断行
%     位置；
%   \item 只在确实不需要断行的短语上使用嵌套，例如标题或表格单元内的少量文字。
% \end{itemize}
%
% \medskip
%
% 此外，\pkg{xeCJKfntef} 还提供了可指定宽度，并让汉字两端分散对齐的环境
% \env{CJKfilltwosides} 和 \env{CJKfilltwosides*}。
%
% \begin{function}[updated=2014-11-04]{CJKfilltwosides}
%   \begin{syntax}
%     \tn{begin}\{CJKfilltwosides\} \oarg{位置} \Arg{宽度}
%       文本内容\verb=\\=
%       文本内容
%     \tn{end}\{CJKfilltwosides\}
%   \end{syntax}
%   环境中的内容被放入垂直盒子中，可选参数 \meta{位置} 指定盒子的基线位置。
%   可以使用 \texttt{t}（顶部）、\texttt{c}（居中）和 \texttt{b}（底部），默认是 \texttt{c}。
%   \meta{宽度} 参数指定盒子的宽度。
%   \env{CJKfilltwosides*} 环境与 \env{CJKfilltwosides} 的区别是，当 \meta{宽度}
%   不大于零或者不大于盒子的自然宽度时，就取盒子的自然宽度。例如
% \end{function}
%
% \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.5\linewidth,gobble=4]
%   \begin{CJKfilltwosides}{.8\linewidth}
%     瞻彼阕者，\\
%     虚室生白，吉祥止止
%   \end{CJKfilltwosides}
% \end{SideBySideExample}
%
% \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.5\linewidth,gobble=4]
%   \begin{CJKfilltwosides*}{0pt}
%     瞻彼阕者，\\
%     虚室生白，吉祥止止
%   \end{CJKfilltwosides*}
% \end{SideBySideExample}
%
% \subsection{其他}
% \label{subsec:others}
%
% \begin{function}[updated=2013-11-16]{\xeCJKVerbAddon,\xeCJKOffVerbAddon}
%   调整文字间距以便于让 CJK 字符占的宽度等于西文等宽字体中两个空格的宽度。如果这两
%   个空格的宽度小于当前 CJK 正常文字的宽度，将对 CJK 字体进行适当地缩小。这有利于
%   等宽字体的代码对齐等情形。需要注意的是，\tn{xeCJKVerbAddon} 对 \pkg{xeCJK} 的内
%   部进行了比较大的修改，使用它之后，将禁止在 CJK 字符类之间自动换行，这与西文在
%   抄录环境中的情况是一致的。所以不应该单独使用，应该放在分组里限制其作用域，否则
%   是无效的。当然它可以和其他关于代码抄录的宏包配合使用。例如，可以应用在
%   \package{fancyvrb} 宏包的 \texttt{formatcom} 选项。此时设置的西文字体应该确实
%   是等宽的以保证对齐。若西文等宽字体发生变动（包括字体大小），则需要在其后面使用
%   \tn{xeCJKVerbAddon}，重新计算间距的宽度。\tn{xeCJKOffVerbAddon}
%   用于在使用 \tn{xeCJKVerbAddon} 的环境中局部取消它的作用。由于 \package{listings}
%   宏包有自己的代码对齐机制，所以 \tn{xeCJKVerbAddon} 在由 \pkg{listings} 定义的
%   代码环境中无效。
% \end{function}
%
% \begin{function}[added=2012-12-03]{\xeCJKnobreak}
%   \begin{syntax}
%     ……汉字。\tn{xeCJKnobreak}\tn{footnote}\{脚注\}
%   \end{syntax}
%   \tn{xeCJKnobreak} 用在全角标点符号后面，目的是确保不能在此处断行。如果已经启用了
%   前面介绍的 |CheckFullRight| 选项，则不需要再用此命令。
% \end{function}
%
% \begin{function}[added=2013-11-09]{\xeCJKShipoutHook}
%   \pkg{xeCJK} 在正文中的一些特殊设置（汉字下加点、在 \env{verbatim} 或
%   \env{lstlisting} 环境中分页）可能会影响到 \TeX 的输出例行程序（output routine）
%   中的内容（比如页眉和页脚）。\tn{xeCJKShipoutHook} 用于恢复正文中的普通设置。
%   \pkg{xeCJK} 已经处理了页眉和页脚的情况，其他的就需要根据情况自行调用。
%   比如，若使用 \pkg{eso-pic} 或者 \pkg{atbegshi} 实现文字水印，并且正文中使用了
%   以上所列的特殊形式，就需要在命令 \tn{AtBeginShipout} 的参数的最前面使用
%   \tn{xeCJKShipoutHook}。
% \end{function}
%
% \section{已知问题和兼容性}
%
% 根据 \pkg{unicode-data} 宏包，\XeTeX 将所有 CJK 表意文字的 \tn{catcode}
% 设置为 $11$。因此汉字可以直接用作控制序列的名字，但是当汉字出现在控制序列后面的
% 时候，要用空格分隔开，否则就会出现“\texttt{! Undefined control sequence.}”的错误。
%
% \pkg{xeCJK} 使用并重新定义了 \pkg{CJK} 宏包的部分宏命令，如 \tn{CJKfamily}、
% \tn{CJKsymbol} 和 \tn{CJKglue} 等。需要指出，\pkg{xeCJK} 不需要 \pkg{CJK}
% 的支持，并且 \pkg{xeCJK} 自动禁止在它之后载入 \pkg{CJK} 宏包。
% 可以在 \pkg{xeCJK} 之\emph{后}载入 \pkg{CJKnumb} 宏包，实现数字的中文化，
% 也可以用功能更完善的 \package{zhnumber} 宏包。
%
% \pkg{xeCJK} 进行了一些处理，使得在使用 \XeTeX 时 \package{listings} 宏包可以
% 支持 Unicode，因此在 \texttt{listings} 定义的代码环境中可以直接使用中文，不再
% 需要使用 \texttt{escapechar}。
%
% 新版本（\texttt{3.x}）的 \pkg{xeCJK} 完全使用 \LaTeXiii{} 的语法来编写。
% \LaTeXiii{} 放弃了 \tn{outer} 宏的概念，因此相关工具在
% 遇到 \tn{outer} 宏时可能会存在问题。按照目前 \pkg{xeCJK} 的实现方式，在 CJK 文字
% 后面遇到 \tn{outer} 宏时会出现类似
% \begin{frameverb}
%   ! Forbidden control sequence found while scanning use of \use_i:nn
% \end{frameverb}
% 的错误。目前已知的有 \package{cprotect} 宏包提供的 \tn{cprotect}。它的定义是
% \begin{frameverb}
%   \outer\long\def\cprotect{\icprotect}
% \end{frameverb}
% 因此，这时可以暂时用 \tn{icprotect} 代替 \tn{cprotect}。事实上，当 \pkg{cprotect}
% 被引入时，\pkg{xeCJK} 将使用
% \begin{frameverb}
%   \let\cprotect\icprotect
% \end{frameverb}
% 来取消 \tn{cprotect} 的外部宏限制。但由于 \tn{cprotect} 的特殊性，应该只在外部
% 使用它，即不要让它出现在任何宏的参数中。其他 \tn{outer} 宏的情况，可以在它前面
% 加上 \tn{relax} 来回避上面的错误。
%
% \pkg{xeCJK} 依赖 \XeTeX 的 \tn{XeTeXinterchartoks} 机制，与使用相同机制的宏包（例如
% \pkg{polyglossia} 和 \pkg{xesearch}）可能会存在不同程度的冲突。
% \pkg{xeCJK} 虽然为此作了一些处理，但与它们共同使用时应该小心。
%
% \subsection{CJK 文字与命令交互时的间距}
%
% \pkg{xeCJK} 利用 \XeTeX 的 interchar 机制，在 CJK 文字之间自动插入
% |CJKglue|，在 CJK 文字与西文之间自动插入 |CJKecglue|，并自动忽略
% CJK 文字间的源码空格。当 CJK 文字被 \LaTeX 命令包裹时，命令引入的
% 分组、盒子或特殊节点可能干扰这一机制。\pkg{xeCJK} 的预期行为是：
% \begin{frameverb}
%   前 中 后            % 直接排版
%   前 \foo{中} 后      % 用命令包裹
% \end{frameverb}
% 两种写法应产生相同的 CJK 间距。
%
% 目前 \pkg{xeCJK} 已确保以下命令在此场景下的间距行为正确：
% \begin{itemize}
%   \item 字体切换命令：\tn{textbf}、\tn{textit}、\tn{emph} 等。
%     这些命令仅切换字体，不改变正文的边界类别。
%   \item 颜色命令：\tn{textcolor}、\tn{colorbox} 等
%     （包括 \LaTeXe{} 和 \LaTeXiii{} 的颜色后端）。
%   \item \pkg{xeCJKfntef} 命令：\tn{CJKunderline}、\tn{CJKunderdblline}、
%     \tn{CJKunderwave}、\tn{CJKsout}、\tn{CJKxout}、\tn{CJKunderdot}。
%   \item 盒子命令：\tn{mbox}、\tn{fbox}、\tn{makebox}、\tn{framebox} 等。
%   \item \pkg{hyperref} 的链接注释：\tn{ref}、\tn{hyperref} 等。
%   \item 以上命令的嵌套组合，例如：
% \begin{frameverb}
%   前 \CJKunderline{\textcolor{red}{中}} 后
%   前 \textcolor{red}{\CJKunderline{中}} 后
% \end{frameverb}
% \end{itemize}
%
% 行内公式也按相同原则处理。\verb*-$x$-、\tn{(}\texttt{x}\tn{)} 和
% \tn{ensuremath}\verb*-{x}- 的边界与直接写出的行内公式一致；上述命令
% 包裹公式时，不应把公式误当成普通西文。公式旁有源码空格时，
% \opt{xCJKecglue=false} 保留普通词间空格，\opt{xCJKecglue=true} 则将其
% 替换为 \opt{CJKecglue}。
%
% 本节只讨论 CJK 间距，不涉及断行。\pkg{xeCJKfntef} 的线型命令相互嵌套时，
% 间距仍然正确，但被嵌套的那一段无法断行，
% 详见 \ref{subsubsec:fntef-nest-linebreak}~节。
%
% 上述命令共用一套边界记录和间距恢复机制，但每类命令仍须提供明确的
% 开始和结束位置，不能自动识别任意宏的内部节点。
% 未列出的第三方命令如果在 CJK 文字间引入特殊的分组或节点结构，
% 仍可能产生多余或缺失的间距。
% 如果遇到间距异常，欢迎到
% \href{https://github.com/CTeX-org/ctex-kit/issues}{GitHub Issues}
% 提交兼容请求。
%
% \subsection{\tn{char} 原语与 interchar 的冲突}
%
% \XeTeX 的 interchar 机制工作在 token 层面，无法区分
% \tn{char} 原语的输出和直接字符输入。因此当第三方宏包在内部使用 \tn{char}
% 从非 CJK 字体取字形时，如果该字符编码恰好属于 \pkg{xeCJK} 的
% interchar 字符分类，就会被拦截并切换到 CJK 字体，产生错误的字形。
% 例如 \pkg{mtpro2} 的~|\overcbrace| 通过~|\char183| 取
% 花括号字形，而 |U+00B7|（MIDDLE DOT）在 \pkg{xeCJK} 中属于
% |FullRight| 类，导致花括号变成中文间隔号。
%
% 为此，\pkg{xeCJK} 提供 \tn{xeCJKchar} 命令，用法与 \tn{char} 完全
% 相同，但在输出字符前临时关闭 |XeTeXinterchartokenstate|。
% 用户可以使用~|\xeCJKchar"00B7| 代替~|\char"00B7|
% 来避免 interchar 拦截。
%
% \pkg{xeCJK} 已为以下宏包的命令提供了自动兼容补丁：
% \begin{itemize}
%   \item \pkg{mtpro2}：|\overcbrace|、|\undercbrace|
%   \item \pkg{pifont}：|\Pifont|（|U| 编码字体切换）
% \end{itemize}
% 如果遇到其他宏包的类似问题，可以在导言区手动包装有冲突的命令：
% \begin{frameverb}
%   % 方法一：直接用 \xeCJKchar 替换 \char
%   \hbox{\myfont\xeCJKchar183}
%
%   % 方法二：包装第三方命令，用 \makexeCJKinactive 关闭 interchar
%   \let\origProblemCmd\problemCmd
%   \renewcommand\problemCmd[1]{\makexeCJKinactive \origProblemCmd{#1}}
% \end{frameverb}
% |\makexeCJKinactive| 是赋值操作，受 \TeX{} 分组约束。这即是说，
% 当用户在 |{...}|、|$...$| 或~|\begingroup...\endgroup|
% 内使用时，结束后自动恢复。
%
% \subsection{\pkg{hyperref} 链接与 CJKecglue}
%
% \pkg{hyperref} 的链接注释命令（如 \tn{ref}、\tn{hyperref} 等）
% 在内部会产生 |pdf:bann| / |pdf:eann| 特殊节点。
% \pkg{xeCJK} 对此做了兼容处理，确保链接注释不影响正常的
% CJKecglue 插入。例如，以下写法的排版效果与不加 \tn{ref} 时等价：
% \begin{frameverb}
%   图\ref{fig}图   % 等价于"图1图"
% \end{frameverb}
% 无论是否加载 \pkg{hyperref}，\tn{ref} 前后都会自动插入
% 适当的 CJKecglue。
%
% \end{documentation}
%
%
% \StopEventually{}
%
%
% \begin{implementation}
%
% \section{\pkg{xeCJK} 代码实现}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=xeCJK>
%    \end{macrocode}
%
% \subsection{运行环境检查}
%
% \pkg{xeCJK} 必须使用 \XeTeX 引擎的支持。
%    \begin{macrocode}
\msg_new:nnn { xeCJK } { Require-XeTeX }
  {
    The~xeCJK~package~requires~XeTeX~to~function.\\\\
    You~must~change~your~typesetting~engine~to~"xelatex" \\
    instead~of~plain~"latex"~or~"pdflatex"~or~"lualatex".\\
    Loading~xeCJK~will~abort!
  }
\sys_if_engine_xetex:F { \msg_critical:nn { xeCJK } { Require-XeTeX } }
%    \end{macrocode}
%
% 应该使用较新版本的 \pkg{expl3} 宏包。
% \changes{v3.10.0}{2026/05/03}{提升 \LaTeXiii{} 最低版本要求至 2025/10/09。}
%    \begin{macrocode}
\msg_new:nnn { xeCJK } { l3-too-old }
  {
    Support~package~`#1'~too~old. \\\\
    Please~update~an~up~to~date~version~of~the~bundles\\\\
    `l3kernel'~and~`l3packages'\\\\
    using~your~TeX~package~manager~or~from~CTAN.\\
    \str_if_eq:nnT {#1} { expl3 } { Loading~xeCJK~will~abort! }
  }
\@ifpackagelater { expl3 } { 2025/10/09 } { }
  { \msg_critical:nnn { xeCJK } { l3-too-old } { expl3 } }
%    \end{macrocode}
%
% \cs{ctex_disable_package:n} 由 \pkg{ctexhook} 提供，
% \cs{ctex_patch_cmd:Nnn} 由 \pkg{ctexpatch} 提供。
%    \begin{macrocode}
\RequirePackage { ctexhook }
\RequirePackage { ctexpatch }
%    \end{macrocode}
%
% \begin{macro}[pTF,int]{\xeCJK_if_package_loaded:n}
% 判断宏包是否被引入，可用于文档正文中。
%    \begin{macrocode}
\prg_new_conditional:Npnn \xeCJK_if_package_loaded:n #1 { p , T , F , TF }
  {
    \tl_if_exist:cTF { ver@ #1 . \c_@@_package_ext_tl }
      { \prg_return_true: } { \prg_return_false: }
  }
\tl_const:Ne \c_@@_package_ext_tl { \@pkgextension }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.7}{2021/06/09}{应用 \tn{disable@package@load} 和
%   \tn{declare@file@substitution}。}
%
% 下面这些 \pkg{CJK} 系列宏包不应该被使用。
%    \begin{macrocode}
\msg_new:nnn { xeCJK } { after-package }
  {
    The~`#1'~package~and~xeCJK~are~incompatible.\\\\
    Please~load~it~after~xeCJK.
  }
\clist_map_inline:nn { CJKnumb }
  {
    \xeCJK_if_package_loaded:nT {#1}
      { \msg_error:nnn { xeCJK } { after-package } {#1} }
  }
\clist_map_inline:nn
  { CJKulem , CJKvert , CJKpunct , CJKutf8 , CJK }
  { \ctex_disable_package:n {#1} }
%    \end{macrocode}
%
% \changes{v3.8.7}{2021/06/09}{将 \pkg{CJKfntef} 包替换为 \pkg{xeCJKfntef} 包。}
%
% 将 \pkg{CJKfntef} 包替换为 \pkg{xeCJKfntef} 包。
%    \begin{macrocode}
\ctex_if_format_at_least:nTF { 2020/10/01 }
  { \ctex_replace_package:nn { CJKfntef } { xeCJKfntef } }
  { \ctex_disable_package:n { CJKfntef } }
%    \end{macrocode}
%
% \changes{v3.9.0}{2022/07/08}{不直接依赖 \pkg{xparse} 和 \pkg{l3keys2e}。}
%
%    \begin{macrocode}
\cs_if_exist:NF \NewDocumentCommand
  { \RequirePackage { xparse } }
\RequirePackage { xtemplate }
%    \end{macrocode}
%
% \subsection{内部工具}
%
% 分配临时变量。
%    \begin{macrocode}
\tl_new:N \l_@@_tmp_tl
\tl_new:N \l_@@_tmpb_tl
\int_new:N \l_@@_tmp_int
\box_new:N \l_@@_tmp_box
\dim_new:N \l_@@_tmp_dim
\bool_new:N \l_@@_tmp_bool
\skip_new:N \l_@@_tmp_skip
\clist_new:N \l_@@_tmp_clist
%    \end{macrocode}
%
% \begin{macro}
%  {\@@_msg_new:nn ,\@@_error:n,\@@_error:ne,\@@_warning:ne,\@@_info:nee}
% 各种信息函数的缩略形式。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_msg_new:nn   { \msg_new:nnn       { xeCJK } }
\cs_new_protected:Npn \@@_msg_new:nnn  { \msg_new:nnnn      { xeCJK } }
\cs_new_protected:Npn \@@_error:n      { \msg_error:nn      { xeCJK } }
\cs_new_protected:Npn \@@_error:ne     { \msg_error:nne     { xeCJK } }
\cs_new_protected:Npn \@@_warning:n    { \msg_warning:nn    { xeCJK } }
\cs_new_protected:Npn \@@_warning:ne   { \msg_warning:nne   { xeCJK } }
\cs_new_protected:Npn \@@_warning:nee  { \msg_warning:nnee  { xeCJK } }
\cs_new_protected:Npn \@@_warning:neee { \msg_warning:nneee { xeCJK } }
\cs_new_protected:Npn \@@_info:nee     { \msg_info:nnee     { xeCJK } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_allow_break:,\xeCJK_no_break:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_allow_break:
  { \tex_penalty:D \c_zero_int }
\cs_new_protected:Npn \xeCJK_no_break:
  { \tex_penalty:D \c_@@_nobreak_penalty_int }
\int_const:Nn \c_@@_nobreak_penalty_int { 10 000 }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.3}{2020/04/07}{依赖 \pkg{ctexhook} 宏包。}
%
% \begin{macro}
%  {\@@_at_end_preamble:n,\@@_after_preamble:n,
%   \@@_after_end_preamble:n,\@@_package_hook:nn}
%  在 \tn{document} 前后和宏包后加上各种钩子，依赖 \pkg{ctexhook}。
%    \begin{macrocode}
\AtBeginDocument           { \xeCJK@document@hook }
\ctex_at_end_preamble:n    { \xeCJK@document@left@hook }
\ctex_after_end_preamble:n { \xeCJK@document@right@hook }
\cs_new_protected:Npn \xeCJK@document@hook
  { \tl_use:N \g_@@_after_preamble_hook_tl }
\cs_new_protected:Npn \xeCJK@document@left@hook
  { \tl_use:N \g_@@_at_end_preamble_hook_tl }
\cs_new_protected:Npn \xeCJK@document@right@hook
  { \tl_use:N \g_@@_after_end_preamble_hook_tl }
\cs_new_protected:Npn \@@_at_end_preamble:n
  { \tl_gput_right:Nn \g_@@_at_end_preamble_hook_tl }
\cs_new_protected:Npn \@@_after_preamble:n
  { \tl_gput_right:Nn \g_@@_after_preamble_hook_tl }
\cs_new_protected:Npn \@@_after_end_preamble:n
  { \tl_gput_right:Nn \g_@@_after_end_preamble_hook_tl }
\cs_new_protected:Npn \@@_package_hook:nn
  { \ctex_at_end_package:nn }
\tl_new:N \g_@@_at_end_preamble_hook_tl
\tl_new:N \g_@@_after_preamble_hook_tl
\tl_new:N \g_@@_after_end_preamble_hook_tl
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.7}{2013/11/09}{使用 \pkg{everypage} 往 \tn{shipout} 盒子里加钩子。}
% \changes{v3.2.16}{2014/12/16}{不再依赖 \pkg{everypage} 宏包。}
%
% \begin{macro}{\xeCJKShipoutHook}
% 在 \tn{shipout} 盒子里加钩子，可以影响到页眉页脚。\tn{AtBeginDvi} 将参数保存在
% 盒子中，而 \pkg{atbegshi} 的 \tn{AtBeginShipout} 在 \tn{shipout} 盒子构建好之后
% 才起作用，所以它们都影响不到页眉页脚。我们通过往 \tn{@begindvi} 里加入钩子来完成。
% 注意，第一次使用 \tn{@begindvi} 之后，它会将自身定义为 \tn{@empty}。
%    \begin{macrocode}
\@@_after_preamble:n
  { \tl_put_right:Nn \@begindvi { \xeCJK@first@begindvi } }
\cs_new_protected:Npn \xeCJK@first@begindvi
  {
    \xeCJKShipoutHook
    \cs_if_exist:NTF \@begindvi
      { \tl_gput_right:Nn }
      { \tl_const:Nn }
    \@begindvi { \xeCJKShipoutHook }
  }
\NewDocumentCommand \xeCJKShipoutHook { }
  {
    \bool_if:NF \l_@@_shipout_hook_bool
      {
        \bool_set_true:N \l_@@_shipout_hook_bool
        \tl_use:N \l_@@_shipout_hook_tl
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_add_to_shipout:n}
% \changes{v3.2.11}{2014/03/14}{不再使用内部名字。}
% 往 \tn{shipout} 盒子中加入钩子。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_add_to_shipout:n
  { \tl_put_right:Nn \l_@@_shipout_hook_tl }
\tl_new:N \l_@@_shipout_hook_tl
\bool_new:N \l_@@_shipout_hook_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_tl_remove_outer_braces:n}
% \changes{v3.2.4}{2013/07/02}{去掉外层分组括号时，移除空格，避免死循环。}
% 去掉 |#1| 外层的分组括号。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_tl_remove_outer_braces:n #1
  {
    \exp_last_unbraced:Ne
    \@@_tl_remove_outer_braces:w { \tl_trim_spaces:n {#1} } \s_stop
  }
\cs_new:Npn \@@_tl_remove_outer_braces:w #1 \s_stop
  {
    \tl_if_single:nTF {#1}
      {
        \tl_if_head_is_N_type:nTF {#1}
          { \tl_trim_spaces:n }
          { \xeCJK_tl_remove_outer_braces:n }
      }
      { \tl_trim_spaces:n }
      {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_cs_clear:N,\xeCJK_cs_gclear:N}
% 让控制序列的意义为空。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_cs_clear:N #1
  { \cs_set_eq:NN #1 \prg_do_nothing: }
\cs_new_protected:Npn \xeCJK_cs_gclear:N #1
  { \cs_gset_eq:NN #1 \prg_do_nothing: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_swap_cs:NN}
% 交换 |#1| 和 |#2| 的意义。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_swap_cs:NN #1#2
  {
    \cs_set_eq:NN \@@_swap_cs_aux:w #1
    \cs_set_eq:NN #1 #2
    \cs_set_eq:NN #2 \@@_swap_cs_aux:w
    \cs_undefine:N \@@_swap_cs_aux:w
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_font_gset_to_current:N}
% \changes{v3.8.4}{2020/06/04}{不缓存 \tn{nullfont}。}
% |#1| 是控制序列的名字，令它等于当前字体命令。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_font_gset_to_current:N
  { \exp_after:wN \@@_font_gset_to_current_aux:NN \tex_the:D \tex_font:D }
\cs_new_protected:Npn \@@_font_gset_to_current_aux:NN #1#2
  { \cs_if_eq:NNF #1 \tex_nullfont:D { \cs_gset_eq:NN #2#1 } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int,pTF]{\xeCJK_glyph_if_exist:N}
% \changes{v3.1.0}{2012/11/19}{改进 \pkg{fontspec} 宏包中定义的
% \cs{font_glyph_if_exist:NnTF}。}
% 判断当前字体中是否含有字符 |#1|。\pkg{fontspec} 中的类似函数在判断为真的时候，
% 会留有一个 \cs{scan_stop:}，造成不必要的边界，同时也不完全可展。因此，我们重新
% 定义它。
%    \begin{macrocode}
\prg_new_conditional:Npnn \xeCJK_glyph_if_exist:N #1 { p , T , F , TF }
  {
    \tex_iffontchar:D \tex_font:D `#1 \exp_stop_f:
      \prg_return_true: \else: \prg_return_false: \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}[int]{\c_xeCJK_space_skip_tl}
% \changes{v3.1.0}{2012/11/18}{字间空格考虑 \tn{spaceskip} 不为零的情况。}
% \changes{v3.2.0}{2013/05/22}{字间空格考虑到 \tn{spacefactor} 和 \tn{xspaceskip} 的情况。}
% 当前字体状态下，一个字间空格产生的 |glue| 的长度，包括伸展和收缩部分。
%    \begin{macrocode}
\tl_const:Nn \c_xeCJK_space_skip_tl
  {
    \int_compare:nNnTF \g_@@_space_factor_int = { 1000 }
      {
        \skip_if_eq:nnTF \tex_spaceskip:D \c_zero_skip
          {
                    \tex_fontdimen:D 2 ~ \tex_font:D
              plus  \tex_fontdimen:D 3 ~ \tex_font:D
              minus \tex_fontdimen:D 4 ~ \tex_font:D
          }
          { \tex_spaceskip:D }
      }
      {
        \skip_if_eq:nnTF \tex_spaceskip:D \c_zero_skip
          {
            \int_compare:nNnTF \g_@@_space_factor_int < { 2000 }
              {
                \@@_space_skip_scale:nnn
                  { \tex_fontdimen:D 2 ~ \tex_font:D }
              }
              {
                \skip_if_eq:nnTF \tex_xspaceskip:D \c_zero_skip
                  {
                    \@@_space_skip_scale:nnn
                      {
                        \tex_fontdimen:D 2 ~ \tex_font:D +
                        \tex_fontdimen:D 7 ~ \tex_font:D
                      }
                  }
                  { \tex_xspaceskip:D  \use_none:nn }
              }
              { \tex_fontdimen:D 3 ~ \tex_font:D }
              { \tex_fontdimen:D 4 ~ \tex_font:D }
          }
          {
            \int_compare:nNnTF \g_@@_space_factor_int < { 2000 }
              { \@@_space_skip_scale:nnn { \tex_spaceskip:D } }
              {
                \skip_if_eq:nnTF \tex_xspaceskip:D \c_zero_skip
                  {
                    \@@_space_skip_scale:nnn
                      {
                        \tex_spaceskip:D +
                        \tex_fontdimen:D 7 ~ \tex_font:D
                      }
                  }
                  { \tex_xspaceskip:D \use_none:nn }
              }
              { \tex_gluestretch:D \tex_spaceskip:D }
              { \tex_glueshrink:D  \tex_spaceskip:D }
          }
      }
  }
\cs_new:Npn \@@_space_skip_scale:nnn #1#2#3
  {
    \dim_eval:n {#1}
    plus \fp_eval:n { \g_@@_space_factor_int / 1000 } #2
    minus
      \int_div_truncate:nn
        { 1000 * \int_value:w #3 } { \g_@@_space_factor_int } sp
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[int]{\xeCJK_reset_space_factor:}
% \begin{variable}[int]{\g_@@_space_factor_int}
% 在 \tn{XeTeXinterchartoks} 里，\tn{spacefactor} 已经被重置为 $1000$。
% 我们需要在 Default 类里保存 \tn{spacefactor} 用于计算空格宽度。
%    \begin{macrocode}
\int_new:N \g_@@_space_factor_int
\cs_new_protected:Npn \xeCJK_reset_space_factor:
  { \int_gset:Nn \g_@@_space_factor_int { 1000 } }
\xeCJK_reset_space_factor:
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_glue_to_skip:nN}
% 取得一个 |glue| 的长度，包括伸展和收缩部分。如果参数不是 |glue|，则取其宽度。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_glue_to_skip:nN #1#2
  {
    \group_begin:
      \hbox_set:Nw \l_@@_tmp_box #1 \scan_stop:
      \@@_if_last_glue:TF
        {
          \exp_args:NNNo \hbox_set_end:
          \skip_set:Nn #2 { \skip_use:N \tex_lastskip:D }
        }
        {
          \exp_args:NNNo \hbox_set_end:
          \skip_set:Nn #2 { \dim_use:N \box_wd:N \l_@@_tmp_box }
        }
    \exp_args:NNNo \group_end:
    \skip_set:Nn #2 { \skip_use:N #2 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_int_until_do:nn}
% \begin{macro}{\@@_int_until_do:wn}
% 由于定义较为简单，可以比 \cs{int_until_do:nNnn} 稍微快一点点。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_int_until_do:nn #1#2
  {
    \@@_int_until_do:wn \use_none:n
      { \reverse_if:N \if_int_compare:w #1#2 }
  }
\cs_new_protected:Npn \@@_int_until_do:wn \use_none:n #1
  { #1 \exp_after:wN \@@_int_until_do:wn \fi: \use_none:n {#1} }
\int_new:N \l_@@_begin_int
\int_new:N \l_@@_end_int
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_peek_catcode_ignore_spaces:NTF}
% \changes{v3.1.1}{2012/12/04}{新增有省略空格标识的 \texttt{peek} 函数。}
% 我们在里面设置了一个变量 \cs{l_@@_peek_ignore_spaces_bool} 用于标识后面的空格
% 是否被省略掉了。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_peek_catcode_ignore_spaces:NTF #1#2#3
  {
    \cs_set_eq:NN \l_@@_peek_search_token #1 \scan_stop:
    \cs_set_protected:Npe \@@_peek_catcode_true:w
      { \exp_not:N \group_align_safe_end: \exp_not:n {#2} }
    \cs_set_protected:Npe \@@_peek_catcode_false:w
      { \exp_not:N \group_align_safe_end: \exp_not:n {#3} }
    \bool_set_false:N \l_@@_peek_ignore_spaces_bool
    \group_align_safe_begin:
    \peek_after:Nw \@@_peek_catcode_ignore_spaces_branches:w
  }
\cs_new_protected:Npn \@@_peek_catcode_ignore_spaces_branches:w
  {
    \if_meaning:w \l_peek_token \c_space_token
      \bool_set_true:N \l_@@_peek_ignore_spaces_bool
      \exp_after:wN \peek_after:Nw
      \exp_after:wN \@@_peek_catcode_ignore_spaces_branches:w
      \tex_romannumeral:D 0
    \else:
      \if_catcode:w
        \exp_not:N \l_peek_token \exp_not:N \l_@@_peek_search_token
        \exp_after:wN \exp_after:wN
        \exp_after:wN \@@_peek_catcode_true:w
      \else:
        \exp_after:wN \exp_after:wN
        \exp_after:wN \@@_peek_catcode_false:w
      \fi:
    \fi:
  }
\cs_new_eq:NN \l_@@_peek_search_token ?
\cs_new_eq:NN \@@_peek_catcode_true:w  \prg_do_nothing:
\cs_new_eq:NN \@@_peek_catcode_false:w \prg_do_nothing:
\bool_new:N \l_@@_peek_ignore_spaces_bool
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.0}{2020/02/10}{应用 \cs{peek_remove_spaces:n}。}
%
% \begin{macro}[int]{\xeCJK_token_value_class:N}
% 用于取得记号 |#1| 所在的 \XeTeX 字符类。|#1| 应为 \tn{catcode} 为 |11| 或 |12|
% 的显性或隐性记号。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_token_value_class:N #1
  { \tex_XeTeXcharclass:D \xeCJK_token_value_charcode:N #1 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_token_value_charcode:N}
% \changes{v3.2.4}{2013/06/30}{考虑 \texttt{charcode} 超出 BMP 的情况。}
% \changes{v3.3.1}{2015/05/08}{\texttt{0.99992} 版修复了 \tn{meaning} 的 Bug。}
% 当记号 |#1| 的 \texttt{charcode} 大于等于 \texttt{0x10000} 时，\XeTeX\
% \texttt{0.9999.0} 版以前的 \tn{meaning} 的返回结果比较特殊\footnote{参见
% \url{http://tug.org/pipermail/xetex/2013-January/023967.html} 和
% \url{http://tex.stackexchange.com/a/64848}。}，需要特别处理。
% \texttt{0.9999.0} 版以后的 \XeTeX 的 \tn{meaning}
% 对于超出 BMP 的字符，会返回两个字符，分别对应于其 UTF-16 编码的首尾代理^^A
% \footnote{参见 \url{http://tug.org/pipermail/xetex/2013-June/024543.html}。}。
% 这一 Bug 在 TeX Live 2015 的 \texttt{0.99992} 版中得到修复^^A
% \footnote{参见\url{http://tug.org/pipermail/xetex/2015-May/025941.html}}。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_token_value_charcode:N #1
  { \exp_after:wN \@@_get_charcode:w \token_to_meaning:N #1 \q_stop }
\group_begin:
  \cs_set:Npn \@@_tmp:w #1 ~ #2 ~ #3#4#5 \q_stop
    {
      \tl_if_empty:nTF { #4#5 }
        {
          \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3 \q_stop
            { \int_eval:n { `##3 } }
        }
        {
          \tl_if_empty:nTF {#5}
            {
              \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3##4 \q_stop
                {
                  \int_eval:n
                    {
                      \tl_if_empty:nTF { ##4 }
                        { `##3 }
                        { ( `##3 - "D800 ) * "400 + ( `##4 - "DC00 ) + "10000 }
                    }
                }
            }
            {
              \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3##4 \q_stop
                { \int_eval:n { \tl_if_empty:nTF { ##4 } { `##3 } { "20000 } } }
            }
        }
    }
  \exp_after:wN \@@_tmp:w \token_to_meaning:N ^^^^^20000 { } \q_stop
\group_end:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF,int]{\xeCJK_if_CJK_class:N}
% 判断字符 |#1| 是否为 CJK 字符类，包括文字和标点符号。
%    \begin{macrocode}
\prg_new_conditional:Npnn \xeCJK_if_CJK_class:N #1 { p , T , F , TF }
  {
    \if_cs_exist:w
      \@@_CJK_class_tl:n { \xeCJK_token_value_class:N #1 }
    \cs_end:
      \prg_return_true: \else: \prg_return_false: \fi:
  }
\cs_new:Npn \@@_CJK_class_tl:n #1
  { c_@@_CJK_class_ \int_eval:n {#1} _tl }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF,int]{\xeCJK_if_same_class:NN}
% 判断两个字符是否同属于一个字符类。
%    \begin{macrocode}
\prg_new_conditional:Npnn \xeCJK_if_same_class:NN #1#2 { p , T , F , TF }
  {
    \if_int_compare:w \xeCJK_token_value_class:N #1 =
                      \xeCJK_token_value_class:N #2 \exp_stop_f:
      \prg_return_true: \else: \prg_return_false: \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_make_boundary:}
% 利用 \cs{scan_stop:} 结束 CJK 分组，用于恢复字体等。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_make_boundary:
  { \bool_if:NT \l_@@_CJK_group_bool { \scan_stop: } }
%    \end{macrocode}
% \end{macro}
%
% \subsection{功能开关}
%
% \begin{macro}{xeCJKactive}
% 事实上，将开启或关闭 \XeTeX 的整个字符类机制。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    xeCJKactive .choice: ,
    xeCJKactive / true  .code:n = { \makexeCJKactive   } ,
    xeCJKactive / false .code:n = { \makexeCJKinactive } ,
    xeCJKactive      .default:n = { true }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\makexeCJKactive, \makexeCJKinactive}
%    \begin{macrocode}
\NewDocumentCommand \makexeCJKactive   { }
  { \tex_XeTeXinterchartokenstate:D = \c_one_int }
\NewDocumentCommand \makexeCJKinactive { }
  { \tex_XeTeXinterchartokenstate:D = \c_zero_int }
%    \end{macrocode}
% \end{macro}
%
% 抑制 |BOM| 和零宽格式字符，避免触发错误的 inter-class toks 插入。
%    \begin{macrocode}
\char_set_catcode_ignore:n { "FEFF }
\char_set_catcode_ignore:n { "200B }
\char_set_catcode_ignore:n { "200C }
\char_set_catcode_ignore:n { "200D }
\char_set_catcode_ignore:n { "2060 }
%    \end{macrocode}
%
% \subsection{字符类别设定}\label{sec:xeCJK-class-set}
%
% \begin{variable}{\g_@@_class_seq,\g_@@_new_class_seq}
% 分别用于记录在 \pkg{xeCJK} 中使用的字符类别名称和新建的字符类别的编号。
%    \begin{macrocode}
\seq_new:N \g_@@_class_seq
\seq_new:N \g_@@_new_class_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[int]{\xeCJK_new_class:n}
% 新建一个字符类别。|#1| 是自定义名称。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_new_class:n #1
  {
    \int_if_exist:cTF { \@@_class_csname:n {#1} }
      { \@@_error:ne { class-already-defined } {#1} }
      {
        \exp_args:Nc \newXeTeXintercharclass
          { \@@_class_csname:n {#1} }
        \clist_new:c { g_@@_#1_range_clist }
        \seq_gput_right:Nn \g_@@_class_seq {#1}
        \seq_gput_right:Nv \g_@@_new_class_seq
          { \@@_class_csname:n {#1} }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_save_class:nn}
% \changes{v3.1.1}{2012/12/06}
% {使用 \cs{xeCJK_save_class:nn} 保存 \XeTeX 预定义的字符类别。}
% 保存 \XeTeX 预定义的字符类别。|#1| 是自定义名称，|#2| 是编号。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_save_class:nn #1#2
  {
    \int_if_exist:cTF { \@@_class_csname:n {#1} }
      { \@@_error:ne { class-already-defined } {#1} }
      {
        \int_const:cn { \@@_class_csname:n {#1} } {#2}
        \clist_new:c { g_@@_#1_range_clist }
        \seq_gput_right:Nn \g_@@_class_seq {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_class_csname:n}
% 字符类名称对应的控制序列名字。
%    \begin{macrocode}
\cs_new:Npn \@@_class_csname:n #1 { c_@@_#1_class_int }
\cs_new_eq:cN { \@@_class_csname:n { Others } } \l_@@_tmp_int
\@@_msg_new:nn { class-already-defined }
  {
    XeTeX~character~class~`#1'~has~been~already~defined.\\\\
    Please~take~another~name. \\
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.0}{2013/05/20}{增加 \texttt{IVS} 字符类用于处理异体字选择符。}
% \changes{v3.3.1}{2015/01/22}{\texttt{IVS} 字符类更名为 \texttt{CM}。}
%
% \pkg{xeCJK} 需要以下字符类别用于字符输出。其中 |Default|、|CJK|、|FullLeft|、
% |FullRight|、|Boundary| 为 \XeTeX\ 中预定义的类别，\pkg{xeCJK} 新增加了\
% |HalfLeft|、|HalfRight|、|NormalSpace| 和 |CM|。其中异体字选择符
% (Ideographic Variation Selectors)\footnote{\url{http://www.unicode.org/reports/tr37/}}
% 需要 \XeTeX\ |0.9999.0| 以上的版本^^A
% \footnote{\url{http://tug.org/pipermail/xetex/2013-March/024118.html}}和相关字体的支持。
% \begin{center}\xeCJKsetup{PunctStyle=hangmobanjiao}
% \begin{tabular}{cll}
% \toprule
%   类别        & 说明               & 例子 \\ \midrule
% |Default|     & 西文一般符号       & abc123 \\
% |CJK|         & CJK 表意符号       & 汉字ぁぃぅ \\
% |FullLeft|    & 全角左标点         & （《：“ \\
% |FullRight|   & 全角右标点         & ，。）》” \\
% |HalfLeft|    & 半角左标点         & ( [ \{ \\
% |HalfRight|   & 半角右标点         & , . ? ) ] \} \\
% |NormalSpace| & 前后原始间距的符号 & / \\
% |Boundary|    & 边界               & 空格 \\
% |CM|          & 组合标识           & 异体字选择符\\
% |HangulJamo|  & 朝鲜文字母（仅兼容旧代码，不再分配字符） & /\\
% |HangulJamoL| & 朝鲜文字母初声     & |ᄻ|\\
% |HangulJamoV| & 朝鲜文字母中声     & |ᆟ|\\
% |HangulJamoT| & 朝鲜文字母终声     & |ᇫ|\\
% |CJStarter|   & 日文条件行首禁则字符 & |ゃ||っ|\\
% |PoZheHao|    & 支持合字的破折号   & ——\\
% \bottomrule
% \end{tabular}
% \end{center}
%
% \changes{v3.3.3}{2016/01/20}{兼容 \LaTeXe{} 2016/02/01 的字符类设置。}
% \changes{v3.3.4}{2016/02/07}{兼容 \XeTeX{} 0.99994 的边界字符类。}
% \begin{macro}[int]{Default,CJK,FullLeft,FullRight,Boundary}
%    \begin{macrocode}
\xeCJK_save_class:nn { Default } { 0 }
%    \end{macrocode}
% \XeTeX{} 0.99994 将字符类总数扩大到 $4096$^^A
% \footnote{\url{http://tug.org/pipermail/xetex/2016-February/026363.html}}。
%    \begin{macrocode}
\str_const:Ne \c_@@_xetex_version_str
  { \int_use:N \tex_XeTeXversion:D \tex_XeTeXrevision:D }
\fp_compare:nNnTF { \c_@@_xetex_version_str } > { 0.99993 }
  { \xeCJK_save_class:nn { Boundary } { 4095 } }
  { \xeCJK_save_class:nn { Boundary } { 255 } }
%    \end{macrocode}
% \LaTeXe{} 2016/02/01 不再预设置 CJK 字符类。
%    \begin{macrocode}
\int_compare:nNnTF { \tex_XeTeXcharclass:D "4E00 } = \c_one_int
  {
    \xeCJK_save_class:nn { CJK }       { 1 }
    \xeCJK_save_class:nn { FullLeft }  { 2 }
    \xeCJK_save_class:nn { FullRight } { 3 }
    \int_const:Nn \c_@@_class_begin_int { 3 }
  }
  {
    \xeCJK_new_class:n { CJK }
    \xeCJK_new_class:n { FullLeft }
    \xeCJK_new_class:n { FullRight }
    \int_const:Nn \c_@@_class_begin_int { 0 }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.15}{2014/11/10}{增加 \texttt{HangulJamo} 字符类。}
% \changes{v3.10.2}{2026/07/04}{增加 \texttt{PoZheHao} 字符类，
%  支持字体的破折号合字功能（\#382）。}
%
% \begin{macro}[int]
%   {HalfLeft,HalfRight,NormalSpace,CM,HangulJamo,HangulJamoL,
%    HangulJamoV,HangulJamoT,CJStarter,PoZheHao}
% 新增西文半角左/右标点、前后原始间距的符号和异体字选择符类，
% 以及支持破折号合字的字符类。
%    \begin{macrocode}
\xeCJK_new_class:n { HalfLeft }
\xeCJK_new_class:n { HalfRight }
\xeCJK_new_class:n { NormalSpace }
\xeCJK_new_class:n { CM }
%    \end{macrocode}
% 旧的 |HangulJamo| 类只为兼容用户代码保留；新代码应按音节位置使用
% |HangulJamoL|、|HangulJamoV| 和 |HangulJamoT|。
%    \begin{macrocode}
\xeCJK_new_class:n { HangulJamo }
\xeCJK_new_class:n { HangulJamoL }
\xeCJK_new_class:n { HangulJamoV }
\xeCJK_new_class:n { HangulJamoT }
\xeCJK_new_class:n { CJStarter }
\xeCJK_new_class:n { PoZheHao }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.11}{2014/04/10}
% {左右角括号 \texttt{U+2329} 和 \texttt{U+232A} 是西文标点符号。}
% \changes{v3.2.15}{2014/11/09}
% {把 REVERSE SOLIDUS（\texttt{U+005C}）、HYPHEN-MINUS（\texttt{U+002D}）和
%  EN DASH（\texttt{U+2013}）归入 \texttt{NormalSpace} 类。}
%
% \begin{variable}
%  {\c_@@_HalfLeft_chars_clist,\c_@@_HalfRight_chars_clist,\c_@@_NormalSpace_chars_clist}
% \hypertarget{CJKcharclass}{西文半角左/右标点和前后原始间距的字符类。}
%    \begin{macrocode}
\clist_const:Nn \c_@@_HalfLeft_chars_clist
  { "28  , "5B , "60 , "7B , "2329 }
\clist_const:Nn \c_@@_HalfRight_chars_clist
  { "21 , "22 , "25 , "27 , "29 , "2C , "2E , "3A , "3B , "3F , "5D , "7D , "232A }
\clist_const:Nn \c_@@_NormalSpace_chars_clist { "2D , "2F , "5C }
%    \end{macrocode}
% \end{variable}
%
% 以下对全角标点符号的归类来源于 \XeTeX 的脚本
% \href{http://sourceforge.net/p/xetex/code/ci/master/tree/source/texk/web2c/xetexdir/unicode-char-prep.pl}
% {\file{unicode-char-prep.pl}} 和 Unicode 数据库\footnote{\url{http://www.unicode.org/reports/tr14/}}。
%
% \changes{v3.2.3}{2013/06/09}{根据 \XeTeX 的脚本重新整理全角标点符号。}
%
% \begin{variable}{\c_@@_OP_chars_clist}
% Open Punctuation (OP)
% \PrintPunctList{OP}{Open Punctuation}
% 以下代码的第一行是中西文共用的左引号。
%    \begin{macrocode}
\clist_const:Nn \c_@@_OP_chars_clist
  {
    "2018 , "201C ,
    "3008 , "300A , "300C , "300E , "3010 , "3014 , "3016 , "3018 , "301A , "301D ,
    "FE17 , "FE35 , "FE37 , "FE39 , "FE3B , "FE3D , "FE3F , "FE41 , "FE43 , "FE47 ,
    "FE59 , "FE5B , "FE5D , "FF08 , "FF3B , "FF5B , "FF5F , "FF62
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_PR_chars_clist}
% \changes{v3.3.0}{2014/12/26}{不把 \texttt{U+20A9} 归入 CJK 的 PR 类。}
% Prefix Numeric (PR)
% \PrintPunctList{PR}{Prefix Numeric}
%    \begin{macrocode}
\clist_const:Nn \c_@@_PR_chars_clist
  { "FE69 , "FF04 , "FFE1 , "FFE5 , "FFE6 }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_FullLeft_chars_clist}
% 以上两类标点符号出现在文字的左边，不应出现在行尾位置。
%    \begin{macrocode}
\clist_const:Ne \c_@@_FullLeft_chars_clist
  {
    \exp_not:V \c_@@_OP_chars_clist ,
    \exp_not:V \c_@@_PR_chars_clist
  }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.3.3}{2015/12/12}
% {把 EN DASH（\texttt{U+2013}）作为半字线连接号归入 \texttt{FullRight} 类。}
% \changes{v3.3.3}{2015/12/12}
% {不再把 \texttt{U+2015} 和 \texttt{U+2500} 归入 \texttt{FullRight} 类。}
% \changes{v3.6.0}{2018/01/14}
% {把 TWO-EM DASH (\texttt{U+2E3A}) 归入 \texttt{FullRight} 类和设为
%  \texttt{LongPunct} 与 \texttt{MiddlePunct}。}
%
% \begin{variable}{\c_@@_CL_chars_clist}
% Close Punctuation (CL)
% \PrintPunctList{CL}{Close Punctuation}
% 以下代码的第一行是中西文共用的一些标点符号。
%    \begin{macrocode}
\clist_const:Nn \c_@@_CL_chars_clist
  {
    "00B7 , "2019 , "201D , "2013 , "2014 , "2025 , "2026 , "2027 , "2E3A ,
    "3001 , "3002 , "3009 , "300B , "300D , "300F , "3011 , "3015 , "3017 , "3019 ,
    "301B , "301E , "301F , "FE11 , "FE12 , "FE18 , "FE36 , "FE38 , "FE3A , "FE3C ,
    "FE3E , "FE40 , "FE42 , "FE44 , "FE48 , "FE50 , "FE52 , "FE5A , "FE5C , "FE5E ,
    "FF09 , "FF0C , "FF0E , "FF3D , "FF5D , "FF60 , "FF61 , "FF63 , "FF64
  }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.3.0}{2014/12/26}{不把 NS 类中的一些有禁则的日文归入 \texttt{FullRight} 类。}
% \changes{v3.6.0}{2018/01/14}
% {将全角浪线 \texttt{U+FF5E} 等连接号归入 \texttt{FullRight} 类和设为 \texttt{MiddlePunct}。}
% \changes{v3.10.0}{2026/04/27}
% {将全角浪线等连接号从 \texttt{MiddlePunct} 改为 \texttt{LongPunct}，
%  消除不必要的标点压缩间距。}
%
% \begin{variable}{\c_@@_NS_chars_clist}
% Nonstarter (NS)
% \PrintPunctList{NS}{Nonstarter}
% \noindent Hyphens (cl-03)
% \PrintPunctList{hyphens}{Hyphens}
% \noindent Iteration marks (cl-09)
% \PrintPunctList{iteration_marks}{Iteration marks}
% 根据 W3C 的资料\footnote{\url{http://www.w3.org/TR/jlreq/}}，\texttt{cl-03} 和
% \texttt{cl-09} 在非常松散的情况下可以没有禁则。我们将全角浪线 \texttt{U+FF5E} 等连接号归入
% \texttt{FullRight} 类并在宏包末尾设为 \texttt{LongPunct}。
%    \begin{macrocode}
\clist_const:Nn \c_@@_hyphens_chars_clist
  { "301C , "30A0 , "FF5E }
\clist_const:Nn \c_@@_iteration_marks_chars_clist
  { "3005 , "303B , "309D , "309E , "30FD , "30FE }
\clist_const:Nn \c_@@_NS_chars_clist
  { "30FB , "FE54 , "FE55 , "FF1A , "FF1B , "FF65 , "16FE0 }
\AtEndOfPackage
  {
    \cs_set:Npn \@@_tmp:w #1
      { \char_generate:nn {#1} { 12 } }
    \@@_add_special_punct:nn { long }
      { \clist_map_function:NN \c_@@_hyphens_chars_clist \@@_tmp:w }
    \cs_undefine:N \@@_tmp:w
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_EX_chars_clist}
% Exclamation/Interrogation (EX)
% \PrintPunctList{EX}{Exclamation/Interrogation}
%    \begin{macrocode}
\clist_const:Nn \c_@@_EX_chars_clist
  { "FE15 , "FE16 , "FE56 , "FE57 , "FF01 , "FF1F }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_IS_chars_clist}
% Infix Numeric Separator (IS)
% \PrintPunctList{IS}{Infix Numeric Separator}
%    \begin{macrocode}
\clist_const:Nn \c_@@_IS_chars_clist { "FE10 , "FE13 , "FE14 }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_CJ_chars_clist}
% Conditional Japanese Starter (CJ)。这类字符的禁则是可选的^^A
% \footnote{\url{https://github.com/CTeX-org/ctex-kit/issues/165}}，
% 为实现的简单计，我们把它们归入 CJK 类，即没有禁则。
% \PrintPunctList{CJ}{Conditional Japanese Starter}
%    \begin{macrocode}
\clist_const:Nn \c_@@_CJ_chars_clist
  {
    "3041 , "3043 , "3045 , "3047 , "3049 , "3063 , "3083 , "3085 , "3087 , "308E ,
    "3095 , "3096 , "30A1 , "30A3 , "30A5 , "30A7 , "30A9 , "30C3 , "30E3 , "30E5 ,
    "30E7 , "30EE , "30F5 , "30F6 , "30FC , "31F0 , "31F1 , "31F2 , "31F3 , "31F4 ,
    "31F5 , "31F6 , "31F7 , "31F8 , "31F9 , "31FA , "31FB , "31FC , "31FD , "31FE ,
    "31FF , "FF67 , "FF68 , "FF69 , "FF6A , "FF6B , "FF6C , "FF6D , "FF6E , "FF6F ,
    "FF70 , "1B132 , "1B150 -> "1B152 , "1B155 , "1B164 -> "1B167
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_PO_chars_clist}
% Postfix Numeric (PO)
% \PrintPunctList{PO}{Postfix Numeric}
%    \begin{macrocode}
\clist_const:Nn \c_@@_PO_chars_clist { "FE6A , "FF05 , "FFE0 }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.3.0}{2014/12/26}{不把小写日文假名归入 \texttt{FullRight} 类。}
%
% \begin{variable}{\c_@@_FullRight_chars_clist}
% 以上六类标点符号出现在文字的右边，不应出现在行首位置。
%    \begin{macrocode}
\clist_const:Ne \c_@@_FullRight_chars_clist
  {
    \exp_not:V \c_@@_CL_chars_clist ,
    \exp_not:V \c_@@_NS_chars_clist ,
    \exp_not:V \c_@@_EX_chars_clist ,
    \exp_not:V \c_@@_IS_chars_clist ,
    \exp_not:V \c_@@_PO_chars_clist ,
    \exp_not:V \c_@@_hyphens_chars_clist
  }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.3.3}{2015/06/25}{补充 Ext-E。}
% \changes{v3.4.1}{2016/08/18}{补充 Unicode 9.0 的西夏文。}
% \changes{v3.5.0}{2017/07/22}{补充 Ext-F。}
% \changes{v3.7.3}{2019/04/15}{补充日文假名扩展。}
% \changes{v3.8.3}{2020/03/15}{同步 Unicode 13.0。}
% \changes{v3.8.3}{2020/04/09}{补充 \texttt{U+02EA} 和 \texttt{U+02EB}。}
% \changes{v3.8.8}{2021/09/15}{同步 Unicode 14.0。}
% \changes{v3.8.8}{2021/09/16}{补充女书。}
% \changes{v3.10.0}{2022/09/17}{同步 Unicode 15.0。}
% \changes{v3.10.0}{2023/12/02}{同步 Unicode 15.1。}
% \changes{v3.10.0}{2026/04/30}{同步 Unicode 17.0。}
%
% \begin{variable}{\c_@@_CJK_chars_clist}
% CJK 字符类，包括文字和标点符号。
%    \begin{macrocode}
\clist_const:Nn \c_@@_CJK_chars_clist
  {
%    \end{macrocode}
% \begin{itemize}[nosep,leftmargin=0pt]
% \item 闽南语、客家话阴去和阳去声调标记
%    \begin{macrocode}
    "02EA -> "02EB ,
%    \end{macrocode}
% \item CJK Radicals Supplement （中日韩部首补充）
%    \begin{macrocode}
    "2E80 -> "2EFF ,
%    \end{macrocode}
% \item Kangxi Radicals （康熙部首）
%    \begin{macrocode}
    "2F00 -> "2FDF ,
%    \end{macrocode}
% \item Ideographic Description Characters （表意文字描述符）
%    \begin{macrocode}
    "2FF0 -> "2FFF ,
%    \end{macrocode}
% \item CJK Symbols and Punctuation （中日韩符号和标点）
%    \begin{macrocode}
    "3000 -> "303F ,
%    \end{macrocode}
% \item Hiragana （日文平假名）
%    \begin{macrocode}
    "3040 -> "309F ,
%    \end{macrocode}
% \item Katakana （日文片假名）
%    \begin{macrocode}
    "30A0 -> "30FF ,
%    \end{macrocode}
% \item Bopomofo （注音字母）
%    \begin{macrocode}
    "3100 -> "312F ,
%    \end{macrocode}
% \item Hangul Compatibility Jamo （谚文兼容字母）
%    \begin{macrocode}
    "3130 -> "318F ,
%    \end{macrocode}
% \item Kanbun （象形字注释标志）
%    \begin{macrocode}
    "3190 -> "319F ,
%    \end{macrocode}
% \item Bopomofo Extended （注音字母扩展）
%    \begin{macrocode}
    "31A0 -> "31BF ,
%    \end{macrocode}
% \item CJK Strokes （中日韩笔画）
%    \begin{macrocode}
    "31C0 -> "31EF ,
%    \end{macrocode}
% \item Katakana Phonetic Extensions （日文片假名语音扩展）
%    \begin{macrocode}
    "31F0 -> "31FF ,
%    \end{macrocode}
% \item Enclosed CJK Letters and Months （带圈中日韩字母和月份）
%    \begin{macrocode}
    "3200 -> "32FF ,
%    \end{macrocode}
% \item CJK Compatibility （中日韩兼容）
%    \begin{macrocode}
    "3300 -> "33FF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-A （中日韩统一表意文字扩展 A）
%    \begin{macrocode}
    "3400 -> "4DBF ,
%    \end{macrocode}
% \item Yijing Hexagrams Symbols （易经六十四卦符号）
%    \begin{macrocode}
    "4DC0 -> "4DFF ,
%    \end{macrocode}
% \item CJK Unified Ideographs （中日韩统一表意文字）
%    \begin{macrocode}
    "4E00 -> "9FFF ,
%    \end{macrocode}
% \item Yi Syllables （彝文音节）
%    \begin{macrocode}
    "A000 -> "A48F ,
%    \end{macrocode}
% \item Yi Radicals （彝文字根）
%    \begin{macrocode}
    "A490 -> "A4CF ,
%    \end{macrocode}
% \item Hangul Syllables （谚文音节）
%    \begin{macrocode}
    "AC00 -> "D7AF ,
%    \end{macrocode}
% \item CJK Compatibility Ideographs （中日韩兼容表意文字）
%    \begin{macrocode}
    "F900 -> "FAFF ,
%    \end{macrocode}
% \item Vertical Forms （竖排形式）
%    \begin{macrocode}
    "FE10 -> "FE1F ,
%    \end{macrocode}
% \item CJK Compatibility Forms （中日韩兼容形式）
%    \begin{macrocode}
    "FE30 -> "FE4F ,
%    \end{macrocode}
% \item Halfwidth and Fullwidth Forms （半角及全角形式）
%    \begin{macrocode}
    "FF00 -> "FFEF ,
%    \end{macrocode}
% \item Ideographic Symbols and Punctuation （表意文字符号及标点）
%    \begin{macrocode}
    "16FE0 -> "16FFF ,
%    \end{macrocode}
% \item Tangut （西夏文）
%    \begin{macrocode}
    "17000 -> "187FF ,
%    \end{macrocode}
% \item Tangut Components （西夏文部首）
%    \begin{macrocode}
    "18800 -> "18AFF ,
%    \end{macrocode}
% \item Khitan Small Script （契丹小字）
%    \begin{macrocode}
    "18B00 -> "18CFF ,
%    \end{macrocode}
% \item Tangut Supplement （西夏文增补）
%    \begin{macrocode}
    "18D00 -> "18D7F ,
%    \end{macrocode}
% \item Tangut Components Supplement （西夏文部首增补）
%    \begin{macrocode}
    "18D80 -> "18DFF ,
%    \end{macrocode}
% \item Kana Extended-B （日文假名扩展 B）
%    \begin{macrocode}
    "1AFF0 -> "1AFFF ,
%    \end{macrocode}
% \item Kana Supplement （日文假名增补）
%    \begin{macrocode}
    "1B000 -> "1B0FF ,
%    \end{macrocode}
% \item Kana Extended-A （日文假名扩展 A）
%    \begin{macrocode}
    "1B100 -> "1B12F ,
%    \end{macrocode}
% \item Small Kana Extension （小型日文假名扩展）
%    \begin{macrocode}
    "1B130 -> "1B16F ,
%    \end{macrocode}
% \item Nushu （女书）
%    \begin{macrocode}
    "1B170 -> "1B2FF ,
%    \end{macrocode}
% \item Enclosed Ideographic Supplement （带圈表意文字增补）
%    \begin{macrocode}
    "1F200 -> "1F2FF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-B （中日韩统一表意文字扩展 B）
%    \begin{macrocode}
    "20000 -> "2A6DF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-C （中日韩统一表意文字扩展 C）
%    \begin{macrocode}
    "2A700 -> "2B73F ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-D （中日韩统一表意文字扩展 D）
%    \begin{macrocode}
    "2B740 -> "2B81F ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-E （中日韩统一表意文字扩展 E）
%    \begin{macrocode}
    "2B820 -> "2CEAF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-F （中日韩统一表意文字扩展 F）
%    \begin{macrocode}
    "2CEB0 -> "2EBEF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-I （中日韩统一表意文字扩展 I）
%    \begin{macrocode}
    "2EBF0 -> "2EE5F ,
%    \end{macrocode}
% \item CJK Compatibility Ideographs Supplement （中日韩兼容表意文字增补）
%    \begin{macrocode}
    "2F800 -> "2FA1F ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-G （中日韩统一表意文字扩展 G）
%    \begin{macrocode}
    "30000 -> "3134F ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-H （中日韩统一表意文字扩展 H）
%    \begin{macrocode}
    "31350 -> "323AF ,
%    \end{macrocode}
% \item CJK Unified Ideographs Extension-J （中日韩统一表意文字扩展 J）
%    \begin{macrocode}
    "323B0 -> "3347F
%    \end{macrocode}
% \end{itemize}
%    \begin{macrocode}
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_CM_chars_clist}
% \changes{v3.3.1}{2015/01/22}{补充音调符号。}
% 包括日文假名浊点和异体字选择符。组合标识是最好是归入 256 类，即透明类，不会影响
% 状态。但也会产生一定问题。比如下面的例子，位于行尾的“二”造成分组不匹配。
% \begin{verbatim}
%   \XeTeXinterchartokenstate=1
%   \XeTeXcharclass`二=256
%   \XeTeXinterchartoks 255 1 = {\bgroup}
%   \XeTeXinterchartoks 1 255 = {\egroup}
%   \XeTeXinterchartoks 1 1 = {x}
%   \font\zhfont="SimSun"
%   \zhfont
%   一二三二
%   \bye
% \end{verbatim}
%    \begin{macrocode}
\clist_const:Nn \c_@@_CM_chars_clist
  {
%    \end{macrocode}
% \begin{itemize}[nosep,leftmargin=0pt]
% \item Diacritics （音调符号）
%    \begin{macrocode}
    "302A -> "302F ,
%    \end{macrocode}
% \item 日文假名浊点
%    \begin{macrocode}
    "3099 -> "309A ,
%    \end{macrocode}
% \item Variation Selectors （异体字选择符）
%    \begin{macrocode}
    "FE00 -> "FE0F ,
%    \end{macrocode}
% \item Variation Selectors Supplement （异体字选择符增补）
%    \begin{macrocode}
    "E0100 -> "E01EF
%    \end{macrocode}
% \end{itemize}
%    \begin{macrocode}
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {\c_@@_HangulJamoL_chars_clist,\c_@@_HangulJamoV_chars_clist,
%    \c_@@_HangulJamoT_chars_clist}
% 朝鲜文字母按 Unicode |Hangul_Syllable_Type| 分成初声（L）、中声（V）和
% 终声（T）三类，以便区分音节内 shaping 与相邻音节边界（\#158）。
%    \begin{macrocode}
\clist_const:Nn \c_@@_HangulJamoL_chars_clist
  { "1100 -> "115F , "A960 -> "A97C }
\clist_const:Nn \c_@@_HangulJamoV_chars_clist
  { "1160 -> "11A7 , "D7B0 -> "D7C6 }
\clist_const:Nn \c_@@_HangulJamoT_chars_clist
  { "11A8 -> "11FF , "D7CB -> "D7FB }
%    \end{macrocode}
% \end{variable}
%
% \subsection{字符类别处理}
%
% \begin{macro}[int]{\xeCJK_class_num:n}
% |#1| 为字符类别名称，用于取得字符类别对应的编号。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_class_num:n #1
  { \use:c { \@@_class_csname:n {#1} } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKDeclareCharClass}
%    \begin{macrocode}
\NewDocumentCommand \xeCJKDeclareCharClass { s > { \TrimSpaces } m m }
  {
    \xeCJK_declare_char_class:nn {#2} {#3}
    \IfBooleanT {#1} { \xeCJKResetPunctClass }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_declare_char_class:nn,\xeCJK_declare_char_class:nN}
% \begin{macro}{\@@_set_char_class_aux:Nnw}
% 用于设置字符所属的类别，|#1| 为类别名称，|#2| 为字符的 |Unicode|，相邻字符用
% 半角逗号隔开，支持类似 |"1100 -> "11FF| 起止范围的使用方式。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_declare_char_class:nn #1#2
  {
    \clist_set:Ne \l_@@_tmp_clist {#2}
    \xeCJK_declare_char_class:nN {#1} \l_@@_tmp_clist
  }
\cs_new_protected:Npn \xeCJK_declare_char_class:nN #1#2
  {
    \clist_gconcat:ccN
      { g_@@_#1_range_clist } { g_@@_#1_range_clist } #2
    \clist_map_inline:Nn #2
      {
        \str_if_eq:nnF {##1} { -> }
          {
            \@@_set_char_class_aux:Nnw \xeCJK_set_char_class:nnn {##1}
              { \xeCJK_class_num:n {#1} }
          }
      }
    \xeCJK_set_char_class:nnn { "3099 } { "309A } { \xeCJK_class_num:n { CM } }
  }
\NewDocumentCommand \@@_set_char_class_aux:Nnw
  { m > { \SplitArgument { 1 } { -> } } m } { #1 #2 }
\cs_generate_variant:Nn \clist_gconcat:NNN { cc }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \changes{v3.10.4}{2026/07/21}
%   {修正 \cs{@@_check_num_range:nnNN} 中缺省端点的范围解析问题。}
% \begin{macro}{\@@_check_num_range:nnNN}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_num_range:nnNN #1#2#3#4
  {
    \bool_lazy_or:nnTF
      { \tl_if_blank_p:n {#1} }
      { \tl_if_blank_p:n {#2} }
      {
        \int_set:Nn #3 { \tl_if_blank:nTF {#1} {#2} {#1} }
        \int_set_eq:NN #4 #3
      }
      {
        \int_set:Nn #3 { \int_min:nn {#1} { \tl_if_novalue:nTF {#2} {#1} {#2} } }
        \int_set:Nn #4 { \int_max:nn {#1} { \tl_if_novalue:nTF {#2} {#1} {#2} } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.3}{2013/06/08}{不再改变 CJK 字符类的 \tn{catcode}。}
% \changes{v3.2.7}{2013/08/29}{修正 \file{unicode-letters.tex} 中谚文符号
% \tn{catcode} 不准的问题。}
%    \begin{macrocode}
\token_if_letter:NF ^^^^ac00
  {
    \int_set:Nn \l_@@_begin_int { "AC00 }
    \int_set:Nn \l_@@_end_int   { "D7A3 }
    \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int }
      {
        \char_set_catcode_letter:n { \l_@@_begin_int }
        \int_incr:N \l_@@_begin_int
      }
  }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_set_char_class:nnn}
% \changes{v3.1.1}{2012/12/05}{在文档中设置字符类别时不重复设置 \tn{catcode}。}
% 设置字符类别，|#1| 和 |#2| 为字符类别起止的 |Unicode|，|#3| 为类别名称对应编号。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_set_char_class:nnn #1#2#3
  {
    \@@_check_num_range:nnNN {#1} {#2} \l_@@_begin_int \l_@@_end_int
    \int_set:Nn \l_@@_tmp_int {#3}
    \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int }
      {
        \tex_XeTeXcharclass:D \l_@@_begin_int = \l_@@_tmp_int
        \int_incr:N \l_@@_begin_int
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_char_class_eq:nn}
% \changes{v3.1.1}{2012/12/06}{交换参数的顺序。}
% 将字符类 |#1| 中的字符全部设置成字符类 |#2|。只适用于 |#1| 的字符类范围为离散的
% 逗号列表的情况。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_char_class_eq:nn #1#2
  {
    \int_set:Nn \l_@@_tmp_int { \xeCJK_class_num:n {#2} }
    \clist_map_inline:cn { c_@@_#1_chars_clist }
      { \tex_XeTeXcharclass:D ##1 = \l_@@_tmp_int }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\normalspacedchars}
% 声明前后不加间距的字符。
%    \begin{macrocode}
\NewDocumentCommand \normalspacedchars { m }
  {
    \tl_map_inline:nn {#1}
      { \tex_XeTeXcharclass:D `##1 = \xeCJK_class_num:n { NormalSpace } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKResetPunctClass}
% \changes{v3.10.2}{2026/07/04}{重置后恢复 \texttt{PoZheHaoLigature}
%  的设置（\#382）。}
% \changes{v3.10.2}{2026/07/05}{重置后恢复 \texttt{LatinPunct}
%  的设置（\#389）。}
% 用于重置标点符号所属的字符类。注意，重新声明 |FullLeft| 和
% |FullRight| 类会覆盖 \opt{PoZheHaoLigature} 与 \opt{LatinPunct}
% 对共用标点的字符类调整，因此在重置后需要按记录的状态恢复这两个
% 选项的设置。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKResetPunctClass { }
  {
    \clist_gclear:N \g_@@_HalfLeft_range_clist
    \clist_gclear:N \g_@@_HalfRight_range_clist
    \clist_gclear:N \g_@@_FullLeft_range_clist
    \clist_gclear:N \g_@@_FullRight_range_clist
    \xeCJK_declare_char_class:nN { HalfLeft  } \c_@@_HalfLeft_chars_clist
    \xeCJK_declare_char_class:nN { HalfRight } \c_@@_HalfRight_chars_clist
    \xeCJK_declare_char_class:nN { FullLeft  } \c_@@_FullLeft_chars_clist
    \xeCJK_declare_char_class:nN { FullRight } \c_@@_FullRight_chars_clist
    \bool_if:NT \l_@@_pozhehao_ligature_bool
      { \keys_set:nn { xeCJK / options } { PoZheHaoLigature = true } }
    \bool_if:NT \l_@@_latin_punct_bool
      { \keys_set:nn { xeCJK / options } { LatinPunct = true } }
  }
\bool_new:N \l_@@_pozhehao_ligature_bool
\bool_new:N \l_@@_latin_punct_bool
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\xeCJKResetCharClass}
% 用于恢复 \pkg{xeCJK} 对字符类别的设置。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKResetCharClass { }
  {
    \clist_gclear:N \g_@@_CJK_range_clist
    \clist_gclear:N \g_@@_NormalSpace_range_clist
    \clist_gclear:N \g_@@_CM_range_clist
    \clist_gclear:N \g_@@_HangulJamo_range_clist
    \clist_gclear:N \g_@@_HangulJamoL_range_clist
    \clist_gclear:N \g_@@_HangulJamoV_range_clist
    \clist_gclear:N \g_@@_HangulJamoT_range_clist
    \clist_gclear:N \g_@@_CJStarter_range_clist
    \xeCJK_declare_char_class:nN { CJK } \c_@@_CJK_chars_clist
    \xeCJK_declare_char_class:nN { NormalSpace } \c_@@_NormalSpace_chars_clist
    \xeCJK_declare_char_class:nN { CM } \c_@@_CM_chars_clist
    \xeCJK_declare_char_class:nN { HangulJamoL } \c_@@_HangulJamoL_chars_clist
    \xeCJK_declare_char_class:nN { HangulJamoV } \c_@@_HangulJamoV_chars_clist
    \xeCJK_declare_char_class:nN { HangulJamoT } \c_@@_HangulJamoT_chars_clist
    \bool_if:NT \l_@@_CJ_strict_bool
      { \xeCJK_declare_char_class:nN { CJStarter } \c_@@_CJ_chars_clist }
    \xeCJKResetPunctClass
  }
\bool_new:N \l_@@_CJ_strict_bool
%    \end{macrocode}
% \end{macro}
%
% 设置字符类别。
%    \begin{macrocode}
\xeCJKResetCharClass
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_inter_class_toks:nnn}
% 在相邻类别之间插入内容。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_inter_class_toks:nnn #1#2#3
  {
    \tex_XeTeXinterchartoks:D \xeCJK_class_num:n {#1} ~
                              \xeCJK_class_num:n {#2} = {#3}
  }
\cs_generate_variant:Nn \xeCJK_inter_class_toks:nnn { nne }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_get_inter_class_toks:nn}
% 取出相邻类别之间的内容。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_get_inter_class_toks:nn #1#2
  {
    \tex_the:D \tex_XeTeXinterchartoks:D \xeCJK_class_num:n {#1} ~
                                         \xeCJK_class_num:n {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_clear_inter_class_toks:nn}
% 清除相邻类别之间的内容。注意，直接赋空值可能会导致 \XeTeX 崩溃。例如
% \begin{verbatim}
%   \XeTeXinterchartokenstate = 1
%   \XeTeXcharclass`A=10
%   \XeTeXinterchartoks 10 10 = {xx}
%   \begingroup
%     \XeTeXinterchartoks 10 10 = {} AA
%   \endgroup
%   \bye
% \end{verbatim}
% 如果把上述例子中的分组 \tn{begingroup} 和 \tn{endgroup} 去掉，则结果正常，甚为怪异。
% 此处 \XeTeX 的 bug 已经在 0.999992 版中修复^^A
% \footnote{\url{http://tug.org/svn/texlive?view=revision&revision=53880}}。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_clear_inter_class_toks:nn #1#2
  { \xeCJK_inter_class_toks:nnn {#1} {#2} { \prg_do_nothing: } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_pre_inter_class_toks:nnn}
% 在相邻类别之间已有的内容前增加内容。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_pre_inter_class_toks:nnn #1#2#3
  {
    \xeCJK_inter_class_toks:nne {#1} {#2}
      { \exp_not:n {#3} \xeCJK_get_inter_class_toks:nn {#1} {#2} }
  }
\cs_generate_variant:Nn \xeCJK_pre_inter_class_toks:nnn { nne }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_app_inter_class_toks:nnn}
% 在相邻类别之间已有的内容后追加内容。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_app_inter_class_toks:nnn #1#2#3
  {
    \xeCJK_inter_class_toks:nne {#1} {#2}
      { \xeCJK_get_inter_class_toks:nn {#1} {#2} \exp_not:n {#3} }
  }
\cs_generate_variant:Nn \xeCJK_app_inter_class_toks:nnn { nne }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_copy_inter_class_toks:nnnn}
% 将 |#3| 和 |#4| 之间的内容复制到 |#1| 和 |#2| 之间。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_copy_inter_class_toks:nnnn #1#2#3#4
  {
    \tl_set:Ne \l_@@_tmp_tl
      { \xeCJK_get_inter_class_toks:nn {#3} {#4} }
    \tl_if_empty:NTF \l_@@_tmp_tl
      {
        \tl_set:Ne \l_@@_tmp_tl
          { \xeCJK_get_inter_class_toks:nn {#1} {#2} }
        \tl_if_empty:NF \l_@@_tmp_tl
          { \xeCJK_clear_inter_class_toks:nn {#1} {#2} }
      }
      { \xeCJK_inter_class_toks:nne {#1} {#2} { \exp_not:o \l_@@_tmp_tl } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_replace_inter_class_toks:nnnn}
% 将 |#1| 和 |#2| 之间出现的 |#3| 用 |#4| 替换。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_replace_inter_class_toks:nnnn #1#2#3#4
  {
    \tl_set:Ne \l_@@_tmp_tl
      { \xeCJK_get_inter_class_toks:nn {#1} {#2} }
    \tl_if_empty:NF \l_@@_tmp_tl
      {
        \tl_replace_all:Nnn \l_@@_tmp_tl {#3} {#4}
        \xeCJK_inter_class_toks:nne {#1} {#2}
          { \exp_not:o \l_@@_tmp_tl }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_clear_Boundary_and_CJK_toks:}
% \changes{v3.4.2}{2016/10/19}{提高效率，避免重复循环。}
% 清除边界与 CJK 文字、全角左右标点之间的内容。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_clear_Boundary_and_CJK_toks:
  { }
\cs_new_protected:Npn \@@_update_clear_toks:n #1
  {
    \cs_gset_protected:Npe \xeCJK_clear_Boundary_and_CJK_toks:
      {
        \exp_not:o { \xeCJK_clear_Boundary_and_CJK_toks: }
        \tex_XeTeXinterchartoks:D
          \xeCJK_class_num:n { Boundary } ~
          \xeCJK_class_num:n {#1} = { \exp_not:N \prg_do_nothing: }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}
%  {\g_@@_base_class_seq,\g_@@_non_CJK_class_seq,\g_@@_CJK_class_seq}
%  保存宏包预先定义的字符类。
%    \begin{macrocode}
\seq_new:N \g_@@_base_class_seq
\seq_gset_eq:NN \g_@@_base_class_seq \g_@@_class_seq
\seq_new:N \g_@@_non_CJK_class_seq
\seq_gset_from_clist:Nn \g_@@_non_CJK_class_seq
  { Default , HalfLeft , HalfRight , NormalSpace , Boundary }
\seq_new:N \g_@@_CJK_class_seq
\cs_new_protected:Npn \@@_save_CJK_class:n #1
  {
    \seq_gput_right:Nn \g_@@_CJK_class_seq {#1}
    \tl_const:cn
      { \@@_CJK_class_tl:n { \use:c { \@@_class_csname:n {#1} } } }
      {#1}
    \@@_update_clear_toks:n {#1}
  }
\clist_map_function:nN
  {
    CJK , FullLeft , FullRight , CM , HangulJamo ,
    HangulJamoL , HangulJamoV , HangulJamoT , CJStarter , PoZheHao
  }
  \@@_save_CJK_class:n
%    \end{macrocode}
% \end{variable}
%
% \subsection{字符输出规则}
%
% \begin{center}
% \begin{tabular}{l*9c}
% \toprule
%   & |Default| & |CJK| & |FullL| & |FullR| & |HalfL|
%   & |HalfR| & |Normal| & |Bound| & |CM| \\ \midrule
% |Default|
%   &
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   &
%   &
%   &
%   & \tokslink{def-bound}
%   & \tokslink{def-cm}\\
% |CJK|
%   & \tokslink{def-cjk}
%   & \tokslink{cjk-cjk}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{cjk-bound}
%   &\\
% |FullLeft|
%   & \tokslink{def-cjk}
%   & \tokslink{fl-fr-others}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{fl-fr-bound}
%   & \tokslink{def-cm}\\
% |FullRight|
%   & \tokslink{def-cjk}
%   & \tokslink{fl-fr-others}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{cjk-fl-fr}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{fl-fr-bound}
%   & \tokslink{def-cm}\\
% |HalfLeft|
%   &
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   &
%   &
%   &
%   &
%   & \tokslink{def-cm}\\
% |HalfRight|
%   &
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   &
%   &
%   &
%   & \tokslink{def-bound}
%   & \tokslink{def-cm}\\
% |NormalSpace|
%   &
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   & \tokslink{def-cjk}
%   &
%   &
%   &
%   & \tokslink{ns-bound}
%   & \tokslink{def-cm}\\
% |Boundary|
%   & \tokslink{bound-def}
%   & \tokslink{bound-cjk}
%   & \tokslink{bound-fl-fr}
%   & \tokslink{bound-fl-fr}
%   & \tokslink{bound-def}
%   &
%   & \tokslink{bound-ns}
%   &
%   & \tokslink{def-cm}\\
% |CM|
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}
%   & \tokslink{def-cm}\\
% \bottomrule
% \end{tabular}
% \end{center}
%
% \changes{v3.4.2}{2016/10/19}{避免在破折号之间折行。}
%
% \begin{macro}[int]{\xeCJK_class_group_begin:,\xeCJK_class_group_end:}
% 在 CJK 类开始时，设置 \tn{XeTeXdashbreakstate} 为零，避免破折号之间的折行。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_class_group_begin:
  {
    \c_group_begin_token
    \bool_set_true:N \l_@@_CJK_group_bool
    \xeCJK_reset_space_factor:
    \int_zero:N \tex_XeTeXdashbreakstate:D
  }
\bool_new:N \l_@@_CJK_group_bool
\cs_new_eq:NN \xeCJK_class_group_end: \c_group_end_token
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{def-cm}{\texttt{CM}} 字符类与 |CJK| 字符类基本相同，只是从 |CJK|
% 转移到 |CM| 时，不加入任何内容。
%    \begin{macrocode}
\AtEndOfPackage
  {
    \seq_map_inline:Nn \g_@@_class_seq
      {
        \str_if_eq:nnTF {#1} { CM }
          { \xeCJK_copy_inter_class_toks:nnnn { CM } {#1} { CJK } { CJK } }
          {
            \xeCJK_copy_inter_class_toks:nnnn { CM } {#1} { CJK } {#1}
            \str_if_eq:nnF {#1} { CJK }
              { \xeCJK_copy_inter_class_toks:nnnn {#1} { CM } {#1} { CJK } }
          }
      }
  }
%    \end{macrocode}
%
% \hypertarget{def-HJ}{\texttt{HangulJamo}} 字符类与 |CJK| 字符类基本相同，只是
% |HangulJamo| 类之间不加入任何内容。
%    \begin{macrocode}
\AtEndOfPackage
  {
    \seq_map_inline:Nn \g_@@_class_seq
      {
        \str_if_eq:nnF {#1} { HangulJamo }
          {
            \xeCJK_copy_inter_class_toks:nnnn { HangulJamo } {#1} { CJK } {#1}
            \xeCJK_copy_inter_class_toks:nnnn {#1} { HangulJamo } {#1} { CJK }
          }
      }
  }
%    \end{macrocode}
%
% \changes{v3.10.3}{2026/07/12}{将朝鲜文字母分为 L/V/T 三类，在音节内保持
%   shaping，在相邻音节间恢复 \tn{CJKglue}（\#158）。}
%
% |HangulJamoL|、|HangulJamoV|、|HangulJamoT| 对外都复制 |CJK| 的
% 转移规则。三类之间先复制 |CJK| 到 |CJK|，再按 UAX~\#29 的 Hangul
% 音节延续规则清空 L--L、L--V、V--V、V--T、T--T 五种转移；其余组合
% 是音节边界，保留 \tn{CJKglue} 和断行机会。
%    \begin{macrocode}
\AtEndOfPackage
  {
    \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT }
      {
        \seq_map_inline:Nn \g_@@_class_seq
          {
            \str_if_eq:nnF {##1} {#1}
              {
                \xeCJK_copy_inter_class_toks:nnnn {#1} {##1} { CJK } {##1}
                \xeCJK_copy_inter_class_toks:nnnn {##1} {#1} {##1} { CJK }
              }
          }
      }
    \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT }
      {
        \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT }
          { \xeCJK_copy_inter_class_toks:nnnn {#1} {##1} { CJK } { CJK } }
      }
    \xeCJK_clear_inter_class_toks:nn { HangulJamoL } { HangulJamoL }
    \xeCJK_clear_inter_class_toks:nn { HangulJamoL } { HangulJamoV }
    \xeCJK_clear_inter_class_toks:nn { HangulJamoV } { HangulJamoV }
    \xeCJK_clear_inter_class_toks:nn { HangulJamoV } { HangulJamoT }
    \xeCJK_clear_inter_class_toks:nn { HangulJamoT } { HangulJamoT }
  }
%    \end{macrocode}
%
% \changes{v3.10.3}{2026/07/12}{新增 \texttt{CJStarter} 类和可选严格行首禁则，
%   保持小假名的普通 CJK 字距（\#165）。}
%
% |CJStarter| 对外复制 |CJK| 转移，并在普通 CJK、同类小假名以及全角右
% 标点之后进入该类时，把禁则 penalty 放在 \tn{CJKglue} 前。字符只有在
% |CJLineBreak=strict| 时才归入该类。
%    \begin{macrocode}
\AtEndOfPackage
  {
    \seq_map_inline:Nn \g_@@_class_seq
      {
        \str_if_eq:nnF {#1} { CJStarter }
          {
            \xeCJK_copy_inter_class_toks:nnnn { CJStarter } {#1} { CJK } {#1}
            \xeCJK_copy_inter_class_toks:nnnn {#1} { CJStarter } {#1} { CJK }
          }
      }
    \xeCJK_copy_inter_class_toks:nnnn
      { CJStarter } { CJStarter } { CJK } { CJK }
    \xeCJK_pre_inter_class_toks:nnn { CJK } { CJStarter } { \xeCJK_no_break: }
    \xeCJK_pre_inter_class_toks:nnn
      { CJStarter } { CJStarter } { \xeCJK_no_break: }
    \xeCJK_inter_class_toks:nnn { FullRight } { CJStarter }
      {
        \xeCJK_FullRight_and_CJStarter:
        \xeCJK_fallback_symbol:NN
        \CJKsymbol
      }
  }
\cs_new_protected:Npn \xeCJK_FullRight_and_CJStarter:
  {
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \xeCJK_no_break:
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
    \@@_select_font:
    \CJKglue
  }
%    \end{macrocode}
%
% \hypertarget{def-PZH}{\texttt{PoZheHao}} 字符类与 |FullRight| 字符类基本相同，
% 只是 |PoZheHao| 类之间不加入任何内容，
% 从而不阻断 OpenType 字体的破折号合字功能（\#382）。
%    \begin{macrocode}
\AtEndOfPackage
  {
    \seq_map_inline:Nn \g_@@_class_seq
      {
        \str_if_eq:nnF {#1} { PoZheHao }
          {
            \xeCJK_copy_inter_class_toks:nnnn { PoZheHao } {#1} { FullRight } {#1}
            \xeCJK_copy_inter_class_toks:nnnn {#1} { PoZheHao } {#1} { FullRight }
          }
      }
  }
%    \end{macrocode}
%
% \hypertarget{def-cjk}{}
%    \begin{macrocode}
\clist_map_inline:nn { Default , HalfLeft , HalfRight , NormalSpace }
  {
    \xeCJK_inter_class_toks:nnn {#1} { CJK }
      {
        \@@_boundary_capture_class:n { CJK }
        \xeCJK_class_group_begin:
        \xeCJK_select_font:
        \xeCJK_clear_inter_class_toks:nn {#1} { CJK }
        \xeCJK_clear_Boundary_and_CJK_toks:
        \xeCJK_fallback_symbol:NN
        \CJKsymbol
      }
    \xeCJK_inter_class_toks:nnn { CJK } {#1}
      {
        \xeCJK_class_group_end:
        \@@_boundary_capture_class:n { default }
      }
  }
%    \end{macrocode}
%
% \hypertarget{bound-def}{}
%    \begin{macrocode}
\clist_map_inline:nn { Default , HalfLeft }
  {
    \xeCJK_inter_class_toks:nnn { Boundary } {#1}
      { \xeCJK_Boundary_and_Default: }
    \xeCJK_app_inter_class_toks:nnn { CJK } {#1}
      { \CJKecglue }
  }
%    \end{macrocode}
%
% \begin{macro}[int]{
%   \xeCJK_Boundary_and_Default:,
%   \@@_check_for_ecglue:,
%   \@@_check_for_ecglue_aux:,
%   \@@_recover_ecglue_source_space:,
%   \@@_recover_ecglue_source_space_aux:,
%   \@@_recover_ecglue_source_space_success:,
%   \@@_recover_ecglue_source_space_restore:,
%   \@@_recover_ecglue_source_space_fallback:,
%   \@@_glue_check_expire_stale:,
%   \@@_skip_if_interword:N}
% \changes{v3.10.0}{2026/04/30}{移除通用 whatsit 恢复，避免
%   \pkg{hyperref} 链接注释等 whatsit 节点导致 \texttt{CJKecglue}
%   在错误位置插入（\#803）。配合 \tn{set@color} 定点补丁保持
%   \tn{textcolor} 场景的正确性（\#315）。}
% \changes{v3.10.4}{2026/07/20}{允许 Boundary 到 Default 的恢复路径
%   暂时移除末尾不含无限阶伸缩、且有收缩量的源码词间 glue；若其下方确有
%   CJK marker，则将其替换为 \tn{CJKecglue}，补齐与 Default 到 CJK
%   方向对称的命令边界处理（\#992）。}
%
% 字体组、颜色组或其他命令结束后，源码空格可能已经生成普通词间 glue，
% 压在仍然有效的 CJK marker 上方。只有已知命令或分组路径设置了
% \cs{g_@@_glue_check_pending_bool} 时，才允许暂时移除末尾 glue 并检查
% 其下方的 marker，避免把直接输入的显式 \tn{hskip} 当成源码空格。
% 这枚 glue 还必须 finite、带 shrink 且自然
% 宽度与当前词间空格相等；对未进入额外句末空白分支的普通词间 glue，
% \TeX{} 会把 stretch 乘以 |\spacefactor/1000|、把 shrink 乘以
% |1000/\spacefactor|，所以不能要求整份 skip 严格相等。当
% |\spacefactor >= 2000| 时还会把 \tn{fontdimen7} 加到自然宽度，
% 自然宽度检查会排除这种情况。只有下方是 CJK、CJK-space 或
% CJK-widow marker 时，才移除这枚 glue，并换成缓存的 \tn{CJKecglue}；
% 否则原样放回。
% 移除前用 skip 变量完整保存 \tn{lastskip}，restore 还回的是同一份
% natural/stretch/shrink 数值，不会按当前 spacefactor 重新计算；只有节点的
% 源码来源无法恢复。
% 节点列表无法区分源码空格与参数完全相同的显式 \tn{hskip}。因此，紧跟
% 在已注册命令之后、自然宽度等于当前词间空格且带 shrink 的显式 glue
% 也可能被当作源码空格。需要保留它时，可在前面加零宽 \tn{kern}，或者
% 改用不同的自然宽度、去掉 shrink，让检查不会继续向前找到 xeCJK 的
% marker。
% restore/fallback 不清除 \cs{g_@@_glue_check_pending_bool}：该 glue 可能来自
% \pkg{ulem} 内部列表，这个布尔状态还要留给命令外真正的 CJK 边界；只有
% 确认并使用 marker 后才结束 pending 状态。
% fallback 也不继续调用 \cs{@@_check_for_ecglue_aux:}。此时候选 glue 仍是
% last node（或已由 restore 原样还回），aux 无法越过它取得下方 marker；
% 这枚没有通过检查的 glue 本身就是要保留的分隔。只有 success 路径确认
% 下方是 CJK marker 并移除 glue 后，才允许改发 \tn{CJKecglue}。
%
% \changes{v3.10.4}{2026/07/20}{新增 \cs{@@_glue_check_expire_stale:}：
%   顶层恢复链发现节点列表为空时清除过期的 pending 状态，阻止
%   \cs{g_@@_glue_check_pending_bool} 跨 \tn{hbox}/\tn{setbox}
%   存活（\#996）。}
% \cs{g_@@_glue_check_pending_bool} 是全局布尔变量。在盒子末尾结束 catcode-2
% 分组时设为 true 后，它可能越过 \tn{setbox}，一直留到下一个水平列表。
% pending=true 只表示“下一次可以检查紧跟在 xeCJK marker 后的源码空格”。
% 空列表中不可能有这样的 marker，所以顶层恢复链（没有活跃 capture）发现
% \cs{tex_lastnodetype:D} 为负时，会直接清除 pending 状态。capture 活跃时
% 不这样做：\pkg{ulem} 等 stream 会在内部固定宽度盒子中触发同一恢复链，此时仍需
% 把 pending 留给命令外的边界；capture 入口本身已经清空旧状态。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Boundary_and_Default:
  {
    \@@_boundary_capture_class:n { default }
    \xeCJK_check_for_ecglue:
  }
\cs_new_protected:Npn \@@_check_for_xecglue:
  {
    \@@_if_last_glue:TF
      { \@@_replace_space: }
      { \@@_check_for_ecglue: }
  }
\cs_new_protected:Npn \@@_check_for_ecglue:
  {
    \@@_if_last_glue:TF
      { \@@_recover_ecglue_source_space: }
      { \@@_check_for_ecglue_aux: }
  }
%    \end{macrocode}
%
% \changes{v3.10.5}{2026/07/29}{\tn{CJKecglue} 补在源码空格位置时改用可搬运
%   通道，避免收缩量固化在 \pkg{ulem} 的定宽片段盒内（\#1037）。}
% 这条路径补的是「源码空格 + 西文词」处的 \tn{CJKecglue}：源码空格已被前视
% 吃掉，marker 落在 \pkg{ulem} 刚打开的片段盒内部，此时直接
% \tn{hskip} 会把收缩量固化在定宽盒里，行断行取不到它，装饰段落因此溢出右
% 边距（\#1037，与 \#1026 是同一机制的另一半：\#1026 修的是西文词右侧，
% 这里是词前）。改经 \cs{@@_ulem_glue:n}，它在下划线状态下先
% \tn{UL@stop} 关闭片段盒、把间距画成外层列表上的 \tn{leaders}、再
% \tn{UL@start} 开新盒，使收缩量回到行上；不在下划线状态时它退化为普通
% \tn{hskip}，因此非 \pkg{ulem} 路径的行为不变。
%
% 不用 \cs{@@_boundary_use_ulem_glue:n}：它放的是裸 glue，会在词前留下一段
% 没有装饰线的空隙（实测 300dpi 下断开 7px）。
%
% 默认实现是普通 \tn{hskip}；\cs{@@_ulem_glue:n} 定义在 \pkg{xeCJKfntef} 里，
% 该宏包未加载时不存在，所以由 \pkg{xeCJKfntef} 在加载时改写这个入口，不能在
% 这里直接引用。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_use_ecglue_skip:
  { \skip_horizontal:N \l_@@_ecglue_skip }
\cs_new_protected:Npn \@@_check_for_ecglue_aux:
  {
    \xeCJK_if_last_node:nTF { CJK }
      { \use_i:nn }
      { \xeCJK_if_last_node:nTF { CJK-widow } }
      { \xeCJK_remove_node: \@@_use_ecglue_skip: }
      {
        \xeCJK_if_last_node:nTF { CJK-space }
          { \xeCJK_remove_node: \@@_use_ecglue_skip: }
          {
            \xeCJK_if_last_node:nTF { math }
              { \xeCJK_remove_node: }
              {
                \@@_boundary_if_last_math_space:TF
                  { \@@_boundary_math_space_consume: }
                  { \@@_glue_check_expire_stale: }
              }
          }
      }
  }
\dim_new:N \l_@@_interword_dim
\prg_new_conditional:Npnn \@@_skip_if_interword:N #1 { T , F , TF }
  {
    \skip_if_finite:nTF {#1}
      {
        \dim_set:Nn \l_@@_tmp_dim {#1}
        \skip_set:Nn \l_@@_tmp_skip { \c_xeCJK_space_skip_tl }
        \dim_set:Nn \l_@@_interword_dim { \l_@@_tmp_skip }
        \dim_compare:nNnTF
          { \l_@@_tmp_dim } = { \l_@@_interword_dim }
          {
            \dim_compare:nNnTF
              { \tex_glueshrink:D #1 } > \c_zero_dim
              { \prg_return_true: }
              { \prg_return_false: }
          }
          { \prg_return_false: }
      }
      { \prg_return_false: }
  }
\cs_new_protected:Npn \@@_recover_ecglue_source_space:
  {
    \bool_if:NTF \g_@@_glue_check_pending_bool
      {
        \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
        \@@_skip_if_interword:NTF \l_@@_last_skip
          {
            \tex_unskip:D
            \@@_recover_ecglue_source_space_aux:
          }
          { \@@_recover_ecglue_source_space_fallback: }
      }
      { \@@_recover_ecglue_source_space_fallback: }
  }
\cs_new_protected:Npn \@@_recover_ecglue_source_space_aux:
  {
    \xeCJK_if_last_node:nTF { CJK }
      { \@@_recover_ecglue_source_space_success: }
      {
        \xeCJK_if_last_node:nTF { CJK-widow }
          { \@@_recover_ecglue_source_space_success: }
          {
            \xeCJK_if_last_node:nTF { CJK-space }
              { \@@_recover_ecglue_source_space_success: }
              {
                \xeCJK_if_last_node:nTF { math }
                  {
                    \xeCJK_remove_node:
                    \@@_recover_ecglue_source_space_restore:
                    \bool_gset_false:N \g_@@_glue_check_pending_bool
                  }
                  {
                    \@@_boundary_if_last_math_space:TF
                      { \@@_boundary_math_space_consume: }
                      { \@@_recover_ecglue_source_space_restore: }
                  }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_recover_ecglue_source_space_success:
  {
    \xeCJK_remove_node:
    \@@_use_ecglue_skip:
    \bool_if:NT \g_@@_glue_check_pending_bool
      { \bool_gset_false:N \g_@@_glue_check_pending_bool }
  }
\cs_new_protected:Npn \@@_recover_ecglue_source_space_restore:
  {
    \skip_horizontal:N \l_@@_last_skip
  }
\cs_new_eq:NN \@@_recover_ecglue_source_space_fallback: \prg_do_nothing:
\cs_new_protected:Npn \@@_glue_check_expire_stale:
  {
    \int_compare:nNnT { \g_@@_boundary_capture_depth_int } = \c_zero_int
      {
        \int_compare:nNnT { \tex_lastnodetype:D } < \c_zero_int
          { \bool_gset_false:N \g_@@_glue_check_pending_bool }
      }
  }
\cs_new_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_ecglue:
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.0}{2016/05/08}{改进 \texttt{xCJKecglue} 的实现。}
%
% \begin{macro}{\@@_replace_space:}
% 将空格替换为 \tn{CJKecglue}。注意由 \tn{leaders} 等产生的 glue，并不能正确地还回去。
% 好在 \LaTeXe{} 中常用的 \tn{hrulefill} 和 \tn{dotfill} 定义末尾都有 |\kern\z@| 保护。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_replace_space:
  {
    \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
    \tex_unskip:D
    \xeCJK_if_last_node:nTF { CJK-space }
      { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip }
      {
        \xeCJK_if_last_node:nTF { CJK }
          {
            \skip_if_eq:nnTF
              { \l_@@_last_skip }
              { \c_xeCJK_space_skip_tl }
              { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip }
              { \skip_horizontal:N \l_@@_last_skip }
          }
          {
            \@@_boundary_if_last_math_space:TF
              {
                \@@_skip_if_interword:NTF \l_@@_last_skip
                  { \@@_boundary_math_space_consume: }
                  { \skip_horizontal:N \l_@@_last_skip }
              }
              { \skip_horizontal:N \l_@@_last_skip }
          }
      }
  }
\skip_new:N \l_@@_last_skip
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{def-bound}{}
%    \begin{macrocode}
\clist_map_inline:nn { Default , HalfRight }
  {
    \xeCJK_inter_class_toks:nnn {#1} { Boundary }
      {
        \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D
        \peek_meaning_remove:NTF \tex_italiccorrection:D
          {
            \tex_italiccorrection:D
            { \xeCJK_make_node:n { default } }
          }
          {
            \token_if_space:NTF \l_peek_token
              { \xeCJK_make_space_node: }
              { { \xeCJK_make_node:n { default } } }
          }
      }
    \xeCJK_pre_inter_class_toks:nnn {#1} { CJK } { \CJKecglue }
  }
%    \end{macrocode}
%
% \changes{v3.2.5}{2013/07/10}
% {修正 \texttt{CJK} 和 \texttt{NormalSpace} 字符类之间因为边界造成的间距不正确的问题。}
%
% \hypertarget{bound-ns}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { Boundary } { NormalSpace }
  { \xeCJK_Boundary_and_NormalSp: }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_Boundary_and_NormalSp:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Boundary_and_NormalSp:
  { \xeCJK_check_for_ecglue_normalsp: }
\cs_new_protected:Npn \@@_check_for_xecglue_normalsp:
  {
    \@@_if_last_glue:TF
      { \@@_replace_space: }
      { \@@_check_for_ecglue_normalsp: }
  }
\cs_new_protected:Npn \@@_check_for_ecglue_normalsp:
  {
    \xeCJK_if_last_node:nT { CJK-space }
      { \xeCJK_remove_node: \xeCJK_space_or_xecglue: }
  }
\cs_new_eq:NN \xeCJK_check_for_ecglue_normalsp:
              \@@_check_for_ecglue_normalsp:
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{ns-bound}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { NormalSpace } { Boundary }
  {
    \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D
    \peek_meaning_remove:NTF \tex_italiccorrection:D
      {
        \tex_italiccorrection:D
        { \xeCJK_make_node:n { normalspace } }
      }
      {
        \token_if_space:NTF \l_peek_token
          { \xeCJK_make_space_node: }
          { { \xeCJK_make_node:n { normalspace } } }
      }
  }
%    \end{macrocode}
%
% \hypertarget{bound-cjk}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { Boundary } { CJK }
  {
    \@@_boundary_capture_class:n { CJK }
    \xeCJK_check_for_glue:
    \xeCJK_class_group_begin:
    \xeCJK_clear_Boundary_and_CJK_toks:
    \xeCJK_select_font:
    \xeCJK_fallback_symbol:NN
    \CJKsymbol
  }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_check_for_glue:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_for_glue:
  {
    \@@_if_last_kern:TF
      { \@@_check_for_glue_auxi: }
      {
        \@@_if_last_glue:TF
          { \@@_check_for_glue_skip: }
          {
            \@@_if_last_math:TF
              { \xeCJK_remove_node: \@@_use_ecglue_skip: }
              {
                \@@_glue_check_expire_stale:
                \@@_check_for_glue_auxii:
              }
          }
      }
  }
%    \end{macrocode}
% \changes{v3.10.0}{2026/05/12}{新增 glue 分支处理，修复 \pkg{xeCJKfntef} 命令右侧空格问题。}
% \changes{v3.10.0}{2026/05/12}{当 glue 下方是 hlist（\tn{mbox} 等命令产生的 hbox）时，
%   不依赖 boolean，直接通过 \cs{g_@@_last_node_tl} 判断前方内容类型，
%   修复 \tn{mbox}、\tn{colorbox} 等 hbox 命令右侧多余 inter-word glue（\#831）。}
% \changes{v3.10.4}{2026/07/20}{由命令边界 framework 接管
%   \pkg{xeCJKfntef}、盒子命令和颜色命令，删除按全局 tl 猜测 hlist/whatsit
%   下方类别的旧回退；glue 分支现在只接受紧邻的 xeCJK marker（\#992）。}
% \changes{v3.10.4}{2026/07/20}{Boundary 到 CJK 的源码空格检查改用
%   与 Default 方向相同的 \cs{@@_skip_if_interword:N} 判断函数，自然宽度
%   不等于当前词间空格的显式 glue 不再被替换为 \tn{CJKglue}（\#996）。}
% 统一 framework 在已知命令出口重放 xeCJK marker，并通过
% \cs{g_@@_glue_check_pending_bool} 开启一次源码空格检查；显式分组的
% catcode 2 结束点也设置同一个布尔状态。若待检查的 glue 下方不是 kern marker，
% 本函数原样还回 glue 并进入标点检测链，不再根据任意 hlist、whatsit 或
% 可能陈旧的 \cs{g_@@_last_node_tl} 猜测输出类别。
%
% 这枚 glue 使用与 Boundary→Default 方向相同的
% \cs{@@_skip_if_interword:N} 判断函数检查：\pkg{listings} 等宏包在 CJK
% 字符间放置的 fil 级列对齐 glue 不是 finite；标题格式中 |\quad| 等
% 有意放置的间距没有 shrink 分量；显式 |\hskip| 的自然宽度通常不等于
% 当前词间空格。这三种情况都不进入源码空格分支。如果显式 glue 在节点
% 列表中与词间空格没有任何区别，\TeX{} 仍无法判断它的来源；需要原样
% 保留时，与 Default 方向一样，在前面加零宽 \tn{kern}。
%
% 本函数仅在 \cs{@@_if_last_glue:TF} 的 true 分支中调用，
% 因此入口处 \cs{tex_lastskip:D} 保证为非零 glue。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_for_glue_skip:
  {
    \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
    \@@_skip_if_interword:NTF \l_@@_last_skip
      {
        \tex_unskip:D
        \@@_if_last_kern:TF
          { \@@_check_for_glue_skip_kern: }
          { \@@_check_for_glue_skip_nonkern: }
      }
      { \@@_check_for_glue_skip_consume_and_fallback: }
  }
\cs_new_protected:Npn \@@_check_for_glue_skip_kern:
  {
    \@@_boundary_if_last_math_space:TF
      {
        \bool_if:NTF \g_@@_glue_check_pending_bool
          { \@@_boundary_math_space_to_CJK: }
          { \@@_check_for_glue_skip_restore: }
      }
      { \@@_check_for_glue_skip_kern_normal: }
  }
\cs_new_protected:Npn \@@_check_for_glue_skip_kern_normal:
  {
    \dim_compare:nNnTF \tex_lastkern:D = { \@@_node:n { math } }
      {
        \bool_if:NTF \g_@@_glue_check_pending_bool
          {
            \xeCJK_remove_node:
            \bool_gset_false:N \g_@@_glue_check_pending_bool
            \bool_if:NTF \l_@@_xecglue_bool
              { \skip_horizontal:N \l_@@_ecglue_skip }
              { \skip_horizontal:N \l_@@_last_skip }
          }
          { \@@_check_for_glue_skip_restore: }
      }
      {
        \bool_if:NTF \g_@@_glue_check_pending_bool
          {
            \bool_gset_false:N \g_@@_glue_check_pending_bool
            \dim_case:nnF { \tex_lastkern:D }
              {
                { \@@_node:n { CJK } }
                {
                  \xeCJK_remove_node:
                  \bool_if:NTF \l_@@_reserve_space_bool
                    { \skip_horizontal:N \l_@@_last_skip }
                    { \CJKglue }
                }
                { \@@_node:n { CJK-space } }
                { \xeCJK_remove_node: \@@_ccglue_or_space: }
                { \@@_node:n { CJK-widow } }
                  {
                    \xeCJK_remove_node:
                    \xeCJK_widow_penalty:
                    \bool_if:NTF \l_@@_reserve_space_bool
                      { \skip_horizontal:N \l_@@_last_skip }
                      { \CJKglue }
                  }
              }
              { \@@_check_for_glue_skip_restore: }
          }
          { \@@_check_for_glue_skip_restore: }
      }
  }
\cs_new_protected:Npn \@@_check_for_glue_skip_nonkern:
  {
    \bool_if:NT \g_@@_glue_check_pending_bool
      { \bool_gset_false:N \g_@@_glue_check_pending_bool }
    \@@_if_last_math:TF
      {
        \bool_if:NTF \l_@@_xecglue_bool
          { \skip_horizontal:N \l_@@_ecglue_skip }
          { \skip_horizontal:N \l_@@_last_skip }
      }
      { \@@_check_for_glue_skip_restore: }
  }
\cs_new_protected:Npn \@@_check_for_glue_skip_restore:
  {
    \skip_horizontal:N \l_@@_last_skip
    \@@_check_for_glue_auxii:
  }
\cs_new_protected:Npn \@@_check_for_glue_skip_consume_and_fallback:
  {
    \bool_if:NT \g_@@_glue_check_pending_bool
      { \bool_gset_false:N \g_@@_glue_check_pending_bool }
    \@@_check_for_glue_auxii:
  }
\cs_new_protected:Npn \@@_check_for_glue_auxi:
  {
    \dim_case:nn { \tex_lastkern:D }
      {
        { \@@_node:n { CJK } }
        { \xeCJK_remove_node: \CJKglue }
        { \@@_node:n { CJK-space } }
        { \xeCJK_remove_node: \@@_ccglue_or_space: }
        { \@@_node:n { CJK-widow } }
        { \xeCJK_remove_node: \xeCJK_widow_penalty: \CJKglue }
        { \@@_node:n { default } }
        { \xeCJK_remove_node: \@@_use_ecglue_skip: }
        { \@@_node:n { math } }
        { \xeCJK_remove_node: \@@_use_ecglue_skip: }
        { \@@_node:n { math-space } }
        { \@@_boundary_math_space_to_CJK: }
        { \@@_node:n { math-space-frozen } }
        { \@@_boundary_math_space_to_CJK: }
      }
  }
\cs_new_protected:Npn \@@_check_for_glue_auxii:
  {
    \xeCJK_if_last_punct:TF
      { \@@_check_for_glue_auxiii: }
      { \xeCJK_check_for_xglue: }
  }
\cs_new_protected:Npn \@@_check_for_glue_auxiii:
  {
    \bool_if:NT \l_@@_last_penalty_bool
      { \tex_penalty:D \l_@@_last_penalty_int }
    \skip_horizontal:N \l_@@_last_skip
    \tl_if_eq:NNF \l_@@_aligni_tl \c_@@_left_tl { \CJKglue }
  }
\cs_new_eq:NN \xeCJK_check_for_xglue: \prg_do_nothing:
\cs_new_protected:Npn \@@_check_for_xglue:
  {
    \@@_if_last_glue:T
      {
        \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
        \tex_unskip:D
        \xeCJK_if_last_node:nTF { CJK-space }
          { \xeCJK_remove_node: \@@_ccglue_or_space: }
          {
            \xeCJK_if_last_node:nTF { default-space }
              { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip }
              { \@@_check_for_xglue_aux: }
          }
      }
  }
\cs_new_protected:Npn \@@_check_for_xglue_aux:
  {
    \skip_if_eq:nnTF
      { \l_@@_last_skip }
      { \c_xeCJK_space_skip_tl }
      {
        \xeCJK_if_last_node:nTF { CJK }
          { \xeCJK_remove_node: \@@_ccglue_or_space: }
          {
            \xeCJK_if_last_node:nTF { default }
              { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip }
              {
                \xeCJK_if_last_node:nTF { math }
                  { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip }
                  {
                    \@@_if_last_math:TF
                      { \skip_horizontal:N \l_@@_ecglue_skip }
                      { \skip_horizontal:N \l_@@_last_skip }
                  }
              }
          }
      }
      { \skip_horizontal:N \l_@@_last_skip }
  }
\cs_new_protected:Npn \@@_ccglue_or_space:
  { \CJKglue }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.9.1}{2022/08/02}{简化部分内部实现。}
% \changes{v3.10.0}{2022/08/31}{修复 \opt{xCJKecglue} 错误。}
% \changes{v3.10.0}{2024/05/03}{修复 \tn{xeCJKnobreak} 错误。}
% \changes{v3.10.0}{2026/04/27}{修复 \tn{textcolor} 等 whatsit 节点导致 \texttt{CJKecglue} 丢失的问题。}
% \changes{v3.10.0}{2026/04/27}{修复字体切换组（如 \tn{texttt}、\tn{zihao}）导致 \texttt{CJKecglue} 使用错误字体度量的问题。}
%
% \begin{macro}[TF]{
%   \@@_if_last_none:,
%   \@@_if_last_hlist:,
%   \@@_if_last_math:,
%   \@@_if_last_glue:,
%   \@@_if_last_kern:,
%   \@@_if_last_penalty:}
% 一些 \hologo{eTeX} 结点判定函数。
%    \begin{macrocode}
\group_begin:
\cs_set:Npn \@@_tmp:nn #1
  {
    \exp_args:Ncc \@@_tmp_aux:NNn
      { @@_if_last_ #1 : }
      { c_@@_ #1 _node }
  }
\cs_set:Npn \@@_tmp_aux:NNn #1#2#3
  {
    \int_const:Nn #2 {#3}
    \prg_new_conditional:Npnn #1 { T , F , TF }
      {
        \if_int_compare:w \tex_lastnodetype:D = #2
          \prg_return_true: \else: \prg_return_false: \fi:
      }
  }
\@@_tmp:nn { none }    { -1 }
\@@_tmp:nn { hlist }   {  1 }
\@@_tmp:nn { math }    { 10 }
\@@_tmp:nn { glue }    { 11 }
\@@_tmp:nn { kern }    { 12 }
\@@_tmp:nn { penalty } { 13 }
\@@_tmp:nn { whatsit } {  9 }
\group_end:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF,int]{\xeCJK_if_last_node:n}
%    \begin{macrocode}
 \prg_new_conditional:Npnn \xeCJK_if_last_node:n #1 { p , T , F , TF }
  {
    \if_dim:w
      \cs_if_exist_use:cTF { c_@@_#1_node_dim }
        { = \tex_lastkern:D }
        { \use:c { c_@@_#1_node_skip } = \tex_lastskip:D }
      \prg_return_true: \else: \prg_return_false: \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.1.0}{2012/11/14}{删除多余的 \texttt{default-itcorr} 结点。}
% \changes{v3.2.4}{2013/07/03}{尽量移除用作判断标志的 \tn{kern}。}
% \changes{v3.10.0}{2026/04/03}{改正拼写错误 \cs{@@_glue_node:n}。}
%
% \begin{macro}[int]{\xeCJK_declare_node:n,\xeCJK_make_node:n}
% 用于判断插入的各种 |kern| 和 |glue|。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_declare_node:n #1
  {
    \int_gincr:N \g_@@_node_int
    \dim_if_exist:cTF { c_@@_#1_node_dim }
      { \dim_gset:cn } { \dim_const:cn }
      { c_@@_#1_node_dim } { \g_@@_node_int sp }
  }
\cs_new_protected:Npn \xeCJK_declare_glue_node:n #1
  {
    \int_gincr:N \g_@@_node_int
    \skip_if_exist:cTF { c_@@_#1_node_skip }
      { \skip_gset:cn } { \skip_const:cn }
      { c_@@_#1_node_skip } { \g_@@_node_int sp }
  }
\int_new:N \g_@@_node_int
\int_gset:Nn \g_@@_node_int { 10 }
\tl_new:N \g_@@_last_node_tl
\cs_new_protected:Npn \xeCJK_make_node:n #1
  {
    \exp_args:Nc \@@_make_node:N { c_@@_#1_node_dim }
    \tl_gset:Nn \g_@@_last_node_tl {#1}
  }
\cs_new:Npn \@@_node:n #1
  { \use:c { c_@@_#1_node_dim } }
\cs_new:Npn \@@_glue_node:n #1
  { \use:c { c_@@_#1_node_skip } }
\cs_new_protected:Npn \@@_make_node:N #1
  {
    \tex_kern:D - #1
    \tex_kern:D   #1
  }
\cs_new_protected:Npn \xeCJK_remove_node:
  {
    \@@_if_last_kern:TF
      { \tex_unkern:D \tex_unkern:D }
      {
        \@@_if_last_glue:T
          { \tex_unskip:D \tex_unskip:D }
      }
    \tl_gclear:N \g_@@_last_node_tl
  }
\xeCJK_declare_node:n { CJK }
\xeCJK_declare_node:n { CJK-space }
\xeCJK_declare_node:n { default }
\xeCJK_declare_node:n { CJK-widow }
\xeCJK_declare_node:n { normalspace }
\xeCJK_declare_glue_node:n { default-space }
\xeCJK_declare_node:n { math }
\xeCJK_declare_node:n { math-space }
\xeCJK_declare_node:n { math-space-frozen }
%    \end{macrocode}
% \changes{v3.10.0}{2026/05/12}{新增 \cs{g_@@_glue_check_pending_bool}
%   用于标记 \pkg{xeCJKfntef} 组结束后的首次 glue 探测。}
% \changes{v3.10.0}{2026/05/13}{新增 \cs{g_@@_reset_color_pending_bool}
%   用于标记 \tn{reset@color} 在 hlist 上下文中触发，延迟到
%   \cs{@@_check_for_glue_skip:} 中处理 \tn{colorbox} 等命令
%   右侧的 color pop whatsit（\#831）。}
% \changes{v3.10.4}{2026/07/20}{删除颜色专用 pending boolean；颜色
%   push/pop 和盒子输出改由 transparent/wrapped-box capture 处理（\#992）。}
%    \begin{macrocode}
\bool_new:N \g_@@_glue_check_pending_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{
%   \@@_boundary_capture_class:n,
%   \@@_boundary_if_math_head:n,
%   \@@_boundary_if_math_tail:n,
%   \@@_boundary_if_math_tail_space:n,
%   \@@_boundary_if_math_edge:n,
%   \@@_boundary_if_last_math_node:,
%   \@@_boundary_math_begin:,
%   \@@_boundary_math_begin_visible:,
%   \@@_boundary_math_end:n,
%   \@@_boundary_math_end_confirm_node:,
%   \@@_boundary_math_end_confirm_space:,
%   \@@_boundary_math_end_confirm_space_visible:,
%   \@@_boundary_math_end_confirm:,
%   \@@_boundary_capture_report_first:n,
%   \@@_boundary_capture_allocate:n,
%   \@@_boundary_capture_suspend:,
%   \@@_boundary_capture_resume:,
%   \@@_boundary_capture_begin:,
%   \@@_boundary_inline_box_begin:,
%   \@@_boundary_inline_box_end:n,
%   \@@_boundary_inline_last_box_begin:,
%   \@@_boundary_inline_last_box_end:n,
%   \@@_boundary_inline_stream_begin:n,
%   \@@_boundary_inline_stream_begin:nn,
%   \@@_boundary_inline_stream_end:n,
%   \@@_boundary_hmode_transparent_begin:,
%   \@@_boundary_hmode_transparent_end:,
%   \@@_boundary_post_transparent:,
%   \@@_boundary_post_transparent_relocate:,
%   \@@_boundary_post_transparent_relocate_glue:,
%   \@@_boundary_box_begin:,
%   \@@_boundary_box_open:N,
%   \@@_boundary_box_end:n,
%   \@@_boundary_box_finish:nn,
%   \@@_boundary_if_capture_box_sized:,
%   \@@_boundary_if_capture_box_visible:,
%   \@@_boundary_box_set_last_from_node:,
%   \@@_boundary_last_box_end:n,
%   \@@_boundary_register_makeboxes:,
%   \@@_boundary_register_box:nn,
%   \@@_boundary_register_wrapped_box:nn,
%   \@@_boundary_register_transparent:n,
%   \@@_boundary_register_post_transparent:n,
%   \@@_boundary_register_stream:nn,
%   \@@_boundary_reserve:n,
%   \@@_boundary_reserve:N,
%   \@@_boundary_if_registered:n,
%   \@@_boundary_user_register:n,
%   \@@_boundary_user_register_command:n,
%   \@@_boundary_user_register_strategy:n,
%   \@@_boundary_user_register_mode:n,
%   \@@_boundary_user_register_validate:,
%   \@@_boundary_user_register_store:,
%   \@@_boundary_user_register_apply:,
%   \@@_boundary_user_register_apply:nn,
%   \@@_boundary_user_register_check:,
%   \@@_boundary_prepare_onearg_box:N,
%   \@@_boundary_prepare_onearg_box:NN,
%   \@@_boundary_wrap_transparent_noarg:NN,
%   \@@_boundary_wrap_transparent_onearg:NN,
%   \@@_boundary_wrap_transparent_onearg_braced:NN}
% \begin{macro}[int]{
%   \@@_boundary_capture_space:,
%   \@@_boundary_capture_emit_left:nn,
%   \@@_boundary_pop_node:N,
%   \@@_boundary_box_end_transparent:n,
%   \@@_boundary_emit_left:nn,
%   \@@_boundary_emit_left:nnn,
%   \@@_boundary_emit_left_math_space:nnn,
%   \@@_boundary_emit_left_normal:nnn,
%   \@@_boundary_use_glue:nn,
%   \@@_boundary_use_ecglue:n,
%   \@@_boundary_use_ccglue:n,
%   \@@_boundary_restore_space:,
%   \@@_boundary_restore_space:n,
%   \@@_boundary_set_last_from_node:n,
%   \@@_boundary_set_math_tail_from_capture:,
%   \@@_boundary_if_last_math_space:,
%   \@@_boundary_math_space_consume:,
%   \@@_boundary_math_space_set_actual:n,
%   \@@_boundary_math_space_encode:,
%   \@@_boundary_math_space_decode:,
%   \@@_boundary_math_space_set_delta:n,
%   \@@_boundary_math_space_expire:,
%   \@@_boundary_math_space_to_CJK:,
%   \@@_boundary_enable_source_space:n,
%   \@@_boundary_replay_node:n,
%   \@@_boundary_replay_before_math_space:,
%   \@@_boundary_replay_before:}
% \changes{v3.10.4}{2026/07/20}{新增命令边界 capture/register 框架，按实际
%   可见输出的首尾类别统一处理盒子、不透明节点流和无可见输出命令（\#992）。}
% \changes{v3.10.4}{2026/07/20}{box/wrapped-box 捕获在盒子直接排出了
%   可见内容（宽度非零、高度或深度非零，且末节点为 char/rule/math/kern）
%   而未观察到任何字符类别时，按 Default 首尾重建边界；math 与 \tn{vrule}
%   等不触发 interchar 转换的可见内容不再被误判为“无可见输出”。已经排好
%   再放入的盒子与只用于留白的盒子仍按无可见输出处理（\#998）。}
% \changes{v3.10.4}{2026/07/21}{重放 Default marker 时按外层列表同步
%   \tn{spacefactor}；post-transparent 同时搬移 marker 与其后的候选 glue，
%   修复 \opt{xCJKecglue} 启用后盒内末尾大写字母和 \tn{null} 遮住边界后缀
%   时的右边界间距（\#1003）。}
% \changes{v3.10.4}{2026/07/21}{新增独立的行内公式边界类别，分别处理公式
%   末尾与公式后已有参数内源码空格，使注册命令能按直接公式的源码空格语义
%   恢复左右边界；另行区分仍参与外层断行的 stream 空格与已冻结在盒子中的
%   空格，正确保留弹性间距的伸缩量；不可见节点将普通 stream 的真实空格
%   与 marker 隔开时按直接公式语义让 marker 过期，并保留中间 glue 与节点
%   的原有次序（\#1002）。}
% \changes{v3.10.4}{2026/07/22}{新增实验性
%   \opt{experiment/boundary-register} 接口，允许专家用户把自定义命令注册到
%   统一命令边界框架（\#1010）。}
%
% 命令边界框架提供五类注册策略。|box| 策略在注册命令执行前保存并移除前一
% 可见字符留下的 marker 和可选源码空格，命令结束后取出其末尾 hbox，重建
% 边界后原样放回；|wrapped-box| 用透明 hbox 收集会直接写出多个节点的盒子命令。
% |stream| 策略让 annotation、数学包装或 verbatim 流直接写入当前列表；
% 其 |default| 变体固定两端为 Default，|first-default| 只固定首类别、
% 仍以实际输出决定末类别。
% |transparent| 策略用于只插入锚点、write 等无可见输出的命令。
% 其中 \tn{null} 还能合法地出现在 \tn{setbox}\meta{box} 后，不能在其前面插入
% hook 代码，故使用 |post-transparent| after-only 变体：只在宽、高、深均为零的
% 盒子确实盖住 marker 或“marker + 候选 glue”时，以列表中的真实 marker 为
% 证据，把这一边界后缀移到盒子后；其下方没有 marker 时按原顺序还原 glue
% 与盒子，不能把任意 glue 搬过 hbox。
% Boundary 到 Default/CJK 的转换会把观察到的类别同时写入所有活跃 capture，
% 因而混合输出和嵌套命令都能分别取得实际首、尾类别。
%
% capture 结束时，框架按“前类别 × 首类别 × 是否有源码空格”重建左边界，
% 再按实际末类别重放 marker 供右边界使用；无可见输出时则完整恢复入口状态。
% Default 字符在盒子内部设置的 \tn{spacefactor} 不会传播到外层列表，故重放
% Default 类 marker 时还要把源码空格检查使用的值同步为外层列表当前的
% \tn{spacefactor}；stream 内容会直接更新外层值，因此使用同一条同步路径。
% “无可见输出”由观察类别、盒子尺寸与末节点类型共同判定。\tn{vrule}
% 不触发 interchar 转换；若盒子宽度非零、高度或深度非零，且末节点是这个
% 盒子直接排出的 char、rule 或标点处理添加的 kern，box/wrapped-box 就按
% Default 首尾重建边界。行内公式另用 |math| 类别：stream 和 box 的正文
% 以 |$|、\tn{(} 或 \tn{ensuremath} 开始时，参数适配器必须在 math-on
% 节点之前报告首类别；正文语法以相应公式结束时只产生候选，正文实际排完后
% 还要由末尾 math 节点或 marker 确认，才把 |math| 记为当前 capture 的
% 确定末类别。未知宏若把公式记号或分组当作参数消费，实际节点检查不会通过。
% capture 结束时以这个明确的正文末类别覆盖命令内部留下的旧 marker，
% 不把任意内部 math 节点猜成可见公式。这样“CJK 前缀
% + 公式后缀”和外层分组中的公式都能重放正确的右边界，同时嵌套命令只把
% 结果逐层写回其实际输出列表，不会把离线 \tn{setbox} 中的公式直接认作
% 外层可见输出。显式公式入口本身只处理当前列表，不把事件广播到外层
% capture，因此不会改变裸公式和离线测量盒子的节点列表。
%
% 若公式后还有参数内尾随源码空格，框架保留这枚空格在命令、框线或装饰范围
% 内的原位置。普通 stream 在外层重放 |math-space| marker，并随 marker 保存
% 这枚空格的伸缩量；box、wrapped-box 和 stream-ulem 则重放
% |math-space-frozen|，表示空格已被冻结在内部盒子中。右侧为 CJK 且
% \opt{xCJKecglue} 启用时，前者从补偿量中扣除实际仍参与外层断行的伸缩量；
% 后者完整保留 \tn{CJKecglue} 的伸缩量，并禁止用负的自然宽度把后续文字
% 拉入框线或装饰范围。命令外紧接的另一枚源码空格按连续源码空格处理，
% 显式 glue 则仍保留。ulem 会先把源码空格改造成 leaders，所以它的参数
% 适配器在交出尾随空格前确认公式节点，再把空格交还 ulem 排版。若
% transparent 或 post-transparent 命令把 special、零尺寸盒子等不可见节点
% 插到普通 stream 的真实空格与 marker 之间，直接公式的恢复链也会被同一
% 节点截断；框架因此让 marker 过期，不在节点之后单独增加补偿 glue；若
% marker 与节点之间还有显式 glue，也把它恢复到节点之前的原位置。
% 宽、高、深均为零的盒子（\tn{null}、
% 空 \tn{mbox}）、只用于留白的盒子（空 \tn{makebox}、strut、零高 rule），
% 以及末尾放着另一个已经排好的盒子或填充 glue 的命令（例如
% \tn{makebox} 放入 \tn{box}，capture 无法知道其中的字符类别），都按
% “没有观察到可见输出”处理，并恢复进入命令前的状态。
% 源码空格、\tn{CJKecglue}、\tn{CJKglue} 及 \opt{CJKspace}、
% \opt{xCJKecglue} 状态都在入口字体和分组上下文中缓存，避免命令内部字体
% 或选项切换改变恢复值。\tn{sbox} 这类只构造离线盒子而不向当前列表输出的
% 路径会暂停观察，并按嵌套层保存、恢复外层 marker 与 pending 状态，防止
% 测量用 scratch box 或其中的颜色切换污染外层 capture 和基础恢复链。
% ulem 这类会把正文拆进固定宽度盒子的子系统使用专用 stream kind；框架仍
% 统一决定应发哪一种左侧 glue，但把数值交回子系统自己的外层 glue 通道，
% 避免弹性分量被困在内部固定宽度盒子中。
%
% 额外 hbox 只用于 \tn{color@b@x} 这类本来会输出多个节点的注册命令；普通
% 盒子命令取出并放回原 hbox，不增加节点层级。每个同步嵌套层使用独立 register，
% 避免外层盒子赋值尚未结束时被内层 capture 覆写；常用的前三层在加载时预分配，
% 更多层则随首次达到的 depth 惰性创建。若 wrapped-box 没有观察到可见字符，
% 结束路径会 unpack 该层透明外层盒子。每层的 token-list 状态同样惰性分配，使
% 注册命令可以任意嵌套而不会静默退回未修复行为；首类别会写入所有尚未取得
% 首字符的外层 capture。box 结束时会读取自身节点列表末尾由框架写入的
% marker，并据此更新末类别；嵌套盒子的推断结果便能逐层传到最外面。
% stream 结束时也以外层节点列表末尾的 marker 为准；
% annotation/math 等没有末尾 marker 的流仍使用运行时观察结果。
% 每层字段在 \cs{@@_boundary_capture_begin:} 中按同一 depth 连续完成 clear、
% 求值与写入。当前不把 flag 序列化单独抽成 helper：它仍需接收动态 depth
% 和不同字段名，只会把可见的状态初始化拆散；若以后继续增加每层字段，再把
% allocate、initialize、restore 一并评估为 record/helper 抽象。
%
% 实验性用户注册接口先把控制序列名、策略和类别模式写入独立属性表，不立即
% 修改命令。通用命令 hook 记录在注册表中；必须直接重定义扫描器或内部排版
% 入口的专用适配器另写入保留表。用户声明在保存时和导言区末尾应用时都查询
% 这两张表，不能把通用 hook 叠加到已有专用适配器外层。随后再调用同一组
% 五种注册函数；LaTeX 在普通 \tn{AtBeginDocument} 代码执行完以后安装命令
% hook。用户接口因此不复制恢复算法，也不改变内建注册的优先级。正文开始前
% 再检查成功登记的控制序列是否存在，避免 LaTeX 对未定义命令的延迟 hook
% 静默失效。
%    \begin{macrocode}
\int_new:N \g_@@_boundary_capture_depth_int
\int_new:N \g_@@_boundary_suspend_depth_int
\seq_new:N \g_@@_boundary_active_seq
\prop_new:N \g_@@_boundary_registered_prop
\prop_new:N \g_@@_boundary_reserved_prop
\prop_new:N \g_@@_boundary_user_registered_prop
\seq_new:N \g_@@_boundary_user_applied_seq
\bool_new:N \g_@@_boundary_user_register_closed_bool
\bool_new:N \l_@@_boundary_user_invalid_bool
\bool_new:N \l_@@_boundary_user_mode_given_bool
\tl_new:N \l_@@_boundary_user_name_tl
\tl_new:N \l_@@_boundary_user_strategy_tl
\tl_new:N \l_@@_boundary_user_mode_tl
\tl_new:N \l_@@_boundary_user_value_tl
\seq_new:N \l_@@_boundary_user_value_seq
\box_new:c { g_@@_boundary_capture_1_box }
\box_new:c { g_@@_boundary_capture_2_box }
\box_new:c { g_@@_boundary_capture_3_box }
\box_new:N \l_@@_boundary_post_box
\tl_new:N \l_@@_boundary_node_tl
\tl_new:N \l_@@_boundary_first_tl
\tl_new:N \l_@@_boundary_last_tl
\tl_new:N \l_@@_boundary_active_tl

\cs_new_protected:Npn \@@_boundary_capture_allocate:n #1
  {
    \box_if_exist:cF { g_@@_boundary_capture_#1_box }
      { \box_new:c { g_@@_boundary_capture_#1_box } }
    \tl_if_exist:cF { g_@@_boundary_capture_#1_before_tl }
      {
        \tl_new:c { g_@@_boundary_capture_#1_before_tl }
        \tl_new:c { g_@@_boundary_capture_#1_before_math_space_skip_tl }
        \tl_new:c { g_@@_boundary_capture_#1_first_tl }
        \tl_new:c { g_@@_boundary_capture_#1_last_tl }
        \tl_new:c { g_@@_boundary_capture_#1_kind_tl }
        \tl_new:c { g_@@_boundary_capture_#1_space_skip_tl }
        \tl_new:c { g_@@_boundary_capture_#1_ecglue_skip_tl }
        \tl_new:c { g_@@_boundary_capture_#1_ccglue_skip_tl }
        \tl_new:c { g_@@_boundary_capture_#1_space_flag_tl }
        \tl_new:c { g_@@_boundary_capture_#1_reserve_space_flag_tl }
        \tl_new:c { g_@@_boundary_capture_#1_xecglue_flag_tl }
        \tl_new:c { g_@@_boundary_capture_#1_math_tail_flag_tl }
        \tl_new:c { g_@@_boundary_capture_#1_math_tail_space_flag_tl }
        \tl_new:c { g_@@_boundary_capture_#1_math_tail_space_skip_tl }
      }
  }
\@@_boundary_capture_allocate:n { 1 }
\@@_boundary_capture_allocate:n { 2 }
\@@_boundary_capture_allocate:n { 3 }

\cs_new_protected:Npn \@@_boundary_capture_class:n #1
  {
    \int_compare:nNnT
      { \g_@@_boundary_capture_depth_int } > \c_zero_int
      {
        \int_compare:nNnT
          { \g_@@_boundary_suspend_depth_int } = \c_zero_int
          {
            \int_step_inline:nn { \g_@@_boundary_capture_depth_int }
              {
                \tl_gset:cn { g_@@_boundary_capture_##1_last_tl } {#1}
                \tl_if_empty:cT { g_@@_boundary_capture_##1_first_tl }
                  {
                    \tl_gset:cn
                      { g_@@_boundary_capture_##1_first_tl } {#1}
                    \tl_if_eq:cnTF
                      { g_@@_boundary_capture_##1_kind_tl } { stream }
                      { \@@_boundary_capture_emit_left:nn {##1} {#1} }
                      {
                        \tl_if_eq:cnT
                          { g_@@_boundary_capture_##1_kind_tl } { stream-ulem }
                          { \@@_boundary_capture_emit_left:nn {##1} {#1} }
                      }
                  }
              }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_begin:
  {
    \mode_if_horizontal:T
      {
        \scan_stop:
        \@@_boundary_pop_node:N \l_@@_boundary_node_tl
        \str_case:Vn \l_@@_boundary_node_tl
          {
            { CJK }
              { \CJKecglue }
            { CJK-space }
              { \xeCJK_space_or_xecglue: }
            { CJK-widow }
              { \xeCJK_widow_penalty: \CJKecglue }
            { default-space }
              { \xeCJK_space_glue: }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_begin_visible:
  {
    \@@_boundary_capture_class:n { math }
    \@@_boundary_math_begin:
  }

\tl_new:N \l_@@_boundary_math_tl
\tl_new:N \l_@@_boundary_math_type_tl
\skip_new:N \l_@@_boundary_math_skip
\skip_new:N \l_@@_boundary_math_actual_skip
\skip_new:N \l_@@_boundary_math_ecglue_skip
\skip_new:N \l_@@_boundary_math_delta_skip
\dim_new:N \l_@@_boundary_math_space_dim
\dim_new:N \l_@@_boundary_math_ecglue_dim
\dim_new:N \l_@@_boundary_math_stretch_dim
\dim_new:N \l_@@_boundary_math_shrink_dim
%    \end{macrocode}
% \changes{v3.10.5}{2026/08/03}{boundary 判断前把参数副本中 catcode 4 的
%   \texttt{\&} 换成 \cs{scan_stop:}，修复 \tn{colorbox} 等命令在 \tn{halign}
%   语境下参数含 |&| 时编译报错的问题（\#1043）。}
%
% \cs{@@_boundary_math_set:n} 把待判断的参数存入 \cs{l_@@_boundary_math_tl}，
% 并把其中 catcode 4 的 |&| 换成 \cs{scan_stop:}。
%
% 触发条件要分清两件事：|&| 在 \LaTeX{} 下\emph{默认}就是 catcode 4
% （\file{latex.ltx}），并非 \tn{halign} 把它设成 4；真正要紧的是\textbf{扫描发生的
% 位置}——当 \cs{tl_if_head_eq_meaning:nNTF} 等 \pkg{expl3} 条件式在\emph{对齐环境内}
% （\env{eqnarray}、\env{align}、\env{tabular}）读取参数时，catcode 4 的 |&| 会终止
% 它正在读的宏参数，报
% \enquote{Argument of \cs{__tl_tl_head:w} has an extra \}}（\#1043）。
% 同一个 token list 在对齐环境\emph{之外}走同一条件式实测 0 错误，所以这不是
% 「\pkg{expl3} 条件式对 catcode-4 记号的固有限制」，而是对齐符在对齐环境里的
% 参数终止语义。
% 这里只是为\emph{语法判断}准备副本，实际排版仍使用原始参数，所以替换不影响
% 输出；换成 \cs{scan_stop:} 而不是直接删除，是为了保住 |&| 的位置，
% 使 |&$x$| 这类参数仍被正确判定为「首项不是公式」。
%
% 两条范围说明，避免后人误以为漏改：
% \begin{itemize}
%   \item \cs{tl_replace_all:NVn} 不进入嵌套花括号组，|{ a { x & y } b }| 只得到
%     |a{x&y}b|。这是够用的：只有\emph{顶层}对齐符会破坏 \pkg{expl3} 条件式的括号组
%     扫描（实测组内含 catcode-4 |&| 时，裸 \cs{tl_if_head_eq_meaning:nNTF}、
%     \cs{tl_reverse:N}、\cs{tl_head:n}、\cs{tl_tail:n} 均不报错），而
%     \cs{@@_boundary_if_math_head:n} 递归进入组时会重新调用自身、再次经过本函数，
%     下一层的 |&| 到那时才需要消解。
%   \item \cs{@@_boundary_if_math_tail_space:n} 不走本函数，因为它只做
%     \cs{tl_trim_right_spaces:n} 与 \cs{tl_if_eq:NNTF}，不做 head/tail 记号扫描，
%     参数含 catcode-4 |&| 时实测 0 错误。需要消解的只有做扫描的
%     \cs{@@_boundary_if_math_head:n} 与 \cs{@@_boundary_if_math_tail:n} 两个入口。
% \end{itemize}
%
% \cs{tl_replace_all:Nnn} 及其变体（这里用的是 \cs{tl_replace_all:NVn}）按 catcode
% 与 charcode 双重相等匹配，所以模式里的 |\&|
% 必须是 catcode 4 的对齐符。在本文件里直接写 |{ & }| 恰好可用，但原因只是
% \begingroup\bfseries\LaTeX{} 环境默认把 |&| 设为 4（\file{latex.ltx}：
% |\catcode`\&=4|），而 \tn{ExplSyntaxOn} 不改 38\endgroup（实测它把 9、32 设为 9，
% 58、95 设为 11，126 设为 10，而 38 前后都是 4）。注意
% \cs{c_code_cctab} 之类的 catcode 表\emph{常量}与此无关：它只在被
% \cs{cctab_select:N} 选中时才生效，本包从不选它。
%
% 关键在于\textbf{字面模式的类别在本文件被读取（tokenise）时就冻结了}，与调用时的
% \tn{catcode} 无关——加载后再改 |&| 的类别，对字面写法和常量写法都没有影响。
% 真正的风险是\emph{加载期}的 régime：若 \tn{usepackage} 之前外层做过
% |\catcode`\&=12|（或在某个非默认 catcode 表下 \tn{input} 本包），字面 |{ & }| 读进来
% 就不是对齐符，此后一律静默失配。\cs{c_@@_alignment_tl} 由
% \cs{char_set_catcode_alignment:N} 在局部组里现场生成，与读取时的 régime 无关，
% 防住的正是这一种情形。
%    \begin{macrocode}
\group_begin:
  \char_set_catcode_alignment:N \&
  \tl_const:Nn \c_@@_alignment_tl { & }
\group_end:

\cs_new_protected:Npn \@@_boundary_math_set:n #1
  {
    \tl_set:Ne \l_@@_boundary_math_tl
      { \tl_trim_spaces:n {#1} }
    \tl_replace_all:NVn \l_@@_boundary_math_tl
      \c_@@_alignment_tl { \scan_stop: }
  }

\prg_new_protected_conditional:Npnn \@@_boundary_if_math_head:n #1
  { T , F , TF }
  {
    \@@_boundary_math_set:n {#1}
    \tl_if_empty:NTF \l_@@_boundary_math_tl
      { \prg_return_false: }
      {
        \exp_args:NV \tl_if_head_is_group:nTF
          \l_@@_boundary_math_tl
          {
            \exp_args:Ne \@@_boundary_if_math_head:nTF
              { \tl_head:V \l_@@_boundary_math_tl }
              { \prg_return_true: }
              { \prg_return_false: }
          }
          {
            \exp_args:NV \tl_if_head_eq_meaning:nNTF
              \l_@@_boundary_math_tl $
              { \prg_return_true: }
              {
                \exp_args:NV \tl_if_head_eq_meaning:nNTF
                  \l_@@_boundary_math_tl \(
                  { \prg_return_true: }
                  {
                    \exp_args:NV \tl_if_head_eq_meaning:nNTF
                      \l_@@_boundary_math_tl \ensuremath
                      { \prg_return_true: }
                      { \prg_return_false: }
                  }
              }
          }
      }
  }

\prg_new_protected_conditional:Npnn \@@_boundary_if_math_tail:n #1
  { T , F , TF }
  {
    \@@_boundary_math_set:n {#1}
    \tl_reverse:N \l_@@_boundary_math_tl
    \exp_args:NV \@@_boundary_if_math_tail_reversed:nTF
      \l_@@_boundary_math_tl
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \cs{tl_reverse:N} 只反转顶层项目，保留花括号组内的次序。末项目为组时，
% 先识别 \tn{ensuremath} 的参数，否则递归检查组内末尾。所有语法判断都只
% 产生候选：未知宏也可能把末尾的分组、|$| 或 \tn{)} 当作参数消费，
% \cs{@@_boundary_math_end:n} 还要用当前列表的实际 math 节点或 marker
% 确认，才会发布末类别。
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn
  \@@_boundary_if_math_tail_reversed:n #1 { T , F , TF }
  {
    \tl_if_empty:nTF {#1}
      { \prg_return_false: }
      {
        \tl_if_head_eq_meaning:nNTF {#1} $
          { \prg_return_true: }
          {
            \tl_if_head_eq_meaning:nNTF {#1} \)
              { \prg_return_true: }
              {
                \tl_if_head_is_group:nTF {#1}
                  {
                    \tl_set:Ne \l_@@_boundary_math_tl
                      { \tl_trim_left_spaces:e { \tl_tail:n {#1} } }
                    \exp_args:NV \tl_if_head_eq_meaning:nNTF
                      \l_@@_boundary_math_tl \ensuremath
                      { \prg_return_true: }
                      {
                        \exp_args:Ne \@@_boundary_if_math_tail:nTF
                          { \tl_head:n {#1} }
                          { \prg_return_true: }
                          { \prg_return_false: }
                      }
                  }
                  { \prg_return_false: }
              }
          }
      }
  }

\prg_new_protected_conditional:Npnn \@@_boundary_if_math_edge:n #1
  { T , F , TF }
  {
    \@@_boundary_if_math_head:nTF {#1}
      { \prg_return_true: }
      {
        \@@_boundary_if_math_tail:nTF {#1}
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_begin:n #1
  {
    \@@_boundary_if_math_head:nT {#1}
      { \@@_boundary_math_begin_visible: }
  }

\cs_new_protected:Npn \@@_boundary_math_end:n #1
  {
    \@@_boundary_if_math_tail:nT {#1}
      {
        \@@_boundary_if_math_tail_space:nTF {#1}
          { \@@_boundary_math_end_confirm_space: }
          { \@@_boundary_math_end_confirm_node: }
      }
  }

\prg_new_protected_conditional:Npnn \@@_boundary_if_math_tail_space:n #1
  { T , F , TF }
  {
    \tl_set:Nn \l_@@_boundary_math_tl {#1}
    \tl_set:Ne \l_@@_boundary_node_tl
      { \tl_trim_right_spaces:n {#1} }
    \tl_if_eq:NNTF \l_@@_boundary_math_tl \l_@@_boundary_node_tl
      { \prg_return_false: }
      { \prg_return_true: }
  }

\prg_new_conditional:Npnn \@@_boundary_if_last_math_node: { T , F , TF }
  {
    \@@_if_last_math:TF
      { \prg_return_true: }
      {
        \xeCJK_if_last_node:nTF { math }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_end_confirm_node:
  {
    \@@_boundary_if_last_math_node:T
      { \@@_boundary_math_end_confirm: }
  }
%    \end{macrocode}
% 参数末尾的字面空格通常会成为一枚普通词间 glue，遮住它前面的公式节点。
% 这里只允许越过语法扫描已经确认的尾随空格，并再次检查节点确为当前字体的
% 词间空格；未知宏留下的其他 glue 不会进入这条路径。确认后把 glue 原样
% 放回命令内部，并记录“公式后有参数内空格”的末类别，供 capture 结束时
% 按普通 stream 或冻结空格分别处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_math_end_confirm_space:
  {
    \@@_if_last_glue:T
      {
        \skip_set_eq:NN \l_@@_boundary_math_skip \tex_lastskip:D
        \@@_skip_if_interword:NT \l_@@_boundary_math_skip
          {
            \tex_unskip:D
            \@@_boundary_if_last_math_node:T
              {
                \@@_boundary_math_end_confirm_space_visible:
              }
            \skip_horizontal:N \l_@@_boundary_math_skip
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_end_confirm_space_visible:
  {
    \@@_boundary_math_end_confirm:
    \tl_gset:cn
      {
        g_@@_boundary_capture_
        \int_use:N \g_@@_boundary_capture_depth_int
        _math_tail_space_flag_tl
      }
      { true }
    \tl_if_eq:cnT
      {
        g_@@_boundary_capture_
        \int_use:N \g_@@_boundary_capture_depth_int _kind_tl
      }
      { stream }
      {
        \tl_gset:ce
          {
            g_@@_boundary_capture_
            \int_use:N \g_@@_boundary_capture_depth_int
            _math_tail_space_skip_tl
          }
          { \skip_use:N \l_@@_boundary_math_skip }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_end_confirm:
  {
    \int_compare:nNnT
      { \g_@@_boundary_capture_depth_int } > \c_zero_int
      {
        \int_compare:nNnT
          { \g_@@_boundary_suspend_depth_int } = \c_zero_int
          {
            \tl_gset:cn
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int _last_tl
              }
              { math }
            \tl_gset:cn
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int _math_tail_flag_tl
              }
              { true }
            \tl_gset:cn
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int
                _math_tail_space_flag_tl
              }
              { false }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_capture_report_first:n #1
  {
    \int_compare:nNnT
      { \g_@@_boundary_capture_depth_int } > \c_zero_int
      {
        \int_compare:nNnT
          { \g_@@_boundary_suspend_depth_int } = \c_zero_int
          {
            \int_step_inline:nn { \g_@@_boundary_capture_depth_int }
              {
                \tl_if_empty:cT { g_@@_boundary_capture_##1_first_tl }
                  {
                    \tl_gset:cn
                      { g_@@_boundary_capture_##1_first_tl } {#1}
                    \tl_if_eq:cnTF
                      { g_@@_boundary_capture_##1_kind_tl } { stream }
                      { \@@_boundary_capture_emit_left:nn {##1} {#1} }
                      {
                        \tl_if_eq:cnT
                          { g_@@_boundary_capture_##1_kind_tl } { stream-ulem }
                          { \@@_boundary_capture_emit_left:nn {##1} {#1} }
                      }
                  }
              }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_capture_emit_left:nn #1#2
  {
    \tl_set:Ne \l_@@_boundary_node_tl
      { \tl_use:c { g_@@_boundary_capture_#1_before_tl } }
    \@@_boundary_emit_left:nVn
      {#1} \l_@@_boundary_node_tl {#2}
    \tl_gset:cn
      { g_@@_boundary_capture_#1_space_flag_tl } { false }
  }

\cs_new_protected:Npn \@@_boundary_capture_suspend:
  {
    \int_gincr:N \g_@@_boundary_suspend_depth_int
    \tl_if_exist:cF
      {
        g_@@_boundary_suspend_\int_use:N
        \g_@@_boundary_suspend_depth_int _node_tl
      }
      {
        \tl_new:c
          {
            g_@@_boundary_suspend_\int_use:N
            \g_@@_boundary_suspend_depth_int _node_tl
          }
        \tl_new:c
          {
            g_@@_boundary_suspend_\int_use:N
            \g_@@_boundary_suspend_depth_int _pending_tl
          }
      }
    \exp_args:Nc \tl_gset_eq:NN
      {
        g_@@_boundary_suspend_\int_use:N
        \g_@@_boundary_suspend_depth_int _node_tl
      }
      \g_@@_last_node_tl
    \tl_gset:ce
      {
        g_@@_boundary_suspend_\int_use:N
        \g_@@_boundary_suspend_depth_int _pending_tl
      }
      { \bool_if:NTF \g_@@_glue_check_pending_bool { true } { false } }
  }

\cs_new_protected:Npn \@@_boundary_capture_resume:
  {
    \tl_gset:Ne \g_@@_last_node_tl
      {
        \tl_use:c
          {
            g_@@_boundary_suspend_\int_use:N
            \g_@@_boundary_suspend_depth_int _node_tl
          }
      }
    \tl_if_eq:cnTF
      {
        g_@@_boundary_suspend_\int_use:N
        \g_@@_boundary_suspend_depth_int _pending_tl
      }
      { true }
      { \bool_gset_true:N \g_@@_glue_check_pending_bool }
      { \bool_gset_false:N \g_@@_glue_check_pending_bool }
    \int_gdecr:N \g_@@_boundary_suspend_depth_int
  }

\cs_new_protected:Npn \@@_boundary_inline_box_begin:
  {
    \int_compare:nNnTF
      { \g_@@_boundary_suspend_depth_int } > \c_zero_int
      { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
      {
        \mode_if_math:TF
          { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
          {
            \mode_leave_vertical:
            \seq_gpush:Nn \g_@@_boundary_active_seq { true }
            \@@_boundary_box_begin:
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_inline_box_end:n #1
  {
    \seq_gpop:NN
      \g_@@_boundary_active_seq
      \l_@@_boundary_active_tl
    \tl_if_eq:NnT \l_@@_boundary_active_tl { true }
      { \@@_boundary_box_end:n {#1} }
  }

\cs_new_protected:Npn \@@_boundary_inline_last_box_begin:
  {
    \int_compare:nNnTF
      { \g_@@_boundary_suspend_depth_int } > \c_zero_int
      { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
      {
        \mode_if_math:TF
          { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
          {
            \mode_leave_vertical:
            \seq_gpush:Nn \g_@@_boundary_active_seq { true }
            \@@_boundary_capture_begin:
            \tl_gset:cn
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _kind_tl
              }
              { box }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_inline_last_box_end:n #1
  {
    \seq_gpop:NN
      \g_@@_boundary_active_seq
      \l_@@_boundary_active_tl
    \tl_if_eq:NnT \l_@@_boundary_active_tl { true }
      { \@@_boundary_last_box_end:n {#1} }
  }

\cs_new_protected:Npn \@@_boundary_inline_stream_begin:n #1
  { \@@_boundary_inline_stream_begin:nn {#1} { stream } }

\cs_new_protected:Npn \@@_boundary_inline_stream_begin:nn #1#2
  {
    \int_compare:nNnTF
      { \g_@@_boundary_suspend_depth_int } > \c_zero_int
      { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
      {
        \mode_if_math:TF
          { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
          {
            \mode_leave_vertical:
            \seq_gpush:Nn \g_@@_boundary_active_seq { true }
            \@@_boundary_capture_begin:
            \tl_gset:cn
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _kind_tl
              }
              {#2}
            \str_case:nn {#1}
              {
                { default }       { \@@_boundary_capture_class:n { default } }
                { first-default } { \@@_boundary_capture_class:n { default } }
              }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_inline_stream_end:n #1
  {
    \seq_gpop:NN
      \g_@@_boundary_active_seq
      \l_@@_boundary_active_tl
    \tl_if_eq:NnT \l_@@_boundary_active_tl { true }
      {
        \scan_stop:
        \@@_boundary_pop_node:N \l_@@_boundary_node_tl
        \tl_set:Ne \l_@@_boundary_first_tl
          {
            \tl_use:c
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _first_tl
              }
          }
        \tl_set:Ne \l_@@_boundary_last_tl
          {
            \tl_use:c
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _last_tl
              }
          }
%    \end{macrocode}
% 流式命令若在最外层留下 xeCJK marker，就以该 marker 记录的
% 类别更新末类别。这适用于内部使用不可见定界字符、最后又把正文末尾
% marker 移到外层列表的命令；没有末尾 marker 的 annotation/math
% 仍使用 capture 观察值。
%    \begin{macrocode}
        \exp_args:NV
          \@@_boundary_set_last_from_node:n \l_@@_boundary_node_tl
        \@@_boundary_set_math_tail_from_capture:
%    \end{macrocode}
% |default| 的开始 hook 已经固定首类别；这里仅覆写末类别即可让
% stream 两端都为 Default，同时不丢弃其他模式实际观察到的首类别。
%    \begin{macrocode}
        \str_if_eq:nnT {#1} { default }
          { \tl_set:Nn \l_@@_boundary_last_tl {#1} }
        \tl_gclear:N \g_@@_last_node_tl
        \bool_gset_false:N \g_@@_glue_check_pending_bool
        \tl_if_empty:NTF \l_@@_boundary_first_tl
          {
            \@@_boundary_replay_before:
          }
          {
            \tl_if_empty:NT \l_@@_boundary_last_tl
              { \tl_set_eq:NN \l_@@_boundary_last_tl \l_@@_boundary_first_tl }
            \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_last_tl
          }
        \int_gdecr:N \g_@@_boundary_capture_depth_int
      }
  }

\cs_new_protected:Npn \@@_boundary_hmode_transparent_begin:
  {
    \int_compare:nNnTF
      { \g_@@_boundary_suspend_depth_int } > \c_zero_int
      { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
      {
        \mode_if_horizontal:TF
          {
            \seq_gpush:Nn \g_@@_boundary_active_seq { true }
            \@@_boundary_capture_begin:
            \tl_gset:cn
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _kind_tl
              }
              { transparent }
          }
          { \seq_gpush:Nn \g_@@_boundary_active_seq { false } }
      }
  }

\cs_new_protected:Npn \@@_boundary_hmode_transparent_end:
  {
    \seq_gpop:NN
      \g_@@_boundary_active_seq
      \l_@@_boundary_active_tl
    \tl_if_eq:NnT \l_@@_boundary_active_tl { true }
      {
        \@@_boundary_replay_before:
        \int_gdecr:N \g_@@_boundary_capture_depth_int
      }
  }

\cs_new_protected:Npn \@@_boundary_post_transparent:
  {
    \mode_if_horizontal:T
      {
        \tl_if_empty:NF \g_@@_last_node_tl
          {
            \@@_if_last_hlist:TF
              {
                \box_set_to_last:N \l_@@_boundary_post_box
                \dim_compare:nNnTF
                  {
                    \box_wd:N \l_@@_boundary_post_box
                    + \box_ht:N \l_@@_boundary_post_box
                    + \box_dp:N \l_@@_boundary_post_box
                  }
                  = { \c_zero_dim }
                  { \@@_boundary_post_transparent_relocate: }
                  { \box_use_drop:N \l_@@_boundary_post_box }
              }
              {
                \xeCJK_if_last_node:TF
                  {
                    \exp_args:NV
                      \@@_boundary_replay_node:n \g_@@_last_node_tl
                  }
                  { }
              }
          }
      }
  }
%    \end{macrocode}
% \changes{v3.10.6}{2026/08/20}{post-transparent 搬移「marker + 候选 glue」
%   后缀前，先用 \cs{skip_if_finite:nTF} 排除 \tn{hfill}/\tn{hfil} 一类
%   无限阶填充 glue，使它们不再被误搬到零尺寸盒子之后，
%   修复 CJK 文字紧接源码空格与 \tn{hfill}\tn{null} 时两侧填充不对称
%   （\#1085）。}
% 零尺寸盒子前可能不只有 marker，还可能有一枚等待 Boundary 恢复链检查的
% glue。第一层先处理盒子直接盖住 marker 的情况；未命中时才暂存至多一枚
% 末尾 glue，再按节点证据检查其下方的 marker。命中后把“marker + glue”整体
% 移到盒子后面，使后续转换看到与没有该盒子时相同的列表末尾。未命中 marker
% 时按原顺序还原，不能把任意 glue 搬过 hbox。普通 stream 的 |math-space|
% 还有一枚真实空格留在 marker 前；零尺寸盒子已经把二者隔开时，应与含同一
% 盒子的直接公式一样让 marker 过期，不能只把补偿状态搬到盒子后面。
% 只有有限阶的候选 glue 才可能是被盒子遮住的源码空格或参数内空格；\tn{hfill}
% 等无限阶（fil/fill）填充 glue 不是候选，保持 marker、glue、盒子的原有次序
% （\#1085，见 \cs{@@_boundary_post_transparent_relocate_glue:}）。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_post_transparent_relocate:
  {
    \@@_boundary_pop_node:N \l_@@_boundary_node_tl
    \tl_if_empty:NTF \l_@@_boundary_node_tl
      {
        \@@_if_last_glue:TF
          { \@@_boundary_post_transparent_relocate_glue: }
          { \box_use_drop:N \l_@@_boundary_post_box }
      }
      {
%    \end{macrocode}
% 盒子直接盖住 marker，中间没有 glue。
%    \begin{macrocode}
        \box_use_drop:N \l_@@_boundary_post_box
        \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space }
          { \@@_boundary_math_space_expire: }
          {
            \exp_args:NV
              \@@_boundary_replay_node:n \l_@@_boundary_node_tl
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_post_transparent_relocate_glue:
  {
    \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
    \skip_if_finite:nTF \l_@@_last_skip
      {
        \tex_unskip:D
        \@@_boundary_pop_node:N \l_@@_boundary_node_tl
        \tl_if_empty:NTF \l_@@_boundary_node_tl
          {
            \skip_horizontal:N \l_@@_last_skip
            \box_use_drop:N \l_@@_boundary_post_box
          }
          {
            \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space }
              {
%    \end{macrocode}
% |math-space| 过期时不再移动后缀；先恢复候选 glue，再放回
% 零尺寸盒子，保持与含同一盒子的直接公式相同的节点顺序。
%    \begin{macrocode}
                \skip_horizontal:N \l_@@_last_skip
                \box_use_drop:N \l_@@_boundary_post_box
                \@@_boundary_math_space_expire:
              }
              {
                \box_use_drop:N \l_@@_boundary_post_box
                \exp_args:NV
                  \@@_boundary_replay_node:n \l_@@_boundary_node_tl
                \skip_horizontal:N \l_@@_last_skip
              }
          }
      }
%    \end{macrocode}
% 只有有限阶（finite）的候选 glue 才可能是被零尺寸盒子遮住的源码空格或
% 参数内空格（后者宽度不一定等于词间空格、也不一定带 shrink，例如
% |math-space| 场景里的显式 \tn{hskip}），因此这里只排除
% \tn{hfill}/\tn{hfil} 一类无限阶（fil/fill）填充 glue，不能收紧成
% \cs{@@_skip_if_interword:N}（它要求带 shrink 且宽度等于词间空格，会误伤
% math-space 的显式 glue）。无限阶 glue 不是任何源码空格候选：不移动它，
% 直接把零尺寸盒子放回末尾，保持 marker、glue、盒子的原有次序。否则会把
% \tn{null} 前的填充 glue 误搬到盒子之后，令 \tn{null} 不在列表末尾，破坏
% 两侧 \tn{hfill} 的对称（\#1085）。
%    \begin{macrocode}
      { \box_use_drop:N \l_@@_boundary_post_box }
  }

\cs_new_protected:Npn \@@_boundary_capture_begin:
  {
    \int_gincr:N \g_@@_boundary_capture_depth_int
    \int_compare:nNnT
      { \g_@@_boundary_capture_depth_int } > { 2 }
      {
        \exp_args:Ne \@@_boundary_capture_allocate:n
          { \int_use:N \g_@@_boundary_capture_depth_int }
      }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _before_tl
      }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _before_math_space_skip_tl
      }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _first_tl
      }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _last_tl
      }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _kind_tl
      }
%    \end{macrocode}
    % 先把用户定义的 glue 实际排入临时盒并读取为 skip；后面的 e-type 只把
    % 已求值的 skip 数值序列化到本层 tl，不展开原始 \CJKglue/\CJKecglue 定义。
%    \begin{macrocode}
    \xeCJK_glue_to_skip:nN
      { \tex_kern:D \c_zero_dim \xeCJK_space_glue: }
      \l_@@_tmp_skip
    \tl_gset:ce
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _space_skip_tl
      }
      { \skip_use:N \l_@@_tmp_skip }
    \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_tmp_skip
    \tl_gset:ce
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _ecglue_skip_tl
      }
      { \skip_use:N \l_@@_tmp_skip }
    \xeCJK_glue_to_skip:nN { \CJKglue } \l_@@_tmp_skip
    \tl_gset:ce
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _ccglue_skip_tl
      }
      { \skip_use:N \l_@@_tmp_skip }
    \tl_gset:cn
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _space_flag_tl
      }
      { false }
    \tl_gset:ce
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _reserve_space_flag_tl
      }
      { \bool_if:NTF \l_@@_reserve_space_bool { true } { false } }
    \tl_gset:ce
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _xecglue_flag_tl
      }
      { \bool_if:NTF \l_@@_xecglue_bool { true } { false } }
    \tl_gset:cn
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _math_tail_flag_tl
      }
      { false }
    \tl_gset:cn
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _math_tail_space_flag_tl
      }
      { false }
    \tl_gclear:c
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _math_tail_space_skip_tl
      }
    \@@_boundary_capture_space:
    \scan_stop:
    \@@_boundary_pop_node:N \l_@@_boundary_node_tl
    \tl_if_eq:NnT \l_@@_boundary_node_tl { math-space }
      {
        \@@_if_last_glue:TF
          {
            \tl_gset:ce
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int
                _before_math_space_skip_tl
              }
              { \skip_use:N \tex_lastskip:D }
          }
          {
            \tl_gset:ce
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int
                _before_math_space_skip_tl
              }
              { \skip_use:N \l_@@_boundary_math_actual_skip }
          }
      }
    \tl_if_eq:NnT \l_@@_boundary_node_tl { CJK-space }
      {
        \tl_gset:cn
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _space_flag_tl
          }
          { true }
      }
    \tl_if_empty:NT \l_@@_boundary_node_tl
      {
        \tl_if_eq:cnT
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _space_flag_tl
          }
          { true }
          {
            \@@_boundary_restore_space:
            \tl_gset:cn
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _space_flag_tl
              }
              { false }
          }
      }
    \exp_args:Nc \tl_gset_eq:NN
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _before_tl
      }
      \l_@@_boundary_node_tl
    \tl_gclear:N \g_@@_last_node_tl
    \bool_gset_false:N \g_@@_glue_check_pending_bool
  }

\cs_new_protected:Npn \@@_boundary_box_begin:
  {
    \@@_boundary_capture_begin:
    \tl_gset:cn
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _kind_tl
      }
      { box }
    \exp_args:Nc \@@_boundary_box_open:N
      {
        g_@@_boundary_capture_\int_use:N
        \g_@@_boundary_capture_depth_int _box
      }
  }

\cs_new_protected:Npn \@@_boundary_box_open:N #1
  {
    \tex_global:D \tex_setbox:D #1
      \tex_hbox:D \c_group_begin_token
  }

\cs_new_protected:Npn \@@_boundary_capture_space:
  {
    \@@_if_last_glue:T
      {
        \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
        \@@_skip_if_interword:NT \l_@@_last_skip
          {
            \tl_gset:ce
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _space_skip_tl
              }
              { \skip_use:N \l_@@_last_skip }
            \tl_gset:cn
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _space_flag_tl
              }
              { true }
            \tex_unskip:D
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_pop_node:N #1
  {
    \tl_clear:N #1
    \xeCJK_if_last_node:TF
      {
        \dim_case:nnF { \l_@@_last_kern_dim }
          {
            { \@@_node:n { CJK } }             { \tl_set:Nn #1 { CJK } }
            { \@@_node:n { CJK-space } }       { \tl_set:Nn #1 { CJK-space } }
            { \@@_node:n { CJK-widow } }       { \tl_set:Nn #1 { CJK-widow } }
            { \@@_node:n { default } }         { \tl_set:Nn #1 { default } }
            { \@@_node:n { math } }            { \tl_set:Nn #1 { math } }
            { \@@_node:n { math-space } }
              {
                \tl_set:Nn #1 { math-space }
                \@@_boundary_math_space_decode:
              }
            { \@@_node:n { math-space-frozen } }
              { \tl_set:Nn #1 { math-space-frozen } }
            { \@@_node:n { normalspace } }     { \tl_set:Nn #1 { normalspace } }
          }
          { \@@_make_node:N \l_@@_last_kern_dim }
      }
      {
        \xeCJK_if_last_node:nT { default-space }
          {
            \xeCJK_remove_node:
            \tl_set:Nn #1 { default-space }
          }
      }
  }

\prg_new_conditional:Npnn \@@_boundary_if_last_math_space: { T , F , TF }
  {
    \xeCJK_if_last_node:nTF { math-space }
      { \prg_return_true: }
      {
        \xeCJK_if_last_node:nTF { math-space-frozen }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_space_consume:
  {
    \xeCJK_if_last_node:nTF { math-space }
      {
        \xeCJK_remove_node:
        \@@_boundary_math_space_decode:
      }
      { \xeCJK_remove_node: }
    \bool_gset_false:N \g_@@_glue_check_pending_bool
  }
%    \end{macrocode}
% |math-space| 后面的两对零净宽 kern 依次保存普通 stream 空格的伸长量
% 和收缩量。marker 位于最外侧，移除 marker 后按相反次序取回两对 kern。
% 这里只保存有限阶词间空格的弹性分量；字体的普通词间空格不会使用 fil 阶。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_math_space_set_actual:n #1
  {
    \skip_set:Nn \l_@@_boundary_math_actual_skip {#1}
    \dim_set:Nn \l_@@_boundary_math_stretch_dim
      { \tex_gluestretch:D \l_@@_boundary_math_actual_skip }
    \dim_set:Nn \l_@@_boundary_math_shrink_dim
      { \tex_glueshrink:D \l_@@_boundary_math_actual_skip }
    \skip_set:Nn \l_@@_boundary_math_actual_skip
      {
        \c_zero_dim
        plus  \l_@@_boundary_math_stretch_dim
        minus \l_@@_boundary_math_shrink_dim
      }
  }

\cs_new_protected:Npn \@@_boundary_math_space_encode:
  {
    \dim_set:Nn \l_@@_boundary_math_stretch_dim
      { \tex_gluestretch:D \l_@@_boundary_math_actual_skip }
    \dim_set:Nn \l_@@_boundary_math_shrink_dim
      { \tex_glueshrink:D \l_@@_boundary_math_actual_skip }
    \@@_make_node:N \l_@@_boundary_math_stretch_dim
    \@@_make_node:N \l_@@_boundary_math_shrink_dim
  }

\cs_new_protected:Npn \@@_boundary_math_space_decode:
  {
    \dim_zero:N \l_@@_boundary_math_stretch_dim
    \dim_zero:N \l_@@_boundary_math_shrink_dim
    \xeCJK_if_last_node:TF
      {
        \dim_set_eq:NN
          \l_@@_boundary_math_shrink_dim \l_@@_last_kern_dim
        \xeCJK_if_last_node:TF
          {
            \dim_set_eq:NN
              \l_@@_boundary_math_stretch_dim \l_@@_last_kern_dim
          }
          { }
      }
      { }
    \skip_set:Nn \l_@@_boundary_math_actual_skip
      {
        \c_zero_dim
        plus  \l_@@_boundary_math_stretch_dim
        minus \l_@@_boundary_math_shrink_dim
      }
  }
%    \end{macrocode}
% 普通 stream 的参数内空格仍在外层列表：自然差额按入口字体的词间空格
% 计算，伸缩差额则扣除参数内实际空格的伸缩量。box、wrapped-box 和
% stream-ulem 已把空格冻结在内部盒子中；它的伸缩分量不再参与外层断行，
% 外层只能扣除词间空格的自然宽度，并完整保留 |CJKecglue| 自己的伸缩分量。
% 若目标自然宽度更小，则把外层自然差额截为零，不能用负 glue 把后续文字
% 拉回框线或装饰范围。命令内外字体不同时，内部空格的宽度差仍属于命令
% 自身的固有宽度。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_math_space_set_delta:n #1
  {
    \str_if_eq:nnTF {#1} { math-space-frozen }
      {
        \dim_set:Nn \l_@@_boundary_math_space_dim
          { \l_@@_boundary_math_skip }
        \dim_set:Nn \l_@@_boundary_math_ecglue_dim
          { \l_@@_boundary_math_ecglue_skip }
        \dim_set:Nn \l_@@_tmp_dim
          {
            \dim_max:nn
              {
                \l_@@_boundary_math_ecglue_dim
                - \l_@@_boundary_math_space_dim
              }
              { \c_zero_dim }
          }
        \skip_set:Nn \l_@@_boundary_math_delta_skip
          {
            \l_@@_boundary_math_ecglue_skip
            - \l_@@_boundary_math_ecglue_dim
            + \l_@@_tmp_dim
          }
      }
      {
        \dim_set:Nn \l_@@_boundary_math_space_dim
          { \l_@@_boundary_math_skip }
        \dim_set:Nn \l_@@_boundary_math_ecglue_dim
          { \l_@@_boundary_math_ecglue_skip }
        \dim_set:Nn \l_@@_tmp_dim
          {
            \l_@@_boundary_math_ecglue_dim
            - \l_@@_boundary_math_space_dim
          }
        \skip_set:Nn \l_@@_boundary_math_delta_skip
          {
            \l_@@_boundary_math_ecglue_skip
            - \l_@@_boundary_math_ecglue_dim
            + \l_@@_tmp_dim
            - \l_@@_boundary_math_actual_skip
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_space_to_CJK:
  {
    \xeCJK_if_last_node:nTF { math-space-frozen }
      { \tl_set:Nn \l_@@_boundary_math_type_tl { math-space-frozen } }
      { \tl_set:Nn \l_@@_boundary_math_type_tl { math-space } }
    \@@_boundary_math_space_consume:
    \bool_if:NT \l_@@_xecglue_bool
      {
        \skip_set:Nn \l_@@_boundary_math_skip
          { \c_xeCJK_space_skip_tl }
        \skip_set_eq:NN
          \l_@@_boundary_math_ecglue_skip \l_@@_ecglue_skip
        \exp_args:NV \@@_boundary_math_space_set_delta:n
          \l_@@_boundary_math_type_tl
        \skip_if_eq:nnF
          { \l_@@_boundary_math_delta_skip } { \c_zero_skip }
          { \skip_horizontal:N \l_@@_boundary_math_delta_skip }
      }
  }

\cs_new_protected:Npn \@@_boundary_box_end:n #1
  {
    \c_group_end_token
    \@@_boundary_box_finish:nn {#1} { wrapped }
  }

\cs_new_protected:Npn \@@_boundary_last_box_end:n #1
  {
    \@@_if_last_hlist:TF
      {
        \exp_args:Nc \box_set_to_last:N
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _box
          }
        \@@_boundary_box_finish:nn {#1} { last }
      }
      {
        \tl_gclear:N \g_@@_last_node_tl
        \bool_gset_false:N \g_@@_glue_check_pending_bool
        \@@_boundary_replay_before:
        \int_gdecr:N \g_@@_boundary_capture_depth_int
      }
  }

\int_new:N \g_@@_boundary_probe_type_int
\box_new:N \l_@@_boundary_probe_box
\tl_new:N \l_@@_boundary_probe_name_tl
\prg_new_protected_conditional:Npnn \@@_boundary_if_capture_box_sized:
  { T , F , TF }
  {
    \tl_set:Ne \l_@@_boundary_probe_name_tl
      {
        g_@@_boundary_capture_
        \int_use:N \g_@@_boundary_capture_depth_int _box
      }
    \bool_lazy_and:nnTF
      {
        ! \dim_compare_p:nNn
            { \box_wd:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim
      }
      {
        \bool_lazy_or_p:nn
          {
            ! \dim_compare_p:nNn
                { \box_ht:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim
          }
          {
            ! \dim_compare_p:nNn
                { \box_dp:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim
          }
      }
      { \prg_return_true: }
      { \prg_return_false: }
  }
\prg_new_protected_conditional:Npnn \@@_boundary_if_capture_box_visible:
  { T , F , TF }
  {
    \@@_boundary_if_capture_box_sized:TF
      {
%    \end{macrocode}
% 只复制节点，不重扫 token，无排版副作用。
%    \begin{macrocode}
        \hbox_set:Nn \l_@@_boundary_probe_box
          {
            \hbox_unpack:c { \l_@@_boundary_probe_name_tl }
            \int_gset:Nn \g_@@_boundary_probe_type_int
              { \tex_lastnodetype:D }
          }
        \box_clear:N \l_@@_boundary_probe_box
%    \end{macrocode}
% \tn{lastnodetype} 编码沿用 eTeX 手册的节点类型表（0 传统 char、
% 1 hlist、3 rule、9 whatsit、10 math、11 glue、12 kern）；
% \XeTeX\ 把 native 文本实现为 word 节点、同样报 9，但它必然已被
% interchar 观察，不会进入这里。这里把 char、rule、math，以及
% 未上报标点类字符后由标点机制添加的 kern 视为盒子直接排出的内容。
%    \begin{macrocode}
        \int_case:nnTF { \g_@@_boundary_probe_type_int }
          {
            { 0 }  { }
            { 3 }  { }
            { 10 } { }
            { 12 } { }
          }
          { \prg_return_true: }
          { \prg_return_false: }
      }
      { \prg_return_false: }
  }

\tl_new:N \g_@@_boundary_probe_last_tl
\cs_new_protected:Npn \@@_boundary_box_set_last_from_node:
  {
    \tl_set:Ne \l_@@_boundary_probe_name_tl
      {
        g_@@_boundary_capture_
        \int_use:N \g_@@_boundary_capture_depth_int _box
      }
    \tl_gclear:N \g_@@_boundary_probe_last_tl
    \hbox_set:Nn \l_@@_boundary_probe_box
      {
        \hbox_unpack:c { \l_@@_boundary_probe_name_tl }
        \@@_boundary_pop_node:N \l_@@_boundary_node_tl
        \tl_if_eq:NnT \l_@@_boundary_node_tl { math-space }
          { \tl_set:Nn \l_@@_boundary_node_tl { math-space-frozen } }
        \tl_gset_eq:NN
          \g_@@_boundary_probe_last_tl \l_@@_boundary_node_tl
      }
    \box_clear:N \l_@@_boundary_probe_box
    \exp_args:NV
      \@@_boundary_set_last_from_node:n \g_@@_boundary_probe_last_tl
  }

\cs_new_protected:Npn \@@_boundary_box_finish:nn #1#2
  {
    \tl_set:Ne \l_@@_boundary_first_tl
      {
        \tl_use:c
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _first_tl
          }
      }
    \tl_set:Ne \l_@@_boundary_last_tl
      {
        \tl_use:c
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _last_tl
          }
      }
    \tl_gclear:N \g_@@_last_node_tl
    \bool_gset_false:N \g_@@_glue_check_pending_bool
%    \end{macrocode}
% 嵌套 box 结束时写入的 marker 会留在当前盒子的节点列表末尾。
% 读取这个 marker 可以更新本层的末类别，使“前面已经观察到 CJK，末尾
% 是推断为 Default 的内容”等情况逐层传到外面；不能把命令内部辅助盒子的
% 推断直接写进所有外层 capture。
% 即使 capture 已记录末类别也不能跳过这一步：内部不可见的定界字符可能
% 使观察值不准，此时应以节点列表末尾的 marker 为准。
%    \begin{macrocode}
    \@@_boundary_box_set_last_from_node:
    \@@_boundary_set_math_tail_from_capture:
%    \end{macrocode}
% 盒子捕获没有独立的 stream 入口，故 |default| 在这里同时覆写实际观察到
% 的首尾类别；stream 路径则由开始 hook 固定首类别、结束 hook 固定
% 末类别，两条路径的公开语义相同。
%    \begin{macrocode}
    \str_if_eq:nnT {#1} { default }
      {
        \tl_set:Nn \l_@@_boundary_first_tl { default }
        \tl_set:Nn \l_@@_boundary_last_tl  { default }
      }
%    \end{macrocode}
% rule 和尚未由公式观察器记录的 math 不触发 interchar 转换。若盒子
% 直接排出了可见内容
% （宽度非零、高度或深度非零，且末节点为 char/rule/math/kern），
% 却没有观察到任何类别，就按 Default 首尾重建边界。空 makebox、strut
% 等只用于留白的盒子，以及放入另一个已经排好盒子的命令，仍按无可见
% 输出处理（issue 998）。
% 只把 default 报告给尚未取得首类别的外层 capture，使 \tn{nfss@text}
% 这类嵌在 stream 内的盒子命令与裸调用等价；各层盒子结束时会读取
% 节点列表末尾由本层写入的 marker，并据此更新末类别。
%    \begin{macrocode}
    \tl_if_empty:NT \l_@@_boundary_first_tl
      {
        \@@_boundary_if_capture_box_visible:T
          {
            \tl_set:Nn \l_@@_boundary_first_tl { default }
            \tl_set:Nn \l_@@_boundary_last_tl  { default }
            \@@_boundary_capture_report_first:n { default }
          }
      }
    \tl_if_empty:NTF \l_@@_boundary_first_tl
      { \@@_boundary_box_end_transparent:n {#2} }
      {
        \tl_set:Ne \l_@@_boundary_node_tl
          {
            \tl_use:c
              {
                g_@@_boundary_capture_\int_use:N
                \g_@@_boundary_capture_depth_int _before_tl
              }
          }
        \exp_args:NVV \@@_boundary_emit_left:nn
          \l_@@_boundary_node_tl
          \l_@@_boundary_first_tl
        \box_use_drop:c
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _box
          }
        \tl_if_empty:NT \l_@@_boundary_last_tl
          { \tl_set_eq:NN \l_@@_boundary_last_tl \l_@@_boundary_first_tl }
        \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_last_tl
      }
    \int_gdecr:N \g_@@_boundary_capture_depth_int
  }

\cs_new_protected:Npn \@@_boundary_box_end_transparent:n #1
  {
    \str_if_eq:nnTF {#1} { wrapped }
      {
        \hbox_unpack_drop:c
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _box
          }
      }
      {
        \box_use_drop:c
          {
            g_@@_boundary_capture_\int_use:N
            \g_@@_boundary_capture_depth_int _box
          }
      }
    \@@_boundary_replay_before:
  }

\cs_new_protected:Npn \@@_boundary_replay_before:
  {
    \tl_set:Ne \l_@@_boundary_node_tl
      {
        \tl_use:c
          {
            g_@@_boundary_capture_
            \int_use:N \g_@@_boundary_capture_depth_int _before_tl
          }
      }
    \clist_if_in:nVTF
      { math-space , math-space-frozen } \l_@@_boundary_node_tl
      {
        \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space }
          { \@@_boundary_replay_before_math_space: }
          { \@@_boundary_replay_node:n { math-space-frozen } }
      }
      {
        \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_node_tl
        \@@_boundary_restore_space:
      }
  }
%    \end{macrocode}
% 普通 stream 的 |math-space| 只在真实参数空格仍紧邻列表末尾时重放。
% transparent 命令若已经在二者之间写入 special、零尺寸盒子等节点，直接
% 公式的源码空格同样不会越过该节点触发恢复；此时必须让 marker 过期，
% 不能把补偿 glue 单独移到不可见节点之后。skip 比较把判断限制在原来那枚
% 真实空格，不把 transparent 命令偶然写出的不同规格 glue 当作边界后缀。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_replay_before_math_space:
  {
    \@@_if_last_glue:T
      {
        \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
        \skip_if_eq:nnT
          { \l_@@_last_skip }
          {
            \tl_use:c
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int
                _before_math_space_skip_tl
              }
          }
          {
            \exp_args:Ne \@@_boundary_math_space_set_actual:n
              {
                \tl_use:c
                  {
                    g_@@_boundary_capture_
                    \int_use:N \g_@@_boundary_capture_depth_int
                    _before_math_space_skip_tl
                  }
              }
            \@@_boundary_replay_node:n { math-space }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_math_space_expire:
  {
    \tl_gclear:N \g_@@_last_node_tl
    \bool_gset_false:N \g_@@_glue_check_pending_bool
  }

\cs_new_protected:Npn \@@_boundary_emit_left:nn #1#2
  {
    \@@_boundary_emit_left:nnn
      { \int_use:N \g_@@_boundary_capture_depth_int } {#1} {#2}
  }

\cs_new_protected:Npn \@@_boundary_emit_left:nnn #1#2#3
  {
    \clist_if_in:nnTF { math-space , math-space-frozen } {#2}
      { \@@_boundary_emit_left_math_space:nnn {#1} {#2} {#3} }
      { \@@_boundary_emit_left_normal:nnn {#1} {#2} {#3} }
  }
\cs_new_protected:Npn \@@_boundary_emit_left_math_space:nnn #1#2#3
  {
    \str_if_eq:nnT {#3} { CJK }
      {
        \tl_if_eq:cnT
          { g_@@_boundary_capture_#1_xecglue_flag_tl } { true }
          {
            \str_if_eq:nnT {#2} { math-space }
              {
                \exp_args:Ne \@@_boundary_math_space_set_actual:n
                  {
                    \tl_use:c
                      { g_@@_boundary_capture_#1_before_math_space_skip_tl }
                  }
              }
            \skip_set:Nn \l_@@_boundary_math_skip
              { \tl_use:c { g_@@_boundary_capture_#1_space_skip_tl } }
            \skip_set:Nn \l_@@_boundary_math_ecglue_skip
              { \tl_use:c { g_@@_boundary_capture_#1_ecglue_skip_tl } }
            \@@_boundary_math_space_set_delta:n {#2}
            \skip_if_eq:nnF
              { \l_@@_boundary_math_delta_skip } { \c_zero_skip }
              {
                \@@_boundary_use_glue:nn {#1}
                  { \l_@@_boundary_math_delta_skip }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_boundary_emit_left_normal:nnn #1#2#3
  {
    \clist_if_in:nnTF { CJK , CJK-space , CJK-widow } {#2}
      {
        \str_if_eq:nnTF {#3} { math }
          {
            \tl_if_eq:cnTF
              { g_@@_boundary_capture_#1_space_flag_tl } { true }
              {
                \tl_if_eq:cnTF
                  { g_@@_boundary_capture_#1_xecglue_flag_tl } { true }
                  { \@@_boundary_use_ecglue:n {#1} }
                  { \@@_boundary_restore_space:n {#1} }
              }
              { \@@_boundary_use_ecglue:n {#1} }
          }
          {
            \str_if_eq:nnTF {#3} { CJK }
              {
                \str_if_eq:nnT {#2} { CJK-widow }
                  { \xeCJK_widow_penalty: }
                \tl_if_eq:cnTF
                  { g_@@_boundary_capture_#1_space_flag_tl } { true }
                  {
                    \tl_if_eq:cnTF
                      { g_@@_boundary_capture_#1_reserve_space_flag_tl }
                      { true }
                      { \@@_boundary_restore_space:n {#1} }
                      { \@@_boundary_use_ccglue:n {#1} }
                  }
                  { \@@_boundary_use_ccglue:n {#1} }
              }
              { \@@_boundary_use_ecglue:n {#1} }
          }
      }
      {
        \clist_if_in:nnTF { default , default-space , math } {#2}
          {
            \str_if_eq:nnTF {#3} { CJK }
              {
                \tl_if_eq:cnTF
                  { g_@@_boundary_capture_#1_space_flag_tl } { true }
                  {
                    \tl_if_eq:cnTF
                      { g_@@_boundary_capture_#1_xecglue_flag_tl } { true }
                      { \@@_boundary_use_ecglue:n {#1} }
                      { \@@_boundary_restore_space:n {#1} }
                  }
                  { \@@_boundary_use_ecglue:n {#1} }
              }
              { \@@_boundary_restore_space:n {#1} }
          }
          { \@@_boundary_restore_space:n {#1} }
      }
  }
\cs_generate_variant:Nn \@@_boundary_emit_left:nnn { nVn }

\cs_new_protected:Npn \@@_boundary_use_ecglue:n #1
  {
    \@@_boundary_use_glue:nn {#1}
      { \tl_use:c { g_@@_boundary_capture_#1_ecglue_skip_tl } }
  }

\cs_new_protected:Npn \@@_boundary_use_ccglue:n #1
  {
    \@@_boundary_use_glue:nn {#1}
      { \tl_use:c { g_@@_boundary_capture_#1_ccglue_skip_tl } }
  }

\cs_new_protected:Npn \@@_boundary_use_glue:nn #1#2
  {
    \tl_if_eq:cnTF
      { g_@@_boundary_capture_#1_kind_tl } { stream-ulem }
      { \use:c { @@_boundary_use_ulem_glue:n } {#2} }
      { \skip_horizontal:n {#2} }
  }

\cs_new_protected:Npn \@@_boundary_restore_space:
  {
    \@@_boundary_restore_space:n
      { \int_use:N \g_@@_boundary_capture_depth_int }
  }

\cs_new_protected:Npn \@@_boundary_restore_space:n #1
  {
    \tl_if_eq:cnT
      { g_@@_boundary_capture_#1_space_flag_tl } { true }
      {
        \@@_boundary_use_glue:nn {#1}
          { \tl_use:c { g_@@_boundary_capture_#1_space_skip_tl } }
      }
  }

\cs_new_protected:Npn \@@_boundary_set_last_from_node:n #1
  {
    \clist_if_in:nnTF { CJK , CJK-space , CJK-widow } {#1}
      { \tl_set:Nn \l_@@_boundary_last_tl { CJK } }
      {
        \clist_if_in:nnT
          { default , default-space } {#1}
          { \tl_set:Nn \l_@@_boundary_last_tl { default } }
        \str_if_eq:nnT {#1} { math }
          { \tl_set:Nn \l_@@_boundary_last_tl { math } }
        \str_if_eq:nnT {#1} { math-space }
          { \tl_set:Nn \l_@@_boundary_last_tl { math-space } }
        \str_if_eq:nnT {#1} { math-space-frozen }
          { \tl_set:Nn \l_@@_boundary_last_tl { math-space-frozen } }
      }
  }

\cs_new_protected:Npn \@@_boundary_set_math_tail_from_capture:
  {
    \tl_if_eq:cnT
      {
        g_@@_boundary_capture_
        \int_use:N \g_@@_boundary_capture_depth_int _math_tail_flag_tl
      }
      { true }
      {
        \tl_if_eq:cnTF
          {
            g_@@_boundary_capture_
            \int_use:N \g_@@_boundary_capture_depth_int
            _math_tail_space_flag_tl
          }
          { true }
          {
            \tl_if_eq:cnTF
              {
                g_@@_boundary_capture_
                \int_use:N \g_@@_boundary_capture_depth_int _kind_tl
              }
              { stream }
              {
                \tl_set:Nn \l_@@_boundary_last_tl { math-space }
                \exp_args:Ne \@@_boundary_math_space_set_actual:n
                  {
                    \tl_use:c
                      {
                        g_@@_boundary_capture_
                        \int_use:N \g_@@_boundary_capture_depth_int
                        _math_tail_space_skip_tl
                      }
                  }
              }
              { \tl_set:Nn \l_@@_boundary_last_tl { math-space-frozen } }
          }
          { \tl_set:Nn \l_@@_boundary_last_tl { math } }
      }
  }
%    \end{macrocode}
% Default 字符在盒子内部更新的 spacefactor 不会传播到外层列表；stream
% 内容则会直接更新外层值。重放 Default 类 marker 时，以当前列表真正的
% spacefactor 同步源码空格检查上下文，避免把盒内末尾大写字母留下的 999
% 错用于盒外以 1000 生成的源码空格。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_replay_node:n #1
    {
    \clist_if_in:nnT
      {
        default , default-space , math , math-space , math-space-frozen ,
        normalspace
      }
      {#1}
      { \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D }
    \str_if_eq:nnTF {#1} { math-space }
      {
        \@@_boundary_math_space_encode:
        \xeCJK_make_node:n {#1}
        \@@_boundary_enable_source_space:n {#1}
      }
      {
        \str_if_eq:nnTF {#1} { math-space-frozen }
          {
            \xeCJK_make_node:n {#1}
            \@@_boundary_enable_source_space:n {#1}
          }
          {
            \str_if_eq:nnTF {#1} { default-space }
              {
                \@@_make_space_node:
                \tl_gset:Nn \g_@@_last_node_tl { default }
              }
              {
                \clist_if_in:nnT
                  { CJK , CJK-space , CJK-widow , default , math , normalspace }
                  {#1}
                  {
                    \xeCJK_make_node:n {#1}
                    \@@_boundary_enable_source_space:n {#1}
                  }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_boundary_enable_source_space:n #1
  {
    \clist_if_in:nnT
      {
        CJK , CJK-space , CJK-widow , math , math-space ,
        math-space-frozen
      }
      {#1}
      { \bool_gset_true:N \g_@@_glue_check_pending_bool }
  }
%    \end{macrocode}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_box:nn #1#2
  {
    \prop_if_in:NnF \g_@@_boundary_registered_prop {#1}
      {
        \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} {#2}
        \AddToHook { cmd / #1 / before } [ xeCJK / boundary ]
          { \@@_boundary_inline_last_box_begin: }
        \AddToHook { cmd / #1 / after } [ xeCJK / boundary ]
          { \@@_boundary_inline_last_box_end:n {#2} }
      }
  }

\cs_new_protected:Npn \@@_boundary_register_wrapped_box:nn #1#2
  {
    \prop_if_in:NnF \g_@@_boundary_registered_prop {#1}
      {
        \prop_gput:Nnn
          \g_@@_boundary_registered_prop {#1} { wrapped-box/#2 }
        \AddToHook { cmd / #1 / before } [ xeCJK / boundary ]
          { \@@_boundary_inline_box_begin: }
        \AddToHook { cmd / #1 / after } [ xeCJK / boundary ]
          { \@@_boundary_inline_box_end:n {#2} }
      }
  }

\cs_new_protected:Npn \@@_boundary_register_transparent:n #1
  {
    \prop_if_in:NnF \g_@@_boundary_registered_prop {#1}
      {
        \prop_gput:Nnn
          \g_@@_boundary_registered_prop {#1} { transparent }
        \AddToHook { cmd / #1 / before } [ xeCJK / boundary ]
          { \@@_boundary_hmode_transparent_begin: }
        \AddToHook { cmd / #1 / after } [ xeCJK / boundary ]
          { \@@_boundary_hmode_transparent_end: }
      }
  }

\cs_new_protected:Npn \@@_boundary_register_post_transparent:n #1
  {
    \prop_if_in:NnF \g_@@_boundary_registered_prop {#1}
      {
        \prop_gput:Nnn
          \g_@@_boundary_registered_prop {#1} { post-transparent }
        \AddToHook { cmd / #1 / after } [ xeCJK / boundary ]
          { \@@_boundary_post_transparent: }
      }
  }

\cs_new_protected:Npn \@@_boundary_register_stream:nn #1#2
  {
    \prop_if_in:NnF \g_@@_boundary_registered_prop {#1}
      {
        \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} { stream/#2 }
        \AddToHook { cmd / #1 / before } [ xeCJK / boundary ]
          { \@@_boundary_inline_stream_begin:n {#2} }
        \AddToHook { cmd / #1 / after } [ xeCJK / boundary ]
          { \@@_boundary_inline_stream_end:n {#2} }
      }
  }
%    \end{macrocode}
% 通用注册函数把命令写入 \cs{g_@@_boundary_registered_prop}。另有一些
% 命令必须直接重定义内部排版入口，不能使用 LaTeX 通用命令 hook；这些
% 入口写入独立的保留表。用户接口同时查询两张表，防止把通用 hook 叠加到
% 已有专用适配器外层。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_reserve:n #1
  { \prop_gput:Nnn \g_@@_boundary_reserved_prop {#1} { adapter } }
\cs_new_protected:Npn \@@_boundary_reserve:N #1
  {
    \exp_args:Ne \@@_boundary_reserve:n { \cs_to_str:N #1 }
  }
\prg_new_conditional:Npnn \@@_boundary_if_registered:n #1 { T , F , TF }
  {
    \prop_if_in:NnTF \g_@@_boundary_registered_prop {#1}
      { \prg_return_true: }
      {
        \prop_if_in:NnTF \g_@@_boundary_reserved_prop {#1}
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }
%    \end{macrocode}
%    \begin{macrocode}
\@@_msg_new:nn { boundary-register-command }
  {
    The~value~of~`command'~in~`experiment/boundary-register'~must~be~
    exactly~one~control~sequence.
  }
\@@_msg_new:nn { boundary-register-choice }
  { Invalid~boundary~registration~choice:~#1. }
\@@_msg_new:nn { boundary-register-missing }
  { The~`#1'~entry~is~required~in~`experiment/boundary-register'. }
\@@_msg_new:nn { boundary-register-mode }
  { The~boundary~registration~strategy~and~mode~cannot~be~combined:~#1. }
\@@_msg_new:nn { boundary-register-duplicate }
  { The~command~`#1'~has~already~been~declared~for~boundary~registration. }
\@@_msg_new:nn { boundary-register-conflict }
  { The~command~`#1'~already~has~an~xeCJK~boundary~registration. }
\@@_msg_new:nn { boundary-register-late }
  {
    The~`experiment/boundary-register'~option~can~only~be~used~before~
    the~end~of~the~preamble.
  }
\@@_msg_new:nn { boundary-register-undefined }
  { The~registered~boundary~command~`#1'~is~still~undefined. }

\cs_new_protected:Npn \@@_boundary_user_register:n #1
  {
    \bool_if:NTF \g_@@_boundary_user_register_closed_bool
      { \@@_error:n { boundary-register-late } }
      {
        \bool_set_false:N \l_@@_boundary_user_invalid_bool
        \bool_set_false:N \l_@@_boundary_user_mode_given_bool
        \tl_clear:N \l_@@_boundary_user_name_tl
        \tl_clear:N \l_@@_boundary_user_strategy_tl
        \tl_set:Nn \l_@@_boundary_user_mode_tl { auto }
        \keys_set:nn { xeCJK / boundary-register } {#1}
        \tl_if_empty:NT \l_@@_boundary_user_name_tl
          {
            \@@_error:ne { boundary-register-missing } { command }
            \bool_set_true:N \l_@@_boundary_user_invalid_bool
          }
        \tl_if_empty:NT \l_@@_boundary_user_strategy_tl
          {
            \@@_error:ne { boundary-register-missing } { strategy }
            \bool_set_true:N \l_@@_boundary_user_invalid_bool
          }
        \bool_if:NF \l_@@_boundary_user_invalid_bool
          { \@@_boundary_user_register_validate: }
        \bool_if:NF \l_@@_boundary_user_invalid_bool
          { \@@_boundary_user_register_store: }
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_command:n #1
  {
    \tl_if_single_token:nTF {#1}
      {
        \token_if_cs:NTF #1
          { \tl_set:Ne \l_@@_boundary_user_name_tl { \cs_to_str:N #1 } }
          {
            \@@_error:n { boundary-register-command }
            \tl_set:Nn \l_@@_boundary_user_name_tl { invalid }
            \bool_set_true:N \l_@@_boundary_user_invalid_bool
          }
      }
      {
        \@@_error:n { boundary-register-command }
        \tl_set:Nn \l_@@_boundary_user_name_tl { invalid }
        \bool_set_true:N \l_@@_boundary_user_invalid_bool
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_strategy:n #1
  {
    \str_case:nnF {#1}
      {
        { box }              { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} }
        { wrapped-box }      { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} }
        { stream }           { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} }
        { transparent }      { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} }
        { post-transparent } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} }
      }
      {
        \@@_error:ne { boundary-register-choice }
          { strategy~=~\tl_to_str:n {#1} }
        \tl_set:Nn \l_@@_boundary_user_strategy_tl { invalid }
        \bool_set_true:N \l_@@_boundary_user_invalid_bool
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_mode:n #1
  {
    \bool_set_true:N \l_@@_boundary_user_mode_given_bool
    \str_case:nnF {#1}
      {
        { auto }          { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} }
        { default }       { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} }
        { first-default } { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} }
      }
      {
        \@@_error:ne { boundary-register-choice }
          { mode~=~\tl_to_str:n {#1} }
        \bool_set_true:N \l_@@_boundary_user_invalid_bool
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_validate:
  {
    \str_case:Vn \l_@@_boundary_user_strategy_tl
      {
        { box }
          {
            \tl_if_eq:NnT \l_@@_boundary_user_mode_tl { first-default }
              {
                \@@_error:ne { boundary-register-mode } { box/first-default }
                \bool_set_true:N \l_@@_boundary_user_invalid_bool
              }
          }
        { wrapped-box }
          {
            \tl_if_eq:NnT \l_@@_boundary_user_mode_tl { first-default }
              {
                \@@_error:ne { boundary-register-mode }
                  { wrapped-box/first-default }
                \bool_set_true:N \l_@@_boundary_user_invalid_bool
              }
          }
        { stream } { }
        { transparent }
          {
            \bool_if:NT \l_@@_boundary_user_mode_given_bool
              {
                \@@_error:ne { boundary-register-mode }
                  { transparent/\l_@@_boundary_user_mode_tl }
                \bool_set_true:N \l_@@_boundary_user_invalid_bool
              }
            \tl_set:Nn \l_@@_boundary_user_mode_tl { none }
          }
        { post-transparent }
          {
            \bool_if:NT \l_@@_boundary_user_mode_given_bool
              {
                \@@_error:ne { boundary-register-mode }
                  { post-transparent/\l_@@_boundary_user_mode_tl }
                \bool_set_true:N \l_@@_boundary_user_invalid_bool
              }
            \tl_set:Nn \l_@@_boundary_user_mode_tl { none }
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_store:
  {
    \exp_args:NV \@@_boundary_if_registered:nTF
      \l_@@_boundary_user_name_tl
      {
        \@@_error:ne { boundary-register-conflict }
          { \c_backslash_str \l_@@_boundary_user_name_tl }
      }
      {
        \exp_args:NNV \prop_if_in:NnTF
          \g_@@_boundary_user_registered_prop \l_@@_boundary_user_name_tl
          {
            \@@_error:ne { boundary-register-duplicate }
              { \c_backslash_str \l_@@_boundary_user_name_tl }
          }
          {
            \tl_set:Ne \l_@@_boundary_user_value_tl
              {
                \l_@@_boundary_user_strategy_tl
                / \l_@@_boundary_user_mode_tl
              }
            \exp_args:NNVV \prop_gput:Nnn
              \g_@@_boundary_user_registered_prop
              \l_@@_boundary_user_name_tl
              \l_@@_boundary_user_value_tl
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_apply:
  {
    \bool_gset_true:N \g_@@_boundary_user_register_closed_bool
    \prop_map_inline:Nn \g_@@_boundary_user_registered_prop
      {
        \@@_boundary_if_registered:nTF {##1}
          {
            \@@_error:ne { boundary-register-conflict }
              { \c_backslash_str ##1 }
          }
          {
            \@@_boundary_user_register_apply:nn {##1} {##2}
            \seq_gput_right:Nn \g_@@_boundary_user_applied_seq {##1}
          }
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_apply:nn #1#2
  {
    \seq_set_split:Nnn \l_@@_boundary_user_value_seq { / } {#2}
    \seq_pop_left:NN
      \l_@@_boundary_user_value_seq \l_@@_boundary_user_strategy_tl
    \seq_pop_left:NN
      \l_@@_boundary_user_value_seq \l_@@_boundary_user_mode_tl
    \str_case:Vn \l_@@_boundary_user_strategy_tl
      {
        { box }
          {
            \exp_args:NnV \@@_boundary_register_box:nn
              {#1} \l_@@_boundary_user_mode_tl
          }
        { wrapped-box }
          {
            \exp_args:NnV \@@_boundary_register_wrapped_box:nn
              {#1} \l_@@_boundary_user_mode_tl
          }
        { stream }
          {
            \exp_args:NnV \@@_boundary_register_stream:nn
              {#1} \l_@@_boundary_user_mode_tl
          }
        { transparent }
          { \@@_boundary_register_transparent:n {#1} }
        { post-transparent }
          { \@@_boundary_register_post_transparent:n {#1} }
      }
  }

\cs_new_protected:Npn \@@_boundary_user_register_check:
  {
    \seq_map_inline:Nn \g_@@_boundary_user_applied_seq
      {
        \cs_if_exist:cF {##1}
          {
            \@@_error:ne { boundary-register-undefined }
              { \c_backslash_str ##1 }
          }
      }
  }
%    \end{macrocode}
% \tn{mbox} 和 \tn{fbox} 的公开命令是 robust wrapper；实际单参数正文由带
% 尾随空格的内部控制序列接收。必须先包装这层，再安装公开命令的 before
% hook：这样 hook 先启动 box capture，正文适配器随后才能报告开头和结尾
% 的公式类别。适配器只检查用户交给盒子命令的可见正文，不监听命令内部的
% \tn{setbox} 测量。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_prepare_onearg_box:N #1
  {
    \group_begin: \exp_args:NcNc \group_end:
      { @@_boundary_prepare_onearg_box:NN }
      #1 { \cs_to_str:N #1 ~ }
  }
\cs_new_protected:Npn \@@_boundary_prepare_onearg_box:NN #1#2
  {
    \@@_boundary_reserve:N #2
    \exp_args:Nc \cs_new_eq:NN
      { @@_boundary_orig_\cs_to_str:N #1 _box:n } #2
    \cs_gset_protected:Npn #2 ##1
      {
        \@@_boundary_math_begin:n {##1}
        \use:c { @@_boundary_orig_\cs_to_str:N #1 _box:n }
          { ##1 \@@_boundary_math_end:n {##1} }
      }
  }
%    \end{macrocode}
% expl3 内部函数不一定允许 LaTeX 通用 cmd hook。下面两个适配器仍复用
% transparent capture 的同一 begin/end，只负责保留零参数或单参数调用签名。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_wrap_transparent_noarg:NN #1#2
  {
    \cs_if_exist:NF #1
      {
        \@@_boundary_reserve:N #2
        \cs_set_eq:NN #1#2
        \cs_gset_protected:Npn #2
          {
            \@@_boundary_hmode_transparent_begin:
            #1
            \@@_boundary_hmode_transparent_end:
          }
      }
  }
%    \end{macrocode}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_wrap_transparent_onearg:NN #1#2
  {
    \cs_if_exist:NF #1
      {
        \@@_boundary_reserve:N #2
        \cs_set_eq:NN #1#2
        \cs_gset_protected:Npn #2 ##1
          {
            \@@_boundary_hmode_transparent_begin:
            #1 ##1
            \@@_boundary_hmode_transparent_end:
          }
      }
  }
%    \end{macrocode}
%
% \changes{v3.10.5}{2026/08/04}{新增按花括号转发参数的 transparent 包装变体，
%   供把参数再次用作 \cs{hbox:n} 内容的目标函数使用（\#1047）。}
%
% 上面那个包装以 |#1 ##1| 的形式转发参数，适用于原函数只是顺序执行参数内容
% 的情形。若原函数会把参数\emph{再次}用作 \cs{hbox:n} 一类的参数，丢失花括号
% 就会改变分组：参数后面相邻的记号会被吸进参数内部。\pkg{hyperref} 的
% \cs{__hyp_target_raise:n} 属于后者，它把参数交给 \cs{hbox:n}，无花括号转发
% 会让紧随其后的 \tn{Hy@SaveSpaceFactor} 被卷进 \tn{hyper@anchorstart} 的参数，
% 结果把 \tn{spacefactor} 赋值写进 \texttt{pdf:dest} 名字、并把锚点名排成
% 可见文本。
%
% 这与 xeCJK 的钩子无关：不挂任何钩子、仅做无花括号透传也能复现（实测盒宽
% 81.16002pt 对 oracle 41.66002pt，该读数来自「不挂钩子的纯透传」对照；在包装变体
% 里误用无花括号版则为 84.49002pt）。因此只需要一个保留花括号的转发变体。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_wrap_transparent_onearg_braced:NN #1#2
  {
    \cs_if_exist:NF #1
      {
        \@@_boundary_reserve:N #2
        \cs_set_eq:NN #1#2
        \cs_gset_protected:Npn #2 ##1
          {
            \@@_boundary_hmode_transparent_begin:
            #1 {##1}
            \@@_boundary_hmode_transparent_end:
          }
      }
  }
%    \end{macrocode}
%
% \changes{v3.10.4}{2026/07/20}{包装内核私有 \tn{@imakebox} 与
%   \tn{@iframebox} 的 \texttt{[\#1][\#2]\#3} 签名以注册 \tn{makebox} /
%   \tn{framebox}；内核若调整该签名须同步此 wrapper（\#992）。}
% 这里包装的是本包最低支持内核 \LaTeXe{} 2026-06-01 中，内部
% \tn{@imakebox}/\tn{@iframebox} 的 |[#1][#2]#3| 参数签名，不是稳定公共
% 接口。若内核改变该签名，必须同步 wrapper；\texttt{command-boundary01}
% 的 optional \tn{makebox}/\tn{framebox} 场景作为漂移门禁。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_makeboxes:
  {
    \clist_map_inline:nn { makebox , framebox , @imakebox , @iframebox }
      { \@@_boundary_reserve:n {##1} }
    \cs_if_exist:NF \@@_orig_imakebox:w
      {
        \cs_new_eq:NN \@@_orig_imakebox:w \@imakebox
        \cs_gset_protected:Npn \@imakebox [##1][##2]##3
          {
            \@@_boundary_inline_last_box_begin:
            \@@_boundary_math_begin:n {##3}
            \@@_orig_imakebox:w [##1][##2]
              { ##3 \@@_boundary_math_end:n {##3} }
            \@@_boundary_inline_last_box_end:n { auto }
          }
      }
    \cs_if_exist:NF \@@_orig_iframebox:w
      {
        \cs_new_eq:NN \@@_orig_iframebox:w \@iframebox
        \cs_gset_protected:Npn \@iframebox [##1][##2]##3
          {
            \@@_boundary_inline_last_box_begin:
            \@@_boundary_math_begin:n {##3}
            \@@_orig_iframebox:w [##1][##2]
              { ##3 \@@_boundary_math_end:n {##3} }
            \@@_boundary_inline_last_box_end:n { auto }
          }
      }
  }
%    \end{macrocode}
% \changes{v3.10.5}{2026/07/29}{\tn{sbox} 改用专用适配器：\tn{global}
%   等前缀必须紧邻 \tn{setbox}，放进 \texttt{cmd/sbox/before} 钩子的赋值
%   会把前缀吃掉，使 \tn{global}\tn{sbox} 静默退化为局部赋值（\#1029）。}
% \tn{sbox} 不能使用通用 \texttt{cmd/.../before} 钩子。钩子代码插在
% \tn{setbox} 之前执行，而调用方写的 \tn{global} 是等待下一个赋值的前缀：
% 钩子里任何赋值都会把它消耗掉，于是 \tn{global}\tn{sbox} 退化为局部赋值，
% 盒子在分组结束时丢失，且不报任何错误（\#1029，\pkg{algorithm2e} 的
% \tn{caption} 即如此消失）。因此这里直接包装内部入口，把暂停观察移到盒子
% 内部，使前缀始终紧邻 \tn{setbox}。
%
% \tn{savebox} 的四种形式（无可选参数、|[wd]|、|[wd][pos]|，以及 picture 形式
% |(x,y)[pos]|，后者经 \tn{@isavepicbox} 汇入）最终都走同一个 \cs{sbox~} 入口，
% 因此排版路径一并
% 覆盖；但 \tn{global}\tn{savebox} 跨分组本来就不生效（前缀在 \tn{savebox}
% 自己的 \tn{@ifnextchar} 前瞻阶段即被消耗，原版 \LaTeX{} 亦然），不在本次
% 修复范围内。
%
% 与 \cs{color@b@x}、\tn{@textcolor} 等适配器不同，这里不保存并委托原定义，而是
% 整体覆盖 \cs{sbox~}：内核 \cs{sbox~} 本体就是赋值语句，委托写法必须把
% \tn{setbox} 留在最外层才能保住前缀，代价明显高于收益。代价是若某个包在本包
% \emph{之前} 也包装了 \cs{sbox~}，它的包装会被丢弃（反序加载正常）。撰写本节时
% 扫描过完整 \TeX{} Live 树，未发现任何宏包重定义 \cs{sbox~} 或对 \tn{sbox}
% 安装 \texttt{cmd/sbox} 钩子，故接受这一取舍；\cs{cs_if_eq:cNF} 保证本包自身
% 不会重复包装。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_sbox:Nn #1#2
  {
    \tex_setbox:D #1 \tex_hbox:D
      {
        \@@_boundary_capture_suspend:
        \color@setgroup #2 \color@endgroup
        \@@_boundary_capture_resume:
      }
  }
\cs_new_protected:Npn \@@_boundary_prepare_sbox:
  {
    \cs_if_eq:cNF { sbox~ } \@@_boundary_sbox:Nn
      { \cs_gset_eq:cN { sbox~ } \@@_boundary_sbox:Nn }
  }

\@@_boundary_prepare_onearg_box:N \mbox
\@@_boundary_prepare_onearg_box:N \fbox
\@@_boundary_register_box:nn { mbox } { auto }
\@@_boundary_register_box:nn { fbox } { auto }
\@@_boundary_register_makeboxes:
\@@_boundary_register_post_transparent:n { null }
\@@_boundary_reserve:n { sbox }
\@@_boundary_prepare_sbox:
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \changes{v3.9.0}{2022/06/06}{修复西文的 character protrusion 功能。}
%
% \begin{macro}[int]{\xeCJK_make_space_node:}
% 用于判断插入空格之前的 node，默认为空，只有用户设置了 \opt{xCJKecglue} 选项才有意义。
% 需要使用 |glue| 来标记，使用 |kern| 会影响 character protrusion 功能。
%    \begin{macrocode}
\cs_new_eq:NN \xeCJK_make_space_node: \prg_do_nothing:
\cs_new_protected:Npe \@@_make_space_node:
  {
    \tex_hskip:D - \@@_glue_node:n { default-space }
    \tex_hskip:D   \@@_glue_node:n { default-space }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{CJKglue}
% CJK 文字之间插入的 |glue|。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CJKglue .code:n =
      {
        \cs_set_protected:Npn \CJKglue {#1}
        \xeCJK_glue_to_skip:nN {#1} \l_@@_ccglue_skip
      }
  }
\skip_new:N \l_@@_ccglue_skip
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.16}{2014/12/16}{整理 \texttt{xCJKecglue} 的部分代码。}
%
% \begin{macro}{CJKecglue,xCJKecglue}
% CJK 与西文和数学行内数学公式之间自动添加的空白。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CJKecglue            .code:n =
      {
        \cs_set_protected:Npn \CJKecglue {#1}
        \xeCJK_glue_to_skip:nN {#1} \l_@@_ecglue_skip
      } ,
    xCJKecglue .choice: ,
    xCJKecglue / true    .code:n =
      {
        \bool_set_true:N  \l_@@_xecglue_bool
        \cs_set_eq:NN \xeCJK_space_or_xecglue: \CJKecglue
        \cs_set_eq:NN \xeCJK_make_space_node: \@@_make_space_node:
        \cs_set_eq:NN \xeCJK_check_for_xglue: \@@_check_for_xglue:
        \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_xecglue:
        \cs_set_eq:NN
          \xeCJK_check_for_ecglue_normalsp:
          \@@_check_for_xecglue_normalsp:
      } ,
    xCJKecglue / false   .code:n =
      {
        \bool_set_false:N \l_@@_xecglue_bool
        \cs_set_eq:NN \xeCJK_space_or_xecglue: \xeCJK_space_glue:
        \xeCJK_cs_clear:N \xeCJK_make_space_node:
        \xeCJK_cs_clear:N \xeCJK_check_for_xglue:
        \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_ecglue:
        \cs_set_eq:NN
          \xeCJK_check_for_ecglue_normalsp:
          \@@_check_for_ecglue_normalsp:
      } ,
    xCJKecglue / unknown .code:n =
      {
        \bool_set_true:N  \l_@@_xecglue_bool
        \cs_set_protected:Npn \CJKecglue {#1}
        \xeCJK_glue_to_skip:nN {#1} \l_@@_ecglue_skip
        \cs_set_eq:NN \xeCJK_space_or_xecglue: \CJKecglue
        \cs_set_eq:NN \xeCJK_make_space_node: \@@_make_space_node:
        \cs_set_eq:NN \xeCJK_check_for_xglue: \@@_check_for_xglue:
        \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_xecglue:
        \cs_set_eq:NN
          \xeCJK_check_for_ecglue_normalsp:
          \@@_check_for_xecglue_normalsp:
      } ,
    xCJKecglue        .default:n = { true }
  }
\cs_new_eq:NN \xeCJK_space_glue: \c_space_tl
\skip_new:N \l_@@_ecglue_skip
\bool_new:N \l_@@_xecglue_bool
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.1}{2016/05/21}{修复 \texttt{CJKspace} 功能失效。}
% \changes{v3.7.2}{2019/04/07}{简化 \texttt{CJKspace} 的实现，并修复错误。}
%
% \begin{macro}{CJKspace}
% 是否保留 CJK 文字间的空白，默认不保留。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CJKspace .choice: ,
    CJKspace / true  .code:n =
      {
        \bool_set_true:N \l_@@_reserve_space_bool
        \cs_set_protected:Npn \@@_ccglue_or_space:
          { \xeCJK_space_glue: }
      } ,
    CJKspace / false .code:n =
      {
        \bool_set_false:N \l_@@_reserve_space_bool
        \cs_set_protected:Npn \@@_ccglue_or_space:
          { \CJKglue }
      } ,
    CJKspace      .default:n = { true } ,
    space            .meta:n = { CJKspace = true  } ,
    nospace          .meta:n = { CJKspace = false }
  }
\bool_new:N \l_@@_reserve_space_bool
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{cjk-bound}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { CJK } { Boundary } { \xeCJK_CJK_and_Boundary:w }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_CJK_and_Boundary:w}
% \changes{v3.2.6}{2013/08/05}{更好的处理边界是 \tn{relax} 的情况。}
% \changes{v3.10.0}{2026/05/04}{修复 CJK 字符后行尾空格在宏命令前
%   未被消除的问题，不再立即输出空格 glue，改为只留标记由后续
%   恢复路径决定间距（\#324）。}
% \changes{v3.10.0}{2026/05/12}{当显式 \texttt{\}} 触发 CJK→Boundary 转换时，
%   设置 \cs{g_@@_glue_check_pending_bool} 以启用后续的 glue 检查，
%   修复 \texttt{前\{中\} 后} 类型排版中多余的 \tn{CJKecglue}（\#831）。}
% 当边界是 \tn{relax} 的时候，它可能是由 |\csname ...\endcsname| 的形式产生的，
% 这样就可能出现问题\footnote{参见 \url{http://bbs.ctex.org/forum.php?mod=viewthread&tid=71563}。}。
% 原来是都在未定义控制序列前都加上 \cs{exp_not:N}，现在则采用分组结束后手工恢复的方式。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_CJK_and_Boundary:w
  {
    \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token
      { \@@_CJK_and_math: }
      {
        \group_align_safe_begin:
        \bool_if:NTF \l_@@_peek_ignore_spaces_bool
          {
            \token_if_macro:NTF \l_peek_token
              {
                \token_if_eq_meaning:NNTF
                  \l_peek_token \@@_boundary_identity:n
                  { \@@_boundary_peek_math:w }
                  { \@@_boundary_reserve_space: }
              }
              {
                \token_if_group_begin:NTF \l_peek_token
                  { \@@_boundary_group_math:w }
                  { \@@_boundary_group_end:n { CJK-space } }
              }
          }
          {
            \token_if_eq_meaning:NNTF \l_peek_token \scan_stop:
              { \@@_CJK_and_Boundary_relax:N }
              {
                \token_if_group_begin:NTF \l_peek_token
                  { \@@_boundary_group_math:w }
                  {
                    \token_if_group_end:NTF \l_peek_token
                      {
                        \bool_gset_true:N \g_@@_glue_check_pending_bool
                        \@@_boundary_group_end:n { CJK }
                      }
                      {
                        \token_if_macro:NTF \l_peek_token
                          {
                            \token_if_eq_meaning:NNTF
                              \l_peek_token \@@_boundary_identity:n
                              { \@@_boundary_peek_math:w }
                              { \@@_boundary_group_end:n { CJK } }
                          }
                          { \@@_boundary_group_end:n { CJK } }
                      }
                  }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_CJK_and_math:
  {
    \bool_if:NTF \l_@@_peek_ignore_spaces_bool
      { \xeCJK_class_group_end: \xeCJK_space_or_xecglue: }
      { \xeCJK_class_group_end: \CJKecglue }
  }
\cs_new_protected:Npn \@@_boundary_CJK_and_math:
  { \group_align_safe_end: \@@_CJK_and_math: }
%    \end{macrocode}
%
% \changes{v3.10.5}{2026/08/02}{只吸收触发转换的那一枚左花括号，不再吞掉整个
%   分组再重新发出，修复 \pkg{tabular} 中 \texttt{中文\textbackslash\textbackslash}
%   报 \texttt{Improper alphabetic constant} 的问题（\#1038）。}
% 这里只需要知道左花括号之后的第一个记号是不是 \tn{\$}，因此不能用 \texttt{n} 型
% 参数把整个分组吞掉：分组的内容可能是别的宏正在执行的语法，提前吞掉再重新发出会
% 破坏它。
%
% 具体的事故是 \#1038：XeTeX 判断字符类时会展开宏，\emph{包括} \tn{protected} 宏
% ——\tn{protected} 只对 \tn{edef} 一类的完全展开语境有效，对「不断展开直到取得
% 一个不可展开记号」的类别前瞻无效。于是 \pkg{tabular} 里 \verb|中文\\| 的 \tn{\\}
% （已 \tn{let} 为 \tn{@tabularcr}）被展开，其替换文本
% \verb|{\ifnum0=`}\fi...| 的首个记号是显式左花括号，本函数因此被选中，并把
% \verb|{\ifnum0=`}| 整个吞掉。重新发出时用的是\emph{隐式}的
% \cs{c_group_end_token}，而 \pkg{latex} 那个平衡花括号的技巧要求反引号紧跟
% \emph{显式}字符记号 \verb|}|，于是报 \texttt{Improper alphabetic constant}。
%
% 改为用 \tn{afterassignment} 加 \tn{let} 把那一枚左花括号当作赋值的右值吸收——
% 这不开新分组——再前瞻其后的第一个记号，最后补发一枚隐式左花括号恢复分组。除这
% 一枚花括号之外，输入流原样保留，因此 \verb|`}| 的相邻关系不被破坏。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_group_math:w
  {
    \tex_afterassignment:D \@@_boundary_group_math_peek:
    \tex_let:D \@@_boundary_group_math_brace:
  }
\cs_new_eq:NN \@@_boundary_group_math_brace: \scan_stop:
\cs_new_protected:Npn \@@_boundary_group_math_peek:
  { \peek_after:Nw \@@_boundary_group_math_branches: }
\cs_new_protected:Npn \@@_boundary_group_math_branches:
  {
    \token_if_math_toggle:NTF \l_peek_token
      { \@@_boundary_CJK_and_math: }
      {
        \bool_if:NTF \l_@@_peek_ignore_spaces_bool
          { \@@_boundary_reserve_space: }
          { \@@_boundary_group_end:n { CJK } }
      }
    \c_group_begin_token
  }
%    \end{macrocode}
% 只展开严格等同于单参数原样输出函数的宏。对任意“可展开”控制序列做
% 预展开会提前执行条件判断或扫描参数，破坏表格等正在运行的 TeX 语法。
%    \begin{macrocode}
\cs_new:Npn \@@_boundary_identity:n #1 {#1}
\cs_new_protected:Npn \@@_boundary_peek_math:w
  {
    \exp_after:wN \peek_after:Nw
    \exp_after:wN \@@_boundary_peek_math_branches:w
    \exp:w \exp_end_continue_f:w
  }
\cs_new_protected:Npn \@@_boundary_peek_math_branches:w
  {
    \token_if_math_toggle:NTF \l_peek_token
      { \@@_boundary_CJK_and_math: }
      {
        \token_if_group_begin:NTF \l_peek_token
          { \@@_boundary_group_math:w }
          {
            \bool_if:NTF \l_@@_peek_ignore_spaces_bool
              { \@@_boundary_reserve_space: }
              { \@@_boundary_group_end:n { CJK } }
          }
      }
  }
\cs_new_protected:Npn \@@_boundary_reserve_space:
  { \@@_boundary_group_end:n { CJK-space } }
\cs_new_protected:Npn \@@_CJK_and_Boundary_relax:N #1
  {
    \@@_boundary_group_end:n { CJK }
    \token_if_eq_meaning:NNTF #1 \scan_stop:
      {#1} { \cs_set_eq:NN #1 \scan_stop: #1 }
  }
\cs_new_protected:Npn \@@_boundary_group_end:n #1
  {
    \group_align_safe_end:
    \xeCJK_class_group_end:
    \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_ecglue_skip
    { \xeCJK_make_node:n {#1} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_ignore_spaces:w}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_ignore_spaces:w
  {
    \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token
      {
        \bool_if:NTF \l_@@_peek_ignore_spaces_bool
          { \xeCJK_space_or_xecglue: } { \CJKecglue }
      }
      {
        \bool_if:NT \l_@@_peek_ignore_spaces_bool
          {
            \dim_case:nn { \tex_lastkern:D }
              {
                { \@@_node:n { CJK } }
                { \xeCJK_remove_node: \xeCJK_make_node:n { CJK-space } }
                { \@@_node:n { default } }
                { \xeCJK_remove_node: \xeCJK_make_space_node: }
              }
            \group_align_safe_begin:
            \token_if_macro:NTF \l_peek_token
              { \@@_reserve_space_aux: }
              { \group_align_safe_end: }
          }
      }
  }
\cs_new_protected:Npn \@@_reserve_space_aux:
  {
    \group_align_safe_end:
    \xeCJK_space_or_xecglue:
  }
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{cjk-cjk}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { CJK } { CJK }
  { \xeCJK_CJK_and_CJK:N }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_CJK_and_CJK:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_CJK_and_CJK:N
  {
    \CJKglue
    \xeCJK_fallback_symbol:NN
    \CJKsymbol
  }
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{fl-fr-others}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { FullLeft }  { CJK }
  {
    \xeCJK_FullLeft_and_CJK:
    \xeCJK_fallback_symbol:NN
    \CJKsymbol
  }
\xeCJK_inter_class_toks:nnn { FullRight } { CJK }
  {
    \xeCJK_FullRight_and_CJK:
    \xeCJK_fallback_symbol:NN
    \CJKsymbol
  }
\seq_map_inline:Nn \g_@@_non_CJK_class_seq
  {
    \clist_map_inline:nn { FullLeft , FullRight }
      {
        \xeCJK_inter_class_toks:nne {#1} {##1}
          { \exp_not:c { xeCJK_Default_and_##1:nN } {#1} }
        \xeCJK_inter_class_toks:nne {##1} {#1}
          { \exp_not:c { xeCJK_##1_and_Default: } }
      }
  }
%    \end{macrocode}
%
% \changes{v3.10.0}{2026/05/03}{新增实验性
%   \opt{experiment/halfright-prebreakpenalty} 选项（\#811）。}
%
% \begin{macro}{\l_@@_halfright_nobreak_bool}
%    \begin{macrocode}
\bool_new:N \l_@@_halfright_nobreak_bool
%    \end{macrocode}
% \end{macro}
%
% 在 |CJK| $\to$ |HalfRight| 和 |FullRight| $\to$ |HalfRight| 的过渡中，
% 条件性地插入禁则 penalty。
%    \begin{macrocode}
\xeCJK_app_inter_class_toks:nnn { CJK } { HalfRight }
  {
    \bool_if:NT \l_@@_halfright_nobreak_bool
      { \xeCJK_no_break: }
  }
%    \end{macrocode}
%
% |FullRight| $\to$ |HalfRight| 需要将 penalty 插入 |punct_glue| 之前，
% 因此覆写而非追加。
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { FullRight } { HalfRight }
  {
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \xeCJK_class_group_end:
    \bool_if:NT \l_@@_halfright_nobreak_bool
      { \xeCJK_no_break: }
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
  }
%    \end{macrocode}
%
% \hypertarget{bound-fl-fr}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { Boundary } { FullLeft }
  { \xeCJK_Boundary_and_FullLeft:N  }
\xeCJK_inter_class_toks:nnn { Boundary } { FullRight }
  { \xeCJK_Boundary_and_FullRight:N  }
%    \end{macrocode}
%
% \hypertarget{fl-fr-bound}{}
%    \begin{macrocode}
\xeCJK_inter_class_toks:nnn { FullLeft } { Boundary }
  { \xeCJK_FullLeft_and_Boundary: }
\xeCJK_inter_class_toks:nnn { FullRight } { Boundary }
  { \xeCJK_FullRight_and_Boundary: }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_FullLeft_and_Boundary:}
% \hypertarget{fl-fr-bound}{}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullLeft_and_Boundary:
  {
    \@@_punct_if_middle:NTF \g_@@_last_punct_tl
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end:
        \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl
        \xeCJK_no_break:
        \@@_punct_glue:NN \c_@@_left_tl  \g_@@_last_punct_tl
        \@@_punct_boundary_guard:
      }
      {
        \xeCJK_class_group_end:
        \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl
        \@@_nobreak_zero_glue:
      }
    \tex_ignorespaces:D
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_and_Boundary:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_and_Boundary:
  {
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \xeCJK_class_group_end:
    \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
    \@@_punct_boundary_guard:
    \tex_ignorespaces:D
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_boundary_guard:}
% \changes{v3.10.0}{2026/05/13}
% {修复 \env{tabular} 中 \tn{unskip} 吞掉标点补偿 glue 的问题（\#827）。}
% \changes{v3.10.0}{2026/05/17}
% {新增 \opt{experiment/punct-measure-fix} 选项：段落模式下通过
%  para/end 钩子恢复被 \tn{unskip} 移除的标点补偿 glue（\#859）。}
% 在 restricted horizontal mode（\env{tabular} 单元格、\tn{hbox} 等）中，
% \tn{\\} 结束行时通过 \tn{unskip} 移除最后一个 glue。
% 这会吞掉标点的补偿 glue，导致单元格宽度错误。
% 此函数在 inner mode 下于 glue 之后插入 |\penalty 0|，
% 使最后节点不再是 glue，从而保护它。
%
% 在段落模式下，当 \opt{experiment/punct-measure-fix} 选项启用时，
% 记录 glue 的自然宽度，并通过 |para/end| 钩子插入等宽
% \tn{kern} 恢复。使用 \tn{kern} 而非 \tn{hskip}：\tn{kern} 不会被
% \tn{unskip} 移除（无需额外保护），也不构成合法断行点
% （避免 CheckSingle 等场景下产生空行）。
%    \begin{macrocode}
\bool_new:N \g_@@_par_guard_bool
\dim_new:N \g_@@_par_guard_dim
\cs_new_protected:Npn \@@_punct_boundary_guard:
  {
    \mode_if_inner:TF
      { \tex_penalty:D \c_zero_int }
      {
        \bool_if:NT \g_@@_punct_measure_fix_bool
          {
            \bool_gset_true:N \g_@@_par_guard_bool
            \dim_gset:Nn \g_@@_par_guard_dim { \tex_lastskip:D }
          }
      }
  }
%    \end{macrocode}
%
% 当 |Boundary| 之后紧跟其他字符类时，说明标点不在段末，
% 需要重置保护标志。
%    \begin{macrocode}
\clist_map_inline:nn
  {
    Default , HalfLeft , HalfRight , NormalSpace ,
    CJK , FullLeft , FullRight , CM , HangulJamo ,
    HangulJamoL , HangulJamoV , HangulJamoT , CJStarter
  }
  {
    \xeCJK_pre_inter_class_toks:nnn { Boundary } {#1}
      { \bool_gset_false:N \g_@@_par_guard_bool }
  }
%    \end{macrocode}
%
% 在 |para/begin| 中重置标志，避免跨段落残留状态。
% 在 |para/end| 中，若标志为真则插入 \tn{kern} 补偿被 \tn{unskip}
% 移除的标点 glue 的自然宽度。
%    \begin{macrocode}
\AddToHook { para/begin }
  { \bool_gset_false:N \g_@@_par_guard_bool }
\AddToHook { para/end }
  {
    \bool_if:NT \g_@@_par_guard_bool
      {
        \tex_kern:D \g_@@_par_guard_dim
        \bool_gset_false:N \g_@@_par_guard_bool
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_punct_node:N}
% 保存标点的当前边界宽度和字符码，通过插入 \tn{kern} 实现。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_punct_node:N #1
  {
    \@@_punct_bound_unitization:NN #1 \l_@@_tmp_dim
    \@@_make_node:N \l_@@_tmp_dim
    \dim_set:Nn \l_@@_tmp_dim { `#1 sp }
    \@@_make_node:N \l_@@_tmp_dim
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_bound_unitization:NN}
% 我们不想出现过大的 \tn{kern}，因此当边界大于 \SI{1}{pt} 时，以 \cs{c_max_dim} 为标准
% 对其进行“单位化”。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_bound_unitization:NN #1#2
  {
    \dim_set:Nn #2
      {
        \dim_max:nn
          { \c_zero_dim }
          { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 }
      }
    \dim_compare:nNnF {#2} < { 1pt }
      { \dim_set:Nn #2 { -1pt * \dim_ratio:nn {#2} { \c_max_dim } } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_punct_bound_kern:N}
% \begin{macro}{\@@_punct_bound_kern:NN}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_punct_bound_kern:N #1
  {
    \exp_after:wN \@@_punct_bound_kern:NN
      \g_@@_last_punct_tl #1
  }
\cs_new_protected:Npn \@@_punct_bound_kern:NN #1#2
  {
    \xeCJK_get_punct_bounds:NN \l_@@_aligni_tl #1
    \xeCJK_get_punct_kerning:NN #1 #2
    \@@_punct_bound_unitization:NN #1 \l_@@_tmp_dim
    \skip_set:Nn \l_@@_punct_kern_skip
      { \@@_use_dim_or_skip:nNN { bound_kern } #1 #2 }
    \dim_compare:nNnF \l_@@_tmp_dim = \l_@@_last_bound_dim
      { \@@_punct_bound_kern_ratio:NN #1 #2 }
    \bool_if:NTF \l_@@_last_penalty_bool
      {
        \tex_penalty:D \l_@@_last_penalty_int
        \skip_horizontal:N
      }
      { \@@_punct_bound_kern_aux:NNN #1 #2 }
      \l_@@_punct_kern_skip
  }
\skip_new:N \l_@@_punct_kern_skip
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_punct_bound_kern_ratio:NN}
% 当标点前后的字体情况不一致时，按一定的比例进行压缩。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_bound_kern_ratio:NN #1#2
  {
    \dim_set:Nn \l_@@_bound_dim
      { \@@_use_punct_dim:nNN { bound_width } #1 #2 }
    \dim_compare:nNnT \l_@@_bound_dim > \c_zero_dim
      {
        \dim_compare:nNnF \l_@@_last_bound_dim > \c_zero_dim
          {
            \dim_set:Nn \l_@@_last_bound_dim
              {
                - \l_@@_last_bound_dim *
                  \dim_ratio:nn { \c_max_dim } { 1pt }
              }
          }
        \@@_punct_bound_kern_ratio_aux:N #2
      }
  }
\cs_new_protected:Npn \@@_punct_bound_kern_ratio_aux:N #1
  {
    \skip_set:Nn \l_@@_punct_kern_skip
      {
        \l_@@_punct_kern_skip *
        \dim_ratio:nn
          {
              \l_@@_last_bound_dim
            + \@@_use_punct_dim:nNN { bound } \c_@@_left_tl #1
          }
          { \l_@@_bound_dim }
      }
  }
%    \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\@@_nobreak_hskip:N,\@@_nobreak_hskip:n,
% \@@_punct_bound_kern:N, \@@_punct_bound_breakable_kern:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_nobreak_hskip:N
  { \xeCJK_no_break: \skip_horizontal:N }
\cs_new_protected:Npn \@@_nobreak_hskip:n
  { \xeCJK_no_break: \skip_horizontal:n }
\cs_new_eq:NN \@@_punct_bound_kern:N \@@_nobreak_hskip:N
\cs_new_protected:Npn \@@_punct_bound_breakable_kern:N
  {
    \tl_if_eq:NNTF \l_@@_aligni_tl \c_@@_right_tl
      {
        \tl_if_eq:NNTF \l_@@_alignii_tl \c_@@_left_tl
          { \skip_horizontal:N }
          { \@@_nobreak_hskip:N }
      }
      { \@@_nobreak_hskip:N }
  }
\cs_new_protected:Npn \@@_punct_bound_kern_aux:NNN #1#2
  {
    \str_if_eq:nnTF {#1} {#2}
      { \@@_nobreak_hskip:N }
      {
        \@@_punct_if_long:NTF #1
          { \skip_horizontal:N }
          {
            \@@_punct_if_long:NTF #2
              { \skip_horizontal:N }
              { \@@_punct_bound_kern:N }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \hypertarget{cjk-fl-fr}{}
%    \begin{macrocode}
\clist_map_inline:nn { CJK , FullLeft , FullRight }
  {
    \clist_map_inline:nn { FullLeft , FullRight }
      {
        \xeCJK_inter_class_toks:nne {#1} {##1}
          { \exp_not:c { xeCJK_#1_and_##1:N } }
      }
  }
%    \end{macrocode}
%
% \begin{macro}{\@@_punct_bound_rule:NN}
% 用于抹去标点符号的全部左/右空白。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_bound_rule:NN #1#2
  {
    \tex_vrule:D
      width - \@@_use_punct_dim:nNN { bound } #1 #2 ~
      depth  \c_zero_dim
      height \c_zero_dim \scan_stop:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_rule:NN}
% 用于减少标点符号的左/右空白。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_rule:NN #1#2
  {
    \tex_vrule:D
      width  \@@_use_punct_dim:nNN { rule } #1 #2 ~
      depth  \c_zero_dim
      height \c_zero_dim \scan_stop:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_glue:NN}
% \changes{v3.2.7}{2013/08/23}
% {标点符号左/右空白的伸展值不超过原始边界，收缩值不小于另一侧边界。}
% 根据所选的标点处理方式在标点符号左/右增加的空白。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_glue:NN #1#2
  { \@@_punct_hskip:n { \@@_use_dim_or_skip:nNN { glue } #1 #2 } }
\cs_new_eq:NN \@@_punct_hskip:n \skip_horizontal:n
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.6.0}{2018/01/23}{总允许长标点与其他标点之间折行。}
% \changes{v3.10.0}{2026/04/27}
% {\texttt{NoBreakLongPunct} 在右侧时禁止折行。}
% \changes{v3.10.2}{2026/07/05}
% {长标点与其他标点之间折行时检查断点两侧的禁则：左标点不得悬于行尾，
%  右标点与 \texttt{NoBreakLongPunct} 不得落于行首（\#456）。}
% \begin{macro}{\xeCJK_punct_kern:NN,\@@_punct_kern:NN}
% 相邻两个标点之间的间距。长标点与其他标点之间允许折行，
% 但断点两侧的禁则必须同时满足，由 \cs{@@_punct_kern_break:NN} 判断。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_kern:NN #1#2
  {
    \str_if_eq:eeTF {#1} {#2}
      { \@@_punct_nobreak_kern:NN }
      {
        \bool_lazy_or:nnTF
          { \@@_punct_if_long_p:N #1 }
          { \@@_punct_if_long_p:N #2 }
          { \@@_punct_kern_break:NN #1 #2 }
          { \@@_punct_nobreak_kern:NN }
      }
    #1 #2
  }
\cs_new_eq:NN \xeCJK_punct_kern:NN \@@_punct_kern:NN
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_kern_break:NN}
% 长标点与其他标点相邻时，判断二者之间的断点是否满足两侧禁则，
% 并留下相应的间距函数（吃掉参数后，后续的 |#1 #2| 交由留下的函数处理）：
% \begin{itemize}
%   \item 断点之前（|#1|）必须是全角右标点或长标点，
%     即全角左标点不得悬于行尾；
%   \item 断点之后（|#2|）必须是全角左标点，或者不属于
%     |NoBreakLongPunct| 的长标点，即全角右标点和省略号等
%     不得落于行首。
% \end{itemize}
% 注意 |#1| 来自 \cs{g_@@_last_punct_tl}，参与字符类判断前
% 需要先展开为字符记号。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_kern_break:NN #1#2
  {
    \exp_after:wN \@@_punct_if_right:NTF #1
      {
        \@@_punct_if_right:NTF #2
          {
            \bool_lazy_and:nnTF
              { \@@_punct_if_long_p:N #2 }
              { ! \@@_punct_if_nobreak_long_p:N #2 }
              { \@@_punct_breakable_kern:NN }
              { \@@_punct_nobreak_kern:NN }
          }
          { \@@_punct_breakable_kern:NN }
      }
      { \@@_punct_nobreak_kern:NN }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_nobreak_kern:NN}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_nobreak_kern:NN #1#2
  { \@@_nobreak_hskip:n { \@@_use_dim_or_skip:nNN { kern } #1 #2 } }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.4}{2013/07/06}
% {使用 \texttt{AllowBreakBetweenPuncts} 时，相应标点符号仍能与边界对齐。}
% \changes{v3.2.7}{2013/08/23}
% {处理 \texttt{AllowBreakBetweenPuncts} 与 \pkg{xeCJKfntef} 的兼容问题。}
%
% \begin{macro}{\@@_punct_breakable_kern:NN}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_punct_breakable_kern:NN #1#2
  {
    \exp_after:wN \@@_punct_if_right:NT #1
      { \@@_punct_rule:NN \c_@@_right_tl #1 }
    \@@_punct_breakable_kern:n
      { \@@_use_dim_or_skip:nNN { bound_kern } #1 #2 }
    \@@_punct_if_right:NF #2
      { \@@_punct_rule:NN \c_@@_left_tl #2 }
  }
\cs_new_eq:NN \@@_punct_breakable_kern:n \skip_horizontal:n
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_last_punct_tl}
% 用于记录当前的标点符号。
%    \begin{macrocode}
\tl_new:N \g_@@_last_punct_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[int]{\xeCJK_FullLeft_and_CJK:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullLeft_and_CJK:
  {
    \@@_punct_if_middle:NTF \g_@@_last_punct_tl
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_no_break:
        \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl
      }
      { }
    \@@_select_font:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullLeft_and_Default:}
% \changes{v3.2.0}{2013/05/20}{修正 \pkg{xeCJK} 使西文在部分情况下无法断词的问题。}
% \changes{v3.7.2}{2018/05/21}{再次修正 FullLeft 类字符与西文连用断词失败的问题。}
% \cs{@@_nobreak_zero_glue:} 用于确保 FullLeft 类后的西文单词可以断词。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullLeft_and_Default:
  {
    \@@_punct_if_middle:NTF \g_@@_last_punct_tl
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end: \xeCJK_no_break:
        \@@_punct_glue:NN \c_@@_left_tl  \g_@@_last_punct_tl
      }
      {
        \xeCJK_class_group_end:
        \@@_nobreak_zero_glue:
      }
  }
\cs_new_protected:Npn \@@_nobreak_zero_glue:
  {
    \tex_penalty:D \c_@@_nobreak_penalty_int
    \skip_horizontal:N \c_zero_skip
  }
\cs_new_protected:Npn \@@_zero_glue:
  { \skip_horizontal:N \c_zero_skip }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_and_CJK:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_and_CJK:
  {
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
    \@@_select_font:
    \CJKglue
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_and_Default:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_and_Default:
  {
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \xeCJK_class_group_end:
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_Default_and_FullLeft:nN}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Default_and_FullLeft:nN #1#2
  {
    \xeCJK_get_punct_bounds:NN \c_@@_left_tl #2
    \@@_Default_and_FullLeft_glue:N #2
    \xeCJK_class_group_begin:
    \xeCJK_select_punct_font:
    \xeCJK_clear_inter_class_toks:nn {#1} { FullLeft }
    \xeCJK_clear_Boundary_and_CJK_toks:
    \tl_gset:Nn \g_@@_last_punct_tl {#2}
    \@@_punct_rule:NN \c_@@_left_tl #2
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol #2
  }
\cs_new_protected:Npn \@@_Default_and_FullLeft_glue:N #1
  { \@@_punct_glue:NN \c_@@_left_tl #1 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_CJK_and_FullLeft:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_CJK_and_FullLeft:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1
    \@@_CJK_and_FullLeft_glue:N #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \@@_punct_rule:NN \c_@@_left_tl #1
    \@@_select_punct_font:
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol #1
  }
\cs_new_protected:Npn \@@_CJK_and_FullLeft_glue:N #1
  {
    \CJKglue
    \@@_punct_glue:NN \c_@@_left_tl #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_Boundary_and_FullLeft:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Boundary_and_FullLeft:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1
    \@@_Boundary_and_FullLeft_glue:N #1
    \xeCJK_class_group_begin:
    \xeCJK_select_punct_font:
    \xeCJK_clear_Boundary_and_CJK_toks:
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \@@_punct_rule:NN \c_@@_left_tl #1
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_Boundary_and_FullLeft_glue:N}
% \changes{v3.2.0}{2013/05/22}{当全角左标点前面是 \texttt{hlist}、\texttt{none}、
% \texttt{glue} 和 \texttt{penalty} 等节点时，压缩其左空白。}
% \changes{v3.2.4}{2013/07/03}{细化边界与全角左标点之间是否压缩空白的判断。}
% \changes{v3.2.5}{2013/07/13}{细化全角左标点是否位于段首的判断。}
% \changes{v3.2.5}{2013/07/13}{增加对 \pkg{enumitem} 宏包修改的 \tn{item} 的判断。}
% 根据 \cs{etex_lastnodetype:D} 的值进行分别处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_Boundary_and_FullLeft_glue:N #1
  {
    \tl_set_eq:NN \l_@@_alignii_tl \c_@@_left_tl
    \group_begin: \exp_args:NNc \group_end: \cs_if_exist_use:NTF
      { @@_bound_type_ \int_use:N \tex_lastnodetype:D _glue:Nn }
      {#1}
      { \use:n }
      { \@@_punct_glue:NN \c_@@_left_tl #1 }
  }
\tl_new:N \c_@@_alignii_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_bound_type_-1_glue:Nn}
% \cs{etex_lastnodetype:D} 为 $-1$ 表示 empty list，常出现在盒子的起始位置，
% 在段落前使用 \tn{noindent} 就是这种情况。
%    \begin{macrocode}
\cs_new_protected:cpn { @@_bound_type_ -1 _glue:Nn } #1#2
  { \@@_zero_glue: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_bound_type_1_glue:Nn}
% \changes{v3.8.5}{2020/06/26}{增加盒子高度判断。}
% \changes{v3.8.9}{2022/05/26}{增加位于段首的支架盒子判断。}
% $1$ 表示 hlist node，在这里用来判断是否位于段首。基于正常情况下，\TeX 会在段落开头插入宽度为
% \tn{parindent} 的水平盒子用于缩进。
%    \begin{macrocode}
\cs_new_protected:cpn { @@_bound_type_  1 _glue:Nn } #1
  {
    \int_do_while:nNnn \tex_lastnodetype:D = \c_@@_hlist_node
      { \@@_bound_hbox_auxi: }
    \@@_if_last_none:TF
      {
        \dim_case:nnF { \box_wd:N \l_@@_indent_box }
          {
            { \tex_parindent:D } { \@@_bound_hbox_auxii:nn }
            { \c_zero_dim }      { \use_i:nn }
          }
          { \use:nn }
      }
      { \use:nn }
      { \hbox_unpack_drop:N \l_@@_indent_box }
  }
\cs_new_protected:Npn \@@_bound_hbox_auxi:
  {
    \box_set_to_last:N \l_@@_tmp_box
    \hbox_set:Nn \l_@@_indent_box
      {
        \box_use:N \l_@@_tmp_box
        \hbox_unpack:N \l_@@_indent_box
      }
  }
\cs_new_protected:Npn \@@_bound_hbox_auxii:nn
  {
    \dim_compare:nNnTF
      { \box_ht:N \l_@@_tmp_box } = \c_zero_dim
      { \use_i:nn }
      { \use:nn }
  }
\box_new:N \l_@@_indent_box
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_bound_type_11_glue:Nn}
% $11$ 表示 glue node，这里判断的目的是当全角左标点出现在 \LaTeX 表格的非 \texttt{p} 列行首时，
% 能够对齐到单元格的边界。判断基于标准 \LaTeX 表格的列格式（\tn{@tabclassz}）定义中，
% 在 \texttt{l} 列和 \texttt{r} 列前为了防止 \tn{tabcolsep} 被无意 \tn{unskip} 掉，
% 都加了 |\hskip1sp|，而 \texttt{c} 列前则有 \tn{hfil}。\package{enumitem} 宏包修改了
% \env{description} 环境中使用的 \tn{item}（\tn{enit@postlabel@i}），
% 在这里起到影响作用的是 |\penalty\z@ \hskip\labelsep|。
%    \begin{macrocode}
\cs_new_protected:cpn { @@_bound_type_ 11 _glue:Nn } #1#2
  {
    \skip_if_finite:nTF { \tex_lastskip:D }
      { \@@_bound_glue_auxi:Nn #1 {#2} }
      { \@@_zero_glue: }
  }
\cs_new_protected:Npn \@@_bound_glue_auxi:Nn #1#2
  {
    \@@_if_last_punct_glue:TF
      { \xeCJK_punct_bound_kern:N #1 }
      { \@@_bound_glue_auxii:n {#2} }
  }
\cs_new_protected:Npn \@@_bound_glue_auxii:n #1
  {
    \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
    \skip_if_eq:nnTF { \l_@@_last_skip } { 1sp }
      { \@@_zero_glue: }
      {
        \skip_if_eq:nnTF { \l_@@_last_skip } { \labelsep }
          {
            \tex_unskip:D
            \@@_if_last_penalty:TF
              {
                \int_compare:nNnTF \tex_lastpenalty:D = \c_zero_int
                  { \skip_horizontal:N \l_@@_last_skip }
                  { \skip_horizontal:N \l_@@_last_skip #1 }
              }
              { \skip_horizontal:N \l_@@_last_skip #1 }
          }
          {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_bound_type_12_glue:Nn}
% $12$ 表示 kern node，用于判断之前的字符是否是 CJK 类，如果是，则插入 \tn{CJKglue}。
%    \begin{macrocode}
\cs_new_protected:cpn { @@_bound_type_ 12 _glue:Nn } #1#2
  {
    \xeCJK_if_last_node:nF { CJK }
      { \xeCJK_if_last_node:nF { CJK-space } { \use_none:nn } }
    \xeCJK_remove_node: \CJKglue
    #2
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_bound_type_13_glue:n}
% $13$ 表示 penalty node，这里判断的目的是全角左标点出现在 \LaTeX 列表环境的 \tn{item} 后面时，
% 能对齐到边界。判断基于 \tn{item} 的内部定义 \tn{@item} 对 \tn{everypar} 进行了修改，在这里起
% 到影响作用的是 |\box\@labels \penalty\z@|。以上判断都比较粗略，暂时也没有想起更好的办法。
%    \begin{macrocode}
\cs_new_protected:cpn { @@_bound_type_ 13 _glue:Nn } #1#2
  {
    \@@_if_last_punct_penalty:TF
      { \xeCJK_punct_bound_kern:N #1 }
      {
        \int_compare:nNnTF \tex_lastpenalty:D = \c_zero_int
          {
            \tex_unpenalty:D
            \@@_if_last_hlist:TF
              { \tex_penalty:D \c_zero_int }
              { \tex_penalty:D \c_zero_int #2 }
          }
          {#2}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_Default_and_FullRight:nN}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Default_and_FullRight:nN #1#2
  {
    \xeCJK_get_punct_bounds:NN \c_@@_right_tl #2
    \@@_Default_and_FullRight_glue:N #2
    \xeCJK_class_group_begin:
    \xeCJK_select_punct_font:
    \xeCJK_clear_inter_class_toks:nn {#1} { FullRight }
    \xeCJK_clear_Boundary_and_CJK_toks:
    \tl_gset:Nn \g_@@_last_punct_tl {#2}
    \xeCJK_FullRight_symbol:N #2
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_Boundary_and_FullRight:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_Boundary_and_FullRight:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1
    \xeCJK_if_last_punct:TF
      {
        \tl_set_eq:NN \l_@@_alignii_tl \c_@@_right_tl
        \xeCJK_punct_bound_kern:N
      }
      { \@@_Default_and_FullRight_glue:N }
      #1
    \xeCJK_class_group_begin:
    \xeCJK_select_punct_font:
    \xeCJK_clear_Boundary_and_CJK_toks:
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \xeCJK_FullRight_symbol:N #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_CJK_and_FullRight:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_CJK_and_FullRight:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1
    \@@_CJK_and_FullRight_glue:N #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \@@_select_punct_font:
    \xeCJK_FullRight_symbol:N #1
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.6.0}{2018/01/23}{解决标点中间被隔开的禁则与压缩问题。}
% \changes{v3.7.3}{2019/04/15}{修复 penalty 数值错误。}
% \changes{v3.8.3}{2020/04/27}{修复 \opt{xCJKecglue} 选项。}
% \changes{v3.8.6}{2020/10/18}{正确还原标点符号后的 penalty 状态。}
%
% \begin{macro}{\xeCJK_if_last_punct:TF}
% \changes{v3.6.1}{2018/02/25}{细化判断。}
% 判断之前是否是一个标点符号。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_if_last_punct:TF
  {
    \bool_set_false:N \l_@@_last_penalty_bool
    \@@_if_last_glue:TF
      { \@@_if_last_punct_glue:TF }
      {
        \@@_if_last_penalty:TF
         { \@@_if_last_punct_penalty:TF }
         { \use_ii:nn }
      }
  }
\cs_new_protected:Npn \@@_if_last_punct_glue:TF
  {
    \prop_get:NoNTF \g_@@_punct_skip_prop
      { \skip_use:N \tex_lastskip:D } \l_@@_tmp_tl
      { \@@_if_last_punct_glue_auxi:TF }
      { \@@_if_last_punct_glue_auxii:TF }
  }
\cs_new_protected:Npn \@@_if_last_punct_glue_auxi:TF
  {
    \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
    \tex_unskip:D
    \int_compare:nNnTF \tex_lastpenalty:D = \c_@@_nobreak_penalty_int
      { \@@_if_last_punct_auxi:TF { \use_i:nn } }
      {
        \xeCJK_if_last_node:TF
          { \@@_if_last_punct_auxii:TF { \use_i:nn } }
          { \use:n }
      }
      { \skip_horizontal:N \l_@@_last_skip \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_glue_auxii:TF
  {
    \group_begin:
      \g_@@_space_factor_int \tex_spacefactor:D
      \skip_if_eq:nnTF { \tex_lastskip:D } { \c_xeCJK_space_skip_tl }
        { \group_end: \@@_if_last_punct_glue_auxiii:TF }
        { \group_end: \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_glue_auxiii:TF
  {
    \skip_set_eq:NN \l_@@_tmp_skip \tex_lastskip:D
    \tex_unskip:D
    \@@_if_last_glue:TF
      {
        \prop_get:NoNTF \g_@@_punct_skip_prop
          { \skip_use:N \tex_lastskip:D } \l_@@_tmp_tl
          { \@@_if_last_punct_glue_auxi:TF { \use_i:nn } }
          { \use:n }
      }
      { \use:n }
      { \skip_horizontal:N \l_@@_tmp_skip \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_penalty:TF
  {
    \int_set_eq:NN \l_@@_last_penalty_int \tex_lastpenalty:D
    \tex_unpenalty:D
    \bool_set_true:N \l_@@_last_penalty_bool
    \@@_if_last_glue:TF
      { \@@_if_last_punct_glue:TF { \use_i:nn } }
      { \use:n }
      { \@@_last_punct_penalty_false:nn }
  }
\cs_new_protected:Npn \@@_last_punct_penalty_false:nn #1#2
  {
    \bool_set_false:N \l_@@_last_penalty_bool
    \tex_penalty:D \l_@@_last_penalty_int
    #2
  }
\cs_new_protected:Npn \@@_if_last_punct_auxi:TF
  {
    \tex_unpenalty:D
    \bool_if:NF \l_@@_last_penalty_bool
      {
        \bool_set_true:N \l_@@_last_penalty_bool
        \int_set_eq:NN \l_@@_last_penalty_int \c_@@_nobreak_penalty_int
      }
    \xeCJK_if_last_node:TF
      { \@@_if_last_punct_auxii:TF { \use_i:nn } }
      { \use:n }
      { \xeCJK_no_break: \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_auxii:TF
  {
    \dim_compare:nNnTF \l_@@_last_kern_dim > \c_zero_dim
      { \@@_if_last_punct_auxiii:TF }
      { \@@_make_node:N \l_@@_last_kern_dim \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_auxiii:TF
  {
    \int_case:nnTF { \tex_XeTeXcharclass:D \l_@@_last_kern_dim }
      {
        { \xeCJK_class_num:n { FullRight } }
        { \tl_set_eq:NN \l_@@_aligni_tl \c_@@_right_tl }
        { \xeCJK_class_num:n { FullLeft } }
        { \tl_set_eq:NN \l_@@_aligni_tl \c_@@_left_tl }
      }
      { \@@_if_last_punct_auxiv:TF }
      { \use_ii:nn }
  }
\cs_new_protected:Npn \@@_if_last_punct_auxiv:TF
  {
    \dim_set_eq:NN \l_@@_tmp_dim \l_@@_last_kern_dim
    \xeCJK_if_last_node:TF
      {
        \tl_gset:Ne \g_@@_last_punct_tl
          { \tex_Uchar:D \l_@@_tmp_dim }
        \dim_set_eq:NN \l_@@_last_bound_dim \l_@@_last_kern_dim
        \use_i:nn
      }
      { \@@_make_node:N \l_@@_tmp_dim \use_ii:nn }
  }
\tl_new:N \l_@@_aligni_tl
\tl_new:N \l_@@_alignii_tl
\int_new:N \l_@@_last_penalty_int
\dim_new:N \l_@@_last_bound_dim
\bool_new:N \l_@@_last_penalty_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJK_if_last_node:TF}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_if_last_node:TF #1#2
  {
    \@@_if_last_kern:TF
      {
        \dim_set_eq:NN \l_@@_last_kern_dim \tex_lastkern:D
        \tex_unkern:D
        \@@_if_last_kern:TF
          {
            \dim_compare:nNnTF \tex_lastkern:D = { - \l_@@_last_kern_dim }
              { \tex_unkern:D #1 }
              { \tex_kern:D \l_@@_last_kern_dim #2 }
          }
          { \tex_kern:D \l_@@_last_kern_dim #2 }
      }
      {#2}
  }
\dim_new:N \l_@@_last_kern_dim
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.6.0}{2018/01/14}
% {修正标点同为 \texttt{LongPunct} 与 \texttt{MiddlePunct} 时的实现错误。}
% \changes{v3.6.0}{2018/01/14}
% {\texttt{Default} 类与 \texttt{MiddlePunct} 之间不应该有 \tn{CJKglue}。}
% \changes{v3.10.0}{2026/04/27}
% {新增 \texttt{NoBreakLongPunct} 属性，禁止在省略号等长标点前断行。}
%
% \begin{macro}
% {\@@_CJK_and_FullRight_glue:N,\@@_Default_and_FullRight_glue:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_CJK_and_FullRight_glue:N #1
  {
    \@@_punct_if_long:NTF #1
      {
        \@@_punct_if_nobreak_long:NTF #1
          { \xeCJK_no_break: }
          { \xeCJK_allow_break: }
      }
      { \xeCJK_no_break: }
    \@@_punct_if_middle:NT #1
      {
        \CJKglue
        \@@_punct_glue:NN \c_@@_right_tl #1
        \@@_punct_rule:NN \c_@@_left_tl #1
      }
  }
\cs_new_protected:Npn \@@_Default_and_FullRight_glue:N #1
  {
    \@@_punct_if_long:NTF #1
      {
        \@@_punct_if_nobreak_long:NTF #1
          { \xeCJK_no_break: }
          { \xeCJK_allow_break: }
      }
      { \xeCJK_no_break: }
    \@@_punct_if_middle:NT #1
      {
        \@@_punct_glue:NN \c_@@_right_tl #1
        \@@_punct_rule:NN \c_@@_left_tl #1
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullLeft_and_FullLeft:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullLeft_and_FullLeft:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1
    \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1
    \@@_punct_kern:NN \g_@@_last_punct_tl #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullLeft_and_FullRight:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullLeft_and_FullRight:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1
    \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1
    \@@_punct_kern:NN \g_@@_last_punct_tl #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \xeCJK_FullRight_symbol:N #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_and_FullLeft:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_and_FullLeft:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1
    \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1
    \xeCJK_punct_kern:NN \g_@@_last_punct_tl #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_and_FullRight:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_and_FullRight:N #1
  {
    \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1
    \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1
    \@@_punct_kern:NN \g_@@_last_punct_tl #1
    \tl_gset:Nn \g_@@_last_punct_tl {#1}
    \xeCJK_FullRight_symbol:N #1
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{全角右标点后的断行}
%
% \begin{macro}{CheckFullRight}
% \changes{v3.1.1}{2012/12/02}{处理全角右标点之后的断行问题。}
% 选项设置。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CheckFullRight .choice: ,
    CheckFullRight / true  .code:n =
      {
        \cs_if_eq:NNF \xeCJK_FullRight_and_Boundary: \xeCJK_check_FullRight:
          {
            \cs_set_eq:NN \@@_save_FullRight_check:
                          \xeCJK_FullRight_and_Boundary:
            \cs_set_eq:NN \@@_save_FullRight_symbol:N
                          \xeCJK_FullRight_symbol:N
            \cs_set_eq:NN \xeCJK_FullRight_and_Boundary:
                          \xeCJK_check_FullRight:
            \cs_set_eq:NN \xeCJK_FullRight_symbol:N
                          \xeCJK_check_FullRight_symbol:Nw
          }
      } ,
    CheckFullRight / false .code:n =
      {
        \cs_if_eq:NNT \xeCJK_FullRight_and_Boundary: \xeCJK_check_FullRight:
          {
            \cs_set_eq:NN \xeCJK_FullRight_and_Boundary:
                          \@@_save_FullRight_check:
            \cs_set_eq:NN \xeCJK_FullRight_symbol:N
                          \@@_save_FullRight_symbol:N
          }
      } ,
    CheckFullRight      .default:n = { true }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{experiment/halfright-prebreakpenalty, experiment/punct-measure-fix,
%   experiment/boundary-register}
%    \begin{macrocode}
\keys_define:nn { xeCJK / boundary-register }
  {
    command .code:n =
      { \@@_boundary_user_register_command:n {#1} } ,
    command .value_required:n = true ,
    strategy .code:n =
      { \@@_boundary_user_register_strategy:n {#1} } ,
    strategy .value_required:n = true ,
    mode .code:n =
      { \@@_boundary_user_register_mode:n {#1} } ,
    mode .value_required:n = true
  }
\keys_define:nn { xeCJK / options / experiment }
  {
    halfright-prebreakpenalty .bool_set:N = \l_@@_halfright_nobreak_bool ,
    halfright-prebreakpenalty .default:n  = true ,
    halfright-prebreakpenalty .initial:n  = false ,
    punct-measure-fix .bool_gset:N = \g_@@_punct_measure_fix_bool ,
    punct-measure-fix .default:n   = true ,
    punct-measure-fix .initial:n   = false ,
    boundary-register .code:n =
      { \@@_boundary_user_register:n {#1} } ,
    boundary-register .value_required:n = true
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_FullRight_symbol:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_FullRight_symbol:N
  {
    \xeCJK_fallback_punct_symbol:NN
    \CJKpunctsymbol
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_FullRight:}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_FullRight:
  {
    \xeCJK_get_punct_bounds:No \c_@@_right_tl \g_@@_last_punct_tl
    \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
    \group_align_safe_begin:
    \token_case_meaning:NoTF \l_peek_token
      { \l_@@_no_break_cs_case_tl }
      {
        \group_align_safe_end:
        \xeCJK_no_break:
        \group_insert_after:N \xeCJK_no_break:
      }
      { \group_align_safe_end: }
    \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl
    \xeCJK_class_group_end:
    \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
    \@@_punct_boundary_guard:
  }
\prg_generate_conditional_variant:Nnn \token_case_meaning:Nn { No } { TF , F }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_FullRight_symbol:Nw}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_FullRight_symbol:Nw #1
  { \peek_remove_spaces:n { \@@_save_FullRight_symbol:N #1 } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_cs_case_keys_define:nNNnn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_cs_case_keys_define:nNNnn #1#2#3#4#5
  {
    \tl_new:N #2
    \seq_new:N #3
    \keys_define:nn { xeCJK / options }
      {
        #1  .code:n =
          {
            \seq_set_split:Nnn #3 { } {##1}
            \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5}
          } ,
        #1+ .code:n =
          {
            \tl_map_inline:nn {##1}
              { \seq_if_in:NnF #3 {####1} { \seq_put_right:Nn #3 {####1} } }
            \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5}
          } ,
        #1- .code:n =
          {
            \tl_map_inline:nn {##1} { \seq_remove_all:Nn #3 {####1} }
            \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5}
          }
      }
  }
\cs_new_protected:Npn \@@_update_cs_case_tl:NNnn #1#2#3#4
  {
    \tl_clear:N #1
    \seq_map_inline:Nn #2 { \tl_put_right:Nn #1 { {##1} {#3} } }
    #4
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{NoBreakCS}
% 设置不能在全角右标点之后断行的控制序列。
%    \begin{macrocode}
\xeCJK_cs_case_keys_define:nNNnn { NoBreakCS }
  \l_@@_no_break_cs_case_tl \l_@@_no_break_cs_seq { } { }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKnobreak}
% \changes{v3.1.1}{2012/12/03}{增加 \tn{nobreak} 的 \pkg{xeCJK} 版本。}
% 为保险起见，我们在这里用了一个循环。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKnobreak { }
  {
    \bool_set_true:N \l_@@_tmp_bool
    \int_while_do:nNnn \tex_lastnodetype:D = \c_@@_glue_node
      {
        \bool_if:NTF \l_@@_tmp_bool
          {
            \bool_set_false:N \l_@@_tmp_bool
            \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D
          }
          { \skip_add:Nn \l_@@_last_skip \tex_lastskip:D }
        \tex_unskip:D
      }
    \xeCJK_if_last_node:TF
      {
        \dim_set_eq:NN \l_@@_tmp_dim \l_@@_last_kern_dim
        \xeCJK_if_last_node:TF
          {
            \@@_if_last_glue:T
              {
                \exp_args:NNNo \tex_unskip:D \xeCJK_no_break:
                \skip_horizontal:n { \skip_use:N \tex_lastskip:D }
              }
            \@@_make_node:N \l_@@_last_kern_dim
          }
          { }
        \@@_make_node:N \l_@@_tmp_dim
      }
      { }
    \xeCJK_no_break:
    \bool_if:NF \l_@@_tmp_bool
      { \skip_horizontal:N \l_@@_last_skip }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{段末孤字处理}
%
% \begin{macro}{CheckSingle}
% 孤字处理功能选项。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CheckSingle .choice: ,
    CheckSingle / true  .code:n =
      {
        \cs_if_eq:NNF \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw
          {
            \cs_set_eq:NN \@@_check_single_save:N \xeCJK_CJK_and_CJK:N
            \cs_set_eq:NN \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw
          }
      } ,
    CheckSingle / false .code:n =
      {
        \cs_if_eq:NNT \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw
          { \cs_set_eq:NN  \xeCJK_CJK_and_CJK:N \@@_check_single_save:N }
      } ,
    CheckSingle      .default:n = { true } ,
    CJKchecksingle      .meta:n = { CheckSingle = true }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.3.1}{2015/04/08}{新选项 \texttt{WidowPenalty}。}
%
% \begin{macro}{WidowPenalty}
% 设置段末汉字的 penalty，默认值是 \num{10000}。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    WidowPenalty .int_set:N = \l_@@_widow_penalty_int ,
    WidowPenalty .default:n = { 10 000 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_widow_penalty:}
% 预防段末孤字而插入的 penalty，值为 \cs{l_@@_widow_penalty_int}。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_widow_penalty:
  { \tex_penalty:D \l_@@_widow_penalty_int }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.7}{2017/03/20}{简化 \texttt{CheckSingle} 的实现，不再展开宏。}
%
% \begin{macro}[int]{\xeCJK_check_single:Nw}
% \begin{macro}{\@@_check_single_end:N}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_single:Nw #1
  {
    \group_align_safe_begin:
    \peek_catcode:NTF \c_catcode_letter_token
      { \xeCJK_check_single:NNw #1 }
      {
        \token_if_other:NTF \l_peek_token
          { \xeCJK_check_single:NNw }
          { \@@_check_single_end:N }
        #1
      }
  }
\cs_new_protected:Npn \@@_check_single_end:N
  {
    \group_align_safe_end:
    \@@_check_single_save:N
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_single:NNw}
% \begin{macro}{\@@_check_single_aux:nNNw}
% \changes{v3.1.1}{2012/12/04}{改进定义，减少使用 \texttt{peek} 函数的次数。}
% \changes{v3.2.7}{2013/08/30}{与 \tn{CJKspace} 兼容。}
% 使用 \cs{group_align_safe_begin:} 和 \cs{group_align_safe_end:} 是为了防止在表格
% 里面报错。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_single:NNw #1#2
  {
    \xeCJK_peek_catcode_ignore_spaces:NTF \c_catcode_letter_token
      {
        \bool_if:NTF \l_@@_peek_ignore_spaces_bool
          {
            \bool_if:NTF \l_@@_reserve_space_bool
              { \@@_check_single_end:N #1 #2 ~ }
              { \@@_check_single_space:NN #1#2 }
          }
          { \@@_check_single_end:N #1 #2 }
      }
      {
        \token_if_other:NTF \l_peek_token
          {
            \bool_if:NTF \l_@@_peek_ignore_spaces_bool
              { \@@_check_single_space:NN }
              { \@@_check_single_end:N }
          }
          {
            \bool_if:NTF \l_@@_peek_ignore_spaces_bool
              { \@@_check_single_aux:nNNw { ~ } }
              { \@@_check_single_aux:nNNw { } }
          }
        #1 #2
      }
  }
\cs_new_protected:Npn \@@_check_single_aux:nNNw #1#2#3
  {
    \token_if_cs:NTF \l_peek_token
      { \xeCJK_check_single_cs:NNn }
      { \xeCJK_check_single_end:NNnw }
    #2 #3 {#1}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_single_end:NNnw}
% \begin{macro}{\@@_check_single_end_aux:NNn,\@@_check_single_end_equation:NNnw}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_single_end_aux:NNn #1#2#3
  { \@@_check_single_end:N #1 #2 #3 }
\cs_new_eq:NN \xeCJK_check_single_end:NNnw \@@_check_single_end_aux:NNn
\cs_new_protected:Npn \@@_check_single_end_equation:NNnw
  {
    \token_if_math_toggle:NTF \l_peek_token
      { \xeCJK_check_single_equation:NNnNw }
      { \@@_check_single_end_aux:NNn }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{PlainEquation}
% \changes{v3.1.1}{2012/12/06}{增加 \texttt{PlainEquation} 选项。}
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    PlainEquation .choice: ,
    PlainEquation / true  .code:n =
      {
        \cs_set_eq:NN \xeCJK_check_single_end:NNnw
                      \@@_check_single_end_equation:NNnw
      } ,
    PlainEquation / false .code:n =
      {
        \cs_set_eq:NN \xeCJK_check_single_end:NNnw
                      \@@_check_single_end_aux:NNn
      } ,
    PlainEquation      .default:n = { true } ,
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_single_space:NN}
% \changes{v3.1.1}{2012/12/13}
% {\texttt{CheckSingle} 支持段末“汉字$+$汉字$+$空格$+$汉字/标点”的形式。}
% \changes{v3.1.2}{2012/12/27}
% {使用 \cs{xeCJK_if_CJK_class:NTF} 来代替 \cs{int_case:nnn} 判断是否是 CJK 字符类。}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_single_space:NN #1#2
  {
    \xeCJK_if_CJK_class:NTF #2
      {
        \xeCJK_if_CJK_class:NTF \l_peek_token
          { \@@_check_single_end:N #1 #2 }
          { \@@_check_single_end:N #1 #2 ~ }
      }
      { \@@_check_single_end:N #1 #2 ~ }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_single_equation:NNnNw}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_single_equation:NNnNw #1#2#3#4
  {
    \peek_catcode:NTF \c_math_toggle_token
      {
        \xeCJK_widow_penalty: \@@_check_single_end:N #1
        \xeCJK_make_node:n { CJK-widow } #2 #4
      }
      { \@@_check_single_end:N #1 #2#3#4 }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.3}{2013/06/11}
% {解决 \texttt{CheckSingle} 选项与 \pkg{tablists} 宏包的冲突。}
% \changes{v3.2.4}{2013/06/26}
% {解决使用 \texttt{CheckSingle} 时，某些 \tn{CJKglue} 不能被正确加入的问题。}
%
% \begin{macro}[int]{\xeCJK_check_single_cs:NNn}
% \changes{v3.3.1}{2015/04/06}{补充可能遗漏的空格。}
% 在使用 \texttt{CheckSingle} 选项时，在 \package{tablists} 宏包定义的
% \env{tabenum} 环境中会出现下面的错误：
% \begin{verbatim}
%   ! Forbidden control sequence found while scanning use of \use_ii:nn.
%   <inserted text>
%                   \par
%    l.10 \item
% \end{verbatim}
% 原因在于 \env{tabenum} 实际上是一个 \TeX 对齐环境（\tn{halign}），\tn{par} 在
% 其中被重定义为 \tn{cr}。而在下面 \cs{token_case_meaning:NnF} 的分支里有对 \tn{par} 的
% \tn{ifx} 判断。解决办法是将判断用 \cs{group_align_safe_begin:} 和
% \cs{group_align_safe_end:} 包起来。或者改用原语 \cs{tex_par:D} 作为判断条件。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_single_cs:NNn #1#2#3
  {
    \token_case_meaning:NoF \l_peek_token
      { \l_@@_check_single_cs_case_tl }
      { \use_iii:nnn }
      { \xeCJK_check_single_env:nnNn }
      {
        \xeCJK_widow_penalty:
        \@@_check_single_end:N #1
        \xeCJK_make_node:n { CJK-widow } #2#3
      }
      { \@@_check_single_end:N #1 #2#3 }
  }
\tl_new:N \l_@@_check_single_cs_case_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_check_single_env:nnNn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_check_single_env:nnNn #1#2#3#4
  {
    \str_case_e:noTF {#4}
      { \l_@@_inline_env_case_tl }
      {#2}
      {#1}
    #3 {#4}
  }
\prg_generate_conditional_variant:Nnn \str_case_e:nn { no } { TF }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.1.1}{2012/12/04}{增加 \texttt{NewLineCS} 和 \texttt{EnvCS} 选项。}
%
% \begin{macro}{NewLineCS}
%    \begin{macrocode}
\xeCJK_cs_case_keys_define:nNNnn { NewLineCS }
  \l_@@_new_line_cs_case_tl \l_@@_new_line_cs_seq
  { \use_ii:nnn }
  {
    \tl_concat:NNN \l_@@_check_single_cs_case_tl
      \l_@@_new_line_cs_case_tl \l_@@_env_cs_case_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{EnvCS}
%    \begin{macrocode}
\xeCJK_cs_case_keys_define:nNNnn { EnvCS }
  \l_@@_env_cs_case_tl \l_@@_env_cs_seq
  { \use:n }
  {
    \tl_concat:NNN \l_@@_check_single_cs_case_tl
      \l_@@_new_line_cs_case_tl \l_@@_env_cs_case_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{InlineEnv}
% \changes{v3.1.1}{2012/12/05}{改变行内环境的设置方式，从而使用 \cs{str_case_x:nnn}
% 代替原来的 \cs{clist_if_in:NnTF} 来判断是否是行内环境。}
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    InlineEnv       .code:n =
      {
        \seq_set_from_clist:Nn \l_@@_inline_env_seq {#1}
        \@@_update_inline_env_case_tl:
      } ,
    InlineEnv+      .code:n =
      {
        \clist_map_inline:nn {#1}
          {
            \seq_if_in:NnF \l_@@_inline_env_seq {##1}
              { \seq_put_right:Nn \l_@@_inline_env_seq {##1} }
          }
        \@@_update_inline_env_case_tl:
      } ,
    InlineEnv-      .code:n =
      {
        \clist_map_inline:nn {#1}
          { \seq_remove_all:Nn \l_@@_inline_env_seq {##1} }
        \@@_update_inline_env_case_tl:
      }
  }
\seq_new:N \l_@@_inline_env_seq
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_update_inline_env_case_tl:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_update_inline_env_case_tl:
  {
    \tl_clear:N \l_@@_inline_env_case_tl
    \seq_map_inline:Nn \l_@@_inline_env_seq
      { \tl_put_right:Nn \l_@@_inline_env_case_tl { {##1} { } } }
  }
\tl_new:N \l_@@_inline_env_case_tl
%    \end{macrocode}
% \end{macro}
%
% \subsection{增加 CJK 子分区}
%
% \begin{variable}{\g_@@_CJK_sub_class_seq}
%    \begin{macrocode}
\seq_new:N \g_@@_CJK_sub_class_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\xeCJKDeclareSubCJKBlock}
% 声明 CJK 子区范围，|#1| 为自定义名称，|#2| 为子区的 |Unicode| 范围。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKDeclareSubCJKBlock
  { s > { \TrimSpaces } m m }
  {
    \xeCJK_declare_sub_char_class:nen { CJK } {#2} {#3}
    \IfBooleanT {#1} { \xeCJKResetPunctClass }
  }
\@onlypreamble \xeCJKDeclareSubCJKBlock
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKCancelSubCJKBlock,\xeCJKRestoreSubCJKBlock}
% 取消和恢复对 CJK 子区的声明。
%    \begin{macrocode}
\bool_new:N \l_@@_sub_cancel_bool
\NewDocumentCommand \xeCJKCancelSubCJKBlock { s m }
  {
    \bool_if:NF \l_@@_sub_cancel_bool
      {
        \bool_set_true:N \l_@@_sub_cancel_bool
        \@@_sub_restore_or_cancel:e {#2}
        \IfBooleanT {#1} { \xeCJKResetPunctClass }
      }
  }
\NewDocumentCommand \xeCJKRestoreSubCJKBlock { s m }
  {
    \bool_if:NT \l_@@_sub_cancel_bool
      {
        \bool_set_false:N \l_@@_sub_cancel_bool
        \@@_sub_restore_or_cancel:e {#2}
        \IfBooleanT {#1} { \xeCJKResetPunctClass }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_sub_restore_or_cancel:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_sub_restore_or_cancel:n #1
  {
    \clist_map_inline:nn {#1}
      {
        \int_if_exist:cTF { \@@_class_csname:n { CJK/##1 } }
          {
            \xeCJK_declare_char_class:nn
              { CJK \bool_if:NF \l_@@_sub_cancel_bool { /##1 } }
              { \use:c { g_@@_CJK/##1_range_clist } }
          }
          { \@@_error:ne { SubBlock-undefined } {##1} }
      }
  }
\cs_generate_variant:Nn \@@_sub_restore_or_cancel:n { e }
\@@_msg_new:nn { SubBlock-undefined }
  {
    The~CJK~sub~block~`#1'~is~undefined.\\\\
    Try~to~use~\token_to_str:N \xeCJKDeclareSubCJKBlock \
    to~declare~it.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_declare_sub_char_class:nnn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_declare_sub_char_class:nnn #1#2#3
  {
    \int_if_exist:cF { \@@_class_csname:n { #1/#2 } }
      {
        \xeCJK_new_class:n { #1/#2 }
        \@@_set_sub_class_toks:nn {#1} {#2}
        \xeCJK_new_sub_key:n {#2}
      }
    \xeCJK_declare_char_class:nn { #1/#2 } {#3}
  }
\cs_generate_variant:Nn \xeCJK_declare_sub_char_class:nnn { ne }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_sub_class_toks:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_sub_class_toks:nn #1#2
  {
    \seq_map_inline:Nn \g_@@_base_class_seq
      {
        \xeCJK_copy_inter_class_toks:nnnn { #1/#2 } {##1} {#1}  {##1}
        \xeCJK_copy_inter_class_toks:nnnn {##1} { #1/#2 } {##1} {#1}
        \str_if_eq:nnTF {##1} { CJK }
          {
            \xeCJK_pre_inter_class_toks:nnn {##1} { #1/#2 }
              { \@@_switch_font:nn {#1} {#2} }
          }
          {
            \xeCJK_replace_inter_class_toks:nnnn {##1} { #1/#2 }
              { \xeCJK_fallback_symbol:NN }
              {
                \@@_switch_font:nn {#1} {#2}
                \xeCJK_fallback_symbol:NN
              }
          }
      }
    \xeCJK_copy_inter_class_toks:nnnn { #1/#2 } { #1/#2 } {#1} {#1}
    \seq_map_inline:Nn \g_@@_CJK_sub_class_seq
      {
        \xeCJK_copy_inter_class_toks:nnnn { #1/#2  } { #1/##1 } {#1} {#1}
        \xeCJK_copy_inter_class_toks:nnnn { #1/##1 } { #1/#2  } {#1} {#1}
        \xeCJK_pre_inter_class_toks:nnn { #1/#2 } { #1/##1 }
          { \@@_switch_font:nn {#2} {##1} }
        \xeCJK_pre_inter_class_toks:nnn { #1/##1 } { #1/#2 }
          { \@@_switch_font:nn {##1} {#2} }
      }
    \seq_gput_right:Nn \g_@@_CJK_sub_class_seq {#2}
    \@@_save_CJK_class:n { #1/#2 }
    \clist_map_inline:nn
      {
        CJK , FullLeft , FullRight , HangulJamo ,
        HangulJamoL , HangulJamoV , HangulJamoT , CJStarter
      }
      {
        \xeCJK_pre_inter_class_toks:nnn { #1/#2 } {##1}
          { \@@_switch_font:nn {#2} {#1} }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{标点处理}
%
% \changes{v3.2.7}{2013/08/22}{实现自定义行首/尾标点符号宽度功能。}
% \changes{v3.7.4}{2019/05/31}{简化行首/尾标点符号宽度的实现。}
%
% \tn{XeTeXglyphbounds} 可以得到一个字符的左右边距，用于标点压缩。如果它不可用，则
% 在文档中只能使用 |plain| 这一标点格式原样输出标点。
%    \begin{macrocode}
\cs_if_exist:NF \tex_XeTeXglyphbounds:D
  {
    \@@_msg_new:nn { XeTeX-too-old }
      {
        \token_to_str:N \tex_XeTeXglyphbounds:D \ is~not~defined.\\
        CJK~punctuation~kerning~will~not~be~available.\\\\
        You~have~to~update~XeTeX~to~the~version~0.9995.0~or~later.
      }
    \@@_error:n { XeTeX-too-old }
    \AtEndOfPackage
      {
        \keys_define:nn { xeCJK / options }
          {
            PunctStyle .code:n =
              { \@@_error:ne { punct-style-unknown } {#1} }
          }
        \seq_gclear:N \g_@@_punct_style_seq
        \@@_set_punct_style:n { plain }
      }
  }
%    \end{macrocode}
%
% \begin{macro}{\xeCJKsetwidth}
% 手动设置参数中的标点符号的宽度。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKsetwidth { s m m }
  {
    \IfBooleanTF {#1}
      {
        \tl_map_inline:en {#2}
          {
            \tl_if_exist:cF { g_@@_punct_width/##1/tl }
              { \tl_new:c { g_@@_punct_width/##1/tl } }
            \tl_gset:cn { g_@@_punct_bound_width/##1/tl } {#3}
          }
      }
      {
        \tl_map_inline:en {#2}
          {
            \tl_if_exist:cF { g_@@_punct_width/##1/tl }
              { \tl_new:c { g_@@_punct_width/##1/tl } }
            \tl_gset:cn { g_@@_punct_width/##1/tl } {#3}
          }
      }
  }
\@onlypreamble \xeCJKsetwidth
\cs_generate_variant:Nn \tl_map_inline:nn { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKsetkern}
% 手动设置相邻标点的距离。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKsetkern { m m m }
  { \tl_gset:cn { g_@@_punct/kern/#1/#2/tl } {#3} }
\@onlypreamble \xeCJKsetkern
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\c_@@_left_tl,\c_@@_right_tl}
%    \begin{macrocode}
\tl_const:Nn \c_@@_left_tl  { left }
\tl_const:Nn \c_@@_right_tl { right }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.2.12}{2014/05/12}{新增 \texttt{RubberPunctSkip} 选项。}
% \changes{v3.4.0}{2016/05/13}{\texttt{RubberPunctSkip} 选项有新的值 \texttt{plus} 和 \texttt{minus}。}
% \changes{v3.10.2}{2026/07/04}{新增 \texttt{PoZheHaoLigature} 选项，
%  支持字体的破折号合字功能（\#382）。}
% \changes{v3.10.2}{2026/07/05}{新增 \texttt{LatinPunct} 选项，
%  中西文共用的弯引号、间隔号与省略号可以切换为西文标点（\#389、\#431）。}
% \changes{v3.10.3}{2026/07/12}{新增 \texttt{CJLineBreak} 选项，
%  为日文小假名提供可选严格行首禁则（\#165）。}
%
% \begin{macro}
%  {AllowBreakBetweenPuncts,KaiMingPunct,LongPunct,NoBreakLongPunct,
%   MiddlePunct,PoZheHaoLigature,LatinPunct,CJLineBreak,
%   PunctWidth,PunctBoundWidth,RubberPunctSkip}
% 相关选项声明。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    AllowBreakBetweenPuncts .choice: ,
    AllowBreakBetweenPuncts / true  .code:n =
      {
        \bool_set_true:N \l_@@_punct_breakable_bool
        \cs_set_eq:NN \xeCJK_punct_kern:NN \@@_punct_breakable_kern:NN
        \cs_set_eq:NN \@@_punct_bound_kern:N
                      \@@_punct_bound_breakable_kern:N
      } ,
    AllowBreakBetweenPuncts / false .code:n =
      {
        \bool_set_false:N \l_@@_punct_breakable_bool
        \cs_set_eq:NN \xeCJK_punct_kern:NN \@@_punct_kern:NN
        \cs_set_eq:NN \@@_punct_bound_kern:N
                      \@@_nobreak_hskip:N
      } ,
    AllowBreakBetweenPuncts      .default:n = { true } ,
    KaiMingPunct  .code:n = { \@@_set_special_punct:nn { mixed_width } {#1} } ,
    KaiMingPunct+ .code:n = { \@@_add_special_punct:nn { mixed_width } {#1} } ,
    KaiMingPunct- .code:n = { \@@_sub_special_punct:nn { mixed_width } {#1} } ,
    LongPunct     .code:n = { \@@_set_special_punct:nn { long } {#1} } ,
    LongPunct+    .code:n = { \@@_add_special_punct:nn { long } {#1} } ,
    LongPunct-    .code:n = { \@@_sub_special_punct:nn { long } {#1} } ,
    NoBreakLongPunct  .code:n = { \@@_set_special_punct:nn { nobreak_long } {#1} } ,
    NoBreakLongPunct+ .code:n = { \@@_add_special_punct:nn { nobreak_long } {#1} } ,
    NoBreakLongPunct- .code:n = { \@@_sub_special_punct:nn { nobreak_long } {#1} } ,
    MiddlePunct   .code:n = { \@@_set_special_punct:nn { middle } {#1} } ,
    MiddlePunct+  .code:n = { \@@_add_special_punct:nn { middle } {#1} } ,
    MiddlePunct-  .code:n = { \@@_sub_special_punct:nn { middle } {#1} } ,
    PoZheHaoLigature .choice: ,
    PoZheHaoLigature / true  .code:n =
      {
        \bool_set_true:N \l_@@_pozhehao_ligature_bool
        \xeCJK_declare_char_class:nn { PoZheHao } { "2014 , "2015 }
      } ,
    PoZheHaoLigature / false .code:n =
      {
        \bool_set_false:N \l_@@_pozhehao_ligature_bool
        \xeCJK_declare_char_class:nn { FullRight } { "2014 }
        \xeCJK_declare_char_class:nn { Default }   { "2015 }
      } ,
    PoZheHaoLigature .default:n = { true } ,
    LatinPunct .choice: ,
    LatinPunct / true  .code:n =
      {
        \bool_set_true:N \l_@@_latin_punct_bool
        \xeCJK_declare_char_class:nn { HalfLeft }
          { "2018 , "201C }
        \xeCJK_declare_char_class:nn { HalfRight }
          { "00B7 , "2019 , "201D , "2025 , "2026 , "2027 }
      } ,
    LatinPunct / false .code:n =
      {
        \bool_set_false:N \l_@@_latin_punct_bool
        \xeCJK_declare_char_class:nn { FullLeft }
          { "2018 , "201C }
        \xeCJK_declare_char_class:nn { FullRight }
          { "00B7 , "2019 , "201D , "2025 , "2026 , "2027 }
      } ,
    LatinPunct .default:n = { true } ,
    CJLineBreak .choices:nn = { normal , strict }
      {
        \str_if_eq:nnTF {#1} { strict }
          {
            \bool_set_true:N \l_@@_CJ_strict_bool
            \xeCJK_declare_char_class:nN { CJStarter } \c_@@_CJ_chars_clist
          }
          {
            \bool_set_false:N \l_@@_CJ_strict_bool
            \xeCJK_declare_char_class:nN { CJK } \c_@@_CJ_chars_clist
          }
      } ,
    CJLineBreak .initial:n = { normal } ,
    PunctWidth      .tl_gset:N = \g_@@_punct_width_tl ,
    PunctBoundWidth .tl_gset:N = \g_@@_punct_bound_width_tl ,
    PunctWidth      .value_required:n = true ,
    PunctBoundWidth .value_required:n = true ,
    RubberPunctSkip .choice: ,
    RubberPunctSkip      .default:n = { true } ,
    RubberPunctSkip / true  .code:n =
      { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip:nNN } ,
    RubberPunctSkip / plus  .code:n =
      { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip_plus:nNN } ,
    RubberPunctSkip / minus .code:n =
      { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip_minus:nNN } ,
    RubberPunctSkip / false .code:n =
      { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_dim:nNN }
  }
\bool_new:N \l_@@_punct_breakable_bool
%    \end{macrocode}
% \end{macro}
%
% 相关选项定义的辅助函数。
%    \begin{macrocode}
\clist_new:N \g_@@_special_punct_clist
\clist_gset:Nn \g_@@_special_punct_clist { mixed_width , long , nobreak_long , middle }
\cs_new:Npn \@@_special_punct_seq:n #1 { g_@@_special_punct_#1_seq }
\cs_new:Npn \@@_special_punct_tl:nN #1#2 { g_@@_special_punct_#1_#2_tl }
\clist_map_inline:Nn \g_@@_special_punct_clist
  { \seq_new:c { \@@_special_punct_seq:n {#1} } }
\cs_new_protected:Npn \@@_set_special_punct:nn #1#2
  {
    \seq_map_inline:cn { \@@_special_punct_seq:n {#1} }
      { \cs_undefine:c { \@@_special_punct_tl:nN {#1} {##1} } }
    \seq_gclear:c { \@@_special_punct_seq:n {#1} }
    \tl_map_inline:en {#2}
      {
        \tl_new:c { \@@_special_punct_tl:nN {#1} {##1} }
        \seq_gput_right:cn { \@@_special_punct_seq:n {#1} } {##1}
      }
  }
\cs_new_protected:Npn \@@_add_special_punct:nn #1#2
  {
    \tl_map_inline:en {#2}
      {
        \seq_if_in:cnF { \@@_special_punct_seq:n {#1} } {##1}
          {
            \tl_new:c { \@@_special_punct_tl:nN {#1} {##1} }
            \seq_gput_right:cn { \@@_special_punct_seq:n {#1} } {##1}
          }
      }
  }
\cs_new_protected:Npn \@@_sub_special_punct:nn #1#2
  {
    \tl_map_inline:en {#2}
      {
        \cs_undefine:c { \@@_special_punct_tl:nN {#1} {##1} }
        \seq_gremove_all:cn { \@@_special_punct_seq:n {#1} } {##1}
      }
  }
%    \end{macrocode}
%
% 判断一个标点符号是否为全角右标点和长标点符号。
% \changes{v3.10.2}{2026/07/04}{\texttt{PoZheHao} 类字符同样按全角右标点
%  处理（\#382）。}
% 注意，|PoZheHao| 类的破折号同样按全角右标点处理，
% 保证它与其他标点相邻时使用正确的间距参数。
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_punct_if_right:N #1 { p , T , F , TF }
  {
    \if_int_compare:w \xeCJK_token_value_class:N #1 =
                      \xeCJK_class_num:n { FullRight }
      \prg_return_true:
    \else:
      \if_int_compare:w \xeCJK_token_value_class:N #1 =
                        \xeCJK_class_num:n { PoZheHao }
        \prg_return_true:
      \else:
        \prg_return_false:
      \fi:
    \fi:
  }
\clist_map_inline:Nn \g_@@_special_punct_clist
  {
    \exp_args:Nc
    \prg_new_conditional:Npnn { @@_punct_if_#1:N } ##1 { p , T , F , TF }
      {
        \if_cs_exist:w \@@_special_punct_tl:nN {#1} {##1} \cs_end:
          \prg_return_true: \else: \prg_return_false: \fi:
      }
  }
%    \end{macrocode}
%
% 一些用于记录的辅助函数。
%    \begin{macrocode}
\cs_new:Npn \@@_punct_csname:n #1
  { c_@@_\l_xeCJK_current_punct_font_tl/\l_xeCJK_punct_style_tl/#1/tl }
\cs_new:Npn \@@_use_punct_dim:nN #1#2
  { \use:c { \@@_punct_csname:n { dim/#1/#2 } } }
\cs_new:Npn \@@_use_punct_dim:nNN #1#2#3
  { \use:c { \@@_punct_csname:n { dim/#1/#2/#3 } } }
\cs_new:Npn \@@_use_punct_skip:nNN #1#2#3
  { \use:c { \@@_punct_csname:n { skip/#1/#2/#3 } } }
\cs_new:Npn \@@_use_punct_skip_plus:nNN #1#2#3
  { \use:c { \@@_punct_csname:n { skip/plus/#1/#2/#3 } } }
\cs_new:Npn \@@_use_punct_skip_minus:nNN #1#2#3
  { \use:c { \@@_punct_csname:n { skip/minus/#1/#2/#3 } } }
\cs_new_protected:Npn \@@_save_punct_dim:nNn #1#2
  { \@@_save_punct_width_aux:nnnn { dim } {#1} { #1/#2 } }
\cs_new_protected:Npn \@@_save_punct_dim:nNNn #1#2#3
  { \@@_save_punct_width_aux:nnnn { dim } {#1} { #1/#2/#3 } }
\cs_new_protected:Npn \@@_save_punct_skip:nNNn #1#2#3#4
  {
    \@@_save_punct_width_aux:nnnn { skip } {#1} { #1/#2/#3 } {#4}
    \@@_save_punct_width_aux:nnnn { skip } {#1} { plus/#1/#2/#3 } {#4}
    \@@_save_punct_width_aux:nnnn { skip } {#1} { minus/#1/#2/#3 } {#4}
  }
\cs_new_protected:Npn \@@_save_punct_skip:nNNnnn #1#2#3#4#5#6
  {
    \exp_last_unbraced:Ne
    \@@_save_punct_skip_aux:nnnnn
      {
        {#1}
        { #1/#2/#3 }
        { \dim_eval:n {#4} }
        { \dim_max:nn { \c_zero_dim } {#5} }
        { \dim_max:nn { \c_zero_dim } {#6} }
      }
  }
\cs_new_protected:Npn \@@_save_punct_skip_aux:nnnnn #1#2#3#4#5
  {
    \@@_save_punct_width_aux:nnnn { skip } {#1}
      {#2}         { #3 ~ plus ~ #4 ~ minus ~ #5 ~ }
    \@@_save_punct_width_aux:nnnn { skip } {#1}
      { plus/#2 }  { #3 ~ plus ~ #4 ~ }
    \@@_save_punct_width_aux:nnnn { skip } {#1}
      { minus/#2 } { #3 ~ minus ~ #5 ~ }
  }
\cs_new_protected:Npn \@@_save_punct_width_aux:nnnn #1#2#3#4
  {
    \@@_save_punct_width_aux:cen
      { \@@_punct_csname:n { #1/#3 } }
      { \use:c { #1_eval:n } {#4} }
      {#2}
  }
\cs_new_protected:Npn \@@_save_punct_width_aux:Nnn #1#2#3
  {
    \tl_const:Nn #1 {#2}
    \str_if_eq:nnT {#3} { glue }
      { \prop_gput:Nnn \g_@@_punct_skip_prop {#2} { } }
  }
\prop_new:N \g_@@_punct_skip_prop
\prop_gput:Non \g_@@_punct_skip_prop { \skip_use:N \c_zero_skip } { }
\cs_generate_variant:Nn \@@_save_punct_width_aux:Nnn { ce }
\cs_new_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip:nNN
%    \end{macrocode}
%
% \changes{v3.1.0}{2012/11/14}{使用 \pkg{xtemplate} 宏包的机制来组织标点符号的处理。}
%
% 定义标点处理模板。
%    \begin{macrocode}
\DeclareObjectType { xeCJK / punctuation } { 0 }
%    \end{macrocode}
%
%    \begin{macrocode}
\DeclareTemplateInterface { xeCJK / punctuation } { basic } { 0 }
  {
    enabled-global-setting     : boolean = true ,
    fixed-punct-width          : length  = \c_max_dim ,
    fixed-punct-ratio          : real    = \c_one_fp ,
    mixed-punct-width          : length  = \KeyValue { fixed-punct-width } ,
    mixed-punct-ratio          : real    = \KeyValue { fixed-punct-ratio } ,
    middle-punct-width         : length  = \KeyValue { fixed-punct-width } ,
    middle-punct-ratio         : real    = \KeyValue { fixed-punct-ratio } ,
    fixed-margin-width         : length  = \c_max_dim ,
    fixed-margin-ratio         : real    = \c_one_fp ,
    mixed-margin-width         : length  = \KeyValue { fixed-margin-width } ,
    mixed-margin-ratio         : real    = \KeyValue { fixed-margin-ratio } ,
    middle-margin-width        : length  = \KeyValue { fixed-margin-width } ,
    middle-margin-ratio        : real    = \KeyValue { fixed-margin-ratio } ,
    bound-punct-width          : length  = \c_max_dim ,
    bound-punct-ratio          : real    = \c_nan_fp ,
    bound-margin-width         : length  = \c_max_dim ,
    bound-margin-ratio         : real    = \c_zero_fp ,
    enabled-hanging            : boolean = false ,
    add-min-bound-to-margin    : boolean = false ,
    optimize-margin            : boolean = false ,
    margin-minimum             : length  = \c_zero_dim ,
    enabled-kerning            : boolean = true ,
    enabled-left-right-kerning : boolean = true ,
    min-bound-to-kerning       : boolean = false ,
    kerning-total-width        : length  = \c_max_dim ,
    kerning-total-ratio        : real    = 0.75 ,
    optimize-kerning           : boolean = false ,
    same-align-margin          : length  = \c_max_dim ,
    same-align-ratio           : real    = \c_nan_fp ,
    different-align-margin     : length  = \c_max_dim ,
    different-align-ratio      : real    = \c_nan_fp ,
    kerning-margin-width       : length  = \c_max_dim ,
    kerning-margin-ratio       : real    = \c_one_fp ,
    kerning-margin-minimum     : length  = \c_zero_dim
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\DeclareTemplateCode { xeCJK / punctuation } { basic } { 0 }
  {
    enabled-global-setting     = \l_@@_enabled_global_setting_bool ,
    fixed-punct-width          = \l_@@_fixed_punct_width_dim ,
    fixed-punct-ratio          = \l_@@_fixed_punct_ratio_fp ,
    mixed-punct-width          = \l_@@_mixed_punct_width_dim ,
    mixed-punct-ratio          = \l_@@_mixed_punct_ratio_fp ,
    middle-punct-width         = \l_@@_middle_punct_width_dim ,
    middle-punct-ratio         = \l_@@_middle_punct_ratio_fp ,
    fixed-margin-width         = \l_@@_fixed_margin_width_dim ,
    fixed-margin-ratio         = \l_@@_fixed_margin_ratio_fp ,
    mixed-margin-width         = \l_@@_mixed_margin_width_dim ,
    mixed-margin-ratio         = \l_@@_mixed_margin_ratio_fp ,
    middle-margin-width        = \l_@@_middle_margin_width_dim ,
    middle-margin-ratio        = \l_@@_middle_margin_ratio_fp ,
    bound-punct-width          = \l_@@_bound_punct_width_dim ,
    bound-punct-ratio          = \l_@@_bound_punct_ratio_fp ,
    bound-margin-width         = \l_@@_bound_margin_width_dim ,
    bound-margin-ratio         = \l_@@_bound_margin_ratio_fp ,
    enabled-hanging            = \l_@@_enabled_hanging_bool ,
    add-min-bound-to-margin    = \l_@@_add_min_bound_to_margin_bool ,
    optimize-margin            = \l_@@_optimize_margin_bool ,
    margin-minimum             = \l_@@_margin_minimum_dim ,
    enabled-kerning            = \l_@@_enabled_kerning_bool ,
    enabled-left-right-kerning = \l_@@_enabled_left_right_kerning_bool ,
    min-bound-to-kerning       = \l_@@_min_bound_to_kerning_bool ,
    kerning-total-width        = \l_@@_kerning_total_width_dim ,
    kerning-total-ratio        = \l_@@_kerning_total_ratio_fp ,
    optimize-kerning           = \l_@@_optimize_kerning_bool ,
    same-align-margin          = \l_@@_same_align_margin_dim ,
    same-align-ratio           = \l_@@_same_align_ratio_fp ,
    different-align-margin     = \l_@@_different_align_margin_dim ,
    different-align-ratio      = \l_@@_different_align_ratio_fp ,
    kerning-margin-width       = \l_@@_kerning_margin_width_dim ,
    kerning-margin-ratio       = \l_@@_kerning_margin_ratio_fp ,
    kerning-margin-minimum     = \l_@@_kerning_margin_minimum_dim
  }
  { \AssignTemplateKeys }
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_get_punct_bounds:NN}
% |#1| 为 \cs{c_@@_left_tl} 或 \cs{c_@@_right_tl}，|#2| 为标点符号。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_get_punct_bounds:NN #1#2
  {
    \tl_if_exist:cF { \@@_punct_csname:n { dim/glue/#1/#2 } }
      { \@@_get_punct_bounds_aux:NN #1 #2 }
  }
\cs_new_protected:Npn \@@_get_punct_bounds_aux:NN
  {
    \tl_if_eq:NNTF \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl
      { \@@_save_punct_margin_plain:NN }
      { \@@_save_punct_margin:NN }
  }
\cs_new_protected:Npn \xeCJK_get_punct_bounds:No
  { \exp_last_unbraced:NNo \xeCJK_get_punct_bounds:NN }
\cs_new_protected:Npn \@@_save_punct_margin_plain:NN #1#2
  {
    \@@_save_punct_dim:nNNn { glue }   #1 #2 { \c_zero_dim }
    \@@_save_punct_dim:nNNn { offset } #1 #2 { \c_zero_dim }
    \@@_save_punct_dim:nNNn { margin } #1 #2 { \c_zero_dim }
    \@@_save_punct_dim:nNNn { rule } \c_@@_left_tl  {#2} { \c_zero_dim }
    \@@_save_punct_dim:nNNn { rule } \c_@@_right_tl {#2} { \c_zero_dim }
    \@@_save_punct_dim:nNNn { bound } \c_@@_left_tl  {#2} { \c_zero_dim }
    \@@_save_punct_dim:nNNn { bound } \c_@@_right_tl {#2} { \c_zero_dim }
    \@@_save_punct_skip:nNNn { glue }  #1 #2 { \c_zero_skip }
  }
\cs_new_protected:Npn \@@_save_punct_margin:NN #1#2
  {
    \group_begin:
      \xeCJK_select_punct_font:
      \xeCJK_fallback_punct_symbol:NN
      \xeCJK_calc_punct_dimen:N #2
    \group_end:
    \dim_set:Nn \l_@@_bound_dim
      { \@@_use_punct_dim:nNN { bound } #1 #2 }
    \tl_if_eq:NNTF #1 \c_@@_right_tl
      { \tl_set_eq:NN \l_@@_reverse_tl \c_@@_left_tl }
      { \tl_set_eq:NN \l_@@_reverse_tl \c_@@_right_tl }
    \dim_set:Nn \l_@@_reverse_bound_dim
      { \@@_use_punct_dim:nNN { bound } \l_@@_reverse_tl #2 }
    \UseInstance { xeCJK / punctuation } { \l_xeCJK_punct_style_tl }
    \xeCJK_punct_margin_process:NN #1 #2
    \xeCJK_punct_offset_process:NN #1 #2
    \@@_punct_if_long:NT #2
      { \@@_long_punct_kerning:N #2 }
  }
\tl_new:N \l_@@_reverse_tl
\dim_new:N \l_@@_bound_dim
\dim_new:N \l_@@_reverse_bound_dim
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.7.0}{2018/03/12}{修正长标点被隔开时的压缩处理错误。}
%
% \begin{macro}[int]{\@@_long_punct_kerning:N}
% \changes{v3.4.3}{2016/10/27}{考虑破折号边界为负值的情况。}
% \changes{v3.10.2}{2026/07/04}{破折号中间的压缩量改为多路取大，
%  保证两个 \texttt{U+2014} 的总宽不超过两个汉字宽（\#382）。}
% 相同长标点压缩。对于破折号（\texttt{U+2014}），计算两标点之间的空白，
% 保证它中间不被断开，同时保证其总宽（字面或字框）不超过两个汉字宽。
% 压缩量取以下三者的最大值
% （\texttt{width} 为字框宽，\texttt{dimen} 为字面宽，$F$ 为当前字号）：
% \begin{itemize}
%   \item \texttt{bound} 之和，用于字面窄于字框的字体（如中易宋体）；
%   \item $\texttt{dimen} + \texttt{width} - 2F$，用于字面溢出字框的字体
%     （如方正兰亭黑）；
%   \item $2\,\texttt{width} - 2F$，用于字框宽于字号的字体（如微软雅黑）。
% \end{itemize}
% 注意，破折号的边界可能为负值（比如方正新书宋），此时不必压缩，
% 故取值不小于零。省略号（\texttt{U+2025}、\texttt{U+2026}）连用时
% 不需要压缩，其余长标点保持按边空压缩。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_long_punct_kerning:N #1
  {
    \dim_set:Nn \l_@@_tmp_dim
      {
        \dim_max:nn
          { \l_@@_bound_dim + \l_@@_reverse_bound_dim }
          { \c_zero_dim }
      }
    \@@_save_punct_dim:nNNn { bound_width } #1 #1 { \l_@@_tmp_dim }
    \dim_set:Nn \l_@@_tmp_dim
      {
        \str_case:nnF {#1}
          {
            { ^^^^2014 }
              {
                - \dim_max:nn
                    { \l_@@_tmp_dim }
                    {
                      \dim_max:nn
                        {
                          \@@_use_punct_dim:nN { dimen } #1 +
                          \@@_use_punct_dim:nN { width } #1 -
                          \f@size pt - \f@size pt
                        }
                        {
                          \@@_use_punct_dim:nN { width } #1 +
                          \@@_use_punct_dim:nN { width } #1 -
                          \f@size pt - \f@size pt
                        }
                    }
              }
            { ^^^^2025 } { \c_zero_dim }
            { ^^^^2026 } { \c_zero_dim }
          }
          { -\l_@@_tmp_dim }
      }
    \@@_save_punct_dim:nNNn  { kern } #1 #1 { \l_@@_tmp_dim }
    \@@_save_punct_skip:nNNn { kern } #1 #1 { \l_@@_tmp_dim }
    \dim_add:Nn \l_@@_tmp_dim
      { \dim_max:nn { \l_@@_bound_dim } { \c_zero_dim } }
    \@@_save_punct_dim:nNNn  { bound_kern } #1 #1 { \l_@@_tmp_dim }
    \@@_save_punct_skip:nNNn { bound_kern } #1 #1 { \l_@@_tmp_dim }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
%   {\xeCJK_get_punct_kerning:NN,\@@_save_punct_kerning_plain:NN,
%    \@@_save_punct_kerning:NN,\@@_save_left_right_kerning:NN}
% 标点压缩。若当前格式关闭左标点到右标点的压缩，则保存零宽且无伸缩量的间距；
% 用户通过 \cs{xeCJKsetkern} 显式设置的字符对仍然优先，除非当前格式同时关闭
% 全局设置。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_get_punct_kerning:NN #1#2
  {
    \tl_if_exist:cF { \@@_punct_csname:n { dim/kern/#1/#2 } }
      {
        \tl_if_eq:NNTF \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl
          { \@@_save_punct_kerning_plain:NN }
          { \@@_save_punct_kerning:NN }
          #1 #2
      }
  }
\cs_new_protected:Npn \xeCJK_get_punct_kerning:oN
  { \exp_after:wN \xeCJK_get_punct_kerning:NN }
\cs_new_protected:Npn \@@_save_punct_kerning_plain:NN #1#2
  {
    \@@_save_punct_dim:nNNn { kern } #1 #2 { \c_zero_dim }
    \@@_save_punct_dim:nNNn { bound_kern } #1 #2 { \c_zero_dim }
    \@@_save_punct_dim:nNNn { bound_width } #1 #2 { \c_zero_dim }
    \@@_save_punct_skip:nNNn { kern } #1 #2 { \c_zero_skip }
    \@@_save_punct_skip:nNNn { bound_kern } #1 #2 { \c_zero_skip }
  }
\cs_new_protected:Npn \@@_save_punct_kerning:NN #1#2
  {
    % 后续分支需要读取当前实例的布尔选项，因此必须在分支前载入实例。
    \UseInstance { xeCJK / punctuation } { \l_xeCJK_punct_style_tl }
    \@@_punct_if_right:NTF #1
      { \xeCJK_punct_kerning_process:NN #1 #2 }
      {
        \@@_punct_if_right:NTF #2
          { \@@_save_left_right_kerning:NN #1 #2 }
          { \xeCJK_punct_kerning_process:NN #1 #2 }
      }
  }
\cs_new_protected:Npn \@@_save_left_right_kerning:NN #1#2
  {
    \bool_if:NTF \l_@@_enabled_left_right_kerning_bool
      { \xeCJK_punct_kerning_process:NN #1 #2 }
      {
        \bool_if:NTF \l_@@_enabled_global_setting_bool
          {
            \tl_if_exist:cTF { g_@@_punct/kern/#1/#2/tl }
              { \xeCJK_punct_kerning_process:NN #1 #2 }
              { \@@_save_punct_kerning_plain:NN #1 #2 }
          }
          { \@@_save_punct_kerning_plain:NN #1 #2 }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\@@_punct_if_full_margin_dash:N}
% \changes{v3.10.2}{2026/07/04}{新增判断，配合破折号占两个汉字宽的
%  空白补偿（\#382）。}
% 判断标点 |#1| 是否为需要全空白补偿的破折号，即 \texttt{U+2014} 且未被
% 归入 |PoZheHao| 类（没有启用破折号合字）。这类破折号在连用时，
% 两个 \texttt{U+2014} 之间被挤压掉的空白恰好等于单个字符左右的总空白，
% 因此在其两端各补偿一整份空白（而非一半），可以优先保证
% 破折号占两个汉字宽。
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_punct_if_full_margin_dash:N #1 { p , T , F , TF }
  {
    \str_if_eq:nnTF {#1} { ^^^^2014 }
      {
        \if_int_compare:w \xeCJK_token_value_class:N #1 =
                          \xeCJK_class_num:n { PoZheHao }
          \prg_return_false:
        \else:
          \prg_return_true:
        \fi:
      }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_punct_margin_process:NN}
% \changes{v3.10.2}{2026/07/04}{未启用合字的破折号两端各补偿一整份空白，
%  保证连用的破折号占两个汉字宽（\#382）。}
% 注意，未启用合字的破折号（\texttt{U+2014}）两端各补偿一整份空白。
% 此时单独一个 \texttt{U+2014} 占一个字宽加一份空白，
% 连用的两个 \texttt{U+2014} 恰好占两个字宽。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_punct_margin_process:NN #1#2
  {
    \dim_set:Nn \l_@@_tmp_dim
      {
        \bool_if:NTF \l_@@_enabled_global_setting_bool
          {
            \cs_if_exist_use:cF { g_@@_punct_width/#2/tl }
              {
                \tl_if_empty:NTF \g_@@_punct_width_tl
                  { \@@_calc_punct_width:N #2 }
                  { \g_@@_punct_width_tl }
              }
          }
          { \@@_calc_punct_width:N #2 }
      }
    \dim_set:Nn \l_@@_margin_dim
      {
        \dim_max:nn
          { \l_@@_margin_minimum_dim }
          {
            \dim_compare:nNnTF \l_@@_tmp_dim < \c_max_dim
              {
                \@@_punct_if_middle:NTF #2
                  {
                    (   \l_@@_tmp_dim
                      - ( \@@_use_punct_dim:nN { dimen } #2 )
                    )
                    \@@_punct_if_full_margin_dash:NF #2 { / 2 }
                  }
                  {
                    \bool_if:NTF \l_@@_optimize_margin_bool
                      {
                        \dim_max:nn
                          {
                            \dim_min:nn
                              { \l_@@_bound_dim }
                              { \l_@@_reverse_bound_dim }
                          }
                      }
                      { \use:n }
                      {
                          \l_@@_tmp_dim
                        - \l_@@_reverse_bound_dim
                        - ( \@@_use_punct_dim:nN { dimen } #2 )
                      }
                  }
              }
              {
                \bool_if:NTF \l_@@_optimize_margin_bool
                  { \dim_min:nn { \l_@@_bound_dim } }
                  { \use:n }
                  { \@@_calc_margin_width:N #2 }
              }
          }
      }
    \@@_save_punct_dim:nNNn { margin } #1 #2 { \l_@@_margin_dim }
  }
\dim_new:N \l_@@_margin_dim
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_calc_punct_width:N}
%    \begin{macrocode}
\cs_new:Npn \@@_calc_punct_width:N #1
  {
    \@@_punct_if_middle:NTF #1
      { \@@_punct_width_or_ratio:nN { middle } }
      {
        \@@_punct_if_mixed_width:NTF #1
          { \@@_punct_width_or_ratio:nN { mixed } }
          { \@@_punct_width_or_ratio:nN { fixed } }
      }
      #1
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_calc_margin_width:N}
%    \begin{macrocode}
\cs_new:Npn \@@_calc_margin_width:N #1
  {
    \@@_punct_if_middle:NTF #1
      {
        \dim_compare:nNnTF \l_@@_middle_margin_width_dim < \c_max_dim
          { \l_@@_middle_margin_width_dim }
          {
            \@@_dim_ratio:Nn \l_@@_middle_margin_ratio_fp
              { ( \l_@@_bound_dim + \l_@@_reverse_bound_dim ) / 2 }
          }
      }
      {
        \@@_punct_if_mixed_width:NTF #1
          { \@@_margin_width_or_ratio:n { mixed } }
          { \@@_margin_width_or_ratio:n { fixed } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_dim_ratio:Nn}
%    \begin{macrocode}
\cs_new:Npn \@@_dim_ratio:Nn #1#2
  { \fp_to_dim:n { #1 \dim_to_fp:n {#2} } }
\cs_generate_variant:Nn \@@_dim_ratio:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.9}{2022/05/26}{修正居中标点悬挂错误。}
%
% \begin{macro}[int]{\xeCJK_punct_offset_process:NN}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_punct_offset_process:NN #1#2
  {
    \dim_set:Nn \l_@@_tmp_dim
      {
        \bool_if:NTF \l_@@_enabled_global_setting_bool
          {
            \cs_if_exist_use:cF { g_@@_punct_bound_width/#2/tl }
              {
                \tl_if_empty:NTF \g_@@_punct_bound_width_tl
                  { \@@_punct_width_or_ratio:nN { bound } #2 }
                  { \g_@@_punct_bound_width_tl }
              }
          }
          { \@@_punct_width_or_ratio:nN { bound } #2 }
      }
    \dim_set:Nn \l_@@_tmp_dim
      {
        \bool_if:NTF \l_@@_enabled_hanging_bool
          { \use:n }
          { \dim_max:nn { \l_@@_margin_minimum_dim } }
          {
            \dim_compare:nNnTF \l_@@_tmp_dim < \c_max_dim
              {
                \@@_punct_if_middle:NTF #2
                  {
                      \l_@@_tmp_dim
                    - \l_@@_margin_dim
                    - ( \@@_use_punct_dim:nN { dimen } #2 )
                  }
                  {
                      \l_@@_tmp_dim
                    - \l_@@_reverse_bound_dim
                    - ( \@@_use_punct_dim:nN { dimen } #2 )
                  }
              }
              {
                \bool_if:NTF \l_@@_optimize_margin_bool
                  { \dim_min:nn { \l_@@_bound_dim } }
                  { \use:n }
                  { \@@_margin_width_or_ratio:n { bound } }
              }
          }
      }
    \@@_save_punct_dim:nNNn { offset } #1 #2
      { \l_@@_tmp_dim }
    \@@_save_punct_dim:nNNn { rule } #1 #2
      { \l_@@_tmp_dim - \l_@@_bound_dim }
    \@@_save_punct_dim:nNNn { rule } \l_@@_reverse_tl #2
      { \l_@@_tmp_dim - \l_@@_reverse_bound_dim }
    \@@_save_punct_dim:nNNn { glue } #1 #2
      { \l_@@_margin_dim - \l_@@_tmp_dim }
    \@@_save_punct_skip:nNNnnn { glue } #1 #2
      { \l_@@_margin_dim - \l_@@_tmp_dim }
      {
        \@@_punct_if_middle:NTF #2
          {
            ( \@@_use_punct_dim:nN { width } #2 -
              \@@_use_punct_dim:nN { dimen } #2 )
            \@@_punct_if_full_margin_dash:NF #2 { / 2 }
            - \l_@@_margin_dim
          }
          { \l_@@_bound_dim - \l_@@_margin_dim }
      }
      {
        \@@_punct_if_middle:NTF #2
          { \l_@@_margin_dim / 2 }
          { \l_@@_margin_dim - \l_@@_reverse_bound_dim }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.7}{2013/08/25}{标点宽度设置禁用比例选项的值改为 \texttt{nan}。}
% \changes{v3.8.0}{2020/02/10}{应用 \cs{fp_if_nan:nTF}。}
%
% \begin{macro}{\@@_punct_width_or_ratio:nN}
%    \begin{macrocode}
\cs_new:Npn \@@_punct_width_or_ratio:nN #1#2
  {
    \dim_compare:nNnTF { \use:c { l_@@_#1_punct_width_dim } } < \c_max_dim
      { \use:c { l_@@_#1_punct_width_dim } }
      {
        \fp_if_nan:nTF { \use:c { l_@@_#1_punct_ratio_fp } }
          { \c_max_dim }
          {
            \@@_dim_ratio:cn
              { l_@@_#1_punct_ratio_fp }
              { \@@_use_punct_dim:nN { width } #2 }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_margin_width_or_ratio:n}
%    \begin{macrocode}
\cs_new:Npn \@@_margin_width_or_ratio:n #1
  {
    \dim_compare:nNnTF { \use:c { l_@@_#1_margin_width_dim } } < \c_max_dim
      { \use:c { l_@@_#1_margin_width_dim } }
      {
        \@@_dim_ratio:cn
          { l_@@_#1_margin_ratio_fp }
          { \l_@@_bound_dim }
      }
    \bool_if:NT \l_@@_add_min_bound_to_margin_bool
      { + \dim_min:nn \l_@@_bound_dim \l_@@_reverse_bound_dim }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.4}{2016/11/30}{不压缩长标点与其他标点的间距。}
%
% \begin{macro}[int]{\xeCJK_punct_kerning_process:NN}
% 当标点之一为长标点时，不必进行压缩。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_punct_kerning_process:NN #1#2
  {
    \dim_set:Nn \l_@@_margin_dim
      { \@@_original_kerning_margin:NN #1 #2 }
    \dim_set:Nn \l_@@_minimum_bound_dim
      { \@@_punct_min_bound:NN #1 #2 }
    \@@_punct_if_long:NTF #1
      { \bool_set_false:N \l_@@_enabled_kerning_bool }
      {
        \@@_punct_if_long:NT #2
          { \bool_set_false:N \l_@@_enabled_kerning_bool }
      }
    \dim_set:Nn \l_@@_kerning_margin_dim
      {
        \bool_if:NTF \l_@@_enabled_global_setting_bool
          {
            \cs_if_exist_use:cF { g_@@_punct/kern/#1/#2/tl }
              { \@@_punct_kerning_process_aux:NN #1 #2 }
          }
          { \@@_punct_kerning_process_aux:NN #1 #2 }
      }
    \@@_save_kerning:nnNN { kern } { bound }  #1 #2
    \@@_save_punct_dim:nNNn { bound_width } #1 #2
      { \l_@@_kerning_margin_dim - \l_@@_tmp_dim }
    \@@_punct_if_right:NTF #1
      {
        \@@_punct_if_right:NTF #2
          { \@@_save_kerning:nnnNN { bound_kern } { offset } { bound } }
          { \@@_save_kerning:nnNN  { bound_kern } { offset } }
      }
      {
        \@@_punct_if_right:NTF #2
          { \@@_save_kerning:nnNN  { bound_kern } { bound } }
          { \@@_save_kerning:nnnNN { bound_kern } { bound } { offset } }
      }
      #1 #2
  }
\cs_new:Npn \@@_punct_kerning_process_aux:NN #1#2
  {
    \bool_if:NTF \l_@@_enabled_kerning_bool
      { \@@_calc_kerning_margin:NN #1 #2 }
      { \l_@@_margin_dim }
  }
\dim_new:N \l_@@_minimum_bound_dim
\dim_new:N \l_@@_kerning_margin_dim
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.0}{2016/05/12}{标点符号的压缩量能伸长到原始空白，能收缩到较小边距。}
%
% \begin{macro}{\@@_save_kerning:nnNN}
% 相邻两个标点符号的间距能伸长到原始空白（未压缩时的状态），能收缩到较小边距。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_save_kerning:nnNN #1#2
  { \@@_save_kerning:nnnNN {#1} {#2} {#2} }
\cs_new_protected:Npn \@@_save_kerning:nnnNN #1#2#3#4#5
  {
    \dim_set:Nn \l_@@_tmp_dim
      {
          \l_@@_kerning_margin_dim
        - ( \@@_use_punct_dim:nNN {#2} \c_@@_right_tl #4 )
        - ( \@@_use_punct_dim:nNN {#3} \c_@@_left_tl  #5 )
      }
    \@@_save_punct_dim:nNNn {#1} #4 #5 { \l_@@_tmp_dim }
    \@@_save_punct_skip:nNNnnn {#1} #4 #5
      { \l_@@_tmp_dim }
      { \l_@@_margin_dim - \l_@@_kerning_margin_dim }
      { \l_@@_kerning_margin_dim - \l_@@_minimum_bound_dim }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_original_kerning_margin:NN}
% 相邻两个标点符号之间的本来空白宽度。
%    \begin{macrocode}
\cs_new:Npn \@@_original_kerning_margin:NN #1#2
  {
    \dim_eval:n
      {
        \@@_use_punct_dim:nNN
          { \@@_punct_if_right:NTF #1 { margin } { bound } } \c_@@_right_tl #1
        +
        \@@_use_punct_dim:nNN
          { \@@_punct_if_right:NTF #2 { bound } { margin } } \c_@@_left_tl #2
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_calc_kerning_margin:NN,\@@_calc_kerning_margin_aux:NN}
%    \begin{macrocode}
\cs_new:Npn \@@_calc_kerning_margin:NN #1#2
  {
    \dim_max:nn
      { \l_@@_kerning_margin_minimum_dim }
      {
        \bool_if:NTF \l_@@_min_bound_to_kerning_bool
          { \l_@@_minimum_bound_dim }
          {
            \bool_if:NTF \l_@@_optimize_kerning_bool
              { \dim_max:nn { \l_@@_minimum_bound_dim } }
              { \use:n }
              { \@@_calc_kerning_margin_aux:NN #1 #2 }
          }
      }
  }
\cs_new:Npn \@@_calc_kerning_margin_aux:NN #1#2
  {
    \dim_compare:nNnTF \l_@@_kerning_total_width_dim < \c_max_dim
      { \@@_calc_kerning_margin:nNN \l_@@_kerning_total_width_dim }
      {
        \fp_if_nan:nTF { \l_@@_kerning_total_ratio_fp }
          {
            \xeCJK_if_same_class:NNTF #1 #2
              { \@@_kerning_width_or_ratio:nNN { same } }
              { \@@_kerning_width_or_ratio:nNN { different } }
          }
          {
            \@@_calc_kerning_margin:nNN
              {
                \@@_dim_ratio:Nn \l_@@_kerning_total_ratio_fp
                  {
                    \@@_use_punct_dim:nN { width } #1 +
                    \@@_use_punct_dim:nN { width } #2
                  }
              }
          }
      }
      #1 #2
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_kerning_width_or_ratio:nNN}
%    \begin{macrocode}
\cs_new:Npn \@@_kerning_width_or_ratio:nNN #1#2#3
  {
    \dim_compare:nNnTF { \use:c { l_@@_#1_align_margin_dim } } < \c_max_dim
      { \use:c { l_@@_#1_align_margin_dim } }
      {
        \fp_if_nan:nTF { \use:c { l_@@_#1_align_ratio_fp } }
          {
            \dim_compare:nNnTF \l_@@_kerning_margin_width_dim < \c_max_dim
              { \l_@@_kerning_margin_width_dim \use_none:n }
              { \@@_dim_ratio:Nn \l_@@_kerning_margin_ratio_fp }
          }
          { \@@_dim_ratio:cn { l_@@_#1_align_ratio_fp } }
          { \l_@@_margin_dim }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_punct_min_bound:NN}
%    \begin{macrocode}
\cs_new:Npn \@@_punct_min_bound:NN #1#2
  {
    \dim_max:nn
      {
        \dim_min:nn
          { \@@_use_punct_dim:nNN { bound } \c_@@_left_tl  #1 }
          { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 }
      }
      {
        \dim_min:nn
          { \@@_use_punct_dim:nNN { bound } \c_@@_left_tl  #2 }
          { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #2 }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_calc_kerning_margin:nNN}
% |#2| 和 |#3| 为相邻的两个标点，|#1| 为要确定的相邻两个标点总共占的宽度。
%    \begin{macrocode}
\cs_new:Npn \@@_calc_kerning_margin:nNN #1#2#3
  {
    \dim_eval:n
      {
          (#1)
        - ( \@@_use_punct_dim:nNN
              { \@@_punct_if_right:NTF #2 { bound } { margin } }
              \c_@@_left_tl #2 )
        - ( \@@_use_punct_dim:nNN
              { \@@_punct_if_right:NTF #3 { margin } { bound } }
              \c_@@_right_tl #3 )
        - ( \@@_use_punct_dim:nN { dimen } #2 )
        - ( \@@_use_punct_dim:nN { dimen } #3 )
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_calc_punct_dimen:N}
% 计算标点的左右实际边距和实际尺寸。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_calc_punct_dimen:N #1
  {
    \@@_save_punct_dim:nNNn { bound } \c_@@_left_tl #1
      { \xeCJK_glyph_bounds:NN 1 #1 }
    \@@_save_punct_dim:nNNn { bound } \c_@@_right_tl #1
      { \xeCJK_glyph_bounds:NN 3 #1 }
    \@@_save_punct_dim:nNn { width } #1
      { \tex_fontcharwd:D \tex_font:D `#1 }
    \@@_save_punct_dim:nNn { dimen } #1
      {
        ( \@@_use_punct_dim:nN { width } #1 )                 -
        ( \@@_use_punct_dim:nNN { bound } \c_@@_left_tl  #1 ) -
        ( \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 )
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_glyph_bounds:NN}
% 用 \tn{XeTeXglyphbounds} 取得标点符号的上下左右空白。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_glyph_bounds:NN #1#2
  { \tex_XeTeXglyphbounds:D #1 ~ \tex_XeTeXcharglyph:D `#2 \exp_stop_f: }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.3}{2020/04/09}{重构 \opt{PunctStyle} 选项，完全展开参数。}
%
% \begin{macro}{PunctStyle}
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  { PunctStyle .code:n = \exp_args:Ne \@@_set_punct_style:n {#1} }
\cs_new_protected:Npn \@@_set_punct_style:n #1
  {
    \IfInstanceExistTF { xeCJK / punctuation } {#1}
      { \tl_set:Nn \l_xeCJK_punct_style_tl {#1} }
      {
        \prop_get:NnNF \c_@@_punct_style_alias_prop
          {#1} \l_xeCJK_punct_style_tl
          { \@@_error:ne { punct-style-unknown } {#1} }
      }
  }
\prop_const_from_keyval:Nn \c_@@_punct_style_alias_prop
  {
    halfwidth     = banjiao ,
    fullwidth     = quanjiao ,
    mixedwidth    = kaiming ,
    marginkerning = hangmobanjiao ,
    plain         = plain
  }
\tl_new:N \l_xeCJK_punct_style_tl
\tl_const:Nn \c_@@_punct_style_plain_tl { plain }
\@@_msg_new:nn { punct-style-unknown }
  {
    Punctuation~style~"#1"~is~unknown. \\\\
    The~available~styles~are~listed~as~follow.\\\\
    "plain,~\seq_use:Nnnn \g_@@_punct_style_seq
      { ~and~ } { ,~ } { ,~and~ }".\\
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_trim_spaces:n}
% \pkg{xparse} 处理函数，先完全展开参数再删除两边空格。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_trim_spaces:n #1
  {
    \tl_set:Ne \ProcessedArgument
      { \exp_args:Ne \tl_trim_spaces:n {#1} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKDeclarePunctStyle}
% 定义新的标点处理风格，已经存在的同名风格将被覆盖。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKDeclarePunctStyle
  { > { \@@_trim_spaces:n } m m }
  {
    \IfInstanceExistTF { xeCJK / punctuation } {#1}
      { \@@_warning:ne { punct-style-already-defined } {#1} }
      { \seq_gput_right:Nn \g_@@_punct_style_seq {#1} }
    \DeclareInstance { xeCJK / punctuation } {#1} { basic } {#2}
  }
\seq_new:N \g_@@_punct_style_seq
\@@_msg_new:nn { punct-style-already-defined }
  {
    Punctuation~style~"#1"~is~already~defined!. \\\\
    The~existing~style~of~"#1"~will~be~overwritten.\\
  }
\@onlypreamble \xeCJKDeclarePunctStyle
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKEditPunctStyle}
% 对已有的标点处理风格进行修改。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKEditPunctStyle
  { > { \@@_trim_spaces:n } m m }
  {
    \IfInstanceExistTF { xeCJK / punctuation } {#1}
      { \EditInstance { xeCJK / punctuation } {#1} {#2} }
      { \@@_error:ne { punct-style-unknown } {#1} }
  }
\@onlypreamble \xeCJKEditPunctStyle
%    \end{macrocode}
% \end{macro}
%
% 默认设置即为全角格式。
% \changes{v3.10.3}{2026/07/13}
% {全角格式启用相邻标点边界优化，并保留左标点后接右标点的自然空白（\#975）。}
%    \begin{macrocode}
\xeCJKDeclarePunctStyle { quanjiao }
  {
    optimize-kerning           = true  ,
    enabled-left-right-kerning = false
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\xeCJKDeclarePunctStyle { hangmobanjiao } { enabled-kerning = false }
%    \end{macrocode}
%
%    \begin{macrocode}
\xeCJKDeclarePunctStyle { banjiao }
  {
    fixed-punct-ratio   = 0.5  ,
    optimize-margin     = true ,
    kerning-total-ratio = 0.5  ,
    optimize-kerning    = true
  }
%    \end{macrocode}
%
% \changes{v3.10.3}{2026/07/13}
% {开明式句末点号的宽度由 \texttt{0.8em} 改为全角（\#975）。}
%    \begin{macrocode}
\xeCJKDeclarePunctStyle { kaiming }
  {
    fixed-punct-ratio   = 0.5  ,
    mixed-punct-ratio   = 1.0  ,
    optimize-margin     = true ,
    kerning-total-ratio = 0.5  ,
    optimize-kerning    = true
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\xeCJKDeclarePunctStyle { CCT }
  {
    fixed-punct-ratio   = 0.7  ,
    optimize-margin     = true ,
    kerning-total-ratio = 0.6  ,
    optimize-kerning    = true
  }
%    \end{macrocode}
%
% \subsection{后备字体}
%
% \changes{v3.8.4}{2020/05/31}{重构后备字体的实现，修正标点符号无后备字体的问题。}
%
% \begin{macro}{AutoFallBack}
% 后备字体的宏包选项声明。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    AutoFallBack .choice: ,
    AutoFallBack / true  .code:n =
      {
        \cs_set_eq:NN \xeCJK_fallback_symbol:NN
                      \@@_fallback_symbol:NN
        \cs_set_eq:NN \xeCJK_fallback_punct_symbol:NN
                      \@@_fallback_punct_symbol:NN
        \cs_set_eq:NN \xeCJK_clear_fallback_font:
                      \@@_clear_fallback_font:
      } ,
    AutoFallBack / false .code:n =
      {
        \xeCJK_cs_clear:N \xeCJK_fallback_symbol:NN
        \xeCJK_cs_clear:N \xeCJK_fallback_punct_symbol:NN
        \xeCJK_cs_clear:N \xeCJK_clear_fallback_font:
      } ,
    AutoFallBack      .default:n = { true } ,
    fallback             .meta:n = { AutoFallBack = true }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_fallback_symbol:NN,\xeCJK_fallback_punct_symbol:NN}
% 测试当前字体中是否存在当前字符，如存在则直接输出，否则启用后备字体。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_fallback_symbol:NN #1#2
  {
    \xeCJK_reset_fallback_font:
    \xeCJK_glyph_if_exist:NF #2
      { \@@_fallback_symbol_aux:NN }
    #1#2
  }
\cs_new_protected:Npn \@@_fallback_punct_symbol:NN #1#2
  {
    \xeCJK_reset_fallback_font:
    \xeCJK_glyph_if_exist:NF #2
      { \@@_fallback_punct_symbol_aux:NN }
    #1#2
  }
\cs_new_eq:NN \xeCJK_fallback_symbol:NN       \prg_do_nothing:
\cs_new_eq:NN \xeCJK_fallback_punct_symbol:NN \prg_do_nothing:
\cs_new_protected:Npn \@@_fallback_symbol_aux:NN
  {
    \@@_fallback_symbol_aux:nnNN
      { \CJK@family }
      { \l_xeCJK_family_tl }
  }
\cs_new_protected:Npn \@@_fallback_punct_symbol_aux:NN
  {
    \@@_fallback_symbol_aux:nnNN
      { \CJK@punctfamily }
      { \l_xeCJK_punct_family_tl }
  }
\cs_new_protected:Npn \@@_fallback_symbol_aux:nnNN
  {
    \cs_set_protected:Npe \xeCJK_reset_fallback_font:
      {
        \tex_the:D \tex_font:D
        \xeCJK_clear_fallback_font:
      }
    \exp_args:Nee \@@_fallback_loop:nnNN
  }
\cs_new_protected:Npn \@@_clear_fallback_font:
  { \cs_set_eq:NN \xeCJK_reset_fallback_font: \prg_do_nothing: }
\cs_new_eq:NN \xeCJK_reset_fallback_font: \prg_do_nothing:
\cs_new_eq:NN \xeCJK_clear_fallback_font: \prg_do_nothing:
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.5.1}{2017/11/16}{修正 fallback 字体后无法忽略空格的错误。}
% \changes{v3.2.12}{2014/05/12}{更新 \cs{int_to_Hex:n}。}
%
% \begin{macro}[int]{\@@_fallback_loop:nnNN}
% \changes{v3.1.0}{2012/11/19}{调整备用字体的循环方式。}
% \changes{v3.2.4}{2013/06/30}
% {使 \tn{CJKfamilydefault} 的 \texttt{FallBack} 设置全局可用。}
% 循环测试后备字体是否包含字符 |#1|。若后备字体中存在该字符或者再没有后备字体，则
% 结束循环。当前字体族没有备用字体时，使用 \tn{CJKfamilydefault} 的设置。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_fallback_loop:nnNN
  {
    \cs_set_eq:NN \@@_fallback_loop:TF \use_i:nn
    \@@_fallback_loop:nnnNN { FallBack }
  }
\cs_new_protected:Npn \@@_fallback_loop:nnnNN #1#2#3
  {
    \xeCJK_select_fallback_font:nnn {#1} {#2} {#3}
    \@@_fallback_loop:TF
      { \@@_fallback_loop_aux:nnnNN }
      { \@@_fallback_missing_glyph:nnnNN }
      {#1} {#2} {#3}
  }
\cs_new_protected:Npn \@@_fallback_loop_aux:nnnNN #1#2#3#4#5
  {
    \xeCJK_glyph_if_exist:NF #5
      { \@@_fallback_loop:nnnNN { #1/FallBack } {#2} {#3} }
    #4#5
  }
\cs_new_protected:Npn \@@_fallback_missing_glyph:nnnNN #1#2#3#4#5
  {
    \@@_warning:neee { missing-glyph } {#1} {#2} {#5}
    #4#5
  }
\cs_new_protected:Npn \xeCJK_select_fallback_font:nnn #1#2
  {
    \@@_select_fallback_font:cnnn
      { \@@_font_csname:n { #2/#1 } } {#1} {#2}
  }
\cs_new_protected:Npn \@@_select_fallback_font:Nnnn #1
  {
    \cs_if_exist:NF #1
      { \@@_fallback_font_initial:NNnnn }
    #1 \use_none:nnn
  }
\cs_generate_variant:Nn \@@_select_fallback_font:Nnnn { c }
\cs_new_protected:Npn \@@_fallback_font_initial:NNnnn #1#2#3#4#5
  {
    \xeCJK_family_if_exist:nTF { #5/#3 }
      { \@@_font_initial:Nn #1 { #5/#3 } }
      { \@@_fallback_font_initial_auxi:Nnnn #1 {#5} {#3} {#4} }
    #1
  }
\cs_new_protected:Npn \@@_fallback_font_initial_auxi:Nnnn #1
  {
    \exp_args:NNe \@@_fallback_font_initial_auxii:Nnnnn
      #1 { \CJKfamilydefault }
  }
\cs_new_protected:Npn \@@_fallback_font_initial_auxii:Nnnnn #1#2#3
  {
    \str_if_eq:nnTF {#2} {#3}
      { \@@_fallback_loop_end:Nnnn }
      { \@@_fallback_font_initial_auxiii:Nnnn }
      #1 {#2}
  }
\cs_new_protected:Npn \@@_fallback_font_initial_auxiii:Nnnn #1#2
  {
    \xeCJK_family_if_exist:nTF {#2}
      { \@@_fallback_font_initial_auxiv:Nnnn }
      { \@@_fallback_loop_end:Nnnn }
      #1 {#2}
  }
\cs_new_protected:Npn \@@_fallback_font_initial_auxiv:Nnnn #1#2#3#4
  {
    \@@_font_initial:Nn #1 {#2}
    \exp_args:Nc \@@_fallback_font_initial_auxiii:Nnnn
      { \@@_font_csname:n { #4/#3/FallBack } }
      { #2/FallBack } { #3/FallBack } {#4}
  }
\cs_new_eq:NN \@@_fallback_loop:TF \use_i:nn
\cs_new_protected:Npn \@@_fallback_loop_end:Nnnn #1#2#3#4
  { \cs_gset_eq:NN #1 \@@_fallback_loop_end: }
\cs_new_protected:Npn \@@_fallback_loop_end:
  { \cs_set_eq:NN \@@_fallback_loop:TF \use_ii:nn }
\@@_msg_new:nn { missing-glyph }
  {
    CJKfamily~`\@@_msg_family_map:n {#2}'~(#1)~
    does~not~contain~glyph~`#3'~
    ( U + \int_to_Hex:n { `#3 } )~\msg_line_context:.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\setCJKfallbackfamilyfont}
% \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。}
%    \begin{macrocode}
\NewDocumentCommand \setCJKfallbackfamilyfont { m o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family_fallback:nnn {#1} } {#2} {#3}
      { }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_set_family_fallback:nnn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_set_family_fallback:nnn #1#2#3
  {
    \group_begin:
    \tl_set:Nn \l_@@_fallback_family_tl {#1}
    \prop_get:NoNF \g_@@_family_font_name_prop
      \l_@@_fallback_family_tl \l_@@_font_name_tl
      { \tl_clear:N \l_@@_font_name_tl }
    \clist_set:Nn \l_@@_public_options_clist {#2}
    \clist_map_function:nN {#3} \@@_set_family_fallback:n
    \group_end:
  }
\cs_new_protected:Npn \@@_set_family_fallback:n #1
  {
    \tl_put_right:Nn \l_@@_fallback_family_tl { /FallBack }
    \@@_get_sub_features:on \l_@@_fallback_family_tl {#1}
    \clist_concat:NNN \l_@@_sub_font_options_clist
      \l_@@_public_options_clist
      \l_@@_sub_font_options_clist
    \exp_args:Nooo \xeCJK_set_family:nnn
      \l_@@_fallback_family_tl
      \l_@@_sub_font_options_clist
      \l_@@_sub_font_name_tl
  }
\tl_new:N \l_@@_fallback_family_tl
\clist_new:N \l_@@_public_options_clist
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{CJK 字体族声明方式}
%
%    \begin{macrocode}
\bool_new:N \g_@@_auto_fake_bold_bool
\bool_new:N \g_@@_auto_fake_slant_bool
\fp_new:N \g_@@_embolden_factor_fp
\fp_new:N \g_@@_slant_factor_fp
%    \end{macrocode}
%
% \begin{macro}{AutoFakeBold, AutoFakeSlant,EmboldenFactor,SlantFactor}
% 伪粗体和伪斜体的宏包选项声明。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    AutoFakeBold .choices:nn = { true , false }
      { \use:c { bool_gset_ #1 :N } \g_@@_auto_fake_bold_bool } ,
    AutoFakeBold / unknown .code:n =
      {
        \bool_gset_true:N  \g_@@_auto_fake_bold_bool
        \fp_gset:Nn \g_@@_embolden_factor_fp { \l_keys_value_tl }
      } ,
    AutoFakeBold  .default:n  = { true } ,
    AutoFakeSlant .choices:nn = { true , false }
      { \use:c { bool_gset_ #1 :N } \g_@@_auto_fake_slant_bool } ,
    AutoFakeSlant / unknown  .code:n =
      {
        \bool_gset_true:N  \g_@@_auto_fake_slant_bool
        \fp_gset:Nn \g_@@_slant_factor_fp { \l_keys_value_tl }
      } ,
    AutoFakeSlant  .default:n = { true } ,
    EmboldenFactor .fp_gset:N = \g_@@_embolden_factor_fp ,
    SlantFactor    .fp_gset:N = \g_@@_slant_factor_fp ,
    BoldFont  .meta:n = { AutoFakeBold  = true } ,
    boldfont  .meta:n = { AutoFakeBold  = true } ,
    SlantFont .meta:n = { AutoFakeSlant = true } ,
    slantfont .meta:n = { AutoFakeSlant = true }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.4}{2013/07/02}{内部调整分区字体的设置方法。}
%
% \begin{macro}[int]{\xeCJK_new_sub_key:n}
% \begin{variable}{\g_@@_sub_key_seq}
% 用于定义 CJK 子区字体和备用字体的选项。
%    \begin{macrocode}
\seq_new:N \g_@@_sub_key_seq
\cs_new_protected:Npn \xeCJK_new_sub_key:n #1
  {
    \seq_gput_right:Nn \g_@@_sub_key_seq {#1}
    \keys_define:nn { xeCJK / features }
      {
        #1 .code:n =
          {
            \tl_if_blank:nTF {##1}
              {
                \prop_clear:N \l_@@_sub_key_prop
                \tl_set:Ne \l_@@_sub_family_name_tl
                  { \l_@@_family_name_tl /#1 }
                \clist_remove_all:Nn \l_@@_font_options_clist {#1}
              }
              {
                \tl_clear:N \l_@@_sub_family_name_tl
                \str_if_eq:nnTF {##1} { * }
                  { \prop_put:Nnn \l_@@_sub_key_prop {#1} { \q_no_value } }
                  { \@@_get_sub_features:nn {#1} {##1} }
              }
          } ,
        #1 .default:n = { }
      }
  }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \changes{v3.2.4}{2013/07/02}{改进获取分区字体属性的办法。}
%
% \begin{macro}{\@@_get_sub_features:nn,\@@_get_sub_features:w}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_sub_features:nn #1#2
  {
    \tl_set:Ne \l_@@_tmp_tl { \xeCJK_tl_remove_outer_braces:n {#2} }
    \clist_clear:N \l_@@_sub_font_options_clist
    \exp_after:wN \@@_get_sub_features:w \l_@@_tmp_tl
      \q_mark [ \q_nil ] \q_mark \q_stop
    \tl_if_empty:NTF \l_@@_sub_font_name_tl
      { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_font_name_tl }
      { \tl_replace_all:Nno \l_@@_sub_font_name_tl { * } \l_@@_font_name_tl }
    \prop_put:Nne \l_@@_sub_key_prop {#1}
      {
        { \exp_not:o \l_@@_sub_font_options_clist }
        { \exp_not:o \l_@@_sub_font_name_tl }
      }
  }
\cs_new_protected:Npn \@@_get_sub_features:w #1 [#2] #3 \q_mark #4 \q_stop
  {
    \quark_if_nil:nTF {#2}
      { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_tmp_tl }
      {
        \tl_set:Ne \l_@@_sub_font_name_tl
          { \xeCJK_tl_remove_outer_braces:n {#3} }
        \tl_if_empty:NTF \l_@@_sub_font_name_tl
          { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_tmp_tl }
          { \clist_set:Nn \l_@@_sub_font_options_clist {#2} }
      }
  }
\tl_new:N \l_@@_sub_family_name_tl
\tl_new:N \l_@@_sub_font_name_tl
\clist_new:N \l_@@_sub_font_options_clist
\cs_generate_variant:Nn \@@_get_sub_features:nn { o }
\cs_generate_variant:Nn \tl_replace_all:Nnn { Nno }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{FallBack}
%    \begin{macrocode}
\xeCJK_new_sub_key:n { FallBack }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{BoldFont,ItalicFont}
% 调用字体的属性声明，同 \pkg{fontspec} 宏包。
%    \begin{macrocode}
\keys_define:nn { xeCJK / features }
  {
    BoldFont   .tl_set:N = \l_@@_font_name_bf_tl ,
    ItalicFont .tl_set:N = \l_@@_font_name_it_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.6}{2013/07/31}{\texttt{AutoFakeBold} 和 \texttt{AutoFakeSlant}
% 选项直接使用 \pkg{fontspec} 的设置，修正不能调用相应实际字体的问题。}
%
% \begin{macro}[int]{AutoFakeBold, AutoFakeSlant}
%    \begin{macrocode}
\keys_define:nn { xeCJK / features }
  {
    AutoFakeBold  .choice: ,
    AutoFakeBold / true    .code:n =
      {
        \bool_set_true:N \l_@@_auto_fake_bold_bool
        \fp_set_eq:NN \l_@@_embolden_factor_fp \g_@@_embolden_factor_fp
      } ,
    AutoFakeBold / false   .code:n =
      { \bool_set_false:N \l_@@_auto_fake_bold_bool } ,
    AutoFakeBold / unknown .code:n =
      {
        \bool_set_true:N \l_@@_auto_fake_bold_bool
        \fp_set:Nn \l_@@_embolden_factor_fp { \l_keys_value_tl }
      } ,
    AutoFakeBold .default:n  = { true } ,
    AutoFakeSlant  .choice: ,
    AutoFakeSlant / true    .code:n =
      {
        \bool_set_true:N \l_@@_auto_fake_slant_bool
        \fp_set_eq:NN \l_@@_slant_factor_fp \g_@@_slant_factor_fp
      } ,
    AutoFakeSlant / false   .code:n =
      { \bool_set_false:N \l_@@_auto_fake_slant_bool } ,
    AutoFakeSlant / unknown .code:n =
      {
        \bool_set_true:N \l_@@_auto_fake_slant_bool
        \fp_set:Nn \l_@@_slant_factor_fp { \l_keys_value_tl }
      } ,
    AutoFakeSlant .default:n  = { true }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_family_initial:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_family_initial:
  {
    \int_gincr:N \g_@@_family_int
    \prop_clear:N \l_@@_sub_key_prop
    \tl_clear:N \l_@@_font_name_bf_tl
    \tl_clear:N \l_@@_font_name_it_tl
    \tl_clear:N \l_@@_sub_family_name_tl
    \clist_clear:N \l_@@_fontspec_options_clist
    \bool_set_eq:NN \l_@@_auto_fake_bold_bool  \g_@@_auto_fake_bold_bool
    \bool_set_eq:NN \l_@@_auto_fake_slant_bool \g_@@_auto_fake_slant_bool
    \fp_set_eq:NN \l_@@_embolden_factor_fp \g_@@_embolden_factor_fp
    \fp_set_eq:NN \l_@@_slant_factor_fp    \g_@@_slant_factor_fp
  }
\int_new:N \g_@@_family_int
\prop_new:N \l_@@_sub_key_prop
\clist_new:N \l_@@_fontspec_options_clist
\bool_new:N \l_@@_auto_fake_bold_bool
\bool_new:N \l_@@_auto_fake_slant_bool
\fp_new:N \l_@@_embolden_factor_fp
\fp_new:N \l_@@_slant_factor_fp
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_set_family:nnn}
% 设置一个 CJK 新字体族，与 \tn{newfontfamily} 类似，增加 |FallBack| 选项。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_set_family:nnn #1#2#3
  {
    \group_begin:
    \@@_set_family_initial:
    \tl_set:Nn \l_@@_family_name_tl {#1}
    \clist_set:Nn \l_@@_font_options_clist {#2}
    \tl_set:Nn \l_@@_font_name_tl {#3}
    \clist_concat:NNN \l_@@_font_options_clist
      \g_@@_default_features_clist \l_@@_font_options_clist
    \keys_set_known:noN { xeCJK / features }
      \l_@@_font_options_clist \l_@@_fontspec_options_clist
    \@@_binding_sub_family:
    \@@_parse_font_shape:
    \@@_check_family:o \l_@@_family_name_tl
    \@@_gset_family_cs:n { \l_@@_family_name_tl }
    \@@_save_family_info:
    \@@_set_sub_block_family:
    \group_end:
  }
\tl_new:N \l_@@_family_name_tl
\tl_new:N \l_@@_font_name_tl
\clist_new:N \l_@@_font_options_clist
\cs_generate_variant:Nn \xeCJK_set_family:nnn { e , o }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.7.2}{2019/03/27}{删除定义新字体族时过滤重复选项的功能。}
%
% \begin{macro}{\@@_binding_sub_family:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_binding_sub_family:
  {
    \tl_if_empty:NF \l_@@_sub_family_name_tl
      { \tl_set_eq:NN \l_@@_family_name_tl \l_@@_sub_family_name_tl }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_gset_family_cs:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_gset_family_cs:n #1
  {
    \cs_gset_protected:cpe { \@@_family_csname:n {#1} }
      {
        \group_begin:
        \exp_not:n { \cs_set_eq:NN \xeCJK@fontfamily \use_none:n }
        \exp_not:n { \fontspec_gset_family:Nnn \g_@@_fontspec_family_tl }
          { \exp_not:o \l_@@_fontspec_options_clist }
          { \exp_not:o \l_@@_font_name_tl }
        \@@_gset_family_nfss_cs:no
          {#1} { \exp_not:N \g_@@_fontspec_family_tl }
        \group_end:
        \tl_set_eq:NN \exp_not:N \l_@@_fontspec_family_tl
                      \exp_not:N \g_@@_fontspec_family_tl
      }
  }
\tl_new:N \g_@@_fontspec_family_tl
\tl_new:N \l_@@_fontspec_family_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_family:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_check_family:n #1
  {
    \prop_gpop:NnNT \g_@@_family_font_name_prop {#1} \l_@@_tmp_tl
      {
        \prop_gpop:NnNT \g_@@_family_name_prop {#1} \l_@@_tmp_tl
          {
            \cs_undefine:c { \@@_family_csname:n {#1} }
            \cs_undefine:c { \@@_family_nfss_csname:n {#1} }
          }
        \@@_warning:nee { CJKfamily-redef } {#1} { \l_@@_tmp_tl }
      }
  }
\cs_generate_variant:Nn \@@_check_family:n { o }
\@@_msg_new:nn { CJKfamily-redef }
  { Redefining~CJKfamily~`\@@_msg_family_map:n {#1}'~(#2). }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_parse_font_shape:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_parse_font_shape:
  {
    \tl_if_blank:oTF \l_@@_font_name_bf_tl
      {
        \bool_if:NT \l_@@_auto_fake_bold_bool
          {
            \clist_put_right:Ne \l_@@_fontspec_options_clist
              { AutoFakeBold = { \fp_use:N \l_@@_embolden_factor_fp } }
          }
      }
      {
        \clist_put_right:Ne \l_@@_fontspec_options_clist
          { BoldFont = { \exp_not:o \l_@@_font_name_bf_tl } }
      }
    \tl_if_blank:oTF \l_@@_font_name_it_tl
      {
        \bool_if:NT \l_@@_auto_fake_slant_bool
          {
            \clist_put_right:Ne \l_@@_fontspec_options_clist
              { AutoFakeSlant = { \fp_use:N \l_@@_slant_factor_fp } }
          }
      }
      {
        \clist_put_right:Ne \l_@@_fontspec_options_clist
          { ItalicFont = { \exp_not:o \l_@@_font_name_it_tl } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}
%  {\g_@@_family_name_prop,\g_@@_family_font_name_prop,\g_@@_family_font_options_prop}
%    \begin{macrocode}
\prop_new:N \g_@@_family_name_prop
\prop_new:N \g_@@_family_font_name_prop
\prop_new:N \g_@@_family_font_options_prop
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_save_family_info:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_save_family_info:
  {
    \exp_args:Nooo \@@_save_family_info:nnn
      \l_@@_family_name_tl
      \l_@@_font_name_tl
      \l_@@_font_options_clist
  }
\cs_new_protected:Npn \@@_save_family_info:nnn #1#2#3
  {
    \prop_gput:Nnn \g_@@_family_font_name_prop    {#1} {#2}
    \prop_gput:Nnn \g_@@_family_font_options_prop {#1} {#3}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_sub_block_family:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_sub_block_family:
  {
    \prop_if_empty:NF \l_@@_sub_key_prop
      {
        \prop_map_function:NN
          \l_@@_sub_key_prop
          \@@_set_sub_block_family:nn
      }
  }
\cs_new_protected:Npn \@@_set_sub_block_family:nn #1#2
  {
    \tl_set:Ne \l_@@_sub_family_name_tl { \l_@@_family_name_tl/#1 }
    \quark_if_no_value:nTF {#2}
      { \@@_copy_sub_family:n {#1} }
      { \xeCJK_set_family:onn \l_@@_sub_family_name_tl #2 }
  }
\cs_new_protected:Npn \@@_copy_sub_family:n #1
  {
    \@@_check_family:o \l_@@_sub_family_name_tl
    \prop_get:NoNT \g_@@_family_font_name_prop
      \l_@@_family_name_tl \l_@@_sub_font_name_tl
      {
        \prop_gput:Noo \g_@@_family_font_name_prop
          \l_@@_sub_family_name_tl \l_@@_sub_font_name_tl
      }
    \prop_get:NoNT \g_@@_family_font_options_prop
      \l_@@_family_name_tl \l_@@_sub_font_options_clist
      {
        \clist_remove_all:Nn \l_@@_sub_font_options_clist { #1 = * }
        \prop_gput:Noo \g_@@_family_font_options_prop
          \l_@@_sub_family_name_tl \l_@@_sub_font_options_clist
      }
    \cs_gset_protected:cpe
      { \@@_family_csname:n { \l_@@_sub_family_name_tl } }
      {
        \xeCJK_family_if_exist:eT { \l_@@_family_name_tl }
          {
            \@@_gset_family_nfss_cs:no
              { \l_@@_sub_family_name_tl }
              { \exp_not:N \l_@@_fontspec_family_tl }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_copy_family:nn,\@@_copy_family:ee}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_copy_family:nn #1#2
  {
    \xeCJK_family_if_exist:nT {#2}
      {
        \prop_gput:Nno \g_@@_family_name_prop
          {#1} \l_@@_fontspec_family_tl
        \tl_map_inline:nn
          {
            \g_@@_family_font_name_prop
            \g_@@_family_font_options_prop
          }
          {
            \prop_get:NnNT ##1 {#2} \l_@@_tmp_tl
              { \prop_gput:Nno ##1 {#1} \l_@@_tmp_tl }
          }
        \cs_gset_eq:cc
          { \@@_family_nfss_csname:n {#1} }
          { \@@_family_nfss_csname:n {#2} }
      }
  }
\cs_generate_variant:Nn \@@_copy_family:nn { ee }
%    \end{macrocode}
% \end{macro}
%
% \subsection{字体切换}
%
% \begin{macro}[int]{\xeCJK_select_font:}
% \begin{variable}[int]{\l_xeCJK_current_font_tl}
% 缓存当前字体的原始格式，以加速编译。
%    \begin{macrocode}
\cs_new:Npn \@@_font_csname:n #1
  { xeCJK/#1/\f@series/\f@shape/\f@size }
\tl_new:N \l_xeCJK_current_font_tl
\tl_set:No \l_xeCJK_current_font_tl
  { \@@_font_csname:n { \CJK@family } }
\cs_new_protected:Npn \xeCJK_select_font:
  {
    \@@_select_font:cn
      { \l_xeCJK_current_font_tl }
      { \l_xeCJK_family_tl }
  }
\cs_new_protected:Npn \@@_select_font:Nn #1#2
  {
    \xeCJK_clear_fallback_font:
    \cs_if_exist:NF #1 { \@@_font_initial:Nn #1 {#2} }
    #1
  }
\cs_generate_variant:Nn \@@_select_font:Nn { c }
\tl_new:N \l_@@_current_coor_tl
\cs_new_eq:NN \xeCJK@setfont \xeCJK_select_font:
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \changes{v3.8.1}{2020/02/14}{修复 \cs{l_xeCJK_current_font_tl} 标记错误。}
%
% \begin{macro}{\@@_font_initial:Nn}
% 注意要将 \tn{selectfont} 放在分组中调用，防止 \tn{f@series} 等字体参数被修改，
% 导致 \cs{l_xeCJK_current_font_tl} 标记前后不一致，引发错误（见~\ghissue{486}）。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_font_initial:Nn #1#2
  {
    \group_begin:
      \@@_font_initial_hook:
      \@@_family_use:n {#2}
      \xeCJK_font_gset_to_current:N #1
    \group_end:
  }
\cs_new_protected:Npn \@@_font_initial_hook:
  { \tl_use:N \g_@@_font_initial_hook_tl }
\cs_new_protected:Npn \@@_gadd_font_initial_hook:n
  { \tl_gput_right:Nn \g_@@_font_initial_hook_tl }
\tl_new:N \g_@@_font_initial_hook_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_select_punct_font:}
% \begin{variable}[int]{\l_xeCJK_current_punct_font_tl}
% 切换标点符号字体。
%    \begin{macrocode}
\cs_new_eq:NN \xeCJK_select_punct_font: \xeCJK_select_font:
\cs_new_protected:Npn \@@_select_punct_font_aux:
  {
    \@@_select_font:cn
      { \l_xeCJK_current_punct_font_tl }
      { \l_xeCJK_punct_family_tl }
  }
\tl_new:N \CJK@punctfamily
\tl_new:N \l_xeCJK_punct_family_tl
\tl_new:N \l_xeCJK_current_punct_font_tl
\tl_set:Nn \CJK@punctfamily { \CJK@family }
\tl_set:Nn \l_xeCJK_punct_family_tl { \l_xeCJK_family_tl }
\tl_set:No \l_xeCJK_current_punct_font_tl
  { \@@_font_csname:n { \CJK@punctfamily } }
\cs_new_eq:NN \@@_select_font: \prg_do_nothing:
\cs_new_eq:NN \@@_select_punct_font: \prg_do_nothing:
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\@@_switch_font:nn}
% \changes{v3.1.0}{2012/11/18}{改进定义，加快切换速度。}
% 两个 CJK 分区之间的字体切换。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_switch_font:nn #1#2
  {
    \str_if_eq:nnF {#1} {#2}
      {
        \@@_info:nee { CJK-block } {#1} {#2}
        \str_if_eq:nnTF {#2} { CJK }
          { \xeCJK_select_font: }
          { \xeCJK_select_font:n {#2} }
      }
  }
\@@_msg_new:nn { CJK-block } { Switch~from~block~`#1'~to~`#2'. }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.2}{2020/02/17}{修复分区字体错误。}
%
% \begin{macro}[int]{\xeCJK_select_font:n,\xeCJK_block_family:nn}
% 若当前 CJK 字体族没有定义子分区 |#1| 的字体，则使用 \tn{CJKfamilydefault} 的对应
% 分区字体；若 \tn{CJKfamilydefault} 也没有定义该分区字体，则使用当前 CJK 字体族的
% 主分区字体。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_select_font:n #1
  {
    \@@_select_font:cnn
      { \@@_font_csname:n { \CJK@family/#1 } }
      { \l_xeCJK_family_tl }
      {#1}
  }
\cs_new_protected:Npn \@@_select_font:Nnn #1#2#3
  {
    \xeCJK_clear_fallback_font:
    \cs_if_exist:NF #1
      { \@@_block_font_initial:Nnn #1 {#2} {#3} }
    #1
  }
\cs_generate_variant:Nn \@@_select_font:Nnn { c }
\cs_new_protected:Npn \@@_block_font_initial:Nnn #1#2#3
  {
    \xeCJK_block_family:nn {#2} {#3}
    \@@_font_initial:Nn #1 { #2/#3 }
  }
\cs_new_protected:Npn \xeCJK_block_family:nn #1#2
  {
    \xeCJK_family_if_exist:eF { #1/#2 }
      {
        \@@_copy_family:ee { #1/#2 }
          {
            \cs_if_exist:cTF
              { \@@_family_csname:n { \CJKfamilydefault/#2 } }
              { \CJKfamilydefault/#2 } {#1}
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}
%   {\@@_family_csname:n,\@@_family_nfss_csname:n,
%    \@@_family_use:n,\@@_gset_family_nfss_cs:nn}
%    \begin{macrocode}
\cs_new:Npn \@@_family_csname:n #1
  { xeCJK/family/#1 }
\cs_new:Npn \@@_family_nfss_csname:n #1
  { xeCJK/family/nfss/#1 }
\cs_new_protected:Npn \@@_family_use:n #1
  { \use:c { \@@_family_nfss_csname:n {#1} } }
\cs_new_protected:Npn \@@_gset_family_nfss_cs:nn #1#2
  {
    \prop_gput:Nnn \g_@@_family_name_prop {#1} {#2}
    \cs_gset_protected:cpe
      { \@@_family_nfss_csname:n {#1} }
      { \@@_nfss_family:nn { \c_@@_encoding_tl } {#2} }
  }
\cs_generate_variant:Nn \@@_gset_family_nfss_cs:nn { no }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_nfss_family:n}
% 用于处理 \LaTeXe\ 2020/02/02 中 \tn{bfseries@rm} 等与 \tn{bfdefault} 不一致可能导致的问题。
%    \begin{macrocode}
\cs_if_exist:NTF \fontseriesforce
  {
    \cs_new_protected:Npn \@@_nfss_family:nn #1#2
      {
        \fontencoding {#1}
        \str_if_eq:eeF { \f@series } { \bfdefault }
          {
            \str_case_e:nn { \f@family }
              {
                { \rmdefault } { \@@_nfss_series:n { rm } }
                { \sfdefault } { \@@_nfss_series:n { sf } }
                { \ttdefault } { \@@_nfss_series:n { tt } }
              }
          }
        \fontfamily {#2}
        \selectfont
      }
    \cs_new_protected:Npn \@@_nfss_series:n #1
      {
        \str_if_eq:eeT { \f@series } { \use:c { bfseries@#1 } }
          { \fontseriesforce { \bfdefault } }
      }
  }
  {
    \cs_new_protected:Npn \@@_nfss_family:nn #1#2
      {
        \fontencoding {#1}
        \tl_set:Nn \f@family {#2}
        \selectfont
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[TF,int]{\xeCJK_family_if_exist:n}
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \xeCJK_family_if_exist:n #1 { T , F , TF }
  {
    \prop_get:NnNTF \g_@@_family_name_prop
      {#1} \l_@@_fontspec_family_tl
      { \prg_return_true: }
      {
        \exp_args:Ne \cs_if_exist_use:cTF
          { \@@_family_csname:n {#1} }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }
\prg_generate_conditional_variant:Nnn \xeCJK_family_if_exist:n { e } { T , F , TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKfamily}
% 用于切换 CJK 字体族。
%    \begin{macrocode}
\NewDocumentCommand \CJKfamily { t+ t- m }
  {
    \xeCJK_family:NNe #1 #2 {#3}
    \tex_ignorespaces:D
  }
\cs_new_protected:Npn \xeCJK_family:NNn #1#2#3
  {
    \tl_if_blank:nTF {#3}
      {
        \bool_if:NF #1 { \bool_if:NF #2 { \use_none:nn } }
        \xeCJK_family_if_exist_use:e { \l_xeCJK_family_tl }
      }
      {
        \bool_if:NTF #2
          { \xeCJK_family_if_exist_use:n {#3} }
          {
            \xeCJK_family_if_exist:nTF {#3}
              {
                \tl_set:Nn \l_xeCJK_family_tl {#3}
                \tl_set_eq:NN \CJK@family \l_@@_fontspec_family_tl
                \bool_if:NT #1 { \@@_family_use:n {#3} }
              }
              { \@@_family_unknown_warning:n {#3} }
          }
      }
  }
\cs_generate_variant:Nn \xeCJK_family:NNn { NNe }
\cs_new_protected:Npn \xeCJK_switch_family:n #1
  {
    \xeCJK_family_if_exist:nTF {#1}
      {
        \tl_set:Nn \l_xeCJK_family_tl {#1}
        \tl_set_eq:NN \CJK@family \l_@@_fontspec_family_tl
      }
      { \@@_family_unknown_warning:n {#1} }
  }
\cs_generate_variant:Nn \xeCJK_switch_family:n { e , o }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.6.0}{2018/01/24}{新增 \opt{PunctFamily} 选项支持对汉字标点单独切换字体。}
%
% \begin{macro}{PunctFamily}
% 设置汉字标点符号的字体。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    PunctFamily .choice: ,
    PunctFamily .value_required:n = { true } ,
    PunctFamily / false   .code:n =
      {
        \tl_set:Nn \CJK@punctfamily { \CJK@family }
        \tl_set:Nn \l_xeCJK_punct_family_tl { \l_xeCJK_family_tl }
        \xeCJK_cs_clear:N \@@_select_font:
        \xeCJK_cs_clear:N \@@_select_punct_font:
        \cs_set_eq:NN \xeCJK_select_punct_font: \xeCJK_select_font:
      } ,
    PunctFamily / unknown .code:n =
      { \xeCJK_punct_family:e {#1} } ,
  }
\cs_new_protected:Npn \xeCJK_punct_family:n #1
  {
    \xeCJK_family_if_exist:nTF {#1}
      {
        \tl_set:Nn \l_xeCJK_punct_family_tl {#1}
        \tl_set_eq:NN \CJK@punctfamily \l_@@_fontspec_family_tl
        \cs_set_eq:NN \@@_select_font: \xeCJK_select_font:
        \cs_set_eq:NN \@@_select_punct_font: \@@_select_punct_font_aux:
        \cs_set_eq:NN \xeCJK_select_punct_font: \@@_select_punct_font:
      }
      { \@@_family_unknown_warning:n {#1} }
  }
\cs_generate_variant:Nn \xeCJK_punct_family:n { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}[int]{\l_xeCJK_family_tl}
% 用于保存文档当前正在使用的 CJK 字体族。
% \changes{v3.2.0}{2013/04/14}{不将其初始化为 \tn{CJKfamilydefault}。}
%    \begin{macrocode}
\tl_new:N \l_xeCJK_family_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\CJK@family}
% 用于保存实际的字体族名称。
% \changes{v3.2.11}{2014/03/29}{引入 \tn{CJK@family} 保存实际的字体族名。}
%    \begin{macrocode}
\tl_new:N \CJK@family
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_gobble_CJKfamily:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_gobble_CJKfamily:
  { \cs_set_eq:NN \CJKfamily \@@_gobble_CJKfamily:wn }
\NewExpandableDocumentCommand \@@_gobble_CJKfamily:wn { t+ t- m } {  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_family_if_exist_use:n}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_family_if_exist_use:n #1
  {
    \xeCJK_family_if_exist:nTF {#1}
      { \@@_family_use:n {#1} }
      { \@@_family_unknown_warning:n {#1} }
  }
\cs_generate_variant:Nn \xeCJK_family_if_exist_use:n { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_family_unknown_warning:n}
% \changes{v3.1.2}{2013/01/01}
% {在没有定义任何 CJK 字体的情况下，不再重复给出字体没有定义的警告。}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_family_unknown_warning:n #1
  {
    \prop_if_empty:NF \g_@@_family_font_name_prop
      {
        \seq_if_in:NnF \g_@@_unknown_family_seq {#1}
          {
            \seq_gput_right:Nn \g_@@_unknown_family_seq {#1}
            \@@_warning:ne { CJKfamily-Unknown } {#1}
          }
      }
  }
\seq_new:N \g_@@_unknown_family_seq
\@@_msg_new:nn { CJKfamily-Unknown }
  {
    Unknown~CJK~family~`\@@_msg_family_map:n {#1}'~is~being~ignored.\\\\
    Try~to~use~`\@@_msg_def_family_map:n {#1}'~to~define~it.
  }
\cs_new:Npn \@@_msg_def_family_map:n #1
  {
    \str_case_e:nnF {#1}
      {
        \CJKrmdefault { \token_to_str:N \setCJKmainfont }
        \CJKsfdefault { \token_to_str:N \setCJKsansfont }
        \CJKttdefault { \token_to_str:N \setCJKmonofont }
      }
      { \token_to_str:N \setCJKfamilyfont \{ #1 \} }
    [<...>]\{<...>\}
  }
\cs_new:Npn \@@_msg_family_map:n #1
  {
    \str_case_e:nnF {#1}
      {
        \CJKrmdefault { \token_to_str:N \CJKrmdefault }
        \CJKsfdefault { \token_to_str:N \CJKsfdefault }
        \CJKttdefault { \token_to_str:N \CJKttdefault }
      }
      {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_pass_args:nnnn}
% 为了支持字体属性可选项在前和在后两种语法，给出两个辅助工具，类似
% \package{fontspec} 的实现。自带展开功能，额外参数 |#4| 用于后处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_pass_args:nnnn #1#2#3#4
  {
    \tl_if_novalue:nTF {#2}
      { \@@_post_arg:w {#1} {#3} {#4} }
      {
        \use:e { #1 {#2} {#3} }
        #4
      }
  }
\NewDocumentCommand \@@_post_arg:w { m m m O { } }
  {
    \use:e { #1 {#4} {#2} }
    #3
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\setCJKmainfont,\setCJKsansfont,\setCJKmonofont}
% 设置文档的 CJK 普通字体、无衬线和等宽字体。
% \changes{v3.2.0}{2013/04/14}{定义中加入 \tn{normalfont}。}
% \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。}
%    \begin{macrocode}
\NewDocumentCommand \setCJKmainfont { o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn { \CJKrmdefault } } {#1} {#2}
      { \@@_preamble_family:n { \CJKrmdefault } }
  }
\cs_new_eq:NN \setCJKromanfont \setCJKmainfont
\NewDocumentCommand \setCJKsansfont { o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn { \CJKsfdefault } } {#1} {#2}
      { \@@_preamble_family:n { \CJKsfdefault } }
  }
\NewDocumentCommand \setCJKmonofont { o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn { \CJKttdefault } } {#1} {#2}
      { \@@_preamble_family:n { \CJKttdefault } }
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
\@onlypreamble \setCJKmainfont
\@onlypreamble \setCJKmathfont
\@onlypreamble \setCJKsansfont
\@onlypreamble \setCJKmonofont
\@onlypreamble \setCJKromanfont
%    \end{macrocode}
%
% \changes{v3.8.2}{2020/02/17}{避免导言区字体警告。}
%
% \begin{macro}{\@@_preamble_family:n}
% 用在 \tn{setCJKmainfont} 等主要命令之后，确保导言区有 CJK 字体可用。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_preamble_family:n #1
  { \str_if_eq:eeT {#1} { \CJKfamilydefault } { \normalfont } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\setCJKfamilyfont, \newCJKfontfamily, \CJKfontspec}
% \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。}
% \changes{v3.10.0}{2026/04/24}{\textbf{Breaking:}
%   \tn{newCJKfontfamily} 定义的字体切换命令改为局部定义，
%   与 \tn{newcommand} 行为一致。在分组内调用时，
%   命令不再泄漏到分组外（\#751）。}
% 分别用于预声明 CJK 字体族和声明并马上调用 CJK 字体族。
%    \begin{macrocode}
\NewDocumentCommand \setCJKfamilyfont { m o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn {#1} } {#2} {#3}
      { }
  }
\@@_msg_new:nn { command-already-defined }
  { Control~sequence~#1~is~already~defined. }
\NewDocumentCommand \newCJKfontfamily { o m o m }
  {
    \tl_set:Ne \l_@@_tmp_tl
      { \tl_if_novalue:nTF {#1} { \cs_to_str:N #2 } {#1} }
    \cs_if_exist:NTF #2
      { \@@_error:ne { command-already-defined } { \token_to_str:N #2 } }
      {
        \cs_set_protected:Npe #2
          { \xeCJK_switch_family:n { \l_@@_tmp_tl } }
      }
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn { \l_@@_tmp_tl } } {#3} {#4}
      { }
  }
\NewDocumentCommand \CJKfontspec { o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_fontspec:nn } {#1} {#2}
      { \tex_ignorespaces:D }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_fontspec:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_fontspec:nn #1#2
  {
    \prop_get:NnNTF \g_@@_fontspec_prop
      { CJKfontspec/#1/#2/id } \l_xeCJK_family_tl
      { \xeCJK_switch_family:o { \l_xeCJK_family_tl } }
      {
        \@@_fontspec:enn
          { CJKfontspec ( \int_eval:n { \g_@@_family_int + 1 } ) }
          {#1} {#2}
      }
  }
\cs_new_protected:Npn \@@_fontspec:nnn #1#2#3
  {
    \prop_gput:Nnn \g_@@_fontspec_prop { CJKfontspec/#2/#3/id } {#1}
    \xeCJK_set_family:nnn {#1} {#2} {#3}
    \xeCJK_switch_family:n {#1}
  }
\cs_generate_variant:Nn \xeCJK_fontspec:nn { oo }
\cs_generate_variant:Nn \@@_fontspec:nnn { e }
\prop_new:N \g_@@_fontspec_prop
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\defaultCJKfontfeatures, \addCJKfontfeatures}
% \changes{v3.2.4}{2013/06/30}{可以单独增加当前各个分区字体的属性。}
% 分别用于设置 CJK 字体的默认属性和增加当前 CJK 字体的属性。
%    \begin{macrocode}
\clist_new:N \g_@@_default_features_clist
\NewDocumentCommand \defaultCJKfontfeatures { m }
  { \clist_gset:Nn \g_@@_default_features_clist {#1} }
\@onlypreamble \defaultCJKfontfeatures
\NewDocumentCommand \addCJKfontfeatures { s O { } m }
  {
    \xeCJK_add_font_features:Nee #1 {#2} {#3}
    \tex_ignorespaces:D
  }
\cs_new_eq:NN \addCJKfontfeature \addCJKfontfeatures
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_add_font_features:Nnn}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_add_font_features:Nnn #1#2#3
  {
    \prop_get:NoNTF \g_@@_family_font_name_prop
      \l_xeCJK_family_tl \l_@@_font_name_tl
      {
        \clist_set:Nn \l_@@_add_font_features_clist {#3}
        \seq_map_inline:Nn \g_@@_sub_key_seq
          { \clist_remove_all:Nn \l_@@_add_font_features_clist {##1} }
        \seq_clear:N \l_@@_sub_key_seq
        \clist_clear:N \l_@@_add_block_features_clist
        \clist_map_function:nN {#2} \@@_add_sub_features:n
        \bool_lazy_and:nnT
          {#1}
          { \seq_if_empty_p:N \l_@@_sub_key_seq }
          {
            \seq_map_function:NN
              \g_@@_sub_key_seq \@@_add_sub_class_features:n
          }
        \prop_get:NoNT \g_@@_family_font_options_prop
          \l_xeCJK_family_tl \l_@@_font_options_clist
          {
            \bool_lazy_or:nnT
              { \seq_if_empty_p:N \l_@@_sub_key_seq }
              {#1}
              {
                \clist_concat:NNN \l_@@_font_options_clist
                  \l_@@_font_options_clist \l_@@_add_font_features_clist
              }
            \clist_concat:NNN \l_@@_font_options_clist
              \l_@@_font_options_clist \l_@@_add_block_features_clist
          }
        \xeCJK_fontspec:oo \l_@@_font_options_clist \l_@@_font_name_tl
      }
      { \@@_warning:n { addCJKfontfeature-ignored } }
  }
\cs_new_protected:Npn \@@_add_sub_features:n #1
  {
    \seq_if_in:NnTF \g_@@_sub_key_seq {#1}
      {
        \seq_put_right:Nn \l_@@_sub_key_seq {#1}
        \@@_add_sub_class_features:n {#1}
      }
      { \@@_warning:ne { SubBlock-undefined } {#1} }
  }
\clist_new:N \l_@@_add_font_features_clist
\clist_new:N \l_@@_add_block_features_clist
\cs_generate_variant:Nn \xeCJK_add_font_features:Nnn { Nee , Nne }
\@@_msg_new:nn { addCJKfontfeature-ignored }
  {
    \token_to_str:N \addCJKfontfeature (s)~ignored.\\\\
    It~cannot~be~used~with~a~font~that~wasn't~selected~by~xeCJK.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_add_sub_class_features:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_add_sub_class_features:n #1
  {
    \prop_get:NoNTF \g_@@_family_font_name_prop
      { \l_xeCJK_family_tl/#1 } \l_@@_sub_font_name_tl
      {
        \prop_get:NoN \g_@@_family_font_options_prop
          { \l_xeCJK_family_tl/#1 } \l_@@_sub_font_options_clist
      }
      {
        \prop_get:NeNTF \g_@@_family_font_name_prop
          { \CJKfamilydefault/#1 } \l_@@_sub_font_name_tl
          {
            \prop_get:NeN \g_@@_family_font_options_prop
              { \CJKfamilydefault/#1 } \l_@@_sub_font_options_clist
          }
          {
            \prop_get:NoN \g_@@_family_font_options_prop
              \l_xeCJK_family_tl \l_@@_sub_font_options_clist
            \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_font_name_tl
          }
      }
    \clist_concat:NNN \l_@@_sub_font_options_clist
      \l_@@_sub_font_options_clist \l_@@_add_font_features_clist
    \clist_put_right:Ne \l_@@_add_block_features_clist
      {
        #1 =
          {
            [ \exp_not:o \l_@@_sub_font_options_clist ]
            { \exp_not:o \l_@@_sub_font_name_tl }
          }
      }
  }
\cs_generate_variant:Nn \prop_get:NnN { Ne }
\prg_generate_conditional_variant:Nnn \prop_get:NnN { Ne } { T , TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{LoadFandol}
% \changes{v3.2.10}{2014/03/01}{当没有设置字体时，使用 Fandol 字体系列。}
% \changes{v3.3.1}{2015/04/08}{为方便 MacTeX 用户，Fandol 字体改用文件名。}
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  { LoadFandol .bool_gset:N = \g_@@_fandol_bool }
\cs_new_protected:Npn \@@_load_fandol:
  {
    \xeCJK_set_family:enn { \CJKrmdefault }
      { Extension = .otf , BoldFont = FandolSong-Bold , ItalicFont = FandolKai-Regular }
      { FandolSong-Regular }
    \xeCJK_set_family:enn { \CJKsfdefault }
      { Extension = .otf , BoldFont = FandolHei-Bold } { FandolHei-Regular }
    \xeCJK_set_family:enn { \CJKttdefault }
      { Extension = .otf } { FandolFang-Regular }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.1.2}{2013/01/01}{修正重定义 \tn{CJKfamilydefault} 无效的问题，恢复容错能力。}
% \changes{v3.2.13}{2014/06/02}{自动调整 \tn{CJKfamilydefault} 时，只将 \tn{familydefault} 展开一次。}
%
% 在导言区结束的时候，若没有声明 CJK 字体，则给出一个警告。如果 \tn{CJKfamilydefault}
% 没有被更改，则在此时根据西文字体的情况更新 \tn{CJKfamilydefault}。
% 如果 \tn{CJKfamilydefault} 对应的字体族没有定义，则使用 \tn{CJKrmdefault} 作为
% 默认字体族。若 \tn{CJKrmdefault} 也没有定义，则使用在导言区设置的第一个 CJK 字体
% 作为默认字体族。最后设置数学字体。
%    \begin{macrocode}
\@@_at_end_preamble:n
  {
    \tl_if_eq:NNT \CJKfamilydefault \l_@@_family_default_init_tl
      {
        \group_begin:
        \cs_set_eq:NN \@@_family_default_wrap:n \exp_not:n
        \tl_gset:Ne \CJKfamilydefault
          {
            \str_case:onF { \familydefault }
              {
                { \rmdefault } { \exp_not:N \CJKrmdefault }
                { \sfdefault } { \exp_not:N \CJKsfdefault }
                { \ttdefault } { \exp_not:N \CJKttdefault }
              }
              { \CJKfamilydefault }
          }
        \group_end:
      }
    \prop_if_empty:NTF \g_@@_family_font_name_prop
      {
        \bool_if:NTF \g_@@_fandol_bool
          {
            \@@_warning:n { fandol }
            \@@_load_fandol:
            \xeCJK_ensure_default_family:
          }
          { \@@_warning:ne { no-CJKfamily } { \CJKfamilydefault } }
      }
      { \xeCJK_ensure_default_family: }
  }
\cs_new_protected:Npn \xeCJK_ensure_default_family:
  {
    \xeCJK_family_if_exist:eF { \CJKfamilydefault }
      {
        \tl_set_eq:NN \l_@@_tmp_tl \CJKfamilydefault
        \str_if_eq:eeTF { \CJKfamilydefault } { \CJKrmdefault }
          { \use:n }
          {
            \xeCJK_family_if_exist:eTF { \CJKrmdefault }
              { \tl_gset:Nn \CJKfamilydefault { \CJKrmdefault } }
          }
          {
            \prop_map_inline:Nn \g_@@_family_font_name_prop
              {
                \prop_map_break:n
                  { \tl_gset_rescan:Nnn \CJKfamilydefault { } { ##1 } }
              }
          }
        \@@_warning:nee { CJKfamilydefault-undefined }
          { \l_@@_tmp_tl } { \CJKfamilydefault }
      }
    \xeCJK_switch_family:e { \CJKfamilydefault }
    \bool_if:NT \g_@@_math_bool { \xeCJK_set_mathfont: }
  }
\@@_msg_new:nn { no-CJKfamily }
  {
    It~seems~that~you~have~not~declared~a~CJKfamily.\\
    If~you~want~to~use~xeCJK~in~the~right~way,~you~should~use\\\\
    `\@@_msg_def_family_map:n {#1}'\\\\
    in~the~preamble~to~declare~the~default~CJKfamily.\\
  }
\@@_msg_new:nn { CJKfamilydefault-undefined }
  {
    Undefined~CJK~default~family~`\@@_msg_family_map:n {#1}'~
    has~been~replaced~by~`\@@_msg_family_map:n {#2}'.\\\\
    Try~to~use~`\@@_msg_def_family_map:n {#1}'~to~define~it.
  }
\@@_msg_new:nn { fandol }
  {
    Fandol~is~being~set~as~the~default~font~for~CJK~text.\\
    Please~make~sure~it~has~been~properly~installed.
  }
%    \end{macrocode}
%
% \subsection{数学字体设置}
%
% \begin{macro}{CJKmath}
% 是否启用 CJK 数学字体的宏包选项。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options } { CJKmath .bool_gset:N = \g_@@_math_bool }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\setCJKmathfont}
% \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。}
% 设置 CJK 数学字体。
%    \begin{macrocode}
\NewDocumentCommand \setCJKmathfont { o m }
  {
    \@@_pass_args:nnnn
      { \xeCJK_set_family:nnn { \c_@@_math_tl } } {#1} {#2}
      { }
  }
\tl_const:Nn \c_@@_math_tl { CJKmath }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_set_mathfont:}
% \changes{v3.2.6}{2013/08/01}{设置粗体时先检查对应字体是否存在。}
% \changes{v3.2.7}{2013/11/09}{将 CJK 字符的数学归类由 $7$ 改为 $0$，解决汉字路径的问题。}
% \changes{v3.2.13}{2014/06/20}{修复参数类型错误。}
% \changes{v3.4.0}{2016/05/01}{CJKmath 的字符范围遵从 \tn{xeCJKDeclareCharClass} 的设置。}
% 当没有设置 CJK 数学字体时，使用 \tn{CJKfamilydefault} 作为数学字体。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_set_mathfont:
  {
    \cs_if_exist_use:N \@@_save_um_char:
    \xeCJK_family_if_exist:eTF { \c_@@_math_tl }
      { \@@_set_mathfont_aux: }
      {
        \xeCJK_family_if_exist:eT { \CJKfamilydefault }
          {
            \@@_copy_family:ee { \c_@@_math_tl } { \CJKfamilydefault }
            \@@_set_mathfont_aux:
          }
      }
    \cs_if_exist_use:N \@@_restore_um_char:
  }
\cs_new_protected:Npn \@@_set_mathfont_aux:
  {
    \tl_const:Ne \c_@@_math_family_tl
      { \l_@@_fontspec_family_tl }
    \xeCJK_declare_mathfont:ee
      { \c_@@_math_tl }
      { \c_@@_math_family_tl }
    \int_const:Nn \c_xeCJK_math_fam_int
      { \use:c { sym \c_@@_math_tl } }
    \clist_gconcat:NNN \g_@@_math_chars_clist
      \g_@@_CJK_range_clist \g_@@_FullLeft_range_clist
    \clist_gconcat:NNN \g_@@_math_chars_clist
      \g_@@_math_chars_clist \g_@@_FullRight_range_clist
    \xeCJK_gset_mathcode:Nn \g_@@_math_chars_clist
      { \c_xeCJK_math_fam_int }
    \xeCJK_set_mathfont_block:
  }
\clist_new:N \g_@@_math_chars_clist
\prop_new:N \g_@@_fam_prop
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.4.0}{2016/05/04}{CJKmath 功能也支持分区字体。}
%
% \begin{macro}[int]{\xeCJK_set_mathfont_block:}
% 分区数学字体。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_set_mathfont_block:
  {
    \seq_if_empty:NF \g_@@_CJK_sub_class_seq
      {
        \seq_map_function:NN
          \g_@@_CJK_sub_class_seq
          \xeCJK_set_mathfont_block:n
      }
  }
\cs_new_protected:Npn \xeCJK_set_mathfont_block:n #1
  {
    \xeCJK_block_family:nn { \c_@@_math_tl } {#1}
    \prop_get:NoNTF \g_@@_fam_prop
      \l_@@_fontspec_family_tl \l_@@_tmp_tl
      { \int_set:Nn \l_@@_fam_int { \l_@@_tmp_tl } }
      {
        \xeCJK_declare_mathfont:ee
          { \c_@@_math_tl / #1 }
          { \l_@@_fontspec_family_tl }
        \@@_set_mathfont_block_aux:cn
          { sym \c_@@_math_tl / #1 } {#1}
      }
    \xeCJK_gset_mathcode:cn { g_@@_CJK/#1_range_clist } { \l_@@_fam_int }
  }
\cs_new_protected:Npn \@@_set_mathfont_block_aux:Nn #1#2
  {
    \int_set_eq:NN \l_@@_fam_int #1
    \prop_gput:Nnn \g_@@_block_fam_prop {#2} {#1}
  }
\int_new:N \l_@@_fam_int
\prop_new:N \g_@@_block_fam_prop
\cs_generate_variant:Nn \@@_set_mathfont_block_aux:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.1}{2020/02/14}{应用 \tn{shapedefault}。}
%
% \begin{macro}[int]{\xeCJK_declare_mathfont:nn}
% 注意从 \LaTeXe\ 2020/02/02 开始，\tn{shapedefault} 初始值是 \texttt{n}，
% 而 \tn{updefault} 初始值是 \texttt{up}，两者并不一致。
% \pkg{fontspec} 包定义字体使用的是 \tn{shapedefault}。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_declare_mathfont:nn #1#2
  {
    \xeCJK_declare_symbol_font:nnnnn {#1} { \c_@@_encoding_tl }
      {#2} { \mddefault } { \shapedefault }
    \cs_if_free:cF
      { \c_@@_encoding_tl/#2/\bfdefault/\shapedefault }
      {
        \SetSymbolFont {#1} { bold } { \c_@@_encoding_tl }
          {#2} { \bfdefault } { \shapedefault }
      }
    \prop_gput:Nne \g_@@_fam_prop {#2} { \exp_not:c { sym #1 } }
  }
\cs_generate_variant:Nn \prop_put:Nnn { Nne }
\cs_generate_variant:Nn \prop_gput:Nnn { Nne }
\cs_generate_variant:Nn \xeCJK_declare_mathfont:nn { ee }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_declare_symbol_font:nnnnn}
% 主要功能同 \tn{DeclareSymbolFont}，不带编码和重复定义检查。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_declare_symbol_font:nnnnn #1
  { \@@_declare_symbol_font:cnnnn { sym #1 } }
\cs_new_protected:Npn \@@_declare_symbol_font:Nnnnn #1
  {
    \xeCJK_new_fam:N #1
    \xeCJK_new_symbol_font:Nnnnn #1
  }
\cs_generate_variant:Nn \@@_declare_symbol_font:Nnnnn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_new_fam:N}
% 我们从 $255$ 往下分配 \tn{fam}，|\count18| 是 \LaTeXe{} 记录最后分配的 \tn{fam} 编号，
% 作为我们的分配器的下限。事实上，还应该相应地减小 \tn{e@mathgroup@top} 才合理，但这可能会有不利影响，
% 我们暂未处理。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_new_fam:N #1
  {
    \int_compare:nNnTF
      { \g_@@_fam_allocation_int } > { \g_@@_fam_bottom_int }
      {
        \int_set_eq:NN \allocationnumber \g_@@_fam_allocation_int
        \int_const:Nn #1 { \allocationnumber }
        \iow_log:e
          {
            \token_to_str:N #1 =
            \token_to_str:N \mathgroup \int_use:N \allocationnumber
          }
        \int_gdecr:N \g_@@_fam_allocation_int
      }
      { \@@_error:n { fam-exhausted } }
  }
\tex_countdef:D \g_@@_fam_bottom_int = 18 ~
\int_new:N \g_@@_fam_allocation_int
\int_gset:Nn \g_@@_fam_allocation_int { 255 }
\@@_msg_new:nn { fam-exhausted }
  { No~room~for~a~new~fam. }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_new_symbol_font:Nnnnn}
% \begin{macro}{\@@_new_symbol_font:NN}
% 功能同 \tn{new@symbolfont}，但我们不增加 \tn{c@mv@normal} 和 \tn{c@mv@bold} 之类的计数器。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_new_symbol_font:Nnnnn #1#2#3#4#5
  { \@@_new_symbol_font:Nc #1 { #2/#3/#4/#5 } }
\cs_new_protected:Npn \@@_new_symbol_font:NN #1#2
  {
    \tl_put_right:Nn \group@list { \group@elt #1 #2 }
    \cs_set:Npn \version@elt ##1
      { \tl_put_right:Nn ##1 { \getanddefine@fonts #1 #2 } }
    \version@list
  }
\cs_generate_variant:Nn \@@_new_symbol_font:NN { Nc }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_gset_mathcode:Nn,
%   \xeCJK_gset_mathcode:Nnn,\xeCJK_gset_mathcode:nnnn}
% CJK 字符的数学类别固定为 $0$（\tn{mathord}）。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_gset_mathcode:Nn #1#2
  {
    \clist_map_inline:Nn #1
      {
        \@@_set_char_class_aux:Nnw \xeCJK_gset_mathcode:nnnn { ##1 }
          { 0 } {#2}
      }
  }
\cs_generate_variant:Nn \xeCJK_gset_mathcode:Nn { c }
\cs_new_protected:Npn \xeCJK_gset_mathcode:nnnn #1#2#3#4
  {
    \@@_check_num_range:nnNN {#1} {#2} \l_@@_begin_int \l_@@_end_int
    \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int }
      {
        \xeCJK_gset_mathcode:Nnn \l_@@_begin_int {#3} {#4}
        \int_incr:N \l_@@_begin_int
      }
  }
\cs_new_protected:Npn \xeCJK_gset_mathcode:Nnn #1#2#3
  { \tex_global:D \tex_Umathcode:D #1 = #2 ~ #3 ~ #1 }
%    \end{macrocode}
% \end{macro}
%
%
% \subsection{抄录环境中的间距调整}
%
% \changes{v3.1.0}{2012/11/19}{放弃使用放缩字体大小的方式，而只采用调整间距的方式
% 与西文等宽字体对齐。并且只适用于与抄录环境下。}
% \changes{v3.2.0}{2013/04/15}{增加 \texttt{Verb} 选项。}
% \changes{v3.2.1}{2013/05/29}{调整 \texttt{Verb} 选项：在命令 \tn{verb} 里使用时，
% 不破坏标点禁则，增加值 \texttt{env+}。}
%
% \begin{macro}{Verb}
% \changes{v3.2.5}{2013/07/25}{微调定义。}
% 如果设置为 \texttt{env}，则只在 \LaTeX 的抄录环境里使用 \tn{xeCJKVerbAddon}，^^A
% 而不包括 \tn{verb}。对当前使用环境的判断基于在标准 \LaTeX 的环境定义里使用
% \tn{begingroup} 和 \tn{endgroup} 来分组。
%    \begin{macrocode}
\int_new:N \l_@@_verb_case_int
\keys_define:nn { xeCJK / options }
  {
    Verb .choices:nn =
      { true , env+ , env , false }
      { \int_set_eq:NN \l_@@_verb_case_int \l_keys_choice_int } ,
    Verb  .default:n = { env }
  }
\cs_new_protected:Npn \@@_verb_font_hook:
  {
    \if_case:w \l_@@_verb_case_int
    \or:
      \@@_nobreak_skip_zero:
    \or:
      \int_compare:nNnTF \tex_currentgrouptype:D = { 14 }
        { \xeCJKVerbAddon }
        { \@@_nobreak_skip: }
    \or:
      \int_compare:nNnTF \tex_currentgrouptype:D = { 14 }
        { \xeCJKVerbAddon }
        { \@@_nobreak_skip_zero: }
    \fi:
  }
\@@_after_preamble:n
  {
    \cs_set_protected:Npe \verbatim@font
      { \exp_not:o { \verbatim@font } \@@_verb_font_hook: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_nobreak_skip_zero:,\@@_nobreak_skip:}
% \changes{v3.2.8}{2013/11/16}{禁止在 \tn{verb} 中断行。}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_nobreak_skip_zero:
  {
    \@@_reset_shipout_skip:
    \cs_set_eq:NN \@@_shipout_check_for_glue: \xeCJK_check_for_glue:
    \cs_set_eq:NN \@@_shipout_boundary:w \xeCJK_CJK_and_Boundary:w
    \tl_put_right:Nn \l_@@_reset_shipout_skip_hook_tl
      {
        \cs_set_eq:NN \xeCJK_check_for_glue: \@@_shipout_check_for_glue:
        \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_shipout_boundary:w
      }
    \xeCJK_cs_clear:N \CJKglue
    \xeCJK_cs_clear:N \CJKecglue
    \xeCJK_cs_clear:N \xeCJK_check_for_glue:
    \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \xeCJK_class_group_end:
    \cs_set_eq:NN \@@_punct_hskip:n \@@_nobreak_hskip:n
    \cs_set_eq:NN \@@_punct_breakable_kern:n \@@_nobreak_hskip:n
  }
\cs_new_protected:Npn \@@_nobreak_skip:
  {
    \@@_reset_shipout_skip:
    \xeCJK_glue_to_skip:nN { \CJKglue } \l_@@_ccglue_skip
    \skip_if_eq:nnTF { \l_@@_ccglue_skip } { \c_zero_skip }
      { \xeCJK_cs_clear:N \CJKglue }
      { \cs_set_eq:NN \CJKglue \@@_nobreak_ccglue: }
    \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_ecglue_skip
    \skip_if_eq:nnTF { \l_@@_ecglue_skip } { \c_zero_skip }
      { \xeCJK_cs_clear:N \CJKecglue }
      { \cs_set_eq:NN \CJKecglue \@@_nobreak_ecglue: }
    \cs_set_eq:NN \@@_punct_hskip:n \@@_nobreak_hskip:n
    \cs_set_eq:NN \@@_punct_breakable_kern:n \@@_nobreak_hskip:n
  }
\cs_new_protected:Npn \@@_nobreak_ccglue:
  { \xeCJK_no_break: \skip_horizontal:N \l_@@_ccglue_skip }
\cs_new_protected:Npn \@@_nobreak_ecglue:
  { \xeCJK_no_break: \skip_horizontal:N \l_@@_ecglue_skip }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_reset_shipout_skip:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_reset_shipout_skip:
  {
    \cs_set_eq:NN \@@_shipout_CJKglue:   \CJKglue
    \cs_set_eq:NN \@@_shipout_CJKecglue: \CJKecglue
    \cs_set_eq:NN \@@_shipout_punct_hskip:n \@@_punct_hskip:n
    \cs_set_eq:NN
      \@@_shipout_punct_breakable_kern:n \@@_punct_breakable_kern:n
    \tl_set:Ne \l_@@_off_verb_addon_tl
      {
        \bool_if:NTF \l_@@_xecglue_bool
          { \keys_set:nn { xeCJK / options } { xCJKecglue = true } }
          { \keys_set:nn { xeCJK / options } { xCJKecglue = false } }
        \exp_not:n
          {
            \cs_set_eq:NN \CJKglue \@@_shipout_CJKglue:
            \cs_set_eq:NN \CJKecglue \@@_shipout_CJKecglue:
            \cs_set_eq:NN \@@_punct_hskip:n \@@_shipout_punct_hskip:n
            \cs_set_eq:NN \@@_punct_breakable_kern:n
                          \@@_shipout_punct_breakable_kern:n
            \l_@@_reset_shipout_skip_hook_tl
          }
      }
    \xeCJK_add_to_shipout:n { \l_@@_off_verb_addon_tl }
    \keys_set:nn { xeCJK / options } { xCJKecglue = false }
  }
\tl_new:N \l_@@_reset_shipout_skip_hook_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKOffVerbAddon,\xeCJKVerbAddon}
% \changes{v3.1.0}{2012/11/19}{新增 \tn{xeCJKVerbAddon} 用于抄录环境中的间距调整。}
% \changes{v3.2.3}{2013/06/04}
% {新增 \tn{xeCJKOffVerbAddon} 用于局部取消 \tn{xeCJKOffVerbAddon} 的影响；并解决
% 跨页使用时影响到页眉页脚的问题。}
% \changes{v3.2.5}{2013/07/13}{禁止自动换行，与西文一致。}
% \changes{v3.2.8}{2013/11/16}{增加是否是等宽字体的判断。}
% \tn{xeCJKVerbAddon} 进行了比较大的调整，应该只在分组环境里使用。为了方便调整间距
% 以利于对齐，这里只把字符分成了两类，并且在 CJK 类与边界（空格）之间也插入
% \tn{CJKecglue}。以字母“M”的宽度是否等于 \tn{fontdimen2} 来判断当前字体是否是
% 等宽字体。如果不是等宽字体，则设置间距为零或正文间距。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKVerbAddon { }
  {
    \int_compare:nNnF \tex_currentgrouplevel:D = \c_zero_int
      {
        \bool_if:NF \l_@@_listings_env_bool
          {
            \dim_compare:nNnTF
              { \tex_fontdimen:D 2 ~ \tex_font:D } =
              { \tex_fontcharwd:D \tex_font:D \c_@@_mono_letter_int }
              {
                \@@_set_verb_exspace:
                \@@_verb_addon:
              }
              {
                \int_if_odd:nTF { \l_@@_verb_case_int }
                  { \@@_nobreak_skip_zero: }
                  { \@@_nobreak_skip: }
              }
          }
      }
  }
\int_const:Nn \c_@@_mono_letter_int { 77 }
\bool_new:N \l_@@_listings_env_bool
\NewDocumentCommand \xeCJKOffVerbAddon { }
  { \tl_use:N \l_@@_off_verb_addon_tl }
\tl_new:N \l_@@_off_verb_addon_tl
\cs_new_protected:Npn \@@_verb_addon:
  {
    \bool_if:NF \l_@@_verb_addon_bool
      { \@@_verb_addon_action: }
    \skip_if_eq:nnTF { \l_@@_verb_exspace_skip } { \c_zero_skip }
      {
        \xeCJK_cs_clear:N \CJKglue
        \xeCJK_cs_clear:N \CJKecglue
      }
      {
        \skip_set_eq:NN \l_@@_ccglue_skip \l_@@_verb_exspace_skip
        \skip_set:Nn \l_@@_ecglue_skip { \l_@@_verb_exspace_skip / 2 }
        \cs_set_eq:NN \CJKglue   \@@_nobreak_ccglue:
        \cs_set_eq:NN \CJKecglue \@@_nobreak_ecglue:
      }
    \cs_set_eq:NN \xeCJK_check_for_glue: \CJKecglue
    \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_verb_CJK_and_Boundary:w
  }
\cs_new_protected:Npn \@@_verb_addon_action:
  {
    \bool_set_true:N \l_@@_verb_addon_bool
    \@@_set_char_class_eq:nn { FullLeft }    { CJK }
    \@@_set_char_class_eq:nn { FullRight }   { CJK }
    \@@_set_char_class_eq:nn { HalfLeft }    { Default }
    \@@_set_char_class_eq:nn { HalfRight }   { Default }
    \@@_set_char_class_eq:nn { NormalSpace } { Default }
    \cs_set_eq:NN \@@_shipout_CJKglue:   \CJKglue
    \cs_set_eq:NN \@@_shipout_CJKecglue: \CJKecglue
    \cs_set_eq:NN \@@_shipout_check_for_glue: \xeCJK_check_for_glue:
    \cs_set_eq:NN \@@_shipout_boundary:w \xeCJK_CJK_and_Boundary:w
    \cs_set_protected:Npe \xeCJKOffVerbAddon
      {
        \@@_reset_char_class:n { FullLeft }
        \@@_reset_char_class:n { FullRight }
        \@@_reset_char_class:n { HalfLeft }
        \@@_reset_char_class:n { HalfLeft }
        \@@_reset_char_class:n { NormalSpace }
        \bool_if:NTF \l_@@_xecglue_bool
          { \keys_set:nn { xeCJK / options } { xCJKecglue = true } }
          { \keys_set:nn { xeCJK / options } { xCJKecglue = false } }
        \exp_not:n
          {
            \cs_set_eq:NN \CJKglue   \@@_shipout_CJKglue:
            \cs_set_eq:NN \CJKecglue \@@_shipout_CJKecglue:
            \cs_set_eq:NN \xeCJK_check_for_glue: \@@_shipout_check_for_glue:
            \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_shipout_boundary:w
          }
      }
    \xeCJK_add_to_shipout:n { \xeCJKOffVerbAddon }
    \keys_set:nn { xeCJK / options } { xCJKecglue = false }
  }
\cs_new_protected:Npn \@@_verb_CJK_and_Boundary:w
  { \xeCJK_class_group_end: \CJKecglue }
\cs_new_protected:Npn \@@_reset_char_class:n #1
  {
    \int_set:Nn \l_@@_tmp_int { \xeCJK_class_num:n {#1} }
    \clist_map_inline:cn { c_@@_#1_chars_clist }
      { \tex_XeTeXcharclass:D ##1 = \l_@@_tmp_int }
  }
\bool_new:N \l_@@_verb_addon_bool
\cs_new_eq:NN \CJKfixedspacing \xeCJKVerbAddon
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_verb_exspace:}
% \changes{v3.1.1}{2012/12/08}{调整间距的计算方法。}
% \changes{v3.2.4}{2013/06/29}{当计算得出的间距为负时，缩小 CJK 字体。}
% 在抄录环境中，CJK 文字之间的间距为当前西文字体两个空格的宽度与当前字体大小之差，
% 而与西文和空格的间距为 CJK 文字之间的间距的一半。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_verb_exspace:
  {
    \tl_if_exist:cTF { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size }
      {
        \skip_set:Nn \l_@@_verb_exspace_skip
          { \use:c { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size } }
      }
      {
        \tl_set:Ne \l_@@_current_coor_tl { \CJK@family/\curr@fontshape }
        \prop_get:NoNTF \g_@@_scale_family_prop
          \l_@@_current_coor_tl \l_xeCJK_family_tl
          {
            \xeCJK_switch_family:o { \l_xeCJK_family_tl }
            \skip_zero:N \l_@@_verb_exspace_skip
          }
          {
            \group_begin: \xeCJK_select_font: \exp_args:NNo \group_end:
            \@@_set_verb_exspace:n
              { \dim_use:N \tex_fontcharwd:D \tex_font:D "4E00 ~ }
          }
      }
  }
\skip_new:N \l_@@_verb_exspace_skip
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_verb_exspace:n}
% 当两个西文空格的宽度小于一个 CJK 文字的宽度时，对目前使用的 CJK 字体进行适当缩小。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_verb_exspace:n #1
  {
    \skip_set:Nn \l_@@_verb_exspace_skip
      { 2 \tex_fontdimen:D 2 ~ \tex_font:D - #1 }
    \dim_compare:nNnTF \l_@@_verb_exspace_skip < \c_zero_dim
      {
        \skip_zero:N \l_@@_verb_exspace_skip
        \exp_args:Nee \@@_set_verb_scale:nn
          { \dim_to_fp:n { 2 \tex_fontdimen:D 2 ~ \tex_font:D } }
          { \dim_to_fp:n {#1} }
      }
      {
        \tl_const:ce { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size }
          { \skip_use:N \l_@@_verb_exspace_skip }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_set_verb_scale:nn}
% 缩小 CJK 字体，并保存相关信息。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_set_verb_scale:nn #1#2
  {
    \fp_set:Nn \l_@@_scale_factor_fp { #1 / #2 }
    \@@_warning:nee { scale-factor }
      { \fp_eval:n { trunc ( \l_@@_scale_factor_fp , 4 ) } }
      { \fp_eval:n {  ceil ( #2 / #1 , 4 ) } }
    \xeCJK_add_font_features:Nne \c_true_bool
      { } { Scale = { \fp_use:N \l_@@_scale_factor_fp } }
    \prop_gput:Noo \g_@@_scale_family_prop
      \l_@@_current_coor_tl \l_xeCJK_family_tl
  }
\@@_msg_new:nn { scale-factor }
  {
    `\token_to_str:N \xeCJKVerbAddon'~may~not~work~properly.\\\\
    You~may~set~`Scale=#1'~to~CJKfamily~
    `\@@_msg_family_map:n { \l_xeCJK_family_tl }',\\
    or~set~`Scale=#2'~to~family~
    `\str_if_eq:eeTF \f@family \ttdefault
      { \token_to_str:N \ttdefault } { \f@family }'.
  }
\fp_new:N \l_@@_scale_factor_fp
\prop_new:N \g_@@_scale_family_prop
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_visible_space:}
% \begin{macro}[int]{\@setupverbvisiblespace}
% \changes{v3.2.5}{2013/07/13}{可视空格考虑传统 \TeX 字体的情况。}
% \changes{v3.8.0}{2020/02/09}{更新可视空格补丁。}
% 如果文档不使用 \texttt{EU1} 作为默认字体编码，那么默认的打字机字体族很可能是
% 传统的 \TeX 字体，这时可视空格按照 \texttt{OT1} 编码传统一般就是字体中的 |\char32|。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_setup_visible_space:
  {
    \xeCJK_make_boundary:
    \xeCJK_glyph_if_exist:NTF { ^^^^2423 }
      { \tl_set:Nn \l_@@_visible_space_tl { ^^^^2423 } }
      {
        \int_compare:nNnTF { \tex_XeTeXfonttype:D \tex_font:D } = \c_zero_int
          {
            \tl_set:Ne \l_@@_visible_space_tl
              {
                \str_if_eq:eeTF { \f@family } { \ttdefault }
                    { \c_catcode_other_space_tl }
                    { \exp_not:N \textvisiblespace }
              }
          }
          { \@@_visible_space_fallback: }
      }
    \cs_set_eq:NN \@xobeysp \l_@@_visible_space_tl
  }
\tl_new:N \l_@@_visible_space_tl
\cs_set_eq:NN \@setupverbvisiblespace \xeCJK_setup_visible_space:
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_visible_space_fallback:}
% 我们使用 |lmtt| 字体中的可视空格符号（|U+2423|）作为当前字体中相应符号
% 的后备，但是 |lmtt| 的字体大小未必与当前字体匹配。因此，这里需要做一些调整，以
% 保证使用后备可视空格符号时，也能保证对齐。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_visible_space_fallback:
  {
    \exp_args:Nc \@@_visible_space_fallback_auxi:N
      { xeCJK/space/\curr@fontshape/\f@size }
  }
\cs_new_protected:Npn \@@_visible_space_fallback_auxi:N #1
  {
    \cs_if_exist:NF #1
      { \@@_visible_space_fallback_auxii:N #1 }
    \tl_set:Nn \l_@@_visible_space_tl {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_visible_space_fallback_auxii:N}
% 当前字体空格的宽度与后备字体 |lmtt| 不一样时，就对 \tn{textvisiblespace} 的字体尺寸
% 按相应的比例放缩。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_visible_space_fallback_auxii:N #1
  {
    \group_begin:
      \exp_args:No \@@_set_visible_space_size:n
        { \dim_use:N \tex_fontdimen:D 2 ~ \tex_font:D }
      \cs_new_protected:Npe #1
        { \group_begin: \tex_the:D \tex_font:D ^^^^2423 \group_end: }
    \group_end:
  }
\cs_new_protected:Npn \@@_set_visible_space_size:n #1
  {
    \fontencoding { \UnicodeEncodingName }
    \tl_set:Nn \f@family { lmtt }
    \selectfont
    \dim_compare:nNnF {#1} = { \tex_fontdimen:D 2 ~ \tex_font:D }
      {
        \fontsize
          {
            \dim_eval:n
              {
                \f@size pt *
                \dim_ratio:nn {#1} { \tex_fontdimen:D 2 ~ \tex_font:D }
              }
          }
          { \f@baselineskip }
        \selectfont
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{\pkg{xeCJK} 其他选项}
%
% \begin{macro}{LocalConfig}
% \changes{v3.1.0}{2012/11/20}{增加 \texttt{LocalConfig} 选项用于载入本地配置文件。}
% 声明载入本地配置文件的选项。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    LocalConfig .choice: ,
    LocalConfig / false   .code:n =
      { \bool_gset_false:N \g_@@_config_bool } ,
    LocalConfig / true    .code:n =
      {
        \bool_gset_true:N \g_@@_config_bool
        \tl_gset:Nn \g_@@_config_name_tl { xeCJK }
      } ,
    LocalConfig / unknown .code:n =
      {
        \bool_gset_true:N \g_@@_config_bool
        \tl_gset:Ne \g_@@_config_name_tl { xeCJK - \l_keys_value_tl }
      } ,
    LocalConfig        .default:n = { true }
  }
\tl_new:N \g_@@_config_name_tl
\bool_new:N \g_@@_config_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{CJKnumber,indentfirst}
% \changes{v3.2.11}{2014/03/14}{放弃 \texttt{indentfirst} 和 \texttt{CJKnumber} 选项。}
% \texttt{CJKnumber} 和 \texttt{indentfirst} 是过时选项。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    CJKnumber         .code:n =
      { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { CJKnumb } } ,
    indentfirst       .code:n =
      { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { indentfirst } } ,
    normalindentfirst .code:n =
      { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { } }
  }
\@@_msg_new:nn { option-deprecated }
  {
    The~`#1'~option~is~deprecated.\\
    \tl_if_empty:nF {#2}
      { You~may~load~the~package~`#2'~after~xeCJK~to~use~its~function.\\ }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{quiet,silent}
% 将调用 \pkg{xeCJK} 时使用的未知的选项传递给 \pkg{fontspec} 宏包。
% 对 \pkg{fontspec} 的 |quiet| 和 |silent| 选项进行修改，使其适用于 \pkg{xeCJK}。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    quiet .code:n =
      {
        \msg_redirect_module:nnn { xeCJK } { warning } { info }
        \msg_redirect_module:nnn { xeCJK } { info }    { none }
        \xeCJK_if_package_loaded:nF { fontspec }
          { \PassOptionsToPackage { quiet } { fontspec } }
      } ,
    silent .code:n =
      {
        \msg_redirect_module:nnn { xeCJK } { warning } { none }
        \msg_redirect_module:nnn { xeCJK } { info }    { none }
        \xeCJK_if_package_loaded:nF { fontspec }
          { \PassOptionsToPackage { silent } { fontspec } }
      } ,
    unknown .code:n =
      {
        \xeCJK_if_package_loaded:nTF { fontspec }
          { \@@_error:ne { key-unknown } { \l_keys_key_str } }
          { \PassOptionsToPackage { \l_keys_key_str } { fontspec } }
      }
  }
\@@_msg_new:nn { key-unknown }
  {
    Sorry,~but~xeCJK/options~does~not~have~a~key~called~`#1'.\\\\
    The~key~`#1'~is~being~ignored.
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{\pkg{xeCJK} 初始化设置}
%
% \begin{macro}[int]{\CJKsymbol, \CJKpunctsymbol}
%    \begin{macrocode}
\cs_new_eq:NN \CJKsymbol      \use:n
\cs_new_eq:NN \CJKpunctsymbol \use:n
%    \end{macrocode}
% \end{macro}
%
% \pkg{xeCJK} 宏包的初始化设置。
%
%    \begin{macrocode}
\keys_set:nn { xeCJK / options }
  {
    CJKglue         = { \skip_horizontal:n { \c_zero_dim plus 0.08 \tex_baselineskip:D } } ,
    CJKecglue       = { ~ } ,
    xCJKecglue      = false ,
    CheckSingle     = false ,
    PlainEquation   = false ,
    CheckFullRight  = false ,
    CJKspace        = false ,
    CJKmath         = false ,
    xeCJKactive     = true  ,
    LocalConfig     = true  ,
    LoadFandol      = true  ,
    RubberPunctSkip = true  ,
    Verb            = env   ,
    EmboldenFactor  = 4     ,
    SlantFactor     = 0.167 ,
    PunctStyle      = quanjiao ,
    NewLineCS       = { \par \[ } ,
    EnvCS           = { \begin \end } ,
    WidowPenalty    = { 10 000 } ,
    NoBreakCS       = { \footnote \footnotemark \nobreak } ,
    KaiMingPunct    = { ^^^^3002 ^^^^ff0e ^^^^ff1f ^^^^ff01 } ,
    LongPunct       = { ^^^^2014 ^^^^2e3a ^^^^2025 ^^^^2026 } ,
    NoBreakLongPunct = { ^^^^2025 ^^^^2026 } ,
    MiddlePunct     = { ^^^^2013 ^^^^2014 ^^^^2e3a ^^^^2027 ^^^^00b7 ^^^^30fb ^^^^ff65 } ,
    AllowBreakBetweenPuncts = false
  }
\defaultCJKfontfeatures { Script = CJK }
%    \end{macrocode}
%
% 半字线连接号\footnote{见\href{http://www.moe.gov.cn/ewebeditor/uploadfile/2015/01/13/20150113092346124.pdf}
% {《夹用英文的中文文本的标点符号用法（草案）》5.13 节。}}应为半角宽度。
%    \begin{macrocode}
\xeCJKsetwidth { ^^^^2013 } { 0.5 em }
%    \end{macrocode}
%
% \changes{v3.7.0}{2018/03/16}{不再默认引入 \pkg{xunicode} 宏包。}
%
% 执行宏包选项，并载入 \pkg{fontspec} 宏包。
%    \begin{macrocode}
\cs_if_exist:NTF \ProcessKeyOptions
  { \ProcessKeyOptions [ xeCJK / options ] }
  {
    \RequirePackage { l3keys2e }
    \ProcessKeysOptions { xeCJK / options }
  }
\RequirePackage { fontspec } [ 2020/02/03 ]
%    \end{macrocode}
%
% \begin{variable}{\c_@@_encoding_tl}
% 保存 \pkg{fontspec} 声明字体时使用的字体编码。
%    \begin{macrocode}
\tl_const:Ne \c_@@_encoding_tl { \g_fontspec_encoding_tl }
%    \end{macrocode}
% \end{variable}
%
% \changes{v3.1.0}{2012/11/21}{改用 \pkg{indentfirst} 宏包处理缩进的问题。}
%
% 对不能通过 \tn{xeCJKsetup} 设置的选项给出警告。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    LocalConfig .code:n =
      { \@@_warning:ne { option-invalid } { \l_keys_key_str } }
  }
\@@_msg_new:nn { option-invalid }
  {
    The~`#1'~option~can~only~be~set~in~the~optional~argument~to~the\\
    \token_to_str:N \usepackage \ command~when~xeCJK~is~being~loaded.\\\\
    Please~do~not~set~it~via~the~\token_to_str:N \xeCJKsetup \ command.
  }
%    \end{macrocode}
%
% \begin{variable}{\CJKrmdefault,\CJKsfdefault,\CJKttdefault,\CJKfamilydefault}
%    \begin{macrocode}
\tl_if_exist:NF \CJKrmdefault
  { \tl_new:N \CJKrmdefault \tl_gset:Nn \CJKrmdefault { rm } }
\tl_if_exist:NF \CJKsfdefault
  { \tl_new:N \CJKsfdefault \tl_gset:Nn \CJKsfdefault { sf } }
\tl_if_exist:NF \CJKttdefault
  { \tl_new:N \CJKttdefault \tl_gset:Nn \CJKttdefault { tt } }
\tl_new:N \l_@@_family_default_init_tl
\cs_new_eq:NN \@@_family_default_wrap:n \use:n
\tl_set:Ne \l_@@_family_default_init_tl
  {
    \exp_not:N \@@_family_default_wrap:n
      {
        \tl_if_exist:NTF \CJKfamilydefault
          { \exp_not:o \CJKfamilydefault }
          { \exp_not:N \CJKrmdefault }
      }
  }
\tl_if_exist:NF \CJKfamilydefault
  { \tl_new:N \CJKfamilydefault }
\tl_gset_eq:NN \CJKfamilydefault \l_@@_family_default_init_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\xeCJKsetup}
% 在导言区或文档中设置 \pkg{xeCJK} 的接口。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKsetup { +m }
  {
    \keys_set:nn { xeCJK / options } {#1}
    \tex_ignorespaces:D
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJKsetemboldenfactor, \xeCJKsetslantfactor}
%    \begin{macrocode}
\NewDocumentCommand \xeCJKsetemboldenfactor { m }
  { \xeCJKsetup { EmboldenFactor = {#1} } }
\NewDocumentCommand \xeCJKsetslantfactor { m }
  { \xeCJKsetup { SlantFactor = {#1} } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\punctstyle, \xeCJKplainchr}
%    \begin{macrocode}
\NewDocumentCommand \punctstyle { m } { \xeCJKsetup { PunctStyle = {#1} } }
\NewDocumentCommand \xeCJKplainchr { } { \xeCJKsetup { PunctStyle = plain } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\CJKsetecglue}
%    \begin{macrocode}
\NewDocumentCommand \CJKsetecglue { m } { \xeCJKsetup { CJKecglue = {#1} } }
\cs_new_eq:NN \xeCJKsetecglue \CJKsetecglue
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\CJKspace,\CJKnospace}
%    \begin{macrocode}
\NewDocumentCommand \CJKspace   { } { \xeCJKsetup { CJKspace = true } }
\NewDocumentCommand \CJKnospace { } { \xeCJKsetup { CJKspace = false } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJKallowbreakbetweenpuncts, \xeCJKnobreakbetweenpuncts}
%    \begin{macrocode}
\NewDocumentCommand \xeCJKallowbreakbetweenpuncts { }
  { \xeCJKsetup { AllowBreakBetweenPuncts = true } }
\NewDocumentCommand \xeCJKnobreakbetweenpuncts { }
  { \xeCJKsetup { AllowBreakBetweenPuncts = false } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJKenablefallback, \xeCJKdisablefallback}
%    \begin{macrocode}
\NewDocumentCommand \xeCJKenablefallback { }
  { \xeCJKsetup { AutoFallBack = true } }
\NewDocumentCommand \xeCJKdisablefallback { }
  { \xeCJKsetup { AutoFallBack = false } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJKsetcharclass}
% \changes{v3.10.0}{2026/04/24}{\textbf{Deprecated:}
%   \tn{xeCJKsetcharclass} 已弃用，
%   调用时报错并提示改用 \tn{xeCJKDeclareCharClass}（\#709）。}
%    \begin{macrocode}
\@@_msg_new:nn { xeCJKsetcharclass-deprecated }
  {
    \token_to_str:N \xeCJKsetcharclass\ is~deprecated~and~
    does~not~update~CJKmath~char~lists.\\\\
    Use~\token_to_str:N \xeCJKDeclareCharClass\ instead.
  }
\NewDocumentCommand \xeCJKsetcharclass { m m m }
  {
    \@@_error:n { xeCJKsetcharclass-deprecated }
    \xeCJK_set_char_class:nnn {#1} {#2} {#3}
    \xeCJKResetPunctClass
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{兼容性修补}
%
% \changes{v3.2.16}{2014/11/20}{修复 \tn{hbar}。}
% \changes{v3.8.0}{2020/02/09}{删除 \tn{hbar} 补丁。}
%
% \begin{macro}[int]{\xeCJK@update@fam,\Url@MathSetup}
% \changes{v3.2.7}{2013/11/09}{使通过 \tn{UrlFont} 等命令设置的 CJK 字体生效。}
% 使通过 \tn{urlstyle} 或者 \tn{UrlFont} 设置的路径中使用的 CJK 字体生效。
% 使用 \tn{everymath} 钩子中数学模式中重定义 CJK 数学字体，以确保我们的设置在
% \tn{check@mathfonts} 之后生效，不会被它覆盖。
% 更合理的方式是定义一个新的 \tn{mathversion} 来切换。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK@update@fam
  {
    \addto@hook \everymath
      {
        \@@_update_main_fam:
        \@@_update_block_fam:
      }
  }
\cs_new_protected:Npn \@@_update_main_fam:
  {
    \group_begin:
      \xeCJK_select_font:
      \exp_last_unbraced:NNNo \group_end:
    \tex_textfont:D \c_xeCJK_math_fam_int \tex_the:D \tex_font:D
  }
\cs_new_protected:Npn \@@_update_block_fam:
  {
    \prop_if_empty:NF \g_@@_block_fam_prop
      {
        \prop_map_function:NN
          \g_@@_block_fam_prop
          \@@_update_block_fam:nn
      }
  }
\cs_new_protected:Npn \@@_update_block_fam:nn #1#2
  {
    \int_set:Nn \l_@@_fam_int {#2}
    \group_begin:
      \xeCJK_select_font:n {#1}
      \exp_last_unbraced:NNNo \group_end:
    \tex_textfont:D \l_@@_fam_int \tex_the:D \tex_font:D
  }
\@@_after_end_preamble:n
  {
    \bool_lazy_and:nnT
      { \g_@@_math_bool }
      { \cs_if_exist_p:N \Url@MathSetup }
      { \tl_put_right:Nn \Url@MathSetup { \xeCJK@update@fam } }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.6}{2013/08/02}{为 \tn{mathrm} 减少一个可能的数学字体族。}
% \changes{v3.8.0}{2020/02/09}{删除 \tn{mathrm} 补丁。}
%
% \changes{v3.4.5}{2017/01/02}{更新 \LaTeXiii{} 的过时用法。}
%
% \begin{macro}[int]{\(,\),\math,\endmath,\ensuremath}
% \begin{macro}{
%   \@@_math_robust:N,
%   \@@_boundary_math_begin:,
%   \@@_boundary_math_begin:n,
%   \@@_boundary_register_text_command:N,
%   \@@_boundary_register_text_command:NN}
% \changes{v3.2.5}{2013/07/25}
% {解决汉字后紧跟 \tn{(}\texttt{...}\tn{)} 形式的行内数学公式时，不能加入间距的问题。}
% \changes{v3.2.6}{2013/08/03}{考虑 \pkg{ulem} 对 \tn{MakeRobust} 的不当定义。}
% \changes{v3.2.6}{2013/08/03}{考虑 \tn{math} 和 \tn{ensuremath}。}
% \changes{v3.3.1}{2015/04/14}{兼容 \LaTeXe{} 2015。}
% \tn{(} 在 \LaTeXe 中的定义是
% \begin{verbatim}
%   \def\({\relax\ifmmode\@badmath\else$\fi}
% \end{verbatim}
% 这个定义最开始的 \tn{relax} 是为了防止 \tn{(} 出现在表格单元格的开始位置时，模式
% 判断不正确（因为 \TeX 会先看单元格中第一个不可展的非空格记号是否是 \tn{omit} 或
% \tn{noalign}）。但是它会造成一个边界，使 \pkg{xeCJK} 不能看到 \tn{relax} 后面出现的
% |$|，从而不能加入间距\footnote{\url{http://tex.stackexchange.com/q/124773}}。使用
% \hologo{eTeX} 的 \tn{protected} 来定义它，可以不需要 \tn{relax}，或者将 \tn{relax}
% 改成 \cs{scan_align_safe_stop:}，都可以避免这些情况。同时 \pkg{fixltx2e} 中还使用了
% |\MakeRobust\(|，我们需要小心处理。另外 \pkg{ulem} 也定义了一个 \tn{MakeRobust}，
% 如果它被放在 \pkg{fixltx2e} 之前载入，那么 \pkg{fixltx2e} 的定义就会失效（因为
% \pkg{fixltx2e} 使用 \tn{providecommand}|*| 来定义 \tn{MakeRobust}）。但是 \pkg{ulem}
% 的定义并不完全正确，没有考虑 \TeX 不会略去控制符号后面的空格的情况。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_math_robust:N #1
  {
    \group_begin: \exp_args:NcNc \group_end:
      { @@_math_robust_aux:NN } #1 { \cs_to_str:N #1 ~ }
  }
\cs_new_protected:Npn \@@_math_robust_aux:NN #1#2
  {
    \exp_args:Ne \str_case:nnTF { \cs_replacement_spec:N #1 }
      {
        { \x@protect #1 \protect #2 } { }
        { \protect #2 } { }
      }
      { \@@_math_robust:NN #1#2 }
      { \@@_math_robust:NN #1#1 }
  }
\cs_new_protected:Npn \@@_math_robust:NN #1#2
  {
    \str_if_eq:eeTF { \cs_argument_spec:N #2 } { }
      {
        \exp_args:No \tl_if_head_eq_meaning:nNTF {#2} \scan_stop:
          {
            \cs_gset_protected:Npe #1
              { \tl_tail:N #2 }
          }
          {
            \cs_if_eq:NNTF #1 \ensuremath
              {
                \cs_gset_protected:Npe #1
                  { \exp_not:o {#2} }
              }
              {
                \@@_warning:nee { robust-failure }
                  { \token_to_str:N #1 } { \token_to_meaning:N #2 }
              }
          }
      }
      {
        \@@_warning:nee { robust-failure }
          { \token_to_str:N #1 } { \token_to_meaning:N #2 }
      }
  }
\@@_msg_new:nnn { robust-failure }
  { xeCJK~can~not~make~`#1'~robust. }
  {
    The~current~meaning~of~`#1'~is:\\
    \iow_indent:n {#2}
  }
\cs_if_eq:NNTF \( \math
  {
    \@@_math_robust:N \(
    \cs_set_eq:NN \math \(
  }
  {
    \@@_math_robust:N \(
    \@@_math_robust:N \math
  }
\cs_if_eq:NNTF \) \endmath
  {
    \@@_math_robust:N \)
    \cs_set_eq:NN \endmath \)
  }
  {
    \@@_math_robust:N \)
    \@@_math_robust:N \endmath
  }
\@@_math_robust:N \ensuremath
%    \end{macrocode}
% stream 的左侧间距必须排在 math-on 节点之前。三个显式公式入口只处理
% 当前列表的边界，再调用已经完成 robust 处理的原命令；它们不直接向所有
% 外层 capture 报告，否则原语 \tn{setbox} 中不可见的测量公式也会被当成
% 外层输出。已经注册的命令分别检查可见正文的开头和结尾：开头是 |$|、
% \tn{(} 或 \tn{ensuremath} 时，在 math-on 前报告首类别；结尾是对应公式
% 时，把 |math| 记为当前 capture 的确定末类别。\tn{ensuremath} 已在数学
% 模式中时不重复处理当前列表。
%    \begin{macrocode}
\clist_map_inline:nn { ( , math , ensuremath }
  { \@@_boundary_reserve:n {#1} }
\cs_if_eq:NNTF \( \math
  {
    \cs_new_eq:NN \@@_orig_inline_math_begin: \(
    \cs_gset_protected:Npn \(
      { \@@_boundary_math_begin: \@@_orig_inline_math_begin: }
    \cs_gset_eq:NN \math \(
  }
  {
    \cs_new_eq:NN \@@_orig_inline_math_begin: \(
    \cs_gset_protected:Npn \(
      { \@@_boundary_math_begin: \@@_orig_inline_math_begin: }
    \cs_new_eq:NN \@@_orig_math_begin: \math
    \cs_gset_protected:Npn \math
      { \@@_boundary_math_begin: \@@_orig_math_begin: }
  }
\cs_new_eq:NN \@@_orig_ensuremath: \ensuremath
\cs_gset_protected:Npn \ensuremath
  {
    \mode_if_math:TF
      { \@@_orig_ensuremath: }
      { \@@_boundary_math_begin: \@@_orig_ensuremath: }
  }
%    \end{macrocode}
% LaTeX 标准文本字体命令的 robust 内部实现均为单参数。这里保存该内部实现，
% 用 stream 包住公式位于正文任一边缘的调用；开头公式在 math-on 前报告，
% 结尾公式在正文参数实际排完时确认。普通正文仍由 interchar 转换报告实际
% 首尾类别。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_text_command:N #1
  {
    \group_begin: \exp_args:NcNc \group_end:
      { @@_boundary_register_text_command:NN }
      #1 { \cs_to_str:N #1 ~ }
  }
\cs_new_protected:Npn \@@_boundary_register_text_command:NN #1#2
  {
    \@@_boundary_reserve:N #1
    \@@_boundary_reserve:N #2
    \exp_args:Nc \cs_new_eq:NN
      { @@_boundary_orig_\cs_to_str:N #1 :n } #2
    \cs_gset_protected:Npn #2 ##1
      {
        \@@_boundary_if_math_edge:nTF {##1}
          {
            \@@_boundary_inline_stream_begin:n { auto }
            \@@_boundary_math_begin:n {##1}
            \use:c { @@_boundary_orig_\cs_to_str:N #1 :n }
              { ##1 \@@_boundary_math_end:n {##1} }
            \@@_boundary_inline_stream_end:n { auto }
          }
          { \use:c { @@_boundary_orig_\cs_to_str:N #1 :n } {##1} }
      }
  }
\clist_map_inline:nn
  {
    \textrm , \textsf , \texttt , \textnormal , \textbf , \textmd ,
    \textit , \textsl , \textsc , \textup , \emph
  }
  { \@@_boundary_register_text_command:N #1 }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \changes{v3.2.5}{2013/07/25}{解决 \pkg{fixltx2e} 和 \pkg{amsthm} 的冲突。}
% \changes{v3.3.1}{2015/04/15}{删去 \pkg{fixltx2e} 和 \pkg{amsthm} 的冲突补丁。}
%
% \changes{v3.1.2}{2013/01/01}
% {修正非 \tn{UTFencname} 编码下面 \pkg{xunicode} 重定义的 \tn{nobreakspace} 会失效的问题。}
% \changes{v3.2.5}{2013/07/18}{恢复 \tn{nobreakspace} 的原始定义。}
% \changes{v3.7.0}{2018/03/18}{对 \tn{nobreakspace} 的恢复放到 \pkg{xunicode-addon} 中处理。}
%
% \changes{v3.1.1}{2012/12/13}{对于与 \pkg{xltxtra} 的冲突给出错误警告。}
% \changes{v3.8.0}{2020/02/09}{删除 \pkg{realscripts} 补丁。}
%
% \changes{v3.8.6}{2020/10/17}{兼容 \LaTeX\ 2020/10/01 的 \pkg{NFSS} 钩子机制。}
%
% \begin{macro}[int]{\fontfamily}
% \begin{macro}[int]{\xeCJK@family}
% \changes{v3.1.1}{2012/12/06}{修改主要 \texttt{CJK} 字体族的自动更新方式。}
% \changes{v3.1.2}{2013/01/01}{不将参数完全展开。}
% \changes{v3.4.6}{2017/02/23}
%   {将族名参数完全展开，以解决与 \pkg{fontspec} 2017/01/24 v2.5d 的兼容问题。}
% \changes{v3.10.0}{2026/04/24}{\textbf{Breaking:}
%   移除对 \LaTeXe\ 2020/10/01 之前版本的字体钩子兼容代码（\#746）。}
% 使用 \LaTeXe\ 2020/10/01 提供的 \pkg{NFSS} 钩子，
% 使主要 |CJK| 字体族能随西文主要字体更新。
%    \begin{macrocode}
\cs_set_eq:NN \xeCJK@family \xeCJK_switch_family:e
\ctex_gadd_ltxhook:nn { rmfamily }   { \xeCJK@family { \CJKrmdefault } }
\ctex_gadd_ltxhook:nn { sffamily }   { \xeCJK@family { \CJKsfdefault } }
\ctex_gadd_ltxhook:nn { ttfamily }   { \xeCJK@family { \CJKttdefault } }
\ctex_gadd_ltxhook:nn { normalfont } { \xeCJK@family { \CJKfamilydefault } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
%<@@=>
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK@fix@penalty}
% \changes{v3.1.0}{2012/11/13}{采用通过不修改原语 \tn{/} 的方式对修复倾斜校正。}
% \LaTeXe 内核中的 \tn{fix@penalty} 被用于诸如 \tn{textit} 之类的文档
% 字体转换命令的定义之中。这里对它进行补丁的目的是修复其中的倾斜校正，并使得这些
% 文档命令与紧随其后的汉字之间可以正确的插入 \tn{CJKecglue} 或者忽略其中的空格。
% 例如 \verb*|这是 \emph{强调} 文本|，第二个空格可以被忽略掉。如果使用 |xCJKecglue|
% 选项，第一个空格也可以被省略。事实上，在 \tn{sw@slant} 的定义中，\tn{@@italiccorr}
% 前面的 \tn{lastskip} 和 \tn{lastpenalty} 有四种情况，这里只对它们都为零的情况进行
% 处理。
%    \begin{macrocode}
\cs_new_eq:NN \xeCJK@fix@penalty \fix@penalty
\tl_replace_once:Nnn \xeCJK@fix@penalty { \@@italiccorr } { \xeCJK@italiccorr }
\tl_replace_once:Nnn \sw@slant          { \fix@penalty }  { \xeCJK@fix@penalty }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK@italiccorr}
% 修复倾斜校正，并处理汉字后面的空格。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK@italiccorr
  {
    \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int
      { \xeCJK_italic_correction: }
      { \@@italiccorr }
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%<@@=xeCJK>
%    \end{macrocode}
%
% \begin{macro}[int]{\xeCJK_italic_correction:}
% 修复倾斜校正，并处理汉字后面的空格。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_italic_correction:
  { \@@_if_last_kern:T { \@@_italic_correction: } }
\cs_new_protected:Npn \@@_italic_correction:
  {
    \dim_case:nnF { \tex_lastkern:D }
      {
        { \@@_node:n { default } }
        {
          \xeCJK_remove_node: \tex_italiccorrection:D
          \xeCJK_make_node:n { default }
        }
        { \@@_node:n { CJK } }
        {
          \xeCJK_remove_node: \tex_italiccorrection:D
          \xeCJK_make_node:n { CJK }
          \@@_italic_correction_aux:
        }
        { \@@_node:n { CJK-space } }
        {
          \xeCJK_remove_node: \tex_italiccorrection:D
          \xeCJK_make_node:n { CJK-space }
          \@@_italic_correction_aux:
        }
      }
      { \tex_italiccorrection:D }
  }
%    \end{macrocode}
% \cs{xeCJK_ignore_spaces:w} 里面用到 |peek| 函数来判断后面是不是空格，而此时它
% 后面还有 $4$ 个 \tn{fi} 或者 |\else...\fi| 没有被展开，将影响 |peek| 函数的
% 判断。因此我们需要用 $2^4-1=15$ 个 \cs{exp_after:wN} 来展开它们。
% 显然，这里用 \cs{exp_last_unbraced:Nf} 会比较方便，但是它会吃掉
% \verb*|\textit{...} | 等后面原来存在的空格作为完全展开的结束。要正确使用它还
% 需要另外的处理（使用 \cs{exp_stop_f:}）。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_italic_correction_aux:
  {
                  \exp_after:wN \exp_after:wN \exp_after:wN
    \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN
    \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN
    \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN
    \xeCJK_ignore_spaces:w
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_xetex_allocator_int}
% \changes{v3.3.1}{2015/04/14}{兼容 \LaTeXe{} 2015。}
% \changes{v3.3.2}{2015/05/15}{\tn{xe@alloc@intercharclass} 总是有定义的。}
% \LaTeXe{} 2015/01/01 接管了 \tn{newXeTeXintercharclass}。
%    \begin{macrocode}
\cs_new_eq:NN \g_@@_xetex_allocator_int \xe@alloc@intercharclass
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_set_others_toks:n}
% 简单处理与同样使用 \tn{XeTeXinterchartoks} 机制的宏包的兼容问题。
%    \begin{macrocode}
\@@_after_end_preamble:n
  {
    \int_compare:nNnF
      { \c_@@_class_begin_int + \seq_count:N \g_@@_new_class_seq } =
      { \g_@@_xetex_allocator_int }
      {
        \int_step_inline:nnn
          { \c_@@_class_begin_int + 1 }
          { \g_@@_xetex_allocator_int }
          {
            \seq_if_in:NnF \g_@@_new_class_seq {#1}
              { \@@_set_others_toks:n {#1} }
          }
      }
  }
\cs_new_protected:Npn \@@_set_others_toks:n #1
  {
    \int_set:cn { \@@_class_csname:n { Others } } {#1}
    \seq_map_inline:Nn \g_@@_CJK_class_seq
      {
        \xeCJK_copy_inter_class_toks:nnnn {##1} { Others } {##1} { NormalSpace }
        \xeCJK_copy_inter_class_toks:nnnn { Others } {##1} { NormalSpace } {##1}
        \xeCJK_app_inter_class_toks:nne {##1} { Others }
          { \xeCJK_get_inter_class_toks:nn { Default } { Others } }
        \xeCJK_pre_inter_class_toks:nne { Others } {##1}
          { \xeCJK_get_inter_class_toks:nn { Others } { Default } }
        \tl_if_blank:eT
          { \xeCJK_get_inter_class_toks:nn { Others } { Boundary } }
          {
            \xeCJK_copy_inter_class_toks:nnnn
              { Others } { Boundary } { Default } { Boundary }
          }
        \tl_if_blank:eT
          { \xeCJK_get_inter_class_toks:nn { Boundary } { Others } }
          {
            \xeCJK_copy_inter_class_toks:nnnn
              { Boundary } { Others } { Boundary } { Default }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_inactive_group_begin:,\@@_inactive_group_end:}
% 用于保护下面歧义宽度标点的分组。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_inactive_group_begin:
  { \group_begin: \makexeCJKinactive }
\cs_new_eq:NN \@@_inactive_group_end: \group_end:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_text_command:}
% \begin{variable}{\c_@@_ambiguous_char_prop}
% 单独处理宽度有分歧的几个标点：包括省略号、破折号、间隔号、引号等中西文混用的
% 符号，保证其命令形式输出的是西文字体。
% 如果 \pkg{xunicode} 宏包被载入，则通过 \pkg{xunicode-addon} 处理。
%    \begin{macrocode}
\prop_const_from_keyval:Nn \c_@@_ambiguous_char_prop
  {
    "00B7 = \textperiodcentered \textcentereddot \textcdot ,
    "2013 = \textendash ,
    "2014 = \textemdash ,
    "2018 = \textquoteleft \textgrq ,
    "2019 = \textquoteright ,
    "201C = \textquotedblleft \textgrqq ,
    "201D = \textquotedblright ,
    "2025 = \texthdotfor ,
    "2026 = \textellipsis ,
    "2027 = \texthyphenationpoint ,
    "2E3A = \texttwoemdash
  }
\@@_at_end_preamble:n { \@@_patch_text_command: }
\cs_new_protected:Npn \@@_patch_text_command:
  {
    \xeCJK_if_package_loaded:nTF { xunicode }
      { \@@_patch_xunicode_ambiguous_char: }
      {
        \exp_args:Ne \@@_patch_tuenc_ambiguous_char:n
          { \UnicodeEncodingName }
        \@@_patch_tuenc_accent:
        \@@_patch_tuenc_composite:
      }
  }
\cs_new_protected:Npn \@@_patch_xunicode_ambiguous_char:
  {
    \RequirePackage { xunicode-addon }
    \prop_map_inline:Nn \c_@@_ambiguous_char_prop
      {
        \tl_map_inline:nn { ##2 }
          {
            \xunadd_set_begin_hook:nn { ####1 }
              { \@@_inactive_group_begin: }
            \xunadd_set_end_hook:nn { ####1 }
              { \@@_inactive_group_end: }
          }
      }
    \xunadd_append_begin_hook:n { \xeCJK_make_boundary: }
  }
\cs_new_protected:Npn \@@_patch_tuenc_ambiguous_char:n #1
  {
    \prop_map_inline:Nn \c_@@_ambiguous_char_prop
      {
        \tl_map_inline:nn { ##2 }
          {
            \cs_if_exist:NF ####1
              { \DeclareTextSymbol ####1 {#1} { ##1 } }
            \@@_patch_ambiguous_char:nN {#1} ####1
          }
      }
  }
\cs_new_protected:Npn \@@_patch_ambiguous_char:nN #1#2
  {
    \exp_args:Ne \@@_patch_ambiguous_char:nn
      { #1 \token_to_str:N #2 }
      { #1 - #2 }
  }
\cs_new_protected:Npe \@@_patch_ambiguous_char:nNn #1#2#3
  {
    \exp_not:N \exp_args:Ne
    \exp_not:N \@@_patch_ambiguous_char:nn
      {
        \c_backslash_str #1
        \exp_not:N \token_to_str:N #2 -
        \exp_not:N \token_to_str:N #3
      }
      { #1 - #2#3 }
  }
\cs_new_protected:Npn \@@_patch_ambiguous_char:nn #1#2
  {
    \cs_if_free:cF {#1}
      { \exp_args:Nc \@@_patch_ambiguous_char:Nn {#1} {#2} }
  }
\cs_new_protected:Npn \@@_patch_ambiguous_char:Nn #1#2
  {
    \token_if_chardef:NTF #1
      {
        \prop_gput:Nne \g_@@_ambiguous_slot_prop {#2}
          { \int_eval:n {#1} }
        \cs_set_protected:Npe #1
          { \@@_ambiguous_char:n { \tex_Uchar:D #1 } }
      }
      {
        \prop_gput:Nne \g_@@_ambiguous_slot_prop {#2}
          { \int_eval:n { \exp_after:wN ` #1 } }
        \cs_set_protected:Npe #1
          { \@@_ambiguous_char:n { \exp_not:o {#1} } }
      }
  }
\cs_new_protected:Npn \@@_ambiguous_char:n #1
  {
    \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int
      { \@@_inactive_group_begin: #1 \@@_inactive_group_end: }
      {#1}
  }
\prop_new:N \g_@@_ambiguous_slot_prop
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\@@_patch_tuenc_composite:}
% \changes{v3.7.2}{2018/05/02}{修复补丁错误。}
% \tn{DeclareUnicodeComposite} 具有检查字符是否存在的功能，
% 当符号命令紧跟在 CJK 字符类后面时，需要使字体回到西文状态
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_text_composite_patch:
  {
    \str_if_eq:eeT { \f@encoding } { \UnicodeEncodingName }
      { \xeCJK_make_boundary: }
  }
%    \end{macrocode}
% 注意 \cs{xeCJK_text_composite_patch:} 可能会结束分组，从而导致 |##1| 没有定义时是
% \tn{undefined} 而不是 \tn{relax}，所以不能将它与 \tn{relax} 作比较。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_tuenc_composite:
  {
    \cs_set_nopar:Npn \@text@composite@x
      {
        \xeCJK_text_composite_patch:
        \cs_if_exist_use:NF
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_tuenc_accent:}
% \tn{add@unicode@accent} 定义最后用于截断数字展开的 \tn{relax} 会造成边界，可能会影响组合标记。
%    \begin{macrocode}
\group_begin:
\char_set_catcode_other:n { "A0 }
\cs_new_protected:Npn \@@_patch_tuenc_accent:
  {
    \cs_set_protected_nopar:Npn \add@unicode@accent ##1 ##2
      {
        \tl_if_blank:nTF { ##2 } { ^^a0 } { ##2 }
        \tex_Uchar:D \tex_numexpr:D ##1 \scan_stop:
      }
  }
\group_end:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_middle_dot:}
% \begin{variable}{\c_@@_middle_dot_prop}
% \changes{v3.2.9}{2013/12/08}
% {完整处理 \file{encguide.pdf} 的编码符号表中，与旧编码的 \texttt{U+00B7} 冲突。}
% 常被用作中文间隔号的 \texttt{U+00B7} 与 T1 等旧字体编码下定义的符号命令冲突。
% 在 \file{encguide.pdf} 的编码符号表中，如下定义有冲突。
% \begin{verbatim}
%   \DeclareTextComposite{\r}{T1}{u}{183}
%   \DeclareTextSymbol{\cyrchvcrs}{T2A}{183}
%   \DeclareTextSymbol{\cyrchldsc}{T2B}{183}
%   \DeclareTextSymbol{\cyrabhha}{T2C}{183}
%   \DeclareTextSymbol\textvibyy{T3}{183}
%   \DeclareTextComposite{\B}{T4}{t}{183}
%   \DeclareTextComposite{\`}{T5}{\ecircumflex}{183}
%   \DeclareTextDoubleComposite{\`}{T5}{\^}{e}{183}
%   \DeclareTextSymbol{\textperiodcentered}{TS1}{183}
%   \DeclareTextSymbol{\cyrchldsc}{X2}{183}
%   \DeclareTextSymbol{\textperiodcentered}{LY1}{183}
% \end{verbatim}
% LGR 编码的符号表有 \texttt{183} 号字符，但在 \file{lgrenc.def} 中未找到相应的
% 符号命令。
%    \begin{macrocode}
\prop_const_from_keyval:Nn \c_@@_middle_dot_prop
  {
    T2A = \cyrchvcrs ,
    T2B = \cyrchldsc ,
    T2C = \cyrabhha ,
    X2  = \cyrchldsc ,
    TS1 = \textperiodcentered ,
    LY1 = \textperiodcentered ,
    T1  = \r u ,
    T4  = \B t ,
    T5  = \` \ecircumflex
  }
\@@_at_end_preamble:n { \@@_patch_middle_dot: }
\cs_new_protected:Npn \@@_patch_middle_dot:
  {
    \prop_map_inline:Nn \c_@@_middle_dot_prop
      { \@@_patch_middle_dot:nw { ##1 } ##2 \q_stop }
    \@@_patch_ambiguous_char:nNn { T5 } \` { \^ - e }
  }
\cs_new_protected:Npn \@@_patch_middle_dot:nw #1#2#3 \q_stop
  {
    \tl_if_empty:nTF {#3}
      { \@@_patch_ambiguous_char:nN {#1} #2 }
      { \@@_patch_ambiguous_char:nNn {#1} #2 {#3} }
  }
%    \end{macrocode}
% \pkg{pifont} 宏包的符号 |\ding{183}| 也有冲突。
% \changes{v3.10.0}{2026/04/24}{在 \tn{Pifont} 中先进入水平模式，
%   防止 \tn{makexeCJKinactive} 在垂直模式下通过分页泄漏到输出例程（\#688）。}
%    \begin{macrocode}
\@@_package_hook:nn { pifont }
  {
    \RenewDocumentCommand \Pifont { m }
      { \mode_leave_vertical: \makexeCJKinactive \usefont { U } {#1} { m } { n } }
  }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \changes{v3.10.0}{2026/04/27}{新增 \tn{xeCJKchar}，绕过 interchar 机制（\#407）。}
% \changes{v3.10.0}{2026/04/29}{撤回对 \tn{char} 的重定义，改为提供 \tn{xeCJKchar}
%   新命令，避免破坏依赖 |\let| 保存 \tn{char} 原语的宏包（\#800）。}
%
% \begin{macro}{\xeCJKchar, \xeCJK_default_char:w}
% \begin{macro}{\@@_default_char_aux:}
% \begin{variable}{\l_@@_default_char_int}
% \tn{char} 是 \TeX{} 原语，用户使用它直接通过字符编码从当前字体取字形，
% 不经过 NFSS 层。一些宏包（如 \pkg{mtpro2}）在数学模式中用 \tn{char} 取
% 非 CJK 字形时，会被 \pkg{xeCJK} 的 interchar 机制拦截并切换到中文字体，
% 导致字形变成中文标点（如间隔号，\#407）。
%
% 参照 \LuaTeX-ja 提供 |\ltjalchar|/|\ltjjachar| 新命令的思路，
% \pkg{xeCJK} 提供 \tn{xeCJKchar} 命令，在输出字符前临时关闭
% |XeTeXinterchartokenstate|，保证输出的字符来自当前字体而不被 interchar
% 拦截。与 \LuaTeX-ja 不同的是，XeTeX 的 interchar 机制工作在 token 层面，
% 没有节点级回调，因此无法自动区分 \tn{char} 和直接字符输入。
% \tn{xeCJKchar} 供用户和第三方宏包显式使用，\tn{char} 本身不做任何修改。
%
% 用法与 \tn{char} 完全相同：|\xeCJKchar"00B7| 或 |\xeCJKchar183|。
% 用户指南和手动补丁方法参见“已知问题和兼容性”一节。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_default_char:w
  {
    \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int
      {
        \c_group_begin_token
          \tex_afterassignment:D \@@_default_char_aux:
          \l_@@_default_char_int =
      }
      { \tex_char:D }
  }
\cs_new_protected:Npn \@@_default_char_aux:
  {
      \tex_XeTeXinterchartokenstate:D = \c_zero_int
      \tex_char:D \l_@@_default_char_int
    \c_group_end_token
  }
\int_new:N \l_@@_default_char_int
\cs_new_eq:NN \xeCJKchar \xeCJK_default_char:w
%    \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \changes{v3.10.0}{2026/04/29}{为 \pkg{mtpro2} 提供兼容补丁，
%   使大花括号内部的 \tn{char} 不被 interchar 拦截（\#407）。}
%
% \pkg{mtpro2} 的 \tn{overcbrace} 和 \tn{undercbrace} 通过
% |\char| 访问 |mt2exe| 等字体的特殊位置，其中部分位置
% （如 183\,=\,|U+00B7|）与 \pkg{xeCJK} 的 interchar 字符分类冲突。
% 由于这两个命令只用在数学模式中，参数不含 CJK 字符，可以在入口处
% 直接关闭 |XeTeXinterchartokenstate|，由数学模式的分组自动恢复。
%    \begin{macrocode}
\@@_package_hook:nn { mtpro2 }
  {
    \cs_set_eq:NN \@@_mtpro_overcbrace:w \overcbrace
    \cs_gset_protected:Npn \overcbrace #1
      {
        \makexeCJKinactive
        \@@_mtpro_overcbrace:w {#1}
      }
    \cs_set_eq:NN \@@_mtpro_undercbrace:w \undercbrace
    \cs_gset_protected:Npn \undercbrace #1
      {
        \makexeCJKinactive
        \@@_mtpro_undercbrace:w {#1}
      }
  }
%    \end{macrocode}
%
% \changes{v3.8.3}{2020/04/09}{兼容 \pkg{unicode-math} 和 \opt{CJKmath} 选项。}
%
% \begin{macro}{\@@_save_um_char:, \@@_save_um_char:}
% 兼容 \pkg{unicode-math} 和 \opt{CJKmath} 选项，
% 避免将一些中西文混用的标点设置为 CJK 字体。
%    \begin{macrocode}
\@@_package_hook:nn { unicode-math }
  {
    \prop_const_from_keyval:Nn \c_@@_um_ambiguous_char_prop
      {
        "00B7 = \cdotp ,
        "2025 = \enleadertwodots ,
        "2026 = \unicodeellipsis
      }
    \cs_new_protected:Npn \@@_save_um_char:
      {
        \cs_set_protected:Npe \@@_restore_um_char:
          {
            \prop_map_function:NN
              \c_@@_um_ambiguous_char_prop
              \@@_restore_um_char_aux:nn
          }
      }
    \cs_new_eq:NN \@@_restore_um_char: \prg_do_nothing:
    \cs_new:Npn \@@_restore_um_char_aux:nn #1#2
      {
        \@@_gset_mathcodenum:nn
          { \int_value:w #1 }
          { \int_value:w \tex_Umathcodenum:D #1 }
      }
    \cs_new_protected:Npn \@@_gset_mathcodenum:nn #1#2
      {
        \int_compare:nNnF { \tex_Umathcodenum:D #1 } = {#2}
          { \tex_global:D \tex_Umathcodenum:D #1 = #2 ~ }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.5}{2020/06/25}{进一步兼容 \pkg{microtype}。}
%
% \begin{macro}{\@@_patch_microtype_get_slot:}
% 兼容 \pkg{microtype}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_patch_microtype_get_slot:
  {
    \cs_new_eq:NN \xeCJK@original@get@slot \MT@get@slot@
    \cs_set_eq:NN \MT@get@slot@ \xeCJK@microtype@get@slot
    \cs_set_eq:NN \MT@warn@unknown@once \use_none:n
  }
\cs_new_protected_nopar:Npn \xeCJK@microtype@get@slot
  {
    \int_compare:nNnT \MT@char < \c_zero_int
      { \@@_get_ambiguous_slot: }
    \xeCJK@original@get@slot
  }
\cs_new_protected:Npn \@@_get_ambiguous_slot:
  {
    \prop_get:NeNT \g_@@_ambiguous_slot_prop
      { \MT@encoding - \tex_the:D \MT@toks } \l_@@_tmp_tl
      { \cs_set_eq:NN \MT@char \l_@@_tmp_tl }
  }
\cs_new_protected:Npn \xeCJK@microtype@restore@pickupfont
  { \@@_gadd_font_initial_hook:n { \MT@ltx@pickupfont } }
\@@_package_hook:nn { microtype }
  {
    \cs_if_free:NF \MT@get@slot@
      { \@@_patch_microtype_get_slot: }
    \MT@addto@setup { \xeCJK@microtype@restore@pickupfont }
  }
%    \end{macrocode}
% \end{macro}
%
% 简单处理与 \pkg{hyperref} 宏包的兼容问题。
% \changes{v3.10.0}{2026/05/03}{通过在 \tn{Hy@BeginAnnot} 中保存并
%   选择性恢复 xeCJK 节点标记，同时清除旧标记，
%   解决目录中链接注释起始处的虚假 \texttt{ecglue}（\#810），
%   并为 \tn{ref} 提供前侧 \texttt{ecglue}（\#809）。}
% \changes{v3.10.3}{2026/07/13}{在顶层 \tn{Hy@EndAnnot} 结束前检测
%   URL 的数学节点，并在链接结束 whatsit 后放置可信的西文边界标记，
%   修复 \tn{url} 右侧的 \texttt{CJKecglue} 丢失问题（\#972）。}
%
% \changes{v3.10.4}{2026/07/20}{把链接 annotation 的入口保存和末尾定点
%   重放迁移到统一 stream capture，按链接实际首尾可见类别恢复两侧边界
%   （\#992）。}
%
% \changes{v3.10.5}{2026/08/04}{把行内锚点的 \tn{hyper@anchor}、
%   \tn{Hy@raisedlink} 与 \cs{__hyp_target_raise:n} 三个出口注册为 transparent
%   capture，使 \tn{hypertarget}、\tn{phantomsection}、\tn{MakeLinkTarget}、
%   无编号标题锚点与手工包裹的锚点不再遮蔽其前后的实际边界；经
%   \tn{hyper@anchorstart} 裸调用的路径（如 \tn{pdfbookmark}）尚未覆盖
%   （\#1047）。}
%
% \pkg{hyperref} 的行内锚点会在 xeCJK 的 marker 与后续字符之间插入不可见
% 节点，使 Boundary$\to$\{Default,CJK\} 路径的 marker 探测失败，锚点右侧的
% \tn{CJKecglue} 因此丢失（\#1047）。这类命令没有可见输出，故与 \pkg{hypdoc}
% 的 \tn{HD@target} 一样注册为 transparent。
%
% 下面\emph{已覆盖}三个出口。区分依据是\emph{调用点}，不是锚点内容：
% \begin{itemize}
%   \item 驱动层的 \tn{hyper@anchor} 承接经 \tn{hyper@@anchor} 进来的锚点。
%     \tn{hypertarget} 的两个分支最终都走这里——\tn{@hyper@@anchor} 在
%     \tn{ifHy@activeanchor} 为假时统一调用 \tn{hyper@anchor}，与目标内容
%     是否为空无关。它直接排出裸的 \texttt{pdf:dest} whatsit。
%   \item \tn{Hy@raisedlink} 承接需要抬升的锚点：无编号标题
%     （\tn{section*}、\tn{chapter*} 以及目录、参考文献等自动生成的
%     无编号标题）、caption、公式编号、脚注、\tn{bibitem}，以及下游手工
%     包裹的写法，例如 \cls{ctxdoc} 的 \tn{exptarget} 定义为
%     \tn{Hy@raisedlink}\verb*-{-\tn{hypertarget}\marg{name}\verb*-{}}-。
%     它在水平模式下输出 \tn{penalty}\tn{@M} 加一个 \tn{smash} 后的
%     \texttt{hbox(0+0)x0} 来盛放 \texttt{pdf:dest} special。
%     注意目录\emph{条目}不走这条路：\tn{contentsline} 用
%     \tn{hyper@linkstart}／\tn{hyper@linkend} 做链接，与抬升锚点无关。
% \end{itemize}
% 因此两处都要注册：只注册 \tn{Hy@raisedlink} 时所有 \tn{hypertarget} 仍缺
% 间距，只注册 \tn{hyper@anchor} 时 \tn{exptarget} 一类写法与无编号标题锚点
% 仍缺间距。
%
% 第三个出口是 \cs{__hyp_target_raise:n}：\tn{phantomsection} 与
% \tn{MakeLinkTarget} 走它，编号标题的锚点也经过它。它自己排出同构的
% \tn{penalty}\tn{@M} 加 \tn{smash} 抬升盒子，不经过 \tn{Hy@raisedlink}。
% 这个函数不接受通用命令 hook（LaTeX hook 机制拒绝 expl3 私有函数），因此用
% \cs{@@_boundary_wrap_transparent_onearg_braced:NN} 包装——必须是\emph{带
% 花括号}转发参数的那个变体，理由见该函数的说明。
%
% \emph{已知未覆盖}：\tn{hyper@anchorstart} 的裸调用。\tn{pdfbookmark} 直接写
% \tn{hyper@anchorstart}\marg{name}\tn{hyper@anchorend}，既不经 \tn{hyper@@anchor}
% 也不经两个抬升出口（计数器实测三者均为 0），因此右侧仍会丢失一枚
% \tn{CJKecglue}（实测 38.33002pt 对 oracle 41.66002pt）。同类裸调用在
% \pkg{hyperref} 与各驱动里还有若干处。
%
% 两种就手的补法都已实测不可行：注册 \tn{@pdfm@dest}（\tn{hyper@anchor} 与
% \tn{hyper@anchorstart} 的共同下游）会让盒宽暴涨并报出十余处错误，因为它的
% 参数含有待展开内容；注册 \tn{hyper@anchorstart} 本身不报错，但也不生效
% ——\tn{pdfbookmark} 仍为 38.33002\,pt，而已覆盖的三处不受影响。为什么
% transparent 在这个入口上无效尚未查明，这条路径需要单独设计适配器，另立议题
% 跟踪。
%
% 判断这类问题应当读分派函数的分支并用计数器实测，不能按公开命令的参数形态
% 推测走哪条路，也不能因为「注册若干处之后报告的现象消失」就断定出口的数量。
% 本节的机制陈述先后被独立复核推翻四次：「按目标是否为空分派」「只有两个
% 出口」「包装失败源于 begin 钩子的赋值」「只有三个出口」，每一次都是从
% 「注册这些之后现象消失」推出更强的断言，每一次都由一支计数器探针或一次
% 隔离实验推翻。因此本节只列\emph{已覆盖}与\emph{已知未覆盖}两份清单，不再
% 给出总数。
%
% \tn{hyper@anchor} 由驱动定义，\pkg{hxetex}、\pkg{hluatex}、\pkg{hpdftex}
% 与 \pkg{hdvipdfm} 使用同一名称，\pkg{hdvips} 经 \pkg{pdfmark} 定义同名命令；
% 名称虽然一致，各驱动的定义时机仍由 \pkg{hyperref} 决定，注册前先检查
% 是否存在，避免装出无效 hook。\pkg{hyperref} 在 \tn{AtEndOfPackage} 阶段
% 载入驱动，故包尾钩子里的存在性检查时机正确。
%
% \tn{Hy@raisedlink} 这一侧不能用 |post-transparent|：那个 after-only 变体
% 只在命令结束后搬移末尾零尺寸盒子下方的 marker 与候选 glue，而
% \tn{Hy@raisedlink} 在盒子\emph{之前}还排出了 \tn{penalty}，marker 与盒子
% 并不相邻，搬移条件不成立。transparent 在入口就取走 marker 与可选源码空格，
% 锚点节点排出后再原样恢复，因而两种节点次序都能覆盖。
%
% \tn{Hy@BeginAnnot} 在 annotation 起始 whatsit 之前启动 stream capture；
% 链接文字中的 Default/CJK 转换随后直接记录实际首尾类别。顶层
% \tn{Hy@EndAnnot} 若看到数学节点，而 capture 尚未由显式公式入口记录
% |math|，则把它作为 URL 等命令的可信 Default 输出记录下来；显式
% \tn{hyperref} 或 \tn{href} 参数以字面数学切换符开始或结束时，由
% \tn{hyper@link}/\tn{hyper@linkurl} 的三参数／两参数适配器分别报告首、末
% |math| 类别。结束 whatsit 之后再结束 capture。这样普通西文、中文、
% 混合链接文字、数学 URL 和可见行内公式都保留各自语义；嵌套 annotation
% 仍只在最外层结束时收束，不会让内部结束点提前重放状态。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_hyperref_annot:
  {
    \clist_map_inline:nn
      {
        Hy@BeginAnnot , Hy@EndAnnot , hyper@link , hyper@linkurl ,
        href , hyperref
      }
      { \@@_boundary_reserve:n {##1} }
    \cs_gset_eq:NN \@@_orig_Hy_BeginAnnot:n \Hy@BeginAnnot
    \cs_gset_eq:NN \@@_orig_Hy_EndAnnot: \Hy@EndAnnot
    \cs_gset_protected:Npn \Hy@BeginAnnot ##1
      {
        \@@_boundary_inline_stream_begin:n { auto }
        \@@_orig_Hy_BeginAnnot:n { ##1 }
      }
    \cs_gset_protected:Npn \Hy@EndAnnot
      {
        \int_compare:nNnT { \c@Hy@AnnotLevel } = { 1 }
          {
            \@@_if_last_math:T
              {
                \tl_if_eq:cnF
                  {
                    g_@@_boundary_capture_
                    \int_use:N \g_@@_boundary_capture_depth_int _last_tl
                  }
                  { math }
                  { \@@_boundary_capture_class:n { default } }
              }
          }
        \@@_orig_Hy_EndAnnot:
        \@@_boundary_inline_stream_end:n { auto }
      }
    \cs_gset_eq:NN \@@_orig_hyper_link:nnn \hyper@link
    \cs_gset_protected:Npn \hyper@link ##1##2##3
      {
        \@@_orig_hyper_link:nnn {##1} {##2}
          {
            \@@_boundary_math_begin:n {##3}
            ##3
            \@@_boundary_math_end:n {##3}
          }
      }
    \cs_gset_eq:NN \@@_orig_hyper_linkurl:nn \hyper@linkurl
    \cs_gset_protected:Npn \hyper@linkurl ##1##2
      {
        \@@_orig_hyper_linkurl:nn
          {
            \@@_boundary_math_begin:n {##1}
            ##1
            \@@_boundary_math_end:n {##1}
          }
          {##2}
      }
    \@@_boundary_register_transparent:n { Hy@raisedlink }
    \cs_if_exist:NT \hyper@anchor
      { \@@_boundary_register_transparent:n { hyper@anchor } }
    \cs_if_exist:NT \__hyp_target_raise:n
      {
        \@@_boundary_wrap_transparent_onearg_braced:NN
          \@@_orig_hyp_target_raise:n \__hyp_target_raise:n
      }
  }
\@@_package_hook:nn { hyperref }
  {
    \pdfstringdefDisableCommands
      {
        \@@_gobble_CJKfamily:
        \xeCJK_cs_clear:N \@@_inactive_group_begin:
        \xeCJK_cs_clear:N \@@_inactive_group_end:
        \xeCJK_cs_clear:N \makexeCJKinactive
        \xeCJK_cs_clear:N \xeCJK_text_composite_patch:
      }
    \@@_boundary_register_hyperref_annot:
  }
%    \end{macrocode}
%
% \changes{v3.10.0}{2026/04/30}{为 \pkg{color}/\pkg{xcolor} 添加兼容补丁，
%   在颜色切换 whatsit 后重放 xeCJK 节点标记，修复 \tn{textcolor}
%   后 \texttt{CJKecglue} 丢失的问题（\#315, \#803）。}
% \changes{v3.10.0}{2026/05/01}{修复 \tn{set@color} 补丁在无真实节点标记时
%   未清除全局状态，导致首次 \tn{textcolor} 在标点或段首后插入虚假
%   \texttt{ecglue} 的问题（\#807）。}
% \changes{v3.10.0}{2026/05/12}{补丁 \tn{reset@color} 以在颜色弹出 whatsit
%   之后重新放置 xeCJK 节点标记，修复 \tn{textcolor} 命令右侧
%   多余 inter-word glue 的问题（\#831）。}
% \changes{v3.10.0}{2026/05/13}{\tn{reset@color} 补丁增加 hlist 回退路径，
%   当 color pop 后最后节点为 hlist 时设置 \cs{g_@@_reset_color_pending_bool}，
%   延迟到 \cs{@@_check_for_glue_skip:} 中处理 \tn{colorbox} 等命令
%   右侧间距（\#831）。}
% \changes{v3.10.0}{2026/05/14}{修复颜色补丁在
%   \cs{g_@@_last_node_tl} 被无关的 \tn{set@color} 清空后
%   仍尝试重放节点导致 \texttt{Missing number} 错误的问题，
%   影响 \pkg{listings} 与 \tn{rulecolor} 组合（\#836）。}
% \changes{v3.10.4}{2026/07/20}{将 \tn{set@color} 与 \tn{reset@color}
%   注册为 transparent capture，删除颜色 push/pop 的专用 marker
%   save/replay 分支（\#992）。}
% \changes{v3.10.0}{2026/05/02}{使用 \cs{l_keys_key_str} 和
%   \cs{l_keys_choice_str} 替代已废弃的 \texttt{\_tl} 版本（\#806）。}
%
% \pkg{color}/\pkg{xcolor} 的颜色切换通过 \tn{set@color} 插入颜色 push
% whatsit，再由 aftergroup 中的 \tn{reset@color} 插入 pop whatsit。两者都没有
% 可见输出，因此分别注册为 transparent：入口保存并移除 xeCJK marker 与源码
% 空格，颜色 whatsit 排出后原样恢复。\tn{color@b@x} 会直接写出多个节点，
% 继续使用 wrapped-box 收集整个盒子命令的输出。\tn{@textcolor} 的当前
% 三参数内部签名由一层 stream 适配器保留；第三个参数以字面数学切换符开始
% 或结束时，适配器分别在 math-on 前报告首类别、在正文参数实际排完时确认
% 末类别。
%    \begin{macrocode}
% \tn{colorbox} 和 \tn{fcolorbox} 的扫描器都会把可见正文留作
% \tn{color@b@x} 的第三个参数。先包装这个共同入口，再安装 wrapped-box
% hook；调用时便会先启动 capture，再由适配器报告正文两端的公式类别。
% \pkg{xcolor} 会重定义这个私有函数，因此它的 package hook 需要按
% 当前定义重新保存原函数。
\cs_new_protected:Npn \@@_boundary_color_box:nnn #1#2#3
  {
    \@@_boundary_math_begin:n {#3}
    \@@_orig_color_box:nnn {#1} {#2}
      { #3 \@@_boundary_math_end:n {#3} }
  }
\cs_new_protected:Npn \@@_boundary_prepare_color_box:
  {
    \@@_boundary_reserve:n { color@b@x }
    \cs_if_eq:NNF \color@b@x \@@_boundary_color_box:nnn
      {
        \cs_gset_eq:NN \@@_orig_color_box:nnn \color@b@x
        \cs_gset_eq:NN \color@b@x \@@_boundary_color_box:nnn
      }
  }
\cs_new_protected:Npn \@@_boundary_textcolor:nnn #1#2#3
  {
    \@@_boundary_if_math_edge:nTF {#3}
      {
        \@@_boundary_inline_stream_begin:n { auto }
        \@@_boundary_math_begin:n {#3}
        \@@_orig_textcolor:nnn {#1} {#2}
          { #3 \@@_boundary_math_end:n {#3} }
        \@@_boundary_inline_stream_end:n { auto }
      }
      { \@@_orig_textcolor:nnn {#1} {#2} {#3} }
  }
\cs_new_protected:Npn \@@_boundary_register_textcolor:
  {
    \@@_boundary_reserve:n { @textcolor }
    \@@_boundary_reserve:n { textcolor }
    \cs_if_eq:NNF \@textcolor \@@_boundary_textcolor:nnn
      {
        \cs_gset_eq:NN \@@_orig_textcolor:nnn \@textcolor
        \cs_gset_eq:NN \@textcolor \@@_boundary_textcolor:nnn
      }
  }
\cs_new_protected:Npn \@@_boundary_register_color:
  {
    \@@_boundary_reserve:n { colorbox }
    \@@_boundary_reserve:n { fcolorbox }
    \@@_boundary_register_transparent:n { set@color }
    \@@_boundary_register_transparent:n { reset@color }
    \@@_boundary_prepare_color_box:
    \@@_boundary_register_wrapped_box:nn { color@b@x } { auto }
    \@@_boundary_register_textcolor:
  }
\@@_package_hook:nn { color }
  { \@@_boundary_register_color: }
\@@_package_hook:nn { xcolor }
  { \@@_boundary_register_color: }
%    \end{macrocode}
%
% \changes{v3.10.0}{2026/05/14}{补丁 \pkg{l3color} 后端的
%   \cs{__color_select:N} 和 \cs{__color_backend_reset:}，
%   使 \pkg{l3color} 接口的颜色切换也能正确保持 xeCJK 间距（\#832）。}
% \changes{v3.10.4}{2026/07/20}{将 \pkg{l3color} 的颜色 push/pop 入口
%   注册为 transparent capture，删除对应专用 marker 保护分支（\#992）。}
%
% \pkg{l3color}（\pkg{expl3} 内置）的颜色机制使用独立的后端代码路径，
% 不经过 \tn{set@color}/\tn{reset@color}。
% \cs{__color_select:N} 负责颜色推入（调用后端 select 并注册
% aftergroup reset），\cs{__color_backend_reset:} 负责颜色弹出。两者都没有
% 可见输出，使用与 LaTeX2e color push/pop 相同的 transparent capture。
% expl3 内部函数不接受通用 cmd hook，因此只用保持原参数签名的薄适配器
% 包围 framework begin/end，不再复制 marker 检测与重放算法。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_l3color:
  {
    \@@_boundary_wrap_transparent_onearg:NN
      \@@_orig_color_select:N \__color_select:N
    \@@_boundary_wrap_transparent_noarg:NN
      \@@_orig_color_backend_reset: \__color_backend_reset:
  }
\@@_boundary_register_l3color:
%    \end{macrocode}
%
% \changes{v3.10.0}{2026/06/23}{为 \pkg{hypdoc} 的 \tn{HD@target} 添加补丁，
%   在它产生的 hbox 之后重放 xeCJK 节点标记，
%   修复 \pkg{l3doc} 中 \tn{cs}、\tn{meta} 等命令后 \texttt{CJKecglue}
%   丢失或保留为原始空格的问题（\#873）。}
%
% \pkg{hypdoc} 的 \tn{HD@target} 通过 \tn{raisebox} 产生 \texttt{hbox(0+0)x0}
% 用于放置 \tn{special\{pdf:dest...\}}。该 hbox 隔在 xeCJK 标记 kern
% 对和后续字符之间，使 \cs{tex_lastkern:D} 探测失败，导致
% Boundary→\{Default,CJK\} 路径中 \cs{xeCJK_if_last_node:n} /
% \cs{@@_if_last_kern:} 检查返回 false，间距因此丢失。
% \changes{v3.10.4}{2026/07/20}{把 \tn{HD@target} 迁移到统一 transparent
%   capture，同时恢复其前后的 marker 与源码空格状态（\#992）。}
% 该命令没有可见输出，因此注册为 transparent：入口保存并移除 marker 与
% 可选源码空格，原命令插入锚点 hbox 后再原样恢复；后续边界不再受这个
% $0\times0$ hbox 遮蔽。
%    \begin{macrocode}
\@@_package_hook:nn { hypdoc }
  { \@@_boundary_register_transparent:n { HD@target } }
%    \end{macrocode}
%
% \changes{v3.10.4}{2026/07/20}{新增 \pkg{siunitx} 兼容：\tn{unit}、\tn{qty}、\tn{num}
%   与 v2 旧名 \tn{si}、\tn{SI} 注册为固定 Default 首尾的 stream
%   capture，修复 CJK 上下文中单位命令边界的空格丢失（\#1000）。}
%
% \pkg{siunitx} 用 math 模式排版数字与单位。math 不触发 interchar
% 转换，入口的 \tn{mathon} 又盖住左侧 marker，情况与修复前的 \tn{eqref}
% 相同；数字与单位输出必然以西文或数字开始、结束，因此也注册为固定
% Default 首尾的 stream capture。v2 旧名 \tn{si}、
% \tn{SI} 是独立顶层命令，不经由 \tn{unit}、\tn{qty} 的 cmd hook，需
% 单独注册。注册前先检查命令是否存在，以兼容未来移除旧名的
% \pkg{siunitx} 版本。若其他宏包恰好定义了同名 \tn{si}/\tn{SI}，
% 这项检查也会把它们一并注册——
% 误注册只是多包一层 Default stream，不改变无 CJK 邻接时的排版，记录
% 为已知限制。数学模式内使用（如 |$x=\unit{kg}$|）时，capture 只压入
% inactive 标记，不改变排版。\tn{ang} 输出角度符号，目前还没有确定
% 应与哪种直接输入比较，因此暂不注册。
%    \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: }
%    \end{macrocode}
%
% \changes{v3.10.0}{2026/06/23}{为 \pkg{url} 的 \tn{Url@FormatString} 添加补丁，
%   在进入数学模式前 drain 缓存的 \texttt{CJKecglue}，
%   修复 CJK 文字与 \tn{url} 命令之间间距丢失的问题（\#880）。}
% \changes{v3.10.4}{2026/07/20}{用固定 Default 首尾的 stream capture
%   包围 \tn{Url@z} 的完整格式化阶段，删除 \#880 的专用 drain（\#992）。}
%
% \pkg{url} 的 \tn{url} 命令通过 \tn{Url@FormatString} 进入数学模式
% 排版 URL 字符串。
% 数学模式 node 隔在 xeCJK 标记 kern 对和数学公式之间，
% 又因 \XeTeX{} 在数学模式中不触发类别转换（interchar class），
% 标记 kern 对永远等不到匹配的 Boundary$\to$\{CJK,Default\} 触发，
% 缓存的 \texttt{CJKecglue} 因此在 CJK 与 \tn{url} 之间丢失。
% \tn{url} 同时支持花括号和任意分隔符扫描，不能用普通
% \texttt{cmd/url/before} / \texttt{cmd/url/after} hook 包围；after hook
% 会进入分隔符参数本身。
% \tn{Url@z} 已经完成参数扫描，并覆盖 \tn{Url@HyperHook}、数学格式化和
% 最后的分组关闭，因此在这里启动和结束固定 Default 首尾的 stream capture。
% 数学模式不会触发 interchar 转换，但 |default| 模式会显式报告两端类别；
% URL 左右边界由统一框架重建，不再需要专用的 marker 移除与补胶逻辑。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_url:
  {
    \clist_map_inline:nn { url , path , nolinkurl , Url@z }
      { \@@_boundary_reserve:n {##1} }
    \cs_if_exist:NF \@@_orig_Url_z:n
      {
        \cs_set_eq:NN \@@_orig_Url_z:n \Url@z
        \cs_gset_protected:Npn \Url@z ##1
          {
            \@@_boundary_inline_stream_begin:n { default }
            \@@_orig_Url_z:n {##1}
            \@@_boundary_inline_stream_end:n { default }
          }
      }
  }
\@@_package_hook:nn { url } { \@@_boundary_register_url: }
%    \end{macrocode}
%
% \changes{v3.10.1}{2026/06/29}{为 \LaTeX{} 核心命令 \tn{verb} 添加补丁，
%   在进入 verb 内容前 drain 缓存的 \texttt{CJKecglue}，
%   修复 CJK 文字与 \tn{verb}（或 \pkg{shortvrb} 的短记号）
%   之间间距丢失的问题（\#910）。}
%
% \LaTeXe{} 的 \tn{verb} 命令展开后会执行 \tn{leavevmode\null}，
% 其中 \tn{null} 等价于 \tn{hbox\{\}}，会产生一个
% \texttt{hbox(0+0)x0} 节点。这个 $0\times0$ 的 hbox 紧跟在 \pkg{xeCJK}
% 的标记 kern 对之后，会切断后续 Boundary$\to$\{Default, CJK\} 类别
% 转换中 \cs{tex_lastkern:D} 的探测路径，导致 CJK 文字与 \tn{verb}
% 之间应有的 \texttt{CJKecglue} 丢失。
%
% \changes{v3.10.4}{2026/07/20}{把 \tn{verb} 从假定西文输出的入口 drain
%   迁移到统一 stream capture，按 verbatim 实际首尾类别恢复两侧边界
%   （\#992）。}
%
% 原 \#910 补丁在入口直接补 \tn{CJKecglue}，隐含假设 verbatim 内容必为
% 西文，因而不能处理中文输出或右边界。现在在原始 \tn{verb} 执行前启动
% auto stream capture：入口先保存外侧 marker、源码空格和 glue 值；分隔符
% 扫描及排版仍完全交给内核。\tn{verb@egroup} 关闭组并刷新 language whatsit
% 后结束 capture，按真正观察到的首尾 Default/CJK 类别同时重建左右边界。
% 数学模式由 capture 入口自动跳过；\pkg{shortvrb} 的短记号与 \tn{verb}*
% 共用同一入口和组结束点，因此继续自动覆盖。
%
% 补丁仍在 \cs{@@_after_preamble:n} 安装，以避开其他宏包在导言区重定义
% \tn{verb} 的时序。
%
% \changes{v3.10.2}{2026/07/03}{\tn{verb} 组关闭时用 \tn{setlanguage}
%   主动排出 language whatsit，修复 \tn{verb} 右侧源码空格场景下
%   \texttt{CJKecglue} 双倍的问题（\#919）。}
%
% \tn{verb} 右侧还有一处独立时序（\#919）：\tn{verb} 组内把
% \tn{language} 切到 \tn{l@nohyphenation}，组关闭后的 language whatsit
% 会延迟到下一字符进入列表时才排出。若让它留到 capture 结束之后，
% 它会遮住 framework 刚刚重放的末尾 marker。这里仍在
% \tn{verb@egroup} 关组后立即执行 \tn{setlanguage\tn{language}}，把
% language whatsit 收进 stream capture；随后 framework 在它之后重放
% 实际末类别，右侧有、无源码空格都交给普通恢复链处理。旧的通用
% whatsit 类别猜测已经删除，不再参与这条路径。
%
% \begin{macro}[int]{\@@_flush_language_whatsit:}
% 按当前 \tn{language} 值主动排出一个 language whatsit。
% \tn{setlanguage\tn{language}} 只排出 whatsit 记录当前值，不改变
% hyphenation 状态，对断行无副作用。仅在（外层）水平模式下执行：
% 数学模式或行首场景没有上述时序问题，restricted horizontal mode
% （\tn{hbox} 内）则根本不产生延迟 whatsit。
% 目前唯一调用点是 \tn{verb@egroup}（见下方
% \cs{@@_boundary_register_verb:}）；
% 未来若有其他切换 \tn{language} 的分组宏需要同样的时序修正，
% 可直接复用本函数，并在此注释中登记新的调用点。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_flush_language_whatsit:
  { \mode_if_horizontal:T { \tex_setlanguage:D \tex_language:D } }
%    \end{macrocode}
% \end{macro}
%
% \pkg{shortvrb} 短记号与 \tn{verb}* 都经由 \tn{verb} 入口且组关闭
% 走同一个 \tn{verb@egroup}，因此 capture 与 flush 两个补丁都自动覆盖。
%
% \begin{macro}[int]{\@@_boundary_register_verb:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_verb:
  {
    \cs_if_exist:NF \@@_orig_verb:
      {
        \cs_set_eq:NN \@@_orig_verb: \verb
        \cs_gset_protected:Npn \verb
          {
            \@@_boundary_inline_stream_begin:n { auto }
            \@@_orig_verb:
          }
        \tl_gput_right:Nn \verb@egroup
          {
            \@@_flush_language_whatsit:
            \@@_boundary_inline_stream_end:n { auto }
          }
      }
  }
\@@_boundary_reserve:n { verb }
\@@_boundary_reserve:n { verb@egroup }
\@@_after_preamble:n { \@@_boundary_register_verb: }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.10.1}{2026/06/30}{为 \cls{l3doc} 的 \cs{__codedoc_meta:n}
%   添加补丁，将参数内容包入 \cs{hbox:n}，
%   修复 \tn{meta} 内首字符前出现多余 \texttt{CJKecglue} 的问题（\#920）。}
% \changes{v3.10.2}{2026/07/06}{为 \pkg{doc} 的 \tn{meta}
%   添加补丁，将参数内容包入 \cs{hbox:n}，
%   修复 \tn{meta} 内首字符前出现多余 \texttt{CJKecglue} 的问题（\#951）。}
%
% \pkg{l3doc} 类用 \cs{__codedoc_meta:n} 排版 \tn{meta} 命令的参数：
% 内部由 \cs{__codedoc_meta_original:n} 依次输出 \tn{ensuremath}\verb*-{-\tn{langle}\verb*-}-
% 数学节点、参数内容、以及对称的 \tn{ensuremath}\verb*-{-\tn{rangle}\verb*-}-。
% 当参数内容的首字符是 CJK 时，Default$\to$CJK 类别转换中的
% \cs{xeCJK_pre_inter_class_toks:nnn} 会自动加上 \tn{CJKecglue}，
% 这会在 $\langle$ 与首个 CJK 字符之间产生多余的间距。
%
% 这里采用 myhsia 在 \pkg{hyperref} / \pkg{hypdoc} 兼容议题中提出的
% \texttt{hbox} 包装方案：把 \cs{__codedoc_meta:n} 的参数装进
% \cs{hbox:n}。hbox 隔离了内部的 \XeTeX{} 类别转换，
% Default$\to$CJK 转换不会跨越 hbox 边界触发，从而避免插入多余的
% \tn{CJKecglue}。
%
% 仅用 hbox 包装会让入口的 math 节点遮住左侧 marker。适配器因此还要
% 用固定 Default 首尾的 stream capture 包围整条命令：framework 负责外侧
% 两端，hbox 只负责尖括号内部的输出规范化。
%
% \changes{v3.10.5}{2026/08/04}{把 \cls{l3doc} 的 stream capture 从内层
%   \cs{__codedoc_meta:n} 上移到公开的 \tn{meta}，使左右两侧的
%   \tn{CJKecglue} 都按命令外的字体度量求值（\#1046）。}
%
% \textbf{capture 必须包住 \tn{texttt} 而不只是内层参数排版函数}：
% \cs{@@_boundary_capture_begin:} 在入口处把 \tn{CJKecglue} 排入临时盒并
% 读成 skip 数值，缓存的度量因此取决于 capture 开始时生效的字体。
% \cls{l3doc} 的 \tn{meta} 定义为 \tn{texttt}\verb*-{-\cs{__codedoc_meta:n}
% \Arg{参数}\verb*-}-，若只包围内层的 \cs{__codedoc_meta:n}，capture
% 就落在 \tn{texttt} 已经切到等宽字体之后：左边界重放的是等宽字体的
% \tn{CJKecglue}（\pkg{lm} 下为不可伸缩的 \texttt{5.25pt}），而右边界在
% capture 结束、字体恢复之后求值，得到正文字体的弹性 \texttt{3.33pt}，
% 两侧因此不对称（\#1046）。把 capture 上移到公开 \tn{meta} 之后，两端
% 都在同一字体上下文中求值。
%
% \tn{Arg}、\tn{marg}、\tn{oarg}、\tn{parg} 不需要这层 capture：它们在
% \cs{__codedoc_meta:n} 两侧各自排出等宽的 \verb*-{-、\verb*-[-、\verb*-(-
% 等实字符，本身就构成正常的 CJK$\to$Default 边界，去掉内层 capture 前后
% 的宽度与可见排版结果完全相同（各类上下文实测），节点列表上少了内层 capture
% 原本留下的一对零效果 \texttt{default} marker kern（$\pm0.0002$\,pt）。这些入口仍写入
% 保留表，防止用户接口把通用 hook 叠加
% 到共享的内部实现上。
%
% 这里选择 \cs{@@_at_end_preamble:n} 而非 \cs{@@_package_hook:nn}，
% 原因在于 \pkg{l3doc} 是文档类，文档类加载早于 \pkg{xeCJK}，
% 需要等到 preamble 结束时才能判定 \cs{__codedoc_meta:n} 是否定义。
% 同时亦有开发者可能会在 \pkg{xeCJK} 前加载 \pkg{doc} 宏包。
% \begin{macro}[int]{\@@_boundary_register_codedoc_meta:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_codedoc_meta:
  {
    \cs_if_exist:NTF \__codedoc_meta:n
      {
        \clist_map_inline:nn
          { __codedoc_meta:n , Arg , marg , oarg , parg }
          { \@@_boundary_reserve:n {##1} }
        \cs_if_exist:NF \@@_orig_codedoc_meta:n
          {
            \cs_set_eq:NN \@@_orig_codedoc_meta:n \__codedoc_meta:n
            \cs_gset_protected:Npn \__codedoc_meta:n ##1
              { \@@_orig_codedoc_meta:n { \hbox:n {##1} } }
            \@@_boundary_register_stream:nn { meta } { default }
          }
      }
      {
        \bool_lazy_and:nnT
          { \cs_if_exist_p:c { ver@doc.sty } }
          { \cs_if_free_p:N \@@_orig_codedoc_meta:n }
          {
            \@@_boundary_reserve:n { meta }
            \cs_set_eq:NN \@@_orig_codedoc_meta:n \meta
            \DeclareDocumentCommand \meta { m }
              {
                \@@_boundary_inline_stream_begin:n { default }
                \@@_orig_codedoc_meta:n { \hbox:n {##1} }
                \@@_boundary_inline_stream_end:n { default }
              }
          }
      }
  }
\@@_at_end_preamble:n { \@@_boundary_register_codedoc_meta: }
% \changes{v3.10.4}{2026/07/20}{将 \tn{eqref} 注册为固定 Default
%   首尾、将 \tn{cs} 注册为仅固定 Default 首端的 stream capture；
%   codedoc/doc 的 meta 内部适配器直接包围固定 Default stream，删除专用
%   drain 并覆盖其所有公共调用方（\#992）。}
% \changes{v3.10.4}{2026/07/20}{将内核 \tn{@setref}（或 \pkg{hyperref}
%   保存的 \tn{real@setref}）注册为 auto stream capture，以统一框架取代
%   \#991 的专用 saved-node/replay 补丁。}
% \tn{eqref} 的圆括号和 \tn{meta} 的数学尖括号决定了命令两端
% 始终是 Default；参数内部即使包含中文，也不能把外侧 oracle
% 误判为裸中文。\tn{cs} 只有开头的反斜线固定为 Default，末尾仍由
% 参数的实际最后字符决定，因此使用 first-default stream 固定首类别并记录
% 真实末类别。
\@@_at_end_preamble:n
  {
    \cs_if_exist:NTF \real@setref
      { \@@_boundary_register_stream:nn { real@setref } { auto } }
      { \@@_boundary_register_stream:nn { @setref } { auto } }
    \cs_if_exist:NT \eqref
      { \@@_boundary_register_stream:nn { eqref } { default } }
    \cs_if_exist:NT \cs
      { \@@_boundary_register_stream:nn { cs } { first-default } }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.10.1}{2026/07/01}{为 \pkg{biblatex} 的
%   \tn{blx@pagetracker} 添加补丁，清空 \cs{g_@@_last_node_tl}，
%   修复中文参考文献条目首字符前出现多余 \texttt{CJKecglue} 的问题（\#931）。}
%
% \pkg{biblatex} 在开启 \opt{pagetracker} 选项（\pkg{authoryear} 等
% \texttt{bbx} 样式默认开启）时，每次排版 \tn{bibitem} 会在
% \tn{blx@pagetracker@context} 内调用 \tn{protected@write} 往
% \texttt{aux} 写入 \tn{abx@aux@page} 记录。该 \tn{write} 产生的
% whatsit 节点会遮蔽前一条目结尾的 marker。历史通用 whatsit 回退曾把
% 可能陈旧的 \cs{g_@@_last_node_tl} 当作列表证据，因而在下一条目首字符
% 前误加 \texttt{CJKecglue}；该通用猜测现已删除。
%
% \changes{v3.10.4}{2026/07/20}{把 \tn{blx@pagetracker} 从单向清空状态
%   改为 transparent capture，使 write whatsit 对两侧实际可见边界透明
%   （\#992）。}
%
% \#931 的旧补丁只清空 \cs{g_@@_last_node_tl}，能阻止虚假 ecglue，却也
% 丢掉了 whatsit 两侧本来应有的 CJKecglue/CJKglue。pagetracker 本身没有
% 可见输出，因此现在保存入口 marker、源码空格和 glue 状态，执行 write
% 后原样重放；已知 whatsit 不再遮蔽真实边界，也不会把陈旧状态带入下一个
% 无关段落。
%
% \textbf{注册点选 \tn{blx@pagetracker} 而非 \tn{blx@pagetracker@context}}：
% \pkg{biblatex} 在 \texttt{bbx} style 加载时通过
% \tn{ExecuteBibliographyOptions\{pagetracker\}} 展开
% \tn{blx@opt@pagetracker@page}，其内部 \tn{let}
% \tn{blx@pagetracker}$\leftarrow$\tn{blx@pagetracker@context}——注意
% 是**\tn{let} 值传递**而非**符号引用**。若补丁挂在
% \tn{blx@pagetracker@context}，只能改到源函数；但 \tn{blx@bibitem}
% 实际调用的是 \tn{blx@pagetracker} 的 \tn{let} 拷贝，早在 \texttt{bbx}
% 加载时就已定型，之后再改源函数已经无效。故补丁点选 \tn{blx@pagetracker}
% 本身，并延迟到 \cs{@@_at_end_preamble:n} 执行——此时 \texttt{bbx} 已
% 完全加载，\tn{blx@pagetracker} 已 \tn{let} 到最终目标（可能是
% \tn{blx@pagetracker@context}、\tn{blx@pagetracker@spread} 或
% \tn{relax}）。
%
% \begin{macro}[int]{\@@_boundary_register_biblatex_pagetracker:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_register_biblatex_pagetracker:
  {
    \cs_if_exist:NT \blx@pagetracker
      { \@@_boundary_register_transparent:n { blx@pagetracker } }
  }
\@@_at_end_preamble:n { \@@_boundary_register_biblatex_pagetracker: }
% 用户声明在全部内建 preamble 末注册之后应用；普通命令 hook 仍由 LaTeX
% 延迟到 \tn{AtBeginDocument} 代码执行完再安装。随后检查目标命令是否存在，
% 避免不存在的控制序列被 LaTeX 静默跳过。
\@@_at_end_preamble:n { \@@_boundary_user_register_apply: }
\@@_after_end_preamble:n { \@@_boundary_user_register_check: }
%    \end{macrocode}
% \end{macro}
%
%
% \changes{v3.1.0}{2012/11/13}{取消 \tn{cprotect} 的外部宏限制。}
% 当探测到 \pkg{cprotect} 宏包被引入时，则取消 \tn{cprotect} 宏的 \tn{outer} 定义。
%    \begin{macrocode}
\@@_package_hook:nn { cprotect }
  {
    \cs_if_free:NF \icprotect
      { \exp_after:wN \tex_let:D \cs:w cprotect \cs_end: \icprotect }
  }
%    \end{macrocode}
%
% \changes{v3.2.11}{2014/03/14}{删除 \tn{xeCJKcaption}。}
% \changes{v3.8.0}{2020/02/09}{删除 \pkg{CJKfntef} 补丁。}
%
% 在 \pkg{listings} 宏包后自动载入 \pkg{xeCJK-listings}。
%    \begin{macrocode}
\@@_package_hook:nn { listings }
  { \RequirePackage { xeCJK-listings } }
%    \end{macrocode}
%
%
% \changes{v3.3.3}{2015/12/12}{解决与 \pkg{microtype} 宏包的兼容问题。}
% 由于 \pkg{xeCJK} 假装 \pkg{CJK} 已经被引入了，这会可能导致旧版本的 \pkg{everysel} 包判断错误。
% 需要在它们判断之前取消定义。
%    \begin{macrocode}
\@@_package_hook:nn { everysel }
  {
    \cs_if_exist:NF \@EverySelectfont@Legacy
      { \cs_undefine:c { ver@CJK . \c_@@_package_ext_tl } }
  }
%    \end{macrocode}
%
% \changes{v3.2.4}{2013/06/25}{不再使用 \texttt{CJKnumber} 选项，可以在
% \pkg{xeCJK} 之后直接使用 \pkg{CJKnumb} 宏包得到中文数字。}
% \changes{v3.8.7}{2021/06/04}{更好地兼容 \pkg{CJKnumb}。}
%
% \begin{macro}[int]{\CJKaddEncHook}
% 为使用 \pkg{CJKnumb} 宏包而作一些处理。另外 \pkg{CJKnumb} 使用的是传统汉字“萬”
% 和“億”，我们在这里把它们修正为简体字。
% \changes{v3.2.10}{2014/03/01}{使用 \pkg{CJKnumb} 时，让 \tn{Unicode} 有定义。}
% \changes{v3.3.1}{2015/05/08}{应用 \texttt{0.99992} 版的新原语 \tn{Ucharcat}。}
%    \begin{macrocode}
\ctex_at_begin_package:nn { CJKnumb }
  {
    \tl_new:N \l_@@_CJK_version_tl
    \tl_set_eq:Nc \l_@@_CJK_version_tl { ver@CJK . \c_@@_package_ext_tl }
    \tl_set:cn { ver@CJK . \c_@@_package_ext_tl } { 9999/99/99 }
    \cs_new_protected:Npn \CJKaddEncHook #1#2
      {
        \str_if_eq:nnT {#1} { \CJK@UnicodeEnc }
          {
            \group_begin:
              \cs_set_eq:NN \Unicode \xeCJK_unicode_char:nn
              \cs_set_eq:NN \def \xdef
              #2
            \group_end:
            \str_gset:Nn \CJK@tenthousand    { ^^^^4e07 }
            \str_gset:Nn \CJK@hundredmillion { ^^^^4ebf }
            \tl_if_exist:NF \CJK@UnicodeEnc
              { \tl_const:Nn \CJK@UnicodeEnc { UTF8 } }
            \cs_if_exist:NF \Unicode
              { \cs_new_eq:NN \Unicode \xeCJK_unicode_char:nn }
          }
      }
    \cs_new:Npn \xeCJK_unicode_char:nn #1#2
      { \tex_Uchar:D \tex_numexpr:D (#1) * 256 + (#2) \scan_stop: }
  }
\ctex_at_end_package:nn { CJKnumb }
  { \tl_set_eq:cN { ver@CJK . \c_@@_package_ext_tl } \l_@@_CJK_version_tl }
%    \end{macrocode}
% \end{macro}
%
% 最后引入本地配置文件。
%    \begin{macrocode}
\bool_if:NT \g_@@_config_bool
  {
    \ExplSyntaxOff
    \file_input:n { \g_@@_config_name_tl .cfg }
    \ExplSyntaxOn
  }
%    \end{macrocode}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJKfntef}}
%
% \changes{v3.1.1}{2012/12/08}{增加小宏包 \pkg{xeCJKfntef}，用于处理下划线的问题。}
% \changes{v3.2.4}{2013/06/23}{修正 \pkg{xeCJKfntef} 与 \pkg{natbib} 等的冲突。}
% \changes{v3.2.14}{2014/10/31}{完善 \tn{varCJKunderline} 的实现。}
% \changes{v3.2.14}{2014/10/31}{解决下划线前后没有 \tn{CJKglue} 或 \tn{CJKecglue} 的问题。}
% \changes{v3.2.14}{2014/11/03}{\pkg{xeCJKfntef} 不再依赖 \pkg{CJKfntef}。}
% \changes{v3.2.15}{2014/11/07}{\pkg{xeCJKfntef} 增加 \texttt{hidden} 选项。}
%
%    \begin{macrocode}
%<*fntef>
%    \end{macrocode}
%
%    \begin{macrocode}
\PassOptionsToPackage { normalem } { ulem }
\DeclareOption* { \PassOptionsToPackage { \CurrentOption } { ulem } }
\ProcessOptions \scan_stop:
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage { xeCJK }
\RequirePackage { ulem }
\RequirePackage { l3draw }
\RequirePackage { accsupp }
%    \end{macrocode}
%
%    \begin{macrocode}
\addto@hook \UL@hook { \xeCJK_hook_for_ulem: }
%    \end{macrocode}
%
% \changes{v3.4.1}{2016/06/03}{新的下划线选项 \texttt{textformat}。}
%
% \begin{macro}[int]{\xeCJK_hook_for_ulem:}
% \changes{v3.1.0}{2012/11/16}{简化对 \pkg{ulem} 宏包的兼容补丁。}
% \changes{v3.1.1}{2012/12/08}{完全处理下划线里的标点符号的有关问题。}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_hook_for_ulem:
  {
    \xeCJK_ulem_detect_node:
    \l_@@_ulem_text_format_tl
    \bool_if:NF \l_@@_ulem_hook_used_bool
      {
        \bool_set_true:N \l_@@_ulem_hook_used_bool
        \@@_ulem_hook:
      }
    \xeCJK_ulem_begin_node:
  }
\cs_new_protected:Npn \@@_ulem_hook:
  {
    \@@_ulem_initial:
    \bool_if:NTF \l_@@_ulem_periodic_bool
      {
        \seq_gput_right:Nn \g_@@_ulem_periodic_later_seq { 0 }
        \cs_set_eq:NN \UL@spfactor \@@_ulem_spfactor_pending:
        \xeCJK_swap_cs:NN
          \UL@leaders \xeCJK_ulem_periodic_leaders:
        \cs_set_eq:NN \@@_ulem_periodic_var_leaders:
          \xeCJK_ulem_periodic_var_leaders:
        \cs_set_eq:NN \xeCJK_ulem_right_skip:
          \@@_ulem_periodic_right_skip:
      }
      {
        \bool_if:NT \l_@@_ulem_subtract_bool
          {
            \xeCJK_swap_cs:NN \UL@leaders \xeCJK_ulem_leaders:
            \cs_set_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_var_leaders:
            \cs_set_eq:NN \xeCJK_ulem_right_skip: \@@_ulem_right_skip:
          }
      }
    \bool_if:NT \l_@@_ulem_hidden_bool
      { \cs_set_eq:NN \UL@putbox \@@_ulem_hidden_box: }
    \bool_if:NTF \l_@@_ulem_skip_bool
      {
        \cs_set_eq:NN \@@_ulem_putbox: \UL@putbox
        \cs_set_eq:NN \@@_ulem_hskip_aux:n \xeCJK_ulem_hskip:n
      }
      {
        \xeCJK_swap_cs:NN \@@_punct_hskip:n \@@_ulem_punct_hskip:n
        \xeCJK_cs_clear:N \@@_ulem_skip_punct_begin:
        \xeCJK_cs_clear:N \@@_ulem_skip_punct_end:
      }
    \xeCJK_glue_to_skip:nN
      {
        \cs_set_eq:NN \  \tex_space:D
        \cs_set_eq:NN \penalty \tex_penalty:D
        \cs_set_eq:NN \hskip \skip_horizontal:N
        \CJKglue
      } \l_@@_ccglue_skip
    \xeCJK_glue_to_skip:nN
      {
        \cs_set_eq:NN \  \tex_space:D
        \cs_set_eq:NN \penalty \tex_penalty:D
        \cs_set_eq:NN \hskip \skip_horizontal:N
        \CJKecglue
      } \l_@@_ecglue_skip
    \xeCJK_glue_to_skip:nN { \xeCJK_space_glue: } \l_@@_space_skip
    \cs_set_protected:Npn \CJKglue
      { \@@_ulem_glue:n \l_@@_ccglue_skip }
    \cs_set_protected:Npn \CJKecglue
      { \@@_ulem_glue:n \l_@@_ecglue_skip }
    \cs_set_protected:Npn \xeCJK_space_glue:
      { \@@_ulem_glue:n \l_@@_space_skip }
    \cs_set_eq:NN \xeCJK_punct_node:N \use_none:n
    \cs_set_eq:NN \xeCJK_if_last_punct:TF \use_ii:nn
    \keys_set:nn { xeCJK / options }
      { CheckFullRight = false , xCJKecglue = false }
  }
\skip_new:N \l_@@_space_skip
\bool_new:N \l_@@_ulem_hook_used_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\UL@word,\xeCJK_ulem_word:nw}
% 修改 \tn{UL@word}，目的是取得分组中的 \tn{UL@leadtype}，以便加入 \cs{xeCJK_ulem_right_skip:}。
%    \begin{macrocode}
\clist_map_inline:nn
  {
    UL@word , ULon , UL@on , UL@onin ,
    uline , uuline , uwave , sout , xout , dashuline , dotuline ,
    xeCJKfntefon , CJKunderline , CJKunderwave , CJKunderdblline ,
    CJKsout , CJKxout , CJKunderanyline , CJKunderanysymbol , CJKunderdot
  }
  { \@@_boundary_reserve:n {#1} }
\cs_new_protected:Npn \xeCJK_ulem_word:nw #1 ~
  {
    \@@_ulem_start:w #1 ~
    \exp_after:wN \if_meaning:w \exp_after:wN \UL@end #1
      \exp_after:wN \@@_ulem_end:
    \else:
      \exp_after:wN \@@_ulem_loop:nw
    \fi:
  }
\cs_new_protected:Npn \@@_ulem_end:
  {
        \c_group_end_token
      \c_group_end_token
      \tex_unskip:D \tex_unskip:D \tex_unskip:D
      \xeCJK_ulem_right_skip:
    \xeCJK_ulem_group_end:
    \xeCJK_ulem_right_node:
    \@@_boundary_inline_stream_end:n { auto }
    \bool_set_false:N \l_@@_ulem_stream_started_bool
    \skip_gzero:N \g_@@_ulem_pending_shrink_skip
    \int_set:Nn \tex_spacefactor:D { \UL@spfactor }
  }
\cs_new_protected:Npn \@@_ulem_loop:nw
  {
    \reverse_if:N \if_mode_math:
      \reverse_if:N \if_dim:w \tex_lastskip:D = \c_zero_dim
        \skip_gset_eq:NN \UL@skip \tex_lastskip:D
        \tex_unskip:D
        \UL@stop
        \@@_ulem_flush_pending_shrink:
        \UL@leaders
      \fi:
    \fi:
    \xeCJK_ulem_word:nw \prg_do_nothing:
  }
\cs_new_protected:Npn \@@_ulem_start:w
  { \exp_after:wN \UL@start }
\cs_set_eq:NN \UL@word \xeCJK_ulem_word:nw
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_ulem_left:, \xeCJK_ulem_detect_node:}
% 在下划线开始之前探测之前的 \texttt{node}，以便随后插入 \tn{CJKglue} 或 \tn{CJKecglue}。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_ulem_left:
  {
    \xeCJK_ulem_left_node:
    \xeCJK_make_group_tag:
  }
\cs_new_eq:NN \xeCJK_ulem_left_node: \prg_do_nothing:
\cs_new_protected:Npn \xeCJK_ulem_detect_node:
  {
    \scan_stop:
    \dim_compare:nNnTF \tex_lastkern:D = \c_zero_dim
      {
        \xeCJK_cs_clear:N \xeCJK_ulem_left_node:
        \xeCJK_cs_clear:N \xeCJK_ulem_begin_node:
        \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n
      }
      {
        \dim_set_eq:NN \l_@@_tmp_dim \tex_lastkern:D
        \tex_unkern:D
        \dim_compare:nNnTF \tex_lastkern:D = { - \l_@@_tmp_dim }
          {
            \tex_unkern:D
            \cs_set_protected:Npe \xeCJK_ulem_left_node:
              {
                \tex_kern:D - \dim_use:N \l_@@_tmp_dim \exp_stop_f:
                \tex_kern:D   \dim_use:N \l_@@_tmp_dim \exp_stop_f:
              }
            \cs_set_protected:Npn \xeCJK_ulem_begin_node:
              { { \xeCJK_make_node:n { ulem-begin } } }
            \cs_set_eq:NN \@@_ulem_hskip:n \@@_ulem_hskip_first:n
          }
          {
            \tex_kern:D \l_@@_tmp_dim
            \xeCJK_cs_clear:N \xeCJK_ulem_left_node:
            \xeCJK_cs_clear:N \xeCJK_ulem_begin_node:
            \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n
          }
      }
  }
\xeCJK_declare_node:n { ulem-begin }
\cs_new_eq:NN \xeCJK_ulem_begin_node: \prg_do_nothing:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_hskip_first:n, \xeCJK_ulem_hskip:n}
% 如果第一次调用的 \tn{CJKglue} 或 \tn{CJKecglue} 由下划线中的第一个文字和之前的
% 内容产生，就不用画下划线。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_hskip_first:n #1
  {
    \xeCJK_if_last_node:nTF { ulem-begin }
      {
        \xeCJK_remove_node:
        \skip_horizontal:n {#1}
      }
      { \xeCJK_ulem_hskip:n {#1} }
    \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n
  }
\cs_new_eq:NN \@@_ulem_hskip:n \@@_ulem_hskip_first:n
\cs_new_protected:Npn \xeCJK_ulem_hskip:n #1
  { { \skip_set:Nn \UL@skip {#1} \UL@leaders } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_ulem_right:, \xeCJK_ulem_right_node:}
% 从 ulem 内部列表取出末尾 marker，并在外层列表原样放回；stream capture
% 结束时会把这个可信 marker 解释为实际末类别。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_ulem_right:
  {
    \scan_stop:
    \dim_compare:nNnTF \tex_lastkern:D = \c_zero_dim
      { \xeCJK_cs_gclear:N \xeCJK_ulem_right_node: }
      {
        \dim_compare:nNnTF \tex_lastkern:D = { 3sp }
          { \xeCJK_cs_gclear:N \xeCJK_ulem_right_node: }
          {
            \exp_args:NNo \tex_unkern:D
            \@@_ulem_right_aux:n { \dim_use:N \tex_lastkern:D }
          }
      }
  }
\cs_new_protected:Npn \@@_ulem_right_aux:n #1
  {
    \dim_compare:nNnTF \tex_lastkern:D = { - #1 }
      {
        \tex_unkern:D
        \cs_gset_protected:Npn \xeCJK_ulem_right_node:
          {
            \tex_kern:D - #1 \exp_stop_f:
            \tex_kern:D   #1 \exp_stop_f:
          }
        \tl_gset:Ne \UL@spfactor { \int_use:N \tex_spacefactor:D }
      }
      {
        \tex_kern:D #1 \exp_stop_f:
        \xeCJK_cs_gclear:N \xeCJK_ulem_right_node:
      }
  }
\cs_new_eq:NN \xeCJK_ulem_right_node: \prg_do_nothing:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_ulem_var_leaders:}
% \pkg{ulem} 通常把每段装饰向左右各扩展 \tn{UL@pixel}；对带减号形式，
% 第一次画装饰时改为先向右移动 \tn{UL@pixel}，使装饰从正文内部开始。
% 后续片段仍使用原来的 \cs{UL@leaders}，所以正文和中文字间距共享普通
% \tn{leaders} 的相位。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_ulem_leaders:
  { \@@_ulem_var_leaders: }
\cs_new_protected:Npn \xeCJK_ulem_var_leaders:
  {
    \scan_stop:
    \skip_if_eq:nnF { \UL@skip } { \c_zero_skip }
      {
        \skip_horizontal:N \UL@pixel
        \UL@leadtype \skip_horizontal:N \UL@skip
        \skip_horizontal:n { - \UL@pixel }
        \cs_gset_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_leaders:
      }
  }
\cs_new_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_var_leaders:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{
%   \xeCJK_ulem_periodic_leaders:,
%   \xeCJK_ulem_periodic_var_leaders:,
%   \@@_ulem_periodic_first_leaders:,
%   \@@_ulem_periodic_later_leaders:,
%   \@@_ulem_periodic_clipped_leaders:nnn}
% 普通 \tn{leaders} 只放置完整的装饰盒。它能让同一行中的各个片段共享
% 相位，但首尾可见余量会随命令的水平位置变化。周期装饰因此只在命令的
% 首段和末段增加 PDF 裁切：底下的 leaders 区间向两侧各扩展一个完整
% 周期，再把可见范围精确限制到正文的对称边界。内部正文片段和
% \tn{CJKglue} 仍使用原来的普通 leaders，不改变伸缩和断行。
%
% 参数 \texttt{\#2}、\texttt{\#3} 是相对正文左右边界向内移动的距离。
% 普通形式的首末段都使用
% \texttt{-\string\UL@pixel,-\string\UL@pixel}，即左右各外伸半个周期。
% 带减号形式的首段使用 \texttt{\string\UL@pixel,\string\UL@pixel}；
% 真正的末段使用
% \texttt{-\string\UL@pixel,\string\UL@pixel}，把内部接点接上，同时让命令
% 右端仍内缩半个周期。首段之后的第一个中文字间距在断点两侧各放一个
% 净宽为零的半周期连接片段；不换行时两半相接，换行时则分别止于上一行
% 末尾和下一行开头。单片段命令只使用首段裁切。
% 若正文没有产生节点，第一次调用 leaders 的内容其实只是 \pkg{ulem} 追加的
% 结尾语法空格。此时保留原来的三段 glue 结构，让 \pkg{ulem} 随后用三次
% \tn{unskip} 删除，不能把这枚空格改造成含 PDF special 和 kern 的裁切结构。
% 正文中的真实空格出现在 \tn{UL@spfactor} 哨兵更新之前，不会被误认为结尾
% 语法空格。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_ulem_periodic_leaders:
  { \@@_ulem_periodic_var_leaders: }
\cs_new_protected:Npn \xeCJK_ulem_periodic_var_leaders:
  {
    \scan_stop:
    \skip_if_eq:nnF { \UL@skip } { \c_zero_skip }
      {
        \cs_if_eq:NNTF \UL@spfactor \@@_ulem_spfactor_pending:
          { \@@_ulem_periodic_first_leaders: }
          {
            \skip_if_eq:nnTF { \UL@skip } { \box_wd:N \UL@box }
              { \@@_ulem_periodic_first_leaders: }
              { \xeCJK_ulem_periodic_leaders: }
          }
      }
  }
\cs_new_protected:Npn \@@_ulem_periodic_first_leaders:
  {
    \bool_if:NTF \l_@@_ulem_subtract_bool
      {
        \@@_ulem_periodic_clipped_leaders:nnn
          { \UL@skip } { \UL@pixel } { \UL@pixel }
      }
      {
        \@@_ulem_periodic_clipped_leaders:nnn
          { \UL@skip } { - \UL@pixel } { - \UL@pixel }
      }
    \cs_gset_eq:NN \@@_ulem_periodic_var_leaders:
      \@@_ulem_periodic_later_leaders:
  }
\cs_new_eq:NN \@@_ulem_periodic_var_leaders:
  \xeCJK_ulem_periodic_var_leaders:
\cs_new_protected:Npn \@@_ulem_periodic_later_leaders:
  {
    \scan_stop:
    \skip_if_eq:nnF { \UL@skip } { \c_zero_skip }
      {
        \cs_if_eq:NNTF \UL@spfactor \@@_ulem_spfactor_pending:
          {
            \seq_get_right:NN
              \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl
            \tl_if_eq:NnTF \l_@@_tmp_tl { 1 }
              { \xeCJK_ulem_periodic_leaders: }
              {
                \seq_gpop_right:NN
                  \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl
                \seq_gput_right:Nn \g_@@_ulem_periodic_later_seq { 1 }
                \bool_if:NTF \l_@@_ulem_subtract_bool
                  {
                    \@@_ulem_periodic_clipped_leaders:nnn
                      { \c_zero_skip } { - \UL@pixel } { \c_zero_dim }
                    \xeCJK_ulem_periodic_leaders:
                    \@@_ulem_periodic_clipped_leaders:nnn
                      { \c_zero_skip } { \c_zero_dim } { - \UL@pixel }
                  }
                  { \xeCJK_ulem_periodic_leaders: }
              }
          }
          { \xeCJK_ulem_periodic_leaders: }
      }
  }
\cs_new_protected:Npn \@@_ulem_periodic_clipped_leaders:nnn #1#2#3
  {
    \skip_set:Nn \l_@@_tmp_skip { #1 - #2 - #3 }
    \dim_compare:nNnTF { \l_@@_tmp_skip } > { \c_zero_dim }
      {
        \tex_kern:D \dim_eval:n {#2}
        \tex_special:D { x:gsave }
        \tex_special:D
          {
            pdf:literal~
            0~
            \dim_to_decimal_in_bp:n { - \box_dp:N \ULC@box - 1pt } ~
            \dim_to_decimal_in_bp:n { \l_@@_tmp_skip } ~
            \dim_to_decimal_in_bp:n
              { \box_ht:N \ULC@box + \box_dp:N \ULC@box + 2pt } ~
            re~W~n
          }
        \tex_kern:D \dim_eval:n { - 2 \UL@pixel }
        \xeCJK_no_break:
        \UL@leadtype
          \skip_horizontal:n { \l_@@_tmp_skip + 4 \UL@pixel }
        \tex_kern:D \dim_eval:n { - 2 \UL@pixel }
        \tex_special:D { x:grestore }
        \tex_kern:D \dim_eval:n {#3}
      }
      { \skip_horizontal:n {#1} }
  }
\bool_new:N \l_@@_ulem_periodic_bool
\seq_new:N \g_@@_ulem_periodic_later_seq
\cs_new_eq:NN \@@_ulem_spfactor_pending: \scan_stop:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_ulem_right_skip:}
% 在装饰完全画好之后检查末尾节点。带减号形式把最后一段的装饰宽度减少
% 两个 \tn{UL@pixel}，再补回一个等宽空白，使装饰停在正文右边界以内
% \tn{UL@pixel}，同时保持命令和正文的宽度不变。若末段窄到不足以容纳
% 缩减后的装饰，则只保留两端空白，不构造负宽度的 leaders。
%    \begin{macrocode}
\cs_new_eq:NN \xeCJK_ulem_right_skip: \prg_do_nothing:
\cs_new_protected:Npn \@@_ulem_right_skip:
  {
    \int_case:nn { \tex_lastnodetype:D }
      {
        { \c_@@_hlist_node }   { \@@_ulem_right_skip_hbox: }
        { \c_@@_glue_node }    { \@@_ulem_right_skip_glue: }
        { \c_@@_penalty_node } { \@@_ulem_right_skip_penalty: }
      }
  }
\cs_new_protected:Npn \@@_ulem_right_skip_hbox:
  {
    \box_set_to_last:N \l_@@_tmp_box
    \@@_if_last_kern:TF
      { \@@_ulem_right_skip_kern: }
      { \@@_ulem_right_skip_glue: }
    \box_use_drop:N \l_@@_tmp_box
  }
\cs_new_protected:Npn \@@_ulem_right_skip_kern:
  {
    \dim_set:Nn \l_@@_tmp_dim { - \box_wd:N \l_@@_tmp_box }
    \dim_compare:nNnT \tex_lastkern:D = \l_@@_tmp_dim
      {
        \tex_unkern:D
        \@@_ulem_right_skip_glue:
        \tex_kern:D \l_@@_tmp_dim
      }
  }
\cs_new_protected:Npn \@@_ulem_right_skip_glue:
  {
    \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel }
      {
        \tex_unskip:D
        \skip_set:Nn \l_@@_tmp_skip
          { \tex_lastskip:D - 2 \UL@pixel }
        \tex_unskip:D
        \dim_compare:nNnT { \l_@@_tmp_skip } > { \c_zero_dim }
          { \UL@leadtype \skip_horizontal:N \l_@@_tmp_skip }
        \skip_horizontal:N \UL@pixel
      }
  }
\cs_new_protected:Npn \@@_ulem_right_skip_penalty:
  {
    \int_set_eq:NN \l_@@_tmp_int \tex_lastpenalty:D
    \tex_unpenalty:D
    \@@_if_last_hlist:T
      { \@@_ulem_right_skip_hbox: }
    \tex_penalty:D \l_@@_tmp_int
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\@@_ulem_periodic_right_skip:}
% \changes{v3.10.5}{2026/07/27}{周期装饰按嵌套层保存末段状态，
%   避免内层命令改写外层右端（\#1012）。}
% 若命令只有一个装饰片段，首段函数已经同时裁定左右边界；出现后续片段时，
% 末段仍是 \pkg{ulem} 原来的负间距、leaders、负间距结构。这里取回末段
% 正文宽度，并用同一裁切函数重画，使命令右边界不再依赖完整图案盒的取整。
% 每个周期装饰在开始时向全局序列压入自己的状态，结束时再弹出。这里不能
% 共用一个全局布尔值：内层周期装饰会在外层完成之前改写它，导致外层末段
% 根据内层的状态决定是否重画。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_periodic_right_skip:
  {
    \seq_gpop_right:NN \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl
    \tl_if_eq:NnTF \l_@@_tmp_tl { 1 }
      { \@@_ulem_periodic_right_skip_aux: }
      { \prg_do_nothing: }
  }
\cs_new_protected:Npn \@@_ulem_periodic_right_skip_aux:
  {
    \int_case:nn { \tex_lastnodetype:D }
      {
        { \c_@@_hlist_node }   { \@@_ulem_periodic_right_skip_hbox: }
        { \c_@@_glue_node }    { \@@_ulem_periodic_right_skip_glue: }
        { \c_@@_penalty_node } { \@@_ulem_periodic_right_skip_penalty: }
      }
  }
\cs_new_protected:Npn \@@_ulem_periodic_right_skip_hbox:
  {
    \box_set_to_last:N \l_@@_tmp_box
    \@@_if_last_kern:TF
      { \@@_ulem_periodic_right_skip_kern: }
      { \@@_ulem_periodic_right_skip_glue: }
    \box_use_drop:N \l_@@_tmp_box
  }
\cs_new_protected:Npn \@@_ulem_periodic_right_skip_kern:
  {
    \dim_set:Nn \l_@@_tmp_dim { - \box_wd:N \l_@@_tmp_box }
    \dim_compare:nNnT \tex_lastkern:D = \l_@@_tmp_dim
      {
        \tex_unkern:D
        \@@_ulem_periodic_right_skip_glue:
        \tex_kern:D \l_@@_tmp_dim
      }
  }
\cs_new_protected:Npn \@@_ulem_periodic_right_skip_glue:
  {
    \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel }
      {
        \tex_unskip:D
        \skip_set:Nn \l_@@_tmp_skip { \tex_lastskip:D - 2 \UL@pixel }
        \tex_unskip:D
        \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel }
          {
            \tex_unskip:D
            \bool_if:NTF \l_@@_ulem_subtract_bool
              {
                \@@_ulem_periodic_clipped_leaders:nnn
                  { \l_@@_tmp_skip } { - \UL@pixel } { \UL@pixel }
              }
              {
                \@@_ulem_periodic_clipped_leaders:nnn
                  { \l_@@_tmp_skip } { - \UL@pixel } { - \UL@pixel }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_ulem_periodic_right_skip_penalty:
  {
    \int_set_eq:NN \l_@@_tmp_int \tex_lastpenalty:D
    \tex_unpenalty:D
    \@@_if_last_hlist:T
      { \@@_ulem_periodic_right_skip_hbox: }
    \tex_penalty:D \l_@@_tmp_int
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.8.3}{2020/04/23}{\opt{hidden} 选项保留原内容的高度和深度。}
%
% \begin{macro}{\@@_ulem_hidden_box:}
% 只画线，不输出盒子。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_hidden_box:
  {
    \tl_if_empty:NF \UL@start
      {
        \box_set_ht:Nn \l_@@_hidden_box { \box_ht:N \UL@box }
        \box_set_dp:Nn \l_@@_hidden_box { \box_dp:N \UL@box }
        \box_use:N \l_@@_hidden_box
        \xeCJK_no_break:
        \xeCJK_ulem_hskip:n { \box_wd:N \UL@box }
        \box_use:N \l_@@_hidden_box
      }
  }
\box_new:N \l_@@_hidden_box
\hbox_set:Nn \l_@@_hidden_box { }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_skip_punct_begin:,\@@_ulem_skip_punct_end:}
% 让下划线跳过标点符号的设置。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_skip_punct_begin:
  {
    \cs_set_eq:NN \UL@putbox \@@_ulem_skip_putbox:
    \cs_set_eq:NN \xeCJK_ulem_hskip:n \skip_horizontal:n
  }
\cs_new_protected:Npn \@@_ulem_skip_punct_end:
  {
    \cs_set_eq:NN \UL@putbox \@@_ulem_putbox:
    \cs_set_eq:NN \xeCJK_ulem_hskip:n \@@_ulem_hskip_aux:n
  }
\cs_new_eq:NN \@@_ulem_putbox: \UL@putbox
\cs_new_protected:Npn \@@_ulem_skip_putbox:
  {
    \tl_if_empty:NF \UL@start
      { \box_use_drop:N \UL@box }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_initial:}
% 这里的设置是为了在下划线状态下，下划线可以自动跳过全角标点符号和正确的在它们
% 前/后断行，并且与行首行末对齐。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_initial:
  {
    \@@_ulem_swap_cs:NN
    \xeCJK_FullLeft_and_Default:   \@@_ulem_FullLeft_and_Default:
    \xeCJK_FullLeft_and_CJK:       \@@_ulem_FullLeft_and_CJK:
    \xeCJK_FullLeft_and_Boundary:  \@@_ulem_FullLeft_and_Boundary:
    \xeCJK_FullRight_and_Default:  \@@_ulem_FullRight_and_Default:
    \xeCJK_FullRight_and_CJK:      \@@_ulem_FullRight_and_CJK:
    \xeCJK_FullRight_and_CJStarter: \@@_ulem_FullRight_and_CJStarter:
    \xeCJK_FullRight_and_Boundary: \@@_ulem_FullRight_and_Boundary:
    \xeCJK_CJK_and_CJK:N           \@@_ulem_CJK_and_CJK:N
    \xeCJK_CJK_and_Boundary:w      \@@_ulem_CJK_and_Boundary:w
    \xeCJK@fix@penalty             \@@_ulem_fix_penalty:
    \@@_punct_breakable_kern:n       \@@_ulem_punct_breakable_kern:n
    \@@_Default_and_FullLeft_glue:N  \@@_ulem_Default_and_FullLeft_glue:N
    \@@_Default_and_FullRight_glue:N \@@_ulem_Default_and_FullRight_glue:N
    \@@_CJK_and_FullLeft_glue:N      \@@_ulem_CJK_and_FullLeft_glue:N
    \@@_CJK_and_FullRight_glue:N     \@@_ulem_CJK_and_FullRight_glue:N
    \@@_Boundary_and_FullLeft_glue:N \@@_ulem_Boundary_and_FullLeft_glue:N
    \q_recursion_tail \q_nil \q_recursion_stop
    \seq_map_inline:Nn \g_@@_CJK_sub_class_seq
      {
        \seq_map_inline:Nn \g_@@_CJK_sub_class_seq
          {
            \str_if_eq:nnTF {##1} {####1}
              {
                \xeCJK_inter_class_toks:nnn { CJK } { CJK/##1 }
                  { \@@_ulem_between_CJK_blocks:nnN { CJK } {##1} }
                \xeCJK_inter_class_toks:nnn { CJK/##1 } { CJK/##1 }
                  { \@@_ulem_between_CJK_blocks:nnN { CJK } {##1} }
              }
              {
                \xeCJK_inter_class_toks:nnn { CJK/##1 } { CJK/####1 }
                  { \@@_ulem_between_CJK_blocks:nnN {##1} {####1} }
              }
          }
      }
  }
\cs_new_protected:Npn \@@_ulem_swap_cs:NN #1#2
  {
    \quark_if_recursion_tail_stop:N #1
    \xeCJK_swap_cs:NN #1#2
    \@@_ulem_swap_cs:NN
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.1.2}{2012/12/27}{解决在下划线状态下使用 \tn{makebox} 时的错误。}
%
% \begin{macro}[int]{\xeCJK_if_ulem_patch:TF}
% 在下划线状态下，\pkg{ulem} 宏包在数学模式或者盒子中使用 \tn{UL@hrest} 恢复
% \verb*|\ | 等的定义，此时不需要使用 \tn{UL@stop} 和 \tn{UL@start} 来断开下划线而
% 产生断点。
%    \begin{macrocode}
\cs_new:Npn \xeCJK_if_ulem_patch:TF
  {
    \if_meaning:w \  \LA@space
      \exp_after:wN \use_ii:nn
    \else:
      \exp_after:wN \use_i:nn
    \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.9.1}{2022/07/28}{修复下划线中数学公式的错误处理。}
%
% \begin{macro}{\@@_ulem_CJK_and_Boundary:w}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_CJK_and_Boundary:w
  {
    \xeCJK_if_ulem_patch:TF
      {
        \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token
          { \xeCJK_class_group_end: \CJKecglue }
          {
            \bool_if:NTF \l_@@_peek_ignore_spaces_bool
              { \@@_ulem_peek_math:w }
              { \@@_ulem_group_end:n { CJK } }
          }
      }
      { \@@_ulem_CJK_and_Boundary:w }
  }
\cs_new_protected:Npn \@@_ulem_group_end:n #1
  {
    \xeCJK_class_group_end: \UL@stop
    \UL@start { \xeCJK_make_node:n {#1} }
    \xeCJK_make_group_tag:
    \@@_boundary_enable_source_space:n {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_peek_math:w}
% 用于处理下划线中，汉字与 |$| 之间有空格的情况^^A
% \footnote{\url{https://github.com/CTeX-org/ctex-kit/issues/614}}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_peek_math:w
  {
    \cs_set_eq:NN \@@_ulem_start:w \@@_ulem_exp_stop:w
    \exp_after:wN \peek_after:Nw
    \exp_after:wN \@@_ulem_peek_math_branches:w
    \exp:w \exp_end_continue_f:w
  }
\cs_new_protected:Npn \@@_ulem_peek_math_branches:w
  {
    \token_if_math_toggle:NTF \l_peek_token
      { \xeCJK_class_group_end: \CJKecglue }
      { \@@_ulem_group_end:n { CJK-space } }
  }
\cs_new_protected:Npn \@@_ulem_exp_stop:w
  {
    \cs_if_eq:NNTF \UL@start \@empty
      { \exp_after:wN \exp_stop_f: }
      { \exp_after:wN \UL@start }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_fix_penalty:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_fix_penalty:
  {
    \xeCJK_if_ulem_patch:TF
      { \fix@penalty }
      { \@@_ulem_fix_penalty: }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.10.0}{2026/04/28}{在下划线内 CJK 分组切换时保存/恢复字体状态（\#465）。}
%
% \begin{macro}{\@@_ulem_save_font_state:,\@@_ulem_restore_font_state:}
% \begin{variable}{\g_@@_ulem_saved_series_tl,\g_@@_ulem_saved_shape_tl,
%   \g_@@_ulem_saved_CJK_family_tl,\g_@@_ulem_saved_CJK_fam_tl}
% 在 \cs{xeCJK_class_group_end:} 前保存字体状态，在
% \cs{@@_ulem_class_group_begin:} 后恢复，使 \tn{bfseries}、
% \tn{sffamily} 等字体声明能跨越下划线内的 CJK 分组边界。
%    \begin{macrocode}
\tl_new:N \g_@@_ulem_saved_series_tl
\tl_new:N \g_@@_ulem_saved_shape_tl
\tl_new:N \g_@@_ulem_saved_CJK_family_tl
\tl_new:N \g_@@_ulem_saved_CJK_fam_tl
\cs_new_protected:Npn \@@_ulem_save_font_state:
  {
    \tl_gset:Ne \g_@@_ulem_saved_series_tl { \f@series }
    \tl_gset:Ne \g_@@_ulem_saved_shape_tl  { \f@shape }
    \tl_gset_eq:NN \g_@@_ulem_saved_CJK_family_tl \l_xeCJK_family_tl
    \tl_gset_eq:NN \g_@@_ulem_saved_CJK_fam_tl \CJK@family
  }
\cs_new_protected:Npn \@@_ulem_restore_font_state:
  {
    \tl_set:Ne \f@series { \g_@@_ulem_saved_series_tl }
    \tl_set:Ne \f@shape  { \g_@@_ulem_saved_shape_tl  }
    \tl_set_eq:NN \l_xeCJK_family_tl \g_@@_ulem_saved_CJK_family_tl
    \tl_set_eq:NN \CJK@family \g_@@_ulem_saved_CJK_fam_tl
  }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\@@_ulem_CJK_and_CJK:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_CJK_and_CJK:N
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_ulem_save_font_state:
        \xeCJK_class_group_end:
        \UL@stop \@@_ulem_ccglue: \UL@start
        \@@_ulem_class_group_begin:
        \@@_ulem_restore_font_state:
        \xeCJK_select_font:
        \xeCJK_fallback_symbol:NN
        \CJKsymbol
      }
      { \@@_ulem_CJK_and_CJK:N }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_class_group_begin:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_class_group_begin:
  {
    \xeCJK_class_group_begin:
    \xeCJK_clear_Boundary_and_CJK_toks:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_between_CJK_blocks:nnN}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_between_CJK_blocks:nnN #1#2
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_ulem_save_font_state:
        \xeCJK_class_group_end:
        \UL@stop \@@_ulem_ccglue: \UL@start
        \xeCJK_class_group_begin:
        \xeCJK_clear_Boundary_and_CJK_toks:
        \@@_ulem_restore_font_state:
        \@@_switch_font:nn {#1} {#2}
        \xeCJK_fallback_symbol:NN
        \CJKsymbol
      }
      {
        \skip_horizontal:N \l_@@_ccglue_skip
        \@@_switch_font:nn {#1} {#2}
        \xeCJK_fallback_symbol:NN
        \CJKsymbol
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_Default_and_FullLeft_glue:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_Default_and_FullLeft_glue:N #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \UL@stop
        \@@_ulem_skip_punct_begin:
        \@@_punct_glue:NN \c_@@_left_tl #1
        \UL@start
      }
      { \@@_ulem_Default_and_FullLeft_glue:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_Boundary_and_FullLeft_glue:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_Boundary_and_FullLeft_glue:N #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \UL@stop
        \@@_ulem_skip_punct_begin:
        \@@_punct_glue:NN \c_@@_left_tl #1
        \UL@start
      }
      { \@@_ulem_Boundary_and_FullLeft_glue:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_CJK_and_FullLeft_glue:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_CJK_and_FullLeft_glue:N #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \xeCJK_class_group_end:
        \UL@stop
        \@@_ulem_skip_punct_begin:
        \@@_ulem_punct_ccglue:
        \@@_punct_glue:NN \c_@@_left_tl #1
        \UL@start
        \@@_ulem_class_group_begin:
        \xeCJK_select_punct_font:
      }
      { \@@_ulem_CJK_and_FullLeft_glue:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_Default_and_FullRight_glue:N}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_Default_and_FullRight_glue:N #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \UL@stop
        \@@_ulem_skip_punct_begin:
        \@@_punct_if_long:NTF #1
          { \xeCJK_allow_break: }
          { \xeCJK_no_break: }
        \@@_punct_if_middle:NT #1
          {
            \@@_punct_glue:NN \c_@@_right_tl #1
            \@@_punct_rule:NN \c_@@_left_tl #1
          }
        \UL@start
      }
      { \@@_ulem_Default_and_FullRight_glue:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_CJK_and_FullRight_glue:N}
% \changes{v3.2.2}{2013/05/30}{修正下划线不能跳过全角右标点的问题。}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_CJK_and_FullRight_glue:N #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \xeCJK_class_group_end:
        \UL@stop
        \@@_ulem_skip_punct_begin:
        \@@_punct_if_long:NTF #1
          { \xeCJK_allow_break: }
          { \xeCJK_no_break: }
        \@@_punct_if_middle:NT #1
          {
            \@@_ulem_punct_ccglue:
            \@@_punct_glue:NN \c_@@_right_tl #1
            \@@_punct_rule:NN \c_@@_left_tl #1
          }
        \UL@start
        \@@_ulem_class_group_begin:
        \xeCJK_select_punct_font:
      }
      { \@@_ulem_CJK_and_FullRight_glue:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullLeft_and_Default:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullLeft_and_Default:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_if_middle:NTF \g_@@_last_punct_tl
          {
            \xeCJK_get_punct_bounds:No \c_@@_left_tl \g_@@_last_punct_tl
            \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
            \xeCJK_class_group_end: \UL@stop \xeCJK_no_break:
            \@@_punct_glue:NN \c_@@_left_tl  \g_@@_last_punct_tl
          }
          { \xeCJK_class_group_end: \UL@stop }
        \@@_ulem_skip_punct_end:
        \xeCJK_no_break:
        \UL@start
      }
      { \@@_ulem_FullLeft_and_Default: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullLeft_and_Boundary:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullLeft_and_Boundary:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_if_middle:NTF \g_@@_last_punct_tl
          {
            \xeCJK_get_punct_bounds:No \c_@@_left_tl \g_@@_last_punct_tl
            \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
            \xeCJK_class_group_end: \UL@stop \xeCJK_no_break:
            \@@_punct_glue:NN \c_@@_left_tl  \g_@@_last_punct_tl
          }
          { \xeCJK_class_group_end: \UL@stop }
        \@@_ulem_skip_punct_end:
        \xeCJK_no_break:
        \UL@start
        \tex_ignorespaces:D
      }
      { \@@_ulem_FullLeft_and_Boundary: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullLeft_and_CJK:}
% \changes{v3.2.3}{2013/06/04}
% {修正全角左标点后下划线与 \tn{CJKunderdot} 连用时结果不正常的问题。}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullLeft_and_CJK:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \xeCJK_FullLeft_and_Default:
        \@@_ulem_class_group_begin:
        \xeCJK_select_font:
      }
      { \@@_ulem_FullLeft_and_CJK: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullRight_and_Default:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullRight_and_Default:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end:
        \UL@stop
        \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
        \@@_ulem_skip_punct_end:
        \UL@start
      }
      { \@@_ulem_FullRight_and_Default: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullRight_and_Boundary:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullRight_and_Boundary:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end:
        \UL@stop
        \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
        \@@_ulem_skip_punct_end:
        \UL@start
        \tex_ignorespaces:D
      }
      { \@@_ulem_FullRight_and_Boundary: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullRight_and_CJK:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullRight_and_CJK:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end:
        \UL@stop
        \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
        \@@_ulem_punct_ccglue:
        \@@_ulem_skip_punct_end:
        \UL@start
        \@@_ulem_class_group_begin:
        \xeCJK_select_font:
      }
      { \@@_ulem_FullRight_and_CJK: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_FullRight_and_CJStarter:}
% 在全角右标点与严格行首禁则字符之间，先于标点胶插入禁则，并保持
% \pkg{xeCJKfntef} 对标点胶和 CJK 字距的下划线处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_FullRight_and_CJStarter:
  {
    \xeCJK_if_ulem_patch:TF
      {
        \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl
        \xeCJK_class_group_end:
        \UL@stop
        \xeCJK_no_break:
        \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl
        \@@_ulem_punct_ccglue:
        \@@_ulem_skip_punct_end:
        \UL@start
        \@@_ulem_class_group_begin:
        \xeCJK_select_font:
      }
      { \@@_ulem_FullRight_and_CJStarter: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_punct_hskip:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_punct_hskip:n
  {
    \xeCJK_if_ulem_patch:TF
      { \xeCJK_ulem_hskip:n }
      { \@@_ulem_punct_hskip:n }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_punct_breakable_kern:n}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_punct_breakable_kern:n #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \xeCJK_class_group_end:
        \UL@stop \xeCJK_ulem_hskip:n {#1} \UL@start
        \@@_ulem_class_group_begin:
        \xeCJK_select_punct_font:
      }
      { \@@_ulem_punct_breakable_kern:n {#1} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_ulem_glue:n,\@@_ulem_ccglue:,\@@_ulem_punct_ccglue:}
% 在下划线状态下的分别代替 \tn{CJKglue} 等。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_ulem_glue:n #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \tl_if_empty:NTF \l_@@_group_tag_tl
          { \UL@stop \@@_ulem_hskip:n {#1} \UL@start }
          {
            \str_if_eq:eeTF { \l_@@_group_tag_tl } { \c_@@_group_tag_tl }
              { \UL@stop \@@_ulem_hskip:n {#1} \UL@start }
              { \@@_ulem_defer_glue:n {#1} }
          }
      }
      { \skip_horizontal:n {#1} }
  }
%    \end{macrocode}
%
% \changes{v3.10.5}{2026/07/29}{\tn{CJKecglue} 补在源码空格位置时改用可搬运
%   通道，避免收缩量固化在 \pkg{ulem} 的定宽片段盒内（\#1037）。}
% 改写 \cs{@@_use_ecglue_skip:}（默认实现在 \pkg{xeCJK} 主体里是普通
% \tn{hskip}），让「源码空格 + 西文词」处补的 \tn{CJKecglue} 经
% \cs{@@_ulem_glue:n} 落到外层列表，收缩量可参与断行（\#1037）。
%
% 必须先用 \cs{l_@@_ulem_stream_started_bool} 判断当前确实在装饰的 stream 里，
% 不能只依赖 \cs{@@_ulem_glue:n} 自带的 \cs{xeCJK_if_ulem_patch:TF}。后者的判据
% 只是 \tn{ }\ 的含义是否等于 \pkg{ulem} 保存的 \tn{LA@space}，它能识别
% \pkg{ulem} 自己造成的变化，却无法区分「不在装饰中」与「在装饰外但 \tn{ }\ 被
% 别的宏包改过定义」。\cs{@@_check_for_ecglue_aux:} 是所有中西文边界都会走的
% 通用路径，一旦在装饰外取到真分支，就会在没有 \tn{UL@box} 打开的列表里执行
% \tn{UL@stop}，导致 \texttt{Too many~\}'s}。\pkg{nath}、\pkg{morehype} 等重定义
% \tn{ }\ 的宏包会让不含任何装饰命令的 \verb|中 abc 文| 直接报错。
%
% 该布尔由 \cs{@@_ulem_stream_begin:} 置真、\cs{@@_ulem_end:} 置假。
%
% 但它单独还不够：行内公式里的装饰命令经 \tn{UL@onmath}／\tn{UL@onin} 结束，不走
% \cs{@@_ulem_end:}，因此同一个公式内装饰命令之后布尔仍为真，而片段盒其实已经
% 关闭。此时若再叠加 \tn{ }\ 被重定义，仍会在没有 \tn{UL@box} 打开的列表里执行
% \tn{UL@stop}——例如 \verb|$\CJKunderline{中}\mbox{中 abc 文}$| 配 \pkg{nath}。
% 因此还要确认片段盒确实打开：\tn{UL@start} 只在 \pkg{ulem} 打开片段盒后才被
% \tn{let} 成 \tn{@empty}（\pkg{ulem} 的 \tn{UL@start} 定义末尾），关闭后不再是它。
% \cs{@@_ulem_exp_stop:w} 已用同一判断表示这件事。
%
% 两个条件都成立才走 ulem 通道，否则退化为主体的默认实现。
%    \begin{macrocode}
\cs_gset_protected:Npn \@@_use_ecglue_skip:
  {
    \bool_lazy_and:nnTF
      { \l_@@_ulem_stream_started_bool }
      { \cs_if_eq_p:NN \UL@start \@empty }
      { \@@_ulem_glue:n { \l_@@_ecglue_skip } }
      { \skip_horizontal:N \l_@@_ecglue_skip }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.10.6}{2026/08/11}{花括号分组内的间距改用「盒内 kern 占位加词尾补伸缩」的
%   方式输出，使 \tn{CJKecglue} 的收缩量与 \tn{CJKglue} 的伸长量都回到外层列表（\#1067）。}
%
% \begin{macro}[int]{\@@_ulem_defer_glue:n, \@@_ulem_flush_pending_shrink:}
% 这两个函数处理 \#1067：装饰正文里用花括号括住一段内容时，该处间距的可收缩量原先会失效。
%
% 成因不在 \pkg{ulem} 的切词——实测两种写法切出的词相同，只差一对花括号。花括号是在词内容
% 交给 \tn{UL@start} 之后、在\emph{片段盒内部}才展开成分组的，于是边界检测在盒内、且在用户
% 分组内触发；\cs{@@_ulem_glue:n} 的 group tag 守卫比对保存的 \texttt{T1L4} 与当前的
% \texttt{T1L5} 不相等，走 else 分支。该分支原先是裸 \cs{skip_horizontal:n}，间距连收缩量
% 一起固化在定宽片段盒内，外层取不到。
%
% 不能改成「照样搬运」：实测强制走 \tn{UL@stop} 之后，搬出来的 glue 落进 \tn{cleaders}
% 内部、仍在盒内——在用户分组内 \tn{UL@stop} 关不掉正确的层级（\pkg{ulem} 开盒时开了两层，
% 用户花括号插在中间），\tn{UL@start} 重开的盒子又把它包了回去。而且绕过守卫会让分组内的
% 字体设置丢失（\file{fntef-font01} 即此，字体从楷体退回宋体）。也就是说「守卫是直接原因」
% 与「绕过守卫无效」两件事同时成立，修法必须绕开这个结构，而不是拆掉守卫。
%
% 因此改为把一处间距拆成两半输出：
%
% \begin{itemize}
%   \item 盒内放一个\emph{不可伸缩}的 \tn{kern}，占住自然宽度，使排版位置与盒子总宽都不变；
%   \item 把伸缩量记进全局的 \cs{g_@@_ulem_pending_shrink_skip}，等到 \cs{@@_ulem_loop:nw}
%     的词尾搬运处——那里已经在片段盒外、用户分组外——再补一个零宽而带伸缩的 glue。
% \end{itemize}
%
% 短路条件是\emph{整个 skip 全零}（\cs{skip_if_eq:nnTF} 比对 \cs{c_zero_skip}），不是「自然
% 宽度为零」。零自然宽度但带弹性的间距仍然要记账搬运——用户可以把间距设成
% \verb|CJKecglue={\hskip 0pt minus 1pt}|，此时自然宽度是零而收缩量非零，若按自然宽度短路，
% 这份收缩量就留在定宽片段盒里出不来（实测压窄 \texttt{1.2pt}：直接输入与无编组装饰都是
% \texttt{18}，而分组形态是 \texttt{1000000}）。
%
% 这条短路只用来跳过真正什么都不做的情形，避免为零 glue 白记一笔账。
%
% 于是 \tn{CJKglue}（\texttt{0pt plus 0.96}，只有伸长量）也走记账通道，它的伸长量随之回到
% 外层列表。这是有意的：分组内的伸长量因此同样能参与断行，与不带装饰时的行为更接近。实测
% \verb|\CJKunderline{文字{\sffamily 文字}}| 拉伸 \texttt{3pt} 的 badness 从 \texttt{3049}
% 降到 \texttt{381}，向无装饰形态的 \texttt{113} 靠拢，而盒子宽度不变（\texttt{40.0pt}）。
% \file{fntef-font01} 的基线因此记录 \tn{kern} 而不再是原处的 \tn{glue}。
%
% 取弹性部分用整个 skip 减去自然宽度（\cs{skip_eval:n} 里的 \texttt{\#1 - \cs{dim_eval:n}
% \{\#1\}}），\emph{不要}分别取 \tn{gluestretch} 与 \tn{glueshrink} 再用 \texttt{plus}／
% \texttt{minus} 重新拼：那两个原语只返回数值，阶数在 \tn{gluestretchorder}／
% \tn{glueshrinkorder} 里，分开取会把 \texttt{1fil} 降成普通阶（实测
% \texttt{3.33pt plus 1fil minus 1.11pt} 用减法得到 \texttt{0.0pt plus 1.0fil minus 1.11pt}，
% 阶数保留；而 \tn{gluestretch} 只给出 \texttt{1.0pt}）。就目前的调用路径而言这两种写法
% 结果相同——装饰状态下 \cs{l_@@_ecglue_skip} 由 \cs{xeCJK_glue_to_skip:nN} 现算，传进来的
% 自然宽度已是零、会走上面那条短路——但减法写法不依赖这个前提，日后间距模板换成带无限阶
% 伸缩的形式也不会静默降阶。
%
% 记账是全局量，因此在两处清零：\cs{@@_ulem_stream_begin:} 进入装饰时清一次，
% \cs{@@_ulem_end:} 结束时再清一次。两处都要，不是重复——结束处那次覆盖正常路径，而行内
% 公式里的装饰命令经 \tn{UL@onmath}／\tn{UL@onin} 结束、\emph{不走} \cs{@@_ulem_end:}，
% 那条路径上的残留只能靠下一次装饰开始时清掉。实测目前四条路径（公式内带编组、公式内
% \tn{mbox}、\tn{mbox} 内装饰、装饰嵌套）的残留都是 \texttt{0pt}，因为记账在词尾都已兑现；
% 开始处这道清零是防御性的，用来隔断将来改动公式路径时可能出现的串味。
%
% 补出的 glue 必须放在 \tn{UL@stop} 之后、\tn{UL@leaders} \emph{之前}。\tn{UL@leaders} 的
% 结构是「\texttt{-\tn{UL@pixel}}、加宽两个 pixel 的 leaders、再 \texttt{-\tn{UL@pixel}}」，
% 末尾那个负值是段与段之间的重叠补偿，要靠紧随其后的内容抵掉。如果把补出的 glue 放在
% \tn{UL@leaders} 之后，编组位于装饰参数\emph{结尾}时它就会把这个负值推到更后面，使其失去
% 正向配对，装饰盒因此比实际内容窄一个 pixel（实测 \verb|\CJKunderline{中{x}}| 得
% \texttt{17.61002pt}，而 \verb|\hbox{中{x}}| 是 \texttt{18.61pt}）。放在 \tn{UL@leaders}
% 之前，glue 落在两段装饰之间，两种位置都正确。\file{fntef-shrink01} 的 TEST 11 同时固定
% 「编组在中间」与「编组在结尾」两种位置，正是因为前者察觉不到这个回归。
%    \begin{macrocode}
\skip_new:N \g_@@_ulem_pending_shrink_skip
\cs_new_protected:Npn \@@_ulem_defer_glue:n #1
  {
    \skip_if_eq:nnTF { #1 } { \c_zero_skip }
      { \skip_horizontal:n {#1} }
      {
        \skip_gadd:Nn \g_@@_ulem_pending_shrink_skip
          { \skip_eval:n { #1 - \dim_eval:n {#1} } }
        \tex_kern:D \dim_eval:n {#1} \scan_stop:
      }
  }
\cs_new_protected:Npn \@@_ulem_flush_pending_shrink:
  {
    \skip_if_eq:nnF { \g_@@_ulem_pending_shrink_skip } { \c_zero_skip }
      {
        \skip_horizontal:N \g_@@_ulem_pending_shrink_skip
        \skip_gzero:N \g_@@_ulem_pending_shrink_skip
      }
  }
\cs_new_protected:Npn \xeCJK_make_group_tag:
  { \tl_set:Ne \l_@@_group_tag_tl { \c_@@_group_tag_tl } }
\tl_new:N \l_@@_group_tag_tl
\tl_const:Nn \c_@@_group_tag_tl
  {
    T \int_use:N \tex_currentgrouptype:D
    L \int_use:N \tex_currentgrouplevel:D
  }
\cs_new_protected:Npn \@@_ulem_ccglue:
  { { \skip_set_eq:NN \UL@skip \l_@@_ccglue_skip \UL@leaders } }
\cs_new_protected:Npn \@@_ulem_punct_ccglue:
  { \@@_punct_hskip:n { \l_@@_ccglue_skip } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{
%   \@@_ulem_stream_begin:,
%   \@@_boundary_ulem_math_tail_space:nnn,
%   \@@_boundary_if_ulem_math_reorder:n,
%   \xeCJK_ulem_group_begin:,
%   \xeCJK_ulem_group_end:,
%   \xeCJK_ulem_on:n}
% \changes{v3.10.5}{2026/07/28}{正文一律以字面记号进入 \pkg{ulem} 的参数：
%   只有“公式加尾随源码空格”才重排，且重排也先拼装再一次展开，避免西文词
%   右侧的 \tn{CJKecglue} 被关进固定宽度装饰片段盒而丢失收缩量（\#1026）。}
% \changes{v3.10.5}{2026/08/05}{在用户手册说明线型命令相互嵌套时被嵌套的
%   一段无法断行，并区分线型与符号型命令、给出替代写法；同时以「能否断行」
%   为可观察量给 \tn{UL@onin} 路径补上回归保护（\#1057）。}
% \changes{v3.10.4}{2026/07/20}{将 xeCJKfntef 线型命令、原生
%   \pkg{ulem} 入口（如 \tn{uline}）、\tn{xeCJKfntefon} 与独立符号命令
%   接入 stream capture，并让内部 pending 通过 framework 的统一辅助函数
%   发布；删除 fntef 专用末状态恢复和 pending 设置（\#992）。}
% \changes{v3.10.4}{2026/07/20}{让嵌套的 \pkg{ulem}/xeCJKfntef
%   线型命令只由最外层启动 stream capture，修复 capture 栈逐次累积的
%   状态泄漏（\#992）。}
% 线型命令的正文参数由 ulem 自己扫描，不能使用普通 cmd after hook。
% ulem 会把正文拆进固定宽度盒子；framework 算出的左侧 glue 因此通过
% \cs{@@_boundary_use_ulem_glue:n} 转交 ulem 的 \tn{UL@stop}/\tn{UL@start}
% 外层通道，保留弹性和断行位置且不把间距画上线。本包的线型命令在
% 装饰符号测量前启动 stream；原生 \tn{uline} 等命令和
% \tn{xeCJKfntefon} 则由共有的 \tn{ULon} 补上同一入口。局部布尔避免
% 本包命令在到达 \tn{ULon} 时重复启动。原生 ulem 命令嵌套本包线型命令
% 时，内层会走 ulem 的 \tn{UL@onin} 路径而不会另行调用
% \cs{@@_ulem_end:}；因此 group 入口与 \tn{ULon} 必须共用同一个“仅最外层
% 启动”辅助函数。所有嵌套层复用外层 stream，最外层唯一的
% \cs{@@_ulem_end:} 把真实末尾 marker 移到外层列表后结束并清除布尔。
% \tn{UL@on}/\tn{UL@onin} 在把正文交给 ulem 扫描前检查其单参数正文是否以
% 字面数学切换符开始；独立符号命令在
% \cs{@@_under_symbol_auxii:nnnnnn} 的对应两端使用同一 begin/end 和公式入口。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_boundary_use_ulem_glue:n #1
  {
    \xeCJK_if_ulem_patch:TF
      {
        \tl_if_empty:NTF \l_@@_group_tag_tl
          { \UL@stop \skip_horizontal:n {#1} \UL@start }
          {
            \str_if_eq:eeTF { \l_@@_group_tag_tl } { \c_@@_group_tag_tl }
              { \UL@stop \skip_horizontal:n {#1} \UL@start }
              { \skip_horizontal:n {#1} }
          }
      }
      { \skip_horizontal:n {#1} }
  }
\cs_new_protected:Npn \@@_ulem_stream_begin:
  {
    \bool_if:NF \l_@@_ulem_stream_started_bool
      {
        \bool_set_true:N \l_@@_ulem_stream_started_bool
        \skip_gzero:N \g_@@_ulem_pending_shrink_skip
        \@@_boundary_inline_stream_begin:nn { auto } { stream-ulem }
      }
  }
\cs_new_protected:Npn \xeCJK_ulem_group_begin:
  {
    \mode_leave_vertical:
    \@@_ulem_stream_begin:
    \c_group_begin_token
  }
\cs_new_protected:Npn \xeCJK_ulem_group_end:
  { \c_group_end_token }
\cs_new_protected:Npn \xeCJK_ulem_on:n
  { \ULon }
\cs_new_eq:NN \@@_orig_ulem_on: \ULon
\cs_set_protected:Npn \ULon
  {
    \@@_ulem_stream_begin:
    \@@_orig_ulem_on:
  }
\bool_new:N \l_@@_ulem_stream_started_bool
%    \end{macrocode}
% ulem 会把源码空格改造成 leaders；等到参数末尾再检查时，公式节点已经被
% 装饰节点遮住。若正文语法确实是“公式+尾随源码空格”，先让 ulem 排完
% 去掉尾随空格的正文，在公式节点仍可见时确认公式末尾，随后把一枚空格
% 交还 ulem 排版；capture 结束时会将它标记为冻结空格。未知宏若消费公式，
% 实际末节点检查仍会失败。
%
% 无论走哪条路径，正文都必须以字面记号进入 \tn{UL@on} 的替换文本，不能再经过
% 一层宏参数间接展开：\pkg{ulem} 自己扫描正文并按源码空格切分片段，正文若作为
% 另一个宏的参数被重新扫描，西文词右侧由边界恢复链补出的 \tn{CJKecglue} 会落在
% 固定宽度的装饰片段盒内部，其收缩量被盒子固化而无法参与断行，整段可收缩量
% 因此按西文词数减少（\#1026）。
%
% 重排路径同样受这条约束。这里先把去掉尾随空格的正文和两端的固定记号拼成
% 完整的 ulem 参数，再用 \cs{exp_args:NV} 把它一次展开到参数位置，使记号与
% 直接书写 \texttt{\#1} 等价；不能改用 \cs{tl_use:N}，那会让正文晚一层展开而
% 重新触发上述问题。
% \tn{UL@on} 需要两端的 \cs{xeCJK_ulem_left:}／\cs{xeCJK_ulem_right:}，
% \tn{UL@onin} 由 \pkg{ulem} 自己的 \tn{UL@onin} 负责这两端，因此拼装时把它们
% 作为前后缀参数传入，不写死在 body 里。
%    \begin{macrocode}
\tl_new:N \l_@@_ulem_body_tl
\cs_new_protected:Npn \@@_boundary_ulem_math_tail_space:nnn #1#2#3
  {
    \tl_set:Nn \l_@@_ulem_body_tl {#2}
    \tl_put_right:Ne \l_@@_ulem_body_tl
      { \tl_trim_right_spaces:n {#1} }
    \tl_put_right:Nn \l_@@_ulem_body_tl
      {
        \@@_boundary_if_last_math_node:T
          { \@@_boundary_math_end_confirm_space_visible: }
        \c_space_tl
      }
    \tl_put_right:Nn \l_@@_ulem_body_tl {#3}
  }
\prg_new_protected_conditional:Npnn \@@_boundary_if_ulem_math_reorder:n #1
  { T , F , TF }
  {
    \@@_boundary_if_math_tail:nTF {#1}
      {
        \@@_boundary_if_math_tail_space:nTF {#1}
          { \prg_return_true: }
          { \prg_return_false: }
      }
      { \prg_return_false: }
  }
\cs_new_eq:NN \@@_ulem_on:n \UL@on
\cs_set_protected:Npn \UL@on #1
  {
    \@@_boundary_math_begin:n {#1}
    \@@_boundary_if_ulem_math_reorder:nTF {#1}
      {
        \@@_boundary_ulem_math_tail_space:nnn {#1}
          { \xeCJK_ulem_left: } { \xeCJK_ulem_right: }
        \exp_args:NV \@@_ulem_on:n \l_@@_ulem_body_tl
      }
      {
        \@@_ulem_on:n
          {
            \xeCJK_ulem_left: #1
            \@@_boundary_math_end:n {#1}
            \xeCJK_ulem_right:
          }
      }
  }
\cs_new_eq:NN \@@_orig_ulem_onin:n \UL@onin
\cs_set_protected:Npn \UL@onin #1
  {
    \@@_boundary_math_begin:n {#1}
    \@@_boundary_if_ulem_math_reorder:nTF {#1}
      {
        \@@_boundary_ulem_math_tail_space:nnn {#1} { } { }
        \exp_args:NV \@@_orig_ulem_onin:n \l_@@_ulem_body_tl
      }
      { \@@_orig_ulem_onin:n { #1 \@@_boundary_math_end:n {#1} } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\xeCJKfntefon}
% \changes{v3.2.15}{2014/11/07}{完善选项。}
% 扩展 \tn{ULon} 的参数。
%    \begin{macrocode}
\NewDocumentCommand \xeCJKfntefon { s t- s o }
  {
    \mode_leave_vertical:
    \xeCJK_ulem_boot:NNNn #1#2#3 {#4}
    \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKunderline}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使下划线相对正文
%   居中，避免起始位置改变时产生水平偏移（\#531）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderline { s t- s o }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_fntef_boot:nnNNNn { underline } { uline } #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_cleaders:D
      \xeCJK_fntef_initial:nnn
        { \l_@@_uline_depth_tl }
        { \l_@@_uline_sep_tl }
        {
          \l_@@_uline_format_tl
          \tex_vrule:D
            height \dim_eval:n { \l_@@_uline_thickness_tl }
            depth \c_zero_dim
            width .2em
        }
      \xeCJK_ulem_on:n
  }
\NewDocumentCommand \varCJKunderline { }
  { \CJKunderline - }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\@@_fntef_wave_symbol:,\@@_fntef_xout_symbol:}
% \begin{variable}{\l_@@_fntef_pattern_dim}
% \changes{v3.10.5}{2026/07/27}{波浪线和斜删除线改用随字号缩放的
%   四分之一字宽绘图单元和普通 \tn{leaders}；带减号形式在首尾各
%   内缩半个周期，避免相邻命令连在一起（\#1012）。}
% 默认波浪符号来自固定的 \texttt{lasy6} 字号，斜删除线来自当前数学字体；
% 两者的宽度与正文的字号和全角字宽没有稳定比例。\pkg{ulem} 又会在每个正文
% 片段中重新排列装饰盒子，因此固定宽度会让余量随字号、内容和伸缩间距改变。
%
% 这里用 \pkg{l3draw} 生成宽度为 \texttt{1em/4} 的完整波浪周期和斜线，
% 常规全角字符片段每字约容纳四个单元。两个默认符号都使用普通
% \tn{leaders}。正文片段、可伸缩的 \tn{CJKglue} 和换行前后的片段虽然由
% \pkg{ulem} 分开排出，却仍以同一行的起点为相位基准，因而不会在接点重新
% 分配余量；胶水仍保留原来的伸缩和断行能力。
%
% 普通形式保留 \pkg{ulem} 的既有几何，即在正文左右各外伸半个图案周期。
% 带减号形式则在正文左右各内缩半个周期；两个相邻命令因此留下一个完整
% 周期的断口，同时不改变正文宽度。一般显式水平跳距仍按 \pkg{ulem} 的原有
% 语义绘制装饰。绘图尺寸全部相对当前 \texttt{em} 计算，因而会随字号缩放；
% 斜删除线覆盖基线以下约 \texttt{.09em} 到基线以上约 \texttt{.84em}，与常见
% 全角汉字的深度和高度相符，也不再依赖数学字体。用户通过
% \opt{underwave/symbol} 指定的符号
% 继续使用原来的 \tn{xleaders}；任意自定义符号的周期、尺寸和接点不属于这里
% 的默认图案处理范围。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_fntef_wave_symbol:
  {
    \dim_set:Nn \l_@@_fntef_pattern_dim { 1 em / 4 }
    \hbox_to_wd:nn { \l_@@_fntef_pattern_dim }
      {
        \hss
        \draw_begin:
          \draw_set_linewidth:n { .04 em }
          \draw_set_cap_butt:
          \draw_path_moveto:n { 0 pt , .055 em }
          \draw_path_curveto:nnn
            { \l_@@_fntef_pattern_dim * 3 / 16 ,  .055 em }
            { \l_@@_fntef_pattern_dim * 5 / 16 , -.055 em }
            { \l_@@_fntef_pattern_dim / 2 , -.055 em }
          \draw_path_curveto:nnn
            { \l_@@_fntef_pattern_dim * 11 / 16 , -.055 em }
            { \l_@@_fntef_pattern_dim * 13 / 16 ,  .055 em }
            { \l_@@_fntef_pattern_dim , .055 em }
          \draw_path_use_clear:n { draw }
        \draw_end:
        \hss
      }
  }
\cs_new_protected:Npn \@@_fntef_xout_symbol:
  {
    \dim_set:Nn \l_@@_fntef_pattern_dim { 1 em / 4 }
    \hbox_to_wd:nn { \l_@@_fntef_pattern_dim }
      {
        \hss
        \draw_begin:
          \draw_set_linewidth:n { .04 em }
          \draw_set_cap_butt:
          \draw_path_moveto:n { 0 pt , -.445 em }
          \draw_path_lineto:n
            { \l_@@_fntef_pattern_dim , .445 em }
          \draw_path_use_clear:n { draw }
        \draw_end:
        \hss
      }
  }
\dim_new:N \l_@@_fntef_pattern_dim
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\CJKunderwave}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{xleaders} 使波浪线相对正文对齐，
%   同时保持字间连接连续（\#967）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderwave { s t- s o }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_fntef_boot:nnNNNn { underwave } { uwave } #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_xleaders:D
      \tl_if_eq:NnT \l_@@_uwave_symbol_tl { \@@_fntef_wave_symbol: }
        {
          \cs_set_eq:NN \ULleaders \tex_leaders:D
          \bool_set_true:N \l_@@_ulem_periodic_bool
        }
      \xeCJK_fntef_initial:nnn
        { \l_@@_uwave_depth_tl }
        { \l_@@_uwave_sep_tl }
        { \l_@@_uwave_format_tl \l_@@_uwave_symbol_tl }
      \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKunderdblline}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使双下划线相对正文居中
%   （\#967）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderdblline { s t- s o }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_fntef_boot:nnNNNn { underdblline } { udbline } #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_cleaders:D
      \xeCJK_fntef_initial:nnn
        { \l_@@_udbline_depth_tl }
        { \l_@@_udbline_sep_tl }
        {
          \l_@@_udbline_format_tl
          \vbox_top:n
            {
              \tex_hrule:D
                height \dim_eval:n { \l_@@_udbline_thickness_tl }
                depth \c_zero_dim
                width .2em
              \tex_kern:D \dim_eval:n { \l_@@_udbline_gap_tl }
              \tex_hrule:D
                height \dim_eval:n { \l_@@_udbline_thickness_tl }
                depth \c_zero_dim
                width .2em
            }
        }
      \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKsout}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使删除线相对正文居中
%   （\#967）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKsout { s t- s o }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_fntef_boot:nnNNNn { sout } { sout } #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_cleaders:D
      \xeCJK_fntef_initial:nn
        {
          \l_@@_sout_format_tl
          \tex_vrule:D
            height \dim_eval:n { \l_@@_sout_thickness_tl }
            depth \c_zero_dim
            width .2em
        }
        {
          \box_move_up:nn
            { \l_@@_sout_height_tl - \box_ht:N \l_@@_fntef_box / 2 }
            { \box_use:N \l_@@_fntef_box }
        }
      \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKxout}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使斜删除线相对正文居中
%   （\#967）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKxout { s t- s o }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_fntef_boot:nnNNNn { xout } { xout } #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_leaders:D
      \xeCJK_fntef_initial:nn
        {
          \l_@@_xout_format_tl
          \@@_fntef_xout_symbol:
        }
        {
          \box_move_down:nn
            { .09 em }
            { \box_use:N \l_@@_fntef_box }
        }
      \bool_set_true:N \l_@@_ulem_periodic_bool
      \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKunderanyline}
% \changes{v3.2.15}{2014/11/07}{完善选项。}
% \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使自定义线相对正文居中
%   （\#967）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderanyline { s t- s o m m }
  {
    \xeCJK_ulem_group_begin:
      \xeCJK_ulem_boot:NNNn #1#2#3 {#4}
      \cs_set_eq:NN \ULleaders \tex_cleaders:D
      \xeCJK_fntef_initial:nn
        {#6}
        {
          \box_move_down:nn
            {#5}
            { \box_use:N \l_@@_fntef_box }
        }
      \tl_if_empty:NF \l_@@_ulem_boxdepth_tl
        { \box_set_dp:Nn \ULC@box { \l_@@_ulem_boxdepth_tl } }
      \tl_if_empty:NF \l_@@_ulem_sep_tl
        {
          \bool_set_true:N \l_@@_fntef_bool
          \dim_set:Nn \l_@@_fntef_dim
            { \l_@@_ulem_sep_tl + \box_dp:N \ULC@box }
        }
      \xeCJK_ulem_on:n
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_fntef_boot:nnNNNn}
% 处理参数问题。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_fntef_boot:nnNNNn #1#2#3#4#5#6
  {
    \bool_lazy_or:nnT {#3} {#5}
      { \bool_set_false:c { l_@@_#2_skip_bool } }
    \bool_if:NT #4
      { \bool_set_true:c { l_@@_#2_subtract_bool } }
    \tl_if_novalue:nF {#6}
      { \keys_set:nn { xeCJK / options / #1 } {#6} }
    \bool_set_eq:Nc \l_@@_ulem_skip_bool { l_@@_#2_skip_bool }
    \bool_set_eq:Nc \l_@@_ulem_hidden_bool { l_@@_#2_hidden_bool }
    \bool_set_eq:Nc \l_@@_ulem_subtract_bool { l_@@_#2_subtract_bool }
    \tl_set_eq:Nc \l_@@_ulem_text_format_tl { l_@@_#2_text_format_tl }
  }
\cs_new_protected:Npn \xeCJK_ulem_boot:NNNn #1#2#3#4
  {
    \bool_lazy_or:nnT {#1} {#3}
      { \bool_set_false:N \l_@@_ulem_skip_bool }
    \bool_if:NT #2
      { \bool_set_true:N \l_@@_ulem_subtract_bool }
    \tl_if_novalue:nF {#4}
      { \keys_set:nn { xeCJK / options / ulem } {#4} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_fntef_initial:n}
% 不支持下划线的嵌套使用。下划线嵌套使用时，里层的下划线会被放在盒子里，不能折行。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_fntef_initial:n
  {
    \bool_if:NTF \l_@@_nest_bool
      { \@@_warning:n { fntef-nesting } }
      {
        \bool_set_true:N \l_@@_nest_bool
        \@@_restore_shipout_fntef:
      }
    \xeCJK_fntef_sbox:n
  }
\cs_new_protected:Npn \xeCJK_fntef_initial:nn #1
  {
    \xeCJK_fntef_initial:n {#1}
    \bool_if:NF \l_@@_fntef_bool
      { \dim_zero:N \l_@@_fntef_dim }
    \markoverwith
  }
\cs_new_protected:Npn \xeCJK_fntef_initial:nnn #1#2#3
  {
    \xeCJK_fntef_initial:n {#3}
    \bool_if:NF \l_@@_fntef_bool
      {
        \bool_set_true:N \l_@@_fntef_bool
        \dim_set:Nn \l_@@_fntef_dim {#1}
      }
    \markoverwith
      {
        \box_move_down:nn
          { \l_@@_fntef_dim + \box_ht:N \l_@@_fntef_box }
          { \box_use:N \l_@@_fntef_box }
      }
    \dim_set:Nn \l_@@_fntef_dim { #2 + \box_dp:N \ULC@box }
  }
\box_new:N \l_@@_fntef_box
\cs_new_eq:NN \xeCJKfntefbox \l_@@_fntef_box
\bool_new:N \l_@@_nest_bool
\bool_new:N \l_@@_fntef_bool
\@@_msg_new:nn { fntef-nesting }
  { Nesting~is~not~supported. }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_fntef_dim}
% 记录下划线或者下划符号的深度，以便它们嵌套使用时能自动调整好距离。
% \tn{ULdepth} 被 \pkg{ulem} 初始化为 \tn{maxdimen}。下划线嵌套时，\pkg{ulem}
% 要使用它作计算，可能会溢出。为简便起见，\cs{l_@@_fntef_dim} 与 \tn{ULdepth}
% 共用一个寄存器。
%    \begin{macrocode}
\cs_new_eq:NN \l_@@_fntef_dim \ULdepth
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[int]{\xeCJK_fntef_sbox:n}
% \changes{v3.10.0}{2026/05/13}{保存并恢复 \cs{g_@@_last_node_tl}，
%   避免 hbox 内渲染装饰符号时 interchar toks 污染全局节点标记，
%   修复 \pkg{xeCJKfntef} 与 \tn{textcolor} 组合使用时
%   多余的 \texttt{CJKecglue}。}
% \changes{v3.10.4}{2026/07/20}{改用 framework 的可嵌套暂停区隔离
%   测量装饰符号时构造的盒子，同时保存、恢复 marker 与 pending 状态（\#992）。}
% \changes{v3.10.5}{2026/07/25}{用空的 \texttt{ActualText} 标记装饰内容，
%   并在构造装饰盒时暂停 LaTeX tagging，避免波浪下划线和斜删除线使用的
%   字形进入 PDF 文本提取结果（\#1017）。}
% 与 \cs{hcoffin_set:Nn} 和 \LaTeXe{} 的 \tn{sbox} 功能类似，确保颜色的正确。
% 虽然 \texttt{coffin} 可以更方便的操作盒子，但速度要慢一点。并且，我们的需求
% 也比较简单，就不用它了。
%
% 装饰盒可能包含波浪线字符、数学斜线或者用户自定义的装饰符号，随后还会被
% \pkg{ulem} 的 leaders 反复复制。用空的 \texttt{ActualText} 包住整个装饰盒，
% 可以保留页面上的绘制结果，同时让 PDF 阅读器在复制和搜索时只看到正文。
% tagged PDF 会给盒子里的数学字符增加内层标记；若不暂停 tagging，内层标记仍会
% 使斜删除线的字符进入文本提取结果。普通文档没有 \cs{tag_suspend:n}，因此这里只
% 在 LaTeX tagging 接口存在时暂停，并在装饰内容结束后立即恢复。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_fntef_sbox:n #1
  {
    \@@_boundary_capture_suspend:
    \hbox_set:Nn \l_@@_fntef_box
      {
        \color_ensure_current:
        \BeginAccSupp { ActualText = { } }
        \cs_if_exist:NT \tag_suspend:n
          { \tag_suspend:n { xeCJKfntef } }
        #1
        \cs_if_exist:NT \tag_resume:n
          { \tag_resume:n { xeCJKfntef } }
        \EndAccSupp { }
      }
    \@@_boundary_capture_resume:
  }
%    \end{macrocode}
% \end{macro}
%
% 最合适的是用 \pkg{xtemplate} 宏包来实现，但是比较难于用 \tn{xeCJKsetup} 来统一
% 设置，所以这里还是用土办法。
%    \begin{macrocode}
\keys_define:nn { xeCJK / options }
  {
    underdot / symbol          .tl_set:N = \l_@@_udot_symbol_tl ,
    underdot / depth           .tl_set:N = \l_@@_udot_depth_tl ,
    underdot / sep             .tl_set:N = \l_@@_udot_sep_tl ,
    underdot / format          .tl_set:N = \l_@@_udot_format_tl ,
    underdot / textformat      .tl_set:N = \l_@@_udot_text_format_tl ,
    underdot / boxdepth        .tl_set:N = \l_@@_udot_boxdepth_tl ,
    symbol / sep               .tl_set:N = \l_@@_symbol_sep_tl ,
    symbol / boxdepth          .tl_set:N = \l_@@_symbol_boxdepth_tl ,
    symbol / textformat        .tl_set:N = \l_@@_symbol_text_format_tl ,
    underline / skip         .bool_set:N = \l_@@_uline_skip_bool ,
    underline / hidden       .bool_set:N = \l_@@_uline_hidden_bool ,
    underline / subtract     .bool_set:N = \l_@@_uline_subtract_bool ,
    underline / thickness      .tl_set:N = \l_@@_uline_thickness_tl ,
    underline / depth          .tl_set:N = \l_@@_uline_depth_tl ,
    underline / sep            .tl_set:N = \l_@@_uline_sep_tl ,
    underline / format         .tl_set:N = \l_@@_uline_format_tl ,
    underline / textformat     .tl_set:N = \l_@@_uline_text_format_tl ,
    underdblline / skip      .bool_set:N = \l_@@_udbline_skip_bool ,
    underdblline / hidden    .bool_set:N = \l_@@_udbline_hidden_bool ,
    underdblline / subtract  .bool_set:N = \l_@@_udbline_subtract_bool ,
    underdblline / thickness   .tl_set:N = \l_@@_udbline_thickness_tl ,
    underdblline / depth       .tl_set:N = \l_@@_udbline_depth_tl ,
    underdblline / sep         .tl_set:N = \l_@@_udbline_sep_tl ,
    underdblline / format      .tl_set:N = \l_@@_udbline_format_tl ,
    underdblline / textformat  .tl_set:N = \l_@@_udbline_text_format_tl ,
    underdblline / gap         .tl_set:N = \l_@@_udbline_gap_tl ,
    underwave / skip         .bool_set:N = \l_@@_uwave_skip_bool ,
    underwave / hidden       .bool_set:N = \l_@@_uwave_hidden_bool ,
    underwave / subtract     .bool_set:N = \l_@@_uwave_subtract_bool ,
    underwave / symbol         .tl_set:N = \l_@@_uwave_symbol_tl ,
    underwave / depth          .tl_set:N = \l_@@_uwave_depth_tl ,
    underwave / sep            .tl_set:N = \l_@@_uwave_sep_tl ,
    underwave / format         .tl_set:N = \l_@@_uwave_format_tl ,
    underwave / textformat     .tl_set:N = \l_@@_uwave_text_format_tl ,
    sout / skip              .bool_set:N = \l_@@_sout_skip_bool ,
    sout / hidden            .bool_set:N = \l_@@_sout_hidden_bool ,
    sout / subtract          .bool_set:N = \l_@@_sout_subtract_bool ,
    sout / thickness           .tl_set:N = \l_@@_sout_thickness_tl ,
    sout / height              .tl_set:N = \l_@@_sout_height_tl ,
    sout / format              .tl_set:N = \l_@@_sout_format_tl ,
    sout / textformat          .tl_set:N = \l_@@_sout_text_format_tl ,
    xout / skip              .bool_set:N = \l_@@_xout_skip_bool ,
    xout / hidden            .bool_set:N = \l_@@_xout_hidden_bool ,
    xout / subtract          .bool_set:N = \l_@@_xout_subtract_bool ,
    xout / format              .tl_set:N = \l_@@_xout_format_tl ,
    xout / textformat          .tl_set:N = \l_@@_xout_text_format_tl ,
    ulem / skip              .bool_set:N = \l_@@_ulem_skip_bool ,
    ulem / hidden            .bool_set:N = \l_@@_ulem_hidden_bool ,
    ulem / subtract          .bool_set:N = \l_@@_ulem_subtract_bool ,
    ulem / sep                 .tl_set:N = \l_@@_ulem_sep_tl ,
    ulem / boxdepth            .tl_set:N = \l_@@_ulem_boxdepth_tl ,
    ulem / textformat          .tl_set:N = \l_@@_ulem_text_format_tl
  }
\clist_map_inline:nn
  { underdot , underline , underdblline , underwave , sout , xout , ulem }
  {
    \keys_define:nn { xeCJK / options }
      { #1 .meta:nn = { xeCJK / options / #1 } { ##1 } }
  }
\keys_set:nn { xeCJK / options }
  {
    underdot / symbol        = \normalfont . ,
    underdot / depth         = 0.20 em ,
    underdot / sep           = 0.04 em ,
    symbol / sep             = \c_zero_dim ,
    underline / skip         = true ,
    underline / thickness    = \ULthickness ,
    underline / depth        = 0.20 em ,
    underline / sep          = 0.07 em ,
    underdblline / skip      = true ,
    underdblline / thickness = \ULthickness ,
    underdblline / depth     = 0.20 em ,
    underdblline / sep       = 0.17 em ,
    underdblline / gap       = 1.1 pt ,
    underwave / skip         = true ,
    underwave / symbol       = \@@_fntef_wave_symbol: ,
    underwave / depth        = 0.20 em ,
    underwave / sep          = 0.00 em ,
    sout / skip              = true ,
    sout / thickness         = \ULthickness ,
    sout / height            = 0.35 em ,
    xout / skip              = true
  }
%    \end{macrocode}
%
% \changes{v3.8.3}{2020/04/07}{取消 \pkg{xeCJKfntef} 的初始彩色设置。}
%
% \begin{macro}{\CJKunderanysymbol}
% \changes{v3.10.0}{2026/04/28}{移除 \tn{ignorespaces}，修复命令后空格丢失（\#465）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderanysymbol { o m m m }
  {
    \xeCJK_under_symbol:nnnnnn { symbol } { symbol } {#1} {#2} {#3} {#4}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CJKunderdot}
% \tn{CJKunderdot} 是 \tn{CJKunderanysymbol} 的特殊情况。\pkg{CJKfntef} 原来使用
% 的是数学符号 \tn{cdot}，这里改成更合适的 |.|。
% \changes{v3.10.0}{2026/04/28}{移除 \tn{ignorespaces}，修复命令后空格丢失（\#465）。}
%    \begin{macrocode}
\NewDocumentCommand \CJKunderdot { o m }
  {
    \xeCJK_under_symbol:nnnnnn { underdot } { udot }
      {#1}
      { \l_@@_udot_depth_tl }
      { \l_@@_udot_format_tl \l_@@_udot_symbol_tl }
      {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_under_symbol:nnnnnn}
% 当处在下划线中时，我们先断开下划线，在分组外设置下划符号。
% \changes{v3.10.0}{2026/05/12}{在独立模式的末尾设置 \cs{g_@@_glue_check_pending_bool}，
%   修复 \tn{CJKunderdot} 等命令右侧空格问题（\#826）。}
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_under_symbol:nnnnnn
  {
    \xeCJK_if_ulem_patch:TF
      { \@@_under_symbol_auxi:nnnnnn }
      { \@@_under_symbol_auxii:nnnnnn }
  }
\cs_new_protected:Npn \@@_under_symbol_auxi:nnnnnn #1#2#3#4#5#6
  {
    \xeCJK_ulem_right: \UL@stop
    \group_begin:
      \xeCJK_under_symbol_initial:nnnnn {#1} {#2} {#3} {#4} {#5}
      \use:c { l_@@_#2_text_format_tl }
      \UL@start \xeCJK_ulem_right_node:
        #6
      \xeCJK_ulem_right: \UL@stop
    \group_end:
    \UL@start \xeCJK_ulem_right_node:
  }
\cs_new_protected:Npn \@@_under_symbol_auxii:nnnnnn #1#2#3#4#5#6
  {
    \@@_boundary_inline_stream_begin:nn { auto } { stream-ulem }
    \@@_boundary_math_begin:n {#6}
    \group_begin:
      \xeCJK_under_symbol_initial:nnnnn {#1} {#2} {#3} {#4} {#5}
      \@@_under_symbol_text_format:c { l_@@_#2_text_format_tl }
      #6
      \@@_boundary_math_end:n {#6}
      \xeCJK_ulem_right:
    \group_end:
    \xeCJK_ulem_right_node:
    \@@_boundary_inline_stream_end:n { auto }
  }
\cs_new_protected:Npn \xeCJK_under_symbol_initial:nnnnn #1#2#3#4#5
  {
    \tl_if_novalue:nF {#3}
      { \keys_set:nn { xeCJK / options / #1 } {#3} }
    \xeCJK_fntef_sbox:n {#5}
    \bool_if:NTF \l_@@_fntef_bool
      { \xeCJK_make_under_symbol:n { \l_@@_fntef_dim } }
      {
        \bool_set_true:N \l_@@_fntef_bool
        \xeCJK_make_under_symbol:n {#4}
      }
    \tl_if_empty:cF { l_@@_#2_boxdepth_tl }
      {
        \box_set_dp:Nn \l_@@_under_symbol_box
          { \use:c { l_@@_#2_boxdepth_tl } }
      }
    \dim_set:Nn \l_@@_fntef_dim
      { \use:c { l_@@_#2_sep_tl } + \box_dp:N \l_@@_under_symbol_box }
    \xeCJK_swap_cs:NN \CJKsymbol \@@_under_CJKsymbol:N
    \@@_restore_shipout_CJKsymbol:
  }
\cs_new_protected:Npn \@@_under_symbol_text_format:N #1
  {
    \tl_if_empty:NF #1
      { \xeCJK_ulem_right: #1 \xeCJK_ulem_right_node: }
  }
\cs_generate_variant:Nn \@@_under_symbol_text_format:N { c }
\box_new:N \l_@@_under_symbol_box
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_make_under_symbol:n}
% 我们量取“一”的宽度作为汉字的宽度。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_make_under_symbol:n #1
  {
    \hbox_set:Nn \l_@@_under_symbol_box
      {
        \box_move_down:nn { #1 + \box_ht:N \l_@@_fntef_box }
          {
            \hbox_to_zero:n
              {
                \xeCJK_select_font:
                \tex_kern:D \tex_fontcharwd:D \tex_font:D "4E00 \exp_stop_f:
                \tex_hss:D \box_use:N \l_@@_fntef_box \tex_hss:D
              }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_restore_shipout_CJKsymbol:}
% \changes{v3.2.3}{2013/06/04}{解决 \tn{CJKunderdot} 跨页使用时影响到页眉页脚的问题。}
% \tn{CJKunderdot} 中对 \tn{CJKsymbol} 的修改会影响到页眉和页脚，需要小心处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_restore_shipout_CJKsymbol:
  {
    \tl_put_right:Nn \l_@@_fntef_shipout_tl
      { \xeCJK_swap_cs:NN \CJKsymbol \@@_under_CJKsymbol:N }
    \@@_restore_shipout_fntef:
    \xeCJK_cs_clear:N \@@_restore_shipout_CJKsymbol:
  }
\cs_new_protected:Npn \@@_restore_shipout_fntef:
  {
    \tl_put_right:Nn \l_@@_fntef_shipout_tl
      {
        \bool_set_false:N \l_@@_fntef_bool
        \dim_zero:N \l_@@_fntef_dim
      }
    \xeCJK_cs_clear:N \@@_restore_shipout_fntef:
  }
\tl_new:N \l_@@_fntef_shipout_tl
\xeCJK_add_to_shipout:n { \l_@@_fntef_shipout_tl }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_under_CJKsymbol:N}
% 盒子放在汉字的左侧，比较容易处理状态转移的问题。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_under_CJKsymbol:N
  {
    \box_use:N \l_@@_under_symbol_box
    \xeCJK_no_break: \@@_under_CJKsymbol:N
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.7.2}{2019/03/24}{改用 \pkg{xparse} 的新参数类型 \texttt{b}
%   定义 \env{CJKfilltwosides*} 环境，不再依赖 \pkg{environ} 包。}
%
% \begin{macro}{CJKfilltwosides}
% \changes{v3.2.4}{2013/06/26}{改用 \texttt{minipage} 和 \LaTeX 表格（\texttt{tabular}）来实现。}
% \changes{v3.3.3}{2015/05/30}{确保进入水平模式。}
% 使用 \texttt{minipage} 和 \LaTeX 表格（\texttt{tabular}）来定义 |CJKfilltwosides| 环境。
% 可选参数 |#1| 表示环境的垂直对齐位置，默认居中；参数 |#2| 表示环境的宽度。
% 带星号的环境，如果 |#2| 不大于零或者不大于环境最长文本行的宽度，则取环境的自然宽度。
%    \begin{macrocode}
\NewDocumentEnvironment { CJKfilltwosides } { O { c } m }
  {
    \use:e { \exp_not:N \minipage [#1] { \dim_eval:n {#2} } }
    \cs_set_eq:NN \CJKglue \xeCJK_fntef_hfilll:
  }
  {
    \endminipage
    \ignorespacesafterend
  }
\NewDocumentEnvironment { CJKfilltwosides* } { O { c } m +b }
  {
    \mode_leave_vertical:
    \cs_set_eq:NN \CJKglue \xeCJK_fntef_hfilll:
    \tl_set:Nn \arraystretch { 1 }
    \cs_if_free:NF \extrarowheight
      { \cs_set_eq:NN \extrarowheight \c_zero_dim }
    \use:e { \@@_fill_two_sides:nnn {#1} { \dim_eval:n {#2} } } {#3}
  }
  { \ignorespacesafterend }
\cs_new_protected:Npn \@@_fill_two_sides:nnn #1#2#3
  {
    \dim_compare:nNnTF {#2} > \c_zero_dim
      {
        \hbox_set:Nn \l_@@_tmp_box
          { \tabular [#1] { @ { } c @ { } }      #3 \endtabular }
        \dim_compare:nNnTF {#2} > { \box_wd:N \l_@@_tmp_box }
          { \tabular [#1] { @ { } p {#2} @ { } } #3 \endtabular }
          { \box_use:N \l_@@_tmp_box }
      }
      { \tabular [#1] { @ { } c @ { } }          #3 \endtabular }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xeCJK_fntef_hfilll:}
% \pkg{colortbl} 将表格 |c| 列用于填充的 \tn{hfil} 改为了更高阶的 \texttt{fill}，
% 影响到了 \env{CJKfilltwosides*}。因此，我们也要用高阶的 \texttt{filll}。
%    \begin{macrocode}
\cs_new_protected:Npn \xeCJK_fntef_hfilll:
  { \skip_horizontal:N \c_@@_filll_skip }
\skip_const:Nn \c_@@_filll_skip { \c_zero_dim plus 1 filll }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</fntef>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-listings}}
%
% \changes{v3.2.2}{2013/06/04}{增加小宏包 \pkg{xeCJK-listings}，用于支持 \pkg{listings} 宏包。}
% \changes{v3.2.3}{2013/06/06}{完善对 \pkg{listings} 宏包的支持。}
% \changes{v3.10.0}{2026/06/23}{修复在 \tn{lstinline} 参数中把非 \texttt{\#}
%   的字符（如 \texttt{\&}）用 \tn{catcode} 设为 catcode~6 时输出错字的问题（\#879）。}
%
% 仿照 \package{luatexja} 宏包中 \pkg{lltjp-listings} 的处理，支持 \package{listings} 宏包。
%
%    \begin{macrocode}
%<*listings>
%    \end{macrocode}
%
%    \begin{macrocode}
\DeclareOption* { \PassOptionsToPackage { \CurrentOption } { xeCJK } }
\ProcessOptions \scan_stop:
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage { xeCJK }
\RequirePackage { listings }
%    \end{macrocode}
%
%    \begin{macrocode}
\lst@AddToHook { Init } { \@@_listings_initial_hook: }
\lst@AddToHook { SelectCharTable } { \@@_listings_toks_hook: }
\lst@AddToHook { OutputBox }
  {
    \tl_set_eq:NN \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl
    \l_@@_restore_listings_toks_tl
    \@@_listings_output_CM:
  }
\lst@AddToHook { PreSet } { \bool_set_true:N \l_@@_listings_env_bool }
%    \end{macrocode}
%
% \begin{macro}{\@@_listings_initial_hook:}
% \changes{v3.2.3}{2013/06/04}
% {解决 \texttt{listings} 环境中代码行号输出不正确的问题，并解决在其中跨页时对页眉
%  和页脚的影响。}
% \changes{v3.2.15}{2014/11/10}{修正 \texttt{breaklines} 无效的问题。}
% \changes{v3.3.1}{2015/04/20}
% {解决 \texttt{prebreak} 和 \texttt{postbreak} 功能失效的问题。}
% 为使代码行号结果正确，需要在 \tn{lst@numberstyle} 中恢复 \tn{XeTeXinterchartoks}。
% 在 \texttt{listings} 环境中换页时，对 \tn{XeTeXinterchartoks} 的修改会影响到
% 页眉和页脚，需要在 \tn{shipout} 盒子中恢复成正常定义。加入 \cs{tex_noindent:D}
% 是为了进入水平模式，防止汉字出现在首行的时候可能会产生额外空行。
% \tn{lst@prebreak} 和 \tn{lst@postbreak} 是在 \tn{discretionary} 中直接输出的，
% 应该恢复正常的 \tn{XeTeXinterchartoks}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_initial_hook:
  {
    \tex_noindent:D
    \bool_gset_false:N \g_@@_listings_CM_bool
    \tl_put_left:Nn \lst@numberstyle { \l_@@_restore_listings_toks_tl }
    \xeCJK_add_to_shipout:n { \l_@@_restore_listings_toks_tl }
    \lst@ifbreaklines
      \cs_set_eq:NN \@@_listings_CJK_toks_hook: \@@_listings_breaklines_toks:
      \tl_if_empty:NF \lst@prebreak
        { \tl_put_left:Nn \lst@prebreak { \l_@@_restore_listings_toks_tl } }
      \tl_if_empty:NF \lst@postbreak
        { \tl_put_left:Nn \lst@postbreak { \l_@@_restore_listings_toks_tl } }
    \fi:
    \int_set:Nn \l_@@_listings_max_char_int
      { \lst@ifec 255 \else: 127 \fi: }
  }
\int_new:N \l_@@_listings_max_char_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_toks_hook:}
% 采用不同的 \tn{XeTeXinterchartoks} 处理方式，输入的时候是将汉字加入到 \pkg{listings}
% 的输出队列，实际输出的时候是普通文字。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_toks_hook:
  {
    \tl_clear:N \l_@@_restore_listings_toks_tl
    \seq_map_function:NN
      \g_@@_class_seq \@@_backup_inter_class_toks:n
    \seq_map_inline:Nn \g_@@_non_CJK_class_seq
      {
        \str_if_eq:nnF { ##1 } { Boundary }
          {
            \xeCJK_inter_class_toks:nnn { Boundary } { ##1 }
              { \@@_listings_process_Default:nN { ##1 } }
          }
      }
    \xeCJK_inter_class_toks:nnn { Boundary } { CM }
      { \@@_listings_process_CM:nN { 0 } }
    \@@_listings_CJK_toks_hook:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backup_inter_class_toks:n}
% 注意，给 \tn{XeTeXinterchartoks} 赋空值，会导致 \XeTeX 崩溃！
%    \begin{macrocode}
\cs_new_protected:Npn \@@_backup_inter_class_toks:n #1
  {
    \tl_set:Ne \l_@@_tmp_tl
      { \xeCJK_get_inter_class_toks:nn { Boundary } {#1} }
    \tl_put_right:Ne \l_@@_restore_listings_toks_tl
      {
        \xeCJK_inter_class_toks:nnn { Boundary } {#1}
          {
            \tl_if_empty:NTF \l_@@_tmp_tl
              { \exp_not:N \prg_do_nothing: }
              { \exp_not:o \l_@@_tmp_tl }
          }
      }
  }
\tl_new:N \l_@@_restore_listings_toks_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_CJK_toks_hook:,\@@_listings_breaklines_toks:}
% 根据 \texttt{breaklines} 选项的使用与否，选择不同的处理方式。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_CJK_toks_hook:
  {
    \xeCJK_inter_class_toks:nnn { Boundary } { CJK }
      { \@@_listings_process_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { FullLeft }
      { \@@_listings_process_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { FullRight }
      { \@@_listings_process_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamo }
      { \@@_listings_process_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoL }
      { \@@_listings_process_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoV }
      { \@@_listings_process_CM:nN { 0 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoT }
      { \@@_listings_process_CM:nN { 0 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { CJStarter }
      { \@@_listings_process_CJK:nN { 2 } }
    \seq_map_inline:Nn \g_@@_CJK_sub_class_seq
      {
        \xeCJK_inter_class_toks:nnn { Boundary } { CJK/##1 }
          { \@@_listings_process_CJK:nN { 2 } }
      }
  }
\cs_new_protected:Npn \@@_listings_breaklines_toks:
  {
    \xeCJK_inter_class_toks:nnn { Boundary } { CJK }
      { \@@_listings_process_breaklines_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamo }
      { \@@_listings_process_breaklines_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoL }
      { \@@_listings_process_breaklines_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoV }
      { \@@_listings_process_CM:nN { 0 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoT }
      { \@@_listings_process_CM:nN { 0 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { CJStarter }
      { \@@_listings_process_breaklines_CJK:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { FullLeft }
      { \@@_listings_process_FullLeft:nN { 2 } }
    \xeCJK_inter_class_toks:nnn { Boundary } { FullRight }
      { \@@_listings_process_FullRight:nN { 2 } }
    \seq_map_inline:Nn \g_@@_CJK_sub_class_seq
      {
        \xeCJK_inter_class_toks:nnn { Boundary } { CJK/##1 }
          { \@@_listings_process_breaklines_CJK:nN { 2 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_process_Default:nN}
% \changes{v3.2.3}{2013/06/08}{在 \texttt{listings} 环境中对 \tn{charcode} 大于
% $255$ 的字符根据其 \tn{catcode} 区分 \texttt{letter} 和 \texttt{other}。}
% \changes{v3.3.1}{2015/01/27}{对 \pkg{listings} 的字符扩展不影响到其符号表中的
% 七位或八位字符。}
% 对于 \tn{charcode} 大于 $255$ 的字符，根据 \tn{catcode} 进行处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_process_Default:nN #1#2
  {
    \int_compare:nNnTF
      { \xeCJK_token_value_charcode:N #2 } > \l_@@_listings_max_char_int
      {
        \token_if_letter:NTF #2
          { \lst@ProcessLetter #2 }
          { \lst@ProcessOther  #2 }
      }
      { \@@_listings_output_Default:nN {#1} #2 }
  }
%    \end{macrocode}
% 输出时，要注意把对应的 \tn{XeTeXinterchartoks} 清空掉，否则会造成死循环。
% \cs{scan_stop:} 是造边界，输出 \cs{group_end:}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_output_Default:nN #1#2
  {
    \group_begin:
      \xeCJK_clear_inter_class_toks:nn { Boundary } {#1}
      \xeCJK_inter_class_toks:nnn {#1} { Boundary } { \group_end: }
      #2
      \scan_stop:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_process_CJK:nN}
% 对 CJK 字符类的处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_process_CJK:nN #1#2
  {
    \token_if_letter:NTF #2
      { \@@_listings_process_letter:nN {#1} #2 }
      { \@@_listings_process_other:nN  {#1} #2 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_append:nN}
% 普通 CJK 字符的宽度为一般基本宽度的两倍，CM 类不增加宽度。这里有一个问题，
% 对 CJK 字符类中的一些半角字符（例如半角日文假名）没有区分开。\pkg{listings} 通过
% 重定义 \tn{lst@Append} 将代码写入外部文件，因此需要保留。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_append:nN #1#2
  {
    \int_add:Nn \lst@length { #1 - 1 }
    \lst@Append #2
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_process_letter:nN,\@@_listings_process_other:nN}
% 在 \texttt{letter} 类中区分汉字和西文字母。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_process_letter:nN
  {
    \lst@whitespacefalse
    \bool_if:NTF \l_@@_listings_letter_bool
      { \lst@lettertrue }
      {
        \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi:
        \bool_set_true:N \l_@@_listings_letter_bool
      }
    \@@_listings_append:nN
  }
\bool_new:N \l_@@_listings_letter_bool
\cs_new_protected:Npn \@@_listings_process_other:nN #1#2
  {
    \lst@whitespacefalse
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \lst@Output \lst@letterfalse
        \bool_set_false:N \l_@@_listings_letter_bool
      }
      { \lst@ifletter \lst@Output \lst@letterfalse \fi: }
    \cs_set_eq:NN \lst@lastother #2
    \@@_listings_append:nN {#1} #2
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.4}{2013/07/05}
% {使 \pkg{listings} 的 \texttt{breaklines} 选项对 CJK 字符类可用，并保持标点符号的禁则。}
%
% \begin{macro}
% {\@@_listings_process_breaklines_CJK:nN,
%  \@@_listings_process_FullLeft:nN,
%  \@@_listings_process_FullRight:nN}
% 当使用 \texttt{breaklines} 选项时，立即输出之前的单个文字，以便于断行。并将标点
% 与它前/后的 CJK 文字放在同一个盒子中，以保持禁则。但是不能区分 letter 和 other。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_process_breaklines_CJK:nN
  {
    \lst@whitespacefalse
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \int_compare:nNnF \l_@@_listings_flag_int = 2 { \lst@Output }
        \lst@lettertrue
      }
      {
        \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi:
        \bool_set_true:N \l_@@_listings_letter_bool
      }
    \int_set_eq:NN \l_@@_listings_flag_int \c_one_int
    \@@_listings_append:nN
  }
\cs_new_protected:Npn \@@_listings_process_FullLeft:nN #1#2
  {
    \lst@whitespacefalse
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \int_compare:nNnF \l_@@_listings_flag_int = 2
          {
            \int_compare:nNnTF \l_@@_listings_flag_int = 3
              { \bool_if:NT \l_@@_punct_breakable_bool { \lst@Output } }
              { \lst@Output }
          }
        \lst@lettertrue
      }
      {
        \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi:
        \bool_set_true:N \l_@@_listings_letter_bool
      }
    \int_set:Nn \l_@@_listings_flag_int { 2 }
    \@@_listings_append:nN {#1} #2
  }
\cs_new_protected:Npn \@@_listings_process_FullRight:nN #1#2
  {
    \lst@whitespacefalse
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \int_compare:nNnT \l_@@_listings_flag_int < 2
          { \@@_punct_if_long:NT #2 { \lst@Output } }
        \lst@lettertrue
      }
      {
        \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi:
        \bool_set_true:N \l_@@_listings_letter_bool
      }
    \int_set:Nn \l_@@_listings_flag_int { 3 }
    \@@_listings_append:nN {#1} #2
  }
\int_new:N \l_@@_listings_flag_int
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\lst@AppendLetter,\lst@AppendOther}
%    \begin{macrocode}
\cs_set_protected:Npn \lst@AppendLetter
  {
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \lst@Output \lst@lettertrue
        \bool_set_false:N \l_@@_listings_letter_bool
      }
      { \reverse_if:N \lst@ifletter \lst@OutputOther \lst@lettertrue \fi: }
    \lst@ifbreaklines \int_zero:N \l_@@_listings_flag_int \fi:
    \lst@Append
  }
\cs_set_protected:Npn \lst@AppendOther
  {
    \bool_if:NTF \l_@@_listings_letter_bool
      {
        \lst@Output \lst@letterfalse
        \bool_set_false:N \l_@@_listings_letter_bool
      }
      { \lst@ifletter \lst@Output \lst@letterfalse \fi: }
    \lst@ifbreaklines \int_zero:N \l_@@_listings_flag_int \fi:
    \tex_futurelet:D \lst@lastother \lst@Append
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_process_CM:nN}
% \texttt{CM} 类作为 \texttt{letter} 处理，不用增加 \tn{lst@length}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_process_CM:nN
  {
    \reverse_if:N \lst@ifflexible
      \bool_gset_true:N \g_@@_listings_CM_bool
    \fi:
    \@@_listings_process_letter:nN
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_output_CM:}
% 在使用 \texttt{columns=fixed} 选项时，\pkg{listings} 会在输出盒子里的每个字符
% 之间加入 \tn{hss}，这就破坏了 \XeTeX 将基本字和组合标识正确的组合起来。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_output_CM:
  {
    \reverse_if:N \lst@ifflexible
      \bool_if:NT \g_@@_listings_CM_bool
        {
          \bool_gset_false:N \g_@@_listings_CM_bool
          \xeCJK_cs_clear:N \lst@FillOutputBox
          \cs_set_eq:NN \CJKglue \tex_hss:D
        }
    \fi:
  }
\bool_new:N \g_@@_listings_CM_bool
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_peek_active_loop:TF}
% \tn{lstinline} 通过判断参数中第一个字符是否是 \texttt{active} 类来区分
% 它是否被用在其他宏的参数之中。如果这第一个字符不在 \pkg{listings} 预定义的
% 符号表中，判断就会出问题。我们在这里通过一个循环跳过这些字符。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_peek_active_loop:TF #1#2#3
  {
    \token_if_active:NTF #3
      { #1#3 }
      {
        \token_if_cs:NTF #3
          { #2#3 }
          {
            \int_compare:nNnTF { `#3 } > { \l_@@_listings_max_char_int }
              { \@@_listings_peek_active_loop:TF { #1#3 } { #2#3 } }
              { #2#3 }
          }
      }
  }
\cs_set_eq:NN \lst@IfNextCharActive \@@_listings_peek_active_loop:TF
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_rescan:Nn,
%  \@@_listings_inside_convert:nw,\@@_listings_inline_group:w}
% 当 \tn{lstinline} 被使用在参数中时，\pkg{listings} 会使用一个循环逐个将
% \tn{lstinline} 参数中的字符设置为活动字符。我们可以通过 \cs{tl_set_rescan:Nnn}
% 来完成这里的 \tn{catcode} 转换，避免将 \tn{charcode} 超过 $255$ 的字符都设置为
% 活动字符。
%
% 当 \tn{lstinline} 出现在宏参数中时，\TeX{} 参数传递将 catcode~6 的 parameter
% token（通常是 |#|）双写为成对的相邻 token。\cs{tl_set_rescan:Nnn} 底层的
% \tn{scantokens} 在字符串化阶段会再次将 catcode~6 token 双写，导致输出中数量
% 翻倍。修复方法是在 rescan 前逐 token 扫描 \cs{l_@@_tmp_tl}，将每个 catcode~6
% 的 token 替换为 \emph{保留原字符码} 的 active token（catcode 13），这是
% \pkg{listings} 期望的字符类型。最初的实现用 \cs{regex_replace_all:nnN} 把所有
% catcode~6 token 硬替换成 \texttt{U+0023}，对一般 |#| 没问题，但当用户用
% \tn{catcode} 把别的字符（如 |&|）改为 parameter 时，原字符码会被丢失，导致
% 输出错字（\#879）。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_listings_rescan:Nn #1#2
  {
    \@@_listings_set_escape:
    \tl_set:Nn \l_@@_tmp_tl {#2}
    \@@_listings_escape_backslash:
    \tl_clear:N \l_@@_tmpb_tl
    \tl_map_inline:Nn \l_@@_tmp_tl
      {
        \token_if_parameter:NTF ##1
          {
            \tl_put_right:Ne \l_@@_tmpb_tl
              {
                \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N
                \char_generate:nn { \int_value:w `##1 } { 13 }
              }
          }
          { \tl_put_right:Nn \l_@@_tmpb_tl { ##1 } }
      }
    \tl_set_eq:NN \l_@@_tmp_tl \l_@@_tmpb_tl
    \tl_set_rescan:Nno #1
      { \char_set_catcode_active:n { 35 } }
      { \l_@@_tmp_tl }
  }
\cs_new_protected:Npn \@@_listings_inside_convert:nw #1 ~ \@empty
  {
    \@@_listings_rescan:Nn \l_@@_tmp_tl {#1}
    \tl_put_right:No \lst@arg { \l_@@_tmp_tl }
  }
\cs_set_eq:NN \lst@InsideConvert@ \@@_listings_inside_convert:nw
\cs_new_protected:Npn \@@_listings_inline_group:w
  {
    \exp_after:wN \@@_listings_inline_group:n
    \exp_after:wN { \if_int_compare:w `} = \c_zero_int \fi:
  }
\cs_set_eq:NN \lst@InlineGJ \@@_listings_inline_group:w
\cs_new_protected:Npn \@@_listings_inline_group:n #1
  {
    \@@_listings_rescan:Nn \lst@arg {#1}
    \lst@InlineGJEnd
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\@@_listings_boundary_stream_end:}
% \changes{v3.10.4}{2026/07/20}{将 \tn{lstinline} 的分隔符、活动字符和
%   花括号扫描路径接入 auto stream capture，使颜色切换前后的四种源码空格
%   组合都与直接输入一致（\#992）。}
% \tn{lstinline} 自己只声明可选参数，正文由多个内部扫描器读取，普通 cmd
% after hook 会过早结束。分隔符路径经过已经打开外层组、即将读取分隔符的
% \tn{lstinline@}，花括号路径则绕过它而直接进入 \tn{lst@InlineG}；两个入口
% 都启动 capture 并设置局部标志。\tn{lst@DeInit} 返回到该外层组后用
% \tn{aftergroup} 安排单 token 结束函数。分隔符、活动字符、花括号与
% short-inline 路径都调用 \tn{lst@DeInit}，因此共用同一结束适配点。
%    \begin{macrocode}
\clist_map_inline:nn { lstinline , lstinline@ , lst@InlineG , lst@DeInit }
  { \@@_boundary_reserve:n {#1} }
\bool_new:N \l_@@_listings_boundary_bool
\cs_new_protected:Npn \@@_listings_boundary_stream_end:
  { \@@_boundary_inline_stream_end:n { auto } }
\cs_new_eq:NN \@@_orig_listings_inline:n \lstinline@
\cs_set_protected:Npn \lstinline@ #1
  {
    \@@_boundary_inline_stream_begin:n { auto }
    \bool_set_true:N \l_@@_listings_boundary_bool
    \@@_orig_listings_inline:n #1
  }
\cs_new_eq:NN \@@_orig_listings_inline_group: \lst@InlineG
\cs_set_protected:Npn \lst@InlineG
  {
    \@@_boundary_inline_stream_begin:n { auto }
    \bool_set_true:N \l_@@_listings_boundary_bool
    \@@_orig_listings_inline_group:
  }
\tl_gput_right:Nn \lst@DeInit
  {
    \bool_if:NT \l_@@_listings_boundary_bool
      { \aftergroup \@@_listings_boundary_stream_end: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_listings_set_escape:}
% 由于我们在上面的修改，需要保留 |\| 用于转义 \tn{lstinline} 参数中的某些 \TeX
% 特殊字符，与原来宏包一致。
%    \begin{macrocode}
\group_begin:
\cs_set:Npn \@@_tmp:w #1
  {
    \group_end:
    \cs_new_protected:Npn \@@_listings_set_escape:
      { \xeCJK_swap_cs:NN #1 \@@_listings_escape:N }
    \cs_new_protected:Npn \@@_listings_escape:N ##1
      { \cs_if_eq:NNTF #1 ##1 { \@@_listings_escape:N } {##1} }
  }
\use:n
  {
    \char_set_catcode_active:N \\
    \@@_tmp:w
  }
  { \ }
%    \end{macrocode}
% \end{macro}
%
%
% \changes{v3.4.8}{2017/05/15}{转义 \tn{lstinline} 参数中的 $\texttt{\textbackslash}_{12}$。}
% \begin{macro}{\@@_listings_escape_backslash:}
% \tn{catcode} 为 $12$ 的 |\| 需要双写转义。
%    \begin{macrocode}
\cs_new_protected:Npe \@@_listings_escape_backslash:
  {
    \tl_replace_all:Nnn \exp_not:N \l_@@_tmp_tl
      { \c_backslash_str }
      { \c_backslash_str \c_backslash_str }
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</listings>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=xunadd>
%    \end{macrocode}
%
% \subsection{\pkg{xunicode-addon}}
%
% \changes{v3.2.5}{2013/07/18}
% {增加小宏包 \pkg{xunicode-addon}，为 \pkg{xunicode} 提供判断字符是否存在的功能。}
% \changes{v3.2.9}{2013/12/08}
% {文档部分增加 \pkg{xunicode} 定义的符号表。}
%
%    \begin{macrocode}
%<*xunicode>
%    \end{macrocode}
%
% \pkg{xunicode} 对编码相关的符号命令的定义中用的是诸如 |\char"0022\relax| 的形式。
% 例如 \tn{textbar} 被展开为 |\char"007C\relax|。并且诸如下述的定义是无效的：
% \begin{verbatim}
%   \DeclareUTFcomposite[\UTFencname]{x1EBF}{\'}{\^e}
% \end{verbatim}
% 我们在这里做的修改是把符号命令定义为实际的字符并且使上述定义生效。另外在使用
% 这些符号命令的时候，先判断当前字体中是否存在对应的字符，如果不存在，则使用这
% 些符号命令的默认设置。
%
%    \begin{macrocode}
\bool_lazy_or:nnF
  { \sys_if_engine_xetex_p: }
  { \sys_if_engine_luatex_p: }
  {
    \msg_new:nnnn { xunicode-addon } { xetex-luatex }
      { This~package~requires~either~XeTeX~or~LuaTeX~to~function.}
      {
        You~must~change~your~typesetting~engine~to,~e.g.,\\
        "xelatex"~or~"lualatex"~instead~of~plain~"latex"~or~"pdflatex".
      }
    \msg_critical:nn { xunicode-addon } { xetex-luatex }
  }
\RequirePackage { xparse }
%    \end{macrocode}
%
% 宏包选项是编码的名字。
%
%    \begin{macrocode}
\clist_new:N \g_@@_encname_clist
\tl_if_exist:NT \UTFencname
  { \clist_gput_right:Ne \g_@@_encname_clist { \UTFencname } }
\DeclareOption*
  { \clist_gput_right:No \g_@@_encname_clist \CurrentOption }
\ProcessOptions \scan_stop:
%    \end{macrocode}
%
% 若 \pkg{xunicode} 已经被调用，则在宏包结束的时候，重新设置 \tn{UTFencname}
% 对应的编码命令。否则设置 \tn{UTFencname}，如果使用的是 \hologo{LuaLaTeX}，则
% 需要作一些设置，使得 \pkg{xunicode} 可用。
%
% \changes{v3.3.3}{2016/02/01}{使用新的 Unicode 编码名称 \texttt{TU}。}
%
%    \begin{macrocode}
\@ifpackageloaded { xunicode } { }
  {
    \clist_get:NNF \g_@@_encname_clist \UTFencname
      {
        \cs_if_exist:NTF \UnicodeEncodingName
          { \tl_set:Ne \UTFencname { \UnicodeEncodingName } }
          {
            \sys_if_engine_xetex:TF
              { \tl_set:Nn \UTFencname { EU1 } }
              { \tl_set:Nn \UTFencname { EU2 } }
          }
        \clist_gset_eq:NN \g_@@_encname_clist \UTFencname
      }
    \sys_if_engine_xetex:TF
      { \RequirePackage { xunicode } }
      {
        \cs_set_eq:NN \@@_tmp:w \XeTeXpicfile
        \cs_set_eq:NN \XeTeXpicfile \prg_do_nothing:
        \RequirePackage { xunicode }
        \cs_set_eq:NN \XeTeXpicfile \@@_tmp:w
      }
  }
\AtEndOfPackage { \@@_reload:N \g_@@_encname_clist }
%    \end{macrocode}
%
% \begin{macro}[int]{\ReloadXunicode}
% 参数可以是多个编码，设置这些编码对应的命令。如果编码没有预先声明，则给出
% 一个错误警告。
%    \begin{macrocode}
\RenewDocumentCommand \ReloadXunicode { m }
  {
    \clist_set:Ne \l_@@_encname_clist {#1}
    \@@_reload:N \l_@@_encname_clist
  }
\cs_new_protected:Npn \@@_reload:N #1
  {
    \cs_set_eq:NN \@@_tmp:w \iftipaonetoken
    \cs_set_eq:NN \iftipaonetoken \scan_stop:
    \use:e
      {
        \ExplSyntaxOff
        \char_set_catcode_letter:n { 64 }
        \exp_not:N \clist_map_function:NN \exp_not:N #1 \@@_reload_aux:n
        \bool_if:NTF \l__kernel_expl_bool
          { \ExplSyntaxOn }
          { \ExplSyntaxOff }
        \char_set_catcode:nn { 64 } { \char_value_catcode:n { 64 } }
      }
    \cs_set_eq:NN \iftipaonetoken \@@_tmp:w
  }
\cs_new_protected:Npn \@@_reload_aux:n #1
  {
    \cs_if_exist:cTF { T@ #1 }
      {
        \tl_set:Nn \UTFencname {#1}
        \clist_if_in:NnF \g_@@_encname_clist {#1}
          { \clist_gput_right:Nn \g_@@_encname_clist {#1} }
        \file_input:n { xunicode.sty }
        \file_input:n { xunicode-extra.def }
      }
      { \msg_error:nnn { xunicode-addon } { encoding-unknown } {#1} }
  }
\clist_new:N \l_@@_encname_clist
\msg_new:nnnn { xunicode-addon } { encoding-unknown }
  { Encoding~scheme~"#1"~unknown. }
  {
    You~may~use \\\\
    \token_to_str:N \usepackage [ #1 , \encodingdefault ] \{fontenc\} \\\\
    before~xunicode-addon~or~xunicode.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFmathsymbols}
% \changes{v3.2.8}{2013/12/04}{修正 \tn{UseMathAsText} 的功能，恢复 \tn{hbar}
% 和增加以 \texttt{text} 打头的文本符号命令。}
% 将文本符号定义为 \tn{protected} 宏后，为了与 \pkg{hyperref} 的书签功能兼容
% 需要作一点额外处理。
%    \begin{macrocode}
\RenewDocumentCommand \DeclareUTFmathsymbols { m }
  {
    \bool_if:NT \l_@@_math_as_UTF_text_bool
      {
        \seq_map_inline:Nn \l_@@_math_as_UTF_text_seq
          { \@@_declare_math_as_UTF_text:n {##1} }
        \bool_set_false:N \l_@@_math_as_UTF_text_bool
      }
  }
\seq_new:N \l_@@_math_as_UTF_text_seq
\seq_set_from_clist:Nn \l_@@_math_as_UTF_text_seq
  { hbar , Finv , aleph , beth , gimel , daleth , Game }
\bool_new:N \l_@@_math_as_UTF_text_bool
\RenewDocumentCommand \UseMathAsText { }
  {
    \math@s@text@true
    \bool_set_true:N \l_@@_math_as_UTF_text_bool
  }
\@onlypreamble \UseMathAsText
\cs_new_protected:Npn \@@_declare_math_as_UTF_text:n #1
  {
    \cs_if_exist:cTF {#1}
      {
        \cs_new_eq:cc { keepmathUTF #1 } {#1}
        \cs_gset_protected:cpe {#1}
          {
            \exp_not:N \mode_if_math:TF
              { \exp_not:c { keepmathUTF #1 } }
              { \exp_not:c { text #1 } }
          }
        \tl_put_right:Ne \l_@@_hyperref_hook_tl
          { \cs_set_eq:NN \exp_not:c {#1} \exp_not:c { text #1 } }
      }
      { \cs_new:cpe {#1} { \exp_not:c { text #1 } } }
  }
\tl_new:N \l_@@_hyperref_hook_tl
\AtBeginDocument
  {
    \cs_if_free:NF \pdfstringdefDisableCommands
      { \pdfstringdefDisableCommands { \l_@@_hyperref_hook_tl } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF]{\@@_glyph_if_exist:n}
% 判断字符在当前字体中是否存在。
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_glyph_if_exist:n #1 { p , T , F , TF }
  {
    \tex_iffontchar:D \tex_font:D \tex_numexpr:D #1 \scan_stop:
      \prg_return_true: \else: \prg_return_false: \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\UndeclareUTFcharacter}
% 取消编码 |#1| 下的符号命令 |#3|。
%    \begin{macrocode}
\RenewDocumentCommand \UndeclareUTFcharacter { O { \UTFencname } m m }
  {
    \@@_if_csname:nTF {#3}
      { \UndeclareTextCommand {#3} }
      { \exp_args:Nc \UndeclareTextCommand { \tl_to_str:n {#3} } }
      {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\UndeclareUTFcomposite}
% 取消编码 |#1| 下的复合符号命令 |#3{#4}|。
%    \begin{macrocode}
\RenewDocumentCommand \UndeclareUTFcomposite { O { \UTFencname } m m m }
  {
    \@@_if_csname:nTF {#3}
      { \@@_undeclare_composite:Nnnn #3 }
      { \exp_args:Nc \@@_undeclare_composite:Nnnn { \tl_to_str:n {#3} } }
      {#1} {#4} {#2}
  }
\cs_new_protected:Npn \@@_undeclare_composite:Nnnn #1#2#3#4
  { \cs_undefine:c { \@@_composite_cs:Nnn #1 {#2} {#3} } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_composite_cs:Nnn,\@@_composite_cs:nnn}
%    \begin{macrocode}
\cs_new:Npe \@@_composite_cs:Nnn #1#2#3
  { \c_backslash_str #2 \exp_not:N \token_to_str:N #1 - \exp_not:N \tl_to_str:n {#3} }
\cs_new:Npe \@@_composite_cs:nnn #1#2#3
  { \c_backslash_str #2 #1 - \exp_not:N \tl_to_str:n {#3} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_if_csname:nTF}
% 判断 |#1| 是否可以作为控制序列的名字。这是因为 \pkg{xunicode} 使用了下面的定义。
% \begin{verbatim}
%   \DeclareUTFcharacter[\UTFencname]{x0149}{'n}
% \end{verbatim}
%    \begin{macrocode}
\prg_new_conditional:Npnn \@@_if_csname:n #1 { TF }
  {
    \tl_if_single_token:nTF {#1}
      {
        \token_if_cs:NTF #1
          { \prg_return_true: }
          {
            \token_if_active:NTF #1
              { \prg_return_true: }
              { \prg_return_false: }
          }
      }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFcharacter}
% 定义编码 |#1| 下的符号命令 |#3|，其对应符号的 Unicode 是 |#2|。
%    \begin{macrocode}
\RenewDocumentCommand \DeclareUTFcharacter { O { \UTFencname } m m }
  {
    \cs_if_exist_use:cF
      { @@_restore_ \tl_to_str:n {#3} : }
      {
        \@@_if_csname:nTF {#3}
          { \@@_declare_character:Nnn #3 }
          { \@@_declare_character:cnn { \tl_to_str:n {#3} } }
        {#1} {#2}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_restore_cmd:N}
% 恢复 \tn{hbar} 和 \tn{nobreakspace} 为原本定义。
%    \begin{macrocode}
\cs_new_protected:cpn
  { @@_restore_ \tl_to_str:n { \hbar } : }
  { \@@_restore_cmd:N \hbar }
\cs_new_protected:cpn
  { @@_restore_ \tl_to_str:n { \nobreakspace } : }
  { \@@_restore_cmd:N \nobreakspace }
\cs_new_protected:Npn \@@_restore_cmd:N #1
  { \@@_restore_cmd:Ne #1 { ? - \token_to_str:N #1 } }
\cs_new_protected:Npn \@@_restore_cmd:Nn #1#2
  {
    \cs_if_free:cF {#2}
      { \@@_restore_cmd:Nc #1 {#2} }
  }
\cs_new_protected:Npn \@@_restore_cmd:NN #1#2
  {
    \cs_gset_eq:NN #1 #2
    \cs_undefine:N #2
  }
\cs_generate_variant:Nn \@@_restore_cmd:Nn { Ne }
\cs_generate_variant:Nn \@@_restore_cmd:NN { Nc }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_character:Nnn}
% 通过 \cs{tex_Uchar:D} 直接由 Unicode |#3| 得到编码 |#2| 下的符号命令
% |#1| 对应的实际字符。\tn{DeclareUTFSymbol} 的参数格式与 \tn{DeclareTextSymbol}
% 完全一致。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_character:Nnn #1#2#3
  {
    \@@_provide_text_command_default:N #1
    \exp_after:wN \@@_declare_character:NNen
      \tex_Uchar:D \@@_check_slot:n {#3} \exp_stop_f:
      #1 { \token_to_str:N #1 } {#2}
  }
\cs_generate_variant:Nn \@@_declare_character:Nnn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFSymbol,\DeclareUTFCommand}
% \tn{DeclareUTFCommand} 只能用于定义不带参数的符号命令。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFSymbol { m O { \UTFencname } m }
  { \@@_declare_character:Nnn #1 {#2} {#3} }
\NewDocumentCommand \DeclareUTFCommand { m O { \UTFencname } m }
  { \@@_text_command:Nonn #1 { \token_to_str:N #1 } {#2} {#3} }
\cs_new_protected:Npn \@@_text_command:Nnnn #1#2#3#4
  { \DeclareTextCommand #1 {#3} { \@@_text_command:nn {#2} {#4} } }
\cs_generate_variant:Nn \@@_text_command:Nnnn { No }
\cs_new_protected:Npn \@@_text_command:nn #1#2
  {
    \@@_begin_hook:nn {#1} {#2}
    #2
    \@@_end_hook:nn {#1} {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_provide_text_command_default:N}
% 如果控制序列 |#1| 已经存在，但不是符号命令，\pkg{xunicode} 会将它定义为
% \tn{UTFencname} 编码下的符号命令。但是编码被转换之后，再使用这些控制序列，
% \pkg{NFSS} 就会报错。为此需要给出这些符号命令的默认定义，与原来的意义相同。
% 这些命令包括
% \begin{verbatim}
%   \nobreakspace   macro:->\protect \nobreakspace
%   \copyright      macro:->\protect \copyright
%   \AA             macro:->\r A
%   \aa             macro:->\r a
%   \textrhookopeno \long macro:->\textrethookbelow {\textopeno }
%   \hbar           macro:->{\mathchar '26\mkern -9muh}
%   \textaolig      macro:->{a\kern -.25em o}
% \end{verbatim}
% 影响比较大的是 \tn{nobreakspace}、\tn{copyright} 和 \tn{hbar}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_provide_text_command_default:N #1
  {
    \cs_if_exist:cF { ? \token_to_str:N #1 }
      {
        \cs_if_free:cF { ? - \token_to_str:N #1 }
          {
            \exp_args:NNv \ProvideTextCommandDefault #1
              { ? - \token_to_str:N #1 }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_character:NNnn}
% 使用编码 |#4| 下的符号命令 |#2| 的时候先判断它对应的实际字符 |#1| 在当前字体中
% 是否存在。如果不存在则转换到 \tn{DeclareTextSymbolDefault} 中设置的编码或者使用
% \cs{Declare\-Text\-Command\-Default} 中设置的命令。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_character:NNnn #1#2#3#4
  { \DeclareTextCommand #2 {#4} { \@@_text_character:nN {#3} {#1} } }
\cs_new_protected:Npn \@@_text_character:nN #1#2
  {
    \@@_begin_hook:nn {#1} {#2}
    \@@_glyph_if_exist:nTF { `#2 }
      {#2} { \cs_if_exist_use:cF { ? #1 } {#2} }
    \@@_end_hook:nn {#1} {#2}
  }
\cs_generate_variant:Nn \@@_declare_character:NNnn { NNe }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_check_slot:n}
% \pkg{xunicode} 中使用的 Unicode 格式是诸如 |x0022| 的形式，这就需要一些转换。
%    \begin{macrocode}
\cs_new:Npn \@@_check_slot:n #1
  {
    \int_eval:n
      {
        \tl_if_head_eq_charcode:nNTF {#1} x
          { " \use_none:n #1 } {#1}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFcomposite}
% 设置编码 |#1| 下的符号命令 |#3| 与它的参数 |#4| 的复合对应的符号的 Unicode 是 |#2|。
%    \begin{macrocode}
\RenewDocumentCommand \DeclareUTFcomposite { O { \UTFencname } m m m }
  {
    \@@_if_csname:nTF {#3}
      { \@@_declare_composite:Nnnn #3 }
      { \@@_declare_composite:cnnn { \tl_to_str:n {#3} } }
      {#1} {#4} {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_composite:Nnnn}
% 这里使用 \cs{tex_afterassignment:D} 是因为 \pkg{xunicode} 有如下的定义。
% \begin{verbatim}
%   \DeclareUTFcomposite[\UTFencname]{x02E8\char"02E5}{\tonebar}{25}
%   \DeclareUTFcomposite[\UTFencname]{x02E5\char"02E8}{\tonebar}{52}
% \end{verbatim}
% 对复合符号命令的定义用的是 \tn{chardef}，这有利于下面字符是否存在的判断。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_composite:Nnnn #1#2#3#4
  {
    \tex_afterassignment:D \use_none_delimit_by_q_stop:w
    \@@_chardef:cn { \@@_composite_cs:Nnn #1 {#2} {#3} }
      { \@@_check_slot:n {#4} }
    \q_stop
  }
\cs_new_protected:Npn \@@_chardef:Nn #1#2
  { \tex_chardef:D #1 = \tex_numexpr:D #2 \scan_stop: }
\cs_generate_variant:Nn \@@_chardef:Nn { c }
\cs_generate_variant:Nn \@@_declare_composite:Nnnn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFCompositeCommand}
% 设置编码 |#2| 下的符号命令 |#1| 与它的参数 |#3| 的复合对应结果是 |#4|。不能直接用
% \cs{Declare\-Text\-Composite\-Command} 来定义，它与我们的机制冲突。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFCompositeCommand { m O { \UTFencname } m m }
  { \cs_set_protected:cpn { \@@_composite_cs:Nnn #1 {#2} {#3} } {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFCompositeSymbol}
% 设置编码 |#2| 下的符号命令 |#1| 与它的参数 |#3| 的复合对应结果是 |#4|。不能直接用
% \cs{Declare\-Text\-Composite} 来定义，它与我们的机制冲突。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFCompositeSymbol { m O { \UTFencname } m m }
  {
    \@@_chardef:cn { \@@_composite_cs:Nnn #1 {#2} {#3} }
      { \@@_check_slot:n {#4} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFComposite}
% 将 |#1| 设置为编码 |#2| 下的带一个参数的复合符号命令。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFComposite { m O { \UTFencname } }
  { \use:e { \@@_declare_composite:Nnn \exp_not:N #1 { \token_to_str:N #1 } {#2} } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFEncodedAccent}
% |#1| 是重音命令，|#2| 是编码，|#3| 是组合重音符号的 Unicode，|#4| 是基本重音符号
% 的 Unicode。当 |#1| 的参数为空时，输出 |#4|，否则是 |#1| 的参数与 |#3| 的组合。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFEncodedAccent { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_accent:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFEncodedAccents}
% |#1| 是重音命令，|#2| 是编码，|#3| 和 |#4| 都是组合重音符号的 Unicode。
% 输出 |#1| 与 |#3|、|#4| 的组合。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFEncodedAccents { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_accents:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFEncodedSymbol}
% |#1| 是带参数的符号命令，|#2| 是编码，|#3| 是组合符号的 Unicode，|#4| 是基本符号的
% Unicode。当 |#1| 的参数为空时，输出 |#4|，否则是 |#1| 的参数与 |#3| 的组合。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFEncodedSymbol { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_symbol:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFEncodedCircle}
% |#1| 是带参数的圆圈符号命令，|#2| 是编码，|#3| 是组合圆圈符号的 Unicode，|#4|
% 是圆圈符号的 Unicode。当 |#1| 的参数为空时，输出 |#4|，否则是 |#1| 的参数与 |#4| 的组合。
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFEncodedCircle { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_circle:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareEncodedCompositeCharacter}
%    \begin{macrocode}
\RenewDocumentCommand \DeclareEncodedCompositeCharacter { m m m m }
  { \DeclareUTFEncodedSymbol #2 [#1] { "#3 } { "0#4 } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareEncodedCompositeAccents}
% \changes{v3.2.9}{2013/12/07}{修正 \pkg{xunicode} 中的错误定义。}
%    \begin{macrocode}
\RenewDocumentCommand \DeclareEncodedCompositeAccents { m m m m }
  { \DeclareUTFEncodedAccents #2 [#1] { "#4 } { "#3 } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFDoubleEncodedAccent}
% \changes{v3.2.10}{2014/02/20}{改进 \tn{t} 等的定义方式。}
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFDoubleEncodedAccent { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_double_accent:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFDoubleEncodedSymbol}
% \changes{v3.2.10}{2014/02/21}{改进 \tn{sliding} 等的定义方式。}
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFDoubleEncodedSymbol { m O { \UTFencname } m m }
  { \@@_declare_encoded:NNnnn \@@_combine_double_symbol:nnNNn #1 {#2} {#3} {#4} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_composite:Nnn}
% 通过 \texttt{lowercase} 技巧，直接由重音符号的 Unicode 得到实际字符。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_composite:Nnn #1#2#3
  { \DeclareTextCommand #1 {#3} { \@@_text_composite:nnn {#2} {#3} } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_text_composite:nnn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_text_composite:nnn #1#2#3
  {
    \@@_begin_hook:nn {#1} {#3}
    \cs_if_exist:cTF { \@@_composite_cs:nnn {#1} {#2} {#3} }
      {
        \@@_text_composite:cnn
          { \@@_composite_cs:nnn {#1} {#2} {#3} } {#1} {#3}
      }
      { \cs_if_exist_use:cTF { ? #1 } { {#3} } {#3} }
    \@@_end_hook:nn {#1} {#3}
  }
\cs_new_protected:Npn \@@_text_composite:Nnn #1#2#3
  {
    \token_if_chardef:NTF #1
      {
        \@@_glyph_if_exist:nTF {#1}
          {#1} { \cs_if_exist_use:cTF { ? #2 } { {#3} } {#3} }
      }
      {#1}
  }
\cs_generate_variant:Nn \@@_text_composite:Nnn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_encoded:NNnnn}
% 通过 \cs{tex_Uchar:D} 直接由重音符号的 Unicode 得到实际字符。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_encoded:NNnnn #1#2#3#4#5
  {
    \exp_after:wN \@@_declare_encoded:NNNNee
      \tex_Uchar:D \@@_check_slot:n {#4} \exp_after:wN \exp_stop_f:
      \tex_Uchar:D \@@_check_slot:n {#5} \exp_stop_f:
      #1 #2 { \token_to_str:N #2 } {#3}
  }
\cs_new_protected:Npn \@@_declare_encoded:NNNNnn #1#2#3#4#5#6
  { \DeclareTextCommand #4 {#6} { #3 {#5} {#6} {#1} {#2} } }
\cs_generate_variant:Nn \@@_declare_encoded:NNnnn { c }
\cs_generate_variant:Nn \@@_declare_encoded:NNNNnn { NNNNee }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_text_combine:NnnNNn}
% 若重音命令 |#2| 与它的参数 |#6| 的复合已经由 \tn{DeclareUTFcomposite} 设置，
% 并且在当前字体中存在该字符，则直接使用。否则使用组合命令。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_text_combine:NnnNNn #1#2#3#4#5#6
  {
    \@@_begin_hook:nn {#2} {#6}
    \cs_if_exist:cTF { \@@_composite_cs:nnn {#2} {#3} {#6} }
      {
        \@@_text_combine:cNnNNn
          { \@@_composite_cs:nnn {#2} {#3} {#6} } #1 {#2} {#4} {#5} {#6}
      }
      { #1 {#6} {#2} {#4} {#5} }
    \@@_end_hook:nn {#2} {#6}
  }
\cs_new_protected:Npn \@@_text_combine:NNnNNn #1#2#3#4#5#6
  {
    \token_if_chardef:NTF #1
      { \@@_glyph_if_exist:nTF {#1} {#1} { #2 {#6} {#3} {#4} {#5} } }
      {#1}
  }
\cs_generate_variant:Nn \@@_text_combine:NNnNNn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_symbol:nnNNn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_symbol:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_symbol:nnNN }
\cs_new_protected:Npn \@@_add_symbol:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      {
        \@@_glyph_if_exist:nTF { `#4 }
          {#4}
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} }
      }
      {
        \@@_glyph_if_exist:nTF { `#3 }
          { #1#3 }
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_accent:nnNNn,\@@_add_accent:nnNN}
% 若组合重音符号的 |#3| 和基本重音符号 |#4| 在当前字体中都不存在，则转换到
% \cs{Declare\-Text\-Accent\-Default} 设置的编码或者使用
% \tn{DeclareTextCommandDefault} 中设置的命令。\texttt{0.9999} 版以前的 \XeTeX
% 需要设置 \tn{XeTeXinputnormalization} 为 $1$，才能使用字体中由基础字符和组合符号
% 对应的实际字符；而 \texttt{0.9999} 版以后的 \XeTeX 默认就启用这个功能，^^A
% \tn{XeTeXinputnormalization} 似乎是无效的，怀疑是使用 HarfBuzz 库替代 ICU 进行
% 字体排版的缘故\footnote{\url{http://tug.org/pipermail/xetex/2013-July/024579.html}}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_accent:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_accent:nnNN }
\cs_new_protected:Npn \@@_add_accent:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      {
        \@@_glyph_if_exist:nTF { `#4 }
          {#4}
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} }
      }
      {
        \@@_glyph_if_exist:nTF { `#3 }
          { #1#3 }
          {
            \@@_glyph_if_exist:nTF { `#4 }
              { \add@accent { `#4 } {#1} }
              { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_accents:nnNNn,\@@_add_accents:nnNN}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_accents:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_accents:nnNN }
\cs_new_protected:Npn \@@_add_accents:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#1} }
      {
        \@@_glyph_if_exist:nTF { `#3 }
          { \@@_glyph_if_exist:nTF { `#4 } }
          { \use_ii:nn }
          { #1#3#4 }
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3#4 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_circle:nnNNn,\@@_add_circle:nnNN,\@@_add_circle:nN}
% 对圆圈中的数字或者字母适当缩小，以适合圆圈的大小。只有字体中存在
% \texttt{U+25EF} 时，才使用这里的设置，否则还是 \LaTeX\ 中的设置。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_circle:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_circle:nnNN }
\cs_new_protected:Npn \@@_add_circle:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      {
        \@@_glyph_if_exist:nTF { `#4 }
          {#4}
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} }
      }
      {
        \@@_glyph_if_exist:nTF { `#4 }
          { \@@_add_circle:nN {#1} #4 }
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#1} }
      }
  }
\cs_new_protected:Npn \@@_add_circle:nN #1#2
  {
    \hcoffin_set:Nn \l_@@_tmp_coffin {#1}
    \hcoffin_set:Nn \l_@@_circle_coffin {#2}
    \fp_set:Nn \l_@@_circle_scale_fp
      {
        \dim_to_decimal_in_unit:nn
          {
            \fp_use:N \l_@@_circle_ratio_fp
            \coffin_wd:N \l_@@_circle_coffin
          }
          { \coffin_wd:N \l_@@_tmp_coffin }
      }
    \coffin_scale:Nnn \l_@@_tmp_coffin
      { \l_@@_circle_scale_fp } { \l_@@_circle_scale_fp }
    \coffin_attach:NnnNnnnn
      \l_@@_circle_coffin { hc } { vc }
      \l_@@_tmp_coffin    { hc } { vc } { \c_zero_dim } { \c_zero_dim }
    \coffin_typeset:Nnnnn \l_@@_circle_coffin
      { H } { l } { \c_zero_dim } { \c_zero_dim }
  }
\fp_new:N \l_@@_circle_scale_fp
\coffin_new:N \l_@@_tmp_coffin
\coffin_new:N \l_@@_circle_coffin
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\settextcircledratio}
% 设置圆圈中文字的宽度与圆圈宽度的比例，预设为 \texttt{0.7}。
%    \begin{macrocode}
\NewDocumentCommand \settextcircledratio { m }
  { \fp_set:Nn \l_@@_circle_ratio_fp {#1} }
\fp_new:N \l_@@_circle_ratio_fp
\settextcircledratio { 0.7 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_double_accent:nnNNn}
% 使 \tn{t} 等组合重音符号放在参数的第一个字母的右边。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_double_accent:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_double_accent:nnNN }
\cs_new_protected:Npn \@@_add_double_accent:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      {
        \@@_glyph_if_exist:nTF { `#4 }
          {#4}
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} }
      }
      {
        \@@_glyph_if_exist:nTF { `#3 }
          { \@@_add_double_symbol:nN {#1} #3 }
          {
            \@@_glyph_if_exist:nTF { `#4 }
              { \add@accent { `#4 } {#1} }
              { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_combine_double_symbol:nnNNn}
% 使 \tn{sliding} 等组合重音符号放在参数的第一个字母的右边。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_combine_double_symbol:nnNNn
  { \@@_text_combine:NnnNNn \@@_add_double_symbol:nnNN }
\cs_new_protected:Npn \@@_add_double_symbol:nnNN #1#2#3#4
  {
    \tl_if_blank:nTF {#1}
      {
        \@@_glyph_if_exist:nTF { `#4 }
          {#4}
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} }
      }
      {
        \@@_glyph_if_exist:nTF { `#3 }
          { \@@_add_double_symbol:nN {#1} #3 }
          { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_add_double_symbol:nN}
% 如果参数的第一个记号是字母类、其他符号类或者由 \tn{chardef} 定义，则将组合符号
% 放在它的右边，否则不作处理。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_add_double_symbol:nN #1#2
  {
    \tl_if_head_is_N_type:nTF {#1}
      {
        \exp_after:wN \exp_after:wN \exp_after:wN
        \@@_add_double_symbol_aux:NnN \exp_after:wN \exp_after:wN
          \tl_head:w #1 \q_stop \exp_after:wN { \use_none:n #1 } #2
      }
      { #1#2 }
  }
\cs_new_protected:Npn \@@_add_double_symbol_aux:NnN #1#2#3
  {
    \bool_lazy_any:nTF
      {
        { \token_if_letter_p:N #1 }
        { \token_if_other_p:N #1 }
        { \token_if_chardef_p:N #1 }
      }
      { #1#3#2 }
      { #1#2#3 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\AtBeginUTFCommand,\AtEndUTFCommand}
% 设置在符号命令前后使用的钩子，可选参数用于指定单个符号命名。可以用 |#1|
% 引用带参数的组合符号命令的参数或者符号命令对应的符号。
% \changes{v3.2.6}{2013/08/15}{可以指定特定符号命令使用的钩子。}
% \changes{v3.7.1}{2018/04/30}{修复代码重构而引入的新错误。}
%    \begin{macrocode}
\NewDocumentCommand \AtBeginUTFCommand { s O { } +m }
  {
    \tl_if_blank:nTF {#2}
      {
        \IfBooleanTF {#1}
          { \xunadd_set_begin_hook:n }
          { \xunadd_append_begin_hook:n }
      }
      { \xunadd_set_begin_hook:nn {#2} }
      {#3}
  }
\NewDocumentCommand \AtEndUTFCommand { s O { } +m }
  {
    \tl_if_blank:nTF {#2}
      {
        \IfBooleanTF {#1}
          { \xunadd_set_end_hook:n }
          { \xunadd_append_end_hook:n }
      }
      { \xunadd_set_end_hook:nn {#2} }
      {#3}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xunadd_set_begin_hook:n,\xunadd_set_end_hook:n}
%    \begin{macrocode}
\cs_new_protected:Npn \xunadd_set_begin_hook:n
  { \tl_set:Nn \l_@@_begin_hook_tl }
\cs_new_protected:Npn \xunadd_append_begin_hook:n
  { \tl_put_right:Nn \l_@@_begin_hook_tl }
\cs_new_protected:Npn \xunadd_set_end_hook:n
  { \tl_set:Nn \l_@@_end_hook_tl }
\cs_new_protected:Npn \xunadd_append_end_hook:n
  { \tl_put_right:Nn \l_@@_end_hook_tl }
\cs_new_protected:Npn \xunadd_set_begin_hook:nn
  { \@@_set_cmd_hook:nnn { begin } }
\cs_new_protected:Npn \xunadd_set_end_hook:nn
  { \@@_set_cmd_hook:nnn { end } }
\cs_new_protected:Npn \@@_set_cmd_hook:nnn #1#2#3
  {
    \cs_set_protected:cpn
      {
        \tl_if_single:nTF {#2}
          { \use:c { @@_#1_csname:n } { \token_to_str:N #2 } }
          { \@@_set_cmd_hook_aux:Nnwn #2 \q_stop {#1} }
      } ##1
      {#3}
  }
\cs_new:Npn \@@_set_cmd_hook_aux:Nnwn #1#2 \q_stop #3
  { \use:c { @@_#3_csname:n } { \token_to_str:N #1 - \tl_to_str:n {#2} } }
\cs_new:Npn \@@_begin_csname:n #1 { @@_begin_#1_hook:n }
\cs_new:Npn \@@_end_csname:n #1   { @@_end_#1_hook:n }
\tl_new:N \l_@@_begin_hook_tl
\tl_new:N \l_@@_end_hook_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_begin_hook:nn,\@@_end_hook:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_begin_hook:nn #1#2
  {
    \tl_use:N \l_@@_begin_hook_tl
    \cs_if_exist_use:cF { \@@_begin_csname:n { #1 - \tl_to_str:n {#2} } }
      { \cs_if_exist_use:cF { \@@_begin_csname:n {#1} } { \use_none:n } }
      {#2}
  }
\cs_new_protected:Npn \@@_end_hook:nn #1#2
  {
    \cs_if_exist_use:cF { \@@_end_csname:n { #1 - \tl_to_str:n {#2} } }
      { \cs_if_exist_use:cF { \@@_end_csname:n {#1} } { \use_none:n } }
      {#2}
    \tl_use:N \l_@@_end_hook_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareUTFTIPACommand}
% \changes{v3.2.10}{2014/02/20}{检查 \tn{t} 和 \tn{sliding} 的参数是否以 \tn{textipa} 开头。}
%    \begin{macrocode}
\NewDocumentCommand \DeclareUTFTIPACommand { O { \UTFencname } m }
  { \use:e { \@@_text_tipa_command:Nnn \exp_not:N #2 { \token_to_str:N #2 } {#1} } }
\cs_new_protected:Npn \@@_text_tipa_command:Nnn #1#2#3
  {
    \cs_set_eq:cc { UTF/#3#2 } { #3#2 }
    \DeclareTextCommand #1 {#3} { \@@_text_tipa_command:nnn {#3} {#2} }
  }
\cs_new_protected:Npn \@@_text_tipa_command:nnn #1#2#3
  {
    \exp_args:Ncc \@@_check_for_tipa:NNn
      { \use_none:n #2 } { UTF/#1#2 } {#3}
  }
\cs_new_protected:Npn \@@_check_for_tipa:NNn #1#2#3
  {
    \tl_if_head_eq_meaning:nNTF {#3} \textipa
      {
        \exp_after:wN \tipacatchonechar \exp_after:wN
          { \exp_after:wN #1 \use_none:n #3 }
      }
      { #2 {#3} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\xunadd_get_slot:nn}
% |#1| 是编码，|#2| 是诸如 \tn{textendash} 或 |\v C| 等形式的文本命令，取得他们对应的字符编码。
%    \begin{macrocode}
\cs_new_protected:Npn \xunadd_get_slot:nn #1#2
  { \@@_get_slot:wn #2 \q_nil \q_stop {#1} }
\cs_new_protected:Npn \@@_get_slot:wn #1#2#3 \q_stop #4
  {
    \int_set:Nn \l_xunadd_slot_int { -1 }
    \bool_set_false:N \l_xunadd_rest_bool
    \group_begin: \exp_args:Nccc \group_end:
    { @@_get_slot:NNnn }
      { #4 \token_to_str:N #1 }
      { \@@_composite_cs:Nnn #1 {#4} {#2} }
      {#2}
      {#3}
  }
\int_new:N \l_xunadd_slot_int
\bool_new:N \l_xunadd_rest_bool
\cs_new_protected:Npn \@@_get_slot:NNnn #1#2#3#4
  {
    \cs_if_free:NF #1
      {
        \cs_if_exist:NTF #2
          { \@@_get_composite_slot:Nn #2 {#4} }
          { \@@_get_character_slot:Nn #1 { #3 #4 } }
      }
  }
\cs_new_protected:Npn \@@_get_composite_slot:Nn #1#2
  {
    \token_if_chardef:NT #1
      {
        \int_set:Nn \l_xunadd_slot_int {#1}
        \quark_if_nil:nF {#2}
          { \bool_set_true:N \l_xunadd_rest_bool }
      }
  }
\cs_new_protected:Npn \@@_get_character_slot:Nn #1
  {
    \exp_after:wN \@@_get_character_slot_aux:wn #1
      \@@_text_character:nN \q_nil \q_nil \q_stop
  }
\cs_new_protected:Npn \@@_get_character_slot_aux:wn
  #1 \@@_text_character:nN #2#3#4 \q_stop #5
  {
    \quark_if_nil:nF {#2}
      {
        \int_set:Nn \l_xunadd_slot_int { `#3 }
        \quark_if_nil:nF {#5}
          { \bool_set_true:N \l_xunadd_rest_bool }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.7.2}{2018/05/12}{解决与 \pkg{microtype} 宏包的兼容问题。}
%
% \begin{macro}[int]{\xunadd@microtype@is@charx}
% \pkg{microtype} 宏包中使用的函数，我们通过对 \tn{MT@is@charx} 打补丁来实现功能。
%    \begin{macrocode}
\cs_new_protected_nopar:Npn \xunadd@microtype@is@charx #1 \relax
  {
    \use:e
      { \xunadd_get_slot:nn { \MT@encoding } { \tex_the:D \MT@toks } }
    \int_compare:nNnTF \l_xunadd_slot_int < \c_zero_int
      { \xunadd@original@is@charx #1 \relax }
      {
        \cs_set_nopar:Npe \MT@char@ { \int_use:N \l_xunadd_slot_int }
        \bool_if:NT \l_xunadd_rest_bool { \MT@norestfalse }
      }
  }
\cs_new_protected_nopar:Npn \xunadd@microtype@hook
  {
    \cs_if_free:NF \MT@is@charx
      {
        \cs_new_eq:NN \xunadd@original@is@charx \MT@is@charx
        \cs_set_eq:NN \MT@is@charx \xunadd@microtype@is@charx
        \cs_set_eq:NN \MT@warn@unknown@once \use_none:n
      }
  }
\@ifpackageloaded { microtype }
  { \use:n } { \AtBeginDocument }
  { \xunadd@microtype@hook }
%    \end{macrocode}
% \end{macro}
%
% \changes{v3.2.8}{2013/12/05}{启用 \pkg{xunicode} 中的带圈数字和字母设置。}
%
%    \begin{macrocode}
%</xunicode>
%<*xunextra>
%    \end{macrocode}
%
% \changes{v3.7.0}{2018/03/16}{补充定义 \tn{texthyphenationpoint} 和 \tn{texttwoemdash}。}
%
% 我们补充定义 HYPHENATION POINT 和 TWO-EM DASH，他们默认被归入 CJK 标点符号。
%    \begin{macrocode}
\DeclareUTFSymbol\texthyphenationpoint{"2027}
\DeclareUTFSymbol\texttwoemdash{"2E3A}
%    \end{macrocode}
%
% 以下内容选自 \pkg{xunicode}，并做了适当修改。
% \changes{v3.10.0}{2023/11/22}{修复 \tn{textsbleftarrow} 的定义。}
%    \begin{macrocode}
\DeclareUTFComposite\textsuperscript
\DeclareUTFComposite\textsubscript
\DeclareUTFEncodedAccent\textsbleftarrow{"20EE}{"02FF}
\DeclareUTFEncodedAccent\`{"0300}{"02CB}
\DeclareUTFEncodedAccent\capitalgrave{"0300}{"02CB}
\DeclareUTFEncodedAccent\'{"0301}{"02CA}
\DeclareUTFEncodedAccent\capitalacute{"0301}{"02CA}
\DeclareUTFEncodedAccent\^{"0302}{"02C6}
\DeclareUTFEncodedAccent\capitalcircumflex{"0302}{"02C6}
\DeclareUTFEncodedAccent\~{"0303}{"02DC}
\DeclareUTFEncodedAccent\capitaltilde{"0303}{"02DC}
\DeclareUTFEncodedAccent\={"0304}{"02C9}
\DeclareUTFEncodedAccent\capitalmacron{"0304}{"02C9}
\DeclareUTFEncodedAccent\textoverline{"0305}{"203E}
\DeclareUTFEncodedAccent\u{"0306}{"02D8}
\DeclareUTFEncodedAccent\capitalbreve{"0306}{"02D8}
\DeclareUTFEncodedAccent\.{"0307}{"02D9}
\DeclareUTFEncodedAccent\capitaldotaccent{"0307}{"02D9}
\DeclareUTFEncodedAccent\"{"0308}{"00A8}
\DeclareUTFEncodedAccent\capitaldieresis{"0308}{"00A8}
\DeclareUTFEncodedAccent\m{"0309}{"0309}
\DeclareUTFEncodedAccent\texthookabove{"0309}{"0309}
\DeclareUTFEncodedAccent\r{"030A}{"02DA}
\DeclareUTFEncodedAccent\capitalring{"030A}{"02DA}
\DeclareUTFEncodedAccent\H{"030B}{"02DD}
\DeclareUTFEncodedAccent\capitalhungarumlaut{"030B}{"02DD}
\DeclareUTFEncodedAccent\v{"030C}{"02C7}
\DeclareUTFEncodedAccent\capitalcaron{"030C}{"02C7}
\DeclareUTFEncodedAccent\textvbaraccent{"030D}{"02C8}
\DeclareUTFEncodedAccent\textdoublevbaraccent{"030E}{"0022}
\DeclareUTFEncodedAccent\U{"030E}{"0022}
\DeclareUTFEncodedAccent\textdoublegrave{"030F}{"02F5}
\DeclareUTFEncodedAccent\G{"030F}{"02F5}
\DeclareUTFEncodedAccent\textdotbreve{"0310}{"0310}
\DeclareUTFEncodedAccent\textroundcap{"0311}{"0311}
\DeclareUTFEncodedAccent\newtie{"0311}{"0311}
\DeclareUTFEncodedAccent\capitalnewtie{"0311}{"0311}
\DeclareUTFEncodedAccent\textturncommaabove{"0312}{"02BB}
\DeclareUTFEncodedAccent\textcommaabove{"0313}{"02BC}
\DeclareUTFEncodedAccent\textrevcommaabove{"0314}{"02BD}
\DeclareUTFEncodedAccent\overbridge{"0346}{"0346}
\DeclareUTFEncodedAccent\crtilde{"034A}{"034A}
\DeclareUTFEncodedAccent\dottedtilde{"034B}{"034B}
\DeclareUTFEncodedAccent\doubletilde{"034C}{"034C}
\DeclareUTFEncodedAccent\textrightarrowhead{"0350}{"02C3}
\DeclareUTFEncodedAccent\textlefthalfring{"0351}{"02D3}
\DeclareUTFEncodedAccent\textrighthalfring{"0357}{"02D2}
\DeclareUTFDoubleEncodedSymbol\textdoublebrevebelow{"035C}{"035C}
\DeclareUTFDoubleEncodedAccent\textdoublebreve{"035D}{"035D}
\DeclareUTFDoubleEncodedAccent\textdoublemacron{"035E}{"035E}
\DeclareUTFDoubleEncodedSymbol\textdoublemacronbelow{"035F}{"035F}
\DeclareUTFDoubleEncodedAccent\textdoubletilde{"0360}{"0360}
\DeclareUTFDoubleEncodedAccent\t{"0361}{"0361}
\DeclareUTFDoubleEncodedAccent\capitaltie{"0361}{"0361}
\DeclareUTFDoubleEncodedAccent\texttoptiebar{"0361}{"0361}
\DeclareUTFDoubleEncodedSymbol\sliding{"0362}{"0362}
\DeclareUTFTIPACommand\t
\DeclareUTFTIPACommand\capitaltie
\DeclareUTFTIPACommand\texttoptiebar
\DeclareUTFTIPACommand\sliding
\DeclareUTFEncodedAccent\texthighrise{"1DC4}{"1DC4}
\DeclareUTFEncodedAccent\textlowrise{"1DC5}{"1DC5}
\DeclareUTFEncodedAccent\textrisefall{"1DC8}{"1DC8}
\DeclareUTFEncodedAccent\textfallrise{"1DC9}{"1DC9}
\DeclareUTFEncodedAccent\textaolig{"1DD5}{"1DD5}
\DeclareUTFCompositeSymbol\textundertie{H}{"1E2A}
\DeclareUTFCompositeSymbol\textundertie{h}{"1E2B}
\DeclareUTFEncodedAccents\textcircumgrave{"0302}{"0301}
\DeclareUTFSymbol\textFinv{"2132}
\DeclareUTFSymbol\textaleph{"2135}
\DeclareUTFSymbol\textbeth{"2136}
\DeclareUTFSymbol\textgimel{"2137}
\DeclareUTFSymbol\textdaleth{"2138}
\DeclareUTFSymbol\textGame{"2141}
\DeclareUTFCompositeCommand\tonebar{25}{\tonebar{2}\tonebar{5}}
\DeclareUTFCompositeCommand\tonebar{52}{\tonebar{5}\tonebar{2}}
\DeclareUTFSymbol\textbigcircle{"25EF}
\DeclareUTFEncodedCircle\textcircled{"20DD}{"25EF}
\DeclareUTFCompositeSymbol\textcircled{0}{"24EA}
\DeclareUTFCompositeSymbol\textcircled{1}{"2460}
\DeclareUTFCompositeSymbol\textcircled{2}{"2461}
\DeclareUTFCompositeSymbol\textcircled{3}{"2462}
\DeclareUTFCompositeSymbol\textcircled{4}{"2463}
\DeclareUTFCompositeSymbol\textcircled{5}{"2464}
\DeclareUTFCompositeSymbol\textcircled{6}{"2465}
\DeclareUTFCompositeSymbol\textcircled{7}{"2466}
\DeclareUTFCompositeSymbol\textcircled{8}{"2467}
\DeclareUTFCompositeSymbol\textcircled{9}{"2468}
\DeclareUTFCompositeSymbol\textcircled{10}{"2469}
\DeclareUTFCompositeSymbol\textcircled{11}{"246A}
\DeclareUTFCompositeSymbol\textcircled{12}{"246B}
\DeclareUTFCompositeSymbol\textcircled{13}{"246C}
\DeclareUTFCompositeSymbol\textcircled{14}{"246D}
\DeclareUTFCompositeSymbol\textcircled{15}{"246E}
\DeclareUTFCompositeSymbol\textcircled{16}{"246F}
\DeclareUTFCompositeSymbol\textcircled{17}{"2470}
\DeclareUTFCompositeSymbol\textcircled{18}{"2471}
\DeclareUTFCompositeSymbol\textcircled{19}{"2472}
\DeclareUTFCompositeSymbol\textcircled{20}{"2473}
\DeclareUTFCompositeSymbol\textcircled{21}{"3251}
\DeclareUTFCompositeSymbol\textcircled{22}{"3252}
\DeclareUTFCompositeSymbol\textcircled{23}{"3253}
\DeclareUTFCompositeSymbol\textcircled{24}{"3254}
\DeclareUTFCompositeSymbol\textcircled{25}{"3255}
\DeclareUTFCompositeSymbol\textcircled{26}{"3256}
\DeclareUTFCompositeSymbol\textcircled{27}{"3257}
\DeclareUTFCompositeSymbol\textcircled{28}{"3258}
\DeclareUTFCompositeSymbol\textcircled{29}{"3259}
\DeclareUTFCompositeSymbol\textcircled{30}{"325A}
\DeclareUTFCompositeSymbol\textcircled{31}{"325B}
\DeclareUTFCompositeSymbol\textcircled{32}{"325C}
\DeclareUTFCompositeSymbol\textcircled{33}{"325D}
\DeclareUTFCompositeSymbol\textcircled{34}{"325E}
\DeclareUTFCompositeSymbol\textcircled{35}{"325F}
\DeclareUTFCompositeSymbol\textcircled{36}{"32B1}
\DeclareUTFCompositeSymbol\textcircled{37}{"32B2}
\DeclareUTFCompositeSymbol\textcircled{38}{"32B3}
\DeclareUTFCompositeSymbol\textcircled{39}{"32B4}
\DeclareUTFCompositeSymbol\textcircled{40}{"32B5}
\DeclareUTFCompositeSymbol\textcircled{41}{"32B6}
\DeclareUTFCompositeSymbol\textcircled{42}{"32B7}
\DeclareUTFCompositeSymbol\textcircled{43}{"32B8}
\DeclareUTFCompositeSymbol\textcircled{44}{"32B9}
\DeclareUTFCompositeSymbol\textcircled{45}{"32BA}
\DeclareUTFCompositeSymbol\textcircled{46}{"32BB}
\DeclareUTFCompositeSymbol\textcircled{47}{"32BC}
\DeclareUTFCompositeSymbol\textcircled{48}{"32BD}
\DeclareUTFCompositeSymbol\textcircled{49}{"32BE}
\DeclareUTFCompositeSymbol\textcircled{50}{"32BF}
\DeclareUTFCompositeSymbol\textcircled{A}{"24B6}
\DeclareUTFCompositeSymbol\textcircled{B}{"24B7}
\DeclareUTFCompositeSymbol\textcircled{C}{"24B8}
\DeclareUTFCompositeSymbol\textcircled{D}{"24B9}
\DeclareUTFCompositeSymbol\textcircled{E}{"24BA}
\DeclareUTFCompositeSymbol\textcircled{F}{"24BB}
\DeclareUTFCompositeSymbol\textcircled{G}{"24BC}
\DeclareUTFCompositeSymbol\textcircled{H}{"24BD}
\DeclareUTFCompositeSymbol\textcircled{I}{"24BE}
\DeclareUTFCompositeSymbol\textcircled{J}{"24BF}
\DeclareUTFCompositeSymbol\textcircled{K}{"24C0}
\DeclareUTFCompositeSymbol\textcircled{L}{"24C1}
\DeclareUTFCompositeSymbol\textcircled{M}{"24C2}
\DeclareUTFCompositeSymbol\textcircled{N}{"24C3}
\DeclareUTFCompositeSymbol\textcircled{O}{"24C4}
\DeclareUTFCompositeSymbol\textcircled{P}{"24C5}
\DeclareUTFCompositeSymbol\textcircled{Q}{"24C6}
\DeclareUTFCompositeSymbol\textcircled{R}{"24C7}
\DeclareUTFCompositeSymbol\textcircled{S}{"24C8}
\DeclareUTFCompositeSymbol\textcircled{T}{"24C9}
\DeclareUTFCompositeSymbol\textcircled{U}{"24CA}
\DeclareUTFCompositeSymbol\textcircled{V}{"24CB}
\DeclareUTFCompositeSymbol\textcircled{W}{"24CC}
\DeclareUTFCompositeSymbol\textcircled{X}{"24CD}
\DeclareUTFCompositeSymbol\textcircled{Y}{"24CE}
\DeclareUTFCompositeSymbol\textcircled{Z}{"24CF}
\DeclareUTFCompositeSymbol\textcircled{a}{"24D0}
\DeclareUTFCompositeSymbol\textcircled{b}{"24D1}
\DeclareUTFCompositeSymbol\textcircled{c}{"24D2}
\DeclareUTFCompositeSymbol\textcircled{d}{"24D3}
\DeclareUTFCompositeSymbol\textcircled{e}{"24D4}
\DeclareUTFCompositeSymbol\textcircled{f}{"24D5}
\DeclareUTFCompositeSymbol\textcircled{g}{"24D6}
\DeclareUTFCompositeSymbol\textcircled{h}{"24D7}
\DeclareUTFCompositeSymbol\textcircled{i}{"24D8}
\DeclareUTFCompositeSymbol\textcircled{j}{"24D9}
\DeclareUTFCompositeSymbol\textcircled{k}{"24DA}
\DeclareUTFCompositeSymbol\textcircled{l}{"24DB}
\DeclareUTFCompositeSymbol\textcircled{m}{"24DC}
\DeclareUTFCompositeSymbol\textcircled{n}{"24DD}
\DeclareUTFCompositeSymbol\textcircled{o}{"24DE}
\DeclareUTFCompositeSymbol\textcircled{p}{"24DF}
\DeclareUTFCompositeSymbol\textcircled{q}{"24E0}
\DeclareUTFCompositeSymbol\textcircled{r}{"24E1}
\DeclareUTFCompositeSymbol\textcircled{s}{"24E2}
\DeclareUTFCompositeSymbol\textcircled{t}{"24E3}
\DeclareUTFCompositeSymbol\textcircled{u}{"24E4}
\DeclareUTFCompositeSymbol\textcircled{v}{"24E5}
\DeclareUTFCompositeSymbol\textcircled{w}{"24E6}
\DeclareUTFCompositeSymbol\textcircled{x}{"24E7}
\DeclareUTFCompositeSymbol\textcircled{y}{"24E8}
\DeclareUTFCompositeSymbol\textcircled{z}{"24E9}
\DeclareUTFCompositeSymbol\textsuperscript{h}{"02B0}
\DeclareUTFCompositeSymbol\textsuperscript{\texthth}{"02B1}
\DeclareUTFCompositeSymbol\textsuperscript{j}{"02B2}
\DeclareUTFCompositeSymbol\textsuperscript{r}{"02B3}
\DeclareUTFCompositeSymbol\textsuperscript{\textturnr}{"02B4}
\DeclareUTFCompositeSymbol\textsuperscript{\textturnrrtail}{"02B5}
\DeclareUTFCompositeSymbol\textsuperscript{\textinvscr}{"02B6}
\DeclareUTFCompositeSymbol\textsuperscript{w}{"02B7}
\DeclareUTFCompositeSymbol\textsuperscript{y}{"02B8}
\DeclareUTFCompositeSymbol\textsuperscript{\textbabygamma}{"02E0}
\DeclareUTFCompositeSymbol\textsuperscript{\textgammalatinsmall}{"02E0}
\DeclareUTFCompositeSymbol\textsuperscript{l}{"02E1}
\DeclareUTFCompositeSymbol\textsuperscript{s}{"02E2}
\DeclareUTFCompositeSymbol\textsuperscript{x}{"02E3}
\DeclareUTFCompositeSymbol\textsuperscript{\textrevglotstop}{"02E4}
\DeclareUTFCompositeSymbol\textsuperscript{\textrevepsilon}{"1D4C}
\DeclareUTFCompositeSymbol\textsuperscript{\cyrn}{"1D78}
\DeclareUTFCompositeSymbol\textsuperscript{\textbarsci}{"1DA7}
\DeclareUTFCompositeSymbol\textsuperscript{V}{"2C7D}
\DeclareUTFCompositeSymbol\textsuperscript{\textHbar}{"A7F8}
\DeclareUTFCompositeSymbol\textsuperscript{\textHslash}{"A7F8}
\DeclareUTFCompositeSymbol\textsuperscript{\oe}{"A7F9}
\DeclareUTFCompositeSymbol\textsubscript{h}{"2095}
\DeclareUTFCompositeSymbol\textsubscript{k}{"2096}
\DeclareUTFCompositeSymbol\textsubscript{l}{"2097}
\DeclareUTFCompositeSymbol\textsubscript{m}{"2098}
\DeclareUTFCompositeSymbol\textsubscript{n}{"2099}
\DeclareUTFCompositeSymbol\textsubscript{p}{"209A}
\DeclareUTFCompositeSymbol\textsubscript{s}{"209B}
\DeclareUTFCompositeSymbol\textsubscript{t}{"209C}
%    \end{macrocode}
%
% \changes{v3.2.9}{2013/12/07}
% {增加 \file{xunicode-extra.def} 中，用于加入 \file{puenc.def} 中的符号定义。}
%
% 以下定义取自 \pkg{hyperref} 的 \file{puenc.def}。
% \changes{v3.10.0}{2023/11/10}{修复拼错的命令名，
%   \tn{cyreref} $\to$ \tn{cyrerev} 和
%   \tn{textDiamandSolid} $\to$ \tn{textDiamondSolid}。}
% \changes{v3.10.0}{2023/11/11}{修复 \tn{textnleqslant} 的定义，
%   修复 \tn{textnbacksim} 和 \tn{textnlessapprox} 定义里的自引。}
% \changes{v3.10.0}{2023/11/27}{修复组合符号 \texttt{U+04AA} 的定义。}
%    \begin{macrocode}
\DeclareUTFEncodedAccent\textinvbreve{"0311}{"0311}
\DeclareUTFEncodedSymbol\textsubbreve{"032E}{"203F}
\DeclareUTFSymbol\textHT{"0009}
\DeclareUTFSymbol\textLF{"000A}
\DeclareUTFSymbol\textCR{"000D}
\DeclareUTFSymbol\textnumbersign{"0023}
\DeclareUTFSymbol\textparenleft{"0028}
\DeclareUTFSymbol\textparenright{"0029}
\DeclareUTFSymbol\textMVPlus{"002B}
\DeclareUTFSymbol\textMVComma{"002C}
\DeclareUTFSymbol\textMVMinus{"002D}
\DeclareUTFSymbol\textMVPeriod{"002E}
\DeclareUTFSymbol\textMVDivision{"002F}
\DeclareUTFSymbol\textMVZero{"0030}
\DeclareUTFSymbol\textMVOne{"0031}
\DeclareUTFSymbol\textMVTwo{"0032}
\DeclareUTFSymbol\textMVThree{"0033}
\DeclareUTFSymbol\textMVFour{"0034}
\DeclareUTFSymbol\textMVFive{"0035}
\DeclareUTFSymbol\textMVSix{"0036}
\DeclareUTFSymbol\textMVSeven{"0037}
\DeclareUTFSymbol\textMVEight{"0038}
\DeclareUTFSymbol\textMVNine{"0039}
\DeclareUTFSymbol\textMVAt{"0040}
\DeclareUTFCompositeCommand\.{\i}{i}
\DeclareUTFCompositeCommand\.{i}{i}
\DeclareUTFSymbol\textlnot{"00AC}
\DeclareUTFSymbol\textplusminus{"00B1}
\DeclareUTFSymbol\textcedilla{"00B8}
\DeclareUTFSymbol\textmultiply{"00D7}
\DeclareUTFSymbol\textThorn{"00DE}
\DeclareUTFSymbol\textdivide{"00F7}
\DeclareUTFSymbol\textHslash{"0126}
\DeclareUTFCompositeSymbol\k{\i}{"012F}
\DeclareUTFCompositeSymbol\.{L}{"013F}
\DeclareUTFCompositeSymbol\.{l}{"0140}
\DeclareUTFSymbol\textnapostrophe{"0149}
\DeclareUTFSymbol\textTslash{"0166}
\DeclareUTFSymbol\texttslash{"0167}
\DeclareUTFSymbol\textlongs{"017F}
\DeclareUTFSymbol\texthausaB{"0181}
\DeclareUTFSymbol\texthausaD{"018A}
\DeclareUTFSymbol\textrevE{"018E}
\DeclareUTFSymbol\texthausaK{"0198}
\DeclareUTFSymbol\textPUnrleg{"019E}
\DeclareUTFSymbol\textinve{"01DD}
\DeclareUTFSymbol\textGslash{"01E4}
\DeclareUTFSymbol\textgslash{"01E5}
\DeclareUTFCompositeSymbol\textinvbreve{E}{"0206}
\DeclareUTFCompositeSymbol\textinvbreve{e}{"0207}
\DeclareUTFCompositeSymbol\textinvbreve{I}{"020A}
\DeclareUTFCompositeSymbol\textinvbreve{i}{"020B}
\DeclareUTFCompositeSymbol\textinvbreve{\i}{"020B}
\DeclareUTFCompositeSymbol\textinvbreve{O}{"020E}
\DeclareUTFCompositeSymbol\textinvbreve{o}{"020F}
\DeclareUTFCompositeSymbol\textinvbreve{U}{"0216}
\DeclareUTFCompositeSymbol\textinvbreve{u}{"0217}
\DeclareUTFSymbol\j{"0237}
\DeclareUTFSymbol\textPUdblig{"0238}
\DeclareUTFSymbol\textPUqplig{"0239}
\DeclareUTFSymbol\textslashc{"023C}
\DeclareUTFSymbol\textniepsilon{"025B}
\DeclareUTFSymbol\textipagamma{"0263}
\DeclareUTFSymbol\textniiota{"0269}
\DeclareUTFSymbol\textniphi{"0278}
\DeclareUTFSymbol\textniupsilon{"028A}
\DeclareUTFSymbol\textring{"02DA}
\DeclareUTFSymbol\texttilde{"02DC}
\DeclareUTFSymbol\texthungarumlaut{"02DD}
\DeclareUTFSymbol\textringlow{"02F3}
\DeclareUTFSymbol\texttildelow{"02F7}
\DeclareUTFCommand\textnewtie{\textinvbreve\ }
\DeclareUTFCommand\textdotbelow{\d\ }
\DeclareUTFSymbol\textmacronbelow{"02CD}
\DeclareUTFCommand\texttie{\t\ }
\DeclareUTFSymbol\textnumeralsigngreek{"0374}
\DeclareUTFSymbol\textnumeralsignlowergreek{"0375}
\DeclareUTFCompositeSymbol\'{\textAlpha}{"0386}
\DeclareUTFCompositeSymbol\'{\textEpsilon}{"0388}
\DeclareUTFCompositeSymbol\'{\textEta}{"0389}
\DeclareUTFCompositeSymbol\'{\textIota}{"038A}
\DeclareUTFCompositeSymbol\'{\textOmicron}{"038C}
\DeclareUTFCompositeSymbol\'{\textUpsilon}{"038E}
\DeclareUTFCompositeSymbol\'{\textOmega}{"038F}
\DeclareUTFCompositeSymbol\'{\textIotadieresis}{"0390}
\DeclareUTFSymbol\textIotadieresis{"03AA}
\DeclareUTFCompositeSymbol\"{\textIota}{"03AA}
\DeclareUTFCompositeSymbol\"{\textUpsilon}{"03AB}
\DeclareUTFCompositeSymbol\'{\textalpha}{"03AC}
\DeclareUTFCompositeSymbol\'{\textepsilon}{"03AD}
\DeclareUTFCompositeSymbol\'{\texteta}{"03AE}
\DeclareUTFCompositeSymbol\'{\textiota}{"03AF}
\DeclareUTFCompositeSymbol\"{\textupsilonacute}{"03B0}
\DeclareUTFSymbol\textmugreek{"03BC}
\DeclareUTFSymbol\textvarsigma{"03C2}
\DeclareUTFCompositeSymbol\"{\textiota}{"03CA}
\DeclareUTFCompositeSymbol\"{\textupsilon}{"03CB}
\DeclareUTFCompositeSymbol\'{\textomicron}{"03CC}
\DeclareUTFSymbol\textupsilonacute{"03CD}
\DeclareUTFCompositeSymbol\'{\textupsilon}{"03CD}
\DeclareUTFCompositeSymbol\'{\textomega}{"03CE}
\DeclareUTFSymbol\textStigmagreek{"03DA}
\DeclareUTFSymbol\textstigmagreek{"03DB}
\DeclareUTFSymbol\textDigammagreek{"03DC}
\DeclareUTFSymbol\textdigammagreek{"03DD}
\DeclareUTFSymbol\textKoppagreek{"03DE}
\DeclareUTFSymbol\textkoppagreek{"03DF}
\DeclareUTFSymbol\textSampigreek{"03E0}
\DeclareUTFSymbol\textsampigreek{"03E1}
\DeclareUTFSymbol\textbackepsilon{"03F6}
\DeclareUTFCompositeSymbol\`{\CYRE}{"0400}
\DeclareUTFSymbol\CYRYO{"0401}
\DeclareUTFCompositeSymbol\"{\CYRE}{"0401}
\DeclareUTFSymbol\CYRDJE{"0402}
\DeclareUTFCompositeSymbol\'{\CYRG}{"0403}
\DeclareUTFSymbol\CYRIE{"0404}
\DeclareUTFSymbol\CYRDZE{"0405}
\DeclareUTFSymbol\CYRII{"0406}
\DeclareUTFSymbol\CYRYI{"0407}
\DeclareUTFCompositeSymbol\"{\CYRII}{"0407}
\DeclareUTFSymbol\CYRJE{"0408}
\DeclareUTFSymbol\CYRLJE{"0409}
\DeclareUTFSymbol\CYRNJE{"040A}
\DeclareUTFSymbol\CYRTSHE{"040B}
\DeclareUTFCompositeSymbol\'{\CYRK}{"040C}
\DeclareUTFCompositeSymbol\`{\CYRI}{"040D}
\DeclareUTFSymbol\CYRUSHRT{"040E}
\DeclareUTFCompositeSymbol\U{\CYRU}{"040E}
\DeclareUTFSymbol\CYRDZHE{"040F}
\DeclareUTFSymbol\CYRA{"0410}
\DeclareUTFSymbol\CYRB{"0411}
\DeclareUTFSymbol\CYRV{"0412}
\DeclareUTFSymbol\CYRG{"0413}
\DeclareUTFSymbol\CYRD{"0414}
\DeclareUTFSymbol\CYRE{"0415}
\DeclareUTFSymbol\CYRZH{"0416}
\DeclareUTFSymbol\CYRZ{"0417}
\DeclareUTFSymbol\CYRI{"0418}
\DeclareUTFSymbol\CYRISHRT{"0419}
\DeclareUTFCompositeSymbol\U{\CYRI}{"0419}
\DeclareUTFSymbol\CYRK{"041A}
\DeclareUTFSymbol\CYRL{"041B}
\DeclareUTFSymbol\CYRM{"041C}
\DeclareUTFSymbol\CYRN{"041D}
\DeclareUTFSymbol\CYRO{"041E}
\DeclareUTFSymbol\CYRP{"041F}
\DeclareUTFSymbol\CYRR{"0420}
\DeclareUTFSymbol\CYRS{"0421}
\DeclareUTFSymbol\CYRT{"0422}
\DeclareUTFSymbol\CYRU{"0423}
\DeclareUTFSymbol\CYRF{"0424}
\DeclareUTFSymbol\CYRH{"0425}
\DeclareUTFSymbol\CYRC{"0426}
\DeclareUTFSymbol\CYRCH{"0427}
\DeclareUTFSymbol\CYRSH{"0428}
\DeclareUTFSymbol\CYRSHCH{"0429}
\DeclareUTFSymbol\CYRHRDSN{"042A}
\DeclareUTFSymbol\CYRERY{"042B}
\DeclareUTFSymbol\CYRSFTSN{"042C}
\DeclareUTFSymbol\CYREREV{"042D}
\DeclareUTFSymbol\CYRYU{"042E}
\DeclareUTFSymbol\CYRYA{"042F}
\DeclareUTFSymbol\cyra{"0430}
\DeclareUTFSymbol\cyrb{"0431}
\DeclareUTFSymbol\cyrv{"0432}
\DeclareUTFSymbol\cyrg{"0433}
\DeclareUTFSymbol\cyrd{"0434}
\DeclareUTFSymbol\cyre{"0435}
\DeclareUTFSymbol\cyrzh{"0436}
\DeclareUTFSymbol\cyrz{"0437}
\DeclareUTFSymbol\cyri{"0438}
\DeclareUTFSymbol\cyrishrt{"0439}
\DeclareUTFCompositeSymbol\U{\cyri}{"0439}
\DeclareUTFSymbol\cyrk{"043A}
\DeclareUTFSymbol\cyrl{"043B}
\DeclareUTFSymbol\cyrm{"043C}
\DeclareUTFSymbol\cyrn{"043D}
\DeclareUTFSymbol\cyro{"043E}
\DeclareUTFSymbol\cyrp{"043F}
\DeclareUTFSymbol\cyrr{"0440}
\DeclareUTFSymbol\cyrs{"0441}
\DeclareUTFSymbol\cyrt{"0442}
\DeclareUTFSymbol\cyru{"0443}
\DeclareUTFSymbol\cyrf{"0444}
\DeclareUTFSymbol\cyrh{"0445}
\DeclareUTFSymbol\cyrc{"0446}
\DeclareUTFSymbol\cyrch{"0447}
\DeclareUTFSymbol\cyrsh{"0448}
\DeclareUTFSymbol\cyrshch{"0449}
\DeclareUTFSymbol\cyrhrdsn{"044A}
\DeclareUTFSymbol\cyrery{"044B}
\DeclareUTFSymbol\cyrsftsn{"044C}
\DeclareUTFSymbol\cyrerev{"044D}
\DeclareUTFSymbol\cyryu{"044E}
\DeclareUTFSymbol\cyrya{"044F}
\DeclareUTFCompositeSymbol\`{\cyre}{"0450}
\DeclareUTFSymbol\cyryo{"0451}
\DeclareUTFCompositeSymbol\"{\cyre}{"0451}
\DeclareUTFSymbol\cyrdje{"0452}
\DeclareUTFCompositeSymbol\'{\cyrg}{"0453}
\DeclareUTFSymbol\cyrie{"0454}
\DeclareUTFSymbol\cyrdze{"0455}
\DeclareUTFSymbol\cyrii{"0456}
\DeclareUTFSymbol\cyryi{"0457}
\DeclareUTFCompositeSymbol\"{\cyrii}{"0457}
\DeclareUTFSymbol\cyrje{"0458}
\DeclareUTFSymbol\cyrlje{"0459}
\DeclareUTFSymbol\cyrnje{"045A}
\DeclareUTFSymbol\cyrtshe{"045B}
\DeclareUTFCompositeSymbol\'{\cyrk}{"045C}
\DeclareUTFCompositeSymbol\`{\cyri}{"045D}
\DeclareUTFSymbol\cyrushrt{"045E}
\DeclareUTFCompositeSymbol\U{\curu}{"045E}
\DeclareUTFSymbol\cyrdzhe{"045F}
\DeclareUTFSymbol\CYROMEGA{"0460}
\DeclareUTFSymbol\cyromega{"0461}
\DeclareUTFSymbol\CYRYAT{"0462}
\DeclareUTFSymbol\cyryat{"0463}
\DeclareUTFSymbol\CYRIOTE{"0464}
\DeclareUTFSymbol\cyriote{"0465}
\DeclareUTFSymbol\CYRLYUS{"0466}
\DeclareUTFSymbol\cyrlyus{"0467}
\DeclareUTFSymbol\CYRIOTLYUS{"0468}
\DeclareUTFSymbol\cyriotlyus{"0469}
\DeclareUTFSymbol\CYRBYUS{"046A}
\DeclareUTFSymbol\cyrbyus{"046B}
\DeclareUTFSymbol\CYRIOTBYUS{"046C}
\DeclareUTFSymbol\cyriotbyus{"046D}
\DeclareUTFSymbol\CYRKSI{"046E}
\DeclareUTFSymbol\cyrksi{"046F}
\DeclareUTFSymbol\CYRPSI{"0470}
\DeclareUTFSymbol\cyrpsi{"0471}
\DeclareUTFSymbol\CYRFITA{"0472}
\DeclareUTFSymbol\cyrfita{"0473}
\DeclareUTFSymbol\CYRIZH{"0474}
\DeclareUTFSymbol\cyrizh{"0475}
\DeclareUTFCompositeSymbol\C{\CYRIZH}{"0476}
\DeclareUTFCompositeSymbol\C{\cyrizh}{"0477}
\DeclareUTFSymbol\CYRUK{"0478}
\DeclareUTFSymbol\cyruk{"0479}
\DeclareUTFSymbol\CYROMEGARND{"047A}
\DeclareUTFSymbol\cyromegarnd{"047B}
\DeclareUTFSymbol\CYROMEGATITLO{"047C}
\DeclareUTFSymbol\cyromegatitlo{"047D}
\DeclareUTFSymbol\CYROT{"047E}
\DeclareUTFSymbol\cyrot{"047F}
\DeclareUTFSymbol\CYRKOPPA{"0480}
\DeclareUTFSymbol\cyrkoppa{"0481}
\DeclareUTFSymbol\cyrthousands{"0482}
\DeclareUTFSymbol\CYRISHRTDSC{"048A}
\DeclareUTFSymbol\cyrishrtdsc{"048B}
\DeclareUTFSymbol\CYRSEMISFTSN{"048C}
\DeclareUTFSymbol\cyrsemisftsn{"048D}
\DeclareUTFSymbol\CYRRTICK{"048E}
\DeclareUTFSymbol\cyrrtick{"048F}
\DeclareUTFSymbol\CYRGUP{"0490}
\DeclareUTFSymbol\cyrgup{"0491}
\DeclareUTFSymbol\CYRGHCRS{"0492}
\DeclareUTFSymbol\cyrghcrs{"0493}
\DeclareUTFSymbol\CYRGHK{"0494}
\DeclareUTFSymbol\cyrghk{"0495}
\DeclareUTFSymbol\CYRZHDSC{"0496}
\DeclareUTFSymbol\cyrzhdsc{"0497}
\DeclareUTFSymbol\CYRZDSC{"0498}
\DeclareUTFCompositeSymbol\c{\CYRZ}{"0498}
\DeclareUTFSymbol\cyrzdsc{"0499}
\DeclareUTFCompositeSymbol\c{\cyrz}{"0499}
\DeclareUTFSymbol\CYRKDSC{"049A}
\DeclareUTFSymbol\cyrkdsc{"049B}
\DeclareUTFSymbol\CYRKVCRS{"049C}
\DeclareUTFSymbol\cyrkvcrs{"049D}
\DeclareUTFSymbol\CYRKHCRS{"049E}
\DeclareUTFSymbol\cyrkhcrs{"049F}
\DeclareUTFSymbol\CYRKBEAK{"04A0}
\DeclareUTFSymbol\cyrkbeak{"04A1}
\DeclareUTFSymbol\CYRNDSC{"04A2}
\DeclareUTFSymbol\cyrndsc{"04A3}
\DeclareUTFSymbol\CYRNG{"04A4}
\DeclareUTFSymbol\cyrng{"04A5}
\DeclareUTFSymbol\CYRPHK{"04A6}
\DeclareUTFSymbol\cyrphk{"04A7}
\DeclareUTFSymbol\CYRABHHA{"04A8}
\DeclareUTFSymbol\cyrabhha{"04A9}
\DeclareUTFSymbol\CYRSDSC{"04AA}
\DeclareUTFCompositeSymbol\k{\CYRS}{"04AA}
\DeclareUTFSymbol\cyrsdsc{"04AB}
\DeclareUTFCompositeSymbol\k{\cyrs}{"04AB}
\DeclareUTFSymbol\CYRTDSC{"04AC}
\DeclareUTFSymbol\cyrtdsc{"04AD}
\DeclareUTFSymbol\CYRY{"04AE}
\DeclareUTFSymbol\cyry{"04AF}
\DeclareUTFSymbol\CYRYHCRS{"04B0}
\DeclareUTFSymbol\cyryhcrs{"04B1}
\DeclareUTFSymbol\CYRHDSC{"04B2}
\DeclareUTFSymbol\cyrhdsc{"04B3}
\DeclareUTFSymbol\CYRTETSE{"04B4}
\DeclareUTFSymbol\cyrtetse{"04B5}
\DeclareUTFSymbol\CYRCHRDSC{"04B6}
\DeclareUTFSymbol\cyrchrdsc{"04B7}
\DeclareUTFSymbol\CYRCHVCRS{"04B8}
\DeclareUTFSymbol\cyrchvcrs{"04B9}
\DeclareUTFSymbol\CYRSHHA{"04BA}
\DeclareUTFSymbol\cyrshha{"04BB}
\DeclareUTFSymbol\CYRABHCH{"04BC}
\DeclareUTFSymbol\cyrabhch{"04BD}
\DeclareUTFSymbol\CYRABHCHDSC{"04BE}
\DeclareUTFCompositeSymbol\k{\CYRABHCH}{"04BE}
\DeclareUTFSymbol\cyrabhchdsc{"04BF}
\DeclareUTFCompositeSymbol\k{\cyrabhch}{"04BF}
\DeclareUTFSymbol\CYRpalochka{"04C0}
\DeclareUTFCompositeSymbol\U{\CYRZH}{"04C1}
\DeclareUTFCompositeSymbol\U{\cyrzh}{"04C2}
\DeclareUTFSymbol\CYRKHK{"04C3}
\DeclareUTFSymbol\cyrkhk{"04C4}
\DeclareUTFSymbol\CYRLDSC{"04C5}
\DeclareUTFSymbol\cyrldsc{"04C6}
\DeclareUTFSymbol\CYRNHK{"04C7}
\DeclareUTFSymbol\cyrnhk{"04C8}
\DeclareUTFSymbol\CYRCHLDSC{"04CB}
\DeclareUTFSymbol\cyrchldsc{"04CC}
\DeclareUTFSymbol\CYRMDSC{"04CD}
\DeclareUTFSymbol\cyrmdsc{"04CE}
\DeclareUTFCompositeSymbol\U{\CYRA}{"04D0}
\DeclareUTFCompositeSymbol\U{\cyra}{"04D1}
\DeclareUTFCompositeSymbol\"{\CYRA}{"04D2}
\DeclareUTFCompositeSymbol\"{\cyra}{"04D3}
\DeclareUTFSymbol\CYRAE{"04D4}
\DeclareUTFSymbol\cyrae{"04D5}
\DeclareUTFCompositeSymbol\U{\CYRE}{"04D6}
\DeclareUTFCompositeSymbol\U{\cyre}{"04D7}
\DeclareUTFSymbol\CYRSCHWA{"04D8}
\DeclareUTFSymbol\cyrschwa{"04D9}
\DeclareUTFCompositeSymbol\"{\CYRSCHWA}{"04DA}
\DeclareUTFCompositeSymbol\"{\cyrschwa}{"04DB}
\DeclareUTFCompositeSymbol\"{\CYRZH}{"04DC}
\DeclareUTFCompositeSymbol\"{\cyrzh}{"04DD}
\DeclareUTFCompositeSymbol\"{\CYRZ}{"04DE}
\DeclareUTFCompositeSymbol\"{\cyrz}{"04DF}
\DeclareUTFSymbol\CYRABHDZE{"04E0}
\DeclareUTFSymbol\cyrabhdze{"04E1}
\DeclareUTFCompositeSymbol\={\CYRI}{"04E2}
\DeclareUTFCompositeSymbol\={\cyri}{"04E3}
\DeclareUTFCompositeSymbol\"{\CYRI}{"04E4}
\DeclareUTFCompositeSymbol\"{\cyri}{"04E5}
\DeclareUTFCompositeSymbol\"{\CYRO}{"04E6}
\DeclareUTFCompositeSymbol\"{\cyro}{"04E7}
\DeclareUTFSymbol\CYROTLD{"04E8}
\DeclareUTFSymbol\cyrotld{"04E9}
\DeclareUTFCompositeSymbol\"{\CYROTLD}{"04EA}
\DeclareUTFCompositeSymbol\"{\cyrotld}{"04EB}
\DeclareUTFCompositeSymbol\"{\CYREREV}{"04EC}
\DeclareUTFCompositeSymbol\"{\cyrerev}{"04ED}
\DeclareUTFCompositeSymbol\={\CYRU}{"04EE}
\DeclareUTFCompositeSymbol\={\cyru}{"04EF}
\DeclareUTFCompositeSymbol\"{\CYRU}{"04F0}
\DeclareUTFCompositeSymbol\"{\cyru}{"04F1}
\DeclareUTFCompositeSymbol\H{\CYRU}{"04F2}
\DeclareUTFCompositeSymbol\H{\cyru}{"04F3}
\DeclareUTFCompositeSymbol\"{\CYRCH}{"04F4}
\DeclareUTFCompositeSymbol\"{\cyrch}{"04F5}
\DeclareUTFSymbol\CYRGDSC{"04F6}
\DeclareUTFSymbol\cyrgdsc{"04F7}
\DeclareUTFCompositeSymbol\"{\CYRERY}{"04F8}
\DeclareUTFCompositeSymbol\"{\cyrery}{"04F9}
\DeclareUTFSymbol\CYRHHK{"04FC}
\DeclareUTFSymbol\cyrhhk{"04FD}
\DeclareUTFSymbol\sofpasuq{"05C3}
\DeclareUTFSymbol\hebalef{"05D0}
\DeclareUTFSymbol\hebbet{"05D1}
\DeclareUTFSymbol\hebgimel{"05D2}
\DeclareUTFSymbol\hebdalet{"05D3}
\DeclareUTFSymbol\hebhe{"05D4}
\DeclareUTFSymbol\hebvav{"05D5}
\DeclareUTFSymbol\hebzayin{"05D6}
\DeclareUTFSymbol\hebhet{"05D7}
\DeclareUTFSymbol\hebtet{"05D8}
\DeclareUTFSymbol\hebyod{"05D9}
\DeclareUTFSymbol\hebfinalkaf{"05DA}
\DeclareUTFSymbol\hebkaf{"05DB}
\DeclareUTFSymbol\heblamed{"05DC}
\DeclareUTFSymbol\hebfinalmem{"05DD}
\DeclareUTFSymbol\hebmem{"05DE}
\DeclareUTFSymbol\hebfinalnun{"05DF}
\DeclareUTFSymbol\hebnun{"05E0}
\DeclareUTFSymbol\hebsamekh{"05E1}
\DeclareUTFSymbol\hebayin{"05E2}
\DeclareUTFSymbol\hebfinalpe{"05E3}
\DeclareUTFSymbol\hebpe{"05E4}
\DeclareUTFSymbol\hebfinaltsadi{"05E5}
\DeclareUTFSymbol\hebtsadi{"05E6}
\DeclareUTFSymbol\hebqof{"05E7}
\DeclareUTFSymbol\hebresh{"05E8}
\DeclareUTFSymbol\hebshin{"05E9}
\DeclareUTFSymbol\hebtav{"05EA}
\DeclareUTFSymbol\doublevav{"05F0}
\DeclareUTFSymbol\vavyod{"05F1}
\DeclareUTFSymbol\doubleyod{"05F2}
\DeclareUTFSymbol\textscd{"1D05}
\DeclareUTFSymbol\textPUsck{"1D0B}
\DeclareUTFSymbol\textPUscm{"1D0D}
\DeclareUTFSymbol\textPUscp{"1D18}
\DeclareUTFSymbol\textPUrevscr{"1D19}
\DeclareUTFSymbol\textiinferior{"1D62}
\DeclareUTFSymbol\textrinferior{"1D63}
\DeclareUTFSymbol\textuinferior{"1D64}
\DeclareUTFSymbol\textvinferior{"1D65}
\DeclareUTFSymbol\textbetainferior{"1D66}
\DeclareUTFSymbol\textgammainferior{"1D67}
\DeclareUTFSymbol\textrhoinferior{"1D68}
\DeclareUTFSymbol\textphiinferior{"1D69}
\DeclareUTFSymbol\textchiinferior{"1D6A}
\DeclareUTFSymbol\textbarsci{"1D7B}
\DeclareUTFSymbol\textbarp{"1D7D}
\DeclareUTFSymbol\textbarscu{"1D7E}
\DeclareUTFSymbol\textPUrhooka{"1D8F}
\DeclareUTFSymbol\textPUrhooke{"1D92}
\DeclareUTFSymbol\textPUrhookepsilon{"1D93}
\DeclareUTFSymbol\textPUrhookopeno{"1D97}
\DeclareUTFCompositeSymbol\textsubbreve{H}{"1E2A}
\DeclareUTFCompositeSymbol\textsubbreve{h}{"1E2B}
\DeclareUTFCompositeSymbol\.{\textlongs}{"1E9B}
\DeclareUTFSymbol\textcompwordmark{"200C}
\DeclareUTFSymbol\texthdotfor{"2025}
\DeclareUTFSymbol\textprime{"2032}
\DeclareUTFSymbol\textsecond{"2033}
\DeclareUTFSymbol\textthird{"2034}
\DeclareUTFSymbol\textbackprime{"2035}
\DeclareUTFSymbol\textlefttherefore{"2056}
\DeclareUTFSymbol\textfourth{"2057}
\DeclareUTFSymbol\textdiamonddots{"2058}
\DeclareUTFSymbol\textzerosuperior{"2070}
\DeclareUTFSymbol\textisuperior{"2071}
\DeclareUTFSymbol\textfoursuperior{"2074}
\DeclareUTFSymbol\textfivesuperior{"2075}
\DeclareUTFSymbol\textsixsuperior{"2076}
\DeclareUTFSymbol\textsevensuperior{"2077}
\DeclareUTFSymbol\texteightsuperior{"2078}
\DeclareUTFSymbol\textninesuperior{"2079}
\DeclareUTFSymbol\textplussuperior{"207A}
\DeclareUTFSymbol\textminussuperior{"207B}
\DeclareUTFSymbol\textequalsuperior{"207C}
\DeclareUTFSymbol\textparenleftsuperior{"207D}
\DeclareUTFSymbol\textparenrightsuperior{"207E}
\DeclareUTFSymbol\textnsuperior{"207F}
\DeclareUTFSymbol\textzeroinferior{"2080}
\DeclareUTFSymbol\textoneinferior{"2081}
\DeclareUTFSymbol\texttwoinferior{"2082}
\DeclareUTFSymbol\textthreeinferior{"2083}
\DeclareUTFSymbol\textfourinferior{"2084}
\DeclareUTFSymbol\textfiveinferior{"2085}
\DeclareUTFSymbol\textsixinferior{"2086}
\DeclareUTFSymbol\textseveninferior{"2087}
\DeclareUTFSymbol\texteightinferior{"2088}
\DeclareUTFSymbol\textnineinferior{"2089}
\DeclareUTFSymbol\textplusinferior{"208A}
\DeclareUTFSymbol\textminusinferior{"208B}
\DeclareUTFSymbol\textequalsinferior{"208C}
\DeclareUTFSymbol\textparenleftinferior{"208D}
\DeclareUTFSymbol\textparenrightinferior{"208E}
\DeclareUTFSymbol\textainferior{"2090}
\DeclareUTFSymbol\texteinferior{"2091}
\DeclareUTFSymbol\textoinferior{"2092}
\DeclareUTFSymbol\textxinferior{"2093}
\DeclareUTFSymbol\textschwainferior{"2094}
\DeclareUTFSymbol\texthinferior{"2095}
\DeclareUTFSymbol\textkinferior{"2096}
\DeclareUTFSymbol\textlinferior{"2097}
\DeclareUTFSymbol\textminferior{"2098}
\DeclareUTFSymbol\textninferior{"2099}
\DeclareUTFSymbol\textpinferior{"209A}
\DeclareUTFSymbol\textsinferior{"209B}
\DeclareUTFSymbol\texttinferior{"209C}
\DeclareUTFSymbol\textpeseta{"20A7}
\DeclareUTFSymbol\textDeleatur{"20B0}
\DeclareUTFSymbol\textguarani{"20B2}
\DeclareUTFSymbol\texthslash{"210F}
\DeclareUTFSymbol\textIm{"2111}
\DeclareUTFSymbol\textell{"2113}
\DeclareUTFSymbol\textwp{"2118}
\DeclareUTFSymbol\textRe{"211C}
\DeclareUTFSymbol\textriota{"2129}
\DeclareUTFSymbol\textangstrom{"212B}
\DeclareUTFSymbol\textfax{"213B}
\DeclareUTFSymbol\textinvamp{"214B}
\DeclareUTFSymbol\textoneseventh{"2150}
\DeclareUTFSymbol\textoneninth{"2151}
\DeclareUTFSymbol\textonetenth{"2152}
\DeclareUTFSymbol\textonethird{"2153}
\DeclareUTFSymbol\texttwothirds{"2154}
\DeclareUTFSymbol\textonefifth{"2155}
\DeclareUTFSymbol\texttwofifths{"2156}
\DeclareUTFSymbol\textthreefifths{"2157}
\DeclareUTFSymbol\textfourfifths{"2158}
\DeclareUTFSymbol\textonesixth{"2159}
\DeclareUTFSymbol\textfivesixths{"215A}
\DeclareUTFSymbol\textoneeighth{"215B}
\DeclareUTFSymbol\textthreeeighths{"215C}
\DeclareUTFSymbol\textfiveeighths{"215D}
\DeclareUTFSymbol\textseveneighths{"215E}
\DeclareUTFSymbol\textrevc{"2184}
\DeclareUTFSymbol\textzerothirds{"2189}
\DeclareUTFSymbol\textnleftarrow{"219A}
\DeclareUTFSymbol\textnrightarrow{"219B}
\DeclareUTFSymbol\texttwoheadleftarrow{"219E}
\DeclareUTFCommand\textntwoheadleftarrow{\textlstrikethru\texttwoheadleftarrow}
\DeclareUTFSymbol\texttwoheaduparrow{"219F}
\DeclareUTFSymbol\texttwoheadrightarrow{"21A0}
\DeclareUTFCommand\textntwoheadrightarrow{\textlstrikethru\texttwoheadrightarrow}
\DeclareUTFSymbol\texttwoheaddownarrow{"21A1}
\DeclareUTFSymbol\textleftarrowtail{"21A2}
\DeclareUTFSymbol\textrightarrowtail{"21A3}
\DeclareUTFSymbol\textmapsto{"21A6}
\DeclareUTFSymbol\texthookleftarrow{"21A9}
\DeclareUTFSymbol\texthookrightarrow{"21AA}
\DeclareUTFSymbol\textlooparrowleft{"21AB}
\DeclareUTFSymbol\textlooparrowright{"21AC}
\DeclareUTFSymbol\textnleftrightarrow{"21AE}
\DeclareUTFSymbol\textlightning{"21AF}
\DeclareUTFSymbol\textdlsh{"21B5}
\DeclareUTFSymbol\textcurvearrowleft{"21B6}
\DeclareUTFSymbol\textcurvearrowright{"21B7}
\DeclareUTFSymbol\textleftharpoonup{"21BC}
\DeclareUTFSymbol\textleftharpoondown{"21BD}
\DeclareUTFSymbol\textupharpoonright{"21BE}
\DeclareUTFSymbol\textupharpoonleft{"21BF}
\DeclareUTFSymbol\textrightharpoonup{"21C0}
\DeclareUTFSymbol\textrightharpoondown{"21C1}
\DeclareUTFSymbol\textdownharpoonright{"21C2}
\DeclareUTFSymbol\textdownharpoonleft{"21C3}
\DeclareUTFSymbol\textrightleftarrows{"21C4}
\DeclareUTFSymbol\textupdownarrows{"21C5}
\DeclareUTFSymbol\textleftrightarrows{"21C6}
\DeclareUTFSymbol\textleftleftarrows{"21C7}
\DeclareUTFSymbol\textupuparrows{"21C8}
\DeclareUTFSymbol\textrightrightarrows{"21C9}
\DeclareUTFSymbol\textdowndownarrows{"21CA}
\DeclareUTFSymbol\textleftrightharpoons{"21CB}
\DeclareUTFSymbol\textrightleftharpoons{"21CC}
\DeclareUTFSymbol\textnLeftarrow{"21CD}
\DeclareUTFSymbol\textnLeftrightarrow{"21CE}
\DeclareUTFSymbol\textnRightarrow{"21CF}
\DeclareUTFSymbol\textLeftarrow{"21D0}
\DeclareUTFSymbol\textUparrow{"21D1}
\DeclareUTFSymbol\textRightarrow{"21D2}
\DeclareUTFSymbol\textDownarrow{"21D3}
\DeclareUTFSymbol\textLeftrightarrow{"21D4}
\DeclareUTFSymbol\textUpdownarrow{"21D5}
\DeclareUTFSymbol\textNwarrow{"21D6}
\DeclareUTFSymbol\textNearrow{"21D7}
\DeclareUTFSymbol\textSearrow{"21D8}
\DeclareUTFSymbol\textSwarrow{"21D9}
\DeclareUTFSymbol\textLleftarrow{"21DA}
\DeclareUTFSymbol\textRrightarrow{"21DB}
\DeclareUTFSymbol\textleftsquigarrow{"21DC}
\DeclareUTFSymbol\textrightsquigarrow{"21DD}
\DeclareUTFSymbol\textdashleftarrow{"21E0}
\DeclareUTFSymbol\textdasheduparrow{"21E1}
\DeclareUTFSymbol\textdashrightarrow{"21E2}
\DeclareUTFSymbol\textdasheddownarrow{"21E3}
\DeclareUTFSymbol\textpointer{"21E8}
\DeclareUTFSymbol\textdownuparrows{"21F5}
\DeclareUTFSymbol\textleftarrowtriangle{"21FD}
\DeclareUTFSymbol\textrightarrowtriangle{"21FE}
\DeclareUTFSymbol\textleftrightarrowtriangle{"21FF}
\DeclareUTFSymbol\textforall{"2200}
\DeclareUTFSymbol\textcomplement{"2201}
\DeclareUTFSymbol\textpartial{"2202}
\DeclareUTFSymbol\textexists{"2203}
\DeclareUTFSymbol\textnexists{"2204}
\DeclareUTFSymbol\textemptyset{"2205}
\DeclareUTFSymbol\texttriangle{"2206}
\DeclareUTFSymbol\textnabla{"2207}
\DeclareUTFSymbol\textin{"2208}
\DeclareUTFSymbol\textnotin{"2209}
\DeclareUTFSymbol\textsmallin{"220A}
\DeclareUTFSymbol\textni{"220B}
\DeclareUTFSymbol\textnotowner{"220C}
\DeclareUTFSymbol\textsmallowns{"220D}
\DeclareUTFSymbol\textprod{"220F}
\DeclareUTFSymbol\textamalg{"2210}
\DeclareUTFSymbol\textsum{"2211}
\DeclareUTFSymbol\textmp{"2213}
\DeclareUTFSymbol\textdotplus{"2214}
\DeclareUTFSymbol\textDivides{"2215}
\DeclareUTFSymbol\textsetminus{"2216}
\DeclareUTFSymbol\textast{"2217}
\DeclareUTFSymbol\textcirc{"2218}
\DeclareUTFSymbol\textbulletoperator{"2219}
\DeclareUTFSymbol\textpropto{"221D}
\DeclareUTFSymbol\textinfty{"221E}
\DeclareUTFSymbol\textangle{"2220}
\DeclareUTFSymbol\textmeasuredangle{"2221}
\DeclareUTFSymbol\textsphericalangle{"2222}
\DeclareUTFSymbol\textmid{"2223}
\DeclareUTFSymbol\textnmid{"2224}
\DeclareUTFSymbol\textparallel{"2225}
\DeclareUTFSymbol\textnparallel{"2226}
\DeclareUTFSymbol\textwedge{"2227}
\DeclareUTFCommand\textowedge{\textcircled\textwedge}
\DeclareUTFSymbol\textvee{"2228}
\DeclareUTFCommand\textovee{\textcircled\textvee}
\DeclareUTFSymbol\textcap{"2229}
\DeclareUTFSymbol\textcup{"222A}
\DeclareUTFSymbol\textint{"222B}
\DeclareUTFSymbol\textiint{"222C}
\DeclareUTFSymbol\textiiint{"222D}
\DeclareUTFSymbol\textoint{"222E}
\DeclareUTFSymbol\textoiint{"222F}
\DeclareUTFSymbol\textointclockwise{"2232}
\DeclareUTFSymbol\textointctrclockwise{"2233}
\DeclareUTFSymbol\texttherefore{"2234}
\DeclareUTFSymbol\textbecause{"2235}
\DeclareUTFSymbol\textvdotdot{"2236}
\DeclareUTFSymbol\textsquaredots{"2237}
\DeclareUTFSymbol\textdotminus{"2238}
\DeclareUTFSymbol\texteqcolon{"2239}
\DeclareUTFSymbol\textsim{"223C}
\DeclareUTFSymbol\textbacksim{"223D}
\DeclareUTFCommand\textnbacksim{\textlstrikethru\textbacksim}
\DeclareUTFSymbol\textwr{"2240}
\DeclareUTFSymbol\textnsim{"2241}
\DeclareUTFSymbol\texteqsim{"2242}
\DeclareUTFCommand\textneqsim{\textlstrikethru\texteqsim}
\DeclareUTFSymbol\textsimeq{"2243}
\DeclareUTFSymbol\textnsimeq{"2244}
\DeclareUTFSymbol\textcong{"2245}
\DeclareUTFSymbol\textncong{"2247}
\DeclareUTFSymbol\textapprox{"2248}
\DeclareUTFSymbol\textnapprox{"2249}
\DeclareUTFSymbol\textapproxeq{"224A}
\DeclareUTFCommand\textnapproxeq{\textlstrikethru\textapproxeq}
\DeclareUTFSymbol\texttriplesim{"224B}
\DeclareUTFCommand\textntriplesim{\textlstrikethru\texttriplesim}
\DeclareUTFSymbol\textbackcong{"224C}
\DeclareUTFCommand\textnbackcong{\textlstrikethru\textbackcong}
\DeclareUTFSymbol\textasymp{"224D}
\DeclareUTFCommand\textnasymp{\textlstrikethru\textasymp}
\DeclareUTFSymbol\textBumpeq{"224E}
\DeclareUTFCommand\textnBumpeq{\textlstrikethru\textBumpeq}
\DeclareUTFSymbol\textbumpeq{"224F}
\DeclareUTFCommand\textnbumpeq{\textlstrikethru\textbumpeq}
\DeclareUTFSymbol\textdoteq{"2250}
\DeclareUTFCommand\textndoteq{\textlstrikethru\textdoteq}
\DeclareUTFSymbol\textdoteqdot{"2251}
\DeclareUTFCommand\textnDoteq{\textlstrikethru\textdoteqdot}
\DeclareUTFSymbol\textfallingdoteq{"2252}
\DeclareUTFCommand\textnfallingdoteq{\textlstrikethru\textfallingdoteq}
\DeclareUTFSymbol\textrisingdoteq{"2253}
\DeclareUTFCommand\textnrisingdoteq{\textlstrikethru\textrisingdoteq}
\DeclareUTFSymbol\textcolonequals{"2254}
\DeclareUTFSymbol\textequalscolon{"2255}
\DeclareUTFSymbol\texteqcirc{"2256}
\DeclareUTFCommand\textneqcirc{\textlstrikethru\texteqcirc}
\DeclareUTFSymbol\textcirceq{"2257}
\DeclareUTFCommand\textncirceq{\textlstrikethru\textcirceq}
\DeclareUTFSymbol\texthateq{"2259}
\DeclareUTFCommand\textnhateq{\textlstrikethru\texthateq}
\DeclareUTFSymbol\texttriangleeq{"225C}
\DeclareUTFSymbol\textneq{"2260}
\DeclareUTFSymbol\textne{"2260}
\DeclareUTFSymbol\textequiv{"2261}
\DeclareUTFSymbol\textnequiv{"2262}
\DeclareUTFSymbol\textleq{"2264}
\DeclareUTFSymbol\textle{"2264}
\DeclareUTFSymbol\textgeq{"2265}
\DeclareUTFSymbol\textge{"2265}
\DeclareUTFSymbol\textleqq{"2266}
\DeclareUTFCommand\textnleqq{\textlstrikethru\textleqq}
\DeclareUTFSymbol\textgeqq{"2267}
\DeclareUTFCommand\textngeqq{\textlstrikethru\textgeqq}
\DeclareUTFSymbol\textlneqq{"2268}
\DeclareUTFSymbol\textgneqq{"2269}
\DeclareUTFSymbol\textll{"226A}
\DeclareUTFCommand\textnll{\textlstrikethru\textll}
\DeclareUTFSymbol\textgg{"226B}
\DeclareUTFCommand\textngg{\textlstrikethru\textgg}
\DeclareUTFSymbol\textbetween{"226C}
\DeclareUTFSymbol\textnless{"226E}
\DeclareUTFSymbol\textngtr{"226F}
\DeclareUTFSymbol\textnleq{"2270}
\DeclareUTFSymbol\textngeq{"2271}
\DeclareUTFSymbol\textlesssim{"2272}
\DeclareUTFSymbol\textgtrsim{"2273}
\DeclareUTFSymbol\textnlesssim{"2274}
\DeclareUTFSymbol\textngtrsim{"2275}
\DeclareUTFSymbol\textlessgtr{"2276}
\DeclareUTFSymbol\textgtrless{"2277}
\DeclareUTFSymbol\textngtrless{"2278}
\DeclareUTFSymbol\textnlessgtr{"2279}
\DeclareUTFSymbol\textprec{"227A}
\DeclareUTFSymbol\textsucc{"227B}
\DeclareUTFSymbol\textpreccurlyeq{"227C}
\DeclareUTFSymbol\textsucccurlyeq{"227D}
\DeclareUTFSymbol\textprecsim{"227E}
\DeclareUTFCommand\textnprecsim{\textlstrikethru\textprecsim}
\DeclareUTFSymbol\textsuccsim{"227F}
\DeclareUTFCommand\textnsuccsim{\textlstrikethru\textsuccsim}
\DeclareUTFSymbol\textnprec{"2280}
\DeclareUTFSymbol\textnsucc{"2281}
\DeclareUTFSymbol\textsubset{"2282}
\DeclareUTFSymbol\textsupset{"2283}
\DeclareUTFSymbol\textnsubset{"2284}
\DeclareUTFSymbol\textnsupset{"2285}
\DeclareUTFSymbol\textsubseteq{"2286}
\DeclareUTFSymbol\textsupseteq{"2287}
\DeclareUTFSymbol\textnsubseteq{"2288}
\DeclareUTFSymbol\textnsupseteq{"2289}
\DeclareUTFSymbol\textsubsetneq{"228A}
\DeclareUTFSymbol\textsupsetneq{"228B}
\DeclareUTFSymbol\textcupdot{"228D}
\DeclareUTFSymbol\textcupplus{"228E}
\DeclareUTFSymbol\textsqsubset{"228F}
\DeclareUTFCommand\textnsqsubset{\textlstrikethru\textsqsubset}
\DeclareUTFSymbol\textsqsupset{"2290}
\DeclareUTFCommand\textnsqsupset{\textlstrikethru\textsqsupset}
\DeclareUTFSymbol\textsqsubseteq{"2291}
\DeclareUTFCommand\textnsqsubseteq{\textlstrikethru\textsqsubseteq}
\DeclareUTFSymbol\textsqsupseteq{"2292}
\DeclareUTFCommand\textnsqsupseteq{\textlstrikethru\textsqsupseteq}
\DeclareUTFSymbol\textsqcap{"2293}
\DeclareUTFSymbol\textsqcup{"2294}
\DeclareUTFSymbol\textoplus{"2295}
\DeclareUTFSymbol\textominus{"2296}
\DeclareUTFSymbol\textotimes{"2297}
\DeclareUTFSymbol\textoslash{"2298}
\DeclareUTFSymbol\textodot{"2299}
\DeclareUTFSymbol\textcircledcirc{"229A}
\DeclareUTFSymbol\textcircledast{"229B}
\DeclareUTFSymbol\textcircleddash{"229D}
\DeclareUTFSymbol\textboxplus{"229E}
\DeclareUTFSymbol\textboxminus{"229F}
\DeclareUTFSymbol\textboxtimes{"22A0}
\DeclareUTFSymbol\textboxdot{"22A1}
\DeclareUTFSymbol\textvdash{"22A2}
\DeclareUTFSymbol\textdashv{"22A3}
\DeclareUTFCommand\textndashv{\textlstrikethru\textdashv}
\DeclareUTFSymbol\texttop{"22A4}
\DeclareUTFCommand\textndownvdash{\textlstrikethru\texttop}
\DeclareUTFSymbol\textbot{"22A5}
\DeclareUTFCommand\textnupvdash{\textlstrikethru\textbot}
\DeclareUTFSymbol\textvDash{"22A8}
\DeclareUTFSymbol\textVdash{"22A9}
\DeclareUTFSymbol\textVvdash{"22AA}
\DeclareUTFCommand\textnVvash{\textlstrikethru\textVvdash}
\DeclareUTFSymbol\textVDash{"22AB}
\DeclareUTFSymbol\textnvdash{"22AC}
\DeclareUTFSymbol\textnvDash{"22AD}
\DeclareUTFSymbol\textnVdash{"22AE}
\DeclareUTFSymbol\textnVDash{"22AF}
\DeclareUTFSymbol\textlhd{"22B2}
\DeclareUTFSymbol\textrhd{"22B3}
\DeclareUTFSymbol\textunlhd{"22B4}
\DeclareUTFSymbol\textunrhd{"22B5}
\DeclareUTFSymbol\textmultimapdotbothA{"22B6}
\DeclareUTFSymbol\textmultimapdotbothB{"22B7}
\DeclareUTFSymbol\textmultimap{"22B8}
\DeclareUTFSymbol\textveebar{"22BB}
\DeclareUTFSymbol\textbarwedge{"22BC}
\DeclareUTFSymbol\textstar{"22C6}
\DeclareUTFSymbol\textdivideontimes{"22C7}
\DeclareUTFSymbol\textbowtie{"22C8}
\DeclareUTFSymbol\textltimes{"22C9}
\DeclareUTFSymbol\textrtimes{"22CA}
\DeclareUTFSymbol\textleftthreetimes{"22CB}
\DeclareUTFSymbol\textrightthreetimes{"22CC}
\DeclareUTFSymbol\textbacksimeq{"22CD}
\DeclareUTFCommand\textnbacksimeq{\textlstrikethru\textbacksimeq}
\DeclareUTFSymbol\textcurlyvee{"22CE}
\DeclareUTFSymbol\textcurlywedge{"22CF}
\DeclareUTFSymbol\textSubset{"22D0}
\DeclareUTFCommand\textnSubset{\textlstrikethru\textSubset}
\DeclareUTFSymbol\textSupset{"22D1}
\DeclareUTFCommand\textnSupset{\textlstrikethru\textSupset}
\DeclareUTFSymbol\textCap{"22D2}
\DeclareUTFSymbol\textCup{"22D3}
\DeclareUTFSymbol\textpitchfork{"22D4}
\DeclareUTFSymbol\textlessdot{"22D6}
\DeclareUTFSymbol\textgtrdot{"22D7}
\DeclareUTFSymbol\textlll{"22D8}
\DeclareUTFSymbol\textggg{"22D9}
\DeclareUTFSymbol\textlesseqgtr{"22DA}
\DeclareUTFSymbol\textgtreqless{"22DB}
\DeclareUTFSymbol\textcurlyeqprec{"22DE}
\DeclareUTFCommand\textncurlyeqprec{\textlstrikethru\textcurlyeqprec}
\DeclareUTFSymbol\textcurlyeqsucc{"22DF}
\DeclareUTFCommand\textncurlyeqsucc{\textlstrikethru\textcurlyeqsucc}
\DeclareUTFSymbol\textnpreccurlyeq{"22E0}
\DeclareUTFSymbol\textnsucccurlyeq{"22E1}
\DeclareUTFSymbol\textnqsubseteq{"22E2}
\DeclareUTFSymbol\textnqsupseteq{"22E3}
\DeclareUTFSymbol\textsqsubsetneq{"22E4}
\DeclareUTFSymbol\textsqsupsetneq{"22E5}
\DeclareUTFSymbol\textlnsim{"22E6}
\DeclareUTFSymbol\textgnsim{"22E7}
\DeclareUTFSymbol\textprecnsim{"22E8}
\DeclareUTFSymbol\textsuccnsim{"22E9}
\DeclareUTFSymbol\textntriangleleft{"22EA}
\DeclareUTFSymbol\textntriangleright{"22EB}
\DeclareUTFSymbol\textntrianglelefteq{"22EC}
\DeclareUTFSymbol\textntrianglerighteq{"22ED}
\DeclareUTFSymbol\textvdots{"22EE}
\DeclareUTFSymbol\textcdots{"22EF}
\DeclareUTFSymbol\textudots{"22F0}
\DeclareUTFSymbol\textddots{"22F1}
\DeclareUTFSymbol\textbarin{"22F6}
\DeclareUTFSymbol\textdiameter{"2300}
\DeclareUTFSymbol\textbackneg{"2310}
\DeclareUTFSymbol\textwasylozenge{"2311}
\DeclareUTFSymbol\textinvbackneg{"2319}
\DeclareUTFSymbol\textclock{"231A}
\DeclareUTFSymbol\textulcorner{"231C}
\DeclareUTFSymbol\texturcorner{"231D}
\DeclareUTFSymbol\textllcorner{"231E}
\DeclareUTFSymbol\textlrcorner{"231F}
\DeclareUTFSymbol\textfrown{"2322}
\DeclareUTFSymbol\textsmile{"2323}
\DeclareUTFSymbol\textKeyboard{"2328}
\DeclareUTFSymbol\textlangle{"2329}
\DeclareUTFSymbol\textrangle{"232A}
\DeclareUTFSymbol\textAPLinv{"2339}
\DeclareUTFSymbol\textTumbler{"233C}
\DeclareUTFSymbol\textstmaryrdbaro{"233D}
\DeclareUTFSymbol\textnotslash{"233F}
\DeclareUTFSymbol\textnotbackslash{"2340}
\DeclareUTFSymbol\textboxbackslash{"2342}
\DeclareUTFSymbol\textAPLleftarrowbox{"2347}
\DeclareUTFSymbol\textAPLrightarrowbox{"2348}
\DeclareUTFSymbol\textAPLuparrowbox{"2350}
\DeclareUTFSymbol\textAPLdownarrowbox{"2357}
\DeclareUTFSymbol\textAPLinput{"235E}
\DeclareUTFSymbol\textRequest{"2370}
\DeclareUTFSymbol\textBeam{"2393}
\DeclareUTFSymbol\texthexagon{"2394}
\DeclareUTFSymbol\textAPLbox{"2395}
\DeclareUTFSymbol\textForwardToIndex{"23ED}
\DeclareUTFSymbol\textRewindToIndex{"23EE}
\DeclareUTFSymbol\textbbslash{"244A}
\DeclareUTFSymbol\textCircledA{"24B6}
\DeclareUTFSymbol\textCleaningF{"24BB}
\DeclareUTFCommand\textCleaningFF{\b\textCleaningF}
\DeclareUTFSymbol\textCleaningP{"24C5}
\DeclareUTFCommand\textCleaningPP{\b\textCleaningP}
\DeclareUTFSymbol\textCuttingLine{"2504}
\DeclareUTFSymbol\textUParrow{"25B2}
\DeclareUTFSymbol\textbigtriangleup{"25B3}
\DeclareUTFSymbol\textForward{"25B6}
\DeclareUTFSymbol\texttriangleright{"25B7}
\DeclareUTFSymbol\textRHD{"25BA}
\DeclareUTFSymbol\textDOWNarrow{"25BC}
\DeclareUTFSymbol\textbigtriangledown{"25BD}
\DeclareUTFSymbol\textRewind{"25C0}
\DeclareUTFSymbol\texttriangleleft{"25C1}
\DeclareUTFSymbol\textLHD{"25C4}
\DeclareUTFSymbol\textdiamond{"25C7}
\DeclareUTFSymbol\textlozenge{"25CA}
\DeclareUTFSymbol\textLEFTCIRCLE{"25D6}
\DeclareUTFSymbol\textRIGHTCIRCLE{"25D7}
\DeclareUTFSymbol\textboxbar{"25EB}
\DeclareUTFSymbol\textCloud{"2601}
\DeclareUTFSymbol\textFiveStar{"2605}
\DeclareUTFSymbol\textFiveStarOpen{"2606}
\DeclareUTFSymbol\textPhone{"260E}
\DeclareUTFSymbol\textboxempty{"2610}
\DeclareUTFSymbol\textCheckedbox{"2611}
\DeclareUTFSymbol\textCrossedbox{"2612}
\DeclareUTFSymbol\textCoffeecup{"2615}
\DeclareUTFSymbol\textHandCuffLeft{"261A}
\DeclareUTFSymbol\textHandCuffRight{"261B}
\DeclareUTFSymbol\textHandLeft{"261C}
\DeclareUTFSymbol\textHandRight{"261E}
\DeclareUTFSymbol\textRadioactivity{"2622}
\DeclareUTFSymbol\textBiohazard{"2623}
\DeclareUTFSymbol\textAnkh{"2625}
\DeclareUTFSymbol\textYinYang{"262F}
\DeclareUTFSymbol\textfrownie{"2639}
\DeclareUTFSymbol\textsmiley{"263A}
\DeclareUTFSymbol\textblacksmiley{"263B}
\DeclareUTFSymbol\textsun{"263C}
\DeclareUTFSymbol\textleftmoon{"263D}
\DeclareUTFSymbol\textrightmoon{"263E}
\DeclareUTFSymbol\textmercury{"263F}
\DeclareUTFSymbol\textPUfemale{"2640}
\DeclareUTFSymbol\textearth{"2641}
\DeclareUTFSymbol\textmale{"2642}
\DeclareUTFSymbol\textjupiter{"2643}
\DeclareUTFSymbol\textsaturn{"2644}
\DeclareUTFSymbol\texturanus{"2645}
\DeclareUTFSymbol\textneptune{"2646}
\DeclareUTFSymbol\textpluto{"2647}
\DeclareUTFSymbol\textaries{"2648}
\DeclareUTFSymbol\texttaurus{"2649}
\DeclareUTFSymbol\textgemini{"264A}
\DeclareUTFSymbol\textcancer{"264B}
\DeclareUTFSymbol\textleo{"264C}
\DeclareUTFSymbol\textvirgo{"264D}
\DeclareUTFSymbol\textlibra{"264E}
\DeclareUTFSymbol\textscorpio{"264F}
\DeclareUTFSymbol\textsagittarius{"2650}
\DeclareUTFSymbol\textcapricornus{"2651}
\DeclareUTFSymbol\textaquarius{"2652}
\DeclareUTFSymbol\textpisces{"2653}
\DeclareUTFSymbol\textspadesuitblack{"2660}
\DeclareUTFSymbol\textheartsuitwhite{"2661}
\DeclareUTFSymbol\textdiamondsuitwhite{"2662}
\DeclareUTFSymbol\textclubsuitblack{"2663}
\DeclareUTFSymbol\textspadesuitwhite{"2664}
\DeclareUTFSymbol\textheartsuitblack{"2665}
\DeclareUTFSymbol\textdiamondsuitblack{"2666}
\DeclareUTFSymbol\textclubsuitwhite{"2667}
\DeclareUTFSymbol\textquarternote{"2669}
\DeclareUTFSymbol\texttwonotes{"266B}
\DeclareUTFSymbol\textsixteenthnote{"266C}
\DeclareUTFSymbol\textflat{"266D}
\DeclareUTFSymbol\textnatural{"266E}
\DeclareUTFSymbol\textsharp{"266F}
\DeclareUTFSymbol\textrecycle{"2672}
\DeclareUTFSymbol\textWheelchair{"267F}
\DeclareUTFSymbol\textFlag{"2691}
\DeclareUTFSymbol\textMineSign{"2692}
\DeclareUTFSymbol\textdsmilitary{"2694}
\DeclareUTFSymbol\textdsmedical{"2695}
\DeclareUTFSymbol\textdsjuridical{"2696}
\DeclareUTFSymbol\textdschemical{"2697}
\DeclareUTFSymbol\textdsbiological{"2698}
\DeclareUTFSymbol\textdscommercial{"269A}
\DeclareUTFSymbol\textmanstar{"269D}
\DeclareUTFSymbol\textdanger{"26A0}
\DeclareUTFSymbol\textFemaleFemale{"26A2}
\DeclareUTFSymbol\textMaleMale{"26A3}
\DeclareUTFSymbol\textFemaleMale{"26A4}
\DeclareUTFSymbol\textHermaphrodite{"26A5}
\DeclareUTFSymbol\textNeutral{"26AA}
\DeclareUTFSymbol\textPUuncrfemale{"26B2}
\DeclareUTFSymbol\texthexstar{"26B9}
\DeclareUTFSymbol\textSoccerBall{"26BD}
\DeclareUTFSymbol\textSunCload{"26C5}
\DeclareUTFSymbol\textRain{"26C6}
\DeclareUTFSymbol\textnoway{"26D4}
\DeclareUTFSymbol\textMountain{"26F0}
\DeclareUTFSymbol\textTent{"26FA}
\DeclareUTFSymbol\textScissorRightBrokenBottom{"2701}
\DeclareUTFSymbol\textScissorRight{"2702}
\DeclareUTFSymbol\textScissorRightBrokenTop{"2703}
\DeclareUTFSymbol\textScissorHollowRight{"2704}
\DeclareUTFSymbol\textPhoneHandset{"2706}
\DeclareUTFSymbol\textTape{"2707}
\DeclareUTFSymbol\textPlane{"2708}
\DeclareUTFSymbol\textEnvelope{"2709}
\DeclareUTFSymbol\textPeace{"270C}
\DeclareUTFSymbol\textWritingHand{"270D}
\DeclareUTFSymbol\textPencilRightDown{"270E}
\DeclareUTFSymbol\textPencilRight{"270F}
\DeclareUTFSymbol\textPencilRightUp{"2710}
\DeclareUTFSymbol\textNibRight{"2711}
\DeclareUTFSymbol\textNibSolidRight{"2712}
\DeclareUTFSymbol\textCheckmark{"2713}
\DeclareUTFSymbol\textCheckmarkBold{"2714}
\DeclareUTFSymbol\textXSolid{"2715}
\DeclareUTFSymbol\textXSolidBold{"2716}
\DeclareUTFSymbol\textXSolidBrush{"2717}
\DeclareUTFSymbol\textPlusOutline{"2719}
\DeclareUTFSymbol\textPlus{"271A}
\DeclareUTFSymbol\textPlusThinCenterOpen{"271B}
\DeclareUTFSymbol\textPlusCenterOpen{"271C}
\DeclareUTFSymbol\textCross{"271D}
\DeclareUTFSymbol\textCrossOpenShadow{"271E}
\DeclareUTFSymbol\textCrossOutline{"271F}
\DeclareUTFSymbol\textCrossMaltese{"2720}
\DeclareUTFSymbol\textDavidStar{"2721}
\DeclareUTFSymbol\textFourAsterisk{"2722}
\DeclareUTFSymbol\textJackStar{"2723}
\DeclareUTFSymbol\textJackStarBold{"2724}
\DeclareUTFSymbol\textClowerTips{"2725}
\DeclareUTFSymbol\textFourStar{"2726}
\DeclareUTFSymbol\textFourStarOpen{"2727}
\DeclareUTFSymbol\textFiveStarOpenCircled{"272A}
\DeclareUTFSymbol\textFiveStarCenterOpen{"272B}
\DeclareUTFSymbol\textFiveStarOpenDotted{"272C}
\DeclareUTFSymbol\textFiveStarOutline{"272D}
\DeclareUTFSymbol\textFiveStarOutlineHeavy{"272E}
\DeclareUTFSymbol\textFiveStarConvex{"272F}
\DeclareUTFSymbol\textFiveStarShadow{"2730}
\DeclareUTFSymbol\textAsteriskBold{"2731}
\DeclareUTFSymbol\textAsteriskCenterOpen{"2732}
\DeclareUTFSymbol\textEightStarTaper{"2734}
\DeclareUTFSymbol\textEightStarConvex{"2735}
\DeclareUTFSymbol\textSixStar{"2736}
\DeclareUTFSymbol\textEightStar{"2737}
\DeclareUTFSymbol\textEightStarBold{"2738}
\DeclareUTFSymbol\textTwelveStar{"2739}
\DeclareUTFSymbol\textSixteenStarLight{"273A}
\DeclareUTFSymbol\textSixFlowerPetalRemoved{"273B}
\DeclareUTFSymbol\textSixFlowerOpenCenter{"273C}
\DeclareUTFSymbol\textAsterisk{"273D}
\DeclareUTFSymbol\textSixFlowerAlternate{"273E}
\DeclareUTFSymbol\textFiveFlowerPetal{"273F}
\DeclareUTFSymbol\textFiveFlowerOpen{"2740}
\DeclareUTFSymbol\textEightFlowerPetal{"2741}
\DeclareUTFSymbol\textSunshineOpenCircled{"2742}
\DeclareUTFSymbol\textSixFlowerAltPetal{"2743}
\DeclareUTFSymbol\textSnowflakeChevron{"2744}
\DeclareUTFSymbol\textSnowflake{"2745}
\DeclareUTFSymbol\textSnowflakeChevronBold{"2746}
\DeclareUTFSymbol\textSparkle{"2747}
\DeclareUTFSymbol\textSparkleBold{"2748}
\DeclareUTFSymbol\textAsteriskRoundedEnds{"2749}
\DeclareUTFSymbol\textEightFlowerPetalRemoved{"274A}
\DeclareUTFSymbol\textEightAsterisk{"274B}
\DeclareUTFSymbol\textCircleShadow{"274D}
\DeclareUTFSymbol\textSquareShadowBottomRight{"274F}
\DeclareUTFSymbol\textSquareTopRight{"2750}
\DeclareUTFSymbol\textSquareCastShadowBottomRight{"2751}
\DeclareUTFSymbol\textSquareCastShadowTopRight{"2752}
\DeclareUTFSymbol\textDiamondSolid{"2756}
\DeclareUTFSymbol\textRectangleThin{"2758}
\DeclareUTFSymbol\textRectangle{"2759}
\DeclareUTFSymbol\textRectangleBold{"275A}
\DeclareUTFSymbol\textperp{"27C2}
\DeclareUTFCommand\textnotperp{\textlstrikethru\textperp}
\DeclareUTFSymbol\textveedot{"27C7}
\DeclareUTFSymbol\textwedgedot{"27D1}
\DeclareUTFSymbol\textleftspoon{"27DC}
\DeclareUTFSymbol\textlbrackdbl{"27E6}
\DeclareUTFSymbol\textrbrackdbl{"27E7}
\DeclareUTFSymbol\textcirclearrowleft{"27F2}
\DeclareUTFSymbol\textcirclearrowright{"27F3}
\DeclareUTFSymbol\textlongleftarrow{"27F5}
\DeclareUTFSymbol\textlongrightarrow{"27F6}
\DeclareUTFSymbol\textlongleftrightarrow{"27F7}
\DeclareUTFSymbol\textLongleftarrow{"27F8}
\DeclareUTFSymbol\textLongrightarrow{"27F9}
\DeclareUTFSymbol\textLongleftrightarrow{"27FA}
\DeclareUTFSymbol\textlongmapsto{"27FC}
\DeclareUTFSymbol\textLongmapsfrom{"27FD}
\DeclareUTFSymbol\textLongmapsto{"27FE}
\DeclareUTFSymbol\textnwsearrow{"2921}
\DeclareUTFSymbol\textneswarrow{"2922}
\DeclareUTFSymbol\textlhooknwarrow{"2923}
\DeclareUTFSymbol\textrhooknearrow{"2924}
\DeclareUTFSymbol\textlhooksearrow{"2925}
\DeclareUTFSymbol\textrhookswarrow{"2926}
\DeclareUTFSymbol\textleadsto{"2933}
\DeclareUTFSymbol\textrcurvearrowne{"2934}
\DeclareUTFSymbol\textlcurvearrowse{"2935}
\DeclareUTFSymbol\textlcurvearrowsw{"2936}
\DeclareUTFSymbol\textrcurvearrowse{"2937}
\DeclareUTFSymbol\textlcurvearrowdown{"2938}
\DeclareUTFSymbol\textrcurvearrowdown{"2939}
\DeclareUTFSymbol\textrcurvearrowleft{"293A}
\DeclareUTFSymbol\textrcurvearrowright{"293B}
\DeclareUTFSymbol\textleftrightharpoon{"294A}
\DeclareUTFSymbol\textrightleftharpoon{"294B}
\DeclareUTFSymbol\textupdownharpoonrightleft{"294C}
\DeclareUTFSymbol\textupdownharpoonleftright{"294D}
\DeclareUTFSymbol\textleftleftharpoons{"2962}
\DeclareUTFSymbol\textupupharpoons{"2963}
\DeclareUTFSymbol\textrightrightharpoons{"2964}
\DeclareUTFSymbol\textdowndownharpoons{"2965}
\DeclareUTFSymbol\textleftbarharpoon{"296A}
\DeclareUTFSymbol\textbarleftharpoon{"296B}
\DeclareUTFSymbol\textrightbarharpoon{"296C}
\DeclareUTFSymbol\textbarrightharpoon{"296D}
\DeclareUTFSymbol\textupdownharpoons{"296E}
\DeclareUTFSymbol\textdownupharpoons{"296F}
\DeclareUTFSymbol\textllparenthesis{"2987}
\DeclareUTFSymbol\textrrparenthesis{"2988}
\DeclareUTFSymbol\textinvdiameter{"29B0}
\DeclareUTFSymbol\textobar{"29B6}
\DeclareUTFSymbol\textobslash{"29B8}
\DeclareUTFSymbol\textobot{"29BA}
\DeclareUTFSymbol\textNoChemicalCleaning{"29BB}
\DeclareUTFSymbol\textolessthan{"29C0}
\DeclareUTFSymbol\textogreaterthan{"29C1}
\DeclareUTFSymbol\textboxslash{"29C4}
\DeclareUTFSymbol\textboxbslash{"29C5}
\DeclareUTFSymbol\textboxast{"29C6}
\DeclareUTFSymbol\textboxcircle{"29C7}
\DeclareUTFSymbol\textboxbox{"29C8}
\DeclareUTFSymbol\textValve{"29D3}
\DeclareUTFSymbol\textmultimapboth{"29DF}
\DeclareUTFSymbol\textshuffle{"29E2}
\DeclareUTFSymbol\textuplus{"2A04}
\DeclareUTFSymbol\textbigdoublewedge{"2A07}
\DeclareUTFSymbol\textbigdoublevee{"2A08}
\DeclareUTFSymbol\textJoin{"2A1D}
\DeclareUTFSymbol\textfatsemi{"2A1F}
\DeclareUTFSymbol\textcircplus{"2A22}
\DeclareUTFSymbol\textminusdot{"2A2A}
\DeclareUTFSymbol\textdottimes{"2A30}
\DeclareUTFSymbol\textdtimes{"2A32}
\DeclareUTFSymbol\textodiv{"2A38}
\DeclareUTFSymbol\textinvneg{"2A3C}
\DeclareUTFSymbol\textsqdoublecap{"2A4E}
\DeclareUTFSymbol\textcapdot{"2A40}
\DeclareUTFSymbol\textsqdoublecup{"2A4F}
\DeclareUTFSymbol\textdoublewedge{"2A55}
\DeclareUTFSymbol\textdoublevee{"2A56}
\DeclareUTFSymbol\textdoublebarwedge{"2A5E}
\DeclareUTFSymbol\textveedoublebar{"2A63}
\DeclareUTFSymbol\texteqdot{"2A66}
\DeclareUTFCommand\textneqdot{\textlstrikethru\texteqdot}
\DeclareUTFSymbol\textcoloncolonequals{"2A74}
\DeclareUTFSymbol\textleqslant{"2A7D}
\DeclareUTFCommand\textnleqslant{\textlstrikethru\textleqslant}
\DeclareUTFSymbol\textgeqslant{"2A7E}
\DeclareUTFCommand\textngeqslant{\textlstrikethru\textgeqslant}
\DeclareUTFSymbol\textlessapprox{"2A85}
\DeclareUTFCommand\textnlessapprox{\textlstrikethru\textlessapprox}
\DeclareUTFSymbol\textgtrapprox{"2A86}
\DeclareUTFCommand\textngtrapprox{\textlstrikethru\textgtrapprox}
\DeclareUTFSymbol\textlneq{"2A87}
\DeclareUTFSymbol\textgneq{"2A88}
\DeclareUTFSymbol\textlnapprox{"2A89}
\DeclareUTFSymbol\textgnapprox{"2A8A}
\DeclareUTFSymbol\textlesseqqgtr{"2A8B}
\DeclareUTFSymbol\textgtreqqless{"2A8C}
\DeclareUTFSymbol\texteqslantless{"2A95}
\DeclareUTFSymbol\texteqslantgtr{"2A96}
\DeclareUTFSymbol\textleftslice{"2AA6}
\DeclareUTFSymbol\textrightslice{"2AA7}
\DeclareUTFSymbol\textpreceq{"2AAF}
\DeclareUTFCommand\textnpreceq{\textlstrikethru\textpreceq}
\DeclareUTFSymbol\textsucceq{"2AB0}
\DeclareUTFCommand\textnsucceq{\textlstrikethru\textsucceq}
\DeclareUTFSymbol\textprecneq{"2AB1}
\DeclareUTFSymbol\textsuccneq{"2AB2}
\DeclareUTFSymbol\textpreceqq{"2AB3}
\DeclareUTFCommand\textnpreceqq{\textlstrikethru\textpreceqq}
\DeclareUTFSymbol\textsucceqq{"2AB4}
\DeclareUTFCommand\textnsucceqq{\textlstrikethru\textsucceqq}
\DeclareUTFSymbol\textprecneqq{"2AB5}
\DeclareUTFSymbol\textsuccneqq{"2AB6}
\DeclareUTFSymbol\textprecapprox{"2AB7}
\DeclareUTFCommand\textnprecapprox{\textlstrikethru\textprecapprox}
\DeclareUTFSymbol\textsuccapprox{"2AB8}
\DeclareUTFCommand\textnsuccapprox{\textlstrikethru\textsuccapprox}
\DeclareUTFSymbol\textprecnapprox{"2AB9}
\DeclareUTFSymbol\textsuccnapprox{"2ABA}
\DeclareUTFSymbol\textsubseteqq{"2AC5}
\DeclareUTFCommand\textnsubseteqq{\textlstrikethru\textsubseteqq}
\DeclareUTFSymbol\textsupseteqq{"2AC6}
\DeclareUTFCommand\textnsupseteqq{\textlstrikethru\textsupseteqq}
\DeclareUTFSymbol\textdashV{"2AE3}
\DeclareUTFCommand\textndashV{\textlstrikethru\textdashV}
\DeclareUTFSymbol\textDashv{"2AE4}
\DeclareUTFCommand\textnDashv{\textlstrikethru\textDashv}
\DeclareUTFSymbol\textDashV{"2AE5}
\DeclareUTFCommand\textnDashV{\textlstrikethru\textDashV}
\DeclareUTFSymbol\textdownmodels{"2AEA}
\DeclareUTFCommand\textndownmodels{\textlstrikethru\textdownmodels}
\DeclareUTFSymbol\textupmodels{"2AEB}
\DeclareUTFCommand\textnupmodels{\textlstrikethru\textupmodels}
\DeclareUTFSymbol\textupspoon{"2AEF}
\DeclareUTFSymbol\textinterleave{"2AF4}
\DeclareUTFSymbol\textsslash{"2AFD}
\DeclareUTFSymbol\textpentagon{"2B20}
\DeclareUTFSymbol\textvarhexagon{"2B21}
\DeclareUTFSymbol\textjinferior{"2C7C}
\DeclareUTFSymbol\textslashdiv{"2E13}
\DeclareUTFSymbol\textinterrobangdown{"2E18}
\DeclareUTFSymbol\textfivedots{"2E2D}
\DeclareUTFSymbol\textPUheng{"A727}
\DeclareUTFSymbol\textPUlhookfour{"A72C}
\DeclareUTFSymbol\textPUscf{"A730}
\DeclareUTFSymbol\textPUaolig{"A735}
\DeclareUTFSymbol\textoo{"A74F}
\DeclareUTFSymbol\textcircumlow{"A788}
\DeclareUTFSymbol\textfi{"FB01}
\DeclareUTFSymbol\textfl{"FB02}
\DeclareUTFSymbol\textGaPa{"1D13B}
\DeclareUTFSymbol\textHaPa{"1D13C}
\DeclareUTFSymbol\textViPa{"1D13D}
\DeclareUTFSymbol\textAcPa{"1D13E}
\DeclareUTFSymbol\textSePa{"1D13F}
\DeclareUTFSymbol\textZwPa{"1D140}
\DeclareUTFSymbol\textfullnote{"1D15D}
\DeclareUTFSymbol\texthalfnote{"1D15E}
\DeclareUTFSymbol\textVier{"1D15F}
\DeclareUTFSymbol\textAcht{"1D160}
\DeclareUTFSymbol\textSech{"1D161}
\DeclareUTFSymbol\textZwdr{"1D162}
\DeclareUTFSymbol\textMundus{"1F30D}
\DeclareUTFSymbol\textMoon{"1F319}
\DeclareUTFSymbol\textManFace{"1F468}
\DeclareUTFSymbol\textWomanFace{"1F469}
\DeclareUTFSymbol\textFax{"1F4E0}
\DeclareUTFSymbol\textFire{"1F525}
\DeclareUTFSymbol\textBicycle{"1F6B2}
\DeclareUTFSymbol\textGentsroom{"1F6B9}
\DeclareUTFSymbol\textLadiesroom{"1F6BA}
\DeclareUTFCommand\textcopyleft{\textcircled\textrevc}
\DeclareUTFCommand\textccsa{\textcircled\textcirclearrowleft}
\DeclareUTFSymbol\textglqq{"201E}
\DeclareUTFSymbol\textgrqq{"201C}
\DeclareUTFSymbol\textglq{"201A}
\DeclareUTFSymbol\textgrq{"2018}
\DeclareUTFSymbol\textflqq{"00AB}
\DeclareUTFSymbol\textfrqq{"00BB}
\DeclareUTFSymbol\textflq{"2039}
\DeclareUTFSymbol\textfrq{"203A}
\DeclareUTFSymbol\textneg{"00AC}
\DeclareUTFSymbol\textcdot{"00B7}
%    \end{macrocode}
%
%    \begin{macrocode}
%</xunextra>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=xeCJK>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK.cfg}}
%
%    \begin{macrocode}
%<*config>
%    \end{macrocode}
%
% 预设的配置文件 \file{xeCJK.cfg} 为一个空文件。可以在里面增加设置，然后保存到
% 本地目录下面。
%    \begin{macrocode}

%    \end{macrocode}
%
%    \begin{macrocode}
%</config>
%    \end{macrocode}
%
% \end{implementation}
%
% \Finale
%
% \DisableImplementation
%
% \begin{implementation}
%
% \makeatletter
% \let\special@index\@gobble
% \makeatother
%
% \section{例子}
%
% \subsection{\pkg{xeCJK-example-autofake.tex}}
%
%    \begin{macrocode}
%<*ex-autofake>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage[AutoFakeBold,AutoFakeSlant]{xeCJK}
\setCJKmainfont[BoldFont=simhei.ttf, SlantedFont=simkai.ttf]{simsun.ttc}
\setCJKsansfont[AutoFakeSlant=false,
  BoldFont=simhei.ttf, SlantedFont=simkai.ttf]{simsun.ttc}
\setCJKmonofont[ItalicFont=simkai.ttf]{simsun.ttc}
\begin{document}
\centering
\begin{tabular}{lllll}
\hline
 {\bf rm} & md & up & \verb|\rmfamily\mdseries\upshape| &
                      {\rmfamily\mdseries\upshape English 中文字体} \\
          & md & it & \verb|\rmfamily\mdseries\itshape| &
                      {\rmfamily\mdseries\itshape English 中文字体} \\
          & md & sl & \verb|\rmfamily\mdseries\slshape| &
                      {\rmfamily\mdseries\slshape English 中文字体} \\ \cline{2-5}
          & bf & up & \verb|\rmfamily\bfseries\upshape| &
                      {\rmfamily\bfseries\upshape English 中文字体} \\
          & bf & it & \verb|\rmfamily\bfseries\itshape| &
                      {\rmfamily\bfseries\itshape English 中文字体} \\
          & bf & sl & \verb|\rmfamily\bfseries\slshape| &
                      {\rmfamily\bfseries\slshape English 中文字体} \\ \hline
 {\bf sf} & md & up & \verb|\sffamily\mdseries\upshape| &
                      {\sffamily\mdseries\upshape English 中文字体} \\
          & md & it & \verb|\sffamily\mdseries\itshape| &
                      {\sffamily\mdseries\itshape English 中文字体} \\
          & md & sl & \verb|\sffamily\mdseries\slshape| &
                      {\sffamily\mdseries\slshape English 中文字体} \\ \cline{2-5}
          & bf & up & \verb|\sffamily\bfseries\upshape| &
                      {\sffamily\bfseries\upshape English 中文字体} \\
          & bf & it & \verb|\sffamily\bfseries\itshape| &
                      {\sffamily\bfseries\itshape English 中文字体} \\
          & bf & sl & \verb|\sffamily\bfseries\slshape| &
                      {\sffamily\bfseries\slshape English 中文字体} \\ \hline
 {\bf tt} & md & up & \verb|\ttfamily\mdseries\upshape| &
                      {\ttfamily\mdseries\upshape English 中文字体} \\
          & md & it & \verb|\ttfamily\mdseries\itshape| &
                      {\ttfamily\mdseries\itshape English 中文字体} \\
          & md & sl & \verb|\ttfamily\mdseries\slshape| &
                      {\ttfamily\mdseries\slshape English 中文字体} \\ \cline{2-5}
          & bf & up & \verb|\ttfamily\bfseries\upshape| &
                      {\ttfamily\bfseries\upshape English 中文字体} \\
          & bf & it & \verb|\ttfamily\bfseries\itshape| &
                      {\ttfamily\bfseries\itshape English 中文字体} \\
          & bf & sl & \verb|\ttfamily\bfseries\slshape| &
                      {\ttfamily\bfseries\slshape English 中文字体} \\ \hline
\end{tabular}
\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-autofake>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-fallback.tex}}
%
%    \begin{macrocode}
%<*ex-fallback>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage[AutoFallBack]{xeCJK}
\usepackage{xeCJKfntef}
\usepackage{array}
\setCJKmainfont[AutoFakeBold,AutoFakeSlant]{KaiTi_GB2312}
\setCJKfallbackfamilyfont{\CJKrmdefault}[BoldFont=SimHei]
  { [SlantedFont=FangSong]{SimSun} ,
    [BoldFont=*]          {SimSun-ExtB} }
\begin{document}
漢字源𣴑考

\textbf{漢字源𣴑考}

\textsl{漢字源𣴑考}

\CJKunderwave{漢字源𣴑考}
\begin{table}[ht]
\caption{生僻字测试}
\medskip\centering
\begin{tabular}{*4{|c>{\ttfamily U+}l}|}
㐀 & 3400  & 㐁 & 3401  & 㐂 & 3402  & 㐃 & 3403  \\
㐄 & 3404  & 㐅 & 3405  & 㐆 & 3406  & 㐇 & 3407  \\
㐈 & 3408  & 㐉 & 3409  & 㐊 & 340A  & 㐋 & 340B  \\
㐌 & 340C  & 㐍 & 340D  & 㐎 & 340E  & 㐏 & 341F  \\
㐐 & 3410  & 㐑 & 3411  & 㐒 & 3412  & 㐓 & 3413  \\
㐔 & 3414  & 㐕 & 3415  & 㐖 & 3416  & 㐗 & 3417  \\
㐘 & 3418  & 㐙 & 3419  & 㐚 & 341A  & 㐛 & 341B  \\
㐜 & 341C  & 㐝 & 341D  & 㐞 & 341E  & 㐟 & 341F  \\[1ex]
𠀀 & 20000 & 𠀁 & 20001 & 𠀂 & 20002 & 𠀃 & 20003 \\
𠀄 & 20004 & 𠀅 & 20005 & 𠀆 & 20006 & 𠀇 & 20007 \\
𠀈 & 20008 & 𠀉 & 20009 & 𠀊 & 2000A & 𠀋 & 2000B \\
𠀌 & 2000C & 𠀍 & 2000D & 𠀎 & 2000E & 𠀏 & 2000F \\
𠀐 & 20010 & 𠀑 & 20011 & 𠀒 & 20012 & 𠀓 & 20013 \\
𠀔 & 20014 & 𠀕 & 20015 & 𠀖 & 20016 & 𠀗 & 20017 \\
𠀘 & 20018 & 𠀙 & 20019 & 𠀚 & 2001A & 𠀛 & 2001B \\
𠀜 & 2001C & 𠀝 & 2001D & 𠀞 & 2001E & 𠀟 & 2001F \\
\end{tabular}
\end{table}
\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-fallback>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-subCJKblock.tex}}
%
%    \begin{macrocode}
%<*ex-block>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage{xeCJK}
\usepackage{array}
\xeCJKDeclareSubCJKBlock{Ext-A} { "3400 -> "4DBF }
\xeCJKDeclareSubCJKBlock{Ext-B} { "20000 -> "2A6DF }
\xeCJKDeclareSubCJKBlock{Kana}  { "3040 -> "309F, "30A0 -> "30FF, "31F0 -> "31FF, }
\xeCJKDeclareSubCJKBlock{Hangul}{ "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF }
\setCJKmainfont[Ext-A=SimHei,Ext-B=SimSun-ExtB]{SimSun}
\setCJKmainfont[Kana]{Meiryo}
\setCJKmainfont[Hangul]{Malgun Gothic}
\parindent=2em
\begin{document}
\long\def\showtext{%
中日韩越统一表意文字（英语：CJKV Unified Ideographs），旧称中日韩统一表意文字（英语：CJK Unified Ideographs），也称统一汉字（英语：Unihan），目的是要把分别来自中文、日文、韩文、越文、壮文中，对于相同起源、本义相同、形状一样或稍异的表意文字（主要为汉字，但也有仿汉字如：方块壮字、日文汉字（かんじ / kanji）、韩文汉字（한자 / hanja）、越南的喃字（Chữ Nôm）与越文汉字（Chữ Nho，在越南也称作儒字），应赋予其在ISO 10646及统一码标准中有相同编码。此计划原本只包含中文、日文及韩文中所使用的汉字，是以旧称中日韩统一表意文字（CJK）。后来，此计划加入了越文的喃字，所以合称中日韩越统一表意文字（CJKV）。

CJK統合漢字（シージェーケーとうごうかんじ、CJK Unified Ideographs）は、ISO/IEC 10646 (Universal Multiple-Octet Coded Character Set, 略称 UCS) および Unicodeにて採用されている符号化用漢字集合およびその符号表である。CJK統合漢字の名称は、中国語(Chinese)、日本語(Japanese)、韓国語(Korean)で使われている漢字をひとまとめにしたことからきている。CJK統合漢字の初版である Unified Repertoire and Ordering (URO) 第二版は1992年に制定されたが、1994年にベトナムで使われていた漢字も含めることにしたため、CJKVと呼ばれる事もある。CJKVは、中国語・日本語・韓国語・ベトナム語 (英語: Chinese-Japanese-Korean-Vietnamese) の略。特に、その4言語で共通して使われる、または使われていた文字体系である漢字（チュノムを含む）のこと。ソフトウェアの国際化、中でも文字コードに関する分野で用いられる。

\CJKspace
한중일월 통합 한자(또는 한중일 통합 한자)는 유니코드에 담겨 있는 한자들의 집합으로, 한국, 중국, 일본에서 쓰이는 한자를 묶은 것이기 때문에 머리 글자를 따서 한중일(CJK) 통합 한자라고 불렀는데, 최근에는 베트남에서 쓰이는 한자도 추가되었기에 한중일월(CJKV) 통합 한자로 부르게 되었다.

처음에 유니코드에는 65,536($=2^{16}$)자만 들어갈 수 있었기 때문에, 가장 많은 문자가 배당되는 한자를 위해서 한국, 중국, 일본에서 사용하는 한자 중에 모양이 유사하며 그 뜻이 같은 글자를 같은 코드로 통합했다. 따라서 문자 코드만으로 그 한자가 사용되는 언어를 알아 낼 수 없는데, 다만 중국의 간체자나 번체자, 일본의 구자체나 신자체 등 분명하게 모양이 다른 글자는 별도의 부호를 할당하고 있다. 이런 문자 할당 정책에 반발하여 TRON과 같은 인코딩이 만들어지기도 했으나, 실제로 통합된 한자의 차이가 별로 크지 않기 때문에 문제가 되지 않는다는 의견도 있다.
\CJKnospace}
\showtext

\bigskip
\xeCJKCancelSubCJKBlock{Kana,Hangul}
\showtext

\bigskip
\xeCJKRestoreSubCJKBlock{Hangul}
\showtext

\begin{table}[ht]
\caption{生僻字测试}
\medskip\centering
\begin{tabular}{*4{|c>{\ttfamily U+}l}|}
㐀 & 3400  & 㐁 & 3401  & 㐂 & 3402  & 㐃 & 3403  \\
㐄 & 3404  & 㐅 & 3405  & 㐆 & 3406  & 㐇 & 3407  \\
㐈 & 3408  & 㐉 & 3409  & 㐊 & 340A  & 㐋 & 340B  \\
㐌 & 340C  & 㐍 & 340D  & 㐎 & 340E  & 㐏 & 341F  \\
㐐 & 3410  & 㐑 & 3411  & 㐒 & 3412  & 㐓 & 3413  \\
㐔 & 3414  & 㐕 & 3415  & 㐖 & 3416  & 㐗 & 3417  \\
㐘 & 3418  & 㐙 & 3419  & 㐚 & 341A  & 㐛 & 341B  \\
㐜 & 341C  & 㐝 & 341D  & 㐞 & 341E  & 㐟 & 341F  \\[1ex]
𠀀 & 20000 & 𠀁 & 20001 & 𠀂 & 20002 & 𠀃 & 20003 \\
𠀄 & 20004 & 𠀅 & 20005 & 𠀆 & 20006 & 𠀇 & 20007 \\
𠀈 & 20008 & 𠀉 & 20009 & 𠀊 & 2000A & 𠀋 & 2000B \\
𠀌 & 2000C & 𠀍 & 2000D & 𠀎 & 2000E & 𠀏 & 2000F \\
𠀐 & 20010 & 𠀑 & 20011 & 𠀒 & 20012 & 𠀓 & 20013 \\
𠀔 & 20014 & 𠀕 & 20015 & 𠀖 & 20016 & 𠀗 & 20017 \\
𠀘 & 20018 & 𠀙 & 20019 & 𠀚 & 2001A & 𠀛 & 2001B \\
𠀜 & 2001C & 𠀝 & 2001D & 𠀞 & 2001E & 𠀟 & 2001F \\
\end{tabular}
\end{table}
\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-block>
%    \end{macrocode}
%
%
% \subsection{\pkg{xeCJK-example-CJKecglue.tex}}
%
%    \begin{macrocode}
%<*ex-ecglue>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{minimal}
\usepackage{xeCJK}
\setCJKmainfont[BoldFont=SimHei]{SimSun}
\long\def\showtext{%
 这是 English 中文 {\itshape Chinese} 中文    \TeX\
  间隔 \textit{Italic} 中文\textbf{字体} a 数学 $b$ 数学 $c$ $d$\par
 这是English中文{\itshape Chinese}中文\TeX\
 间隔\textit{Italic}中文\textbf{字体}a数学$b$数学$c$ $d$\par
This is an example. 这是一个例子}
\begin{document}
\showtext

\hrulefill\bigskip

\xeCJKsetup{xCJKecglue=\quad}
\showtext
\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-ecglue>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-boundary-register.tex}}
%
%    \begin{macrocode}
%<*ex-boundary-register>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage{xeCJK}

\setCJKmainfont{FandolSong-Regular.otf}
\setCJKmonofont{FandolFang-Regular.otf}
\setlength{\fboxsep}{2pt}
\setlength{\fboxrule}{0.4pt}
\setlength{\parindent}{0pt}

\newcommand\MyBox[1]{\hbox{#1}}
\newcommand\MyWrapped[1]{\kern0pt #1\kern0pt}
\newcommand\MyStream[1]{\special{xeCJK-boundary-example}#1}
\newcommand\MyTransparent{\special{xeCJK-boundary-example}}
\newcommand\MyPostTransparent{\hbox{}}

\xeCJKsetup
  {
    CJKecglue = {\hskip 10pt},
    CJKglue   = {\hskip 2pt},
    experiment/boundary-register =
      { command = \MyBox, strategy = box },
    experiment/boundary-register =
      { command = \MyWrapped, strategy = wrapped-box },
    experiment/boundary-register =
      { command = \MyStream, strategy = stream },
    experiment/boundary-register =
      { command = \MyTransparent, strategy = transparent },
    experiment/boundary-register =
      { command = \MyPostTransparent, strategy = post-transparent }
  }

\newcommand\BoundaryCell[2]
  {
    \begin{tabular}[c]{@{}c@{}}
      \fbox{\hbox{#1}} \\
      \fbox{\hbox{#2}}
    \end{tabular}
  }

\newcommand\BoundaryRow[4]
  {
    \texttt{#1}
      & \BoundaryCell{#2#3#4}{#2\MyStream{#3}#4}
      & \BoundaryCell{#2 #3#4}{#2 \MyStream{#3}#4}
      & \BoundaryCell{#2#3 #4}{#2\MyStream{#3} #4}
      & \BoundaryCell{#2 #3 #4}{#2 \MyStream{#3} #4} \\[2pt]
  }

\newcommand\TestBoundaryMatrix[1]
  {
    \subsection*{\texttt{xCJKecglue=#1}}
    \xeCJKsetup{xCJKecglue=#1}
    \begin{tabular}{@{}lcccc@{}}
      & \texttt{00} & \texttt{10} & \texttt{01} & \texttt{11} \\
      \BoundaryRow{西文}{中}{A}{中}
      \BoundaryRow{中文}{A}{中文}{B}
      \BoundaryRow{中西}{A}{中A}{B}
      \BoundaryRow{西中}{A}{A中}{B}
      \BoundaryRow{公式}{中}{$x$}{中}
    \end{tabular}
  }

\begin{document}

\section*{\normalfont 用户命令的边界间距检查}
每格上方是直接输入，下方是采用 \texttt{stream} 策略注册的命令；
两只方框等宽表示这一格与直接输入一致。中西、西中两行分别只在左边界和
右边界产生中西文间距，用于排除两侧误差互相抵消的情况。公式行故意展示
通用注册不分析命令参数的限制：公式位于命令边界时，还需要专用参数适配，
不能把不等宽的结果当作正确行为。

\begingroup
  \small
  \setlength\tabcolsep{2pt}
  \TestBoundaryMatrix{false}
  \TestBoundaryMatrix{true}
\endgroup

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-boundary-register>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-checksingle.tex}}
%
%    \begin{macrocode}
%<*ex-single>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{minimal}
\usepackage{xeCJK}
\setCJKmainfont{SimSun}
\long\def\showtext{一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。
\[x^2+y^2\]
一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。
$$x^2+y^2$$
一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。
\begin{equation}
x^2+y^2
\end{equation}
一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。}
\begin{document}
\hsize=30em
\parindent=0pt
\showtext

\hrulefill\bigskip

\xeCJKsetup{CheckSingle}
\showtext

\hrulefill\bigskip

\xeCJKsetup{PlainEquation}
\showtext
\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-single>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-CJKfntef.tex}}
%
%    \begin{macrocode}
%<*ex-fntef>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{ctexart}
\usepackage{xcolor}
\usepackage{xeCJKfntef}
\xeCJKDeclareSubCJKBlock{test}{ `殆 , `已 }
\setCJKmainfont[test=SimHei]{SimSun}

\def\showtext{%
  庄子曰：“吾生也有涯，而知也无涯。以有涯随无涯，殆已！已而为知者，殆而已矣！%
  为善无近名，为恶无近刑，缘督以为经，可以保身，可以全生，可以养亲，可以尽年。”}
\newcommand\CJKfntef[1]{%
  \csname#1\endcsname{\showtext}\par
  \csname#1\endcsname*{\showtext}\par
  \CJKunderdot{\showtext\csname#1\endcsname{\showtext}}\par
  \CJKunderdot{\showtext\csname#1\endcsname*{\showtext}}\par
  \csname#1\endcsname{\showtext\CJKunderdot{\showtext}}\par
  \csname#1\endcsname*{\showtext\CJKunderdot{\showtext}}\par\bigskip}

\begin{document}

\section{\textsf{xeCJKfntef} 的\CJKunderdot{简单测试文件}}

\CJKunderline{汉 字}\CJKunderline{加下划线}
\varCJKunderline{汉字}\varCJKunderline{加下划线}
\CJKunderanyline{0.5em}{\sixly \kern-.021em\char58 \kern-.021em}{自定义下划线}
\CJKunderanyline{0.2em}{\kern-.03em\vtop{\kern.2ex\hrule width.2em\kern 0.11em
  \hrule height 0.1em}\kern-.03em}{自定义下划线}

\CJKunderanyline{0.5em}{-}{\showtext}

\CJKunderanysymbol{0.2em}{-}{\showtext}

\CJKunderdot{\showtext}

\hrule depth \dp\strutbox width 0 pt \relax

\begin{CJKfilltwosides}{40mm}
两端分散对齐\\
分散对齐\\
\CJKunderdot{汉字可加点}\\
\CJKunderline{汉字可加下划线}
\end{CJKfilltwosides}

\hrule depth \dp\strutbox width 0 pt \relax

\CJKfntef{CJKunderline}
\CJKfntef{CJKunderwave}
\CJKfntef{CJKunderdblline}
\CJKfntef{CJKsout}
\CJKfntef{CJKxout}

\def\Book{\CJKunderwave-}
\def\Noun{\CJKunderline-}

如上所述，\Book{中國文學史}原是\Noun{復旦大學出版社}出版的，\Book{中國文學史新著}則由\Noun{上海文藝出版社}出版。現承\Noun{復旦大學出版社}\Noun{賀聖遂}先生的盛情和\Noun{上海文藝出版總社}\Noun{楊益萍}、\Noun{何承偉}先生以及有關負責人\Noun{陳鳴華}先生的高誼，此書增訂本改由兩社聯合出版。\Noun{賀聖遂}先生並與\Noun{杜榮根}副社長、\Noun{韓結根}編審共同擔任此書的責任編輯。對上述諸位先生謹在這裡表示衷心的感謝。對\Book{中國文學史新著}原版的責任編輯\Noun{戴俊}先生（現為\Noun{上海三聯書店}副總經理）也在此一併致謝。

\Noun{與文學史的}\Noun{應有任務}\Noun{對文學發展過程}\Noun{的內在聯繫的描述}\Noun{還有很大的距離}\Noun{至於距離的所在}\Noun{本書原已有說明}\Book{此不贅述}\Book{為了對學術負責}\Book{我們決定重寫一部並迅即出版}

\Noun{南朝}\Noun{梁}\Noun{劉勰}\Book{文心雕龍}\Book{養氣}

\Noun{鎌池和馬}\Book{魔法禁書目錄}\Book{某科學的超電磁砲}

\Noun{西尾維新}\Book{化物語}\Book{偽物語}\Book{貓物語}%
\Book{傾物语}\Book{囤物語}\Book{鬼物語}\Book{戀物語}\Book{花物語}

\Noun{岡田麿里}\Book{我們仍未知道那天所看見的花的名字。}\Book{絕園的暴風雨}

\Noun{虚淵玄}\Book{Fate/Zero}\Book{PSYCHO-PASS}\Book{ALDNOAH.ZERO}

\Noun{劉海洋}\Book{\LaTeX 入門}

\def\NAME{%
  \x{御坂美琴}\x{白井黑子}\x{戰場原緋多木}\x{阿良良木歷}%
  \x{宿海仁太}\x{本間芽衣子}\x{瀧川吉野}\x{鎖部葉風}%
  \x{衛宮切嗣}\x{言峰綺禮}\x{常守朱}\x{狡嚙慎也}%
  \x{界塚伊奈帆}\x*{艾瑟依拉姆·沃斯·艾莉歐斯亞}}

\let\x\Noun\NAME

\let\x\Book\NAME

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-fntef>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-punctstyle.tex}}
%
%    \begin{macrocode}
%<*ex-punctstyle>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage[draft,columns=1,margin=false,headings=false]{typogrid}
\usepackage{xeCJK}
\usepackage{xeCJKfntef}
\setCJKmainfont{SimSun}
\xeCJKsetup{AllowBreakBetweenPuncts}
\xeCJKDeclarePunctStyle { mine }
  {
    fixed-punct-ratio       = nan ,
    fixed-margin-width      = 0 pt ,
    mixed-margin-width      = \maxdimen ,
    mixed-margin-ratio      = 0.5 ,
    middle-margin-width     = \maxdimen ,
    middle-margin-ratio     = 0.5 ,
    add-min-bound-to-margin = true ,
    min-bound-to-kerning    = true ,
    kerning-margin-minimum  = 0.1 em ,
    bound-punct-width       = 0 em ,
    enabled-hanging         = true
  }
\xeCJKDeclarePunctStyle { JIS } { bound-punct-ratio = .5 }

\begin{document}
\setlength\parindent{2em}
\newcommand\showexample[1]{%
  \texttt{------ #1 ------}\par\xeCJKsetup{PunctStyle=#1}\showtexts\par}

\newcommand\showtexts{%
《第一回\quad 甄士隐梦幻识通灵\quad 贾雨村风尘怀闺秀》\\
《第一回\quad 甄士隐梦幻识通灵\quad 贾雨村风尘怀闺秀》

列位看官：你道此书从何而来？说起根由，虽近荒唐，细按则深有趣味。待在下将此来历注明，方使阅者了然不惑。

\CJKunderline*{原来女娲氏炼石补天之时，于大荒山无稽崖炼成高经十二丈、方经二十四丈顽石三万六千五百零一块。娲皇氏只用了三万六千五百块，只单单剩了一块未用，便弃在此山青埂峰下。谁知此石自经煆炼之后，灵性已通，因见众石俱得补天，独自己无材不堪入选，遂自怨自叹，日夜悲号惭愧。}

一日，正当嗟悼之际，俄见一僧一道远远而来，生得骨格不凡，丰神迥别，说说笑笑，来至峰下，坐于石边，高谈快论：先是说些云山雾海、神仙玄幻之事，后便说到红尘中荣华富贵。此石听了，不觉打动凡心，也想要到人间去享一享这荣华富贵，但自恨粗蠢，不得已，便口吐人言，向那僧道说道：“大师，弟子蠢物，不能见礼了！适闻二位谈那人世间荣耀繁华，心切慕之。弟子质虽粗蠢，性却稍通，况见二师仙形道体，定非凡品，必有补天济世之材，利物济人之德。如蒙发一点慈心，携带弟子得入红尘，在那富贵场中，温柔乡里受享几年，自当永佩洪恩，万劫不忘也！”二仙师听毕，齐憨笑道：“善哉，善哉！那红尘中有却有些乐事，但不能永远依恃；况又有‘美中不足，好事多磨’八个字紧相连属，瞬息间则又乐极悲生，人非物换，究竟是到头一梦，万境归空，倒不如不去的好。”这石凡心已炽，那里听得进这话去，乃复苦求再四。二仙知不可强制，乃叹道：“此亦静极思动，无中生有之数也！既如此，我们便携你去受享受享，只是到不得意时，切莫后悔！”石道：“自然，自然。”那僧又道：“若说你性灵，却又如此质蠢，并更无奇贵之处。如此也只好踮脚而已。也罢！我如今大施佛法，助你助，待劫终之日，复还本质，以了此案。你道好否？”石头听了，感谢不尽。那僧便念咒书符，大展幻术，将一块大石登时变成一块鲜明莹洁的美玉，且又缩成扇坠大小的可佩可拿。那僧托于掌上，笑道：“形体倒也是个宝物了！还只没有实在的好处，须得再镌上数字，使人一见便知是奇物方妙。然后好携你到那昌明隆盛之邦、诗礼簪缨之族、花柳繁华地、温柔富贵乡去安身乐业。”石头听了，喜不能禁，乃问：“不知赐了弟子那哪几件奇处？又不知携了弟子到何地方？望乞明示，使弟子不惑。”那僧笑道：“你且莫问，日后自然明白的。”说着，便袖了这石，同那道人飘然而去，竟不知投奔何方何舍。

\hfill
——曹雪芹《红楼梦》\unskip\unkern}


\showexample{mine}
\showexample{quanjiao}
\showexample{JIS}
\showexample{banjiao}
\showexample{CCT}
\showexample{kaiming}
\showexample{hangmobanjiao}
\showexample{plain}

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-punctstyle>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-verbatim.tex}}
%
%    \begin{macrocode}
%<*ex-verb>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass[a4paper]{article}
\usepackage{xeCJK}
\usepackage{fullpage}
\xeCJKDeclareSubCJKBlock{Hangul}
  { "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF }
\setCJKmainfont{Adobe Song Std}
\setCJKmainfont[Hangul]{Adobe Myungjo Std}
%%\setmonofont{Inconsolata}
%%\setmonofont{Source Code Pro}
%%\setCJKmonofont{Adobe Kaiti Std}
\setmonofont{TeX Gyre Cursor}
\setCJKmonofont{Adobe Fangsong Std}
\begin{document}

\begin{verbatim}
*************************************************************
*                                                           *
*      精致甲版红楼梦 - 精致工作室特别奉献（2010年9月）     *
*                                                           *
*        GBK简体、GBK繁体、Big5繁体、PDF简体、PDF繁体       *
*                                                           *
*  为 GBK、Big5 特别度身订制，并经反复修订，方便纯文本阅读  *
*                                                           *
*  精致甲、乙版红楼梦下载专页                               *
*  http://www.speedy7.com/cn/stguru/gb2312/redmansions.htm  *
*  http://www.speedy7.com/cn/stguru/big5/redmansions.htm    *
*                                                           *
*  此电子文件可随意传播、发布、下载。                       *
*  传播时建议保留此版本说明供查阅与识别。                   *
*  未经修订者同意，不得将此版本用于商业用途。               *
*  如您对红楼梦不具深入了解，请勿随便修改其中的内容。       *
*                                                           *
*************************************************************
\end{verbatim}

\verb|a 汉 字 b|

\verb*|a 汉 字 b|

\begin{verbatim}
lang_set = {
  ["English"]   = lang.en,
  ["Français"]  = lang.fr,
  ["Español"]   = lang.es,
  ["中文"]      = lang.zh,
  ["日本語"]    = lang.ja,
  ["한국어"]    = lang.ko,
}
\end{verbatim}

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-verb>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-CM.tex}}
%
%    \begin{macrocode}
%<*ex-cm>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage{xeCJK}
\usepackage{hologo}

\setCJKmainfont{HanaMinA} %% http://fonts.jp/hanazono/

\begin{document}

\hologo{XeTeX} でIVSを使うテスト。

消󠄀化󠄀器󠄂 / 消󠄁火器󠄃 / 消󠄂化󠄁器󠄄

消󠄃火器󠄅 / 消化󠄂器󠄆 / 消火器󠄇

消化󠄃器󠄈 / 消火器󠄉 / 辻󠄂辻󠄃辻󠄄辻󠄅　

\CJKfontspec{Meiryo}

か

がか゚

ぼぽ

ぼぽ

ばぱ

ばぱ

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-cm>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-listings.tex}}
%
%    \begin{macrocode}
%<*ex-listings>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}
\usepackage[margin=1in]{geometry}
\usepackage{listings,xcolor}
\usepackage{showexpl}

\usepackage{xeCJK}
\setCJKmainfont{HanaMinA}
\setCJKmonofont{SimSun}
\xeCJKDeclareSubCJKBlock{Kana}  { "3040 -> "309F, "30A0 -> "30FF, "31F0 -> "31FF }
\setCJKmonofont[Kana]{Meiryo}
\setmonofont{Latin Modern Mono Light}

\makeatletter
\lstset{%
  basicstyle=\small\ttfamily,breakindent=4\lst@width,
  numbers=left,numberstyle=\tiny\color{gray},
  commentstyle=\color{green!50!black},keywordstyle=\color{blue}\bfseries,
  identifierstyle=\color{violet},stringstyle=\color{brown},
  escapebegin=\normalfont}
\lstnewenvironment{cppcode}[1][]
  {\lstset{language=C++,#1}}
  {}
\makeatother

\begin{document}

\section{\lstinline{\\lstinline} 测试}

\section{\lstinline|甲*乙| 测试}

\lstinline|abc汉字abc|

\lstinline|甲*乙|

\lstinline[mathescape]|数学公式$x^2+y^2$|

\section{\lstinline{lstlisting} 环境测试}

\begin{lstlisting}[basicstyle=\rmfamily]
纯文字text测试
    纯文字text测试
文字+文字
文字（符号）文字
辻󠄂辻󠄃辻󠄄辻󠄅
かがか゚
\end{lstlisting}

\begin{lstlisting}
text纯文字测试
\end{lstlisting}

\begin{lstlisting}
text 纯文字测试
\end{lstlisting}

\begin{lstlisting}
text,纯文字测试
\end{lstlisting}

\section{自定义环境测试}

\begin{LTXexample}[pos=t,varwidth,numbersep=5pt,columns=fixed]
\begin{cppcode}[escapechar=`,morekeywords=返回]
#define 返回 return
#include <iostream>
/*
 * 块注释
 * `逃逸字符，测试$f(x)$`
 */
int main()
{
    // 行注释
    const char *欢迎 = "hello 世界（ワールド）";
    std::cout << 欢迎 << std::endl;
    返回 0;
}
\end{cppcode}
\end{LTXexample}

\section{\texttt{breaklines} 测试}

\begin{lstlisting}[linewidth=10em,frame=single,rulecolor=\color{black},breaklines]
断行文字text测试，断行文字text测试，断行文字text测试，断行文字text测试
断行文字text测试，断行文字text测试，断行文字text测试，断行文字text测试
\end{lstlisting}

\section{\lstinline|\\lstinputlisting| 测试}

\lstinputlisting[language={[AlLaTeX]TeX}]{\jobname}

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-listings>
%    \end{macrocode}
%
% \subsection{\pkg{xeCJK-example-mathblock.tex}}
%
%    \begin{macrocode}
%<*ex-mathblock>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass{article}

\usepackage{url}
\usepackage[CJKmath]{xeCJK}

\setCJKmainfont[BoldFont=SimHei]{SimSun}
\setCJKmonofont{KaiTi}

\ExplSyntaxOn
\tl_const:Nn \c_test_text_tl { 天地玄黃宇宙洪荒日月盈昃辰宿列張寒來暑往秋收冬藏閏餘成歲律呂調陽 }
\cs_new:Npn \test_color:n #1
  { \exp_args:No \str_range:nnn { \mdfivesum {#1} } { 1 } { 6 } }
\tl_map_inline:Nn \c_test_text_tl
  {
    \xeCJKDeclareSubCJKBlock {#1} { `#1 }
    \setCJKmainfont
      [ #1 , Color = \test_color:n {#1} , BoldFont = SimHei ] { SimSun }
    \setCJKmonofont [ #1 ] { KaiTi }
  }
\cs_new_protected:Npn \TEST
  {
    \exp_args:No \path { \c_test_text_tl } \par
    \c_test_text_tl \par
    $ \c_test_text_tl $
  }
\ExplSyntaxOff

\begin{document}

\TEST

\bigskip

\bfseries\mathversion{bold}\TEST

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</ex-mathblock>
%    \end{macrocode}
%
% \subsection{\pkg{xunicode-symbols.tex}}
%
% 该文档用于生成 \pkg{xunicode} 中所声明字符的对照表。每一个字符
% 先尝试用主字体 |FreeSerif| 渲染；若主字体不覆盖该码位，则按顺序
% 回退到 |Noto Sans Symbols 2|、|Symbola|、|Segoe UI Symbol|、
% |DejaVu Sans| 中第一个能渲染该字符的字体。
%
% 在 \CTeX{} 团队的发版构建中，运行环境同时安装了 |FreeSerif|
% （Ubuntu \pkg{fonts-freefont-ttf}）和 |Noto Sans Symbols 2|，
% 因此 pre-release 内附的 \file{xunicode-symbols.pdf} 通常已能完整
% 渲染所有字符。用户在本地通过 |l3build install --full| 重新编译时，
% 输出是否完整取决于本机已安装的字体；若仅依赖系统自带字体（例如
% Windows 上仅有 |Segoe UI Symbol|），表中的部分字符会出现
% “Missing character” 提示。此时建议补装 |Noto Sans Symbols 2|
% 或直接使用 pre-release 中已编译好的版本。
%
% \changes{v3.10.0}{2026/06/23}{\file{xunicode-symbols.tex} 改为按
%   \pkg{Noto Sans Symbols 2}/\pkg{Symbola}/\pkg{Segoe UI Symbol}/\pkg{DejaVu Sans}
%   逐字符尝试的多级 fallback，缓解 Windows 等仅有
%   \pkg{Segoe UI Symbol} 时部分字符缺失的问题（\#878）。}
%
%    \begin{macrocode}
%<*xunicode-symbols>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=xunsym>
%    \end{macrocode}
%
%    \begin{macrocode}
\documentclass[a4paper]{article}
\usepackage[margin=2cm]{geometry}
\usepackage{fontspec}
\usepackage{xunicode-addon}
\usepackage{array,longtable,booktabs}
\usepackage{zref-base}

\setmainfont{FreeSerif}
\IfFontExistsTF{Noto Sans Symbols 2}
  {\newfontfamily\xunsymNoto{Noto Sans Symbols 2}}{}
\IfFontExistsTF{Symbola}
  {\newfontfamily\xunsymSymbola{Symbola}}{}
\IfFontExistsTF{Segoe UI Symbol}
  {\newfontfamily\xunsymSegoe{Segoe UI Symbol}}{}
\IfFontExistsTF{DejaVu Sans}
  {\newfontfamily\xunsymDejaVu{DejaVu Sans}}{}

\makeatletter
\ExplSyntaxOn
\cs_new_protected:Npe \@@_restore_catcode:
  { \char_set_catcode:nn { 0 } { \char_value_catcode:n { 0 } } }
\file_if_exist:nTF { xunicode-commands.tex }
  { \char_set_catcode_comment:n { 0 } }
  { \char_set_catcode_ignore:n { 0 } }
^^@ \tl_new:N \l_@@_command_tl
^^@ \int_new:N \l_@@_command_int
^^@ \iow_new:N \g_@@_command_iow
^^@ \iow_new:N \g_@@_combine_mark_iow
^^@ \ior_new:N \g_@@_unicode_ior
^^@ \prop_new:N \l_@@_command_prop
^^@ \prop_new:N \l_@@_combine_mark_prop
^^@ \prop_new:N \l_@@_combine_marks_prop
^^@ \cs_new_protected:Npn \SaveUTFCommands
^^@   {
^^@     \group_begin:
^^@     \@@_restore_catcode:
^^@     \cs_set_eq:NN \DeclareTextAccent \use_none:nnn
^^@     \cs_set_eq:NN \DeclareUTFComposite \use_none:n
^^@     \cs_set_eq:NN \DeclareUTFEncodedAccent \use_none:nnn
^^@     \cs_set_eq:NN \DeclareUTFDoubleEncodedAccent \use_none:nnn
^^@     \cs_set_eq:NN \DeclareUTFDoubleEncodedSymbol \use_none:nnn
^^@     \cs_set_eq:NN \DeclareUTFCommand \use_none:nn
^^@     \cs_set_eq:NN \DeclareUTFTIPACommand \use_none:n
^^@     \cs_set_eq:NN \DeclareUTFCompositeCommand \use_none:nnn
^^@     \cs_set_eq:NN \DeclareUTFSymbol \@@_save_UTF_command:Nn
^^@     \cs_set_eq:NN \DeclareUTFCompositeSymbol \@@_save_UTF_command:Nnn
^^@     \cs_set_eq:NN \DeclareEncodedCompositeCharacter \@@_save_combine_mark:nnnn
^^@     \cs_set_eq:NN \DeclareEncodedCompositeAccents \@@_save_combine_marks:nnnn
^^@     \DeclareDocumentCommand \DeclareUTFcharacter { O { \UTFencname } m m }
^^@       { \@@_save_UTF_command:NNn \l_@@_command_prop {##3} {##2} }
^^@     \DeclareDocumentCommand \DeclareUTFcomposite { O { \UTFencname } m m m }
^^@       { \@@_save_UTF_command:NNnn \l_@@_command_prop {##3} {##4} {##2} }
^^@     \ReloadXunicode { \UTFencname }
^^@     \ior_open:NnTF \g_@@_unicode_ior { UnicodeData.txt }
^^@       {
^^@         \iow_open:Nn \g_@@_command_iow { xunicode-commands.tex }
^^@         \iow_open:Nn \g_@@_combine_mark_iow { xunicode-combine-marks.tex }
^^@         \ior_map_inline:Nn \g_@@_unicode_ior { \@@_make_table:w ##1 \q_stop }
^^@         \@@_write_combine_marks:
^^@         \ior_close:N \g_@@_unicode_ior
^^@         \iow_close:N \g_@@_command_iow
^^@         \iow_close:N \g_@@_combine_mark_iow
^^@         \group_end:
^^@       }
^^@       {
^^@         \group_end:
^^@         \msg_fatal:nn { @@ } { UnicodeData }
^^@       }
^^@   }
^^@ \msg_new:nnn { @@ } { UnicodeData }
^^@   { File~`UnicodeData.txt`~not~found. }
^^@ \cs_new_protected:Npn \@@_save_UTF_command:Nn
^^@   { \@@_save_UTF_command:NNn \l_@@_command_prop }
^^@ \cs_new_protected:Npn \@@_save_UTF_command:Nnn
^^@   { \@@_save_UTF_command:NNnn \l_@@_command_prop }
^^@ \cs_new_protected:Npn \@@_save_UTF_command:NNn #1#2#3
^^@   { \@@_save_UTF_text_command:Nxx #1 { \use_none:n #3 } { \token_to_str:N #2 } }
^^@ \cs_new_protected:Npn \@@_save_UTF_command:NNnn #1#2#3#4
^^@   {
^^@     \@@_save_UTF_text_command:Nxx #1 { \use_none:n #4 }
^^@       {
^^@         \token_to_str:N #2 \c_left_brace_str
^^@         \tl_if_single:nTF {#3} { \token_to_str:N #3 } { \tl_to_str:n {#3} }
^^@         \c_right_brace_str
^^@       }
^^@   }
^^@ \cs_new_protected:Npn \@@_save_combine_mark:nnnn #1#2#3#4
^^@   { \@@_save_UTF_text_command:Nxx \l_@@_combine_mark_prop {#3} { \token_to_str:N #2 } }
^^@ \cs_new_protected:Npn \@@_save_combine_marks:nnnn #1#2#3#4
^^@   { \@@_save_UTF_text_command:Nxx \l_@@_combine_marks_prop { { "#4 } { "#3 } } { \token_to_str:N #2 } }
^^@ \cs_new_protected:Npn \@@_save_UTF_text_command:Nxx #1#2#3
^^@   { \use:e { \@@_save_UTF_text_command:Nnn \exp_not:N #1 {#2} {#3} } }
^^@ \cs_new_protected:Npn \@@_save_UTF_text_command:Nnn #1#2#3
^^@   {
^^@     \prop_get:NnNTF #1 {#2} \l_@@_command_tl
^^@       { \prop_put:Nno #1 {#2} { \l_@@_command_tl , #3 } }
^^@       { \prop_put:Nnn #1 {#2} {#3} }
^^@   }
^^@ \cs_new_protected:Npn \@@_make_table:w #1 ; #2 ; #3 \q_stop
^^@   {
^^@     \prop_get:NnNT \l_@@_command_prop {#1} \l_@@_command_tl
^^@       {
^^@         \iow_now:Ne \g_@@_command_iow
^^@           { \token_to_str:N \UnicodeTextSymbol { "#1 } { \l_@@_command_tl } {#2} }
^^@       }
^^@     \prop_get:NnNT \l_@@_combine_mark_prop {#1} \l_@@_command_tl
^^@       {
^^@         \iow_now:Ne \g_@@_combine_mark_iow
^^@           { \token_to_str:N \UnicodeCombineMark { "#1 } { \l_@@_command_tl } {#2} }
^^@       }
^^@   }
^^@ \cs_new_protected:Npn \@@_write_combine_marks:
^^@   {
^^@     \prop_map_inline:Nn \l_@@_combine_marks_prop
^^@       {
^^@         \iow_now:Ne \g_@@_combine_mark_iow
^^@           { \token_to_str:N \UnicodeCombineMarks ##1 {##2} }
^^@       }
^^@   }
^^@ \ExplSyntaxOff
^^@ \SaveUTFCommands
^^@ \ExplSyntaxOn
\@@_restore_catcode:
\int_new:N \g_@@_symbol_int
\int_new:N \g_@@_table_int
\DeclareDocumentCommand \UnicodeTextSymbol { m v m }
  {
    \int_gincr:N \g_@@_symbol_int
    \use_none:n #1 &
    \scan_stop:
    \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f:
      \cs_if_exist_use:N \xunsymNoto
      \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f:
        \cs_if_exist_use:N \xunsymSymbola
        \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f:
          \cs_if_exist_use:N \xunsymSegoe
          \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f:
            \cs_if_exist_use:N \xunsymDejaVu
          \fi:
        \fi:
      \fi:
    \fi:
    \tex_char:D #1 \exp_stop_f: &
    \tl_set:Nn \l_tmpa_clist {#2}
    \clist_use:Nn \l_tmpa_clist { \par }
    \strut
    & #3 \\
  }
\cs_set_eq:NN \UnicodeCombineMark \UnicodeTextSymbol
\DeclareDocumentCommand \UnicodeCombineMarks { m m v }
  {
    \int_gincr:N \g_@@_symbol_int
    \use_none:n #1 ~ \use_none:n #2 &
    \tex_char:D #1 \exp_stop_f: \tex_char:D #2 \exp_stop_f: &
    \tl_set:Nn \l_tmpa_clist {#3}
    \clist_use:Nn \l_tmpa_clist { \par }
    \strut \\
  }
\DeclareDocumentCommand \UTFTABLE { m m }
  {
    \int_gincr:N \g_@@_table_int
    \exp_args:Ne \@@_make_table:nnn { UTFTABLE - \int_use:N \g_@@_table_int } {#1} {#2}
  }
\cs_new_protected:Npn \@@_make_table:nnn #1#2#3
  {
    \section{#2~(\zref@extractdefault{#1}{default}{0})}
    \zref@refused{#1}
    \noindent
    \begin{longtable}[c]{>{\ttfamily\footnotesize}rc>{\ttfamily\footnotesize}p{55mm}>{\tiny}l}
      \toprule
      \multicolumn1c{\normalfont\normalsize USV} &
      \normalfont\normalsize Symbol &
      \normalfont\normalsize Macro(s) &
      \normalfont\normalsize Description \\
      \midrule \endhead
      \bottomrule \endfoot
      \int_gzero:N \g_@@_symbol_int
      \input{#3}
    \end{longtable}
    \group_begin:
      \zref@setcurrent{default}{\int_use:N\g_@@_symbol_int}
      \zref@wrapper@immediate{\zref@labelbyprops{#1}{default}}
    \group_end:
  }
\cs_generate_variant:Nn \@@_make_table:nnn { x }
\ExplSyntaxOff

\begin{document}

\UTFTABLE{Symbols}{xunicode-commands.tex}

\UTFTABLE{Combining Marks}{xunicode-combine-marks.tex}

\end{document}
%    \end{macrocode}
%
%    \begin{macrocode}
%</xunicode-symbols>
%    \end{macrocode}
%
% \end{implementation}
%
\endinput
