% \iffalse meta-comment
%
% File: ctex-engine.dtx
% -----------------------------------------------------------------------
%   Copyright (C) 2003--2026
%   CTEX.ORG and any individual authors listed elsewhere in this file.
% -----------------------------------------------------------------------
%   This work may be distributed and/or modified under the conditions   *
%   of the LaTeX Project Public License (LPPL), either version 1.3c of  *
%   this license or (at your option) any later version.                 *
%   The latest version of this license is in                            *
%                                                                       *
%       http://www.latex-project.org/lppl.txt                           *
%                                                                       *
%   and version 1.3c or later is part of all distributions of LaTeX     *
%   version 2008 or later.                                              *
%                                                                       *
%   This work has the LPPL maintenance status `maintained'.             *
% -----------------------------------------------------------------------
%   This work consists of the files ctex.dtx,                           *
%                                   ctex-kernel.dtx,                    *
%                                   ctex-engine.dtx,                    *
%                                   ctex-scheme.dtx,                    *
%                                   ctex-auxpkg.dtx,                    *
%                                   ctex-fontset.dtx,                   *
%                                   ctexpunct.spa,                      *
%                                   ctxdoc.cls,                         *
%                                   ctxdocstrip.tex,                    *
%                                   ctex-zhconv.lua,                    *
%                                   ctex-zhconv-index.lua,              *
%                                   ctex-zhconv-make.lua,               *
%                 the derived files ctex.ins,                           *
%                                   ctex.sty,                           *
%                                   ctexsize.sty,                       *
%                                   ctexheading.sty,                    *
%                                   ctexart.cls,                        *
%                                   ctexbook.cls,                       *
%                                   ctexrep.cls,                        *
%                                   ctexbeamer.cls,                     *
%                                   ctexcap.sty,                        *
%                                   ctexhook.sty,                       *
%                                   ctexpatch.sty,                      *
%                                   ctex-c5size.clo,                    *
%                                   ctex-cs4size.clo,                   *
%                                   ctex-heading-article.def,           *
%                                   ctex-heading-book.def,              *
%                                   ctex-heading-report.def,            *
%                                   ctex-heading-beamer.def,            *
%                                   ctexbackend.cfg,                    *
%                                   ctex-engine-pdftex.def,             *
%                                   ctex-engine-xetex.def,              *
%                                   ctex-engine-luatex.def,             *
%                                   ctex-engine-aptex.def,              *
%                                   ctex-engine-uptex.def,              *
%                                   ctex-name-utf8.cfg,                 *
%                                   ctex-name-gbk.cfg,                  *
%                                   ctex.cfg,                           *
%                                   ctexopts.cfg,                       *
%                                   ctex-scheme-plain.def,              *
%                                   ctex-scheme-plain-article.def,      *
%                                   ctex-scheme-plain-book.def,         *
%                                   ctex-scheme-plain-report.def,       *
%                                   ctex-scheme-plain-beamer.def,       *
%                                   ctex-scheme-chinese.def,            *
%                                   ctex-scheme-chinese-article.def,    *
%                                   ctex-scheme-chinese-book.def,       *
%                                   ctex-scheme-chinese-report.def,     *
%                                   ctex-scheme-chinese-beamer.def,     *
%                                   ctex-fontset-adobe.def,             *
%                                   ctex-fontset-fandol.def,            *
%                                   ctex-fontset-founder.def,           *
%                                   ctex-fontset-hanyi.def,             *
%                                   ctex-fontset-mac.def,               *
%                                   ctex-fontset-macnew.def,            *
%                                   ctex-fontset-macold.def,            *
%                                   ctex-fontset-ubuntu.def,            *
%                                   ctex-fontset-windows.def,           *
%                                   c19rm.fd,                           *
%                                   c19sf.fd,                           *
%                                   c19tt.fd,                           *
%                                   c70rm.fd,                           *
%                                   c70sf.fd,                           *
%                                   c70tt.fd,                           *
%                                   jy2zhrm.fd,                         *
%                                   jy2zhsf.fd,                         *
%                                   jy2zhtt.fd,                         *
%                                   jt2zhrm.fd,                         *
%                                   jt2zhsf.fd,                         *
%                                   jt2zhtt.fd,                         *
%                                   ctexspa.def,                        *
%                     translator-theorem-dictionary-ChineseUTF8.dict,   *
%                     translator-theorem-dictionary-ChineseGBK.dict,    *
%                                   ctex-spa-make.tex,                  *
%                                   ctex-spa-macro.tex,                 *
%                                   ctex-zhmap-adobe.tex,               *
%                                   ctex-zhmap-fandol.tex,              *
%                                   ctex-zhmap-founder.tex,             *
%                                   ctex-zhmap-hanyi.tex,               *
%                                   ctex-zhmap-mac.tex,                 *
%                                   ctex-zhmap-ubuntu.tex,              *
%                                   ctex-zhmap-windows.tex,             *
%           the documentation files ctex.pdf,                           *
%                               and README.md.                          *
% -----------------------------------------------------------------------
%                                                                       *
%   Any modification of this file should ensure that the copyright and  *
%   license information is placed in the derived files.                 *
%                                                                       *
% -----------------------------------------------------------------------
%
%<*!driver>
%<+!driver>\GetIdInfo $Id: ctex-engine.dtx 2.6.5 2026-08-11 16:50:29 +0800 79898321 Liam <liamhuang0205@gmail.com>$
%<pdftex>  {(pdf)LaTeX adapter (CTEX)}
%<pdftex>\ProvidesExplFile{ctex-engine-pdftex.def}
%<xetex>  {XeLaTeX adapter (CTEX)}
%<xetex>\ProvidesExplFile{ctex-engine-xetex.def}
%<luatex>  {LuaLaTeX adapter (CTEX)}
%<luatex>\ProvidesExplFile{ctex-engine-luatex.def}
%<aptex>  {Asian pTeX adapter (CTEX)}
%<aptex>\ProvidesExplFile{ctex-engine-aptex.def}
%<uptex>  {upTeX adapter (CTEX)}
%<uptex>\ProvidesExplFile{ctex-engine-uptex.def}
%<backend>  {Backend configuration file (CTEX)}
%<backend>\ProvidesFile{ctexbackend.cfg}%
%<!backend>  {\ExplFileDate}{\ExplFileVersion}{\ExplFileDescription}
%<backend>  [\ExplFileDate\space v\ExplFileVersion\space \ExplFileDescription]%
%</!driver>
%
% \fi
%
% \begin{implementation}
%
% \paragraph*{\fbox{\file{\jobname-engine.dtx}}}
%
%    \begin{macrocode}
%<@@=ctex>
%    \end{macrocode}
%
% \subsection{特定引擎支持与设置}
%
% \changes{v2.5}{2020/04/22}{给 \LaTeX 和 \upLaTeX 下的文档类指定驱动为 \dvipdfmx。}
%
% \subsubsection{\texttt{ctexbackend.cfg}}
%
% 对于 \XeLaTeX{}/\pdfLaTeX{}/\LuaLaTeX{} 等默认直接输出 PDF 的编译方式，用户
% 无需为涉及驱动的宏包指定驱动选项。对于 \LaTeX 和 \upLaTeX{} 等默认不直接输出 PDF 的编译方式，
% 用户则需要指定驱动选项。
%
% 由于历史遗留问题，在使用 \LaTeX{} 或 \upLaTeX{} 等编译时，
% 大多数涉及驱动的宏包选定的默认输出驱动都是 Dvips。
% 考虑当前实际使用频率，以及考虑到 \CTeX{} 宏集对中文支持的默认方式，
% 我们在用户使用 \CTeX{} 系列文档类时，将默认的输出驱动改为 \dvipdfmx。
%
% 具体来说，如果 |dvips|，|dvipdfmx|，|dvisvgm| 等驱动没有在文档类的全局选项中被明确指定，
% 我们就在 \tn{@classoptionslist} 开头加入 |dvipdfmx|。
%
% 本段代码只在 \cls{ctexart} 等文档类开头载入，不在 \pkg{ctex} 中使用。
% 并且需要放在 \pkg{expl3} 之前载入，保证它载入正确的 backend 文件。
%    \begin{macrocode}
%<*backend>
\begingroup
\expandafter\ifx\csname Umathchardef\endcsname\relax
\else\expandafter\endgroup\expandafter\endinput\fi
  \ifodd
    \expandafter\ifx\csname      pdfoutput\endcsname\relax
    \expandafter\ifx\csname enablecjktoken\endcsname\relax 0\else 1\fi
                                   \else\ifnum\pdfoutput>0 0\else 1\fi\fi\space
    \def\x#1{%
      \if\relax\detokenize{#1}\relax
        \gdef\@classoptionslist{dvipdfmx}%
      \else
        \let\CTEX@add\@ne
        \@tfor\x:={dvips}{dvipdfmx}{dvisvgm}\do{%
          \expandafter\in@\expandafter{\expandafter,\x,}{,#1,}%
          \ifin@ \let\CTEX@add\tw@ \@break@tfor \fi}%
        \ifodd\CTEX@add \gdef\@classoptionslist{dvipdfmx,#1}\fi
      \fi}
    \expandafter\x\expandafter{\@classoptionslist}
  \fi
\endgroup
%</backend>
%    \end{macrocode}
%
% \subsubsection{\pkg{ctex-engine-pdftex.def}}
%
% \begin{macro}[int]{\ctex_set_zhmap:n}
% 设置 \upTeX{} 字体映射，同时作用于 \tn{AtBeginDvi} 与
% \tn{AtBeginShipoutFirst}。该宏对 \pdfTeX{} 和 \upTeX{} 均有用。
% \tn{AtBeginDvi} 直接将 \tn{special} 保存到盒子中，
% \tn{AtBeginShipoutFirst} 是保存到宏中，并且不展开参数。
%
% 可以使用 \LaTeX\ 2020/10/01 的钩子机制来统一设置。
%     \begin{macrocode}
%<*pdftex|uptex|aptex>
\cs_new_protected:Npn \ctex_set_zhmap:n
  { \tl_gput_right:Ne \g_@@_zhmap_tl }
\cs_new_protected:Npn \ctex_use_zhmap:
  { \tl_use:N \g_@@_zhmap_tl }
\cs_if_exist:NTF \ctex_gadd_ltxhook:nn
  {
    \cs_new_protected:Npn \ctex_at_shipout_first:n
      { \ctex_gadd_ltxhook:nn { shipout/firstpage } }
    \ctex_at_shipout_first:n { \ctex_use_zhmap: }
  }
  {
    \cs_new_protected:Npn \ctex_add_dvi_zhmap:
      { \AtBeginDvi { \ctex_use_zhmap: } }
    \ctex_after_end_preamble:n { \ctex_add_dvi_zhmap: }
    \ctex_at_end_package:nn { atbegshi }
      {
        \cs_new_protected:Npn \ctex_at_shipout_first:n
          { \AtBeginShipoutFirst }
        \ctex_at_shipout_first:n { \ctex_use_zhmap: }
        \cs_gset_eq:NN \ctex_add_dvi_zhmap: \prg_do_nothing:
      }
  }
\tl_new:N \g_@@_zhmap_tl
\@onlypreamble \ctex_set_zhmap:n
%</pdftex|uptex|aptex>
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%<*pdftex>
%    \end{macrocode}
%
% \changes{v2.1}{2015/05/18}{给 \pdfLaTeX{} 下的非 UTF-8 编码 CJK 字体族加上 CMap。}
%
% \begin{variable}{\c_@@_cmap_encoding_seq}
% 需要加上 CMap 的 CJK 字体编码。
%    \begin{macrocode}
\seq_const_from_clist:Nn \c_@@_cmap_encoding_seq
  { C19 , C10 , C00 , C09 , C40 , C60 }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[int]{\ctex_family_cmap:nn}
% 在 \tn{DeclareFontFamily} 的 \meta{loading-settings} 中给 CJK 字体族加上 CMap。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_family_cmap:nn #1#2
  {
    \cs_if_free:cF { #1 + #2 }
      {
        \seq_if_in:NnT \c_@@_cmap_encoding_seq {#1}
          { \tl_gput_right:cn { #1 + #2 } { \ctex_add_cmap:n {#1} } }
      }
  }
\cs_generate_variant:Nn \ctex_family_cmap:nn { ee }
\cs_new_eq:NN \CTEX@Family@CMap \ctex_family_cmap:ee
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.5.7}{2021/06/06}{更好地兼容 \pkg{cmap} 包。}
%
% \begin{macro}[int]{\ctex_add_cmap:n}
% \changes{v2.5.7}{2021/06/06}{确保 cmap 文件存在。}
% \changes{v2.5.10}{2022/07/11}{使用封装好的函数。}
% 给 |#1| 编码的 CJK 字体加上 CMap。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_add_cmap:n #1
  {
    \cs_if_free:NF \CJK@plane
      { \exp_args:Ne \@@_add_cmap_auxi:n { #1 \CJK@plane } }
  }
\cs_new_protected:Npn \@@_add_cmap_auxi:n #1
  { \exp_args:Nc \@@_add_cmap_auxii:Nn { @@_add_cmap_ #1 : } {#1} }
\cs_new_protected:Npn \@@_add_cmap_auxii:Nn #1#2
  {
    \cs_if_exist:NF #1 { \@@_save_cmap:Nn #1 {#2} }
    #1
  }
\cs_new_protected:Npn \@@_save_cmap:Nn #1#2
  {
    \exp_args:Ne \file_get_full_name:nNTF
      { \str_lowercase:n {#2} .cmap } \l_@@_cmap_file_tl
      {
        \pdf_object_unnamed_write:ne
          { fstream }
          { { } { \l_@@_cmap_file_tl } }
        \cs_new_protected:Npx #1
          {
            \pdfnobuiltintounicode \tex_font:D
            \tex_pdffontattr:D \tex_font:D
              { /ToUnicode ~ \pdf_object_ref_last: }
          }
      }
      { \cs_new_eq:NN #1 \prg_do_nothing: }
  }
\tl_new:N \l_@@_cmap_file_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\DeclareFontFamily}
% 只在 \pdfLaTeX{} 下加 CMap。如 \pkg{cmap} 宏包被引入，则不重复设置。
%    \begin{macrocode}
\sys_if_output_pdf:T
  {
    \exp_args:Nno \use:n
      { \cs_gset_protected:Npn \DeclareFontFamily #1#2#3 }
      {
        \DeclareFontFamily {#1} {#2} {#3}
        \CTEX@Family@CMap  {#1} {#2}
      }
    \ctex_at_end_package:nn { cmap }
      {
        \cs_gset_eq:NN \ctex_add_cmap:n \use_none:n
        \cs_gset_eq:NN \CTEX@Family@CMap \use_none:nn
      }
  }
%    \end{macrocode}
% \end{macro}
%
% 首先检查选项，决定是否载入 \pkg{zhmCJK} 宏包。
%    \begin{macrocode}
\bool_if:NTF \g_@@_zhmCJK_bool
  {
    \exp_args:Ne \PassOptionsToPackage
      { encoding = \g_@@_encoding_tl }
      { zhmCJK }
    \RequirePackage { zhmCJK }
  }
%    \end{macrocode}
% 不载入 \pkg{zhmCJK} 宏包时直接调用 \pkg{CJK} 及相关宏包。
%    \begin{macrocode}
  {
    \str_if_eq:onTF { \g_@@_encoding_tl } { GBK }
      { \RequirePackage { CJK } }
      { \RequirePackage { CJKutf8 } }
    \RequirePackage { CJKpunct , CJKspace }
%    \end{macrocode}
%
% \changes{v2.4.4}{2016/09/09}{解决 \opt{zhmap} 文件的 \tn{catcode} 问题。}
%
% \begin{macro}[int]{\ctex_load_zhmap:nnnn}
% 载入 \pkg{zhmetrics} 的字体映射文件，同时设置 \tn{CJKrmdefault} 等。
%    \begin{macrocode}
    \cs_new_protected:Npn \ctex_load_zhmap:nnnn #1#2#3#4
      {
        \tl_set:Nn \CJKrmdefault {#1}
        \tl_set:Nn \CJKsfdefault {#2}
        \tl_set:Nn \CJKttdefault {#3}
        \ctex_set_zhmap:n { \ctex_file_input:n { ctex-zhmap- #4 .tex } }
      }
    \@onlypreamble \ctex_load_zhmap:nnnn
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.5.10}{2022/06/10}{取消 \LaTeX\ 2022-06-01 对书名号的定义。}
%
% \begin{macro}{\ctex_undeclare_unicode_character:n}
% \changes{v2.6.0}{2024/04/20}{替换废弃命令 \cs{char_to_utfviii_bytes:n}。}
% \LaTeX\ 2022-06-01 将通常用作中文书名号的 U+3008 和 U+3009 分别定义为
% \tn{textlangle} 和 \tn{textrangle}，这个定义会被 \pkg{CJKutf8} 包优先使用。
% 我们总是使用中文书名号，需要取消 \LaTeX\ 的定义，作用是局部的，不使用 \cs{cs_undefine:N}。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_undeclare_unicode_character:n #1
  { \cs_set_eq:cN { u8 : \codepoint_str_generate:n {#1} } \tex_undefined:D }
\CJKaddEncHook { UTF8 }
  {
    \ctex_undeclare_unicode_character:n { "3008 }
    \ctex_undeclare_unicode_character:n { "3009 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_CJK_input:n,\CJK@input}
% 载入 \pkg{CJK} 包的 \file{.enc} 和 \file{.bdg} 等文件时，需要设置 \tn{endlinechar} 为 $-1$。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_CJK_input:n #1
  {
    \ctex_push_file:
      \int_set:Nn \tex_endlinechar:D { -1 }
      \file_input:n {#1}
    \ctex_pop_file:
  }
\cs_set_eq:NN \CJK@input \ctex_CJK_input:n
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_plane_to_utfxvibe:Nn,\CJK@surr}
% \changes{v2.0}{2014/04/08}{解决与 \tn{nouppercase} 的冲突。}
% \pkg{fancyhdr} 宏包的 \tn{nouppercase} 会将 \tn{uppercase} 定义为 \tn{relax}，而
% \tn{CJK@surr} 需要用它将 \tn{CJK@plane} 转化成大写字母，这就造成了冲突^^A
% \footnote{\url{https://github.com/CTeX-org/ctex-kit/issues/146}}。
% 我们在这里给出 \tn{CJK@surr} 的一个不依赖 \tn{uppercase} 的实现。
%    \begin{macrocode}
\cs_if_free:NF \CJK@surr
  {
    \cs_new_protected:Npn \ctex_plane_to_utfxvibe:Nn #1#2
      {
        \int_set:Nn \l_@@_tmp_int
          { \exp_args:Ne \int_from_hex:n {#2} }
        \int_compare:nNnTF \l_@@_tmp_int < { 256 }
          { \tl_gset:Ne #1 { \int_to_Hex:n { \l_@@_tmp_int } } }
          {
            \int_sub:Nn \l_@@_tmp_int { 256 }
            \tl_gset:Ne #1
              {
                \int_to_Hex:n
                  { \int_div_truncate:nn { \l_@@_tmp_int } { 4 } + "D800 }
                \int_to_Hex:n
                  { \int_mod:nn { \l_@@_tmp_int } { 4 } + "DC }
              }
          }
      }
    \cs_set_eq:NN \CJK@surr \ctex_plane_to_utfxvibe:Nn
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\CJK@addcmap}
% \changes{v2.5.7}{2021/06/06}{应用 \tn{pdfnobuiltintounicode}。}
% \LaTeX \ 2021-06-01 默认载入 \file{glyphtounicode.tex}，我们对 CJK 字体禁用这一内建设置。
%    \begin{macrocode}
\cs_if_free:NF \CJK@addcmap
  {
    \ctex_patch_cmd:Nnn \CJK@addcmap
      { \pdffontattr \font@name }
      {
        \pdfnobuiltintounicode \font@name
        \pdffontattr \font@name
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \pkg{CJKpunct} 宏包会在 \tn{AtBeginDocument} 的里设置标点格式为 \opt{quanjiao}。
%    \begin{macrocode}
\AtBeginDocument
  {
    \use:e
      {
        \str_if_eq:nnF { \l_@@_punct_tl } { quanjiao }
          { \exp_not:N \punctstyle { \l_@@_punct_tl } }
      }
  }
%    \end{macrocode}
%
% 在导言区末尾更新 \tn{CJKfamilydefault}，注意要在 \tn{CJK@envStart} 之前使用。
%    \begin{macrocode}
\ctex_at_end_preamble:n { \ctex_update_default_family: }
%    \end{macrocode}
%
% 从 \LaTeX\ 2018-04-01 开始，传统引擎的默认编码被设置成 UTF-8，
% 汉字的首字节已经被设置成活动字符，我们无需再使用 \tn{CJK@makeActive} 设置。
% \tn{CJK@envStart} 的定义是
% \begin{verbatim}
%   \def\CJK@envStart#1#2#3{
%     \CJK@upperReset
%     \ifCJK@lowercase@
%       \CJK@lowerReset
%     \fi%
%     \CJK@makeActive%
%     \CJK@global\let\CJK@selectFamily \CJK@selFam
%     \CJK@global\let\CJK@selectEnc \CJK@selEnc%
%     \def\CJK@@@enc{#2}
%     \ifx\CJK@@@enc \@empty
%       \PackageInfo{CJK}{
%         no encoding parameter given,\MessageBreak
%         waiting for \protect\CJKenc\space commands}
%     \else
%       \CJKenc{#2}
%     \fi
%     \CJKfontenc{#2}{#1}
%     \CJKfamily{#3}
%     \def\CJK@series{\f@series}
%     \def\CJK@shape{\f@shape}%
%     \csname CJKhook\endcsname}
% \end{verbatim}
% \tn{CJK@upperReset} 可能会有一定风险，因此我们直到导言区末尾才使用
% \tn{CJK@envStart}。这样可以避免将 \env{CJK} 环境内置入 \env{document} 环境的
% 最里层，最后也就不需要 \tn{clearpage}。\pkg{zhmCJK} 已经提供类似功能。
% 注意先使用 \cs{ctex_update_default_family:} 更新 \tn{CJKfamilydefault}。
%    \begin{macrocode}
\bool_if:NF \g_@@_zhmCJK_bool
  {
    \str_if_eq:onTF { \g_@@_encoding_tl } { UTF8 }
      { \CJK@loadBinding { UTF8 } }
      { \CJK@loadBinding { standard } }
    \exp_args:Ne \ctex_at_end_preamble:n
      {
        \exp_not:N \CJK@envStart
          { } { \g_@@_encoding_tl } { \exp_not:N \CJKfamilydefault }
        \exp_not:N \CJKtilde
      }
  }
%    \end{macrocode}
%
% \begin{macro}[int]{\ctex_auto_ignorespaces:}
% 保存 \tn{CJK@@ignorespaces} 的定义，方便使用。
%    \begin{macrocode}
\cs_new_eq:NN \ctex_auto_ignorespaces: \CJK@@@@ignorespaces
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.5.10}{2022/07/08}{解决 \pkg{CJK} 包与 \cs{text_uppercase:n} 等转化函数的冲突。}
% \changes{v2.6.0}{2024/04/20}{修复参数签名，\cs{@@_char_auxi:NNNN} 原误为 \texttt{NNN}。}
% \changes{v2.6.0}{2026/05/06}{4 字节 UTF-8 字符也优先使用
%   \cs{DeclareUnicodeCharacter} 的定义（\#815）。}
%
% \begin{macro}{\CJK@X,\CJK@XX,\CJK@XXX,\CJK@XXXX}
% 最新 \LaTeX\ 中的 \tn{MakeUppercase} 等大小写转化命令被定义为 \LaTeXiii\ 中对应的
% \cs{text_uppercase:n} 等函数。
% 它会先将参数用 \cs{text_expand:n} 逐步展开，这与 \pkg{CJK} 包的定义不兼容。
% 可以有多种方法解决这个冲突，最简单直接的方法是将首字节的活动字符用 \tn{protected} 来定义，
% 但可能有一定的兼容性问题。保险起见，我们采用将 \tn{CJK@X} 等内部宏转化为 \tn{protected}
% 宏的方式，并将参数提前由字符转化为数字，避免在移动参数中被进一步展开。
%    \begin{macrocode}
\cs_new:Npn \@@_char:N #1
  { \exp_args:Ne \CTEX@char@n { \int_value:w `#1 } }
\cs_new:Npn \@@_char:NN #1#2
  {
    \token_if_eq_meaning:NNTF #2 \protect
      { \@@_char_aux:NN #1 }
      { \@@_char_aux:NN #1#2 }
  }
\cs_new:Npn \@@_char_aux:NN #1#2
  {
    \exp_last_unbraced:Ne \CTEX@char@nn
      {
        { \int_value:w `#1 }
        { \int_value:w `#2 }
      }
  }
\cs_new:Npn \@@_char:NNN #1#2#3
  {
    \token_if_eq_meaning:NNTF #2 \protect
      { \@@_char_auxi:NNNN #1#3 }
      { \@@_char_auxii:NNN #1#2#3 }
  }
\cs_new:Npn \@@_char_auxi:NNNN #1#2#3#4
  { \@@_char_auxii:NNN #1#2#4 }
\cs_new:Npn \@@_char_auxii:NNN #1#2#3
  {
    \exp_last_unbraced:Ne \CTEX@char@nnn
      {
        { \int_value:w `#1 }
        { \int_value:w `#2 }
        { \int_value:w `#3 }
      }
  }
\cs_new:Npn \@@_char:NNNN #1#2#3#4
  {
    \token_if_eq_meaning:NNTF #2 \protect
      { \@@_char_auxi:NNNNN #1#3 }
      { \@@_char_auxii:NNNN #1#2#3#4 }
  }
\cs_new:Npn \@@_char_auxi:NNNNN #1#2#3#4#5
  { \@@_char_auxii:NNNN #1#2#3#5 }
\cs_new:Npn \@@_char_auxii:NNNN #1#2#3#4
  {
    \exp_last_unbraced:Ne \CTEX@char@nnnn
      {
        { \int_value:w `#1 }
        { \int_value:w `#2 }
        { \int_value:w `#3 }
        { \int_value:w `#4 }
      }
  }
\cs_gset_eq:NN \CJK@X    \@@_char:N
\cs_gset_eq:NN \CJK@XX   \@@_char:NN
\cs_gset_eq:NN \CJK@XXX  \@@_char:NNN
\cs_gset_eq:NN \CJK@XXXX \@@_char:NNNN
%    \end{macrocode}
% \pkg{CJKutf8} 包优先使用 |\u8:xxx|/|\u8:xxxx| 的 \LaTeX\ 定义。
%    \begin{macrocode}
\str_if_eq:onF { \g_@@_encoding_tl } { GBK }
  {
    \exp_args:Nne \use:n
      { \cs_gset:Npn \@@_char_aux:NN #1#2 }
      {
        \exp_not:N \cs_if_exist_use:cF
          { u8: \exp_not:N \tl_to_str:n { #1#2 } }
          { \exp_not:o { \@@_char_aux:NN {#1} {#2} } }
      }
    \exp_args:Nne \use:n
      { \cs_gset:Npn \@@_char_auxii:NNN #1#2#3 }
      {
        \exp_not:N \cs_if_exist_use:cF
          { u8: \exp_not:N \tl_to_str:n { #1#2#3 } }
          { \exp_not:o { \@@_char_auxii:NNN {#1} {#2} {#3} } }
      }
    \exp_args:Nne \use:n
      { \cs_gset:Npn \@@_char_auxii:NNNN #1#2#3#4 }
      {
        \exp_not:N \cs_if_exist_use:cF
          { u8: \exp_not:N \tl_to_str:n { #1#2#3#4 } }
          { \exp_not:o { \@@_char_auxii:NNNN {#1} {#2} {#3} {#4} } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\mule@arg}
% 处理文档被 |extconv| 等工具预处理过的情况，只考虑最简单的情形。
%    \begin{macrocode}
\group_begin:
\char_set_catcode_active:n { "7F }
\cs_new:Npn \@@_char_preproc:w #1 ^^7f #2 ^^7f
  { \exp_args:No \CTEX@char@nn { \int_value:w `#1 } {#2} }
\cs_gset_eq:NN \mule@arg \@@_char_preproc:w
\group_end:
\exp_args:Ne \ctex_at_end:n
  {
    \char_set_catcode:nn
      { \int_value:w "7F }
      { \char_value_catcode:n { "7F } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@char@n,\CTEX@char@nn,\CTEX@char@nnn,\CTEX@char@nnnn}
% 额外加入 \cs{mode_leave_vertical:} 是为了解决某些历史遗留问题。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@char@n #1
  {
    \mode_leave_vertical:
    \use:c { CJK@ #1 }
    \CJK@ignorespaces
  }
\cs_new_protected:Npn \CTEX@char@nn #1#2
  {
    \mode_leave_vertical:
    \use:c { CJK@ #1 } {#2}
    \CJK@ignorespaces
  }
\cs_new_protected:Npn \CTEX@char@nnn  #1#2#3
  {
    \mode_leave_vertical:
    \use:c { CJK@ #1 } {#2} {#3}
    \CJK@ignorespaces
  }
\cs_new_protected:Npn \CTEX@char@nnnn #1#2#3#4
  {
    \mode_leave_vertical:
    \use:c { CJK@ #1 } {#2} {#3} {#4}
    \CJK@ignorespaces
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@char@nn@n,\CTEX@char@nnn@n,\CTEX@char@nnnn@n}
% 以下给 \tn{CTEX@char@nn} 等定义在 \cs{text_expand:n} 中的等价形式，
% 将它们的参数用只有一个参数的 \tn{CTEX@char@nn@n} 等包装起来，
% 并将 \tn{CTEX@char@nn@n} 等放入排除列表中，避免可能的大小写转化。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@char@nn@n   #1 { \CTEX@char@nn   #1 }
\cs_new_protected:Npn \CTEX@char@nnn@n  #1 { \CTEX@char@nnn  #1 }
\cs_new_protected:Npn \CTEX@char@nnnn@n #1 { \CTEX@char@nnnn #1 }
\cs_new:Npn \@@_char_wrap:nn #1#2
  { \CTEX@char@nn@n   { {#1} {#2} } }
\cs_new:Npn \@@_char_wrap:nnn #1#2#3
  { \CTEX@char@nnn@n  { {#1} {#2} {#3} } }
\cs_new:Npn \@@_char_wrap:nnnn #1#2#3#4
  { \CTEX@char@nnnn@n { {#1} {#2} {#3} {#4} } }
\tl_put_right:Nn \l_text_case_exclude_arg_tl
  {
    \CTEX@char@n
    \CTEX@char@nn@n
    \CTEX@char@nnn@n
    \CTEX@char@nnnn@n
  }
\text_declare_expand_equivalent:Nn \CTEX@char@nn   { \@@_char_wrap:nn }
\text_declare_expand_equivalent:Nn \CTEX@char@nnn  { \@@_char_wrap:nnn }
\text_declare_expand_equivalent:Nn \CTEX@char@nnnn { \@@_char_wrap:nnnn }
%    \end{macrocode}
% 以下给 \tn{CTEX@char@nn@n} 等定义在 \cs{text_purify:n} 中的等价形式，
% 将参数中的数字转化回字符。
%    \begin{macrocode}
\cs_new:Npn \@@_char_raw:n #1
  { \char_generate:nn {#1} { 12 } }
\cs_new:Npn \@@_char_raw:nn #1#2
  {
    \char_generate:nn {#1} { 12 }
    \char_generate:nn {#2} { 12 }
  }
\cs_new:Npn \@@_char_raw:nnn #1#2#3
  {
    \char_generate:nn {#1} { 12 }
    \char_generate:nn {#2} { 12 }
    \char_generate:nn {#3} { 12 }
  }
\cs_new:Npn \@@_char_raw:nnnn #1#2#3#4
  {
    \char_generate:nn {#1} { 12 }
    \char_generate:nn {#2} { 12 }
    \char_generate:nn {#3} { 12 }
    \char_generate:nn {#4} { 12 }
  }
\cs_new:Npn \@@_char_raw_nn:n   #1 { \use:e { \@@_char_raw:nn   #1 } }
\cs_new:Npn \@@_char_raw_nnn:n  #1 { \use:e { \@@_char_raw:nnn  #1 } }
\cs_new:Npn \@@_char_raw_nnnn:n #1 { \use:e { \@@_char_raw:nnnn #1 } }
\text_declare_purify_equivalent:Nn \CTEX@char@n      { \@@_char_raw:n }
\text_declare_purify_equivalent:Nn \CTEX@char@nn@n   { \@@_char_raw_nn:n }
\text_declare_purify_equivalent:Nn \CTEX@char@nnn@n  { \@@_char_raw_nnn:n }
\text_declare_purify_equivalent:Nn \CTEX@char@nnnn@n { \@@_char_raw_nnnn:n }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ignorespaces_case:N,\ctex_set_ignorespaces:}
% 设置忽略空格的方式。根据 \opt{space} 选项的值重定义 \tn{CJK@ignorespaces}，
% 并保存起来供 \tn{CJKhook} 备用。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ignorespaces_case:N #1
  {
    \cs_set_protected:Npn \ctex_set_ignorespaces:
      { \cs_set_eq:NN \CJK@ignorespaces #1 }
    \ctex_set_ignorespaces:
  }
\cs_new_protected:Npn \ctex_set_ignorespaces:
  { \cs_set_eq:NN \CJK@ignorespaces \ctex_auto_ignorespaces: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\CJKhook}
% \changes{v2.5.5}{2020/10/06}{不再通过旧的钩子命令来定义。}
% \env{CJK} 和 \env{CJK*} 环境都会重新定义 \tn{CJK@ignorespaces}。我们在 \pkg{CJK}
% 宏包提供的 \tn{CJKhook} 里重新设置它，让这两个环境忽略空格的方式都受 \opt{space}
% 选项的控制。这对 \pkg{zhmCJK} 是必要的。
%    \begin{macrocode}
\tl_if_exist:NF \CJKhook { \tl_new:N \CJKhook }
\tl_gput_right:Nn \CJKhook { \ctex_set_ignorespaces: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_punct_set:n}
% 设置 CJK 族对应到实际的字体。|#1| 是 \opt{fontset} 的名字。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_punct_set:n #1
  {
    \clist_map_inline:Nn \c_@@_punct_family_clist
      {
        \cs_if_free:cF { c_@@_ #1 ##1 _punct_spaces_tl }
          {
            \cs_set_eq:cc
              { CJKpunct@ ##1 @spaces }
              { c_@@_ #1 ##1 _punct_spaces_tl }
          }
      }
  }
\clist_const:Nn \c_@@_punct_family_clist
  {
    zhsong , zhhei , zhfs , zhkai , zhli , zhyou ,
    zhsongb , zhheil , zhheib , zhyoub ,
    zhyahei , zhyaheib , zhpf , zhpfb
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_punct_map_family:nn}
% CJK 族 |#1| 使用族 |#2| 的边界信息。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_punct_map_family:nn #1#2
  {
    \cs_if_free:cF { CJKpunct@ #2 @spaces }
      { \cs_set_eq:cc { CJKpunct@ #1 @spaces } { CJKpunct@ #2 @spaces } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_punct_map_bfseries:nn}
% CJK 族 |#1| 的 \tn{bfseries} 使用族 |#2| 的边界信息。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_punct_map_bfseries:nn #1#2
  {
    \clist_map_inline:nn {#1}
      {
        \ctex_punct_map_series:nnn { ##1 } { b } {#2}
        \ctex_punct_map_series:nnn { ##1 } { bx } {#2}
      }
  }
\cs_new_protected:Npn \ctex_punct_map_series:nnn #1#2#3
  {
    \CJKpunctmapfamily { C19 } {#1} {#2} { m }  {#3}
    \CJKpunctmapfamily { C19 } {#1} {#2} { it } {#3}
    \CJKpunctmapfamily { C19 } {#1} {#2} { sl } {#3}
    \CJKpunctmapfamily { C70 } {#1} {#2} { m }  {#3}
    \CJKpunctmapfamily { C70 } {#1} {#2} { it } {#3}
    \CJKpunctmapfamily { C70 } {#1} {#2} { sl } {#3}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_punct_map_itshape:nn}
% CJK 族 |#1| 的 \tn{itshape} 使用族 |#2| 的边界信息。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_punct_map_itshape:nn #1#2
  {
    \CJKpunctmapfamily { C19 } {#1} { m }  { it } {#2}
    \CJKpunctmapfamily { C19 } {#1} { b }  { it } {#2}
    \CJKpunctmapfamily { C19 } {#1} { bx } { it } {#2}
    \CJKpunctmapfamily { C70 } {#1} { m }  { it } {#2}
    \CJKpunctmapfamily { C70 } {#1} { b }  { it } {#2}
    \CJKpunctmapfamily { C70 } {#1} { bx } { it } {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_punct_space:nn,\ctexspadef}
% 定义标点的边界信息。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_punct_space:nn #1#2
  { \tl_const:cn { c_@@_ #1 _punct_spaces_tl } {#2} }
\cs_new_eq:NN \ctexspadef \ctex_punct_space:nn
%    \end{macrocode}
% \end{macro}
%
% 载入边界信息文件。
%     \begin{macrocode}
\ctex_file_input:n { ctexspa.def }
%    \end{macrocode}
%
%    \begin{macrocode}
%</pdftex>
%    \end{macrocode}
%
% \subsubsection{\pkg{ctex-engine-xetex.def}}
%
%    \begin{macrocode}
%<*xetex>
%    \end{macrocode}
%
% \changes{v2.4.4}{2016/09/12}{不再默认设置 \pkg{xeCJK} 的伪粗体。}
%    \begin{macrocode}
\RequirePackage { xeCJK }
\exp_args:Ne \xeCJKsetup
  {
    LoadFandol   = false ,
    PunctStyle   = \l_@@_punct_tl
  }
%    \end{macrocode}
%
% 最新版本的 \pkg{fontspec} 默认对 \tn{rmfamily} 和 \tn{sffamily} 设置
% |Ligatures=TeX|，对 \tn{ttfamily} 设置 |WordSpace={1,0,0}| 和
% |PunctuationSpace=WordSpace|。
%    \begin{macrocode}
\@ifpackagelater { fontspec } { 2014/05/25 } { }
  { \msg_error:nnn { ctex } { package-too-old } { fontspec } }
%    \end{macrocode}
%
%    \begin{macrocode}
%</xetex>
%    \end{macrocode}
%
% \subsubsection{\pkg{ctex-engine-luatex.def}}
%
%    \begin{macrocode}
%<*luatex>
%    \end{macrocode}
%
% \changes{v2.0}{2014/03/08}{通过 \pkg{LuaTeX-ja} 宏包支持 \LuaLaTeX。}
% \changes{v2.3}{2015/09/25}{更新 \pkg{LuaTeX-ja} 支持（20150922.0）。}
% \changes{v2.5}{2020/04/18}{更新 \pkg{LuaTeX-ja} 支持（20200412.0）。}
% \changes{v2.5.4}{2020/08/16}{更新 \pkg{LuaTeX-ja} 支持（20200808.0）。}
% \changes{v2.6.3}{2026/07/13}{禁止在 LuaTeX 后端载入不兼容的
%   \pkg{CJKfntef}，并推荐使用 \pkg{lua-ul}（\#381）。}
%
% \pkg{LuaTeX-ja} 为了兼容 p\LaTeX 的使用习惯，对 \LaTeXe 的 \pkg{NFSS} 作了不少
% 修改和扩充，这对于简体中文用户来说不是必要的。我们在这里禁用它。
%    \begin{macrocode}
\msg_new:nnn { ctex } { luatexja-loaded }
  {
    Package~`luatexja'~cannot~be~loaded~before~`ctex'.\\
    Loading~file~`#1'~will~abort!
  }
\@ifpackageloaded { luatexja }
  { \msg_critical:nne { ctex } { luatexja-loaded } { \g_file_curr_name_str } }
  {
    \ctex_at_begin_package:nn { luatexja }
      { \msg_redirect_name:nnn { ctexhook } { disable-package } { info } }
    \ctex_at_end_package:nn { luatexja }
      { \msg_redirect_name:nnn { ctexhook } { disable-package } { } }
    \ctex_disable_package:n { ltj-latex }
  }
%    \end{macrocode}
%
% \pkg{CJKfntef} 会载入传统 \pkg{CJK} 宏包，覆盖 \pkg{LuaTeX-ja} 与
% \CTeX{} 共用的字体族接口，使 \tn{setCJKmainfont} 的结果依赖宏包加载顺序。
% 两套字体协议并不兼容，因此在 LuaTeX 后端直接禁止载入，并引导用户使用基于
% LuaTeX 节点回调的 \pkg{lua-ul}。若 \pkg{CJKfntef} 已经载入，字体状态可能已经
% 被污染，只能中止加载。
%    \begin{macrocode}
\msg_new:nnn { ctex } { cjkfntef-luatex }
  {
    Package~`#1'~is~incompatible~with~the~ctex~LuaTeX~backend.\\
    Use~the~`lua-ul'~package~instead;~try~
    \token_to_str:N \underLine,~
    \token_to_str:N \strikeThrough,~or~
    \token_to_str:N \highLight.
  }
\@ifpackageloaded { CJKfntef }
  { \msg_critical:nnn { ctex } { cjkfntef-luatex } { CJKfntef } }
  {
    % 复用通用禁载机制，但改用包含迁移路径的专用消息。
    \@@_disable_package_aux:nnnn
      { ctex } { cjkfntef-luatex } { CJKfntef } { }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage { luatexja }
\@ifpackagelater { luatexja } { 2020/04/12 } { }
  { \msg_error:nnn { ctex } { package-too-old } { luatexja } }
%    \end{macrocode}
%
%    \begin{macrocode}
\RequirePackage { fontspec }
\@ifpackagelater { fontspec } { 2020/02/21 } { }
  { \msg_error:nnn { ctex } { package-too-old } { fontspec } }
%    \end{macrocode}
%
% 引擎文件是通过 \cs{ctex_file_input:n} 载入的，其中的 \tn{catcodetable} 机制会完整恢复
% 文件载入之前的 \tn{catcode} 状态，在引擎文件中的 \tn{catcode} 设置都无效。
% 因此，对 \tn{ltjlineendcomment} 的设置要放到文件之外进行。
%    \begin{macrocode}
\ctex_at_end:n { \char_set_catcode_comment:n { \ltjlineendcomment } }
%    \end{macrocode}
%
% \paragraph{\pkg{LuaTeX-ja} 的默认设置}
%
%    \begin{macrocode}
\ExplSyntaxOff
%    \end{macrocode}
%
% 以下设置抄录自 \file{lltjdefs.sty}，略有改动。
%
% \changes{v2.4.11}{2017/08/17}{不把 Enclosed Alphanumerics 设置为 JAchar。}
% U+2460--U+24FF (Enclosed Alphanumerics) 原属于字符范围 6，是 JAchar，
% 我们把它们归入字符范围 3，改成  ALchar。
%    \begin{macrocode}
\ltjdefcharrange{1}{"80-"36F, "1E00-"1EFF}
\ltjdefcharrange{2}{"370-"4FF, "1F00-"1FFF}
\ltjdefcharrange{3}{%
  "2000-"243F, "2460-"24FF, "2500-"27BF, "2900-"29FF, "2B00-"2BFF}
\ltjdefcharrange{4}{%
   "500-"10FF, "1200-"1DFF, "2440-"245F, "27C0-"28FF, "2A00-"2AFF,
  "2C00-"2E7F, "4DC0-"4DFF, "A4D0-"A95F, "A980-"ABFF, "E000-"F8FF,
  "FB00-"FE0F, "FE20-"FE2F, "FE70-"FEFF, "10000-"1AFFF, "1B170-"1F0FF,
  "1F300-"1FFFF, "2000-"206F}
\ltjdefcharrange{5}{"D800-"DFFF, "E0000-"E00FF, "E01F0-"10FFFF}
\ltjdefcharrange{6}{%
  "2E80-"2EFF, "3000-"30FF, "3190-"319F, "31F0-"4DBF,
  "4E00-"9FFF, "F900-"FAFF, "FE10-"FE1F, "FE30-"FE6F, "FF00-"FFEF,
  "1B000-"1B16F, "1F100-"1F2FF, "20000-"3FFFF, "E0100-"E01EF}
\ltjdefcharrange{7}{%
  "1100-"11FF, "2F00-"2FFF, "3100-"318F, "31A0-"31EF, "A000-"A4CF,
  "A960-"A97F, "AC00-"D7FF}
\ltjdefcharrange{8}{"A7, "A8, "B0, "B1, "B4, "B6, "D7, "F7}
%    \end{macrocode}
% 将间隔号、引号、破折号等中西文公用的标点符号归入字符范围 9，将他们设置为 JAchar。
%    \begin{macrocode}
\ltjdefcharrange{9}{%
  "00B7, "2018, "2019, "201C, "201D, "2013, "2014, "2025, "2026, "2027, "2E3A}
%    \end{macrocode}
% \changes{v2.4.11}{2017/08/17}{不把希腊和西里尔字母设置为 JAchar。}
% \pkg{LuaTeX-ja} 默认把字符范围 2 和 3 设置为 JAchar，我们这里把它们都改成 ALchar。
%    \begin{macrocode}
\ltjsetparameter{jacharrange={-1, -2, -3, -4, -5, +6, +7, -8, +9}}
\directlua{for x=128,255 do luatexja.math.is_math_letters[x] = true end}
%    \end{macrocode}
%
% 以下设置抄录自 2020/08/08 之前的 \file{ltj-latex.sty}。
% 自 2020/08/08 开始，\pkg{LuaTeX-ja} 引入新的缓存机制，此段设置被整合进 \file{luatexja.lua}。
%    \begin{macrocode}
\@ifpackagelater{luatexja}{2020/08/08}
  { \ltjsetparameter { autospacing, autoxspacing, differentjfm = paverage } }
  {
    \directlua{
      local s = kpse.find_file('ltj-kinsoku.lua', 'tex')
      luatexja.stack.charprop_stack_table[0] = s and dofile(s) or {}
    }
    \ltjsetparameter{kanjiskip=\z@ plus .4pt minus .5pt,
      xkanjiskip=.25\zw plus 1pt minus 1pt,
      autospacing, autoxspacing, jacharrange={-1},
      yalbaselineshift=\z@, yjabaselineshift=\z@,
      jcharwidowpenalty=500, differentjfm=paverage
    }
  }
%    \end{macrocode}
%
%    \begin{macrocode}
\ExplSyntaxOn
%    \end{macrocode}
%
% \paragraph{\pkg{LuaTeX-ja} 的补丁}
%
%    \begin{macrocode}
%<@@=ctex_ltj>
%    \end{macrocode}
%
% \changes{v2.5.3}{2020/05/31}{不再依赖 \pkg{xunicode}，单独补丁 \file{tuenc.def}。}
%
% \begin{macro}{\CTEX@alchar, \CTEX@beginallalchar, \CTEX@endallalchar}
% 分组中的字符都是 ALchar 类。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@alchar #1
  { \CTEX@beginallalchar #1 \CTEX@endallalchar }
\cs_new_protected:Npn \CTEX@beginallalchar
  {
    \group_begin:
      \ctex_ltj_zero_globaldefs:
      \ltj@allalchar
  }
\cs_new_protected:Npn \CTEX@endallalchar
  { \group_end: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@chardef@text@cmd}
% 补丁 \tn{chardef@text@cmd}，应用于 \tn{DeclareTextSymbol}，使其定义的符号都是 ALchar。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@chardef@text@cmd #1
  {
    \cs_set_eq:NN \@ifdefinable \@@@@ifdefinable
    \tl_set:Nn \l_@@_cmd_tl {#1}
    \tex_afterassignment:D \@@_chardef_text_cmd:
    \tex_chardef:D #1
  }
\tl_new:N \l_@@_cmd_tl
\cs_new_protected:Npn \@@_chardef_text_cmd:
  { \exp_after:wN \@@_chardef_text_cmd_aux:N \l_@@_cmd_tl }
\cs_new_protected:Npn \@@_chardef_text_cmd_aux:N #1
  {
    \int_compare:nNnF {#1} < { "80 }
      { \cs_set_protected:Npx #1 { \CTEX@alchar { \tex_Uchar:D #1 } } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@text@composite@x}
% \tn{@text@composite@x} 的重定义，应用于 \tn{DeclareUnicodeComposite} 等。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@text@composite@x #1#2
  {
    \CTEX@beginallalchar
      \cs_if_exist_use:NF #1 {#2}
    \CTEX@endallalchar
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@add@unicode@accent}
% \tn{add@unicode@accent} 的重定义，应用于 \tn{DeclareUnicodeAccent}。
%    \begin{macrocode}
\cs_new_protected:Npx \CTEX@add@unicode@accent #1#2
  {
    \CTEX@beginallalchar
      \exp_not:N \tl_if_blank:nTF {#2} { \tex_Uchar:D "A0 ~ } {#2}
      \exp_not:N \tex_Uchar:D \tex_numexpr:D #1 \scan_stop:
    \CTEX@endallalchar
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@patch@text@cmd}
% 单独补丁由 \tn{DeclareTextCommand} 定义的命令。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@patch@text@cmd #1
  {
    \exp_args:NNc \@@_patch_text_cmd:NN #1
      { \UnicodeEncodingName \token_to_str:N #1 }
  }
\cs_new_protected:Npn \@@_patch_text_cmd:NN #1#2
  {
    \cs_set_eq:NN \CTEX@textcmd #2
    \ctex_preto_cmd:NnnTF \CTEX@textcmd
      { \ExplSyntaxOff \makeatletter }
      { \CTEX@beginallalchar }
      {
        \ctex_appto_cmd:NnnTF \CTEX@textcmd
          { \ExplSyntaxOff \makeatletter }
          { \CTEX@endallalchar }
          { \cs_set_eq:NN #2 \CTEX@textcmd }
          { \ctex_patch_failure:N #1 }
      }
      { \ctex_patch_failure:N #1 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\CTEX@patch@tunec}
% 重新载入 \file{tunec.def}，使补丁生效。
%    \begin{macrocode}
\cs_new_protected:Npn \CTEX@patch@tunec
  {
    \cs_set_eq:NN \chardef@text@cmd   \CTEX@chardef@text@cmd
    \ctex_file_input:n { tuenc.def }
    \cs_set_eq:NN \@text@composite@x  \CTEX@text@composite@x
    \cs_set_eq:NN \add@unicode@accent \CTEX@add@unicode@accent
    \CTEX@patch@text@cmd \textasteriskcentered
  }
\@ifpackageloaded { xunicode }
  { }
  { \CTEX@patch@tunec }
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.3}{2015/09/26}{更新 \pkg{unicode-math} 宏包补丁。}
%
% 在 \LaTeX{} 下，\pkg{LuaTeX-ja} 对 \pkg{fontspec}、\pkg{xunicode}、\pkg{unicode-math}
% 和 \pkg{listings} 打了补丁。其中前三个是把 \tn{char} 换成 \tn{ltjalchar}，确保
% 字符是 ALchar 类。我们这里用 \pkg{xunicode-addon} 来处理 \pkg{xunicode}。
%    \begin{macrocode}
\ctex_at_end_package:nn { xunicode }
  {
    \RequirePackage { xunicode-addon }
    \AtBeginUTFCommand { \CTEX@beginallalchar }
    \AtEndUTFCommand   { \CTEX@endallalchar }
  }
%    \end{macrocode}
%
% \changes{v2.4.2}{2016/05/15}{恢复 \pkg{luatexja} 对 \tn{emshape} 和
% \tn{eminnershape} 的重定义。}
% \changes{v2.4.3}{2016/08/26}{简化 \pkg{fontspec} 补丁。}
% \changes{v2.5}{2020/04/18}{删除 \pkg{fontspec} 补丁。}
%
% 对 \pkg{listings} 的补丁是让代码环境支持 JAchar 类。
%    \begin{macrocode}
\ctex_at_end_package:nn { listings }
  { \RequirePackage { lltjp-listings } }
%    \end{macrocode}
%
% \paragraph{\pkg{Lua} 函数}
%
% \changes{v2.5.8}{2021/11/29}{简化部分 \pkg{Lua} 函数。}
%
% 直接用 |token.set_lua| 定义，不是传统意义上的 \TeX \ 宏。
%    \begin{macrocode}
\group_begin:
\char_set_catcode_space:n { 32 }
\lua_now:e
  {
%    \end{macrocode}
% \begin{macro}{ctex.newluacmd}
% 定义新的 \pkg{Lua} 函数。
%    \begin{macrocode}
    ctex = ctex or { }
    local ctex = ctex
    local functions = lua.get_functions_table()
    local new_luafunction = luatexbase.new_luafunction
    local create, set_lua = token.create, token.set_lua
    local lua_cmds = {
      lua_call            = true ,
      lua_expandable_call = true ,
    }
    local newluacmd = function (name, func, ...)
      local tok = create(name)
      local id = lua_cmds[tok.cmdname] and tok.index
      local id = id or new_luafunction(name)
      set_lua(name, id, ...)
      functions[id] = func
    end
    ctex.newluacmd = newluacmd
%    \end{macrocode}
% \end{macro}
%    \begin{macrocode}
    local ltjfont = luatexja.jfont
    local getattribute = tex.getattribute
    local tex_set, sprint = tex.set, tex.sprint
    local scan_int, scan_arg = token.scan_int, token.scan_argument
%    \end{macrocode}
% \begin{macro}{\ctex_ltj_add_kyenc:n}
% 保存 jfont 的编码，用于判断。
%    \begin{macrocode}
    newluacmd("ctex_ltj_add_kyenc:n", ltjfont.add_kyenc_list, "global", "protected")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_is_kenc:n}
% 判断编码是否属于 jfont。
%    \begin{macrocode}
    newluacmd("ctex_ltj_is_kenc:n", ltjfont.is_kenc, "global")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_patch_external_font:n}
% 若对字体的定义完全相同，则它们有相同的 \texttt{font.id}。因此如果字形是由
% \textsf{NFSS} 的替换机制定义的，它们就有相同的 \texttt{font.id}。
% |print_aftl_address| 函数的定义是
% \begin{verbatim}
%   function luatexja.jfont.print_aftl_address()
%     return ';ltjaltfont' .. tostring(aftl_base):sub(8)
%   end
% \end{verbatim}
% 主要目的是，如果当前字形有替代字体，则往字形的定义中加入一些标志，确保
% \texttt{font.id} 唯一。
%    \begin{macrocode}
    newluacmd("ctex_ltj_patch_external_font:n", function ()
      local s = scan_arg()
      local is_braced, is_quoted
      if s:sub(1,1) == '{' and s:sub(-1)=='}' then
        is_braced = true; s = s:sub(2,-2)
      end
      if s:sub(1,1) == '"' and s:sub(-1) == '"' then
        is_quoted = true; s = s:sub(2,-2)
      end
      s = s .. ltjfont.print_aftl_address()
      if     is_braced then s = '{'..s..'}'
      elseif is_quoted then s = '"'..s..'"'
      end
      sprint(-2, s)
    end, "global")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_set_alt_font:nnnn}
% |#1| 和 |#2| 分别是字符区间的首末，|#3| 是基础字体，|#4| 是替代字体。
%    \begin{macrocode}
    newluacmd("ctex_ltj_set_alt_font:nnnn", function ()
      local b = tonumber(scan_arg())
      local e = tonumber(scan_arg())
      local alt  = scan_arg()
      local base = scan_arg()
      ltjfont.set_alt_font_latex(b, e, alt, base)
    end, "global", "protected")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_clear_alt_font:n}
% 清除 |#1| 的替代字体。
%    \begin{macrocode}
    newluacmd("ctex_ltj_clear_alt_font:n", function ()
      local base = scan_arg()
      ltjfont.clear_alt_font_latex(base)
    end, "global", "protected")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_pickup_alt_font:nn}
% 定义 |#1| 的替代字体，|#2| 是字体大小。\\
% 会在内部执行 \tn{ltj@pickup@altfont@auxy} 和 \tn{ltj@pickup@altfont@copy}。
%    \begin{macrocode}
    newluacmd("ctex_ltj_pickup_alt_font:nn", function ()
      local base = scan_arg()
      local size = scan_arg()
      ltjfont.output_alt_font_cmd("y", base)
      ltjfont.pickup_alt_font_a(size)
    end, "global", "protected")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_pickup_alt_font:Nn}
% |#1| 是 \texttt{font.id}，|#2| 是字体名称。
% 在 \tn{ltj@pickup@altfont@copy} 之中使用。
%    \begin{macrocode}
    newluacmd("@@_pickup_alt_font:Nn", function ()
      local num  = scan_int()
      local base = scan_arg()
      ltjfont.pickup_alt_font_b(num, base)
    end, "global", "protected")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\@@_if_alt_set:nT}
% 判断是否存在替代字体。会设置变量 |aftl_base| 和返回 \tn{@firstofone} 或 \tn{@gobble}。
%    \begin{macrocode}
    newluacmd("@@_if_alt_set:nT", ltjfont.does_alt_set, "global")
%    \end{macrocode}
% \end{macro}
% \begin{macro}{\ctex_ltj_zero_globaldefs:}
% 设置 \tn{globaldefs} 为 $0$，避免全局设置，应当在分组中使用。
% 这里通过 Lua 设置，可以不受外部 \TeX\ 环境中的 \tn{globaldefs} 的影响。
%    \begin{macrocode}
    newluacmd("ctex_ltj_zero_globaldefs:", function ()
      tex_set("globaldefs", 0)
    end, "global", "protected")
  }
\group_end:
%    \end{macrocode}
% \end{macro}
%
% \paragraph{字体切换方式}
%
% \begin{macro}[int]{\ctex_ltj_select_font:,\CJK@family}
% \tn{CJK@family} 保存的是当前 CJK 实际的字体族名，如果为空表示没有设置过字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_select_font:
  {
    \group_begin: \exp_args:NNc \group_end:
    \cs_if_exist_use:NF { \l_@@_current_font_tl }
      { \tl_if_empty:NF \CJK@family { \@@_select_font_aux: } }
  }
\tl_new:N \CJK@family
\tl_new:N \l_@@_current_font_tl
\tl_set:Nn \l_@@_current_font_tl
  { \CJK@encoding / \CJK@family / \f@series / \f@shape / \f@size }
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.5.8}{2021/11/18}{兼容 \pkg{microtype}。}
%
% \begin{macro}{\@@_select_font_aux:}
% 使用 \tn{pickup@font} 取得字体名称前，总需要先设置 \tn{font@name}。在这里将
% \tn{f@family} 换成 CJK 字体族，并确保编码正确。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_select_font_aux:
  {
    \group_begin:
      \tl_set_eq:NN \f@encoding \CJK@encoding
      \tl_set_eq:NN \f@family \CJK@family
      \cs_set_eq:NN \pickup@font \ctex_ltj_pickup_font:
      \@@_push_fontname:n { \curr@fontshape / \f@size }
      \ctex_ltj_pickup_font:
    \group_end:
    \font@name
    \@@_pop_fontname:
%    \end{macrocode}
% 当字形未定义的时候，\textsf{NFSS} 就会启动替换机制（\tn{wrong@fontshape}）。
% 第一次启动后，\cs{l_@@_current_font_tl} 还是没有定义。为此，我们再次选择字体，
% 确保它有定义和指向正确的 \texttt{font.id}。这对 \opt{AlternateFont} 的设置
% 特别重要。
%    \begin{macrocode}
    \cs_if_exist:cF { \l_@@_current_font_tl }
      { \@@_select_font_aux: }
  }
\cs_new_protected:Npn \@@_push_fontname:n #1
  {
    \seq_gpush:No \g_@@_fontname_seq { \font@name }
    \tl_gset:Ne \font@name { \exp_not:c {#1} }
  }
\cs_new_protected:Npn \@@_pop_fontname:
  {
    \seq_gpop:NNT \g_@@_fontname_seq \l_@@_tmp_tl
      { \tl_gset_eq:NN \font@name \l_@@_tmp_tl }
  }
\seq_new:N \g_@@_fontname_seq
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_pickup_font:}
% 替换 \tn{define@newfont} 内部调用的 \tn{extract@font} 和 \tn{do@subst@correction}。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_pickup_font:
  {
    \exp_after:wN \cs_if_exist:NF \font@name
      {
        \group_begin:
          \cs_set_eq:NN \extract@font \ctex_ltj_extract_font:
          \cs_set_eq:NN \do@subst@correction \ctex_ltj_subst_font:
          \define@newfont
        \group_end:
      }
  }
\cs_new_eq:NN \pickup@jfont \ctex_ltj_pickup_font:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_extract_font:}
% \pkg{LuaTeX-ja} 的 \tn{globaljfont} 在 \pkg{luatexja-core} 中定义：
% \begin{verbatim}
%   %%%%%%%% \jfont\CS={...:...;jfm=metric;...}, \globaljfont
%   \protected\def\jfont#1{%
%     \afterassignment\ltj@@jfont
%     \directlua{luatexja.jfont.jfontdefX
%       (false, 'yoko','\luatexluaescapestring{\noexpand#1}')}}
%   \protected\def\globaljfont#1{%
%     \afterassignment\ltj@@jfont
%     \directlua{luatexja.jfont.jfontdefX
%       (true,  'yoko','\luatexluaescapestring{\noexpand#1}')}}
%   \newluafunction\ltj@@jfont@inner
%   \directlua{
%     local t = lua.get_functions_table()
%     t[\the\ltj@@jfont@inner] = luatexja.jfont.jfontdefY
%   }
%   \def\ltj@@jfont{\luafunction\ltj@@jfont@inner}
% \end{verbatim}
% \texttt{jfontdefX} 函数的作用是把 \tn{CS} 定义为其后的字体，\texttt{jfontdefY}
% 的作用是更新 \texttt{JFM} 和记录相关字体信息。最后的工作是：
% \begin{verbatim}
%   tex.sprint(cat_lp, global_flag, '\\protected\\expandafter\\def\\csname ',
%     (cstemp==' ') and '\\space' or cstemp, '\\endcsname{\\ltj@cur'..
%     (jfm_dir == 'yoko' and 'j' or 't') .. 'fnt', fn, '\\relax}')
% \end{verbatim}
% \tn{CS} 的作用就是把 \tn{ltj@curjfnt} 设置为刚才定义的字体的 \texttt{font.id}。
%    \begin{macrocode}
\cs_new_protected_nopar:Npn \ctex_ltj_extract_font:
  {
    \get@external@font
    \ctex_ltj_if_alternate_shape_exist:nT { \curr@fontshape }
      {
        \tl_set:Ne \external@font
          { \exp_after:wN \@@_patch_external_font:w \external@font }
      }
    \exp_after:wN \globaljfont \font@name \external@font \scan_stop:
%    \end{macrocode}
% 这里 \tn{font@name} 不会直接改变当前字体，而 \tn{DeclareFontFamily} 和
% \tn{DeclareFontShape} 的最后一个参数通常要使用 \tn{font} 来引用当前字体。
% 为此，我们在分组内启用之前定义的字体，以便能得到正确的 \tn{font}。对字体参数的
% 赋值总是全局的，不会受到分组的影响。
%    \begin{macrocode}
    \font@name
    \ctex_ltj_use_jfont:
    \use:c { \f@encoding + \f@family }
    \use:c { \curr@fontshape }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ctex_ltj_use_jfont:}
% 使用 jfont，确保当前的 \tn{font} 是 jfont。
%    \begin{macrocode}
\cs_new_protected_nopar:Npn \ctex_ltj_use_jfont:
  { \tex_setfontid:D \ltj@curjfnt }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_subst_font:}
% \tn{do@subst@correction} 在设置通过 \texttt{sub} 或者 \texttt{ssub} 函数定义的
% 字体时会用到。如果没有设置 \opt{SlantedFont}，\pkg{fontspec} 会设置
% \tn{itdefault} 作为 \tn{sldefault} 的替代字形，因而会用到这个函数。它的本来定义是：
% \begin{verbatim}
%   \def\do@subst@correction{%
%       \xdef\subst@correction{%
%          \font@name
%          \global\expandafter\font
%            \csname \curr@fontshape/\f@size\endcsname
%            \noexpand\fontname\font
%           \relax}%
%       \aftergroup\subst@correction
%   }
% \end{verbatim}
% 我们在这里不需要定义新字体，而是设置对应字体的命令。
%    \begin{macrocode}
\cs_new_protected_nopar:Npn \ctex_ltj_subst_font:
  {
    \ctex_ltj_if_alternate_shape_exist:nF { \curr@fontshape }
      {
        \group_begin:
        \tl_set_eq:NN \CJK@family \f@family
        \cs_if_exist:cF { \l_@@_current_font_tl  }
          {
            \cs_gset_protected_nopar:Npx \subst@correction
              {
                \cs_new_eq:NN
                  \exp_not:c { \l_@@_current_font_tl }
                  \font@name
              }
            \group_insert_after:N \group_insert_after:N
            \group_insert_after:N \subst@correction
          }
        \group_end:
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int,TF]{\ctex_ltj_if_alternate_shape_exist:n}
% 即 \pkg{LuaTeX-ja} 中的 \tn{ltj@@does@alt@set}，判断是否存在替代字体。
%    \begin{macrocode}
\prg_new_conditional:Npnn \ctex_ltj_if_alternate_shape_exist:n #1 { T , F , TF }
  {
    \@@_if_alt_set:nT {#1} { \prg_return_true: \use_none:n }
    \prg_return_false:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_patch_external_font:w}
%    \begin{macrocode}
\cs_new:Npn \@@_patch_external_font:w #1 ~ at
  { \ctex_ltj_patch_external_font:n {#1} ~ at }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_select_alternate_font:}
% 在 \tn{selectfont} 中更新替代字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_select_alternate_font:
  {
    \ctex_ltj_if_alternate_shape_exist:nT { \l_@@_current_shape_tl }
      {
        \ctex_ltj_pickup_alt_font:nn
          { \l_@@_current_shape_tl } { \f@size }
      }
  }
\tl_new:N \l_@@_current_shape_tl
\tl_set:Nn \l_@@_current_shape_tl
  { \CJK@encoding / \CJK@family / \f@series / \f@shape }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ltj@pickup@altfont@auxy}
% 被用在函数 |output_alt_font_cmd| 中，作用是定义替代字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ltj@pickup@altfont@auxy #1
  {
    \cs_if_exist:cF { #1/\f@size }
      {
        \group_begin:
          \use:e { \exp_not:N \split@name #1 / \f@size } \@nil
          \@@_push_fontname:n { \curr@fontshape / \f@size }
          \ctex_ltj_pickup_font:
        \group_end:
        \@@_pop_fontname:
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ltj@pickup@altfont@copy}
% 被用在函数 |pickup_alt_font_a| 中。\tn{ltj@@getjfontnumber} 的作用是将字体命令
% |#1| 对应的 \texttt{font.id} 保存到 \tn{ltj@tempcntc} 中。
%    \begin{macrocode}
\cs_new_protected:Npn \ltj@pickup@altfont@copy #1#2
  {
    \ltj@@@@getjfontnumber #1
    \@@_pickup_alt_font:Nn \ltj@tempcntc {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \paragraph{数学字体族}
%
% 以下内容来自 \file{lltjfont.sty}，目的是让汉字可以在数学环境中直接使用。
%
% \begin{macro}[int]{\ctex_ltj_if_jfont:nTF}
% 参数 |#1| 是一个 \LaTeXe{} 编码名称或者字体命令。\LaTeXe{} 字体命令的一般形式是：
% \begin{quote}\ttfamily\small
%   \textbackslash\meta{encoding}/\meta{family}/\meta{series}/\meta{shape}
% \end{quote}
% 通过截取名字中的 \meta{encoding} 来判断是否是 jfont。
% 最后会设置 \tn{ifin@} 为对应的 \tn{iftrue} 或者 \tn{iffalse}。
%    \begin{macrocode}
\cs_new:Npn \ctex_ltj_if_jfont:nTF #1
  {
    \ctex_ltj_is_kenc:n { \@@_ltj_if_jfont:w #1 / \q_stop }
    \legacy_if:nTF { in@ }
  }
\cs_new:Npn \@@_ltj_if_jfont:w #1 / #2 \q_stop
  {#1}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_if_jfont_math:NTF}
% |#1| 是一个形式为 |\M@|\meta{encoding} 的命令，它由 \tn{DeclareFontEncoding} 的
% 第三个参数来定义。
%    \begin{macrocode}
\cs_new:Npn \ctex_ltj_if_jfont_math:NTF #1
  { \exp_after:wN \@@_if_jfont_math:w \token_to_str:N #1 \q_stop }
\group_begin:
  \char_set_catcode_other:N M
  \cs_new:Npn \@@_if_jfont_math:w #1 M #2#3 \q_stop
    { \ctex_ltj_if_jfont:nTF {#3} }
\group_end:
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\getanddefine@fonts,\ctex_ltj_get_and_define_fonts:nN}
% 在使用的场合，\tn{escapechar} 已经被设置成 $-1$，使用 \cs{token_to_str:N} 就
% 可以得到名字，不必使用 \cs{cs_to_str:N}。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_get_and_define_fonts:nN #1#2
  {
    \exp_args:No \ctex_ltj_if_jfont:nTF { \token_to_str:N #2 }
      { \ctex_ltj_get_and_define_fonts_ja:nN }
      { \ctex_ltj_get_and_define_fonts_al:nN }
      {#1} #2
  }
\cs_new_eq:NN \ctex_ltj_get_and_define_fonts_al:nN \getanddefine@fonts
\cs_set_eq:NN \getanddefine@fonts \ctex_ltj_get_and_define_fonts:nN
\cs_new_protected:Npn \ctex_ltj_get_and_define_fonts_ja:nN #1#2
  {
    \tl_gset:Ne \font@name { \use:c { \token_to_str:N #2 / \tf@size } }
    \ctex_ltj_pickup_font: \tl_set_eq:NN \textfont@name \font@name
    \tl_gset:Ne \font@name { \use:c { \token_to_str:N #2 / \sf@size } }
    \ctex_ltj_pickup_font: \tl_set_eq:NN \scriptfont@name \font@name
    \tl_gset:Ne \font@name { \use:c { \token_to_str:N #2 / \ssf@size } }
    \ctex_ltj_pickup_font:
    \tl_put_right:Ne \math@fonts
      {
        \ltj@setpar@global
        \ltj@@@@set@stackfont #1 , \textfont@name   \c_colon_str { MJT }
        \ltj@@@@set@stackfont #1 , \scriptfont@name \c_colon_str { MJS }
        \ltj@@@@set@stackfont #1 , \font@name       \c_colon_str { MJSS }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\use@mathgroup,\ctex_ltj_use_math_group:Nn}
% 在使用 \pkg{unicode-math} 宏包时，\cs{ctex_ltj_math_group_hook:} 将被重定义。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_use_math_group:Nn #1#2
  {
    \mode_if_math:T
      {
        \math@bgroup
          \cs_if_eq:cNF { M@ \f@encoding } #1 {#1}
          \ctex_ltj_math_group_hook:
          \ctex_ltj_if_jfont_math:NTF #1
            { \jfam } { \mathgroup } #2 \scan_stop:
        \math@egroup
      }
  }
\cs_new_eq:NN \ctex_ltj_math_group_hook: \prg_do_nothing:
\cs_set_eq:NN \use@mathgroup \ctex_ltj_use_math_group:Nn
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.4.3}{2016/08/25}{更新 \pkg{unicode-math} 补丁。}
% \changes{v2.4.9}{2017/02/27}{调整 \pkg{unicode-math} 补丁的代码顺序。}
% 对 \pkg{unicode-math} 的补丁主要是将 \file{unicode-math-table.tex} 中的数学符号设置为
% \pkg{luatexja} 中的数学字母。
% 本段代码应放在 \cs{ctex_ltj_math_group_hook:} 的定义之后，避免因宏包载入顺序而造成的编译错误。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_math_letter:NN #1#2
  {
    \group_begin:
      \cs_set_protected:Npn #1 ##1##2##3
        { \ltjsetmathletter { ##1 } }
      #2
    \group_end:
  }
\ctex_at_end_package:nn { unicode-math }
  {
    \cs_if_exist:NTF \um_input_math_symbol_table:
      {
        \ctex_ltj_set_math_letter:NN
          \um_sym:nnn
          \um_input_math_symbol_table:
      }
      {
        \cs_set_eq:NN \use@mathgroup \ctex_ltj_use_math_group:Nn
        \cs_set_protected:Npn \ctex_ltj_math_group_hook:
          { \__um_switchto_literal: }
        \ctex_ltj_set_math_letter:NN
          \__um_sym:nnn
          \__um_input_math_symbol_table:
      }
  }
%    \end{macrocode}
%
% \paragraph{字体族的定义与使用}
%
% \changes{v2.5.9}{2022/05/26}{依赖 \pkg{chinese-jfm} 宏包。}
%
% \begin{macro}[int]{\ctex_set_jfm:n}
% \begin{variable}{\l_@@_jfm_tl, \l_@@_redirect_jfm_prop}
% 设置 \texttt{JFM}，需要进行一些重定向操作。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_set_jfm:n #1
  {
    \prop_get:NnNF \l_@@_redirect_jfm_prop {#1} \l_@@_jfm_tl
      { \tl_set:Nn \l_@@_jfm_tl {#1} }
  }
\cs_generate_variant:Nn \ctex_set_jfm:n { o }
\prop_new:N \l_@@_redirect_jfm_prop
\prop_set_from_keyval:Nn \l_@@_redirect_jfm_prop
  {
    plain    = mono ,
    quanjiao = zh_CN / quanjiao ,
    banjiao  = zh_CN / banjiao ,
    kaiming  = zh_CN / kaiming
  }
\keys_define:nn { ctex_ltj / fontspec }
  {
    JFM           .code:n = \ctex_set_jfm:n {#1} ,
    JFM .value_required:n = true
  }
\tl_new:N \l_@@_jfm_tl
%    \end{macrocode}
% 注意这里的 |@@=ctex_ltj|。
%    \begin{macrocode}
\ctex_set_jfm:o { \l__ctex_punct_tl }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}[int]{\CJK@encoding}
% 在 \LaTeX 下，\pkg{LuaTeX-ja} 依赖字体编码来实现特殊设置。例如上述的
% \cs{ctex_ltj_if_jfont:nTF} 就是通过判断编码来实现的，它在设置数学字体时会用到。所以
% 不应该与西文共用 \texttt{EU2}。定义字体族 song 为 \tn{CJK@encoding} 的默认替换
% 字体。下划线 |_| 不在 \tn{nfss@catcodes} 里，可以放心使用。
%    \begin{macrocode}
\tl_const:Nn \CJK@encoding { LTJY3 }
\DeclareFontEncoding { \CJK@encoding } { } { }
\use:e
  {
    \exp_not:N \DeclareFontSubstitution
      { \CJK@encoding } { song } { \mddefault } { \shapedefault }
  }
\ctex_ltj_add_kyenc:n { \CJK@encoding }
\DeclareFontFamily { \CJK@encoding } { song } { }
\DeclareFontShape { \CJK@encoding } { song } { \mddefault } { \shapedefault }
  { <-> psft:SimSun:cid=Adobe-GB1-5;jfm=\l_@@_jfm_tl } { }
\DeclareFontShape { \CJK@encoding } { song } { \bfdefault } { \shapedefault }
  { <-> psft:SimHei:cid=Adobe-GB1-5;jfm=\l_@@_jfm_tl } { }
\tl_const:Nn \c_@@_math_tl { CJKmath }
\DeclareSymbolFont { \c_@@_math_tl }
  { \CJK@encoding } { song } { \mddefault } { \shapedefault }
\SetSymbolFont { \c_@@_math_tl } { bold }
  { \CJK@encoding } { song } { \bfdefault } { \shapedefault }
\int_const:Nn \c_@@_math_fam_int { \use:c { sym \c_@@_math_tl } }
\jfam \c_@@_math_fam_int
%    \end{macrocode}
% \end{macro}
%
% 这是 \pkg{luatexja-fontspec} 中新增的一些字体选项。
%    \begin{macrocode}
\newfontfeature { CID }     {    cid = #1 }
\newfontfeature { JFM }     {    jfm = #1 }
\newfontfeature { JFM-var } { jfmvar = #1 }
%    \end{macrocode}
%
% 在新版本的 \pkg{fontspec} 中，\cs{__fontspec_fontname_wrap:n} 变成了私有函数。
%    \begin{macrocode}
\keys_define:nn { fontspec-preparse-external }
  {
    NoEmbed .code:n =
      { \cs_set_eq:NN \__fontspec_fontname_wrap:n \@@_noembed_wrap:n }
  }
\cs_new:Npn \@@_noembed_wrap:n #1 { psft: #1 }
%    \end{macrocode}
%
% \begin{macro}[int]{\ctex_ltj_set_family:nnn}
% 将自定义的字体族名与 \pkg{fontspec} 实际设置的名字对应起来。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_family:nnn #1#2#3
  {
    \group_begin:
    \clist_clear:N \l_@@_char_range_clist
    \prop_clear:N \l_@@_alternate_prop
    \tl_set:Nn \l_@@_base_CJKfamily_tl {#1}
    \keys_set_known:nnN { ctex_ltj / fontspec } {#2} \l_@@_tmp_tl
    \clist_set:No \l_@@_font_options_clist { \l_@@_tmp_tl }
    \ctex_ltj_set_alternate_family:nnF {#1} {#3}
      {
        \prop_gput:Nnn \g_@@_family_font_name_prop {#1} {#3}
        \prop_gput:Nno \g_@@_family_font_options_prop
          {#1} { \l_@@_font_options_clist }
        \@@_update_family_uid:N \l_@@_font_options_clist
        \@@_use_global_options:N \l_@@_font_options_clist
        \@@_gset_family_cs:nn {#1} {#3}
      }
    \group_end:
  }
\tl_new:N \l_@@_base_CJKfamily_tl
\clist_new:N \l_@@_font_options_clist
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_use_global_options:N}
% 应用默认字体选项，并总是设置 \opt{JFM} 和 \opt{NFSSEncoding} 。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_use_global_options:N #1
  {
    \clist_concat:NNN #1 \g_@@_default_features_clist #1
    \clist_put_left:Ne #1
      { NFSSEncoding = \CJK@encoding , JFM = \l_@@_jfm_tl }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}
%  {\g_@@_family_name_prop,\g_@@_family_font_name_prop,\g_@@_family_font_options_prop}
% 分别保存 \pkg{fontspec} 设置的字体族名、字体名称和字体选项。
%    \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}{\@@_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
      {
        \cs_undefine:c { \@@_family_csname:n {#1} }
        \cs_undefine:c { \@@_alternate_cs:n {#1} }
        \prop_gpop:NnNT \g_@@_family_name_prop {#1} \l_@@_base_family_tl
          {
            \use:c { \@@_alternate_cs:n { clear / #1 } }
            \cs_undefine:c { \@@_alternate_cs:n { clear / #1 } }
            \cs_undefine:c { \@@_alternate_cs:n { reset / #1 } }
            \prop_gremove:Nn \g_@@_reset_alternate_prop {#1}
          }
        \msg_warning:nnee { ctex } { redefine-family } {#1} { \l_@@_tmp_tl }
      }
  }
\tl_new:N \l_@@_tmp_tl
\msg_new:nnn { ctex } { redefine-family }
  { Redefining~CJKfamily~`\@@_msg_family_map:n {#1}'~(#2). }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_gset_family_cs:nn}
% 在设置字体时，实际上并不是马上就定义。而是只保存相关参数，在通过 \tn{CJKfamily}
% 第一次使用时才定义。需要注意将编码改为 \tn{CJK@encoding}。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_gset_family_cs:nn #1#2
  {
    \cs_gset_protected:cpx { \@@_family_csname:n {#1} }
      {
        \group_begin:
        \exp_not:n { \cs_set_eq:NN \CJKfamily \use_none:n }
        \exp_not:n { \fontspec_gset_family:Nnn \g_@@_fontspec_family_tl }
          { \exp_not:o { \l_@@_font_options_clist } } {#2}
        \prop_gput:Nno \exp_not:N \g_@@_family_name_prop {#1}
          { \exp_not:N \g_@@_fontspec_family_tl }
        \@@_set_alternate_family:n {#1}
        \group_end:
      }
  }
\tl_new:N \l_@@_base_family_tl
\tl_new:N \g_@@_fontspec_family_tl
\cs_new:Npn \@@_family_csname:n #1 { ctex_ltj/family/#1 }
\cs_new_protected:Npn \@@_set_alternate_family:n #1
  {
    \tl_set:Nn \l_@@_base_CJKfamily_tl {#1}
    \tl_set_eq:NN \l_@@_base_family_tl \g_@@_fontspec_family_tl
    \cs_if_exist_use:c { \@@_alternate_cs:n { reset / #1 } }
    \cs_if_exist_use:c { \@@_alternate_cs:n {#1} }
  }
\cs_new:Npn \@@_alternate_cs:n #1 { ctex_ltj/alternate_family/#1 }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\CJKfamily}
% 切换字体。
%    \begin{macrocode}
\NewDocumentCommand \CJKfamily { m }
  { \ctex_ltj_switch_family:e {#1} \tex_ignorespaces:D }
\cs_new_protected:Npn \ctex_ltj_switch_family:n #1
  {
    \ctex_ltj_family_if_exist:nNTF {#1} \CJK@family
      {
        \tl_set:Nn \l_ctex_ltj_family_tl {#1}
        \selectfont
      }
      { \@@_family_unknown_warning:n {#1} }
  }
\tl_new:N \l_ctex_ltj_family_tl
\cs_generate_variant:Nn \ctex_ltj_switch_family:n { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int,TF]{\ctex_ltj_family_if_exist:nN}
% 判断 CJK 字体族 |#1| 是否存在，若存在则把实际族名保存到 |#2| 中。
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \ctex_ltj_family_if_exist:nN #1#2 { T , F , TF }
  {
    \prop_get:NnNTF \g_@@_family_name_prop {#1} #2
      { \prg_return_true: }
      {
        \cs_if_exist_use:cTF { \@@_family_csname:n {#1} }
          {
            \tl_set_eq:NN #2 \g_@@_fontspec_family_tl
            \prg_return_true:
          }
          { \prg_return_false: }
      }
  }
\prg_generate_conditional_variant:Nnn \ctex_ltj_family_if_exist:nN { e } { T , F , TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_family_unknown_warning:n}
%    \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}
            \msg_warning:nnn { ctex } { family-unknown } {#1}
          }
      }
  }
\seq_new:N \g_@@_unknown_family_seq
\msg_new:nnn { ctex } { family-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}[int]{\ctex_ltj_fontspec:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_fontspec:nn #1#2
  {
    \prop_get:NnNTF \g_@@_fontspec_prop
      { CJKfontspec/#1/#2/id } \l_ctex_ltj_family_tl
      { \ctex_ltj_switch_family:e { \l_ctex_ltj_family_tl } }
      {
        \int_gincr:N \g_@@_family_int
        \@@_fontspec:enn
          { CJKfontspec ( \int_use:N \g_@@_family_int ) }
          {#1} {#2}
      }
  }
\cs_new_protected:Npn \ctex_ltj_fontspec:ee #1#2
  { \use:e { \ctex_ltj_fontspec:nn {#1} {#2} } }
\cs_new_protected:Npn \@@_fontspec:nnn #1#2#3
  {
    \bool_if:NT \l_@@_add_alternate_bool
      {
        \cs_if_free:cF
          { \@@_alternate_cs:n { reset / \l_ctex_ltj_family_tl } }
          {
            \cs_gset_eq:cc
              { \@@_alternate_cs:n { reset / #1 } }
              { \@@_alternate_cs:n { reset / \l_ctex_ltj_family_tl } }
            \cs_gset_eq:cc
              { \@@_alternate_cs:n { clear / #1 } }
              { \@@_alternate_cs:n { clear / \l_ctex_ltj_family_tl } }
          }
        \bool_set_false:N \l_@@_add_alternate_bool
      }
    \prop_gput:Nnn \g_@@_fontspec_prop { CJKfontspec/#2/#3/id } {#1}
    \ctex_ltj_set_family:nnn {#1} {#2} {#3}
    \ctex_ltj_switch_family:n {#1}
  }
\cs_generate_variant:Nn \@@_fontspec:nnn { e }
\prop_new:N \g_@@_fontspec_prop
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
% {\ctex_ltj_add_font_features:n,\ctex_ltj_add_font_features:nn}
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_add_font_features:n #1
  { \ctex_ltj_add_font_features:en { \l_ctex_ltj_family_tl } {#1} }
\cs_new_protected:Npn \ctex_ltj_add_font_features:nn #1#2
  {
    \prop_get:NnNTF \g_@@_family_font_name_prop
      {#1} \l_@@_tmp_tl
      {
        \prop_get:NnN \g_@@_family_font_options_prop
          {#1} \l_@@_font_options_clist
        \clist_put_right:Nn \l_@@_font_options_clist {#2}
        \bool_set_true:N \l_@@_add_alternate_bool
        \ctex_ltj_fontspec:ee
          { \exp_not:o { \l_@@_font_options_clist } }
          { \exp_not:o { \l_@@_tmp_tl } }
      }
      { \msg_warning:nn { ctex } { addCJKfontfeature-ignored } }
  }
\bool_new:N \l_@@_add_alternate_bool
\cs_generate_variant:Nn \ctex_ltj_add_font_features:n  { e }
\cs_generate_variant:Nn \ctex_ltj_add_font_features:nn { e }
\msg_new:nnn { ctex } { addCJKfontfeature-ignored }
  {
    \token_to_str:N \addCJKfontfeature (s)~ignored.\\
    It~cannot~be~used~with~a~font~that~wasn't~selected~by~ctex.
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_pass_args:nnnn}
% 为了支持字体属性可选项在前在后两种语法，给出两个辅助工具，自带展开功能。
%    \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}
%
% \changes{v2.4.6}{2016/11/20}{支持字体属性可选项在后的新语法。}
% \changes{v2.6.0}{2026/04/24}{\textbf{Breaking:}
%   \tn{newCJKfontfamily} 定义的字体切换命令改为局部定义，
%   与 \tn{newcommand} 行为一致。在分组内调用时，
%   命令不再泄漏到分组外（\#751）。}
%
% \begin{macro}[int]
% {\setCJKfamilyfont,\newCJKfontfamily,\CJKfontspec,\addCJKfontfeatures}
%    \begin{macrocode}
\NewDocumentCommand \setCJKfamilyfont { m o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn {#1} } {#2} {#3}
      { }
  }
\msg_new:nnn { ctex } { 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
      { \msg_error:nne { ctex } { command-already-defined }
          { \token_to_str:N #2 } }
      {
        \cs_set_protected:Npx #2
          { \ctex_ltj_switch_family:n { \l_@@_tmp_tl } }
      }
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn { \l_@@_tmp_tl } } {#3} {#4}
      { }
  }
\NewDocumentCommand \CJKfontspec { o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_fontspec:nn } {#1} {#2}
      { \tex_ignorespaces:D }
  }
\NewDocumentCommand \addCJKfontfeatures { m }
  {
    \ctex_ltj_add_font_features:e {#1}
    \tex_ignorespaces:D
  }
\cs_new_eq:NN \addCJKfontfeature \addCJKfontfeatures
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]
% {\setCJKmainfont,\setCJKsansfont,\setCJKmonofont,
%  \setCJKmathfont,\defaultCJKfontfeatures}
%    \begin{macrocode}
\NewDocumentCommand \setCJKmainfont { o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn { \CJKrmdefault } } {#1} {#2}
      { \normalfont }
  }
\cs_new_eq:NN \setCJKromanfont \setCJKmainfont
\NewDocumentCommand \setCJKsansfont { o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn { \CJKsfdefault } } {#1} {#2}
      { \normalfont }
  }
\NewDocumentCommand \setCJKmonofont { o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn { \CJKttdefault } } {#1} {#2}
      { \normalfont }
  }
\NewDocumentCommand \setCJKmathfont { o m }
  {
    \@@_pass_args:nnnn
      { \ctex_ltj_set_family:nnn { \c_@@_math_tl } } {#1} {#2}
      { }
  }
\NewDocumentCommand \defaultCJKfontfeatures { m }
  { \clist_gset:Nn \g_@@_default_features_clist {#1} }
\clist_new:N \g_@@_default_features_clist
\@onlypreamble \setCJKmainfont
\@onlypreamble \setCJKsansfont
\@onlypreamble \setCJKmonofont
\@onlypreamble \setCJKmathfont
\@onlypreamble \setCJKromanfont
\@onlypreamble \defaultCJKfontfeatures
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.4.8}{2017/02/23}
%   {解决与 \pkg{fontspec} 2017/01/24 v2.5d 的字体族匹配兼容问题。}
%
% \begin{macro}[int]{\ctex_ltj_ensure_default_family:}
% 在导言区结束确认 \tn{CJKfamilydefault} 确实存在。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_ensure_default_family:
  {
    \prop_if_empty:NF \g_@@_family_font_name_prop
      {
        \ctex_ltj_family_if_exist:eNF { \CJKfamilydefault } \l_@@_tmp_tl
          {
            \str_if_eq:eeTF { \CJKfamilydefault } { \CJKrmdefault }
              { \use:n }
              {
                \ctex_ltj_family_if_exist:eNTF { \CJKrmdefault } \l_@@_tmp_tl
                  { \tl_gset:Nn \CJKfamilydefault { \CJKrmdefault } \use_none:n }
                  { \use:n }
              }
              {
                \prop_map_inline:Nn \g_@@_family_font_name_prop
                  {
                    \prop_map_break:n
                      { \tl_gset_rescan:Nnn \CJKfamilydefault { } { ##1 } }
                  }
              }
          }
        \normalfont
        \ctex_ltj_update_mathfont:
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_update_mathfont:}
% 更新数学字体为实际的字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_update_mathfont:
  {
    \ctex_ltj_family_if_exist:eNTF { \c_@@_math_tl } \l_@@_tmp_tl
      { \ctex_ltj_update_mathfont:n { \l_@@_tmp_tl } }
      {
        \ctex_ltj_family_if_exist:eNT { \CJKfamilydefault } \l_@@_tmp_tl
          { \ctex_ltj_update_mathfont:n { \l_@@_tmp_tl } }
      }
  }
\cs_new_protected:Npn \ctex_ltj_update_mathfont:n #1
  {
    \tl_const:Ne \c_@@_math_family_tl {#1}
    \DeclareSymbolFont { \c_@@_math_tl } { \CJK@encoding }
      { \c_@@_math_family_tl } { \mddefault } { \shapedefault }
    \cs_if_free:cTF
      { \CJK@encoding/\c_@@_math_family_tl/\bfdefault/\shapedefault }
      {
        \SetSymbolFont { \c_@@_math_tl } { bold } { \CJK@encoding }
          { \c_@@_math_family_tl } { \mddefault } { \shapedefault }
      }
      {
        \SetSymbolFont { \c_@@_math_tl } { bold } { \CJK@encoding }
          { \c_@@_math_family_tl } { \bfdefault } { \shapedefault }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \paragraph{替代字体的设置}
%
% \changes{v2.5}{2020/04/30}{重构字体选项 \opt{AlternateFont}。}
%
% \begin{macro}{AlternateFont,CharRange}
% 设置替代字体的选项。
%    \begin{macrocode}
\keys_define:nn { ctex_ltj / fontspec }
  {
    AlternateFont  .code:n = \ctex_ltj_set_alternate_prop:n {#1} ,
    AlternateFont  .value_required:n = true ,
    CharRange .clist_set:N = \l_@@_char_range_clist ,
    CharRange .value_required:n = true
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_prop:n}
% 保存替代字体序列。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_prop:n #1
  { \clist_map_function:nN {#1} \@@_push_alternate_prop:n }
\cs_new_protected:Npn \@@_push_alternate_prop:n #1
  {
    \clist_set:Ne \l_@@_tmp_clist { \tl_head:n {#1} }
    \tl_remove_all:Nn \l_@@_tmp_clist { ~ }
    \exp_args:No \@@_push_alternate_prop:nn
      { \l_@@_tmp_clist } {#1}
  }
\cs_new_protected:Npn \@@_push_alternate_prop:nn #1
  {
    \prop_remove:Nn \l_@@_alternate_prop {#1}
    \prop_put:Nnn \l_@@_alternate_prop {#1}
  }
\clist_new:N \l_@@_tmp_clist
\prop_new:N \l_@@_alternate_prop
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_family:nnF}
% 如果在字体的选项中设置了 \opt{CharRange}，则只设置替代字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_family:nnF
  {
    \clist_if_empty:NTF \l_@@_char_range_clist
      { \@@_set_family_aux:nnn }
      { \@@_set_alternate_family_aux:nnn }
  }
\cs_new_protected:Npn \@@_set_family_aux:nnn #1#2#3
  {
    \@@_check_family:n {#1}
    \prop_if_empty:NF \l_@@_alternate_prop
      { \ctex_ltj_save_alternate_seq:cn { \@@_alternate_cs:n {#1} } {#2} }
    #3
  }
\cs_new_protected:Npn \@@_set_alternate_family_aux:nnn #1#2#3
  { \ctex_ltj_set_alternate_family:nn {#1} {#2} }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_save_alternate_seq:Nn}
% 保存由 \opt{AlternateFont} 设置的替代字体序列。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_save_alternate_seq:Nn #1#2
  {
    \prop_map_inline:Nn \l_@@_alternate_prop
      { \@@_save_alternate_auxi:w ##2 { } \q_mark #1 {#2} }
  }
\cs_new_protected:Npn \@@_save_alternate_auxi:w #1#2#
  {
    \tl_if_blank:nTF {#2}
      { \@@_save_alternate_auxii:w {#1} }
      { \@@_save_alternate_auxii:w {#1} {#2} }
  }
\cs_new_protected:Npn \@@_save_alternate_auxii:w #1#2#3 #4 \q_mark #5#6
  {
    \clist_set:Nn \l_@@_char_range_clist {#1}
    \clist_set:Nn \l_@@_alternate_options_clist {#3}
    \@@_use_global_options:N \l_@@_alternate_options_clist
    \tl_if_blank:nTF {#2}
      { \tl_set:Nn \l_@@_tmp_tl {#6} }
      {
        \tl_set:Ne \l_@@_tmp_tl { \tl_trim_spaces:n {#2} }
        \tl_replace_all:Nnn \l_@@_tmp_tl { * } {#6}
      }
    \use:e
      {
        \ctex_ltj_save_alternate_family:Nnnn \exp_not:N #5
          { \exp_not:o { \l_@@_char_range_clist } }
          { \exp_not:o { \l_@@_alternate_options_clist } }
          { \exp_not:o { \l_@@_tmp_tl } }
      }
  }
\clist_new:N \l_@@_alternate_options_clist
\cs_generate_variant:Nn \ctex_ltj_save_alternate_seq:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_family:nn}
% 设置选项 \opt{CharRange} 范围内的替代字体。如果已经定义了主字体，我们也马上
% 定义替代字体，否则只保存起来备用。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_family:nn #1#2
  {
    \@@_update_family_uid:N \l_@@_font_options_clist
    \@@_use_global_options:N \l_@@_font_options_clist
    \ctex_ltj_set_alternate_family:coonn
      { \@@_alternate_cs:n {#1} }
      { \l_@@_char_range_clist }
      { \l_@@_font_options_clist } {#2} {#1}
  }
\cs_new_protected:Npn \ctex_ltj_set_alternate_family:Nnnnn #1#2#3#4#5
  {
    \prop_get:NnNT \g_@@_family_name_prop {#5} \l_@@_base_family_tl
      { \ctex_ltj_set_alternate_family:nnn {#2} {#3} {#4} }
    \ctex_ltj_save_alternate_family:Nnnn #1 {#2} {#3} {#4}
  }
\cs_generate_variant:Nn \ctex_ltj_set_alternate_family:Nnnnn { coo }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_save_alternate_family:Nnnn}
% 保存替代字体序列的定义，以备定义主字体时使用。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_save_alternate_family:Nnnn #1#2#3#4
  {
    \cs_if_exist:NF #1 { \cs_set_eq:NN #1 \prg_do_nothing: }
    \cs_gset_protected:Npx #1
      { \exp_not:o { #1 \ctex_ltj_set_alternate_family:nnn {#2} {#3} {#4} } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_family:nnn}
% 实际定义替代字体族。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_family:nnn #1#2#3
  {
    \group_begin:
    \cs_set_eq:NN \CJKfamily \use_none:n
    \ctex_ltj_swap_cs:NN
      \DeclareFontShape@ \ctex_ltj_declare_alternate_shape:nnnnnn
    \tl_set:Nn \l_@@_char_range_clist {#1}
    \fontspec_set_family:Nnn \l_@@_alternate_family_tl {#2} {#3}
    \group_end:
  }
\tl_new:N \l_@@_alternate_family_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_swap_cs:NN}
% 交换两个控制序列的意义。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_swap_cs:NN #1#2
  {
    \cs_set_eq:NN \@@_tmp:w #1
    \cs_set_eq:NN #1 #2
    \cs_set_eq:NN #2 \@@_tmp:w
    \cs_undefine:N \@@_tmp:w
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{LTJFONTUID}
% \begin{macro}{\@@_update_family_uid:N}
% \pkg{fontspec} 在一个字体族的选项和字体名称相同的时候，就不定义新字体。为了
% 避免混淆替代字体的设置，我们新定义一个虚拟的选项 \opt{LTJFONTUID}，确保
% \pkg{fontspec} 对 CJK 字体族总是定义新字体。
%    \begin{macrocode}
\keys_define:nn { fontspec } { LTJFONTUID .code:n = }
\cs_new_protected:Npn \@@_update_family_uid:N #1
  {
    \int_gincr:N \g_@@_family_int
    \clist_put_right:Ne #1 { LTJFONTUID = \int_use:N \g_@@_family_int }
  }
\int_new:N \g_@@_family_int
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_declare_alternate_shape:nnnnnn}
% 在定义替代字体的字形时，通过字符范围与主字体的对应字形关联起来。
% \tn{DeclareFontShape@} 一个有六个参数，我们只需要使用它的第三个参数 \meta{series}
% 和第四个参数 \meta{shape}。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_declare_alternate_shape:nnnnnn #1#2#3#4#5#6
  {
    \ctex_ltj_declare_alternate_shape:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
    \ctex_ltj_set_alternate_shape:Nnnnnnn \l_@@_char_range_clist
      { \l_@@_base_family_tl } {#3} {#4} {#2} {#3} {#4}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_shape:Nnnnnnn}
% 与 \pkg{LuaTeX-ja} 的 \tn{DeclareAlternateKanjiFont} 的功能类似，区别是固定编码
% 为 \tn{CJK@encoding}。这个设置总是全局的。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_shape:Nnnnnnn #1#2#3#4#5#6#7
  {
    \clist_map_inline:Nn #1
      {
        \prop_get:NnNTF \g_@@_char_range_prop { ##1 } \l_@@_char_range_tl
          {
            \ctex_ltj_set_alternate_shape:nnN { #2/#3/#4 } { #5/#6/#7 }
              \l_@@_char_range_tl
          }
          { \ctex_ltj_set_alternate_shape:nnn { #2/#3/#4 } { #5/#6/#7 } { ##1 } }
      }
    \@@_save_alternate_shape:cn
      { \@@_alternate_cs:n { clear / \l_@@_base_CJKfamily_tl } }
      { \ctex_ltj_clear_alt_font:n { \CJK@encoding/#2/#3/#4 } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_shape:nnn}
% 我们使用 \texttt{->} 而不是像 \pkg{LuaTeX-ja} 一样使用 \texttt{-} 作为区间的
% 分隔符。\pkg{LuaTeX-ja} 支持使用负数来引用由 \texttt{JFM} 设置的字符类。如果
% 使用 \texttt{-} 作为分隔符，那么负数单独使用时，就需要把它放在两层花括号之内
% （例如 |{{-1}}|），或者使用类似 |{-1}-{-1}| 的形式才不会解释错误。
%    \begin{macrocode}
\NewDocumentCommand \ctex_ltj_set_alternate_shape:nnn
  { m m > { \SplitArgument { 1 } { -> } } m }
  { \ctex_ltj_set_alternate_shape:nnnn {#1} {#2} #3 }
\cs_new_protected:Npn \ctex_ltj_set_alternate_shape:nnnn #1#2#3#4
  {
    \ctex_ltj_set_alternate_shape:e
      {
        \@@_range_normalization:nn {#3} {#4}
        { \CJK@encoding / \exp_not:n {#2} }
        { \CJK@encoding / \exp_not:n {#1} }
      }
  }
\cs_new_protected:Npn \ctex_ltj_set_alternate_shape:n #1
  {
    \ctex_ltj_set_alt_font:nnnn #1
    \@@_save_alternate_shape:cn
      { \@@_alternate_cs:n { reset / \l_@@_base_CJKfamily_tl } }
      { \ctex_ltj_set_alt_font:nnnn #1 }
  }
\cs_generate_variant:Nn \ctex_ltj_set_alternate_shape:n { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_set_alternate_shape:nnN}
% 若字符范围预先由 \texttt{declarecharrange} 声明，则可以直接使用。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_alternate_shape:nnN #1#2#3
  {
    \tl_map_inline:Nn #3
      {
        \ctex_ltj_set_alternate_shape:n
          {
            ##1
            { \CJK@encoding/#2 }
            { \CJK@encoding/#1 }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_save_alternate_shape:Nn}
% 将实际设置的替换字形保存起来用于清除或恢复。
% 暂时令 \cs{l_@@_base_family_tl} 为 \cs{scan_stop:} 是让它不被展开，使得替换
% 字体的设置可以在 \tn{addCJKfontfeature} 中直接使用。
%    \begin{macrocode}
\cs_new_protected:Npn \@@_save_alternate_shape:Nn #1#2
  {
    \group_begin:
      \cs_if_exist:NF #1 { \cs_set_eq:NN #1 \prg_do_nothing: }
      \cs_set_eq:NN \l_@@_base_family_tl \scan_stop:
      \cs_gset_protected:Npx #1 { \exp_not:o {#1} #2 }
    \group_end:
  }
\cs_generate_variant:Nn \@@_save_alternate_shape:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{clearalternatefont,resetalternatefont}
% 清除和重置操作总是全局的。
%    \begin{macrocode}
\ctex_define:n
  {
    clearalternatefont    .code:n =
      { \clist_map_function:eN {#1} \ctex_ltj_clear_alternate_font:n } ,
    resetalternatefont    .code:n =
      { \clist_map_function:eN {#1} \ctex_ltj_reset_alternate_font:n } ,
    clearalternatefont .default:n = \l_ctex_ltj_family_tl ,
    resetalternatefont .default:n = \l_ctex_ltj_family_tl
  }
\cs_new_protected:Npn \ctex_ltj_clear_alternate_font:n #1
  {
    \group_begin:
      \ctex_ltj_family_if_exist:eNTF {#1} \l_@@_base_family_tl
        {
          \cs_if_exist_use:cT
            { \@@_alternate_cs:n { clear / #1 } }
            {
              \prop_gput:Nno \g_@@_reset_alternate_prop
                {#1} { \l_@@_base_family_tl }
              \tl_set_eq:NN \CJK@family \l_@@_base_family_tl
              \selectfont
            }
        }
        { \@@_family_unknown_warning:n {#1} }
    \group_end:
  }
\cs_new_protected:Npn \ctex_ltj_reset_alternate_font:n #1
  {
    \group_begin:
      \prop_gpop:NnNT \g_@@_reset_alternate_prop {#1} \CJK@family
        {
          \tl_set_eq:NN \l_@@_base_family_tl \CJK@family
          \use:c { \@@_alternate_cs:n { reset / #1 } }
          \selectfont
        }
    \group_end:
  }
\prop_new:N \g_@@_reset_alternate_prop
\cs_generate_variant:Nn \clist_map_function:nN { e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{declarecharrange}
% 预先声明字符范围。
%    \begin{macrocode}
\ctex_define:n
  {
    declarecharrange .code:n = \ctex_ltj_declare_char_range:e {#1} ,
    declarecharrange .value_required:n = true
  }
\cs_new_protected:Npn \ctex_ltj_declare_char_range:n #1
  { \clist_map_inline:nn {#1} { \@@_declare_char_range:nn ##1 } }
\cs_generate_variant:Nn \ctex_ltj_declare_char_range:n { e }
\cs_new_protected:Npn \@@_declare_char_range:nn #1
  { \tl_trim_spaces_apply:nN {#1} \ctex_ltj_declare_char_range:nn }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_declare_char_range:nn}
% \begin{variable}{\g_@@_char_range_prop}
% |#1| 是名字，|#2| 是范围。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_declare_char_range:nn #1#2
  {
    \tl_clear:N \l_@@_char_range_tl
    \clist_map_function:nN {#2} \ctex_ltj_save_char_range:n
    \prop_gput:Nno \g_@@_char_range_prop {#1} { \l_@@_char_range_tl }
    \ctex_ltj_def_char_range_key:n {#1}
    \tl_clear:N \l_@@_char_range_tl
  }
\tl_new:N \l_@@_char_range_tl
\prop_new:N \g_@@_char_range_prop
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_save_char_range:n}
% 预先解释字符区间的意义。
%    \begin{macrocode}
\NewDocumentCommand \ctex_ltj_save_char_range:n
  { > { \SplitArgument { 1 } { -> } } m }
  { \ctex_ltj_save_char_range:nn #1 }
\cs_new_protected:Npn \ctex_ltj_save_char_range:nn #1#2
  {
    \tl_put_right:Ne \l_@@_char_range_tl
      { { \@@_range_normalization:nn {#1} {#2} } }
  }
\cs_new:Npn \@@_range_normalization:nn #1#2
  {
    \tl_if_novalue:nTF {#2}
      {
        { \int_eval:n {#1} }
        { \int_eval:n {#1} }
      }
      {
        { \int_eval:n { \tl_if_blank:nTF {#1} { \c_@@_range_min_int } {#1} } }
        { \int_eval:n { \tl_if_blank:nTF {#2} { \c_@@_range_max_int } {#2} } }
      }
  }
\int_const:Nn \c_@@_range_min_int { "80 }
\int_const:Nn \c_@@_range_max_int { \c_max_char_int }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_def_char_range_key:n}
% 在字体设置选项中定义字符范围键。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_def_char_range_key:n #1
  {
    \keys_if_exist:nnF { ctex_ltj / fontspec } {#1}
      {
        \keys_define:nn { ctex_ltj / fontspec }
          { #1 .code:n = \ctex_ltj_char_range_key:nn {#1} { ##1 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_ltj_char_range_key:nn}
% 如果字符范围键没有值，则只设置的这个字符范围内的替代字体。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_char_range_key:nn #1#2
  {
    \tl_if_blank:nTF {#2}
      { \clist_set:Nn \l_@@_char_range_clist {#1} }
      { \@@_push_alternate_prop:nn {#1} { {#1} #2 } }
  }
%    \end{macrocode}
% \end{macro}
%
% \paragraph{其他设置}
%
% 在抄录环境中禁用 \opt{autospacing} 和 \opt{autoxspacing}。然而，\pkg{LuaTeX-ja}
% 还是会使 JAchar 自动折行。没有看到有简单的禁用折行的办法，可能需要设置所有的
% JAchar 的 \opt{prebreakpenalty} 或 \opt{postbreakpenalty} 为 \texttt{10000}：
% \begin{verbatim}
%   \directlua
%     {
%       luatexja.isglobal = tex.globaldefs > 0 and "global" or ""
%       for i = 0x80, 0x10FFFF do
%         if luatexja.charrange.jcr_table_main[i] > 0 and
%            luatexja.charrange.jcr_table_main[i] < 218 and
%            luatexja.charrange.is_japanese_char_curlist(i) then
%           luatexja.stack.set_stack_table(luatexja.stack_table_index.PRE + i, 10000)
%         end
%       end
%     }
% \end{verbatim}
%    \begin{macrocode}
\AtBeginDocument
  {
    \ctex_appto_cmd:NnnTF \verbatim@font
      { \char_set_catcode_letter:n { 64 } }
      { \CTEX@verbatim@font@hook }
      { }
      { \ctex_patch_failure:N \verbatim@font }
  }
\cs_new_protected:Npn \CTEX@verbatim@font@hook
  { \ltjsetparameter { autospacing = false , autoxspacing = false } }
%    \end{macrocode}
%
% \changes{v2.6.0}{2026/04/27}{修复内联 \tn{verb} 前 \opt{xkanjiskip} 丢失的问题
%   （移植 \pkg{lltjcore} 对 \tn{verb} 和 \tn{do@noligs} 的修复）。}
%
% \pkg{LuaTeX-ja} 的 \file{lltjcore.sty} 会将 \LaTeX{} 的 \tn{verb} 中
% \tn{null}（即 |\hbox{}|）替换为 |\vadjust{}|，因为空 |\hbox{}| 会阻断
% \opt{xkanjiskip} 的自动插入。由于 \pkg{ctex} 禁用了 \pkg{ltj-latex}，
% 这里需要自行复制该修复。
%    \begin{macrocode}
\@namedef { ver@lltjcore.sty } { }
\if@compatibility \else
  \def \verb
    {
      \relax \ifmmode \hbox \else \leavevmode \vadjust { } \fi
      \bgroup
        \verb@eol@error \let \do \@makeother \dospecials
        \verbatim@font \@noligs
        \language \l@nohyphenation
        \@ifstar \@sverb \@verb
    }
\fi
\patchcmd { \do@noligs } { \kern \z@ } { \vadjust { } } { } { }
%    \end{macrocode}
%
% \begin{macro}{\@@@@italiccorr}
% \LaTeX{} 的倾斜校正也要重新定义。
%    \begin{macrocode}
\cs_set_eq:NN \@@@@italiccorr \/
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.4.12}{2018/01/27}{正确使用 \tn{ltjsetkanjiskip} 和 \tn{ltjsetxkanjiskip}。}
%
% \begin{macro}[int]{\ctex_ltj_set_kanjiskip:N,\ctex_ltj_set_xkanjiskip:N}
% \tn{ltjsetkanjiskip} 和 \tn{ltjsetxkanjiskip} 是相应的 \tn{ltjsetparameter}
% 的快捷方式，在使用他们时，要注意先使用 \tn{ltj@setpar@global}。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_ltj_set_kanjiskip:N
  { \ltj@setpar@global \ltjsetkanjiskip }
\cs_new_protected:Npn \ctex_ltj_set_xkanjiskip:N
  { \ltj@setpar@global \ltjsetxkanjiskip }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%<@@=ctex>
%</luatex>
%    \end{macrocode}
%
% \subsubsection{\pkg{ctex-engine-uptex.def}}
%
%    \begin{macrocode}
%<*uptex|aptex>
%    \end{macrocode}
%
% \changes{v2.4}{2016/02/28}{初步支持 \upLaTeX。}
% \changes{v2.4.15}{2019/04/05}{显式补丁 \upLaTeX 的 \tn{rmfamily} 等字体命令。}
%
% 按 \pkg{CJK} 的命名习惯模拟 \tn{CJKfamily}。
%    \begin{macrocode}
\NewDocumentCommand \CJKfamily { m }
  { \kanjifamily {#1} \selectfont }
%    \end{macrocode}
%
% \changes{v2.4.15}{2019/04/05}{将 \upLaTeX 的默认字体由 \texttt{mc} 改为
%   \texttt{zhrm}，并启用 \tn{jfam}。}
%
% 将 \upLaTeX 的默认字体由 |mc| 改为 |zhrm|，并启用 \tn{jfam}。
%    \begin{macrocode}
\DeclareErrorKanjiFont    {JY2}{zhrm}{m}{n}{10}
\DeclareKanjiSubstitution {JY2}{zhrm}{m}{n}
\DeclareKanjiSubstitution {JT2}{zhrm}{m}{n}
\DeclareSymbolFont{mincho}{JY2}{zhrm}{m}{n}
\SetSymbolFont{mincho}{bold}{JY2}{zhrm}{bx}{n}
\jfam \symmincho
%    \end{macrocode}
%
% \begin{macro}[int]{\em}
% \changes{v2.4.2}{2016/05/15}{兼容 \upLaTeX{} 2016/05/07u00 的定义。}
% 取消 \upLaTeX{} 对 \tn{em} 使用 |\mcfamily|、|\gtfamily| 命令的重定义，恢复
% \LaTeXe{} 对 \tn{em} 的原始定义。如果用户已经重定义了 \tn{em}，则新定义保持
% 不变。\upLaTeX{} 2016/05/07u00 的定义有所变化，这一行为可以由用户通过 \pkg{platexrelease}
% 包改变，需要分支处理。
%    \begin{macrocode}
\ctex_patch_cmd_once:NnnnTF \em
  { \ExplSyntaxOff }
  { \eminnershape \else \gtfamily \itshape }
  { \eminnershape \else \itshape }
  { }
  {
    \ctex_patch_cmd:Nnn \em
      { \mcfamily \upshape \else \gtfamily \itshape }
      { \eminnershape \else \itshape }
  }
\cs_set_nopar:Npn \eminnershape { \upshape }
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.4}{2016/04/24}{正确设置 \upTeX{} 下字体命令。}
% \begin{macro}[int]{\ctex_set_upfamily:nnn}
% 将 NFSS 字体族 |#1| 设置为 JFM 字体名 |#2|，粗体形式字体名 |#3|。其中字体名
% 形如 |upzhserif|，不包括表示方向的后缀 |-h| 与 |-v|。粗体字体名为空时不设置该
% 字形。本命令不设置字体映射，需要复用已有的字体映射或另行设置。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_set_upfamily:nnn #1#2#3
  {
    \DeclareKanjiFamily{JY2}{#1}{}
    \DeclareKanjiFamily{JT2}{#1}{}
    \DeclareFontShape{JY2}{#1}{m}{n}{<->~ #2-h}{}
    \DeclareFontShape{JT2}{#1}{m}{n}{<->~ #2-v}{}
    \tl_if_empty:nF {#3}
      {
        \DeclareFontShape{JY2}{#1}{b}{n}{<->~ #3-h}{}
        \DeclareFontShape{JT2}{#1}{b}{n}{<->~ #3-v}{}
        \DeclareFontShape{JY2}{#1}{bx}{n}{<->~ #3-h}{}
        \DeclareFontShape{JT2}{#1}{bx}{n}{<->~ #3-v}{}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_set_upmap:nnn}
% 设置 \upTeX{} 字体映射。|#1| 是形如 |upserif| 的 PS TFM 字体名，不带表示粗体
% 的后缀 |b| 与表示排版方向的后缀 |-h| 与 |-v|。|#2| 与 |#3| 是普通与粗体的实际
% 字体名。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_set_upmap:nnn #1#2#3
  {
    \ctex_set_zhmap:n
      {
        \special { pdf:mapline~#1-h~UniGB-UTF16-H~#2 }
        \special { pdf:mapline~#1-v~UniGB-UTF16-V~#2 }
        \tl_if_empty:nF {#3}
          {
            \special { pdf:mapline~#1b-h~UniGB-UTF16-H~#3 }
            \special { pdf:mapline~#1b-v~UniGB-UTF16-V~#3 }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_set_upmap_unicode:nnn}
% 设置 \upTeX{} 字体映射，使用 |unicode| CMap。参数同上。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_set_upmap_unicode:nnn #1#2#3
  {
    \ctex_set_zhmap:n
      {
        \special { pdf:mapline~#1-h~unicode~#2 }
        \special { pdf:mapline~#1-v~unicode~#2 }
        \tl_if_empty:nF {#3}
          {
            \special { pdf:mapline~#1b-h~unicode~#3 }
            \special { pdf:mapline~#1b-v~unicode~#3 }
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_set_upfonts:nnnnnn}
% 设置 \upTeX{} 基本字体映射，按 \pkg{zhmetrics-uptex} 的定义，依次设置衬线体
% 正、粗、意大利，无衬线体正、粗，等宽体正——共 6 种字体，并分横排及直排。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_set_upfonts:nnnnnn #1#2#3#4#5#6
  {
    \ctex_set_upmap:nnn { upserif   } {#1} {#2}
    \ctex_set_upmap:nnn { upserifit } {#3} {}
    \ctex_set_upmap:nnn { upsans    } {#4} {#5}
    \ctex_set_upmap:nnn { upmono    } {#6} {}
  }
%    \end{macrocode}
% \end{macro}
%
% 以下命令只能在导言区使用。
%    \begin{macrocode}
\@onlypreamble \ctex_set_upfamily:nnn
\@onlypreamble \ctex_set_upmap:nnn
\@onlypreamble \ctex_set_upmap_unicode:nnn
\@onlypreamble \ctex_set_upfonts:nnnnnn
%    \end{macrocode}
%
% \changes{v2.4.7}{2016/12/27}{依赖 \pkg{pxeverysel} 宏包。}
%
% \pkg{everysel} 宏包（2011/10/28）未考虑 \upLaTeX{} 对 \tn{selectfont} 的修
% 改，需要引入 \pkg{pxeverysel} 宏包。
%    \begin{macrocode}
\bool_if:NT \c_@@_everysel_loaded_bool
  { \RequirePackage { pxeverysel } }
%    \end{macrocode}
%
%    \begin{macrocode}
%</uptex|aptex>
%    \end{macrocode}
%
% \subsubsection{修改主要字体命令}
%
% \changes{v2.5.2}{2020/05/06}{兼容 \LaTeX\ 2020-02-02 之前的版本。}
% \changes{v2.5.4}{2020/06/07}{修正主要字体命令补丁。}
% \changes{v2.6.0}{2026/04/24}{\textbf{Breaking:}
%   移除对 \LaTeX\ 2020/10/01 之前版本的字体钩子兼容代码。
%   现在需要 \LaTeX\ 2020/10/01 或更新版本（\#746）。}
%
% 修改 \tn{rmfamily} 等主要字体命令，使得中文字体能随西文主要字体更新。
% 使用 \LaTeX\ 2020/10/01 提供的 \texttt{rmfamily} 等 \pkg{NFSS} 钩子。
%
% \pkg{xeCJK} 和 \pkg{zhmCJK} 已经有相同的工作，本段代码不需要对他们使用。
%    \begin{macrocode}
%<*pdftex|luatex|uptex|aptex>
%<pdftex>\reverse_if:N \if_bool:N \g_@@_zhmCJK_bool
%    \end{macrocode}
%
% \changes{v2.5.5}{2020/10/17}{进一步应用 \LaTeX\ 2020/10/01 的新钩子。}
%
% \begin{macro}[int]{\ctex_provide_font_hook:NNN,\CTEX@rmfamilyhook}
% 给 \tn{rmfamily} 等字体命令加钩子，钩子名字统一为 \tn{CTEX@rmfamilyhook} 等。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_provide_font_hook:NNN #1#2
  {
    \exp_args:Nc \@@_provide_font_hook_aux:NNNN
      { CTEX \cs_to_str:N #2 } #1#2
  }
\cs_new_protected:Npn \@@_provide_font_hook_aux:NNNN #1#2#3#4
  {
    \tl_new:N #1
    \exp_args:Ne \ctex_gadd_ltxhook:nn { \cs_to_str:N #2 } {#1}
  }
\ctex_provide_font_hook:NNN \rmfamily \@rmfamilyhook \selectfont
\ctex_provide_font_hook:NNN \sffamily \@sffamilyhook \selectfont
\ctex_provide_font_hook:NNN \ttfamily \@ttfamilyhook \selectfont
%<pdftex|luatex>\ctex_provide_font_hook:NNN \normalfont \@defaultfamilyhook \usefont
%    \end{macrocode}
% \end{macro}
%
% 按 \pkg{CJK} 的命名习惯模拟部分命令，并设置默认字体。
%    \begin{macrocode}
\tl_if_exist:NF \CJKfamilydefault
  { \tl_const:Nn \CJKfamilydefault { \CJKrmdefault } }
%<*pdftex|luatex>
\tl_if_exist:NF \CJKrmdefault { \tl_const:Nn \CJKrmdefault { rm } }
\tl_if_exist:NF \CJKsfdefault { \tl_const:Nn \CJKsfdefault { sf } }
\tl_if_exist:NF \CJKttdefault { \tl_const:Nn \CJKttdefault { tt } }
\tl_gput_right:Nn \CTEX@rmfamilyhook { \CJKfamily { \CJKrmdefault } }
\tl_gput_right:Nn \CTEX@sffamilyhook { \CJKfamily { \CJKsfdefault } }
\tl_gput_right:Nn \CTEX@ttfamilyhook { \CJKfamily { \CJKttdefault } }
\tl_gput_right:Nn \CTEX@defaultfamilyhook { \CJKfamily { \CJKfamilydefault } }
%</pdftex|luatex>
%    \end{macrocode}
% \upLaTeX 不需要补丁 \tn{normalfont}，只需要修改 \tn{kanjifamilydefault}。
%    \begin{macrocode}
%<*uptex|aptex>
\tl_if_exist:NF \CJKrmdefault { \tl_const:Nn \CJKrmdefault { zhrm } }
\tl_if_exist:NF \CJKsfdefault { \tl_const:Nn \CJKsfdefault { zhsf } }
\tl_if_exist:NF \CJKttdefault { \tl_const:Nn \CJKttdefault { zhtt } }
\tl_gput_right:Nn \CTEX@rmfamilyhook { \kanjifamily { \CJKrmdefault } }
\tl_gput_right:Nn \CTEX@sffamilyhook { \kanjifamily { \CJKsfdefault } }
\tl_gput_right:Nn \CTEX@ttfamilyhook { \kanjifamily { \CJKttdefault } }
\tl_gset:Nn \kanjifamilydefault { \CJKfamilydefault }
%</uptex|aptex>
%    \end{macrocode}
%
% \pkg{zhmCJK} 判断结束。
%    \begin{macrocode}
%<pdftex>\fi:
%    \end{macrocode}
%
% 使修改立刻生效，保证导言区字体族正确。
%    \begin{macrocode}
\normalfont
%    \end{macrocode}
%
% 在导言区末尾更新 \tn{CJKfamilydefault}，\pdfTeX 已经在之前使用过此处代码。
%    \begin{macrocode}
%<!pdftex>\ctex_at_end_preamble:n { \ctex_update_default_family: }
%    \end{macrocode}
%
% \changes{v2.4}{2016/02/15}{正确更新 \pkg{CJK} 包的 \tn{CJKfamilydefault}。}
% \changes{v2.4.1}{2016/04/26}{正确更新 \upLaTeX{} 的 \tn{CJKfamilydefault}。}
%
% \begin{macro}[int]{\ctex_update_default_family:}
% 在导言区结束，如果 \tn{CJKfamilydefault} 没有被更改，则在此时根据西文字体的情况
% 更新 \tn{CJKfamilydefault}。\pkg{xeCJK} 已经有这个功能，不需要再调整。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_update_default_family:
  {
    \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:
      }
%    \end{macrocode}
% 使用 \LuaLaTeX{} 时，自动调整得到的 \tn{CJKfamilydefault} 可能没有定义，需要
% 确认它的存在性。使用 \pkg{CJK} 宏包或 \upLaTeX{}
% 时，\texttt{C19rm}、\texttt{JY2rm} 等总是有定义的，不需要确认。
%    \begin{macrocode}
%<luatex>    \ctex_ltj_ensure_default_family:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_family_default_init_tl}
% 往 \tn{CJKfamilydefault} 中加入标志，用于判断它是否被更改。
%    \begin{macrocode}
\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
      { \exp_not:o { \CJKfamilydefault } }
  }
\tl_gset_eq:NN \CJKfamilydefault \l_@@_family_default_init_tl
%    \end{macrocode}
% \end{variable}
%
%    \begin{macrocode}
%</pdftex|luatex|uptex|aptex>
%    \end{macrocode}
%
% \changes{v2.0}{2014/04/16}{自动检测操作系统，载入对应的字体配置。}
% \changes{v2.5}{2019/10/25}{操作系统检测移动至载入中文字库处，且不再需要
%   依赖特定引擎。}
%
% \subsubsection{\pkg{hyperref} 兼容性处理}\label{subsubsec:hyperref}
%
% \changes{v2.5.7}{2021/06/06}{禁用 \dvipdfmx\ 驱动的 \opt{unicode} 书签设置。}
%
% 在 \pdfTeX{} 下使用 \texttt{GBK} 编码，\dvipdfmx{} 驱动可以直接用它的
% \tn{special} 命令，其他模式用 \pkg{xCJK2uni} 宏包处理。使用 \texttt{UTF-8} 编
% 码时，\pkg{CJKutf8} 已经处理了书签问题，但仍需要设置 \opt{pdfencoding} 为
% \opt{unicode}，目的是在书签的开头写入 BOM (|\376\377|)，提示这是
% \texttt{UTF-16BE} 字节流。
% \pkg{hyperref} 2021-02-04 版开始默认设置 \opt{unicode} 为 \opt{true}，
% 对于 \dvipdfmx\ 驱动，我们需要禁用这个设置，为此设置 \opt{pdfencoding} 为 \opt{pdfdoc}。
%
% \changes{v2.6.0}{2026/04/25}{修复 \pkg{hyperref} 在 \pkg{ctex}
%   之前被加载时 \opt{driverfallback} 选项重复设置的警告（\#715）。}
%
% \opt{driverfallback} 是 \pkg{hyperref} 的加载选项，加载后即被禁用，不能通过
% \tn{hypersetup} 设置。因此当 \pkg{hyperref} 已被其他宏包（如 \cls{beamer}）
% 加载时，只能跳过此选项。
%    \begin{macrocode}
%<*pdftex>
\@ifpackageloaded { hyperref }
  { }
  { \PassOptionsToPackage { driverfallback = dvipdfmx } { hyperref } }
\str_if_eq:onTF { \g_@@_encoding_tl } { GBK }
  {
    \ctex_hypersetup:n { CJKbookmarks = true }
    \sys_if_output_pdf:TF
      { \ctex_at_end_package:nn { hyperref } { \RequirePackage { xCJK2uni } } }
      {
        \ctex_hypersetup:n { pdfencoding = pdfdoc }
        \ctex_at_end_package:nn { hyperref }
          {
            \str_if_eq:onTF { \Hy@driver } { hdvipdfm }
              {
                \ctex_at_shipout_first:n
                  { \special { pdf:tounicode~GBK-EUC-UCS2 } }
              }
              { \RequirePackage { xCJK2uni } }
          }
      }
  }
  { \ctex_hypersetup:n { pdfencoding = unicode } }
%</pdftex>
%    \end{macrocode}
% \XeTeX 和 \LuaTeX 统一设置 \opt{pdfencoding} 为 \opt{unicode}。
%    \begin{macrocode}
%<*xetex|luatex>
\ctex_hypersetup:n { pdfencoding = unicode }
%</xetex|luatex>
%    \end{macrocode}
%
% 我们假定 \upTeX{} 使用 \dvipdfmx{} 驱动输出，于是使用与 \pdfTeX{} 类似的设
% 置。注意 \upTeX{} 需要使用 UTF8-UTF16 的编码转换。
%    \begin{macrocode}
%<*uptex|aptex>
\@ifpackageloaded { hyperref }
  { \ctex_hypersetup:n { pdfencoding = pdfdoc } }
  {
    \ctex_hypersetup:n
      {
        driverfallback = dvipdfmx ,
        pdfencoding    = pdfdoc
      }
  }
\ctex_at_end_package:nn { hyperref }
  {
    \ctex_at_shipout_first:n
      { \special { pdf:tounicode~UTF8-UTF16 } }
  }
%</uptex|aptex>
%    \end{macrocode}
%
%    \begin{macrocode}
%<*pdftex|xetex|luatex|uptex|aptex>
%    \end{macrocode}
%
% \subsubsection{\pkg{CJKfntef}、\pkg{xeCJKfntef} 相关设置}
%
% \changes{v2.0}{2015/03/25}{默认关闭 \pkg{CJKfntef} 或 \pkg{xeCJKfntef} 的彩
% 色设置。}
% \changes{v2.0}{2015/03/25}{\tn{CTEXunderdot}、\tn{CTEXunderline}、
% \tn{CTEXunderdblline}、\tn{CTEXunderwave}、\tn{CTEXsout}、\tn{CTEXxout} 是过
% 时命令；\env{CTEXfilltwosides} 是过时环境。}
% \changes{v2.5}{2019/11/10}{不再自动载入 \pkg{CJKfntef} 或 \pkg{xeCJKfntef} 宏包。}
% \changes{v2.5}{2019/11/10}{移除 \tn{CTEXunderdot}、\tn{CTEXunderline}、
% \tn{CTEXunderdblline}、\tn{CTEXunderwave}、\tn{CTEXsout}、\tn{CTEXxout}、
% \env{CTEXfilltwosides} 等命令和环境。}
%
% 对 \pdfTeX{} 与 \XeTeX{} 引擎，分别在 \pkg{CJKfntef}、\pkg{xeCJKfntef} 宏包
% 的末尾关闭彩色显式等多余格式。
%
%    \begin{macrocode}
%<*pdftex>
\ctex_at_end_package:nn { CJKfntef }
  {
    \normalem
    \cs_new_protected:Npn \@@_clear_fntef_color:n #1
      { \tl_clear:c { CJK#1color } }
%</pdftex>
%<*xetex>
\ctex_at_end_package:nn { xeCJKfntef }
  {
    \@ifpackagelater { xeCJKfntef } { 2014/11/04 }
      {
        \cs_new_protected:Npn \@@_clear_fntef_color:n #1
          { \xeCJKsetup { #1 / format = { } } }
      }
      {
        \cs_new_protected:Npn \@@_clear_fntef_color:n #1
          { \tl_clear:c { CJK#1color } }
      }
%</xetex>
%<*pdftex|xetex>
    \clist_map_inline:nn
      { underdot , underline , underdblline , underwave , sout , xout }
      { \@@_clear_fntef_color:n {#1} }
  }
%</pdftex|xetex>
%    \end{macrocode}
%
% \subsubsection{\tn{ccwd} 的更新}
%
% \begin{macro}[int]{\ctex_update_ccwd:,\ccwd}
% \changes{v2.4.1}{2016/04/29}{正确设置 \upTeX{} 下的 \tn{ccwd}。}
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_update_ccwd:
%<*pdftex|xetex>
  {
    \hbox_set:Nn \l_@@_tmp_box { \CJKglue }
    \dim_set:Nn \ccwd { \box_wd:N \l_@@_tmp_box + \f@size \p@ }
  }
%</pdftex|xetex>
%<*luatex>
  { \skip_set:Nn \ccwd { \ltjgetparameter { kanjiskip } + \zw } }
%</luatex>
%<*uptex|aptex>
  { \skip_set:Nn \ccwd { 1zw + \tex_kanjiskip:D } }
%</uptex|aptex>
\dim_new:N \ccwd
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_update_ccglue:}
% 更新字间距。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_update_ccglue:
%<*pdftex|xetex>
  {
    \cs_set_protected:Npn \CJKglue
      { \skip_horizontal:N \l_@@_ccglue_skip }
    \cs_if_exist:NT \@@_ccglue:
      { \cs_set_eq:NN \@@_ccglue: \CJKglue }
  }
%</pdftex|xetex>
%<*luatex>
  { \ctex_ltj_set_kanjiskip:N \l_@@_ccglue_skip }
%</luatex>
%<*uptex|aptex>
  { \skip_set_eq:NN \tex_kanjiskip:D \l_@@_ccglue_skip }
%</uptex|aptex>
\skip_new:N \l_@@_ccglue_skip
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int,pTF]{\ctex_if_ccglue_touched:}
% 检查用户是否修改过汉字间距。
%
% \changes{v2.6.5}{2026/08/11}{\tn{selectfont} 不再重置用户自行设置的汉字间距
%   （\#1068）。}
%
% 后面重定义 \cs{@@_update_stretch_auxii:} 的那段\emph{必须对所有引擎生效}。
% \cs{ctex_update_stretch:} 分两支：\opt{linestretch} 设为 \tn{maxdimen} 时走
% \cs{@@_update_stretch_auxi:}，那一支自带本守卫；否则走
% \cs{@@_update_stretch_auxiii:}，而它没有守卫、会直接重设间距。默认
% \opt{linestretch} 是 \tn{ccwd} 而不是 \tn{maxdimen}，所以实际走的是后一支——
% 这段重定义正是给后一支补上守卫的地方。
%
% \#1068 之前这段被 docstrip 守卫限定在 \pdfTeX{} 与 \XeTeX{} 上，于是 \LuaTeX{} 与
% \upTeX{} 下用户设过的间距会被随后的 \tn{selectfont} 覆盖（报告只提到 \LuaTeX{}，
% 实测 \upTeX{} 同样如此）。判据是解包产物里 \cs{@@_update_stretch_auxii:} 的重定义
% 出现次数：修好之前 \file{ctex-engine-xetex.def} 里有一处，
% \file{ctex-engine-luatex.def} 与 \file{ctex-engine-uptex.def} 里都是零处。
%
% 上面这个条件本身各引擎的实现不同（\pdfTeX{} 与 \XeTeX{} 比较 \tn{CJKglue} 是否仍与
% \cs{@@_ccglue:} 同义，\LuaTeX{} 与 \upTeX{} 比较 \cs{l_@@_ccglue_skip} 与引擎参数
% 是否相等），但三者都在，不需要另写；缺的只是调用它的那个位置。
%
% 修好之后，用户设过间距的文档里 \tn{ccwd} 会随之变大（\cs{ctex_update_ccwd:} 算的是
% “间距加汉字宽度”），\tn{parindent} 因为默认取 \opt{autoindent} 的 |2\ccwd| 也跟着
% 变宽。这是手册承诺的语义而非本次引入的副作用：手册对 \tn{ccwd} 的定义是“相邻两个
% 汉字中心之间的距离，包含字距在内。因此修改字距会间接修改字宽”，\tn{ziju} 一节也重申
% “这个命令会影响 \tn{ccwd” 的值}。
%
% 判据是跨引擎跨版本的对照：\XeTeX{} 在\emph{未修改的} v2.6.4 上，同一份样例的
% \tn{parindent} 就已经是 \texttt{41.07874pt}（\tn{ccwd} \texttt{20.53937pt}），与本次修好后
% \LuaTeX{} 的读数一致。\LuaTeX{} 先前之所以停在 \texttt{21.07874pt}，正是因为间距被错误重置、
% \tn{ccwd} 也就没变。所以这里是把 \LuaTeX{} 与 \upTeX{} 对齐到既有行为，不是改变它。
%    \begin{macrocode}
\prg_new_conditional:Npnn \ctex_if_ccglue_touched: { TF }
  {
%<*pdftex|xetex>
    \if_meaning:w \CJKglue \@@_ccglue:
      \prg_return_false: \else: \prg_return_true: \fi:
%</pdftex|xetex>
%<*luatex>
    \skip_if_eq:nnTF { \l_@@_ccglue_skip } { \ltjgetparameter { kanjiskip } }
      { \prg_return_false: } { \prg_return_true: }
%</luatex>
%<*uptex|aptex>
    \skip_if_eq:nnTF { \l_@@_ccglue_skip } { \tex_kanjiskip:D }
      { \prg_return_false: } { \prg_return_true: }
%</uptex|aptex>
  }
%<pdftex|xetex>\ctex_at_end:n { \cs_new_eq:NN \@@_ccglue: \CJKglue }
\ctex_at_end:n
  {
    \cs_set_protected:Npn \@@_update_stretch_auxii:
      {
        \ctex_if_ccglue_touched:TF
          { \ctex_update_ccwd: }
          { \@@_update_stretch_auxiii: }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[int]{\ctex_update_em_unit:}
% 将当前汉字的宽度保存到 \tn{ccwd} 中备用。不采用 \texttt{1em}，因为这时的
% \texttt{1em} 实际上来自西文字体的信息，未必等于汉字的宽度，这似乎在传统的
% \file{.tfm} 字体上表现更明显。在 \pdfTeX{} 和 \XeTeX{} 下，直接使用 |\f@size\p@|
% 作为汉字的宽度，这应该对大多数汉字字体都成立，但不适用于诸如“方正兰亭黑长”之类
% 的特殊字体。在 \XeTeX{} 可以用 \tn{fontcharwd} 来改进。而在 \pdfTeX{} 下，若使用
% \pkg{zhmetrics} 技术，所有的汉字共享同一个 \file{.tfm}，\tn{fontcharwd} 也就没有
% 意义。在 \LuaTeX{} 下，\pkg{LuaTeX-ja} 总是按照 JFM 中的设置输出汉字的宽度，可以
% 直接用 \tn{zw} 作为汉字宽度。\upTeX{} 可以直接使用原生的长度单位 |zw|。
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_update_em_unit:
%<pdftex|xetex>  { \dim_set:Nn \ccwd { \f@size \p@ } }
%<luatex>  { \dim_set:Nn \ccwd { \zw } }
%<uptex|aptex>  { \dim_set:Nn \ccwd { 1zw } }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{其他}
%
% \begin{macro}[int]{\ctex_add_to_selectfont:n,\CTEX@selectfont@hook}
% \changes{v2.4.13}{2018/03/23}{修正导言区 \tn{selectfont} 钩子位置。}
% \changes{v2.5}{2020/04/19}{兼容 \pkg{KOMA-Script} 的 \tn{selectfont} 补丁。}
%    \begin{macrocode}
\cs_new_protected:Npn \ctex_add_to_selectfont:n #1
  {
    \cs_set_protected:Npx \CTEX@selectfont@hook
      { \exp_not:o { \CTEX@selectfont@hook #1 } }
  }
\cs_new_eq:NN \CTEX@selectfont@hook \prg_do_nothing:
%    \end{macrocode}
% 使用 \pkg{everysel} 包的情况。
% \tn{EverySelectfont} 直到文档开始时才有效。为了 \tn{ccwd} 和 \pkg{LuaTeX-ja} 的
% 字体设置在导言区也可用，我们还需要在这里手工修改 \tn{selectfont}。\pkg{everysel}
% 宏包会用 \tn{CheckCommand} 来检查 \tn{selectfont} 是否为标准定义。我们修改了
% \tn{selectfont}，所以会给出一个警告。为了消除这个警告，在它检查之前，还原本来
% 定义。\pkg{pxeverysel} 宏包取消了检查，但也需要恢复定义，避免重复使用钩子。
% \pkg{KOMA-Script} 宏包也会进行检查，我们需要小心处理。
%    \begin{macrocode}
\bool_if:NTF \c_@@_everysel_loaded_bool
  {
    \cs_if_free:NF \@EverySelectfont@Init
      {
        \group_begin:
        \cs_set:Npn \@@_tmp:N #1
          {
            \tl_set:Nn \l_@@_tmp_tl {#1}
            \cs_new_eq:NN \CTEX@selectfont@save #1
            \cs_new_protected:Npn \@@_restore_selectfont:
              {
                \cs_if_free:NF \scr@new@selectfont
                  {
%    \end{macrocode}
% \pkg{CJK} 直接修改 \tn{selectfont} 和 \pkg{pxeverysel} 的补丁，
% 会使 \pkg{KOMA-Script} 的 \tn{par@update} 失效。
%    \begin{macrocode}
%<*pdftex|uptex|aptex>
                    \cs_if_free:NF \par@update
%<*uptex|aptex>
                      {
                        \tl_put_right:Nn \@EverySelectfont@Init
                          { \tl_put_right:Nn #1 { \par@update } }
                      }
%</uptex|aptex>
%<*pdftex>
                      { \tl_put_right:Nn #1 { \par@update } }
                    \cs_set_eq:NN \scr@selectfont \CTEX@selectfont@save
%</pdftex>
%</pdftex|uptex|aptex>
                    \cs_set_eq:NN \scr@new@selectfont #1
%<!pdftex>                    \cs_set_eq:NN \CTEX@selectfont@save \scr@selectfont
                  }
                \tl_put_left:Nn \@EverySelectfont@Init
                  { \cs_set_eq:NN #1 \CTEX@selectfont@save }
                \cs_undefine:N \@@_restore_selectfont:
              }
          }
        \ctex_parse_name:NN \@@_tmp:N \selectfont
      \exp_last_unbraced:NNo \group_end:
      \ctex_patch_cmd_once:NnnnTF { \l_@@_tmp_tl }
        { \ExplSyntaxOff }
        { \size@update }
        { \CTEX@selectfont@hook \size@update }
        { \@@_restore_selectfont: }
        { \ctex_patch_failure:N \selectfont }
      }
    \cs_new_protected:Npn \ctex_gadd_selectfont_hook:n
      { \EverySelectfont }
  }
%    \end{macrocode}
% 使用 \LaTeX \ 2021-06-01 的新钩子，不使用 \pkg{everysel} 包的情况。
%    \begin{macrocode}
  {
    \cs_new_protected:Npn \ctex_gadd_selectfont_hook:n
      { \ctex_gadd_ltxhook:nn { selectfont } }
  }
%    \end{macrocode}
% \end{macro}
%
% \tn{CJK@plane} 有定义，说明处于 \pkg{CJK} 宏包的 \tn{CJKsymbol} 之内，不必使用钩子。
%    \begin{macrocode}
%<*pdftex>
\ctex_gadd_selectfont_hook:n
  { \cs_if_exist:NF \CJK@plane { \CTEX@selectfont@hook } }
%</pdftex>
%<*xetex|luatex|uptex|aptex>
\ctex_gadd_selectfont_hook:n { \CTEX@selectfont@hook }
%</xetex|luatex|uptex|aptex>
%    \end{macrocode}
%
% Attribute 寄存器 \tn{ltj@curjfnt} 的初始值是 $-1$，必须把它设置为一个有效的
% \texttt{font.id}，否则编译时会直接退出。
%    \begin{macrocode}
%<*luatex>
\ctex_add_to_selectfont:n
  {
    \ctex_ltj_select_font:
    \ctex_ltj_select_alternate_font:
  }
\tl_set:Nn \CJK@family { song } \selectfont
\tl_clear:N \CJK@family
%</luatex>
%    \end{macrocode}
%
% \changes{v2.4.1}{2016/05/01}{随字体更新 \upTeX{} 的 \tn{xkanjiskip}。}
%
% \begin{macro}[int]{\ctex_update_xkanjiskip:}
% \begin{variable}{\l_@@_xkanjiskip_skip}
% \upTeX{} 和 \pkg{LuaTeX-ja} 对 \tn{xkanjiskip} 都是即时赋值。单位 \opt{zw} 与字体相关，因此
% 需要每次 \tn{selectfont} 的时候更新一次 \tn{xkanjiskip}。如果用户设置过
% \tn{xkanjiskip}，就不更新。注意，同 \TeX{} 的 \tn{baselineskip} 一样，如果在
% 一个段落内多次设置了 \tn{kanjiskip} 或 \tn{xkanjiskip}，只有最后的设置会影响
% 全段。
%    \begin{macrocode}
%<*luatex|uptex|aptex>
\cs_new_protected:Npn \ctex_update_xkanjiskip:
  {
    \skip_if_eq:nnT
%<luatex>      { \ltjgetparameter { xkanjiskip } } { \l_@@_xkanjiskip_skip }
%<uptex|aptex>      { \tex_xkanjiskip:D } { \l_@@_xkanjiskip_skip }
      {
        \skip_set:Nn \l_@@_xkanjiskip_skip { \l_@@_xkanjiskip_tl }
%<luatex>        \ctex_ltj_set_xkanjiskip:N \l_@@_xkanjiskip_skip
%<uptex|aptex>        \skip_set_eq:NN \tex_xkanjiskip:D \l_@@_xkanjiskip_skip
      }
  }
\tl_new:N \l_@@_xkanjiskip_tl
\tl_set:Nn \l_@@_xkanjiskip_tl
%<luatex>  { .25\zw plus 1pt minus 1pt }
%<uptex|aptex>  { .25zw plus 1pt minus 1pt }
\skip_new:N \l_@@_xkanjiskip_skip
\skip_set:Nn \l_@@_xkanjiskip_skip
%<luatex>  { \ltjgetparameter { xkanjiskip } }
%<uptex|aptex>  { \tex_xkanjiskip:D }
%    \end{macrocode}
% \end{variable}
% \end{macro}
%
%    \begin{macrocode}
\ctex_add_to_selectfont:n { \ctex_update_xkanjiskip: }
%</luatex|uptex|aptex>
%    \end{macrocode}
%
% \changes{v2.4.10}{2017/07/23}{定义 \tn{cht}，\tn{cdp} 和 \tn{cwd}。}
%
% \begin{macro}[int]{\cht,\cdp,\cwd,\ctex_update_kanjisize:}
% 分别从 \file{.jfm} 中读取字符高度、深度和宽度，目前仅考虑横排的情况。
%    \begin{macrocode}
%<*luatex>
\dim_new:N \cht
\dim_new:N \cdp
\dim_new:N \cwd
\group_begin:
\char_set_catcode_space:n { 32 }
\lua_now:e
  {
    local nulltable = { }
    local fmt = luatexja.jfont.font_metric_table
    local getattribute = tex.getattribute
    local setdimen = tex.setdimen
    ctex.newluacmd("ctex_update_kanjisize:", function ()
      local ft = fmt[getattribute("ltj@curjfnt")] or nulltable
      local ft = ft and ft.char_type or nulltable
      local fk = ft and ft[0] or nulltable
      setdimen("cht", fk.height or 0)
      setdimen("cdp", fk.depth or 0)
      setdimen("cwd", fk.width or ft.zw or 0)
    end, "global", "protected")
  }
\group_end:
\ctex_add_to_selectfont:n { \ctex_update_kanjisize: }
%</luatex>
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{space}
% 在导言区或正文中设置忽略空格方式。
% \pdfTeX{} 和 \XeTeX{} 下初始设置为 \opt{auto}，\LuaTeX{}、\upTeX{} 下是无效
% 选项。
%    \begin{macrocode}
\ctex_define:n
  {
%<*pdftex|xetex>
    space .choice: ,
    space / true  .code:n =
%<pdftex>      { \ctex_ignorespaces_case:N \prg_do_nothing: } ,
%<xetex>      { \xeCJKsetup { CJKspace = true } } ,
    space / auto  .code:n =
%<pdftex>      { \ctex_ignorespaces_case:N \ctex_auto_ignorespaces: } ,
%<xetex>      { \xeCJKsetup { CJKspace = false } } ,
    space / false .code:n =
%<pdftex>      { \ctex_ignorespaces_case:N \tex_ignorespaces:D } ,
%<xetex>      { \xeCJKsetup { CJKspace = false } } ,
    space .default:n = { true } ,
    space .initial:n = { auto }
%</pdftex|xetex>
%<*luatex|uptex|aptex>
    space .code:n =
      { \msg_warning:nn { ctex } { invalid-option } }
%</luatex|uptex|aptex>
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{punct}
% 在导言区或正文中设置标点符号输出格式。\pkg{LuaTeX-ja} 设置的是字体的默认 \texttt{JFM}，
% 只会影响到之后设置的字体。\upTeX{} 暂时无效。
%    \begin{macrocode}
\ctex_define:n
  {
    punct .code:n =
      {
        \tl_set:Ne \l_@@_punct_tl {#1}
%<pdftex>        \punctstyle { \l_@@_punct_tl }
%<xetex>        \xeCJKsetup { PunctStyle = \l_@@_punct_tl }
%<luatex>        \ctex_set_jfm:o { \l_@@_punct_tl }
%<uptex|aptex>        \msg_warning:nn { ctex } { invalid-option }
      } ,
    punct .default:n = { quanjiao } ,
  }
%    \end{macrocode}
% \end{macro}
%
% \changes{v2.6.0}{2026/05/03}{新增实验性 \opt{experiment/CJKecglue} 选项（\#717）。}
%
% \begin{macro}{experiment,experiment/CJKecglue}
% 实验性选项。\opt{experiment/CJKecglue} 用于统一设置 CJK 文字与西文之间的间距。
% \XeTeX{} 下转发给 \pkg{xeCJK}，\LuaTeX{} 和 \upTeX{} 下设置 |xkanjiskip|，
% \pdfTeX{} 下不支持。
%    \begin{macrocode}
\ctex_define:n
  { experiment .meta:nn = { ctex / experiment } {#1} }
\keys_define:nn { ctex / experiment }
  {
%<*xetex>
    CJKecglue .code:n =
      { \xeCJKsetup { CJKecglue = { \skip_horizontal:n {#1} } } } ,
%</xetex>
%<*luatex|uptex|aptex>
    CJKecglue .code:n =
      {
        \tl_set:Nn \l_@@_xkanjiskip_tl {#1}
        \skip_set:Nn \l_@@_xkanjiskip_skip {#1}
%<luatex>        \ctex_ltj_set_xkanjiskip:N \l_@@_xkanjiskip_skip
%<uptex|aptex>        \skip_set_eq:NN \tex_xkanjiskip:D \l_@@_xkanjiskip_skip
      } ,
%</luatex|uptex|aptex>
%<*pdftex>
    CJKecglue .code:n =
      { \msg_warning:nn { ctex } { CJKecglue-unsupported } } ,
%</pdftex>
    CJKecglue .value_required:n = true
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</pdftex|xetex|luatex|uptex|aptex>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=>
%    \end{macrocode}
%
% \end{implementation}
