% \iffalse meta-comment % !TEX program = XeLaTeX %<*internal> \iffalse % %<*readme> xeCJK ===== `xeCJK` is a package written for XeLaTeX which allows users to typeset CJK scripts easily. - Different default fonts for CJK and other characters. - Spaces automatically ignored between CJK characters. - Special effects on full-width CJK punctuation. - Automatic adjustment of the space between CJK and other characters. Contributing ------------ This package is a part of the [ctex-kit](https://github.com/CTeX-org/ctex-kit) project. Issues and pull requests are welcome. Copyright and Licence --------------------- Copyright (C) 2007--2010 by Wenchang Sun Copyright (C) 2009--2022 by Leo Liu Copyright (C) 2012--2022 by Qing Lee Copyright (C) 2026 by Liam Huang ---------------------------------------------------------------------- This work may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version 1.3c of this license or (at your option) any later version. This version of this license is in http://www.latex-project.org/lppl/lppl-1-3c.txt and the latest version of this license is in http://www.latex-project.org/lppl.txt and version 1.3 or later is part of all distributions of LaTeX version 2005/12/01 or later. This work has the LPPL maintenance status "maintained". The Current Maintainers of this work are Leo Liu, Qing Lee, and Liam Huang. This package consists of the files xeCJK.dtx, full-stop.map, fullwidth-stop.map, han-simp.map, han-trad.map, and the derived files xeCJK.pdf, xeCJK.sty, xeCJK.cfg, xeCJK.ins, xeCJKfntef.sty, xeCJK-listings.sty, xunicode-addon.sty, xunicode-extra.def, xeCJK-example-autofake.tex, xeCJK-example-fallback.tex, xeCJK-example-subCJKblock.tex, xeCJK-example-CJKecglue.tex, xeCJK-example-boundary-register.tex, xeCJK-example-checksingle.tex, xeCJK-example-CJKfntef.tex, xeCJK-example-punctstyle.tex, xeCJK-example-verbatim.tex, xeCJK-example-CM.tex, xeCJK-example-listings.tex, xeCJK-example-mathblock.tex, xunicode-symbols.tex, xunicode-commands.tex, xunicode-combine-marks.tex, xunicode-symbols.pdf, full-stop.tec, fullwidth-stop.tec, han-simp.tec, han-trad.tec, and README.md (this file). % %<*internal> \fi \begingroup \def\temp{LaTeX2e} \expandafter\endgroup\ifx\temp\fmtname\else \csname fi\endcsname % %<*install> \input ctxdocstrip % \preamble Copyright (C) 2007--2010 by Wenchang Sun Copyright (C) 2009--2022 by Leo Liu Copyright (C) 2012--2022 by Qing Lee Copyright (C) 2026 by Liam Huang ---------------------------------------------------------------------- This work may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version 1.3c of this license or (at your option) any later version. This version of this license is in http://www.latex-project.org/lppl/lppl-1-3c.txt and the latest version of this license is in http://www.latex-project.org/lppl.txt and version 1.3 or later is part of all distributions of LaTeX version 2005/12/01 or later. This work has the LPPL maintenance status "maintained". The Current Maintainers of this work are Leo Liu, Qing Lee, and Liam Huang. ---------------------------------------------------------------------- \endpreamble \postamble This package consists of the files xeCJK.dtx, full-stop.map, fullwidth-stop.map, han-simp.map, han-trad.map, and the derived files xeCJK.pdf, xeCJK.sty, xeCJK.cfg, xeCJK.ins, xeCJKfntef.sty, xeCJK-listings.sty, xunicode-addon.sty, xunicode-extra.def, xeCJK-example-autofake.tex, xeCJK-example-fallback.tex, xeCJK-example-subCJKblock.tex, xeCJK-example-CJKecglue.tex, xeCJK-example-boundary-register.tex, xeCJK-example-checksingle.tex, xeCJK-example-CJKfntef.tex, xeCJK-example-punctstyle.tex, xeCJK-example-verbatim.tex, xeCJK-example-CM.tex, xeCJK-example-listings.tex, xeCJK-example-mathblock.tex, xunicode-symbols.tex, xunicode-commands.tex, xunicode-combine-marks.tex, xunicode-symbols.pdf, full-stop.tec, fullwidth-stop.tec, han-simp.tec, han-trad.tec, and README.md. \endpostamble \generate { % %<*internal> \usedir{source/xelatex/xecjk} \file{xeCJK.ins} {\from{\jobname.dtx}{install}} % %<*install> \usedir{tex/xelatex/xecjk} \file{xeCJK.sty} {\from{\jobname.dtx}{package}} \file{xeCJKfntef.sty} {\from{\jobname.dtx}{fntef}} \file{xeCJK-listings.sty} {\from{\jobname.dtx}{listings}} \file{xunicode-addon.sty} {\from{\jobname.dtx}{xunicode}} \file{xunicode-extra.def} {\from{\jobname.dtx}{xunextra}} \usepreamble\emptypreamble \usepostamble\emptypostamble \usedir{tex/xelatex/xecjk/config} \file{xeCJK.cfg} {\from{\jobname.dtx}{config}} \usedir{doc/xelatex/xecjk/example} \file{xeCJK-example-autofake.tex} {\from{\jobname.dtx}{ex-autofake}} \file{xeCJK-example-fallback.tex} {\from{\jobname.dtx}{ex-fallback}} \file{xeCJK-example-subCJKblock.tex} {\from{\jobname.dtx}{ex-block}} \file{xeCJK-example-CJKecglue.tex} {\from{\jobname.dtx}{ex-ecglue}} \file{xeCJK-example-boundary-register.tex} {\from{\jobname.dtx}{ex-boundary-register}} \file{xeCJK-example-checksingle.tex} {\from{\jobname.dtx}{ex-single}} \file{xeCJK-example-CJKfntef.tex} {\from{\jobname.dtx}{ex-fntef}} \file{xeCJK-example-punctstyle.tex} {\from{\jobname.dtx}{ex-punctstyle}} \file{xeCJK-example-verbatim.tex} {\from{\jobname.dtx}{ex-verb}} \file{xeCJK-example-CM.tex} {\from{\jobname.dtx}{ex-cm}} \file{xeCJK-example-listings.tex} {\from{\jobname.dtx}{ex-listings}} \file{xeCJK-example-mathblock.tex} {\from{\jobname.dtx}{ex-mathblock}} \file{xunicode-symbols.tex} {\from{\jobname.dtx}{xunicode-symbols}} \nopreamble\nopostamble \usedir{doc/xelatex/xecjk} \file{README.md} {\from{\jobname.dtx}{readme}} } \endbatchfile % %<*internal> \fi % %<*package|config|fntef|listings|xunicode|xunextra> %\NeedsTeXFormat{LaTeX2e}[2026/06/01] %\RequirePackage{expl3} %<+!driver>\GetIdInfo$Id: xeCJK.dtx 7023032e 2026-08-21 08:33:37 +0800 Liam $ % {Typesetting CJK scripts with XeLaTeX} % {Configuration file for xeCJK package} % {xeCJK font effect} % {xeCJK patch file for listings} % {addon file for xunicode} % {extra definition for xunicode} %\ProvidesExplPackage{\ExplFileName} %\ProvidesExplFile{\ExplFileName.cfg} %\ProvidesExplPackage{xeCJKfntef} %\ProvidesExplPackage{xeCJK-listings} %\ProvidesExplPackage{xunicode-addon} %\ProvidesExplFile{xunicode-extra.def} % {\ExplFileDate}{3.10.6}{\ExplFileDescription} % %<*driver> \documentclass{ctxdoc} \usepackage{xeCJKfntef} \xeCJKDeclareSubCJKBlock{SP} { "2E3A , "301C , "30A0 , "FF65 } \xeCJKDeclareSubCJKBlock{Ext-B} { "20000 -> "2A6DF } \xeCJKDeclareSubCJKBlock{Hangul} { "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF } \xeCJKsetup{PoZheHaoLigature} \setCJKmainfont{Noto Serif CJK SC}[ Language = Chinese Simplified, ItalicFont = LXGWWenKaiGBLite-Regular.ttf ] \setCJKsansfont{Noto Sans CJK SC}[Language=Chinese Simplified] \setCJKmonofont{LXGWZhuqueFangsong-Regular.ttf}[Language=Chinese Simplified] \setCJKmainfont[SP, Language=Chinese Simplified]{Noto Serif CJK SC} \IfFontExistsTF{SimSun-ExtB} {\setCJKmainfont[Ext-B]{SimSun-ExtB}} {\IfFontExistsTF{HanaMinB} {\setCJKmainfont[Ext-B]{HanaMinB}}{}} \setCJKmainfont[Hangul, Script=Hangul, Language=Korean]{Noto Serif CJK KR} \usepackage{menukeys, docext} \newcommand\PSKeyVal[2]{\item[\keys{#1}]\val{#2}:} \newcommand\tokslink[1]{\hyperlink{#1}{\ding{51}}} \newcommand\ghissue[1]{% \href{https://github.com/CTeX-org/ctex-kit/issues/#1}{\##1}} \AtBeginDocument{\DeleteShortVerb{\"}} \ExplSyntaxOn \box_new:N \l__codedoc_mdcode_tmpa_box \box_new:N \l__codedoc_mdcode_tmpb_box \fp_new:N \l__codedoc_mdcode_scale_fp \dim_new:N \l__codedoc_mdcode_depth_dim \DeclareDocumentCommand \mdcode { s m } { \mode_leave_vertical: \hbox_set:Nn \l__codedoc_mdcode_tmpb_box { \menu {} } \fp_set:Nn \l__codedoc_mdcode_scale_fp { \dim_to_decimal:n { \ccwd } / \dim_to_decimal:n { \box_ht_plus_dp:N \l__codedoc_mdcode_tmpb_box } } % \box_scale:Nnn 以基线为轴按比例缩放,menukeys 的盒子深度占比大于汉字, % 缩放后深度仍然偏大,外边框看上去比周围文字低,因此要把盒子上移。 % % 上移的基准不能取汉字的声明深度:汉字字形的墨迹会伸到基线以下,声明深度只是 % 字体给出的度量值,两者并不相等。实测正文字号下墨迹比基线低 1.80pt,而声明 % 深度为 1.59pt,只差 0.21pt——按声明深度上移会让边框底边高过汉字底边,看起来 % 偏高。这里按实测的墨迹与声明深度之比 1.131 折算出墨迹深度,边框底边即与汉字 % 墨迹底边齐平;该比例在 \small、正常、\large 三种字号下实测一致。 \hbox_set:Nn \l__codedoc_mdcode_tmpb_box { 汉 } \dim_set:Nn \l__codedoc_mdcode_depth_dim { 1.131 \box_dp:N \l__codedoc_mdcode_tmpb_box } \tl_set_rescan:Nnn \l__codedoc_mdcode_rescan_tl { \char_gset_active_eq:NN \- \textminus \char_set_catcode_active:N \- } { #2 } \clist_map_inline:Nn \l__codedoc_mdcode_rescan_tl { \hbox_set:Nn \l__codedoc_mdcode_tmpa_box { \bool_if:NTF #1 { \menu { \texttt { ##1 } } } { \menu { ##1 } } } \box_scale:Nnn \l__codedoc_mdcode_tmpa_box { \l__codedoc_mdcode_scale_fp } { \l__codedoc_mdcode_scale_fp } \box_move_up:nn { \box_dp:N \l__codedoc_mdcode_tmpa_box - \l__codedoc_mdcode_depth_dim } { \box_use_drop:N \l__codedoc_mdcode_tmpa_box } } } \NewDocumentCommand \PrintPunctList { O{7} m m } { \par \begingroup \CJKfontspec[Language=Chinese ~ Simplified]{Noto ~ Serif ~ CJK ~ SC} \tl_clear:N \l_tmpa_tl \int_zero:N \l_tmpa_int \tl_set:Ne \l_tmpb_tl { \tl_to_str:n { c__xeCJK_#2_chars_clist } } \int_set:Nn \l_tmpb_int { \clist_count:c { \l_tmpb_tl } } \clist_map_inline:cn { \l_tmpb_tl } { \int_incr:N \l_tmpa_int \tl_put_right:Ne \l_tmpa_tl { \use_none:n ##1 & \tex_char:D ##1 \scan_stop: \int_compare:nNnF \l_tmpa_int = \l_tmpb_int { \int_compare:nNnTF { \int_mod:nn \l_tmpa_int {#1} } = \c_zero_int { \exp_not:N \\ \scan_stop: } { & } } } } \noindent\hfill\linespread{1}\selectfont \begin{tabular}{|*{#1}{>{\footnotesize\ttfamily U+}c|c|}} \tl_use:N \l_tmpa_tl \end{tabular}\hfill\null \endgroup \par } \ExplSyntaxOff \begin{document} \DocInput{\jobname.dtx} \IndexLayout \PrintChanges \PrintIndex \end{document} % % \fi % % \changes{v3.1.0}{2012/11/13}{放弃对 \tn{outer} 宏的特殊处理。} % \changes{v3.1.1}{2012/12/07}{不再依赖 \pkg{xpatch} 宏包。} % \changes{v3.2.2}{2013/06/01}{修正某些重音不能正确显示的问题。} % \changes{v3.2.3}{2013/06/07}{提供四个 TECkit 映射文件用于句号转换和简繁互换。} % \changes{v3.2.4}{2013/07/02}{遵循 \LaTeXiii{} 变量需要预先声明的原则。} % \changes{v3.2.6}{2013/07/29}{\texttt{case} 类函数的用法与 \LaTeXiii{} 同步。} % \changes{v3.3.2}{2015/05/15}{随 Unicode 7.0 更新简繁汉字映射。} % \changes{v3.3.3}{2015/09/25}{更新 \LaTeXiii{} 代码。} % \changes{v3.5.0}{2017/07/19}{常数 \cs{c_minus_one} 已过时。} % \changes{v3.5.0}{2017/07/22}{使用 \texttt{lazy} 函数对 Boolean 表达式 % 进行最小化运算(\LaTeXiii{} 2017/07/19)。} % \changes{v3.6.0}{2018/01/13}{同步 \LaTeXiii{} 2017/12/16。} % \changes{v3.6.1}{2018/02/27}{减少 \texttt{bool} 运算。} % \changes{v3.7.2}{2019/03/23}{同步 \LaTeXiii{} 2019/03/05。} % \changes{v3.8.0}{2020/02/09}{兼容 \LaTeXe\ 2020/02/02 对 \pkg{NFSS} 的修改。} % \changes{v3.8.0}{2020/02/09}{清理过时的兼容性补丁代码。} % \changes{v3.8.3}{2020/04/07}{删除 \texttt{\_nopar}。} % \changes{v3.10.0}{2026/04/29}{提升版本号至 v3.10.0。} % \changes{v3.10.0}{2026/05/04}{文档字体从 Source Han Serif 统一为 % Noto Serif CJK(\#686)。} % \changes{v3.10.2}{2026/07/05}{文档字体启用 \opt{Language} 与 % \opt{PoZheHaoLigature},破折号按全角字形合字输出(\#382)。} % \changes{v3.10.2}{2026/07/05}{文档等宽字体改用朱雀仿宋,意大利体改用 % 霞鹜文楷 GB Lite(\#908)。} % \changes{v3.10.0}{2026/05/13}{修复 \tn{textcolor} 包裹 \pkg{ulem} % 类下划线命令时 CJK 字间距异常的问题(\#830)。} % \changes{v3.10.1}{2026/06/29}{提升版本号至 v3.10.1。} % \changes{v3.10.2}{2026/07/03}{提升版本号至 v3.10.2。} % \changes{v3.10.3}{2026/07/12}{提升版本号至 v3.10.3。} % \changes{v3.10.4}{2026/07/18}{提升版本号至 v3.10.4。} % % \GetFileId{xeCJK.sty} % % \title{\bfseries\pkg{xeCJK} 宏包} % \author{\href{http://www.ctex.org}{CTEX.ORG}} % \date{\ctexkitbuilddate\qquad\fileversion\thanks{\ctexkitrev{\ExplFileVersion}.}} % \maketitle % % \tableofcontents % \vspace{\baselineskip} % % \begin{documentation} % % \section{简介} % % \pkg{xeCJK} 是一个 \XeLaTeX 宏包,用于排版中日韩(CJK)文字。主要功能: % \begin{enumerate} % \item 分别设置 CJK 和英文字体; % \item 自动忽略 CJK 文字间的空格而保留其他空格,允许在非标点汉字和英文 % 字母 (a -- z, A -- Z) 间断行; % \item 提供多种标点处理方式: 全角式、半角式、开明式、行末半角式和 CCT 式; % \item 自动调整中英文间空白。 % \end{enumerate} % % \pkg{xeCJK} 使用了 \XeTeX 的一些最新特性,需要 \XeTeX{} 0.9995.0 (2009/06/29) 以 % 后的版本。\pkg{xeCJK} 依赖 \LaTeXiii{} 项目的宏包套件 % \package{l3kernel} 和 \package{l3packages}。 % \pkg{xeCJK} 还需要通过 \package{fontspec} 宏包来调用系统字体。 % \pkg{xeCJK} 会自动根据需要载入这些宏包。 % % \pkg{xeCJK} 的原始作者是孙文昌,2009 年 5 月起宏包被收入 \ctexkit\ 项目, % 陆续由刘海洋\footnote{\email{leoliu.pku@gmail.com}}、 % 李清\footnote{\email{sobenlee@gmail.com}}及 % 黄晨成\footnote{\email{liamhuang0205@gmail.com}}维护。 % 目前活跃的维护者是黄晨成。 % % \section{基本用法} % % 与其他 \LaTeX{} 宏包一样,引入 \pkg{xeCJK} 宏包只要在导言区使用 % \begin{frameverb} % \usepackage{xeCJK} % \end{frameverb} % 在引入 \pkg{xeCJK} 宏包之后,只要设置 CJK 文字的字体,就可以在文档中使用中日 % 韩文字了。 % % 可以在各种文档类中使用 \pkg{xeCJK} 宏包,最简单的示例是: % \begin{ctexexam} % \documentclass{article} % \usepackage{xeCJK} % \setCJKmainfont{SimSun} % % \begin{document} % 中文 \LaTeX 示例。 % \end{document} % \end{ctexexam} % 上述示例设置了中文字体 SimSun(宋体)。运行此示例要求系统安装了设置的字体, % 源文件用 UTF-8 编码保存,使用 \XeLaTeX{} 编译。 % % \pkg{xeCJK} 只提供了字体和标点控制等基本 CJK 语言支持。对于中文文档,可以使 % 用更为高层的 \package{ctex} 宏包或文档类,它将自动调用 \pkg{xeCJK} 并设置好中文 % 字体,同时提供了进一步的本地化支持。详细内容参看 \package{ctex} 宏包套件的说明。 % % \pkg{xeCJK} 提供了大量选项,可以在宏包调用时作为宏包选项或用 \tn{xeCJKsetup} % 命令进行设置,详见 \ref{subsec:opts}~节。除了 \tn{setCJKmainfont} 命令, % \pkg{xeCJK} 还提供了许多其他命令设置和选择中文字体,详见 % \ref{subsec:fontset}~节。其他更详细的功能也都将在下面详细说明。在本文档所在的 % 文件夹的 |example| 目录下面也有一些例子可以参考。 % % \section{用户手册} % % \subsection{宏包选项} % \label{subsec:opts} % % \pkg{xeCJK} 以 \meta{key}| = |\meta{val} 的形式提供宏包选项,用户可以在调用宏包 % 的时候直接设置这些选项,也可以在调用宏包之后使用 \tn{xeCJKsetup} 来设置这些选 % 项。\pkg{xeCJK} 内部调用 \pkg{fontspec} 宏包,可以在调用 \pkg{xeCJK} 的时候, % 使用它的宏包选项。\pkg{xeCJK} 会将 \pkg{fontspec} 的选项传递给它。 % % \begin{function}{\xeCJKsetup} % \begin{syntax} % \tn{xeCJKsetup} \{\meta{key_1} = \meta{val_1}, \meta{key_2} = \meta{val_2}, ...\} % \end{syntax} % 其中 \meta{key_1}, \meta{key_2} 是设置选项,而 \meta{val_1}, \meta{val_2} 则是对应选项的 % 设置内容。多个选项可以在一个语句中完成设置。例如 % \begin{ctexexam} % \usepackage[PunctStyle=kaiming]{xeCJK} % \end{ctexexam} % 等价于 % \begin{ctexexam} % \usepackage{xeCJK} % ...... % \xeCJKsetup{PunctStyle=kaiming} % \end{ctexexam} % \end{function} % % 带有 \exptarget\expstar{} 或者~\rexptarget\rexpstar{} 标记的选项或命令 % 只能在导言区中使用,其中~\rexpstar{} 还表示这个选项或命令只 % 影响随后定义的 CJK 字体。其余不带特殊标记的选项或命令,如果没有特别说明, % 则可以在导言区或正文中使用。\textbf{粗体}表示 \pkg{xeCJK} 的默认设置。 % % \begin{function}[EXP,added=2012-11-22]{LocalConfig} % \begin{syntax} % LocalConfig = \Arg{\TTF|name} % \end{syntax} % 是否使用本地配置文件 \texttt{xeCJK-\meta{name}.cfg}。\meta{name} 可以是不包含 % 空格的任意使文件名合法的字符串。如果设置为 |true|,则使用的是 \texttt{xeCJK.cfg}; % 设置为 |false| 则不载入配置文件。可以把将要在下文介绍到的对 \pkg{xeCJK} 的一些 % 设置(例如设置常用 CJK 字体、修改字符范围和定义新的标点输出格式等)保存到文件 % \texttt{xeCJK-\meta{name}.cfg}。然后把这个文件放在本地的 |TDS| 目录下的适当 % 位置。使用 \TeX~Live 的用户,可以新建下列目录,然后再把 % \texttt{xeCJK-\meta{name}.cfg} 放在里面: % \begin{frameverb} % texlive/texmf-local/tex/xelatex/xecjk % \end{frameverb} % 最后还需要在命令行下执行 |mktexlsr|,刷新文件名数据库以便 \TeX 系统能够找到它。 % \end{function} % % 请注意,\pkg{xeCJK} 宏包中只有上述 |LocalConfig| 选项需要在调用 \pkg{xeCJK} 时 % 设置,而不能通过 \tn{xeCJKsetup} 来设置。 % % \begin{function}{xeCJKactive} % \begin{syntax} % xeCJKactive = \meta{\TTF} % \end{syntax} % 打开/关闭对中文的特殊处理。事实上,这个选项会打开/关闭 \XeTeX 的整个字符类机制,依赖 % 这个机制的宏包都会受到影响。 % \end{function} % % \begin{function}{CJKspace} % \begin{syntax} % CJKspace = \meta{\TFF} % \end{syntax} % 缺省状态下,\pkg{xeCJK} 会忽略 CJK 文字之间的空格,使用这一选项来保留它们之间的空格。 % \end{function} % % \begin{function}[EXP,updated=2016-05-04]{CJKmath} % \begin{syntax} % CJKmath = \meta{\TFF} % \end{syntax} % 是否支持在数学环境中直接输入 CJK 字符。使用这个选项后,可以直接在数学环境中 % 输出 CJK 字符。\pkg{url} 宏包将一个 URL 放在一个特殊的数学环境中排版,所以如果在 % \tn{path} 等命令的路径参数中含有汉字,则需要启用这个选项,路径中的汉字才能显示。 % \end{function} % % \begin{function}{CJKglue} % \begin{syntax} % CJKglue = \{\tn{hskip} 0pt plus 0.08\tn{baselineskip}\} % \end{syntax} % 设置 CJK 文字之间插入的 |glue|,上边是 \pkg{xeCJK} 的默认值。一般来说,除非有 % 特殊需要(例如,改变文字间距等),否则不需要设置这个选项,使用默认值即可。如果要设置 % 这个选项,为了行末的对齐,设置的 |glue| 最好有一定的弹性。 % \end{function} % % \begin{function}{CJKecglue} % \begin{syntax} % CJKecglue = \Arg{glue} % \end{syntax} % 设置 CJK 文字与西文、CJK 文字与行内数学公式之间的间距,默认值是一个空格。使用这个 % 选项设置的 \meta{glue} 最好也要有一定的弹性。请注意,这里设置的 \meta{glue} 只影响 % \pkg{xeCJK} 根据需要自动添加的空白,源文件中直接输入的 CJK 文字与西文之间的空格不 % 受影响(直接输出)。有时候 \pkg{xeCJK} 可能不能正确地调整间距,需要手动加空格。 % \end{function} % % \begin{function}{xCJKecglue} % \begin{syntax} % xCJKecglue = \Arg{\TFF|glue} % \end{syntax} % 缺省状态下,\pkg{xeCJK} 保留源文件中直接输入在 CJK 文字与西文或行内 % 数学公式之间的空格;这些空格使用普通词间空格。设为 |true| 后, % \pkg{xeCJK} 会将它们替换为 |CJKecglue|。也可以直接把这个选项设置为 % 一段 \meta{glue};这等价于先用该值设置 |CJKecglue|,再设置 % |xCJKecglue=true|。 % \end{function} % % \changes{v3.10.4}{2026/07/21}{明确 \opt{xCJKecglue} 同时控制行内 % 数学公式旁的源码空格(\#1002)。} % \changes{v3.10.4}{2026/07/20}{在用户手册说明 \TeX{} 无法区分源码空格 % 与某些显式 glue,并给出前置零宽 \tn{kern} 的处理方法(\#992)。} % \changes{v3.10.4}{2026/07/20}{补充 Boundary→CJK 方向的说明:某些 % 显式 glue 也可能被替换为 |CJKglue|(\#996)。} % 注意:\TeX{} 的节点列表只保存 glue 的数值,不保存它来自源码空格还是 % 显式 \tn{hskip}。在 \tn{mbox}、\tn{textcolor} 等命令或分组之后,如果 % 显式写出的 glue 与当前词间空格自然宽度相同,并且带有 shrink, % \pkg{xeCJK} 就无法判断它的来源,可能按空格恢复规则将其替换为 % |CJKecglue| 或 |CJKglue|。自然宽度不同或没有 shrink 的显式 glue % 不受影响。如果必须原样保留前一种 glue,请在它前面加零宽 % ~|\kern0pt|,也可以改用不同的自然宽度或去掉 shrink。这样检查会停在 % 这枚 kern,不再继续向前寻找 xeCJK 写入的 marker。 % % \begin{function}[updated=2013-06-26]{CheckSingle} % \begin{syntax} % CheckSingle = \meta{\TFF} % \end{syntax} % 是否避免单个 CJK 文字单独占一个段落的最后一行。需要说明的是,这个选项只有在 % 段末的最后一个字是 CJK 文字或者标点符号,并且倒数第二和第三个字都是文字才能 % 正确处理孤字的问题。如果这倒数三个字有作为控制序列的参数的情况,那么一般 % 来说也不能正确处理。 % \end{function} % % \begin{function}[added=2015-04-08]{WidowPenalty} % \begin{syntax} % WidowPenalty = \Arg{penalty|(10000)} % \end{syntax} % 使用 \texttt{CheckSingle} 选项后,设置段末三个汉字之间的 penalty。 % 初始值为 \num{10000};也即,禁止在它们之间折行。 % \end{function} % % \begin{function}[added=2012-12-06]{PlainEquation} % \begin{syntax} % PlainEquation = \meta{\TFF} % \end{syntax} % 如果使用了 |$$...$$| 的形式来输入行间数学公式,就需要启用本选项,以便 % |CheckSingle| 选项能够正确识别。推荐使用~|\[...\]| 的形式来输入行间数学公式。 % \end{function} % % \begin{function}[added=2012-12-04]{NewLineCS,NewLineCS+,NewLineCS-} % \begin{syntax} % NewLineCS = \{ \tn{par} \tn{[} \} % \end{syntax} % 设置造成断行的控制序列,以便 |CheckSingle| 选项能够正确识别。 % 以上是 \pkg{xeCJK} 的初始设置。 % \end{function} % % \begin{function}[added=2012-12-04]{EnvCS,EnvCS+,EnvCS-} % \begin{syntax} % EnvCS = \{ \tn{begin} \tn{end} \} % \end{syntax} % 设置 \LaTeX 环境开始和结束的控制序列,以便 |CheckSingle| 选项能够正确识别。 % 以上是 \pkg{xeCJK} 的初始设置。 % \end{function} % % \begin{function}[updated=2012-12-06]{InlineEnv,InlineEnv+,InlineEnv-} % \begin{syntax} % InlineEnv = \{\meta{env_1}, \meta{env_2}, \meta{env_3}, ...\} % \end{syntax} % 使用 |CheckSingle| 选项时,\pkg{xeCJK} 会将 CJK 文字后接着的 \LaTeX{} 环境 % 起止点视为可断行点。如果有某些特殊的 \LaTeX 环境没有造成断行,可以使用该选项声明, % 以便 |CheckSingle| 能正确识别。 % \end{function} % % \begin{function}{AutoFallBack} % \begin{syntax} % AutoFallBack = \meta{\TFF} % \end{syntax} % 当文档中有个别生僻字时,可以使用这个选项,自动使用预先设置好的后备字体来输出这些生僻 % 字。后备字体的设置方法将在 \ref{subsec:fontset}~节中介绍。 % \end{function} % % \begin{function}[rEXP]{AutoFakeBold} % \begin{syntax} % AutoFakeBold = \Arg{\TFF|数字} % \end{syntax} % 全局设定当没有声明对应的粗体时,是否使用^^A % \textbf{\CJKfontspec[AutoFakeBold]{Noto Serif CJK SC}伪粗体}; % 当输入的是数字时,将使用伪粗体,并将使用输入的数字作为伪粗体的默认粗细程度。 % \end{function} % % \begin{function}[rEXP]{AutoFakeSlant} % \begin{syntax} % AutoFakeSlant = \Arg{\TFF|数字} % \end{syntax} % 全局设定当没有声明对应的斜体时,是否使用^^A % \textit{\CJKfontspec[AutoFakeSlant]{Noto Serif CJK SC}伪斜体}; % 当输入的是数字时,将使用伪斜体,并将使用输入的数字作为伪斜体的默认倾斜程度。 % 倾斜程度的取值范围是 $[-0.999, 0.999]$。 % \end{function} % % \begin{function}[rEXP]{EmboldenFactor} % \begin{syntax} % EmboldenFactor = \Arg{数字|(4)} % \end{syntax} % 设置伪粗体的默认粗细程度。 % \end{function} % % \begin{function}[rEXP]{SlantFactor} % \begin{syntax} % SlantFactor = \Arg{数字|(0.167)} % \end{syntax} % 设置伪斜体的倾斜程度,范围是 $[-0.999, 0.999]$。 % \end{function} % % \begin{function}[updated=2012-11-10]{PunctStyle} % \begin{syntax} % PunctStyle = \Arg{(quanjiao)|banjiao|kaiming|hangmobanjiao|CCT|plain|...} % \end{syntax} % 设置标点处理格式。\pkg{xeCJK} 中预先定义好的格式为 % \begin{optdesc} % \item[quanjiao] 全角式:所有标点占一个汉字宽度;左标点后接右标点时保留 % 字体的自然空白,其余相邻标点在字形边界允许时压缩到约 1.5 汉字宽度; % \item[banjiao] 半角式:所有标点占半个汉字宽度; % \item[kaiming] 开明式:句末点号用全角,其他半角; % \item[hangmobanjiao] 行末半角式:所有标点占一个汉字宽度,行首行末对齐; % \item[CCT] CCT 格式:所有标点符号的宽度略小于一个汉字宽度; % \item[plain] 原样(不调整标点间距)。 % \end{optdesc} % 可以使用 \ref{subsec:punctstyle} 中介绍的 \tn{xeCJKDeclarePunctStyle} 定义新的标点 % 格式。 % \end{function} % % \begin{function}[added=2018-01-24]{PunctFamily} % \begin{syntax} % PunctFamily = \Arg{\keys[emph=1,delimiter=\textbar]{false,family}} % \end{syntax} % 默认情况下,CJK 标点符号的字体与 CJK 正文一致,\opt{PunctFamily} 用于单独对标点符号设置字体。 % \meta{family} 需要使用随后说明的 \tn{setCJKfamilyfont} 或 \tn{newCJKfontfamily} % 预先定义。\opt{false} 表示取消本选项的作用,让标点符号字体与正文一致。 % \end{function} % % \begin{function}[EXP]{KaiMingPunct,KaiMingPunct+,KaiMingPunct-} % \begin{syntax} % KaiMingPunct = \Arg{(. 。? !)} % \end{syntax} % 设置开明(|kaiming|)标点处理格式时的句末点号,|KaiMingPunct| 后带的~\mdcode*{+,-} % 分别表示从已有的开明句末点号中增加或减少标点。 % \end{function} % % \begin{function}[EXP]{LongPunct,LongPunct+,LongPunct-} % \begin{syntax} % LongPunct = \Arg{(— ⸺ ‥ … )} % \end{syntax} % 设置长标点,例如破折号~\mdcode{——} 与省略号~\mdcode{……},允许在长标点前后 % 断行,但是禁止在它们之间断行。同时属于 |NoBreakLongPunct| % 的长标点则禁止在其前面断行。长标点与其他标点相邻时, % 断点两侧还须满足通常的标点禁则:全角左标点不悬于行尾, % 全角右标点不落于行首。 % \end{function} % % \begin{function}[EXP]{NoBreakLongPunct,NoBreakLongPunct+,NoBreakLongPunct-} % \begin{syntax} % NoBreakLongPunct = \Arg{(‥ … )} % \end{syntax} % 设置禁止在其前面断行的长标点。属于 |NoBreakLongPunct| 的字符必须同时属于 % |LongPunct|。与普通长标点不同,\pkg{xeCJK} 禁止在这类标点前面断行,但仍 % 允许在其后面断行,并禁止在相同长标点之间断行。缺省值包含省略号 % ~\mdcode{‥} 和~\mdcode{…}。 % \end{function} % % \begin{function}[EXP]{MiddlePunct,MiddlePunct+,MiddlePunct-} % \begin{syntax} % MiddlePunct = \Arg{(– — ⸺ · · ・ 〜゠~)} % \end{syntax} % 设置居中显示的标点,例如间隔号~\mdcode{·}。对于在 CJK 文字之间的居中标点, % \pkg{xeCJK} 会根据不同的标点处理格式,调整居中标点与前后文字之间的空白,保证 % 其确实居中。对于行末出现的居中标点,允许在其后面断行,但禁止在它前面断行。 % \end{function} % % \begin{function}[added=2026-07-04]{PoZheHaoLigature} % \begin{syntax} % PoZheHaoLigature = \meta{\TFF} % \end{syntax} % 一些设计优良的 OpenType 字体(如思源宋体、思源黑体)提供了破折号合字功能: % 连续的两个 \texttt{U+2014} 会被替换为一个两倍字宽的字形。 % 缺省状态下,\pkg{xeCJK} 在破折号之间插入的标点处理代码会阻断这一功能。 % 启用这一选项后,\pkg{xeCJK} 把 \texttt{U+2014} 和 \texttt{U+2015} 归入 % |PoZheHao| 字符类,它们之间不插入任何内容,从而恢复字体的合字功能。 % 这一选项需要字体的配合(一般还需启用 |fwid| 或 |locl| 等 OpenType 特性 % 得到全角字形),对不支持破折号合字的字体(多数中文字库)不要启用它, % 否则连续破折号中间可能出现空隙。由于标点度量按字体缓存, % 这一选项建议在导言区或使用相应字体排版破折号之前设置。 % 例如: % \begin{ctexexam} % \setCJKmainfont{Noto Serif CJK SC}[RawFeature=+fwid] % \xeCJKsetup{PoZheHaoLigature} % \end{ctexexam} % \end{function} % % \begin{function}[added=2026-07-05]{LatinPunct} % \begin{syntax} % LatinPunct = \meta{\TFF} % \end{syntax} % Unicode 中的弯引号(|U+2018|、|U+2019|、|U+201C|、|U+201D|)、 % 间隔号(|U+00B7|)与省略号(|U+2025|、|U+2026|、|U+2027|)等码位 % 是中西文共用的。缺省状态下,\pkg{xeCJK} 把它们归入全角标点, % 用中文字体输出全角字形;在以西文为主的文档中, % 夹在英文单词中间的撇号(如 |Children’s| 的 |U+2019|)会因此变成 % 突兀的全角形式。启用这一选项后,这些共用标点改归入西文半角标点类 % (|HalfLeft| / |HalfRight|),使用当前西文字体输出, % 也不再参与标点压缩。这一选项缺省未启用;|LatinPunct| 等价于 % |LatinPunct = true|。破折号(|U+2014|)的归属不受此选项影响, % 见 \opt{PoZheHaoLigature}。 % 例如: % \begin{ctexexam} % \xeCJKsetup{LatinPunct} % 共用标点使用西文字体 % \end{ctexexam} % 这一选项可以在导言区或正文中随时切换, % 作用范围是当前 \TeX{} 分组。 % \end{function} % % \begin{function}[added=2026-07-12]{CJLineBreak} % \begin{syntax} % CJLineBreak = \Arg{(normal)|strict} % \end{syntax} % 设置 Unicode |Line_Break=CJ| 的日文小假名等字符是否采用严格行首禁则。 % |normal| 将它们作为普通 CJK 文字处理,保持历史默认行为;|strict| 保持 % 同样的字距,但在这些字符前插入 penalty \num{10000},防止它们出现在行首。 % 例如 |きゃ| 和 |きゃっ| 中的小假名前都不可断行。该选项可在分组内局部 % 切换;调用 \tn{xeCJKResetCharClass} 后仍保留当前设置。 % \end{function} % % \begin{function}[EXP]{PunctWidth} % \begin{syntax} % PunctWidth = \Arg{length} % \end{syntax} % 缺省状态下,\pkg{xeCJK} 会根据所选择的标点处理格式自动计算标点所占的宽度,如果对缺 % 省设置不满意,可以通过这一选项来改变它。为了使得标点所占的宽度能够适应字体大小的变化, % 这里设置的 |length| 的单位最好用 |em| 等相对距离单位,而不建议使用诸如 |pt| 之类的 % 绝对距离单位。这里的设置可用于除了 |plain| 以外的所有标点处理格式。同时,这里的 % 设置对所有的 CJK 标点都生效,如果只要设置部分标点,请使用 \ref{subsec:punct}~节的 % \tn{xeCJKsetwidth}。 % \end{function} % % \begin{function}[EXP,added=2013-08-22]{PunctBoundWidth} % \begin{syntax} % PunctBoundWidth = \Arg{length} % \end{syntax} % 与以上选项类似,但设置的是标点符号出现在行首/尾时的宽度。 % \end{function} % % \begin{function}{AllowBreakBetweenPuncts} % \begin{syntax} % AllowBreakBetweenPuncts = \meta{\TFF} % \end{syntax} % 缺省状态下,\pkg{xeCJK} 禁止在相邻 CJK 右标点和 CJK 左标点之间换行,可以使用 % 这一选项改变这一设置。 % \end{function} % % \begin{function}[updated=2016-05-13]{RubberPunctSkip} % \begin{syntax} % RubberPunctSkip = \meta{\TTF|plus|minus} % \end{syntax} % 缺省状态下,标点符号前/后的间距有一定的弹性。可以伸长到原始边界宽度,可以收缩到 % 标点另一侧的边界宽度。将本选项设置为 \texttt{plus},将只允许伸长;设置为 % \texttt{minus} 只允许收缩。设置为 \texttt{false} 将禁用这一特性, % 从而使得前/后的间距为固定值。 % \end{function} % % \begin{function}[added=2012-12-02]{CheckFullRight} % \begin{syntax} % CheckFullRight = \meta{\TFF} % \end{syntax} % 某些控制序列要求不能在它的前面断行。但是在缺省状态下,单个全角右标点的后面总是 % 可以断行的。因此当这些控制序列出现在全角右标点后面时,可能会出现意料之外的断行。 % 此时可以使用这个选项来避免这个情况。 % \end{function} % % \begin{function}[added=2012-12-02]{NoBreakCS,NoBreakCS+,NoBreakCS-} % \begin{syntax} % NoBreakCS = \{ \tn{footnote} \tn{footnotemark} \tn{nobreak} \} % \end{syntax} % 设置不能在全角右标点后断行的控制序列。以上是 \pkg{xeCJK} 的默认设置。如果这些 % 控制序列在文档中只出现少量几次,也可以不必使用 |CheckFullRight| 选项,而是手工 % 在这些控制序列前面加上 \ref{subsec:others}~节介绍的 \tn{xeCJKnobreak}。 % \end{function} % % \begin{function}[added=2026-05-03]{experiment/halfright-prebreakpenalty} % \begin{syntax} % experiment/halfright-prebreakpenalty = \meta{\TFF} % \end{syntax} % 这是一个实验性选项。缺省状态下,\pkg{xeCJK} 不会禁止在 % CJK 文字或全角右标点后面紧跟的半角右标点(如 % ~\mdcode{., {,}, ;, :, !, ?, ), ]} % 等)之前换行。例如~\mdcode{).} 中,\mdcode{.} 有可能被排在下一行的行首。 % 打开此选项后,\pkg{xeCJK} 会在这类位置插入禁则(penalty \num{10000}), % 防止半角右标点出现在行首。 % % 此选项的效果类似于 \pkg{LuaTeX-ja} 中的 |prebreakpenalty| 机制, % 但受限于 \XeTeX{} interchar 机制,粒度为字符类级别而非逐字符级别。 % 例如: % \begin{verbatim} % \xeCJKsetup{experiment/halfright-prebreakpenalty} % \end{verbatim} % \end{function} % % \begin{function}[added=2026-05-17]{experiment/punct-measure-fix} % \begin{syntax} % experiment/punct-measure-fix = \meta{\TFF} % \end{syntax} % 这是一个实验性选项。缺省状态下,\LaTeX{} 的 \cs{para_end:} 在执行 % \cs{tex_par:D} 之前会通过 \tn{unskip} 移除水平列表末尾的 glue。如果 % 段末恰好是全角标点,其标点补偿 glue 也会被移除,导致 \pkg{tabularray} % 等使用 \tn{par} 结束测量段落的宏包得到不正确的宽度。 % % 打开此选项后,\pkg{xeCJK} 会在 |para/end| 钩子中插入一个 % \tn{kern},补偿被 \tn{unskip} 移除的标点 glue 的自然宽度,确保 % 测量宽度与排版宽度一致。 % % \textbf{注意:}此修复涉及一个权衡。\tn{kern} 只保留了原 glue 的 % 自然宽度,丢弃了弹性分量(stretch/shrink)。对于段末最后一行, % \tn{parfillskip} 提供的 |0pt plus 1fil| 拉伸量会吸收所有剩余空间, % 因此弹性丢失在绝大多数情况下无视觉影响。但在 CheckSingle 等 % 强制 overfull 的极端场景下,最后一行的 overfull 报告值可能增大。 % % 使用示例: % \begin{verbatim} % \xeCJKsetup{experiment/punct-measure-fix} % \end{verbatim} % \end{function} % % \begin{function}[added=2026-07-22]{experiment/boundary-register} % \begin{syntax} % experiment/boundary-register = % \ \{ % \ command = \meta{control sequence}, % \ strategy = \meta{strategy}, mode = \meta{mode} % \ \} % \end{syntax} % 这是一个面向熟悉 \TeX{} 节点和命令实现的用户的实验性接口。它把 % \pkg{xeCJK} 尚未处理的命令接入命令边界框架,使命令包装前后的可见内容 % 尽量具有相同的中西文间距。一个键值只注册一个命令;需要注册多个命令时, % 可以重复使用本选项。 % % |command| 必须是一个控制序列。命令名含有~\mdcode*{@} 时,用户应在声明外使用 % \tn{makeatletter} 和 \tn{makeatother};注册公开的 \LaTeX3 命令时, % 应使用 \tn{ExplSyntaxOn} 和 \tn{ExplSyntaxOff} 让~\mdcode*{\_} 和~\mdcode*{:} 成为命令名 % 的一部分。这个接口不接受不带反斜线的命令名字符串。 % % |strategy| 可以取以下值: % \begin{keyval} % \item[\keys{box}] % 命令结束时在当前列表末尾留下一个水平盒子。 % \item[\keys{wrapped-box}] % 命令会直接写出多个节点,并且能够在 \pkg{xeCJK} 打开的水平盒子中 % 完整执行。 % \item[\keys{stream}] % 命令的可见内容直接写入当前列表。 % \item[\keys{transparent}] % 命令没有可见输出,只留下 PDF 锚点等特殊节点。 % \item[\keys{post-transparent}] % 命令结束时留下零尺寸水平盒子,只能在命令执行后恢复边界状态。 % \end{keyval} % \end{function} % 下面的五个命令分别给出每种策略最简单的典型形式。实际命令只有在节点 % 结构与相应示例一致时,才应采用同一种策略。 % \begin{verbatim} % \newcommand\mybox[1]{\hbox{#1}} % \newcommand\mywrapped[1]{\kern0pt #1\kern0pt} % \newcommand\mystream[1]{\special{my-stream}#1} % \newcommand\myanchor{\special{my-anchor}} % \newcommand\myzero{\hbox{}} % \xeCJKsetup % { % experiment/boundary-register = { command = \mybox, % strategy = box }, % experiment/boundary-register = { command = \mywrapped, % strategy = wrapped-box }, % experiment/boundary-register = { command = \mystream, % strategy = stream }, % experiment/boundary-register = { command = \myanchor, % strategy = transparent }, % experiment/boundary-register = { command = \myzero, % strategy = post-transparent } % } % \end{verbatim} % % |box|、|wrapped-box| 和 |stream| 可以使用 |mode|。省略时默认为 % |auto|,即采用运行时观察到的实际首、尾类别。|default| 把两端固定视为 % Default;|first-default| 只把左端固定视为 Default,并且只适用于 % |stream|。|transparent| 和 |post-transparent| 不接受 |mode|。 % % 用户声明在导言区收集,到导言区末尾统一注册;即使声明写在分组内,也会 % 对整篇文档生效。目标命令可以在普通的 \tn{AtBeginDocument} 中定义; % 如果它到正文开始以后才定义,注册不会生效并会给出错误。已经由 % \pkg{xeCJK} 处理的命令和重复声明也会报错,本接口不提供反注册或覆盖 % 内建注册的功能。命令 hook 安装完成后,如果其他代码重新定义了目标命令, % 新定义可能不再包含这里登记的处理。 % % 并非所有命令都适合通用注册。\tn{verb}、\tn{url} 等使用特殊参数读取 % 方式的命令可能需要专用适配;公开命令如果只负责读取参数,应当注册真正 % 排版内容的内部命令。能够作为 |command| 读入的控制序列,也不一定能由 % \LaTeX{} 通用命令 hook 完整包住。通用注册不会分析命令参数;如果行内 % 公式位于可见内容的开头或结尾,|auto| 未必能观察到公式边界。 % % 特别地,\emph{命令本身就是一条赋值语句时不能用通用注册}。\tn{global}、 % \tn{long} 这类前缀在 \TeX{} 中是“等待下一个赋值”的状态,而 |before| % 钩子的代码插在该赋值之前执行;钩子里任何赋值都会把前缀消耗掉,调用方写的 % \tn{global} 便静默失效,且不产生任何错误或警告。\tn{sbox} 属于这一类, % 本包为它使用专用适配器,把暂停观察移到盒子内部,使 \tn{global} 始终紧邻 % \tn{setbox}(\#1029)。用户注册自定义的“保存盒子”类命令时应同样处理。 % % 顺带说明一个与本包无关的既有限制:\tn{global}\tn{savebox} 本来就不能跨 % 分组生效。这是因为 \tn{savebox} 是 robust 命令,\tn{global} 在它自己的 % \tn{@ifnextchar} 前瞻阶段就被消耗,前缀根本到不了内部入口;不加载本包的 % 原版 \LaTeX{} 亦然。需要全局保存盒子时应使用 \tn{global}\tn{sbox} 或 % \tn{global}\tn{setbox}。 % \tn{mbox} 等现有命令另有参数适配代码,用户注册不会自动取得这部分能力。 % % \emph{注册的层级也会影响结果}。框架在进入命令时就把 \tn{CJKecglue}、 % \tn{CJKglue} 和词间空格排入临时盒并读成数值,缓存的度量因此取决于进入 % 命令那一刻生效的字体。如果目标命令的定义里包含字体切换,注册就必须包住 % 最外层的那次切换:注册在切换内侧时,左边界会用切换后的字体度量,而右边界 % 在命令结束、字体已经恢复之后求值,两侧数值不一致。这种写法在纯西文和 % 纯中文文档里都看不出问题,只有中西文边界两侧都出现时才暴露。因此,若公开 % 命令形如~|\newcommand\mycmd[1]{\texttt{...#1...}}|,应当注册这个公开命令, % 而不是它内部负责排版参数的辅助命令(\#1046)。 % % 注册后应当把命令与“去掉命令包装后的相同可见内容”比较,并分别检查左右 % 边界、四种源码空格以及 \opt{xCJKecglue} 的两个取值。随包提供的 % \pkg{xeCJK-example-boundary-register.tex} 用明显不同的 % \tn{CJKecglue} 和 \tn{CJKglue} 展示了这种检查方法。其中公式行还会 % 显示上述参数适配限制;方框不等宽时,说明不能只靠所选通用策略处理该 % 命令。 % % \begin{function}[updated=2013-11-16]{Verb} % \begin{syntax} % Verb = \meta{\TF|(env)|env+} % \end{syntax} % \texttt{true} 表示在 \tn{verb} 命令或 \texttt{verbatim} 环境里不自动调整中英文 % 之间的间距。\texttt{env} 选项在 \texttt{verbatim} 环境里自动计算中西文间距和中文 % 之间的间距,以便于保持代码的对齐;\texttt{env} 选项不调整 \tn{verb} 里的间距,^^A % \texttt{env+} 选项还将正文里设置的间距应用到 \tn{verb} 里。^^A % 这个选项对使用到 \tn{verbatim@font} 命令的情形均有效,更一般的情况可以使用 % \ref{subsec:others}~节介绍的 \tn{xeCJKVerbAddon}。\texttt{false} 表示不作任何 % 处理。以上选项的值除 \texttt{false} 外,都禁止在汉字之间和汉字与西文之间自动换行。 % \end{function} % % \begin{function}[rEXP,added=2014-03-01]{LoadFandol} % \begin{syntax} % LoadFandol = \meta{\TTF} % \end{syntax} % 当没有在导言区设置 CJK 字体时,是否使用 Fandol 字体。如果启用这个选项,需要 % 安装 \package{Fandol} 字体系列。 % \end{function} % % \subsection{字体设置与选择} % \label{subsec:fontset} % % \begin{function}[EXP,updated=2016-11-18]{\setCJKmainfont} % \begin{syntax} % \tn{setCJKmainfont} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKmainfont} \oarg{font features} \Arg{font name} % \end{syntax} % 设置正文罗马族的 CJK 字体,影响 \tn{rmfamily} 和 \tn{textrm} 的字体。后面两个 % 参数继承自 \pkg{fontspec} 宏包, \meta{font features} 表示字体属性选项, % \meta{font name} 是字体名。字体名可以是字体族名,也可以是字体的文件名,查 % 找字体名见 \ref{subsubsec:fontsearch}~节;可用的字体属性选项参见 % \pkg{fontspec} 宏包的文档。需要说明的是 \pkg{xeCJK} 修改了 |AutoFakeBold| % 和 |AutoFakeSlant| 选项,以便配合全局伪粗体和伪斜体的设定。 % % 出于兼容性考虑,字体属性可选项可以放在字体名称前面,也可以放在后面。 % 如果可选项放在后面,字体名称与可选项之间不要有空格或者换行。 % \end{function} % % \begin{function}[label = ]{AutoFakeBold,AutoFakeSlant} % \begin{syntax} % AutoFakeBold = \Arg{\TF|数字} % AutoFakeSlant = \Arg{\TF|数字} % \end{syntax} % 局部设置当前字体族的伪粗和伪斜属性。如果没有在局部给出这些选项,将使用全局设定。 % \end{function} % % \begin{function}[added=2013-06-07]{Mapping} % \begin{syntax} % Mapping = \Arg{fullwidth-stop|full-stop|han-trad|han-simp|...} % \end{syntax} % \pkg{xeCJK} 提供了以上四个 \href{http://scripts.sil.org/teckit}{TECKit} 映射 % 文件,可以在设置字体的时候通过 \texttt{Mapping} 选项来使用它们。其中 % \texttt{fullwidth-stop} 用于将正常句号~\mdcode{。} % 转换成全角实心句号~\mdcode{.}, % \texttt{full-stop} 的作用相反。\texttt{han-trad} 用于将简体中文转换成繁体中文, % \texttt{han-simp} 的作用相反。需要注意的是,简繁互换都是简单机械的字字对译, % 不能做到完全准确,使用时要小心。例如简体的“发挥”和“头发”被转换成繁体的 % “發揮”和“頭發”,显然后者应作“頭髮”。也可以根据实际需要,制作新的映射文件, % 请参考 TECKit 的文档。 % \end{function} % % \begin{function}[EXP,updated=2016-11-18]{\setCJKsansfont} % \begin{syntax} % \tn{setCJKsansfont} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKsansfont} \oarg{font features} \Arg{font name} % \end{syntax} % 设置正文无衬线族的 CJK 字体,影响 \tn{sffamily} 和 \tn{textsf} 的字体。 % \end{function} % % \begin{function}[EXP,updated=2016-11-18]{\setCJKmonofont} % \begin{syntax} % \tn{setCJKmonofont} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKmonofont} \oarg{font features} \Arg{font name} % \end{syntax} % 设置正文等宽族的 CJK 字体,影响 \tn{ttfamily} 和 \tn{texttt} 的字体。 % \end{function} % % \begin{function}[EXP,updated=2016-11-18]{\setCJKfamilyfont} % \begin{syntax} % \tn{setCJKfamilyfont} \Arg{family} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKfamilyfont} \Arg{family} \oarg{font features} \Arg{font name} % \end{syntax} % 声明新的 CJK 字体族 \meta{family} 并指定字体。 % \end{function} % % \begin{function}[updated=2012-10-27]{\CJKfamily} % \begin{syntax} % \tn{CJKfamily} \Arg{family} % \tn{CJKfamily} + \Arg{family} % \tn{CJKfamily} - \Arg{family} % \end{syntax} % 用于在文档中切换 |CJK| 字体族,\meta{family} 必须预先声明。\tn{CJKfamily} 仅对 % CJK 字符类有效,\tn{CJKfamily}|+| 对所有字符类均有效,\tn{CJKfamily}|-| 对非 CJK 字 % 符类有效。当 \tn{CJKfamily}|+| 和 \tn{CJKfamily}|-| 的参数为空时,则使用当前的 |CJK| 字体族。 % \end{function} % % \begin{function}[EXP,updated=2016-11-18]{\newCJKfontfamily} % \begin{syntax} % \tn{newCJKfontfamily} \oarg{family} \cs{\meta{font-switch}} \Arg{font name} \oarg{font features} 或\\ % \tn{newCJKfontfamily} \oarg{family} \cs{\meta{font-switch}} \oarg{font features} \Arg{font name} % \end{syntax} % 声明新的 CJK 字体族 \meta{family} 并指定字体,并定义 \cs{\meta{font-switch}},在 % 文档中可以使用它来切换 CJK 字体族。可以不必指定 \meta{family},这时候 \meta{family} % 将等于 \meta{font-switch}。 % \end{function} % % 事实上,\tn{newCJKfontfamily} 是 \tn{setCJKfamilyfont} 和 % \tn{CJKfamily} 的合并。例如 % \begin{ctexexam} % \newCJKfontfamily[song]\songti{SimSun} % \end{ctexexam} % 等价于 % \begin{ctexexam} % \setCJKfamilyfont{song}{SimSun} % \newcommand*{\songti}{\CJKfamily{song}} % \end{ctexexam} % % \begin{function}[updated=2016-11-18]{\CJKfontspec} % \begin{syntax} % \tn{CJKfontspec} \Arg{font name} \oarg{font features} 或\\ % \tn{CJKfontspec} \oarg{font features} \Arg{font name} % \end{syntax} % 在文档中定义新的 CJK 字体族,并马上使用它。 % \end{function} % % \begin{function}[rEXP]{\defaultCJKfontfeatures} % \begin{syntax} % \tn{defaultCJKfontfeatures} \Arg{font features} % \end{syntax} % 全局设置 CJK 字体族的默认选项。例如,使用 % \begin{ctexexam} % \defaultCJKfontfeatures{Scale=0.962216} % \end{ctexexam} % 可以将全部 CJK 字体缩小为 |0.962216|。\pkg{xeCJK} 宏包的初始化设置是 % \begin{frameverb} % \defaultCJKfontfeatures{Script=CJK} % \end{frameverb} % \end{function} % % \begin{function}[updated=2013-06-30]{\addCJKfontfeatures} % \begin{syntax} % \tn{addCJKfontfeatures} \Arg{font features} % \tn{addCJKfontfeatures} * \Arg{font features} % \tn{addCJKfontfeatures} \oarg{block_1, block_2, ...} \Arg{font features} % \tn{addCJKfontfeatures} * \oarg{block_1, block_2, ...} \Arg{font features} % \end{syntax} % 临时增加当前使用的 CJK 字体的选项。第一条命令,仅对当前 CJK 主分区字体有效; % 第二条对主分区和其他分区的字体都有效;第三条仅对可选参数中指定的分区有效; % 第四条对主分区和可选参数中指定的分区有效。例如,使用 % \begin{ctexexam} % \addCJKfontfeatures{Scale=1.1} % \end{ctexexam} % 可以将文档中当前使用的 CJK 主分区字体放大为 |1.1|。 % \end{function} % % \begin{function}{\CJKrmdefault} % 保存 \tn{textrm} 和 \tn{rmfamily} 所使用的 CJK 字体族,默认值是 |rm|。 % \end{function} % % \begin{function}{\CJKsfdefault} % 保存 \tn{textsf} 和 \tn{sffamily} 所使用的 CJK 字体族,默认值是 |sf|。 % \end{function} % % \begin{function}{\CJKttdefault} % 保存 \tn{texttt} 和 \tn{ttfamily} 所使用的 CJK 字体族,默认值是 |tt|。 % \end{function} % % \begin{function}[updated=2013-01-01]{\CJKfamilydefault} % 保存 \tn{textnormal} 和 \tn{normalfont} 所使用的 CJK 字体族。类似西文字体的 \tn{familydefault}。 % 初始值是 \tn{CJKrmdefault}。如果没有在导言区中修改它,\pkg{xeCJK} 会在导言区 % 结束的时候根据西文字体的情况自动更新 \tn{CJKfamilydefault}。因此,在导言区里使用 % \begin{frameverb} % \renewcommand\familydefault{\sfdefault} % \end{frameverb} % 就可以将全文的 CJK 和西文默认字体都改为无衬线字体族。 % \end{function} % % \begin{function}[EXP,updated=2016-11-18]{\setCJKmathfont} % \begin{syntax} % \tn{setCJKmathfont} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKmathfont} \oarg{font features} \Arg{font name} % \end{syntax} % 设置数学公式中的 CJK 字体族。如果使用了 |CJKmath| 选项,但是没有使用 % \tn{setCJKmathfont} 设置数学公式中的 CJK 字体,那么将使用 \tn{CJKfamilydefault} % 作为数学公式中的 CJK 字体。 % \end{function} % % \begin{function}[EXP, label=, updated=2016-11-18]{\setCJKfallbackfamilyfont} % \begin{syntax} % \tn{setCJKfallbackfamilyfont} \Arg{family} \Arg{font name} \oarg{font features} 或\\ % \tn{setCJKfallbackfamilyfont} \Arg{family} \oarg{font features} \Arg{font name} % \end{syntax} % 设置 CJK 字体族 \meta{family} 的备用字体。例如,使用 % \begin{ctexexam} % \setCJKmainfont{SimSun} % \setCJKfallbackfamilyfont{\CJKrmdefault}{SimSun-ExtB} % \end{ctexexam} % 可以将 |SimSun-ExtB| 作为 |SimSun| 的备用字体。 % \end{function} % % \begin{function}{FallBack} % \begin{syntax} % FallBack = \{\oarg{font features} \Arg{font name}\} % \end{syntax} % \pkg{xeCJK} 在 \meta{font features} 里增加了 |FallBack| 这个选项。用来在声明主 % 字体的时候,同时设置备用字体。例如,上面的例子等价于: % \begin{ctexexam} % \setCJKmainfont[FallBack=SimSun-ExtB]{SimSun} % \end{ctexexam} % 如果 |FallBack| 的值为空,将设置的是备用字体。例如, % \begin{ctexexam} % \setCJKmainfont[FallBack,AutoFakeBold,Scale=.97]{SimSun-ExtB} % \end{ctexexam} % 等价于 % \begin{ctexexam} % \setCJKfallbackfamilyfont{\CJKrmdefault}[AutoFakeBold,Scale=.97]{SimSun-ExtB} % \end{ctexexam} % \end{function} % % \begin{function}[EXP,updated=2013-06-30]{\setCJKfallbackfamilyfont} % \begin{syntax} % \tn{setCJKfallbackfamilyfont} \Arg{family} % \ \{ % \ \{\oarg{font features_1} \Arg{font name_1}\} , % \ \{\oarg{font features_2} \Arg{font name_2}\} , % \ ...... % \ \}\oarg{common font features} 或\\ % \tn{setCJKfallbackfamilyfont} \Arg{family} \oarg{common font features} % \ \{ % \ \{\oarg{font features_1} \Arg{font name_1}\} , % \ \{\oarg{font features_2} \Arg{font name_2}\} , % \ ...... % \ \} % \end{syntax} % \tn{setCJKfallbackfamilyfont} 还可以用于设置多层的备用字体。例如,使用 % \begin{ctexexam} % \setCJKmainfont[AutoFakeBold,AutoFakeSlant]{KaiTi_GB2312} % \setCJKfallbackfamilyfont{\CJKrmdefault}[AutoFakeSlant] % { [BoldFont=SimHei]{SimSun} , % [AutoFakeBold] {SimSun-ExtB} } % \end{ctexexam} % 之后,就设置了 |SimSun| 是 |KaiTi_GB2312| 的备用字体,而 |SimSun-ExtB| 是 % |SimSun| 的备用字体。若当前字体族缺字,并没有备用字体,则尝试使用 % \tn{CJKfamilydefault} 的备用字体。 % \end{function} % % \subsubsection{\XeTeX 的字体名查找} % \label{subsubsec:fontsearch} % % 由于在 \pkg{fontspec} 宏包文档中缺少关于如何查看 \XeTeX{} 可用字体名的说明, % 这里略作说明。 % % \XeTeX{} 通常使用 fontconfig 库查找和调用字体,因此,可以用 |fc-list| 命令显 % 示可用的字体。在命令行(Windows 的“命令提示符”,Linux 的 Console)下运行以 % 下命令: % \begin{frameverb} % fc-list > fontlist.txt % \end{frameverb} % 可以将系统中所有安装的字体列表存入 \file{fontlist.txt} 文件中(可能很长)。 % % |fc-list| 命令列出的信息很多,而且在安装字体较多的 Windows 系统上的输出将非 % 常庞大,如其中可能包含: % \begin{frameverb} % Times New Roman:style=cursiva,kurzíva,kursiv,Πλάγια,Italic, % Kursivoitu,Italique,Dőlt,Corsivo,Cursief,kursywa,Itálico,Курсив, % İtalik,Poševno,nghiêng,Etzana % Times New Roman:style=Negreta cursiva,tučné kurzíva,fed kursiv, % Fett Kursiv,Έντονα Πλάγια,Bold Italic,Negrita Cursiva, % Lihavoitu Kursivoi,Gras Italique,Félkövér dőlt,Grassetto Corsivo, % Vet Cursief,Halvfet Kursiv,Pogrubiona kursywa,Negrito Itálico, % Полужирный Курсив,Tučná kurzíva,Fet Kursiv,Kalın İtalik, % Krepko poševno,nghiêng đậm,Lodi etzana % Times New Roman:style=Negreta,tučné,fed,Fett,Έντονα,Bold,Negrita, % Lihavoitu,Gras,Félkövér,Grassetto,Vet,Halvfet,Pogrubiona,Negrito, % Полужирный,Fet,Kalın,Krepko,đậm,Lodia % Times New Roman:style=Normal,obyčejné,Standard,Κανονικά,Regular, % Normaali,Normál,Normale,Standaard,Normalny,Обычный,Normálne,Navadno, % thường,Arrunta % 宋体,SimSun:style=Regular % 黑体,SimHei:style=Normal,obyčejné,Standard,Κανονικά,Regular,Normaali, % Normál,Normale,Standaard,Normalny,Обычный,Normálne,Navadno,Arrunta % \end{frameverb} % 在 \pkg{fontspec} 或 \pkg{xeCJK} 中使用的字体族名是上面列表中冒号前的部分。 % 例如可以使用 % \begin{ctexexam} % \setmainfont{Times New Roman} % \setCJKmainfont{SimSun} % 或者 \setCJKmainfont{宋体} % \end{ctexexam} % 来设置字体。 % % 为了方便起见,|fc-list| 命令也可以加上各种选项控制输出格式,例如,如果只要列出 % 所有的中文字体的字体族名,可以用命令: % \begin{frameverb} % fc-list -f "%{family}\n" :lang=zh > zhfont.txt % \end{frameverb} % 这样就把字体列表保存在文件 \file{zhfont.txt} 中\footnote{由于汉字编码原因, % Windows 下总需要把字体列表输出到文件中以防止乱码。}。这样列出的字体列表就比较 % 简明易用,如 Windows 下预装的中文字体: % \begin{frameverb} % Arial Unicode MS % FangSong,仿宋 % KaiTi,楷体 % Microsoft YaHei,微软雅黑 % MingLiU,細明體 % NSimSun,新宋体 % PMingLiU,新細明體 % SimHei,黑体 % SimSun,宋体 % \end{frameverb} % 要列出日文和韩文的字体,可以把~|:lang=zh| 选项中的 |zh| 改成 |ja| 或 |ko|。 % % \pkg{fontspec} 和 \pkg{xeCJK} 也可以使用字体的文件名访问字体。例如 Windows % 下的宋体也可以使用命令: % \begin{frameverb} % \setCJKmainfont{simsun.ttc} % \end{frameverb} % 来设置。设置字体文件名的相关选项和语法在 \pkg{fontspec} 宏包手册中叙述甚详, % 这里不再赘述。有个别字体名不规范的中文字体,\pkg{xeCJK} 宏包可能无法正确地通 % 过字体名访问,那么也可以使用这种方式设置。 % % \subsection{CJK 分区字体设置} % \label{subsec:block} % % 众所周知,CJK 文字数量极其庞大,单一的字体不可能涵盖所有的 CJK 文字。\pkg{xeCJK} 可 % 以在同一 CJK 字体族下,自动使用不同的字体输出 CJK 字符范围内不同区块里的文字。首先要 % 声明 CJK 子分区。 % % \begin{function}[EXP]{\xeCJKDeclareSubCJKBlock} % \begin{syntax} % \tn{xeCJKDeclareSubCJKBlock} \Arg{block} \Arg{block range} % \tn{xeCJKDeclareSubCJKBlock} * \Arg{block} \Arg{block range} % \end{syntax} % 其中 \meta{block range} 是逗号列表,可以是 CJK 字符的 |Unicode| 范围,也可以是单个字符 % 的 |Unicode|。例如 % \end{function} % \begin{ctexexam} % { `中 -> `文 , "3400 -> "4DBF , "5000 -> "7000 , `汉 , `字 , "3500 } % \end{ctexexam} % 的形式。需要注意的是,这里设置的 \meta{block range} 除非确实需要(例如某些特殊字体使用 % 了 |Unicode| 中的私人使用区的情况),否则不要超出源代码中预设的 % \hyperlink{CJKcharclass}{CJK 文字范围}。使用 % \begin{ctexexam} % \xeCJKDeclareSubCJKBlock{SPUA}{ "E400 -> "E4DA , "E500 -> "E5E8 , "E600 -> "E6CE } % \xeCJKDeclareSubCJKBlock{Ext-B}{ "20000 -> "2A6DF } % \end{ctexexam} % 会声明 |SPUA| 和 |Ext-B| 这两个子分区;同时,这将会在 \ref{subsec:fontset}~节当中介绍的 % CJK 字体设置命令的 \meta{font features} 里新建 |SPUA| 和 |Ext-B| 两个选项。 % 新建的这两个选项,其使用方法与 \ref{subsec:fontset} 介绍的 |FallBack| 类似——可以 % 通过它们来设置字体。 % % 例如,可以使用 % \begin{ctexexam} % \setCJKmainfont[SPUA=SunmanPUA,Ext-B=SimSun-ExtB]{SimSun} % \end{ctexexam} % 设置文档的主字体是 |SimSun|,|SPUA| 分区的字体是 |SunmanPUA|,同时设置 |Ext-B| 分区 % 的字体是 |SimSun-ExtB|。 % % \tn{xeCJKDeclareSubCJKBlock} 应该在声明所有的 CJK 字体族之前使用。如果有某个 CJK 字 % 体族没有设置 \meta{block} 选项,将使用 \tn{CJKfamilydefault} 的 \meta{block} 选项 % 作为该 CJK 字体族的 \meta{block} 选项。如果希望在使用某 CJK 字体族时,不在 CJK 主 % 分区与 \meta{block} 之间切换字体,可以使用 \meta{block}|=*| 选项。带星号的命令除了 % 设置 CJK 子分区以外,还重置标点符号所属的字符类。 % % \begin{function}{\xeCJKCancelSubCJKBlock} % \begin{syntax} % \tn{xeCJKCancelSubCJKBlock} \Arg{block_1, block_2, ...} % \tn{xeCJKCancelSubCJKBlock} * \Arg{block_1, block_2, ...} % \end{syntax} % 在文档中取消对 |CJK| 分区的声明。带星号的命令还重置标点符号所属的字符类。 % \end{function} % % \begin{function}{\xeCJKRestoreSubCJKBlock} % \begin{syntax} % \tn{xeCJKRestoreSubCJKBlock} \Arg{block_1, block_2, ...} % \tn{xeCJKRestoreSubCJKBlock} * \Arg{block_1, block_2, ...} % \end{syntax} % 在文档中恢复对 |CJK| 分区的声明。带星号的命令还重置标点符号所属的字符类。 % \end{function} % % \subsection{设置 CJK 字符范围} % % \begin{function}[EXP]{\xeCJKDeclareCharClass} % \begin{syntax} % \tn{xeCJKDeclareCharClass} \Arg{class} \Arg{class range} % \tn{xeCJKDeclareCharClass} * \Arg{class} \Arg{class range} % \end{syntax} % \meta{class range} 的格式和 \ref{subsec:block}~节的 \meta{block range} 相同。 % \meta{class} 的有效值见源代码(第 \ref{sec:xeCJK-class-set}~节)。\pkg{xeCJK} 已 % 经支持 |Unicode| 中所有 CJK 文字和标点。一般来说,不要轻易改变字符类别。带星号的 % 命令除了设置字符类别以外,为了确保标点处理的正确性,还重置标点符号所属的字符类。 % \end{function} % % \begin{function}[EXP]{\xeCJKResetCharClass} % 用于恢复 \pkg{xeCJK} 对各个字符类别的初始化设置。 % \end{function} % % \begin{function}[EXP]{\xeCJKResetPunctClass} % 用于重置标点符号所属的字符类。 % \end{function} % % \begin{function}{\normalspacedchars} % \begin{syntax} % \tn{normalspacedchars} \Arg{char list} % \end{syntax} % 在 \meta{char list} 中出现的字符两端不自动添加空格, % 初始设置是~\mdcode{/}、\mdcode{\textbackslash}~和~\mdcode{-} (|U+002D|)。 % \end{function} % % \subsection{标点符号的处理} % % \pkg{xeCJK} 对标点符号的输出宽度的调整是通过调整其左边或右边的空白宽度来实现的。 % 按照目前的处理方式,对于位于左边的标点符号(如左引号),\pkg{xeCJK} 只能调整它 % 左边的空白;对于位于右边的标点符号(如右引号),\pkg{xeCJK} 只能调整它右边的空 % 白;对于居中的标点符号,则调整其左右空白,以保证其居中。对于标点符号的相关设置, % 只能在导言区中进行。 % % \subsubsection{设置特定标点符号的宽度和间距} % \label{subsec:punct} % % 这里的设置可用于除 |plain| 以外的所有标点处理格式。 % % \begin{function}[EXP,updated=2013-08-22]{\xeCJKsetwidth} % \begin{syntax} % \tn{xeCJKsetwidth} \Arg{标点列表} \Arg{length} % \tn{xeCJKsetwidth} * \Arg{标点列表} \Arg{length} % \end{syntax} % \meta{标点列表} 可以是单个标点,也可以是多个标点。例如, % \begin{ctexexam} % \xeCJKsetwidth{。?}{0.7em} % \end{ctexexam} % 将设置句号和问号所占的宽度为 |0.7em|。带星号的命令,设置标点符号出现在行首/尾 % 时的宽度。 % \end{function} % % \begin{function}[EXP]{\xeCJKsetkern} % \begin{syntax} % \tn{xeCJKsetkern} \Arg{前标点} \Arg{后标点} \Arg{length} % \end{syntax} % \pkg{xeCJK} 会根据选定的标点处理格式自动调整相邻的前后两个 |CJK| 标点符号的 % 空白宽度。如果需要对个别情况进行特殊调整,可以使用这个命令。例如, % \begin{ctexexam} % \xeCJKsetkern{:}{“}{0.3em} % \end{ctexexam} % 将设置冒号与左双引号之间的空白宽度为 |0.3em|。 % \end{function} % % \subsubsection{定义标点符号处理格式} % \label{subsec:punctstyle} % % \begin{function}[EXP,updated=2013-08-22]{\xeCJKDeclarePunctStyle} % \begin{syntax} % \tn{xeCJKDeclarePunctStyle} \Arg{style} \Arg{options} % \end{syntax} % 定义新的标点符号处理格式,已经存在的同名格式将被覆盖。可以设置的选项将在下面介绍。 % \end{function} % % \begin{function}[EXP,updated=2013-08-22]{\xeCJKEditPunctStyle} % \begin{syntax} % \tn{xeCJKEditPunctStyle} \Arg{style} \Arg{options} % \end{syntax} % 修改已有的标点符号处理格式。 % \end{function} % % 下面是可以设置的标点符号格式选项。其中左边一栏是选项名称,中间是选项的输入值类 % 型,右边则是相关说明。某些选项之间是互斥的,具有优先级关系。要使下一级的选项有 % 效,则需要先禁用上一级的设置:对于 \meta{boolean} 类型的选项,将其设置为 |false|, % 对于 \meta{length} 类型的选项,将其设置为 \tn{maxdimen},而对于 \meta{real} 类型 % 的选项,将其设置为 |nan|。 % % \changes{v3.10.3}{2026/07/13} % {新增 \texttt{enabled-left-right-kerning} 标点格式选项(\#975)。} % \begin{keyval} % \PSKeyVal{enabled-global-setting}{\TTF} % 是否使用 \tn{xeCJKsetup} 的 |PunctWidth|、|PunctBoundWidth| 选项和 % \tn{xeCJKsetwidth}、\tn{xeCJKsetkern} 的设置。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{fixed-punct-width}{length} % 设置单个标点符号的宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{fixed-punct-ratio}{real} % 设置单个标点符号的输出宽度与实际宽度的比例。默认值是 |1.0|。 % \PSKeyVal{mixed-punct-width}{length} % 设置句末标点符号的宽度。其中句末标点符号通过 \tn{xeCJKsetup} 的 |KaiMingPunct| % 来设置。默认值是与 |fixed-punct-width| 选项的值相同。 % \PSKeyVal{mixed-punct-ratio}{real} % 设置句末标点符号的宽度比例。默认值是与 |fixed-punct-ratio| 选项的值相同。 % \PSKeyVal{middle-punct-width}{length} % 设置居中标点符号的宽度。其中居中标点符号通过 \tn{xeCJKsetup} 的 |MiddlePunct| % 来设置。默认值是与 |fixed-punct-width| 选项的值相同。 % \PSKeyVal{middle-punct-ratio}{real} % 设置居中标点符号的宽度比例。默认值是与 |fixed-punct-ratio| 选项的值相同。 % \end{keyval} % % 以上几个选项设置的是标点的固定宽度或比例,\pkg{xeCJK} 会根据设定的选项计算标点符号 % 左/右的空白宽度。下面的选项设置的是标点符号左/右的空白宽度或比例,因此不同标点符号 % 的宽度可能会不同。为了使下面的选项生效,需要先禁用上面的相应选项。优先级自上而下。 % % \begin{keyval} % \PSKeyVal{fixed-margin-width}{length} % 设置标点的左/右空白宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{fixed-margin-ratio}{real} % 设置标点的左/右空白宽度与当前字体中该标点的相应实际边界宽度的比例。默认值是~|1.0|。 % \PSKeyVal{mixed-margin-width}{length} % 设置句末标点的左/右空白宽度。默认值是与 |fixed-margin-width| 的值相同。 % \PSKeyVal{mixed-margin-ratio}{real} % 设置句末标点的左/右空白宽度的比例。默认值是与 |fixed-margin-ratio| 的值相同。 % \PSKeyVal{middle-margin-width}{length} % 设置居中标点的两边空白宽度。默认值是与 |fixed-margin-width| 的值相同。 % \PSKeyVal{middle-margin-ratio}{real} % 设置居中标点的两边空白宽度之和与两边实际两边边界宽度之和的比例。 % 默认值是与 |fixed-margin-ratio| 的值相同。 % \end{keyval} % % 下面选项设置标点符号出现在行首或者行尾时的宽度或比例。 % % \begin{keyval} % \PSKeyVal{bound-punct-width}{length} % 设置标点符号出现在行首/尾时的宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{bound-punct-ratio}{real} % 设置标点符号出现在行首/尾时的输出宽度与实际宽度的比例。默认值是 |nan|。 % \PSKeyVal{bound-margin-width}{length} % 设置标点符号出现在行首/尾时的左/右空白宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{bound-margin-ratio}{real} % 设置标点符号出现在行首/尾时的左/右空白宽度与相应实际边界宽度的比例。默认值是 |0|。 % \PSKeyVal{enabled-hanging}{\TFF} % 当以上选项的计算结果得到的宽度小于标点符号的实际边界宽度时,是否允许标点符号 % 悬挂出页面边界。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{add-min-bound-to-margin}{\TFF} % 是否在以上计算结果的基础上再加上标点的左右实际边界宽度中的最小值。这个选项 % 对居中的标点无效。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{optimize-margin}{\TFF} % 使用以上设置空白宽度或比例的选项时,最终输出的标点符号左/右的空白宽度可能大 % 于原来的实际边界宽度。若此时本选项被设置为 |true|,则使用原来的实际边界宽度。 % 而使用 |fixed-punct-width| 选项计算得出的左/右宽度可能小于该标点的另一侧宽 % 度,若此时本选项被启用,则使用该标点的另一侧宽度。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{margin-minimum}{length} % 指定标点符号左/右的最小空白宽度。当经过以上选项设置的空白宽度小于这个选项的值 % 时,则使用这个选项的值。默认值是 |0pt|。 % \end{keyval} % % 下面的选项处理的是前后相邻的两个标点符号之间的空白宽度。这些选项是互斥的,优先级 % 自上而下。 % % \begin{keyval} % \PSKeyVal{enabled-kerning}{\TTF} % 是否调整前后相邻的两个标点之间的空白宽度。如果设置为 |false|,则每个标点都按 % 原来的输出宽度输出。 % \PSKeyVal{enabled-left-right-kerning}{\TTF} % 是否压缩左标点后紧跟右标点时二者之间的空白。 % 预设值 |quanjiao| 将它设为 |false|,保留字体的自然空白。 % 通过 \tn{xeCJKsetkern} 显式设置的字符对不受此选项影响。 % \PSKeyVal{min-bound-to-kerning}{\TFF} % 是否使用当前字体中前面标点实际左右边界的最小值与后面标点实际左右边界的最小值 % 中的最大值作为两个标点之间的空白宽度。 % \PSKeyVal{kerning-total-width}{length} % 设置两个标点的总共宽度。此时 \pkg{xeCJK} 会自动计算两个标点之间的空白宽度。 % 默认值是 \tn{maxdimen}。 % \PSKeyVal{kerning-total-ratio}{real} % 设置两个标点的总共输出宽度与实际宽度的比例。默认值是 |0.75|。 % \PSKeyVal{same-align-margin}{length} % 前后两个标点位于同侧时,它们之间的空白宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{same-align-ratio}{real} % 前后两个标点位于同侧时,它们之间的空白宽度与实际输出宽度的比例。默认值是~|nan|。 % \PSKeyVal{different-align-margin}{length} % 前后两个标点位于异侧时,它们之间的空白宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{different-align-ratio}{real} % 前后两个标点位于异侧时,它们之间的空白宽度与实际输出宽度的比例。默认值是~|nan|。 % \PSKeyVal{kerning-margin-width}{length} % 设置前后两个标点之间的空白宽度。默认值是 \tn{maxdimen}。 % \PSKeyVal{kerning-margin-ratio}{real} % 设置前后两个标点之间的空白宽度与实际输出空白的比例。默认值是 |1.0|。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{optimize-kerning}{\TFF} % 以上选项计算的标点间空白宽度,可能小于 |min-bound-to-kerning| 选项的结果。 % 当出现这一情况时,若此选项被设置为 |true|,则使用该选项的空白宽度。 % \end{keyval} % % \begin{keyval} % \PSKeyVal{kerning-margin-minimum}{length} % 指定两个标点之间的最小空白宽度。当经过以上选项设置的空白宽度小于这个选项的值 % 时,则使用这个选项的值。默认值是 |0pt|。 % \end{keyval} % % 事实上,\pkg{xeCJK} 的默认设置就相当于中文全角(|quanjiao|)格式。可以使用上面 % 说明的选项定义新的标点处理格式。例如,使用 % \begin{ctexexam} % \xeCJKDeclarePunctStyle { mine } % { % fixed-punct-ratio = nan , % fixed-margin-width = 0 pt , % mixed-margin-width = \maxdimen , % mixed-margin-ratio = 0.5 , % middle-margin-width = \maxdimen , % middle-margin-ratio = 0.5 , % add-min-bound-to-margin = true , % bound-punct-width = 0 em , % enabled-hanging = true , % min-bound-to-kerning = true , % kerning-margin-minimum = 0.1 em % } % \end{ctexexam} % 就定义了一个名为 |mine| 的标点处理格式。可以在导言区通过 % \begin{frameverb} % \xeCJKsetup{PunctStyle=mine} % \end{frameverb} % 在文档中使用这个格式。它的意义是:使用标点符号的实际左右边界中的最小值作为其左/右 % 空白的宽度,对于句末标点和居中标点,再加上实际边界空白的一半;当标点出现在行首或 % 行尾时宽度为零,允许悬挂出页面边界;使用相邻两个标点的实际边界中的较小值作为它们 % 之间的空白宽度,并且最小的空白宽度是 |0.1em|。再例如,使用 % \begin{ctexexam} % \xeCJKEditPunctStyle { hangmobanjiao } { enabled-global-setting = false } % \end{ctexexam} % 将使得 \tn{xeCJKsetkern} 等的设置对 |hangmobanjiao| 这一格式无效。 % % \subsection{\pkg{xeCJKfntef} 用法说明} % % \pkg{xeCJK} 包含有一个子宏包 \pkg{xeCJKfntef},可以用它来实现\CJKunderdot{给汉字加点}% % 以及\CJKunderline{可正确断行的下划线}等效果。它是 \pkg{CJKfntef} 宏包在 \XeLaTeX 下的替换版本,基本用法完全一致。 % % \pkg{xeCJKfntef} 基于 \package{ulem} 宏包,除了兼容 \pkg{ulem} 定义的一些命令外, % 还进行了一些扩充: % % \begin{function}[updated=2014-11-04] % {\CJKunderline,\CJKunderdblline,\CJKunderwave,\CJKsout,\CJKxout} % \begin{syntax} % \tn{CJKunderline} [*] [-] \oarg{选项} \Arg{内容} % \end{syntax} % \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=6] % \CJKunderline{虚室生白,吉祥止止}\\ % \CJKunderdblline{虚室生白,吉祥止止}\\ % \CJKunderwave{虚室生白,吉祥止止}\\ % \CJKsout{虚室生白,吉祥止止}\\ % \CJKxout{虚室生白,吉祥止止} % \end{SideBySideExample} % \end{function} % % \csappto{NoHighlight@Attributes}{\catcode37=14\relax} % % \begin{Example}[frame=single,numbers=left,gobble=4] % \CJKunderline-{南朝}\CJKunderline-{梁}\CJKunderline-{劉勰}% % \CJKunderwave-{文心雕龍}\CJKunderwave-{養氣}\\ % \CJKunderline*[thickness=1pt, hidden=true]{瞻彼阕者,虚室生白,吉祥止止} % \end{Example} % % \begin{function}[updated=2014-11-04]{\CJKunderdot} % \begin{syntax} % \tn{CJKunderdot} \oarg{选项} \Arg{内容} % \end{syntax} % 在汉字下加点,可以和上述下划线命令嵌套使用,嵌套后仍然可以正常断行。例如\smallskip % % \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=6] % \CJKunderline{虚室生白,\CJKunderdot{吉祥}止止}\\ % \CJKunderdot{虚室生白,\CJKunderline{吉祥}止止} % \end{SideBySideExample} % 下划线之间的嵌套则有断行方面的限制,详见本节末尾“嵌套使用时的断行”。 % \end{function} % % \bigskip % % 对上述六种对象,\pkg{xeCJKfntef} 提供了一些选项,设置点或线的位置和颜色。可以用 % \tn{xeCJKsetup} 预先统一设置它们,也可以在使用时特别设置。 % % \begin{function}[added=2014-11-04]{skip} % \begin{syntax} % \tn{xeCJKsetup} \{ underline/skip = \meta{\TTF} \} % \tn{xeCJKsetup} \{ underline = \{ skip = \meta{\TTF} , ... \} \} % \end{syntax} % 默认情况下,下划线会自动跳过中文标点符号,可以设置本选项为 \texttt{false}, % 禁用这一功能。相应下划线命令后加上 |*| 号,具有相同的效果。 % \end{function} % % \begin{function}{subtract} % 设置本选项为 \texttt{true},使得下划线的首尾减少一定距离,避免前后的下划线 % 连在一起,适用于古籍标点整理中的专名号和书名号。在相应下划线命令后加上~|-| % 号,具有相同的效果。 % \end{function} % % \begin{function}{hidden} % 设置本选项为 \texttt{true},将隐藏文本内容,只画下划线。 % \end{function} % % \begin{function}{format} % \begin{syntax} % \tn{xeCJKsetup} \{ underline/format = \tn{color}\{red\} \} % \tn{xeCJKsetup} \{ underwave = \{ format = \tn{color}\{red\}, ... \} \} % \end{syntax} % 设置线或点的格式,比如颜色。 % \end{function} % % \begin{function}[added=2016-06-03]{textformat} % 设置下划线或点的正文的格式。例如:\smallskip % \begin{Example}[frame=single,numbers=left,gobble=6] % \CJKunderline[textformat=\color{blue}]{虚室生白,吉祥止止}\\ % \CJKunderdot[textformat=\bfseries, format=\color{red}]{虚室生白,吉祥止止} % \end{Example} % \end{function} % % \begin{function}{symbol} % 设置 \tn{CJKunderwave} 或 \tn{CJKunderdot} 的符号。 % \end{function} % % 默认波浪线由 \pkg{l3draw} 绘制,每个全角字符约容纳四个完整周期,符号的宽度、 % 振幅和粗细都会随字号缩放。默认斜删除线也使用相同比例的矢量图案,不再依赖 % 数学字体。两种默认图案都用普通 \tn{leaders} 排列,使同一行中被正文片段和 % 中文字间距分开的图案保持相同相位。命令后的减号会让装饰在正文左右各内缩半个 % 周期,因此相邻命令之间会留下一个完整周期的断口。也可以用本选项换成其他波浪 % 符号。例如,下面改用 \texttt{lasy} 字体中的波浪符号: % % \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.35\linewidth,gobble=4] % % \usepackage{fix-cm} % \xeCJKsetup{% % underwave/symbol= % \fontsize{0.5em}{0pt}% % \fontencoding{U}\fontfamily{lasy}\selectfont % \char 58\relax} % \footnotesize % \CJKunderwave{瞻彼阕者,虚室生白,吉祥止止} % \end{SideBySideExample} % % \begin{function}{thickness} % 设置 \tn{CJKunderline}、\tn{CJKunderdblline} 和 \tn{CJKsout} 的线的厚度。 % 初始值是 \tn{ULthickness}。 % \end{function} % % \begin{function}{depth} % 设置线或点的深度(基线到线或点的顶部的距离)。初始值都是 \texttt{0.2em}。 % \end{function} % % \begin{function}{boxdepth} % \tn{CJKunderdot} 可能会影响到行距,可以设置本选项进行调整。如果不希望 % \tn{CJKunderdot} 影响行距,可以将本选项设置为 \texttt{0pt}。 % \end{function} % % \begin{function}{sep} % 设置 \tn{CJKunderdot} 与 \tn{CJKunderline}、\tn{CJKunderdblline} 或 % \tn{CJKunderwave} 嵌套使用时,点与线或者线与点的距离。 % \end{function} % % \begin{function}{gap} % 设置 \tn{CJKunderdblline} 的两条线之间的距离。初始值是 \texttt{1.1pt}。 % \end{function} % % \begin{function}{height} % 设置删除线 \tn{CJKsout} 的高度(线的中心到基线的距离)。初始值是 \texttt{0.35em}。 % \end{function} % 例如,我们可以设置 \tn{CJKsout} 的厚度和颜色,让它具有类似高亮的效果:\smallskip % % \begin{Example}[frame=single,numbers=left] % \CJKsout*[thickness=2.5ex, format=\color{yellow}]{瞻彼阕者,虚室生白,吉祥止止} % \end{Example} % % \medskip % % 此外还有允许自定义下划线和符号的 \tn{CJKunderanyline} 和 \tn{CJKunderanysymbol}。 % % \begin{function}[updated=2014-11-07]{\CJKunderanyline} % \begin{syntax} % \tn{CJKunderanyline} [*] [-] \oarg{选项} \Arg{深度} \Arg{下划内容} \Arg{文本内容} % \end{syntax} % \pkg{xeCJKfntef} 先将 \meta{下划内容} 放进一个盒子(\tn{xeCJKfntefbox})里,然后 % 向下移动 \meta{深度} 给定的距离,再用于填充。可用的 \meta{选项} 是 \texttt{textformat}、 % \texttt{skip}、\texttt{hidden}、\texttt{subtract}、\texttt{sep} 和 % \texttt{boxdepth}。选项 \texttt{sep} 和 \texttt{boxdepth} 的初始值是空,表示 % 禁用该选项的功能。可以在 \tn{xeCJKsetup} 中通过对象 \texttt{ulem} 来设置。 % \end{function} % % 例如,高亮效果也可以如下实现:\smallskip % % \begin{Example}[frame=single,numbers=left,gobble=4] % \CJKunderanyline*{0.5ex}{\color{yellow}\rule{1.5pt}{2.5ex}}{虚室生白,吉祥止止} % \end{Example} % % \begin{function}[updated=2014-11-04]{\CJKunderanysymbol} % \begin{syntax} % \tn{CJKunderanysymbol} \oarg{选项} \Arg{深度} \Arg{符号} \Arg{文本内容} % \end{syntax} % \pkg{xeCJKfntef} 将 \meta{符号} 放进一个盒子(\tn{xeCJKfntefbox})里。 % \meta{深度} 参数用于设置盒子顶部的深度(基线到盒子顶部的距离)。 % 可用的 \meta{选项} 是 \texttt{textformat}、\texttt{sep} 和 \texttt{boxdepth},意义与 % \tn{CJKunderdot} 的相同。 % \end{function} % % 例如,给汉字加三角形,可以如下设置:\smallskip % % \begin{Example}[frame=single,numbers=left,gobble=4] % \CJKunderanysymbol[sep=0.1em]{0.2em}{\tiny$\triangle$} % {瞻彼阕者,虚室生白,\CJKunderline{吉祥止止}} % \end{Example} % % \begin{function}[updated=2014-11-07]{\xeCJKfntefon} % \begin{syntax} % \tn{xeCJKfntefon} [*] [-] \oarg{选项} % \end{syntax} % 功能与用法 \pkg{ulem} 宏包的 \tn{ULon} 相同,扩展了可选参数符号~\mdcode*{*} 和~\mdcode*{-}, % 可用的 \meta{选项} 是 \texttt{textformat}、 \texttt{skip}、\texttt{hidden} 和 % \texttt{subtract}。 这四个选项对 \pkg{ulem} 宏包定义的 \tn{uline} 等命令也有效,需要在 % \tn{xeCJKsetup} 中通过对象 \texttt{ulem} 来设置。例如\smallskip % % \begin{Example}[frame=single,numbers=left,gobble=6] % \xeCJKsetup{ulem={textformat=\bfseries\color{red}, skip=true}} % \uline{虚室生白,吉祥止止} % \end{Example} % \end{function} % % \medskip % % \pkg{xeCJKfntef} 的下划线和删除线命令内部使用 \package{ulem} 宏包的 % \tn{hbox} 机制排版文字。举例来说,|汉字 \CJKsout{文字} 汉字| 中 % \tn{CJKsout} 前后的源码空格会被 \pkg{xeCJK} 自动处理为 CJK 文字间距 % (\texttt{CJKglue});间距方面的效果与 |汉字 文字 汉字| 一致。 % 关于 \pkg{xeCJKfntef} 命令与颜色命令的嵌套组合等更多细节, % 请参见“已知问题和兼容性”一节中关于“CJK 文字与命令交互时的间距”的说明。 % % \medskip % % \subsubsection{嵌套使用时的断行} % \label{subsubsec:fntef-nest-linebreak} % % 按装饰的绘制方式,\pkg{xeCJKfntef} 的命令可以分成两类,它们在嵌套时的断行 % 能力不同: % % \begin{itemize} % \item 线型命令:\tn{CJKunderline}、\tn{CJKunderdblline}、\tn{CJKunderwave}、 % \tn{CJKsout}、\tn{CJKxout} 和 \tn{CJKunderanyline}。它们借助 \pkg{ulem} % 扫描正文并沿基线画出连续的线条。 % \item 符号型命令:\tn{CJKunderdot} 和 \tn{CJKunderanysymbol}。它们逐字放置 % 独立的点或符号,不需要 \pkg{ulem} 的扫描过程。 % \end{itemize} % % 单独使用时,以上命令都可以正常断行。符号型命令与任何命令嵌套,也都保持正常 % 断行。 % % 但是\emph{两个线型命令相互嵌套时,被嵌套的那一段无法断行}。原因是 \pkg{ulem} % 只允许最外层的线型命令启动扫描:内层命令会复用外层已经打开的扫描过程,它的 % 正文被整段放进一个固定宽度的盒子,盒子内部不再留有断点,因此 \TeX{} 只能让 % 这一段溢出到页面右边,并给出 \texttt{Overfull \tn{hbox}} 警告。嵌套的先后 % 顺序不影响这一结果,原生的 \tn{uline} 等 \pkg{ulem} 命令与本宏包的线型命令 % 相互嵌套时同样如此。 % % 这是 \pkg{ulem} 的扫描机制决定的,\pkg{xeCJKfntef} 目前没有绕开它的办法。 % 需要在较长的正文上同时得到两种装饰时,可以改用下面的写法: % % \begin{itemize} % \item 其中一种装饰改用符号型命令,例如把内层的 \tn{CJKunderwave} 换成 % \tn{CJKunderdot}; % \item 按语义把正文分成若干段,每段只用一个线型命令,段与段之间保留断行 % 位置; % \item 只在确实不需要断行的短语上使用嵌套,例如标题或表格单元内的少量文字。 % \end{itemize} % % \medskip % % 此外,\pkg{xeCJKfntef} 还提供了可指定宽度,并让汉字两端分散对齐的环境 % \env{CJKfilltwosides} 和 \env{CJKfilltwosides*}。 % % \begin{function}[updated=2014-11-04]{CJKfilltwosides} % \begin{syntax} % \tn{begin}\{CJKfilltwosides\} \oarg{位置} \Arg{宽度} % 文本内容\verb=\\= % 文本内容 % \tn{end}\{CJKfilltwosides\} % \end{syntax} % 环境中的内容被放入垂直盒子中,可选参数 \meta{位置} 指定盒子的基线位置。 % 可以使用 \texttt{t}(顶部)、\texttt{c}(居中)和 \texttt{b}(底部),默认是 \texttt{c}。 % \meta{宽度} 参数指定盒子的宽度。 % \env{CJKfilltwosides*} 环境与 \env{CJKfilltwosides} 的区别是,当 \meta{宽度} % 不大于零或者不大于盒子的自然宽度时,就取盒子的自然宽度。例如 % \end{function} % % \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.5\linewidth,gobble=4] % \begin{CJKfilltwosides}{.8\linewidth} % 瞻彼阕者,\\ % 虚室生白,吉祥止止 % \end{CJKfilltwosides} % \end{SideBySideExample} % % \begin{SideBySideExample}[frame=single,numbers=left,xrightmargin=.5\linewidth,gobble=4] % \begin{CJKfilltwosides*}{0pt} % 瞻彼阕者,\\ % 虚室生白,吉祥止止 % \end{CJKfilltwosides*} % \end{SideBySideExample} % % \subsection{其他} % \label{subsec:others} % % \begin{function}[updated=2013-11-16]{\xeCJKVerbAddon,\xeCJKOffVerbAddon} % 调整文字间距以便于让 CJK 字符占的宽度等于西文等宽字体中两个空格的宽度。如果这两 % 个空格的宽度小于当前 CJK 正常文字的宽度,将对 CJK 字体进行适当地缩小。这有利于 % 等宽字体的代码对齐等情形。需要注意的是,\tn{xeCJKVerbAddon} 对 \pkg{xeCJK} 的内 % 部进行了比较大的修改,使用它之后,将禁止在 CJK 字符类之间自动换行,这与西文在 % 抄录环境中的情况是一致的。所以不应该单独使用,应该放在分组里限制其作用域,否则 % 是无效的。当然它可以和其他关于代码抄录的宏包配合使用。例如,可以应用在 % \package{fancyvrb} 宏包的 \texttt{formatcom} 选项。此时设置的西文字体应该确实 % 是等宽的以保证对齐。若西文等宽字体发生变动(包括字体大小),则需要在其后面使用 % \tn{xeCJKVerbAddon},重新计算间距的宽度。\tn{xeCJKOffVerbAddon} % 用于在使用 \tn{xeCJKVerbAddon} 的环境中局部取消它的作用。由于 \package{listings} % 宏包有自己的代码对齐机制,所以 \tn{xeCJKVerbAddon} 在由 \pkg{listings} 定义的 % 代码环境中无效。 % \end{function} % % \begin{function}[added=2012-12-03]{\xeCJKnobreak} % \begin{syntax} % ……汉字。\tn{xeCJKnobreak}\tn{footnote}\{脚注\} % \end{syntax} % \tn{xeCJKnobreak} 用在全角标点符号后面,目的是确保不能在此处断行。如果已经启用了 % 前面介绍的 |CheckFullRight| 选项,则不需要再用此命令。 % \end{function} % % \begin{function}[added=2013-11-09]{\xeCJKShipoutHook} % \pkg{xeCJK} 在正文中的一些特殊设置(汉字下加点、在 \env{verbatim} 或 % \env{lstlisting} 环境中分页)可能会影响到 \TeX 的输出例行程序(output routine) % 中的内容(比如页眉和页脚)。\tn{xeCJKShipoutHook} 用于恢复正文中的普通设置。 % \pkg{xeCJK} 已经处理了页眉和页脚的情况,其他的就需要根据情况自行调用。 % 比如,若使用 \pkg{eso-pic} 或者 \pkg{atbegshi} 实现文字水印,并且正文中使用了 % 以上所列的特殊形式,就需要在命令 \tn{AtBeginShipout} 的参数的最前面使用 % \tn{xeCJKShipoutHook}。 % \end{function} % % \section{已知问题和兼容性} % % 根据 \pkg{unicode-data} 宏包,\XeTeX 将所有 CJK 表意文字的 \tn{catcode} % 设置为 $11$。因此汉字可以直接用作控制序列的名字,但是当汉字出现在控制序列后面的 % 时候,要用空格分隔开,否则就会出现“\texttt{! Undefined control sequence.}”的错误。 % % \pkg{xeCJK} 使用并重新定义了 \pkg{CJK} 宏包的部分宏命令,如 \tn{CJKfamily}、 % \tn{CJKsymbol} 和 \tn{CJKglue} 等。需要指出,\pkg{xeCJK} 不需要 \pkg{CJK} % 的支持,并且 \pkg{xeCJK} 自动禁止在它之后载入 \pkg{CJK} 宏包。 % 可以在 \pkg{xeCJK} 之\emph{后}载入 \pkg{CJKnumb} 宏包,实现数字的中文化, % 也可以用功能更完善的 \package{zhnumber} 宏包。 % % \pkg{xeCJK} 进行了一些处理,使得在使用 \XeTeX 时 \package{listings} 宏包可以 % 支持 Unicode,因此在 \texttt{listings} 定义的代码环境中可以直接使用中文,不再 % 需要使用 \texttt{escapechar}。 % % 新版本(\texttt{3.x})的 \pkg{xeCJK} 完全使用 \LaTeXiii{} 的语法来编写。 % \LaTeXiii{} 放弃了 \tn{outer} 宏的概念,因此相关工具在 % 遇到 \tn{outer} 宏时可能会存在问题。按照目前 \pkg{xeCJK} 的实现方式,在 CJK 文字 % 后面遇到 \tn{outer} 宏时会出现类似 % \begin{frameverb} % ! Forbidden control sequence found while scanning use of \use_i:nn % \end{frameverb} % 的错误。目前已知的有 \package{cprotect} 宏包提供的 \tn{cprotect}。它的定义是 % \begin{frameverb} % \outer\long\def\cprotect{\icprotect} % \end{frameverb} % 因此,这时可以暂时用 \tn{icprotect} 代替 \tn{cprotect}。事实上,当 \pkg{cprotect} % 被引入时,\pkg{xeCJK} 将使用 % \begin{frameverb} % \let\cprotect\icprotect % \end{frameverb} % 来取消 \tn{cprotect} 的外部宏限制。但由于 \tn{cprotect} 的特殊性,应该只在外部 % 使用它,即不要让它出现在任何宏的参数中。其他 \tn{outer} 宏的情况,可以在它前面 % 加上 \tn{relax} 来回避上面的错误。 % % \pkg{xeCJK} 依赖 \XeTeX 的 \tn{XeTeXinterchartoks} 机制,与使用相同机制的宏包(例如 % \pkg{polyglossia} 和 \pkg{xesearch})可能会存在不同程度的冲突。 % \pkg{xeCJK} 虽然为此作了一些处理,但与它们共同使用时应该小心。 % % \subsection{CJK 文字与命令交互时的间距} % % \pkg{xeCJK} 利用 \XeTeX 的 interchar 机制,在 CJK 文字之间自动插入 % |CJKglue|,在 CJK 文字与西文之间自动插入 |CJKecglue|,并自动忽略 % CJK 文字间的源码空格。当 CJK 文字被 \LaTeX 命令包裹时,命令引入的 % 分组、盒子或特殊节点可能干扰这一机制。\pkg{xeCJK} 的预期行为是: % \begin{frameverb} % 前 中 后 % 直接排版 % 前 \foo{中} 后 % 用命令包裹 % \end{frameverb} % 两种写法应产生相同的 CJK 间距。 % % 目前 \pkg{xeCJK} 已确保以下命令在此场景下的间距行为正确: % \begin{itemize} % \item 字体切换命令:\tn{textbf}、\tn{textit}、\tn{emph} 等。 % 这些命令仅切换字体,不改变正文的边界类别。 % \item 颜色命令:\tn{textcolor}、\tn{colorbox} 等 % (包括 \LaTeXe{} 和 \LaTeXiii{} 的颜色后端)。 % \item \pkg{xeCJKfntef} 命令:\tn{CJKunderline}、\tn{CJKunderdblline}、 % \tn{CJKunderwave}、\tn{CJKsout}、\tn{CJKxout}、\tn{CJKunderdot}。 % \item 盒子命令:\tn{mbox}、\tn{fbox}、\tn{makebox}、\tn{framebox} 等。 % \item \pkg{hyperref} 的链接注释:\tn{ref}、\tn{hyperref} 等。 % \item 以上命令的嵌套组合,例如: % \begin{frameverb} % 前 \CJKunderline{\textcolor{red}{中}} 后 % 前 \textcolor{red}{\CJKunderline{中}} 后 % \end{frameverb} % \end{itemize} % % 行内公式也按相同原则处理。\verb*-$x$-、\tn{(}\texttt{x}\tn{)} 和 % \tn{ensuremath}\verb*-{x}- 的边界与直接写出的行内公式一致;上述命令 % 包裹公式时,不应把公式误当成普通西文。公式旁有源码空格时, % \opt{xCJKecglue=false} 保留普通词间空格,\opt{xCJKecglue=true} 则将其 % 替换为 \opt{CJKecglue}。 % % 本节只讨论 CJK 间距,不涉及断行。\pkg{xeCJKfntef} 的线型命令相互嵌套时, % 间距仍然正确,但被嵌套的那一段无法断行, % 详见 \ref{subsubsec:fntef-nest-linebreak}~节。 % % 上述命令共用一套边界记录和间距恢复机制,但每类命令仍须提供明确的 % 开始和结束位置,不能自动识别任意宏的内部节点。 % 未列出的第三方命令如果在 CJK 文字间引入特殊的分组或节点结构, % 仍可能产生多余或缺失的间距。 % 如果遇到间距异常,欢迎到 % \href{https://github.com/CTeX-org/ctex-kit/issues}{GitHub Issues} % 提交兼容请求。 % % \subsection{\tn{char} 原语与 interchar 的冲突} % % \XeTeX 的 interchar 机制工作在 token 层面,无法区分 % \tn{char} 原语的输出和直接字符输入。因此当第三方宏包在内部使用 \tn{char} % 从非 CJK 字体取字形时,如果该字符编码恰好属于 \pkg{xeCJK} 的 % interchar 字符分类,就会被拦截并切换到 CJK 字体,产生错误的字形。 % 例如 \pkg{mtpro2} 的~|\overcbrace| 通过~|\char183| 取 % 花括号字形,而 |U+00B7|(MIDDLE DOT)在 \pkg{xeCJK} 中属于 % |FullRight| 类,导致花括号变成中文间隔号。 % % 为此,\pkg{xeCJK} 提供 \tn{xeCJKchar} 命令,用法与 \tn{char} 完全 % 相同,但在输出字符前临时关闭 |XeTeXinterchartokenstate|。 % 用户可以使用~|\xeCJKchar"00B7| 代替~|\char"00B7| % 来避免 interchar 拦截。 % % \pkg{xeCJK} 已为以下宏包的命令提供了自动兼容补丁: % \begin{itemize} % \item \pkg{mtpro2}:|\overcbrace|、|\undercbrace| % \item \pkg{pifont}:|\Pifont|(|U| 编码字体切换) % \end{itemize} % 如果遇到其他宏包的类似问题,可以在导言区手动包装有冲突的命令: % \begin{frameverb} % % 方法一:直接用 \xeCJKchar 替换 \char % \hbox{\myfont\xeCJKchar183} % % % 方法二:包装第三方命令,用 \makexeCJKinactive 关闭 interchar % \let\origProblemCmd\problemCmd % \renewcommand\problemCmd[1]{\makexeCJKinactive \origProblemCmd{#1}} % \end{frameverb} % |\makexeCJKinactive| 是赋值操作,受 \TeX{} 分组约束。这即是说, % 当用户在 |{...}|、|$...$| 或~|\begingroup...\endgroup| % 内使用时,结束后自动恢复。 % % \subsection{\pkg{hyperref} 链接与 CJKecglue} % % \pkg{hyperref} 的链接注释命令(如 \tn{ref}、\tn{hyperref} 等) % 在内部会产生 |pdf:bann| / |pdf:eann| 特殊节点。 % \pkg{xeCJK} 对此做了兼容处理,确保链接注释不影响正常的 % CJKecglue 插入。例如,以下写法的排版效果与不加 \tn{ref} 时等价: % \begin{frameverb} % 图\ref{fig}图 % 等价于"图1图" % \end{frameverb} % 无论是否加载 \pkg{hyperref},\tn{ref} 前后都会自动插入 % 适当的 CJKecglue。 % % \end{documentation} % % % \StopEventually{} % % % \begin{implementation} % % \section{\pkg{xeCJK} 代码实现} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=xeCJK> % \end{macrocode} % % \subsection{运行环境检查} % % \pkg{xeCJK} 必须使用 \XeTeX 引擎的支持。 % \begin{macrocode} \msg_new:nnn { xeCJK } { Require-XeTeX } { The~xeCJK~package~requires~XeTeX~to~function.\\\\ You~must~change~your~typesetting~engine~to~"xelatex" \\ instead~of~plain~"latex"~or~"pdflatex"~or~"lualatex".\\ Loading~xeCJK~will~abort! } \sys_if_engine_xetex:F { \msg_critical:nn { xeCJK } { Require-XeTeX } } % \end{macrocode} % % 应该使用较新版本的 \pkg{expl3} 宏包。 % \changes{v3.10.0}{2026/05/03}{提升 \LaTeXiii{} 最低版本要求至 2025/10/09。} % \begin{macrocode} \msg_new:nnn { xeCJK } { l3-too-old } { Support~package~`#1'~too~old. \\\\ Please~update~an~up~to~date~version~of~the~bundles\\\\ `l3kernel'~and~`l3packages'\\\\ using~your~TeX~package~manager~or~from~CTAN.\\ \str_if_eq:nnT {#1} { expl3 } { Loading~xeCJK~will~abort! } } \@ifpackagelater { expl3 } { 2025/10/09 } { } { \msg_critical:nnn { xeCJK } { l3-too-old } { expl3 } } % \end{macrocode} % % \cs{ctex_disable_package:n} 由 \pkg{ctexhook} 提供, % \cs{ctex_patch_cmd:Nnn} 由 \pkg{ctexpatch} 提供。 % \begin{macrocode} \RequirePackage { ctexhook } \RequirePackage { ctexpatch } % \end{macrocode} % % \begin{macro}[pTF,int]{\xeCJK_if_package_loaded:n} % 判断宏包是否被引入,可用于文档正文中。 % \begin{macrocode} \prg_new_conditional:Npnn \xeCJK_if_package_loaded:n #1 { p , T , F , TF } { \tl_if_exist:cTF { ver@ #1 . \c_@@_package_ext_tl } { \prg_return_true: } { \prg_return_false: } } \tl_const:Ne \c_@@_package_ext_tl { \@pkgextension } % \end{macrocode} % \end{macro} % % \changes{v3.8.7}{2021/06/09}{应用 \tn{disable@package@load} 和 % \tn{declare@file@substitution}。} % % 下面这些 \pkg{CJK} 系列宏包不应该被使用。 % \begin{macrocode} \msg_new:nnn { xeCJK } { after-package } { The~`#1'~package~and~xeCJK~are~incompatible.\\\\ Please~load~it~after~xeCJK. } \clist_map_inline:nn { CJKnumb } { \xeCJK_if_package_loaded:nT {#1} { \msg_error:nnn { xeCJK } { after-package } {#1} } } \clist_map_inline:nn { CJKulem , CJKvert , CJKpunct , CJKutf8 , CJK } { \ctex_disable_package:n {#1} } % \end{macrocode} % % \changes{v3.8.7}{2021/06/09}{将 \pkg{CJKfntef} 包替换为 \pkg{xeCJKfntef} 包。} % % 将 \pkg{CJKfntef} 包替换为 \pkg{xeCJKfntef} 包。 % \begin{macrocode} \ctex_if_format_at_least:nTF { 2020/10/01 } { \ctex_replace_package:nn { CJKfntef } { xeCJKfntef } } { \ctex_disable_package:n { CJKfntef } } % \end{macrocode} % % \changes{v3.9.0}{2022/07/08}{不直接依赖 \pkg{xparse} 和 \pkg{l3keys2e}。} % % \begin{macrocode} \cs_if_exist:NF \NewDocumentCommand { \RequirePackage { xparse } } \RequirePackage { xtemplate } % \end{macrocode} % % \subsection{内部工具} % % 分配临时变量。 % \begin{macrocode} \tl_new:N \l_@@_tmp_tl \tl_new:N \l_@@_tmpb_tl \int_new:N \l_@@_tmp_int \box_new:N \l_@@_tmp_box \dim_new:N \l_@@_tmp_dim \bool_new:N \l_@@_tmp_bool \skip_new:N \l_@@_tmp_skip \clist_new:N \l_@@_tmp_clist % \end{macrocode} % % \begin{macro} % {\@@_msg_new:nn ,\@@_error:n,\@@_error:ne,\@@_warning:ne,\@@_info:nee} % 各种信息函数的缩略形式。 % \begin{macrocode} \cs_new_protected:Npn \@@_msg_new:nn { \msg_new:nnn { xeCJK } } \cs_new_protected:Npn \@@_msg_new:nnn { \msg_new:nnnn { xeCJK } } \cs_new_protected:Npn \@@_error:n { \msg_error:nn { xeCJK } } \cs_new_protected:Npn \@@_error:ne { \msg_error:nne { xeCJK } } \cs_new_protected:Npn \@@_warning:n { \msg_warning:nn { xeCJK } } \cs_new_protected:Npn \@@_warning:ne { \msg_warning:nne { xeCJK } } \cs_new_protected:Npn \@@_warning:nee { \msg_warning:nnee { xeCJK } } \cs_new_protected:Npn \@@_warning:neee { \msg_warning:nneee { xeCJK } } \cs_new_protected:Npn \@@_info:nee { \msg_info:nnee { xeCJK } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_allow_break:,\xeCJK_no_break:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_allow_break: { \tex_penalty:D \c_zero_int } \cs_new_protected:Npn \xeCJK_no_break: { \tex_penalty:D \c_@@_nobreak_penalty_int } \int_const:Nn \c_@@_nobreak_penalty_int { 10 000 } % \end{macrocode} % \end{macro} % % \changes{v3.8.3}{2020/04/07}{依赖 \pkg{ctexhook} 宏包。} % % \begin{macro} % {\@@_at_end_preamble:n,\@@_after_preamble:n, % \@@_after_end_preamble:n,\@@_package_hook:nn} % 在 \tn{document} 前后和宏包后加上各种钩子,依赖 \pkg{ctexhook}。 % \begin{macrocode} \AtBeginDocument { \xeCJK@document@hook } \ctex_at_end_preamble:n { \xeCJK@document@left@hook } \ctex_after_end_preamble:n { \xeCJK@document@right@hook } \cs_new_protected:Npn \xeCJK@document@hook { \tl_use:N \g_@@_after_preamble_hook_tl } \cs_new_protected:Npn \xeCJK@document@left@hook { \tl_use:N \g_@@_at_end_preamble_hook_tl } \cs_new_protected:Npn \xeCJK@document@right@hook { \tl_use:N \g_@@_after_end_preamble_hook_tl } \cs_new_protected:Npn \@@_at_end_preamble:n { \tl_gput_right:Nn \g_@@_at_end_preamble_hook_tl } \cs_new_protected:Npn \@@_after_preamble:n { \tl_gput_right:Nn \g_@@_after_preamble_hook_tl } \cs_new_protected:Npn \@@_after_end_preamble:n { \tl_gput_right:Nn \g_@@_after_end_preamble_hook_tl } \cs_new_protected:Npn \@@_package_hook:nn { \ctex_at_end_package:nn } \tl_new:N \g_@@_at_end_preamble_hook_tl \tl_new:N \g_@@_after_preamble_hook_tl \tl_new:N \g_@@_after_end_preamble_hook_tl % \end{macrocode} % \end{macro} % % \changes{v3.2.7}{2013/11/09}{使用 \pkg{everypage} 往 \tn{shipout} 盒子里加钩子。} % \changes{v3.2.16}{2014/12/16}{不再依赖 \pkg{everypage} 宏包。} % % \begin{macro}{\xeCJKShipoutHook} % 在 \tn{shipout} 盒子里加钩子,可以影响到页眉页脚。\tn{AtBeginDvi} 将参数保存在 % 盒子中,而 \pkg{atbegshi} 的 \tn{AtBeginShipout} 在 \tn{shipout} 盒子构建好之后 % 才起作用,所以它们都影响不到页眉页脚。我们通过往 \tn{@begindvi} 里加入钩子来完成。 % 注意,第一次使用 \tn{@begindvi} 之后,它会将自身定义为 \tn{@empty}。 % \begin{macrocode} \@@_after_preamble:n { \tl_put_right:Nn \@begindvi { \xeCJK@first@begindvi } } \cs_new_protected:Npn \xeCJK@first@begindvi { \xeCJKShipoutHook \cs_if_exist:NTF \@begindvi { \tl_gput_right:Nn } { \tl_const:Nn } \@begindvi { \xeCJKShipoutHook } } \NewDocumentCommand \xeCJKShipoutHook { } { \bool_if:NF \l_@@_shipout_hook_bool { \bool_set_true:N \l_@@_shipout_hook_bool \tl_use:N \l_@@_shipout_hook_tl } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_add_to_shipout:n} % \changes{v3.2.11}{2014/03/14}{不再使用内部名字。} % 往 \tn{shipout} 盒子中加入钩子。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_add_to_shipout:n { \tl_put_right:Nn \l_@@_shipout_hook_tl } \tl_new:N \l_@@_shipout_hook_tl \bool_new:N \l_@@_shipout_hook_bool % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_tl_remove_outer_braces:n} % \changes{v3.2.4}{2013/07/02}{去掉外层分组括号时,移除空格,避免死循环。} % 去掉 |#1| 外层的分组括号。 % \begin{macrocode} \cs_new:Npn \xeCJK_tl_remove_outer_braces:n #1 { \exp_last_unbraced:Ne \@@_tl_remove_outer_braces:w { \tl_trim_spaces:n {#1} } \s_stop } \cs_new:Npn \@@_tl_remove_outer_braces:w #1 \s_stop { \tl_if_single:nTF {#1} { \tl_if_head_is_N_type:nTF {#1} { \tl_trim_spaces:n } { \xeCJK_tl_remove_outer_braces:n } } { \tl_trim_spaces:n } {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_cs_clear:N,\xeCJK_cs_gclear:N} % 让控制序列的意义为空。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_cs_clear:N #1 { \cs_set_eq:NN #1 \prg_do_nothing: } \cs_new_protected:Npn \xeCJK_cs_gclear:N #1 { \cs_gset_eq:NN #1 \prg_do_nothing: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_swap_cs:NN} % 交换 |#1| 和 |#2| 的意义。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_swap_cs:NN #1#2 { \cs_set_eq:NN \@@_swap_cs_aux:w #1 \cs_set_eq:NN #1 #2 \cs_set_eq:NN #2 \@@_swap_cs_aux:w \cs_undefine:N \@@_swap_cs_aux:w } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_font_gset_to_current:N} % \changes{v3.8.4}{2020/06/04}{不缓存 \tn{nullfont}。} % |#1| 是控制序列的名字,令它等于当前字体命令。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_font_gset_to_current:N { \exp_after:wN \@@_font_gset_to_current_aux:NN \tex_the:D \tex_font:D } \cs_new_protected:Npn \@@_font_gset_to_current_aux:NN #1#2 { \cs_if_eq:NNF #1 \tex_nullfont:D { \cs_gset_eq:NN #2#1 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int,pTF]{\xeCJK_glyph_if_exist:N} % \changes{v3.1.0}{2012/11/19}{改进 \pkg{fontspec} 宏包中定义的 % \cs{font_glyph_if_exist:NnTF}。} % 判断当前字体中是否含有字符 |#1|。\pkg{fontspec} 中的类似函数在判断为真的时候, % 会留有一个 \cs{scan_stop:},造成不必要的边界,同时也不完全可展。因此,我们重新 % 定义它。 % \begin{macrocode} \prg_new_conditional:Npnn \xeCJK_glyph_if_exist:N #1 { p , T , F , TF } { \tex_iffontchar:D \tex_font:D `#1 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{variable}[int]{\c_xeCJK_space_skip_tl} % \changes{v3.1.0}{2012/11/18}{字间空格考虑 \tn{spaceskip} 不为零的情况。} % \changes{v3.2.0}{2013/05/22}{字间空格考虑到 \tn{spacefactor} 和 \tn{xspaceskip} 的情况。} % 当前字体状态下,一个字间空格产生的 |glue| 的长度,包括伸展和收缩部分。 % \begin{macrocode} \tl_const:Nn \c_xeCJK_space_skip_tl { \int_compare:nNnTF \g_@@_space_factor_int = { 1000 } { \skip_if_eq:nnTF \tex_spaceskip:D \c_zero_skip { \tex_fontdimen:D 2 ~ \tex_font:D plus \tex_fontdimen:D 3 ~ \tex_font:D minus \tex_fontdimen:D 4 ~ \tex_font:D } { \tex_spaceskip:D } } { \skip_if_eq:nnTF \tex_spaceskip:D \c_zero_skip { \int_compare:nNnTF \g_@@_space_factor_int < { 2000 } { \@@_space_skip_scale:nnn { \tex_fontdimen:D 2 ~ \tex_font:D } } { \skip_if_eq:nnTF \tex_xspaceskip:D \c_zero_skip { \@@_space_skip_scale:nnn { \tex_fontdimen:D 2 ~ \tex_font:D + \tex_fontdimen:D 7 ~ \tex_font:D } } { \tex_xspaceskip:D \use_none:nn } } { \tex_fontdimen:D 3 ~ \tex_font:D } { \tex_fontdimen:D 4 ~ \tex_font:D } } { \int_compare:nNnTF \g_@@_space_factor_int < { 2000 } { \@@_space_skip_scale:nnn { \tex_spaceskip:D } } { \skip_if_eq:nnTF \tex_xspaceskip:D \c_zero_skip { \@@_space_skip_scale:nnn { \tex_spaceskip:D + \tex_fontdimen:D 7 ~ \tex_font:D } } { \tex_xspaceskip:D \use_none:nn } } { \tex_gluestretch:D \tex_spaceskip:D } { \tex_glueshrink:D \tex_spaceskip:D } } } } \cs_new:Npn \@@_space_skip_scale:nnn #1#2#3 { \dim_eval:n {#1} plus \fp_eval:n { \g_@@_space_factor_int / 1000 } #2 minus \int_div_truncate:nn { 1000 * \int_value:w #3 } { \g_@@_space_factor_int } sp } % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\xeCJK_reset_space_factor:} % \begin{variable}[int]{\g_@@_space_factor_int} % 在 \tn{XeTeXinterchartoks} 里,\tn{spacefactor} 已经被重置为 $1000$。 % 我们需要在 Default 类里保存 \tn{spacefactor} 用于计算空格宽度。 % \begin{macrocode} \int_new:N \g_@@_space_factor_int \cs_new_protected:Npn \xeCJK_reset_space_factor: { \int_gset:Nn \g_@@_space_factor_int { 1000 } } \xeCJK_reset_space_factor: % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}[int]{\xeCJK_glue_to_skip:nN} % 取得一个 |glue| 的长度,包括伸展和收缩部分。如果参数不是 |glue|,则取其宽度。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_glue_to_skip:nN #1#2 { \group_begin: \hbox_set:Nw \l_@@_tmp_box #1 \scan_stop: \@@_if_last_glue:TF { \exp_args:NNNo \hbox_set_end: \skip_set:Nn #2 { \skip_use:N \tex_lastskip:D } } { \exp_args:NNNo \hbox_set_end: \skip_set:Nn #2 { \dim_use:N \box_wd:N \l_@@_tmp_box } } \exp_args:NNNo \group_end: \skip_set:Nn #2 { \skip_use:N #2 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_int_until_do:nn} % \begin{macro}{\@@_int_until_do:wn} % 由于定义较为简单,可以比 \cs{int_until_do:nNnn} 稍微快一点点。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_int_until_do:nn #1#2 { \@@_int_until_do:wn \use_none:n { \reverse_if:N \if_int_compare:w #1#2 } } \cs_new_protected:Npn \@@_int_until_do:wn \use_none:n #1 { #1 \exp_after:wN \@@_int_until_do:wn \fi: \use_none:n {#1} } \int_new:N \l_@@_begin_int \int_new:N \l_@@_end_int % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\xeCJK_peek_catcode_ignore_spaces:NTF} % \changes{v3.1.1}{2012/12/04}{新增有省略空格标识的 \texttt{peek} 函数。} % 我们在里面设置了一个变量 \cs{l_@@_peek_ignore_spaces_bool} 用于标识后面的空格 % 是否被省略掉了。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_peek_catcode_ignore_spaces:NTF #1#2#3 { \cs_set_eq:NN \l_@@_peek_search_token #1 \scan_stop: \cs_set_protected:Npe \@@_peek_catcode_true:w { \exp_not:N \group_align_safe_end: \exp_not:n {#2} } \cs_set_protected:Npe \@@_peek_catcode_false:w { \exp_not:N \group_align_safe_end: \exp_not:n {#3} } \bool_set_false:N \l_@@_peek_ignore_spaces_bool \group_align_safe_begin: \peek_after:Nw \@@_peek_catcode_ignore_spaces_branches:w } \cs_new_protected:Npn \@@_peek_catcode_ignore_spaces_branches:w { \if_meaning:w \l_peek_token \c_space_token \bool_set_true:N \l_@@_peek_ignore_spaces_bool \exp_after:wN \peek_after:Nw \exp_after:wN \@@_peek_catcode_ignore_spaces_branches:w \tex_romannumeral:D 0 \else: \if_catcode:w \exp_not:N \l_peek_token \exp_not:N \l_@@_peek_search_token \exp_after:wN \exp_after:wN \exp_after:wN \@@_peek_catcode_true:w \else: \exp_after:wN \exp_after:wN \exp_after:wN \@@_peek_catcode_false:w \fi: \fi: } \cs_new_eq:NN \l_@@_peek_search_token ? \cs_new_eq:NN \@@_peek_catcode_true:w \prg_do_nothing: \cs_new_eq:NN \@@_peek_catcode_false:w \prg_do_nothing: \bool_new:N \l_@@_peek_ignore_spaces_bool % \end{macrocode} % \end{macro} % % \changes{v3.8.0}{2020/02/10}{应用 \cs{peek_remove_spaces:n}。} % % \begin{macro}[int]{\xeCJK_token_value_class:N} % 用于取得记号 |#1| 所在的 \XeTeX 字符类。|#1| 应为 \tn{catcode} 为 |11| 或 |12| % 的显性或隐性记号。 % \begin{macrocode} \cs_new:Npn \xeCJK_token_value_class:N #1 { \tex_XeTeXcharclass:D \xeCJK_token_value_charcode:N #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_token_value_charcode:N} % \changes{v3.2.4}{2013/06/30}{考虑 \texttt{charcode} 超出 BMP 的情况。} % \changes{v3.3.1}{2015/05/08}{\texttt{0.99992} 版修复了 \tn{meaning} 的 Bug。} % 当记号 |#1| 的 \texttt{charcode} 大于等于 \texttt{0x10000} 时,\XeTeX\ % \texttt{0.9999.0} 版以前的 \tn{meaning} 的返回结果比较特殊\footnote{参见 % \url{http://tug.org/pipermail/xetex/2013-January/023967.html} 和 % \url{http://tex.stackexchange.com/a/64848}。},需要特别处理。 % \texttt{0.9999.0} 版以后的 \XeTeX 的 \tn{meaning} % 对于超出 BMP 的字符,会返回两个字符,分别对应于其 UTF-16 编码的首尾代理^^A % \footnote{参见 \url{http://tug.org/pipermail/xetex/2013-June/024543.html}。}。 % 这一 Bug 在 TeX Live 2015 的 \texttt{0.99992} 版中得到修复^^A % \footnote{参见\url{http://tug.org/pipermail/xetex/2015-May/025941.html}}。 % \begin{macrocode} \cs_new:Npn \xeCJK_token_value_charcode:N #1 { \exp_after:wN \@@_get_charcode:w \token_to_meaning:N #1 \q_stop } \group_begin: \cs_set:Npn \@@_tmp:w #1 ~ #2 ~ #3#4#5 \q_stop { \tl_if_empty:nTF { #4#5 } { \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3 \q_stop { \int_eval:n { `##3 } } } { \tl_if_empty:nTF {#5} { \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3##4 \q_stop { \int_eval:n { \tl_if_empty:nTF { ##4 } { `##3 } { ( `##3 - "D800 ) * "400 + ( `##4 - "DC00 ) + "10000 } } } } { \cs_new:Npn \@@_get_charcode:w ##1 ~ ##2 ~ ##3##4 \q_stop { \int_eval:n { \tl_if_empty:nTF { ##4 } { `##3 } { "20000 } } } } } } \exp_after:wN \@@_tmp:w \token_to_meaning:N ^^^^^20000 { } \q_stop \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[pTF,int]{\xeCJK_if_CJK_class:N} % 判断字符 |#1| 是否为 CJK 字符类,包括文字和标点符号。 % \begin{macrocode} \prg_new_conditional:Npnn \xeCJK_if_CJK_class:N #1 { p , T , F , TF } { \if_cs_exist:w \@@_CJK_class_tl:n { \xeCJK_token_value_class:N #1 } \cs_end: \prg_return_true: \else: \prg_return_false: \fi: } \cs_new:Npn \@@_CJK_class_tl:n #1 { c_@@_CJK_class_ \int_eval:n {#1} _tl } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF,int]{\xeCJK_if_same_class:NN} % 判断两个字符是否同属于一个字符类。 % \begin{macrocode} \prg_new_conditional:Npnn \xeCJK_if_same_class:NN #1#2 { p , T , F , TF } { \if_int_compare:w \xeCJK_token_value_class:N #1 = \xeCJK_token_value_class:N #2 \exp_stop_f: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_make_boundary:} % 利用 \cs{scan_stop:} 结束 CJK 分组,用于恢复字体等。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_make_boundary: { \bool_if:NT \l_@@_CJK_group_bool { \scan_stop: } } % \end{macrocode} % \end{macro} % % \subsection{功能开关} % % \begin{macro}{xeCJKactive} % 事实上,将开启或关闭 \XeTeX 的整个字符类机制。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { xeCJKactive .choice: , xeCJKactive / true .code:n = { \makexeCJKactive } , xeCJKactive / false .code:n = { \makexeCJKinactive } , xeCJKactive .default:n = { true } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\makexeCJKactive, \makexeCJKinactive} % \begin{macrocode} \NewDocumentCommand \makexeCJKactive { } { \tex_XeTeXinterchartokenstate:D = \c_one_int } \NewDocumentCommand \makexeCJKinactive { } { \tex_XeTeXinterchartokenstate:D = \c_zero_int } % \end{macrocode} % \end{macro} % % 抑制 |BOM| 和零宽格式字符,避免触发错误的 inter-class toks 插入。 % \begin{macrocode} \char_set_catcode_ignore:n { "FEFF } \char_set_catcode_ignore:n { "200B } \char_set_catcode_ignore:n { "200C } \char_set_catcode_ignore:n { "200D } \char_set_catcode_ignore:n { "2060 } % \end{macrocode} % % \subsection{字符类别设定}\label{sec:xeCJK-class-set} % % \begin{variable}{\g_@@_class_seq,\g_@@_new_class_seq} % 分别用于记录在 \pkg{xeCJK} 中使用的字符类别名称和新建的字符类别的编号。 % \begin{macrocode} \seq_new:N \g_@@_class_seq \seq_new:N \g_@@_new_class_seq % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\xeCJK_new_class:n} % 新建一个字符类别。|#1| 是自定义名称。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_new_class:n #1 { \int_if_exist:cTF { \@@_class_csname:n {#1} } { \@@_error:ne { class-already-defined } {#1} } { \exp_args:Nc \newXeTeXintercharclass { \@@_class_csname:n {#1} } \clist_new:c { g_@@_#1_range_clist } \seq_gput_right:Nn \g_@@_class_seq {#1} \seq_gput_right:Nv \g_@@_new_class_seq { \@@_class_csname:n {#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_save_class:nn} % \changes{v3.1.1}{2012/12/06} % {使用 \cs{xeCJK_save_class:nn} 保存 \XeTeX 预定义的字符类别。} % 保存 \XeTeX 预定义的字符类别。|#1| 是自定义名称,|#2| 是编号。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_save_class:nn #1#2 { \int_if_exist:cTF { \@@_class_csname:n {#1} } { \@@_error:ne { class-already-defined } {#1} } { \int_const:cn { \@@_class_csname:n {#1} } {#2} \clist_new:c { g_@@_#1_range_clist } \seq_gput_right:Nn \g_@@_class_seq {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_class_csname:n} % 字符类名称对应的控制序列名字。 % \begin{macrocode} \cs_new:Npn \@@_class_csname:n #1 { c_@@_#1_class_int } \cs_new_eq:cN { \@@_class_csname:n { Others } } \l_@@_tmp_int \@@_msg_new:nn { class-already-defined } { XeTeX~character~class~`#1'~has~been~already~defined.\\\\ Please~take~another~name. \\ } % \end{macrocode} % \end{macro} % % \changes{v3.2.0}{2013/05/20}{增加 \texttt{IVS} 字符类用于处理异体字选择符。} % \changes{v3.3.1}{2015/01/22}{\texttt{IVS} 字符类更名为 \texttt{CM}。} % % \pkg{xeCJK} 需要以下字符类别用于字符输出。其中 |Default|、|CJK|、|FullLeft|、 % |FullRight|、|Boundary| 为 \XeTeX\ 中预定义的类别,\pkg{xeCJK} 新增加了\ % |HalfLeft|、|HalfRight|、|NormalSpace| 和 |CM|。其中异体字选择符 % (Ideographic Variation Selectors)\footnote{\url{http://www.unicode.org/reports/tr37/}} % 需要 \XeTeX\ |0.9999.0| 以上的版本^^A % \footnote{\url{http://tug.org/pipermail/xetex/2013-March/024118.html}}和相关字体的支持。 % \begin{center}\xeCJKsetup{PunctStyle=hangmobanjiao} % \begin{tabular}{cll} % \toprule % 类别 & 说明 & 例子 \\ \midrule % |Default| & 西文一般符号 & abc123 \\ % |CJK| & CJK 表意符号 & 汉字ぁぃぅ \\ % |FullLeft| & 全角左标点 & (《:“ \\ % |FullRight| & 全角右标点 & ,。)》” \\ % |HalfLeft| & 半角左标点 & ( [ \{ \\ % |HalfRight| & 半角右标点 & , . ? ) ] \} \\ % |NormalSpace| & 前后原始间距的符号 & / \\ % |Boundary| & 边界 & 空格 \\ % |CM| & 组合标识 & 异体字选择符\\ % |HangulJamo| & 朝鲜文字母(仅兼容旧代码,不再分配字符) & /\\ % |HangulJamoL| & 朝鲜文字母初声 & |ᄻ|\\ % |HangulJamoV| & 朝鲜文字母中声 & |ᆟ|\\ % |HangulJamoT| & 朝鲜文字母终声 & |ᇫ|\\ % |CJStarter| & 日文条件行首禁则字符 & |ゃ||っ|\\ % |PoZheHao| & 支持合字的破折号 & ——\\ % \bottomrule % \end{tabular} % \end{center} % % \changes{v3.3.3}{2016/01/20}{兼容 \LaTeXe{} 2016/02/01 的字符类设置。} % \changes{v3.3.4}{2016/02/07}{兼容 \XeTeX{} 0.99994 的边界字符类。} % \begin{macro}[int]{Default,CJK,FullLeft,FullRight,Boundary} % \begin{macrocode} \xeCJK_save_class:nn { Default } { 0 } % \end{macrocode} % \XeTeX{} 0.99994 将字符类总数扩大到 $4096$^^A % \footnote{\url{http://tug.org/pipermail/xetex/2016-February/026363.html}}。 % \begin{macrocode} \str_const:Ne \c_@@_xetex_version_str { \int_use:N \tex_XeTeXversion:D \tex_XeTeXrevision:D } \fp_compare:nNnTF { \c_@@_xetex_version_str } > { 0.99993 } { \xeCJK_save_class:nn { Boundary } { 4095 } } { \xeCJK_save_class:nn { Boundary } { 255 } } % \end{macrocode} % \LaTeXe{} 2016/02/01 不再预设置 CJK 字符类。 % \begin{macrocode} \int_compare:nNnTF { \tex_XeTeXcharclass:D "4E00 } = \c_one_int { \xeCJK_save_class:nn { CJK } { 1 } \xeCJK_save_class:nn { FullLeft } { 2 } \xeCJK_save_class:nn { FullRight } { 3 } \int_const:Nn \c_@@_class_begin_int { 3 } } { \xeCJK_new_class:n { CJK } \xeCJK_new_class:n { FullLeft } \xeCJK_new_class:n { FullRight } \int_const:Nn \c_@@_class_begin_int { 0 } } % \end{macrocode} % \end{macro} % % \changes{v3.2.15}{2014/11/10}{增加 \texttt{HangulJamo} 字符类。} % \changes{v3.10.2}{2026/07/04}{增加 \texttt{PoZheHao} 字符类, % 支持字体的破折号合字功能(\#382)。} % % \begin{macro}[int] % {HalfLeft,HalfRight,NormalSpace,CM,HangulJamo,HangulJamoL, % HangulJamoV,HangulJamoT,CJStarter,PoZheHao} % 新增西文半角左/右标点、前后原始间距的符号和异体字选择符类, % 以及支持破折号合字的字符类。 % \begin{macrocode} \xeCJK_new_class:n { HalfLeft } \xeCJK_new_class:n { HalfRight } \xeCJK_new_class:n { NormalSpace } \xeCJK_new_class:n { CM } % \end{macrocode} % 旧的 |HangulJamo| 类只为兼容用户代码保留;新代码应按音节位置使用 % |HangulJamoL|、|HangulJamoV| 和 |HangulJamoT|。 % \begin{macrocode} \xeCJK_new_class:n { HangulJamo } \xeCJK_new_class:n { HangulJamoL } \xeCJK_new_class:n { HangulJamoV } \xeCJK_new_class:n { HangulJamoT } \xeCJK_new_class:n { CJStarter } \xeCJK_new_class:n { PoZheHao } % \end{macrocode} % \end{macro} % % \changes{v3.2.11}{2014/04/10} % {左右角括号 \texttt{U+2329} 和 \texttt{U+232A} 是西文标点符号。} % \changes{v3.2.15}{2014/11/09} % {把 REVERSE SOLIDUS(\texttt{U+005C})、HYPHEN-MINUS(\texttt{U+002D})和 % EN DASH(\texttt{U+2013})归入 \texttt{NormalSpace} 类。} % % \begin{variable} % {\c_@@_HalfLeft_chars_clist,\c_@@_HalfRight_chars_clist,\c_@@_NormalSpace_chars_clist} % \hypertarget{CJKcharclass}{西文半角左/右标点和前后原始间距的字符类。} % \begin{macrocode} \clist_const:Nn \c_@@_HalfLeft_chars_clist { "28 , "5B , "60 , "7B , "2329 } \clist_const:Nn \c_@@_HalfRight_chars_clist { "21 , "22 , "25 , "27 , "29 , "2C , "2E , "3A , "3B , "3F , "5D , "7D , "232A } \clist_const:Nn \c_@@_NormalSpace_chars_clist { "2D , "2F , "5C } % \end{macrocode} % \end{variable} % % 以下对全角标点符号的归类来源于 \XeTeX 的脚本 % \href{http://sourceforge.net/p/xetex/code/ci/master/tree/source/texk/web2c/xetexdir/unicode-char-prep.pl} % {\file{unicode-char-prep.pl}} 和 Unicode 数据库\footnote{\url{http://www.unicode.org/reports/tr14/}}。 % % \changes{v3.2.3}{2013/06/09}{根据 \XeTeX 的脚本重新整理全角标点符号。} % % \begin{variable}{\c_@@_OP_chars_clist} % Open Punctuation (OP) % \PrintPunctList{OP}{Open Punctuation} % 以下代码的第一行是中西文共用的左引号。 % \begin{macrocode} \clist_const:Nn \c_@@_OP_chars_clist { "2018 , "201C , "3008 , "300A , "300C , "300E , "3010 , "3014 , "3016 , "3018 , "301A , "301D , "FE17 , "FE35 , "FE37 , "FE39 , "FE3B , "FE3D , "FE3F , "FE41 , "FE43 , "FE47 , "FE59 , "FE5B , "FE5D , "FF08 , "FF3B , "FF5B , "FF5F , "FF62 } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_PR_chars_clist} % \changes{v3.3.0}{2014/12/26}{不把 \texttt{U+20A9} 归入 CJK 的 PR 类。} % Prefix Numeric (PR) % \PrintPunctList{PR}{Prefix Numeric} % \begin{macrocode} \clist_const:Nn \c_@@_PR_chars_clist { "FE69 , "FF04 , "FFE1 , "FFE5 , "FFE6 } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_FullLeft_chars_clist} % 以上两类标点符号出现在文字的左边,不应出现在行尾位置。 % \begin{macrocode} \clist_const:Ne \c_@@_FullLeft_chars_clist { \exp_not:V \c_@@_OP_chars_clist , \exp_not:V \c_@@_PR_chars_clist } % \end{macrocode} % \end{variable} % % \changes{v3.3.3}{2015/12/12} % {把 EN DASH(\texttt{U+2013})作为半字线连接号归入 \texttt{FullRight} 类。} % \changes{v3.3.3}{2015/12/12} % {不再把 \texttt{U+2015} 和 \texttt{U+2500} 归入 \texttt{FullRight} 类。} % \changes{v3.6.0}{2018/01/14} % {把 TWO-EM DASH (\texttt{U+2E3A}) 归入 \texttt{FullRight} 类和设为 % \texttt{LongPunct} 与 \texttt{MiddlePunct}。} % % \begin{variable}{\c_@@_CL_chars_clist} % Close Punctuation (CL) % \PrintPunctList{CL}{Close Punctuation} % 以下代码的第一行是中西文共用的一些标点符号。 % \begin{macrocode} \clist_const:Nn \c_@@_CL_chars_clist { "00B7 , "2019 , "201D , "2013 , "2014 , "2025 , "2026 , "2027 , "2E3A , "3001 , "3002 , "3009 , "300B , "300D , "300F , "3011 , "3015 , "3017 , "3019 , "301B , "301E , "301F , "FE11 , "FE12 , "FE18 , "FE36 , "FE38 , "FE3A , "FE3C , "FE3E , "FE40 , "FE42 , "FE44 , "FE48 , "FE50 , "FE52 , "FE5A , "FE5C , "FE5E , "FF09 , "FF0C , "FF0E , "FF3D , "FF5D , "FF60 , "FF61 , "FF63 , "FF64 } % \end{macrocode} % \end{variable} % % \changes{v3.3.0}{2014/12/26}{不把 NS 类中的一些有禁则的日文归入 \texttt{FullRight} 类。} % \changes{v3.6.0}{2018/01/14} % {将全角浪线 \texttt{U+FF5E} 等连接号归入 \texttt{FullRight} 类和设为 \texttt{MiddlePunct}。} % \changes{v3.10.0}{2026/04/27} % {将全角浪线等连接号从 \texttt{MiddlePunct} 改为 \texttt{LongPunct}, % 消除不必要的标点压缩间距。} % % \begin{variable}{\c_@@_NS_chars_clist} % Nonstarter (NS) % \PrintPunctList{NS}{Nonstarter} % \noindent Hyphens (cl-03) % \PrintPunctList{hyphens}{Hyphens} % \noindent Iteration marks (cl-09) % \PrintPunctList{iteration_marks}{Iteration marks} % 根据 W3C 的资料\footnote{\url{http://www.w3.org/TR/jlreq/}},\texttt{cl-03} 和 % \texttt{cl-09} 在非常松散的情况下可以没有禁则。我们将全角浪线 \texttt{U+FF5E} 等连接号归入 % \texttt{FullRight} 类并在宏包末尾设为 \texttt{LongPunct}。 % \begin{macrocode} \clist_const:Nn \c_@@_hyphens_chars_clist { "301C , "30A0 , "FF5E } \clist_const:Nn \c_@@_iteration_marks_chars_clist { "3005 , "303B , "309D , "309E , "30FD , "30FE } \clist_const:Nn \c_@@_NS_chars_clist { "30FB , "FE54 , "FE55 , "FF1A , "FF1B , "FF65 , "16FE0 } \AtEndOfPackage { \cs_set:Npn \@@_tmp:w #1 { \char_generate:nn {#1} { 12 } } \@@_add_special_punct:nn { long } { \clist_map_function:NN \c_@@_hyphens_chars_clist \@@_tmp:w } \cs_undefine:N \@@_tmp:w } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_EX_chars_clist} % Exclamation/Interrogation (EX) % \PrintPunctList{EX}{Exclamation/Interrogation} % \begin{macrocode} \clist_const:Nn \c_@@_EX_chars_clist { "FE15 , "FE16 , "FE56 , "FE57 , "FF01 , "FF1F } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_IS_chars_clist} % Infix Numeric Separator (IS) % \PrintPunctList{IS}{Infix Numeric Separator} % \begin{macrocode} \clist_const:Nn \c_@@_IS_chars_clist { "FE10 , "FE13 , "FE14 } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_CJ_chars_clist} % Conditional Japanese Starter (CJ)。这类字符的禁则是可选的^^A % \footnote{\url{https://github.com/CTeX-org/ctex-kit/issues/165}}, % 为实现的简单计,我们把它们归入 CJK 类,即没有禁则。 % \PrintPunctList{CJ}{Conditional Japanese Starter} % \begin{macrocode} \clist_const:Nn \c_@@_CJ_chars_clist { "3041 , "3043 , "3045 , "3047 , "3049 , "3063 , "3083 , "3085 , "3087 , "308E , "3095 , "3096 , "30A1 , "30A3 , "30A5 , "30A7 , "30A9 , "30C3 , "30E3 , "30E5 , "30E7 , "30EE , "30F5 , "30F6 , "30FC , "31F0 , "31F1 , "31F2 , "31F3 , "31F4 , "31F5 , "31F6 , "31F7 , "31F8 , "31F9 , "31FA , "31FB , "31FC , "31FD , "31FE , "31FF , "FF67 , "FF68 , "FF69 , "FF6A , "FF6B , "FF6C , "FF6D , "FF6E , "FF6F , "FF70 , "1B132 , "1B150 -> "1B152 , "1B155 , "1B164 -> "1B167 } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_PO_chars_clist} % Postfix Numeric (PO) % \PrintPunctList{PO}{Postfix Numeric} % \begin{macrocode} \clist_const:Nn \c_@@_PO_chars_clist { "FE6A , "FF05 , "FFE0 } % \end{macrocode} % \end{variable} % % \changes{v3.3.0}{2014/12/26}{不把小写日文假名归入 \texttt{FullRight} 类。} % % \begin{variable}{\c_@@_FullRight_chars_clist} % 以上六类标点符号出现在文字的右边,不应出现在行首位置。 % \begin{macrocode} \clist_const:Ne \c_@@_FullRight_chars_clist { \exp_not:V \c_@@_CL_chars_clist , \exp_not:V \c_@@_NS_chars_clist , \exp_not:V \c_@@_EX_chars_clist , \exp_not:V \c_@@_IS_chars_clist , \exp_not:V \c_@@_PO_chars_clist , \exp_not:V \c_@@_hyphens_chars_clist } % \end{macrocode} % \end{variable} % % \changes{v3.3.3}{2015/06/25}{补充 Ext-E。} % \changes{v3.4.1}{2016/08/18}{补充 Unicode 9.0 的西夏文。} % \changes{v3.5.0}{2017/07/22}{补充 Ext-F。} % \changes{v3.7.3}{2019/04/15}{补充日文假名扩展。} % \changes{v3.8.3}{2020/03/15}{同步 Unicode 13.0。} % \changes{v3.8.3}{2020/04/09}{补充 \texttt{U+02EA} 和 \texttt{U+02EB}。} % \changes{v3.8.8}{2021/09/15}{同步 Unicode 14.0。} % \changes{v3.8.8}{2021/09/16}{补充女书。} % \changes{v3.10.0}{2022/09/17}{同步 Unicode 15.0。} % \changes{v3.10.0}{2023/12/02}{同步 Unicode 15.1。} % \changes{v3.10.0}{2026/04/30}{同步 Unicode 17.0。} % % \begin{variable}{\c_@@_CJK_chars_clist} % CJK 字符类,包括文字和标点符号。 % \begin{macrocode} \clist_const:Nn \c_@@_CJK_chars_clist { % \end{macrocode} % \begin{itemize}[nosep,leftmargin=0pt] % \item 闽南语、客家话阴去和阳去声调标记 % \begin{macrocode} "02EA -> "02EB , % \end{macrocode} % \item CJK Radicals Supplement (中日韩部首补充) % \begin{macrocode} "2E80 -> "2EFF , % \end{macrocode} % \item Kangxi Radicals (康熙部首) % \begin{macrocode} "2F00 -> "2FDF , % \end{macrocode} % \item Ideographic Description Characters (表意文字描述符) % \begin{macrocode} "2FF0 -> "2FFF , % \end{macrocode} % \item CJK Symbols and Punctuation (中日韩符号和标点) % \begin{macrocode} "3000 -> "303F , % \end{macrocode} % \item Hiragana (日文平假名) % \begin{macrocode} "3040 -> "309F , % \end{macrocode} % \item Katakana (日文片假名) % \begin{macrocode} "30A0 -> "30FF , % \end{macrocode} % \item Bopomofo (注音字母) % \begin{macrocode} "3100 -> "312F , % \end{macrocode} % \item Hangul Compatibility Jamo (谚文兼容字母) % \begin{macrocode} "3130 -> "318F , % \end{macrocode} % \item Kanbun (象形字注释标志) % \begin{macrocode} "3190 -> "319F , % \end{macrocode} % \item Bopomofo Extended (注音字母扩展) % \begin{macrocode} "31A0 -> "31BF , % \end{macrocode} % \item CJK Strokes (中日韩笔画) % \begin{macrocode} "31C0 -> "31EF , % \end{macrocode} % \item Katakana Phonetic Extensions (日文片假名语音扩展) % \begin{macrocode} "31F0 -> "31FF , % \end{macrocode} % \item Enclosed CJK Letters and Months (带圈中日韩字母和月份) % \begin{macrocode} "3200 -> "32FF , % \end{macrocode} % \item CJK Compatibility (中日韩兼容) % \begin{macrocode} "3300 -> "33FF , % \end{macrocode} % \item CJK Unified Ideographs Extension-A (中日韩统一表意文字扩展 A) % \begin{macrocode} "3400 -> "4DBF , % \end{macrocode} % \item Yijing Hexagrams Symbols (易经六十四卦符号) % \begin{macrocode} "4DC0 -> "4DFF , % \end{macrocode} % \item CJK Unified Ideographs (中日韩统一表意文字) % \begin{macrocode} "4E00 -> "9FFF , % \end{macrocode} % \item Yi Syllables (彝文音节) % \begin{macrocode} "A000 -> "A48F , % \end{macrocode} % \item Yi Radicals (彝文字根) % \begin{macrocode} "A490 -> "A4CF , % \end{macrocode} % \item Hangul Syllables (谚文音节) % \begin{macrocode} "AC00 -> "D7AF , % \end{macrocode} % \item CJK Compatibility Ideographs (中日韩兼容表意文字) % \begin{macrocode} "F900 -> "FAFF , % \end{macrocode} % \item Vertical Forms (竖排形式) % \begin{macrocode} "FE10 -> "FE1F , % \end{macrocode} % \item CJK Compatibility Forms (中日韩兼容形式) % \begin{macrocode} "FE30 -> "FE4F , % \end{macrocode} % \item Halfwidth and Fullwidth Forms (半角及全角形式) % \begin{macrocode} "FF00 -> "FFEF , % \end{macrocode} % \item Ideographic Symbols and Punctuation (表意文字符号及标点) % \begin{macrocode} "16FE0 -> "16FFF , % \end{macrocode} % \item Tangut (西夏文) % \begin{macrocode} "17000 -> "187FF , % \end{macrocode} % \item Tangut Components (西夏文部首) % \begin{macrocode} "18800 -> "18AFF , % \end{macrocode} % \item Khitan Small Script (契丹小字) % \begin{macrocode} "18B00 -> "18CFF , % \end{macrocode} % \item Tangut Supplement (西夏文增补) % \begin{macrocode} "18D00 -> "18D7F , % \end{macrocode} % \item Tangut Components Supplement (西夏文部首增补) % \begin{macrocode} "18D80 -> "18DFF , % \end{macrocode} % \item Kana Extended-B (日文假名扩展 B) % \begin{macrocode} "1AFF0 -> "1AFFF , % \end{macrocode} % \item Kana Supplement (日文假名增补) % \begin{macrocode} "1B000 -> "1B0FF , % \end{macrocode} % \item Kana Extended-A (日文假名扩展 A) % \begin{macrocode} "1B100 -> "1B12F , % \end{macrocode} % \item Small Kana Extension (小型日文假名扩展) % \begin{macrocode} "1B130 -> "1B16F , % \end{macrocode} % \item Nushu (女书) % \begin{macrocode} "1B170 -> "1B2FF , % \end{macrocode} % \item Enclosed Ideographic Supplement (带圈表意文字增补) % \begin{macrocode} "1F200 -> "1F2FF , % \end{macrocode} % \item CJK Unified Ideographs Extension-B (中日韩统一表意文字扩展 B) % \begin{macrocode} "20000 -> "2A6DF , % \end{macrocode} % \item CJK Unified Ideographs Extension-C (中日韩统一表意文字扩展 C) % \begin{macrocode} "2A700 -> "2B73F , % \end{macrocode} % \item CJK Unified Ideographs Extension-D (中日韩统一表意文字扩展 D) % \begin{macrocode} "2B740 -> "2B81F , % \end{macrocode} % \item CJK Unified Ideographs Extension-E (中日韩统一表意文字扩展 E) % \begin{macrocode} "2B820 -> "2CEAF , % \end{macrocode} % \item CJK Unified Ideographs Extension-F (中日韩统一表意文字扩展 F) % \begin{macrocode} "2CEB0 -> "2EBEF , % \end{macrocode} % \item CJK Unified Ideographs Extension-I (中日韩统一表意文字扩展 I) % \begin{macrocode} "2EBF0 -> "2EE5F , % \end{macrocode} % \item CJK Compatibility Ideographs Supplement (中日韩兼容表意文字增补) % \begin{macrocode} "2F800 -> "2FA1F , % \end{macrocode} % \item CJK Unified Ideographs Extension-G (中日韩统一表意文字扩展 G) % \begin{macrocode} "30000 -> "3134F , % \end{macrocode} % \item CJK Unified Ideographs Extension-H (中日韩统一表意文字扩展 H) % \begin{macrocode} "31350 -> "323AF , % \end{macrocode} % \item CJK Unified Ideographs Extension-J (中日韩统一表意文字扩展 J) % \begin{macrocode} "323B0 -> "3347F % \end{macrocode} % \end{itemize} % \begin{macrocode} } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_CM_chars_clist} % \changes{v3.3.1}{2015/01/22}{补充音调符号。} % 包括日文假名浊点和异体字选择符。组合标识是最好是归入 256 类,即透明类,不会影响 % 状态。但也会产生一定问题。比如下面的例子,位于行尾的“二”造成分组不匹配。 % \begin{verbatim} % \XeTeXinterchartokenstate=1 % \XeTeXcharclass`二=256 % \XeTeXinterchartoks 255 1 = {\bgroup} % \XeTeXinterchartoks 1 255 = {\egroup} % \XeTeXinterchartoks 1 1 = {x} % \font\zhfont="SimSun" % \zhfont % 一二三二 % \bye % \end{verbatim} % \begin{macrocode} \clist_const:Nn \c_@@_CM_chars_clist { % \end{macrocode} % \begin{itemize}[nosep,leftmargin=0pt] % \item Diacritics (音调符号) % \begin{macrocode} "302A -> "302F , % \end{macrocode} % \item 日文假名浊点 % \begin{macrocode} "3099 -> "309A , % \end{macrocode} % \item Variation Selectors (异体字选择符) % \begin{macrocode} "FE00 -> "FE0F , % \end{macrocode} % \item Variation Selectors Supplement (异体字选择符增补) % \begin{macrocode} "E0100 -> "E01EF % \end{macrocode} % \end{itemize} % \begin{macrocode} } % \end{macrocode} % \end{variable} % % \begin{variable} % {\c_@@_HangulJamoL_chars_clist,\c_@@_HangulJamoV_chars_clist, % \c_@@_HangulJamoT_chars_clist} % 朝鲜文字母按 Unicode |Hangul_Syllable_Type| 分成初声(L)、中声(V)和 % 终声(T)三类,以便区分音节内 shaping 与相邻音节边界(\#158)。 % \begin{macrocode} \clist_const:Nn \c_@@_HangulJamoL_chars_clist { "1100 -> "115F , "A960 -> "A97C } \clist_const:Nn \c_@@_HangulJamoV_chars_clist { "1160 -> "11A7 , "D7B0 -> "D7C6 } \clist_const:Nn \c_@@_HangulJamoT_chars_clist { "11A8 -> "11FF , "D7CB -> "D7FB } % \end{macrocode} % \end{variable} % % \subsection{字符类别处理} % % \begin{macro}[int]{\xeCJK_class_num:n} % |#1| 为字符类别名称,用于取得字符类别对应的编号。 % \begin{macrocode} \cs_new:Npn \xeCJK_class_num:n #1 { \use:c { \@@_class_csname:n {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKDeclareCharClass} % \begin{macrocode} \NewDocumentCommand \xeCJKDeclareCharClass { s > { \TrimSpaces } m m } { \xeCJK_declare_char_class:nn {#2} {#3} \IfBooleanT {#1} { \xeCJKResetPunctClass } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_declare_char_class:nn,\xeCJK_declare_char_class:nN} % \begin{macro}{\@@_set_char_class_aux:Nnw} % 用于设置字符所属的类别,|#1| 为类别名称,|#2| 为字符的 |Unicode|,相邻字符用 % 半角逗号隔开,支持类似 |"1100 -> "11FF| 起止范围的使用方式。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_declare_char_class:nn #1#2 { \clist_set:Ne \l_@@_tmp_clist {#2} \xeCJK_declare_char_class:nN {#1} \l_@@_tmp_clist } \cs_new_protected:Npn \xeCJK_declare_char_class:nN #1#2 { \clist_gconcat:ccN { g_@@_#1_range_clist } { g_@@_#1_range_clist } #2 \clist_map_inline:Nn #2 { \str_if_eq:nnF {##1} { -> } { \@@_set_char_class_aux:Nnw \xeCJK_set_char_class:nnn {##1} { \xeCJK_class_num:n {#1} } } } \xeCJK_set_char_class:nnn { "3099 } { "309A } { \xeCJK_class_num:n { CM } } } \NewDocumentCommand \@@_set_char_class_aux:Nnw { m > { \SplitArgument { 1 } { -> } } m } { #1 #2 } \cs_generate_variant:Nn \clist_gconcat:NNN { cc } % \end{macrocode} % \end{macro} % \end{macro} % % \changes{v3.10.4}{2026/07/21} % {修正 \cs{@@_check_num_range:nnNN} 中缺省端点的范围解析问题。} % \begin{macro}{\@@_check_num_range:nnNN} % \begin{macrocode} \cs_new_protected:Npn \@@_check_num_range:nnNN #1#2#3#4 { \bool_lazy_or:nnTF { \tl_if_blank_p:n {#1} } { \tl_if_blank_p:n {#2} } { \int_set:Nn #3 { \tl_if_blank:nTF {#1} {#2} {#1} } \int_set_eq:NN #4 #3 } { \int_set:Nn #3 { \int_min:nn {#1} { \tl_if_novalue:nTF {#2} {#1} {#2} } } \int_set:Nn #4 { \int_max:nn {#1} { \tl_if_novalue:nTF {#2} {#1} {#2} } } } } % \end{macrocode} % \end{macro} % % \changes{v3.2.3}{2013/06/08}{不再改变 CJK 字符类的 \tn{catcode}。} % \changes{v3.2.7}{2013/08/29}{修正 \file{unicode-letters.tex} 中谚文符号 % \tn{catcode} 不准的问题。} % \begin{macrocode} \token_if_letter:NF ^^^^ac00 { \int_set:Nn \l_@@_begin_int { "AC00 } \int_set:Nn \l_@@_end_int { "D7A3 } \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int } { \char_set_catcode_letter:n { \l_@@_begin_int } \int_incr:N \l_@@_begin_int } } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_set_char_class:nnn} % \changes{v3.1.1}{2012/12/05}{在文档中设置字符类别时不重复设置 \tn{catcode}。} % 设置字符类别,|#1| 和 |#2| 为字符类别起止的 |Unicode|,|#3| 为类别名称对应编号。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_set_char_class:nnn #1#2#3 { \@@_check_num_range:nnNN {#1} {#2} \l_@@_begin_int \l_@@_end_int \int_set:Nn \l_@@_tmp_int {#3} \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int } { \tex_XeTeXcharclass:D \l_@@_begin_int = \l_@@_tmp_int \int_incr:N \l_@@_begin_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_char_class_eq:nn} % \changes{v3.1.1}{2012/12/06}{交换参数的顺序。} % 将字符类 |#1| 中的字符全部设置成字符类 |#2|。只适用于 |#1| 的字符类范围为离散的 % 逗号列表的情况。 % \begin{macrocode} \cs_new_protected:Npn \@@_set_char_class_eq:nn #1#2 { \int_set:Nn \l_@@_tmp_int { \xeCJK_class_num:n {#2} } \clist_map_inline:cn { c_@@_#1_chars_clist } { \tex_XeTeXcharclass:D ##1 = \l_@@_tmp_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\normalspacedchars} % 声明前后不加间距的字符。 % \begin{macrocode} \NewDocumentCommand \normalspacedchars { m } { \tl_map_inline:nn {#1} { \tex_XeTeXcharclass:D `##1 = \xeCJK_class_num:n { NormalSpace } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKResetPunctClass} % \changes{v3.10.2}{2026/07/04}{重置后恢复 \texttt{PoZheHaoLigature} % 的设置(\#382)。} % \changes{v3.10.2}{2026/07/05}{重置后恢复 \texttt{LatinPunct} % 的设置(\#389)。} % 用于重置标点符号所属的字符类。注意,重新声明 |FullLeft| 和 % |FullRight| 类会覆盖 \opt{PoZheHaoLigature} 与 \opt{LatinPunct} % 对共用标点的字符类调整,因此在重置后需要按记录的状态恢复这两个 % 选项的设置。 % \begin{macrocode} \NewDocumentCommand \xeCJKResetPunctClass { } { \clist_gclear:N \g_@@_HalfLeft_range_clist \clist_gclear:N \g_@@_HalfRight_range_clist \clist_gclear:N \g_@@_FullLeft_range_clist \clist_gclear:N \g_@@_FullRight_range_clist \xeCJK_declare_char_class:nN { HalfLeft } \c_@@_HalfLeft_chars_clist \xeCJK_declare_char_class:nN { HalfRight } \c_@@_HalfRight_chars_clist \xeCJK_declare_char_class:nN { FullLeft } \c_@@_FullLeft_chars_clist \xeCJK_declare_char_class:nN { FullRight } \c_@@_FullRight_chars_clist \bool_if:NT \l_@@_pozhehao_ligature_bool { \keys_set:nn { xeCJK / options } { PoZheHaoLigature = true } } \bool_if:NT \l_@@_latin_punct_bool { \keys_set:nn { xeCJK / options } { LatinPunct = true } } } \bool_new:N \l_@@_pozhehao_ligature_bool \bool_new:N \l_@@_latin_punct_bool % \end{macrocode} % \end{macro} % % % \begin{macro}{\xeCJKResetCharClass} % 用于恢复 \pkg{xeCJK} 对字符类别的设置。 % \begin{macrocode} \NewDocumentCommand \xeCJKResetCharClass { } { \clist_gclear:N \g_@@_CJK_range_clist \clist_gclear:N \g_@@_NormalSpace_range_clist \clist_gclear:N \g_@@_CM_range_clist \clist_gclear:N \g_@@_HangulJamo_range_clist \clist_gclear:N \g_@@_HangulJamoL_range_clist \clist_gclear:N \g_@@_HangulJamoV_range_clist \clist_gclear:N \g_@@_HangulJamoT_range_clist \clist_gclear:N \g_@@_CJStarter_range_clist \xeCJK_declare_char_class:nN { CJK } \c_@@_CJK_chars_clist \xeCJK_declare_char_class:nN { NormalSpace } \c_@@_NormalSpace_chars_clist \xeCJK_declare_char_class:nN { CM } \c_@@_CM_chars_clist \xeCJK_declare_char_class:nN { HangulJamoL } \c_@@_HangulJamoL_chars_clist \xeCJK_declare_char_class:nN { HangulJamoV } \c_@@_HangulJamoV_chars_clist \xeCJK_declare_char_class:nN { HangulJamoT } \c_@@_HangulJamoT_chars_clist \bool_if:NT \l_@@_CJ_strict_bool { \xeCJK_declare_char_class:nN { CJStarter } \c_@@_CJ_chars_clist } \xeCJKResetPunctClass } \bool_new:N \l_@@_CJ_strict_bool % \end{macrocode} % \end{macro} % % 设置字符类别。 % \begin{macrocode} \xeCJKResetCharClass % \end{macrocode} % % \begin{macro}[int]{\xeCJK_inter_class_toks:nnn} % 在相邻类别之间插入内容。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_inter_class_toks:nnn #1#2#3 { \tex_XeTeXinterchartoks:D \xeCJK_class_num:n {#1} ~ \xeCJK_class_num:n {#2} = {#3} } \cs_generate_variant:Nn \xeCJK_inter_class_toks:nnn { nne } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_get_inter_class_toks:nn} % 取出相邻类别之间的内容。 % \begin{macrocode} \cs_new:Npn \xeCJK_get_inter_class_toks:nn #1#2 { \tex_the:D \tex_XeTeXinterchartoks:D \xeCJK_class_num:n {#1} ~ \xeCJK_class_num:n {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_clear_inter_class_toks:nn} % 清除相邻类别之间的内容。注意,直接赋空值可能会导致 \XeTeX 崩溃。例如 % \begin{verbatim} % \XeTeXinterchartokenstate = 1 % \XeTeXcharclass`A=10 % \XeTeXinterchartoks 10 10 = {xx} % \begingroup % \XeTeXinterchartoks 10 10 = {} AA % \endgroup % \bye % \end{verbatim} % 如果把上述例子中的分组 \tn{begingroup} 和 \tn{endgroup} 去掉,则结果正常,甚为怪异。 % 此处 \XeTeX 的 bug 已经在 0.999992 版中修复^^A % \footnote{\url{http://tug.org/svn/texlive?view=revision&revision=53880}}。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_clear_inter_class_toks:nn #1#2 { \xeCJK_inter_class_toks:nnn {#1} {#2} { \prg_do_nothing: } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_pre_inter_class_toks:nnn} % 在相邻类别之间已有的内容前增加内容。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_pre_inter_class_toks:nnn #1#2#3 { \xeCJK_inter_class_toks:nne {#1} {#2} { \exp_not:n {#3} \xeCJK_get_inter_class_toks:nn {#1} {#2} } } \cs_generate_variant:Nn \xeCJK_pre_inter_class_toks:nnn { nne } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_app_inter_class_toks:nnn} % 在相邻类别之间已有的内容后追加内容。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_app_inter_class_toks:nnn #1#2#3 { \xeCJK_inter_class_toks:nne {#1} {#2} { \xeCJK_get_inter_class_toks:nn {#1} {#2} \exp_not:n {#3} } } \cs_generate_variant:Nn \xeCJK_app_inter_class_toks:nnn { nne } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_copy_inter_class_toks:nnnn} % 将 |#3| 和 |#4| 之间的内容复制到 |#1| 和 |#2| 之间。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_copy_inter_class_toks:nnnn #1#2#3#4 { \tl_set:Ne \l_@@_tmp_tl { \xeCJK_get_inter_class_toks:nn {#3} {#4} } \tl_if_empty:NTF \l_@@_tmp_tl { \tl_set:Ne \l_@@_tmp_tl { \xeCJK_get_inter_class_toks:nn {#1} {#2} } \tl_if_empty:NF \l_@@_tmp_tl { \xeCJK_clear_inter_class_toks:nn {#1} {#2} } } { \xeCJK_inter_class_toks:nne {#1} {#2} { \exp_not:o \l_@@_tmp_tl } } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_replace_inter_class_toks:nnnn} % 将 |#1| 和 |#2| 之间出现的 |#3| 用 |#4| 替换。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_replace_inter_class_toks:nnnn #1#2#3#4 { \tl_set:Ne \l_@@_tmp_tl { \xeCJK_get_inter_class_toks:nn {#1} {#2} } \tl_if_empty:NF \l_@@_tmp_tl { \tl_replace_all:Nnn \l_@@_tmp_tl {#3} {#4} \xeCJK_inter_class_toks:nne {#1} {#2} { \exp_not:o \l_@@_tmp_tl } } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_clear_Boundary_and_CJK_toks:} % \changes{v3.4.2}{2016/10/19}{提高效率,避免重复循环。} % 清除边界与 CJK 文字、全角左右标点之间的内容。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_clear_Boundary_and_CJK_toks: { } \cs_new_protected:Npn \@@_update_clear_toks:n #1 { \cs_gset_protected:Npe \xeCJK_clear_Boundary_and_CJK_toks: { \exp_not:o { \xeCJK_clear_Boundary_and_CJK_toks: } \tex_XeTeXinterchartoks:D \xeCJK_class_num:n { Boundary } ~ \xeCJK_class_num:n {#1} = { \exp_not:N \prg_do_nothing: } } } % \end{macrocode} % \end{macro} % % \begin{variable} % {\g_@@_base_class_seq,\g_@@_non_CJK_class_seq,\g_@@_CJK_class_seq} % 保存宏包预先定义的字符类。 % \begin{macrocode} \seq_new:N \g_@@_base_class_seq \seq_gset_eq:NN \g_@@_base_class_seq \g_@@_class_seq \seq_new:N \g_@@_non_CJK_class_seq \seq_gset_from_clist:Nn \g_@@_non_CJK_class_seq { Default , HalfLeft , HalfRight , NormalSpace , Boundary } \seq_new:N \g_@@_CJK_class_seq \cs_new_protected:Npn \@@_save_CJK_class:n #1 { \seq_gput_right:Nn \g_@@_CJK_class_seq {#1} \tl_const:cn { \@@_CJK_class_tl:n { \use:c { \@@_class_csname:n {#1} } } } {#1} \@@_update_clear_toks:n {#1} } \clist_map_function:nN { CJK , FullLeft , FullRight , CM , HangulJamo , HangulJamoL , HangulJamoV , HangulJamoT , CJStarter , PoZheHao } \@@_save_CJK_class:n % \end{macrocode} % \end{variable} % % \subsection{字符输出规则} % % \begin{center} % \begin{tabular}{l*9c} % \toprule % & |Default| & |CJK| & |FullL| & |FullR| & |HalfL| % & |HalfR| & |Normal| & |Bound| & |CM| \\ \midrule % |Default| % & % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & % & % & % & \tokslink{def-bound} % & \tokslink{def-cm}\\ % |CJK| % & \tokslink{def-cjk} % & \tokslink{cjk-cjk} % & \tokslink{cjk-fl-fr} % & \tokslink{cjk-fl-fr} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{cjk-bound} % &\\ % |FullLeft| % & \tokslink{def-cjk} % & \tokslink{fl-fr-others} % & \tokslink{cjk-fl-fr} % & \tokslink{cjk-fl-fr} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{fl-fr-bound} % & \tokslink{def-cm}\\ % |FullRight| % & \tokslink{def-cjk} % & \tokslink{fl-fr-others} % & \tokslink{cjk-fl-fr} % & \tokslink{cjk-fl-fr} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{fl-fr-bound} % & \tokslink{def-cm}\\ % |HalfLeft| % & % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & % & % & % & % & \tokslink{def-cm}\\ % |HalfRight| % & % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & % & % & % & \tokslink{def-bound} % & \tokslink{def-cm}\\ % |NormalSpace| % & % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & \tokslink{def-cjk} % & % & % & % & \tokslink{ns-bound} % & \tokslink{def-cm}\\ % |Boundary| % & \tokslink{bound-def} % & \tokslink{bound-cjk} % & \tokslink{bound-fl-fr} % & \tokslink{bound-fl-fr} % & \tokslink{bound-def} % & % & \tokslink{bound-ns} % & % & \tokslink{def-cm}\\ % |CM| % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm} % & \tokslink{def-cm}\\ % \bottomrule % \end{tabular} % \end{center} % % \changes{v3.4.2}{2016/10/19}{避免在破折号之间折行。} % % \begin{macro}[int]{\xeCJK_class_group_begin:,\xeCJK_class_group_end:} % 在 CJK 类开始时,设置 \tn{XeTeXdashbreakstate} 为零,避免破折号之间的折行。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_class_group_begin: { \c_group_begin_token \bool_set_true:N \l_@@_CJK_group_bool \xeCJK_reset_space_factor: \int_zero:N \tex_XeTeXdashbreakstate:D } \bool_new:N \l_@@_CJK_group_bool \cs_new_eq:NN \xeCJK_class_group_end: \c_group_end_token % \end{macrocode} % \end{macro} % % \hypertarget{def-cm}{\texttt{CM}} 字符类与 |CJK| 字符类基本相同,只是从 |CJK| % 转移到 |CM| 时,不加入任何内容。 % \begin{macrocode} \AtEndOfPackage { \seq_map_inline:Nn \g_@@_class_seq { \str_if_eq:nnTF {#1} { CM } { \xeCJK_copy_inter_class_toks:nnnn { CM } {#1} { CJK } { CJK } } { \xeCJK_copy_inter_class_toks:nnnn { CM } {#1} { CJK } {#1} \str_if_eq:nnF {#1} { CJK } { \xeCJK_copy_inter_class_toks:nnnn {#1} { CM } {#1} { CJK } } } } } % \end{macrocode} % % \hypertarget{def-HJ}{\texttt{HangulJamo}} 字符类与 |CJK| 字符类基本相同,只是 % |HangulJamo| 类之间不加入任何内容。 % \begin{macrocode} \AtEndOfPackage { \seq_map_inline:Nn \g_@@_class_seq { \str_if_eq:nnF {#1} { HangulJamo } { \xeCJK_copy_inter_class_toks:nnnn { HangulJamo } {#1} { CJK } {#1} \xeCJK_copy_inter_class_toks:nnnn {#1} { HangulJamo } {#1} { CJK } } } } % \end{macrocode} % % \changes{v3.10.3}{2026/07/12}{将朝鲜文字母分为 L/V/T 三类,在音节内保持 % shaping,在相邻音节间恢复 \tn{CJKglue}(\#158)。} % % |HangulJamoL|、|HangulJamoV|、|HangulJamoT| 对外都复制 |CJK| 的 % 转移规则。三类之间先复制 |CJK| 到 |CJK|,再按 UAX~\#29 的 Hangul % 音节延续规则清空 L--L、L--V、V--V、V--T、T--T 五种转移;其余组合 % 是音节边界,保留 \tn{CJKglue} 和断行机会。 % \begin{macrocode} \AtEndOfPackage { \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT } { \seq_map_inline:Nn \g_@@_class_seq { \str_if_eq:nnF {##1} {#1} { \xeCJK_copy_inter_class_toks:nnnn {#1} {##1} { CJK } {##1} \xeCJK_copy_inter_class_toks:nnnn {##1} {#1} {##1} { CJK } } } } \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT } { \clist_map_inline:nn { HangulJamoL , HangulJamoV , HangulJamoT } { \xeCJK_copy_inter_class_toks:nnnn {#1} {##1} { CJK } { CJK } } } \xeCJK_clear_inter_class_toks:nn { HangulJamoL } { HangulJamoL } \xeCJK_clear_inter_class_toks:nn { HangulJamoL } { HangulJamoV } \xeCJK_clear_inter_class_toks:nn { HangulJamoV } { HangulJamoV } \xeCJK_clear_inter_class_toks:nn { HangulJamoV } { HangulJamoT } \xeCJK_clear_inter_class_toks:nn { HangulJamoT } { HangulJamoT } } % \end{macrocode} % % \changes{v3.10.3}{2026/07/12}{新增 \texttt{CJStarter} 类和可选严格行首禁则, % 保持小假名的普通 CJK 字距(\#165)。} % % |CJStarter| 对外复制 |CJK| 转移,并在普通 CJK、同类小假名以及全角右 % 标点之后进入该类时,把禁则 penalty 放在 \tn{CJKglue} 前。字符只有在 % |CJLineBreak=strict| 时才归入该类。 % \begin{macrocode} \AtEndOfPackage { \seq_map_inline:Nn \g_@@_class_seq { \str_if_eq:nnF {#1} { CJStarter } { \xeCJK_copy_inter_class_toks:nnnn { CJStarter } {#1} { CJK } {#1} \xeCJK_copy_inter_class_toks:nnnn {#1} { CJStarter } {#1} { CJK } } } \xeCJK_copy_inter_class_toks:nnnn { CJStarter } { CJStarter } { CJK } { CJK } \xeCJK_pre_inter_class_toks:nnn { CJK } { CJStarter } { \xeCJK_no_break: } \xeCJK_pre_inter_class_toks:nnn { CJStarter } { CJStarter } { \xeCJK_no_break: } \xeCJK_inter_class_toks:nnn { FullRight } { CJStarter } { \xeCJK_FullRight_and_CJStarter: \xeCJK_fallback_symbol:NN \CJKsymbol } } \cs_new_protected:Npn \xeCJK_FullRight_and_CJStarter: { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_no_break: \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_select_font: \CJKglue } % \end{macrocode} % % \hypertarget{def-PZH}{\texttt{PoZheHao}} 字符类与 |FullRight| 字符类基本相同, % 只是 |PoZheHao| 类之间不加入任何内容, % 从而不阻断 OpenType 字体的破折号合字功能(\#382)。 % \begin{macrocode} \AtEndOfPackage { \seq_map_inline:Nn \g_@@_class_seq { \str_if_eq:nnF {#1} { PoZheHao } { \xeCJK_copy_inter_class_toks:nnnn { PoZheHao } {#1} { FullRight } {#1} \xeCJK_copy_inter_class_toks:nnnn {#1} { PoZheHao } {#1} { FullRight } } } } % \end{macrocode} % % \hypertarget{def-cjk}{} % \begin{macrocode} \clist_map_inline:nn { Default , HalfLeft , HalfRight , NormalSpace } { \xeCJK_inter_class_toks:nnn {#1} { CJK } { \@@_boundary_capture_class:n { CJK } \xeCJK_class_group_begin: \xeCJK_select_font: \xeCJK_clear_inter_class_toks:nn {#1} { CJK } \xeCJK_clear_Boundary_and_CJK_toks: \xeCJK_fallback_symbol:NN \CJKsymbol } \xeCJK_inter_class_toks:nnn { CJK } {#1} { \xeCJK_class_group_end: \@@_boundary_capture_class:n { default } } } % \end{macrocode} % % \hypertarget{bound-def}{} % \begin{macrocode} \clist_map_inline:nn { Default , HalfLeft } { \xeCJK_inter_class_toks:nnn { Boundary } {#1} { \xeCJK_Boundary_and_Default: } \xeCJK_app_inter_class_toks:nnn { CJK } {#1} { \CJKecglue } } % \end{macrocode} % % \begin{macro}[int]{ % \xeCJK_Boundary_and_Default:, % \@@_check_for_ecglue:, % \@@_check_for_ecglue_aux:, % \@@_recover_ecglue_source_space:, % \@@_recover_ecglue_source_space_aux:, % \@@_recover_ecglue_source_space_success:, % \@@_recover_ecglue_source_space_restore:, % \@@_recover_ecglue_source_space_fallback:, % \@@_glue_check_expire_stale:, % \@@_skip_if_interword:N} % \changes{v3.10.0}{2026/04/30}{移除通用 whatsit 恢复,避免 % \pkg{hyperref} 链接注释等 whatsit 节点导致 \texttt{CJKecglue} % 在错误位置插入(\#803)。配合 \tn{set@color} 定点补丁保持 % \tn{textcolor} 场景的正确性(\#315)。} % \changes{v3.10.4}{2026/07/20}{允许 Boundary 到 Default 的恢复路径 % 暂时移除末尾不含无限阶伸缩、且有收缩量的源码词间 glue;若其下方确有 % CJK marker,则将其替换为 \tn{CJKecglue},补齐与 Default 到 CJK % 方向对称的命令边界处理(\#992)。} % % 字体组、颜色组或其他命令结束后,源码空格可能已经生成普通词间 glue, % 压在仍然有效的 CJK marker 上方。只有已知命令或分组路径设置了 % \cs{g_@@_glue_check_pending_bool} 时,才允许暂时移除末尾 glue 并检查 % 其下方的 marker,避免把直接输入的显式 \tn{hskip} 当成源码空格。 % 这枚 glue 还必须 finite、带 shrink 且自然 % 宽度与当前词间空格相等;对未进入额外句末空白分支的普通词间 glue, % \TeX{} 会把 stretch 乘以 |\spacefactor/1000|、把 shrink 乘以 % |1000/\spacefactor|,所以不能要求整份 skip 严格相等。当 % |\spacefactor >= 2000| 时还会把 \tn{fontdimen7} 加到自然宽度, % 自然宽度检查会排除这种情况。只有下方是 CJK、CJK-space 或 % CJK-widow marker 时,才移除这枚 glue,并换成缓存的 \tn{CJKecglue}; % 否则原样放回。 % 移除前用 skip 变量完整保存 \tn{lastskip},restore 还回的是同一份 % natural/stretch/shrink 数值,不会按当前 spacefactor 重新计算;只有节点的 % 源码来源无法恢复。 % 节点列表无法区分源码空格与参数完全相同的显式 \tn{hskip}。因此,紧跟 % 在已注册命令之后、自然宽度等于当前词间空格且带 shrink 的显式 glue % 也可能被当作源码空格。需要保留它时,可在前面加零宽 \tn{kern},或者 % 改用不同的自然宽度、去掉 shrink,让检查不会继续向前找到 xeCJK 的 % marker。 % restore/fallback 不清除 \cs{g_@@_glue_check_pending_bool}:该 glue 可能来自 % \pkg{ulem} 内部列表,这个布尔状态还要留给命令外真正的 CJK 边界;只有 % 确认并使用 marker 后才结束 pending 状态。 % fallback 也不继续调用 \cs{@@_check_for_ecglue_aux:}。此时候选 glue 仍是 % last node(或已由 restore 原样还回),aux 无法越过它取得下方 marker; % 这枚没有通过检查的 glue 本身就是要保留的分隔。只有 success 路径确认 % 下方是 CJK marker 并移除 glue 后,才允许改发 \tn{CJKecglue}。 % % \changes{v3.10.4}{2026/07/20}{新增 \cs{@@_glue_check_expire_stale:}: % 顶层恢复链发现节点列表为空时清除过期的 pending 状态,阻止 % \cs{g_@@_glue_check_pending_bool} 跨 \tn{hbox}/\tn{setbox} % 存活(\#996)。} % \cs{g_@@_glue_check_pending_bool} 是全局布尔变量。在盒子末尾结束 catcode-2 % 分组时设为 true 后,它可能越过 \tn{setbox},一直留到下一个水平列表。 % pending=true 只表示“下一次可以检查紧跟在 xeCJK marker 后的源码空格”。 % 空列表中不可能有这样的 marker,所以顶层恢复链(没有活跃 capture)发现 % \cs{tex_lastnodetype:D} 为负时,会直接清除 pending 状态。capture 活跃时 % 不这样做:\pkg{ulem} 等 stream 会在内部固定宽度盒子中触发同一恢复链,此时仍需 % 把 pending 留给命令外的边界;capture 入口本身已经清空旧状态。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Boundary_and_Default: { \@@_boundary_capture_class:n { default } \xeCJK_check_for_ecglue: } \cs_new_protected:Npn \@@_check_for_xecglue: { \@@_if_last_glue:TF { \@@_replace_space: } { \@@_check_for_ecglue: } } \cs_new_protected:Npn \@@_check_for_ecglue: { \@@_if_last_glue:TF { \@@_recover_ecglue_source_space: } { \@@_check_for_ecglue_aux: } } % \end{macrocode} % % \changes{v3.10.5}{2026/07/29}{\tn{CJKecglue} 补在源码空格位置时改用可搬运 % 通道,避免收缩量固化在 \pkg{ulem} 的定宽片段盒内(\#1037)。} % 这条路径补的是「源码空格 + 西文词」处的 \tn{CJKecglue}:源码空格已被前视 % 吃掉,marker 落在 \pkg{ulem} 刚打开的片段盒内部,此时直接 % \tn{hskip} 会把收缩量固化在定宽盒里,行断行取不到它,装饰段落因此溢出右 % 边距(\#1037,与 \#1026 是同一机制的另一半:\#1026 修的是西文词右侧, % 这里是词前)。改经 \cs{@@_ulem_glue:n},它在下划线状态下先 % \tn{UL@stop} 关闭片段盒、把间距画成外层列表上的 \tn{leaders}、再 % \tn{UL@start} 开新盒,使收缩量回到行上;不在下划线状态时它退化为普通 % \tn{hskip},因此非 \pkg{ulem} 路径的行为不变。 % % 不用 \cs{@@_boundary_use_ulem_glue:n}:它放的是裸 glue,会在词前留下一段 % 没有装饰线的空隙(实测 300dpi 下断开 7px)。 % % 默认实现是普通 \tn{hskip};\cs{@@_ulem_glue:n} 定义在 \pkg{xeCJKfntef} 里, % 该宏包未加载时不存在,所以由 \pkg{xeCJKfntef} 在加载时改写这个入口,不能在 % 这里直接引用。 % \begin{macrocode} \cs_new_protected:Npn \@@_use_ecglue_skip: { \skip_horizontal:N \l_@@_ecglue_skip } \cs_new_protected:Npn \@@_check_for_ecglue_aux: { \xeCJK_if_last_node:nTF { CJK } { \use_i:nn } { \xeCJK_if_last_node:nTF { CJK-widow } } { \xeCJK_remove_node: \@@_use_ecglue_skip: } { \xeCJK_if_last_node:nTF { CJK-space } { \xeCJK_remove_node: \@@_use_ecglue_skip: } { \xeCJK_if_last_node:nTF { math } { \xeCJK_remove_node: } { \@@_boundary_if_last_math_space:TF { \@@_boundary_math_space_consume: } { \@@_glue_check_expire_stale: } } } } } \dim_new:N \l_@@_interword_dim \prg_new_conditional:Npnn \@@_skip_if_interword:N #1 { T , F , TF } { \skip_if_finite:nTF {#1} { \dim_set:Nn \l_@@_tmp_dim {#1} \skip_set:Nn \l_@@_tmp_skip { \c_xeCJK_space_skip_tl } \dim_set:Nn \l_@@_interword_dim { \l_@@_tmp_skip } \dim_compare:nNnTF { \l_@@_tmp_dim } = { \l_@@_interword_dim } { \dim_compare:nNnTF { \tex_glueshrink:D #1 } > \c_zero_dim { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } { \prg_return_false: } } \cs_new_protected:Npn \@@_recover_ecglue_source_space: { \bool_if:NTF \g_@@_glue_check_pending_bool { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \@@_skip_if_interword:NTF \l_@@_last_skip { \tex_unskip:D \@@_recover_ecglue_source_space_aux: } { \@@_recover_ecglue_source_space_fallback: } } { \@@_recover_ecglue_source_space_fallback: } } \cs_new_protected:Npn \@@_recover_ecglue_source_space_aux: { \xeCJK_if_last_node:nTF { CJK } { \@@_recover_ecglue_source_space_success: } { \xeCJK_if_last_node:nTF { CJK-widow } { \@@_recover_ecglue_source_space_success: } { \xeCJK_if_last_node:nTF { CJK-space } { \@@_recover_ecglue_source_space_success: } { \xeCJK_if_last_node:nTF { math } { \xeCJK_remove_node: \@@_recover_ecglue_source_space_restore: \bool_gset_false:N \g_@@_glue_check_pending_bool } { \@@_boundary_if_last_math_space:TF { \@@_boundary_math_space_consume: } { \@@_recover_ecglue_source_space_restore: } } } } } } \cs_new_protected:Npn \@@_recover_ecglue_source_space_success: { \xeCJK_remove_node: \@@_use_ecglue_skip: \bool_if:NT \g_@@_glue_check_pending_bool { \bool_gset_false:N \g_@@_glue_check_pending_bool } } \cs_new_protected:Npn \@@_recover_ecglue_source_space_restore: { \skip_horizontal:N \l_@@_last_skip } \cs_new_eq:NN \@@_recover_ecglue_source_space_fallback: \prg_do_nothing: \cs_new_protected:Npn \@@_glue_check_expire_stale: { \int_compare:nNnT { \g_@@_boundary_capture_depth_int } = \c_zero_int { \int_compare:nNnT { \tex_lastnodetype:D } < \c_zero_int { \bool_gset_false:N \g_@@_glue_check_pending_bool } } } \cs_new_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_ecglue: % \end{macrocode} % \end{macro} % % \changes{v3.4.0}{2016/05/08}{改进 \texttt{xCJKecglue} 的实现。} % % \begin{macro}{\@@_replace_space:} % 将空格替换为 \tn{CJKecglue}。注意由 \tn{leaders} 等产生的 glue,并不能正确地还回去。 % 好在 \LaTeXe{} 中常用的 \tn{hrulefill} 和 \tn{dotfill} 定义末尾都有 |\kern\z@| 保护。 % \begin{macrocode} \cs_new_protected:Npn \@@_replace_space: { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \tex_unskip:D \xeCJK_if_last_node:nTF { CJK-space } { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip } { \xeCJK_if_last_node:nTF { CJK } { \skip_if_eq:nnTF { \l_@@_last_skip } { \c_xeCJK_space_skip_tl } { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip } { \skip_horizontal:N \l_@@_last_skip } } { \@@_boundary_if_last_math_space:TF { \@@_skip_if_interword:NTF \l_@@_last_skip { \@@_boundary_math_space_consume: } { \skip_horizontal:N \l_@@_last_skip } } { \skip_horizontal:N \l_@@_last_skip } } } } \skip_new:N \l_@@_last_skip % \end{macrocode} % \end{macro} % % \hypertarget{def-bound}{} % \begin{macrocode} \clist_map_inline:nn { Default , HalfRight } { \xeCJK_inter_class_toks:nnn {#1} { Boundary } { \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D \peek_meaning_remove:NTF \tex_italiccorrection:D { \tex_italiccorrection:D { \xeCJK_make_node:n { default } } } { \token_if_space:NTF \l_peek_token { \xeCJK_make_space_node: } { { \xeCJK_make_node:n { default } } } } } \xeCJK_pre_inter_class_toks:nnn {#1} { CJK } { \CJKecglue } } % \end{macrocode} % % \changes{v3.2.5}{2013/07/10} % {修正 \texttt{CJK} 和 \texttt{NormalSpace} 字符类之间因为边界造成的间距不正确的问题。} % % \hypertarget{bound-ns}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { Boundary } { NormalSpace } { \xeCJK_Boundary_and_NormalSp: } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_Boundary_and_NormalSp:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Boundary_and_NormalSp: { \xeCJK_check_for_ecglue_normalsp: } \cs_new_protected:Npn \@@_check_for_xecglue_normalsp: { \@@_if_last_glue:TF { \@@_replace_space: } { \@@_check_for_ecglue_normalsp: } } \cs_new_protected:Npn \@@_check_for_ecglue_normalsp: { \xeCJK_if_last_node:nT { CJK-space } { \xeCJK_remove_node: \xeCJK_space_or_xecglue: } } \cs_new_eq:NN \xeCJK_check_for_ecglue_normalsp: \@@_check_for_ecglue_normalsp: % \end{macrocode} % \end{macro} % % \hypertarget{ns-bound}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { NormalSpace } { Boundary } { \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D \peek_meaning_remove:NTF \tex_italiccorrection:D { \tex_italiccorrection:D { \xeCJK_make_node:n { normalspace } } } { \token_if_space:NTF \l_peek_token { \xeCJK_make_space_node: } { { \xeCJK_make_node:n { normalspace } } } } } % \end{macrocode} % % \hypertarget{bound-cjk}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { Boundary } { CJK } { \@@_boundary_capture_class:n { CJK } \xeCJK_check_for_glue: \xeCJK_class_group_begin: \xeCJK_clear_Boundary_and_CJK_toks: \xeCJK_select_font: \xeCJK_fallback_symbol:NN \CJKsymbol } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_check_for_glue:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_for_glue: { \@@_if_last_kern:TF { \@@_check_for_glue_auxi: } { \@@_if_last_glue:TF { \@@_check_for_glue_skip: } { \@@_if_last_math:TF { \xeCJK_remove_node: \@@_use_ecglue_skip: } { \@@_glue_check_expire_stale: \@@_check_for_glue_auxii: } } } } % \end{macrocode} % \changes{v3.10.0}{2026/05/12}{新增 glue 分支处理,修复 \pkg{xeCJKfntef} 命令右侧空格问题。} % \changes{v3.10.0}{2026/05/12}{当 glue 下方是 hlist(\tn{mbox} 等命令产生的 hbox)时, % 不依赖 boolean,直接通过 \cs{g_@@_last_node_tl} 判断前方内容类型, % 修复 \tn{mbox}、\tn{colorbox} 等 hbox 命令右侧多余 inter-word glue(\#831)。} % \changes{v3.10.4}{2026/07/20}{由命令边界 framework 接管 % \pkg{xeCJKfntef}、盒子命令和颜色命令,删除按全局 tl 猜测 hlist/whatsit % 下方类别的旧回退;glue 分支现在只接受紧邻的 xeCJK marker(\#992)。} % \changes{v3.10.4}{2026/07/20}{Boundary 到 CJK 的源码空格检查改用 % 与 Default 方向相同的 \cs{@@_skip_if_interword:N} 判断函数,自然宽度 % 不等于当前词间空格的显式 glue 不再被替换为 \tn{CJKglue}(\#996)。} % 统一 framework 在已知命令出口重放 xeCJK marker,并通过 % \cs{g_@@_glue_check_pending_bool} 开启一次源码空格检查;显式分组的 % catcode 2 结束点也设置同一个布尔状态。若待检查的 glue 下方不是 kern marker, % 本函数原样还回 glue 并进入标点检测链,不再根据任意 hlist、whatsit 或 % 可能陈旧的 \cs{g_@@_last_node_tl} 猜测输出类别。 % % 这枚 glue 使用与 Boundary→Default 方向相同的 % \cs{@@_skip_if_interword:N} 判断函数检查:\pkg{listings} 等宏包在 CJK % 字符间放置的 fil 级列对齐 glue 不是 finite;标题格式中 |\quad| 等 % 有意放置的间距没有 shrink 分量;显式 |\hskip| 的自然宽度通常不等于 % 当前词间空格。这三种情况都不进入源码空格分支。如果显式 glue 在节点 % 列表中与词间空格没有任何区别,\TeX{} 仍无法判断它的来源;需要原样 % 保留时,与 Default 方向一样,在前面加零宽 \tn{kern}。 % % 本函数仅在 \cs{@@_if_last_glue:TF} 的 true 分支中调用, % 因此入口处 \cs{tex_lastskip:D} 保证为非零 glue。 % \begin{macrocode} \cs_new_protected:Npn \@@_check_for_glue_skip: { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \@@_skip_if_interword:NTF \l_@@_last_skip { \tex_unskip:D \@@_if_last_kern:TF { \@@_check_for_glue_skip_kern: } { \@@_check_for_glue_skip_nonkern: } } { \@@_check_for_glue_skip_consume_and_fallback: } } \cs_new_protected:Npn \@@_check_for_glue_skip_kern: { \@@_boundary_if_last_math_space:TF { \bool_if:NTF \g_@@_glue_check_pending_bool { \@@_boundary_math_space_to_CJK: } { \@@_check_for_glue_skip_restore: } } { \@@_check_for_glue_skip_kern_normal: } } \cs_new_protected:Npn \@@_check_for_glue_skip_kern_normal: { \dim_compare:nNnTF \tex_lastkern:D = { \@@_node:n { math } } { \bool_if:NTF \g_@@_glue_check_pending_bool { \xeCJK_remove_node: \bool_gset_false:N \g_@@_glue_check_pending_bool \bool_if:NTF \l_@@_xecglue_bool { \skip_horizontal:N \l_@@_ecglue_skip } { \skip_horizontal:N \l_@@_last_skip } } { \@@_check_for_glue_skip_restore: } } { \bool_if:NTF \g_@@_glue_check_pending_bool { \bool_gset_false:N \g_@@_glue_check_pending_bool \dim_case:nnF { \tex_lastkern:D } { { \@@_node:n { CJK } } { \xeCJK_remove_node: \bool_if:NTF \l_@@_reserve_space_bool { \skip_horizontal:N \l_@@_last_skip } { \CJKglue } } { \@@_node:n { CJK-space } } { \xeCJK_remove_node: \@@_ccglue_or_space: } { \@@_node:n { CJK-widow } } { \xeCJK_remove_node: \xeCJK_widow_penalty: \bool_if:NTF \l_@@_reserve_space_bool { \skip_horizontal:N \l_@@_last_skip } { \CJKglue } } } { \@@_check_for_glue_skip_restore: } } { \@@_check_for_glue_skip_restore: } } } \cs_new_protected:Npn \@@_check_for_glue_skip_nonkern: { \bool_if:NT \g_@@_glue_check_pending_bool { \bool_gset_false:N \g_@@_glue_check_pending_bool } \@@_if_last_math:TF { \bool_if:NTF \l_@@_xecglue_bool { \skip_horizontal:N \l_@@_ecglue_skip } { \skip_horizontal:N \l_@@_last_skip } } { \@@_check_for_glue_skip_restore: } } \cs_new_protected:Npn \@@_check_for_glue_skip_restore: { \skip_horizontal:N \l_@@_last_skip \@@_check_for_glue_auxii: } \cs_new_protected:Npn \@@_check_for_glue_skip_consume_and_fallback: { \bool_if:NT \g_@@_glue_check_pending_bool { \bool_gset_false:N \g_@@_glue_check_pending_bool } \@@_check_for_glue_auxii: } \cs_new_protected:Npn \@@_check_for_glue_auxi: { \dim_case:nn { \tex_lastkern:D } { { \@@_node:n { CJK } } { \xeCJK_remove_node: \CJKglue } { \@@_node:n { CJK-space } } { \xeCJK_remove_node: \@@_ccglue_or_space: } { \@@_node:n { CJK-widow } } { \xeCJK_remove_node: \xeCJK_widow_penalty: \CJKglue } { \@@_node:n { default } } { \xeCJK_remove_node: \@@_use_ecglue_skip: } { \@@_node:n { math } } { \xeCJK_remove_node: \@@_use_ecglue_skip: } { \@@_node:n { math-space } } { \@@_boundary_math_space_to_CJK: } { \@@_node:n { math-space-frozen } } { \@@_boundary_math_space_to_CJK: } } } \cs_new_protected:Npn \@@_check_for_glue_auxii: { \xeCJK_if_last_punct:TF { \@@_check_for_glue_auxiii: } { \xeCJK_check_for_xglue: } } \cs_new_protected:Npn \@@_check_for_glue_auxiii: { \bool_if:NT \l_@@_last_penalty_bool { \tex_penalty:D \l_@@_last_penalty_int } \skip_horizontal:N \l_@@_last_skip \tl_if_eq:NNF \l_@@_aligni_tl \c_@@_left_tl { \CJKglue } } \cs_new_eq:NN \xeCJK_check_for_xglue: \prg_do_nothing: \cs_new_protected:Npn \@@_check_for_xglue: { \@@_if_last_glue:T { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \tex_unskip:D \xeCJK_if_last_node:nTF { CJK-space } { \xeCJK_remove_node: \@@_ccglue_or_space: } { \xeCJK_if_last_node:nTF { default-space } { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip } { \@@_check_for_xglue_aux: } } } } \cs_new_protected:Npn \@@_check_for_xglue_aux: { \skip_if_eq:nnTF { \l_@@_last_skip } { \c_xeCJK_space_skip_tl } { \xeCJK_if_last_node:nTF { CJK } { \xeCJK_remove_node: \@@_ccglue_or_space: } { \xeCJK_if_last_node:nTF { default } { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip } { \xeCJK_if_last_node:nTF { math } { \xeCJK_remove_node: \skip_horizontal:N \l_@@_ecglue_skip } { \@@_if_last_math:TF { \skip_horizontal:N \l_@@_ecglue_skip } { \skip_horizontal:N \l_@@_last_skip } } } } } { \skip_horizontal:N \l_@@_last_skip } } \cs_new_protected:Npn \@@_ccglue_or_space: { \CJKglue } % \end{macrocode} % \end{macro} % % \changes{v3.9.1}{2022/08/02}{简化部分内部实现。} % \changes{v3.10.0}{2022/08/31}{修复 \opt{xCJKecglue} 错误。} % \changes{v3.10.0}{2024/05/03}{修复 \tn{xeCJKnobreak} 错误。} % \changes{v3.10.0}{2026/04/27}{修复 \tn{textcolor} 等 whatsit 节点导致 \texttt{CJKecglue} 丢失的问题。} % \changes{v3.10.0}{2026/04/27}{修复字体切换组(如 \tn{texttt}、\tn{zihao})导致 \texttt{CJKecglue} 使用错误字体度量的问题。} % % \begin{macro}[TF]{ % \@@_if_last_none:, % \@@_if_last_hlist:, % \@@_if_last_math:, % \@@_if_last_glue:, % \@@_if_last_kern:, % \@@_if_last_penalty:} % 一些 \hologo{eTeX} 结点判定函数。 % \begin{macrocode} \group_begin: \cs_set:Npn \@@_tmp:nn #1 { \exp_args:Ncc \@@_tmp_aux:NNn { @@_if_last_ #1 : } { c_@@_ #1 _node } } \cs_set:Npn \@@_tmp_aux:NNn #1#2#3 { \int_const:Nn #2 {#3} \prg_new_conditional:Npnn #1 { T , F , TF } { \if_int_compare:w \tex_lastnodetype:D = #2 \prg_return_true: \else: \prg_return_false: \fi: } } \@@_tmp:nn { none } { -1 } \@@_tmp:nn { hlist } { 1 } \@@_tmp:nn { math } { 10 } \@@_tmp:nn { glue } { 11 } \@@_tmp:nn { kern } { 12 } \@@_tmp:nn { penalty } { 13 } \@@_tmp:nn { whatsit } { 9 } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}[pTF,int]{\xeCJK_if_last_node:n} % \begin{macrocode} \prg_new_conditional:Npnn \xeCJK_if_last_node:n #1 { p , T , F , TF } { \if_dim:w \cs_if_exist_use:cTF { c_@@_#1_node_dim } { = \tex_lastkern:D } { \use:c { c_@@_#1_node_skip } = \tex_lastskip:D } \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \changes{v3.1.0}{2012/11/14}{删除多余的 \texttt{default-itcorr} 结点。} % \changes{v3.2.4}{2013/07/03}{尽量移除用作判断标志的 \tn{kern}。} % \changes{v3.10.0}{2026/04/03}{改正拼写错误 \cs{@@_glue_node:n}。} % % \begin{macro}[int]{\xeCJK_declare_node:n,\xeCJK_make_node:n} % 用于判断插入的各种 |kern| 和 |glue|。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_declare_node:n #1 { \int_gincr:N \g_@@_node_int \dim_if_exist:cTF { c_@@_#1_node_dim } { \dim_gset:cn } { \dim_const:cn } { c_@@_#1_node_dim } { \g_@@_node_int sp } } \cs_new_protected:Npn \xeCJK_declare_glue_node:n #1 { \int_gincr:N \g_@@_node_int \skip_if_exist:cTF { c_@@_#1_node_skip } { \skip_gset:cn } { \skip_const:cn } { c_@@_#1_node_skip } { \g_@@_node_int sp } } \int_new:N \g_@@_node_int \int_gset:Nn \g_@@_node_int { 10 } \tl_new:N \g_@@_last_node_tl \cs_new_protected:Npn \xeCJK_make_node:n #1 { \exp_args:Nc \@@_make_node:N { c_@@_#1_node_dim } \tl_gset:Nn \g_@@_last_node_tl {#1} } \cs_new:Npn \@@_node:n #1 { \use:c { c_@@_#1_node_dim } } \cs_new:Npn \@@_glue_node:n #1 { \use:c { c_@@_#1_node_skip } } \cs_new_protected:Npn \@@_make_node:N #1 { \tex_kern:D - #1 \tex_kern:D #1 } \cs_new_protected:Npn \xeCJK_remove_node: { \@@_if_last_kern:TF { \tex_unkern:D \tex_unkern:D } { \@@_if_last_glue:T { \tex_unskip:D \tex_unskip:D } } \tl_gclear:N \g_@@_last_node_tl } \xeCJK_declare_node:n { CJK } \xeCJK_declare_node:n { CJK-space } \xeCJK_declare_node:n { default } \xeCJK_declare_node:n { CJK-widow } \xeCJK_declare_node:n { normalspace } \xeCJK_declare_glue_node:n { default-space } \xeCJK_declare_node:n { math } \xeCJK_declare_node:n { math-space } \xeCJK_declare_node:n { math-space-frozen } % \end{macrocode} % \changes{v3.10.0}{2026/05/12}{新增 \cs{g_@@_glue_check_pending_bool} % 用于标记 \pkg{xeCJKfntef} 组结束后的首次 glue 探测。} % \changes{v3.10.0}{2026/05/13}{新增 \cs{g_@@_reset_color_pending_bool} % 用于标记 \tn{reset@color} 在 hlist 上下文中触发,延迟到 % \cs{@@_check_for_glue_skip:} 中处理 \tn{colorbox} 等命令 % 右侧的 color pop whatsit(\#831)。} % \changes{v3.10.4}{2026/07/20}{删除颜色专用 pending boolean;颜色 % push/pop 和盒子输出改由 transparent/wrapped-box capture 处理(\#992)。} % \begin{macrocode} \bool_new:N \g_@@_glue_check_pending_bool % \end{macrocode} % \end{macro} % % \begin{macro}[int]{ % \@@_boundary_capture_class:n, % \@@_boundary_if_math_head:n, % \@@_boundary_if_math_tail:n, % \@@_boundary_if_math_tail_space:n, % \@@_boundary_if_math_edge:n, % \@@_boundary_if_last_math_node:, % \@@_boundary_math_begin:, % \@@_boundary_math_begin_visible:, % \@@_boundary_math_end:n, % \@@_boundary_math_end_confirm_node:, % \@@_boundary_math_end_confirm_space:, % \@@_boundary_math_end_confirm_space_visible:, % \@@_boundary_math_end_confirm:, % \@@_boundary_capture_report_first:n, % \@@_boundary_capture_allocate:n, % \@@_boundary_capture_suspend:, % \@@_boundary_capture_resume:, % \@@_boundary_capture_begin:, % \@@_boundary_inline_box_begin:, % \@@_boundary_inline_box_end:n, % \@@_boundary_inline_last_box_begin:, % \@@_boundary_inline_last_box_end:n, % \@@_boundary_inline_stream_begin:n, % \@@_boundary_inline_stream_begin:nn, % \@@_boundary_inline_stream_end:n, % \@@_boundary_hmode_transparent_begin:, % \@@_boundary_hmode_transparent_end:, % \@@_boundary_post_transparent:, % \@@_boundary_post_transparent_relocate:, % \@@_boundary_post_transparent_relocate_glue:, % \@@_boundary_box_begin:, % \@@_boundary_box_open:N, % \@@_boundary_box_end:n, % \@@_boundary_box_finish:nn, % \@@_boundary_if_capture_box_sized:, % \@@_boundary_if_capture_box_visible:, % \@@_boundary_box_set_last_from_node:, % \@@_boundary_last_box_end:n, % \@@_boundary_register_makeboxes:, % \@@_boundary_register_box:nn, % \@@_boundary_register_wrapped_box:nn, % \@@_boundary_register_transparent:n, % \@@_boundary_register_post_transparent:n, % \@@_boundary_register_stream:nn, % \@@_boundary_reserve:n, % \@@_boundary_reserve:N, % \@@_boundary_if_registered:n, % \@@_boundary_user_register:n, % \@@_boundary_user_register_command:n, % \@@_boundary_user_register_strategy:n, % \@@_boundary_user_register_mode:n, % \@@_boundary_user_register_validate:, % \@@_boundary_user_register_store:, % \@@_boundary_user_register_apply:, % \@@_boundary_user_register_apply:nn, % \@@_boundary_user_register_check:, % \@@_boundary_prepare_onearg_box:N, % \@@_boundary_prepare_onearg_box:NN, % \@@_boundary_wrap_transparent_noarg:NN, % \@@_boundary_wrap_transparent_onearg:NN, % \@@_boundary_wrap_transparent_onearg_braced:NN} % \begin{macro}[int]{ % \@@_boundary_capture_space:, % \@@_boundary_capture_emit_left:nn, % \@@_boundary_pop_node:N, % \@@_boundary_box_end_transparent:n, % \@@_boundary_emit_left:nn, % \@@_boundary_emit_left:nnn, % \@@_boundary_emit_left_math_space:nnn, % \@@_boundary_emit_left_normal:nnn, % \@@_boundary_use_glue:nn, % \@@_boundary_use_ecglue:n, % \@@_boundary_use_ccglue:n, % \@@_boundary_restore_space:, % \@@_boundary_restore_space:n, % \@@_boundary_set_last_from_node:n, % \@@_boundary_set_math_tail_from_capture:, % \@@_boundary_if_last_math_space:, % \@@_boundary_math_space_consume:, % \@@_boundary_math_space_set_actual:n, % \@@_boundary_math_space_encode:, % \@@_boundary_math_space_decode:, % \@@_boundary_math_space_set_delta:n, % \@@_boundary_math_space_expire:, % \@@_boundary_math_space_to_CJK:, % \@@_boundary_enable_source_space:n, % \@@_boundary_replay_node:n, % \@@_boundary_replay_before_math_space:, % \@@_boundary_replay_before:} % \changes{v3.10.4}{2026/07/20}{新增命令边界 capture/register 框架,按实际 % 可见输出的首尾类别统一处理盒子、不透明节点流和无可见输出命令(\#992)。} % \changes{v3.10.4}{2026/07/20}{box/wrapped-box 捕获在盒子直接排出了 % 可见内容(宽度非零、高度或深度非零,且末节点为 char/rule/math/kern) % 而未观察到任何字符类别时,按 Default 首尾重建边界;math 与 \tn{vrule} % 等不触发 interchar 转换的可见内容不再被误判为“无可见输出”。已经排好 % 再放入的盒子与只用于留白的盒子仍按无可见输出处理(\#998)。} % \changes{v3.10.4}{2026/07/21}{重放 Default marker 时按外层列表同步 % \tn{spacefactor};post-transparent 同时搬移 marker 与其后的候选 glue, % 修复 \opt{xCJKecglue} 启用后盒内末尾大写字母和 \tn{null} 遮住边界后缀 % 时的右边界间距(\#1003)。} % \changes{v3.10.4}{2026/07/21}{新增独立的行内公式边界类别,分别处理公式 % 末尾与公式后已有参数内源码空格,使注册命令能按直接公式的源码空格语义 % 恢复左右边界;另行区分仍参与外层断行的 stream 空格与已冻结在盒子中的 % 空格,正确保留弹性间距的伸缩量;不可见节点将普通 stream 的真实空格 % 与 marker 隔开时按直接公式语义让 marker 过期,并保留中间 glue 与节点 % 的原有次序(\#1002)。} % \changes{v3.10.4}{2026/07/22}{新增实验性 % \opt{experiment/boundary-register} 接口,允许专家用户把自定义命令注册到 % 统一命令边界框架(\#1010)。} % % 命令边界框架提供五类注册策略。|box| 策略在注册命令执行前保存并移除前一 % 可见字符留下的 marker 和可选源码空格,命令结束后取出其末尾 hbox,重建 % 边界后原样放回;|wrapped-box| 用透明 hbox 收集会直接写出多个节点的盒子命令。 % |stream| 策略让 annotation、数学包装或 verbatim 流直接写入当前列表; % 其 |default| 变体固定两端为 Default,|first-default| 只固定首类别、 % 仍以实际输出决定末类别。 % |transparent| 策略用于只插入锚点、write 等无可见输出的命令。 % 其中 \tn{null} 还能合法地出现在 \tn{setbox}\meta{box} 后,不能在其前面插入 % hook 代码,故使用 |post-transparent| after-only 变体:只在宽、高、深均为零的 % 盒子确实盖住 marker 或“marker + 候选 glue”时,以列表中的真实 marker 为 % 证据,把这一边界后缀移到盒子后;其下方没有 marker 时按原顺序还原 glue % 与盒子,不能把任意 glue 搬过 hbox。 % Boundary 到 Default/CJK 的转换会把观察到的类别同时写入所有活跃 capture, % 因而混合输出和嵌套命令都能分别取得实际首、尾类别。 % % capture 结束时,框架按“前类别 × 首类别 × 是否有源码空格”重建左边界, % 再按实际末类别重放 marker 供右边界使用;无可见输出时则完整恢复入口状态。 % Default 字符在盒子内部设置的 \tn{spacefactor} 不会传播到外层列表,故重放 % Default 类 marker 时还要把源码空格检查使用的值同步为外层列表当前的 % \tn{spacefactor};stream 内容会直接更新外层值,因此使用同一条同步路径。 % “无可见输出”由观察类别、盒子尺寸与末节点类型共同判定。\tn{vrule} % 不触发 interchar 转换;若盒子宽度非零、高度或深度非零,且末节点是这个 % 盒子直接排出的 char、rule 或标点处理添加的 kern,box/wrapped-box 就按 % Default 首尾重建边界。行内公式另用 |math| 类别:stream 和 box 的正文 % 以 |$|、\tn{(} 或 \tn{ensuremath} 开始时,参数适配器必须在 math-on % 节点之前报告首类别;正文语法以相应公式结束时只产生候选,正文实际排完后 % 还要由末尾 math 节点或 marker 确认,才把 |math| 记为当前 capture 的 % 确定末类别。未知宏若把公式记号或分组当作参数消费,实际节点检查不会通过。 % capture 结束时以这个明确的正文末类别覆盖命令内部留下的旧 marker, % 不把任意内部 math 节点猜成可见公式。这样“CJK 前缀 % + 公式后缀”和外层分组中的公式都能重放正确的右边界,同时嵌套命令只把 % 结果逐层写回其实际输出列表,不会把离线 \tn{setbox} 中的公式直接认作 % 外层可见输出。显式公式入口本身只处理当前列表,不把事件广播到外层 % capture,因此不会改变裸公式和离线测量盒子的节点列表。 % % 若公式后还有参数内尾随源码空格,框架保留这枚空格在命令、框线或装饰范围 % 内的原位置。普通 stream 在外层重放 |math-space| marker,并随 marker 保存 % 这枚空格的伸缩量;box、wrapped-box 和 stream-ulem 则重放 % |math-space-frozen|,表示空格已被冻结在内部盒子中。右侧为 CJK 且 % \opt{xCJKecglue} 启用时,前者从补偿量中扣除实际仍参与外层断行的伸缩量; % 后者完整保留 \tn{CJKecglue} 的伸缩量,并禁止用负的自然宽度把后续文字 % 拉入框线或装饰范围。命令外紧接的另一枚源码空格按连续源码空格处理, % 显式 glue 则仍保留。ulem 会先把源码空格改造成 leaders,所以它的参数 % 适配器在交出尾随空格前确认公式节点,再把空格交还 ulem 排版。若 % transparent 或 post-transparent 命令把 special、零尺寸盒子等不可见节点 % 插到普通 stream 的真实空格与 marker 之间,直接公式的恢复链也会被同一 % 节点截断;框架因此让 marker 过期,不在节点之后单独增加补偿 glue;若 % marker 与节点之间还有显式 glue,也把它恢复到节点之前的原位置。 % 宽、高、深均为零的盒子(\tn{null}、 % 空 \tn{mbox})、只用于留白的盒子(空 \tn{makebox}、strut、零高 rule), % 以及末尾放着另一个已经排好的盒子或填充 glue 的命令(例如 % \tn{makebox} 放入 \tn{box},capture 无法知道其中的字符类别),都按 % “没有观察到可见输出”处理,并恢复进入命令前的状态。 % 源码空格、\tn{CJKecglue}、\tn{CJKglue} 及 \opt{CJKspace}、 % \opt{xCJKecglue} 状态都在入口字体和分组上下文中缓存,避免命令内部字体 % 或选项切换改变恢复值。\tn{sbox} 这类只构造离线盒子而不向当前列表输出的 % 路径会暂停观察,并按嵌套层保存、恢复外层 marker 与 pending 状态,防止 % 测量用 scratch box 或其中的颜色切换污染外层 capture 和基础恢复链。 % ulem 这类会把正文拆进固定宽度盒子的子系统使用专用 stream kind;框架仍 % 统一决定应发哪一种左侧 glue,但把数值交回子系统自己的外层 glue 通道, % 避免弹性分量被困在内部固定宽度盒子中。 % % 额外 hbox 只用于 \tn{color@b@x} 这类本来会输出多个节点的注册命令;普通 % 盒子命令取出并放回原 hbox,不增加节点层级。每个同步嵌套层使用独立 register, % 避免外层盒子赋值尚未结束时被内层 capture 覆写;常用的前三层在加载时预分配, % 更多层则随首次达到的 depth 惰性创建。若 wrapped-box 没有观察到可见字符, % 结束路径会 unpack 该层透明外层盒子。每层的 token-list 状态同样惰性分配,使 % 注册命令可以任意嵌套而不会静默退回未修复行为;首类别会写入所有尚未取得 % 首字符的外层 capture。box 结束时会读取自身节点列表末尾由框架写入的 % marker,并据此更新末类别;嵌套盒子的推断结果便能逐层传到最外面。 % stream 结束时也以外层节点列表末尾的 marker 为准; % annotation/math 等没有末尾 marker 的流仍使用运行时观察结果。 % 每层字段在 \cs{@@_boundary_capture_begin:} 中按同一 depth 连续完成 clear、 % 求值与写入。当前不把 flag 序列化单独抽成 helper:它仍需接收动态 depth % 和不同字段名,只会把可见的状态初始化拆散;若以后继续增加每层字段,再把 % allocate、initialize、restore 一并评估为 record/helper 抽象。 % % 实验性用户注册接口先把控制序列名、策略和类别模式写入独立属性表,不立即 % 修改命令。通用命令 hook 记录在注册表中;必须直接重定义扫描器或内部排版 % 入口的专用适配器另写入保留表。用户声明在保存时和导言区末尾应用时都查询 % 这两张表,不能把通用 hook 叠加到已有专用适配器外层。随后再调用同一组 % 五种注册函数;LaTeX 在普通 \tn{AtBeginDocument} 代码执行完以后安装命令 % hook。用户接口因此不复制恢复算法,也不改变内建注册的优先级。正文开始前 % 再检查成功登记的控制序列是否存在,避免 LaTeX 对未定义命令的延迟 hook % 静默失效。 % \begin{macrocode} \int_new:N \g_@@_boundary_capture_depth_int \int_new:N \g_@@_boundary_suspend_depth_int \seq_new:N \g_@@_boundary_active_seq \prop_new:N \g_@@_boundary_registered_prop \prop_new:N \g_@@_boundary_reserved_prop \prop_new:N \g_@@_boundary_user_registered_prop \seq_new:N \g_@@_boundary_user_applied_seq \bool_new:N \g_@@_boundary_user_register_closed_bool \bool_new:N \l_@@_boundary_user_invalid_bool \bool_new:N \l_@@_boundary_user_mode_given_bool \tl_new:N \l_@@_boundary_user_name_tl \tl_new:N \l_@@_boundary_user_strategy_tl \tl_new:N \l_@@_boundary_user_mode_tl \tl_new:N \l_@@_boundary_user_value_tl \seq_new:N \l_@@_boundary_user_value_seq \box_new:c { g_@@_boundary_capture_1_box } \box_new:c { g_@@_boundary_capture_2_box } \box_new:c { g_@@_boundary_capture_3_box } \box_new:N \l_@@_boundary_post_box \tl_new:N \l_@@_boundary_node_tl \tl_new:N \l_@@_boundary_first_tl \tl_new:N \l_@@_boundary_last_tl \tl_new:N \l_@@_boundary_active_tl \cs_new_protected:Npn \@@_boundary_capture_allocate:n #1 { \box_if_exist:cF { g_@@_boundary_capture_#1_box } { \box_new:c { g_@@_boundary_capture_#1_box } } \tl_if_exist:cF { g_@@_boundary_capture_#1_before_tl } { \tl_new:c { g_@@_boundary_capture_#1_before_tl } \tl_new:c { g_@@_boundary_capture_#1_before_math_space_skip_tl } \tl_new:c { g_@@_boundary_capture_#1_first_tl } \tl_new:c { g_@@_boundary_capture_#1_last_tl } \tl_new:c { g_@@_boundary_capture_#1_kind_tl } \tl_new:c { g_@@_boundary_capture_#1_space_skip_tl } \tl_new:c { g_@@_boundary_capture_#1_ecglue_skip_tl } \tl_new:c { g_@@_boundary_capture_#1_ccglue_skip_tl } \tl_new:c { g_@@_boundary_capture_#1_space_flag_tl } \tl_new:c { g_@@_boundary_capture_#1_reserve_space_flag_tl } \tl_new:c { g_@@_boundary_capture_#1_xecglue_flag_tl } \tl_new:c { g_@@_boundary_capture_#1_math_tail_flag_tl } \tl_new:c { g_@@_boundary_capture_#1_math_tail_space_flag_tl } \tl_new:c { g_@@_boundary_capture_#1_math_tail_space_skip_tl } } } \@@_boundary_capture_allocate:n { 1 } \@@_boundary_capture_allocate:n { 2 } \@@_boundary_capture_allocate:n { 3 } \cs_new_protected:Npn \@@_boundary_capture_class:n #1 { \int_compare:nNnT { \g_@@_boundary_capture_depth_int } > \c_zero_int { \int_compare:nNnT { \g_@@_boundary_suspend_depth_int } = \c_zero_int { \int_step_inline:nn { \g_@@_boundary_capture_depth_int } { \tl_gset:cn { g_@@_boundary_capture_##1_last_tl } {#1} \tl_if_empty:cT { g_@@_boundary_capture_##1_first_tl } { \tl_gset:cn { g_@@_boundary_capture_##1_first_tl } {#1} \tl_if_eq:cnTF { g_@@_boundary_capture_##1_kind_tl } { stream } { \@@_boundary_capture_emit_left:nn {##1} {#1} } { \tl_if_eq:cnT { g_@@_boundary_capture_##1_kind_tl } { stream-ulem } { \@@_boundary_capture_emit_left:nn {##1} {#1} } } } } } } } \cs_new_protected:Npn \@@_boundary_math_begin: { \mode_if_horizontal:T { \scan_stop: \@@_boundary_pop_node:N \l_@@_boundary_node_tl \str_case:Vn \l_@@_boundary_node_tl { { CJK } { \CJKecglue } { CJK-space } { \xeCJK_space_or_xecglue: } { CJK-widow } { \xeCJK_widow_penalty: \CJKecglue } { default-space } { \xeCJK_space_glue: } } } } \cs_new_protected:Npn \@@_boundary_math_begin_visible: { \@@_boundary_capture_class:n { math } \@@_boundary_math_begin: } \tl_new:N \l_@@_boundary_math_tl \tl_new:N \l_@@_boundary_math_type_tl \skip_new:N \l_@@_boundary_math_skip \skip_new:N \l_@@_boundary_math_actual_skip \skip_new:N \l_@@_boundary_math_ecglue_skip \skip_new:N \l_@@_boundary_math_delta_skip \dim_new:N \l_@@_boundary_math_space_dim \dim_new:N \l_@@_boundary_math_ecglue_dim \dim_new:N \l_@@_boundary_math_stretch_dim \dim_new:N \l_@@_boundary_math_shrink_dim % \end{macrocode} % \changes{v3.10.5}{2026/08/03}{boundary 判断前把参数副本中 catcode 4 的 % \texttt{\&} 换成 \cs{scan_stop:},修复 \tn{colorbox} 等命令在 \tn{halign} % 语境下参数含 |&| 时编译报错的问题(\#1043)。} % % \cs{@@_boundary_math_set:n} 把待判断的参数存入 \cs{l_@@_boundary_math_tl}, % 并把其中 catcode 4 的 |&| 换成 \cs{scan_stop:}。 % % 触发条件要分清两件事:|&| 在 \LaTeX{} 下\emph{默认}就是 catcode 4 % (\file{latex.ltx}),并非 \tn{halign} 把它设成 4;真正要紧的是\textbf{扫描发生的 % 位置}——当 \cs{tl_if_head_eq_meaning:nNTF} 等 \pkg{expl3} 条件式在\emph{对齐环境内} % (\env{eqnarray}、\env{align}、\env{tabular})读取参数时,catcode 4 的 |&| 会终止 % 它正在读的宏参数,报 % \enquote{Argument of \cs{__tl_tl_head:w} has an extra \}}(\#1043)。 % 同一个 token list 在对齐环境\emph{之外}走同一条件式实测 0 错误,所以这不是 % 「\pkg{expl3} 条件式对 catcode-4 记号的固有限制」,而是对齐符在对齐环境里的 % 参数终止语义。 % 这里只是为\emph{语法判断}准备副本,实际排版仍使用原始参数,所以替换不影响 % 输出;换成 \cs{scan_stop:} 而不是直接删除,是为了保住 |&| 的位置, % 使 |&$x$| 这类参数仍被正确判定为「首项不是公式」。 % % 两条范围说明,避免后人误以为漏改: % \begin{itemize} % \item \cs{tl_replace_all:NVn} 不进入嵌套花括号组,|{ a { x & y } b }| 只得到 % |a{x&y}b|。这是够用的:只有\emph{顶层}对齐符会破坏 \pkg{expl3} 条件式的括号组 % 扫描(实测组内含 catcode-4 |&| 时,裸 \cs{tl_if_head_eq_meaning:nNTF}、 % \cs{tl_reverse:N}、\cs{tl_head:n}、\cs{tl_tail:n} 均不报错),而 % \cs{@@_boundary_if_math_head:n} 递归进入组时会重新调用自身、再次经过本函数, % 下一层的 |&| 到那时才需要消解。 % \item \cs{@@_boundary_if_math_tail_space:n} 不走本函数,因为它只做 % \cs{tl_trim_right_spaces:n} 与 \cs{tl_if_eq:NNTF},不做 head/tail 记号扫描, % 参数含 catcode-4 |&| 时实测 0 错误。需要消解的只有做扫描的 % \cs{@@_boundary_if_math_head:n} 与 \cs{@@_boundary_if_math_tail:n} 两个入口。 % \end{itemize} % % \cs{tl_replace_all:Nnn} 及其变体(这里用的是 \cs{tl_replace_all:NVn})按 catcode % 与 charcode 双重相等匹配,所以模式里的 |\&| % 必须是 catcode 4 的对齐符。在本文件里直接写 |{ & }| 恰好可用,但原因只是 % \begingroup\bfseries\LaTeX{} 环境默认把 |&| 设为 4(\file{latex.ltx}: % |\catcode`\&=4|),而 \tn{ExplSyntaxOn} 不改 38\endgroup(实测它把 9、32 设为 9, % 58、95 设为 11,126 设为 10,而 38 前后都是 4)。注意 % \cs{c_code_cctab} 之类的 catcode 表\emph{常量}与此无关:它只在被 % \cs{cctab_select:N} 选中时才生效,本包从不选它。 % % 关键在于\textbf{字面模式的类别在本文件被读取(tokenise)时就冻结了},与调用时的 % \tn{catcode} 无关——加载后再改 |&| 的类别,对字面写法和常量写法都没有影响。 % 真正的风险是\emph{加载期}的 régime:若 \tn{usepackage} 之前外层做过 % |\catcode`\&=12|(或在某个非默认 catcode 表下 \tn{input} 本包),字面 |{ & }| 读进来 % 就不是对齐符,此后一律静默失配。\cs{c_@@_alignment_tl} 由 % \cs{char_set_catcode_alignment:N} 在局部组里现场生成,与读取时的 régime 无关, % 防住的正是这一种情形。 % \begin{macrocode} \group_begin: \char_set_catcode_alignment:N \& \tl_const:Nn \c_@@_alignment_tl { & } \group_end: \cs_new_protected:Npn \@@_boundary_math_set:n #1 { \tl_set:Ne \l_@@_boundary_math_tl { \tl_trim_spaces:n {#1} } \tl_replace_all:NVn \l_@@_boundary_math_tl \c_@@_alignment_tl { \scan_stop: } } \prg_new_protected_conditional:Npnn \@@_boundary_if_math_head:n #1 { T , F , TF } { \@@_boundary_math_set:n {#1} \tl_if_empty:NTF \l_@@_boundary_math_tl { \prg_return_false: } { \exp_args:NV \tl_if_head_is_group:nTF \l_@@_boundary_math_tl { \exp_args:Ne \@@_boundary_if_math_head:nTF { \tl_head:V \l_@@_boundary_math_tl } { \prg_return_true: } { \prg_return_false: } } { \exp_args:NV \tl_if_head_eq_meaning:nNTF \l_@@_boundary_math_tl $ { \prg_return_true: } { \exp_args:NV \tl_if_head_eq_meaning:nNTF \l_@@_boundary_math_tl \( { \prg_return_true: } { \exp_args:NV \tl_if_head_eq_meaning:nNTF \l_@@_boundary_math_tl \ensuremath { \prg_return_true: } { \prg_return_false: } } } } } } \prg_new_protected_conditional:Npnn \@@_boundary_if_math_tail:n #1 { T , F , TF } { \@@_boundary_math_set:n {#1} \tl_reverse:N \l_@@_boundary_math_tl \exp_args:NV \@@_boundary_if_math_tail_reversed:nTF \l_@@_boundary_math_tl { \prg_return_true: } { \prg_return_false: } } % \end{macrocode} % \cs{tl_reverse:N} 只反转顶层项目,保留花括号组内的次序。末项目为组时, % 先识别 \tn{ensuremath} 的参数,否则递归检查组内末尾。所有语法判断都只 % 产生候选:未知宏也可能把末尾的分组、|$| 或 \tn{)} 当作参数消费, % \cs{@@_boundary_math_end:n} 还要用当前列表的实际 math 节点或 marker % 确认,才会发布末类别。 % \begin{macrocode} \prg_new_protected_conditional:Npnn \@@_boundary_if_math_tail_reversed:n #1 { T , F , TF } { \tl_if_empty:nTF {#1} { \prg_return_false: } { \tl_if_head_eq_meaning:nNTF {#1} $ { \prg_return_true: } { \tl_if_head_eq_meaning:nNTF {#1} \) { \prg_return_true: } { \tl_if_head_is_group:nTF {#1} { \tl_set:Ne \l_@@_boundary_math_tl { \tl_trim_left_spaces:e { \tl_tail:n {#1} } } \exp_args:NV \tl_if_head_eq_meaning:nNTF \l_@@_boundary_math_tl \ensuremath { \prg_return_true: } { \exp_args:Ne \@@_boundary_if_math_tail:nTF { \tl_head:n {#1} } { \prg_return_true: } { \prg_return_false: } } } { \prg_return_false: } } } } } \prg_new_protected_conditional:Npnn \@@_boundary_if_math_edge:n #1 { T , F , TF } { \@@_boundary_if_math_head:nTF {#1} { \prg_return_true: } { \@@_boundary_if_math_tail:nTF {#1} { \prg_return_true: } { \prg_return_false: } } } \cs_new_protected:Npn \@@_boundary_math_begin:n #1 { \@@_boundary_if_math_head:nT {#1} { \@@_boundary_math_begin_visible: } } \cs_new_protected:Npn \@@_boundary_math_end:n #1 { \@@_boundary_if_math_tail:nT {#1} { \@@_boundary_if_math_tail_space:nTF {#1} { \@@_boundary_math_end_confirm_space: } { \@@_boundary_math_end_confirm_node: } } } \prg_new_protected_conditional:Npnn \@@_boundary_if_math_tail_space:n #1 { T , F , TF } { \tl_set:Nn \l_@@_boundary_math_tl {#1} \tl_set:Ne \l_@@_boundary_node_tl { \tl_trim_right_spaces:n {#1} } \tl_if_eq:NNTF \l_@@_boundary_math_tl \l_@@_boundary_node_tl { \prg_return_false: } { \prg_return_true: } } \prg_new_conditional:Npnn \@@_boundary_if_last_math_node: { T , F , TF } { \@@_if_last_math:TF { \prg_return_true: } { \xeCJK_if_last_node:nTF { math } { \prg_return_true: } { \prg_return_false: } } } \cs_new_protected:Npn \@@_boundary_math_end_confirm_node: { \@@_boundary_if_last_math_node:T { \@@_boundary_math_end_confirm: } } % \end{macrocode} % 参数末尾的字面空格通常会成为一枚普通词间 glue,遮住它前面的公式节点。 % 这里只允许越过语法扫描已经确认的尾随空格,并再次检查节点确为当前字体的 % 词间空格;未知宏留下的其他 glue 不会进入这条路径。确认后把 glue 原样 % 放回命令内部,并记录“公式后有参数内空格”的末类别,供 capture 结束时 % 按普通 stream 或冻结空格分别处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_math_end_confirm_space: { \@@_if_last_glue:T { \skip_set_eq:NN \l_@@_boundary_math_skip \tex_lastskip:D \@@_skip_if_interword:NT \l_@@_boundary_math_skip { \tex_unskip:D \@@_boundary_if_last_math_node:T { \@@_boundary_math_end_confirm_space_visible: } \skip_horizontal:N \l_@@_boundary_math_skip } } } \cs_new_protected:Npn \@@_boundary_math_end_confirm_space_visible: { \@@_boundary_math_end_confirm: \tl_gset:cn { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_flag_tl } { true } \tl_if_eq:cnT { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _kind_tl } { stream } { \tl_gset:ce { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_skip_tl } { \skip_use:N \l_@@_boundary_math_skip } } } \cs_new_protected:Npn \@@_boundary_math_end_confirm: { \int_compare:nNnT { \g_@@_boundary_capture_depth_int } > \c_zero_int { \int_compare:nNnT { \g_@@_boundary_suspend_depth_int } = \c_zero_int { \tl_gset:cn { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _last_tl } { math } \tl_gset:cn { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_flag_tl } { true } \tl_gset:cn { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_flag_tl } { false } } } } \cs_new_protected:Npn \@@_boundary_capture_report_first:n #1 { \int_compare:nNnT { \g_@@_boundary_capture_depth_int } > \c_zero_int { \int_compare:nNnT { \g_@@_boundary_suspend_depth_int } = \c_zero_int { \int_step_inline:nn { \g_@@_boundary_capture_depth_int } { \tl_if_empty:cT { g_@@_boundary_capture_##1_first_tl } { \tl_gset:cn { g_@@_boundary_capture_##1_first_tl } {#1} \tl_if_eq:cnTF { g_@@_boundary_capture_##1_kind_tl } { stream } { \@@_boundary_capture_emit_left:nn {##1} {#1} } { \tl_if_eq:cnT { g_@@_boundary_capture_##1_kind_tl } { stream-ulem } { \@@_boundary_capture_emit_left:nn {##1} {#1} } } } } } } } \cs_new_protected:Npn \@@_boundary_capture_emit_left:nn #1#2 { \tl_set:Ne \l_@@_boundary_node_tl { \tl_use:c { g_@@_boundary_capture_#1_before_tl } } \@@_boundary_emit_left:nVn {#1} \l_@@_boundary_node_tl {#2} \tl_gset:cn { g_@@_boundary_capture_#1_space_flag_tl } { false } } \cs_new_protected:Npn \@@_boundary_capture_suspend: { \int_gincr:N \g_@@_boundary_suspend_depth_int \tl_if_exist:cF { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _node_tl } { \tl_new:c { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _node_tl } \tl_new:c { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _pending_tl } } \exp_args:Nc \tl_gset_eq:NN { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _node_tl } \g_@@_last_node_tl \tl_gset:ce { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _pending_tl } { \bool_if:NTF \g_@@_glue_check_pending_bool { true } { false } } } \cs_new_protected:Npn \@@_boundary_capture_resume: { \tl_gset:Ne \g_@@_last_node_tl { \tl_use:c { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _node_tl } } \tl_if_eq:cnTF { g_@@_boundary_suspend_\int_use:N \g_@@_boundary_suspend_depth_int _pending_tl } { true } { \bool_gset_true:N \g_@@_glue_check_pending_bool } { \bool_gset_false:N \g_@@_glue_check_pending_bool } \int_gdecr:N \g_@@_boundary_suspend_depth_int } \cs_new_protected:Npn \@@_boundary_inline_box_begin: { \int_compare:nNnTF { \g_@@_boundary_suspend_depth_int } > \c_zero_int { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_if_math:TF { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_leave_vertical: \seq_gpush:Nn \g_@@_boundary_active_seq { true } \@@_boundary_box_begin: } } } \cs_new_protected:Npn \@@_boundary_inline_box_end:n #1 { \seq_gpop:NN \g_@@_boundary_active_seq \l_@@_boundary_active_tl \tl_if_eq:NnT \l_@@_boundary_active_tl { true } { \@@_boundary_box_end:n {#1} } } \cs_new_protected:Npn \@@_boundary_inline_last_box_begin: { \int_compare:nNnTF { \g_@@_boundary_suspend_depth_int } > \c_zero_int { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_if_math:TF { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_leave_vertical: \seq_gpush:Nn \g_@@_boundary_active_seq { true } \@@_boundary_capture_begin: \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _kind_tl } { box } } } } \cs_new_protected:Npn \@@_boundary_inline_last_box_end:n #1 { \seq_gpop:NN \g_@@_boundary_active_seq \l_@@_boundary_active_tl \tl_if_eq:NnT \l_@@_boundary_active_tl { true } { \@@_boundary_last_box_end:n {#1} } } \cs_new_protected:Npn \@@_boundary_inline_stream_begin:n #1 { \@@_boundary_inline_stream_begin:nn {#1} { stream } } \cs_new_protected:Npn \@@_boundary_inline_stream_begin:nn #1#2 { \int_compare:nNnTF { \g_@@_boundary_suspend_depth_int } > \c_zero_int { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_if_math:TF { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_leave_vertical: \seq_gpush:Nn \g_@@_boundary_active_seq { true } \@@_boundary_capture_begin: \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _kind_tl } {#2} \str_case:nn {#1} { { default } { \@@_boundary_capture_class:n { default } } { first-default } { \@@_boundary_capture_class:n { default } } } } } } \cs_new_protected:Npn \@@_boundary_inline_stream_end:n #1 { \seq_gpop:NN \g_@@_boundary_active_seq \l_@@_boundary_active_tl \tl_if_eq:NnT \l_@@_boundary_active_tl { true } { \scan_stop: \@@_boundary_pop_node:N \l_@@_boundary_node_tl \tl_set:Ne \l_@@_boundary_first_tl { \tl_use:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _first_tl } } \tl_set:Ne \l_@@_boundary_last_tl { \tl_use:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _last_tl } } % \end{macrocode} % 流式命令若在最外层留下 xeCJK marker,就以该 marker 记录的 % 类别更新末类别。这适用于内部使用不可见定界字符、最后又把正文末尾 % marker 移到外层列表的命令;没有末尾 marker 的 annotation/math % 仍使用 capture 观察值。 % \begin{macrocode} \exp_args:NV \@@_boundary_set_last_from_node:n \l_@@_boundary_node_tl \@@_boundary_set_math_tail_from_capture: % \end{macrocode} % |default| 的开始 hook 已经固定首类别;这里仅覆写末类别即可让 % stream 两端都为 Default,同时不丢弃其他模式实际观察到的首类别。 % \begin{macrocode} \str_if_eq:nnT {#1} { default } { \tl_set:Nn \l_@@_boundary_last_tl {#1} } \tl_gclear:N \g_@@_last_node_tl \bool_gset_false:N \g_@@_glue_check_pending_bool \tl_if_empty:NTF \l_@@_boundary_first_tl { \@@_boundary_replay_before: } { \tl_if_empty:NT \l_@@_boundary_last_tl { \tl_set_eq:NN \l_@@_boundary_last_tl \l_@@_boundary_first_tl } \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_last_tl } \int_gdecr:N \g_@@_boundary_capture_depth_int } } \cs_new_protected:Npn \@@_boundary_hmode_transparent_begin: { \int_compare:nNnTF { \g_@@_boundary_suspend_depth_int } > \c_zero_int { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } { \mode_if_horizontal:TF { \seq_gpush:Nn \g_@@_boundary_active_seq { true } \@@_boundary_capture_begin: \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _kind_tl } { transparent } } { \seq_gpush:Nn \g_@@_boundary_active_seq { false } } } } \cs_new_protected:Npn \@@_boundary_hmode_transparent_end: { \seq_gpop:NN \g_@@_boundary_active_seq \l_@@_boundary_active_tl \tl_if_eq:NnT \l_@@_boundary_active_tl { true } { \@@_boundary_replay_before: \int_gdecr:N \g_@@_boundary_capture_depth_int } } \cs_new_protected:Npn \@@_boundary_post_transparent: { \mode_if_horizontal:T { \tl_if_empty:NF \g_@@_last_node_tl { \@@_if_last_hlist:TF { \box_set_to_last:N \l_@@_boundary_post_box \dim_compare:nNnTF { \box_wd:N \l_@@_boundary_post_box + \box_ht:N \l_@@_boundary_post_box + \box_dp:N \l_@@_boundary_post_box } = { \c_zero_dim } { \@@_boundary_post_transparent_relocate: } { \box_use_drop:N \l_@@_boundary_post_box } } { \xeCJK_if_last_node:TF { \exp_args:NV \@@_boundary_replay_node:n \g_@@_last_node_tl } { } } } } } % \end{macrocode} % \changes{v3.10.6}{2026/08/20}{post-transparent 搬移「marker + 候选 glue」 % 后缀前,先用 \cs{skip_if_finite:nTF} 排除 \tn{hfill}/\tn{hfil} 一类 % 无限阶填充 glue,使它们不再被误搬到零尺寸盒子之后, % 修复 CJK 文字紧接源码空格与 \tn{hfill}\tn{null} 时两侧填充不对称 % (\#1085)。} % 零尺寸盒子前可能不只有 marker,还可能有一枚等待 Boundary 恢复链检查的 % glue。第一层先处理盒子直接盖住 marker 的情况;未命中时才暂存至多一枚 % 末尾 glue,再按节点证据检查其下方的 marker。命中后把“marker + glue”整体 % 移到盒子后面,使后续转换看到与没有该盒子时相同的列表末尾。未命中 marker % 时按原顺序还原,不能把任意 glue 搬过 hbox。普通 stream 的 |math-space| % 还有一枚真实空格留在 marker 前;零尺寸盒子已经把二者隔开时,应与含同一 % 盒子的直接公式一样让 marker 过期,不能只把补偿状态搬到盒子后面。 % 只有有限阶的候选 glue 才可能是被盒子遮住的源码空格或参数内空格;\tn{hfill} % 等无限阶(fil/fill)填充 glue 不是候选,保持 marker、glue、盒子的原有次序 % (\#1085,见 \cs{@@_boundary_post_transparent_relocate_glue:})。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_post_transparent_relocate: { \@@_boundary_pop_node:N \l_@@_boundary_node_tl \tl_if_empty:NTF \l_@@_boundary_node_tl { \@@_if_last_glue:TF { \@@_boundary_post_transparent_relocate_glue: } { \box_use_drop:N \l_@@_boundary_post_box } } { % \end{macrocode} % 盒子直接盖住 marker,中间没有 glue。 % \begin{macrocode} \box_use_drop:N \l_@@_boundary_post_box \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space } { \@@_boundary_math_space_expire: } { \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_node_tl } } } \cs_new_protected:Npn \@@_boundary_post_transparent_relocate_glue: { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \skip_if_finite:nTF \l_@@_last_skip { \tex_unskip:D \@@_boundary_pop_node:N \l_@@_boundary_node_tl \tl_if_empty:NTF \l_@@_boundary_node_tl { \skip_horizontal:N \l_@@_last_skip \box_use_drop:N \l_@@_boundary_post_box } { \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space } { % \end{macrocode} % |math-space| 过期时不再移动后缀;先恢复候选 glue,再放回 % 零尺寸盒子,保持与含同一盒子的直接公式相同的节点顺序。 % \begin{macrocode} \skip_horizontal:N \l_@@_last_skip \box_use_drop:N \l_@@_boundary_post_box \@@_boundary_math_space_expire: } { \box_use_drop:N \l_@@_boundary_post_box \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_node_tl \skip_horizontal:N \l_@@_last_skip } } } % \end{macrocode} % 只有有限阶(finite)的候选 glue 才可能是被零尺寸盒子遮住的源码空格或 % 参数内空格(后者宽度不一定等于词间空格、也不一定带 shrink,例如 % |math-space| 场景里的显式 \tn{hskip}),因此这里只排除 % \tn{hfill}/\tn{hfil} 一类无限阶(fil/fill)填充 glue,不能收紧成 % \cs{@@_skip_if_interword:N}(它要求带 shrink 且宽度等于词间空格,会误伤 % math-space 的显式 glue)。无限阶 glue 不是任何源码空格候选:不移动它, % 直接把零尺寸盒子放回末尾,保持 marker、glue、盒子的原有次序。否则会把 % \tn{null} 前的填充 glue 误搬到盒子之后,令 \tn{null} 不在列表末尾,破坏 % 两侧 \tn{hfill} 的对称(\#1085)。 % \begin{macrocode} { \box_use_drop:N \l_@@_boundary_post_box } } \cs_new_protected:Npn \@@_boundary_capture_begin: { \int_gincr:N \g_@@_boundary_capture_depth_int \int_compare:nNnT { \g_@@_boundary_capture_depth_int } > { 2 } { \exp_args:Ne \@@_boundary_capture_allocate:n { \int_use:N \g_@@_boundary_capture_depth_int } } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _before_tl } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _before_math_space_skip_tl } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _first_tl } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _last_tl } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _kind_tl } % \end{macrocode} % 先把用户定义的 glue 实际排入临时盒并读取为 skip;后面的 e-type 只把 % 已求值的 skip 数值序列化到本层 tl,不展开原始 \CJKglue/\CJKecglue 定义。 % \begin{macrocode} \xeCJK_glue_to_skip:nN { \tex_kern:D \c_zero_dim \xeCJK_space_glue: } \l_@@_tmp_skip \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_skip_tl } { \skip_use:N \l_@@_tmp_skip } \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_tmp_skip \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _ecglue_skip_tl } { \skip_use:N \l_@@_tmp_skip } \xeCJK_glue_to_skip:nN { \CJKglue } \l_@@_tmp_skip \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _ccglue_skip_tl } { \skip_use:N \l_@@_tmp_skip } \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_flag_tl } { false } \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _reserve_space_flag_tl } { \bool_if:NTF \l_@@_reserve_space_bool { true } { false } } \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _xecglue_flag_tl } { \bool_if:NTF \l_@@_xecglue_bool { true } { false } } \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _math_tail_flag_tl } { false } \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_flag_tl } { false } \tl_gclear:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_skip_tl } \@@_boundary_capture_space: \scan_stop: \@@_boundary_pop_node:N \l_@@_boundary_node_tl \tl_if_eq:NnT \l_@@_boundary_node_tl { math-space } { \@@_if_last_glue:TF { \tl_gset:ce { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _before_math_space_skip_tl } { \skip_use:N \tex_lastskip:D } } { \tl_gset:ce { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _before_math_space_skip_tl } { \skip_use:N \l_@@_boundary_math_actual_skip } } } \tl_if_eq:NnT \l_@@_boundary_node_tl { CJK-space } { \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_flag_tl } { true } } \tl_if_empty:NT \l_@@_boundary_node_tl { \tl_if_eq:cnT { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_flag_tl } { true } { \@@_boundary_restore_space: \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_flag_tl } { false } } } \exp_args:Nc \tl_gset_eq:NN { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _before_tl } \l_@@_boundary_node_tl \tl_gclear:N \g_@@_last_node_tl \bool_gset_false:N \g_@@_glue_check_pending_bool } \cs_new_protected:Npn \@@_boundary_box_begin: { \@@_boundary_capture_begin: \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _kind_tl } { box } \exp_args:Nc \@@_boundary_box_open:N { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _box } } \cs_new_protected:Npn \@@_boundary_box_open:N #1 { \tex_global:D \tex_setbox:D #1 \tex_hbox:D \c_group_begin_token } \cs_new_protected:Npn \@@_boundary_capture_space: { \@@_if_last_glue:T { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \@@_skip_if_interword:NT \l_@@_last_skip { \tl_gset:ce { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_skip_tl } { \skip_use:N \l_@@_last_skip } \tl_gset:cn { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _space_flag_tl } { true } \tex_unskip:D } } } \cs_new_protected:Npn \@@_boundary_pop_node:N #1 { \tl_clear:N #1 \xeCJK_if_last_node:TF { \dim_case:nnF { \l_@@_last_kern_dim } { { \@@_node:n { CJK } } { \tl_set:Nn #1 { CJK } } { \@@_node:n { CJK-space } } { \tl_set:Nn #1 { CJK-space } } { \@@_node:n { CJK-widow } } { \tl_set:Nn #1 { CJK-widow } } { \@@_node:n { default } } { \tl_set:Nn #1 { default } } { \@@_node:n { math } } { \tl_set:Nn #1 { math } } { \@@_node:n { math-space } } { \tl_set:Nn #1 { math-space } \@@_boundary_math_space_decode: } { \@@_node:n { math-space-frozen } } { \tl_set:Nn #1 { math-space-frozen } } { \@@_node:n { normalspace } } { \tl_set:Nn #1 { normalspace } } } { \@@_make_node:N \l_@@_last_kern_dim } } { \xeCJK_if_last_node:nT { default-space } { \xeCJK_remove_node: \tl_set:Nn #1 { default-space } } } } \prg_new_conditional:Npnn \@@_boundary_if_last_math_space: { T , F , TF } { \xeCJK_if_last_node:nTF { math-space } { \prg_return_true: } { \xeCJK_if_last_node:nTF { math-space-frozen } { \prg_return_true: } { \prg_return_false: } } } \cs_new_protected:Npn \@@_boundary_math_space_consume: { \xeCJK_if_last_node:nTF { math-space } { \xeCJK_remove_node: \@@_boundary_math_space_decode: } { \xeCJK_remove_node: } \bool_gset_false:N \g_@@_glue_check_pending_bool } % \end{macrocode} % |math-space| 后面的两对零净宽 kern 依次保存普通 stream 空格的伸长量 % 和收缩量。marker 位于最外侧,移除 marker 后按相反次序取回两对 kern。 % 这里只保存有限阶词间空格的弹性分量;字体的普通词间空格不会使用 fil 阶。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_math_space_set_actual:n #1 { \skip_set:Nn \l_@@_boundary_math_actual_skip {#1} \dim_set:Nn \l_@@_boundary_math_stretch_dim { \tex_gluestretch:D \l_@@_boundary_math_actual_skip } \dim_set:Nn \l_@@_boundary_math_shrink_dim { \tex_glueshrink:D \l_@@_boundary_math_actual_skip } \skip_set:Nn \l_@@_boundary_math_actual_skip { \c_zero_dim plus \l_@@_boundary_math_stretch_dim minus \l_@@_boundary_math_shrink_dim } } \cs_new_protected:Npn \@@_boundary_math_space_encode: { \dim_set:Nn \l_@@_boundary_math_stretch_dim { \tex_gluestretch:D \l_@@_boundary_math_actual_skip } \dim_set:Nn \l_@@_boundary_math_shrink_dim { \tex_glueshrink:D \l_@@_boundary_math_actual_skip } \@@_make_node:N \l_@@_boundary_math_stretch_dim \@@_make_node:N \l_@@_boundary_math_shrink_dim } \cs_new_protected:Npn \@@_boundary_math_space_decode: { \dim_zero:N \l_@@_boundary_math_stretch_dim \dim_zero:N \l_@@_boundary_math_shrink_dim \xeCJK_if_last_node:TF { \dim_set_eq:NN \l_@@_boundary_math_shrink_dim \l_@@_last_kern_dim \xeCJK_if_last_node:TF { \dim_set_eq:NN \l_@@_boundary_math_stretch_dim \l_@@_last_kern_dim } { } } { } \skip_set:Nn \l_@@_boundary_math_actual_skip { \c_zero_dim plus \l_@@_boundary_math_stretch_dim minus \l_@@_boundary_math_shrink_dim } } % \end{macrocode} % 普通 stream 的参数内空格仍在外层列表:自然差额按入口字体的词间空格 % 计算,伸缩差额则扣除参数内实际空格的伸缩量。box、wrapped-box 和 % stream-ulem 已把空格冻结在内部盒子中;它的伸缩分量不再参与外层断行, % 外层只能扣除词间空格的自然宽度,并完整保留 |CJKecglue| 自己的伸缩分量。 % 若目标自然宽度更小,则把外层自然差额截为零,不能用负 glue 把后续文字 % 拉回框线或装饰范围。命令内外字体不同时,内部空格的宽度差仍属于命令 % 自身的固有宽度。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_math_space_set_delta:n #1 { \str_if_eq:nnTF {#1} { math-space-frozen } { \dim_set:Nn \l_@@_boundary_math_space_dim { \l_@@_boundary_math_skip } \dim_set:Nn \l_@@_boundary_math_ecglue_dim { \l_@@_boundary_math_ecglue_skip } \dim_set:Nn \l_@@_tmp_dim { \dim_max:nn { \l_@@_boundary_math_ecglue_dim - \l_@@_boundary_math_space_dim } { \c_zero_dim } } \skip_set:Nn \l_@@_boundary_math_delta_skip { \l_@@_boundary_math_ecglue_skip - \l_@@_boundary_math_ecglue_dim + \l_@@_tmp_dim } } { \dim_set:Nn \l_@@_boundary_math_space_dim { \l_@@_boundary_math_skip } \dim_set:Nn \l_@@_boundary_math_ecglue_dim { \l_@@_boundary_math_ecglue_skip } \dim_set:Nn \l_@@_tmp_dim { \l_@@_boundary_math_ecglue_dim - \l_@@_boundary_math_space_dim } \skip_set:Nn \l_@@_boundary_math_delta_skip { \l_@@_boundary_math_ecglue_skip - \l_@@_boundary_math_ecglue_dim + \l_@@_tmp_dim - \l_@@_boundary_math_actual_skip } } } \cs_new_protected:Npn \@@_boundary_math_space_to_CJK: { \xeCJK_if_last_node:nTF { math-space-frozen } { \tl_set:Nn \l_@@_boundary_math_type_tl { math-space-frozen } } { \tl_set:Nn \l_@@_boundary_math_type_tl { math-space } } \@@_boundary_math_space_consume: \bool_if:NT \l_@@_xecglue_bool { \skip_set:Nn \l_@@_boundary_math_skip { \c_xeCJK_space_skip_tl } \skip_set_eq:NN \l_@@_boundary_math_ecglue_skip \l_@@_ecglue_skip \exp_args:NV \@@_boundary_math_space_set_delta:n \l_@@_boundary_math_type_tl \skip_if_eq:nnF { \l_@@_boundary_math_delta_skip } { \c_zero_skip } { \skip_horizontal:N \l_@@_boundary_math_delta_skip } } } \cs_new_protected:Npn \@@_boundary_box_end:n #1 { \c_group_end_token \@@_boundary_box_finish:nn {#1} { wrapped } } \cs_new_protected:Npn \@@_boundary_last_box_end:n #1 { \@@_if_last_hlist:TF { \exp_args:Nc \box_set_to_last:N { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _box } \@@_boundary_box_finish:nn {#1} { last } } { \tl_gclear:N \g_@@_last_node_tl \bool_gset_false:N \g_@@_glue_check_pending_bool \@@_boundary_replay_before: \int_gdecr:N \g_@@_boundary_capture_depth_int } } \int_new:N \g_@@_boundary_probe_type_int \box_new:N \l_@@_boundary_probe_box \tl_new:N \l_@@_boundary_probe_name_tl \prg_new_protected_conditional:Npnn \@@_boundary_if_capture_box_sized: { T , F , TF } { \tl_set:Ne \l_@@_boundary_probe_name_tl { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _box } \bool_lazy_and:nnTF { ! \dim_compare_p:nNn { \box_wd:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim } { \bool_lazy_or_p:nn { ! \dim_compare_p:nNn { \box_ht:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim } { ! \dim_compare_p:nNn { \box_dp:c { \l_@@_boundary_probe_name_tl } } = \c_zero_dim } } { \prg_return_true: } { \prg_return_false: } } \prg_new_protected_conditional:Npnn \@@_boundary_if_capture_box_visible: { T , F , TF } { \@@_boundary_if_capture_box_sized:TF { % \end{macrocode} % 只复制节点,不重扫 token,无排版副作用。 % \begin{macrocode} \hbox_set:Nn \l_@@_boundary_probe_box { \hbox_unpack:c { \l_@@_boundary_probe_name_tl } \int_gset:Nn \g_@@_boundary_probe_type_int { \tex_lastnodetype:D } } \box_clear:N \l_@@_boundary_probe_box % \end{macrocode} % \tn{lastnodetype} 编码沿用 eTeX 手册的节点类型表(0 传统 char、 % 1 hlist、3 rule、9 whatsit、10 math、11 glue、12 kern); % \XeTeX\ 把 native 文本实现为 word 节点、同样报 9,但它必然已被 % interchar 观察,不会进入这里。这里把 char、rule、math,以及 % 未上报标点类字符后由标点机制添加的 kern 视为盒子直接排出的内容。 % \begin{macrocode} \int_case:nnTF { \g_@@_boundary_probe_type_int } { { 0 } { } { 3 } { } { 10 } { } { 12 } { } } { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } \tl_new:N \g_@@_boundary_probe_last_tl \cs_new_protected:Npn \@@_boundary_box_set_last_from_node: { \tl_set:Ne \l_@@_boundary_probe_name_tl { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _box } \tl_gclear:N \g_@@_boundary_probe_last_tl \hbox_set:Nn \l_@@_boundary_probe_box { \hbox_unpack:c { \l_@@_boundary_probe_name_tl } \@@_boundary_pop_node:N \l_@@_boundary_node_tl \tl_if_eq:NnT \l_@@_boundary_node_tl { math-space } { \tl_set:Nn \l_@@_boundary_node_tl { math-space-frozen } } \tl_gset_eq:NN \g_@@_boundary_probe_last_tl \l_@@_boundary_node_tl } \box_clear:N \l_@@_boundary_probe_box \exp_args:NV \@@_boundary_set_last_from_node:n \g_@@_boundary_probe_last_tl } \cs_new_protected:Npn \@@_boundary_box_finish:nn #1#2 { \tl_set:Ne \l_@@_boundary_first_tl { \tl_use:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _first_tl } } \tl_set:Ne \l_@@_boundary_last_tl { \tl_use:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _last_tl } } \tl_gclear:N \g_@@_last_node_tl \bool_gset_false:N \g_@@_glue_check_pending_bool % \end{macrocode} % 嵌套 box 结束时写入的 marker 会留在当前盒子的节点列表末尾。 % 读取这个 marker 可以更新本层的末类别,使“前面已经观察到 CJK,末尾 % 是推断为 Default 的内容”等情况逐层传到外面;不能把命令内部辅助盒子的 % 推断直接写进所有外层 capture。 % 即使 capture 已记录末类别也不能跳过这一步:内部不可见的定界字符可能 % 使观察值不准,此时应以节点列表末尾的 marker 为准。 % \begin{macrocode} \@@_boundary_box_set_last_from_node: \@@_boundary_set_math_tail_from_capture: % \end{macrocode} % 盒子捕获没有独立的 stream 入口,故 |default| 在这里同时覆写实际观察到 % 的首尾类别;stream 路径则由开始 hook 固定首类别、结束 hook 固定 % 末类别,两条路径的公开语义相同。 % \begin{macrocode} \str_if_eq:nnT {#1} { default } { \tl_set:Nn \l_@@_boundary_first_tl { default } \tl_set:Nn \l_@@_boundary_last_tl { default } } % \end{macrocode} % rule 和尚未由公式观察器记录的 math 不触发 interchar 转换。若盒子 % 直接排出了可见内容 % (宽度非零、高度或深度非零,且末节点为 char/rule/math/kern), % 却没有观察到任何类别,就按 Default 首尾重建边界。空 makebox、strut % 等只用于留白的盒子,以及放入另一个已经排好盒子的命令,仍按无可见 % 输出处理(issue 998)。 % 只把 default 报告给尚未取得首类别的外层 capture,使 \tn{nfss@text} % 这类嵌在 stream 内的盒子命令与裸调用等价;各层盒子结束时会读取 % 节点列表末尾由本层写入的 marker,并据此更新末类别。 % \begin{macrocode} \tl_if_empty:NT \l_@@_boundary_first_tl { \@@_boundary_if_capture_box_visible:T { \tl_set:Nn \l_@@_boundary_first_tl { default } \tl_set:Nn \l_@@_boundary_last_tl { default } \@@_boundary_capture_report_first:n { default } } } \tl_if_empty:NTF \l_@@_boundary_first_tl { \@@_boundary_box_end_transparent:n {#2} } { \tl_set:Ne \l_@@_boundary_node_tl { \tl_use:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _before_tl } } \exp_args:NVV \@@_boundary_emit_left:nn \l_@@_boundary_node_tl \l_@@_boundary_first_tl \box_use_drop:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _box } \tl_if_empty:NT \l_@@_boundary_last_tl { \tl_set_eq:NN \l_@@_boundary_last_tl \l_@@_boundary_first_tl } \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_last_tl } \int_gdecr:N \g_@@_boundary_capture_depth_int } \cs_new_protected:Npn \@@_boundary_box_end_transparent:n #1 { \str_if_eq:nnTF {#1} { wrapped } { \hbox_unpack_drop:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _box } } { \box_use_drop:c { g_@@_boundary_capture_\int_use:N \g_@@_boundary_capture_depth_int _box } } \@@_boundary_replay_before: } \cs_new_protected:Npn \@@_boundary_replay_before: { \tl_set:Ne \l_@@_boundary_node_tl { \tl_use:c { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _before_tl } } \clist_if_in:nVTF { math-space , math-space-frozen } \l_@@_boundary_node_tl { \tl_if_eq:NnTF \l_@@_boundary_node_tl { math-space } { \@@_boundary_replay_before_math_space: } { \@@_boundary_replay_node:n { math-space-frozen } } } { \exp_args:NV \@@_boundary_replay_node:n \l_@@_boundary_node_tl \@@_boundary_restore_space: } } % \end{macrocode} % 普通 stream 的 |math-space| 只在真实参数空格仍紧邻列表末尾时重放。 % transparent 命令若已经在二者之间写入 special、零尺寸盒子等节点,直接 % 公式的源码空格同样不会越过该节点触发恢复;此时必须让 marker 过期, % 不能把补偿 glue 单独移到不可见节点之后。skip 比较把判断限制在原来那枚 % 真实空格,不把 transparent 命令偶然写出的不同规格 glue 当作边界后缀。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_replay_before_math_space: { \@@_if_last_glue:T { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \skip_if_eq:nnT { \l_@@_last_skip } { \tl_use:c { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _before_math_space_skip_tl } } { \exp_args:Ne \@@_boundary_math_space_set_actual:n { \tl_use:c { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _before_math_space_skip_tl } } \@@_boundary_replay_node:n { math-space } } } } \cs_new_protected:Npn \@@_boundary_math_space_expire: { \tl_gclear:N \g_@@_last_node_tl \bool_gset_false:N \g_@@_glue_check_pending_bool } \cs_new_protected:Npn \@@_boundary_emit_left:nn #1#2 { \@@_boundary_emit_left:nnn { \int_use:N \g_@@_boundary_capture_depth_int } {#1} {#2} } \cs_new_protected:Npn \@@_boundary_emit_left:nnn #1#2#3 { \clist_if_in:nnTF { math-space , math-space-frozen } {#2} { \@@_boundary_emit_left_math_space:nnn {#1} {#2} {#3} } { \@@_boundary_emit_left_normal:nnn {#1} {#2} {#3} } } \cs_new_protected:Npn \@@_boundary_emit_left_math_space:nnn #1#2#3 { \str_if_eq:nnT {#3} { CJK } { \tl_if_eq:cnT { g_@@_boundary_capture_#1_xecglue_flag_tl } { true } { \str_if_eq:nnT {#2} { math-space } { \exp_args:Ne \@@_boundary_math_space_set_actual:n { \tl_use:c { g_@@_boundary_capture_#1_before_math_space_skip_tl } } } \skip_set:Nn \l_@@_boundary_math_skip { \tl_use:c { g_@@_boundary_capture_#1_space_skip_tl } } \skip_set:Nn \l_@@_boundary_math_ecglue_skip { \tl_use:c { g_@@_boundary_capture_#1_ecglue_skip_tl } } \@@_boundary_math_space_set_delta:n {#2} \skip_if_eq:nnF { \l_@@_boundary_math_delta_skip } { \c_zero_skip } { \@@_boundary_use_glue:nn {#1} { \l_@@_boundary_math_delta_skip } } } } } \cs_new_protected:Npn \@@_boundary_emit_left_normal:nnn #1#2#3 { \clist_if_in:nnTF { CJK , CJK-space , CJK-widow } {#2} { \str_if_eq:nnTF {#3} { math } { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_space_flag_tl } { true } { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_xecglue_flag_tl } { true } { \@@_boundary_use_ecglue:n {#1} } { \@@_boundary_restore_space:n {#1} } } { \@@_boundary_use_ecglue:n {#1} } } { \str_if_eq:nnTF {#3} { CJK } { \str_if_eq:nnT {#2} { CJK-widow } { \xeCJK_widow_penalty: } \tl_if_eq:cnTF { g_@@_boundary_capture_#1_space_flag_tl } { true } { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_reserve_space_flag_tl } { true } { \@@_boundary_restore_space:n {#1} } { \@@_boundary_use_ccglue:n {#1} } } { \@@_boundary_use_ccglue:n {#1} } } { \@@_boundary_use_ecglue:n {#1} } } } { \clist_if_in:nnTF { default , default-space , math } {#2} { \str_if_eq:nnTF {#3} { CJK } { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_space_flag_tl } { true } { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_xecglue_flag_tl } { true } { \@@_boundary_use_ecglue:n {#1} } { \@@_boundary_restore_space:n {#1} } } { \@@_boundary_use_ecglue:n {#1} } } { \@@_boundary_restore_space:n {#1} } } { \@@_boundary_restore_space:n {#1} } } } \cs_generate_variant:Nn \@@_boundary_emit_left:nnn { nVn } \cs_new_protected:Npn \@@_boundary_use_ecglue:n #1 { \@@_boundary_use_glue:nn {#1} { \tl_use:c { g_@@_boundary_capture_#1_ecglue_skip_tl } } } \cs_new_protected:Npn \@@_boundary_use_ccglue:n #1 { \@@_boundary_use_glue:nn {#1} { \tl_use:c { g_@@_boundary_capture_#1_ccglue_skip_tl } } } \cs_new_protected:Npn \@@_boundary_use_glue:nn #1#2 { \tl_if_eq:cnTF { g_@@_boundary_capture_#1_kind_tl } { stream-ulem } { \use:c { @@_boundary_use_ulem_glue:n } {#2} } { \skip_horizontal:n {#2} } } \cs_new_protected:Npn \@@_boundary_restore_space: { \@@_boundary_restore_space:n { \int_use:N \g_@@_boundary_capture_depth_int } } \cs_new_protected:Npn \@@_boundary_restore_space:n #1 { \tl_if_eq:cnT { g_@@_boundary_capture_#1_space_flag_tl } { true } { \@@_boundary_use_glue:nn {#1} { \tl_use:c { g_@@_boundary_capture_#1_space_skip_tl } } } } \cs_new_protected:Npn \@@_boundary_set_last_from_node:n #1 { \clist_if_in:nnTF { CJK , CJK-space , CJK-widow } {#1} { \tl_set:Nn \l_@@_boundary_last_tl { CJK } } { \clist_if_in:nnT { default , default-space } {#1} { \tl_set:Nn \l_@@_boundary_last_tl { default } } \str_if_eq:nnT {#1} { math } { \tl_set:Nn \l_@@_boundary_last_tl { math } } \str_if_eq:nnT {#1} { math-space } { \tl_set:Nn \l_@@_boundary_last_tl { math-space } } \str_if_eq:nnT {#1} { math-space-frozen } { \tl_set:Nn \l_@@_boundary_last_tl { math-space-frozen } } } } \cs_new_protected:Npn \@@_boundary_set_math_tail_from_capture: { \tl_if_eq:cnT { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_flag_tl } { true } { \tl_if_eq:cnTF { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_flag_tl } { true } { \tl_if_eq:cnTF { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _kind_tl } { stream } { \tl_set:Nn \l_@@_boundary_last_tl { math-space } \exp_args:Ne \@@_boundary_math_space_set_actual:n { \tl_use:c { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _math_tail_space_skip_tl } } } { \tl_set:Nn \l_@@_boundary_last_tl { math-space-frozen } } } { \tl_set:Nn \l_@@_boundary_last_tl { math } } } } % \end{macrocode} % Default 字符在盒子内部更新的 spacefactor 不会传播到外层列表;stream % 内容则会直接更新外层值。重放 Default 类 marker 时,以当前列表真正的 % spacefactor 同步源码空格检查上下文,避免把盒内末尾大写字母留下的 999 % 错用于盒外以 1000 生成的源码空格。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_replay_node:n #1 { \clist_if_in:nnT { default , default-space , math , math-space , math-space-frozen , normalspace } {#1} { \int_gset_eq:NN \g_@@_space_factor_int \tex_spacefactor:D } \str_if_eq:nnTF {#1} { math-space } { \@@_boundary_math_space_encode: \xeCJK_make_node:n {#1} \@@_boundary_enable_source_space:n {#1} } { \str_if_eq:nnTF {#1} { math-space-frozen } { \xeCJK_make_node:n {#1} \@@_boundary_enable_source_space:n {#1} } { \str_if_eq:nnTF {#1} { default-space } { \@@_make_space_node: \tl_gset:Nn \g_@@_last_node_tl { default } } { \clist_if_in:nnT { CJK , CJK-space , CJK-widow , default , math , normalspace } {#1} { \xeCJK_make_node:n {#1} \@@_boundary_enable_source_space:n {#1} } } } } } \cs_new_protected:Npn \@@_boundary_enable_source_space:n #1 { \clist_if_in:nnT { CJK , CJK-space , CJK-widow , math , math-space , math-space-frozen } {#1} { \bool_gset_true:N \g_@@_glue_check_pending_bool } } % \end{macrocode} % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_box:nn #1#2 { \prop_if_in:NnF \g_@@_boundary_registered_prop {#1} { \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} {#2} \AddToHook { cmd / #1 / before } [ xeCJK / boundary ] { \@@_boundary_inline_last_box_begin: } \AddToHook { cmd / #1 / after } [ xeCJK / boundary ] { \@@_boundary_inline_last_box_end:n {#2} } } } \cs_new_protected:Npn \@@_boundary_register_wrapped_box:nn #1#2 { \prop_if_in:NnF \g_@@_boundary_registered_prop {#1} { \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} { wrapped-box/#2 } \AddToHook { cmd / #1 / before } [ xeCJK / boundary ] { \@@_boundary_inline_box_begin: } \AddToHook { cmd / #1 / after } [ xeCJK / boundary ] { \@@_boundary_inline_box_end:n {#2} } } } \cs_new_protected:Npn \@@_boundary_register_transparent:n #1 { \prop_if_in:NnF \g_@@_boundary_registered_prop {#1} { \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} { transparent } \AddToHook { cmd / #1 / before } [ xeCJK / boundary ] { \@@_boundary_hmode_transparent_begin: } \AddToHook { cmd / #1 / after } [ xeCJK / boundary ] { \@@_boundary_hmode_transparent_end: } } } \cs_new_protected:Npn \@@_boundary_register_post_transparent:n #1 { \prop_if_in:NnF \g_@@_boundary_registered_prop {#1} { \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} { post-transparent } \AddToHook { cmd / #1 / after } [ xeCJK / boundary ] { \@@_boundary_post_transparent: } } } \cs_new_protected:Npn \@@_boundary_register_stream:nn #1#2 { \prop_if_in:NnF \g_@@_boundary_registered_prop {#1} { \prop_gput:Nnn \g_@@_boundary_registered_prop {#1} { stream/#2 } \AddToHook { cmd / #1 / before } [ xeCJK / boundary ] { \@@_boundary_inline_stream_begin:n {#2} } \AddToHook { cmd / #1 / after } [ xeCJK / boundary ] { \@@_boundary_inline_stream_end:n {#2} } } } % \end{macrocode} % 通用注册函数把命令写入 \cs{g_@@_boundary_registered_prop}。另有一些 % 命令必须直接重定义内部排版入口,不能使用 LaTeX 通用命令 hook;这些 % 入口写入独立的保留表。用户接口同时查询两张表,防止把通用 hook 叠加到 % 已有专用适配器外层。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_reserve:n #1 { \prop_gput:Nnn \g_@@_boundary_reserved_prop {#1} { adapter } } \cs_new_protected:Npn \@@_boundary_reserve:N #1 { \exp_args:Ne \@@_boundary_reserve:n { \cs_to_str:N #1 } } \prg_new_conditional:Npnn \@@_boundary_if_registered:n #1 { T , F , TF } { \prop_if_in:NnTF \g_@@_boundary_registered_prop {#1} { \prg_return_true: } { \prop_if_in:NnTF \g_@@_boundary_reserved_prop {#1} { \prg_return_true: } { \prg_return_false: } } } % \end{macrocode} % \begin{macrocode} \@@_msg_new:nn { boundary-register-command } { The~value~of~`command'~in~`experiment/boundary-register'~must~be~ exactly~one~control~sequence. } \@@_msg_new:nn { boundary-register-choice } { Invalid~boundary~registration~choice:~#1. } \@@_msg_new:nn { boundary-register-missing } { The~`#1'~entry~is~required~in~`experiment/boundary-register'. } \@@_msg_new:nn { boundary-register-mode } { The~boundary~registration~strategy~and~mode~cannot~be~combined:~#1. } \@@_msg_new:nn { boundary-register-duplicate } { The~command~`#1'~has~already~been~declared~for~boundary~registration. } \@@_msg_new:nn { boundary-register-conflict } { The~command~`#1'~already~has~an~xeCJK~boundary~registration. } \@@_msg_new:nn { boundary-register-late } { The~`experiment/boundary-register'~option~can~only~be~used~before~ the~end~of~the~preamble. } \@@_msg_new:nn { boundary-register-undefined } { The~registered~boundary~command~`#1'~is~still~undefined. } \cs_new_protected:Npn \@@_boundary_user_register:n #1 { \bool_if:NTF \g_@@_boundary_user_register_closed_bool { \@@_error:n { boundary-register-late } } { \bool_set_false:N \l_@@_boundary_user_invalid_bool \bool_set_false:N \l_@@_boundary_user_mode_given_bool \tl_clear:N \l_@@_boundary_user_name_tl \tl_clear:N \l_@@_boundary_user_strategy_tl \tl_set:Nn \l_@@_boundary_user_mode_tl { auto } \keys_set:nn { xeCJK / boundary-register } {#1} \tl_if_empty:NT \l_@@_boundary_user_name_tl { \@@_error:ne { boundary-register-missing } { command } \bool_set_true:N \l_@@_boundary_user_invalid_bool } \tl_if_empty:NT \l_@@_boundary_user_strategy_tl { \@@_error:ne { boundary-register-missing } { strategy } \bool_set_true:N \l_@@_boundary_user_invalid_bool } \bool_if:NF \l_@@_boundary_user_invalid_bool { \@@_boundary_user_register_validate: } \bool_if:NF \l_@@_boundary_user_invalid_bool { \@@_boundary_user_register_store: } } } \cs_new_protected:Npn \@@_boundary_user_register_command:n #1 { \tl_if_single_token:nTF {#1} { \token_if_cs:NTF #1 { \tl_set:Ne \l_@@_boundary_user_name_tl { \cs_to_str:N #1 } } { \@@_error:n { boundary-register-command } \tl_set:Nn \l_@@_boundary_user_name_tl { invalid } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } { \@@_error:n { boundary-register-command } \tl_set:Nn \l_@@_boundary_user_name_tl { invalid } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } \cs_new_protected:Npn \@@_boundary_user_register_strategy:n #1 { \str_case:nnF {#1} { { box } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} } { wrapped-box } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} } { stream } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} } { transparent } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} } { post-transparent } { \tl_set:Nn \l_@@_boundary_user_strategy_tl {#1} } } { \@@_error:ne { boundary-register-choice } { strategy~=~\tl_to_str:n {#1} } \tl_set:Nn \l_@@_boundary_user_strategy_tl { invalid } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } \cs_new_protected:Npn \@@_boundary_user_register_mode:n #1 { \bool_set_true:N \l_@@_boundary_user_mode_given_bool \str_case:nnF {#1} { { auto } { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} } { default } { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} } { first-default } { \tl_set:Nn \l_@@_boundary_user_mode_tl {#1} } } { \@@_error:ne { boundary-register-choice } { mode~=~\tl_to_str:n {#1} } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } \cs_new_protected:Npn \@@_boundary_user_register_validate: { \str_case:Vn \l_@@_boundary_user_strategy_tl { { box } { \tl_if_eq:NnT \l_@@_boundary_user_mode_tl { first-default } { \@@_error:ne { boundary-register-mode } { box/first-default } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } { wrapped-box } { \tl_if_eq:NnT \l_@@_boundary_user_mode_tl { first-default } { \@@_error:ne { boundary-register-mode } { wrapped-box/first-default } \bool_set_true:N \l_@@_boundary_user_invalid_bool } } { stream } { } { transparent } { \bool_if:NT \l_@@_boundary_user_mode_given_bool { \@@_error:ne { boundary-register-mode } { transparent/\l_@@_boundary_user_mode_tl } \bool_set_true:N \l_@@_boundary_user_invalid_bool } \tl_set:Nn \l_@@_boundary_user_mode_tl { none } } { post-transparent } { \bool_if:NT \l_@@_boundary_user_mode_given_bool { \@@_error:ne { boundary-register-mode } { post-transparent/\l_@@_boundary_user_mode_tl } \bool_set_true:N \l_@@_boundary_user_invalid_bool } \tl_set:Nn \l_@@_boundary_user_mode_tl { none } } } } \cs_new_protected:Npn \@@_boundary_user_register_store: { \exp_args:NV \@@_boundary_if_registered:nTF \l_@@_boundary_user_name_tl { \@@_error:ne { boundary-register-conflict } { \c_backslash_str \l_@@_boundary_user_name_tl } } { \exp_args:NNV \prop_if_in:NnTF \g_@@_boundary_user_registered_prop \l_@@_boundary_user_name_tl { \@@_error:ne { boundary-register-duplicate } { \c_backslash_str \l_@@_boundary_user_name_tl } } { \tl_set:Ne \l_@@_boundary_user_value_tl { \l_@@_boundary_user_strategy_tl / \l_@@_boundary_user_mode_tl } \exp_args:NNVV \prop_gput:Nnn \g_@@_boundary_user_registered_prop \l_@@_boundary_user_name_tl \l_@@_boundary_user_value_tl } } } \cs_new_protected:Npn \@@_boundary_user_register_apply: { \bool_gset_true:N \g_@@_boundary_user_register_closed_bool \prop_map_inline:Nn \g_@@_boundary_user_registered_prop { \@@_boundary_if_registered:nTF {##1} { \@@_error:ne { boundary-register-conflict } { \c_backslash_str ##1 } } { \@@_boundary_user_register_apply:nn {##1} {##2} \seq_gput_right:Nn \g_@@_boundary_user_applied_seq {##1} } } } \cs_new_protected:Npn \@@_boundary_user_register_apply:nn #1#2 { \seq_set_split:Nnn \l_@@_boundary_user_value_seq { / } {#2} \seq_pop_left:NN \l_@@_boundary_user_value_seq \l_@@_boundary_user_strategy_tl \seq_pop_left:NN \l_@@_boundary_user_value_seq \l_@@_boundary_user_mode_tl \str_case:Vn \l_@@_boundary_user_strategy_tl { { box } { \exp_args:NnV \@@_boundary_register_box:nn {#1} \l_@@_boundary_user_mode_tl } { wrapped-box } { \exp_args:NnV \@@_boundary_register_wrapped_box:nn {#1} \l_@@_boundary_user_mode_tl } { stream } { \exp_args:NnV \@@_boundary_register_stream:nn {#1} \l_@@_boundary_user_mode_tl } { transparent } { \@@_boundary_register_transparent:n {#1} } { post-transparent } { \@@_boundary_register_post_transparent:n {#1} } } } \cs_new_protected:Npn \@@_boundary_user_register_check: { \seq_map_inline:Nn \g_@@_boundary_user_applied_seq { \cs_if_exist:cF {##1} { \@@_error:ne { boundary-register-undefined } { \c_backslash_str ##1 } } } } % \end{macrocode} % \tn{mbox} 和 \tn{fbox} 的公开命令是 robust wrapper;实际单参数正文由带 % 尾随空格的内部控制序列接收。必须先包装这层,再安装公开命令的 before % hook:这样 hook 先启动 box capture,正文适配器随后才能报告开头和结尾 % 的公式类别。适配器只检查用户交给盒子命令的可见正文,不监听命令内部的 % \tn{setbox} 测量。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_prepare_onearg_box:N #1 { \group_begin: \exp_args:NcNc \group_end: { @@_boundary_prepare_onearg_box:NN } #1 { \cs_to_str:N #1 ~ } } \cs_new_protected:Npn \@@_boundary_prepare_onearg_box:NN #1#2 { \@@_boundary_reserve:N #2 \exp_args:Nc \cs_new_eq:NN { @@_boundary_orig_\cs_to_str:N #1 _box:n } #2 \cs_gset_protected:Npn #2 ##1 { \@@_boundary_math_begin:n {##1} \use:c { @@_boundary_orig_\cs_to_str:N #1 _box:n } { ##1 \@@_boundary_math_end:n {##1} } } } % \end{macrocode} % expl3 内部函数不一定允许 LaTeX 通用 cmd hook。下面两个适配器仍复用 % transparent capture 的同一 begin/end,只负责保留零参数或单参数调用签名。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_wrap_transparent_noarg:NN #1#2 { \cs_if_exist:NF #1 { \@@_boundary_reserve:N #2 \cs_set_eq:NN #1#2 \cs_gset_protected:Npn #2 { \@@_boundary_hmode_transparent_begin: #1 \@@_boundary_hmode_transparent_end: } } } % \end{macrocode} % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_wrap_transparent_onearg:NN #1#2 { \cs_if_exist:NF #1 { \@@_boundary_reserve:N #2 \cs_set_eq:NN #1#2 \cs_gset_protected:Npn #2 ##1 { \@@_boundary_hmode_transparent_begin: #1 ##1 \@@_boundary_hmode_transparent_end: } } } % \end{macrocode} % % \changes{v3.10.5}{2026/08/04}{新增按花括号转发参数的 transparent 包装变体, % 供把参数再次用作 \cs{hbox:n} 内容的目标函数使用(\#1047)。} % % 上面那个包装以 |#1 ##1| 的形式转发参数,适用于原函数只是顺序执行参数内容 % 的情形。若原函数会把参数\emph{再次}用作 \cs{hbox:n} 一类的参数,丢失花括号 % 就会改变分组:参数后面相邻的记号会被吸进参数内部。\pkg{hyperref} 的 % \cs{__hyp_target_raise:n} 属于后者,它把参数交给 \cs{hbox:n},无花括号转发 % 会让紧随其后的 \tn{Hy@SaveSpaceFactor} 被卷进 \tn{hyper@anchorstart} 的参数, % 结果把 \tn{spacefactor} 赋值写进 \texttt{pdf:dest} 名字、并把锚点名排成 % 可见文本。 % % 这与 xeCJK 的钩子无关:不挂任何钩子、仅做无花括号透传也能复现(实测盒宽 % 81.16002pt 对 oracle 41.66002pt,该读数来自「不挂钩子的纯透传」对照;在包装变体 % 里误用无花括号版则为 84.49002pt)。因此只需要一个保留花括号的转发变体。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_wrap_transparent_onearg_braced:NN #1#2 { \cs_if_exist:NF #1 { \@@_boundary_reserve:N #2 \cs_set_eq:NN #1#2 \cs_gset_protected:Npn #2 ##1 { \@@_boundary_hmode_transparent_begin: #1 {##1} \@@_boundary_hmode_transparent_end: } } } % \end{macrocode} % % \changes{v3.10.4}{2026/07/20}{包装内核私有 \tn{@imakebox} 与 % \tn{@iframebox} 的 \texttt{[\#1][\#2]\#3} 签名以注册 \tn{makebox} / % \tn{framebox};内核若调整该签名须同步此 wrapper(\#992)。} % 这里包装的是本包最低支持内核 \LaTeXe{} 2026-06-01 中,内部 % \tn{@imakebox}/\tn{@iframebox} 的 |[#1][#2]#3| 参数签名,不是稳定公共 % 接口。若内核改变该签名,必须同步 wrapper;\texttt{command-boundary01} % 的 optional \tn{makebox}/\tn{framebox} 场景作为漂移门禁。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_makeboxes: { \clist_map_inline:nn { makebox , framebox , @imakebox , @iframebox } { \@@_boundary_reserve:n {##1} } \cs_if_exist:NF \@@_orig_imakebox:w { \cs_new_eq:NN \@@_orig_imakebox:w \@imakebox \cs_gset_protected:Npn \@imakebox [##1][##2]##3 { \@@_boundary_inline_last_box_begin: \@@_boundary_math_begin:n {##3} \@@_orig_imakebox:w [##1][##2] { ##3 \@@_boundary_math_end:n {##3} } \@@_boundary_inline_last_box_end:n { auto } } } \cs_if_exist:NF \@@_orig_iframebox:w { \cs_new_eq:NN \@@_orig_iframebox:w \@iframebox \cs_gset_protected:Npn \@iframebox [##1][##2]##3 { \@@_boundary_inline_last_box_begin: \@@_boundary_math_begin:n {##3} \@@_orig_iframebox:w [##1][##2] { ##3 \@@_boundary_math_end:n {##3} } \@@_boundary_inline_last_box_end:n { auto } } } } % \end{macrocode} % \changes{v3.10.5}{2026/07/29}{\tn{sbox} 改用专用适配器:\tn{global} % 等前缀必须紧邻 \tn{setbox},放进 \texttt{cmd/sbox/before} 钩子的赋值 % 会把前缀吃掉,使 \tn{global}\tn{sbox} 静默退化为局部赋值(\#1029)。} % \tn{sbox} 不能使用通用 \texttt{cmd/.../before} 钩子。钩子代码插在 % \tn{setbox} 之前执行,而调用方写的 \tn{global} 是等待下一个赋值的前缀: % 钩子里任何赋值都会把它消耗掉,于是 \tn{global}\tn{sbox} 退化为局部赋值, % 盒子在分组结束时丢失,且不报任何错误(\#1029,\pkg{algorithm2e} 的 % \tn{caption} 即如此消失)。因此这里直接包装内部入口,把暂停观察移到盒子 % 内部,使前缀始终紧邻 \tn{setbox}。 % % \tn{savebox} 的四种形式(无可选参数、|[wd]|、|[wd][pos]|,以及 picture 形式 % |(x,y)[pos]|,后者经 \tn{@isavepicbox} 汇入)最终都走同一个 \cs{sbox~} 入口, % 因此排版路径一并 % 覆盖;但 \tn{global}\tn{savebox} 跨分组本来就不生效(前缀在 \tn{savebox} % 自己的 \tn{@ifnextchar} 前瞻阶段即被消耗,原版 \LaTeX{} 亦然),不在本次 % 修复范围内。 % % 与 \cs{color@b@x}、\tn{@textcolor} 等适配器不同,这里不保存并委托原定义,而是 % 整体覆盖 \cs{sbox~}:内核 \cs{sbox~} 本体就是赋值语句,委托写法必须把 % \tn{setbox} 留在最外层才能保住前缀,代价明显高于收益。代价是若某个包在本包 % \emph{之前} 也包装了 \cs{sbox~},它的包装会被丢弃(反序加载正常)。撰写本节时 % 扫描过完整 \TeX{} Live 树,未发现任何宏包重定义 \cs{sbox~} 或对 \tn{sbox} % 安装 \texttt{cmd/sbox} 钩子,故接受这一取舍;\cs{cs_if_eq:cNF} 保证本包自身 % 不会重复包装。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_sbox:Nn #1#2 { \tex_setbox:D #1 \tex_hbox:D { \@@_boundary_capture_suspend: \color@setgroup #2 \color@endgroup \@@_boundary_capture_resume: } } \cs_new_protected:Npn \@@_boundary_prepare_sbox: { \cs_if_eq:cNF { sbox~ } \@@_boundary_sbox:Nn { \cs_gset_eq:cN { sbox~ } \@@_boundary_sbox:Nn } } \@@_boundary_prepare_onearg_box:N \mbox \@@_boundary_prepare_onearg_box:N \fbox \@@_boundary_register_box:nn { mbox } { auto } \@@_boundary_register_box:nn { fbox } { auto } \@@_boundary_register_makeboxes: \@@_boundary_register_post_transparent:n { null } \@@_boundary_reserve:n { sbox } \@@_boundary_prepare_sbox: % \end{macrocode} % \end{macro} % \end{macro} % % \changes{v3.9.0}{2022/06/06}{修复西文的 character protrusion 功能。} % % \begin{macro}[int]{\xeCJK_make_space_node:} % 用于判断插入空格之前的 node,默认为空,只有用户设置了 \opt{xCJKecglue} 选项才有意义。 % 需要使用 |glue| 来标记,使用 |kern| 会影响 character protrusion 功能。 % \begin{macrocode} \cs_new_eq:NN \xeCJK_make_space_node: \prg_do_nothing: \cs_new_protected:Npe \@@_make_space_node: { \tex_hskip:D - \@@_glue_node:n { default-space } \tex_hskip:D \@@_glue_node:n { default-space } } % \end{macrocode} % \end{macro} % % \begin{macro}{CJKglue} % CJK 文字之间插入的 |glue|。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CJKglue .code:n = { \cs_set_protected:Npn \CJKglue {#1} \xeCJK_glue_to_skip:nN {#1} \l_@@_ccglue_skip } } \skip_new:N \l_@@_ccglue_skip % \end{macrocode} % \end{macro} % % \changes{v3.2.16}{2014/12/16}{整理 \texttt{xCJKecglue} 的部分代码。} % % \begin{macro}{CJKecglue,xCJKecglue} % CJK 与西文和数学行内数学公式之间自动添加的空白。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CJKecglue .code:n = { \cs_set_protected:Npn \CJKecglue {#1} \xeCJK_glue_to_skip:nN {#1} \l_@@_ecglue_skip } , xCJKecglue .choice: , xCJKecglue / true .code:n = { \bool_set_true:N \l_@@_xecglue_bool \cs_set_eq:NN \xeCJK_space_or_xecglue: \CJKecglue \cs_set_eq:NN \xeCJK_make_space_node: \@@_make_space_node: \cs_set_eq:NN \xeCJK_check_for_xglue: \@@_check_for_xglue: \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_xecglue: \cs_set_eq:NN \xeCJK_check_for_ecglue_normalsp: \@@_check_for_xecglue_normalsp: } , xCJKecglue / false .code:n = { \bool_set_false:N \l_@@_xecglue_bool \cs_set_eq:NN \xeCJK_space_or_xecglue: \xeCJK_space_glue: \xeCJK_cs_clear:N \xeCJK_make_space_node: \xeCJK_cs_clear:N \xeCJK_check_for_xglue: \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_ecglue: \cs_set_eq:NN \xeCJK_check_for_ecglue_normalsp: \@@_check_for_ecglue_normalsp: } , xCJKecglue / unknown .code:n = { \bool_set_true:N \l_@@_xecglue_bool \cs_set_protected:Npn \CJKecglue {#1} \xeCJK_glue_to_skip:nN {#1} \l_@@_ecglue_skip \cs_set_eq:NN \xeCJK_space_or_xecglue: \CJKecglue \cs_set_eq:NN \xeCJK_make_space_node: \@@_make_space_node: \cs_set_eq:NN \xeCJK_check_for_xglue: \@@_check_for_xglue: \cs_set_eq:NN \xeCJK_check_for_ecglue: \@@_check_for_xecglue: \cs_set_eq:NN \xeCJK_check_for_ecglue_normalsp: \@@_check_for_xecglue_normalsp: } , xCJKecglue .default:n = { true } } \cs_new_eq:NN \xeCJK_space_glue: \c_space_tl \skip_new:N \l_@@_ecglue_skip \bool_new:N \l_@@_xecglue_bool % \end{macrocode} % \end{macro} % % \changes{v3.4.1}{2016/05/21}{修复 \texttt{CJKspace} 功能失效。} % \changes{v3.7.2}{2019/04/07}{简化 \texttt{CJKspace} 的实现,并修复错误。} % % \begin{macro}{CJKspace} % 是否保留 CJK 文字间的空白,默认不保留。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CJKspace .choice: , CJKspace / true .code:n = { \bool_set_true:N \l_@@_reserve_space_bool \cs_set_protected:Npn \@@_ccglue_or_space: { \xeCJK_space_glue: } } , CJKspace / false .code:n = { \bool_set_false:N \l_@@_reserve_space_bool \cs_set_protected:Npn \@@_ccglue_or_space: { \CJKglue } } , CJKspace .default:n = { true } , space .meta:n = { CJKspace = true } , nospace .meta:n = { CJKspace = false } } \bool_new:N \l_@@_reserve_space_bool % \end{macrocode} % \end{macro} % % \hypertarget{cjk-bound}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { CJK } { Boundary } { \xeCJK_CJK_and_Boundary:w } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_CJK_and_Boundary:w} % \changes{v3.2.6}{2013/08/05}{更好的处理边界是 \tn{relax} 的情况。} % \changes{v3.10.0}{2026/05/04}{修复 CJK 字符后行尾空格在宏命令前 % 未被消除的问题,不再立即输出空格 glue,改为只留标记由后续 % 恢复路径决定间距(\#324)。} % \changes{v3.10.0}{2026/05/12}{当显式 \texttt{\}} 触发 CJK→Boundary 转换时, % 设置 \cs{g_@@_glue_check_pending_bool} 以启用后续的 glue 检查, % 修复 \texttt{前\{中\} 后} 类型排版中多余的 \tn{CJKecglue}(\#831)。} % 当边界是 \tn{relax} 的时候,它可能是由 |\csname ...\endcsname| 的形式产生的, % 这样就可能出现问题\footnote{参见 \url{http://bbs.ctex.org/forum.php?mod=viewthread&tid=71563}。}。 % 原来是都在未定义控制序列前都加上 \cs{exp_not:N},现在则采用分组结束后手工恢复的方式。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_CJK_and_Boundary:w { \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token { \@@_CJK_and_math: } { \group_align_safe_begin: \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \token_if_macro:NTF \l_peek_token { \token_if_eq_meaning:NNTF \l_peek_token \@@_boundary_identity:n { \@@_boundary_peek_math:w } { \@@_boundary_reserve_space: } } { \token_if_group_begin:NTF \l_peek_token { \@@_boundary_group_math:w } { \@@_boundary_group_end:n { CJK-space } } } } { \token_if_eq_meaning:NNTF \l_peek_token \scan_stop: { \@@_CJK_and_Boundary_relax:N } { \token_if_group_begin:NTF \l_peek_token { \@@_boundary_group_math:w } { \token_if_group_end:NTF \l_peek_token { \bool_gset_true:N \g_@@_glue_check_pending_bool \@@_boundary_group_end:n { CJK } } { \token_if_macro:NTF \l_peek_token { \token_if_eq_meaning:NNTF \l_peek_token \@@_boundary_identity:n { \@@_boundary_peek_math:w } { \@@_boundary_group_end:n { CJK } } } { \@@_boundary_group_end:n { CJK } } } } } } } } \cs_new_protected:Npn \@@_CJK_and_math: { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \xeCJK_class_group_end: \xeCJK_space_or_xecglue: } { \xeCJK_class_group_end: \CJKecglue } } \cs_new_protected:Npn \@@_boundary_CJK_and_math: { \group_align_safe_end: \@@_CJK_and_math: } % \end{macrocode} % % \changes{v3.10.5}{2026/08/02}{只吸收触发转换的那一枚左花括号,不再吞掉整个 % 分组再重新发出,修复 \pkg{tabular} 中 \texttt{中文\textbackslash\textbackslash} % 报 \texttt{Improper alphabetic constant} 的问题(\#1038)。} % 这里只需要知道左花括号之后的第一个记号是不是 \tn{\$},因此不能用 \texttt{n} 型 % 参数把整个分组吞掉:分组的内容可能是别的宏正在执行的语法,提前吞掉再重新发出会 % 破坏它。 % % 具体的事故是 \#1038:XeTeX 判断字符类时会展开宏,\emph{包括} \tn{protected} 宏 % ——\tn{protected} 只对 \tn{edef} 一类的完全展开语境有效,对「不断展开直到取得 % 一个不可展开记号」的类别前瞻无效。于是 \pkg{tabular} 里 \verb|中文\\| 的 \tn{\\} % (已 \tn{let} 为 \tn{@tabularcr})被展开,其替换文本 % \verb|{\ifnum0=`}\fi...| 的首个记号是显式左花括号,本函数因此被选中,并把 % \verb|{\ifnum0=`}| 整个吞掉。重新发出时用的是\emph{隐式}的 % \cs{c_group_end_token},而 \pkg{latex} 那个平衡花括号的技巧要求反引号紧跟 % \emph{显式}字符记号 \verb|}|,于是报 \texttt{Improper alphabetic constant}。 % % 改为用 \tn{afterassignment} 加 \tn{let} 把那一枚左花括号当作赋值的右值吸收—— % 这不开新分组——再前瞻其后的第一个记号,最后补发一枚隐式左花括号恢复分组。除这 % 一枚花括号之外,输入流原样保留,因此 \verb|`}| 的相邻关系不被破坏。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_group_math:w { \tex_afterassignment:D \@@_boundary_group_math_peek: \tex_let:D \@@_boundary_group_math_brace: } \cs_new_eq:NN \@@_boundary_group_math_brace: \scan_stop: \cs_new_protected:Npn \@@_boundary_group_math_peek: { \peek_after:Nw \@@_boundary_group_math_branches: } \cs_new_protected:Npn \@@_boundary_group_math_branches: { \token_if_math_toggle:NTF \l_peek_token { \@@_boundary_CJK_and_math: } { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \@@_boundary_reserve_space: } { \@@_boundary_group_end:n { CJK } } } \c_group_begin_token } % \end{macrocode} % 只展开严格等同于单参数原样输出函数的宏。对任意“可展开”控制序列做 % 预展开会提前执行条件判断或扫描参数,破坏表格等正在运行的 TeX 语法。 % \begin{macrocode} \cs_new:Npn \@@_boundary_identity:n #1 {#1} \cs_new_protected:Npn \@@_boundary_peek_math:w { \exp_after:wN \peek_after:Nw \exp_after:wN \@@_boundary_peek_math_branches:w \exp:w \exp_end_continue_f:w } \cs_new_protected:Npn \@@_boundary_peek_math_branches:w { \token_if_math_toggle:NTF \l_peek_token { \@@_boundary_CJK_and_math: } { \token_if_group_begin:NTF \l_peek_token { \@@_boundary_group_math:w } { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \@@_boundary_reserve_space: } { \@@_boundary_group_end:n { CJK } } } } } \cs_new_protected:Npn \@@_boundary_reserve_space: { \@@_boundary_group_end:n { CJK-space } } \cs_new_protected:Npn \@@_CJK_and_Boundary_relax:N #1 { \@@_boundary_group_end:n { CJK } \token_if_eq_meaning:NNTF #1 \scan_stop: {#1} { \cs_set_eq:NN #1 \scan_stop: #1 } } \cs_new_protected:Npn \@@_boundary_group_end:n #1 { \group_align_safe_end: \xeCJK_class_group_end: \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_ecglue_skip { \xeCJK_make_node:n {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_ignore_spaces:w} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_ignore_spaces:w { \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \xeCJK_space_or_xecglue: } { \CJKecglue } } { \bool_if:NT \l_@@_peek_ignore_spaces_bool { \dim_case:nn { \tex_lastkern:D } { { \@@_node:n { CJK } } { \xeCJK_remove_node: \xeCJK_make_node:n { CJK-space } } { \@@_node:n { default } } { \xeCJK_remove_node: \xeCJK_make_space_node: } } \group_align_safe_begin: \token_if_macro:NTF \l_peek_token { \@@_reserve_space_aux: } { \group_align_safe_end: } } } } \cs_new_protected:Npn \@@_reserve_space_aux: { \group_align_safe_end: \xeCJK_space_or_xecglue: } % \end{macrocode} % \end{macro} % % \hypertarget{cjk-cjk}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { CJK } { CJK } { \xeCJK_CJK_and_CJK:N } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_CJK_and_CJK:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_CJK_and_CJK:N { \CJKglue \xeCJK_fallback_symbol:NN \CJKsymbol } % \end{macrocode} % \end{macro} % % \hypertarget{fl-fr-others}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { FullLeft } { CJK } { \xeCJK_FullLeft_and_CJK: \xeCJK_fallback_symbol:NN \CJKsymbol } \xeCJK_inter_class_toks:nnn { FullRight } { CJK } { \xeCJK_FullRight_and_CJK: \xeCJK_fallback_symbol:NN \CJKsymbol } \seq_map_inline:Nn \g_@@_non_CJK_class_seq { \clist_map_inline:nn { FullLeft , FullRight } { \xeCJK_inter_class_toks:nne {#1} {##1} { \exp_not:c { xeCJK_Default_and_##1:nN } {#1} } \xeCJK_inter_class_toks:nne {##1} {#1} { \exp_not:c { xeCJK_##1_and_Default: } } } } % \end{macrocode} % % \changes{v3.10.0}{2026/05/03}{新增实验性 % \opt{experiment/halfright-prebreakpenalty} 选项(\#811)。} % % \begin{macro}{\l_@@_halfright_nobreak_bool} % \begin{macrocode} \bool_new:N \l_@@_halfright_nobreak_bool % \end{macrocode} % \end{macro} % % 在 |CJK| $\to$ |HalfRight| 和 |FullRight| $\to$ |HalfRight| 的过渡中, % 条件性地插入禁则 penalty。 % \begin{macrocode} \xeCJK_app_inter_class_toks:nnn { CJK } { HalfRight } { \bool_if:NT \l_@@_halfright_nobreak_bool { \xeCJK_no_break: } } % \end{macrocode} % % |FullRight| $\to$ |HalfRight| 需要将 penalty 插入 |punct_glue| 之前, % 因此覆写而非追加。 % \begin{macrocode} \xeCJK_inter_class_toks:nnn { FullRight } { HalfRight } { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \bool_if:NT \l_@@_halfright_nobreak_bool { \xeCJK_no_break: } \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl } % \end{macrocode} % % \hypertarget{bound-fl-fr}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { Boundary } { FullLeft } { \xeCJK_Boundary_and_FullLeft:N } \xeCJK_inter_class_toks:nnn { Boundary } { FullRight } { \xeCJK_Boundary_and_FullRight:N } % \end{macrocode} % % \hypertarget{fl-fr-bound}{} % \begin{macrocode} \xeCJK_inter_class_toks:nnn { FullLeft } { Boundary } { \xeCJK_FullLeft_and_Boundary: } \xeCJK_inter_class_toks:nnn { FullRight } { Boundary } { \xeCJK_FullRight_and_Boundary: } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_FullLeft_and_Boundary:} % \hypertarget{fl-fr-bound}{} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullLeft_and_Boundary: { \@@_punct_if_middle:NTF \g_@@_last_punct_tl { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl \xeCJK_no_break: \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl \@@_punct_boundary_guard: } { \xeCJK_class_group_end: \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl \@@_nobreak_zero_glue: } \tex_ignorespaces:D } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_and_Boundary:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_and_Boundary: { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_punct_boundary_guard: \tex_ignorespaces:D } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_boundary_guard:} % \changes{v3.10.0}{2026/05/13} % {修复 \env{tabular} 中 \tn{unskip} 吞掉标点补偿 glue 的问题(\#827)。} % \changes{v3.10.0}{2026/05/17} % {新增 \opt{experiment/punct-measure-fix} 选项:段落模式下通过 % para/end 钩子恢复被 \tn{unskip} 移除的标点补偿 glue(\#859)。} % 在 restricted horizontal mode(\env{tabular} 单元格、\tn{hbox} 等)中, % \tn{\\} 结束行时通过 \tn{unskip} 移除最后一个 glue。 % 这会吞掉标点的补偿 glue,导致单元格宽度错误。 % 此函数在 inner mode 下于 glue 之后插入 |\penalty 0|, % 使最后节点不再是 glue,从而保护它。 % % 在段落模式下,当 \opt{experiment/punct-measure-fix} 选项启用时, % 记录 glue 的自然宽度,并通过 |para/end| 钩子插入等宽 % \tn{kern} 恢复。使用 \tn{kern} 而非 \tn{hskip}:\tn{kern} 不会被 % \tn{unskip} 移除(无需额外保护),也不构成合法断行点 % (避免 CheckSingle 等场景下产生空行)。 % \begin{macrocode} \bool_new:N \g_@@_par_guard_bool \dim_new:N \g_@@_par_guard_dim \cs_new_protected:Npn \@@_punct_boundary_guard: { \mode_if_inner:TF { \tex_penalty:D \c_zero_int } { \bool_if:NT \g_@@_punct_measure_fix_bool { \bool_gset_true:N \g_@@_par_guard_bool \dim_gset:Nn \g_@@_par_guard_dim { \tex_lastskip:D } } } } % \end{macrocode} % % 当 |Boundary| 之后紧跟其他字符类时,说明标点不在段末, % 需要重置保护标志。 % \begin{macrocode} \clist_map_inline:nn { Default , HalfLeft , HalfRight , NormalSpace , CJK , FullLeft , FullRight , CM , HangulJamo , HangulJamoL , HangulJamoV , HangulJamoT , CJStarter } { \xeCJK_pre_inter_class_toks:nnn { Boundary } {#1} { \bool_gset_false:N \g_@@_par_guard_bool } } % \end{macrocode} % % 在 |para/begin| 中重置标志,避免跨段落残留状态。 % 在 |para/end| 中,若标志为真则插入 \tn{kern} 补偿被 \tn{unskip} % 移除的标点 glue 的自然宽度。 % \begin{macrocode} \AddToHook { para/begin } { \bool_gset_false:N \g_@@_par_guard_bool } \AddToHook { para/end } { \bool_if:NT \g_@@_par_guard_bool { \tex_kern:D \g_@@_par_guard_dim \bool_gset_false:N \g_@@_par_guard_bool } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_punct_node:N} % 保存标点的当前边界宽度和字符码,通过插入 \tn{kern} 实现。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_punct_node:N #1 { \@@_punct_bound_unitization:NN #1 \l_@@_tmp_dim \@@_make_node:N \l_@@_tmp_dim \dim_set:Nn \l_@@_tmp_dim { `#1 sp } \@@_make_node:N \l_@@_tmp_dim } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_bound_unitization:NN} % 我们不想出现过大的 \tn{kern},因此当边界大于 \SI{1}{pt} 时,以 \cs{c_max_dim} 为标准 % 对其进行“单位化”。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_bound_unitization:NN #1#2 { \dim_set:Nn #2 { \dim_max:nn { \c_zero_dim } { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 } } \dim_compare:nNnF {#2} < { 1pt } { \dim_set:Nn #2 { -1pt * \dim_ratio:nn {#2} { \c_max_dim } } } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_punct_bound_kern:N} % \begin{macro}{\@@_punct_bound_kern:NN} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_punct_bound_kern:N #1 { \exp_after:wN \@@_punct_bound_kern:NN \g_@@_last_punct_tl #1 } \cs_new_protected:Npn \@@_punct_bound_kern:NN #1#2 { \xeCJK_get_punct_bounds:NN \l_@@_aligni_tl #1 \xeCJK_get_punct_kerning:NN #1 #2 \@@_punct_bound_unitization:NN #1 \l_@@_tmp_dim \skip_set:Nn \l_@@_punct_kern_skip { \@@_use_dim_or_skip:nNN { bound_kern } #1 #2 } \dim_compare:nNnF \l_@@_tmp_dim = \l_@@_last_bound_dim { \@@_punct_bound_kern_ratio:NN #1 #2 } \bool_if:NTF \l_@@_last_penalty_bool { \tex_penalty:D \l_@@_last_penalty_int \skip_horizontal:N } { \@@_punct_bound_kern_aux:NNN #1 #2 } \l_@@_punct_kern_skip } \skip_new:N \l_@@_punct_kern_skip % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_punct_bound_kern_ratio:NN} % 当标点前后的字体情况不一致时,按一定的比例进行压缩。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_bound_kern_ratio:NN #1#2 { \dim_set:Nn \l_@@_bound_dim { \@@_use_punct_dim:nNN { bound_width } #1 #2 } \dim_compare:nNnT \l_@@_bound_dim > \c_zero_dim { \dim_compare:nNnF \l_@@_last_bound_dim > \c_zero_dim { \dim_set:Nn \l_@@_last_bound_dim { - \l_@@_last_bound_dim * \dim_ratio:nn { \c_max_dim } { 1pt } } } \@@_punct_bound_kern_ratio_aux:N #2 } } \cs_new_protected:Npn \@@_punct_bound_kern_ratio_aux:N #1 { \skip_set:Nn \l_@@_punct_kern_skip { \l_@@_punct_kern_skip * \dim_ratio:nn { \l_@@_last_bound_dim + \@@_use_punct_dim:nNN { bound } \c_@@_left_tl #1 } { \l_@@_bound_dim } } } % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_nobreak_hskip:N,\@@_nobreak_hskip:n, % \@@_punct_bound_kern:N, \@@_punct_bound_breakable_kern:N} % \begin{macrocode} \cs_new_protected:Npn \@@_nobreak_hskip:N { \xeCJK_no_break: \skip_horizontal:N } \cs_new_protected:Npn \@@_nobreak_hskip:n { \xeCJK_no_break: \skip_horizontal:n } \cs_new_eq:NN \@@_punct_bound_kern:N \@@_nobreak_hskip:N \cs_new_protected:Npn \@@_punct_bound_breakable_kern:N { \tl_if_eq:NNTF \l_@@_aligni_tl \c_@@_right_tl { \tl_if_eq:NNTF \l_@@_alignii_tl \c_@@_left_tl { \skip_horizontal:N } { \@@_nobreak_hskip:N } } { \@@_nobreak_hskip:N } } \cs_new_protected:Npn \@@_punct_bound_kern_aux:NNN #1#2 { \str_if_eq:nnTF {#1} {#2} { \@@_nobreak_hskip:N } { \@@_punct_if_long:NTF #1 { \skip_horizontal:N } { \@@_punct_if_long:NTF #2 { \skip_horizontal:N } { \@@_punct_bound_kern:N } } } } % \end{macrocode} % \end{macro} % % \hypertarget{cjk-fl-fr}{} % \begin{macrocode} \clist_map_inline:nn { CJK , FullLeft , FullRight } { \clist_map_inline:nn { FullLeft , FullRight } { \xeCJK_inter_class_toks:nne {#1} {##1} { \exp_not:c { xeCJK_#1_and_##1:N } } } } % \end{macrocode} % % \begin{macro}{\@@_punct_bound_rule:NN} % 用于抹去标点符号的全部左/右空白。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_bound_rule:NN #1#2 { \tex_vrule:D width - \@@_use_punct_dim:nNN { bound } #1 #2 ~ depth \c_zero_dim height \c_zero_dim \scan_stop: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_rule:NN} % 用于减少标点符号的左/右空白。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_rule:NN #1#2 { \tex_vrule:D width \@@_use_punct_dim:nNN { rule } #1 #2 ~ depth \c_zero_dim height \c_zero_dim \scan_stop: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_glue:NN} % \changes{v3.2.7}{2013/08/23} % {标点符号左/右空白的伸展值不超过原始边界,收缩值不小于另一侧边界。} % 根据所选的标点处理方式在标点符号左/右增加的空白。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_glue:NN #1#2 { \@@_punct_hskip:n { \@@_use_dim_or_skip:nNN { glue } #1 #2 } } \cs_new_eq:NN \@@_punct_hskip:n \skip_horizontal:n % \end{macrocode} % \end{macro} % % \changes{v3.6.0}{2018/01/23}{总允许长标点与其他标点之间折行。} % \changes{v3.10.0}{2026/04/27} % {\texttt{NoBreakLongPunct} 在右侧时禁止折行。} % \changes{v3.10.2}{2026/07/05} % {长标点与其他标点之间折行时检查断点两侧的禁则:左标点不得悬于行尾, % 右标点与 \texttt{NoBreakLongPunct} 不得落于行首(\#456)。} % \begin{macro}{\xeCJK_punct_kern:NN,\@@_punct_kern:NN} % 相邻两个标点之间的间距。长标点与其他标点之间允许折行, % 但断点两侧的禁则必须同时满足,由 \cs{@@_punct_kern_break:NN} 判断。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_kern:NN #1#2 { \str_if_eq:eeTF {#1} {#2} { \@@_punct_nobreak_kern:NN } { \bool_lazy_or:nnTF { \@@_punct_if_long_p:N #1 } { \@@_punct_if_long_p:N #2 } { \@@_punct_kern_break:NN #1 #2 } { \@@_punct_nobreak_kern:NN } } #1 #2 } \cs_new_eq:NN \xeCJK_punct_kern:NN \@@_punct_kern:NN % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_kern_break:NN} % 长标点与其他标点相邻时,判断二者之间的断点是否满足两侧禁则, % 并留下相应的间距函数(吃掉参数后,后续的 |#1 #2| 交由留下的函数处理): % \begin{itemize} % \item 断点之前(|#1|)必须是全角右标点或长标点, % 即全角左标点不得悬于行尾; % \item 断点之后(|#2|)必须是全角左标点,或者不属于 % |NoBreakLongPunct| 的长标点,即全角右标点和省略号等 % 不得落于行首。 % \end{itemize} % 注意 |#1| 来自 \cs{g_@@_last_punct_tl},参与字符类判断前 % 需要先展开为字符记号。 % \begin{macrocode} \cs_new_protected:Npn \@@_punct_kern_break:NN #1#2 { \exp_after:wN \@@_punct_if_right:NTF #1 { \@@_punct_if_right:NTF #2 { \bool_lazy_and:nnTF { \@@_punct_if_long_p:N #2 } { ! \@@_punct_if_nobreak_long_p:N #2 } { \@@_punct_breakable_kern:NN } { \@@_punct_nobreak_kern:NN } } { \@@_punct_breakable_kern:NN } } { \@@_punct_nobreak_kern:NN } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_nobreak_kern:NN} % \begin{macrocode} \cs_new_protected:Npn \@@_punct_nobreak_kern:NN #1#2 { \@@_nobreak_hskip:n { \@@_use_dim_or_skip:nNN { kern } #1 #2 } } % \end{macrocode} % \end{macro} % % \changes{v3.2.4}{2013/07/06} % {使用 \texttt{AllowBreakBetweenPuncts} 时,相应标点符号仍能与边界对齐。} % \changes{v3.2.7}{2013/08/23} % {处理 \texttt{AllowBreakBetweenPuncts} 与 \pkg{xeCJKfntef} 的兼容问题。} % % \begin{macro}{\@@_punct_breakable_kern:NN} % \begin{macrocode} \cs_new_protected:Npn \@@_punct_breakable_kern:NN #1#2 { \exp_after:wN \@@_punct_if_right:NT #1 { \@@_punct_rule:NN \c_@@_right_tl #1 } \@@_punct_breakable_kern:n { \@@_use_dim_or_skip:nNN { bound_kern } #1 #2 } \@@_punct_if_right:NF #2 { \@@_punct_rule:NN \c_@@_left_tl #2 } } \cs_new_eq:NN \@@_punct_breakable_kern:n \skip_horizontal:n % \end{macrocode} % \end{macro} % % \begin{variable}{\g_@@_last_punct_tl} % 用于记录当前的标点符号。 % \begin{macrocode} \tl_new:N \g_@@_last_punct_tl % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\xeCJK_FullLeft_and_CJK:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullLeft_and_CJK: { \@@_punct_if_middle:NTF \g_@@_last_punct_tl { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_no_break: \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl } { } \@@_select_font: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullLeft_and_Default:} % \changes{v3.2.0}{2013/05/20}{修正 \pkg{xeCJK} 使西文在部分情况下无法断词的问题。} % \changes{v3.7.2}{2018/05/21}{再次修正 FullLeft 类字符与西文连用断词失败的问题。} % \cs{@@_nobreak_zero_glue:} 用于确保 FullLeft 类后的西文单词可以断词。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullLeft_and_Default: { \@@_punct_if_middle:NTF \g_@@_last_punct_tl { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \xeCJK_no_break: \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl } { \xeCJK_class_group_end: \@@_nobreak_zero_glue: } } \cs_new_protected:Npn \@@_nobreak_zero_glue: { \tex_penalty:D \c_@@_nobreak_penalty_int \skip_horizontal:N \c_zero_skip } \cs_new_protected:Npn \@@_zero_glue: { \skip_horizontal:N \c_zero_skip } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_and_CJK:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_and_CJK: { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_select_font: \CJKglue } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_and_Default:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_and_Default: { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_Default_and_FullLeft:nN} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Default_and_FullLeft:nN #1#2 { \xeCJK_get_punct_bounds:NN \c_@@_left_tl #2 \@@_Default_and_FullLeft_glue:N #2 \xeCJK_class_group_begin: \xeCJK_select_punct_font: \xeCJK_clear_inter_class_toks:nn {#1} { FullLeft } \xeCJK_clear_Boundary_and_CJK_toks: \tl_gset:Nn \g_@@_last_punct_tl {#2} \@@_punct_rule:NN \c_@@_left_tl #2 \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol #2 } \cs_new_protected:Npn \@@_Default_and_FullLeft_glue:N #1 { \@@_punct_glue:NN \c_@@_left_tl #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_CJK_and_FullLeft:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_CJK_and_FullLeft:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1 \@@_CJK_and_FullLeft_glue:N #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \@@_punct_rule:NN \c_@@_left_tl #1 \@@_select_punct_font: \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol #1 } \cs_new_protected:Npn \@@_CJK_and_FullLeft_glue:N #1 { \CJKglue \@@_punct_glue:NN \c_@@_left_tl #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_Boundary_and_FullLeft:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Boundary_and_FullLeft:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1 \@@_Boundary_and_FullLeft_glue:N #1 \xeCJK_class_group_begin: \xeCJK_select_punct_font: \xeCJK_clear_Boundary_and_CJK_toks: \tl_gset:Nn \g_@@_last_punct_tl {#1} \@@_punct_rule:NN \c_@@_left_tl #1 \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_Boundary_and_FullLeft_glue:N} % \changes{v3.2.0}{2013/05/22}{当全角左标点前面是 \texttt{hlist}、\texttt{none}、 % \texttt{glue} 和 \texttt{penalty} 等节点时,压缩其左空白。} % \changes{v3.2.4}{2013/07/03}{细化边界与全角左标点之间是否压缩空白的判断。} % \changes{v3.2.5}{2013/07/13}{细化全角左标点是否位于段首的判断。} % \changes{v3.2.5}{2013/07/13}{增加对 \pkg{enumitem} 宏包修改的 \tn{item} 的判断。} % 根据 \cs{etex_lastnodetype:D} 的值进行分别处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_Boundary_and_FullLeft_glue:N #1 { \tl_set_eq:NN \l_@@_alignii_tl \c_@@_left_tl \group_begin: \exp_args:NNc \group_end: \cs_if_exist_use:NTF { @@_bound_type_ \int_use:N \tex_lastnodetype:D _glue:Nn } {#1} { \use:n } { \@@_punct_glue:NN \c_@@_left_tl #1 } } \tl_new:N \c_@@_alignii_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bound_type_-1_glue:Nn} % \cs{etex_lastnodetype:D} 为 $-1$ 表示 empty list,常出现在盒子的起始位置, % 在段落前使用 \tn{noindent} 就是这种情况。 % \begin{macrocode} \cs_new_protected:cpn { @@_bound_type_ -1 _glue:Nn } #1#2 { \@@_zero_glue: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bound_type_1_glue:Nn} % \changes{v3.8.5}{2020/06/26}{增加盒子高度判断。} % \changes{v3.8.9}{2022/05/26}{增加位于段首的支架盒子判断。} % $1$ 表示 hlist node,在这里用来判断是否位于段首。基于正常情况下,\TeX 会在段落开头插入宽度为 % \tn{parindent} 的水平盒子用于缩进。 % \begin{macrocode} \cs_new_protected:cpn { @@_bound_type_ 1 _glue:Nn } #1 { \int_do_while:nNnn \tex_lastnodetype:D = \c_@@_hlist_node { \@@_bound_hbox_auxi: } \@@_if_last_none:TF { \dim_case:nnF { \box_wd:N \l_@@_indent_box } { { \tex_parindent:D } { \@@_bound_hbox_auxii:nn } { \c_zero_dim } { \use_i:nn } } { \use:nn } } { \use:nn } { \hbox_unpack_drop:N \l_@@_indent_box } } \cs_new_protected:Npn \@@_bound_hbox_auxi: { \box_set_to_last:N \l_@@_tmp_box \hbox_set:Nn \l_@@_indent_box { \box_use:N \l_@@_tmp_box \hbox_unpack:N \l_@@_indent_box } } \cs_new_protected:Npn \@@_bound_hbox_auxii:nn { \dim_compare:nNnTF { \box_ht:N \l_@@_tmp_box } = \c_zero_dim { \use_i:nn } { \use:nn } } \box_new:N \l_@@_indent_box % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bound_type_11_glue:Nn} % $11$ 表示 glue node,这里判断的目的是当全角左标点出现在 \LaTeX 表格的非 \texttt{p} 列行首时, % 能够对齐到单元格的边界。判断基于标准 \LaTeX 表格的列格式(\tn{@tabclassz})定义中, % 在 \texttt{l} 列和 \texttt{r} 列前为了防止 \tn{tabcolsep} 被无意 \tn{unskip} 掉, % 都加了 |\hskip1sp|,而 \texttt{c} 列前则有 \tn{hfil}。\package{enumitem} 宏包修改了 % \env{description} 环境中使用的 \tn{item}(\tn{enit@postlabel@i}), % 在这里起到影响作用的是 |\penalty\z@ \hskip\labelsep|。 % \begin{macrocode} \cs_new_protected:cpn { @@_bound_type_ 11 _glue:Nn } #1#2 { \skip_if_finite:nTF { \tex_lastskip:D } { \@@_bound_glue_auxi:Nn #1 {#2} } { \@@_zero_glue: } } \cs_new_protected:Npn \@@_bound_glue_auxi:Nn #1#2 { \@@_if_last_punct_glue:TF { \xeCJK_punct_bound_kern:N #1 } { \@@_bound_glue_auxii:n {#2} } } \cs_new_protected:Npn \@@_bound_glue_auxii:n #1 { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \skip_if_eq:nnTF { \l_@@_last_skip } { 1sp } { \@@_zero_glue: } { \skip_if_eq:nnTF { \l_@@_last_skip } { \labelsep } { \tex_unskip:D \@@_if_last_penalty:TF { \int_compare:nNnTF \tex_lastpenalty:D = \c_zero_int { \skip_horizontal:N \l_@@_last_skip } { \skip_horizontal:N \l_@@_last_skip #1 } } { \skip_horizontal:N \l_@@_last_skip #1 } } {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bound_type_12_glue:Nn} % $12$ 表示 kern node,用于判断之前的字符是否是 CJK 类,如果是,则插入 \tn{CJKglue}。 % \begin{macrocode} \cs_new_protected:cpn { @@_bound_type_ 12 _glue:Nn } #1#2 { \xeCJK_if_last_node:nF { CJK } { \xeCJK_if_last_node:nF { CJK-space } { \use_none:nn } } \xeCJK_remove_node: \CJKglue #2 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_bound_type_13_glue:n} % $13$ 表示 penalty node,这里判断的目的是全角左标点出现在 \LaTeX 列表环境的 \tn{item} 后面时, % 能对齐到边界。判断基于 \tn{item} 的内部定义 \tn{@item} 对 \tn{everypar} 进行了修改,在这里起 % 到影响作用的是 |\box\@labels \penalty\z@|。以上判断都比较粗略,暂时也没有想起更好的办法。 % \begin{macrocode} \cs_new_protected:cpn { @@_bound_type_ 13 _glue:Nn } #1#2 { \@@_if_last_punct_penalty:TF { \xeCJK_punct_bound_kern:N #1 } { \int_compare:nNnTF \tex_lastpenalty:D = \c_zero_int { \tex_unpenalty:D \@@_if_last_hlist:TF { \tex_penalty:D \c_zero_int } { \tex_penalty:D \c_zero_int #2 } } {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_Default_and_FullRight:nN} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Default_and_FullRight:nN #1#2 { \xeCJK_get_punct_bounds:NN \c_@@_right_tl #2 \@@_Default_and_FullRight_glue:N #2 \xeCJK_class_group_begin: \xeCJK_select_punct_font: \xeCJK_clear_inter_class_toks:nn {#1} { FullRight } \xeCJK_clear_Boundary_and_CJK_toks: \tl_gset:Nn \g_@@_last_punct_tl {#2} \xeCJK_FullRight_symbol:N #2 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_Boundary_and_FullRight:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_Boundary_and_FullRight:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1 \xeCJK_if_last_punct:TF { \tl_set_eq:NN \l_@@_alignii_tl \c_@@_right_tl \xeCJK_punct_bound_kern:N } { \@@_Default_and_FullRight_glue:N } #1 \xeCJK_class_group_begin: \xeCJK_select_punct_font: \xeCJK_clear_Boundary_and_CJK_toks: \tl_gset:Nn \g_@@_last_punct_tl {#1} \xeCJK_FullRight_symbol:N #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_CJK_and_FullRight:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_CJK_and_FullRight:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1 \@@_CJK_and_FullRight_glue:N #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \@@_select_punct_font: \xeCJK_FullRight_symbol:N #1 } % \end{macrocode} % \end{macro} % % \changes{v3.6.0}{2018/01/23}{解决标点中间被隔开的禁则与压缩问题。} % \changes{v3.7.3}{2019/04/15}{修复 penalty 数值错误。} % \changes{v3.8.3}{2020/04/27}{修复 \opt{xCJKecglue} 选项。} % \changes{v3.8.6}{2020/10/18}{正确还原标点符号后的 penalty 状态。} % % \begin{macro}{\xeCJK_if_last_punct:TF} % \changes{v3.6.1}{2018/02/25}{细化判断。} % 判断之前是否是一个标点符号。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_if_last_punct:TF { \bool_set_false:N \l_@@_last_penalty_bool \@@_if_last_glue:TF { \@@_if_last_punct_glue:TF } { \@@_if_last_penalty:TF { \@@_if_last_punct_penalty:TF } { \use_ii:nn } } } \cs_new_protected:Npn \@@_if_last_punct_glue:TF { \prop_get:NoNTF \g_@@_punct_skip_prop { \skip_use:N \tex_lastskip:D } \l_@@_tmp_tl { \@@_if_last_punct_glue_auxi:TF } { \@@_if_last_punct_glue_auxii:TF } } \cs_new_protected:Npn \@@_if_last_punct_glue_auxi:TF { \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D \tex_unskip:D \int_compare:nNnTF \tex_lastpenalty:D = \c_@@_nobreak_penalty_int { \@@_if_last_punct_auxi:TF { \use_i:nn } } { \xeCJK_if_last_node:TF { \@@_if_last_punct_auxii:TF { \use_i:nn } } { \use:n } } { \skip_horizontal:N \l_@@_last_skip \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_glue_auxii:TF { \group_begin: \g_@@_space_factor_int \tex_spacefactor:D \skip_if_eq:nnTF { \tex_lastskip:D } { \c_xeCJK_space_skip_tl } { \group_end: \@@_if_last_punct_glue_auxiii:TF } { \group_end: \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_glue_auxiii:TF { \skip_set_eq:NN \l_@@_tmp_skip \tex_lastskip:D \tex_unskip:D \@@_if_last_glue:TF { \prop_get:NoNTF \g_@@_punct_skip_prop { \skip_use:N \tex_lastskip:D } \l_@@_tmp_tl { \@@_if_last_punct_glue_auxi:TF { \use_i:nn } } { \use:n } } { \use:n } { \skip_horizontal:N \l_@@_tmp_skip \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_penalty:TF { \int_set_eq:NN \l_@@_last_penalty_int \tex_lastpenalty:D \tex_unpenalty:D \bool_set_true:N \l_@@_last_penalty_bool \@@_if_last_glue:TF { \@@_if_last_punct_glue:TF { \use_i:nn } } { \use:n } { \@@_last_punct_penalty_false:nn } } \cs_new_protected:Npn \@@_last_punct_penalty_false:nn #1#2 { \bool_set_false:N \l_@@_last_penalty_bool \tex_penalty:D \l_@@_last_penalty_int #2 } \cs_new_protected:Npn \@@_if_last_punct_auxi:TF { \tex_unpenalty:D \bool_if:NF \l_@@_last_penalty_bool { \bool_set_true:N \l_@@_last_penalty_bool \int_set_eq:NN \l_@@_last_penalty_int \c_@@_nobreak_penalty_int } \xeCJK_if_last_node:TF { \@@_if_last_punct_auxii:TF { \use_i:nn } } { \use:n } { \xeCJK_no_break: \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_auxii:TF { \dim_compare:nNnTF \l_@@_last_kern_dim > \c_zero_dim { \@@_if_last_punct_auxiii:TF } { \@@_make_node:N \l_@@_last_kern_dim \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_auxiii:TF { \int_case:nnTF { \tex_XeTeXcharclass:D \l_@@_last_kern_dim } { { \xeCJK_class_num:n { FullRight } } { \tl_set_eq:NN \l_@@_aligni_tl \c_@@_right_tl } { \xeCJK_class_num:n { FullLeft } } { \tl_set_eq:NN \l_@@_aligni_tl \c_@@_left_tl } } { \@@_if_last_punct_auxiv:TF } { \use_ii:nn } } \cs_new_protected:Npn \@@_if_last_punct_auxiv:TF { \dim_set_eq:NN \l_@@_tmp_dim \l_@@_last_kern_dim \xeCJK_if_last_node:TF { \tl_gset:Ne \g_@@_last_punct_tl { \tex_Uchar:D \l_@@_tmp_dim } \dim_set_eq:NN \l_@@_last_bound_dim \l_@@_last_kern_dim \use_i:nn } { \@@_make_node:N \l_@@_tmp_dim \use_ii:nn } } \tl_new:N \l_@@_aligni_tl \tl_new:N \l_@@_alignii_tl \int_new:N \l_@@_last_penalty_int \dim_new:N \l_@@_last_bound_dim \bool_new:N \l_@@_last_penalty_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJK_if_last_node:TF} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_if_last_node:TF #1#2 { \@@_if_last_kern:TF { \dim_set_eq:NN \l_@@_last_kern_dim \tex_lastkern:D \tex_unkern:D \@@_if_last_kern:TF { \dim_compare:nNnTF \tex_lastkern:D = { - \l_@@_last_kern_dim } { \tex_unkern:D #1 } { \tex_kern:D \l_@@_last_kern_dim #2 } } { \tex_kern:D \l_@@_last_kern_dim #2 } } {#2} } \dim_new:N \l_@@_last_kern_dim % \end{macrocode} % \end{macro} % % \changes{v3.6.0}{2018/01/14} % {修正标点同为 \texttt{LongPunct} 与 \texttt{MiddlePunct} 时的实现错误。} % \changes{v3.6.0}{2018/01/14} % {\texttt{Default} 类与 \texttt{MiddlePunct} 之间不应该有 \tn{CJKglue}。} % \changes{v3.10.0}{2026/04/27} % {新增 \texttt{NoBreakLongPunct} 属性,禁止在省略号等长标点前断行。} % % \begin{macro} % {\@@_CJK_and_FullRight_glue:N,\@@_Default_and_FullRight_glue:N} % \begin{macrocode} \cs_new_protected:Npn \@@_CJK_and_FullRight_glue:N #1 { \@@_punct_if_long:NTF #1 { \@@_punct_if_nobreak_long:NTF #1 { \xeCJK_no_break: } { \xeCJK_allow_break: } } { \xeCJK_no_break: } \@@_punct_if_middle:NT #1 { \CJKglue \@@_punct_glue:NN \c_@@_right_tl #1 \@@_punct_rule:NN \c_@@_left_tl #1 } } \cs_new_protected:Npn \@@_Default_and_FullRight_glue:N #1 { \@@_punct_if_long:NTF #1 { \@@_punct_if_nobreak_long:NTF #1 { \xeCJK_no_break: } { \xeCJK_allow_break: } } { \xeCJK_no_break: } \@@_punct_if_middle:NT #1 { \@@_punct_glue:NN \c_@@_right_tl #1 \@@_punct_rule:NN \c_@@_left_tl #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullLeft_and_FullLeft:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullLeft_and_FullLeft:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1 \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1 \@@_punct_kern:NN \g_@@_last_punct_tl #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullLeft_and_FullRight:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullLeft_and_FullRight:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1 \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1 \@@_punct_kern:NN \g_@@_last_punct_tl #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \xeCJK_FullRight_symbol:N #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_and_FullLeft:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_and_FullLeft:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_left_tl #1 \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1 \xeCJK_punct_kern:NN \g_@@_last_punct_tl #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol #1 } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_and_FullRight:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_and_FullRight:N #1 { \xeCJK_get_punct_bounds:NN \c_@@_right_tl #1 \xeCJK_get_punct_kerning:oN \g_@@_last_punct_tl #1 \@@_punct_kern:NN \g_@@_last_punct_tl #1 \tl_gset:Nn \g_@@_last_punct_tl {#1} \xeCJK_FullRight_symbol:N #1 } % \end{macrocode} % \end{macro} % % \subsection{全角右标点后的断行} % % \begin{macro}{CheckFullRight} % \changes{v3.1.1}{2012/12/02}{处理全角右标点之后的断行问题。} % 选项设置。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CheckFullRight .choice: , CheckFullRight / true .code:n = { \cs_if_eq:NNF \xeCJK_FullRight_and_Boundary: \xeCJK_check_FullRight: { \cs_set_eq:NN \@@_save_FullRight_check: \xeCJK_FullRight_and_Boundary: \cs_set_eq:NN \@@_save_FullRight_symbol:N \xeCJK_FullRight_symbol:N \cs_set_eq:NN \xeCJK_FullRight_and_Boundary: \xeCJK_check_FullRight: \cs_set_eq:NN \xeCJK_FullRight_symbol:N \xeCJK_check_FullRight_symbol:Nw } } , CheckFullRight / false .code:n = { \cs_if_eq:NNT \xeCJK_FullRight_and_Boundary: \xeCJK_check_FullRight: { \cs_set_eq:NN \xeCJK_FullRight_and_Boundary: \@@_save_FullRight_check: \cs_set_eq:NN \xeCJK_FullRight_symbol:N \@@_save_FullRight_symbol:N } } , CheckFullRight .default:n = { true } } % \end{macrocode} % \end{macro} % % \begin{macro}{experiment/halfright-prebreakpenalty, experiment/punct-measure-fix, % experiment/boundary-register} % \begin{macrocode} \keys_define:nn { xeCJK / boundary-register } { command .code:n = { \@@_boundary_user_register_command:n {#1} } , command .value_required:n = true , strategy .code:n = { \@@_boundary_user_register_strategy:n {#1} } , strategy .value_required:n = true , mode .code:n = { \@@_boundary_user_register_mode:n {#1} } , mode .value_required:n = true } \keys_define:nn { xeCJK / options / experiment } { halfright-prebreakpenalty .bool_set:N = \l_@@_halfright_nobreak_bool , halfright-prebreakpenalty .default:n = true , halfright-prebreakpenalty .initial:n = false , punct-measure-fix .bool_gset:N = \g_@@_punct_measure_fix_bool , punct-measure-fix .default:n = true , punct-measure-fix .initial:n = false , boundary-register .code:n = { \@@_boundary_user_register:n {#1} } , boundary-register .value_required:n = true } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_FullRight_symbol:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_FullRight_symbol:N { \xeCJK_fallback_punct_symbol:NN \CJKpunctsymbol } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_FullRight:} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_FullRight: { \xeCJK_get_punct_bounds:No \c_@@_right_tl \g_@@_last_punct_tl \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \group_align_safe_begin: \token_case_meaning:NoTF \l_peek_token { \l_@@_no_break_cs_case_tl } { \group_align_safe_end: \xeCJK_no_break: \group_insert_after:N \xeCJK_no_break: } { \group_align_safe_end: } \exp_after:wN \xeCJK_punct_node:N \g_@@_last_punct_tl \xeCJK_class_group_end: \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_punct_boundary_guard: } \prg_generate_conditional_variant:Nnn \token_case_meaning:Nn { No } { TF , F } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_FullRight_symbol:Nw} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_FullRight_symbol:Nw #1 { \peek_remove_spaces:n { \@@_save_FullRight_symbol:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_cs_case_keys_define:nNNnn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_cs_case_keys_define:nNNnn #1#2#3#4#5 { \tl_new:N #2 \seq_new:N #3 \keys_define:nn { xeCJK / options } { #1 .code:n = { \seq_set_split:Nnn #3 { } {##1} \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5} } , #1+ .code:n = { \tl_map_inline:nn {##1} { \seq_if_in:NnF #3 {####1} { \seq_put_right:Nn #3 {####1} } } \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5} } , #1- .code:n = { \tl_map_inline:nn {##1} { \seq_remove_all:Nn #3 {####1} } \@@_update_cs_case_tl:NNnn #2#3 {#4} {#5} } } } \cs_new_protected:Npn \@@_update_cs_case_tl:NNnn #1#2#3#4 { \tl_clear:N #1 \seq_map_inline:Nn #2 { \tl_put_right:Nn #1 { {##1} {#3} } } #4 } % \end{macrocode} % \end{macro} % % \begin{macro}{NoBreakCS} % 设置不能在全角右标点之后断行的控制序列。 % \begin{macrocode} \xeCJK_cs_case_keys_define:nNNnn { NoBreakCS } \l_@@_no_break_cs_case_tl \l_@@_no_break_cs_seq { } { } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKnobreak} % \changes{v3.1.1}{2012/12/03}{增加 \tn{nobreak} 的 \pkg{xeCJK} 版本。} % 为保险起见,我们在这里用了一个循环。 % \begin{macrocode} \NewDocumentCommand \xeCJKnobreak { } { \bool_set_true:N \l_@@_tmp_bool \int_while_do:nNnn \tex_lastnodetype:D = \c_@@_glue_node { \bool_if:NTF \l_@@_tmp_bool { \bool_set_false:N \l_@@_tmp_bool \skip_set_eq:NN \l_@@_last_skip \tex_lastskip:D } { \skip_add:Nn \l_@@_last_skip \tex_lastskip:D } \tex_unskip:D } \xeCJK_if_last_node:TF { \dim_set_eq:NN \l_@@_tmp_dim \l_@@_last_kern_dim \xeCJK_if_last_node:TF { \@@_if_last_glue:T { \exp_args:NNNo \tex_unskip:D \xeCJK_no_break: \skip_horizontal:n { \skip_use:N \tex_lastskip:D } } \@@_make_node:N \l_@@_last_kern_dim } { } \@@_make_node:N \l_@@_tmp_dim } { } \xeCJK_no_break: \bool_if:NF \l_@@_tmp_bool { \skip_horizontal:N \l_@@_last_skip } } % \end{macrocode} % \end{macro} % % \subsection{段末孤字处理} % % \begin{macro}{CheckSingle} % 孤字处理功能选项。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CheckSingle .choice: , CheckSingle / true .code:n = { \cs_if_eq:NNF \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw { \cs_set_eq:NN \@@_check_single_save:N \xeCJK_CJK_and_CJK:N \cs_set_eq:NN \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw } } , CheckSingle / false .code:n = { \cs_if_eq:NNT \xeCJK_CJK_and_CJK:N \xeCJK_check_single:Nw { \cs_set_eq:NN \xeCJK_CJK_and_CJK:N \@@_check_single_save:N } } , CheckSingle .default:n = { true } , CJKchecksingle .meta:n = { CheckSingle = true } } % \end{macrocode} % \end{macro} % % \changes{v3.3.1}{2015/04/08}{新选项 \texttt{WidowPenalty}。} % % \begin{macro}{WidowPenalty} % 设置段末汉字的 penalty,默认值是 \num{10000}。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { WidowPenalty .int_set:N = \l_@@_widow_penalty_int , WidowPenalty .default:n = { 10 000 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_widow_penalty:} % 预防段末孤字而插入的 penalty,值为 \cs{l_@@_widow_penalty_int}。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_widow_penalty: { \tex_penalty:D \l_@@_widow_penalty_int } % \end{macrocode} % \end{macro} % % \changes{v3.4.7}{2017/03/20}{简化 \texttt{CheckSingle} 的实现,不再展开宏。} % % \begin{macro}[int]{\xeCJK_check_single:Nw} % \begin{macro}{\@@_check_single_end:N} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_single:Nw #1 { \group_align_safe_begin: \peek_catcode:NTF \c_catcode_letter_token { \xeCJK_check_single:NNw #1 } { \token_if_other:NTF \l_peek_token { \xeCJK_check_single:NNw } { \@@_check_single_end:N } #1 } } \cs_new_protected:Npn \@@_check_single_end:N { \group_align_safe_end: \@@_check_single_save:N } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_single:NNw} % \begin{macro}{\@@_check_single_aux:nNNw} % \changes{v3.1.1}{2012/12/04}{改进定义,减少使用 \texttt{peek} 函数的次数。} % \changes{v3.2.7}{2013/08/30}{与 \tn{CJKspace} 兼容。} % 使用 \cs{group_align_safe_begin:} 和 \cs{group_align_safe_end:} 是为了防止在表格 % 里面报错。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_single:NNw #1#2 { \xeCJK_peek_catcode_ignore_spaces:NTF \c_catcode_letter_token { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \bool_if:NTF \l_@@_reserve_space_bool { \@@_check_single_end:N #1 #2 ~ } { \@@_check_single_space:NN #1#2 } } { \@@_check_single_end:N #1 #2 } } { \token_if_other:NTF \l_peek_token { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \@@_check_single_space:NN } { \@@_check_single_end:N } } { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \@@_check_single_aux:nNNw { ~ } } { \@@_check_single_aux:nNNw { } } } #1 #2 } } \cs_new_protected:Npn \@@_check_single_aux:nNNw #1#2#3 { \token_if_cs:NTF \l_peek_token { \xeCJK_check_single_cs:NNn } { \xeCJK_check_single_end:NNnw } #2 #3 {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_single_end:NNnw} % \begin{macro}{\@@_check_single_end_aux:NNn,\@@_check_single_end_equation:NNnw} % \begin{macrocode} \cs_new_protected:Npn \@@_check_single_end_aux:NNn #1#2#3 { \@@_check_single_end:N #1 #2 #3 } \cs_new_eq:NN \xeCJK_check_single_end:NNnw \@@_check_single_end_aux:NNn \cs_new_protected:Npn \@@_check_single_end_equation:NNnw { \token_if_math_toggle:NTF \l_peek_token { \xeCJK_check_single_equation:NNnNw } { \@@_check_single_end_aux:NNn } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{PlainEquation} % \changes{v3.1.1}{2012/12/06}{增加 \texttt{PlainEquation} 选项。} % \begin{macrocode} \keys_define:nn { xeCJK / options } { PlainEquation .choice: , PlainEquation / true .code:n = { \cs_set_eq:NN \xeCJK_check_single_end:NNnw \@@_check_single_end_equation:NNnw } , PlainEquation / false .code:n = { \cs_set_eq:NN \xeCJK_check_single_end:NNnw \@@_check_single_end_aux:NNn } , PlainEquation .default:n = { true } , } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_check_single_space:NN} % \changes{v3.1.1}{2012/12/13} % {\texttt{CheckSingle} 支持段末“汉字$+$汉字$+$空格$+$汉字/标点”的形式。} % \changes{v3.1.2}{2012/12/27} % {使用 \cs{xeCJK_if_CJK_class:NTF} 来代替 \cs{int_case:nnn} 判断是否是 CJK 字符类。} % \begin{macrocode} \cs_new_protected:Npn \@@_check_single_space:NN #1#2 { \xeCJK_if_CJK_class:NTF #2 { \xeCJK_if_CJK_class:NTF \l_peek_token { \@@_check_single_end:N #1 #2 } { \@@_check_single_end:N #1 #2 ~ } } { \@@_check_single_end:N #1 #2 ~ } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_single_equation:NNnNw} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_single_equation:NNnNw #1#2#3#4 { \peek_catcode:NTF \c_math_toggle_token { \xeCJK_widow_penalty: \@@_check_single_end:N #1 \xeCJK_make_node:n { CJK-widow } #2 #4 } { \@@_check_single_end:N #1 #2#3#4 } } % \end{macrocode} % \end{macro} % % \changes{v3.2.3}{2013/06/11} % {解决 \texttt{CheckSingle} 选项与 \pkg{tablists} 宏包的冲突。} % \changes{v3.2.4}{2013/06/26} % {解决使用 \texttt{CheckSingle} 时,某些 \tn{CJKglue} 不能被正确加入的问题。} % % \begin{macro}[int]{\xeCJK_check_single_cs:NNn} % \changes{v3.3.1}{2015/04/06}{补充可能遗漏的空格。} % 在使用 \texttt{CheckSingle} 选项时,在 \package{tablists} 宏包定义的 % \env{tabenum} 环境中会出现下面的错误: % \begin{verbatim} % ! Forbidden control sequence found while scanning use of \use_ii:nn. % % \par % l.10 \item % \end{verbatim} % 原因在于 \env{tabenum} 实际上是一个 \TeX 对齐环境(\tn{halign}),\tn{par} 在 % 其中被重定义为 \tn{cr}。而在下面 \cs{token_case_meaning:NnF} 的分支里有对 \tn{par} 的 % \tn{ifx} 判断。解决办法是将判断用 \cs{group_align_safe_begin:} 和 % \cs{group_align_safe_end:} 包起来。或者改用原语 \cs{tex_par:D} 作为判断条件。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_single_cs:NNn #1#2#3 { \token_case_meaning:NoF \l_peek_token { \l_@@_check_single_cs_case_tl } { \use_iii:nnn } { \xeCJK_check_single_env:nnNn } { \xeCJK_widow_penalty: \@@_check_single_end:N #1 \xeCJK_make_node:n { CJK-widow } #2#3 } { \@@_check_single_end:N #1 #2#3 } } \tl_new:N \l_@@_check_single_cs_case_tl % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_check_single_env:nnNn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_check_single_env:nnNn #1#2#3#4 { \str_case_e:noTF {#4} { \l_@@_inline_env_case_tl } {#2} {#1} #3 {#4} } \prg_generate_conditional_variant:Nnn \str_case_e:nn { no } { TF } % \end{macrocode} % \end{macro} % % \changes{v3.1.1}{2012/12/04}{增加 \texttt{NewLineCS} 和 \texttt{EnvCS} 选项。} % % \begin{macro}{NewLineCS} % \begin{macrocode} \xeCJK_cs_case_keys_define:nNNnn { NewLineCS } \l_@@_new_line_cs_case_tl \l_@@_new_line_cs_seq { \use_ii:nnn } { \tl_concat:NNN \l_@@_check_single_cs_case_tl \l_@@_new_line_cs_case_tl \l_@@_env_cs_case_tl } % \end{macrocode} % \end{macro} % % \begin{macro}{EnvCS} % \begin{macrocode} \xeCJK_cs_case_keys_define:nNNnn { EnvCS } \l_@@_env_cs_case_tl \l_@@_env_cs_seq { \use:n } { \tl_concat:NNN \l_@@_check_single_cs_case_tl \l_@@_new_line_cs_case_tl \l_@@_env_cs_case_tl } % \end{macrocode} % \end{macro} % % \begin{macro}{InlineEnv} % \changes{v3.1.1}{2012/12/05}{改变行内环境的设置方式,从而使用 \cs{str_case_x:nnn} % 代替原来的 \cs{clist_if_in:NnTF} 来判断是否是行内环境。} % \begin{macrocode} \keys_define:nn { xeCJK / options } { InlineEnv .code:n = { \seq_set_from_clist:Nn \l_@@_inline_env_seq {#1} \@@_update_inline_env_case_tl: } , InlineEnv+ .code:n = { \clist_map_inline:nn {#1} { \seq_if_in:NnF \l_@@_inline_env_seq {##1} { \seq_put_right:Nn \l_@@_inline_env_seq {##1} } } \@@_update_inline_env_case_tl: } , InlineEnv- .code:n = { \clist_map_inline:nn {#1} { \seq_remove_all:Nn \l_@@_inline_env_seq {##1} } \@@_update_inline_env_case_tl: } } \seq_new:N \l_@@_inline_env_seq % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_update_inline_env_case_tl:} % \begin{macrocode} \cs_new_protected:Npn \@@_update_inline_env_case_tl: { \tl_clear:N \l_@@_inline_env_case_tl \seq_map_inline:Nn \l_@@_inline_env_seq { \tl_put_right:Nn \l_@@_inline_env_case_tl { {##1} { } } } } \tl_new:N \l_@@_inline_env_case_tl % \end{macrocode} % \end{macro} % % \subsection{增加 CJK 子分区} % % \begin{variable}{\g_@@_CJK_sub_class_seq} % \begin{macrocode} \seq_new:N \g_@@_CJK_sub_class_seq % \end{macrocode} % \end{variable} % % \begin{macro}{\xeCJKDeclareSubCJKBlock} % 声明 CJK 子区范围,|#1| 为自定义名称,|#2| 为子区的 |Unicode| 范围。 % \begin{macrocode} \NewDocumentCommand \xeCJKDeclareSubCJKBlock { s > { \TrimSpaces } m m } { \xeCJK_declare_sub_char_class:nen { CJK } {#2} {#3} \IfBooleanT {#1} { \xeCJKResetPunctClass } } \@onlypreamble \xeCJKDeclareSubCJKBlock % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKCancelSubCJKBlock,\xeCJKRestoreSubCJKBlock} % 取消和恢复对 CJK 子区的声明。 % \begin{macrocode} \bool_new:N \l_@@_sub_cancel_bool \NewDocumentCommand \xeCJKCancelSubCJKBlock { s m } { \bool_if:NF \l_@@_sub_cancel_bool { \bool_set_true:N \l_@@_sub_cancel_bool \@@_sub_restore_or_cancel:e {#2} \IfBooleanT {#1} { \xeCJKResetPunctClass } } } \NewDocumentCommand \xeCJKRestoreSubCJKBlock { s m } { \bool_if:NT \l_@@_sub_cancel_bool { \bool_set_false:N \l_@@_sub_cancel_bool \@@_sub_restore_or_cancel:e {#2} \IfBooleanT {#1} { \xeCJKResetPunctClass } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_sub_restore_or_cancel:n} % \begin{macrocode} \cs_new_protected:Npn \@@_sub_restore_or_cancel:n #1 { \clist_map_inline:nn {#1} { \int_if_exist:cTF { \@@_class_csname:n { CJK/##1 } } { \xeCJK_declare_char_class:nn { CJK \bool_if:NF \l_@@_sub_cancel_bool { /##1 } } { \use:c { g_@@_CJK/##1_range_clist } } } { \@@_error:ne { SubBlock-undefined } {##1} } } } \cs_generate_variant:Nn \@@_sub_restore_or_cancel:n { e } \@@_msg_new:nn { SubBlock-undefined } { The~CJK~sub~block~`#1'~is~undefined.\\\\ Try~to~use~\token_to_str:N \xeCJKDeclareSubCJKBlock \ to~declare~it. } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_declare_sub_char_class:nnn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_declare_sub_char_class:nnn #1#2#3 { \int_if_exist:cF { \@@_class_csname:n { #1/#2 } } { \xeCJK_new_class:n { #1/#2 } \@@_set_sub_class_toks:nn {#1} {#2} \xeCJK_new_sub_key:n {#2} } \xeCJK_declare_char_class:nn { #1/#2 } {#3} } \cs_generate_variant:Nn \xeCJK_declare_sub_char_class:nnn { ne } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_sub_class_toks:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_set_sub_class_toks:nn #1#2 { \seq_map_inline:Nn \g_@@_base_class_seq { \xeCJK_copy_inter_class_toks:nnnn { #1/#2 } {##1} {#1} {##1} \xeCJK_copy_inter_class_toks:nnnn {##1} { #1/#2 } {##1} {#1} \str_if_eq:nnTF {##1} { CJK } { \xeCJK_pre_inter_class_toks:nnn {##1} { #1/#2 } { \@@_switch_font:nn {#1} {#2} } } { \xeCJK_replace_inter_class_toks:nnnn {##1} { #1/#2 } { \xeCJK_fallback_symbol:NN } { \@@_switch_font:nn {#1} {#2} \xeCJK_fallback_symbol:NN } } } \xeCJK_copy_inter_class_toks:nnnn { #1/#2 } { #1/#2 } {#1} {#1} \seq_map_inline:Nn \g_@@_CJK_sub_class_seq { \xeCJK_copy_inter_class_toks:nnnn { #1/#2 } { #1/##1 } {#1} {#1} \xeCJK_copy_inter_class_toks:nnnn { #1/##1 } { #1/#2 } {#1} {#1} \xeCJK_pre_inter_class_toks:nnn { #1/#2 } { #1/##1 } { \@@_switch_font:nn {#2} {##1} } \xeCJK_pre_inter_class_toks:nnn { #1/##1 } { #1/#2 } { \@@_switch_font:nn {##1} {#2} } } \seq_gput_right:Nn \g_@@_CJK_sub_class_seq {#2} \@@_save_CJK_class:n { #1/#2 } \clist_map_inline:nn { CJK , FullLeft , FullRight , HangulJamo , HangulJamoL , HangulJamoV , HangulJamoT , CJStarter } { \xeCJK_pre_inter_class_toks:nnn { #1/#2 } {##1} { \@@_switch_font:nn {#2} {#1} } } } % \end{macrocode} % \end{macro} % % \subsection{标点处理} % % \changes{v3.2.7}{2013/08/22}{实现自定义行首/尾标点符号宽度功能。} % \changes{v3.7.4}{2019/05/31}{简化行首/尾标点符号宽度的实现。} % % \tn{XeTeXglyphbounds} 可以得到一个字符的左右边距,用于标点压缩。如果它不可用,则 % 在文档中只能使用 |plain| 这一标点格式原样输出标点。 % \begin{macrocode} \cs_if_exist:NF \tex_XeTeXglyphbounds:D { \@@_msg_new:nn { XeTeX-too-old } { \token_to_str:N \tex_XeTeXglyphbounds:D \ is~not~defined.\\ CJK~punctuation~kerning~will~not~be~available.\\\\ You~have~to~update~XeTeX~to~the~version~0.9995.0~or~later. } \@@_error:n { XeTeX-too-old } \AtEndOfPackage { \keys_define:nn { xeCJK / options } { PunctStyle .code:n = { \@@_error:ne { punct-style-unknown } {#1} } } \seq_gclear:N \g_@@_punct_style_seq \@@_set_punct_style:n { plain } } } % \end{macrocode} % % \begin{macro}{\xeCJKsetwidth} % 手动设置参数中的标点符号的宽度。 % \begin{macrocode} \NewDocumentCommand \xeCJKsetwidth { s m m } { \IfBooleanTF {#1} { \tl_map_inline:en {#2} { \tl_if_exist:cF { g_@@_punct_width/##1/tl } { \tl_new:c { g_@@_punct_width/##1/tl } } \tl_gset:cn { g_@@_punct_bound_width/##1/tl } {#3} } } { \tl_map_inline:en {#2} { \tl_if_exist:cF { g_@@_punct_width/##1/tl } { \tl_new:c { g_@@_punct_width/##1/tl } } \tl_gset:cn { g_@@_punct_width/##1/tl } {#3} } } } \@onlypreamble \xeCJKsetwidth \cs_generate_variant:Nn \tl_map_inline:nn { e } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKsetkern} % 手动设置相邻标点的距离。 % \begin{macrocode} \NewDocumentCommand \xeCJKsetkern { m m m } { \tl_gset:cn { g_@@_punct/kern/#1/#2/tl } {#3} } \@onlypreamble \xeCJKsetkern % \end{macrocode} % \end{macro} % % \begin{variable}{\c_@@_left_tl,\c_@@_right_tl} % \begin{macrocode} \tl_const:Nn \c_@@_left_tl { left } \tl_const:Nn \c_@@_right_tl { right } % \end{macrocode} % \end{variable} % % \changes{v3.2.12}{2014/05/12}{新增 \texttt{RubberPunctSkip} 选项。} % \changes{v3.4.0}{2016/05/13}{\texttt{RubberPunctSkip} 选项有新的值 \texttt{plus} 和 \texttt{minus}。} % \changes{v3.10.2}{2026/07/04}{新增 \texttt{PoZheHaoLigature} 选项, % 支持字体的破折号合字功能(\#382)。} % \changes{v3.10.2}{2026/07/05}{新增 \texttt{LatinPunct} 选项, % 中西文共用的弯引号、间隔号与省略号可以切换为西文标点(\#389、\#431)。} % \changes{v3.10.3}{2026/07/12}{新增 \texttt{CJLineBreak} 选项, % 为日文小假名提供可选严格行首禁则(\#165)。} % % \begin{macro} % {AllowBreakBetweenPuncts,KaiMingPunct,LongPunct,NoBreakLongPunct, % MiddlePunct,PoZheHaoLigature,LatinPunct,CJLineBreak, % PunctWidth,PunctBoundWidth,RubberPunctSkip} % 相关选项声明。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { AllowBreakBetweenPuncts .choice: , AllowBreakBetweenPuncts / true .code:n = { \bool_set_true:N \l_@@_punct_breakable_bool \cs_set_eq:NN \xeCJK_punct_kern:NN \@@_punct_breakable_kern:NN \cs_set_eq:NN \@@_punct_bound_kern:N \@@_punct_bound_breakable_kern:N } , AllowBreakBetweenPuncts / false .code:n = { \bool_set_false:N \l_@@_punct_breakable_bool \cs_set_eq:NN \xeCJK_punct_kern:NN \@@_punct_kern:NN \cs_set_eq:NN \@@_punct_bound_kern:N \@@_nobreak_hskip:N } , AllowBreakBetweenPuncts .default:n = { true } , KaiMingPunct .code:n = { \@@_set_special_punct:nn { mixed_width } {#1} } , KaiMingPunct+ .code:n = { \@@_add_special_punct:nn { mixed_width } {#1} } , KaiMingPunct- .code:n = { \@@_sub_special_punct:nn { mixed_width } {#1} } , LongPunct .code:n = { \@@_set_special_punct:nn { long } {#1} } , LongPunct+ .code:n = { \@@_add_special_punct:nn { long } {#1} } , LongPunct- .code:n = { \@@_sub_special_punct:nn { long } {#1} } , NoBreakLongPunct .code:n = { \@@_set_special_punct:nn { nobreak_long } {#1} } , NoBreakLongPunct+ .code:n = { \@@_add_special_punct:nn { nobreak_long } {#1} } , NoBreakLongPunct- .code:n = { \@@_sub_special_punct:nn { nobreak_long } {#1} } , MiddlePunct .code:n = { \@@_set_special_punct:nn { middle } {#1} } , MiddlePunct+ .code:n = { \@@_add_special_punct:nn { middle } {#1} } , MiddlePunct- .code:n = { \@@_sub_special_punct:nn { middle } {#1} } , PoZheHaoLigature .choice: , PoZheHaoLigature / true .code:n = { \bool_set_true:N \l_@@_pozhehao_ligature_bool \xeCJK_declare_char_class:nn { PoZheHao } { "2014 , "2015 } } , PoZheHaoLigature / false .code:n = { \bool_set_false:N \l_@@_pozhehao_ligature_bool \xeCJK_declare_char_class:nn { FullRight } { "2014 } \xeCJK_declare_char_class:nn { Default } { "2015 } } , PoZheHaoLigature .default:n = { true } , LatinPunct .choice: , LatinPunct / true .code:n = { \bool_set_true:N \l_@@_latin_punct_bool \xeCJK_declare_char_class:nn { HalfLeft } { "2018 , "201C } \xeCJK_declare_char_class:nn { HalfRight } { "00B7 , "2019 , "201D , "2025 , "2026 , "2027 } } , LatinPunct / false .code:n = { \bool_set_false:N \l_@@_latin_punct_bool \xeCJK_declare_char_class:nn { FullLeft } { "2018 , "201C } \xeCJK_declare_char_class:nn { FullRight } { "00B7 , "2019 , "201D , "2025 , "2026 , "2027 } } , LatinPunct .default:n = { true } , CJLineBreak .choices:nn = { normal , strict } { \str_if_eq:nnTF {#1} { strict } { \bool_set_true:N \l_@@_CJ_strict_bool \xeCJK_declare_char_class:nN { CJStarter } \c_@@_CJ_chars_clist } { \bool_set_false:N \l_@@_CJ_strict_bool \xeCJK_declare_char_class:nN { CJK } \c_@@_CJ_chars_clist } } , CJLineBreak .initial:n = { normal } , PunctWidth .tl_gset:N = \g_@@_punct_width_tl , PunctBoundWidth .tl_gset:N = \g_@@_punct_bound_width_tl , PunctWidth .value_required:n = true , PunctBoundWidth .value_required:n = true , RubberPunctSkip .choice: , RubberPunctSkip .default:n = { true } , RubberPunctSkip / true .code:n = { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip:nNN } , RubberPunctSkip / plus .code:n = { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip_plus:nNN } , RubberPunctSkip / minus .code:n = { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip_minus:nNN } , RubberPunctSkip / false .code:n = { \cs_set_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_dim:nNN } } \bool_new:N \l_@@_punct_breakable_bool % \end{macrocode} % \end{macro} % % 相关选项定义的辅助函数。 % \begin{macrocode} \clist_new:N \g_@@_special_punct_clist \clist_gset:Nn \g_@@_special_punct_clist { mixed_width , long , nobreak_long , middle } \cs_new:Npn \@@_special_punct_seq:n #1 { g_@@_special_punct_#1_seq } \cs_new:Npn \@@_special_punct_tl:nN #1#2 { g_@@_special_punct_#1_#2_tl } \clist_map_inline:Nn \g_@@_special_punct_clist { \seq_new:c { \@@_special_punct_seq:n {#1} } } \cs_new_protected:Npn \@@_set_special_punct:nn #1#2 { \seq_map_inline:cn { \@@_special_punct_seq:n {#1} } { \cs_undefine:c { \@@_special_punct_tl:nN {#1} {##1} } } \seq_gclear:c { \@@_special_punct_seq:n {#1} } \tl_map_inline:en {#2} { \tl_new:c { \@@_special_punct_tl:nN {#1} {##1} } \seq_gput_right:cn { \@@_special_punct_seq:n {#1} } {##1} } } \cs_new_protected:Npn \@@_add_special_punct:nn #1#2 { \tl_map_inline:en {#2} { \seq_if_in:cnF { \@@_special_punct_seq:n {#1} } {##1} { \tl_new:c { \@@_special_punct_tl:nN {#1} {##1} } \seq_gput_right:cn { \@@_special_punct_seq:n {#1} } {##1} } } } \cs_new_protected:Npn \@@_sub_special_punct:nn #1#2 { \tl_map_inline:en {#2} { \cs_undefine:c { \@@_special_punct_tl:nN {#1} {##1} } \seq_gremove_all:cn { \@@_special_punct_seq:n {#1} } {##1} } } % \end{macrocode} % % 判断一个标点符号是否为全角右标点和长标点符号。 % \changes{v3.10.2}{2026/07/04}{\texttt{PoZheHao} 类字符同样按全角右标点 % 处理(\#382)。} % 注意,|PoZheHao| 类的破折号同样按全角右标点处理, % 保证它与其他标点相邻时使用正确的间距参数。 % \begin{macrocode} \prg_new_conditional:Npnn \@@_punct_if_right:N #1 { p , T , F , TF } { \if_int_compare:w \xeCJK_token_value_class:N #1 = \xeCJK_class_num:n { FullRight } \prg_return_true: \else: \if_int_compare:w \xeCJK_token_value_class:N #1 = \xeCJK_class_num:n { PoZheHao } \prg_return_true: \else: \prg_return_false: \fi: \fi: } \clist_map_inline:Nn \g_@@_special_punct_clist { \exp_args:Nc \prg_new_conditional:Npnn { @@_punct_if_#1:N } ##1 { p , T , F , TF } { \if_cs_exist:w \@@_special_punct_tl:nN {#1} {##1} \cs_end: \prg_return_true: \else: \prg_return_false: \fi: } } % \end{macrocode} % % 一些用于记录的辅助函数。 % \begin{macrocode} \cs_new:Npn \@@_punct_csname:n #1 { c_@@_\l_xeCJK_current_punct_font_tl/\l_xeCJK_punct_style_tl/#1/tl } \cs_new:Npn \@@_use_punct_dim:nN #1#2 { \use:c { \@@_punct_csname:n { dim/#1/#2 } } } \cs_new:Npn \@@_use_punct_dim:nNN #1#2#3 { \use:c { \@@_punct_csname:n { dim/#1/#2/#3 } } } \cs_new:Npn \@@_use_punct_skip:nNN #1#2#3 { \use:c { \@@_punct_csname:n { skip/#1/#2/#3 } } } \cs_new:Npn \@@_use_punct_skip_plus:nNN #1#2#3 { \use:c { \@@_punct_csname:n { skip/plus/#1/#2/#3 } } } \cs_new:Npn \@@_use_punct_skip_minus:nNN #1#2#3 { \use:c { \@@_punct_csname:n { skip/minus/#1/#2/#3 } } } \cs_new_protected:Npn \@@_save_punct_dim:nNn #1#2 { \@@_save_punct_width_aux:nnnn { dim } {#1} { #1/#2 } } \cs_new_protected:Npn \@@_save_punct_dim:nNNn #1#2#3 { \@@_save_punct_width_aux:nnnn { dim } {#1} { #1/#2/#3 } } \cs_new_protected:Npn \@@_save_punct_skip:nNNn #1#2#3#4 { \@@_save_punct_width_aux:nnnn { skip } {#1} { #1/#2/#3 } {#4} \@@_save_punct_width_aux:nnnn { skip } {#1} { plus/#1/#2/#3 } {#4} \@@_save_punct_width_aux:nnnn { skip } {#1} { minus/#1/#2/#3 } {#4} } \cs_new_protected:Npn \@@_save_punct_skip:nNNnnn #1#2#3#4#5#6 { \exp_last_unbraced:Ne \@@_save_punct_skip_aux:nnnnn { {#1} { #1/#2/#3 } { \dim_eval:n {#4} } { \dim_max:nn { \c_zero_dim } {#5} } { \dim_max:nn { \c_zero_dim } {#6} } } } \cs_new_protected:Npn \@@_save_punct_skip_aux:nnnnn #1#2#3#4#5 { \@@_save_punct_width_aux:nnnn { skip } {#1} {#2} { #3 ~ plus ~ #4 ~ minus ~ #5 ~ } \@@_save_punct_width_aux:nnnn { skip } {#1} { plus/#2 } { #3 ~ plus ~ #4 ~ } \@@_save_punct_width_aux:nnnn { skip } {#1} { minus/#2 } { #3 ~ minus ~ #5 ~ } } \cs_new_protected:Npn \@@_save_punct_width_aux:nnnn #1#2#3#4 { \@@_save_punct_width_aux:cen { \@@_punct_csname:n { #1/#3 } } { \use:c { #1_eval:n } {#4} } {#2} } \cs_new_protected:Npn \@@_save_punct_width_aux:Nnn #1#2#3 { \tl_const:Nn #1 {#2} \str_if_eq:nnT {#3} { glue } { \prop_gput:Nnn \g_@@_punct_skip_prop {#2} { } } } \prop_new:N \g_@@_punct_skip_prop \prop_gput:Non \g_@@_punct_skip_prop { \skip_use:N \c_zero_skip } { } \cs_generate_variant:Nn \@@_save_punct_width_aux:Nnn { ce } \cs_new_eq:NN \@@_use_dim_or_skip:nNN \@@_use_punct_skip:nNN % \end{macrocode} % % \changes{v3.1.0}{2012/11/14}{使用 \pkg{xtemplate} 宏包的机制来组织标点符号的处理。} % % 定义标点处理模板。 % \begin{macrocode} \DeclareObjectType { xeCJK / punctuation } { 0 } % \end{macrocode} % % \begin{macrocode} \DeclareTemplateInterface { xeCJK / punctuation } { basic } { 0 } { enabled-global-setting : boolean = true , fixed-punct-width : length = \c_max_dim , fixed-punct-ratio : real = \c_one_fp , mixed-punct-width : length = \KeyValue { fixed-punct-width } , mixed-punct-ratio : real = \KeyValue { fixed-punct-ratio } , middle-punct-width : length = \KeyValue { fixed-punct-width } , middle-punct-ratio : real = \KeyValue { fixed-punct-ratio } , fixed-margin-width : length = \c_max_dim , fixed-margin-ratio : real = \c_one_fp , mixed-margin-width : length = \KeyValue { fixed-margin-width } , mixed-margin-ratio : real = \KeyValue { fixed-margin-ratio } , middle-margin-width : length = \KeyValue { fixed-margin-width } , middle-margin-ratio : real = \KeyValue { fixed-margin-ratio } , bound-punct-width : length = \c_max_dim , bound-punct-ratio : real = \c_nan_fp , bound-margin-width : length = \c_max_dim , bound-margin-ratio : real = \c_zero_fp , enabled-hanging : boolean = false , add-min-bound-to-margin : boolean = false , optimize-margin : boolean = false , margin-minimum : length = \c_zero_dim , enabled-kerning : boolean = true , enabled-left-right-kerning : boolean = true , min-bound-to-kerning : boolean = false , kerning-total-width : length = \c_max_dim , kerning-total-ratio : real = 0.75 , optimize-kerning : boolean = false , same-align-margin : length = \c_max_dim , same-align-ratio : real = \c_nan_fp , different-align-margin : length = \c_max_dim , different-align-ratio : real = \c_nan_fp , kerning-margin-width : length = \c_max_dim , kerning-margin-ratio : real = \c_one_fp , kerning-margin-minimum : length = \c_zero_dim } % \end{macrocode} % % \begin{macrocode} \DeclareTemplateCode { xeCJK / punctuation } { basic } { 0 } { enabled-global-setting = \l_@@_enabled_global_setting_bool , fixed-punct-width = \l_@@_fixed_punct_width_dim , fixed-punct-ratio = \l_@@_fixed_punct_ratio_fp , mixed-punct-width = \l_@@_mixed_punct_width_dim , mixed-punct-ratio = \l_@@_mixed_punct_ratio_fp , middle-punct-width = \l_@@_middle_punct_width_dim , middle-punct-ratio = \l_@@_middle_punct_ratio_fp , fixed-margin-width = \l_@@_fixed_margin_width_dim , fixed-margin-ratio = \l_@@_fixed_margin_ratio_fp , mixed-margin-width = \l_@@_mixed_margin_width_dim , mixed-margin-ratio = \l_@@_mixed_margin_ratio_fp , middle-margin-width = \l_@@_middle_margin_width_dim , middle-margin-ratio = \l_@@_middle_margin_ratio_fp , bound-punct-width = \l_@@_bound_punct_width_dim , bound-punct-ratio = \l_@@_bound_punct_ratio_fp , bound-margin-width = \l_@@_bound_margin_width_dim , bound-margin-ratio = \l_@@_bound_margin_ratio_fp , enabled-hanging = \l_@@_enabled_hanging_bool , add-min-bound-to-margin = \l_@@_add_min_bound_to_margin_bool , optimize-margin = \l_@@_optimize_margin_bool , margin-minimum = \l_@@_margin_minimum_dim , enabled-kerning = \l_@@_enabled_kerning_bool , enabled-left-right-kerning = \l_@@_enabled_left_right_kerning_bool , min-bound-to-kerning = \l_@@_min_bound_to_kerning_bool , kerning-total-width = \l_@@_kerning_total_width_dim , kerning-total-ratio = \l_@@_kerning_total_ratio_fp , optimize-kerning = \l_@@_optimize_kerning_bool , same-align-margin = \l_@@_same_align_margin_dim , same-align-ratio = \l_@@_same_align_ratio_fp , different-align-margin = \l_@@_different_align_margin_dim , different-align-ratio = \l_@@_different_align_ratio_fp , kerning-margin-width = \l_@@_kerning_margin_width_dim , kerning-margin-ratio = \l_@@_kerning_margin_ratio_fp , kerning-margin-minimum = \l_@@_kerning_margin_minimum_dim } { \AssignTemplateKeys } % \end{macrocode} % % \begin{macro}[int]{\xeCJK_get_punct_bounds:NN} % |#1| 为 \cs{c_@@_left_tl} 或 \cs{c_@@_right_tl},|#2| 为标点符号。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_get_punct_bounds:NN #1#2 { \tl_if_exist:cF { \@@_punct_csname:n { dim/glue/#1/#2 } } { \@@_get_punct_bounds_aux:NN #1 #2 } } \cs_new_protected:Npn \@@_get_punct_bounds_aux:NN { \tl_if_eq:NNTF \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl { \@@_save_punct_margin_plain:NN } { \@@_save_punct_margin:NN } } \cs_new_protected:Npn \xeCJK_get_punct_bounds:No { \exp_last_unbraced:NNo \xeCJK_get_punct_bounds:NN } \cs_new_protected:Npn \@@_save_punct_margin_plain:NN #1#2 { \@@_save_punct_dim:nNNn { glue } #1 #2 { \c_zero_dim } \@@_save_punct_dim:nNNn { offset } #1 #2 { \c_zero_dim } \@@_save_punct_dim:nNNn { margin } #1 #2 { \c_zero_dim } \@@_save_punct_dim:nNNn { rule } \c_@@_left_tl {#2} { \c_zero_dim } \@@_save_punct_dim:nNNn { rule } \c_@@_right_tl {#2} { \c_zero_dim } \@@_save_punct_dim:nNNn { bound } \c_@@_left_tl {#2} { \c_zero_dim } \@@_save_punct_dim:nNNn { bound } \c_@@_right_tl {#2} { \c_zero_dim } \@@_save_punct_skip:nNNn { glue } #1 #2 { \c_zero_skip } } \cs_new_protected:Npn \@@_save_punct_margin:NN #1#2 { \group_begin: \xeCJK_select_punct_font: \xeCJK_fallback_punct_symbol:NN \xeCJK_calc_punct_dimen:N #2 \group_end: \dim_set:Nn \l_@@_bound_dim { \@@_use_punct_dim:nNN { bound } #1 #2 } \tl_if_eq:NNTF #1 \c_@@_right_tl { \tl_set_eq:NN \l_@@_reverse_tl \c_@@_left_tl } { \tl_set_eq:NN \l_@@_reverse_tl \c_@@_right_tl } \dim_set:Nn \l_@@_reverse_bound_dim { \@@_use_punct_dim:nNN { bound } \l_@@_reverse_tl #2 } \UseInstance { xeCJK / punctuation } { \l_xeCJK_punct_style_tl } \xeCJK_punct_margin_process:NN #1 #2 \xeCJK_punct_offset_process:NN #1 #2 \@@_punct_if_long:NT #2 { \@@_long_punct_kerning:N #2 } } \tl_new:N \l_@@_reverse_tl \dim_new:N \l_@@_bound_dim \dim_new:N \l_@@_reverse_bound_dim % \end{macrocode} % \end{macro} % % \changes{v3.7.0}{2018/03/12}{修正长标点被隔开时的压缩处理错误。} % % \begin{macro}[int]{\@@_long_punct_kerning:N} % \changes{v3.4.3}{2016/10/27}{考虑破折号边界为负值的情况。} % \changes{v3.10.2}{2026/07/04}{破折号中间的压缩量改为多路取大, % 保证两个 \texttt{U+2014} 的总宽不超过两个汉字宽(\#382)。} % 相同长标点压缩。对于破折号(\texttt{U+2014}),计算两标点之间的空白, % 保证它中间不被断开,同时保证其总宽(字面或字框)不超过两个汉字宽。 % 压缩量取以下三者的最大值 % (\texttt{width} 为字框宽,\texttt{dimen} 为字面宽,$F$ 为当前字号): % \begin{itemize} % \item \texttt{bound} 之和,用于字面窄于字框的字体(如中易宋体); % \item $\texttt{dimen} + \texttt{width} - 2F$,用于字面溢出字框的字体 % (如方正兰亭黑); % \item $2\,\texttt{width} - 2F$,用于字框宽于字号的字体(如微软雅黑)。 % \end{itemize} % 注意,破折号的边界可能为负值(比如方正新书宋),此时不必压缩, % 故取值不小于零。省略号(\texttt{U+2025}、\texttt{U+2026})连用时 % 不需要压缩,其余长标点保持按边空压缩。 % \begin{macrocode} \cs_new_protected:Npn \@@_long_punct_kerning:N #1 { \dim_set:Nn \l_@@_tmp_dim { \dim_max:nn { \l_@@_bound_dim + \l_@@_reverse_bound_dim } { \c_zero_dim } } \@@_save_punct_dim:nNNn { bound_width } #1 #1 { \l_@@_tmp_dim } \dim_set:Nn \l_@@_tmp_dim { \str_case:nnF {#1} { { ^^^^2014 } { - \dim_max:nn { \l_@@_tmp_dim } { \dim_max:nn { \@@_use_punct_dim:nN { dimen } #1 + \@@_use_punct_dim:nN { width } #1 - \f@size pt - \f@size pt } { \@@_use_punct_dim:nN { width } #1 + \@@_use_punct_dim:nN { width } #1 - \f@size pt - \f@size pt } } } { ^^^^2025 } { \c_zero_dim } { ^^^^2026 } { \c_zero_dim } } { -\l_@@_tmp_dim } } \@@_save_punct_dim:nNNn { kern } #1 #1 { \l_@@_tmp_dim } \@@_save_punct_skip:nNNn { kern } #1 #1 { \l_@@_tmp_dim } \dim_add:Nn \l_@@_tmp_dim { \dim_max:nn { \l_@@_bound_dim } { \c_zero_dim } } \@@_save_punct_dim:nNNn { bound_kern } #1 #1 { \l_@@_tmp_dim } \@@_save_punct_skip:nNNn { bound_kern } #1 #1 { \l_@@_tmp_dim } } % \end{macrocode} % \end{macro} % % \begin{macro}[int] % {\xeCJK_get_punct_kerning:NN,\@@_save_punct_kerning_plain:NN, % \@@_save_punct_kerning:NN,\@@_save_left_right_kerning:NN} % 标点压缩。若当前格式关闭左标点到右标点的压缩,则保存零宽且无伸缩量的间距; % 用户通过 \cs{xeCJKsetkern} 显式设置的字符对仍然优先,除非当前格式同时关闭 % 全局设置。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_get_punct_kerning:NN #1#2 { \tl_if_exist:cF { \@@_punct_csname:n { dim/kern/#1/#2 } } { \tl_if_eq:NNTF \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl { \@@_save_punct_kerning_plain:NN } { \@@_save_punct_kerning:NN } #1 #2 } } \cs_new_protected:Npn \xeCJK_get_punct_kerning:oN { \exp_after:wN \xeCJK_get_punct_kerning:NN } \cs_new_protected:Npn \@@_save_punct_kerning_plain:NN #1#2 { \@@_save_punct_dim:nNNn { kern } #1 #2 { \c_zero_dim } \@@_save_punct_dim:nNNn { bound_kern } #1 #2 { \c_zero_dim } \@@_save_punct_dim:nNNn { bound_width } #1 #2 { \c_zero_dim } \@@_save_punct_skip:nNNn { kern } #1 #2 { \c_zero_skip } \@@_save_punct_skip:nNNn { bound_kern } #1 #2 { \c_zero_skip } } \cs_new_protected:Npn \@@_save_punct_kerning:NN #1#2 { % 后续分支需要读取当前实例的布尔选项,因此必须在分支前载入实例。 \UseInstance { xeCJK / punctuation } { \l_xeCJK_punct_style_tl } \@@_punct_if_right:NTF #1 { \xeCJK_punct_kerning_process:NN #1 #2 } { \@@_punct_if_right:NTF #2 { \@@_save_left_right_kerning:NN #1 #2 } { \xeCJK_punct_kerning_process:NN #1 #2 } } } \cs_new_protected:Npn \@@_save_left_right_kerning:NN #1#2 { \bool_if:NTF \l_@@_enabled_left_right_kerning_bool { \xeCJK_punct_kerning_process:NN #1 #2 } { \bool_if:NTF \l_@@_enabled_global_setting_bool { \tl_if_exist:cTF { g_@@_punct/kern/#1/#2/tl } { \xeCJK_punct_kerning_process:NN #1 #2 } { \@@_save_punct_kerning_plain:NN #1 #2 } } { \@@_save_punct_kerning_plain:NN #1 #2 } } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\@@_punct_if_full_margin_dash:N} % \changes{v3.10.2}{2026/07/04}{新增判断,配合破折号占两个汉字宽的 % 空白补偿(\#382)。} % 判断标点 |#1| 是否为需要全空白补偿的破折号,即 \texttt{U+2014} 且未被 % 归入 |PoZheHao| 类(没有启用破折号合字)。这类破折号在连用时, % 两个 \texttt{U+2014} 之间被挤压掉的空白恰好等于单个字符左右的总空白, % 因此在其两端各补偿一整份空白(而非一半),可以优先保证 % 破折号占两个汉字宽。 % \begin{macrocode} \prg_new_conditional:Npnn \@@_punct_if_full_margin_dash:N #1 { p , T , F , TF } { \str_if_eq:nnTF {#1} { ^^^^2014 } { \if_int_compare:w \xeCJK_token_value_class:N #1 = \xeCJK_class_num:n { PoZheHao } \prg_return_false: \else: \prg_return_true: \fi: } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_punct_margin_process:NN} % \changes{v3.10.2}{2026/07/04}{未启用合字的破折号两端各补偿一整份空白, % 保证连用的破折号占两个汉字宽(\#382)。} % 注意,未启用合字的破折号(\texttt{U+2014})两端各补偿一整份空白。 % 此时单独一个 \texttt{U+2014} 占一个字宽加一份空白, % 连用的两个 \texttt{U+2014} 恰好占两个字宽。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_punct_margin_process:NN #1#2 { \dim_set:Nn \l_@@_tmp_dim { \bool_if:NTF \l_@@_enabled_global_setting_bool { \cs_if_exist_use:cF { g_@@_punct_width/#2/tl } { \tl_if_empty:NTF \g_@@_punct_width_tl { \@@_calc_punct_width:N #2 } { \g_@@_punct_width_tl } } } { \@@_calc_punct_width:N #2 } } \dim_set:Nn \l_@@_margin_dim { \dim_max:nn { \l_@@_margin_minimum_dim } { \dim_compare:nNnTF \l_@@_tmp_dim < \c_max_dim { \@@_punct_if_middle:NTF #2 { ( \l_@@_tmp_dim - ( \@@_use_punct_dim:nN { dimen } #2 ) ) \@@_punct_if_full_margin_dash:NF #2 { / 2 } } { \bool_if:NTF \l_@@_optimize_margin_bool { \dim_max:nn { \dim_min:nn { \l_@@_bound_dim } { \l_@@_reverse_bound_dim } } } { \use:n } { \l_@@_tmp_dim - \l_@@_reverse_bound_dim - ( \@@_use_punct_dim:nN { dimen } #2 ) } } } { \bool_if:NTF \l_@@_optimize_margin_bool { \dim_min:nn { \l_@@_bound_dim } } { \use:n } { \@@_calc_margin_width:N #2 } } } } \@@_save_punct_dim:nNNn { margin } #1 #2 { \l_@@_margin_dim } } \dim_new:N \l_@@_margin_dim % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_calc_punct_width:N} % \begin{macrocode} \cs_new:Npn \@@_calc_punct_width:N #1 { \@@_punct_if_middle:NTF #1 { \@@_punct_width_or_ratio:nN { middle } } { \@@_punct_if_mixed_width:NTF #1 { \@@_punct_width_or_ratio:nN { mixed } } { \@@_punct_width_or_ratio:nN { fixed } } } #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_calc_margin_width:N} % \begin{macrocode} \cs_new:Npn \@@_calc_margin_width:N #1 { \@@_punct_if_middle:NTF #1 { \dim_compare:nNnTF \l_@@_middle_margin_width_dim < \c_max_dim { \l_@@_middle_margin_width_dim } { \@@_dim_ratio:Nn \l_@@_middle_margin_ratio_fp { ( \l_@@_bound_dim + \l_@@_reverse_bound_dim ) / 2 } } } { \@@_punct_if_mixed_width:NTF #1 { \@@_margin_width_or_ratio:n { mixed } } { \@@_margin_width_or_ratio:n { fixed } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_dim_ratio:Nn} % \begin{macrocode} \cs_new:Npn \@@_dim_ratio:Nn #1#2 { \fp_to_dim:n { #1 \dim_to_fp:n {#2} } } \cs_generate_variant:Nn \@@_dim_ratio:Nn { c } % \end{macrocode} % \end{macro} % % \changes{v3.8.9}{2022/05/26}{修正居中标点悬挂错误。} % % \begin{macro}[int]{\xeCJK_punct_offset_process:NN} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_punct_offset_process:NN #1#2 { \dim_set:Nn \l_@@_tmp_dim { \bool_if:NTF \l_@@_enabled_global_setting_bool { \cs_if_exist_use:cF { g_@@_punct_bound_width/#2/tl } { \tl_if_empty:NTF \g_@@_punct_bound_width_tl { \@@_punct_width_or_ratio:nN { bound } #2 } { \g_@@_punct_bound_width_tl } } } { \@@_punct_width_or_ratio:nN { bound } #2 } } \dim_set:Nn \l_@@_tmp_dim { \bool_if:NTF \l_@@_enabled_hanging_bool { \use:n } { \dim_max:nn { \l_@@_margin_minimum_dim } } { \dim_compare:nNnTF \l_@@_tmp_dim < \c_max_dim { \@@_punct_if_middle:NTF #2 { \l_@@_tmp_dim - \l_@@_margin_dim - ( \@@_use_punct_dim:nN { dimen } #2 ) } { \l_@@_tmp_dim - \l_@@_reverse_bound_dim - ( \@@_use_punct_dim:nN { dimen } #2 ) } } { \bool_if:NTF \l_@@_optimize_margin_bool { \dim_min:nn { \l_@@_bound_dim } } { \use:n } { \@@_margin_width_or_ratio:n { bound } } } } } \@@_save_punct_dim:nNNn { offset } #1 #2 { \l_@@_tmp_dim } \@@_save_punct_dim:nNNn { rule } #1 #2 { \l_@@_tmp_dim - \l_@@_bound_dim } \@@_save_punct_dim:nNNn { rule } \l_@@_reverse_tl #2 { \l_@@_tmp_dim - \l_@@_reverse_bound_dim } \@@_save_punct_dim:nNNn { glue } #1 #2 { \l_@@_margin_dim - \l_@@_tmp_dim } \@@_save_punct_skip:nNNnnn { glue } #1 #2 { \l_@@_margin_dim - \l_@@_tmp_dim } { \@@_punct_if_middle:NTF #2 { ( \@@_use_punct_dim:nN { width } #2 - \@@_use_punct_dim:nN { dimen } #2 ) \@@_punct_if_full_margin_dash:NF #2 { / 2 } - \l_@@_margin_dim } { \l_@@_bound_dim - \l_@@_margin_dim } } { \@@_punct_if_middle:NTF #2 { \l_@@_margin_dim / 2 } { \l_@@_margin_dim - \l_@@_reverse_bound_dim } } } % \end{macrocode} % \end{macro} % % \changes{v3.2.7}{2013/08/25}{标点宽度设置禁用比例选项的值改为 \texttt{nan}。} % \changes{v3.8.0}{2020/02/10}{应用 \cs{fp_if_nan:nTF}。} % % \begin{macro}{\@@_punct_width_or_ratio:nN} % \begin{macrocode} \cs_new:Npn \@@_punct_width_or_ratio:nN #1#2 { \dim_compare:nNnTF { \use:c { l_@@_#1_punct_width_dim } } < \c_max_dim { \use:c { l_@@_#1_punct_width_dim } } { \fp_if_nan:nTF { \use:c { l_@@_#1_punct_ratio_fp } } { \c_max_dim } { \@@_dim_ratio:cn { l_@@_#1_punct_ratio_fp } { \@@_use_punct_dim:nN { width } #2 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_margin_width_or_ratio:n} % \begin{macrocode} \cs_new:Npn \@@_margin_width_or_ratio:n #1 { \dim_compare:nNnTF { \use:c { l_@@_#1_margin_width_dim } } < \c_max_dim { \use:c { l_@@_#1_margin_width_dim } } { \@@_dim_ratio:cn { l_@@_#1_margin_ratio_fp } { \l_@@_bound_dim } } \bool_if:NT \l_@@_add_min_bound_to_margin_bool { + \dim_min:nn \l_@@_bound_dim \l_@@_reverse_bound_dim } } % \end{macrocode} % \end{macro} % % \changes{v3.4.4}{2016/11/30}{不压缩长标点与其他标点的间距。} % % \begin{macro}[int]{\xeCJK_punct_kerning_process:NN} % 当标点之一为长标点时,不必进行压缩。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_punct_kerning_process:NN #1#2 { \dim_set:Nn \l_@@_margin_dim { \@@_original_kerning_margin:NN #1 #2 } \dim_set:Nn \l_@@_minimum_bound_dim { \@@_punct_min_bound:NN #1 #2 } \@@_punct_if_long:NTF #1 { \bool_set_false:N \l_@@_enabled_kerning_bool } { \@@_punct_if_long:NT #2 { \bool_set_false:N \l_@@_enabled_kerning_bool } } \dim_set:Nn \l_@@_kerning_margin_dim { \bool_if:NTF \l_@@_enabled_global_setting_bool { \cs_if_exist_use:cF { g_@@_punct/kern/#1/#2/tl } { \@@_punct_kerning_process_aux:NN #1 #2 } } { \@@_punct_kerning_process_aux:NN #1 #2 } } \@@_save_kerning:nnNN { kern } { bound } #1 #2 \@@_save_punct_dim:nNNn { bound_width } #1 #2 { \l_@@_kerning_margin_dim - \l_@@_tmp_dim } \@@_punct_if_right:NTF #1 { \@@_punct_if_right:NTF #2 { \@@_save_kerning:nnnNN { bound_kern } { offset } { bound } } { \@@_save_kerning:nnNN { bound_kern } { offset } } } { \@@_punct_if_right:NTF #2 { \@@_save_kerning:nnNN { bound_kern } { bound } } { \@@_save_kerning:nnnNN { bound_kern } { bound } { offset } } } #1 #2 } \cs_new:Npn \@@_punct_kerning_process_aux:NN #1#2 { \bool_if:NTF \l_@@_enabled_kerning_bool { \@@_calc_kerning_margin:NN #1 #2 } { \l_@@_margin_dim } } \dim_new:N \l_@@_minimum_bound_dim \dim_new:N \l_@@_kerning_margin_dim % \end{macrocode} % \end{macro} % % \changes{v3.4.0}{2016/05/12}{标点符号的压缩量能伸长到原始空白,能收缩到较小边距。} % % \begin{macro}{\@@_save_kerning:nnNN} % 相邻两个标点符号的间距能伸长到原始空白(未压缩时的状态),能收缩到较小边距。 % \begin{macrocode} \cs_new_protected:Npn \@@_save_kerning:nnNN #1#2 { \@@_save_kerning:nnnNN {#1} {#2} {#2} } \cs_new_protected:Npn \@@_save_kerning:nnnNN #1#2#3#4#5 { \dim_set:Nn \l_@@_tmp_dim { \l_@@_kerning_margin_dim - ( \@@_use_punct_dim:nNN {#2} \c_@@_right_tl #4 ) - ( \@@_use_punct_dim:nNN {#3} \c_@@_left_tl #5 ) } \@@_save_punct_dim:nNNn {#1} #4 #5 { \l_@@_tmp_dim } \@@_save_punct_skip:nNNnnn {#1} #4 #5 { \l_@@_tmp_dim } { \l_@@_margin_dim - \l_@@_kerning_margin_dim } { \l_@@_kerning_margin_dim - \l_@@_minimum_bound_dim } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_original_kerning_margin:NN} % 相邻两个标点符号之间的本来空白宽度。 % \begin{macrocode} \cs_new:Npn \@@_original_kerning_margin:NN #1#2 { \dim_eval:n { \@@_use_punct_dim:nNN { \@@_punct_if_right:NTF #1 { margin } { bound } } \c_@@_right_tl #1 + \@@_use_punct_dim:nNN { \@@_punct_if_right:NTF #2 { bound } { margin } } \c_@@_left_tl #2 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_calc_kerning_margin:NN,\@@_calc_kerning_margin_aux:NN} % \begin{macrocode} \cs_new:Npn \@@_calc_kerning_margin:NN #1#2 { \dim_max:nn { \l_@@_kerning_margin_minimum_dim } { \bool_if:NTF \l_@@_min_bound_to_kerning_bool { \l_@@_minimum_bound_dim } { \bool_if:NTF \l_@@_optimize_kerning_bool { \dim_max:nn { \l_@@_minimum_bound_dim } } { \use:n } { \@@_calc_kerning_margin_aux:NN #1 #2 } } } } \cs_new:Npn \@@_calc_kerning_margin_aux:NN #1#2 { \dim_compare:nNnTF \l_@@_kerning_total_width_dim < \c_max_dim { \@@_calc_kerning_margin:nNN \l_@@_kerning_total_width_dim } { \fp_if_nan:nTF { \l_@@_kerning_total_ratio_fp } { \xeCJK_if_same_class:NNTF #1 #2 { \@@_kerning_width_or_ratio:nNN { same } } { \@@_kerning_width_or_ratio:nNN { different } } } { \@@_calc_kerning_margin:nNN { \@@_dim_ratio:Nn \l_@@_kerning_total_ratio_fp { \@@_use_punct_dim:nN { width } #1 + \@@_use_punct_dim:nN { width } #2 } } } } #1 #2 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_kerning_width_or_ratio:nNN} % \begin{macrocode} \cs_new:Npn \@@_kerning_width_or_ratio:nNN #1#2#3 { \dim_compare:nNnTF { \use:c { l_@@_#1_align_margin_dim } } < \c_max_dim { \use:c { l_@@_#1_align_margin_dim } } { \fp_if_nan:nTF { \use:c { l_@@_#1_align_ratio_fp } } { \dim_compare:nNnTF \l_@@_kerning_margin_width_dim < \c_max_dim { \l_@@_kerning_margin_width_dim \use_none:n } { \@@_dim_ratio:Nn \l_@@_kerning_margin_ratio_fp } } { \@@_dim_ratio:cn { l_@@_#1_align_ratio_fp } } { \l_@@_margin_dim } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_punct_min_bound:NN} % \begin{macrocode} \cs_new:Npn \@@_punct_min_bound:NN #1#2 { \dim_max:nn { \dim_min:nn { \@@_use_punct_dim:nNN { bound } \c_@@_left_tl #1 } { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 } } { \dim_min:nn { \@@_use_punct_dim:nNN { bound } \c_@@_left_tl #2 } { \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #2 } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_calc_kerning_margin:nNN} % |#2| 和 |#3| 为相邻的两个标点,|#1| 为要确定的相邻两个标点总共占的宽度。 % \begin{macrocode} \cs_new:Npn \@@_calc_kerning_margin:nNN #1#2#3 { \dim_eval:n { (#1) - ( \@@_use_punct_dim:nNN { \@@_punct_if_right:NTF #2 { bound } { margin } } \c_@@_left_tl #2 ) - ( \@@_use_punct_dim:nNN { \@@_punct_if_right:NTF #3 { margin } { bound } } \c_@@_right_tl #3 ) - ( \@@_use_punct_dim:nN { dimen } #2 ) - ( \@@_use_punct_dim:nN { dimen } #3 ) } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_calc_punct_dimen:N} % 计算标点的左右实际边距和实际尺寸。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_calc_punct_dimen:N #1 { \@@_save_punct_dim:nNNn { bound } \c_@@_left_tl #1 { \xeCJK_glyph_bounds:NN 1 #1 } \@@_save_punct_dim:nNNn { bound } \c_@@_right_tl #1 { \xeCJK_glyph_bounds:NN 3 #1 } \@@_save_punct_dim:nNn { width } #1 { \tex_fontcharwd:D \tex_font:D `#1 } \@@_save_punct_dim:nNn { dimen } #1 { ( \@@_use_punct_dim:nN { width } #1 ) - ( \@@_use_punct_dim:nNN { bound } \c_@@_left_tl #1 ) - ( \@@_use_punct_dim:nNN { bound } \c_@@_right_tl #1 ) } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_glyph_bounds:NN} % 用 \tn{XeTeXglyphbounds} 取得标点符号的上下左右空白。 % \begin{macrocode} \cs_new:Npn \xeCJK_glyph_bounds:NN #1#2 { \tex_XeTeXglyphbounds:D #1 ~ \tex_XeTeXcharglyph:D `#2 \exp_stop_f: } % \end{macrocode} % \end{macro} % % \changes{v3.8.3}{2020/04/09}{重构 \opt{PunctStyle} 选项,完全展开参数。} % % \begin{macro}{PunctStyle} % \begin{macrocode} \keys_define:nn { xeCJK / options } { PunctStyle .code:n = \exp_args:Ne \@@_set_punct_style:n {#1} } \cs_new_protected:Npn \@@_set_punct_style:n #1 { \IfInstanceExistTF { xeCJK / punctuation } {#1} { \tl_set:Nn \l_xeCJK_punct_style_tl {#1} } { \prop_get:NnNF \c_@@_punct_style_alias_prop {#1} \l_xeCJK_punct_style_tl { \@@_error:ne { punct-style-unknown } {#1} } } } \prop_const_from_keyval:Nn \c_@@_punct_style_alias_prop { halfwidth = banjiao , fullwidth = quanjiao , mixedwidth = kaiming , marginkerning = hangmobanjiao , plain = plain } \tl_new:N \l_xeCJK_punct_style_tl \tl_const:Nn \c_@@_punct_style_plain_tl { plain } \@@_msg_new:nn { punct-style-unknown } { Punctuation~style~"#1"~is~unknown. \\\\ The~available~styles~are~listed~as~follow.\\\\ "plain,~\seq_use:Nnnn \g_@@_punct_style_seq { ~and~ } { ,~ } { ,~and~ }".\\ } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_trim_spaces:n} % \pkg{xparse} 处理函数,先完全展开参数再删除两边空格。 % \begin{macrocode} \cs_new_protected:Npn \@@_trim_spaces:n #1 { \tl_set:Ne \ProcessedArgument { \exp_args:Ne \tl_trim_spaces:n {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKDeclarePunctStyle} % 定义新的标点处理风格,已经存在的同名风格将被覆盖。 % \begin{macrocode} \NewDocumentCommand \xeCJKDeclarePunctStyle { > { \@@_trim_spaces:n } m m } { \IfInstanceExistTF { xeCJK / punctuation } {#1} { \@@_warning:ne { punct-style-already-defined } {#1} } { \seq_gput_right:Nn \g_@@_punct_style_seq {#1} } \DeclareInstance { xeCJK / punctuation } {#1} { basic } {#2} } \seq_new:N \g_@@_punct_style_seq \@@_msg_new:nn { punct-style-already-defined } { Punctuation~style~"#1"~is~already~defined!. \\\\ The~existing~style~of~"#1"~will~be~overwritten.\\ } \@onlypreamble \xeCJKDeclarePunctStyle % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKEditPunctStyle} % 对已有的标点处理风格进行修改。 % \begin{macrocode} \NewDocumentCommand \xeCJKEditPunctStyle { > { \@@_trim_spaces:n } m m } { \IfInstanceExistTF { xeCJK / punctuation } {#1} { \EditInstance { xeCJK / punctuation } {#1} {#2} } { \@@_error:ne { punct-style-unknown } {#1} } } \@onlypreamble \xeCJKEditPunctStyle % \end{macrocode} % \end{macro} % % 默认设置即为全角格式。 % \changes{v3.10.3}{2026/07/13} % {全角格式启用相邻标点边界优化,并保留左标点后接右标点的自然空白(\#975)。} % \begin{macrocode} \xeCJKDeclarePunctStyle { quanjiao } { optimize-kerning = true , enabled-left-right-kerning = false } % \end{macrocode} % % \begin{macrocode} \xeCJKDeclarePunctStyle { hangmobanjiao } { enabled-kerning = false } % \end{macrocode} % % \begin{macrocode} \xeCJKDeclarePunctStyle { banjiao } { fixed-punct-ratio = 0.5 , optimize-margin = true , kerning-total-ratio = 0.5 , optimize-kerning = true } % \end{macrocode} % % \changes{v3.10.3}{2026/07/13} % {开明式句末点号的宽度由 \texttt{0.8em} 改为全角(\#975)。} % \begin{macrocode} \xeCJKDeclarePunctStyle { kaiming } { fixed-punct-ratio = 0.5 , mixed-punct-ratio = 1.0 , optimize-margin = true , kerning-total-ratio = 0.5 , optimize-kerning = true } % \end{macrocode} % % \begin{macrocode} \xeCJKDeclarePunctStyle { CCT } { fixed-punct-ratio = 0.7 , optimize-margin = true , kerning-total-ratio = 0.6 , optimize-kerning = true } % \end{macrocode} % % \subsection{后备字体} % % \changes{v3.8.4}{2020/05/31}{重构后备字体的实现,修正标点符号无后备字体的问题。} % % \begin{macro}{AutoFallBack} % 后备字体的宏包选项声明。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { AutoFallBack .choice: , AutoFallBack / true .code:n = { \cs_set_eq:NN \xeCJK_fallback_symbol:NN \@@_fallback_symbol:NN \cs_set_eq:NN \xeCJK_fallback_punct_symbol:NN \@@_fallback_punct_symbol:NN \cs_set_eq:NN \xeCJK_clear_fallback_font: \@@_clear_fallback_font: } , AutoFallBack / false .code:n = { \xeCJK_cs_clear:N \xeCJK_fallback_symbol:NN \xeCJK_cs_clear:N \xeCJK_fallback_punct_symbol:NN \xeCJK_cs_clear:N \xeCJK_clear_fallback_font: } , AutoFallBack .default:n = { true } , fallback .meta:n = { AutoFallBack = true } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_fallback_symbol:NN,\xeCJK_fallback_punct_symbol:NN} % 测试当前字体中是否存在当前字符,如存在则直接输出,否则启用后备字体。 % \begin{macrocode} \cs_new_protected:Npn \@@_fallback_symbol:NN #1#2 { \xeCJK_reset_fallback_font: \xeCJK_glyph_if_exist:NF #2 { \@@_fallback_symbol_aux:NN } #1#2 } \cs_new_protected:Npn \@@_fallback_punct_symbol:NN #1#2 { \xeCJK_reset_fallback_font: \xeCJK_glyph_if_exist:NF #2 { \@@_fallback_punct_symbol_aux:NN } #1#2 } \cs_new_eq:NN \xeCJK_fallback_symbol:NN \prg_do_nothing: \cs_new_eq:NN \xeCJK_fallback_punct_symbol:NN \prg_do_nothing: \cs_new_protected:Npn \@@_fallback_symbol_aux:NN { \@@_fallback_symbol_aux:nnNN { \CJK@family } { \l_xeCJK_family_tl } } \cs_new_protected:Npn \@@_fallback_punct_symbol_aux:NN { \@@_fallback_symbol_aux:nnNN { \CJK@punctfamily } { \l_xeCJK_punct_family_tl } } \cs_new_protected:Npn \@@_fallback_symbol_aux:nnNN { \cs_set_protected:Npe \xeCJK_reset_fallback_font: { \tex_the:D \tex_font:D \xeCJK_clear_fallback_font: } \exp_args:Nee \@@_fallback_loop:nnNN } \cs_new_protected:Npn \@@_clear_fallback_font: { \cs_set_eq:NN \xeCJK_reset_fallback_font: \prg_do_nothing: } \cs_new_eq:NN \xeCJK_reset_fallback_font: \prg_do_nothing: \cs_new_eq:NN \xeCJK_clear_fallback_font: \prg_do_nothing: % \end{macrocode} % \end{macro} % % \changes{v3.5.1}{2017/11/16}{修正 fallback 字体后无法忽略空格的错误。} % \changes{v3.2.12}{2014/05/12}{更新 \cs{int_to_Hex:n}。} % % \begin{macro}[int]{\@@_fallback_loop:nnNN} % \changes{v3.1.0}{2012/11/19}{调整备用字体的循环方式。} % \changes{v3.2.4}{2013/06/30} % {使 \tn{CJKfamilydefault} 的 \texttt{FallBack} 设置全局可用。} % 循环测试后备字体是否包含字符 |#1|。若后备字体中存在该字符或者再没有后备字体,则 % 结束循环。当前字体族没有备用字体时,使用 \tn{CJKfamilydefault} 的设置。 % \begin{macrocode} \cs_new_protected:Npn \@@_fallback_loop:nnNN { \cs_set_eq:NN \@@_fallback_loop:TF \use_i:nn \@@_fallback_loop:nnnNN { FallBack } } \cs_new_protected:Npn \@@_fallback_loop:nnnNN #1#2#3 { \xeCJK_select_fallback_font:nnn {#1} {#2} {#3} \@@_fallback_loop:TF { \@@_fallback_loop_aux:nnnNN } { \@@_fallback_missing_glyph:nnnNN } {#1} {#2} {#3} } \cs_new_protected:Npn \@@_fallback_loop_aux:nnnNN #1#2#3#4#5 { \xeCJK_glyph_if_exist:NF #5 { \@@_fallback_loop:nnnNN { #1/FallBack } {#2} {#3} } #4#5 } \cs_new_protected:Npn \@@_fallback_missing_glyph:nnnNN #1#2#3#4#5 { \@@_warning:neee { missing-glyph } {#1} {#2} {#5} #4#5 } \cs_new_protected:Npn \xeCJK_select_fallback_font:nnn #1#2 { \@@_select_fallback_font:cnnn { \@@_font_csname:n { #2/#1 } } {#1} {#2} } \cs_new_protected:Npn \@@_select_fallback_font:Nnnn #1 { \cs_if_exist:NF #1 { \@@_fallback_font_initial:NNnnn } #1 \use_none:nnn } \cs_generate_variant:Nn \@@_select_fallback_font:Nnnn { c } \cs_new_protected:Npn \@@_fallback_font_initial:NNnnn #1#2#3#4#5 { \xeCJK_family_if_exist:nTF { #5/#3 } { \@@_font_initial:Nn #1 { #5/#3 } } { \@@_fallback_font_initial_auxi:Nnnn #1 {#5} {#3} {#4} } #1 } \cs_new_protected:Npn \@@_fallback_font_initial_auxi:Nnnn #1 { \exp_args:NNe \@@_fallback_font_initial_auxii:Nnnnn #1 { \CJKfamilydefault } } \cs_new_protected:Npn \@@_fallback_font_initial_auxii:Nnnnn #1#2#3 { \str_if_eq:nnTF {#2} {#3} { \@@_fallback_loop_end:Nnnn } { \@@_fallback_font_initial_auxiii:Nnnn } #1 {#2} } \cs_new_protected:Npn \@@_fallback_font_initial_auxiii:Nnnn #1#2 { \xeCJK_family_if_exist:nTF {#2} { \@@_fallback_font_initial_auxiv:Nnnn } { \@@_fallback_loop_end:Nnnn } #1 {#2} } \cs_new_protected:Npn \@@_fallback_font_initial_auxiv:Nnnn #1#2#3#4 { \@@_font_initial:Nn #1 {#2} \exp_args:Nc \@@_fallback_font_initial_auxiii:Nnnn { \@@_font_csname:n { #4/#3/FallBack } } { #2/FallBack } { #3/FallBack } {#4} } \cs_new_eq:NN \@@_fallback_loop:TF \use_i:nn \cs_new_protected:Npn \@@_fallback_loop_end:Nnnn #1#2#3#4 { \cs_gset_eq:NN #1 \@@_fallback_loop_end: } \cs_new_protected:Npn \@@_fallback_loop_end: { \cs_set_eq:NN \@@_fallback_loop:TF \use_ii:nn } \@@_msg_new:nn { missing-glyph } { CJKfamily~`\@@_msg_family_map:n {#2}'~(#1)~ does~not~contain~glyph~`#3'~ ( U + \int_to_Hex:n { `#3 } )~\msg_line_context:. } % \end{macrocode} % \end{macro} % % \begin{macro}{\setCJKfallbackfamilyfont} % \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。} % \begin{macrocode} \NewDocumentCommand \setCJKfallbackfamilyfont { m o m } { \@@_pass_args:nnnn { \xeCJK_set_family_fallback:nnn {#1} } {#2} {#3} { } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_set_family_fallback:nnn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_set_family_fallback:nnn #1#2#3 { \group_begin: \tl_set:Nn \l_@@_fallback_family_tl {#1} \prop_get:NoNF \g_@@_family_font_name_prop \l_@@_fallback_family_tl \l_@@_font_name_tl { \tl_clear:N \l_@@_font_name_tl } \clist_set:Nn \l_@@_public_options_clist {#2} \clist_map_function:nN {#3} \@@_set_family_fallback:n \group_end: } \cs_new_protected:Npn \@@_set_family_fallback:n #1 { \tl_put_right:Nn \l_@@_fallback_family_tl { /FallBack } \@@_get_sub_features:on \l_@@_fallback_family_tl {#1} \clist_concat:NNN \l_@@_sub_font_options_clist \l_@@_public_options_clist \l_@@_sub_font_options_clist \exp_args:Nooo \xeCJK_set_family:nnn \l_@@_fallback_family_tl \l_@@_sub_font_options_clist \l_@@_sub_font_name_tl } \tl_new:N \l_@@_fallback_family_tl \clist_new:N \l_@@_public_options_clist % \end{macrocode} % \end{macro} % % % \subsection{CJK 字体族声明方式} % % \begin{macrocode} \bool_new:N \g_@@_auto_fake_bold_bool \bool_new:N \g_@@_auto_fake_slant_bool \fp_new:N \g_@@_embolden_factor_fp \fp_new:N \g_@@_slant_factor_fp % \end{macrocode} % % \begin{macro}{AutoFakeBold, AutoFakeSlant,EmboldenFactor,SlantFactor} % 伪粗体和伪斜体的宏包选项声明。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { AutoFakeBold .choices:nn = { true , false } { \use:c { bool_gset_ #1 :N } \g_@@_auto_fake_bold_bool } , AutoFakeBold / unknown .code:n = { \bool_gset_true:N \g_@@_auto_fake_bold_bool \fp_gset:Nn \g_@@_embolden_factor_fp { \l_keys_value_tl } } , AutoFakeBold .default:n = { true } , AutoFakeSlant .choices:nn = { true , false } { \use:c { bool_gset_ #1 :N } \g_@@_auto_fake_slant_bool } , AutoFakeSlant / unknown .code:n = { \bool_gset_true:N \g_@@_auto_fake_slant_bool \fp_gset:Nn \g_@@_slant_factor_fp { \l_keys_value_tl } } , AutoFakeSlant .default:n = { true } , EmboldenFactor .fp_gset:N = \g_@@_embolden_factor_fp , SlantFactor .fp_gset:N = \g_@@_slant_factor_fp , BoldFont .meta:n = { AutoFakeBold = true } , boldfont .meta:n = { AutoFakeBold = true } , SlantFont .meta:n = { AutoFakeSlant = true } , slantfont .meta:n = { AutoFakeSlant = true } } % \end{macrocode} % \end{macro} % % \changes{v3.2.4}{2013/07/02}{内部调整分区字体的设置方法。} % % \begin{macro}[int]{\xeCJK_new_sub_key:n} % \begin{variable}{\g_@@_sub_key_seq} % 用于定义 CJK 子区字体和备用字体的选项。 % \begin{macrocode} \seq_new:N \g_@@_sub_key_seq \cs_new_protected:Npn \xeCJK_new_sub_key:n #1 { \seq_gput_right:Nn \g_@@_sub_key_seq {#1} \keys_define:nn { xeCJK / features } { #1 .code:n = { \tl_if_blank:nTF {##1} { \prop_clear:N \l_@@_sub_key_prop \tl_set:Ne \l_@@_sub_family_name_tl { \l_@@_family_name_tl /#1 } \clist_remove_all:Nn \l_@@_font_options_clist {#1} } { \tl_clear:N \l_@@_sub_family_name_tl \str_if_eq:nnTF {##1} { * } { \prop_put:Nnn \l_@@_sub_key_prop {#1} { \q_no_value } } { \@@_get_sub_features:nn {#1} {##1} } } } , #1 .default:n = { } } } % \end{macrocode} % \end{variable} % \end{macro} % % \changes{v3.2.4}{2013/07/02}{改进获取分区字体属性的办法。} % % \begin{macro}{\@@_get_sub_features:nn,\@@_get_sub_features:w} % \begin{macrocode} \cs_new_protected:Npn \@@_get_sub_features:nn #1#2 { \tl_set:Ne \l_@@_tmp_tl { \xeCJK_tl_remove_outer_braces:n {#2} } \clist_clear:N \l_@@_sub_font_options_clist \exp_after:wN \@@_get_sub_features:w \l_@@_tmp_tl \q_mark [ \q_nil ] \q_mark \q_stop \tl_if_empty:NTF \l_@@_sub_font_name_tl { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_font_name_tl } { \tl_replace_all:Nno \l_@@_sub_font_name_tl { * } \l_@@_font_name_tl } \prop_put:Nne \l_@@_sub_key_prop {#1} { { \exp_not:o \l_@@_sub_font_options_clist } { \exp_not:o \l_@@_sub_font_name_tl } } } \cs_new_protected:Npn \@@_get_sub_features:w #1 [#2] #3 \q_mark #4 \q_stop { \quark_if_nil:nTF {#2} { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_tmp_tl } { \tl_set:Ne \l_@@_sub_font_name_tl { \xeCJK_tl_remove_outer_braces:n {#3} } \tl_if_empty:NTF \l_@@_sub_font_name_tl { \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_tmp_tl } { \clist_set:Nn \l_@@_sub_font_options_clist {#2} } } } \tl_new:N \l_@@_sub_family_name_tl \tl_new:N \l_@@_sub_font_name_tl \clist_new:N \l_@@_sub_font_options_clist \cs_generate_variant:Nn \@@_get_sub_features:nn { o } \cs_generate_variant:Nn \tl_replace_all:Nnn { Nno } % \end{macrocode} % \end{macro} % % \begin{macro}{FallBack} % \begin{macrocode} \xeCJK_new_sub_key:n { FallBack } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{BoldFont,ItalicFont} % 调用字体的属性声明,同 \pkg{fontspec} 宏包。 % \begin{macrocode} \keys_define:nn { xeCJK / features } { BoldFont .tl_set:N = \l_@@_font_name_bf_tl , ItalicFont .tl_set:N = \l_@@_font_name_it_tl } % \end{macrocode} % \end{macro} % % \changes{v3.2.6}{2013/07/31}{\texttt{AutoFakeBold} 和 \texttt{AutoFakeSlant} % 选项直接使用 \pkg{fontspec} 的设置,修正不能调用相应实际字体的问题。} % % \begin{macro}[int]{AutoFakeBold, AutoFakeSlant} % \begin{macrocode} \keys_define:nn { xeCJK / features } { AutoFakeBold .choice: , AutoFakeBold / true .code:n = { \bool_set_true:N \l_@@_auto_fake_bold_bool \fp_set_eq:NN \l_@@_embolden_factor_fp \g_@@_embolden_factor_fp } , AutoFakeBold / false .code:n = { \bool_set_false:N \l_@@_auto_fake_bold_bool } , AutoFakeBold / unknown .code:n = { \bool_set_true:N \l_@@_auto_fake_bold_bool \fp_set:Nn \l_@@_embolden_factor_fp { \l_keys_value_tl } } , AutoFakeBold .default:n = { true } , AutoFakeSlant .choice: , AutoFakeSlant / true .code:n = { \bool_set_true:N \l_@@_auto_fake_slant_bool \fp_set_eq:NN \l_@@_slant_factor_fp \g_@@_slant_factor_fp } , AutoFakeSlant / false .code:n = { \bool_set_false:N \l_@@_auto_fake_slant_bool } , AutoFakeSlant / unknown .code:n = { \bool_set_true:N \l_@@_auto_fake_slant_bool \fp_set:Nn \l_@@_slant_factor_fp { \l_keys_value_tl } } , AutoFakeSlant .default:n = { true } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_family_initial:} % \begin{macrocode} \cs_new_protected:Npn \@@_set_family_initial: { \int_gincr:N \g_@@_family_int \prop_clear:N \l_@@_sub_key_prop \tl_clear:N \l_@@_font_name_bf_tl \tl_clear:N \l_@@_font_name_it_tl \tl_clear:N \l_@@_sub_family_name_tl \clist_clear:N \l_@@_fontspec_options_clist \bool_set_eq:NN \l_@@_auto_fake_bold_bool \g_@@_auto_fake_bold_bool \bool_set_eq:NN \l_@@_auto_fake_slant_bool \g_@@_auto_fake_slant_bool \fp_set_eq:NN \l_@@_embolden_factor_fp \g_@@_embolden_factor_fp \fp_set_eq:NN \l_@@_slant_factor_fp \g_@@_slant_factor_fp } \int_new:N \g_@@_family_int \prop_new:N \l_@@_sub_key_prop \clist_new:N \l_@@_fontspec_options_clist \bool_new:N \l_@@_auto_fake_bold_bool \bool_new:N \l_@@_auto_fake_slant_bool \fp_new:N \l_@@_embolden_factor_fp \fp_new:N \l_@@_slant_factor_fp % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_set_family:nnn} % 设置一个 CJK 新字体族,与 \tn{newfontfamily} 类似,增加 |FallBack| 选项。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_set_family:nnn #1#2#3 { \group_begin: \@@_set_family_initial: \tl_set:Nn \l_@@_family_name_tl {#1} \clist_set:Nn \l_@@_font_options_clist {#2} \tl_set:Nn \l_@@_font_name_tl {#3} \clist_concat:NNN \l_@@_font_options_clist \g_@@_default_features_clist \l_@@_font_options_clist \keys_set_known:noN { xeCJK / features } \l_@@_font_options_clist \l_@@_fontspec_options_clist \@@_binding_sub_family: \@@_parse_font_shape: \@@_check_family:o \l_@@_family_name_tl \@@_gset_family_cs:n { \l_@@_family_name_tl } \@@_save_family_info: \@@_set_sub_block_family: \group_end: } \tl_new:N \l_@@_family_name_tl \tl_new:N \l_@@_font_name_tl \clist_new:N \l_@@_font_options_clist \cs_generate_variant:Nn \xeCJK_set_family:nnn { e , o } % \end{macrocode} % \end{macro} % % \changes{v3.7.2}{2019/03/27}{删除定义新字体族时过滤重复选项的功能。} % % \begin{macro}{\@@_binding_sub_family:} % \begin{macrocode} \cs_new_protected:Npn \@@_binding_sub_family: { \tl_if_empty:NF \l_@@_sub_family_name_tl { \tl_set_eq:NN \l_@@_family_name_tl \l_@@_sub_family_name_tl } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_gset_family_cs:n} % \begin{macrocode} \cs_new_protected:Npn \@@_gset_family_cs:n #1 { \cs_gset_protected:cpe { \@@_family_csname:n {#1} } { \group_begin: \exp_not:n { \cs_set_eq:NN \xeCJK@fontfamily \use_none:n } \exp_not:n { \fontspec_gset_family:Nnn \g_@@_fontspec_family_tl } { \exp_not:o \l_@@_fontspec_options_clist } { \exp_not:o \l_@@_font_name_tl } \@@_gset_family_nfss_cs:no {#1} { \exp_not:N \g_@@_fontspec_family_tl } \group_end: \tl_set_eq:NN \exp_not:N \l_@@_fontspec_family_tl \exp_not:N \g_@@_fontspec_family_tl } } \tl_new:N \g_@@_fontspec_family_tl \tl_new:N \l_@@_fontspec_family_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_check_family:n} % \begin{macrocode} \cs_new_protected:Npn \@@_check_family:n #1 { \prop_gpop:NnNT \g_@@_family_font_name_prop {#1} \l_@@_tmp_tl { \prop_gpop:NnNT \g_@@_family_name_prop {#1} \l_@@_tmp_tl { \cs_undefine:c { \@@_family_csname:n {#1} } \cs_undefine:c { \@@_family_nfss_csname:n {#1} } } \@@_warning:nee { CJKfamily-redef } {#1} { \l_@@_tmp_tl } } } \cs_generate_variant:Nn \@@_check_family:n { o } \@@_msg_new:nn { CJKfamily-redef } { Redefining~CJKfamily~`\@@_msg_family_map:n {#1}'~(#2). } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_parse_font_shape:} % \begin{macrocode} \cs_new_protected:Npn \@@_parse_font_shape: { \tl_if_blank:oTF \l_@@_font_name_bf_tl { \bool_if:NT \l_@@_auto_fake_bold_bool { \clist_put_right:Ne \l_@@_fontspec_options_clist { AutoFakeBold = { \fp_use:N \l_@@_embolden_factor_fp } } } } { \clist_put_right:Ne \l_@@_fontspec_options_clist { BoldFont = { \exp_not:o \l_@@_font_name_bf_tl } } } \tl_if_blank:oTF \l_@@_font_name_it_tl { \bool_if:NT \l_@@_auto_fake_slant_bool { \clist_put_right:Ne \l_@@_fontspec_options_clist { AutoFakeSlant = { \fp_use:N \l_@@_slant_factor_fp } } } } { \clist_put_right:Ne \l_@@_fontspec_options_clist { ItalicFont = { \exp_not:o \l_@@_font_name_it_tl } } } } % \end{macrocode} % \end{macro} % % \begin{variable} % {\g_@@_family_name_prop,\g_@@_family_font_name_prop,\g_@@_family_font_options_prop} % \begin{macrocode} \prop_new:N \g_@@_family_name_prop \prop_new:N \g_@@_family_font_name_prop \prop_new:N \g_@@_family_font_options_prop % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_save_family_info:} % \begin{macrocode} \cs_new_protected:Npn \@@_save_family_info: { \exp_args:Nooo \@@_save_family_info:nnn \l_@@_family_name_tl \l_@@_font_name_tl \l_@@_font_options_clist } \cs_new_protected:Npn \@@_save_family_info:nnn #1#2#3 { \prop_gput:Nnn \g_@@_family_font_name_prop {#1} {#2} \prop_gput:Nnn \g_@@_family_font_options_prop {#1} {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_sub_block_family:} % \begin{macrocode} \cs_new_protected:Npn \@@_set_sub_block_family: { \prop_if_empty:NF \l_@@_sub_key_prop { \prop_map_function:NN \l_@@_sub_key_prop \@@_set_sub_block_family:nn } } \cs_new_protected:Npn \@@_set_sub_block_family:nn #1#2 { \tl_set:Ne \l_@@_sub_family_name_tl { \l_@@_family_name_tl/#1 } \quark_if_no_value:nTF {#2} { \@@_copy_sub_family:n {#1} } { \xeCJK_set_family:onn \l_@@_sub_family_name_tl #2 } } \cs_new_protected:Npn \@@_copy_sub_family:n #1 { \@@_check_family:o \l_@@_sub_family_name_tl \prop_get:NoNT \g_@@_family_font_name_prop \l_@@_family_name_tl \l_@@_sub_font_name_tl { \prop_gput:Noo \g_@@_family_font_name_prop \l_@@_sub_family_name_tl \l_@@_sub_font_name_tl } \prop_get:NoNT \g_@@_family_font_options_prop \l_@@_family_name_tl \l_@@_sub_font_options_clist { \clist_remove_all:Nn \l_@@_sub_font_options_clist { #1 = * } \prop_gput:Noo \g_@@_family_font_options_prop \l_@@_sub_family_name_tl \l_@@_sub_font_options_clist } \cs_gset_protected:cpe { \@@_family_csname:n { \l_@@_sub_family_name_tl } } { \xeCJK_family_if_exist:eT { \l_@@_family_name_tl } { \@@_gset_family_nfss_cs:no { \l_@@_sub_family_name_tl } { \exp_not:N \l_@@_fontspec_family_tl } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_copy_family:nn,\@@_copy_family:ee} % \begin{macrocode} \cs_new_protected:Npn \@@_copy_family:nn #1#2 { \xeCJK_family_if_exist:nT {#2} { \prop_gput:Nno \g_@@_family_name_prop {#1} \l_@@_fontspec_family_tl \tl_map_inline:nn { \g_@@_family_font_name_prop \g_@@_family_font_options_prop } { \prop_get:NnNT ##1 {#2} \l_@@_tmp_tl { \prop_gput:Nno ##1 {#1} \l_@@_tmp_tl } } \cs_gset_eq:cc { \@@_family_nfss_csname:n {#1} } { \@@_family_nfss_csname:n {#2} } } } \cs_generate_variant:Nn \@@_copy_family:nn { ee } % \end{macrocode} % \end{macro} % % \subsection{字体切换} % % \begin{macro}[int]{\xeCJK_select_font:} % \begin{variable}[int]{\l_xeCJK_current_font_tl} % 缓存当前字体的原始格式,以加速编译。 % \begin{macrocode} \cs_new:Npn \@@_font_csname:n #1 { xeCJK/#1/\f@series/\f@shape/\f@size } \tl_new:N \l_xeCJK_current_font_tl \tl_set:No \l_xeCJK_current_font_tl { \@@_font_csname:n { \CJK@family } } \cs_new_protected:Npn \xeCJK_select_font: { \@@_select_font:cn { \l_xeCJK_current_font_tl } { \l_xeCJK_family_tl } } \cs_new_protected:Npn \@@_select_font:Nn #1#2 { \xeCJK_clear_fallback_font: \cs_if_exist:NF #1 { \@@_font_initial:Nn #1 {#2} } #1 } \cs_generate_variant:Nn \@@_select_font:Nn { c } \tl_new:N \l_@@_current_coor_tl \cs_new_eq:NN \xeCJK@setfont \xeCJK_select_font: % \end{macrocode} % \end{variable} % \end{macro} % % \changes{v3.8.1}{2020/02/14}{修复 \cs{l_xeCJK_current_font_tl} 标记错误。} % % \begin{macro}{\@@_font_initial:Nn} % 注意要将 \tn{selectfont} 放在分组中调用,防止 \tn{f@series} 等字体参数被修改, % 导致 \cs{l_xeCJK_current_font_tl} 标记前后不一致,引发错误(见~\ghissue{486})。 % \begin{macrocode} \cs_new_protected:Npn \@@_font_initial:Nn #1#2 { \group_begin: \@@_font_initial_hook: \@@_family_use:n {#2} \xeCJK_font_gset_to_current:N #1 \group_end: } \cs_new_protected:Npn \@@_font_initial_hook: { \tl_use:N \g_@@_font_initial_hook_tl } \cs_new_protected:Npn \@@_gadd_font_initial_hook:n { \tl_gput_right:Nn \g_@@_font_initial_hook_tl } \tl_new:N \g_@@_font_initial_hook_tl % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_select_punct_font:} % \begin{variable}[int]{\l_xeCJK_current_punct_font_tl} % 切换标点符号字体。 % \begin{macrocode} \cs_new_eq:NN \xeCJK_select_punct_font: \xeCJK_select_font: \cs_new_protected:Npn \@@_select_punct_font_aux: { \@@_select_font:cn { \l_xeCJK_current_punct_font_tl } { \l_xeCJK_punct_family_tl } } \tl_new:N \CJK@punctfamily \tl_new:N \l_xeCJK_punct_family_tl \tl_new:N \l_xeCJK_current_punct_font_tl \tl_set:Nn \CJK@punctfamily { \CJK@family } \tl_set:Nn \l_xeCJK_punct_family_tl { \l_xeCJK_family_tl } \tl_set:No \l_xeCJK_current_punct_font_tl { \@@_font_csname:n { \CJK@punctfamily } } \cs_new_eq:NN \@@_select_font: \prg_do_nothing: \cs_new_eq:NN \@@_select_punct_font: \prg_do_nothing: % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\@@_switch_font:nn} % \changes{v3.1.0}{2012/11/18}{改进定义,加快切换速度。} % 两个 CJK 分区之间的字体切换。 % \begin{macrocode} \cs_new_protected:Npn \@@_switch_font:nn #1#2 { \str_if_eq:nnF {#1} {#2} { \@@_info:nee { CJK-block } {#1} {#2} \str_if_eq:nnTF {#2} { CJK } { \xeCJK_select_font: } { \xeCJK_select_font:n {#2} } } } \@@_msg_new:nn { CJK-block } { Switch~from~block~`#1'~to~`#2'. } % \end{macrocode} % \end{macro} % % \changes{v3.8.2}{2020/02/17}{修复分区字体错误。} % % \begin{macro}[int]{\xeCJK_select_font:n,\xeCJK_block_family:nn} % 若当前 CJK 字体族没有定义子分区 |#1| 的字体,则使用 \tn{CJKfamilydefault} 的对应 % 分区字体;若 \tn{CJKfamilydefault} 也没有定义该分区字体,则使用当前 CJK 字体族的 % 主分区字体。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_select_font:n #1 { \@@_select_font:cnn { \@@_font_csname:n { \CJK@family/#1 } } { \l_xeCJK_family_tl } {#1} } \cs_new_protected:Npn \@@_select_font:Nnn #1#2#3 { \xeCJK_clear_fallback_font: \cs_if_exist:NF #1 { \@@_block_font_initial:Nnn #1 {#2} {#3} } #1 } \cs_generate_variant:Nn \@@_select_font:Nnn { c } \cs_new_protected:Npn \@@_block_font_initial:Nnn #1#2#3 { \xeCJK_block_family:nn {#2} {#3} \@@_font_initial:Nn #1 { #2/#3 } } \cs_new_protected:Npn \xeCJK_block_family:nn #1#2 { \xeCJK_family_if_exist:eF { #1/#2 } { \@@_copy_family:ee { #1/#2 } { \cs_if_exist:cTF { \@@_family_csname:n { \CJKfamilydefault/#2 } } { \CJKfamilydefault/#2 } {#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro} % {\@@_family_csname:n,\@@_family_nfss_csname:n, % \@@_family_use:n,\@@_gset_family_nfss_cs:nn} % \begin{macrocode} \cs_new:Npn \@@_family_csname:n #1 { xeCJK/family/#1 } \cs_new:Npn \@@_family_nfss_csname:n #1 { xeCJK/family/nfss/#1 } \cs_new_protected:Npn \@@_family_use:n #1 { \use:c { \@@_family_nfss_csname:n {#1} } } \cs_new_protected:Npn \@@_gset_family_nfss_cs:nn #1#2 { \prop_gput:Nnn \g_@@_family_name_prop {#1} {#2} \cs_gset_protected:cpe { \@@_family_nfss_csname:n {#1} } { \@@_nfss_family:nn { \c_@@_encoding_tl } {#2} } } \cs_generate_variant:Nn \@@_gset_family_nfss_cs:nn { no } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_nfss_family:n} % 用于处理 \LaTeXe\ 2020/02/02 中 \tn{bfseries@rm} 等与 \tn{bfdefault} 不一致可能导致的问题。 % \begin{macrocode} \cs_if_exist:NTF \fontseriesforce { \cs_new_protected:Npn \@@_nfss_family:nn #1#2 { \fontencoding {#1} \str_if_eq:eeF { \f@series } { \bfdefault } { \str_case_e:nn { \f@family } { { \rmdefault } { \@@_nfss_series:n { rm } } { \sfdefault } { \@@_nfss_series:n { sf } } { \ttdefault } { \@@_nfss_series:n { tt } } } } \fontfamily {#2} \selectfont } \cs_new_protected:Npn \@@_nfss_series:n #1 { \str_if_eq:eeT { \f@series } { \use:c { bfseries@#1 } } { \fontseriesforce { \bfdefault } } } } { \cs_new_protected:Npn \@@_nfss_family:nn #1#2 { \fontencoding {#1} \tl_set:Nn \f@family {#2} \selectfont } } % \end{macrocode} % \end{macro} % % \begin{macro}[TF,int]{\xeCJK_family_if_exist:n} % \begin{macrocode} \prg_new_protected_conditional:Npnn \xeCJK_family_if_exist:n #1 { T , F , TF } { \prop_get:NnNTF \g_@@_family_name_prop {#1} \l_@@_fontspec_family_tl { \prg_return_true: } { \exp_args:Ne \cs_if_exist_use:cTF { \@@_family_csname:n {#1} } { \prg_return_true: } { \prg_return_false: } } } \prg_generate_conditional_variant:Nnn \xeCJK_family_if_exist:n { e } { T , F , TF } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKfamily} % 用于切换 CJK 字体族。 % \begin{macrocode} \NewDocumentCommand \CJKfamily { t+ t- m } { \xeCJK_family:NNe #1 #2 {#3} \tex_ignorespaces:D } \cs_new_protected:Npn \xeCJK_family:NNn #1#2#3 { \tl_if_blank:nTF {#3} { \bool_if:NF #1 { \bool_if:NF #2 { \use_none:nn } } \xeCJK_family_if_exist_use:e { \l_xeCJK_family_tl } } { \bool_if:NTF #2 { \xeCJK_family_if_exist_use:n {#3} } { \xeCJK_family_if_exist:nTF {#3} { \tl_set:Nn \l_xeCJK_family_tl {#3} \tl_set_eq:NN \CJK@family \l_@@_fontspec_family_tl \bool_if:NT #1 { \@@_family_use:n {#3} } } { \@@_family_unknown_warning:n {#3} } } } } \cs_generate_variant:Nn \xeCJK_family:NNn { NNe } \cs_new_protected:Npn \xeCJK_switch_family:n #1 { \xeCJK_family_if_exist:nTF {#1} { \tl_set:Nn \l_xeCJK_family_tl {#1} \tl_set_eq:NN \CJK@family \l_@@_fontspec_family_tl } { \@@_family_unknown_warning:n {#1} } } \cs_generate_variant:Nn \xeCJK_switch_family:n { e , o } % \end{macrocode} % \end{macro} % % \changes{v3.6.0}{2018/01/24}{新增 \opt{PunctFamily} 选项支持对汉字标点单独切换字体。} % % \begin{macro}{PunctFamily} % 设置汉字标点符号的字体。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { PunctFamily .choice: , PunctFamily .value_required:n = { true } , PunctFamily / false .code:n = { \tl_set:Nn \CJK@punctfamily { \CJK@family } \tl_set:Nn \l_xeCJK_punct_family_tl { \l_xeCJK_family_tl } \xeCJK_cs_clear:N \@@_select_font: \xeCJK_cs_clear:N \@@_select_punct_font: \cs_set_eq:NN \xeCJK_select_punct_font: \xeCJK_select_font: } , PunctFamily / unknown .code:n = { \xeCJK_punct_family:e {#1} } , } \cs_new_protected:Npn \xeCJK_punct_family:n #1 { \xeCJK_family_if_exist:nTF {#1} { \tl_set:Nn \l_xeCJK_punct_family_tl {#1} \tl_set_eq:NN \CJK@punctfamily \l_@@_fontspec_family_tl \cs_set_eq:NN \@@_select_font: \xeCJK_select_font: \cs_set_eq:NN \@@_select_punct_font: \@@_select_punct_font_aux: \cs_set_eq:NN \xeCJK_select_punct_font: \@@_select_punct_font: } { \@@_family_unknown_warning:n {#1} } } \cs_generate_variant:Nn \xeCJK_punct_family:n { e } % \end{macrocode} % \end{macro} % % \begin{variable}[int]{\l_xeCJK_family_tl} % 用于保存文档当前正在使用的 CJK 字体族。 % \changes{v3.2.0}{2013/04/14}{不将其初始化为 \tn{CJKfamilydefault}。} % \begin{macrocode} \tl_new:N \l_xeCJK_family_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\CJK@family} % 用于保存实际的字体族名称。 % \changes{v3.2.11}{2014/03/29}{引入 \tn{CJK@family} 保存实际的字体族名。} % \begin{macrocode} \tl_new:N \CJK@family % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_gobble_CJKfamily:} % \begin{macrocode} \cs_new_protected:Npn \@@_gobble_CJKfamily: { \cs_set_eq:NN \CJKfamily \@@_gobble_CJKfamily:wn } \NewExpandableDocumentCommand \@@_gobble_CJKfamily:wn { t+ t- m } { } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_family_if_exist_use:n} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_family_if_exist_use:n #1 { \xeCJK_family_if_exist:nTF {#1} { \@@_family_use:n {#1} } { \@@_family_unknown_warning:n {#1} } } \cs_generate_variant:Nn \xeCJK_family_if_exist_use:n { e } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_family_unknown_warning:n} % \changes{v3.1.2}{2013/01/01} % {在没有定义任何 CJK 字体的情况下,不再重复给出字体没有定义的警告。} % \begin{macrocode} \cs_new_protected:Npn \@@_family_unknown_warning:n #1 { \prop_if_empty:NF \g_@@_family_font_name_prop { \seq_if_in:NnF \g_@@_unknown_family_seq {#1} { \seq_gput_right:Nn \g_@@_unknown_family_seq {#1} \@@_warning:ne { CJKfamily-Unknown } {#1} } } } \seq_new:N \g_@@_unknown_family_seq \@@_msg_new:nn { CJKfamily-Unknown } { Unknown~CJK~family~`\@@_msg_family_map:n {#1}'~is~being~ignored.\\\\ Try~to~use~`\@@_msg_def_family_map:n {#1}'~to~define~it. } \cs_new:Npn \@@_msg_def_family_map:n #1 { \str_case_e:nnF {#1} { \CJKrmdefault { \token_to_str:N \setCJKmainfont } \CJKsfdefault { \token_to_str:N \setCJKsansfont } \CJKttdefault { \token_to_str:N \setCJKmonofont } } { \token_to_str:N \setCJKfamilyfont \{ #1 \} } [<...>]\{<...>\} } \cs_new:Npn \@@_msg_family_map:n #1 { \str_case_e:nnF {#1} { \CJKrmdefault { \token_to_str:N \CJKrmdefault } \CJKsfdefault { \token_to_str:N \CJKsfdefault } \CJKttdefault { \token_to_str:N \CJKttdefault } } {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_pass_args:nnnn} % 为了支持字体属性可选项在前和在后两种语法,给出两个辅助工具,类似 % \package{fontspec} 的实现。自带展开功能,额外参数 |#4| 用于后处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_pass_args:nnnn #1#2#3#4 { \tl_if_novalue:nTF {#2} { \@@_post_arg:w {#1} {#3} {#4} } { \use:e { #1 {#2} {#3} } #4 } } \NewDocumentCommand \@@_post_arg:w { m m m O { } } { \use:e { #1 {#4} {#2} } #3 } % \end{macrocode} % \end{macro} % % \begin{macro}{\setCJKmainfont,\setCJKsansfont,\setCJKmonofont} % 设置文档的 CJK 普通字体、无衬线和等宽字体。 % \changes{v3.2.0}{2013/04/14}{定义中加入 \tn{normalfont}。} % \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。} % \begin{macrocode} \NewDocumentCommand \setCJKmainfont { o m } { \@@_pass_args:nnnn { \xeCJK_set_family:nnn { \CJKrmdefault } } {#1} {#2} { \@@_preamble_family:n { \CJKrmdefault } } } \cs_new_eq:NN \setCJKromanfont \setCJKmainfont \NewDocumentCommand \setCJKsansfont { o m } { \@@_pass_args:nnnn { \xeCJK_set_family:nnn { \CJKsfdefault } } {#1} {#2} { \@@_preamble_family:n { \CJKsfdefault } } } \NewDocumentCommand \setCJKmonofont { o m } { \@@_pass_args:nnnn { \xeCJK_set_family:nnn { \CJKttdefault } } {#1} {#2} { \@@_preamble_family:n { \CJKttdefault } } } % \end{macrocode} % \end{macro} % % \begin{macrocode} \@onlypreamble \setCJKmainfont \@onlypreamble \setCJKmathfont \@onlypreamble \setCJKsansfont \@onlypreamble \setCJKmonofont \@onlypreamble \setCJKromanfont % \end{macrocode} % % \changes{v3.8.2}{2020/02/17}{避免导言区字体警告。} % % \begin{macro}{\@@_preamble_family:n} % 用在 \tn{setCJKmainfont} 等主要命令之后,确保导言区有 CJK 字体可用。 % \begin{macrocode} \cs_new_protected:Npn \@@_preamble_family:n #1 { \str_if_eq:eeT {#1} { \CJKfamilydefault } { \normalfont } } % \end{macrocode} % \end{macro} % % \begin{macro}{\setCJKfamilyfont, \newCJKfontfamily, \CJKfontspec} % \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。} % \changes{v3.10.0}{2026/04/24}{\textbf{Breaking:} % \tn{newCJKfontfamily} 定义的字体切换命令改为局部定义, % 与 \tn{newcommand} 行为一致。在分组内调用时, % 命令不再泄漏到分组外(\#751)。} % 分别用于预声明 CJK 字体族和声明并马上调用 CJK 字体族。 % \begin{macrocode} \NewDocumentCommand \setCJKfamilyfont { m o m } { \@@_pass_args:nnnn { \xeCJK_set_family:nnn {#1} } {#2} {#3} { } } \@@_msg_new:nn { command-already-defined } { Control~sequence~#1~is~already~defined. } \NewDocumentCommand \newCJKfontfamily { o m o m } { \tl_set:Ne \l_@@_tmp_tl { \tl_if_novalue:nTF {#1} { \cs_to_str:N #2 } {#1} } \cs_if_exist:NTF #2 { \@@_error:ne { command-already-defined } { \token_to_str:N #2 } } { \cs_set_protected:Npe #2 { \xeCJK_switch_family:n { \l_@@_tmp_tl } } } \@@_pass_args:nnnn { \xeCJK_set_family:nnn { \l_@@_tmp_tl } } {#3} {#4} { } } \NewDocumentCommand \CJKfontspec { o m } { \@@_pass_args:nnnn { \xeCJK_fontspec:nn } {#1} {#2} { \tex_ignorespaces:D } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_fontspec:nn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_fontspec:nn #1#2 { \prop_get:NnNTF \g_@@_fontspec_prop { CJKfontspec/#1/#2/id } \l_xeCJK_family_tl { \xeCJK_switch_family:o { \l_xeCJK_family_tl } } { \@@_fontspec:enn { CJKfontspec ( \int_eval:n { \g_@@_family_int + 1 } ) } {#1} {#2} } } \cs_new_protected:Npn \@@_fontspec:nnn #1#2#3 { \prop_gput:Nnn \g_@@_fontspec_prop { CJKfontspec/#2/#3/id } {#1} \xeCJK_set_family:nnn {#1} {#2} {#3} \xeCJK_switch_family:n {#1} } \cs_generate_variant:Nn \xeCJK_fontspec:nn { oo } \cs_generate_variant:Nn \@@_fontspec:nnn { e } \prop_new:N \g_@@_fontspec_prop % \end{macrocode} % \end{macro} % % \begin{macro}{\defaultCJKfontfeatures, \addCJKfontfeatures} % \changes{v3.2.4}{2013/06/30}{可以单独增加当前各个分区字体的属性。} % 分别用于设置 CJK 字体的默认属性和增加当前 CJK 字体的属性。 % \begin{macrocode} \clist_new:N \g_@@_default_features_clist \NewDocumentCommand \defaultCJKfontfeatures { m } { \clist_gset:Nn \g_@@_default_features_clist {#1} } \@onlypreamble \defaultCJKfontfeatures \NewDocumentCommand \addCJKfontfeatures { s O { } m } { \xeCJK_add_font_features:Nee #1 {#2} {#3} \tex_ignorespaces:D } \cs_new_eq:NN \addCJKfontfeature \addCJKfontfeatures % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_add_font_features:Nnn} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_add_font_features:Nnn #1#2#3 { \prop_get:NoNTF \g_@@_family_font_name_prop \l_xeCJK_family_tl \l_@@_font_name_tl { \clist_set:Nn \l_@@_add_font_features_clist {#3} \seq_map_inline:Nn \g_@@_sub_key_seq { \clist_remove_all:Nn \l_@@_add_font_features_clist {##1} } \seq_clear:N \l_@@_sub_key_seq \clist_clear:N \l_@@_add_block_features_clist \clist_map_function:nN {#2} \@@_add_sub_features:n \bool_lazy_and:nnT {#1} { \seq_if_empty_p:N \l_@@_sub_key_seq } { \seq_map_function:NN \g_@@_sub_key_seq \@@_add_sub_class_features:n } \prop_get:NoNT \g_@@_family_font_options_prop \l_xeCJK_family_tl \l_@@_font_options_clist { \bool_lazy_or:nnT { \seq_if_empty_p:N \l_@@_sub_key_seq } {#1} { \clist_concat:NNN \l_@@_font_options_clist \l_@@_font_options_clist \l_@@_add_font_features_clist } \clist_concat:NNN \l_@@_font_options_clist \l_@@_font_options_clist \l_@@_add_block_features_clist } \xeCJK_fontspec:oo \l_@@_font_options_clist \l_@@_font_name_tl } { \@@_warning:n { addCJKfontfeature-ignored } } } \cs_new_protected:Npn \@@_add_sub_features:n #1 { \seq_if_in:NnTF \g_@@_sub_key_seq {#1} { \seq_put_right:Nn \l_@@_sub_key_seq {#1} \@@_add_sub_class_features:n {#1} } { \@@_warning:ne { SubBlock-undefined } {#1} } } \clist_new:N \l_@@_add_font_features_clist \clist_new:N \l_@@_add_block_features_clist \cs_generate_variant:Nn \xeCJK_add_font_features:Nnn { Nee , Nne } \@@_msg_new:nn { addCJKfontfeature-ignored } { \token_to_str:N \addCJKfontfeature (s)~ignored.\\\\ It~cannot~be~used~with~a~font~that~wasn't~selected~by~xeCJK. } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_add_sub_class_features:n} % \begin{macrocode} \cs_new_protected:Npn \@@_add_sub_class_features:n #1 { \prop_get:NoNTF \g_@@_family_font_name_prop { \l_xeCJK_family_tl/#1 } \l_@@_sub_font_name_tl { \prop_get:NoN \g_@@_family_font_options_prop { \l_xeCJK_family_tl/#1 } \l_@@_sub_font_options_clist } { \prop_get:NeNTF \g_@@_family_font_name_prop { \CJKfamilydefault/#1 } \l_@@_sub_font_name_tl { \prop_get:NeN \g_@@_family_font_options_prop { \CJKfamilydefault/#1 } \l_@@_sub_font_options_clist } { \prop_get:NoN \g_@@_family_font_options_prop \l_xeCJK_family_tl \l_@@_sub_font_options_clist \tl_set_eq:NN \l_@@_sub_font_name_tl \l_@@_font_name_tl } } \clist_concat:NNN \l_@@_sub_font_options_clist \l_@@_sub_font_options_clist \l_@@_add_font_features_clist \clist_put_right:Ne \l_@@_add_block_features_clist { #1 = { [ \exp_not:o \l_@@_sub_font_options_clist ] { \exp_not:o \l_@@_sub_font_name_tl } } } } \cs_generate_variant:Nn \prop_get:NnN { Ne } \prg_generate_conditional_variant:Nnn \prop_get:NnN { Ne } { T , TF } % \end{macrocode} % \end{macro} % % \begin{macro}{LoadFandol} % \changes{v3.2.10}{2014/03/01}{当没有设置字体时,使用 Fandol 字体系列。} % \changes{v3.3.1}{2015/04/08}{为方便 MacTeX 用户,Fandol 字体改用文件名。} % \begin{macrocode} \keys_define:nn { xeCJK / options } { LoadFandol .bool_gset:N = \g_@@_fandol_bool } \cs_new_protected:Npn \@@_load_fandol: { \xeCJK_set_family:enn { \CJKrmdefault } { Extension = .otf , BoldFont = FandolSong-Bold , ItalicFont = FandolKai-Regular } { FandolSong-Regular } \xeCJK_set_family:enn { \CJKsfdefault } { Extension = .otf , BoldFont = FandolHei-Bold } { FandolHei-Regular } \xeCJK_set_family:enn { \CJKttdefault } { Extension = .otf } { FandolFang-Regular } } % \end{macrocode} % \end{macro} % % \changes{v3.1.2}{2013/01/01}{修正重定义 \tn{CJKfamilydefault} 无效的问题,恢复容错能力。} % \changes{v3.2.13}{2014/06/02}{自动调整 \tn{CJKfamilydefault} 时,只将 \tn{familydefault} 展开一次。} % % 在导言区结束的时候,若没有声明 CJK 字体,则给出一个警告。如果 \tn{CJKfamilydefault} % 没有被更改,则在此时根据西文字体的情况更新 \tn{CJKfamilydefault}。 % 如果 \tn{CJKfamilydefault} 对应的字体族没有定义,则使用 \tn{CJKrmdefault} 作为 % 默认字体族。若 \tn{CJKrmdefault} 也没有定义,则使用在导言区设置的第一个 CJK 字体 % 作为默认字体族。最后设置数学字体。 % \begin{macrocode} \@@_at_end_preamble:n { \tl_if_eq:NNT \CJKfamilydefault \l_@@_family_default_init_tl { \group_begin: \cs_set_eq:NN \@@_family_default_wrap:n \exp_not:n \tl_gset:Ne \CJKfamilydefault { \str_case:onF { \familydefault } { { \rmdefault } { \exp_not:N \CJKrmdefault } { \sfdefault } { \exp_not:N \CJKsfdefault } { \ttdefault } { \exp_not:N \CJKttdefault } } { \CJKfamilydefault } } \group_end: } \prop_if_empty:NTF \g_@@_family_font_name_prop { \bool_if:NTF \g_@@_fandol_bool { \@@_warning:n { fandol } \@@_load_fandol: \xeCJK_ensure_default_family: } { \@@_warning:ne { no-CJKfamily } { \CJKfamilydefault } } } { \xeCJK_ensure_default_family: } } \cs_new_protected:Npn \xeCJK_ensure_default_family: { \xeCJK_family_if_exist:eF { \CJKfamilydefault } { \tl_set_eq:NN \l_@@_tmp_tl \CJKfamilydefault \str_if_eq:eeTF { \CJKfamilydefault } { \CJKrmdefault } { \use:n } { \xeCJK_family_if_exist:eTF { \CJKrmdefault } { \tl_gset:Nn \CJKfamilydefault { \CJKrmdefault } } } { \prop_map_inline:Nn \g_@@_family_font_name_prop { \prop_map_break:n { \tl_gset_rescan:Nnn \CJKfamilydefault { } { ##1 } } } } \@@_warning:nee { CJKfamilydefault-undefined } { \l_@@_tmp_tl } { \CJKfamilydefault } } \xeCJK_switch_family:e { \CJKfamilydefault } \bool_if:NT \g_@@_math_bool { \xeCJK_set_mathfont: } } \@@_msg_new:nn { no-CJKfamily } { It~seems~that~you~have~not~declared~a~CJKfamily.\\ If~you~want~to~use~xeCJK~in~the~right~way,~you~should~use\\\\ `\@@_msg_def_family_map:n {#1}'\\\\ in~the~preamble~to~declare~the~default~CJKfamily.\\ } \@@_msg_new:nn { CJKfamilydefault-undefined } { Undefined~CJK~default~family~`\@@_msg_family_map:n {#1}'~ has~been~replaced~by~`\@@_msg_family_map:n {#2}'.\\\\ Try~to~use~`\@@_msg_def_family_map:n {#1}'~to~define~it. } \@@_msg_new:nn { fandol } { Fandol~is~being~set~as~the~default~font~for~CJK~text.\\ Please~make~sure~it~has~been~properly~installed. } % \end{macrocode} % % \subsection{数学字体设置} % % \begin{macro}{CJKmath} % 是否启用 CJK 数学字体的宏包选项。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CJKmath .bool_gset:N = \g_@@_math_bool } % \end{macrocode} % \end{macro} % % \begin{macro}{\setCJKmathfont} % \changes{v3.4.3}{2016/11/18}{允许字体属性可选项在后的新语法。} % 设置 CJK 数学字体。 % \begin{macrocode} \NewDocumentCommand \setCJKmathfont { o m } { \@@_pass_args:nnnn { \xeCJK_set_family:nnn { \c_@@_math_tl } } {#1} {#2} { } } \tl_const:Nn \c_@@_math_tl { CJKmath } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_set_mathfont:} % \changes{v3.2.6}{2013/08/01}{设置粗体时先检查对应字体是否存在。} % \changes{v3.2.7}{2013/11/09}{将 CJK 字符的数学归类由 $7$ 改为 $0$,解决汉字路径的问题。} % \changes{v3.2.13}{2014/06/20}{修复参数类型错误。} % \changes{v3.4.0}{2016/05/01}{CJKmath 的字符范围遵从 \tn{xeCJKDeclareCharClass} 的设置。} % 当没有设置 CJK 数学字体时,使用 \tn{CJKfamilydefault} 作为数学字体。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_set_mathfont: { \cs_if_exist_use:N \@@_save_um_char: \xeCJK_family_if_exist:eTF { \c_@@_math_tl } { \@@_set_mathfont_aux: } { \xeCJK_family_if_exist:eT { \CJKfamilydefault } { \@@_copy_family:ee { \c_@@_math_tl } { \CJKfamilydefault } \@@_set_mathfont_aux: } } \cs_if_exist_use:N \@@_restore_um_char: } \cs_new_protected:Npn \@@_set_mathfont_aux: { \tl_const:Ne \c_@@_math_family_tl { \l_@@_fontspec_family_tl } \xeCJK_declare_mathfont:ee { \c_@@_math_tl } { \c_@@_math_family_tl } \int_const:Nn \c_xeCJK_math_fam_int { \use:c { sym \c_@@_math_tl } } \clist_gconcat:NNN \g_@@_math_chars_clist \g_@@_CJK_range_clist \g_@@_FullLeft_range_clist \clist_gconcat:NNN \g_@@_math_chars_clist \g_@@_math_chars_clist \g_@@_FullRight_range_clist \xeCJK_gset_mathcode:Nn \g_@@_math_chars_clist { \c_xeCJK_math_fam_int } \xeCJK_set_mathfont_block: } \clist_new:N \g_@@_math_chars_clist \prop_new:N \g_@@_fam_prop % \end{macrocode} % \end{macro} % % \changes{v3.4.0}{2016/05/04}{CJKmath 功能也支持分区字体。} % % \begin{macro}[int]{\xeCJK_set_mathfont_block:} % 分区数学字体。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_set_mathfont_block: { \seq_if_empty:NF \g_@@_CJK_sub_class_seq { \seq_map_function:NN \g_@@_CJK_sub_class_seq \xeCJK_set_mathfont_block:n } } \cs_new_protected:Npn \xeCJK_set_mathfont_block:n #1 { \xeCJK_block_family:nn { \c_@@_math_tl } {#1} \prop_get:NoNTF \g_@@_fam_prop \l_@@_fontspec_family_tl \l_@@_tmp_tl { \int_set:Nn \l_@@_fam_int { \l_@@_tmp_tl } } { \xeCJK_declare_mathfont:ee { \c_@@_math_tl / #1 } { \l_@@_fontspec_family_tl } \@@_set_mathfont_block_aux:cn { sym \c_@@_math_tl / #1 } {#1} } \xeCJK_gset_mathcode:cn { g_@@_CJK/#1_range_clist } { \l_@@_fam_int } } \cs_new_protected:Npn \@@_set_mathfont_block_aux:Nn #1#2 { \int_set_eq:NN \l_@@_fam_int #1 \prop_gput:Nnn \g_@@_block_fam_prop {#2} {#1} } \int_new:N \l_@@_fam_int \prop_new:N \g_@@_block_fam_prop \cs_generate_variant:Nn \@@_set_mathfont_block_aux:Nn { c } % \end{macrocode} % \end{macro} % % \changes{v3.8.1}{2020/02/14}{应用 \tn{shapedefault}。} % % \begin{macro}[int]{\xeCJK_declare_mathfont:nn} % 注意从 \LaTeXe\ 2020/02/02 开始,\tn{shapedefault} 初始值是 \texttt{n}, % 而 \tn{updefault} 初始值是 \texttt{up},两者并不一致。 % \pkg{fontspec} 包定义字体使用的是 \tn{shapedefault}。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_declare_mathfont:nn #1#2 { \xeCJK_declare_symbol_font:nnnnn {#1} { \c_@@_encoding_tl } {#2} { \mddefault } { \shapedefault } \cs_if_free:cF { \c_@@_encoding_tl/#2/\bfdefault/\shapedefault } { \SetSymbolFont {#1} { bold } { \c_@@_encoding_tl } {#2} { \bfdefault } { \shapedefault } } \prop_gput:Nne \g_@@_fam_prop {#2} { \exp_not:c { sym #1 } } } \cs_generate_variant:Nn \prop_put:Nnn { Nne } \cs_generate_variant:Nn \prop_gput:Nnn { Nne } \cs_generate_variant:Nn \xeCJK_declare_mathfont:nn { ee } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_declare_symbol_font:nnnnn} % 主要功能同 \tn{DeclareSymbolFont},不带编码和重复定义检查。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_declare_symbol_font:nnnnn #1 { \@@_declare_symbol_font:cnnnn { sym #1 } } \cs_new_protected:Npn \@@_declare_symbol_font:Nnnnn #1 { \xeCJK_new_fam:N #1 \xeCJK_new_symbol_font:Nnnnn #1 } \cs_generate_variant:Nn \@@_declare_symbol_font:Nnnnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_new_fam:N} % 我们从 $255$ 往下分配 \tn{fam},|\count18| 是 \LaTeXe{} 记录最后分配的 \tn{fam} 编号, % 作为我们的分配器的下限。事实上,还应该相应地减小 \tn{e@mathgroup@top} 才合理,但这可能会有不利影响, % 我们暂未处理。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_new_fam:N #1 { \int_compare:nNnTF { \g_@@_fam_allocation_int } > { \g_@@_fam_bottom_int } { \int_set_eq:NN \allocationnumber \g_@@_fam_allocation_int \int_const:Nn #1 { \allocationnumber } \iow_log:e { \token_to_str:N #1 = \token_to_str:N \mathgroup \int_use:N \allocationnumber } \int_gdecr:N \g_@@_fam_allocation_int } { \@@_error:n { fam-exhausted } } } \tex_countdef:D \g_@@_fam_bottom_int = 18 ~ \int_new:N \g_@@_fam_allocation_int \int_gset:Nn \g_@@_fam_allocation_int { 255 } \@@_msg_new:nn { fam-exhausted } { No~room~for~a~new~fam. } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_new_symbol_font:Nnnnn} % \begin{macro}{\@@_new_symbol_font:NN} % 功能同 \tn{new@symbolfont},但我们不增加 \tn{c@mv@normal} 和 \tn{c@mv@bold} 之类的计数器。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_new_symbol_font:Nnnnn #1#2#3#4#5 { \@@_new_symbol_font:Nc #1 { #2/#3/#4/#5 } } \cs_new_protected:Npn \@@_new_symbol_font:NN #1#2 { \tl_put_right:Nn \group@list { \group@elt #1 #2 } \cs_set:Npn \version@elt ##1 { \tl_put_right:Nn ##1 { \getanddefine@fonts #1 #2 } } \version@list } \cs_generate_variant:Nn \@@_new_symbol_font:NN { Nc } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[int]{\xeCJK_gset_mathcode:Nn, % \xeCJK_gset_mathcode:Nnn,\xeCJK_gset_mathcode:nnnn} % CJK 字符的数学类别固定为 $0$(\tn{mathord})。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_gset_mathcode:Nn #1#2 { \clist_map_inline:Nn #1 { \@@_set_char_class_aux:Nnw \xeCJK_gset_mathcode:nnnn { ##1 } { 0 } {#2} } } \cs_generate_variant:Nn \xeCJK_gset_mathcode:Nn { c } \cs_new_protected:Npn \xeCJK_gset_mathcode:nnnn #1#2#3#4 { \@@_check_num_range:nnNN {#1} {#2} \l_@@_begin_int \l_@@_end_int \xeCJK_int_until_do:nn { \l_@@_begin_int > \l_@@_end_int } { \xeCJK_gset_mathcode:Nnn \l_@@_begin_int {#3} {#4} \int_incr:N \l_@@_begin_int } } \cs_new_protected:Npn \xeCJK_gset_mathcode:Nnn #1#2#3 { \tex_global:D \tex_Umathcode:D #1 = #2 ~ #3 ~ #1 } % \end{macrocode} % \end{macro} % % % \subsection{抄录环境中的间距调整} % % \changes{v3.1.0}{2012/11/19}{放弃使用放缩字体大小的方式,而只采用调整间距的方式 % 与西文等宽字体对齐。并且只适用于与抄录环境下。} % \changes{v3.2.0}{2013/04/15}{增加 \texttt{Verb} 选项。} % \changes{v3.2.1}{2013/05/29}{调整 \texttt{Verb} 选项:在命令 \tn{verb} 里使用时, % 不破坏标点禁则,增加值 \texttt{env+}。} % % \begin{macro}{Verb} % \changes{v3.2.5}{2013/07/25}{微调定义。} % 如果设置为 \texttt{env},则只在 \LaTeX 的抄录环境里使用 \tn{xeCJKVerbAddon},^^A % 而不包括 \tn{verb}。对当前使用环境的判断基于在标准 \LaTeX 的环境定义里使用 % \tn{begingroup} 和 \tn{endgroup} 来分组。 % \begin{macrocode} \int_new:N \l_@@_verb_case_int \keys_define:nn { xeCJK / options } { Verb .choices:nn = { true , env+ , env , false } { \int_set_eq:NN \l_@@_verb_case_int \l_keys_choice_int } , Verb .default:n = { env } } \cs_new_protected:Npn \@@_verb_font_hook: { \if_case:w \l_@@_verb_case_int \or: \@@_nobreak_skip_zero: \or: \int_compare:nNnTF \tex_currentgrouptype:D = { 14 } { \xeCJKVerbAddon } { \@@_nobreak_skip: } \or: \int_compare:nNnTF \tex_currentgrouptype:D = { 14 } { \xeCJKVerbAddon } { \@@_nobreak_skip_zero: } \fi: } \@@_after_preamble:n { \cs_set_protected:Npe \verbatim@font { \exp_not:o { \verbatim@font } \@@_verb_font_hook: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_nobreak_skip_zero:,\@@_nobreak_skip:} % \changes{v3.2.8}{2013/11/16}{禁止在 \tn{verb} 中断行。} % \begin{macrocode} \cs_new_protected:Npn \@@_nobreak_skip_zero: { \@@_reset_shipout_skip: \cs_set_eq:NN \@@_shipout_check_for_glue: \xeCJK_check_for_glue: \cs_set_eq:NN \@@_shipout_boundary:w \xeCJK_CJK_and_Boundary:w \tl_put_right:Nn \l_@@_reset_shipout_skip_hook_tl { \cs_set_eq:NN \xeCJK_check_for_glue: \@@_shipout_check_for_glue: \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_shipout_boundary:w } \xeCJK_cs_clear:N \CJKglue \xeCJK_cs_clear:N \CJKecglue \xeCJK_cs_clear:N \xeCJK_check_for_glue: \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \xeCJK_class_group_end: \cs_set_eq:NN \@@_punct_hskip:n \@@_nobreak_hskip:n \cs_set_eq:NN \@@_punct_breakable_kern:n \@@_nobreak_hskip:n } \cs_new_protected:Npn \@@_nobreak_skip: { \@@_reset_shipout_skip: \xeCJK_glue_to_skip:nN { \CJKglue } \l_@@_ccglue_skip \skip_if_eq:nnTF { \l_@@_ccglue_skip } { \c_zero_skip } { \xeCJK_cs_clear:N \CJKglue } { \cs_set_eq:NN \CJKglue \@@_nobreak_ccglue: } \xeCJK_glue_to_skip:nN { \CJKecglue } \l_@@_ecglue_skip \skip_if_eq:nnTF { \l_@@_ecglue_skip } { \c_zero_skip } { \xeCJK_cs_clear:N \CJKecglue } { \cs_set_eq:NN \CJKecglue \@@_nobreak_ecglue: } \cs_set_eq:NN \@@_punct_hskip:n \@@_nobreak_hskip:n \cs_set_eq:NN \@@_punct_breakable_kern:n \@@_nobreak_hskip:n } \cs_new_protected:Npn \@@_nobreak_ccglue: { \xeCJK_no_break: \skip_horizontal:N \l_@@_ccglue_skip } \cs_new_protected:Npn \@@_nobreak_ecglue: { \xeCJK_no_break: \skip_horizontal:N \l_@@_ecglue_skip } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_reset_shipout_skip:} % \begin{macrocode} \cs_new_protected:Npn \@@_reset_shipout_skip: { \cs_set_eq:NN \@@_shipout_CJKglue: \CJKglue \cs_set_eq:NN \@@_shipout_CJKecglue: \CJKecglue \cs_set_eq:NN \@@_shipout_punct_hskip:n \@@_punct_hskip:n \cs_set_eq:NN \@@_shipout_punct_breakable_kern:n \@@_punct_breakable_kern:n \tl_set:Ne \l_@@_off_verb_addon_tl { \bool_if:NTF \l_@@_xecglue_bool { \keys_set:nn { xeCJK / options } { xCJKecglue = true } } { \keys_set:nn { xeCJK / options } { xCJKecglue = false } } \exp_not:n { \cs_set_eq:NN \CJKglue \@@_shipout_CJKglue: \cs_set_eq:NN \CJKecglue \@@_shipout_CJKecglue: \cs_set_eq:NN \@@_punct_hskip:n \@@_shipout_punct_hskip:n \cs_set_eq:NN \@@_punct_breakable_kern:n \@@_shipout_punct_breakable_kern:n \l_@@_reset_shipout_skip_hook_tl } } \xeCJK_add_to_shipout:n { \l_@@_off_verb_addon_tl } \keys_set:nn { xeCJK / options } { xCJKecglue = false } } \tl_new:N \l_@@_reset_shipout_skip_hook_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKOffVerbAddon,\xeCJKVerbAddon} % \changes{v3.1.0}{2012/11/19}{新增 \tn{xeCJKVerbAddon} 用于抄录环境中的间距调整。} % \changes{v3.2.3}{2013/06/04} % {新增 \tn{xeCJKOffVerbAddon} 用于局部取消 \tn{xeCJKOffVerbAddon} 的影响;并解决 % 跨页使用时影响到页眉页脚的问题。} % \changes{v3.2.5}{2013/07/13}{禁止自动换行,与西文一致。} % \changes{v3.2.8}{2013/11/16}{增加是否是等宽字体的判断。} % \tn{xeCJKVerbAddon} 进行了比较大的调整,应该只在分组环境里使用。为了方便调整间距 % 以利于对齐,这里只把字符分成了两类,并且在 CJK 类与边界(空格)之间也插入 % \tn{CJKecglue}。以字母“M”的宽度是否等于 \tn{fontdimen2} 来判断当前字体是否是 % 等宽字体。如果不是等宽字体,则设置间距为零或正文间距。 % \begin{macrocode} \NewDocumentCommand \xeCJKVerbAddon { } { \int_compare:nNnF \tex_currentgrouplevel:D = \c_zero_int { \bool_if:NF \l_@@_listings_env_bool { \dim_compare:nNnTF { \tex_fontdimen:D 2 ~ \tex_font:D } = { \tex_fontcharwd:D \tex_font:D \c_@@_mono_letter_int } { \@@_set_verb_exspace: \@@_verb_addon: } { \int_if_odd:nTF { \l_@@_verb_case_int } { \@@_nobreak_skip_zero: } { \@@_nobreak_skip: } } } } } \int_const:Nn \c_@@_mono_letter_int { 77 } \bool_new:N \l_@@_listings_env_bool \NewDocumentCommand \xeCJKOffVerbAddon { } { \tl_use:N \l_@@_off_verb_addon_tl } \tl_new:N \l_@@_off_verb_addon_tl \cs_new_protected:Npn \@@_verb_addon: { \bool_if:NF \l_@@_verb_addon_bool { \@@_verb_addon_action: } \skip_if_eq:nnTF { \l_@@_verb_exspace_skip } { \c_zero_skip } { \xeCJK_cs_clear:N \CJKglue \xeCJK_cs_clear:N \CJKecglue } { \skip_set_eq:NN \l_@@_ccglue_skip \l_@@_verb_exspace_skip \skip_set:Nn \l_@@_ecglue_skip { \l_@@_verb_exspace_skip / 2 } \cs_set_eq:NN \CJKglue \@@_nobreak_ccglue: \cs_set_eq:NN \CJKecglue \@@_nobreak_ecglue: } \cs_set_eq:NN \xeCJK_check_for_glue: \CJKecglue \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_verb_CJK_and_Boundary:w } \cs_new_protected:Npn \@@_verb_addon_action: { \bool_set_true:N \l_@@_verb_addon_bool \@@_set_char_class_eq:nn { FullLeft } { CJK } \@@_set_char_class_eq:nn { FullRight } { CJK } \@@_set_char_class_eq:nn { HalfLeft } { Default } \@@_set_char_class_eq:nn { HalfRight } { Default } \@@_set_char_class_eq:nn { NormalSpace } { Default } \cs_set_eq:NN \@@_shipout_CJKglue: \CJKglue \cs_set_eq:NN \@@_shipout_CJKecglue: \CJKecglue \cs_set_eq:NN \@@_shipout_check_for_glue: \xeCJK_check_for_glue: \cs_set_eq:NN \@@_shipout_boundary:w \xeCJK_CJK_and_Boundary:w \cs_set_protected:Npe \xeCJKOffVerbAddon { \@@_reset_char_class:n { FullLeft } \@@_reset_char_class:n { FullRight } \@@_reset_char_class:n { HalfLeft } \@@_reset_char_class:n { HalfLeft } \@@_reset_char_class:n { NormalSpace } \bool_if:NTF \l_@@_xecglue_bool { \keys_set:nn { xeCJK / options } { xCJKecglue = true } } { \keys_set:nn { xeCJK / options } { xCJKecglue = false } } \exp_not:n { \cs_set_eq:NN \CJKglue \@@_shipout_CJKglue: \cs_set_eq:NN \CJKecglue \@@_shipout_CJKecglue: \cs_set_eq:NN \xeCJK_check_for_glue: \@@_shipout_check_for_glue: \cs_set_eq:NN \xeCJK_CJK_and_Boundary:w \@@_shipout_boundary:w } } \xeCJK_add_to_shipout:n { \xeCJKOffVerbAddon } \keys_set:nn { xeCJK / options } { xCJKecglue = false } } \cs_new_protected:Npn \@@_verb_CJK_and_Boundary:w { \xeCJK_class_group_end: \CJKecglue } \cs_new_protected:Npn \@@_reset_char_class:n #1 { \int_set:Nn \l_@@_tmp_int { \xeCJK_class_num:n {#1} } \clist_map_inline:cn { c_@@_#1_chars_clist } { \tex_XeTeXcharclass:D ##1 = \l_@@_tmp_int } } \bool_new:N \l_@@_verb_addon_bool \cs_new_eq:NN \CJKfixedspacing \xeCJKVerbAddon % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_verb_exspace:} % \changes{v3.1.1}{2012/12/08}{调整间距的计算方法。} % \changes{v3.2.4}{2013/06/29}{当计算得出的间距为负时,缩小 CJK 字体。} % 在抄录环境中,CJK 文字之间的间距为当前西文字体两个空格的宽度与当前字体大小之差, % 而与西文和空格的间距为 CJK 文字之间的间距的一半。 % \begin{macrocode} \cs_new_protected:Npn \@@_set_verb_exspace: { \tl_if_exist:cTF { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size } { \skip_set:Nn \l_@@_verb_exspace_skip { \use:c { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size } } } { \tl_set:Ne \l_@@_current_coor_tl { \CJK@family/\curr@fontshape } \prop_get:NoNTF \g_@@_scale_family_prop \l_@@_current_coor_tl \l_xeCJK_family_tl { \xeCJK_switch_family:o { \l_xeCJK_family_tl } \skip_zero:N \l_@@_verb_exspace_skip } { \group_begin: \xeCJK_select_font: \exp_args:NNo \group_end: \@@_set_verb_exspace:n { \dim_use:N \tex_fontcharwd:D \tex_font:D "4E00 ~ } } } } \skip_new:N \l_@@_verb_exspace_skip % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_verb_exspace:n} % 当两个西文空格的宽度小于一个 CJK 文字的宽度时,对目前使用的 CJK 字体进行适当缩小。 % \begin{macrocode} \cs_new_protected:Npn \@@_set_verb_exspace:n #1 { \skip_set:Nn \l_@@_verb_exspace_skip { 2 \tex_fontdimen:D 2 ~ \tex_font:D - #1 } \dim_compare:nNnTF \l_@@_verb_exspace_skip < \c_zero_dim { \skip_zero:N \l_@@_verb_exspace_skip \exp_args:Nee \@@_set_verb_scale:nn { \dim_to_fp:n { 2 \tex_fontdimen:D 2 ~ \tex_font:D } } { \dim_to_fp:n {#1} } } { \tl_const:ce { xeCJK/verb/\CJK@family/\curr@fontshape/\f@size } { \skip_use:N \l_@@_verb_exspace_skip } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_verb_scale:nn} % 缩小 CJK 字体,并保存相关信息。 % \begin{macrocode} \cs_new_protected:Npn \@@_set_verb_scale:nn #1#2 { \fp_set:Nn \l_@@_scale_factor_fp { #1 / #2 } \@@_warning:nee { scale-factor } { \fp_eval:n { trunc ( \l_@@_scale_factor_fp , 4 ) } } { \fp_eval:n { ceil ( #2 / #1 , 4 ) } } \xeCJK_add_font_features:Nne \c_true_bool { } { Scale = { \fp_use:N \l_@@_scale_factor_fp } } \prop_gput:Noo \g_@@_scale_family_prop \l_@@_current_coor_tl \l_xeCJK_family_tl } \@@_msg_new:nn { scale-factor } { `\token_to_str:N \xeCJKVerbAddon'~may~not~work~properly.\\\\ You~may~set~`Scale=#1'~to~CJKfamily~ `\@@_msg_family_map:n { \l_xeCJK_family_tl }',\\ or~set~`Scale=#2'~to~family~ `\str_if_eq:eeTF \f@family \ttdefault { \token_to_str:N \ttdefault } { \f@family }'. } \fp_new:N \l_@@_scale_factor_fp \prop_new:N \g_@@_scale_family_prop % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_visible_space:} % \begin{macro}[int]{\@setupverbvisiblespace} % \changes{v3.2.5}{2013/07/13}{可视空格考虑传统 \TeX 字体的情况。} % \changes{v3.8.0}{2020/02/09}{更新可视空格补丁。} % 如果文档不使用 \texttt{EU1} 作为默认字体编码,那么默认的打字机字体族很可能是 % 传统的 \TeX 字体,这时可视空格按照 \texttt{OT1} 编码传统一般就是字体中的 |\char32|。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_setup_visible_space: { \xeCJK_make_boundary: \xeCJK_glyph_if_exist:NTF { ^^^^2423 } { \tl_set:Nn \l_@@_visible_space_tl { ^^^^2423 } } { \int_compare:nNnTF { \tex_XeTeXfonttype:D \tex_font:D } = \c_zero_int { \tl_set:Ne \l_@@_visible_space_tl { \str_if_eq:eeTF { \f@family } { \ttdefault } { \c_catcode_other_space_tl } { \exp_not:N \textvisiblespace } } } { \@@_visible_space_fallback: } } \cs_set_eq:NN \@xobeysp \l_@@_visible_space_tl } \tl_new:N \l_@@_visible_space_tl \cs_set_eq:NN \@setupverbvisiblespace \xeCJK_setup_visible_space: % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_visible_space_fallback:} % 我们使用 |lmtt| 字体中的可视空格符号(|U+2423|)作为当前字体中相应符号 % 的后备,但是 |lmtt| 的字体大小未必与当前字体匹配。因此,这里需要做一些调整,以 % 保证使用后备可视空格符号时,也能保证对齐。 % \begin{macrocode} \cs_new_protected:Npn \@@_visible_space_fallback: { \exp_args:Nc \@@_visible_space_fallback_auxi:N { xeCJK/space/\curr@fontshape/\f@size } } \cs_new_protected:Npn \@@_visible_space_fallback_auxi:N #1 { \cs_if_exist:NF #1 { \@@_visible_space_fallback_auxii:N #1 } \tl_set:Nn \l_@@_visible_space_tl {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_visible_space_fallback_auxii:N} % 当前字体空格的宽度与后备字体 |lmtt| 不一样时,就对 \tn{textvisiblespace} 的字体尺寸 % 按相应的比例放缩。 % \begin{macrocode} \cs_new_protected:Npn \@@_visible_space_fallback_auxii:N #1 { \group_begin: \exp_args:No \@@_set_visible_space_size:n { \dim_use:N \tex_fontdimen:D 2 ~ \tex_font:D } \cs_new_protected:Npe #1 { \group_begin: \tex_the:D \tex_font:D ^^^^2423 \group_end: } \group_end: } \cs_new_protected:Npn \@@_set_visible_space_size:n #1 { \fontencoding { \UnicodeEncodingName } \tl_set:Nn \f@family { lmtt } \selectfont \dim_compare:nNnF {#1} = { \tex_fontdimen:D 2 ~ \tex_font:D } { \fontsize { \dim_eval:n { \f@size pt * \dim_ratio:nn {#1} { \tex_fontdimen:D 2 ~ \tex_font:D } } } { \f@baselineskip } \selectfont } } % \end{macrocode} % \end{macro} % % \subsection{\pkg{xeCJK} 其他选项} % % \begin{macro}{LocalConfig} % \changes{v3.1.0}{2012/11/20}{增加 \texttt{LocalConfig} 选项用于载入本地配置文件。} % 声明载入本地配置文件的选项。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { LocalConfig .choice: , LocalConfig / false .code:n = { \bool_gset_false:N \g_@@_config_bool } , LocalConfig / true .code:n = { \bool_gset_true:N \g_@@_config_bool \tl_gset:Nn \g_@@_config_name_tl { xeCJK } } , LocalConfig / unknown .code:n = { \bool_gset_true:N \g_@@_config_bool \tl_gset:Ne \g_@@_config_name_tl { xeCJK - \l_keys_value_tl } } , LocalConfig .default:n = { true } } \tl_new:N \g_@@_config_name_tl \bool_new:N \g_@@_config_bool % \end{macrocode} % \end{macro} % % \begin{macro}[int]{CJKnumber,indentfirst} % \changes{v3.2.11}{2014/03/14}{放弃 \texttt{indentfirst} 和 \texttt{CJKnumber} 选项。} % \texttt{CJKnumber} 和 \texttt{indentfirst} 是过时选项。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { CJKnumber .code:n = { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { CJKnumb } } , indentfirst .code:n = { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { indentfirst } } , normalindentfirst .code:n = { \@@_warning:nee { option-deprecated } { \l_keys_key_str } { } } } \@@_msg_new:nn { option-deprecated } { The~`#1'~option~is~deprecated.\\ \tl_if_empty:nF {#2} { You~may~load~the~package~`#2'~after~xeCJK~to~use~its~function.\\ } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{quiet,silent} % 将调用 \pkg{xeCJK} 时使用的未知的选项传递给 \pkg{fontspec} 宏包。 % 对 \pkg{fontspec} 的 |quiet| 和 |silent| 选项进行修改,使其适用于 \pkg{xeCJK}。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { quiet .code:n = { \msg_redirect_module:nnn { xeCJK } { warning } { info } \msg_redirect_module:nnn { xeCJK } { info } { none } \xeCJK_if_package_loaded:nF { fontspec } { \PassOptionsToPackage { quiet } { fontspec } } } , silent .code:n = { \msg_redirect_module:nnn { xeCJK } { warning } { none } \msg_redirect_module:nnn { xeCJK } { info } { none } \xeCJK_if_package_loaded:nF { fontspec } { \PassOptionsToPackage { silent } { fontspec } } } , unknown .code:n = { \xeCJK_if_package_loaded:nTF { fontspec } { \@@_error:ne { key-unknown } { \l_keys_key_str } } { \PassOptionsToPackage { \l_keys_key_str } { fontspec } } } } \@@_msg_new:nn { key-unknown } { Sorry,~but~xeCJK/options~does~not~have~a~key~called~`#1'.\\\\ The~key~`#1'~is~being~ignored. } % \end{macrocode} % \end{macro} % % \subsection{\pkg{xeCJK} 初始化设置} % % \begin{macro}[int]{\CJKsymbol, \CJKpunctsymbol} % \begin{macrocode} \cs_new_eq:NN \CJKsymbol \use:n \cs_new_eq:NN \CJKpunctsymbol \use:n % \end{macrocode} % \end{macro} % % \pkg{xeCJK} 宏包的初始化设置。 % % \begin{macrocode} \keys_set:nn { xeCJK / options } { CJKglue = { \skip_horizontal:n { \c_zero_dim plus 0.08 \tex_baselineskip:D } } , CJKecglue = { ~ } , xCJKecglue = false , CheckSingle = false , PlainEquation = false , CheckFullRight = false , CJKspace = false , CJKmath = false , xeCJKactive = true , LocalConfig = true , LoadFandol = true , RubberPunctSkip = true , Verb = env , EmboldenFactor = 4 , SlantFactor = 0.167 , PunctStyle = quanjiao , NewLineCS = { \par \[ } , EnvCS = { \begin \end } , WidowPenalty = { 10 000 } , NoBreakCS = { \footnote \footnotemark \nobreak } , KaiMingPunct = { ^^^^3002 ^^^^ff0e ^^^^ff1f ^^^^ff01 } , LongPunct = { ^^^^2014 ^^^^2e3a ^^^^2025 ^^^^2026 } , NoBreakLongPunct = { ^^^^2025 ^^^^2026 } , MiddlePunct = { ^^^^2013 ^^^^2014 ^^^^2e3a ^^^^2027 ^^^^00b7 ^^^^30fb ^^^^ff65 } , AllowBreakBetweenPuncts = false } \defaultCJKfontfeatures { Script = CJK } % \end{macrocode} % % 半字线连接号\footnote{见\href{http://www.moe.gov.cn/ewebeditor/uploadfile/2015/01/13/20150113092346124.pdf} % {《夹用英文的中文文本的标点符号用法(草案)》5.13 节。}}应为半角宽度。 % \begin{macrocode} \xeCJKsetwidth { ^^^^2013 } { 0.5 em } % \end{macrocode} % % \changes{v3.7.0}{2018/03/16}{不再默认引入 \pkg{xunicode} 宏包。} % % 执行宏包选项,并载入 \pkg{fontspec} 宏包。 % \begin{macrocode} \cs_if_exist:NTF \ProcessKeyOptions { \ProcessKeyOptions [ xeCJK / options ] } { \RequirePackage { l3keys2e } \ProcessKeysOptions { xeCJK / options } } \RequirePackage { fontspec } [ 2020/02/03 ] % \end{macrocode} % % \begin{variable}{\c_@@_encoding_tl} % 保存 \pkg{fontspec} 声明字体时使用的字体编码。 % \begin{macrocode} \tl_const:Ne \c_@@_encoding_tl { \g_fontspec_encoding_tl } % \end{macrocode} % \end{variable} % % \changes{v3.1.0}{2012/11/21}{改用 \pkg{indentfirst} 宏包处理缩进的问题。} % % 对不能通过 \tn{xeCJKsetup} 设置的选项给出警告。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { LocalConfig .code:n = { \@@_warning:ne { option-invalid } { \l_keys_key_str } } } \@@_msg_new:nn { option-invalid } { The~`#1'~option~can~only~be~set~in~the~optional~argument~to~the\\ \token_to_str:N \usepackage \ command~when~xeCJK~is~being~loaded.\\\\ Please~do~not~set~it~via~the~\token_to_str:N \xeCJKsetup \ command. } % \end{macrocode} % % \begin{variable}{\CJKrmdefault,\CJKsfdefault,\CJKttdefault,\CJKfamilydefault} % \begin{macrocode} \tl_if_exist:NF \CJKrmdefault { \tl_new:N \CJKrmdefault \tl_gset:Nn \CJKrmdefault { rm } } \tl_if_exist:NF \CJKsfdefault { \tl_new:N \CJKsfdefault \tl_gset:Nn \CJKsfdefault { sf } } \tl_if_exist:NF \CJKttdefault { \tl_new:N \CJKttdefault \tl_gset:Nn \CJKttdefault { tt } } \tl_new:N \l_@@_family_default_init_tl \cs_new_eq:NN \@@_family_default_wrap:n \use:n \tl_set:Ne \l_@@_family_default_init_tl { \exp_not:N \@@_family_default_wrap:n { \tl_if_exist:NTF \CJKfamilydefault { \exp_not:o \CJKfamilydefault } { \exp_not:N \CJKrmdefault } } } \tl_if_exist:NF \CJKfamilydefault { \tl_new:N \CJKfamilydefault } \tl_gset_eq:NN \CJKfamilydefault \l_@@_family_default_init_tl % \end{macrocode} % \end{variable} % % \begin{macro}{\xeCJKsetup} % 在导言区或文档中设置 \pkg{xeCJK} 的接口。 % \begin{macrocode} \NewDocumentCommand \xeCJKsetup { +m } { \keys_set:nn { xeCJK / options } {#1} \tex_ignorespaces:D } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJKsetemboldenfactor, \xeCJKsetslantfactor} % \begin{macrocode} \NewDocumentCommand \xeCJKsetemboldenfactor { m } { \xeCJKsetup { EmboldenFactor = {#1} } } \NewDocumentCommand \xeCJKsetslantfactor { m } { \xeCJKsetup { SlantFactor = {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\punctstyle, \xeCJKplainchr} % \begin{macrocode} \NewDocumentCommand \punctstyle { m } { \xeCJKsetup { PunctStyle = {#1} } } \NewDocumentCommand \xeCJKplainchr { } { \xeCJKsetup { PunctStyle = plain } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\CJKsetecglue} % \begin{macrocode} \NewDocumentCommand \CJKsetecglue { m } { \xeCJKsetup { CJKecglue = {#1} } } \cs_new_eq:NN \xeCJKsetecglue \CJKsetecglue % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\CJKspace,\CJKnospace} % \begin{macrocode} \NewDocumentCommand \CJKspace { } { \xeCJKsetup { CJKspace = true } } \NewDocumentCommand \CJKnospace { } { \xeCJKsetup { CJKspace = false } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJKallowbreakbetweenpuncts, \xeCJKnobreakbetweenpuncts} % \begin{macrocode} \NewDocumentCommand \xeCJKallowbreakbetweenpuncts { } { \xeCJKsetup { AllowBreakBetweenPuncts = true } } \NewDocumentCommand \xeCJKnobreakbetweenpuncts { } { \xeCJKsetup { AllowBreakBetweenPuncts = false } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJKenablefallback, \xeCJKdisablefallback} % \begin{macrocode} \NewDocumentCommand \xeCJKenablefallback { } { \xeCJKsetup { AutoFallBack = true } } \NewDocumentCommand \xeCJKdisablefallback { } { \xeCJKsetup { AutoFallBack = false } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJKsetcharclass} % \changes{v3.10.0}{2026/04/24}{\textbf{Deprecated:} % \tn{xeCJKsetcharclass} 已弃用, % 调用时报错并提示改用 \tn{xeCJKDeclareCharClass}(\#709)。} % \begin{macrocode} \@@_msg_new:nn { xeCJKsetcharclass-deprecated } { \token_to_str:N \xeCJKsetcharclass\ is~deprecated~and~ does~not~update~CJKmath~char~lists.\\\\ Use~\token_to_str:N \xeCJKDeclareCharClass\ instead. } \NewDocumentCommand \xeCJKsetcharclass { m m m } { \@@_error:n { xeCJKsetcharclass-deprecated } \xeCJK_set_char_class:nnn {#1} {#2} {#3} \xeCJKResetPunctClass } % \end{macrocode} % \end{macro} % % \subsection{兼容性修补} % % \changes{v3.2.16}{2014/11/20}{修复 \tn{hbar}。} % \changes{v3.8.0}{2020/02/09}{删除 \tn{hbar} 补丁。} % % \begin{macro}[int]{\xeCJK@update@fam,\Url@MathSetup} % \changes{v3.2.7}{2013/11/09}{使通过 \tn{UrlFont} 等命令设置的 CJK 字体生效。} % 使通过 \tn{urlstyle} 或者 \tn{UrlFont} 设置的路径中使用的 CJK 字体生效。 % 使用 \tn{everymath} 钩子中数学模式中重定义 CJK 数学字体,以确保我们的设置在 % \tn{check@mathfonts} 之后生效,不会被它覆盖。 % 更合理的方式是定义一个新的 \tn{mathversion} 来切换。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK@update@fam { \addto@hook \everymath { \@@_update_main_fam: \@@_update_block_fam: } } \cs_new_protected:Npn \@@_update_main_fam: { \group_begin: \xeCJK_select_font: \exp_last_unbraced:NNNo \group_end: \tex_textfont:D \c_xeCJK_math_fam_int \tex_the:D \tex_font:D } \cs_new_protected:Npn \@@_update_block_fam: { \prop_if_empty:NF \g_@@_block_fam_prop { \prop_map_function:NN \g_@@_block_fam_prop \@@_update_block_fam:nn } } \cs_new_protected:Npn \@@_update_block_fam:nn #1#2 { \int_set:Nn \l_@@_fam_int {#2} \group_begin: \xeCJK_select_font:n {#1} \exp_last_unbraced:NNNo \group_end: \tex_textfont:D \l_@@_fam_int \tex_the:D \tex_font:D } \@@_after_end_preamble:n { \bool_lazy_and:nnT { \g_@@_math_bool } { \cs_if_exist_p:N \Url@MathSetup } { \tl_put_right:Nn \Url@MathSetup { \xeCJK@update@fam } } } % \end{macrocode} % \end{macro} % % \changes{v3.2.6}{2013/08/02}{为 \tn{mathrm} 减少一个可能的数学字体族。} % \changes{v3.8.0}{2020/02/09}{删除 \tn{mathrm} 补丁。} % % \changes{v3.4.5}{2017/01/02}{更新 \LaTeXiii{} 的过时用法。} % % \begin{macro}[int]{\(,\),\math,\endmath,\ensuremath} % \begin{macro}{ % \@@_math_robust:N, % \@@_boundary_math_begin:, % \@@_boundary_math_begin:n, % \@@_boundary_register_text_command:N, % \@@_boundary_register_text_command:NN} % \changes{v3.2.5}{2013/07/25} % {解决汉字后紧跟 \tn{(}\texttt{...}\tn{)} 形式的行内数学公式时,不能加入间距的问题。} % \changes{v3.2.6}{2013/08/03}{考虑 \pkg{ulem} 对 \tn{MakeRobust} 的不当定义。} % \changes{v3.2.6}{2013/08/03}{考虑 \tn{math} 和 \tn{ensuremath}。} % \changes{v3.3.1}{2015/04/14}{兼容 \LaTeXe{} 2015。} % \tn{(} 在 \LaTeXe 中的定义是 % \begin{verbatim} % \def\({\relax\ifmmode\@badmath\else$\fi} % \end{verbatim} % 这个定义最开始的 \tn{relax} 是为了防止 \tn{(} 出现在表格单元格的开始位置时,模式 % 判断不正确(因为 \TeX 会先看单元格中第一个不可展的非空格记号是否是 \tn{omit} 或 % \tn{noalign})。但是它会造成一个边界,使 \pkg{xeCJK} 不能看到 \tn{relax} 后面出现的 % |$|,从而不能加入间距\footnote{\url{http://tex.stackexchange.com/q/124773}}。使用 % \hologo{eTeX} 的 \tn{protected} 来定义它,可以不需要 \tn{relax},或者将 \tn{relax} % 改成 \cs{scan_align_safe_stop:},都可以避免这些情况。同时 \pkg{fixltx2e} 中还使用了 % |\MakeRobust\(|,我们需要小心处理。另外 \pkg{ulem} 也定义了一个 \tn{MakeRobust}, % 如果它被放在 \pkg{fixltx2e} 之前载入,那么 \pkg{fixltx2e} 的定义就会失效(因为 % \pkg{fixltx2e} 使用 \tn{providecommand}|*| 来定义 \tn{MakeRobust})。但是 \pkg{ulem} % 的定义并不完全正确,没有考虑 \TeX 不会略去控制符号后面的空格的情况。 % \begin{macrocode} \cs_new_protected:Npn \@@_math_robust:N #1 { \group_begin: \exp_args:NcNc \group_end: { @@_math_robust_aux:NN } #1 { \cs_to_str:N #1 ~ } } \cs_new_protected:Npn \@@_math_robust_aux:NN #1#2 { \exp_args:Ne \str_case:nnTF { \cs_replacement_spec:N #1 } { { \x@protect #1 \protect #2 } { } { \protect #2 } { } } { \@@_math_robust:NN #1#2 } { \@@_math_robust:NN #1#1 } } \cs_new_protected:Npn \@@_math_robust:NN #1#2 { \str_if_eq:eeTF { \cs_argument_spec:N #2 } { } { \exp_args:No \tl_if_head_eq_meaning:nNTF {#2} \scan_stop: { \cs_gset_protected:Npe #1 { \tl_tail:N #2 } } { \cs_if_eq:NNTF #1 \ensuremath { \cs_gset_protected:Npe #1 { \exp_not:o {#2} } } { \@@_warning:nee { robust-failure } { \token_to_str:N #1 } { \token_to_meaning:N #2 } } } } { \@@_warning:nee { robust-failure } { \token_to_str:N #1 } { \token_to_meaning:N #2 } } } \@@_msg_new:nnn { robust-failure } { xeCJK~can~not~make~`#1'~robust. } { The~current~meaning~of~`#1'~is:\\ \iow_indent:n {#2} } \cs_if_eq:NNTF \( \math { \@@_math_robust:N \( \cs_set_eq:NN \math \( } { \@@_math_robust:N \( \@@_math_robust:N \math } \cs_if_eq:NNTF \) \endmath { \@@_math_robust:N \) \cs_set_eq:NN \endmath \) } { \@@_math_robust:N \) \@@_math_robust:N \endmath } \@@_math_robust:N \ensuremath % \end{macrocode} % stream 的左侧间距必须排在 math-on 节点之前。三个显式公式入口只处理 % 当前列表的边界,再调用已经完成 robust 处理的原命令;它们不直接向所有 % 外层 capture 报告,否则原语 \tn{setbox} 中不可见的测量公式也会被当成 % 外层输出。已经注册的命令分别检查可见正文的开头和结尾:开头是 |$|、 % \tn{(} 或 \tn{ensuremath} 时,在 math-on 前报告首类别;结尾是对应公式 % 时,把 |math| 记为当前 capture 的确定末类别。\tn{ensuremath} 已在数学 % 模式中时不重复处理当前列表。 % \begin{macrocode} \clist_map_inline:nn { ( , math , ensuremath } { \@@_boundary_reserve:n {#1} } \cs_if_eq:NNTF \( \math { \cs_new_eq:NN \@@_orig_inline_math_begin: \( \cs_gset_protected:Npn \( { \@@_boundary_math_begin: \@@_orig_inline_math_begin: } \cs_gset_eq:NN \math \( } { \cs_new_eq:NN \@@_orig_inline_math_begin: \( \cs_gset_protected:Npn \( { \@@_boundary_math_begin: \@@_orig_inline_math_begin: } \cs_new_eq:NN \@@_orig_math_begin: \math \cs_gset_protected:Npn \math { \@@_boundary_math_begin: \@@_orig_math_begin: } } \cs_new_eq:NN \@@_orig_ensuremath: \ensuremath \cs_gset_protected:Npn \ensuremath { \mode_if_math:TF { \@@_orig_ensuremath: } { \@@_boundary_math_begin: \@@_orig_ensuremath: } } % \end{macrocode} % LaTeX 标准文本字体命令的 robust 内部实现均为单参数。这里保存该内部实现, % 用 stream 包住公式位于正文任一边缘的调用;开头公式在 math-on 前报告, % 结尾公式在正文参数实际排完时确认。普通正文仍由 interchar 转换报告实际 % 首尾类别。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_text_command:N #1 { \group_begin: \exp_args:NcNc \group_end: { @@_boundary_register_text_command:NN } #1 { \cs_to_str:N #1 ~ } } \cs_new_protected:Npn \@@_boundary_register_text_command:NN #1#2 { \@@_boundary_reserve:N #1 \@@_boundary_reserve:N #2 \exp_args:Nc \cs_new_eq:NN { @@_boundary_orig_\cs_to_str:N #1 :n } #2 \cs_gset_protected:Npn #2 ##1 { \@@_boundary_if_math_edge:nTF {##1} { \@@_boundary_inline_stream_begin:n { auto } \@@_boundary_math_begin:n {##1} \use:c { @@_boundary_orig_\cs_to_str:N #1 :n } { ##1 \@@_boundary_math_end:n {##1} } \@@_boundary_inline_stream_end:n { auto } } { \use:c { @@_boundary_orig_\cs_to_str:N #1 :n } {##1} } } } \clist_map_inline:nn { \textrm , \textsf , \texttt , \textnormal , \textbf , \textmd , \textit , \textsl , \textsc , \textup , \emph } { \@@_boundary_register_text_command:N #1 } % \end{macrocode} % \end{macro} % \end{macro} % % \changes{v3.2.5}{2013/07/25}{解决 \pkg{fixltx2e} 和 \pkg{amsthm} 的冲突。} % \changes{v3.3.1}{2015/04/15}{删去 \pkg{fixltx2e} 和 \pkg{amsthm} 的冲突补丁。} % % \changes{v3.1.2}{2013/01/01} % {修正非 \tn{UTFencname} 编码下面 \pkg{xunicode} 重定义的 \tn{nobreakspace} 会失效的问题。} % \changes{v3.2.5}{2013/07/18}{恢复 \tn{nobreakspace} 的原始定义。} % \changes{v3.7.0}{2018/03/18}{对 \tn{nobreakspace} 的恢复放到 \pkg{xunicode-addon} 中处理。} % % \changes{v3.1.1}{2012/12/13}{对于与 \pkg{xltxtra} 的冲突给出错误警告。} % \changes{v3.8.0}{2020/02/09}{删除 \pkg{realscripts} 补丁。} % % \changes{v3.8.6}{2020/10/17}{兼容 \LaTeX\ 2020/10/01 的 \pkg{NFSS} 钩子机制。} % % \begin{macro}[int]{\fontfamily} % \begin{macro}[int]{\xeCJK@family} % \changes{v3.1.1}{2012/12/06}{修改主要 \texttt{CJK} 字体族的自动更新方式。} % \changes{v3.1.2}{2013/01/01}{不将参数完全展开。} % \changes{v3.4.6}{2017/02/23} % {将族名参数完全展开,以解决与 \pkg{fontspec} 2017/01/24 v2.5d 的兼容问题。} % \changes{v3.10.0}{2026/04/24}{\textbf{Breaking:} % 移除对 \LaTeXe\ 2020/10/01 之前版本的字体钩子兼容代码(\#746)。} % 使用 \LaTeXe\ 2020/10/01 提供的 \pkg{NFSS} 钩子, % 使主要 |CJK| 字体族能随西文主要字体更新。 % \begin{macrocode} \cs_set_eq:NN \xeCJK@family \xeCJK_switch_family:e \ctex_gadd_ltxhook:nn { rmfamily } { \xeCJK@family { \CJKrmdefault } } \ctex_gadd_ltxhook:nn { sffamily } { \xeCJK@family { \CJKsfdefault } } \ctex_gadd_ltxhook:nn { ttfamily } { \xeCJK@family { \CJKttdefault } } \ctex_gadd_ltxhook:nn { normalfont } { \xeCJK@family { \CJKfamilydefault } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} %<@@=> % \end{macrocode} % % \begin{macro}[int]{\xeCJK@fix@penalty} % \changes{v3.1.0}{2012/11/13}{采用通过不修改原语 \tn{/} 的方式对修复倾斜校正。} % \LaTeXe 内核中的 \tn{fix@penalty} 被用于诸如 \tn{textit} 之类的文档 % 字体转换命令的定义之中。这里对它进行补丁的目的是修复其中的倾斜校正,并使得这些 % 文档命令与紧随其后的汉字之间可以正确的插入 \tn{CJKecglue} 或者忽略其中的空格。 % 例如 \verb*|这是 \emph{强调} 文本|,第二个空格可以被忽略掉。如果使用 |xCJKecglue| % 选项,第一个空格也可以被省略。事实上,在 \tn{sw@slant} 的定义中,\tn{@@italiccorr} % 前面的 \tn{lastskip} 和 \tn{lastpenalty} 有四种情况,这里只对它们都为零的情况进行 % 处理。 % \begin{macrocode} \cs_new_eq:NN \xeCJK@fix@penalty \fix@penalty \tl_replace_once:Nnn \xeCJK@fix@penalty { \@@italiccorr } { \xeCJK@italiccorr } \tl_replace_once:Nnn \sw@slant { \fix@penalty } { \xeCJK@fix@penalty } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK@italiccorr} % 修复倾斜校正,并处理汉字后面的空格。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK@italiccorr { \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int { \xeCJK_italic_correction: } { \@@italiccorr } } % \end{macrocode} % \end{macro} % % \begin{macrocode} %<@@=xeCJK> % \end{macrocode} % % \begin{macro}[int]{\xeCJK_italic_correction:} % 修复倾斜校正,并处理汉字后面的空格。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_italic_correction: { \@@_if_last_kern:T { \@@_italic_correction: } } \cs_new_protected:Npn \@@_italic_correction: { \dim_case:nnF { \tex_lastkern:D } { { \@@_node:n { default } } { \xeCJK_remove_node: \tex_italiccorrection:D \xeCJK_make_node:n { default } } { \@@_node:n { CJK } } { \xeCJK_remove_node: \tex_italiccorrection:D \xeCJK_make_node:n { CJK } \@@_italic_correction_aux: } { \@@_node:n { CJK-space } } { \xeCJK_remove_node: \tex_italiccorrection:D \xeCJK_make_node:n { CJK-space } \@@_italic_correction_aux: } } { \tex_italiccorrection:D } } % \end{macrocode} % \cs{xeCJK_ignore_spaces:w} 里面用到 |peek| 函数来判断后面是不是空格,而此时它 % 后面还有 $4$ 个 \tn{fi} 或者 |\else...\fi| 没有被展开,将影响 |peek| 函数的 % 判断。因此我们需要用 $2^4-1=15$ 个 \cs{exp_after:wN} 来展开它们。 % 显然,这里用 \cs{exp_last_unbraced:Nf} 会比较方便,但是它会吃掉 % \verb*|\textit{...} | 等后面原来存在的空格作为完全展开的结束。要正确使用它还 % 需要另外的处理(使用 \cs{exp_stop_f:})。 % \begin{macrocode} \cs_new_protected:Npn \@@_italic_correction_aux: { \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \exp_after:wN \xeCJK_ignore_spaces:w } % \end{macrocode} % \end{macro} % % \begin{variable}{\g_@@_xetex_allocator_int} % \changes{v3.3.1}{2015/04/14}{兼容 \LaTeXe{} 2015。} % \changes{v3.3.2}{2015/05/15}{\tn{xe@alloc@intercharclass} 总是有定义的。} % \LaTeXe{} 2015/01/01 接管了 \tn{newXeTeXintercharclass}。 % \begin{macrocode} \cs_new_eq:NN \g_@@_xetex_allocator_int \xe@alloc@intercharclass % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_set_others_toks:n} % 简单处理与同样使用 \tn{XeTeXinterchartoks} 机制的宏包的兼容问题。 % \begin{macrocode} \@@_after_end_preamble:n { \int_compare:nNnF { \c_@@_class_begin_int + \seq_count:N \g_@@_new_class_seq } = { \g_@@_xetex_allocator_int } { \int_step_inline:nnn { \c_@@_class_begin_int + 1 } { \g_@@_xetex_allocator_int } { \seq_if_in:NnF \g_@@_new_class_seq {#1} { \@@_set_others_toks:n {#1} } } } } \cs_new_protected:Npn \@@_set_others_toks:n #1 { \int_set:cn { \@@_class_csname:n { Others } } {#1} \seq_map_inline:Nn \g_@@_CJK_class_seq { \xeCJK_copy_inter_class_toks:nnnn {##1} { Others } {##1} { NormalSpace } \xeCJK_copy_inter_class_toks:nnnn { Others } {##1} { NormalSpace } {##1} \xeCJK_app_inter_class_toks:nne {##1} { Others } { \xeCJK_get_inter_class_toks:nn { Default } { Others } } \xeCJK_pre_inter_class_toks:nne { Others } {##1} { \xeCJK_get_inter_class_toks:nn { Others } { Default } } \tl_if_blank:eT { \xeCJK_get_inter_class_toks:nn { Others } { Boundary } } { \xeCJK_copy_inter_class_toks:nnnn { Others } { Boundary } { Default } { Boundary } } \tl_if_blank:eT { \xeCJK_get_inter_class_toks:nn { Boundary } { Others } } { \xeCJK_copy_inter_class_toks:nnnn { Boundary } { Others } { Boundary } { Default } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_inactive_group_begin:,\@@_inactive_group_end:} % 用于保护下面歧义宽度标点的分组。 % \begin{macrocode} \cs_new_protected:Npn \@@_inactive_group_begin: { \group_begin: \makexeCJKinactive } \cs_new_eq:NN \@@_inactive_group_end: \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_patch_text_command:} % \begin{variable}{\c_@@_ambiguous_char_prop} % 单独处理宽度有分歧的几个标点:包括省略号、破折号、间隔号、引号等中西文混用的 % 符号,保证其命令形式输出的是西文字体。 % 如果 \pkg{xunicode} 宏包被载入,则通过 \pkg{xunicode-addon} 处理。 % \begin{macrocode} \prop_const_from_keyval:Nn \c_@@_ambiguous_char_prop { "00B7 = \textperiodcentered \textcentereddot \textcdot , "2013 = \textendash , "2014 = \textemdash , "2018 = \textquoteleft \textgrq , "2019 = \textquoteright , "201C = \textquotedblleft \textgrqq , "201D = \textquotedblright , "2025 = \texthdotfor , "2026 = \textellipsis , "2027 = \texthyphenationpoint , "2E3A = \texttwoemdash } \@@_at_end_preamble:n { \@@_patch_text_command: } \cs_new_protected:Npn \@@_patch_text_command: { \xeCJK_if_package_loaded:nTF { xunicode } { \@@_patch_xunicode_ambiguous_char: } { \exp_args:Ne \@@_patch_tuenc_ambiguous_char:n { \UnicodeEncodingName } \@@_patch_tuenc_accent: \@@_patch_tuenc_composite: } } \cs_new_protected:Npn \@@_patch_xunicode_ambiguous_char: { \RequirePackage { xunicode-addon } \prop_map_inline:Nn \c_@@_ambiguous_char_prop { \tl_map_inline:nn { ##2 } { \xunadd_set_begin_hook:nn { ####1 } { \@@_inactive_group_begin: } \xunadd_set_end_hook:nn { ####1 } { \@@_inactive_group_end: } } } \xunadd_append_begin_hook:n { \xeCJK_make_boundary: } } \cs_new_protected:Npn \@@_patch_tuenc_ambiguous_char:n #1 { \prop_map_inline:Nn \c_@@_ambiguous_char_prop { \tl_map_inline:nn { ##2 } { \cs_if_exist:NF ####1 { \DeclareTextSymbol ####1 {#1} { ##1 } } \@@_patch_ambiguous_char:nN {#1} ####1 } } } \cs_new_protected:Npn \@@_patch_ambiguous_char:nN #1#2 { \exp_args:Ne \@@_patch_ambiguous_char:nn { #1 \token_to_str:N #2 } { #1 - #2 } } \cs_new_protected:Npe \@@_patch_ambiguous_char:nNn #1#2#3 { \exp_not:N \exp_args:Ne \exp_not:N \@@_patch_ambiguous_char:nn { \c_backslash_str #1 \exp_not:N \token_to_str:N #2 - \exp_not:N \token_to_str:N #3 } { #1 - #2#3 } } \cs_new_protected:Npn \@@_patch_ambiguous_char:nn #1#2 { \cs_if_free:cF {#1} { \exp_args:Nc \@@_patch_ambiguous_char:Nn {#1} {#2} } } \cs_new_protected:Npn \@@_patch_ambiguous_char:Nn #1#2 { \token_if_chardef:NTF #1 { \prop_gput:Nne \g_@@_ambiguous_slot_prop {#2} { \int_eval:n {#1} } \cs_set_protected:Npe #1 { \@@_ambiguous_char:n { \tex_Uchar:D #1 } } } { \prop_gput:Nne \g_@@_ambiguous_slot_prop {#2} { \int_eval:n { \exp_after:wN ` #1 } } \cs_set_protected:Npe #1 { \@@_ambiguous_char:n { \exp_not:o {#1} } } } } \cs_new_protected:Npn \@@_ambiguous_char:n #1 { \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int { \@@_inactive_group_begin: #1 \@@_inactive_group_end: } {#1} } \prop_new:N \g_@@_ambiguous_slot_prop % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\@@_patch_tuenc_composite:} % \changes{v3.7.2}{2018/05/02}{修复补丁错误。} % \tn{DeclareUnicodeComposite} 具有检查字符是否存在的功能, % 当符号命令紧跟在 CJK 字符类后面时,需要使字体回到西文状态 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_text_composite_patch: { \str_if_eq:eeT { \f@encoding } { \UnicodeEncodingName } { \xeCJK_make_boundary: } } % \end{macrocode} % 注意 \cs{xeCJK_text_composite_patch:} 可能会结束分组,从而导致 |##1| 没有定义时是 % \tn{undefined} 而不是 \tn{relax},所以不能将它与 \tn{relax} 作比较。 % \begin{macrocode} \cs_new_protected:Npn \@@_patch_tuenc_composite: { \cs_set_nopar:Npn \@text@composite@x { \xeCJK_text_composite_patch: \cs_if_exist_use:NF } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_patch_tuenc_accent:} % \tn{add@unicode@accent} 定义最后用于截断数字展开的 \tn{relax} 会造成边界,可能会影响组合标记。 % \begin{macrocode} \group_begin: \char_set_catcode_other:n { "A0 } \cs_new_protected:Npn \@@_patch_tuenc_accent: { \cs_set_protected_nopar:Npn \add@unicode@accent ##1 ##2 { \tl_if_blank:nTF { ##2 } { ^^a0 } { ##2 } \tex_Uchar:D \tex_numexpr:D ##1 \scan_stop: } } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_patch_middle_dot:} % \begin{variable}{\c_@@_middle_dot_prop} % \changes{v3.2.9}{2013/12/08} % {完整处理 \file{encguide.pdf} 的编码符号表中,与旧编码的 \texttt{U+00B7} 冲突。} % 常被用作中文间隔号的 \texttt{U+00B7} 与 T1 等旧字体编码下定义的符号命令冲突。 % 在 \file{encguide.pdf} 的编码符号表中,如下定义有冲突。 % \begin{verbatim} % \DeclareTextComposite{\r}{T1}{u}{183} % \DeclareTextSymbol{\cyrchvcrs}{T2A}{183} % \DeclareTextSymbol{\cyrchldsc}{T2B}{183} % \DeclareTextSymbol{\cyrabhha}{T2C}{183} % \DeclareTextSymbol\textvibyy{T3}{183} % \DeclareTextComposite{\B}{T4}{t}{183} % \DeclareTextComposite{\`}{T5}{\ecircumflex}{183} % \DeclareTextDoubleComposite{\`}{T5}{\^}{e}{183} % \DeclareTextSymbol{\textperiodcentered}{TS1}{183} % \DeclareTextSymbol{\cyrchldsc}{X2}{183} % \DeclareTextSymbol{\textperiodcentered}{LY1}{183} % \end{verbatim} % LGR 编码的符号表有 \texttt{183} 号字符,但在 \file{lgrenc.def} 中未找到相应的 % 符号命令。 % \begin{macrocode} \prop_const_from_keyval:Nn \c_@@_middle_dot_prop { T2A = \cyrchvcrs , T2B = \cyrchldsc , T2C = \cyrabhha , X2 = \cyrchldsc , TS1 = \textperiodcentered , LY1 = \textperiodcentered , T1 = \r u , T4 = \B t , T5 = \` \ecircumflex } \@@_at_end_preamble:n { \@@_patch_middle_dot: } \cs_new_protected:Npn \@@_patch_middle_dot: { \prop_map_inline:Nn \c_@@_middle_dot_prop { \@@_patch_middle_dot:nw { ##1 } ##2 \q_stop } \@@_patch_ambiguous_char:nNn { T5 } \` { \^ - e } } \cs_new_protected:Npn \@@_patch_middle_dot:nw #1#2#3 \q_stop { \tl_if_empty:nTF {#3} { \@@_patch_ambiguous_char:nN {#1} #2 } { \@@_patch_ambiguous_char:nNn {#1} #2 {#3} } } % \end{macrocode} % \pkg{pifont} 宏包的符号 |\ding{183}| 也有冲突。 % \changes{v3.10.0}{2026/04/24}{在 \tn{Pifont} 中先进入水平模式, % 防止 \tn{makexeCJKinactive} 在垂直模式下通过分页泄漏到输出例程(\#688)。} % \begin{macrocode} \@@_package_hook:nn { pifont } { \RenewDocumentCommand \Pifont { m } { \mode_leave_vertical: \makexeCJKinactive \usefont { U } {#1} { m } { n } } } % \end{macrocode} % \end{variable} % \end{macro} % % \changes{v3.10.0}{2026/04/27}{新增 \tn{xeCJKchar},绕过 interchar 机制(\#407)。} % \changes{v3.10.0}{2026/04/29}{撤回对 \tn{char} 的重定义,改为提供 \tn{xeCJKchar} % 新命令,避免破坏依赖 |\let| 保存 \tn{char} 原语的宏包(\#800)。} % % \begin{macro}{\xeCJKchar, \xeCJK_default_char:w} % \begin{macro}{\@@_default_char_aux:} % \begin{variable}{\l_@@_default_char_int} % \tn{char} 是 \TeX{} 原语,用户使用它直接通过字符编码从当前字体取字形, % 不经过 NFSS 层。一些宏包(如 \pkg{mtpro2})在数学模式中用 \tn{char} 取 % 非 CJK 字形时,会被 \pkg{xeCJK} 的 interchar 机制拦截并切换到中文字体, % 导致字形变成中文标点(如间隔号,\#407)。 % % 参照 \LuaTeX-ja 提供 |\ltjalchar|/|\ltjjachar| 新命令的思路, % \pkg{xeCJK} 提供 \tn{xeCJKchar} 命令,在输出字符前临时关闭 % |XeTeXinterchartokenstate|,保证输出的字符来自当前字体而不被 interchar % 拦截。与 \LuaTeX-ja 不同的是,XeTeX 的 interchar 机制工作在 token 层面, % 没有节点级回调,因此无法自动区分 \tn{char} 和直接字符输入。 % \tn{xeCJKchar} 供用户和第三方宏包显式使用,\tn{char} 本身不做任何修改。 % % 用法与 \tn{char} 完全相同:|\xeCJKchar"00B7| 或 |\xeCJKchar183|。 % 用户指南和手动补丁方法参见“已知问题和兼容性”一节。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_default_char:w { \int_compare:nNnTF \tex_XeTeXinterchartokenstate:D > \c_zero_int { \c_group_begin_token \tex_afterassignment:D \@@_default_char_aux: \l_@@_default_char_int = } { \tex_char:D } } \cs_new_protected:Npn \@@_default_char_aux: { \tex_XeTeXinterchartokenstate:D = \c_zero_int \tex_char:D \l_@@_default_char_int \c_group_end_token } \int_new:N \l_@@_default_char_int \cs_new_eq:NN \xeCJKchar \xeCJK_default_char:w % \end{macrocode} % \end{variable} % \end{macro} % \end{macro} % % \changes{v3.10.0}{2026/04/29}{为 \pkg{mtpro2} 提供兼容补丁, % 使大花括号内部的 \tn{char} 不被 interchar 拦截(\#407)。} % % \pkg{mtpro2} 的 \tn{overcbrace} 和 \tn{undercbrace} 通过 % |\char| 访问 |mt2exe| 等字体的特殊位置,其中部分位置 % (如 183\,=\,|U+00B7|)与 \pkg{xeCJK} 的 interchar 字符分类冲突。 % 由于这两个命令只用在数学模式中,参数不含 CJK 字符,可以在入口处 % 直接关闭 |XeTeXinterchartokenstate|,由数学模式的分组自动恢复。 % \begin{macrocode} \@@_package_hook:nn { mtpro2 } { \cs_set_eq:NN \@@_mtpro_overcbrace:w \overcbrace \cs_gset_protected:Npn \overcbrace #1 { \makexeCJKinactive \@@_mtpro_overcbrace:w {#1} } \cs_set_eq:NN \@@_mtpro_undercbrace:w \undercbrace \cs_gset_protected:Npn \undercbrace #1 { \makexeCJKinactive \@@_mtpro_undercbrace:w {#1} } } % \end{macrocode} % % \changes{v3.8.3}{2020/04/09}{兼容 \pkg{unicode-math} 和 \opt{CJKmath} 选项。} % % \begin{macro}{\@@_save_um_char:, \@@_save_um_char:} % 兼容 \pkg{unicode-math} 和 \opt{CJKmath} 选项, % 避免将一些中西文混用的标点设置为 CJK 字体。 % \begin{macrocode} \@@_package_hook:nn { unicode-math } { \prop_const_from_keyval:Nn \c_@@_um_ambiguous_char_prop { "00B7 = \cdotp , "2025 = \enleadertwodots , "2026 = \unicodeellipsis } \cs_new_protected:Npn \@@_save_um_char: { \cs_set_protected:Npe \@@_restore_um_char: { \prop_map_function:NN \c_@@_um_ambiguous_char_prop \@@_restore_um_char_aux:nn } } \cs_new_eq:NN \@@_restore_um_char: \prg_do_nothing: \cs_new:Npn \@@_restore_um_char_aux:nn #1#2 { \@@_gset_mathcodenum:nn { \int_value:w #1 } { \int_value:w \tex_Umathcodenum:D #1 } } \cs_new_protected:Npn \@@_gset_mathcodenum:nn #1#2 { \int_compare:nNnF { \tex_Umathcodenum:D #1 } = {#2} { \tex_global:D \tex_Umathcodenum:D #1 = #2 ~ } } } % \end{macrocode} % \end{macro} % % \changes{v3.8.5}{2020/06/25}{进一步兼容 \pkg{microtype}。} % % \begin{macro}{\@@_patch_microtype_get_slot:} % 兼容 \pkg{microtype}。 % \begin{macrocode} \cs_new_protected:Npn \@@_patch_microtype_get_slot: { \cs_new_eq:NN \xeCJK@original@get@slot \MT@get@slot@ \cs_set_eq:NN \MT@get@slot@ \xeCJK@microtype@get@slot \cs_set_eq:NN \MT@warn@unknown@once \use_none:n } \cs_new_protected_nopar:Npn \xeCJK@microtype@get@slot { \int_compare:nNnT \MT@char < \c_zero_int { \@@_get_ambiguous_slot: } \xeCJK@original@get@slot } \cs_new_protected:Npn \@@_get_ambiguous_slot: { \prop_get:NeNT \g_@@_ambiguous_slot_prop { \MT@encoding - \tex_the:D \MT@toks } \l_@@_tmp_tl { \cs_set_eq:NN \MT@char \l_@@_tmp_tl } } \cs_new_protected:Npn \xeCJK@microtype@restore@pickupfont { \@@_gadd_font_initial_hook:n { \MT@ltx@pickupfont } } \@@_package_hook:nn { microtype } { \cs_if_free:NF \MT@get@slot@ { \@@_patch_microtype_get_slot: } \MT@addto@setup { \xeCJK@microtype@restore@pickupfont } } % \end{macrocode} % \end{macro} % % 简单处理与 \pkg{hyperref} 宏包的兼容问题。 % \changes{v3.10.0}{2026/05/03}{通过在 \tn{Hy@BeginAnnot} 中保存并 % 选择性恢复 xeCJK 节点标记,同时清除旧标记, % 解决目录中链接注释起始处的虚假 \texttt{ecglue}(\#810), % 并为 \tn{ref} 提供前侧 \texttt{ecglue}(\#809)。} % \changes{v3.10.3}{2026/07/13}{在顶层 \tn{Hy@EndAnnot} 结束前检测 % URL 的数学节点,并在链接结束 whatsit 后放置可信的西文边界标记, % 修复 \tn{url} 右侧的 \texttt{CJKecglue} 丢失问题(\#972)。} % % \changes{v3.10.4}{2026/07/20}{把链接 annotation 的入口保存和末尾定点 % 重放迁移到统一 stream capture,按链接实际首尾可见类别恢复两侧边界 % (\#992)。} % % \changes{v3.10.5}{2026/08/04}{把行内锚点的 \tn{hyper@anchor}、 % \tn{Hy@raisedlink} 与 \cs{__hyp_target_raise:n} 三个出口注册为 transparent % capture,使 \tn{hypertarget}、\tn{phantomsection}、\tn{MakeLinkTarget}、 % 无编号标题锚点与手工包裹的锚点不再遮蔽其前后的实际边界;经 % \tn{hyper@anchorstart} 裸调用的路径(如 \tn{pdfbookmark})尚未覆盖 % (\#1047)。} % % \pkg{hyperref} 的行内锚点会在 xeCJK 的 marker 与后续字符之间插入不可见 % 节点,使 Boundary$\to$\{Default,CJK\} 路径的 marker 探测失败,锚点右侧的 % \tn{CJKecglue} 因此丢失(\#1047)。这类命令没有可见输出,故与 \pkg{hypdoc} % 的 \tn{HD@target} 一样注册为 transparent。 % % 下面\emph{已覆盖}三个出口。区分依据是\emph{调用点},不是锚点内容: % \begin{itemize} % \item 驱动层的 \tn{hyper@anchor} 承接经 \tn{hyper@@anchor} 进来的锚点。 % \tn{hypertarget} 的两个分支最终都走这里——\tn{@hyper@@anchor} 在 % \tn{ifHy@activeanchor} 为假时统一调用 \tn{hyper@anchor},与目标内容 % 是否为空无关。它直接排出裸的 \texttt{pdf:dest} whatsit。 % \item \tn{Hy@raisedlink} 承接需要抬升的锚点:无编号标题 % (\tn{section*}、\tn{chapter*} 以及目录、参考文献等自动生成的 % 无编号标题)、caption、公式编号、脚注、\tn{bibitem},以及下游手工 % 包裹的写法,例如 \cls{ctxdoc} 的 \tn{exptarget} 定义为 % \tn{Hy@raisedlink}\verb*-{-\tn{hypertarget}\marg{name}\verb*-{}}-。 % 它在水平模式下输出 \tn{penalty}\tn{@M} 加一个 \tn{smash} 后的 % \texttt{hbox(0+0)x0} 来盛放 \texttt{pdf:dest} special。 % 注意目录\emph{条目}不走这条路:\tn{contentsline} 用 % \tn{hyper@linkstart}/\tn{hyper@linkend} 做链接,与抬升锚点无关。 % \end{itemize} % 因此两处都要注册:只注册 \tn{Hy@raisedlink} 时所有 \tn{hypertarget} 仍缺 % 间距,只注册 \tn{hyper@anchor} 时 \tn{exptarget} 一类写法与无编号标题锚点 % 仍缺间距。 % % 第三个出口是 \cs{__hyp_target_raise:n}:\tn{phantomsection} 与 % \tn{MakeLinkTarget} 走它,编号标题的锚点也经过它。它自己排出同构的 % \tn{penalty}\tn{@M} 加 \tn{smash} 抬升盒子,不经过 \tn{Hy@raisedlink}。 % 这个函数不接受通用命令 hook(LaTeX hook 机制拒绝 expl3 私有函数),因此用 % \cs{@@_boundary_wrap_transparent_onearg_braced:NN} 包装——必须是\emph{带 % 花括号}转发参数的那个变体,理由见该函数的说明。 % % \emph{已知未覆盖}:\tn{hyper@anchorstart} 的裸调用。\tn{pdfbookmark} 直接写 % \tn{hyper@anchorstart}\marg{name}\tn{hyper@anchorend},既不经 \tn{hyper@@anchor} % 也不经两个抬升出口(计数器实测三者均为 0),因此右侧仍会丢失一枚 % \tn{CJKecglue}(实测 38.33002pt 对 oracle 41.66002pt)。同类裸调用在 % \pkg{hyperref} 与各驱动里还有若干处。 % % 两种就手的补法都已实测不可行:注册 \tn{@pdfm@dest}(\tn{hyper@anchor} 与 % \tn{hyper@anchorstart} 的共同下游)会让盒宽暴涨并报出十余处错误,因为它的 % 参数含有待展开内容;注册 \tn{hyper@anchorstart} 本身不报错,但也不生效 % ——\tn{pdfbookmark} 仍为 38.33002\,pt,而已覆盖的三处不受影响。为什么 % transparent 在这个入口上无效尚未查明,这条路径需要单独设计适配器,另立议题 % 跟踪。 % % 判断这类问题应当读分派函数的分支并用计数器实测,不能按公开命令的参数形态 % 推测走哪条路,也不能因为「注册若干处之后报告的现象消失」就断定出口的数量。 % 本节的机制陈述先后被独立复核推翻四次:「按目标是否为空分派」「只有两个 % 出口」「包装失败源于 begin 钩子的赋值」「只有三个出口」,每一次都是从 % 「注册这些之后现象消失」推出更强的断言,每一次都由一支计数器探针或一次 % 隔离实验推翻。因此本节只列\emph{已覆盖}与\emph{已知未覆盖}两份清单,不再 % 给出总数。 % % \tn{hyper@anchor} 由驱动定义,\pkg{hxetex}、\pkg{hluatex}、\pkg{hpdftex} % 与 \pkg{hdvipdfm} 使用同一名称,\pkg{hdvips} 经 \pkg{pdfmark} 定义同名命令; % 名称虽然一致,各驱动的定义时机仍由 \pkg{hyperref} 决定,注册前先检查 % 是否存在,避免装出无效 hook。\pkg{hyperref} 在 \tn{AtEndOfPackage} 阶段 % 载入驱动,故包尾钩子里的存在性检查时机正确。 % % \tn{Hy@raisedlink} 这一侧不能用 |post-transparent|:那个 after-only 变体 % 只在命令结束后搬移末尾零尺寸盒子下方的 marker 与候选 glue,而 % \tn{Hy@raisedlink} 在盒子\emph{之前}还排出了 \tn{penalty},marker 与盒子 % 并不相邻,搬移条件不成立。transparent 在入口就取走 marker 与可选源码空格, % 锚点节点排出后再原样恢复,因而两种节点次序都能覆盖。 % % \tn{Hy@BeginAnnot} 在 annotation 起始 whatsit 之前启动 stream capture; % 链接文字中的 Default/CJK 转换随后直接记录实际首尾类别。顶层 % \tn{Hy@EndAnnot} 若看到数学节点,而 capture 尚未由显式公式入口记录 % |math|,则把它作为 URL 等命令的可信 Default 输出记录下来;显式 % \tn{hyperref} 或 \tn{href} 参数以字面数学切换符开始或结束时,由 % \tn{hyper@link}/\tn{hyper@linkurl} 的三参数/两参数适配器分别报告首、末 % |math| 类别。结束 whatsit 之后再结束 capture。这样普通西文、中文、 % 混合链接文字、数学 URL 和可见行内公式都保留各自语义;嵌套 annotation % 仍只在最外层结束时收束,不会让内部结束点提前重放状态。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_hyperref_annot: { \clist_map_inline:nn { Hy@BeginAnnot , Hy@EndAnnot , hyper@link , hyper@linkurl , href , hyperref } { \@@_boundary_reserve:n {##1} } \cs_gset_eq:NN \@@_orig_Hy_BeginAnnot:n \Hy@BeginAnnot \cs_gset_eq:NN \@@_orig_Hy_EndAnnot: \Hy@EndAnnot \cs_gset_protected:Npn \Hy@BeginAnnot ##1 { \@@_boundary_inline_stream_begin:n { auto } \@@_orig_Hy_BeginAnnot:n { ##1 } } \cs_gset_protected:Npn \Hy@EndAnnot { \int_compare:nNnT { \c@Hy@AnnotLevel } = { 1 } { \@@_if_last_math:T { \tl_if_eq:cnF { g_@@_boundary_capture_ \int_use:N \g_@@_boundary_capture_depth_int _last_tl } { math } { \@@_boundary_capture_class:n { default } } } } \@@_orig_Hy_EndAnnot: \@@_boundary_inline_stream_end:n { auto } } \cs_gset_eq:NN \@@_orig_hyper_link:nnn \hyper@link \cs_gset_protected:Npn \hyper@link ##1##2##3 { \@@_orig_hyper_link:nnn {##1} {##2} { \@@_boundary_math_begin:n {##3} ##3 \@@_boundary_math_end:n {##3} } } \cs_gset_eq:NN \@@_orig_hyper_linkurl:nn \hyper@linkurl \cs_gset_protected:Npn \hyper@linkurl ##1##2 { \@@_orig_hyper_linkurl:nn { \@@_boundary_math_begin:n {##1} ##1 \@@_boundary_math_end:n {##1} } {##2} } \@@_boundary_register_transparent:n { Hy@raisedlink } \cs_if_exist:NT \hyper@anchor { \@@_boundary_register_transparent:n { hyper@anchor } } \cs_if_exist:NT \__hyp_target_raise:n { \@@_boundary_wrap_transparent_onearg_braced:NN \@@_orig_hyp_target_raise:n \__hyp_target_raise:n } } \@@_package_hook:nn { hyperref } { \pdfstringdefDisableCommands { \@@_gobble_CJKfamily: \xeCJK_cs_clear:N \@@_inactive_group_begin: \xeCJK_cs_clear:N \@@_inactive_group_end: \xeCJK_cs_clear:N \makexeCJKinactive \xeCJK_cs_clear:N \xeCJK_text_composite_patch: } \@@_boundary_register_hyperref_annot: } % \end{macrocode} % % \changes{v3.10.0}{2026/04/30}{为 \pkg{color}/\pkg{xcolor} 添加兼容补丁, % 在颜色切换 whatsit 后重放 xeCJK 节点标记,修复 \tn{textcolor} % 后 \texttt{CJKecglue} 丢失的问题(\#315, \#803)。} % \changes{v3.10.0}{2026/05/01}{修复 \tn{set@color} 补丁在无真实节点标记时 % 未清除全局状态,导致首次 \tn{textcolor} 在标点或段首后插入虚假 % \texttt{ecglue} 的问题(\#807)。} % \changes{v3.10.0}{2026/05/12}{补丁 \tn{reset@color} 以在颜色弹出 whatsit % 之后重新放置 xeCJK 节点标记,修复 \tn{textcolor} 命令右侧 % 多余 inter-word glue 的问题(\#831)。} % \changes{v3.10.0}{2026/05/13}{\tn{reset@color} 补丁增加 hlist 回退路径, % 当 color pop 后最后节点为 hlist 时设置 \cs{g_@@_reset_color_pending_bool}, % 延迟到 \cs{@@_check_for_glue_skip:} 中处理 \tn{colorbox} 等命令 % 右侧间距(\#831)。} % \changes{v3.10.0}{2026/05/14}{修复颜色补丁在 % \cs{g_@@_last_node_tl} 被无关的 \tn{set@color} 清空后 % 仍尝试重放节点导致 \texttt{Missing number} 错误的问题, % 影响 \pkg{listings} 与 \tn{rulecolor} 组合(\#836)。} % \changes{v3.10.4}{2026/07/20}{将 \tn{set@color} 与 \tn{reset@color} % 注册为 transparent capture,删除颜色 push/pop 的专用 marker % save/replay 分支(\#992)。} % \changes{v3.10.0}{2026/05/02}{使用 \cs{l_keys_key_str} 和 % \cs{l_keys_choice_str} 替代已废弃的 \texttt{\_tl} 版本(\#806)。} % % \pkg{color}/\pkg{xcolor} 的颜色切换通过 \tn{set@color} 插入颜色 push % whatsit,再由 aftergroup 中的 \tn{reset@color} 插入 pop whatsit。两者都没有 % 可见输出,因此分别注册为 transparent:入口保存并移除 xeCJK marker 与源码 % 空格,颜色 whatsit 排出后原样恢复。\tn{color@b@x} 会直接写出多个节点, % 继续使用 wrapped-box 收集整个盒子命令的输出。\tn{@textcolor} 的当前 % 三参数内部签名由一层 stream 适配器保留;第三个参数以字面数学切换符开始 % 或结束时,适配器分别在 math-on 前报告首类别、在正文参数实际排完时确认 % 末类别。 % \begin{macrocode} % \tn{colorbox} 和 \tn{fcolorbox} 的扫描器都会把可见正文留作 % \tn{color@b@x} 的第三个参数。先包装这个共同入口,再安装 wrapped-box % hook;调用时便会先启动 capture,再由适配器报告正文两端的公式类别。 % \pkg{xcolor} 会重定义这个私有函数,因此它的 package hook 需要按 % 当前定义重新保存原函数。 \cs_new_protected:Npn \@@_boundary_color_box:nnn #1#2#3 { \@@_boundary_math_begin:n {#3} \@@_orig_color_box:nnn {#1} {#2} { #3 \@@_boundary_math_end:n {#3} } } \cs_new_protected:Npn \@@_boundary_prepare_color_box: { \@@_boundary_reserve:n { color@b@x } \cs_if_eq:NNF \color@b@x \@@_boundary_color_box:nnn { \cs_gset_eq:NN \@@_orig_color_box:nnn \color@b@x \cs_gset_eq:NN \color@b@x \@@_boundary_color_box:nnn } } \cs_new_protected:Npn \@@_boundary_textcolor:nnn #1#2#3 { \@@_boundary_if_math_edge:nTF {#3} { \@@_boundary_inline_stream_begin:n { auto } \@@_boundary_math_begin:n {#3} \@@_orig_textcolor:nnn {#1} {#2} { #3 \@@_boundary_math_end:n {#3} } \@@_boundary_inline_stream_end:n { auto } } { \@@_orig_textcolor:nnn {#1} {#2} {#3} } } \cs_new_protected:Npn \@@_boundary_register_textcolor: { \@@_boundary_reserve:n { @textcolor } \@@_boundary_reserve:n { textcolor } \cs_if_eq:NNF \@textcolor \@@_boundary_textcolor:nnn { \cs_gset_eq:NN \@@_orig_textcolor:nnn \@textcolor \cs_gset_eq:NN \@textcolor \@@_boundary_textcolor:nnn } } \cs_new_protected:Npn \@@_boundary_register_color: { \@@_boundary_reserve:n { colorbox } \@@_boundary_reserve:n { fcolorbox } \@@_boundary_register_transparent:n { set@color } \@@_boundary_register_transparent:n { reset@color } \@@_boundary_prepare_color_box: \@@_boundary_register_wrapped_box:nn { color@b@x } { auto } \@@_boundary_register_textcolor: } \@@_package_hook:nn { color } { \@@_boundary_register_color: } \@@_package_hook:nn { xcolor } { \@@_boundary_register_color: } % \end{macrocode} % % \changes{v3.10.0}{2026/05/14}{补丁 \pkg{l3color} 后端的 % \cs{__color_select:N} 和 \cs{__color_backend_reset:}, % 使 \pkg{l3color} 接口的颜色切换也能正确保持 xeCJK 间距(\#832)。} % \changes{v3.10.4}{2026/07/20}{将 \pkg{l3color} 的颜色 push/pop 入口 % 注册为 transparent capture,删除对应专用 marker 保护分支(\#992)。} % % \pkg{l3color}(\pkg{expl3} 内置)的颜色机制使用独立的后端代码路径, % 不经过 \tn{set@color}/\tn{reset@color}。 % \cs{__color_select:N} 负责颜色推入(调用后端 select 并注册 % aftergroup reset),\cs{__color_backend_reset:} 负责颜色弹出。两者都没有 % 可见输出,使用与 LaTeX2e color push/pop 相同的 transparent capture。 % expl3 内部函数不接受通用 cmd hook,因此只用保持原参数签名的薄适配器 % 包围 framework begin/end,不再复制 marker 检测与重放算法。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_l3color: { \@@_boundary_wrap_transparent_onearg:NN \@@_orig_color_select:N \__color_select:N \@@_boundary_wrap_transparent_noarg:NN \@@_orig_color_backend_reset: \__color_backend_reset: } \@@_boundary_register_l3color: % \end{macrocode} % % \changes{v3.10.0}{2026/06/23}{为 \pkg{hypdoc} 的 \tn{HD@target} 添加补丁, % 在它产生的 hbox 之后重放 xeCJK 节点标记, % 修复 \pkg{l3doc} 中 \tn{cs}、\tn{meta} 等命令后 \texttt{CJKecglue} % 丢失或保留为原始空格的问题(\#873)。} % % \pkg{hypdoc} 的 \tn{HD@target} 通过 \tn{raisebox} 产生 \texttt{hbox(0+0)x0} % 用于放置 \tn{special\{pdf:dest...\}}。该 hbox 隔在 xeCJK 标记 kern % 对和后续字符之间,使 \cs{tex_lastkern:D} 探测失败,导致 % Boundary→\{Default,CJK\} 路径中 \cs{xeCJK_if_last_node:n} / % \cs{@@_if_last_kern:} 检查返回 false,间距因此丢失。 % \changes{v3.10.4}{2026/07/20}{把 \tn{HD@target} 迁移到统一 transparent % capture,同时恢复其前后的 marker 与源码空格状态(\#992)。} % 该命令没有可见输出,因此注册为 transparent:入口保存并移除 marker 与 % 可选源码空格,原命令插入锚点 hbox 后再原样恢复;后续边界不再受这个 % $0\times0$ hbox 遮蔽。 % \begin{macrocode} \@@_package_hook:nn { hypdoc } { \@@_boundary_register_transparent:n { HD@target } } % \end{macrocode} % % \changes{v3.10.4}{2026/07/20}{新增 \pkg{siunitx} 兼容:\tn{unit}、\tn{qty}、\tn{num} % 与 v2 旧名 \tn{si}、\tn{SI} 注册为固定 Default 首尾的 stream % capture,修复 CJK 上下文中单位命令边界的空格丢失(\#1000)。} % % \pkg{siunitx} 用 math 模式排版数字与单位。math 不触发 interchar % 转换,入口的 \tn{mathon} 又盖住左侧 marker,情况与修复前的 \tn{eqref} % 相同;数字与单位输出必然以西文或数字开始、结束,因此也注册为固定 % Default 首尾的 stream capture。v2 旧名 \tn{si}、 % \tn{SI} 是独立顶层命令,不经由 \tn{unit}、\tn{qty} 的 cmd hook,需 % 单独注册。注册前先检查命令是否存在,以兼容未来移除旧名的 % \pkg{siunitx} 版本。若其他宏包恰好定义了同名 \tn{si}/\tn{SI}, % 这项检查也会把它们一并注册—— % 误注册只是多包一层 Default stream,不改变无 CJK 邻接时的排版,记录 % 为已知限制。数学模式内使用(如 |$x=\unit{kg}$|)时,capture 只压入 % inactive 标记,不改变排版。\tn{ang} 输出角度符号,目前还没有确定 % 应与哪种直接输入比较,因此暂不注册。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_siunitx: { \@@_boundary_register_stream:nn { unit } { default } \@@_boundary_register_stream:nn { qty } { default } \@@_boundary_register_stream:nn { num } { default } \cs_if_exist:NT \si { \@@_boundary_register_stream:nn { si } { default } } \cs_if_exist:NT \SI { \@@_boundary_register_stream:nn { SI } { default } } } \@@_package_hook:nn { siunitx } { \@@_boundary_register_siunitx: } % \end{macrocode} % % \changes{v3.10.0}{2026/06/23}{为 \pkg{url} 的 \tn{Url@FormatString} 添加补丁, % 在进入数学模式前 drain 缓存的 \texttt{CJKecglue}, % 修复 CJK 文字与 \tn{url} 命令之间间距丢失的问题(\#880)。} % \changes{v3.10.4}{2026/07/20}{用固定 Default 首尾的 stream capture % 包围 \tn{Url@z} 的完整格式化阶段,删除 \#880 的专用 drain(\#992)。} % % \pkg{url} 的 \tn{url} 命令通过 \tn{Url@FormatString} 进入数学模式 % 排版 URL 字符串。 % 数学模式 node 隔在 xeCJK 标记 kern 对和数学公式之间, % 又因 \XeTeX{} 在数学模式中不触发类别转换(interchar class), % 标记 kern 对永远等不到匹配的 Boundary$\to$\{CJK,Default\} 触发, % 缓存的 \texttt{CJKecglue} 因此在 CJK 与 \tn{url} 之间丢失。 % \tn{url} 同时支持花括号和任意分隔符扫描,不能用普通 % \texttt{cmd/url/before} / \texttt{cmd/url/after} hook 包围;after hook % 会进入分隔符参数本身。 % \tn{Url@z} 已经完成参数扫描,并覆盖 \tn{Url@HyperHook}、数学格式化和 % 最后的分组关闭,因此在这里启动和结束固定 Default 首尾的 stream capture。 % 数学模式不会触发 interchar 转换,但 |default| 模式会显式报告两端类别; % URL 左右边界由统一框架重建,不再需要专用的 marker 移除与补胶逻辑。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_url: { \clist_map_inline:nn { url , path , nolinkurl , Url@z } { \@@_boundary_reserve:n {##1} } \cs_if_exist:NF \@@_orig_Url_z:n { \cs_set_eq:NN \@@_orig_Url_z:n \Url@z \cs_gset_protected:Npn \Url@z ##1 { \@@_boundary_inline_stream_begin:n { default } \@@_orig_Url_z:n {##1} \@@_boundary_inline_stream_end:n { default } } } } \@@_package_hook:nn { url } { \@@_boundary_register_url: } % \end{macrocode} % % \changes{v3.10.1}{2026/06/29}{为 \LaTeX{} 核心命令 \tn{verb} 添加补丁, % 在进入 verb 内容前 drain 缓存的 \texttt{CJKecglue}, % 修复 CJK 文字与 \tn{verb}(或 \pkg{shortvrb} 的短记号) % 之间间距丢失的问题(\#910)。} % % \LaTeXe{} 的 \tn{verb} 命令展开后会执行 \tn{leavevmode\null}, % 其中 \tn{null} 等价于 \tn{hbox\{\}},会产生一个 % \texttt{hbox(0+0)x0} 节点。这个 $0\times0$ 的 hbox 紧跟在 \pkg{xeCJK} % 的标记 kern 对之后,会切断后续 Boundary$\to$\{Default, CJK\} 类别 % 转换中 \cs{tex_lastkern:D} 的探测路径,导致 CJK 文字与 \tn{verb} % 之间应有的 \texttt{CJKecglue} 丢失。 % % \changes{v3.10.4}{2026/07/20}{把 \tn{verb} 从假定西文输出的入口 drain % 迁移到统一 stream capture,按 verbatim 实际首尾类别恢复两侧边界 % (\#992)。} % % 原 \#910 补丁在入口直接补 \tn{CJKecglue},隐含假设 verbatim 内容必为 % 西文,因而不能处理中文输出或右边界。现在在原始 \tn{verb} 执行前启动 % auto stream capture:入口先保存外侧 marker、源码空格和 glue 值;分隔符 % 扫描及排版仍完全交给内核。\tn{verb@egroup} 关闭组并刷新 language whatsit % 后结束 capture,按真正观察到的首尾 Default/CJK 类别同时重建左右边界。 % 数学模式由 capture 入口自动跳过;\pkg{shortvrb} 的短记号与 \tn{verb}* % 共用同一入口和组结束点,因此继续自动覆盖。 % % 补丁仍在 \cs{@@_after_preamble:n} 安装,以避开其他宏包在导言区重定义 % \tn{verb} 的时序。 % % \changes{v3.10.2}{2026/07/03}{\tn{verb} 组关闭时用 \tn{setlanguage} % 主动排出 language whatsit,修复 \tn{verb} 右侧源码空格场景下 % \texttt{CJKecglue} 双倍的问题(\#919)。} % % \tn{verb} 右侧还有一处独立时序(\#919):\tn{verb} 组内把 % \tn{language} 切到 \tn{l@nohyphenation},组关闭后的 language whatsit % 会延迟到下一字符进入列表时才排出。若让它留到 capture 结束之后, % 它会遮住 framework 刚刚重放的末尾 marker。这里仍在 % \tn{verb@egroup} 关组后立即执行 \tn{setlanguage\tn{language}},把 % language whatsit 收进 stream capture;随后 framework 在它之后重放 % 实际末类别,右侧有、无源码空格都交给普通恢复链处理。旧的通用 % whatsit 类别猜测已经删除,不再参与这条路径。 % % \begin{macro}[int]{\@@_flush_language_whatsit:} % 按当前 \tn{language} 值主动排出一个 language whatsit。 % \tn{setlanguage\tn{language}} 只排出 whatsit 记录当前值,不改变 % hyphenation 状态,对断行无副作用。仅在(外层)水平模式下执行: % 数学模式或行首场景没有上述时序问题,restricted horizontal mode % (\tn{hbox} 内)则根本不产生延迟 whatsit。 % 目前唯一调用点是 \tn{verb@egroup}(见下方 % \cs{@@_boundary_register_verb:}); % 未来若有其他切换 \tn{language} 的分组宏需要同样的时序修正, % 可直接复用本函数,并在此注释中登记新的调用点。 % \begin{macrocode} \cs_new_protected:Npn \@@_flush_language_whatsit: { \mode_if_horizontal:T { \tex_setlanguage:D \tex_language:D } } % \end{macrocode} % \end{macro} % % \pkg{shortvrb} 短记号与 \tn{verb}* 都经由 \tn{verb} 入口且组关闭 % 走同一个 \tn{verb@egroup},因此 capture 与 flush 两个补丁都自动覆盖。 % % \begin{macro}[int]{\@@_boundary_register_verb:} % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_verb: { \cs_if_exist:NF \@@_orig_verb: { \cs_set_eq:NN \@@_orig_verb: \verb \cs_gset_protected:Npn \verb { \@@_boundary_inline_stream_begin:n { auto } \@@_orig_verb: } \tl_gput_right:Nn \verb@egroup { \@@_flush_language_whatsit: \@@_boundary_inline_stream_end:n { auto } } } } \@@_boundary_reserve:n { verb } \@@_boundary_reserve:n { verb@egroup } \@@_after_preamble:n { \@@_boundary_register_verb: } % \end{macrocode} % \end{macro} % % \changes{v3.10.1}{2026/06/30}{为 \cls{l3doc} 的 \cs{__codedoc_meta:n} % 添加补丁,将参数内容包入 \cs{hbox:n}, % 修复 \tn{meta} 内首字符前出现多余 \texttt{CJKecglue} 的问题(\#920)。} % \changes{v3.10.2}{2026/07/06}{为 \pkg{doc} 的 \tn{meta} % 添加补丁,将参数内容包入 \cs{hbox:n}, % 修复 \tn{meta} 内首字符前出现多余 \texttt{CJKecglue} 的问题(\#951)。} % % \pkg{l3doc} 类用 \cs{__codedoc_meta:n} 排版 \tn{meta} 命令的参数: % 内部由 \cs{__codedoc_meta_original:n} 依次输出 \tn{ensuremath}\verb*-{-\tn{langle}\verb*-}- % 数学节点、参数内容、以及对称的 \tn{ensuremath}\verb*-{-\tn{rangle}\verb*-}-。 % 当参数内容的首字符是 CJK 时,Default$\to$CJK 类别转换中的 % \cs{xeCJK_pre_inter_class_toks:nnn} 会自动加上 \tn{CJKecglue}, % 这会在 $\langle$ 与首个 CJK 字符之间产生多余的间距。 % % 这里采用 myhsia 在 \pkg{hyperref} / \pkg{hypdoc} 兼容议题中提出的 % \texttt{hbox} 包装方案:把 \cs{__codedoc_meta:n} 的参数装进 % \cs{hbox:n}。hbox 隔离了内部的 \XeTeX{} 类别转换, % Default$\to$CJK 转换不会跨越 hbox 边界触发,从而避免插入多余的 % \tn{CJKecglue}。 % % 仅用 hbox 包装会让入口的 math 节点遮住左侧 marker。适配器因此还要 % 用固定 Default 首尾的 stream capture 包围整条命令:framework 负责外侧 % 两端,hbox 只负责尖括号内部的输出规范化。 % % \changes{v3.10.5}{2026/08/04}{把 \cls{l3doc} 的 stream capture 从内层 % \cs{__codedoc_meta:n} 上移到公开的 \tn{meta},使左右两侧的 % \tn{CJKecglue} 都按命令外的字体度量求值(\#1046)。} % % \textbf{capture 必须包住 \tn{texttt} 而不只是内层参数排版函数}: % \cs{@@_boundary_capture_begin:} 在入口处把 \tn{CJKecglue} 排入临时盒并 % 读成 skip 数值,缓存的度量因此取决于 capture 开始时生效的字体。 % \cls{l3doc} 的 \tn{meta} 定义为 \tn{texttt}\verb*-{-\cs{__codedoc_meta:n} % \Arg{参数}\verb*-}-,若只包围内层的 \cs{__codedoc_meta:n},capture % 就落在 \tn{texttt} 已经切到等宽字体之后:左边界重放的是等宽字体的 % \tn{CJKecglue}(\pkg{lm} 下为不可伸缩的 \texttt{5.25pt}),而右边界在 % capture 结束、字体恢复之后求值,得到正文字体的弹性 \texttt{3.33pt}, % 两侧因此不对称(\#1046)。把 capture 上移到公开 \tn{meta} 之后,两端 % 都在同一字体上下文中求值。 % % \tn{Arg}、\tn{marg}、\tn{oarg}、\tn{parg} 不需要这层 capture:它们在 % \cs{__codedoc_meta:n} 两侧各自排出等宽的 \verb*-{-、\verb*-[-、\verb*-(- % 等实字符,本身就构成正常的 CJK$\to$Default 边界,去掉内层 capture 前后 % 的宽度与可见排版结果完全相同(各类上下文实测),节点列表上少了内层 capture % 原本留下的一对零效果 \texttt{default} marker kern($\pm0.0002$\,pt)。这些入口仍写入 % 保留表,防止用户接口把通用 hook 叠加 % 到共享的内部实现上。 % % 这里选择 \cs{@@_at_end_preamble:n} 而非 \cs{@@_package_hook:nn}, % 原因在于 \pkg{l3doc} 是文档类,文档类加载早于 \pkg{xeCJK}, % 需要等到 preamble 结束时才能判定 \cs{__codedoc_meta:n} 是否定义。 % 同时亦有开发者可能会在 \pkg{xeCJK} 前加载 \pkg{doc} 宏包。 % \begin{macro}[int]{\@@_boundary_register_codedoc_meta:} % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_codedoc_meta: { \cs_if_exist:NTF \__codedoc_meta:n { \clist_map_inline:nn { __codedoc_meta:n , Arg , marg , oarg , parg } { \@@_boundary_reserve:n {##1} } \cs_if_exist:NF \@@_orig_codedoc_meta:n { \cs_set_eq:NN \@@_orig_codedoc_meta:n \__codedoc_meta:n \cs_gset_protected:Npn \__codedoc_meta:n ##1 { \@@_orig_codedoc_meta:n { \hbox:n {##1} } } \@@_boundary_register_stream:nn { meta } { default } } } { \bool_lazy_and:nnT { \cs_if_exist_p:c { ver@doc.sty } } { \cs_if_free_p:N \@@_orig_codedoc_meta:n } { \@@_boundary_reserve:n { meta } \cs_set_eq:NN \@@_orig_codedoc_meta:n \meta \DeclareDocumentCommand \meta { m } { \@@_boundary_inline_stream_begin:n { default } \@@_orig_codedoc_meta:n { \hbox:n {##1} } \@@_boundary_inline_stream_end:n { default } } } } } \@@_at_end_preamble:n { \@@_boundary_register_codedoc_meta: } % \changes{v3.10.4}{2026/07/20}{将 \tn{eqref} 注册为固定 Default % 首尾、将 \tn{cs} 注册为仅固定 Default 首端的 stream capture; % codedoc/doc 的 meta 内部适配器直接包围固定 Default stream,删除专用 % drain 并覆盖其所有公共调用方(\#992)。} % \changes{v3.10.4}{2026/07/20}{将内核 \tn{@setref}(或 \pkg{hyperref} % 保存的 \tn{real@setref})注册为 auto stream capture,以统一框架取代 % \#991 的专用 saved-node/replay 补丁。} % \tn{eqref} 的圆括号和 \tn{meta} 的数学尖括号决定了命令两端 % 始终是 Default;参数内部即使包含中文,也不能把外侧 oracle % 误判为裸中文。\tn{cs} 只有开头的反斜线固定为 Default,末尾仍由 % 参数的实际最后字符决定,因此使用 first-default stream 固定首类别并记录 % 真实末类别。 \@@_at_end_preamble:n { \cs_if_exist:NTF \real@setref { \@@_boundary_register_stream:nn { real@setref } { auto } } { \@@_boundary_register_stream:nn { @setref } { auto } } \cs_if_exist:NT \eqref { \@@_boundary_register_stream:nn { eqref } { default } } \cs_if_exist:NT \cs { \@@_boundary_register_stream:nn { cs } { first-default } } } % \end{macrocode} % \end{macro} % % \changes{v3.10.1}{2026/07/01}{为 \pkg{biblatex} 的 % \tn{blx@pagetracker} 添加补丁,清空 \cs{g_@@_last_node_tl}, % 修复中文参考文献条目首字符前出现多余 \texttt{CJKecglue} 的问题(\#931)。} % % \pkg{biblatex} 在开启 \opt{pagetracker} 选项(\pkg{authoryear} 等 % \texttt{bbx} 样式默认开启)时,每次排版 \tn{bibitem} 会在 % \tn{blx@pagetracker@context} 内调用 \tn{protected@write} 往 % \texttt{aux} 写入 \tn{abx@aux@page} 记录。该 \tn{write} 产生的 % whatsit 节点会遮蔽前一条目结尾的 marker。历史通用 whatsit 回退曾把 % 可能陈旧的 \cs{g_@@_last_node_tl} 当作列表证据,因而在下一条目首字符 % 前误加 \texttt{CJKecglue};该通用猜测现已删除。 % % \changes{v3.10.4}{2026/07/20}{把 \tn{blx@pagetracker} 从单向清空状态 % 改为 transparent capture,使 write whatsit 对两侧实际可见边界透明 % (\#992)。} % % \#931 的旧补丁只清空 \cs{g_@@_last_node_tl},能阻止虚假 ecglue,却也 % 丢掉了 whatsit 两侧本来应有的 CJKecglue/CJKglue。pagetracker 本身没有 % 可见输出,因此现在保存入口 marker、源码空格和 glue 状态,执行 write % 后原样重放;已知 whatsit 不再遮蔽真实边界,也不会把陈旧状态带入下一个 % 无关段落。 % % \textbf{注册点选 \tn{blx@pagetracker} 而非 \tn{blx@pagetracker@context}}: % \pkg{biblatex} 在 \texttt{bbx} style 加载时通过 % \tn{ExecuteBibliographyOptions\{pagetracker\}} 展开 % \tn{blx@opt@pagetracker@page},其内部 \tn{let} % \tn{blx@pagetracker}$\leftarrow$\tn{blx@pagetracker@context}——注意 % 是**\tn{let} 值传递**而非**符号引用**。若补丁挂在 % \tn{blx@pagetracker@context},只能改到源函数;但 \tn{blx@bibitem} % 实际调用的是 \tn{blx@pagetracker} 的 \tn{let} 拷贝,早在 \texttt{bbx} % 加载时就已定型,之后再改源函数已经无效。故补丁点选 \tn{blx@pagetracker} % 本身,并延迟到 \cs{@@_at_end_preamble:n} 执行——此时 \texttt{bbx} 已 % 完全加载,\tn{blx@pagetracker} 已 \tn{let} 到最终目标(可能是 % \tn{blx@pagetracker@context}、\tn{blx@pagetracker@spread} 或 % \tn{relax})。 % % \begin{macro}[int]{\@@_boundary_register_biblatex_pagetracker:} % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_register_biblatex_pagetracker: { \cs_if_exist:NT \blx@pagetracker { \@@_boundary_register_transparent:n { blx@pagetracker } } } \@@_at_end_preamble:n { \@@_boundary_register_biblatex_pagetracker: } % 用户声明在全部内建 preamble 末注册之后应用;普通命令 hook 仍由 LaTeX % 延迟到 \tn{AtBeginDocument} 代码执行完再安装。随后检查目标命令是否存在, % 避免不存在的控制序列被 LaTeX 静默跳过。 \@@_at_end_preamble:n { \@@_boundary_user_register_apply: } \@@_after_end_preamble:n { \@@_boundary_user_register_check: } % \end{macrocode} % \end{macro} % % % \changes{v3.1.0}{2012/11/13}{取消 \tn{cprotect} 的外部宏限制。} % 当探测到 \pkg{cprotect} 宏包被引入时,则取消 \tn{cprotect} 宏的 \tn{outer} 定义。 % \begin{macrocode} \@@_package_hook:nn { cprotect } { \cs_if_free:NF \icprotect { \exp_after:wN \tex_let:D \cs:w cprotect \cs_end: \icprotect } } % \end{macrocode} % % \changes{v3.2.11}{2014/03/14}{删除 \tn{xeCJKcaption}。} % \changes{v3.8.0}{2020/02/09}{删除 \pkg{CJKfntef} 补丁。} % % 在 \pkg{listings} 宏包后自动载入 \pkg{xeCJK-listings}。 % \begin{macrocode} \@@_package_hook:nn { listings } { \RequirePackage { xeCJK-listings } } % \end{macrocode} % % % \changes{v3.3.3}{2015/12/12}{解决与 \pkg{microtype} 宏包的兼容问题。} % 由于 \pkg{xeCJK} 假装 \pkg{CJK} 已经被引入了,这会可能导致旧版本的 \pkg{everysel} 包判断错误。 % 需要在它们判断之前取消定义。 % \begin{macrocode} \@@_package_hook:nn { everysel } { \cs_if_exist:NF \@EverySelectfont@Legacy { \cs_undefine:c { ver@CJK . \c_@@_package_ext_tl } } } % \end{macrocode} % % \changes{v3.2.4}{2013/06/25}{不再使用 \texttt{CJKnumber} 选项,可以在 % \pkg{xeCJK} 之后直接使用 \pkg{CJKnumb} 宏包得到中文数字。} % \changes{v3.8.7}{2021/06/04}{更好地兼容 \pkg{CJKnumb}。} % % \begin{macro}[int]{\CJKaddEncHook} % 为使用 \pkg{CJKnumb} 宏包而作一些处理。另外 \pkg{CJKnumb} 使用的是传统汉字“萬” % 和“億”,我们在这里把它们修正为简体字。 % \changes{v3.2.10}{2014/03/01}{使用 \pkg{CJKnumb} 时,让 \tn{Unicode} 有定义。} % \changes{v3.3.1}{2015/05/08}{应用 \texttt{0.99992} 版的新原语 \tn{Ucharcat}。} % \begin{macrocode} \ctex_at_begin_package:nn { CJKnumb } { \tl_new:N \l_@@_CJK_version_tl \tl_set_eq:Nc \l_@@_CJK_version_tl { ver@CJK . \c_@@_package_ext_tl } \tl_set:cn { ver@CJK . \c_@@_package_ext_tl } { 9999/99/99 } \cs_new_protected:Npn \CJKaddEncHook #1#2 { \str_if_eq:nnT {#1} { \CJK@UnicodeEnc } { \group_begin: \cs_set_eq:NN \Unicode \xeCJK_unicode_char:nn \cs_set_eq:NN \def \xdef #2 \group_end: \str_gset:Nn \CJK@tenthousand { ^^^^4e07 } \str_gset:Nn \CJK@hundredmillion { ^^^^4ebf } \tl_if_exist:NF \CJK@UnicodeEnc { \tl_const:Nn \CJK@UnicodeEnc { UTF8 } } \cs_if_exist:NF \Unicode { \cs_new_eq:NN \Unicode \xeCJK_unicode_char:nn } } } \cs_new:Npn \xeCJK_unicode_char:nn #1#2 { \tex_Uchar:D \tex_numexpr:D (#1) * 256 + (#2) \scan_stop: } } \ctex_at_end_package:nn { CJKnumb } { \tl_set_eq:cN { ver@CJK . \c_@@_package_ext_tl } \l_@@_CJK_version_tl } % \end{macrocode} % \end{macro} % % 最后引入本地配置文件。 % \begin{macrocode} \bool_if:NT \g_@@_config_bool { \ExplSyntaxOff \file_input:n { \g_@@_config_name_tl .cfg } \ExplSyntaxOn } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJKfntef}} % % \changes{v3.1.1}{2012/12/08}{增加小宏包 \pkg{xeCJKfntef},用于处理下划线的问题。} % \changes{v3.2.4}{2013/06/23}{修正 \pkg{xeCJKfntef} 与 \pkg{natbib} 等的冲突。} % \changes{v3.2.14}{2014/10/31}{完善 \tn{varCJKunderline} 的实现。} % \changes{v3.2.14}{2014/10/31}{解决下划线前后没有 \tn{CJKglue} 或 \tn{CJKecglue} 的问题。} % \changes{v3.2.14}{2014/11/03}{\pkg{xeCJKfntef} 不再依赖 \pkg{CJKfntef}。} % \changes{v3.2.15}{2014/11/07}{\pkg{xeCJKfntef} 增加 \texttt{hidden} 选项。} % % \begin{macrocode} %<*fntef> % \end{macrocode} % % \begin{macrocode} \PassOptionsToPackage { normalem } { ulem } \DeclareOption* { \PassOptionsToPackage { \CurrentOption } { ulem } } \ProcessOptions \scan_stop: % \end{macrocode} % % \begin{macrocode} \RequirePackage { xeCJK } \RequirePackage { ulem } \RequirePackage { l3draw } \RequirePackage { accsupp } % \end{macrocode} % % \begin{macrocode} \addto@hook \UL@hook { \xeCJK_hook_for_ulem: } % \end{macrocode} % % \changes{v3.4.1}{2016/06/03}{新的下划线选项 \texttt{textformat}。} % % \begin{macro}[int]{\xeCJK_hook_for_ulem:} % \changes{v3.1.0}{2012/11/16}{简化对 \pkg{ulem} 宏包的兼容补丁。} % \changes{v3.1.1}{2012/12/08}{完全处理下划线里的标点符号的有关问题。} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_hook_for_ulem: { \xeCJK_ulem_detect_node: \l_@@_ulem_text_format_tl \bool_if:NF \l_@@_ulem_hook_used_bool { \bool_set_true:N \l_@@_ulem_hook_used_bool \@@_ulem_hook: } \xeCJK_ulem_begin_node: } \cs_new_protected:Npn \@@_ulem_hook: { \@@_ulem_initial: \bool_if:NTF \l_@@_ulem_periodic_bool { \seq_gput_right:Nn \g_@@_ulem_periodic_later_seq { 0 } \cs_set_eq:NN \UL@spfactor \@@_ulem_spfactor_pending: \xeCJK_swap_cs:NN \UL@leaders \xeCJK_ulem_periodic_leaders: \cs_set_eq:NN \@@_ulem_periodic_var_leaders: \xeCJK_ulem_periodic_var_leaders: \cs_set_eq:NN \xeCJK_ulem_right_skip: \@@_ulem_periodic_right_skip: } { \bool_if:NT \l_@@_ulem_subtract_bool { \xeCJK_swap_cs:NN \UL@leaders \xeCJK_ulem_leaders: \cs_set_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_var_leaders: \cs_set_eq:NN \xeCJK_ulem_right_skip: \@@_ulem_right_skip: } } \bool_if:NT \l_@@_ulem_hidden_bool { \cs_set_eq:NN \UL@putbox \@@_ulem_hidden_box: } \bool_if:NTF \l_@@_ulem_skip_bool { \cs_set_eq:NN \@@_ulem_putbox: \UL@putbox \cs_set_eq:NN \@@_ulem_hskip_aux:n \xeCJK_ulem_hskip:n } { \xeCJK_swap_cs:NN \@@_punct_hskip:n \@@_ulem_punct_hskip:n \xeCJK_cs_clear:N \@@_ulem_skip_punct_begin: \xeCJK_cs_clear:N \@@_ulem_skip_punct_end: } \xeCJK_glue_to_skip:nN { \cs_set_eq:NN \ \tex_space:D \cs_set_eq:NN \penalty \tex_penalty:D \cs_set_eq:NN \hskip \skip_horizontal:N \CJKglue } \l_@@_ccglue_skip \xeCJK_glue_to_skip:nN { \cs_set_eq:NN \ \tex_space:D \cs_set_eq:NN \penalty \tex_penalty:D \cs_set_eq:NN \hskip \skip_horizontal:N \CJKecglue } \l_@@_ecglue_skip \xeCJK_glue_to_skip:nN { \xeCJK_space_glue: } \l_@@_space_skip \cs_set_protected:Npn \CJKglue { \@@_ulem_glue:n \l_@@_ccglue_skip } \cs_set_protected:Npn \CJKecglue { \@@_ulem_glue:n \l_@@_ecglue_skip } \cs_set_protected:Npn \xeCJK_space_glue: { \@@_ulem_glue:n \l_@@_space_skip } \cs_set_eq:NN \xeCJK_punct_node:N \use_none:n \cs_set_eq:NN \xeCJK_if_last_punct:TF \use_ii:nn \keys_set:nn { xeCJK / options } { CheckFullRight = false , xCJKecglue = false } } \skip_new:N \l_@@_space_skip \bool_new:N \l_@@_ulem_hook_used_bool % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\UL@word,\xeCJK_ulem_word:nw} % 修改 \tn{UL@word},目的是取得分组中的 \tn{UL@leadtype},以便加入 \cs{xeCJK_ulem_right_skip:}。 % \begin{macrocode} \clist_map_inline:nn { UL@word , ULon , UL@on , UL@onin , uline , uuline , uwave , sout , xout , dashuline , dotuline , xeCJKfntefon , CJKunderline , CJKunderwave , CJKunderdblline , CJKsout , CJKxout , CJKunderanyline , CJKunderanysymbol , CJKunderdot } { \@@_boundary_reserve:n {#1} } \cs_new_protected:Npn \xeCJK_ulem_word:nw #1 ~ { \@@_ulem_start:w #1 ~ \exp_after:wN \if_meaning:w \exp_after:wN \UL@end #1 \exp_after:wN \@@_ulem_end: \else: \exp_after:wN \@@_ulem_loop:nw \fi: } \cs_new_protected:Npn \@@_ulem_end: { \c_group_end_token \c_group_end_token \tex_unskip:D \tex_unskip:D \tex_unskip:D \xeCJK_ulem_right_skip: \xeCJK_ulem_group_end: \xeCJK_ulem_right_node: \@@_boundary_inline_stream_end:n { auto } \bool_set_false:N \l_@@_ulem_stream_started_bool \skip_gzero:N \g_@@_ulem_pending_shrink_skip \int_set:Nn \tex_spacefactor:D { \UL@spfactor } } \cs_new_protected:Npn \@@_ulem_loop:nw { \reverse_if:N \if_mode_math: \reverse_if:N \if_dim:w \tex_lastskip:D = \c_zero_dim \skip_gset_eq:NN \UL@skip \tex_lastskip:D \tex_unskip:D \UL@stop \@@_ulem_flush_pending_shrink: \UL@leaders \fi: \fi: \xeCJK_ulem_word:nw \prg_do_nothing: } \cs_new_protected:Npn \@@_ulem_start:w { \exp_after:wN \UL@start } \cs_set_eq:NN \UL@word \xeCJK_ulem_word:nw % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_ulem_left:, \xeCJK_ulem_detect_node:} % 在下划线开始之前探测之前的 \texttt{node},以便随后插入 \tn{CJKglue} 或 \tn{CJKecglue}。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_ulem_left: { \xeCJK_ulem_left_node: \xeCJK_make_group_tag: } \cs_new_eq:NN \xeCJK_ulem_left_node: \prg_do_nothing: \cs_new_protected:Npn \xeCJK_ulem_detect_node: { \scan_stop: \dim_compare:nNnTF \tex_lastkern:D = \c_zero_dim { \xeCJK_cs_clear:N \xeCJK_ulem_left_node: \xeCJK_cs_clear:N \xeCJK_ulem_begin_node: \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n } { \dim_set_eq:NN \l_@@_tmp_dim \tex_lastkern:D \tex_unkern:D \dim_compare:nNnTF \tex_lastkern:D = { - \l_@@_tmp_dim } { \tex_unkern:D \cs_set_protected:Npe \xeCJK_ulem_left_node: { \tex_kern:D - \dim_use:N \l_@@_tmp_dim \exp_stop_f: \tex_kern:D \dim_use:N \l_@@_tmp_dim \exp_stop_f: } \cs_set_protected:Npn \xeCJK_ulem_begin_node: { { \xeCJK_make_node:n { ulem-begin } } } \cs_set_eq:NN \@@_ulem_hskip:n \@@_ulem_hskip_first:n } { \tex_kern:D \l_@@_tmp_dim \xeCJK_cs_clear:N \xeCJK_ulem_left_node: \xeCJK_cs_clear:N \xeCJK_ulem_begin_node: \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n } } } \xeCJK_declare_node:n { ulem-begin } \cs_new_eq:NN \xeCJK_ulem_begin_node: \prg_do_nothing: % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_hskip_first:n, \xeCJK_ulem_hskip:n} % 如果第一次调用的 \tn{CJKglue} 或 \tn{CJKecglue} 由下划线中的第一个文字和之前的 % 内容产生,就不用画下划线。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_hskip_first:n #1 { \xeCJK_if_last_node:nTF { ulem-begin } { \xeCJK_remove_node: \skip_horizontal:n {#1} } { \xeCJK_ulem_hskip:n {#1} } \cs_set_eq:NN \@@_ulem_hskip:n \xeCJK_ulem_hskip:n } \cs_new_eq:NN \@@_ulem_hskip:n \@@_ulem_hskip_first:n \cs_new_protected:Npn \xeCJK_ulem_hskip:n #1 { { \skip_set:Nn \UL@skip {#1} \UL@leaders } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_ulem_right:, \xeCJK_ulem_right_node:} % 从 ulem 内部列表取出末尾 marker,并在外层列表原样放回;stream capture % 结束时会把这个可信 marker 解释为实际末类别。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_ulem_right: { \scan_stop: \dim_compare:nNnTF \tex_lastkern:D = \c_zero_dim { \xeCJK_cs_gclear:N \xeCJK_ulem_right_node: } { \dim_compare:nNnTF \tex_lastkern:D = { 3sp } { \xeCJK_cs_gclear:N \xeCJK_ulem_right_node: } { \exp_args:NNo \tex_unkern:D \@@_ulem_right_aux:n { \dim_use:N \tex_lastkern:D } } } } \cs_new_protected:Npn \@@_ulem_right_aux:n #1 { \dim_compare:nNnTF \tex_lastkern:D = { - #1 } { \tex_unkern:D \cs_gset_protected:Npn \xeCJK_ulem_right_node: { \tex_kern:D - #1 \exp_stop_f: \tex_kern:D #1 \exp_stop_f: } \tl_gset:Ne \UL@spfactor { \int_use:N \tex_spacefactor:D } } { \tex_kern:D #1 \exp_stop_f: \xeCJK_cs_gclear:N \xeCJK_ulem_right_node: } } \cs_new_eq:NN \xeCJK_ulem_right_node: \prg_do_nothing: % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_ulem_var_leaders:} % \pkg{ulem} 通常把每段装饰向左右各扩展 \tn{UL@pixel};对带减号形式, % 第一次画装饰时改为先向右移动 \tn{UL@pixel},使装饰从正文内部开始。 % 后续片段仍使用原来的 \cs{UL@leaders},所以正文和中文字间距共享普通 % \tn{leaders} 的相位。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_ulem_leaders: { \@@_ulem_var_leaders: } \cs_new_protected:Npn \xeCJK_ulem_var_leaders: { \scan_stop: \skip_if_eq:nnF { \UL@skip } { \c_zero_skip } { \skip_horizontal:N \UL@pixel \UL@leadtype \skip_horizontal:N \UL@skip \skip_horizontal:n { - \UL@pixel } \cs_gset_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_leaders: } } \cs_new_eq:NN \@@_ulem_var_leaders: \xeCJK_ulem_var_leaders: % \end{macrocode} % \end{macro} % % \begin{macro}[int]{ % \xeCJK_ulem_periodic_leaders:, % \xeCJK_ulem_periodic_var_leaders:, % \@@_ulem_periodic_first_leaders:, % \@@_ulem_periodic_later_leaders:, % \@@_ulem_periodic_clipped_leaders:nnn} % 普通 \tn{leaders} 只放置完整的装饰盒。它能让同一行中的各个片段共享 % 相位,但首尾可见余量会随命令的水平位置变化。周期装饰因此只在命令的 % 首段和末段增加 PDF 裁切:底下的 leaders 区间向两侧各扩展一个完整 % 周期,再把可见范围精确限制到正文的对称边界。内部正文片段和 % \tn{CJKglue} 仍使用原来的普通 leaders,不改变伸缩和断行。 % % 参数 \texttt{\#2}、\texttt{\#3} 是相对正文左右边界向内移动的距离。 % 普通形式的首末段都使用 % \texttt{-\string\UL@pixel,-\string\UL@pixel},即左右各外伸半个周期。 % 带减号形式的首段使用 \texttt{\string\UL@pixel,\string\UL@pixel}; % 真正的末段使用 % \texttt{-\string\UL@pixel,\string\UL@pixel},把内部接点接上,同时让命令 % 右端仍内缩半个周期。首段之后的第一个中文字间距在断点两侧各放一个 % 净宽为零的半周期连接片段;不换行时两半相接,换行时则分别止于上一行 % 末尾和下一行开头。单片段命令只使用首段裁切。 % 若正文没有产生节点,第一次调用 leaders 的内容其实只是 \pkg{ulem} 追加的 % 结尾语法空格。此时保留原来的三段 glue 结构,让 \pkg{ulem} 随后用三次 % \tn{unskip} 删除,不能把这枚空格改造成含 PDF special 和 kern 的裁切结构。 % 正文中的真实空格出现在 \tn{UL@spfactor} 哨兵更新之前,不会被误认为结尾 % 语法空格。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_ulem_periodic_leaders: { \@@_ulem_periodic_var_leaders: } \cs_new_protected:Npn \xeCJK_ulem_periodic_var_leaders: { \scan_stop: \skip_if_eq:nnF { \UL@skip } { \c_zero_skip } { \cs_if_eq:NNTF \UL@spfactor \@@_ulem_spfactor_pending: { \@@_ulem_periodic_first_leaders: } { \skip_if_eq:nnTF { \UL@skip } { \box_wd:N \UL@box } { \@@_ulem_periodic_first_leaders: } { \xeCJK_ulem_periodic_leaders: } } } } \cs_new_protected:Npn \@@_ulem_periodic_first_leaders: { \bool_if:NTF \l_@@_ulem_subtract_bool { \@@_ulem_periodic_clipped_leaders:nnn { \UL@skip } { \UL@pixel } { \UL@pixel } } { \@@_ulem_periodic_clipped_leaders:nnn { \UL@skip } { - \UL@pixel } { - \UL@pixel } } \cs_gset_eq:NN \@@_ulem_periodic_var_leaders: \@@_ulem_periodic_later_leaders: } \cs_new_eq:NN \@@_ulem_periodic_var_leaders: \xeCJK_ulem_periodic_var_leaders: \cs_new_protected:Npn \@@_ulem_periodic_later_leaders: { \scan_stop: \skip_if_eq:nnF { \UL@skip } { \c_zero_skip } { \cs_if_eq:NNTF \UL@spfactor \@@_ulem_spfactor_pending: { \seq_get_right:NN \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl \tl_if_eq:NnTF \l_@@_tmp_tl { 1 } { \xeCJK_ulem_periodic_leaders: } { \seq_gpop_right:NN \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl \seq_gput_right:Nn \g_@@_ulem_periodic_later_seq { 1 } \bool_if:NTF \l_@@_ulem_subtract_bool { \@@_ulem_periodic_clipped_leaders:nnn { \c_zero_skip } { - \UL@pixel } { \c_zero_dim } \xeCJK_ulem_periodic_leaders: \@@_ulem_periodic_clipped_leaders:nnn { \c_zero_skip } { \c_zero_dim } { - \UL@pixel } } { \xeCJK_ulem_periodic_leaders: } } } { \xeCJK_ulem_periodic_leaders: } } } \cs_new_protected:Npn \@@_ulem_periodic_clipped_leaders:nnn #1#2#3 { \skip_set:Nn \l_@@_tmp_skip { #1 - #2 - #3 } \dim_compare:nNnTF { \l_@@_tmp_skip } > { \c_zero_dim } { \tex_kern:D \dim_eval:n {#2} \tex_special:D { x:gsave } \tex_special:D { pdf:literal~ 0~ \dim_to_decimal_in_bp:n { - \box_dp:N \ULC@box - 1pt } ~ \dim_to_decimal_in_bp:n { \l_@@_tmp_skip } ~ \dim_to_decimal_in_bp:n { \box_ht:N \ULC@box + \box_dp:N \ULC@box + 2pt } ~ re~W~n } \tex_kern:D \dim_eval:n { - 2 \UL@pixel } \xeCJK_no_break: \UL@leadtype \skip_horizontal:n { \l_@@_tmp_skip + 4 \UL@pixel } \tex_kern:D \dim_eval:n { - 2 \UL@pixel } \tex_special:D { x:grestore } \tex_kern:D \dim_eval:n {#3} } { \skip_horizontal:n {#1} } } \bool_new:N \l_@@_ulem_periodic_bool \seq_new:N \g_@@_ulem_periodic_later_seq \cs_new_eq:NN \@@_ulem_spfactor_pending: \scan_stop: % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_ulem_right_skip:} % 在装饰完全画好之后检查末尾节点。带减号形式把最后一段的装饰宽度减少 % 两个 \tn{UL@pixel},再补回一个等宽空白,使装饰停在正文右边界以内 % \tn{UL@pixel},同时保持命令和正文的宽度不变。若末段窄到不足以容纳 % 缩减后的装饰,则只保留两端空白,不构造负宽度的 leaders。 % \begin{macrocode} \cs_new_eq:NN \xeCJK_ulem_right_skip: \prg_do_nothing: \cs_new_protected:Npn \@@_ulem_right_skip: { \int_case:nn { \tex_lastnodetype:D } { { \c_@@_hlist_node } { \@@_ulem_right_skip_hbox: } { \c_@@_glue_node } { \@@_ulem_right_skip_glue: } { \c_@@_penalty_node } { \@@_ulem_right_skip_penalty: } } } \cs_new_protected:Npn \@@_ulem_right_skip_hbox: { \box_set_to_last:N \l_@@_tmp_box \@@_if_last_kern:TF { \@@_ulem_right_skip_kern: } { \@@_ulem_right_skip_glue: } \box_use_drop:N \l_@@_tmp_box } \cs_new_protected:Npn \@@_ulem_right_skip_kern: { \dim_set:Nn \l_@@_tmp_dim { - \box_wd:N \l_@@_tmp_box } \dim_compare:nNnT \tex_lastkern:D = \l_@@_tmp_dim { \tex_unkern:D \@@_ulem_right_skip_glue: \tex_kern:D \l_@@_tmp_dim } } \cs_new_protected:Npn \@@_ulem_right_skip_glue: { \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel } { \tex_unskip:D \skip_set:Nn \l_@@_tmp_skip { \tex_lastskip:D - 2 \UL@pixel } \tex_unskip:D \dim_compare:nNnT { \l_@@_tmp_skip } > { \c_zero_dim } { \UL@leadtype \skip_horizontal:N \l_@@_tmp_skip } \skip_horizontal:N \UL@pixel } } \cs_new_protected:Npn \@@_ulem_right_skip_penalty: { \int_set_eq:NN \l_@@_tmp_int \tex_lastpenalty:D \tex_unpenalty:D \@@_if_last_hlist:T { \@@_ulem_right_skip_hbox: } \tex_penalty:D \l_@@_tmp_int } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\@@_ulem_periodic_right_skip:} % \changes{v3.10.5}{2026/07/27}{周期装饰按嵌套层保存末段状态, % 避免内层命令改写外层右端(\#1012)。} % 若命令只有一个装饰片段,首段函数已经同时裁定左右边界;出现后续片段时, % 末段仍是 \pkg{ulem} 原来的负间距、leaders、负间距结构。这里取回末段 % 正文宽度,并用同一裁切函数重画,使命令右边界不再依赖完整图案盒的取整。 % 每个周期装饰在开始时向全局序列压入自己的状态,结束时再弹出。这里不能 % 共用一个全局布尔值:内层周期装饰会在外层完成之前改写它,导致外层末段 % 根据内层的状态决定是否重画。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_periodic_right_skip: { \seq_gpop_right:NN \g_@@_ulem_periodic_later_seq \l_@@_tmp_tl \tl_if_eq:NnTF \l_@@_tmp_tl { 1 } { \@@_ulem_periodic_right_skip_aux: } { \prg_do_nothing: } } \cs_new_protected:Npn \@@_ulem_periodic_right_skip_aux: { \int_case:nn { \tex_lastnodetype:D } { { \c_@@_hlist_node } { \@@_ulem_periodic_right_skip_hbox: } { \c_@@_glue_node } { \@@_ulem_periodic_right_skip_glue: } { \c_@@_penalty_node } { \@@_ulem_periodic_right_skip_penalty: } } } \cs_new_protected:Npn \@@_ulem_periodic_right_skip_hbox: { \box_set_to_last:N \l_@@_tmp_box \@@_if_last_kern:TF { \@@_ulem_periodic_right_skip_kern: } { \@@_ulem_periodic_right_skip_glue: } \box_use_drop:N \l_@@_tmp_box } \cs_new_protected:Npn \@@_ulem_periodic_right_skip_kern: { \dim_set:Nn \l_@@_tmp_dim { - \box_wd:N \l_@@_tmp_box } \dim_compare:nNnT \tex_lastkern:D = \l_@@_tmp_dim { \tex_unkern:D \@@_ulem_periodic_right_skip_glue: \tex_kern:D \l_@@_tmp_dim } } \cs_new_protected:Npn \@@_ulem_periodic_right_skip_glue: { \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel } { \tex_unskip:D \skip_set:Nn \l_@@_tmp_skip { \tex_lastskip:D - 2 \UL@pixel } \tex_unskip:D \skip_if_eq:nnT { \tex_lastskip:D } { - \UL@pixel } { \tex_unskip:D \bool_if:NTF \l_@@_ulem_subtract_bool { \@@_ulem_periodic_clipped_leaders:nnn { \l_@@_tmp_skip } { - \UL@pixel } { \UL@pixel } } { \@@_ulem_periodic_clipped_leaders:nnn { \l_@@_tmp_skip } { - \UL@pixel } { - \UL@pixel } } } } } \cs_new_protected:Npn \@@_ulem_periodic_right_skip_penalty: { \int_set_eq:NN \l_@@_tmp_int \tex_lastpenalty:D \tex_unpenalty:D \@@_if_last_hlist:T { \@@_ulem_periodic_right_skip_hbox: } \tex_penalty:D \l_@@_tmp_int } % \end{macrocode} % \end{macro} % % \changes{v3.8.3}{2020/04/23}{\opt{hidden} 选项保留原内容的高度和深度。} % % \begin{macro}{\@@_ulem_hidden_box:} % 只画线,不输出盒子。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_hidden_box: { \tl_if_empty:NF \UL@start { \box_set_ht:Nn \l_@@_hidden_box { \box_ht:N \UL@box } \box_set_dp:Nn \l_@@_hidden_box { \box_dp:N \UL@box } \box_use:N \l_@@_hidden_box \xeCJK_no_break: \xeCJK_ulem_hskip:n { \box_wd:N \UL@box } \box_use:N \l_@@_hidden_box } } \box_new:N \l_@@_hidden_box \hbox_set:Nn \l_@@_hidden_box { } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_skip_punct_begin:,\@@_ulem_skip_punct_end:} % 让下划线跳过标点符号的设置。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_skip_punct_begin: { \cs_set_eq:NN \UL@putbox \@@_ulem_skip_putbox: \cs_set_eq:NN \xeCJK_ulem_hskip:n \skip_horizontal:n } \cs_new_protected:Npn \@@_ulem_skip_punct_end: { \cs_set_eq:NN \UL@putbox \@@_ulem_putbox: \cs_set_eq:NN \xeCJK_ulem_hskip:n \@@_ulem_hskip_aux:n } \cs_new_eq:NN \@@_ulem_putbox: \UL@putbox \cs_new_protected:Npn \@@_ulem_skip_putbox: { \tl_if_empty:NF \UL@start { \box_use_drop:N \UL@box } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_initial:} % 这里的设置是为了在下划线状态下,下划线可以自动跳过全角标点符号和正确的在它们 % 前/后断行,并且与行首行末对齐。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_initial: { \@@_ulem_swap_cs:NN \xeCJK_FullLeft_and_Default: \@@_ulem_FullLeft_and_Default: \xeCJK_FullLeft_and_CJK: \@@_ulem_FullLeft_and_CJK: \xeCJK_FullLeft_and_Boundary: \@@_ulem_FullLeft_and_Boundary: \xeCJK_FullRight_and_Default: \@@_ulem_FullRight_and_Default: \xeCJK_FullRight_and_CJK: \@@_ulem_FullRight_and_CJK: \xeCJK_FullRight_and_CJStarter: \@@_ulem_FullRight_and_CJStarter: \xeCJK_FullRight_and_Boundary: \@@_ulem_FullRight_and_Boundary: \xeCJK_CJK_and_CJK:N \@@_ulem_CJK_and_CJK:N \xeCJK_CJK_and_Boundary:w \@@_ulem_CJK_and_Boundary:w \xeCJK@fix@penalty \@@_ulem_fix_penalty: \@@_punct_breakable_kern:n \@@_ulem_punct_breakable_kern:n \@@_Default_and_FullLeft_glue:N \@@_ulem_Default_and_FullLeft_glue:N \@@_Default_and_FullRight_glue:N \@@_ulem_Default_and_FullRight_glue:N \@@_CJK_and_FullLeft_glue:N \@@_ulem_CJK_and_FullLeft_glue:N \@@_CJK_and_FullRight_glue:N \@@_ulem_CJK_and_FullRight_glue:N \@@_Boundary_and_FullLeft_glue:N \@@_ulem_Boundary_and_FullLeft_glue:N \q_recursion_tail \q_nil \q_recursion_stop \seq_map_inline:Nn \g_@@_CJK_sub_class_seq { \seq_map_inline:Nn \g_@@_CJK_sub_class_seq { \str_if_eq:nnTF {##1} {####1} { \xeCJK_inter_class_toks:nnn { CJK } { CJK/##1 } { \@@_ulem_between_CJK_blocks:nnN { CJK } {##1} } \xeCJK_inter_class_toks:nnn { CJK/##1 } { CJK/##1 } { \@@_ulem_between_CJK_blocks:nnN { CJK } {##1} } } { \xeCJK_inter_class_toks:nnn { CJK/##1 } { CJK/####1 } { \@@_ulem_between_CJK_blocks:nnN {##1} {####1} } } } } } \cs_new_protected:Npn \@@_ulem_swap_cs:NN #1#2 { \quark_if_recursion_tail_stop:N #1 \xeCJK_swap_cs:NN #1#2 \@@_ulem_swap_cs:NN } % \end{macrocode} % \end{macro} % % \changes{v3.1.2}{2012/12/27}{解决在下划线状态下使用 \tn{makebox} 时的错误。} % % \begin{macro}[int]{\xeCJK_if_ulem_patch:TF} % 在下划线状态下,\pkg{ulem} 宏包在数学模式或者盒子中使用 \tn{UL@hrest} 恢复 % \verb*|\ | 等的定义,此时不需要使用 \tn{UL@stop} 和 \tn{UL@start} 来断开下划线而 % 产生断点。 % \begin{macrocode} \cs_new:Npn \xeCJK_if_ulem_patch:TF { \if_meaning:w \ \LA@space \exp_after:wN \use_ii:nn \else: \exp_after:wN \use_i:nn \fi: } % \end{macrocode} % \end{macro} % % \changes{v3.9.1}{2022/07/28}{修复下划线中数学公式的错误处理。} % % \begin{macro}{\@@_ulem_CJK_and_Boundary:w} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_CJK_and_Boundary:w { \xeCJK_if_ulem_patch:TF { \xeCJK_peek_catcode_ignore_spaces:NTF \c_math_toggle_token { \xeCJK_class_group_end: \CJKecglue } { \bool_if:NTF \l_@@_peek_ignore_spaces_bool { \@@_ulem_peek_math:w } { \@@_ulem_group_end:n { CJK } } } } { \@@_ulem_CJK_and_Boundary:w } } \cs_new_protected:Npn \@@_ulem_group_end:n #1 { \xeCJK_class_group_end: \UL@stop \UL@start { \xeCJK_make_node:n {#1} } \xeCJK_make_group_tag: \@@_boundary_enable_source_space:n {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_peek_math:w} % 用于处理下划线中,汉字与 |$| 之间有空格的情况^^A % \footnote{\url{https://github.com/CTeX-org/ctex-kit/issues/614}}。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_peek_math:w { \cs_set_eq:NN \@@_ulem_start:w \@@_ulem_exp_stop:w \exp_after:wN \peek_after:Nw \exp_after:wN \@@_ulem_peek_math_branches:w \exp:w \exp_end_continue_f:w } \cs_new_protected:Npn \@@_ulem_peek_math_branches:w { \token_if_math_toggle:NTF \l_peek_token { \xeCJK_class_group_end: \CJKecglue } { \@@_ulem_group_end:n { CJK-space } } } \cs_new_protected:Npn \@@_ulem_exp_stop:w { \cs_if_eq:NNTF \UL@start \@empty { \exp_after:wN \exp_stop_f: } { \exp_after:wN \UL@start } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_fix_penalty:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_fix_penalty: { \xeCJK_if_ulem_patch:TF { \fix@penalty } { \@@_ulem_fix_penalty: } } % \end{macrocode} % \end{macro} % % \changes{v3.10.0}{2026/04/28}{在下划线内 CJK 分组切换时保存/恢复字体状态(\#465)。} % % \begin{macro}{\@@_ulem_save_font_state:,\@@_ulem_restore_font_state:} % \begin{variable}{\g_@@_ulem_saved_series_tl,\g_@@_ulem_saved_shape_tl, % \g_@@_ulem_saved_CJK_family_tl,\g_@@_ulem_saved_CJK_fam_tl} % 在 \cs{xeCJK_class_group_end:} 前保存字体状态,在 % \cs{@@_ulem_class_group_begin:} 后恢复,使 \tn{bfseries}、 % \tn{sffamily} 等字体声明能跨越下划线内的 CJK 分组边界。 % \begin{macrocode} \tl_new:N \g_@@_ulem_saved_series_tl \tl_new:N \g_@@_ulem_saved_shape_tl \tl_new:N \g_@@_ulem_saved_CJK_family_tl \tl_new:N \g_@@_ulem_saved_CJK_fam_tl \cs_new_protected:Npn \@@_ulem_save_font_state: { \tl_gset:Ne \g_@@_ulem_saved_series_tl { \f@series } \tl_gset:Ne \g_@@_ulem_saved_shape_tl { \f@shape } \tl_gset_eq:NN \g_@@_ulem_saved_CJK_family_tl \l_xeCJK_family_tl \tl_gset_eq:NN \g_@@_ulem_saved_CJK_fam_tl \CJK@family } \cs_new_protected:Npn \@@_ulem_restore_font_state: { \tl_set:Ne \f@series { \g_@@_ulem_saved_series_tl } \tl_set:Ne \f@shape { \g_@@_ulem_saved_shape_tl } \tl_set_eq:NN \l_xeCJK_family_tl \g_@@_ulem_saved_CJK_family_tl \tl_set_eq:NN \CJK@family \g_@@_ulem_saved_CJK_fam_tl } % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\@@_ulem_CJK_and_CJK:N} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_CJK_and_CJK:N { \xeCJK_if_ulem_patch:TF { \@@_ulem_save_font_state: \xeCJK_class_group_end: \UL@stop \@@_ulem_ccglue: \UL@start \@@_ulem_class_group_begin: \@@_ulem_restore_font_state: \xeCJK_select_font: \xeCJK_fallback_symbol:NN \CJKsymbol } { \@@_ulem_CJK_and_CJK:N } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_class_group_begin:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_class_group_begin: { \xeCJK_class_group_begin: \xeCJK_clear_Boundary_and_CJK_toks: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_between_CJK_blocks:nnN} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_between_CJK_blocks:nnN #1#2 { \xeCJK_if_ulem_patch:TF { \@@_ulem_save_font_state: \xeCJK_class_group_end: \UL@stop \@@_ulem_ccglue: \UL@start \xeCJK_class_group_begin: \xeCJK_clear_Boundary_and_CJK_toks: \@@_ulem_restore_font_state: \@@_switch_font:nn {#1} {#2} \xeCJK_fallback_symbol:NN \CJKsymbol } { \skip_horizontal:N \l_@@_ccglue_skip \@@_switch_font:nn {#1} {#2} \xeCJK_fallback_symbol:NN \CJKsymbol } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_Default_and_FullLeft_glue:N} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_Default_and_FullLeft_glue:N #1 { \xeCJK_if_ulem_patch:TF { \UL@stop \@@_ulem_skip_punct_begin: \@@_punct_glue:NN \c_@@_left_tl #1 \UL@start } { \@@_ulem_Default_and_FullLeft_glue:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_Boundary_and_FullLeft_glue:N} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_Boundary_and_FullLeft_glue:N #1 { \xeCJK_if_ulem_patch:TF { \UL@stop \@@_ulem_skip_punct_begin: \@@_punct_glue:NN \c_@@_left_tl #1 \UL@start } { \@@_ulem_Boundary_and_FullLeft_glue:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_CJK_and_FullLeft_glue:N} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_CJK_and_FullLeft_glue:N #1 { \xeCJK_if_ulem_patch:TF { \xeCJK_class_group_end: \UL@stop \@@_ulem_skip_punct_begin: \@@_ulem_punct_ccglue: \@@_punct_glue:NN \c_@@_left_tl #1 \UL@start \@@_ulem_class_group_begin: \xeCJK_select_punct_font: } { \@@_ulem_CJK_and_FullLeft_glue:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_Default_and_FullRight_glue:N} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_Default_and_FullRight_glue:N #1 { \xeCJK_if_ulem_patch:TF { \UL@stop \@@_ulem_skip_punct_begin: \@@_punct_if_long:NTF #1 { \xeCJK_allow_break: } { \xeCJK_no_break: } \@@_punct_if_middle:NT #1 { \@@_punct_glue:NN \c_@@_right_tl #1 \@@_punct_rule:NN \c_@@_left_tl #1 } \UL@start } { \@@_ulem_Default_and_FullRight_glue:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_CJK_and_FullRight_glue:N} % \changes{v3.2.2}{2013/05/30}{修正下划线不能跳过全角右标点的问题。} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_CJK_and_FullRight_glue:N #1 { \xeCJK_if_ulem_patch:TF { \xeCJK_class_group_end: \UL@stop \@@_ulem_skip_punct_begin: \@@_punct_if_long:NTF #1 { \xeCJK_allow_break: } { \xeCJK_no_break: } \@@_punct_if_middle:NT #1 { \@@_ulem_punct_ccglue: \@@_punct_glue:NN \c_@@_right_tl #1 \@@_punct_rule:NN \c_@@_left_tl #1 } \UL@start \@@_ulem_class_group_begin: \xeCJK_select_punct_font: } { \@@_ulem_CJK_and_FullRight_glue:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullLeft_and_Default:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullLeft_and_Default: { \xeCJK_if_ulem_patch:TF { \@@_punct_if_middle:NTF \g_@@_last_punct_tl { \xeCJK_get_punct_bounds:No \c_@@_left_tl \g_@@_last_punct_tl \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \xeCJK_no_break: \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl } { \xeCJK_class_group_end: \UL@stop } \@@_ulem_skip_punct_end: \xeCJK_no_break: \UL@start } { \@@_ulem_FullLeft_and_Default: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullLeft_and_Boundary:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullLeft_and_Boundary: { \xeCJK_if_ulem_patch:TF { \@@_punct_if_middle:NTF \g_@@_last_punct_tl { \xeCJK_get_punct_bounds:No \c_@@_left_tl \g_@@_last_punct_tl \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \xeCJK_no_break: \@@_punct_glue:NN \c_@@_left_tl \g_@@_last_punct_tl } { \xeCJK_class_group_end: \UL@stop } \@@_ulem_skip_punct_end: \xeCJK_no_break: \UL@start \tex_ignorespaces:D } { \@@_ulem_FullLeft_and_Boundary: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullLeft_and_CJK:} % \changes{v3.2.3}{2013/06/04} % {修正全角左标点后下划线与 \tn{CJKunderdot} 连用时结果不正常的问题。} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullLeft_and_CJK: { \xeCJK_if_ulem_patch:TF { \xeCJK_FullLeft_and_Default: \@@_ulem_class_group_begin: \xeCJK_select_font: } { \@@_ulem_FullLeft_and_CJK: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullRight_and_Default:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullRight_and_Default: { \xeCJK_if_ulem_patch:TF { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_ulem_skip_punct_end: \UL@start } { \@@_ulem_FullRight_and_Default: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullRight_and_Boundary:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullRight_and_Boundary: { \xeCJK_if_ulem_patch:TF { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_ulem_skip_punct_end: \UL@start \tex_ignorespaces:D } { \@@_ulem_FullRight_and_Boundary: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullRight_and_CJK:} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullRight_and_CJK: { \xeCJK_if_ulem_patch:TF { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_ulem_punct_ccglue: \@@_ulem_skip_punct_end: \UL@start \@@_ulem_class_group_begin: \xeCJK_select_font: } { \@@_ulem_FullRight_and_CJK: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_FullRight_and_CJStarter:} % 在全角右标点与严格行首禁则字符之间,先于标点胶插入禁则,并保持 % \pkg{xeCJKfntef} 对标点胶和 CJK 字距的下划线处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_FullRight_and_CJStarter: { \xeCJK_if_ulem_patch:TF { \@@_punct_rule:NN \c_@@_right_tl \g_@@_last_punct_tl \xeCJK_class_group_end: \UL@stop \xeCJK_no_break: \@@_punct_glue:NN \c_@@_right_tl \g_@@_last_punct_tl \@@_ulem_punct_ccglue: \@@_ulem_skip_punct_end: \UL@start \@@_ulem_class_group_begin: \xeCJK_select_font: } { \@@_ulem_FullRight_and_CJStarter: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_punct_hskip:n} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_punct_hskip:n { \xeCJK_if_ulem_patch:TF { \xeCJK_ulem_hskip:n } { \@@_ulem_punct_hskip:n } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_punct_breakable_kern:n} % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_punct_breakable_kern:n #1 { \xeCJK_if_ulem_patch:TF { \xeCJK_class_group_end: \UL@stop \xeCJK_ulem_hskip:n {#1} \UL@start \@@_ulem_class_group_begin: \xeCJK_select_punct_font: } { \@@_ulem_punct_breakable_kern:n {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_ulem_glue:n,\@@_ulem_ccglue:,\@@_ulem_punct_ccglue:} % 在下划线状态下的分别代替 \tn{CJKglue} 等。 % \begin{macrocode} \cs_new_protected:Npn \@@_ulem_glue:n #1 { \xeCJK_if_ulem_patch:TF { \tl_if_empty:NTF \l_@@_group_tag_tl { \UL@stop \@@_ulem_hskip:n {#1} \UL@start } { \str_if_eq:eeTF { \l_@@_group_tag_tl } { \c_@@_group_tag_tl } { \UL@stop \@@_ulem_hskip:n {#1} \UL@start } { \@@_ulem_defer_glue:n {#1} } } } { \skip_horizontal:n {#1} } } % \end{macrocode} % % \changes{v3.10.5}{2026/07/29}{\tn{CJKecglue} 补在源码空格位置时改用可搬运 % 通道,避免收缩量固化在 \pkg{ulem} 的定宽片段盒内(\#1037)。} % 改写 \cs{@@_use_ecglue_skip:}(默认实现在 \pkg{xeCJK} 主体里是普通 % \tn{hskip}),让「源码空格 + 西文词」处补的 \tn{CJKecglue} 经 % \cs{@@_ulem_glue:n} 落到外层列表,收缩量可参与断行(\#1037)。 % % 必须先用 \cs{l_@@_ulem_stream_started_bool} 判断当前确实在装饰的 stream 里, % 不能只依赖 \cs{@@_ulem_glue:n} 自带的 \cs{xeCJK_if_ulem_patch:TF}。后者的判据 % 只是 \tn{ }\ 的含义是否等于 \pkg{ulem} 保存的 \tn{LA@space},它能识别 % \pkg{ulem} 自己造成的变化,却无法区分「不在装饰中」与「在装饰外但 \tn{ }\ 被 % 别的宏包改过定义」。\cs{@@_check_for_ecglue_aux:} 是所有中西文边界都会走的 % 通用路径,一旦在装饰外取到真分支,就会在没有 \tn{UL@box} 打开的列表里执行 % \tn{UL@stop},导致 \texttt{Too many~\}'s}。\pkg{nath}、\pkg{morehype} 等重定义 % \tn{ }\ 的宏包会让不含任何装饰命令的 \verb|中 abc 文| 直接报错。 % % 该布尔由 \cs{@@_ulem_stream_begin:} 置真、\cs{@@_ulem_end:} 置假。 % % 但它单独还不够:行内公式里的装饰命令经 \tn{UL@onmath}/\tn{UL@onin} 结束,不走 % \cs{@@_ulem_end:},因此同一个公式内装饰命令之后布尔仍为真,而片段盒其实已经 % 关闭。此时若再叠加 \tn{ }\ 被重定义,仍会在没有 \tn{UL@box} 打开的列表里执行 % \tn{UL@stop}——例如 \verb|$\CJKunderline{中}\mbox{中 abc 文}$| 配 \pkg{nath}。 % 因此还要确认片段盒确实打开:\tn{UL@start} 只在 \pkg{ulem} 打开片段盒后才被 % \tn{let} 成 \tn{@empty}(\pkg{ulem} 的 \tn{UL@start} 定义末尾),关闭后不再是它。 % \cs{@@_ulem_exp_stop:w} 已用同一判断表示这件事。 % % 两个条件都成立才走 ulem 通道,否则退化为主体的默认实现。 % \begin{macrocode} \cs_gset_protected:Npn \@@_use_ecglue_skip: { \bool_lazy_and:nnTF { \l_@@_ulem_stream_started_bool } { \cs_if_eq_p:NN \UL@start \@empty } { \@@_ulem_glue:n { \l_@@_ecglue_skip } } { \skip_horizontal:N \l_@@_ecglue_skip } } % \end{macrocode} % \end{macro} % % \changes{v3.10.6}{2026/08/11}{花括号分组内的间距改用「盒内 kern 占位加词尾补伸缩」的 % 方式输出,使 \tn{CJKecglue} 的收缩量与 \tn{CJKglue} 的伸长量都回到外层列表(\#1067)。} % % \begin{macro}[int]{\@@_ulem_defer_glue:n, \@@_ulem_flush_pending_shrink:} % 这两个函数处理 \#1067:装饰正文里用花括号括住一段内容时,该处间距的可收缩量原先会失效。 % % 成因不在 \pkg{ulem} 的切词——实测两种写法切出的词相同,只差一对花括号。花括号是在词内容 % 交给 \tn{UL@start} 之后、在\emph{片段盒内部}才展开成分组的,于是边界检测在盒内、且在用户 % 分组内触发;\cs{@@_ulem_glue:n} 的 group tag 守卫比对保存的 \texttt{T1L4} 与当前的 % \texttt{T1L5} 不相等,走 else 分支。该分支原先是裸 \cs{skip_horizontal:n},间距连收缩量 % 一起固化在定宽片段盒内,外层取不到。 % % 不能改成「照样搬运」:实测强制走 \tn{UL@stop} 之后,搬出来的 glue 落进 \tn{cleaders} % 内部、仍在盒内——在用户分组内 \tn{UL@stop} 关不掉正确的层级(\pkg{ulem} 开盒时开了两层, % 用户花括号插在中间),\tn{UL@start} 重开的盒子又把它包了回去。而且绕过守卫会让分组内的 % 字体设置丢失(\file{fntef-font01} 即此,字体从楷体退回宋体)。也就是说「守卫是直接原因」 % 与「绕过守卫无效」两件事同时成立,修法必须绕开这个结构,而不是拆掉守卫。 % % 因此改为把一处间距拆成两半输出: % % \begin{itemize} % \item 盒内放一个\emph{不可伸缩}的 \tn{kern},占住自然宽度,使排版位置与盒子总宽都不变; % \item 把伸缩量记进全局的 \cs{g_@@_ulem_pending_shrink_skip},等到 \cs{@@_ulem_loop:nw} % 的词尾搬运处——那里已经在片段盒外、用户分组外——再补一个零宽而带伸缩的 glue。 % \end{itemize} % % 短路条件是\emph{整个 skip 全零}(\cs{skip_if_eq:nnTF} 比对 \cs{c_zero_skip}),不是「自然 % 宽度为零」。零自然宽度但带弹性的间距仍然要记账搬运——用户可以把间距设成 % \verb|CJKecglue={\hskip 0pt minus 1pt}|,此时自然宽度是零而收缩量非零,若按自然宽度短路, % 这份收缩量就留在定宽片段盒里出不来(实测压窄 \texttt{1.2pt}:直接输入与无编组装饰都是 % \texttt{18},而分组形态是 \texttt{1000000})。 % % 这条短路只用来跳过真正什么都不做的情形,避免为零 glue 白记一笔账。 % % 于是 \tn{CJKglue}(\texttt{0pt plus 0.96},只有伸长量)也走记账通道,它的伸长量随之回到 % 外层列表。这是有意的:分组内的伸长量因此同样能参与断行,与不带装饰时的行为更接近。实测 % \verb|\CJKunderline{文字{\sffamily 文字}}| 拉伸 \texttt{3pt} 的 badness 从 \texttt{3049} % 降到 \texttt{381},向无装饰形态的 \texttt{113} 靠拢,而盒子宽度不变(\texttt{40.0pt})。 % \file{fntef-font01} 的基线因此记录 \tn{kern} 而不再是原处的 \tn{glue}。 % % 取弹性部分用整个 skip 减去自然宽度(\cs{skip_eval:n} 里的 \texttt{\#1 - \cs{dim_eval:n} % \{\#1\}}),\emph{不要}分别取 \tn{gluestretch} 与 \tn{glueshrink} 再用 \texttt{plus}/ % \texttt{minus} 重新拼:那两个原语只返回数值,阶数在 \tn{gluestretchorder}/ % \tn{glueshrinkorder} 里,分开取会把 \texttt{1fil} 降成普通阶(实测 % \texttt{3.33pt plus 1fil minus 1.11pt} 用减法得到 \texttt{0.0pt plus 1.0fil minus 1.11pt}, % 阶数保留;而 \tn{gluestretch} 只给出 \texttt{1.0pt})。就目前的调用路径而言这两种写法 % 结果相同——装饰状态下 \cs{l_@@_ecglue_skip} 由 \cs{xeCJK_glue_to_skip:nN} 现算,传进来的 % 自然宽度已是零、会走上面那条短路——但减法写法不依赖这个前提,日后间距模板换成带无限阶 % 伸缩的形式也不会静默降阶。 % % 记账是全局量,因此在两处清零:\cs{@@_ulem_stream_begin:} 进入装饰时清一次, % \cs{@@_ulem_end:} 结束时再清一次。两处都要,不是重复——结束处那次覆盖正常路径,而行内 % 公式里的装饰命令经 \tn{UL@onmath}/\tn{UL@onin} 结束、\emph{不走} \cs{@@_ulem_end:}, % 那条路径上的残留只能靠下一次装饰开始时清掉。实测目前四条路径(公式内带编组、公式内 % \tn{mbox}、\tn{mbox} 内装饰、装饰嵌套)的残留都是 \texttt{0pt},因为记账在词尾都已兑现; % 开始处这道清零是防御性的,用来隔断将来改动公式路径时可能出现的串味。 % % 补出的 glue 必须放在 \tn{UL@stop} 之后、\tn{UL@leaders} \emph{之前}。\tn{UL@leaders} 的 % 结构是「\texttt{-\tn{UL@pixel}}、加宽两个 pixel 的 leaders、再 \texttt{-\tn{UL@pixel}}」, % 末尾那个负值是段与段之间的重叠补偿,要靠紧随其后的内容抵掉。如果把补出的 glue 放在 % \tn{UL@leaders} 之后,编组位于装饰参数\emph{结尾}时它就会把这个负值推到更后面,使其失去 % 正向配对,装饰盒因此比实际内容窄一个 pixel(实测 \verb|\CJKunderline{中{x}}| 得 % \texttt{17.61002pt},而 \verb|\hbox{中{x}}| 是 \texttt{18.61pt})。放在 \tn{UL@leaders} % 之前,glue 落在两段装饰之间,两种位置都正确。\file{fntef-shrink01} 的 TEST 11 同时固定 % 「编组在中间」与「编组在结尾」两种位置,正是因为前者察觉不到这个回归。 % \begin{macrocode} \skip_new:N \g_@@_ulem_pending_shrink_skip \cs_new_protected:Npn \@@_ulem_defer_glue:n #1 { \skip_if_eq:nnTF { #1 } { \c_zero_skip } { \skip_horizontal:n {#1} } { \skip_gadd:Nn \g_@@_ulem_pending_shrink_skip { \skip_eval:n { #1 - \dim_eval:n {#1} } } \tex_kern:D \dim_eval:n {#1} \scan_stop: } } \cs_new_protected:Npn \@@_ulem_flush_pending_shrink: { \skip_if_eq:nnF { \g_@@_ulem_pending_shrink_skip } { \c_zero_skip } { \skip_horizontal:N \g_@@_ulem_pending_shrink_skip \skip_gzero:N \g_@@_ulem_pending_shrink_skip } } \cs_new_protected:Npn \xeCJK_make_group_tag: { \tl_set:Ne \l_@@_group_tag_tl { \c_@@_group_tag_tl } } \tl_new:N \l_@@_group_tag_tl \tl_const:Nn \c_@@_group_tag_tl { T \int_use:N \tex_currentgrouptype:D L \int_use:N \tex_currentgrouplevel:D } \cs_new_protected:Npn \@@_ulem_ccglue: { { \skip_set_eq:NN \UL@skip \l_@@_ccglue_skip \UL@leaders } } \cs_new_protected:Npn \@@_ulem_punct_ccglue: { \@@_punct_hskip:n { \l_@@_ccglue_skip } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{ % \@@_ulem_stream_begin:, % \@@_boundary_ulem_math_tail_space:nnn, % \@@_boundary_if_ulem_math_reorder:n, % \xeCJK_ulem_group_begin:, % \xeCJK_ulem_group_end:, % \xeCJK_ulem_on:n} % \changes{v3.10.5}{2026/07/28}{正文一律以字面记号进入 \pkg{ulem} 的参数: % 只有“公式加尾随源码空格”才重排,且重排也先拼装再一次展开,避免西文词 % 右侧的 \tn{CJKecglue} 被关进固定宽度装饰片段盒而丢失收缩量(\#1026)。} % \changes{v3.10.5}{2026/08/05}{在用户手册说明线型命令相互嵌套时被嵌套的 % 一段无法断行,并区分线型与符号型命令、给出替代写法;同时以「能否断行」 % 为可观察量给 \tn{UL@onin} 路径补上回归保护(\#1057)。} % \changes{v3.10.4}{2026/07/20}{将 xeCJKfntef 线型命令、原生 % \pkg{ulem} 入口(如 \tn{uline})、\tn{xeCJKfntefon} 与独立符号命令 % 接入 stream capture,并让内部 pending 通过 framework 的统一辅助函数 % 发布;删除 fntef 专用末状态恢复和 pending 设置(\#992)。} % \changes{v3.10.4}{2026/07/20}{让嵌套的 \pkg{ulem}/xeCJKfntef % 线型命令只由最外层启动 stream capture,修复 capture 栈逐次累积的 % 状态泄漏(\#992)。} % 线型命令的正文参数由 ulem 自己扫描,不能使用普通 cmd after hook。 % ulem 会把正文拆进固定宽度盒子;framework 算出的左侧 glue 因此通过 % \cs{@@_boundary_use_ulem_glue:n} 转交 ulem 的 \tn{UL@stop}/\tn{UL@start} % 外层通道,保留弹性和断行位置且不把间距画上线。本包的线型命令在 % 装饰符号测量前启动 stream;原生 \tn{uline} 等命令和 % \tn{xeCJKfntefon} 则由共有的 \tn{ULon} 补上同一入口。局部布尔避免 % 本包命令在到达 \tn{ULon} 时重复启动。原生 ulem 命令嵌套本包线型命令 % 时,内层会走 ulem 的 \tn{UL@onin} 路径而不会另行调用 % \cs{@@_ulem_end:};因此 group 入口与 \tn{ULon} 必须共用同一个“仅最外层 % 启动”辅助函数。所有嵌套层复用外层 stream,最外层唯一的 % \cs{@@_ulem_end:} 把真实末尾 marker 移到外层列表后结束并清除布尔。 % \tn{UL@on}/\tn{UL@onin} 在把正文交给 ulem 扫描前检查其单参数正文是否以 % 字面数学切换符开始;独立符号命令在 % \cs{@@_under_symbol_auxii:nnnnnn} 的对应两端使用同一 begin/end 和公式入口。 % \begin{macrocode} \cs_new_protected:Npn \@@_boundary_use_ulem_glue:n #1 { \xeCJK_if_ulem_patch:TF { \tl_if_empty:NTF \l_@@_group_tag_tl { \UL@stop \skip_horizontal:n {#1} \UL@start } { \str_if_eq:eeTF { \l_@@_group_tag_tl } { \c_@@_group_tag_tl } { \UL@stop \skip_horizontal:n {#1} \UL@start } { \skip_horizontal:n {#1} } } } { \skip_horizontal:n {#1} } } \cs_new_protected:Npn \@@_ulem_stream_begin: { \bool_if:NF \l_@@_ulem_stream_started_bool { \bool_set_true:N \l_@@_ulem_stream_started_bool \skip_gzero:N \g_@@_ulem_pending_shrink_skip \@@_boundary_inline_stream_begin:nn { auto } { stream-ulem } } } \cs_new_protected:Npn \xeCJK_ulem_group_begin: { \mode_leave_vertical: \@@_ulem_stream_begin: \c_group_begin_token } \cs_new_protected:Npn \xeCJK_ulem_group_end: { \c_group_end_token } \cs_new_protected:Npn \xeCJK_ulem_on:n { \ULon } \cs_new_eq:NN \@@_orig_ulem_on: \ULon \cs_set_protected:Npn \ULon { \@@_ulem_stream_begin: \@@_orig_ulem_on: } \bool_new:N \l_@@_ulem_stream_started_bool % \end{macrocode} % ulem 会把源码空格改造成 leaders;等到参数末尾再检查时,公式节点已经被 % 装饰节点遮住。若正文语法确实是“公式+尾随源码空格”,先让 ulem 排完 % 去掉尾随空格的正文,在公式节点仍可见时确认公式末尾,随后把一枚空格 % 交还 ulem 排版;capture 结束时会将它标记为冻结空格。未知宏若消费公式, % 实际末节点检查仍会失败。 % % 无论走哪条路径,正文都必须以字面记号进入 \tn{UL@on} 的替换文本,不能再经过 % 一层宏参数间接展开:\pkg{ulem} 自己扫描正文并按源码空格切分片段,正文若作为 % 另一个宏的参数被重新扫描,西文词右侧由边界恢复链补出的 \tn{CJKecglue} 会落在 % 固定宽度的装饰片段盒内部,其收缩量被盒子固化而无法参与断行,整段可收缩量 % 因此按西文词数减少(\#1026)。 % % 重排路径同样受这条约束。这里先把去掉尾随空格的正文和两端的固定记号拼成 % 完整的 ulem 参数,再用 \cs{exp_args:NV} 把它一次展开到参数位置,使记号与 % 直接书写 \texttt{\#1} 等价;不能改用 \cs{tl_use:N},那会让正文晚一层展开而 % 重新触发上述问题。 % \tn{UL@on} 需要两端的 \cs{xeCJK_ulem_left:}/\cs{xeCJK_ulem_right:}, % \tn{UL@onin} 由 \pkg{ulem} 自己的 \tn{UL@onin} 负责这两端,因此拼装时把它们 % 作为前后缀参数传入,不写死在 body 里。 % \begin{macrocode} \tl_new:N \l_@@_ulem_body_tl \cs_new_protected:Npn \@@_boundary_ulem_math_tail_space:nnn #1#2#3 { \tl_set:Nn \l_@@_ulem_body_tl {#2} \tl_put_right:Ne \l_@@_ulem_body_tl { \tl_trim_right_spaces:n {#1} } \tl_put_right:Nn \l_@@_ulem_body_tl { \@@_boundary_if_last_math_node:T { \@@_boundary_math_end_confirm_space_visible: } \c_space_tl } \tl_put_right:Nn \l_@@_ulem_body_tl {#3} } \prg_new_protected_conditional:Npnn \@@_boundary_if_ulem_math_reorder:n #1 { T , F , TF } { \@@_boundary_if_math_tail:nTF {#1} { \@@_boundary_if_math_tail_space:nTF {#1} { \prg_return_true: } { \prg_return_false: } } { \prg_return_false: } } \cs_new_eq:NN \@@_ulem_on:n \UL@on \cs_set_protected:Npn \UL@on #1 { \@@_boundary_math_begin:n {#1} \@@_boundary_if_ulem_math_reorder:nTF {#1} { \@@_boundary_ulem_math_tail_space:nnn {#1} { \xeCJK_ulem_left: } { \xeCJK_ulem_right: } \exp_args:NV \@@_ulem_on:n \l_@@_ulem_body_tl } { \@@_ulem_on:n { \xeCJK_ulem_left: #1 \@@_boundary_math_end:n {#1} \xeCJK_ulem_right: } } } \cs_new_eq:NN \@@_orig_ulem_onin:n \UL@onin \cs_set_protected:Npn \UL@onin #1 { \@@_boundary_math_begin:n {#1} \@@_boundary_if_ulem_math_reorder:nTF {#1} { \@@_boundary_ulem_math_tail_space:nnn {#1} { } { } \exp_args:NV \@@_orig_ulem_onin:n \l_@@_ulem_body_tl } { \@@_orig_ulem_onin:n { #1 \@@_boundary_math_end:n {#1} } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\xeCJKfntefon} % \changes{v3.2.15}{2014/11/07}{完善选项。} % 扩展 \tn{ULon} 的参数。 % \begin{macrocode} \NewDocumentCommand \xeCJKfntefon { s t- s o } { \mode_leave_vertical: \xeCJK_ulem_boot:NNNn #1#2#3 {#4} \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKunderline} % \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使下划线相对正文 % 居中,避免起始位置改变时产生水平偏移(\#531)。} % \begin{macrocode} \NewDocumentCommand \CJKunderline { s t- s o } { \xeCJK_ulem_group_begin: \xeCJK_fntef_boot:nnNNNn { underline } { uline } #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_cleaders:D \xeCJK_fntef_initial:nnn { \l_@@_uline_depth_tl } { \l_@@_uline_sep_tl } { \l_@@_uline_format_tl \tex_vrule:D height \dim_eval:n { \l_@@_uline_thickness_tl } depth \c_zero_dim width .2em } \xeCJK_ulem_on:n } \NewDocumentCommand \varCJKunderline { } { \CJKunderline - } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\@@_fntef_wave_symbol:,\@@_fntef_xout_symbol:} % \begin{variable}{\l_@@_fntef_pattern_dim} % \changes{v3.10.5}{2026/07/27}{波浪线和斜删除线改用随字号缩放的 % 四分之一字宽绘图单元和普通 \tn{leaders};带减号形式在首尾各 % 内缩半个周期,避免相邻命令连在一起(\#1012)。} % 默认波浪符号来自固定的 \texttt{lasy6} 字号,斜删除线来自当前数学字体; % 两者的宽度与正文的字号和全角字宽没有稳定比例。\pkg{ulem} 又会在每个正文 % 片段中重新排列装饰盒子,因此固定宽度会让余量随字号、内容和伸缩间距改变。 % % 这里用 \pkg{l3draw} 生成宽度为 \texttt{1em/4} 的完整波浪周期和斜线, % 常规全角字符片段每字约容纳四个单元。两个默认符号都使用普通 % \tn{leaders}。正文片段、可伸缩的 \tn{CJKglue} 和换行前后的片段虽然由 % \pkg{ulem} 分开排出,却仍以同一行的起点为相位基准,因而不会在接点重新 % 分配余量;胶水仍保留原来的伸缩和断行能力。 % % 普通形式保留 \pkg{ulem} 的既有几何,即在正文左右各外伸半个图案周期。 % 带减号形式则在正文左右各内缩半个周期;两个相邻命令因此留下一个完整 % 周期的断口,同时不改变正文宽度。一般显式水平跳距仍按 \pkg{ulem} 的原有 % 语义绘制装饰。绘图尺寸全部相对当前 \texttt{em} 计算,因而会随字号缩放; % 斜删除线覆盖基线以下约 \texttt{.09em} 到基线以上约 \texttt{.84em},与常见 % 全角汉字的深度和高度相符,也不再依赖数学字体。用户通过 % \opt{underwave/symbol} 指定的符号 % 继续使用原来的 \tn{xleaders};任意自定义符号的周期、尺寸和接点不属于这里 % 的默认图案处理范围。 % \begin{macrocode} \cs_new_protected:Npn \@@_fntef_wave_symbol: { \dim_set:Nn \l_@@_fntef_pattern_dim { 1 em / 4 } \hbox_to_wd:nn { \l_@@_fntef_pattern_dim } { \hss \draw_begin: \draw_set_linewidth:n { .04 em } \draw_set_cap_butt: \draw_path_moveto:n { 0 pt , .055 em } \draw_path_curveto:nnn { \l_@@_fntef_pattern_dim * 3 / 16 , .055 em } { \l_@@_fntef_pattern_dim * 5 / 16 , -.055 em } { \l_@@_fntef_pattern_dim / 2 , -.055 em } \draw_path_curveto:nnn { \l_@@_fntef_pattern_dim * 11 / 16 , -.055 em } { \l_@@_fntef_pattern_dim * 13 / 16 , .055 em } { \l_@@_fntef_pattern_dim , .055 em } \draw_path_use_clear:n { draw } \draw_end: \hss } } \cs_new_protected:Npn \@@_fntef_xout_symbol: { \dim_set:Nn \l_@@_fntef_pattern_dim { 1 em / 4 } \hbox_to_wd:nn { \l_@@_fntef_pattern_dim } { \hss \draw_begin: \draw_set_linewidth:n { .04 em } \draw_set_cap_butt: \draw_path_moveto:n { 0 pt , -.445 em } \draw_path_lineto:n { \l_@@_fntef_pattern_dim , .445 em } \draw_path_use_clear:n { draw } \draw_end: \hss } } \dim_new:N \l_@@_fntef_pattern_dim % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\CJKunderwave} % \changes{v3.10.3}{2026/07/12}{使用 \tn{xleaders} 使波浪线相对正文对齐, % 同时保持字间连接连续(\#967)。} % \begin{macrocode} \NewDocumentCommand \CJKunderwave { s t- s o } { \xeCJK_ulem_group_begin: \xeCJK_fntef_boot:nnNNNn { underwave } { uwave } #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_xleaders:D \tl_if_eq:NnT \l_@@_uwave_symbol_tl { \@@_fntef_wave_symbol: } { \cs_set_eq:NN \ULleaders \tex_leaders:D \bool_set_true:N \l_@@_ulem_periodic_bool } \xeCJK_fntef_initial:nnn { \l_@@_uwave_depth_tl } { \l_@@_uwave_sep_tl } { \l_@@_uwave_format_tl \l_@@_uwave_symbol_tl } \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKunderdblline} % \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使双下划线相对正文居中 % (\#967)。} % \begin{macrocode} \NewDocumentCommand \CJKunderdblline { s t- s o } { \xeCJK_ulem_group_begin: \xeCJK_fntef_boot:nnNNNn { underdblline } { udbline } #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_cleaders:D \xeCJK_fntef_initial:nnn { \l_@@_udbline_depth_tl } { \l_@@_udbline_sep_tl } { \l_@@_udbline_format_tl \vbox_top:n { \tex_hrule:D height \dim_eval:n { \l_@@_udbline_thickness_tl } depth \c_zero_dim width .2em \tex_kern:D \dim_eval:n { \l_@@_udbline_gap_tl } \tex_hrule:D height \dim_eval:n { \l_@@_udbline_thickness_tl } depth \c_zero_dim width .2em } } \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKsout} % \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使删除线相对正文居中 % (\#967)。} % \begin{macrocode} \NewDocumentCommand \CJKsout { s t- s o } { \xeCJK_ulem_group_begin: \xeCJK_fntef_boot:nnNNNn { sout } { sout } #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_cleaders:D \xeCJK_fntef_initial:nn { \l_@@_sout_format_tl \tex_vrule:D height \dim_eval:n { \l_@@_sout_thickness_tl } depth \c_zero_dim width .2em } { \box_move_up:nn { \l_@@_sout_height_tl - \box_ht:N \l_@@_fntef_box / 2 } { \box_use:N \l_@@_fntef_box } } \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKxout} % \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使斜删除线相对正文居中 % (\#967)。} % \begin{macrocode} \NewDocumentCommand \CJKxout { s t- s o } { \xeCJK_ulem_group_begin: \xeCJK_fntef_boot:nnNNNn { xout } { xout } #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_leaders:D \xeCJK_fntef_initial:nn { \l_@@_xout_format_tl \@@_fntef_xout_symbol: } { \box_move_down:nn { .09 em } { \box_use:N \l_@@_fntef_box } } \bool_set_true:N \l_@@_ulem_periodic_bool \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKunderanyline} % \changes{v3.2.15}{2014/11/07}{完善选项。} % \changes{v3.10.3}{2026/07/12}{使用 \tn{cleaders} 使自定义线相对正文居中 % (\#967)。} % \begin{macrocode} \NewDocumentCommand \CJKunderanyline { s t- s o m m } { \xeCJK_ulem_group_begin: \xeCJK_ulem_boot:NNNn #1#2#3 {#4} \cs_set_eq:NN \ULleaders \tex_cleaders:D \xeCJK_fntef_initial:nn {#6} { \box_move_down:nn {#5} { \box_use:N \l_@@_fntef_box } } \tl_if_empty:NF \l_@@_ulem_boxdepth_tl { \box_set_dp:Nn \ULC@box { \l_@@_ulem_boxdepth_tl } } \tl_if_empty:NF \l_@@_ulem_sep_tl { \bool_set_true:N \l_@@_fntef_bool \dim_set:Nn \l_@@_fntef_dim { \l_@@_ulem_sep_tl + \box_dp:N \ULC@box } } \xeCJK_ulem_on:n } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_fntef_boot:nnNNNn} % 处理参数问题。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_fntef_boot:nnNNNn #1#2#3#4#5#6 { \bool_lazy_or:nnT {#3} {#5} { \bool_set_false:c { l_@@_#2_skip_bool } } \bool_if:NT #4 { \bool_set_true:c { l_@@_#2_subtract_bool } } \tl_if_novalue:nF {#6} { \keys_set:nn { xeCJK / options / #1 } {#6} } \bool_set_eq:Nc \l_@@_ulem_skip_bool { l_@@_#2_skip_bool } \bool_set_eq:Nc \l_@@_ulem_hidden_bool { l_@@_#2_hidden_bool } \bool_set_eq:Nc \l_@@_ulem_subtract_bool { l_@@_#2_subtract_bool } \tl_set_eq:Nc \l_@@_ulem_text_format_tl { l_@@_#2_text_format_tl } } \cs_new_protected:Npn \xeCJK_ulem_boot:NNNn #1#2#3#4 { \bool_lazy_or:nnT {#1} {#3} { \bool_set_false:N \l_@@_ulem_skip_bool } \bool_if:NT #2 { \bool_set_true:N \l_@@_ulem_subtract_bool } \tl_if_novalue:nF {#4} { \keys_set:nn { xeCJK / options / ulem } {#4} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_fntef_initial:n} % 不支持下划线的嵌套使用。下划线嵌套使用时,里层的下划线会被放在盒子里,不能折行。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_fntef_initial:n { \bool_if:NTF \l_@@_nest_bool { \@@_warning:n { fntef-nesting } } { \bool_set_true:N \l_@@_nest_bool \@@_restore_shipout_fntef: } \xeCJK_fntef_sbox:n } \cs_new_protected:Npn \xeCJK_fntef_initial:nn #1 { \xeCJK_fntef_initial:n {#1} \bool_if:NF \l_@@_fntef_bool { \dim_zero:N \l_@@_fntef_dim } \markoverwith } \cs_new_protected:Npn \xeCJK_fntef_initial:nnn #1#2#3 { \xeCJK_fntef_initial:n {#3} \bool_if:NF \l_@@_fntef_bool { \bool_set_true:N \l_@@_fntef_bool \dim_set:Nn \l_@@_fntef_dim {#1} } \markoverwith { \box_move_down:nn { \l_@@_fntef_dim + \box_ht:N \l_@@_fntef_box } { \box_use:N \l_@@_fntef_box } } \dim_set:Nn \l_@@_fntef_dim { #2 + \box_dp:N \ULC@box } } \box_new:N \l_@@_fntef_box \cs_new_eq:NN \xeCJKfntefbox \l_@@_fntef_box \bool_new:N \l_@@_nest_bool \bool_new:N \l_@@_fntef_bool \@@_msg_new:nn { fntef-nesting } { Nesting~is~not~supported. } % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_fntef_dim} % 记录下划线或者下划符号的深度,以便它们嵌套使用时能自动调整好距离。 % \tn{ULdepth} 被 \pkg{ulem} 初始化为 \tn{maxdimen}。下划线嵌套时,\pkg{ulem} % 要使用它作计算,可能会溢出。为简便起见,\cs{l_@@_fntef_dim} 与 \tn{ULdepth} % 共用一个寄存器。 % \begin{macrocode} \cs_new_eq:NN \l_@@_fntef_dim \ULdepth % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\xeCJK_fntef_sbox:n} % \changes{v3.10.0}{2026/05/13}{保存并恢复 \cs{g_@@_last_node_tl}, % 避免 hbox 内渲染装饰符号时 interchar toks 污染全局节点标记, % 修复 \pkg{xeCJKfntef} 与 \tn{textcolor} 组合使用时 % 多余的 \texttt{CJKecglue}。} % \changes{v3.10.4}{2026/07/20}{改用 framework 的可嵌套暂停区隔离 % 测量装饰符号时构造的盒子,同时保存、恢复 marker 与 pending 状态(\#992)。} % \changes{v3.10.5}{2026/07/25}{用空的 \texttt{ActualText} 标记装饰内容, % 并在构造装饰盒时暂停 LaTeX tagging,避免波浪下划线和斜删除线使用的 % 字形进入 PDF 文本提取结果(\#1017)。} % 与 \cs{hcoffin_set:Nn} 和 \LaTeXe{} 的 \tn{sbox} 功能类似,确保颜色的正确。 % 虽然 \texttt{coffin} 可以更方便的操作盒子,但速度要慢一点。并且,我们的需求 % 也比较简单,就不用它了。 % % 装饰盒可能包含波浪线字符、数学斜线或者用户自定义的装饰符号,随后还会被 % \pkg{ulem} 的 leaders 反复复制。用空的 \texttt{ActualText} 包住整个装饰盒, % 可以保留页面上的绘制结果,同时让 PDF 阅读器在复制和搜索时只看到正文。 % tagged PDF 会给盒子里的数学字符增加内层标记;若不暂停 tagging,内层标记仍会 % 使斜删除线的字符进入文本提取结果。普通文档没有 \cs{tag_suspend:n},因此这里只 % 在 LaTeX tagging 接口存在时暂停,并在装饰内容结束后立即恢复。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_fntef_sbox:n #1 { \@@_boundary_capture_suspend: \hbox_set:Nn \l_@@_fntef_box { \color_ensure_current: \BeginAccSupp { ActualText = { } } \cs_if_exist:NT \tag_suspend:n { \tag_suspend:n { xeCJKfntef } } #1 \cs_if_exist:NT \tag_resume:n { \tag_resume:n { xeCJKfntef } } \EndAccSupp { } } \@@_boundary_capture_resume: } % \end{macrocode} % \end{macro} % % 最合适的是用 \pkg{xtemplate} 宏包来实现,但是比较难于用 \tn{xeCJKsetup} 来统一 % 设置,所以这里还是用土办法。 % \begin{macrocode} \keys_define:nn { xeCJK / options } { underdot / symbol .tl_set:N = \l_@@_udot_symbol_tl , underdot / depth .tl_set:N = \l_@@_udot_depth_tl , underdot / sep .tl_set:N = \l_@@_udot_sep_tl , underdot / format .tl_set:N = \l_@@_udot_format_tl , underdot / textformat .tl_set:N = \l_@@_udot_text_format_tl , underdot / boxdepth .tl_set:N = \l_@@_udot_boxdepth_tl , symbol / sep .tl_set:N = \l_@@_symbol_sep_tl , symbol / boxdepth .tl_set:N = \l_@@_symbol_boxdepth_tl , symbol / textformat .tl_set:N = \l_@@_symbol_text_format_tl , underline / skip .bool_set:N = \l_@@_uline_skip_bool , underline / hidden .bool_set:N = \l_@@_uline_hidden_bool , underline / subtract .bool_set:N = \l_@@_uline_subtract_bool , underline / thickness .tl_set:N = \l_@@_uline_thickness_tl , underline / depth .tl_set:N = \l_@@_uline_depth_tl , underline / sep .tl_set:N = \l_@@_uline_sep_tl , underline / format .tl_set:N = \l_@@_uline_format_tl , underline / textformat .tl_set:N = \l_@@_uline_text_format_tl , underdblline / skip .bool_set:N = \l_@@_udbline_skip_bool , underdblline / hidden .bool_set:N = \l_@@_udbline_hidden_bool , underdblline / subtract .bool_set:N = \l_@@_udbline_subtract_bool , underdblline / thickness .tl_set:N = \l_@@_udbline_thickness_tl , underdblline / depth .tl_set:N = \l_@@_udbline_depth_tl , underdblline / sep .tl_set:N = \l_@@_udbline_sep_tl , underdblline / format .tl_set:N = \l_@@_udbline_format_tl , underdblline / textformat .tl_set:N = \l_@@_udbline_text_format_tl , underdblline / gap .tl_set:N = \l_@@_udbline_gap_tl , underwave / skip .bool_set:N = \l_@@_uwave_skip_bool , underwave / hidden .bool_set:N = \l_@@_uwave_hidden_bool , underwave / subtract .bool_set:N = \l_@@_uwave_subtract_bool , underwave / symbol .tl_set:N = \l_@@_uwave_symbol_tl , underwave / depth .tl_set:N = \l_@@_uwave_depth_tl , underwave / sep .tl_set:N = \l_@@_uwave_sep_tl , underwave / format .tl_set:N = \l_@@_uwave_format_tl , underwave / textformat .tl_set:N = \l_@@_uwave_text_format_tl , sout / skip .bool_set:N = \l_@@_sout_skip_bool , sout / hidden .bool_set:N = \l_@@_sout_hidden_bool , sout / subtract .bool_set:N = \l_@@_sout_subtract_bool , sout / thickness .tl_set:N = \l_@@_sout_thickness_tl , sout / height .tl_set:N = \l_@@_sout_height_tl , sout / format .tl_set:N = \l_@@_sout_format_tl , sout / textformat .tl_set:N = \l_@@_sout_text_format_tl , xout / skip .bool_set:N = \l_@@_xout_skip_bool , xout / hidden .bool_set:N = \l_@@_xout_hidden_bool , xout / subtract .bool_set:N = \l_@@_xout_subtract_bool , xout / format .tl_set:N = \l_@@_xout_format_tl , xout / textformat .tl_set:N = \l_@@_xout_text_format_tl , ulem / skip .bool_set:N = \l_@@_ulem_skip_bool , ulem / hidden .bool_set:N = \l_@@_ulem_hidden_bool , ulem / subtract .bool_set:N = \l_@@_ulem_subtract_bool , ulem / sep .tl_set:N = \l_@@_ulem_sep_tl , ulem / boxdepth .tl_set:N = \l_@@_ulem_boxdepth_tl , ulem / textformat .tl_set:N = \l_@@_ulem_text_format_tl } \clist_map_inline:nn { underdot , underline , underdblline , underwave , sout , xout , ulem } { \keys_define:nn { xeCJK / options } { #1 .meta:nn = { xeCJK / options / #1 } { ##1 } } } \keys_set:nn { xeCJK / options } { underdot / symbol = \normalfont . , underdot / depth = 0.20 em , underdot / sep = 0.04 em , symbol / sep = \c_zero_dim , underline / skip = true , underline / thickness = \ULthickness , underline / depth = 0.20 em , underline / sep = 0.07 em , underdblline / skip = true , underdblline / thickness = \ULthickness , underdblline / depth = 0.20 em , underdblline / sep = 0.17 em , underdblline / gap = 1.1 pt , underwave / skip = true , underwave / symbol = \@@_fntef_wave_symbol: , underwave / depth = 0.20 em , underwave / sep = 0.00 em , sout / skip = true , sout / thickness = \ULthickness , sout / height = 0.35 em , xout / skip = true } % \end{macrocode} % % \changes{v3.8.3}{2020/04/07}{取消 \pkg{xeCJKfntef} 的初始彩色设置。} % % \begin{macro}{\CJKunderanysymbol} % \changes{v3.10.0}{2026/04/28}{移除 \tn{ignorespaces},修复命令后空格丢失(\#465)。} % \begin{macrocode} \NewDocumentCommand \CJKunderanysymbol { o m m m } { \xeCJK_under_symbol:nnnnnn { symbol } { symbol } {#1} {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}{\CJKunderdot} % \tn{CJKunderdot} 是 \tn{CJKunderanysymbol} 的特殊情况。\pkg{CJKfntef} 原来使用 % 的是数学符号 \tn{cdot},这里改成更合适的 |.|。 % \changes{v3.10.0}{2026/04/28}{移除 \tn{ignorespaces},修复命令后空格丢失(\#465)。} % \begin{macrocode} \NewDocumentCommand \CJKunderdot { o m } { \xeCJK_under_symbol:nnnnnn { underdot } { udot } {#1} { \l_@@_udot_depth_tl } { \l_@@_udot_format_tl \l_@@_udot_symbol_tl } {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_under_symbol:nnnnnn} % 当处在下划线中时,我们先断开下划线,在分组外设置下划符号。 % \changes{v3.10.0}{2026/05/12}{在独立模式的末尾设置 \cs{g_@@_glue_check_pending_bool}, % 修复 \tn{CJKunderdot} 等命令右侧空格问题(\#826)。} % \begin{macrocode} \cs_new_protected:Npn \xeCJK_under_symbol:nnnnnn { \xeCJK_if_ulem_patch:TF { \@@_under_symbol_auxi:nnnnnn } { \@@_under_symbol_auxii:nnnnnn } } \cs_new_protected:Npn \@@_under_symbol_auxi:nnnnnn #1#2#3#4#5#6 { \xeCJK_ulem_right: \UL@stop \group_begin: \xeCJK_under_symbol_initial:nnnnn {#1} {#2} {#3} {#4} {#5} \use:c { l_@@_#2_text_format_tl } \UL@start \xeCJK_ulem_right_node: #6 \xeCJK_ulem_right: \UL@stop \group_end: \UL@start \xeCJK_ulem_right_node: } \cs_new_protected:Npn \@@_under_symbol_auxii:nnnnnn #1#2#3#4#5#6 { \@@_boundary_inline_stream_begin:nn { auto } { stream-ulem } \@@_boundary_math_begin:n {#6} \group_begin: \xeCJK_under_symbol_initial:nnnnn {#1} {#2} {#3} {#4} {#5} \@@_under_symbol_text_format:c { l_@@_#2_text_format_tl } #6 \@@_boundary_math_end:n {#6} \xeCJK_ulem_right: \group_end: \xeCJK_ulem_right_node: \@@_boundary_inline_stream_end:n { auto } } \cs_new_protected:Npn \xeCJK_under_symbol_initial:nnnnn #1#2#3#4#5 { \tl_if_novalue:nF {#3} { \keys_set:nn { xeCJK / options / #1 } {#3} } \xeCJK_fntef_sbox:n {#5} \bool_if:NTF \l_@@_fntef_bool { \xeCJK_make_under_symbol:n { \l_@@_fntef_dim } } { \bool_set_true:N \l_@@_fntef_bool \xeCJK_make_under_symbol:n {#4} } \tl_if_empty:cF { l_@@_#2_boxdepth_tl } { \box_set_dp:Nn \l_@@_under_symbol_box { \use:c { l_@@_#2_boxdepth_tl } } } \dim_set:Nn \l_@@_fntef_dim { \use:c { l_@@_#2_sep_tl } + \box_dp:N \l_@@_under_symbol_box } \xeCJK_swap_cs:NN \CJKsymbol \@@_under_CJKsymbol:N \@@_restore_shipout_CJKsymbol: } \cs_new_protected:Npn \@@_under_symbol_text_format:N #1 { \tl_if_empty:NF #1 { \xeCJK_ulem_right: #1 \xeCJK_ulem_right_node: } } \cs_generate_variant:Nn \@@_under_symbol_text_format:N { c } \box_new:N \l_@@_under_symbol_box % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_make_under_symbol:n} % 我们量取“一”的宽度作为汉字的宽度。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_make_under_symbol:n #1 { \hbox_set:Nn \l_@@_under_symbol_box { \box_move_down:nn { #1 + \box_ht:N \l_@@_fntef_box } { \hbox_to_zero:n { \xeCJK_select_font: \tex_kern:D \tex_fontcharwd:D \tex_font:D "4E00 \exp_stop_f: \tex_hss:D \box_use:N \l_@@_fntef_box \tex_hss:D } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_restore_shipout_CJKsymbol:} % \changes{v3.2.3}{2013/06/04}{解决 \tn{CJKunderdot} 跨页使用时影响到页眉页脚的问题。} % \tn{CJKunderdot} 中对 \tn{CJKsymbol} 的修改会影响到页眉和页脚,需要小心处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_restore_shipout_CJKsymbol: { \tl_put_right:Nn \l_@@_fntef_shipout_tl { \xeCJK_swap_cs:NN \CJKsymbol \@@_under_CJKsymbol:N } \@@_restore_shipout_fntef: \xeCJK_cs_clear:N \@@_restore_shipout_CJKsymbol: } \cs_new_protected:Npn \@@_restore_shipout_fntef: { \tl_put_right:Nn \l_@@_fntef_shipout_tl { \bool_set_false:N \l_@@_fntef_bool \dim_zero:N \l_@@_fntef_dim } \xeCJK_cs_clear:N \@@_restore_shipout_fntef: } \tl_new:N \l_@@_fntef_shipout_tl \xeCJK_add_to_shipout:n { \l_@@_fntef_shipout_tl } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_under_CJKsymbol:N} % 盒子放在汉字的左侧,比较容易处理状态转移的问题。 % \begin{macrocode} \cs_new_protected:Npn \@@_under_CJKsymbol:N { \box_use:N \l_@@_under_symbol_box \xeCJK_no_break: \@@_under_CJKsymbol:N } % \end{macrocode} % \end{macro} % % \changes{v3.7.2}{2019/03/24}{改用 \pkg{xparse} 的新参数类型 \texttt{b} % 定义 \env{CJKfilltwosides*} 环境,不再依赖 \pkg{environ} 包。} % % \begin{macro}{CJKfilltwosides} % \changes{v3.2.4}{2013/06/26}{改用 \texttt{minipage} 和 \LaTeX 表格(\texttt{tabular})来实现。} % \changes{v3.3.3}{2015/05/30}{确保进入水平模式。} % 使用 \texttt{minipage} 和 \LaTeX 表格(\texttt{tabular})来定义 |CJKfilltwosides| 环境。 % 可选参数 |#1| 表示环境的垂直对齐位置,默认居中;参数 |#2| 表示环境的宽度。 % 带星号的环境,如果 |#2| 不大于零或者不大于环境最长文本行的宽度,则取环境的自然宽度。 % \begin{macrocode} \NewDocumentEnvironment { CJKfilltwosides } { O { c } m } { \use:e { \exp_not:N \minipage [#1] { \dim_eval:n {#2} } } \cs_set_eq:NN \CJKglue \xeCJK_fntef_hfilll: } { \endminipage \ignorespacesafterend } \NewDocumentEnvironment { CJKfilltwosides* } { O { c } m +b } { \mode_leave_vertical: \cs_set_eq:NN \CJKglue \xeCJK_fntef_hfilll: \tl_set:Nn \arraystretch { 1 } \cs_if_free:NF \extrarowheight { \cs_set_eq:NN \extrarowheight \c_zero_dim } \use:e { \@@_fill_two_sides:nnn {#1} { \dim_eval:n {#2} } } {#3} } { \ignorespacesafterend } \cs_new_protected:Npn \@@_fill_two_sides:nnn #1#2#3 { \dim_compare:nNnTF {#2} > \c_zero_dim { \hbox_set:Nn \l_@@_tmp_box { \tabular [#1] { @ { } c @ { } } #3 \endtabular } \dim_compare:nNnTF {#2} > { \box_wd:N \l_@@_tmp_box } { \tabular [#1] { @ { } p {#2} @ { } } #3 \endtabular } { \box_use:N \l_@@_tmp_box } } { \tabular [#1] { @ { } c @ { } } #3 \endtabular } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xeCJK_fntef_hfilll:} % \pkg{colortbl} 将表格 |c| 列用于填充的 \tn{hfil} 改为了更高阶的 \texttt{fill}, % 影响到了 \env{CJKfilltwosides*}。因此,我们也要用高阶的 \texttt{filll}。 % \begin{macrocode} \cs_new_protected:Npn \xeCJK_fntef_hfilll: { \skip_horizontal:N \c_@@_filll_skip } \skip_const:Nn \c_@@_filll_skip { \c_zero_dim plus 1 filll } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-listings}} % % \changes{v3.2.2}{2013/06/04}{增加小宏包 \pkg{xeCJK-listings},用于支持 \pkg{listings} 宏包。} % \changes{v3.2.3}{2013/06/06}{完善对 \pkg{listings} 宏包的支持。} % \changes{v3.10.0}{2026/06/23}{修复在 \tn{lstinline} 参数中把非 \texttt{\#} % 的字符(如 \texttt{\&})用 \tn{catcode} 设为 catcode~6 时输出错字的问题(\#879)。} % % 仿照 \package{luatexja} 宏包中 \pkg{lltjp-listings} 的处理,支持 \package{listings} 宏包。 % % \begin{macrocode} %<*listings> % \end{macrocode} % % \begin{macrocode} \DeclareOption* { \PassOptionsToPackage { \CurrentOption } { xeCJK } } \ProcessOptions \scan_stop: % \end{macrocode} % % \begin{macrocode} \RequirePackage { xeCJK } \RequirePackage { listings } % \end{macrocode} % % \begin{macrocode} \lst@AddToHook { Init } { \@@_listings_initial_hook: } \lst@AddToHook { SelectCharTable } { \@@_listings_toks_hook: } \lst@AddToHook { OutputBox } { \tl_set_eq:NN \l_xeCJK_punct_style_tl \c_@@_punct_style_plain_tl \l_@@_restore_listings_toks_tl \@@_listings_output_CM: } \lst@AddToHook { PreSet } { \bool_set_true:N \l_@@_listings_env_bool } % \end{macrocode} % % \begin{macro}{\@@_listings_initial_hook:} % \changes{v3.2.3}{2013/06/04} % {解决 \texttt{listings} 环境中代码行号输出不正确的问题,并解决在其中跨页时对页眉 % 和页脚的影响。} % \changes{v3.2.15}{2014/11/10}{修正 \texttt{breaklines} 无效的问题。} % \changes{v3.3.1}{2015/04/20} % {解决 \texttt{prebreak} 和 \texttt{postbreak} 功能失效的问题。} % 为使代码行号结果正确,需要在 \tn{lst@numberstyle} 中恢复 \tn{XeTeXinterchartoks}。 % 在 \texttt{listings} 环境中换页时,对 \tn{XeTeXinterchartoks} 的修改会影响到 % 页眉和页脚,需要在 \tn{shipout} 盒子中恢复成正常定义。加入 \cs{tex_noindent:D} % 是为了进入水平模式,防止汉字出现在首行的时候可能会产生额外空行。 % \tn{lst@prebreak} 和 \tn{lst@postbreak} 是在 \tn{discretionary} 中直接输出的, % 应该恢复正常的 \tn{XeTeXinterchartoks}。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_initial_hook: { \tex_noindent:D \bool_gset_false:N \g_@@_listings_CM_bool \tl_put_left:Nn \lst@numberstyle { \l_@@_restore_listings_toks_tl } \xeCJK_add_to_shipout:n { \l_@@_restore_listings_toks_tl } \lst@ifbreaklines \cs_set_eq:NN \@@_listings_CJK_toks_hook: \@@_listings_breaklines_toks: \tl_if_empty:NF \lst@prebreak { \tl_put_left:Nn \lst@prebreak { \l_@@_restore_listings_toks_tl } } \tl_if_empty:NF \lst@postbreak { \tl_put_left:Nn \lst@postbreak { \l_@@_restore_listings_toks_tl } } \fi: \int_set:Nn \l_@@_listings_max_char_int { \lst@ifec 255 \else: 127 \fi: } } \int_new:N \l_@@_listings_max_char_int % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_toks_hook:} % 采用不同的 \tn{XeTeXinterchartoks} 处理方式,输入的时候是将汉字加入到 \pkg{listings} % 的输出队列,实际输出的时候是普通文字。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_toks_hook: { \tl_clear:N \l_@@_restore_listings_toks_tl \seq_map_function:NN \g_@@_class_seq \@@_backup_inter_class_toks:n \seq_map_inline:Nn \g_@@_non_CJK_class_seq { \str_if_eq:nnF { ##1 } { Boundary } { \xeCJK_inter_class_toks:nnn { Boundary } { ##1 } { \@@_listings_process_Default:nN { ##1 } } } } \xeCJK_inter_class_toks:nnn { Boundary } { CM } { \@@_listings_process_CM:nN { 0 } } \@@_listings_CJK_toks_hook: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backup_inter_class_toks:n} % 注意,给 \tn{XeTeXinterchartoks} 赋空值,会导致 \XeTeX 崩溃! % \begin{macrocode} \cs_new_protected:Npn \@@_backup_inter_class_toks:n #1 { \tl_set:Ne \l_@@_tmp_tl { \xeCJK_get_inter_class_toks:nn { Boundary } {#1} } \tl_put_right:Ne \l_@@_restore_listings_toks_tl { \xeCJK_inter_class_toks:nnn { Boundary } {#1} { \tl_if_empty:NTF \l_@@_tmp_tl { \exp_not:N \prg_do_nothing: } { \exp_not:o \l_@@_tmp_tl } } } } \tl_new:N \l_@@_restore_listings_toks_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_CJK_toks_hook:,\@@_listings_breaklines_toks:} % 根据 \texttt{breaklines} 选项的使用与否,选择不同的处理方式。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_CJK_toks_hook: { \xeCJK_inter_class_toks:nnn { Boundary } { CJK } { \@@_listings_process_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { FullLeft } { \@@_listings_process_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { FullRight } { \@@_listings_process_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamo } { \@@_listings_process_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoL } { \@@_listings_process_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoV } { \@@_listings_process_CM:nN { 0 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoT } { \@@_listings_process_CM:nN { 0 } } \xeCJK_inter_class_toks:nnn { Boundary } { CJStarter } { \@@_listings_process_CJK:nN { 2 } } \seq_map_inline:Nn \g_@@_CJK_sub_class_seq { \xeCJK_inter_class_toks:nnn { Boundary } { CJK/##1 } { \@@_listings_process_CJK:nN { 2 } } } } \cs_new_protected:Npn \@@_listings_breaklines_toks: { \xeCJK_inter_class_toks:nnn { Boundary } { CJK } { \@@_listings_process_breaklines_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamo } { \@@_listings_process_breaklines_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoL } { \@@_listings_process_breaklines_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoV } { \@@_listings_process_CM:nN { 0 } } \xeCJK_inter_class_toks:nnn { Boundary } { HangulJamoT } { \@@_listings_process_CM:nN { 0 } } \xeCJK_inter_class_toks:nnn { Boundary } { CJStarter } { \@@_listings_process_breaklines_CJK:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { FullLeft } { \@@_listings_process_FullLeft:nN { 2 } } \xeCJK_inter_class_toks:nnn { Boundary } { FullRight } { \@@_listings_process_FullRight:nN { 2 } } \seq_map_inline:Nn \g_@@_CJK_sub_class_seq { \xeCJK_inter_class_toks:nnn { Boundary } { CJK/##1 } { \@@_listings_process_breaklines_CJK:nN { 2 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_process_Default:nN} % \changes{v3.2.3}{2013/06/08}{在 \texttt{listings} 环境中对 \tn{charcode} 大于 % $255$ 的字符根据其 \tn{catcode} 区分 \texttt{letter} 和 \texttt{other}。} % \changes{v3.3.1}{2015/01/27}{对 \pkg{listings} 的字符扩展不影响到其符号表中的 % 七位或八位字符。} % 对于 \tn{charcode} 大于 $255$ 的字符,根据 \tn{catcode} 进行处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_process_Default:nN #1#2 { \int_compare:nNnTF { \xeCJK_token_value_charcode:N #2 } > \l_@@_listings_max_char_int { \token_if_letter:NTF #2 { \lst@ProcessLetter #2 } { \lst@ProcessOther #2 } } { \@@_listings_output_Default:nN {#1} #2 } } % \end{macrocode} % 输出时,要注意把对应的 \tn{XeTeXinterchartoks} 清空掉,否则会造成死循环。 % \cs{scan_stop:} 是造边界,输出 \cs{group_end:}。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_output_Default:nN #1#2 { \group_begin: \xeCJK_clear_inter_class_toks:nn { Boundary } {#1} \xeCJK_inter_class_toks:nnn {#1} { Boundary } { \group_end: } #2 \scan_stop: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_process_CJK:nN} % 对 CJK 字符类的处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_process_CJK:nN #1#2 { \token_if_letter:NTF #2 { \@@_listings_process_letter:nN {#1} #2 } { \@@_listings_process_other:nN {#1} #2 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_append:nN} % 普通 CJK 字符的宽度为一般基本宽度的两倍,CM 类不增加宽度。这里有一个问题, % 对 CJK 字符类中的一些半角字符(例如半角日文假名)没有区分开。\pkg{listings} 通过 % 重定义 \tn{lst@Append} 将代码写入外部文件,因此需要保留。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_append:nN #1#2 { \int_add:Nn \lst@length { #1 - 1 } \lst@Append #2 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_process_letter:nN,\@@_listings_process_other:nN} % 在 \texttt{letter} 类中区分汉字和西文字母。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_process_letter:nN { \lst@whitespacefalse \bool_if:NTF \l_@@_listings_letter_bool { \lst@lettertrue } { \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi: \bool_set_true:N \l_@@_listings_letter_bool } \@@_listings_append:nN } \bool_new:N \l_@@_listings_letter_bool \cs_new_protected:Npn \@@_listings_process_other:nN #1#2 { \lst@whitespacefalse \bool_if:NTF \l_@@_listings_letter_bool { \lst@Output \lst@letterfalse \bool_set_false:N \l_@@_listings_letter_bool } { \lst@ifletter \lst@Output \lst@letterfalse \fi: } \cs_set_eq:NN \lst@lastother #2 \@@_listings_append:nN {#1} #2 } % \end{macrocode} % \end{macro} % % \changes{v3.2.4}{2013/07/05} % {使 \pkg{listings} 的 \texttt{breaklines} 选项对 CJK 字符类可用,并保持标点符号的禁则。} % % \begin{macro} % {\@@_listings_process_breaklines_CJK:nN, % \@@_listings_process_FullLeft:nN, % \@@_listings_process_FullRight:nN} % 当使用 \texttt{breaklines} 选项时,立即输出之前的单个文字,以便于断行。并将标点 % 与它前/后的 CJK 文字放在同一个盒子中,以保持禁则。但是不能区分 letter 和 other。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_process_breaklines_CJK:nN { \lst@whitespacefalse \bool_if:NTF \l_@@_listings_letter_bool { \int_compare:nNnF \l_@@_listings_flag_int = 2 { \lst@Output } \lst@lettertrue } { \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi: \bool_set_true:N \l_@@_listings_letter_bool } \int_set_eq:NN \l_@@_listings_flag_int \c_one_int \@@_listings_append:nN } \cs_new_protected:Npn \@@_listings_process_FullLeft:nN #1#2 { \lst@whitespacefalse \bool_if:NTF \l_@@_listings_letter_bool { \int_compare:nNnF \l_@@_listings_flag_int = 2 { \int_compare:nNnTF \l_@@_listings_flag_int = 3 { \bool_if:NT \l_@@_punct_breakable_bool { \lst@Output } } { \lst@Output } } \lst@lettertrue } { \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi: \bool_set_true:N \l_@@_listings_letter_bool } \int_set:Nn \l_@@_listings_flag_int { 2 } \@@_listings_append:nN {#1} #2 } \cs_new_protected:Npn \@@_listings_process_FullRight:nN #1#2 { \lst@whitespacefalse \bool_if:NTF \l_@@_listings_letter_bool { \int_compare:nNnT \l_@@_listings_flag_int < 2 { \@@_punct_if_long:NT #2 { \lst@Output } } \lst@lettertrue } { \lst@ifletter \lst@Output \else: \lst@OutputOther \lst@lettertrue \fi: \bool_set_true:N \l_@@_listings_letter_bool } \int_set:Nn \l_@@_listings_flag_int { 3 } \@@_listings_append:nN {#1} #2 } \int_new:N \l_@@_listings_flag_int % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\lst@AppendLetter,\lst@AppendOther} % \begin{macrocode} \cs_set_protected:Npn \lst@AppendLetter { \bool_if:NTF \l_@@_listings_letter_bool { \lst@Output \lst@lettertrue \bool_set_false:N \l_@@_listings_letter_bool } { \reverse_if:N \lst@ifletter \lst@OutputOther \lst@lettertrue \fi: } \lst@ifbreaklines \int_zero:N \l_@@_listings_flag_int \fi: \lst@Append } \cs_set_protected:Npn \lst@AppendOther { \bool_if:NTF \l_@@_listings_letter_bool { \lst@Output \lst@letterfalse \bool_set_false:N \l_@@_listings_letter_bool } { \lst@ifletter \lst@Output \lst@letterfalse \fi: } \lst@ifbreaklines \int_zero:N \l_@@_listings_flag_int \fi: \tex_futurelet:D \lst@lastother \lst@Append } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_process_CM:nN} % \texttt{CM} 类作为 \texttt{letter} 处理,不用增加 \tn{lst@length}。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_process_CM:nN { \reverse_if:N \lst@ifflexible \bool_gset_true:N \g_@@_listings_CM_bool \fi: \@@_listings_process_letter:nN } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_output_CM:} % 在使用 \texttt{columns=fixed} 选项时,\pkg{listings} 会在输出盒子里的每个字符 % 之间加入 \tn{hss},这就破坏了 \XeTeX 将基本字和组合标识正确的组合起来。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_output_CM: { \reverse_if:N \lst@ifflexible \bool_if:NT \g_@@_listings_CM_bool { \bool_gset_false:N \g_@@_listings_CM_bool \xeCJK_cs_clear:N \lst@FillOutputBox \cs_set_eq:NN \CJKglue \tex_hss:D } \fi: } \bool_new:N \g_@@_listings_CM_bool % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_peek_active_loop:TF} % \tn{lstinline} 通过判断参数中第一个字符是否是 \texttt{active} 类来区分 % 它是否被用在其他宏的参数之中。如果这第一个字符不在 \pkg{listings} 预定义的 % 符号表中,判断就会出问题。我们在这里通过一个循环跳过这些字符。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_peek_active_loop:TF #1#2#3 { \token_if_active:NTF #3 { #1#3 } { \token_if_cs:NTF #3 { #2#3 } { \int_compare:nNnTF { `#3 } > { \l_@@_listings_max_char_int } { \@@_listings_peek_active_loop:TF { #1#3 } { #2#3 } } { #2#3 } } } } \cs_set_eq:NN \lst@IfNextCharActive \@@_listings_peek_active_loop:TF % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_rescan:Nn, % \@@_listings_inside_convert:nw,\@@_listings_inline_group:w} % 当 \tn{lstinline} 被使用在参数中时,\pkg{listings} 会使用一个循环逐个将 % \tn{lstinline} 参数中的字符设置为活动字符。我们可以通过 \cs{tl_set_rescan:Nnn} % 来完成这里的 \tn{catcode} 转换,避免将 \tn{charcode} 超过 $255$ 的字符都设置为 % 活动字符。 % % 当 \tn{lstinline} 出现在宏参数中时,\TeX{} 参数传递将 catcode~6 的 parameter % token(通常是 |#|)双写为成对的相邻 token。\cs{tl_set_rescan:Nnn} 底层的 % \tn{scantokens} 在字符串化阶段会再次将 catcode~6 token 双写,导致输出中数量 % 翻倍。修复方法是在 rescan 前逐 token 扫描 \cs{l_@@_tmp_tl},将每个 catcode~6 % 的 token 替换为 \emph{保留原字符码} 的 active token(catcode 13),这是 % \pkg{listings} 期望的字符类型。最初的实现用 \cs{regex_replace_all:nnN} 把所有 % catcode~6 token 硬替换成 \texttt{U+0023},对一般 |#| 没问题,但当用户用 % \tn{catcode} 把别的字符(如 |&|)改为 parameter 时,原字符码会被丢失,导致 % 输出错字(\#879)。 % \begin{macrocode} \cs_new_protected:Npn \@@_listings_rescan:Nn #1#2 { \@@_listings_set_escape: \tl_set:Nn \l_@@_tmp_tl {#2} \@@_listings_escape_backslash: \tl_clear:N \l_@@_tmpb_tl \tl_map_inline:Nn \l_@@_tmp_tl { \token_if_parameter:NTF ##1 { \tl_put_right:Ne \l_@@_tmpb_tl { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \char_generate:nn { \int_value:w `##1 } { 13 } } } { \tl_put_right:Nn \l_@@_tmpb_tl { ##1 } } } \tl_set_eq:NN \l_@@_tmp_tl \l_@@_tmpb_tl \tl_set_rescan:Nno #1 { \char_set_catcode_active:n { 35 } } { \l_@@_tmp_tl } } \cs_new_protected:Npn \@@_listings_inside_convert:nw #1 ~ \@empty { \@@_listings_rescan:Nn \l_@@_tmp_tl {#1} \tl_put_right:No \lst@arg { \l_@@_tmp_tl } } \cs_set_eq:NN \lst@InsideConvert@ \@@_listings_inside_convert:nw \cs_new_protected:Npn \@@_listings_inline_group:w { \exp_after:wN \@@_listings_inline_group:n \exp_after:wN { \if_int_compare:w `} = \c_zero_int \fi: } \cs_set_eq:NN \lst@InlineGJ \@@_listings_inline_group:w \cs_new_protected:Npn \@@_listings_inline_group:n #1 { \@@_listings_rescan:Nn \lst@arg {#1} \lst@InlineGJEnd } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\@@_listings_boundary_stream_end:} % \changes{v3.10.4}{2026/07/20}{将 \tn{lstinline} 的分隔符、活动字符和 % 花括号扫描路径接入 auto stream capture,使颜色切换前后的四种源码空格 % 组合都与直接输入一致(\#992)。} % \tn{lstinline} 自己只声明可选参数,正文由多个内部扫描器读取,普通 cmd % after hook 会过早结束。分隔符路径经过已经打开外层组、即将读取分隔符的 % \tn{lstinline@},花括号路径则绕过它而直接进入 \tn{lst@InlineG};两个入口 % 都启动 capture 并设置局部标志。\tn{lst@DeInit} 返回到该外层组后用 % \tn{aftergroup} 安排单 token 结束函数。分隔符、活动字符、花括号与 % short-inline 路径都调用 \tn{lst@DeInit},因此共用同一结束适配点。 % \begin{macrocode} \clist_map_inline:nn { lstinline , lstinline@ , lst@InlineG , lst@DeInit } { \@@_boundary_reserve:n {#1} } \bool_new:N \l_@@_listings_boundary_bool \cs_new_protected:Npn \@@_listings_boundary_stream_end: { \@@_boundary_inline_stream_end:n { auto } } \cs_new_eq:NN \@@_orig_listings_inline:n \lstinline@ \cs_set_protected:Npn \lstinline@ #1 { \@@_boundary_inline_stream_begin:n { auto } \bool_set_true:N \l_@@_listings_boundary_bool \@@_orig_listings_inline:n #1 } \cs_new_eq:NN \@@_orig_listings_inline_group: \lst@InlineG \cs_set_protected:Npn \lst@InlineG { \@@_boundary_inline_stream_begin:n { auto } \bool_set_true:N \l_@@_listings_boundary_bool \@@_orig_listings_inline_group: } \tl_gput_right:Nn \lst@DeInit { \bool_if:NT \l_@@_listings_boundary_bool { \aftergroup \@@_listings_boundary_stream_end: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_listings_set_escape:} % 由于我们在上面的修改,需要保留 |\| 用于转义 \tn{lstinline} 参数中的某些 \TeX % 特殊字符,与原来宏包一致。 % \begin{macrocode} \group_begin: \cs_set:Npn \@@_tmp:w #1 { \group_end: \cs_new_protected:Npn \@@_listings_set_escape: { \xeCJK_swap_cs:NN #1 \@@_listings_escape:N } \cs_new_protected:Npn \@@_listings_escape:N ##1 { \cs_if_eq:NNTF #1 ##1 { \@@_listings_escape:N } {##1} } } \use:n { \char_set_catcode_active:N \\ \@@_tmp:w } { \ } % \end{macrocode} % \end{macro} % % % \changes{v3.4.8}{2017/05/15}{转义 \tn{lstinline} 参数中的 $\texttt{\textbackslash}_{12}$。} % \begin{macro}{\@@_listings_escape_backslash:} % \tn{catcode} 为 $12$ 的 |\| 需要双写转义。 % \begin{macrocode} \cs_new_protected:Npe \@@_listings_escape_backslash: { \tl_replace_all:Nnn \exp_not:N \l_@@_tmp_tl { \c_backslash_str } { \c_backslash_str \c_backslash_str } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<@@=xunadd> % \end{macrocode} % % \subsection{\pkg{xunicode-addon}} % % \changes{v3.2.5}{2013/07/18} % {增加小宏包 \pkg{xunicode-addon},为 \pkg{xunicode} 提供判断字符是否存在的功能。} % \changes{v3.2.9}{2013/12/08} % {文档部分增加 \pkg{xunicode} 定义的符号表。} % % \begin{macrocode} %<*xunicode> % \end{macrocode} % % \pkg{xunicode} 对编码相关的符号命令的定义中用的是诸如 |\char"0022\relax| 的形式。 % 例如 \tn{textbar} 被展开为 |\char"007C\relax|。并且诸如下述的定义是无效的: % \begin{verbatim} % \DeclareUTFcomposite[\UTFencname]{x1EBF}{\'}{\^e} % \end{verbatim} % 我们在这里做的修改是把符号命令定义为实际的字符并且使上述定义生效。另外在使用 % 这些符号命令的时候,先判断当前字体中是否存在对应的字符,如果不存在,则使用这 % 些符号命令的默认设置。 % % \begin{macrocode} \bool_lazy_or:nnF { \sys_if_engine_xetex_p: } { \sys_if_engine_luatex_p: } { \msg_new:nnnn { xunicode-addon } { xetex-luatex } { This~package~requires~either~XeTeX~or~LuaTeX~to~function.} { You~must~change~your~typesetting~engine~to,~e.g.,\\ "xelatex"~or~"lualatex"~instead~of~plain~"latex"~or~"pdflatex". } \msg_critical:nn { xunicode-addon } { xetex-luatex } } \RequirePackage { xparse } % \end{macrocode} % % 宏包选项是编码的名字。 % % \begin{macrocode} \clist_new:N \g_@@_encname_clist \tl_if_exist:NT \UTFencname { \clist_gput_right:Ne \g_@@_encname_clist { \UTFencname } } \DeclareOption* { \clist_gput_right:No \g_@@_encname_clist \CurrentOption } \ProcessOptions \scan_stop: % \end{macrocode} % % 若 \pkg{xunicode} 已经被调用,则在宏包结束的时候,重新设置 \tn{UTFencname} % 对应的编码命令。否则设置 \tn{UTFencname},如果使用的是 \hologo{LuaLaTeX},则 % 需要作一些设置,使得 \pkg{xunicode} 可用。 % % \changes{v3.3.3}{2016/02/01}{使用新的 Unicode 编码名称 \texttt{TU}。} % % \begin{macrocode} \@ifpackageloaded { xunicode } { } { \clist_get:NNF \g_@@_encname_clist \UTFencname { \cs_if_exist:NTF \UnicodeEncodingName { \tl_set:Ne \UTFencname { \UnicodeEncodingName } } { \sys_if_engine_xetex:TF { \tl_set:Nn \UTFencname { EU1 } } { \tl_set:Nn \UTFencname { EU2 } } } \clist_gset_eq:NN \g_@@_encname_clist \UTFencname } \sys_if_engine_xetex:TF { \RequirePackage { xunicode } } { \cs_set_eq:NN \@@_tmp:w \XeTeXpicfile \cs_set_eq:NN \XeTeXpicfile \prg_do_nothing: \RequirePackage { xunicode } \cs_set_eq:NN \XeTeXpicfile \@@_tmp:w } } \AtEndOfPackage { \@@_reload:N \g_@@_encname_clist } % \end{macrocode} % % \begin{macro}[int]{\ReloadXunicode} % 参数可以是多个编码,设置这些编码对应的命令。如果编码没有预先声明,则给出 % 一个错误警告。 % \begin{macrocode} \RenewDocumentCommand \ReloadXunicode { m } { \clist_set:Ne \l_@@_encname_clist {#1} \@@_reload:N \l_@@_encname_clist } \cs_new_protected:Npn \@@_reload:N #1 { \cs_set_eq:NN \@@_tmp:w \iftipaonetoken \cs_set_eq:NN \iftipaonetoken \scan_stop: \use:e { \ExplSyntaxOff \char_set_catcode_letter:n { 64 } \exp_not:N \clist_map_function:NN \exp_not:N #1 \@@_reload_aux:n \bool_if:NTF \l__kernel_expl_bool { \ExplSyntaxOn } { \ExplSyntaxOff } \char_set_catcode:nn { 64 } { \char_value_catcode:n { 64 } } } \cs_set_eq:NN \iftipaonetoken \@@_tmp:w } \cs_new_protected:Npn \@@_reload_aux:n #1 { \cs_if_exist:cTF { T@ #1 } { \tl_set:Nn \UTFencname {#1} \clist_if_in:NnF \g_@@_encname_clist {#1} { \clist_gput_right:Nn \g_@@_encname_clist {#1} } \file_input:n { xunicode.sty } \file_input:n { xunicode-extra.def } } { \msg_error:nnn { xunicode-addon } { encoding-unknown } {#1} } } \clist_new:N \l_@@_encname_clist \msg_new:nnnn { xunicode-addon } { encoding-unknown } { Encoding~scheme~"#1"~unknown. } { You~may~use \\\\ \token_to_str:N \usepackage [ #1 , \encodingdefault ] \{fontenc\} \\\\ before~xunicode-addon~or~xunicode. } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFmathsymbols} % \changes{v3.2.8}{2013/12/04}{修正 \tn{UseMathAsText} 的功能,恢复 \tn{hbar} % 和增加以 \texttt{text} 打头的文本符号命令。} % 将文本符号定义为 \tn{protected} 宏后,为了与 \pkg{hyperref} 的书签功能兼容 % 需要作一点额外处理。 % \begin{macrocode} \RenewDocumentCommand \DeclareUTFmathsymbols { m } { \bool_if:NT \l_@@_math_as_UTF_text_bool { \seq_map_inline:Nn \l_@@_math_as_UTF_text_seq { \@@_declare_math_as_UTF_text:n {##1} } \bool_set_false:N \l_@@_math_as_UTF_text_bool } } \seq_new:N \l_@@_math_as_UTF_text_seq \seq_set_from_clist:Nn \l_@@_math_as_UTF_text_seq { hbar , Finv , aleph , beth , gimel , daleth , Game } \bool_new:N \l_@@_math_as_UTF_text_bool \RenewDocumentCommand \UseMathAsText { } { \math@s@text@true \bool_set_true:N \l_@@_math_as_UTF_text_bool } \@onlypreamble \UseMathAsText \cs_new_protected:Npn \@@_declare_math_as_UTF_text:n #1 { \cs_if_exist:cTF {#1} { \cs_new_eq:cc { keepmathUTF #1 } {#1} \cs_gset_protected:cpe {#1} { \exp_not:N \mode_if_math:TF { \exp_not:c { keepmathUTF #1 } } { \exp_not:c { text #1 } } } \tl_put_right:Ne \l_@@_hyperref_hook_tl { \cs_set_eq:NN \exp_not:c {#1} \exp_not:c { text #1 } } } { \cs_new:cpe {#1} { \exp_not:c { text #1 } } } } \tl_new:N \l_@@_hyperref_hook_tl \AtBeginDocument { \cs_if_free:NF \pdfstringdefDisableCommands { \pdfstringdefDisableCommands { \l_@@_hyperref_hook_tl } } } % \end{macrocode} % \end{macro} % % \begin{macro}[pTF]{\@@_glyph_if_exist:n} % 判断字符在当前字体中是否存在。 % \begin{macrocode} \prg_new_conditional:Npnn \@@_glyph_if_exist:n #1 { p , T , F , TF } { \tex_iffontchar:D \tex_font:D \tex_numexpr:D #1 \scan_stop: \prg_return_true: \else: \prg_return_false: \fi: } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\UndeclareUTFcharacter} % 取消编码 |#1| 下的符号命令 |#3|。 % \begin{macrocode} \RenewDocumentCommand \UndeclareUTFcharacter { O { \UTFencname } m m } { \@@_if_csname:nTF {#3} { \UndeclareTextCommand {#3} } { \exp_args:Nc \UndeclareTextCommand { \tl_to_str:n {#3} } } {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\UndeclareUTFcomposite} % 取消编码 |#1| 下的复合符号命令 |#3{#4}|。 % \begin{macrocode} \RenewDocumentCommand \UndeclareUTFcomposite { O { \UTFencname } m m m } { \@@_if_csname:nTF {#3} { \@@_undeclare_composite:Nnnn #3 } { \exp_args:Nc \@@_undeclare_composite:Nnnn { \tl_to_str:n {#3} } } {#1} {#4} {#2} } \cs_new_protected:Npn \@@_undeclare_composite:Nnnn #1#2#3#4 { \cs_undefine:c { \@@_composite_cs:Nnn #1 {#2} {#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_composite_cs:Nnn,\@@_composite_cs:nnn} % \begin{macrocode} \cs_new:Npe \@@_composite_cs:Nnn #1#2#3 { \c_backslash_str #2 \exp_not:N \token_to_str:N #1 - \exp_not:N \tl_to_str:n {#3} } \cs_new:Npe \@@_composite_cs:nnn #1#2#3 { \c_backslash_str #2 #1 - \exp_not:N \tl_to_str:n {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_if_csname:nTF} % 判断 |#1| 是否可以作为控制序列的名字。这是因为 \pkg{xunicode} 使用了下面的定义。 % \begin{verbatim} % \DeclareUTFcharacter[\UTFencname]{x0149}{'n} % \end{verbatim} % \begin{macrocode} \prg_new_conditional:Npnn \@@_if_csname:n #1 { TF } { \tl_if_single_token:nTF {#1} { \token_if_cs:NTF #1 { \prg_return_true: } { \token_if_active:NTF #1 { \prg_return_true: } { \prg_return_false: } } } { \prg_return_false: } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFcharacter} % 定义编码 |#1| 下的符号命令 |#3|,其对应符号的 Unicode 是 |#2|。 % \begin{macrocode} \RenewDocumentCommand \DeclareUTFcharacter { O { \UTFencname } m m } { \cs_if_exist_use:cF { @@_restore_ \tl_to_str:n {#3} : } { \@@_if_csname:nTF {#3} { \@@_declare_character:Nnn #3 } { \@@_declare_character:cnn { \tl_to_str:n {#3} } } {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_restore_cmd:N} % 恢复 \tn{hbar} 和 \tn{nobreakspace} 为原本定义。 % \begin{macrocode} \cs_new_protected:cpn { @@_restore_ \tl_to_str:n { \hbar } : } { \@@_restore_cmd:N \hbar } \cs_new_protected:cpn { @@_restore_ \tl_to_str:n { \nobreakspace } : } { \@@_restore_cmd:N \nobreakspace } \cs_new_protected:Npn \@@_restore_cmd:N #1 { \@@_restore_cmd:Ne #1 { ? - \token_to_str:N #1 } } \cs_new_protected:Npn \@@_restore_cmd:Nn #1#2 { \cs_if_free:cF {#2} { \@@_restore_cmd:Nc #1 {#2} } } \cs_new_protected:Npn \@@_restore_cmd:NN #1#2 { \cs_gset_eq:NN #1 #2 \cs_undefine:N #2 } \cs_generate_variant:Nn \@@_restore_cmd:Nn { Ne } \cs_generate_variant:Nn \@@_restore_cmd:NN { Nc } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_declare_character:Nnn} % 通过 \cs{tex_Uchar:D} 直接由 Unicode |#3| 得到编码 |#2| 下的符号命令 % |#1| 对应的实际字符。\tn{DeclareUTFSymbol} 的参数格式与 \tn{DeclareTextSymbol} % 完全一致。 % \begin{macrocode} \cs_new_protected:Npn \@@_declare_character:Nnn #1#2#3 { \@@_provide_text_command_default:N #1 \exp_after:wN \@@_declare_character:NNen \tex_Uchar:D \@@_check_slot:n {#3} \exp_stop_f: #1 { \token_to_str:N #1 } {#2} } \cs_generate_variant:Nn \@@_declare_character:Nnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFSymbol,\DeclareUTFCommand} % \tn{DeclareUTFCommand} 只能用于定义不带参数的符号命令。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFSymbol { m O { \UTFencname } m } { \@@_declare_character:Nnn #1 {#2} {#3} } \NewDocumentCommand \DeclareUTFCommand { m O { \UTFencname } m } { \@@_text_command:Nonn #1 { \token_to_str:N #1 } {#2} {#3} } \cs_new_protected:Npn \@@_text_command:Nnnn #1#2#3#4 { \DeclareTextCommand #1 {#3} { \@@_text_command:nn {#2} {#4} } } \cs_generate_variant:Nn \@@_text_command:Nnnn { No } \cs_new_protected:Npn \@@_text_command:nn #1#2 { \@@_begin_hook:nn {#1} {#2} #2 \@@_end_hook:nn {#1} {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_provide_text_command_default:N} % 如果控制序列 |#1| 已经存在,但不是符号命令,\pkg{xunicode} 会将它定义为 % \tn{UTFencname} 编码下的符号命令。但是编码被转换之后,再使用这些控制序列, % \pkg{NFSS} 就会报错。为此需要给出这些符号命令的默认定义,与原来的意义相同。 % 这些命令包括 % \begin{verbatim} % \nobreakspace macro:->\protect \nobreakspace % \copyright macro:->\protect \copyright % \AA macro:->\r A % \aa macro:->\r a % \textrhookopeno \long macro:->\textrethookbelow {\textopeno } % \hbar macro:->{\mathchar '26\mkern -9muh} % \textaolig macro:->{a\kern -.25em o} % \end{verbatim} % 影响比较大的是 \tn{nobreakspace}、\tn{copyright} 和 \tn{hbar}。 % \begin{macrocode} \cs_new_protected:Npn \@@_provide_text_command_default:N #1 { \cs_if_exist:cF { ? \token_to_str:N #1 } { \cs_if_free:cF { ? - \token_to_str:N #1 } { \exp_args:NNv \ProvideTextCommandDefault #1 { ? - \token_to_str:N #1 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_declare_character:NNnn} % 使用编码 |#4| 下的符号命令 |#2| 的时候先判断它对应的实际字符 |#1| 在当前字体中 % 是否存在。如果不存在则转换到 \tn{DeclareTextSymbolDefault} 中设置的编码或者使用 % \cs{Declare\-Text\-Command\-Default} 中设置的命令。 % \begin{macrocode} \cs_new_protected:Npn \@@_declare_character:NNnn #1#2#3#4 { \DeclareTextCommand #2 {#4} { \@@_text_character:nN {#3} {#1} } } \cs_new_protected:Npn \@@_text_character:nN #1#2 { \@@_begin_hook:nn {#1} {#2} \@@_glyph_if_exist:nTF { `#2 } {#2} { \cs_if_exist_use:cF { ? #1 } {#2} } \@@_end_hook:nn {#1} {#2} } \cs_generate_variant:Nn \@@_declare_character:NNnn { NNe } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_check_slot:n} % \pkg{xunicode} 中使用的 Unicode 格式是诸如 |x0022| 的形式,这就需要一些转换。 % \begin{macrocode} \cs_new:Npn \@@_check_slot:n #1 { \int_eval:n { \tl_if_head_eq_charcode:nNTF {#1} x { " \use_none:n #1 } {#1} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFcomposite} % 设置编码 |#1| 下的符号命令 |#3| 与它的参数 |#4| 的复合对应的符号的 Unicode 是 |#2|。 % \begin{macrocode} \RenewDocumentCommand \DeclareUTFcomposite { O { \UTFencname } m m m } { \@@_if_csname:nTF {#3} { \@@_declare_composite:Nnnn #3 } { \@@_declare_composite:cnnn { \tl_to_str:n {#3} } } {#1} {#4} {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_declare_composite:Nnnn} % 这里使用 \cs{tex_afterassignment:D} 是因为 \pkg{xunicode} 有如下的定义。 % \begin{verbatim} % \DeclareUTFcomposite[\UTFencname]{x02E8\char"02E5}{\tonebar}{25} % \DeclareUTFcomposite[\UTFencname]{x02E5\char"02E8}{\tonebar}{52} % \end{verbatim} % 对复合符号命令的定义用的是 \tn{chardef},这有利于下面字符是否存在的判断。 % \begin{macrocode} \cs_new_protected:Npn \@@_declare_composite:Nnnn #1#2#3#4 { \tex_afterassignment:D \use_none_delimit_by_q_stop:w \@@_chardef:cn { \@@_composite_cs:Nnn #1 {#2} {#3} } { \@@_check_slot:n {#4} } \q_stop } \cs_new_protected:Npn \@@_chardef:Nn #1#2 { \tex_chardef:D #1 = \tex_numexpr:D #2 \scan_stop: } \cs_generate_variant:Nn \@@_chardef:Nn { c } \cs_generate_variant:Nn \@@_declare_composite:Nnnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFCompositeCommand} % 设置编码 |#2| 下的符号命令 |#1| 与它的参数 |#3| 的复合对应结果是 |#4|。不能直接用 % \cs{Declare\-Text\-Composite\-Command} 来定义,它与我们的机制冲突。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFCompositeCommand { m O { \UTFencname } m m } { \cs_set_protected:cpn { \@@_composite_cs:Nnn #1 {#2} {#3} } {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFCompositeSymbol} % 设置编码 |#2| 下的符号命令 |#1| 与它的参数 |#3| 的复合对应结果是 |#4|。不能直接用 % \cs{Declare\-Text\-Composite} 来定义,它与我们的机制冲突。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFCompositeSymbol { m O { \UTFencname } m m } { \@@_chardef:cn { \@@_composite_cs:Nnn #1 {#2} {#3} } { \@@_check_slot:n {#4} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFComposite} % 将 |#1| 设置为编码 |#2| 下的带一个参数的复合符号命令。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFComposite { m O { \UTFencname } } { \use:e { \@@_declare_composite:Nnn \exp_not:N #1 { \token_to_str:N #1 } {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFEncodedAccent} % |#1| 是重音命令,|#2| 是编码,|#3| 是组合重音符号的 Unicode,|#4| 是基本重音符号 % 的 Unicode。当 |#1| 的参数为空时,输出 |#4|,否则是 |#1| 的参数与 |#3| 的组合。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFEncodedAccent { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_accent:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFEncodedAccents} % |#1| 是重音命令,|#2| 是编码,|#3| 和 |#4| 都是组合重音符号的 Unicode。 % 输出 |#1| 与 |#3|、|#4| 的组合。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFEncodedAccents { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_accents:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFEncodedSymbol} % |#1| 是带参数的符号命令,|#2| 是编码,|#3| 是组合符号的 Unicode,|#4| 是基本符号的 % Unicode。当 |#1| 的参数为空时,输出 |#4|,否则是 |#1| 的参数与 |#3| 的组合。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFEncodedSymbol { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_symbol:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFEncodedCircle} % |#1| 是带参数的圆圈符号命令,|#2| 是编码,|#3| 是组合圆圈符号的 Unicode,|#4| % 是圆圈符号的 Unicode。当 |#1| 的参数为空时,输出 |#4|,否则是 |#1| 的参数与 |#4| 的组合。 % \begin{macrocode} \NewDocumentCommand \DeclareUTFEncodedCircle { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_circle:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareEncodedCompositeCharacter} % \begin{macrocode} \RenewDocumentCommand \DeclareEncodedCompositeCharacter { m m m m } { \DeclareUTFEncodedSymbol #2 [#1] { "#3 } { "0#4 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareEncodedCompositeAccents} % \changes{v3.2.9}{2013/12/07}{修正 \pkg{xunicode} 中的错误定义。} % \begin{macrocode} \RenewDocumentCommand \DeclareEncodedCompositeAccents { m m m m } { \DeclareUTFEncodedAccents #2 [#1] { "#4 } { "#3 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFDoubleEncodedAccent} % \changes{v3.2.10}{2014/02/20}{改进 \tn{t} 等的定义方式。} % \begin{macrocode} \NewDocumentCommand \DeclareUTFDoubleEncodedAccent { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_double_accent:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFDoubleEncodedSymbol} % \changes{v3.2.10}{2014/02/21}{改进 \tn{sliding} 等的定义方式。} % \begin{macrocode} \NewDocumentCommand \DeclareUTFDoubleEncodedSymbol { m O { \UTFencname } m m } { \@@_declare_encoded:NNnnn \@@_combine_double_symbol:nnNNn #1 {#2} {#3} {#4} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_declare_composite:Nnn} % 通过 \texttt{lowercase} 技巧,直接由重音符号的 Unicode 得到实际字符。 % \begin{macrocode} \cs_new_protected:Npn \@@_declare_composite:Nnn #1#2#3 { \DeclareTextCommand #1 {#3} { \@@_text_composite:nnn {#2} {#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_text_composite:nnn} % \begin{macrocode} \cs_new_protected:Npn \@@_text_composite:nnn #1#2#3 { \@@_begin_hook:nn {#1} {#3} \cs_if_exist:cTF { \@@_composite_cs:nnn {#1} {#2} {#3} } { \@@_text_composite:cnn { \@@_composite_cs:nnn {#1} {#2} {#3} } {#1} {#3} } { \cs_if_exist_use:cTF { ? #1 } { {#3} } {#3} } \@@_end_hook:nn {#1} {#3} } \cs_new_protected:Npn \@@_text_composite:Nnn #1#2#3 { \token_if_chardef:NTF #1 { \@@_glyph_if_exist:nTF {#1} {#1} { \cs_if_exist_use:cTF { ? #2 } { {#3} } {#3} } } {#1} } \cs_generate_variant:Nn \@@_text_composite:Nnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_declare_encoded:NNnnn} % 通过 \cs{tex_Uchar:D} 直接由重音符号的 Unicode 得到实际字符。 % \begin{macrocode} \cs_new_protected:Npn \@@_declare_encoded:NNnnn #1#2#3#4#5 { \exp_after:wN \@@_declare_encoded:NNNNee \tex_Uchar:D \@@_check_slot:n {#4} \exp_after:wN \exp_stop_f: \tex_Uchar:D \@@_check_slot:n {#5} \exp_stop_f: #1 #2 { \token_to_str:N #2 } {#3} } \cs_new_protected:Npn \@@_declare_encoded:NNNNnn #1#2#3#4#5#6 { \DeclareTextCommand #4 {#6} { #3 {#5} {#6} {#1} {#2} } } \cs_generate_variant:Nn \@@_declare_encoded:NNnnn { c } \cs_generate_variant:Nn \@@_declare_encoded:NNNNnn { NNNNee } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_text_combine:NnnNNn} % 若重音命令 |#2| 与它的参数 |#6| 的复合已经由 \tn{DeclareUTFcomposite} 设置, % 并且在当前字体中存在该字符,则直接使用。否则使用组合命令。 % \begin{macrocode} \cs_new_protected:Npn \@@_text_combine:NnnNNn #1#2#3#4#5#6 { \@@_begin_hook:nn {#2} {#6} \cs_if_exist:cTF { \@@_composite_cs:nnn {#2} {#3} {#6} } { \@@_text_combine:cNnNNn { \@@_composite_cs:nnn {#2} {#3} {#6} } #1 {#2} {#4} {#5} {#6} } { #1 {#6} {#2} {#4} {#5} } \@@_end_hook:nn {#2} {#6} } \cs_new_protected:Npn \@@_text_combine:NNnNNn #1#2#3#4#5#6 { \token_if_chardef:NTF #1 { \@@_glyph_if_exist:nTF {#1} {#1} { #2 {#6} {#3} {#4} {#5} } } {#1} } \cs_generate_variant:Nn \@@_text_combine:NNnNNn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_symbol:nnNNn} % \begin{macrocode} \cs_new_protected:Npn \@@_combine_symbol:nnNNn { \@@_text_combine:NnnNNn \@@_add_symbol:nnNN } \cs_new_protected:Npn \@@_add_symbol:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_glyph_if_exist:nTF { `#4 } {#4} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} } } { \@@_glyph_if_exist:nTF { `#3 } { #1#3 } { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_accent:nnNNn,\@@_add_accent:nnNN} % 若组合重音符号的 |#3| 和基本重音符号 |#4| 在当前字体中都不存在,则转换到 % \cs{Declare\-Text\-Accent\-Default} 设置的编码或者使用 % \tn{DeclareTextCommandDefault} 中设置的命令。\texttt{0.9999} 版以前的 \XeTeX % 需要设置 \tn{XeTeXinputnormalization} 为 $1$,才能使用字体中由基础字符和组合符号 % 对应的实际字符;而 \texttt{0.9999} 版以后的 \XeTeX 默认就启用这个功能,^^A % \tn{XeTeXinputnormalization} 似乎是无效的,怀疑是使用 HarfBuzz 库替代 ICU 进行 % 字体排版的缘故\footnote{\url{http://tug.org/pipermail/xetex/2013-July/024579.html}}。 % \begin{macrocode} \cs_new_protected:Npn \@@_combine_accent:nnNNn { \@@_text_combine:NnnNNn \@@_add_accent:nnNN } \cs_new_protected:Npn \@@_add_accent:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_glyph_if_exist:nTF { `#4 } {#4} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} } } { \@@_glyph_if_exist:nTF { `#3 } { #1#3 } { \@@_glyph_if_exist:nTF { `#4 } { \add@accent { `#4 } {#1} } { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_accents:nnNNn,\@@_add_accents:nnNN} % \begin{macrocode} \cs_new_protected:Npn \@@_combine_accents:nnNNn { \@@_text_combine:NnnNNn \@@_add_accents:nnNN } \cs_new_protected:Npn \@@_add_accents:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#1} } { \@@_glyph_if_exist:nTF { `#3 } { \@@_glyph_if_exist:nTF { `#4 } } { \use_ii:nn } { #1#3#4 } { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3#4 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_circle:nnNNn,\@@_add_circle:nnNN,\@@_add_circle:nN} % 对圆圈中的数字或者字母适当缩小,以适合圆圈的大小。只有字体中存在 % \texttt{U+25EF} 时,才使用这里的设置,否则还是 \LaTeX\ 中的设置。 % \begin{macrocode} \cs_new_protected:Npn \@@_combine_circle:nnNNn { \@@_text_combine:NnnNNn \@@_add_circle:nnNN } \cs_new_protected:Npn \@@_add_circle:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_glyph_if_exist:nTF { `#4 } {#4} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} } } { \@@_glyph_if_exist:nTF { `#4 } { \@@_add_circle:nN {#1} #4 } { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#1} } } } \cs_new_protected:Npn \@@_add_circle:nN #1#2 { \hcoffin_set:Nn \l_@@_tmp_coffin {#1} \hcoffin_set:Nn \l_@@_circle_coffin {#2} \fp_set:Nn \l_@@_circle_scale_fp { \dim_to_decimal_in_unit:nn { \fp_use:N \l_@@_circle_ratio_fp \coffin_wd:N \l_@@_circle_coffin } { \coffin_wd:N \l_@@_tmp_coffin } } \coffin_scale:Nnn \l_@@_tmp_coffin { \l_@@_circle_scale_fp } { \l_@@_circle_scale_fp } \coffin_attach:NnnNnnnn \l_@@_circle_coffin { hc } { vc } \l_@@_tmp_coffin { hc } { vc } { \c_zero_dim } { \c_zero_dim } \coffin_typeset:Nnnnn \l_@@_circle_coffin { H } { l } { \c_zero_dim } { \c_zero_dim } } \fp_new:N \l_@@_circle_scale_fp \coffin_new:N \l_@@_tmp_coffin \coffin_new:N \l_@@_circle_coffin % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\settextcircledratio} % 设置圆圈中文字的宽度与圆圈宽度的比例,预设为 \texttt{0.7}。 % \begin{macrocode} \NewDocumentCommand \settextcircledratio { m } { \fp_set:Nn \l_@@_circle_ratio_fp {#1} } \fp_new:N \l_@@_circle_ratio_fp \settextcircledratio { 0.7 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_double_accent:nnNNn} % 使 \tn{t} 等组合重音符号放在参数的第一个字母的右边。 % \begin{macrocode} \cs_new_protected:Npn \@@_combine_double_accent:nnNNn { \@@_text_combine:NnnNNn \@@_add_double_accent:nnNN } \cs_new_protected:Npn \@@_add_double_accent:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_glyph_if_exist:nTF { `#4 } {#4} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} } } { \@@_glyph_if_exist:nTF { `#3 } { \@@_add_double_symbol:nN {#1} #3 } { \@@_glyph_if_exist:nTF { `#4 } { \add@accent { `#4 } {#1} } { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_combine_double_symbol:nnNNn} % 使 \tn{sliding} 等组合重音符号放在参数的第一个字母的右边。 % \begin{macrocode} \cs_new_protected:Npn \@@_combine_double_symbol:nnNNn { \@@_text_combine:NnnNNn \@@_add_double_symbol:nnNN } \cs_new_protected:Npn \@@_add_double_symbol:nnNN #1#2#3#4 { \tl_if_blank:nTF {#1} { \@@_glyph_if_exist:nTF { `#4 } {#4} { \cs_if_exist_use:cTF { ? #2 } { {#1} } {#4} } } { \@@_glyph_if_exist:nTF { `#3 } { \@@_add_double_symbol:nN {#1} #3 } { \cs_if_exist_use:cTF { ? #2 } { {#1} } { #1#3 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_add_double_symbol:nN} % 如果参数的第一个记号是字母类、其他符号类或者由 \tn{chardef} 定义,则将组合符号 % 放在它的右边,否则不作处理。 % \begin{macrocode} \cs_new_protected:Npn \@@_add_double_symbol:nN #1#2 { \tl_if_head_is_N_type:nTF {#1} { \exp_after:wN \exp_after:wN \exp_after:wN \@@_add_double_symbol_aux:NnN \exp_after:wN \exp_after:wN \tl_head:w #1 \q_stop \exp_after:wN { \use_none:n #1 } #2 } { #1#2 } } \cs_new_protected:Npn \@@_add_double_symbol_aux:NnN #1#2#3 { \bool_lazy_any:nTF { { \token_if_letter_p:N #1 } { \token_if_other_p:N #1 } { \token_if_chardef_p:N #1 } } { #1#3#2 } { #1#2#3 } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\AtBeginUTFCommand,\AtEndUTFCommand} % 设置在符号命令前后使用的钩子,可选参数用于指定单个符号命名。可以用 |#1| % 引用带参数的组合符号命令的参数或者符号命令对应的符号。 % \changes{v3.2.6}{2013/08/15}{可以指定特定符号命令使用的钩子。} % \changes{v3.7.1}{2018/04/30}{修复代码重构而引入的新错误。} % \begin{macrocode} \NewDocumentCommand \AtBeginUTFCommand { s O { } +m } { \tl_if_blank:nTF {#2} { \IfBooleanTF {#1} { \xunadd_set_begin_hook:n } { \xunadd_append_begin_hook:n } } { \xunadd_set_begin_hook:nn {#2} } {#3} } \NewDocumentCommand \AtEndUTFCommand { s O { } +m } { \tl_if_blank:nTF {#2} { \IfBooleanTF {#1} { \xunadd_set_end_hook:n } { \xunadd_append_end_hook:n } } { \xunadd_set_end_hook:nn {#2} } {#3} } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xunadd_set_begin_hook:n,\xunadd_set_end_hook:n} % \begin{macrocode} \cs_new_protected:Npn \xunadd_set_begin_hook:n { \tl_set:Nn \l_@@_begin_hook_tl } \cs_new_protected:Npn \xunadd_append_begin_hook:n { \tl_put_right:Nn \l_@@_begin_hook_tl } \cs_new_protected:Npn \xunadd_set_end_hook:n { \tl_set:Nn \l_@@_end_hook_tl } \cs_new_protected:Npn \xunadd_append_end_hook:n { \tl_put_right:Nn \l_@@_end_hook_tl } \cs_new_protected:Npn \xunadd_set_begin_hook:nn { \@@_set_cmd_hook:nnn { begin } } \cs_new_protected:Npn \xunadd_set_end_hook:nn { \@@_set_cmd_hook:nnn { end } } \cs_new_protected:Npn \@@_set_cmd_hook:nnn #1#2#3 { \cs_set_protected:cpn { \tl_if_single:nTF {#2} { \use:c { @@_#1_csname:n } { \token_to_str:N #2 } } { \@@_set_cmd_hook_aux:Nnwn #2 \q_stop {#1} } } ##1 {#3} } \cs_new:Npn \@@_set_cmd_hook_aux:Nnwn #1#2 \q_stop #3 { \use:c { @@_#3_csname:n } { \token_to_str:N #1 - \tl_to_str:n {#2} } } \cs_new:Npn \@@_begin_csname:n #1 { @@_begin_#1_hook:n } \cs_new:Npn \@@_end_csname:n #1 { @@_end_#1_hook:n } \tl_new:N \l_@@_begin_hook_tl \tl_new:N \l_@@_end_hook_tl % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_begin_hook:nn,\@@_end_hook:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_begin_hook:nn #1#2 { \tl_use:N \l_@@_begin_hook_tl \cs_if_exist_use:cF { \@@_begin_csname:n { #1 - \tl_to_str:n {#2} } } { \cs_if_exist_use:cF { \@@_begin_csname:n {#1} } { \use_none:n } } {#2} } \cs_new_protected:Npn \@@_end_hook:nn #1#2 { \cs_if_exist_use:cF { \@@_end_csname:n { #1 - \tl_to_str:n {#2} } } { \cs_if_exist_use:cF { \@@_end_csname:n {#1} } { \use_none:n } } {#2} \tl_use:N \l_@@_end_hook_tl } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\DeclareUTFTIPACommand} % \changes{v3.2.10}{2014/02/20}{检查 \tn{t} 和 \tn{sliding} 的参数是否以 \tn{textipa} 开头。} % \begin{macrocode} \NewDocumentCommand \DeclareUTFTIPACommand { O { \UTFencname } m } { \use:e { \@@_text_tipa_command:Nnn \exp_not:N #2 { \token_to_str:N #2 } {#1} } } \cs_new_protected:Npn \@@_text_tipa_command:Nnn #1#2#3 { \cs_set_eq:cc { UTF/#3#2 } { #3#2 } \DeclareTextCommand #1 {#3} { \@@_text_tipa_command:nnn {#3} {#2} } } \cs_new_protected:Npn \@@_text_tipa_command:nnn #1#2#3 { \exp_args:Ncc \@@_check_for_tipa:NNn { \use_none:n #2 } { UTF/#1#2 } {#3} } \cs_new_protected:Npn \@@_check_for_tipa:NNn #1#2#3 { \tl_if_head_eq_meaning:nNTF {#3} \textipa { \exp_after:wN \tipacatchonechar \exp_after:wN { \exp_after:wN #1 \use_none:n #3 } } { #2 {#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}[int]{\xunadd_get_slot:nn} % |#1| 是编码,|#2| 是诸如 \tn{textendash} 或 |\v C| 等形式的文本命令,取得他们对应的字符编码。 % \begin{macrocode} \cs_new_protected:Npn \xunadd_get_slot:nn #1#2 { \@@_get_slot:wn #2 \q_nil \q_stop {#1} } \cs_new_protected:Npn \@@_get_slot:wn #1#2#3 \q_stop #4 { \int_set:Nn \l_xunadd_slot_int { -1 } \bool_set_false:N \l_xunadd_rest_bool \group_begin: \exp_args:Nccc \group_end: { @@_get_slot:NNnn } { #4 \token_to_str:N #1 } { \@@_composite_cs:Nnn #1 {#4} {#2} } {#2} {#3} } \int_new:N \l_xunadd_slot_int \bool_new:N \l_xunadd_rest_bool \cs_new_protected:Npn \@@_get_slot:NNnn #1#2#3#4 { \cs_if_free:NF #1 { \cs_if_exist:NTF #2 { \@@_get_composite_slot:Nn #2 {#4} } { \@@_get_character_slot:Nn #1 { #3 #4 } } } } \cs_new_protected:Npn \@@_get_composite_slot:Nn #1#2 { \token_if_chardef:NT #1 { \int_set:Nn \l_xunadd_slot_int {#1} \quark_if_nil:nF {#2} { \bool_set_true:N \l_xunadd_rest_bool } } } \cs_new_protected:Npn \@@_get_character_slot:Nn #1 { \exp_after:wN \@@_get_character_slot_aux:wn #1 \@@_text_character:nN \q_nil \q_nil \q_stop } \cs_new_protected:Npn \@@_get_character_slot_aux:wn #1 \@@_text_character:nN #2#3#4 \q_stop #5 { \quark_if_nil:nF {#2} { \int_set:Nn \l_xunadd_slot_int { `#3 } \quark_if_nil:nF {#5} { \bool_set_true:N \l_xunadd_rest_bool } } } % \end{macrocode} % \end{macro} % % \changes{v3.7.2}{2018/05/12}{解决与 \pkg{microtype} 宏包的兼容问题。} % % \begin{macro}[int]{\xunadd@microtype@is@charx} % \pkg{microtype} 宏包中使用的函数,我们通过对 \tn{MT@is@charx} 打补丁来实现功能。 % \begin{macrocode} \cs_new_protected_nopar:Npn \xunadd@microtype@is@charx #1 \relax { \use:e { \xunadd_get_slot:nn { \MT@encoding } { \tex_the:D \MT@toks } } \int_compare:nNnTF \l_xunadd_slot_int < \c_zero_int { \xunadd@original@is@charx #1 \relax } { \cs_set_nopar:Npe \MT@char@ { \int_use:N \l_xunadd_slot_int } \bool_if:NT \l_xunadd_rest_bool { \MT@norestfalse } } } \cs_new_protected_nopar:Npn \xunadd@microtype@hook { \cs_if_free:NF \MT@is@charx { \cs_new_eq:NN \xunadd@original@is@charx \MT@is@charx \cs_set_eq:NN \MT@is@charx \xunadd@microtype@is@charx \cs_set_eq:NN \MT@warn@unknown@once \use_none:n } } \@ifpackageloaded { microtype } { \use:n } { \AtBeginDocument } { \xunadd@microtype@hook } % \end{macrocode} % \end{macro} % % \changes{v3.2.8}{2013/12/05}{启用 \pkg{xunicode} 中的带圈数字和字母设置。} % % \begin{macrocode} % %<*xunextra> % \end{macrocode} % % \changes{v3.7.0}{2018/03/16}{补充定义 \tn{texthyphenationpoint} 和 \tn{texttwoemdash}。} % % 我们补充定义 HYPHENATION POINT 和 TWO-EM DASH,他们默认被归入 CJK 标点符号。 % \begin{macrocode} \DeclareUTFSymbol\texthyphenationpoint{"2027} \DeclareUTFSymbol\texttwoemdash{"2E3A} % \end{macrocode} % % 以下内容选自 \pkg{xunicode},并做了适当修改。 % \changes{v3.10.0}{2023/11/22}{修复 \tn{textsbleftarrow} 的定义。} % \begin{macrocode} \DeclareUTFComposite\textsuperscript \DeclareUTFComposite\textsubscript \DeclareUTFEncodedAccent\textsbleftarrow{"20EE}{"02FF} \DeclareUTFEncodedAccent\`{"0300}{"02CB} \DeclareUTFEncodedAccent\capitalgrave{"0300}{"02CB} \DeclareUTFEncodedAccent\'{"0301}{"02CA} \DeclareUTFEncodedAccent\capitalacute{"0301}{"02CA} \DeclareUTFEncodedAccent\^{"0302}{"02C6} \DeclareUTFEncodedAccent\capitalcircumflex{"0302}{"02C6} \DeclareUTFEncodedAccent\~{"0303}{"02DC} \DeclareUTFEncodedAccent\capitaltilde{"0303}{"02DC} \DeclareUTFEncodedAccent\={"0304}{"02C9} \DeclareUTFEncodedAccent\capitalmacron{"0304}{"02C9} \DeclareUTFEncodedAccent\textoverline{"0305}{"203E} \DeclareUTFEncodedAccent\u{"0306}{"02D8} \DeclareUTFEncodedAccent\capitalbreve{"0306}{"02D8} \DeclareUTFEncodedAccent\.{"0307}{"02D9} \DeclareUTFEncodedAccent\capitaldotaccent{"0307}{"02D9} \DeclareUTFEncodedAccent\"{"0308}{"00A8} \DeclareUTFEncodedAccent\capitaldieresis{"0308}{"00A8} \DeclareUTFEncodedAccent\m{"0309}{"0309} \DeclareUTFEncodedAccent\texthookabove{"0309}{"0309} \DeclareUTFEncodedAccent\r{"030A}{"02DA} \DeclareUTFEncodedAccent\capitalring{"030A}{"02DA} \DeclareUTFEncodedAccent\H{"030B}{"02DD} \DeclareUTFEncodedAccent\capitalhungarumlaut{"030B}{"02DD} \DeclareUTFEncodedAccent\v{"030C}{"02C7} \DeclareUTFEncodedAccent\capitalcaron{"030C}{"02C7} \DeclareUTFEncodedAccent\textvbaraccent{"030D}{"02C8} \DeclareUTFEncodedAccent\textdoublevbaraccent{"030E}{"0022} \DeclareUTFEncodedAccent\U{"030E}{"0022} \DeclareUTFEncodedAccent\textdoublegrave{"030F}{"02F5} \DeclareUTFEncodedAccent\G{"030F}{"02F5} \DeclareUTFEncodedAccent\textdotbreve{"0310}{"0310} \DeclareUTFEncodedAccent\textroundcap{"0311}{"0311} \DeclareUTFEncodedAccent\newtie{"0311}{"0311} \DeclareUTFEncodedAccent\capitalnewtie{"0311}{"0311} \DeclareUTFEncodedAccent\textturncommaabove{"0312}{"02BB} \DeclareUTFEncodedAccent\textcommaabove{"0313}{"02BC} \DeclareUTFEncodedAccent\textrevcommaabove{"0314}{"02BD} \DeclareUTFEncodedAccent\overbridge{"0346}{"0346} \DeclareUTFEncodedAccent\crtilde{"034A}{"034A} \DeclareUTFEncodedAccent\dottedtilde{"034B}{"034B} \DeclareUTFEncodedAccent\doubletilde{"034C}{"034C} \DeclareUTFEncodedAccent\textrightarrowhead{"0350}{"02C3} \DeclareUTFEncodedAccent\textlefthalfring{"0351}{"02D3} \DeclareUTFEncodedAccent\textrighthalfring{"0357}{"02D2} \DeclareUTFDoubleEncodedSymbol\textdoublebrevebelow{"035C}{"035C} \DeclareUTFDoubleEncodedAccent\textdoublebreve{"035D}{"035D} \DeclareUTFDoubleEncodedAccent\textdoublemacron{"035E}{"035E} \DeclareUTFDoubleEncodedSymbol\textdoublemacronbelow{"035F}{"035F} \DeclareUTFDoubleEncodedAccent\textdoubletilde{"0360}{"0360} \DeclareUTFDoubleEncodedAccent\t{"0361}{"0361} \DeclareUTFDoubleEncodedAccent\capitaltie{"0361}{"0361} \DeclareUTFDoubleEncodedAccent\texttoptiebar{"0361}{"0361} \DeclareUTFDoubleEncodedSymbol\sliding{"0362}{"0362} \DeclareUTFTIPACommand\t \DeclareUTFTIPACommand\capitaltie \DeclareUTFTIPACommand\texttoptiebar \DeclareUTFTIPACommand\sliding \DeclareUTFEncodedAccent\texthighrise{"1DC4}{"1DC4} \DeclareUTFEncodedAccent\textlowrise{"1DC5}{"1DC5} \DeclareUTFEncodedAccent\textrisefall{"1DC8}{"1DC8} \DeclareUTFEncodedAccent\textfallrise{"1DC9}{"1DC9} \DeclareUTFEncodedAccent\textaolig{"1DD5}{"1DD5} \DeclareUTFCompositeSymbol\textundertie{H}{"1E2A} \DeclareUTFCompositeSymbol\textundertie{h}{"1E2B} \DeclareUTFEncodedAccents\textcircumgrave{"0302}{"0301} \DeclareUTFSymbol\textFinv{"2132} \DeclareUTFSymbol\textaleph{"2135} \DeclareUTFSymbol\textbeth{"2136} \DeclareUTFSymbol\textgimel{"2137} \DeclareUTFSymbol\textdaleth{"2138} \DeclareUTFSymbol\textGame{"2141} \DeclareUTFCompositeCommand\tonebar{25}{\tonebar{2}\tonebar{5}} \DeclareUTFCompositeCommand\tonebar{52}{\tonebar{5}\tonebar{2}} \DeclareUTFSymbol\textbigcircle{"25EF} \DeclareUTFEncodedCircle\textcircled{"20DD}{"25EF} \DeclareUTFCompositeSymbol\textcircled{0}{"24EA} \DeclareUTFCompositeSymbol\textcircled{1}{"2460} \DeclareUTFCompositeSymbol\textcircled{2}{"2461} \DeclareUTFCompositeSymbol\textcircled{3}{"2462} \DeclareUTFCompositeSymbol\textcircled{4}{"2463} \DeclareUTFCompositeSymbol\textcircled{5}{"2464} \DeclareUTFCompositeSymbol\textcircled{6}{"2465} \DeclareUTFCompositeSymbol\textcircled{7}{"2466} \DeclareUTFCompositeSymbol\textcircled{8}{"2467} \DeclareUTFCompositeSymbol\textcircled{9}{"2468} \DeclareUTFCompositeSymbol\textcircled{10}{"2469} \DeclareUTFCompositeSymbol\textcircled{11}{"246A} \DeclareUTFCompositeSymbol\textcircled{12}{"246B} \DeclareUTFCompositeSymbol\textcircled{13}{"246C} \DeclareUTFCompositeSymbol\textcircled{14}{"246D} \DeclareUTFCompositeSymbol\textcircled{15}{"246E} \DeclareUTFCompositeSymbol\textcircled{16}{"246F} \DeclareUTFCompositeSymbol\textcircled{17}{"2470} \DeclareUTFCompositeSymbol\textcircled{18}{"2471} \DeclareUTFCompositeSymbol\textcircled{19}{"2472} \DeclareUTFCompositeSymbol\textcircled{20}{"2473} \DeclareUTFCompositeSymbol\textcircled{21}{"3251} \DeclareUTFCompositeSymbol\textcircled{22}{"3252} \DeclareUTFCompositeSymbol\textcircled{23}{"3253} \DeclareUTFCompositeSymbol\textcircled{24}{"3254} \DeclareUTFCompositeSymbol\textcircled{25}{"3255} \DeclareUTFCompositeSymbol\textcircled{26}{"3256} \DeclareUTFCompositeSymbol\textcircled{27}{"3257} \DeclareUTFCompositeSymbol\textcircled{28}{"3258} \DeclareUTFCompositeSymbol\textcircled{29}{"3259} \DeclareUTFCompositeSymbol\textcircled{30}{"325A} \DeclareUTFCompositeSymbol\textcircled{31}{"325B} \DeclareUTFCompositeSymbol\textcircled{32}{"325C} \DeclareUTFCompositeSymbol\textcircled{33}{"325D} \DeclareUTFCompositeSymbol\textcircled{34}{"325E} \DeclareUTFCompositeSymbol\textcircled{35}{"325F} \DeclareUTFCompositeSymbol\textcircled{36}{"32B1} \DeclareUTFCompositeSymbol\textcircled{37}{"32B2} \DeclareUTFCompositeSymbol\textcircled{38}{"32B3} \DeclareUTFCompositeSymbol\textcircled{39}{"32B4} \DeclareUTFCompositeSymbol\textcircled{40}{"32B5} \DeclareUTFCompositeSymbol\textcircled{41}{"32B6} \DeclareUTFCompositeSymbol\textcircled{42}{"32B7} \DeclareUTFCompositeSymbol\textcircled{43}{"32B8} \DeclareUTFCompositeSymbol\textcircled{44}{"32B9} \DeclareUTFCompositeSymbol\textcircled{45}{"32BA} \DeclareUTFCompositeSymbol\textcircled{46}{"32BB} \DeclareUTFCompositeSymbol\textcircled{47}{"32BC} \DeclareUTFCompositeSymbol\textcircled{48}{"32BD} \DeclareUTFCompositeSymbol\textcircled{49}{"32BE} \DeclareUTFCompositeSymbol\textcircled{50}{"32BF} \DeclareUTFCompositeSymbol\textcircled{A}{"24B6} \DeclareUTFCompositeSymbol\textcircled{B}{"24B7} \DeclareUTFCompositeSymbol\textcircled{C}{"24B8} \DeclareUTFCompositeSymbol\textcircled{D}{"24B9} \DeclareUTFCompositeSymbol\textcircled{E}{"24BA} \DeclareUTFCompositeSymbol\textcircled{F}{"24BB} \DeclareUTFCompositeSymbol\textcircled{G}{"24BC} \DeclareUTFCompositeSymbol\textcircled{H}{"24BD} \DeclareUTFCompositeSymbol\textcircled{I}{"24BE} \DeclareUTFCompositeSymbol\textcircled{J}{"24BF} \DeclareUTFCompositeSymbol\textcircled{K}{"24C0} \DeclareUTFCompositeSymbol\textcircled{L}{"24C1} \DeclareUTFCompositeSymbol\textcircled{M}{"24C2} \DeclareUTFCompositeSymbol\textcircled{N}{"24C3} \DeclareUTFCompositeSymbol\textcircled{O}{"24C4} \DeclareUTFCompositeSymbol\textcircled{P}{"24C5} \DeclareUTFCompositeSymbol\textcircled{Q}{"24C6} \DeclareUTFCompositeSymbol\textcircled{R}{"24C7} \DeclareUTFCompositeSymbol\textcircled{S}{"24C8} \DeclareUTFCompositeSymbol\textcircled{T}{"24C9} \DeclareUTFCompositeSymbol\textcircled{U}{"24CA} \DeclareUTFCompositeSymbol\textcircled{V}{"24CB} \DeclareUTFCompositeSymbol\textcircled{W}{"24CC} \DeclareUTFCompositeSymbol\textcircled{X}{"24CD} \DeclareUTFCompositeSymbol\textcircled{Y}{"24CE} \DeclareUTFCompositeSymbol\textcircled{Z}{"24CF} \DeclareUTFCompositeSymbol\textcircled{a}{"24D0} \DeclareUTFCompositeSymbol\textcircled{b}{"24D1} \DeclareUTFCompositeSymbol\textcircled{c}{"24D2} \DeclareUTFCompositeSymbol\textcircled{d}{"24D3} \DeclareUTFCompositeSymbol\textcircled{e}{"24D4} \DeclareUTFCompositeSymbol\textcircled{f}{"24D5} \DeclareUTFCompositeSymbol\textcircled{g}{"24D6} \DeclareUTFCompositeSymbol\textcircled{h}{"24D7} \DeclareUTFCompositeSymbol\textcircled{i}{"24D8} \DeclareUTFCompositeSymbol\textcircled{j}{"24D9} \DeclareUTFCompositeSymbol\textcircled{k}{"24DA} \DeclareUTFCompositeSymbol\textcircled{l}{"24DB} \DeclareUTFCompositeSymbol\textcircled{m}{"24DC} \DeclareUTFCompositeSymbol\textcircled{n}{"24DD} \DeclareUTFCompositeSymbol\textcircled{o}{"24DE} \DeclareUTFCompositeSymbol\textcircled{p}{"24DF} \DeclareUTFCompositeSymbol\textcircled{q}{"24E0} \DeclareUTFCompositeSymbol\textcircled{r}{"24E1} \DeclareUTFCompositeSymbol\textcircled{s}{"24E2} \DeclareUTFCompositeSymbol\textcircled{t}{"24E3} \DeclareUTFCompositeSymbol\textcircled{u}{"24E4} \DeclareUTFCompositeSymbol\textcircled{v}{"24E5} \DeclareUTFCompositeSymbol\textcircled{w}{"24E6} \DeclareUTFCompositeSymbol\textcircled{x}{"24E7} \DeclareUTFCompositeSymbol\textcircled{y}{"24E8} \DeclareUTFCompositeSymbol\textcircled{z}{"24E9} \DeclareUTFCompositeSymbol\textsuperscript{h}{"02B0} \DeclareUTFCompositeSymbol\textsuperscript{\texthth}{"02B1} \DeclareUTFCompositeSymbol\textsuperscript{j}{"02B2} \DeclareUTFCompositeSymbol\textsuperscript{r}{"02B3} \DeclareUTFCompositeSymbol\textsuperscript{\textturnr}{"02B4} \DeclareUTFCompositeSymbol\textsuperscript{\textturnrrtail}{"02B5} \DeclareUTFCompositeSymbol\textsuperscript{\textinvscr}{"02B6} \DeclareUTFCompositeSymbol\textsuperscript{w}{"02B7} \DeclareUTFCompositeSymbol\textsuperscript{y}{"02B8} \DeclareUTFCompositeSymbol\textsuperscript{\textbabygamma}{"02E0} \DeclareUTFCompositeSymbol\textsuperscript{\textgammalatinsmall}{"02E0} \DeclareUTFCompositeSymbol\textsuperscript{l}{"02E1} \DeclareUTFCompositeSymbol\textsuperscript{s}{"02E2} \DeclareUTFCompositeSymbol\textsuperscript{x}{"02E3} \DeclareUTFCompositeSymbol\textsuperscript{\textrevglotstop}{"02E4} \DeclareUTFCompositeSymbol\textsuperscript{\textrevepsilon}{"1D4C} \DeclareUTFCompositeSymbol\textsuperscript{\cyrn}{"1D78} \DeclareUTFCompositeSymbol\textsuperscript{\textbarsci}{"1DA7} \DeclareUTFCompositeSymbol\textsuperscript{V}{"2C7D} \DeclareUTFCompositeSymbol\textsuperscript{\textHbar}{"A7F8} \DeclareUTFCompositeSymbol\textsuperscript{\textHslash}{"A7F8} \DeclareUTFCompositeSymbol\textsuperscript{\oe}{"A7F9} \DeclareUTFCompositeSymbol\textsubscript{h}{"2095} \DeclareUTFCompositeSymbol\textsubscript{k}{"2096} \DeclareUTFCompositeSymbol\textsubscript{l}{"2097} \DeclareUTFCompositeSymbol\textsubscript{m}{"2098} \DeclareUTFCompositeSymbol\textsubscript{n}{"2099} \DeclareUTFCompositeSymbol\textsubscript{p}{"209A} \DeclareUTFCompositeSymbol\textsubscript{s}{"209B} \DeclareUTFCompositeSymbol\textsubscript{t}{"209C} % \end{macrocode} % % \changes{v3.2.9}{2013/12/07} % {增加 \file{xunicode-extra.def} 中,用于加入 \file{puenc.def} 中的符号定义。} % % 以下定义取自 \pkg{hyperref} 的 \file{puenc.def}。 % \changes{v3.10.0}{2023/11/10}{修复拼错的命令名, % \tn{cyreref} $\to$ \tn{cyrerev} 和 % \tn{textDiamandSolid} $\to$ \tn{textDiamondSolid}。} % \changes{v3.10.0}{2023/11/11}{修复 \tn{textnleqslant} 的定义, % 修复 \tn{textnbacksim} 和 \tn{textnlessapprox} 定义里的自引。} % \changes{v3.10.0}{2023/11/27}{修复组合符号 \texttt{U+04AA} 的定义。} % \begin{macrocode} \DeclareUTFEncodedAccent\textinvbreve{"0311}{"0311} \DeclareUTFEncodedSymbol\textsubbreve{"032E}{"203F} \DeclareUTFSymbol\textHT{"0009} \DeclareUTFSymbol\textLF{"000A} \DeclareUTFSymbol\textCR{"000D} \DeclareUTFSymbol\textnumbersign{"0023} \DeclareUTFSymbol\textparenleft{"0028} \DeclareUTFSymbol\textparenright{"0029} \DeclareUTFSymbol\textMVPlus{"002B} \DeclareUTFSymbol\textMVComma{"002C} \DeclareUTFSymbol\textMVMinus{"002D} \DeclareUTFSymbol\textMVPeriod{"002E} \DeclareUTFSymbol\textMVDivision{"002F} \DeclareUTFSymbol\textMVZero{"0030} \DeclareUTFSymbol\textMVOne{"0031} \DeclareUTFSymbol\textMVTwo{"0032} \DeclareUTFSymbol\textMVThree{"0033} \DeclareUTFSymbol\textMVFour{"0034} \DeclareUTFSymbol\textMVFive{"0035} \DeclareUTFSymbol\textMVSix{"0036} \DeclareUTFSymbol\textMVSeven{"0037} \DeclareUTFSymbol\textMVEight{"0038} \DeclareUTFSymbol\textMVNine{"0039} \DeclareUTFSymbol\textMVAt{"0040} \DeclareUTFCompositeCommand\.{\i}{i} \DeclareUTFCompositeCommand\.{i}{i} \DeclareUTFSymbol\textlnot{"00AC} \DeclareUTFSymbol\textplusminus{"00B1} \DeclareUTFSymbol\textcedilla{"00B8} \DeclareUTFSymbol\textmultiply{"00D7} \DeclareUTFSymbol\textThorn{"00DE} \DeclareUTFSymbol\textdivide{"00F7} \DeclareUTFSymbol\textHslash{"0126} \DeclareUTFCompositeSymbol\k{\i}{"012F} \DeclareUTFCompositeSymbol\.{L}{"013F} \DeclareUTFCompositeSymbol\.{l}{"0140} \DeclareUTFSymbol\textnapostrophe{"0149} \DeclareUTFSymbol\textTslash{"0166} \DeclareUTFSymbol\texttslash{"0167} \DeclareUTFSymbol\textlongs{"017F} \DeclareUTFSymbol\texthausaB{"0181} \DeclareUTFSymbol\texthausaD{"018A} \DeclareUTFSymbol\textrevE{"018E} \DeclareUTFSymbol\texthausaK{"0198} \DeclareUTFSymbol\textPUnrleg{"019E} \DeclareUTFSymbol\textinve{"01DD} \DeclareUTFSymbol\textGslash{"01E4} \DeclareUTFSymbol\textgslash{"01E5} \DeclareUTFCompositeSymbol\textinvbreve{E}{"0206} \DeclareUTFCompositeSymbol\textinvbreve{e}{"0207} \DeclareUTFCompositeSymbol\textinvbreve{I}{"020A} \DeclareUTFCompositeSymbol\textinvbreve{i}{"020B} \DeclareUTFCompositeSymbol\textinvbreve{\i}{"020B} \DeclareUTFCompositeSymbol\textinvbreve{O}{"020E} \DeclareUTFCompositeSymbol\textinvbreve{o}{"020F} \DeclareUTFCompositeSymbol\textinvbreve{U}{"0216} \DeclareUTFCompositeSymbol\textinvbreve{u}{"0217} \DeclareUTFSymbol\j{"0237} \DeclareUTFSymbol\textPUdblig{"0238} \DeclareUTFSymbol\textPUqplig{"0239} \DeclareUTFSymbol\textslashc{"023C} \DeclareUTFSymbol\textniepsilon{"025B} \DeclareUTFSymbol\textipagamma{"0263} \DeclareUTFSymbol\textniiota{"0269} \DeclareUTFSymbol\textniphi{"0278} \DeclareUTFSymbol\textniupsilon{"028A} \DeclareUTFSymbol\textring{"02DA} \DeclareUTFSymbol\texttilde{"02DC} \DeclareUTFSymbol\texthungarumlaut{"02DD} \DeclareUTFSymbol\textringlow{"02F3} \DeclareUTFSymbol\texttildelow{"02F7} \DeclareUTFCommand\textnewtie{\textinvbreve\ } \DeclareUTFCommand\textdotbelow{\d\ } \DeclareUTFSymbol\textmacronbelow{"02CD} \DeclareUTFCommand\texttie{\t\ } \DeclareUTFSymbol\textnumeralsigngreek{"0374} \DeclareUTFSymbol\textnumeralsignlowergreek{"0375} \DeclareUTFCompositeSymbol\'{\textAlpha}{"0386} \DeclareUTFCompositeSymbol\'{\textEpsilon}{"0388} \DeclareUTFCompositeSymbol\'{\textEta}{"0389} \DeclareUTFCompositeSymbol\'{\textIota}{"038A} \DeclareUTFCompositeSymbol\'{\textOmicron}{"038C} \DeclareUTFCompositeSymbol\'{\textUpsilon}{"038E} \DeclareUTFCompositeSymbol\'{\textOmega}{"038F} \DeclareUTFCompositeSymbol\'{\textIotadieresis}{"0390} \DeclareUTFSymbol\textIotadieresis{"03AA} \DeclareUTFCompositeSymbol\"{\textIota}{"03AA} \DeclareUTFCompositeSymbol\"{\textUpsilon}{"03AB} \DeclareUTFCompositeSymbol\'{\textalpha}{"03AC} \DeclareUTFCompositeSymbol\'{\textepsilon}{"03AD} \DeclareUTFCompositeSymbol\'{\texteta}{"03AE} \DeclareUTFCompositeSymbol\'{\textiota}{"03AF} \DeclareUTFCompositeSymbol\"{\textupsilonacute}{"03B0} \DeclareUTFSymbol\textmugreek{"03BC} \DeclareUTFSymbol\textvarsigma{"03C2} \DeclareUTFCompositeSymbol\"{\textiota}{"03CA} \DeclareUTFCompositeSymbol\"{\textupsilon}{"03CB} \DeclareUTFCompositeSymbol\'{\textomicron}{"03CC} \DeclareUTFSymbol\textupsilonacute{"03CD} \DeclareUTFCompositeSymbol\'{\textupsilon}{"03CD} \DeclareUTFCompositeSymbol\'{\textomega}{"03CE} \DeclareUTFSymbol\textStigmagreek{"03DA} \DeclareUTFSymbol\textstigmagreek{"03DB} \DeclareUTFSymbol\textDigammagreek{"03DC} \DeclareUTFSymbol\textdigammagreek{"03DD} \DeclareUTFSymbol\textKoppagreek{"03DE} \DeclareUTFSymbol\textkoppagreek{"03DF} \DeclareUTFSymbol\textSampigreek{"03E0} \DeclareUTFSymbol\textsampigreek{"03E1} \DeclareUTFSymbol\textbackepsilon{"03F6} \DeclareUTFCompositeSymbol\`{\CYRE}{"0400} \DeclareUTFSymbol\CYRYO{"0401} \DeclareUTFCompositeSymbol\"{\CYRE}{"0401} \DeclareUTFSymbol\CYRDJE{"0402} \DeclareUTFCompositeSymbol\'{\CYRG}{"0403} \DeclareUTFSymbol\CYRIE{"0404} \DeclareUTFSymbol\CYRDZE{"0405} \DeclareUTFSymbol\CYRII{"0406} \DeclareUTFSymbol\CYRYI{"0407} \DeclareUTFCompositeSymbol\"{\CYRII}{"0407} \DeclareUTFSymbol\CYRJE{"0408} \DeclareUTFSymbol\CYRLJE{"0409} \DeclareUTFSymbol\CYRNJE{"040A} \DeclareUTFSymbol\CYRTSHE{"040B} \DeclareUTFCompositeSymbol\'{\CYRK}{"040C} \DeclareUTFCompositeSymbol\`{\CYRI}{"040D} \DeclareUTFSymbol\CYRUSHRT{"040E} \DeclareUTFCompositeSymbol\U{\CYRU}{"040E} \DeclareUTFSymbol\CYRDZHE{"040F} \DeclareUTFSymbol\CYRA{"0410} \DeclareUTFSymbol\CYRB{"0411} \DeclareUTFSymbol\CYRV{"0412} \DeclareUTFSymbol\CYRG{"0413} \DeclareUTFSymbol\CYRD{"0414} \DeclareUTFSymbol\CYRE{"0415} \DeclareUTFSymbol\CYRZH{"0416} \DeclareUTFSymbol\CYRZ{"0417} \DeclareUTFSymbol\CYRI{"0418} \DeclareUTFSymbol\CYRISHRT{"0419} \DeclareUTFCompositeSymbol\U{\CYRI}{"0419} \DeclareUTFSymbol\CYRK{"041A} \DeclareUTFSymbol\CYRL{"041B} \DeclareUTFSymbol\CYRM{"041C} \DeclareUTFSymbol\CYRN{"041D} \DeclareUTFSymbol\CYRO{"041E} \DeclareUTFSymbol\CYRP{"041F} \DeclareUTFSymbol\CYRR{"0420} \DeclareUTFSymbol\CYRS{"0421} \DeclareUTFSymbol\CYRT{"0422} \DeclareUTFSymbol\CYRU{"0423} \DeclareUTFSymbol\CYRF{"0424} \DeclareUTFSymbol\CYRH{"0425} \DeclareUTFSymbol\CYRC{"0426} \DeclareUTFSymbol\CYRCH{"0427} \DeclareUTFSymbol\CYRSH{"0428} \DeclareUTFSymbol\CYRSHCH{"0429} \DeclareUTFSymbol\CYRHRDSN{"042A} \DeclareUTFSymbol\CYRERY{"042B} \DeclareUTFSymbol\CYRSFTSN{"042C} \DeclareUTFSymbol\CYREREV{"042D} \DeclareUTFSymbol\CYRYU{"042E} \DeclareUTFSymbol\CYRYA{"042F} \DeclareUTFSymbol\cyra{"0430} \DeclareUTFSymbol\cyrb{"0431} \DeclareUTFSymbol\cyrv{"0432} \DeclareUTFSymbol\cyrg{"0433} \DeclareUTFSymbol\cyrd{"0434} \DeclareUTFSymbol\cyre{"0435} \DeclareUTFSymbol\cyrzh{"0436} \DeclareUTFSymbol\cyrz{"0437} \DeclareUTFSymbol\cyri{"0438} \DeclareUTFSymbol\cyrishrt{"0439} \DeclareUTFCompositeSymbol\U{\cyri}{"0439} \DeclareUTFSymbol\cyrk{"043A} \DeclareUTFSymbol\cyrl{"043B} \DeclareUTFSymbol\cyrm{"043C} \DeclareUTFSymbol\cyrn{"043D} \DeclareUTFSymbol\cyro{"043E} \DeclareUTFSymbol\cyrp{"043F} \DeclareUTFSymbol\cyrr{"0440} \DeclareUTFSymbol\cyrs{"0441} \DeclareUTFSymbol\cyrt{"0442} \DeclareUTFSymbol\cyru{"0443} \DeclareUTFSymbol\cyrf{"0444} \DeclareUTFSymbol\cyrh{"0445} \DeclareUTFSymbol\cyrc{"0446} \DeclareUTFSymbol\cyrch{"0447} \DeclareUTFSymbol\cyrsh{"0448} \DeclareUTFSymbol\cyrshch{"0449} \DeclareUTFSymbol\cyrhrdsn{"044A} \DeclareUTFSymbol\cyrery{"044B} \DeclareUTFSymbol\cyrsftsn{"044C} \DeclareUTFSymbol\cyrerev{"044D} \DeclareUTFSymbol\cyryu{"044E} \DeclareUTFSymbol\cyrya{"044F} \DeclareUTFCompositeSymbol\`{\cyre}{"0450} \DeclareUTFSymbol\cyryo{"0451} \DeclareUTFCompositeSymbol\"{\cyre}{"0451} \DeclareUTFSymbol\cyrdje{"0452} \DeclareUTFCompositeSymbol\'{\cyrg}{"0453} \DeclareUTFSymbol\cyrie{"0454} \DeclareUTFSymbol\cyrdze{"0455} \DeclareUTFSymbol\cyrii{"0456} \DeclareUTFSymbol\cyryi{"0457} \DeclareUTFCompositeSymbol\"{\cyrii}{"0457} \DeclareUTFSymbol\cyrje{"0458} \DeclareUTFSymbol\cyrlje{"0459} \DeclareUTFSymbol\cyrnje{"045A} \DeclareUTFSymbol\cyrtshe{"045B} \DeclareUTFCompositeSymbol\'{\cyrk}{"045C} \DeclareUTFCompositeSymbol\`{\cyri}{"045D} \DeclareUTFSymbol\cyrushrt{"045E} \DeclareUTFCompositeSymbol\U{\curu}{"045E} \DeclareUTFSymbol\cyrdzhe{"045F} \DeclareUTFSymbol\CYROMEGA{"0460} \DeclareUTFSymbol\cyromega{"0461} \DeclareUTFSymbol\CYRYAT{"0462} \DeclareUTFSymbol\cyryat{"0463} \DeclareUTFSymbol\CYRIOTE{"0464} \DeclareUTFSymbol\cyriote{"0465} \DeclareUTFSymbol\CYRLYUS{"0466} \DeclareUTFSymbol\cyrlyus{"0467} \DeclareUTFSymbol\CYRIOTLYUS{"0468} \DeclareUTFSymbol\cyriotlyus{"0469} \DeclareUTFSymbol\CYRBYUS{"046A} \DeclareUTFSymbol\cyrbyus{"046B} \DeclareUTFSymbol\CYRIOTBYUS{"046C} \DeclareUTFSymbol\cyriotbyus{"046D} \DeclareUTFSymbol\CYRKSI{"046E} \DeclareUTFSymbol\cyrksi{"046F} \DeclareUTFSymbol\CYRPSI{"0470} \DeclareUTFSymbol\cyrpsi{"0471} \DeclareUTFSymbol\CYRFITA{"0472} \DeclareUTFSymbol\cyrfita{"0473} \DeclareUTFSymbol\CYRIZH{"0474} \DeclareUTFSymbol\cyrizh{"0475} \DeclareUTFCompositeSymbol\C{\CYRIZH}{"0476} \DeclareUTFCompositeSymbol\C{\cyrizh}{"0477} \DeclareUTFSymbol\CYRUK{"0478} \DeclareUTFSymbol\cyruk{"0479} \DeclareUTFSymbol\CYROMEGARND{"047A} \DeclareUTFSymbol\cyromegarnd{"047B} \DeclareUTFSymbol\CYROMEGATITLO{"047C} \DeclareUTFSymbol\cyromegatitlo{"047D} \DeclareUTFSymbol\CYROT{"047E} \DeclareUTFSymbol\cyrot{"047F} \DeclareUTFSymbol\CYRKOPPA{"0480} \DeclareUTFSymbol\cyrkoppa{"0481} \DeclareUTFSymbol\cyrthousands{"0482} \DeclareUTFSymbol\CYRISHRTDSC{"048A} \DeclareUTFSymbol\cyrishrtdsc{"048B} \DeclareUTFSymbol\CYRSEMISFTSN{"048C} \DeclareUTFSymbol\cyrsemisftsn{"048D} \DeclareUTFSymbol\CYRRTICK{"048E} \DeclareUTFSymbol\cyrrtick{"048F} \DeclareUTFSymbol\CYRGUP{"0490} \DeclareUTFSymbol\cyrgup{"0491} \DeclareUTFSymbol\CYRGHCRS{"0492} \DeclareUTFSymbol\cyrghcrs{"0493} \DeclareUTFSymbol\CYRGHK{"0494} \DeclareUTFSymbol\cyrghk{"0495} \DeclareUTFSymbol\CYRZHDSC{"0496} \DeclareUTFSymbol\cyrzhdsc{"0497} \DeclareUTFSymbol\CYRZDSC{"0498} \DeclareUTFCompositeSymbol\c{\CYRZ}{"0498} \DeclareUTFSymbol\cyrzdsc{"0499} \DeclareUTFCompositeSymbol\c{\cyrz}{"0499} \DeclareUTFSymbol\CYRKDSC{"049A} \DeclareUTFSymbol\cyrkdsc{"049B} \DeclareUTFSymbol\CYRKVCRS{"049C} \DeclareUTFSymbol\cyrkvcrs{"049D} \DeclareUTFSymbol\CYRKHCRS{"049E} \DeclareUTFSymbol\cyrkhcrs{"049F} \DeclareUTFSymbol\CYRKBEAK{"04A0} \DeclareUTFSymbol\cyrkbeak{"04A1} \DeclareUTFSymbol\CYRNDSC{"04A2} \DeclareUTFSymbol\cyrndsc{"04A3} \DeclareUTFSymbol\CYRNG{"04A4} \DeclareUTFSymbol\cyrng{"04A5} \DeclareUTFSymbol\CYRPHK{"04A6} \DeclareUTFSymbol\cyrphk{"04A7} \DeclareUTFSymbol\CYRABHHA{"04A8} \DeclareUTFSymbol\cyrabhha{"04A9} \DeclareUTFSymbol\CYRSDSC{"04AA} \DeclareUTFCompositeSymbol\k{\CYRS}{"04AA} \DeclareUTFSymbol\cyrsdsc{"04AB} \DeclareUTFCompositeSymbol\k{\cyrs}{"04AB} \DeclareUTFSymbol\CYRTDSC{"04AC} \DeclareUTFSymbol\cyrtdsc{"04AD} \DeclareUTFSymbol\CYRY{"04AE} \DeclareUTFSymbol\cyry{"04AF} \DeclareUTFSymbol\CYRYHCRS{"04B0} \DeclareUTFSymbol\cyryhcrs{"04B1} \DeclareUTFSymbol\CYRHDSC{"04B2} \DeclareUTFSymbol\cyrhdsc{"04B3} \DeclareUTFSymbol\CYRTETSE{"04B4} \DeclareUTFSymbol\cyrtetse{"04B5} \DeclareUTFSymbol\CYRCHRDSC{"04B6} \DeclareUTFSymbol\cyrchrdsc{"04B7} \DeclareUTFSymbol\CYRCHVCRS{"04B8} \DeclareUTFSymbol\cyrchvcrs{"04B9} \DeclareUTFSymbol\CYRSHHA{"04BA} \DeclareUTFSymbol\cyrshha{"04BB} \DeclareUTFSymbol\CYRABHCH{"04BC} \DeclareUTFSymbol\cyrabhch{"04BD} \DeclareUTFSymbol\CYRABHCHDSC{"04BE} \DeclareUTFCompositeSymbol\k{\CYRABHCH}{"04BE} \DeclareUTFSymbol\cyrabhchdsc{"04BF} \DeclareUTFCompositeSymbol\k{\cyrabhch}{"04BF} \DeclareUTFSymbol\CYRpalochka{"04C0} \DeclareUTFCompositeSymbol\U{\CYRZH}{"04C1} \DeclareUTFCompositeSymbol\U{\cyrzh}{"04C2} \DeclareUTFSymbol\CYRKHK{"04C3} \DeclareUTFSymbol\cyrkhk{"04C4} \DeclareUTFSymbol\CYRLDSC{"04C5} \DeclareUTFSymbol\cyrldsc{"04C6} \DeclareUTFSymbol\CYRNHK{"04C7} \DeclareUTFSymbol\cyrnhk{"04C8} \DeclareUTFSymbol\CYRCHLDSC{"04CB} \DeclareUTFSymbol\cyrchldsc{"04CC} \DeclareUTFSymbol\CYRMDSC{"04CD} \DeclareUTFSymbol\cyrmdsc{"04CE} \DeclareUTFCompositeSymbol\U{\CYRA}{"04D0} \DeclareUTFCompositeSymbol\U{\cyra}{"04D1} \DeclareUTFCompositeSymbol\"{\CYRA}{"04D2} \DeclareUTFCompositeSymbol\"{\cyra}{"04D3} \DeclareUTFSymbol\CYRAE{"04D4} \DeclareUTFSymbol\cyrae{"04D5} \DeclareUTFCompositeSymbol\U{\CYRE}{"04D6} \DeclareUTFCompositeSymbol\U{\cyre}{"04D7} \DeclareUTFSymbol\CYRSCHWA{"04D8} \DeclareUTFSymbol\cyrschwa{"04D9} \DeclareUTFCompositeSymbol\"{\CYRSCHWA}{"04DA} \DeclareUTFCompositeSymbol\"{\cyrschwa}{"04DB} \DeclareUTFCompositeSymbol\"{\CYRZH}{"04DC} \DeclareUTFCompositeSymbol\"{\cyrzh}{"04DD} \DeclareUTFCompositeSymbol\"{\CYRZ}{"04DE} \DeclareUTFCompositeSymbol\"{\cyrz}{"04DF} \DeclareUTFSymbol\CYRABHDZE{"04E0} \DeclareUTFSymbol\cyrabhdze{"04E1} \DeclareUTFCompositeSymbol\={\CYRI}{"04E2} \DeclareUTFCompositeSymbol\={\cyri}{"04E3} \DeclareUTFCompositeSymbol\"{\CYRI}{"04E4} \DeclareUTFCompositeSymbol\"{\cyri}{"04E5} \DeclareUTFCompositeSymbol\"{\CYRO}{"04E6} \DeclareUTFCompositeSymbol\"{\cyro}{"04E7} \DeclareUTFSymbol\CYROTLD{"04E8} \DeclareUTFSymbol\cyrotld{"04E9} \DeclareUTFCompositeSymbol\"{\CYROTLD}{"04EA} \DeclareUTFCompositeSymbol\"{\cyrotld}{"04EB} \DeclareUTFCompositeSymbol\"{\CYREREV}{"04EC} \DeclareUTFCompositeSymbol\"{\cyrerev}{"04ED} \DeclareUTFCompositeSymbol\={\CYRU}{"04EE} \DeclareUTFCompositeSymbol\={\cyru}{"04EF} \DeclareUTFCompositeSymbol\"{\CYRU}{"04F0} \DeclareUTFCompositeSymbol\"{\cyru}{"04F1} \DeclareUTFCompositeSymbol\H{\CYRU}{"04F2} \DeclareUTFCompositeSymbol\H{\cyru}{"04F3} \DeclareUTFCompositeSymbol\"{\CYRCH}{"04F4} \DeclareUTFCompositeSymbol\"{\cyrch}{"04F5} \DeclareUTFSymbol\CYRGDSC{"04F6} \DeclareUTFSymbol\cyrgdsc{"04F7} \DeclareUTFCompositeSymbol\"{\CYRERY}{"04F8} \DeclareUTFCompositeSymbol\"{\cyrery}{"04F9} \DeclareUTFSymbol\CYRHHK{"04FC} \DeclareUTFSymbol\cyrhhk{"04FD} \DeclareUTFSymbol\sofpasuq{"05C3} \DeclareUTFSymbol\hebalef{"05D0} \DeclareUTFSymbol\hebbet{"05D1} \DeclareUTFSymbol\hebgimel{"05D2} \DeclareUTFSymbol\hebdalet{"05D3} \DeclareUTFSymbol\hebhe{"05D4} \DeclareUTFSymbol\hebvav{"05D5} \DeclareUTFSymbol\hebzayin{"05D6} \DeclareUTFSymbol\hebhet{"05D7} \DeclareUTFSymbol\hebtet{"05D8} \DeclareUTFSymbol\hebyod{"05D9} \DeclareUTFSymbol\hebfinalkaf{"05DA} \DeclareUTFSymbol\hebkaf{"05DB} \DeclareUTFSymbol\heblamed{"05DC} \DeclareUTFSymbol\hebfinalmem{"05DD} \DeclareUTFSymbol\hebmem{"05DE} \DeclareUTFSymbol\hebfinalnun{"05DF} \DeclareUTFSymbol\hebnun{"05E0} \DeclareUTFSymbol\hebsamekh{"05E1} \DeclareUTFSymbol\hebayin{"05E2} \DeclareUTFSymbol\hebfinalpe{"05E3} \DeclareUTFSymbol\hebpe{"05E4} \DeclareUTFSymbol\hebfinaltsadi{"05E5} \DeclareUTFSymbol\hebtsadi{"05E6} \DeclareUTFSymbol\hebqof{"05E7} \DeclareUTFSymbol\hebresh{"05E8} \DeclareUTFSymbol\hebshin{"05E9} \DeclareUTFSymbol\hebtav{"05EA} \DeclareUTFSymbol\doublevav{"05F0} \DeclareUTFSymbol\vavyod{"05F1} \DeclareUTFSymbol\doubleyod{"05F2} \DeclareUTFSymbol\textscd{"1D05} \DeclareUTFSymbol\textPUsck{"1D0B} \DeclareUTFSymbol\textPUscm{"1D0D} \DeclareUTFSymbol\textPUscp{"1D18} \DeclareUTFSymbol\textPUrevscr{"1D19} \DeclareUTFSymbol\textiinferior{"1D62} \DeclareUTFSymbol\textrinferior{"1D63} \DeclareUTFSymbol\textuinferior{"1D64} \DeclareUTFSymbol\textvinferior{"1D65} \DeclareUTFSymbol\textbetainferior{"1D66} \DeclareUTFSymbol\textgammainferior{"1D67} \DeclareUTFSymbol\textrhoinferior{"1D68} \DeclareUTFSymbol\textphiinferior{"1D69} \DeclareUTFSymbol\textchiinferior{"1D6A} \DeclareUTFSymbol\textbarsci{"1D7B} \DeclareUTFSymbol\textbarp{"1D7D} \DeclareUTFSymbol\textbarscu{"1D7E} \DeclareUTFSymbol\textPUrhooka{"1D8F} \DeclareUTFSymbol\textPUrhooke{"1D92} \DeclareUTFSymbol\textPUrhookepsilon{"1D93} \DeclareUTFSymbol\textPUrhookopeno{"1D97} \DeclareUTFCompositeSymbol\textsubbreve{H}{"1E2A} \DeclareUTFCompositeSymbol\textsubbreve{h}{"1E2B} \DeclareUTFCompositeSymbol\.{\textlongs}{"1E9B} \DeclareUTFSymbol\textcompwordmark{"200C} \DeclareUTFSymbol\texthdotfor{"2025} \DeclareUTFSymbol\textprime{"2032} \DeclareUTFSymbol\textsecond{"2033} \DeclareUTFSymbol\textthird{"2034} \DeclareUTFSymbol\textbackprime{"2035} \DeclareUTFSymbol\textlefttherefore{"2056} \DeclareUTFSymbol\textfourth{"2057} \DeclareUTFSymbol\textdiamonddots{"2058} \DeclareUTFSymbol\textzerosuperior{"2070} \DeclareUTFSymbol\textisuperior{"2071} \DeclareUTFSymbol\textfoursuperior{"2074} \DeclareUTFSymbol\textfivesuperior{"2075} \DeclareUTFSymbol\textsixsuperior{"2076} \DeclareUTFSymbol\textsevensuperior{"2077} \DeclareUTFSymbol\texteightsuperior{"2078} \DeclareUTFSymbol\textninesuperior{"2079} \DeclareUTFSymbol\textplussuperior{"207A} \DeclareUTFSymbol\textminussuperior{"207B} \DeclareUTFSymbol\textequalsuperior{"207C} \DeclareUTFSymbol\textparenleftsuperior{"207D} \DeclareUTFSymbol\textparenrightsuperior{"207E} \DeclareUTFSymbol\textnsuperior{"207F} \DeclareUTFSymbol\textzeroinferior{"2080} \DeclareUTFSymbol\textoneinferior{"2081} \DeclareUTFSymbol\texttwoinferior{"2082} \DeclareUTFSymbol\textthreeinferior{"2083} \DeclareUTFSymbol\textfourinferior{"2084} \DeclareUTFSymbol\textfiveinferior{"2085} \DeclareUTFSymbol\textsixinferior{"2086} \DeclareUTFSymbol\textseveninferior{"2087} \DeclareUTFSymbol\texteightinferior{"2088} \DeclareUTFSymbol\textnineinferior{"2089} \DeclareUTFSymbol\textplusinferior{"208A} \DeclareUTFSymbol\textminusinferior{"208B} \DeclareUTFSymbol\textequalsinferior{"208C} \DeclareUTFSymbol\textparenleftinferior{"208D} \DeclareUTFSymbol\textparenrightinferior{"208E} \DeclareUTFSymbol\textainferior{"2090} \DeclareUTFSymbol\texteinferior{"2091} \DeclareUTFSymbol\textoinferior{"2092} \DeclareUTFSymbol\textxinferior{"2093} \DeclareUTFSymbol\textschwainferior{"2094} \DeclareUTFSymbol\texthinferior{"2095} \DeclareUTFSymbol\textkinferior{"2096} \DeclareUTFSymbol\textlinferior{"2097} \DeclareUTFSymbol\textminferior{"2098} \DeclareUTFSymbol\textninferior{"2099} \DeclareUTFSymbol\textpinferior{"209A} \DeclareUTFSymbol\textsinferior{"209B} \DeclareUTFSymbol\texttinferior{"209C} \DeclareUTFSymbol\textpeseta{"20A7} \DeclareUTFSymbol\textDeleatur{"20B0} \DeclareUTFSymbol\textguarani{"20B2} \DeclareUTFSymbol\texthslash{"210F} \DeclareUTFSymbol\textIm{"2111} \DeclareUTFSymbol\textell{"2113} \DeclareUTFSymbol\textwp{"2118} \DeclareUTFSymbol\textRe{"211C} \DeclareUTFSymbol\textriota{"2129} \DeclareUTFSymbol\textangstrom{"212B} \DeclareUTFSymbol\textfax{"213B} \DeclareUTFSymbol\textinvamp{"214B} \DeclareUTFSymbol\textoneseventh{"2150} \DeclareUTFSymbol\textoneninth{"2151} \DeclareUTFSymbol\textonetenth{"2152} \DeclareUTFSymbol\textonethird{"2153} \DeclareUTFSymbol\texttwothirds{"2154} \DeclareUTFSymbol\textonefifth{"2155} \DeclareUTFSymbol\texttwofifths{"2156} \DeclareUTFSymbol\textthreefifths{"2157} \DeclareUTFSymbol\textfourfifths{"2158} \DeclareUTFSymbol\textonesixth{"2159} \DeclareUTFSymbol\textfivesixths{"215A} \DeclareUTFSymbol\textoneeighth{"215B} \DeclareUTFSymbol\textthreeeighths{"215C} \DeclareUTFSymbol\textfiveeighths{"215D} \DeclareUTFSymbol\textseveneighths{"215E} \DeclareUTFSymbol\textrevc{"2184} \DeclareUTFSymbol\textzerothirds{"2189} \DeclareUTFSymbol\textnleftarrow{"219A} \DeclareUTFSymbol\textnrightarrow{"219B} \DeclareUTFSymbol\texttwoheadleftarrow{"219E} \DeclareUTFCommand\textntwoheadleftarrow{\textlstrikethru\texttwoheadleftarrow} \DeclareUTFSymbol\texttwoheaduparrow{"219F} \DeclareUTFSymbol\texttwoheadrightarrow{"21A0} \DeclareUTFCommand\textntwoheadrightarrow{\textlstrikethru\texttwoheadrightarrow} \DeclareUTFSymbol\texttwoheaddownarrow{"21A1} \DeclareUTFSymbol\textleftarrowtail{"21A2} \DeclareUTFSymbol\textrightarrowtail{"21A3} \DeclareUTFSymbol\textmapsto{"21A6} \DeclareUTFSymbol\texthookleftarrow{"21A9} \DeclareUTFSymbol\texthookrightarrow{"21AA} \DeclareUTFSymbol\textlooparrowleft{"21AB} \DeclareUTFSymbol\textlooparrowright{"21AC} \DeclareUTFSymbol\textnleftrightarrow{"21AE} \DeclareUTFSymbol\textlightning{"21AF} \DeclareUTFSymbol\textdlsh{"21B5} \DeclareUTFSymbol\textcurvearrowleft{"21B6} \DeclareUTFSymbol\textcurvearrowright{"21B7} \DeclareUTFSymbol\textleftharpoonup{"21BC} \DeclareUTFSymbol\textleftharpoondown{"21BD} \DeclareUTFSymbol\textupharpoonright{"21BE} \DeclareUTFSymbol\textupharpoonleft{"21BF} \DeclareUTFSymbol\textrightharpoonup{"21C0} \DeclareUTFSymbol\textrightharpoondown{"21C1} \DeclareUTFSymbol\textdownharpoonright{"21C2} \DeclareUTFSymbol\textdownharpoonleft{"21C3} \DeclareUTFSymbol\textrightleftarrows{"21C4} \DeclareUTFSymbol\textupdownarrows{"21C5} \DeclareUTFSymbol\textleftrightarrows{"21C6} \DeclareUTFSymbol\textleftleftarrows{"21C7} \DeclareUTFSymbol\textupuparrows{"21C8} \DeclareUTFSymbol\textrightrightarrows{"21C9} \DeclareUTFSymbol\textdowndownarrows{"21CA} \DeclareUTFSymbol\textleftrightharpoons{"21CB} \DeclareUTFSymbol\textrightleftharpoons{"21CC} \DeclareUTFSymbol\textnLeftarrow{"21CD} \DeclareUTFSymbol\textnLeftrightarrow{"21CE} \DeclareUTFSymbol\textnRightarrow{"21CF} \DeclareUTFSymbol\textLeftarrow{"21D0} \DeclareUTFSymbol\textUparrow{"21D1} \DeclareUTFSymbol\textRightarrow{"21D2} \DeclareUTFSymbol\textDownarrow{"21D3} \DeclareUTFSymbol\textLeftrightarrow{"21D4} \DeclareUTFSymbol\textUpdownarrow{"21D5} \DeclareUTFSymbol\textNwarrow{"21D6} \DeclareUTFSymbol\textNearrow{"21D7} \DeclareUTFSymbol\textSearrow{"21D8} \DeclareUTFSymbol\textSwarrow{"21D9} \DeclareUTFSymbol\textLleftarrow{"21DA} \DeclareUTFSymbol\textRrightarrow{"21DB} \DeclareUTFSymbol\textleftsquigarrow{"21DC} \DeclareUTFSymbol\textrightsquigarrow{"21DD} \DeclareUTFSymbol\textdashleftarrow{"21E0} \DeclareUTFSymbol\textdasheduparrow{"21E1} \DeclareUTFSymbol\textdashrightarrow{"21E2} \DeclareUTFSymbol\textdasheddownarrow{"21E3} \DeclareUTFSymbol\textpointer{"21E8} \DeclareUTFSymbol\textdownuparrows{"21F5} \DeclareUTFSymbol\textleftarrowtriangle{"21FD} \DeclareUTFSymbol\textrightarrowtriangle{"21FE} \DeclareUTFSymbol\textleftrightarrowtriangle{"21FF} \DeclareUTFSymbol\textforall{"2200} \DeclareUTFSymbol\textcomplement{"2201} \DeclareUTFSymbol\textpartial{"2202} \DeclareUTFSymbol\textexists{"2203} \DeclareUTFSymbol\textnexists{"2204} \DeclareUTFSymbol\textemptyset{"2205} \DeclareUTFSymbol\texttriangle{"2206} \DeclareUTFSymbol\textnabla{"2207} \DeclareUTFSymbol\textin{"2208} \DeclareUTFSymbol\textnotin{"2209} \DeclareUTFSymbol\textsmallin{"220A} \DeclareUTFSymbol\textni{"220B} \DeclareUTFSymbol\textnotowner{"220C} \DeclareUTFSymbol\textsmallowns{"220D} \DeclareUTFSymbol\textprod{"220F} \DeclareUTFSymbol\textamalg{"2210} \DeclareUTFSymbol\textsum{"2211} \DeclareUTFSymbol\textmp{"2213} \DeclareUTFSymbol\textdotplus{"2214} \DeclareUTFSymbol\textDivides{"2215} \DeclareUTFSymbol\textsetminus{"2216} \DeclareUTFSymbol\textast{"2217} \DeclareUTFSymbol\textcirc{"2218} \DeclareUTFSymbol\textbulletoperator{"2219} \DeclareUTFSymbol\textpropto{"221D} \DeclareUTFSymbol\textinfty{"221E} \DeclareUTFSymbol\textangle{"2220} \DeclareUTFSymbol\textmeasuredangle{"2221} \DeclareUTFSymbol\textsphericalangle{"2222} \DeclareUTFSymbol\textmid{"2223} \DeclareUTFSymbol\textnmid{"2224} \DeclareUTFSymbol\textparallel{"2225} \DeclareUTFSymbol\textnparallel{"2226} \DeclareUTFSymbol\textwedge{"2227} \DeclareUTFCommand\textowedge{\textcircled\textwedge} \DeclareUTFSymbol\textvee{"2228} \DeclareUTFCommand\textovee{\textcircled\textvee} \DeclareUTFSymbol\textcap{"2229} \DeclareUTFSymbol\textcup{"222A} \DeclareUTFSymbol\textint{"222B} \DeclareUTFSymbol\textiint{"222C} \DeclareUTFSymbol\textiiint{"222D} \DeclareUTFSymbol\textoint{"222E} \DeclareUTFSymbol\textoiint{"222F} \DeclareUTFSymbol\textointclockwise{"2232} \DeclareUTFSymbol\textointctrclockwise{"2233} \DeclareUTFSymbol\texttherefore{"2234} \DeclareUTFSymbol\textbecause{"2235} \DeclareUTFSymbol\textvdotdot{"2236} \DeclareUTFSymbol\textsquaredots{"2237} \DeclareUTFSymbol\textdotminus{"2238} \DeclareUTFSymbol\texteqcolon{"2239} \DeclareUTFSymbol\textsim{"223C} \DeclareUTFSymbol\textbacksim{"223D} \DeclareUTFCommand\textnbacksim{\textlstrikethru\textbacksim} \DeclareUTFSymbol\textwr{"2240} \DeclareUTFSymbol\textnsim{"2241} \DeclareUTFSymbol\texteqsim{"2242} \DeclareUTFCommand\textneqsim{\textlstrikethru\texteqsim} \DeclareUTFSymbol\textsimeq{"2243} \DeclareUTFSymbol\textnsimeq{"2244} \DeclareUTFSymbol\textcong{"2245} \DeclareUTFSymbol\textncong{"2247} \DeclareUTFSymbol\textapprox{"2248} \DeclareUTFSymbol\textnapprox{"2249} \DeclareUTFSymbol\textapproxeq{"224A} \DeclareUTFCommand\textnapproxeq{\textlstrikethru\textapproxeq} \DeclareUTFSymbol\texttriplesim{"224B} \DeclareUTFCommand\textntriplesim{\textlstrikethru\texttriplesim} \DeclareUTFSymbol\textbackcong{"224C} \DeclareUTFCommand\textnbackcong{\textlstrikethru\textbackcong} \DeclareUTFSymbol\textasymp{"224D} \DeclareUTFCommand\textnasymp{\textlstrikethru\textasymp} \DeclareUTFSymbol\textBumpeq{"224E} \DeclareUTFCommand\textnBumpeq{\textlstrikethru\textBumpeq} \DeclareUTFSymbol\textbumpeq{"224F} \DeclareUTFCommand\textnbumpeq{\textlstrikethru\textbumpeq} \DeclareUTFSymbol\textdoteq{"2250} \DeclareUTFCommand\textndoteq{\textlstrikethru\textdoteq} \DeclareUTFSymbol\textdoteqdot{"2251} \DeclareUTFCommand\textnDoteq{\textlstrikethru\textdoteqdot} \DeclareUTFSymbol\textfallingdoteq{"2252} \DeclareUTFCommand\textnfallingdoteq{\textlstrikethru\textfallingdoteq} \DeclareUTFSymbol\textrisingdoteq{"2253} \DeclareUTFCommand\textnrisingdoteq{\textlstrikethru\textrisingdoteq} \DeclareUTFSymbol\textcolonequals{"2254} \DeclareUTFSymbol\textequalscolon{"2255} \DeclareUTFSymbol\texteqcirc{"2256} \DeclareUTFCommand\textneqcirc{\textlstrikethru\texteqcirc} \DeclareUTFSymbol\textcirceq{"2257} \DeclareUTFCommand\textncirceq{\textlstrikethru\textcirceq} \DeclareUTFSymbol\texthateq{"2259} \DeclareUTFCommand\textnhateq{\textlstrikethru\texthateq} \DeclareUTFSymbol\texttriangleeq{"225C} \DeclareUTFSymbol\textneq{"2260} \DeclareUTFSymbol\textne{"2260} \DeclareUTFSymbol\textequiv{"2261} \DeclareUTFSymbol\textnequiv{"2262} \DeclareUTFSymbol\textleq{"2264} \DeclareUTFSymbol\textle{"2264} \DeclareUTFSymbol\textgeq{"2265} \DeclareUTFSymbol\textge{"2265} \DeclareUTFSymbol\textleqq{"2266} \DeclareUTFCommand\textnleqq{\textlstrikethru\textleqq} \DeclareUTFSymbol\textgeqq{"2267} \DeclareUTFCommand\textngeqq{\textlstrikethru\textgeqq} \DeclareUTFSymbol\textlneqq{"2268} \DeclareUTFSymbol\textgneqq{"2269} \DeclareUTFSymbol\textll{"226A} \DeclareUTFCommand\textnll{\textlstrikethru\textll} \DeclareUTFSymbol\textgg{"226B} \DeclareUTFCommand\textngg{\textlstrikethru\textgg} \DeclareUTFSymbol\textbetween{"226C} \DeclareUTFSymbol\textnless{"226E} \DeclareUTFSymbol\textngtr{"226F} \DeclareUTFSymbol\textnleq{"2270} \DeclareUTFSymbol\textngeq{"2271} \DeclareUTFSymbol\textlesssim{"2272} \DeclareUTFSymbol\textgtrsim{"2273} \DeclareUTFSymbol\textnlesssim{"2274} \DeclareUTFSymbol\textngtrsim{"2275} \DeclareUTFSymbol\textlessgtr{"2276} \DeclareUTFSymbol\textgtrless{"2277} \DeclareUTFSymbol\textngtrless{"2278} \DeclareUTFSymbol\textnlessgtr{"2279} \DeclareUTFSymbol\textprec{"227A} \DeclareUTFSymbol\textsucc{"227B} \DeclareUTFSymbol\textpreccurlyeq{"227C} \DeclareUTFSymbol\textsucccurlyeq{"227D} \DeclareUTFSymbol\textprecsim{"227E} \DeclareUTFCommand\textnprecsim{\textlstrikethru\textprecsim} \DeclareUTFSymbol\textsuccsim{"227F} \DeclareUTFCommand\textnsuccsim{\textlstrikethru\textsuccsim} \DeclareUTFSymbol\textnprec{"2280} \DeclareUTFSymbol\textnsucc{"2281} \DeclareUTFSymbol\textsubset{"2282} \DeclareUTFSymbol\textsupset{"2283} \DeclareUTFSymbol\textnsubset{"2284} \DeclareUTFSymbol\textnsupset{"2285} \DeclareUTFSymbol\textsubseteq{"2286} \DeclareUTFSymbol\textsupseteq{"2287} \DeclareUTFSymbol\textnsubseteq{"2288} \DeclareUTFSymbol\textnsupseteq{"2289} \DeclareUTFSymbol\textsubsetneq{"228A} \DeclareUTFSymbol\textsupsetneq{"228B} \DeclareUTFSymbol\textcupdot{"228D} \DeclareUTFSymbol\textcupplus{"228E} \DeclareUTFSymbol\textsqsubset{"228F} \DeclareUTFCommand\textnsqsubset{\textlstrikethru\textsqsubset} \DeclareUTFSymbol\textsqsupset{"2290} \DeclareUTFCommand\textnsqsupset{\textlstrikethru\textsqsupset} \DeclareUTFSymbol\textsqsubseteq{"2291} \DeclareUTFCommand\textnsqsubseteq{\textlstrikethru\textsqsubseteq} \DeclareUTFSymbol\textsqsupseteq{"2292} \DeclareUTFCommand\textnsqsupseteq{\textlstrikethru\textsqsupseteq} \DeclareUTFSymbol\textsqcap{"2293} \DeclareUTFSymbol\textsqcup{"2294} \DeclareUTFSymbol\textoplus{"2295} \DeclareUTFSymbol\textominus{"2296} \DeclareUTFSymbol\textotimes{"2297} \DeclareUTFSymbol\textoslash{"2298} \DeclareUTFSymbol\textodot{"2299} \DeclareUTFSymbol\textcircledcirc{"229A} \DeclareUTFSymbol\textcircledast{"229B} \DeclareUTFSymbol\textcircleddash{"229D} \DeclareUTFSymbol\textboxplus{"229E} \DeclareUTFSymbol\textboxminus{"229F} \DeclareUTFSymbol\textboxtimes{"22A0} \DeclareUTFSymbol\textboxdot{"22A1} \DeclareUTFSymbol\textvdash{"22A2} \DeclareUTFSymbol\textdashv{"22A3} \DeclareUTFCommand\textndashv{\textlstrikethru\textdashv} \DeclareUTFSymbol\texttop{"22A4} \DeclareUTFCommand\textndownvdash{\textlstrikethru\texttop} \DeclareUTFSymbol\textbot{"22A5} \DeclareUTFCommand\textnupvdash{\textlstrikethru\textbot} \DeclareUTFSymbol\textvDash{"22A8} \DeclareUTFSymbol\textVdash{"22A9} \DeclareUTFSymbol\textVvdash{"22AA} \DeclareUTFCommand\textnVvash{\textlstrikethru\textVvdash} \DeclareUTFSymbol\textVDash{"22AB} \DeclareUTFSymbol\textnvdash{"22AC} \DeclareUTFSymbol\textnvDash{"22AD} \DeclareUTFSymbol\textnVdash{"22AE} \DeclareUTFSymbol\textnVDash{"22AF} \DeclareUTFSymbol\textlhd{"22B2} \DeclareUTFSymbol\textrhd{"22B3} \DeclareUTFSymbol\textunlhd{"22B4} \DeclareUTFSymbol\textunrhd{"22B5} \DeclareUTFSymbol\textmultimapdotbothA{"22B6} \DeclareUTFSymbol\textmultimapdotbothB{"22B7} \DeclareUTFSymbol\textmultimap{"22B8} \DeclareUTFSymbol\textveebar{"22BB} \DeclareUTFSymbol\textbarwedge{"22BC} \DeclareUTFSymbol\textstar{"22C6} \DeclareUTFSymbol\textdivideontimes{"22C7} \DeclareUTFSymbol\textbowtie{"22C8} \DeclareUTFSymbol\textltimes{"22C9} \DeclareUTFSymbol\textrtimes{"22CA} \DeclareUTFSymbol\textleftthreetimes{"22CB} \DeclareUTFSymbol\textrightthreetimes{"22CC} \DeclareUTFSymbol\textbacksimeq{"22CD} \DeclareUTFCommand\textnbacksimeq{\textlstrikethru\textbacksimeq} \DeclareUTFSymbol\textcurlyvee{"22CE} \DeclareUTFSymbol\textcurlywedge{"22CF} \DeclareUTFSymbol\textSubset{"22D0} \DeclareUTFCommand\textnSubset{\textlstrikethru\textSubset} \DeclareUTFSymbol\textSupset{"22D1} \DeclareUTFCommand\textnSupset{\textlstrikethru\textSupset} \DeclareUTFSymbol\textCap{"22D2} \DeclareUTFSymbol\textCup{"22D3} \DeclareUTFSymbol\textpitchfork{"22D4} \DeclareUTFSymbol\textlessdot{"22D6} \DeclareUTFSymbol\textgtrdot{"22D7} \DeclareUTFSymbol\textlll{"22D8} \DeclareUTFSymbol\textggg{"22D9} \DeclareUTFSymbol\textlesseqgtr{"22DA} \DeclareUTFSymbol\textgtreqless{"22DB} \DeclareUTFSymbol\textcurlyeqprec{"22DE} \DeclareUTFCommand\textncurlyeqprec{\textlstrikethru\textcurlyeqprec} \DeclareUTFSymbol\textcurlyeqsucc{"22DF} \DeclareUTFCommand\textncurlyeqsucc{\textlstrikethru\textcurlyeqsucc} \DeclareUTFSymbol\textnpreccurlyeq{"22E0} \DeclareUTFSymbol\textnsucccurlyeq{"22E1} \DeclareUTFSymbol\textnqsubseteq{"22E2} \DeclareUTFSymbol\textnqsupseteq{"22E3} \DeclareUTFSymbol\textsqsubsetneq{"22E4} \DeclareUTFSymbol\textsqsupsetneq{"22E5} \DeclareUTFSymbol\textlnsim{"22E6} \DeclareUTFSymbol\textgnsim{"22E7} \DeclareUTFSymbol\textprecnsim{"22E8} \DeclareUTFSymbol\textsuccnsim{"22E9} \DeclareUTFSymbol\textntriangleleft{"22EA} \DeclareUTFSymbol\textntriangleright{"22EB} \DeclareUTFSymbol\textntrianglelefteq{"22EC} \DeclareUTFSymbol\textntrianglerighteq{"22ED} \DeclareUTFSymbol\textvdots{"22EE} \DeclareUTFSymbol\textcdots{"22EF} \DeclareUTFSymbol\textudots{"22F0} \DeclareUTFSymbol\textddots{"22F1} \DeclareUTFSymbol\textbarin{"22F6} \DeclareUTFSymbol\textdiameter{"2300} \DeclareUTFSymbol\textbackneg{"2310} \DeclareUTFSymbol\textwasylozenge{"2311} \DeclareUTFSymbol\textinvbackneg{"2319} \DeclareUTFSymbol\textclock{"231A} \DeclareUTFSymbol\textulcorner{"231C} \DeclareUTFSymbol\texturcorner{"231D} \DeclareUTFSymbol\textllcorner{"231E} \DeclareUTFSymbol\textlrcorner{"231F} \DeclareUTFSymbol\textfrown{"2322} \DeclareUTFSymbol\textsmile{"2323} \DeclareUTFSymbol\textKeyboard{"2328} \DeclareUTFSymbol\textlangle{"2329} \DeclareUTFSymbol\textrangle{"232A} \DeclareUTFSymbol\textAPLinv{"2339} \DeclareUTFSymbol\textTumbler{"233C} \DeclareUTFSymbol\textstmaryrdbaro{"233D} \DeclareUTFSymbol\textnotslash{"233F} \DeclareUTFSymbol\textnotbackslash{"2340} \DeclareUTFSymbol\textboxbackslash{"2342} \DeclareUTFSymbol\textAPLleftarrowbox{"2347} \DeclareUTFSymbol\textAPLrightarrowbox{"2348} \DeclareUTFSymbol\textAPLuparrowbox{"2350} \DeclareUTFSymbol\textAPLdownarrowbox{"2357} \DeclareUTFSymbol\textAPLinput{"235E} \DeclareUTFSymbol\textRequest{"2370} \DeclareUTFSymbol\textBeam{"2393} \DeclareUTFSymbol\texthexagon{"2394} \DeclareUTFSymbol\textAPLbox{"2395} \DeclareUTFSymbol\textForwardToIndex{"23ED} \DeclareUTFSymbol\textRewindToIndex{"23EE} \DeclareUTFSymbol\textbbslash{"244A} \DeclareUTFSymbol\textCircledA{"24B6} \DeclareUTFSymbol\textCleaningF{"24BB} \DeclareUTFCommand\textCleaningFF{\b\textCleaningF} \DeclareUTFSymbol\textCleaningP{"24C5} \DeclareUTFCommand\textCleaningPP{\b\textCleaningP} \DeclareUTFSymbol\textCuttingLine{"2504} \DeclareUTFSymbol\textUParrow{"25B2} \DeclareUTFSymbol\textbigtriangleup{"25B3} \DeclareUTFSymbol\textForward{"25B6} \DeclareUTFSymbol\texttriangleright{"25B7} \DeclareUTFSymbol\textRHD{"25BA} \DeclareUTFSymbol\textDOWNarrow{"25BC} \DeclareUTFSymbol\textbigtriangledown{"25BD} \DeclareUTFSymbol\textRewind{"25C0} \DeclareUTFSymbol\texttriangleleft{"25C1} \DeclareUTFSymbol\textLHD{"25C4} \DeclareUTFSymbol\textdiamond{"25C7} \DeclareUTFSymbol\textlozenge{"25CA} \DeclareUTFSymbol\textLEFTCIRCLE{"25D6} \DeclareUTFSymbol\textRIGHTCIRCLE{"25D7} \DeclareUTFSymbol\textboxbar{"25EB} \DeclareUTFSymbol\textCloud{"2601} \DeclareUTFSymbol\textFiveStar{"2605} \DeclareUTFSymbol\textFiveStarOpen{"2606} \DeclareUTFSymbol\textPhone{"260E} \DeclareUTFSymbol\textboxempty{"2610} \DeclareUTFSymbol\textCheckedbox{"2611} \DeclareUTFSymbol\textCrossedbox{"2612} \DeclareUTFSymbol\textCoffeecup{"2615} \DeclareUTFSymbol\textHandCuffLeft{"261A} \DeclareUTFSymbol\textHandCuffRight{"261B} \DeclareUTFSymbol\textHandLeft{"261C} \DeclareUTFSymbol\textHandRight{"261E} \DeclareUTFSymbol\textRadioactivity{"2622} \DeclareUTFSymbol\textBiohazard{"2623} \DeclareUTFSymbol\textAnkh{"2625} \DeclareUTFSymbol\textYinYang{"262F} \DeclareUTFSymbol\textfrownie{"2639} \DeclareUTFSymbol\textsmiley{"263A} \DeclareUTFSymbol\textblacksmiley{"263B} \DeclareUTFSymbol\textsun{"263C} \DeclareUTFSymbol\textleftmoon{"263D} \DeclareUTFSymbol\textrightmoon{"263E} \DeclareUTFSymbol\textmercury{"263F} \DeclareUTFSymbol\textPUfemale{"2640} \DeclareUTFSymbol\textearth{"2641} \DeclareUTFSymbol\textmale{"2642} \DeclareUTFSymbol\textjupiter{"2643} \DeclareUTFSymbol\textsaturn{"2644} \DeclareUTFSymbol\texturanus{"2645} \DeclareUTFSymbol\textneptune{"2646} \DeclareUTFSymbol\textpluto{"2647} \DeclareUTFSymbol\textaries{"2648} \DeclareUTFSymbol\texttaurus{"2649} \DeclareUTFSymbol\textgemini{"264A} \DeclareUTFSymbol\textcancer{"264B} \DeclareUTFSymbol\textleo{"264C} \DeclareUTFSymbol\textvirgo{"264D} \DeclareUTFSymbol\textlibra{"264E} \DeclareUTFSymbol\textscorpio{"264F} \DeclareUTFSymbol\textsagittarius{"2650} \DeclareUTFSymbol\textcapricornus{"2651} \DeclareUTFSymbol\textaquarius{"2652} \DeclareUTFSymbol\textpisces{"2653} \DeclareUTFSymbol\textspadesuitblack{"2660} \DeclareUTFSymbol\textheartsuitwhite{"2661} \DeclareUTFSymbol\textdiamondsuitwhite{"2662} \DeclareUTFSymbol\textclubsuitblack{"2663} \DeclareUTFSymbol\textspadesuitwhite{"2664} \DeclareUTFSymbol\textheartsuitblack{"2665} \DeclareUTFSymbol\textdiamondsuitblack{"2666} \DeclareUTFSymbol\textclubsuitwhite{"2667} \DeclareUTFSymbol\textquarternote{"2669} \DeclareUTFSymbol\texttwonotes{"266B} \DeclareUTFSymbol\textsixteenthnote{"266C} \DeclareUTFSymbol\textflat{"266D} \DeclareUTFSymbol\textnatural{"266E} \DeclareUTFSymbol\textsharp{"266F} \DeclareUTFSymbol\textrecycle{"2672} \DeclareUTFSymbol\textWheelchair{"267F} \DeclareUTFSymbol\textFlag{"2691} \DeclareUTFSymbol\textMineSign{"2692} \DeclareUTFSymbol\textdsmilitary{"2694} \DeclareUTFSymbol\textdsmedical{"2695} \DeclareUTFSymbol\textdsjuridical{"2696} \DeclareUTFSymbol\textdschemical{"2697} \DeclareUTFSymbol\textdsbiological{"2698} \DeclareUTFSymbol\textdscommercial{"269A} \DeclareUTFSymbol\textmanstar{"269D} \DeclareUTFSymbol\textdanger{"26A0} \DeclareUTFSymbol\textFemaleFemale{"26A2} \DeclareUTFSymbol\textMaleMale{"26A3} \DeclareUTFSymbol\textFemaleMale{"26A4} \DeclareUTFSymbol\textHermaphrodite{"26A5} \DeclareUTFSymbol\textNeutral{"26AA} \DeclareUTFSymbol\textPUuncrfemale{"26B2} \DeclareUTFSymbol\texthexstar{"26B9} \DeclareUTFSymbol\textSoccerBall{"26BD} \DeclareUTFSymbol\textSunCload{"26C5} \DeclareUTFSymbol\textRain{"26C6} \DeclareUTFSymbol\textnoway{"26D4} \DeclareUTFSymbol\textMountain{"26F0} \DeclareUTFSymbol\textTent{"26FA} \DeclareUTFSymbol\textScissorRightBrokenBottom{"2701} \DeclareUTFSymbol\textScissorRight{"2702} \DeclareUTFSymbol\textScissorRightBrokenTop{"2703} \DeclareUTFSymbol\textScissorHollowRight{"2704} \DeclareUTFSymbol\textPhoneHandset{"2706} \DeclareUTFSymbol\textTape{"2707} \DeclareUTFSymbol\textPlane{"2708} \DeclareUTFSymbol\textEnvelope{"2709} \DeclareUTFSymbol\textPeace{"270C} \DeclareUTFSymbol\textWritingHand{"270D} \DeclareUTFSymbol\textPencilRightDown{"270E} \DeclareUTFSymbol\textPencilRight{"270F} \DeclareUTFSymbol\textPencilRightUp{"2710} \DeclareUTFSymbol\textNibRight{"2711} \DeclareUTFSymbol\textNibSolidRight{"2712} \DeclareUTFSymbol\textCheckmark{"2713} \DeclareUTFSymbol\textCheckmarkBold{"2714} \DeclareUTFSymbol\textXSolid{"2715} \DeclareUTFSymbol\textXSolidBold{"2716} \DeclareUTFSymbol\textXSolidBrush{"2717} \DeclareUTFSymbol\textPlusOutline{"2719} \DeclareUTFSymbol\textPlus{"271A} \DeclareUTFSymbol\textPlusThinCenterOpen{"271B} \DeclareUTFSymbol\textPlusCenterOpen{"271C} \DeclareUTFSymbol\textCross{"271D} \DeclareUTFSymbol\textCrossOpenShadow{"271E} \DeclareUTFSymbol\textCrossOutline{"271F} \DeclareUTFSymbol\textCrossMaltese{"2720} \DeclareUTFSymbol\textDavidStar{"2721} \DeclareUTFSymbol\textFourAsterisk{"2722} \DeclareUTFSymbol\textJackStar{"2723} \DeclareUTFSymbol\textJackStarBold{"2724} \DeclareUTFSymbol\textClowerTips{"2725} \DeclareUTFSymbol\textFourStar{"2726} \DeclareUTFSymbol\textFourStarOpen{"2727} \DeclareUTFSymbol\textFiveStarOpenCircled{"272A} \DeclareUTFSymbol\textFiveStarCenterOpen{"272B} \DeclareUTFSymbol\textFiveStarOpenDotted{"272C} \DeclareUTFSymbol\textFiveStarOutline{"272D} \DeclareUTFSymbol\textFiveStarOutlineHeavy{"272E} \DeclareUTFSymbol\textFiveStarConvex{"272F} \DeclareUTFSymbol\textFiveStarShadow{"2730} \DeclareUTFSymbol\textAsteriskBold{"2731} \DeclareUTFSymbol\textAsteriskCenterOpen{"2732} \DeclareUTFSymbol\textEightStarTaper{"2734} \DeclareUTFSymbol\textEightStarConvex{"2735} \DeclareUTFSymbol\textSixStar{"2736} \DeclareUTFSymbol\textEightStar{"2737} \DeclareUTFSymbol\textEightStarBold{"2738} \DeclareUTFSymbol\textTwelveStar{"2739} \DeclareUTFSymbol\textSixteenStarLight{"273A} \DeclareUTFSymbol\textSixFlowerPetalRemoved{"273B} \DeclareUTFSymbol\textSixFlowerOpenCenter{"273C} \DeclareUTFSymbol\textAsterisk{"273D} \DeclareUTFSymbol\textSixFlowerAlternate{"273E} \DeclareUTFSymbol\textFiveFlowerPetal{"273F} \DeclareUTFSymbol\textFiveFlowerOpen{"2740} \DeclareUTFSymbol\textEightFlowerPetal{"2741} \DeclareUTFSymbol\textSunshineOpenCircled{"2742} \DeclareUTFSymbol\textSixFlowerAltPetal{"2743} \DeclareUTFSymbol\textSnowflakeChevron{"2744} \DeclareUTFSymbol\textSnowflake{"2745} \DeclareUTFSymbol\textSnowflakeChevronBold{"2746} \DeclareUTFSymbol\textSparkle{"2747} \DeclareUTFSymbol\textSparkleBold{"2748} \DeclareUTFSymbol\textAsteriskRoundedEnds{"2749} \DeclareUTFSymbol\textEightFlowerPetalRemoved{"274A} \DeclareUTFSymbol\textEightAsterisk{"274B} \DeclareUTFSymbol\textCircleShadow{"274D} \DeclareUTFSymbol\textSquareShadowBottomRight{"274F} \DeclareUTFSymbol\textSquareTopRight{"2750} \DeclareUTFSymbol\textSquareCastShadowBottomRight{"2751} \DeclareUTFSymbol\textSquareCastShadowTopRight{"2752} \DeclareUTFSymbol\textDiamondSolid{"2756} \DeclareUTFSymbol\textRectangleThin{"2758} \DeclareUTFSymbol\textRectangle{"2759} \DeclareUTFSymbol\textRectangleBold{"275A} \DeclareUTFSymbol\textperp{"27C2} \DeclareUTFCommand\textnotperp{\textlstrikethru\textperp} \DeclareUTFSymbol\textveedot{"27C7} \DeclareUTFSymbol\textwedgedot{"27D1} \DeclareUTFSymbol\textleftspoon{"27DC} \DeclareUTFSymbol\textlbrackdbl{"27E6} \DeclareUTFSymbol\textrbrackdbl{"27E7} \DeclareUTFSymbol\textcirclearrowleft{"27F2} \DeclareUTFSymbol\textcirclearrowright{"27F3} \DeclareUTFSymbol\textlongleftarrow{"27F5} \DeclareUTFSymbol\textlongrightarrow{"27F6} \DeclareUTFSymbol\textlongleftrightarrow{"27F7} \DeclareUTFSymbol\textLongleftarrow{"27F8} \DeclareUTFSymbol\textLongrightarrow{"27F9} \DeclareUTFSymbol\textLongleftrightarrow{"27FA} \DeclareUTFSymbol\textlongmapsto{"27FC} \DeclareUTFSymbol\textLongmapsfrom{"27FD} \DeclareUTFSymbol\textLongmapsto{"27FE} \DeclareUTFSymbol\textnwsearrow{"2921} \DeclareUTFSymbol\textneswarrow{"2922} \DeclareUTFSymbol\textlhooknwarrow{"2923} \DeclareUTFSymbol\textrhooknearrow{"2924} \DeclareUTFSymbol\textlhooksearrow{"2925} \DeclareUTFSymbol\textrhookswarrow{"2926} \DeclareUTFSymbol\textleadsto{"2933} \DeclareUTFSymbol\textrcurvearrowne{"2934} \DeclareUTFSymbol\textlcurvearrowse{"2935} \DeclareUTFSymbol\textlcurvearrowsw{"2936} \DeclareUTFSymbol\textrcurvearrowse{"2937} \DeclareUTFSymbol\textlcurvearrowdown{"2938} \DeclareUTFSymbol\textrcurvearrowdown{"2939} \DeclareUTFSymbol\textrcurvearrowleft{"293A} \DeclareUTFSymbol\textrcurvearrowright{"293B} \DeclareUTFSymbol\textleftrightharpoon{"294A} \DeclareUTFSymbol\textrightleftharpoon{"294B} \DeclareUTFSymbol\textupdownharpoonrightleft{"294C} \DeclareUTFSymbol\textupdownharpoonleftright{"294D} \DeclareUTFSymbol\textleftleftharpoons{"2962} \DeclareUTFSymbol\textupupharpoons{"2963} \DeclareUTFSymbol\textrightrightharpoons{"2964} \DeclareUTFSymbol\textdowndownharpoons{"2965} \DeclareUTFSymbol\textleftbarharpoon{"296A} \DeclareUTFSymbol\textbarleftharpoon{"296B} \DeclareUTFSymbol\textrightbarharpoon{"296C} \DeclareUTFSymbol\textbarrightharpoon{"296D} \DeclareUTFSymbol\textupdownharpoons{"296E} \DeclareUTFSymbol\textdownupharpoons{"296F} \DeclareUTFSymbol\textllparenthesis{"2987} \DeclareUTFSymbol\textrrparenthesis{"2988} \DeclareUTFSymbol\textinvdiameter{"29B0} \DeclareUTFSymbol\textobar{"29B6} \DeclareUTFSymbol\textobslash{"29B8} \DeclareUTFSymbol\textobot{"29BA} \DeclareUTFSymbol\textNoChemicalCleaning{"29BB} \DeclareUTFSymbol\textolessthan{"29C0} \DeclareUTFSymbol\textogreaterthan{"29C1} \DeclareUTFSymbol\textboxslash{"29C4} \DeclareUTFSymbol\textboxbslash{"29C5} \DeclareUTFSymbol\textboxast{"29C6} \DeclareUTFSymbol\textboxcircle{"29C7} \DeclareUTFSymbol\textboxbox{"29C8} \DeclareUTFSymbol\textValve{"29D3} \DeclareUTFSymbol\textmultimapboth{"29DF} \DeclareUTFSymbol\textshuffle{"29E2} \DeclareUTFSymbol\textuplus{"2A04} \DeclareUTFSymbol\textbigdoublewedge{"2A07} \DeclareUTFSymbol\textbigdoublevee{"2A08} \DeclareUTFSymbol\textJoin{"2A1D} \DeclareUTFSymbol\textfatsemi{"2A1F} \DeclareUTFSymbol\textcircplus{"2A22} \DeclareUTFSymbol\textminusdot{"2A2A} \DeclareUTFSymbol\textdottimes{"2A30} \DeclareUTFSymbol\textdtimes{"2A32} \DeclareUTFSymbol\textodiv{"2A38} \DeclareUTFSymbol\textinvneg{"2A3C} \DeclareUTFSymbol\textsqdoublecap{"2A4E} \DeclareUTFSymbol\textcapdot{"2A40} \DeclareUTFSymbol\textsqdoublecup{"2A4F} \DeclareUTFSymbol\textdoublewedge{"2A55} \DeclareUTFSymbol\textdoublevee{"2A56} \DeclareUTFSymbol\textdoublebarwedge{"2A5E} \DeclareUTFSymbol\textveedoublebar{"2A63} \DeclareUTFSymbol\texteqdot{"2A66} \DeclareUTFCommand\textneqdot{\textlstrikethru\texteqdot} \DeclareUTFSymbol\textcoloncolonequals{"2A74} \DeclareUTFSymbol\textleqslant{"2A7D} \DeclareUTFCommand\textnleqslant{\textlstrikethru\textleqslant} \DeclareUTFSymbol\textgeqslant{"2A7E} \DeclareUTFCommand\textngeqslant{\textlstrikethru\textgeqslant} \DeclareUTFSymbol\textlessapprox{"2A85} \DeclareUTFCommand\textnlessapprox{\textlstrikethru\textlessapprox} \DeclareUTFSymbol\textgtrapprox{"2A86} \DeclareUTFCommand\textngtrapprox{\textlstrikethru\textgtrapprox} \DeclareUTFSymbol\textlneq{"2A87} \DeclareUTFSymbol\textgneq{"2A88} \DeclareUTFSymbol\textlnapprox{"2A89} \DeclareUTFSymbol\textgnapprox{"2A8A} \DeclareUTFSymbol\textlesseqqgtr{"2A8B} \DeclareUTFSymbol\textgtreqqless{"2A8C} \DeclareUTFSymbol\texteqslantless{"2A95} \DeclareUTFSymbol\texteqslantgtr{"2A96} \DeclareUTFSymbol\textleftslice{"2AA6} \DeclareUTFSymbol\textrightslice{"2AA7} \DeclareUTFSymbol\textpreceq{"2AAF} \DeclareUTFCommand\textnpreceq{\textlstrikethru\textpreceq} \DeclareUTFSymbol\textsucceq{"2AB0} \DeclareUTFCommand\textnsucceq{\textlstrikethru\textsucceq} \DeclareUTFSymbol\textprecneq{"2AB1} \DeclareUTFSymbol\textsuccneq{"2AB2} \DeclareUTFSymbol\textpreceqq{"2AB3} \DeclareUTFCommand\textnpreceqq{\textlstrikethru\textpreceqq} \DeclareUTFSymbol\textsucceqq{"2AB4} \DeclareUTFCommand\textnsucceqq{\textlstrikethru\textsucceqq} \DeclareUTFSymbol\textprecneqq{"2AB5} \DeclareUTFSymbol\textsuccneqq{"2AB6} \DeclareUTFSymbol\textprecapprox{"2AB7} \DeclareUTFCommand\textnprecapprox{\textlstrikethru\textprecapprox} \DeclareUTFSymbol\textsuccapprox{"2AB8} \DeclareUTFCommand\textnsuccapprox{\textlstrikethru\textsuccapprox} \DeclareUTFSymbol\textprecnapprox{"2AB9} \DeclareUTFSymbol\textsuccnapprox{"2ABA} \DeclareUTFSymbol\textsubseteqq{"2AC5} \DeclareUTFCommand\textnsubseteqq{\textlstrikethru\textsubseteqq} \DeclareUTFSymbol\textsupseteqq{"2AC6} \DeclareUTFCommand\textnsupseteqq{\textlstrikethru\textsupseteqq} \DeclareUTFSymbol\textdashV{"2AE3} \DeclareUTFCommand\textndashV{\textlstrikethru\textdashV} \DeclareUTFSymbol\textDashv{"2AE4} \DeclareUTFCommand\textnDashv{\textlstrikethru\textDashv} \DeclareUTFSymbol\textDashV{"2AE5} \DeclareUTFCommand\textnDashV{\textlstrikethru\textDashV} \DeclareUTFSymbol\textdownmodels{"2AEA} \DeclareUTFCommand\textndownmodels{\textlstrikethru\textdownmodels} \DeclareUTFSymbol\textupmodels{"2AEB} \DeclareUTFCommand\textnupmodels{\textlstrikethru\textupmodels} \DeclareUTFSymbol\textupspoon{"2AEF} \DeclareUTFSymbol\textinterleave{"2AF4} \DeclareUTFSymbol\textsslash{"2AFD} \DeclareUTFSymbol\textpentagon{"2B20} \DeclareUTFSymbol\textvarhexagon{"2B21} \DeclareUTFSymbol\textjinferior{"2C7C} \DeclareUTFSymbol\textslashdiv{"2E13} \DeclareUTFSymbol\textinterrobangdown{"2E18} \DeclareUTFSymbol\textfivedots{"2E2D} \DeclareUTFSymbol\textPUheng{"A727} \DeclareUTFSymbol\textPUlhookfour{"A72C} \DeclareUTFSymbol\textPUscf{"A730} \DeclareUTFSymbol\textPUaolig{"A735} \DeclareUTFSymbol\textoo{"A74F} \DeclareUTFSymbol\textcircumlow{"A788} \DeclareUTFSymbol\textfi{"FB01} \DeclareUTFSymbol\textfl{"FB02} \DeclareUTFSymbol\textGaPa{"1D13B} \DeclareUTFSymbol\textHaPa{"1D13C} \DeclareUTFSymbol\textViPa{"1D13D} \DeclareUTFSymbol\textAcPa{"1D13E} \DeclareUTFSymbol\textSePa{"1D13F} \DeclareUTFSymbol\textZwPa{"1D140} \DeclareUTFSymbol\textfullnote{"1D15D} \DeclareUTFSymbol\texthalfnote{"1D15E} \DeclareUTFSymbol\textVier{"1D15F} \DeclareUTFSymbol\textAcht{"1D160} \DeclareUTFSymbol\textSech{"1D161} \DeclareUTFSymbol\textZwdr{"1D162} \DeclareUTFSymbol\textMundus{"1F30D} \DeclareUTFSymbol\textMoon{"1F319} \DeclareUTFSymbol\textManFace{"1F468} \DeclareUTFSymbol\textWomanFace{"1F469} \DeclareUTFSymbol\textFax{"1F4E0} \DeclareUTFSymbol\textFire{"1F525} \DeclareUTFSymbol\textBicycle{"1F6B2} \DeclareUTFSymbol\textGentsroom{"1F6B9} \DeclareUTFSymbol\textLadiesroom{"1F6BA} \DeclareUTFCommand\textcopyleft{\textcircled\textrevc} \DeclareUTFCommand\textccsa{\textcircled\textcirclearrowleft} \DeclareUTFSymbol\textglqq{"201E} \DeclareUTFSymbol\textgrqq{"201C} \DeclareUTFSymbol\textglq{"201A} \DeclareUTFSymbol\textgrq{"2018} \DeclareUTFSymbol\textflqq{"00AB} \DeclareUTFSymbol\textfrqq{"00BB} \DeclareUTFSymbol\textflq{"2039} \DeclareUTFSymbol\textfrq{"203A} \DeclareUTFSymbol\textneg{"00AC} \DeclareUTFSymbol\textcdot{"00B7} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<@@=xeCJK> % \end{macrocode} % % \subsection{\pkg{xeCJK.cfg}} % % \begin{macrocode} %<*config> % \end{macrocode} % % 预设的配置文件 \file{xeCJK.cfg} 为一个空文件。可以在里面增加设置,然后保存到 % 本地目录下面。 % \begin{macrocode} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \Finale % % \DisableImplementation % % \begin{implementation} % % \makeatletter % \let\special@index\@gobble % \makeatother % % \section{例子} % % \subsection{\pkg{xeCJK-example-autofake.tex}} % % \begin{macrocode} %<*ex-autofake> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage[AutoFakeBold,AutoFakeSlant]{xeCJK} \setCJKmainfont[BoldFont=simhei.ttf, SlantedFont=simkai.ttf]{simsun.ttc} \setCJKsansfont[AutoFakeSlant=false, BoldFont=simhei.ttf, SlantedFont=simkai.ttf]{simsun.ttc} \setCJKmonofont[ItalicFont=simkai.ttf]{simsun.ttc} \begin{document} \centering \begin{tabular}{lllll} \hline {\bf rm} & md & up & \verb|\rmfamily\mdseries\upshape| & {\rmfamily\mdseries\upshape English 中文字体} \\ & md & it & \verb|\rmfamily\mdseries\itshape| & {\rmfamily\mdseries\itshape English 中文字体} \\ & md & sl & \verb|\rmfamily\mdseries\slshape| & {\rmfamily\mdseries\slshape English 中文字体} \\ \cline{2-5} & bf & up & \verb|\rmfamily\bfseries\upshape| & {\rmfamily\bfseries\upshape English 中文字体} \\ & bf & it & \verb|\rmfamily\bfseries\itshape| & {\rmfamily\bfseries\itshape English 中文字体} \\ & bf & sl & \verb|\rmfamily\bfseries\slshape| & {\rmfamily\bfseries\slshape English 中文字体} \\ \hline {\bf sf} & md & up & \verb|\sffamily\mdseries\upshape| & {\sffamily\mdseries\upshape English 中文字体} \\ & md & it & \verb|\sffamily\mdseries\itshape| & {\sffamily\mdseries\itshape English 中文字体} \\ & md & sl & \verb|\sffamily\mdseries\slshape| & {\sffamily\mdseries\slshape English 中文字体} \\ \cline{2-5} & bf & up & \verb|\sffamily\bfseries\upshape| & {\sffamily\bfseries\upshape English 中文字体} \\ & bf & it & \verb|\sffamily\bfseries\itshape| & {\sffamily\bfseries\itshape English 中文字体} \\ & bf & sl & \verb|\sffamily\bfseries\slshape| & {\sffamily\bfseries\slshape English 中文字体} \\ \hline {\bf tt} & md & up & \verb|\ttfamily\mdseries\upshape| & {\ttfamily\mdseries\upshape English 中文字体} \\ & md & it & \verb|\ttfamily\mdseries\itshape| & {\ttfamily\mdseries\itshape English 中文字体} \\ & md & sl & \verb|\ttfamily\mdseries\slshape| & {\ttfamily\mdseries\slshape English 中文字体} \\ \cline{2-5} & bf & up & \verb|\ttfamily\bfseries\upshape| & {\ttfamily\bfseries\upshape English 中文字体} \\ & bf & it & \verb|\ttfamily\bfseries\itshape| & {\ttfamily\bfseries\itshape English 中文字体} \\ & bf & sl & \verb|\ttfamily\bfseries\slshape| & {\ttfamily\bfseries\slshape English 中文字体} \\ \hline \end{tabular} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-fallback.tex}} % % \begin{macrocode} %<*ex-fallback> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage[AutoFallBack]{xeCJK} \usepackage{xeCJKfntef} \usepackage{array} \setCJKmainfont[AutoFakeBold,AutoFakeSlant]{KaiTi_GB2312} \setCJKfallbackfamilyfont{\CJKrmdefault}[BoldFont=SimHei] { [SlantedFont=FangSong]{SimSun} , [BoldFont=*] {SimSun-ExtB} } \begin{document} 漢字源𣴑考 \textbf{漢字源𣴑考} \textsl{漢字源𣴑考} \CJKunderwave{漢字源𣴑考} \begin{table}[ht] \caption{生僻字测试} \medskip\centering \begin{tabular}{*4{|c>{\ttfamily U+}l}|} 㐀 & 3400 & 㐁 & 3401 & 㐂 & 3402 & 㐃 & 3403 \\ 㐄 & 3404 & 㐅 & 3405 & 㐆 & 3406 & 㐇 & 3407 \\ 㐈 & 3408 & 㐉 & 3409 & 㐊 & 340A & 㐋 & 340B \\ 㐌 & 340C & 㐍 & 340D & 㐎 & 340E & 㐏 & 341F \\ 㐐 & 3410 & 㐑 & 3411 & 㐒 & 3412 & 㐓 & 3413 \\ 㐔 & 3414 & 㐕 & 3415 & 㐖 & 3416 & 㐗 & 3417 \\ 㐘 & 3418 & 㐙 & 3419 & 㐚 & 341A & 㐛 & 341B \\ 㐜 & 341C & 㐝 & 341D & 㐞 & 341E & 㐟 & 341F \\[1ex] 𠀀 & 20000 & 𠀁 & 20001 & 𠀂 & 20002 & 𠀃 & 20003 \\ 𠀄 & 20004 & 𠀅 & 20005 & 𠀆 & 20006 & 𠀇 & 20007 \\ 𠀈 & 20008 & 𠀉 & 20009 & 𠀊 & 2000A & 𠀋 & 2000B \\ 𠀌 & 2000C & 𠀍 & 2000D & 𠀎 & 2000E & 𠀏 & 2000F \\ 𠀐 & 20010 & 𠀑 & 20011 & 𠀒 & 20012 & 𠀓 & 20013 \\ 𠀔 & 20014 & 𠀕 & 20015 & 𠀖 & 20016 & 𠀗 & 20017 \\ 𠀘 & 20018 & 𠀙 & 20019 & 𠀚 & 2001A & 𠀛 & 2001B \\ 𠀜 & 2001C & 𠀝 & 2001D & 𠀞 & 2001E & 𠀟 & 2001F \\ \end{tabular} \end{table} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-subCJKblock.tex}} % % \begin{macrocode} %<*ex-block> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage{xeCJK} \usepackage{array} \xeCJKDeclareSubCJKBlock{Ext-A} { "3400 -> "4DBF } \xeCJKDeclareSubCJKBlock{Ext-B} { "20000 -> "2A6DF } \xeCJKDeclareSubCJKBlock{Kana} { "3040 -> "309F, "30A0 -> "30FF, "31F0 -> "31FF, } \xeCJKDeclareSubCJKBlock{Hangul}{ "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF } \setCJKmainfont[Ext-A=SimHei,Ext-B=SimSun-ExtB]{SimSun} \setCJKmainfont[Kana]{Meiryo} \setCJKmainfont[Hangul]{Malgun Gothic} \parindent=2em \begin{document} \long\def\showtext{% 中日韩越统一表意文字(英语:CJKV Unified Ideographs),旧称中日韩统一表意文字(英语:CJK Unified Ideographs),也称统一汉字(英语:Unihan),目的是要把分别来自中文、日文、韩文、越文、壮文中,对于相同起源、本义相同、形状一样或稍异的表意文字(主要为汉字,但也有仿汉字如:方块壮字、日文汉字(かんじ / kanji)、韩文汉字(한자 / hanja)、越南的喃字(Chữ Nôm)与越文汉字(Chữ Nho,在越南也称作儒字),应赋予其在ISO 10646及统一码标准中有相同编码。此计划原本只包含中文、日文及韩文中所使用的汉字,是以旧称中日韩统一表意文字(CJK)。后来,此计划加入了越文的喃字,所以合称中日韩越统一表意文字(CJKV)。 CJK統合漢字(シージェーケーとうごうかんじ、CJK Unified Ideographs)は、ISO/IEC 10646 (Universal Multiple-Octet Coded Character Set, 略称 UCS) および Unicodeにて採用されている符号化用漢字集合およびその符号表である。CJK統合漢字の名称は、中国語(Chinese)、日本語(Japanese)、韓国語(Korean)で使われている漢字をひとまとめにしたことからきている。CJK統合漢字の初版である Unified Repertoire and Ordering (URO) 第二版は1992年に制定されたが、1994年にベトナムで使われていた漢字も含めることにしたため、CJKVと呼ばれる事もある。CJKVは、中国語・日本語・韓国語・ベトナム語 (英語: Chinese-Japanese-Korean-Vietnamese) の略。特に、その4言語で共通して使われる、または使われていた文字体系である漢字(チュノムを含む)のこと。ソフトウェアの国際化、中でも文字コードに関する分野で用いられる。 \CJKspace 한중일월 통합 한자(또는 한중일 통합 한자)는 유니코드에 담겨 있는 한자들의 집합으로, 한국, 중국, 일본에서 쓰이는 한자를 묶은 것이기 때문에 머리 글자를 따서 한중일(CJK) 통합 한자라고 불렀는데, 최근에는 베트남에서 쓰이는 한자도 추가되었기에 한중일월(CJKV) 통합 한자로 부르게 되었다. 처음에 유니코드에는 65,536($=2^{16}$)자만 들어갈 수 있었기 때문에, 가장 많은 문자가 배당되는 한자를 위해서 한국, 중국, 일본에서 사용하는 한자 중에 모양이 유사하며 그 뜻이 같은 글자를 같은 코드로 통합했다. 따라서 문자 코드만으로 그 한자가 사용되는 언어를 알아 낼 수 없는데, 다만 중국의 간체자나 번체자, 일본의 구자체나 신자체 등 분명하게 모양이 다른 글자는 별도의 부호를 할당하고 있다. 이런 문자 할당 정책에 반발하여 TRON과 같은 인코딩이 만들어지기도 했으나, 실제로 통합된 한자의 차이가 별로 크지 않기 때문에 문제가 되지 않는다는 의견도 있다. \CJKnospace} \showtext \bigskip \xeCJKCancelSubCJKBlock{Kana,Hangul} \showtext \bigskip \xeCJKRestoreSubCJKBlock{Hangul} \showtext \begin{table}[ht] \caption{生僻字测试} \medskip\centering \begin{tabular}{*4{|c>{\ttfamily U+}l}|} 㐀 & 3400 & 㐁 & 3401 & 㐂 & 3402 & 㐃 & 3403 \\ 㐄 & 3404 & 㐅 & 3405 & 㐆 & 3406 & 㐇 & 3407 \\ 㐈 & 3408 & 㐉 & 3409 & 㐊 & 340A & 㐋 & 340B \\ 㐌 & 340C & 㐍 & 340D & 㐎 & 340E & 㐏 & 341F \\ 㐐 & 3410 & 㐑 & 3411 & 㐒 & 3412 & 㐓 & 3413 \\ 㐔 & 3414 & 㐕 & 3415 & 㐖 & 3416 & 㐗 & 3417 \\ 㐘 & 3418 & 㐙 & 3419 & 㐚 & 341A & 㐛 & 341B \\ 㐜 & 341C & 㐝 & 341D & 㐞 & 341E & 㐟 & 341F \\[1ex] 𠀀 & 20000 & 𠀁 & 20001 & 𠀂 & 20002 & 𠀃 & 20003 \\ 𠀄 & 20004 & 𠀅 & 20005 & 𠀆 & 20006 & 𠀇 & 20007 \\ 𠀈 & 20008 & 𠀉 & 20009 & 𠀊 & 2000A & 𠀋 & 2000B \\ 𠀌 & 2000C & 𠀍 & 2000D & 𠀎 & 2000E & 𠀏 & 2000F \\ 𠀐 & 20010 & 𠀑 & 20011 & 𠀒 & 20012 & 𠀓 & 20013 \\ 𠀔 & 20014 & 𠀕 & 20015 & 𠀖 & 20016 & 𠀗 & 20017 \\ 𠀘 & 20018 & 𠀙 & 20019 & 𠀚 & 2001A & 𠀛 & 2001B \\ 𠀜 & 2001C & 𠀝 & 2001D & 𠀞 & 2001E & 𠀟 & 2001F \\ \end{tabular} \end{table} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % % \subsection{\pkg{xeCJK-example-CJKecglue.tex}} % % \begin{macrocode} %<*ex-ecglue> % \end{macrocode} % % \begin{macrocode} \documentclass{minimal} \usepackage{xeCJK} \setCJKmainfont[BoldFont=SimHei]{SimSun} \long\def\showtext{% 这是 English 中文 {\itshape Chinese} 中文 \TeX\ 间隔 \textit{Italic} 中文\textbf{字体} a 数学 $b$ 数学 $c$ $d$\par 这是English中文{\itshape Chinese}中文\TeX\ 间隔\textit{Italic}中文\textbf{字体}a数学$b$数学$c$ $d$\par This is an example. 这是一个例子} \begin{document} \showtext \hrulefill\bigskip \xeCJKsetup{xCJKecglue=\quad} \showtext \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-boundary-register.tex}} % % \begin{macrocode} %<*ex-boundary-register> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage{xeCJK} \setCJKmainfont{FandolSong-Regular.otf} \setCJKmonofont{FandolFang-Regular.otf} \setlength{\fboxsep}{2pt} \setlength{\fboxrule}{0.4pt} \setlength{\parindent}{0pt} \newcommand\MyBox[1]{\hbox{#1}} \newcommand\MyWrapped[1]{\kern0pt #1\kern0pt} \newcommand\MyStream[1]{\special{xeCJK-boundary-example}#1} \newcommand\MyTransparent{\special{xeCJK-boundary-example}} \newcommand\MyPostTransparent{\hbox{}} \xeCJKsetup { CJKecglue = {\hskip 10pt}, CJKglue = {\hskip 2pt}, experiment/boundary-register = { command = \MyBox, strategy = box }, experiment/boundary-register = { command = \MyWrapped, strategy = wrapped-box }, experiment/boundary-register = { command = \MyStream, strategy = stream }, experiment/boundary-register = { command = \MyTransparent, strategy = transparent }, experiment/boundary-register = { command = \MyPostTransparent, strategy = post-transparent } } \newcommand\BoundaryCell[2] { \begin{tabular}[c]{@{}c@{}} \fbox{\hbox{#1}} \\ \fbox{\hbox{#2}} \end{tabular} } \newcommand\BoundaryRow[4] { \texttt{#1} & \BoundaryCell{#2#3#4}{#2\MyStream{#3}#4} & \BoundaryCell{#2 #3#4}{#2 \MyStream{#3}#4} & \BoundaryCell{#2#3 #4}{#2\MyStream{#3} #4} & \BoundaryCell{#2 #3 #4}{#2 \MyStream{#3} #4} \\[2pt] } \newcommand\TestBoundaryMatrix[1] { \subsection*{\texttt{xCJKecglue=#1}} \xeCJKsetup{xCJKecglue=#1} \begin{tabular}{@{}lcccc@{}} & \texttt{00} & \texttt{10} & \texttt{01} & \texttt{11} \\ \BoundaryRow{西文}{中}{A}{中} \BoundaryRow{中文}{A}{中文}{B} \BoundaryRow{中西}{A}{中A}{B} \BoundaryRow{西中}{A}{A中}{B} \BoundaryRow{公式}{中}{$x$}{中} \end{tabular} } \begin{document} \section*{\normalfont 用户命令的边界间距检查} 每格上方是直接输入,下方是采用 \texttt{stream} 策略注册的命令; 两只方框等宽表示这一格与直接输入一致。中西、西中两行分别只在左边界和 右边界产生中西文间距,用于排除两侧误差互相抵消的情况。公式行故意展示 通用注册不分析命令参数的限制:公式位于命令边界时,还需要专用参数适配, 不能把不等宽的结果当作正确行为。 \begingroup \small \setlength\tabcolsep{2pt} \TestBoundaryMatrix{false} \TestBoundaryMatrix{true} \endgroup \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-checksingle.tex}} % % \begin{macrocode} %<*ex-single> % \end{macrocode} % % \begin{macrocode} \documentclass{minimal} \usepackage{xeCJK} \setCJKmainfont{SimSun} \long\def\showtext{一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。 \[x^2+y^2\] 一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。 $$x^2+y^2$$ 一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。 \begin{equation} x^2+y^2 \end{equation} 一二三四五六七八九十一二三四五六七八九十一二三四五六七八九十。} \begin{document} \hsize=30em \parindent=0pt \showtext \hrulefill\bigskip \xeCJKsetup{CheckSingle} \showtext \hrulefill\bigskip \xeCJKsetup{PlainEquation} \showtext \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-CJKfntef.tex}} % % \begin{macrocode} %<*ex-fntef> % \end{macrocode} % % \begin{macrocode} \documentclass{ctexart} \usepackage{xcolor} \usepackage{xeCJKfntef} \xeCJKDeclareSubCJKBlock{test}{ `殆 , `已 } \setCJKmainfont[test=SimHei]{SimSun} \def\showtext{% 庄子曰:“吾生也有涯,而知也无涯。以有涯随无涯,殆已!已而为知者,殆而已矣!% 为善无近名,为恶无近刑,缘督以为经,可以保身,可以全生,可以养亲,可以尽年。”} \newcommand\CJKfntef[1]{% \csname#1\endcsname{\showtext}\par \csname#1\endcsname*{\showtext}\par \CJKunderdot{\showtext\csname#1\endcsname{\showtext}}\par \CJKunderdot{\showtext\csname#1\endcsname*{\showtext}}\par \csname#1\endcsname{\showtext\CJKunderdot{\showtext}}\par \csname#1\endcsname*{\showtext\CJKunderdot{\showtext}}\par\bigskip} \begin{document} \section{\textsf{xeCJKfntef} 的\CJKunderdot{简单测试文件}} \CJKunderline{汉 字}\CJKunderline{加下划线} \varCJKunderline{汉字}\varCJKunderline{加下划线} \CJKunderanyline{0.5em}{\sixly \kern-.021em\char58 \kern-.021em}{自定义下划线} \CJKunderanyline{0.2em}{\kern-.03em\vtop{\kern.2ex\hrule width.2em\kern 0.11em \hrule height 0.1em}\kern-.03em}{自定义下划线} \CJKunderanyline{0.5em}{-}{\showtext} \CJKunderanysymbol{0.2em}{-}{\showtext} \CJKunderdot{\showtext} \hrule depth \dp\strutbox width 0 pt \relax \begin{CJKfilltwosides}{40mm} 两端分散对齐\\ 分散对齐\\ \CJKunderdot{汉字可加点}\\ \CJKunderline{汉字可加下划线} \end{CJKfilltwosides} \hrule depth \dp\strutbox width 0 pt \relax \CJKfntef{CJKunderline} \CJKfntef{CJKunderwave} \CJKfntef{CJKunderdblline} \CJKfntef{CJKsout} \CJKfntef{CJKxout} \def\Book{\CJKunderwave-} \def\Noun{\CJKunderline-} 如上所述,\Book{中國文學史}原是\Noun{復旦大學出版社}出版的,\Book{中國文學史新著}則由\Noun{上海文藝出版社}出版。現承\Noun{復旦大學出版社}\Noun{賀聖遂}先生的盛情和\Noun{上海文藝出版總社}\Noun{楊益萍}、\Noun{何承偉}先生以及有關負責人\Noun{陳鳴華}先生的高誼,此書增訂本改由兩社聯合出版。\Noun{賀聖遂}先生並與\Noun{杜榮根}副社長、\Noun{韓結根}編審共同擔任此書的責任編輯。對上述諸位先生謹在這裡表示衷心的感謝。對\Book{中國文學史新著}原版的責任編輯\Noun{戴俊}先生(現為\Noun{上海三聯書店}副總經理)也在此一併致謝。 \Noun{與文學史的}\Noun{應有任務}\Noun{對文學發展過程}\Noun{的內在聯繫的描述}\Noun{還有很大的距離}\Noun{至於距離的所在}\Noun{本書原已有說明}\Book{此不贅述}\Book{為了對學術負責}\Book{我們決定重寫一部並迅即出版} \Noun{南朝}\Noun{梁}\Noun{劉勰}\Book{文心雕龍}\Book{養氣} \Noun{鎌池和馬}\Book{魔法禁書目錄}\Book{某科學的超電磁砲} \Noun{西尾維新}\Book{化物語}\Book{偽物語}\Book{貓物語}% \Book{傾物语}\Book{囤物語}\Book{鬼物語}\Book{戀物語}\Book{花物語} \Noun{岡田麿里}\Book{我們仍未知道那天所看見的花的名字。}\Book{絕園的暴風雨} \Noun{虚淵玄}\Book{Fate/Zero}\Book{PSYCHO-PASS}\Book{ALDNOAH.ZERO} \Noun{劉海洋}\Book{\LaTeX 入門} \def\NAME{% \x{御坂美琴}\x{白井黑子}\x{戰場原緋多木}\x{阿良良木歷}% \x{宿海仁太}\x{本間芽衣子}\x{瀧川吉野}\x{鎖部葉風}% \x{衛宮切嗣}\x{言峰綺禮}\x{常守朱}\x{狡嚙慎也}% \x{界塚伊奈帆}\x*{艾瑟依拉姆·沃斯·艾莉歐斯亞}} \let\x\Noun\NAME \let\x\Book\NAME \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-punctstyle.tex}} % % \begin{macrocode} %<*ex-punctstyle> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage[draft,columns=1,margin=false,headings=false]{typogrid} \usepackage{xeCJK} \usepackage{xeCJKfntef} \setCJKmainfont{SimSun} \xeCJKsetup{AllowBreakBetweenPuncts} \xeCJKDeclarePunctStyle { mine } { fixed-punct-ratio = nan , fixed-margin-width = 0 pt , mixed-margin-width = \maxdimen , mixed-margin-ratio = 0.5 , middle-margin-width = \maxdimen , middle-margin-ratio = 0.5 , add-min-bound-to-margin = true , min-bound-to-kerning = true , kerning-margin-minimum = 0.1 em , bound-punct-width = 0 em , enabled-hanging = true } \xeCJKDeclarePunctStyle { JIS } { bound-punct-ratio = .5 } \begin{document} \setlength\parindent{2em} \newcommand\showexample[1]{% \texttt{------ #1 ------}\par\xeCJKsetup{PunctStyle=#1}\showtexts\par} \newcommand\showtexts{% 《第一回\quad 甄士隐梦幻识通灵\quad 贾雨村风尘怀闺秀》\\ 《第一回\quad 甄士隐梦幻识通灵\quad 贾雨村风尘怀闺秀》 列位看官:你道此书从何而来?说起根由,虽近荒唐,细按则深有趣味。待在下将此来历注明,方使阅者了然不惑。 \CJKunderline*{原来女娲氏炼石补天之时,于大荒山无稽崖炼成高经十二丈、方经二十四丈顽石三万六千五百零一块。娲皇氏只用了三万六千五百块,只单单剩了一块未用,便弃在此山青埂峰下。谁知此石自经煆炼之后,灵性已通,因见众石俱得补天,独自己无材不堪入选,遂自怨自叹,日夜悲号惭愧。} 一日,正当嗟悼之际,俄见一僧一道远远而来,生得骨格不凡,丰神迥别,说说笑笑,来至峰下,坐于石边,高谈快论:先是说些云山雾海、神仙玄幻之事,后便说到红尘中荣华富贵。此石听了,不觉打动凡心,也想要到人间去享一享这荣华富贵,但自恨粗蠢,不得已,便口吐人言,向那僧道说道:“大师,弟子蠢物,不能见礼了!适闻二位谈那人世间荣耀繁华,心切慕之。弟子质虽粗蠢,性却稍通,况见二师仙形道体,定非凡品,必有补天济世之材,利物济人之德。如蒙发一点慈心,携带弟子得入红尘,在那富贵场中,温柔乡里受享几年,自当永佩洪恩,万劫不忘也!”二仙师听毕,齐憨笑道:“善哉,善哉!那红尘中有却有些乐事,但不能永远依恃;况又有‘美中不足,好事多磨’八个字紧相连属,瞬息间则又乐极悲生,人非物换,究竟是到头一梦,万境归空,倒不如不去的好。”这石凡心已炽,那里听得进这话去,乃复苦求再四。二仙知不可强制,乃叹道:“此亦静极思动,无中生有之数也!既如此,我们便携你去受享受享,只是到不得意时,切莫后悔!”石道:“自然,自然。”那僧又道:“若说你性灵,却又如此质蠢,并更无奇贵之处。如此也只好踮脚而已。也罢!我如今大施佛法,助你助,待劫终之日,复还本质,以了此案。你道好否?”石头听了,感谢不尽。那僧便念咒书符,大展幻术,将一块大石登时变成一块鲜明莹洁的美玉,且又缩成扇坠大小的可佩可拿。那僧托于掌上,笑道:“形体倒也是个宝物了!还只没有实在的好处,须得再镌上数字,使人一见便知是奇物方妙。然后好携你到那昌明隆盛之邦、诗礼簪缨之族、花柳繁华地、温柔富贵乡去安身乐业。”石头听了,喜不能禁,乃问:“不知赐了弟子那哪几件奇处?又不知携了弟子到何地方?望乞明示,使弟子不惑。”那僧笑道:“你且莫问,日后自然明白的。”说着,便袖了这石,同那道人飘然而去,竟不知投奔何方何舍。 \hfill ——曹雪芹《红楼梦》\unskip\unkern} \showexample{mine} \showexample{quanjiao} \showexample{JIS} \showexample{banjiao} \showexample{CCT} \showexample{kaiming} \showexample{hangmobanjiao} \showexample{plain} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-verbatim.tex}} % % \begin{macrocode} %<*ex-verb> % \end{macrocode} % % \begin{macrocode} \documentclass[a4paper]{article} \usepackage{xeCJK} \usepackage{fullpage} \xeCJKDeclareSubCJKBlock{Hangul} { "1100 -> "11FF, "3130 -> "318F, "A960 -> "A97F, "AC00 -> "D7AF } \setCJKmainfont{Adobe Song Std} \setCJKmainfont[Hangul]{Adobe Myungjo Std} %%\setmonofont{Inconsolata} %%\setmonofont{Source Code Pro} %%\setCJKmonofont{Adobe Kaiti Std} \setmonofont{TeX Gyre Cursor} \setCJKmonofont{Adobe Fangsong Std} \begin{document} \begin{verbatim} ************************************************************* * * * 精致甲版红楼梦 - 精致工作室特别奉献(2010年9月) * * * * GBK简体、GBK繁体、Big5繁体、PDF简体、PDF繁体 * * * * 为 GBK、Big5 特别度身订制,并经反复修订,方便纯文本阅读 * * * * 精致甲、乙版红楼梦下载专页 * * http://www.speedy7.com/cn/stguru/gb2312/redmansions.htm * * http://www.speedy7.com/cn/stguru/big5/redmansions.htm * * * * 此电子文件可随意传播、发布、下载。 * * 传播时建议保留此版本说明供查阅与识别。 * * 未经修订者同意,不得将此版本用于商业用途。 * * 如您对红楼梦不具深入了解,请勿随便修改其中的内容。 * * * ************************************************************* \end{verbatim} \verb|a 汉 字 b| \verb*|a 汉 字 b| \begin{verbatim} lang_set = { ["English"] = lang.en, ["Français"] = lang.fr, ["Español"] = lang.es, ["中文"] = lang.zh, ["日本語"] = lang.ja, ["한국어"] = lang.ko, } \end{verbatim} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-CM.tex}} % % \begin{macrocode} %<*ex-cm> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage{xeCJK} \usepackage{hologo} \setCJKmainfont{HanaMinA} %% http://fonts.jp/hanazono/ \begin{document} \hologo{XeTeX} でIVSを使うテスト。 消󠄀化󠄀器󠄂 / 消󠄁火器󠄃 / 消󠄂化󠄁器󠄄 消󠄃火器󠄅 / 消化󠄂器󠄆 / 消火器󠄇 消化󠄃器󠄈 / 消火器󠄉 / 辻󠄂辻󠄃辻󠄄辻󠄅  \CJKfontspec{Meiryo} か がか゚ ぼぽ ぼぽ ばぱ ばぱ \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-listings.tex}} % % \begin{macrocode} %<*ex-listings> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage[margin=1in]{geometry} \usepackage{listings,xcolor} \usepackage{showexpl} \usepackage{xeCJK} \setCJKmainfont{HanaMinA} \setCJKmonofont{SimSun} \xeCJKDeclareSubCJKBlock{Kana} { "3040 -> "309F, "30A0 -> "30FF, "31F0 -> "31FF } \setCJKmonofont[Kana]{Meiryo} \setmonofont{Latin Modern Mono Light} \makeatletter \lstset{% basicstyle=\small\ttfamily,breakindent=4\lst@width, numbers=left,numberstyle=\tiny\color{gray}, commentstyle=\color{green!50!black},keywordstyle=\color{blue}\bfseries, identifierstyle=\color{violet},stringstyle=\color{brown}, escapebegin=\normalfont} \lstnewenvironment{cppcode}[1][] {\lstset{language=C++,#1}} {} \makeatother \begin{document} \section{\lstinline{\\lstinline} 测试} \section{\lstinline|甲*乙| 测试} \lstinline|abc汉字abc| \lstinline|甲*乙| \lstinline[mathescape]|数学公式$x^2+y^2$| \section{\lstinline{lstlisting} 环境测试} \begin{lstlisting}[basicstyle=\rmfamily] 纯文字text测试 纯文字text测试 文字+文字 文字(符号)文字 辻󠄂辻󠄃辻󠄄辻󠄅 かがか゚ \end{lstlisting} \begin{lstlisting} text纯文字测试 \end{lstlisting} \begin{lstlisting} text 纯文字测试 \end{lstlisting} \begin{lstlisting} text,纯文字测试 \end{lstlisting} \section{自定义环境测试} \begin{LTXexample}[pos=t,varwidth,numbersep=5pt,columns=fixed] \begin{cppcode}[escapechar=`,morekeywords=返回] #define 返回 return #include /* * 块注释 * `逃逸字符,测试$f(x)$` */ int main() { // 行注释 const char *欢迎 = "hello 世界(ワールド)"; std::cout << 欢迎 << std::endl; 返回 0; } \end{cppcode} \end{LTXexample} \section{\texttt{breaklines} 测试} \begin{lstlisting}[linewidth=10em,frame=single,rulecolor=\color{black},breaklines] 断行文字text测试,断行文字text测试,断行文字text测试,断行文字text测试 断行文字text测试,断行文字text测试,断行文字text测试,断行文字text测试 \end{lstlisting} \section{\lstinline|\\lstinputlisting| 测试} \lstinputlisting[language={[AlLaTeX]TeX}]{\jobname} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xeCJK-example-mathblock.tex}} % % \begin{macrocode} %<*ex-mathblock> % \end{macrocode} % % \begin{macrocode} \documentclass{article} \usepackage{url} \usepackage[CJKmath]{xeCJK} \setCJKmainfont[BoldFont=SimHei]{SimSun} \setCJKmonofont{KaiTi} \ExplSyntaxOn \tl_const:Nn \c_test_text_tl { 天地玄黃宇宙洪荒日月盈昃辰宿列張寒來暑往秋收冬藏閏餘成歲律呂調陽 } \cs_new:Npn \test_color:n #1 { \exp_args:No \str_range:nnn { \mdfivesum {#1} } { 1 } { 6 } } \tl_map_inline:Nn \c_test_text_tl { \xeCJKDeclareSubCJKBlock {#1} { `#1 } \setCJKmainfont [ #1 , Color = \test_color:n {#1} , BoldFont = SimHei ] { SimSun } \setCJKmonofont [ #1 ] { KaiTi } } \cs_new_protected:Npn \TEST { \exp_args:No \path { \c_test_text_tl } \par \c_test_text_tl \par $ \c_test_text_tl $ } \ExplSyntaxOff \begin{document} \TEST \bigskip \bfseries\mathversion{bold}\TEST \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \subsection{\pkg{xunicode-symbols.tex}} % % 该文档用于生成 \pkg{xunicode} 中所声明字符的对照表。每一个字符 % 先尝试用主字体 |FreeSerif| 渲染;若主字体不覆盖该码位,则按顺序 % 回退到 |Noto Sans Symbols 2|、|Symbola|、|Segoe UI Symbol|、 % |DejaVu Sans| 中第一个能渲染该字符的字体。 % % 在 \CTeX{} 团队的发版构建中,运行环境同时安装了 |FreeSerif| % (Ubuntu \pkg{fonts-freefont-ttf})和 |Noto Sans Symbols 2|, % 因此 pre-release 内附的 \file{xunicode-symbols.pdf} 通常已能完整 % 渲染所有字符。用户在本地通过 |l3build install --full| 重新编译时, % 输出是否完整取决于本机已安装的字体;若仅依赖系统自带字体(例如 % Windows 上仅有 |Segoe UI Symbol|),表中的部分字符会出现 % “Missing character” 提示。此时建议补装 |Noto Sans Symbols 2| % 或直接使用 pre-release 中已编译好的版本。 % % \changes{v3.10.0}{2026/06/23}{\file{xunicode-symbols.tex} 改为按 % \pkg{Noto Sans Symbols 2}/\pkg{Symbola}/\pkg{Segoe UI Symbol}/\pkg{DejaVu Sans} % 逐字符尝试的多级 fallback,缓解 Windows 等仅有 % \pkg{Segoe UI Symbol} 时部分字符缺失的问题(\#878)。} % % \begin{macrocode} %<*xunicode-symbols> % \end{macrocode} % % \begin{macrocode} %<@@=xunsym> % \end{macrocode} % % \begin{macrocode} \documentclass[a4paper]{article} \usepackage[margin=2cm]{geometry} \usepackage{fontspec} \usepackage{xunicode-addon} \usepackage{array,longtable,booktabs} \usepackage{zref-base} \setmainfont{FreeSerif} \IfFontExistsTF{Noto Sans Symbols 2} {\newfontfamily\xunsymNoto{Noto Sans Symbols 2}}{} \IfFontExistsTF{Symbola} {\newfontfamily\xunsymSymbola{Symbola}}{} \IfFontExistsTF{Segoe UI Symbol} {\newfontfamily\xunsymSegoe{Segoe UI Symbol}}{} \IfFontExistsTF{DejaVu Sans} {\newfontfamily\xunsymDejaVu{DejaVu Sans}}{} \makeatletter \ExplSyntaxOn \cs_new_protected:Npe \@@_restore_catcode: { \char_set_catcode:nn { 0 } { \char_value_catcode:n { 0 } } } \file_if_exist:nTF { xunicode-commands.tex } { \char_set_catcode_comment:n { 0 } } { \char_set_catcode_ignore:n { 0 } } ^^@ \tl_new:N \l_@@_command_tl ^^@ \int_new:N \l_@@_command_int ^^@ \iow_new:N \g_@@_command_iow ^^@ \iow_new:N \g_@@_combine_mark_iow ^^@ \ior_new:N \g_@@_unicode_ior ^^@ \prop_new:N \l_@@_command_prop ^^@ \prop_new:N \l_@@_combine_mark_prop ^^@ \prop_new:N \l_@@_combine_marks_prop ^^@ \cs_new_protected:Npn \SaveUTFCommands ^^@ { ^^@ \group_begin: ^^@ \@@_restore_catcode: ^^@ \cs_set_eq:NN \DeclareTextAccent \use_none:nnn ^^@ \cs_set_eq:NN \DeclareUTFComposite \use_none:n ^^@ \cs_set_eq:NN \DeclareUTFEncodedAccent \use_none:nnn ^^@ \cs_set_eq:NN \DeclareUTFDoubleEncodedAccent \use_none:nnn ^^@ \cs_set_eq:NN \DeclareUTFDoubleEncodedSymbol \use_none:nnn ^^@ \cs_set_eq:NN \DeclareUTFCommand \use_none:nn ^^@ \cs_set_eq:NN \DeclareUTFTIPACommand \use_none:n ^^@ \cs_set_eq:NN \DeclareUTFCompositeCommand \use_none:nnn ^^@ \cs_set_eq:NN \DeclareUTFSymbol \@@_save_UTF_command:Nn ^^@ \cs_set_eq:NN \DeclareUTFCompositeSymbol \@@_save_UTF_command:Nnn ^^@ \cs_set_eq:NN \DeclareEncodedCompositeCharacter \@@_save_combine_mark:nnnn ^^@ \cs_set_eq:NN \DeclareEncodedCompositeAccents \@@_save_combine_marks:nnnn ^^@ \DeclareDocumentCommand \DeclareUTFcharacter { O { \UTFencname } m m } ^^@ { \@@_save_UTF_command:NNn \l_@@_command_prop {##3} {##2} } ^^@ \DeclareDocumentCommand \DeclareUTFcomposite { O { \UTFencname } m m m } ^^@ { \@@_save_UTF_command:NNnn \l_@@_command_prop {##3} {##4} {##2} } ^^@ \ReloadXunicode { \UTFencname } ^^@ \ior_open:NnTF \g_@@_unicode_ior { UnicodeData.txt } ^^@ { ^^@ \iow_open:Nn \g_@@_command_iow { xunicode-commands.tex } ^^@ \iow_open:Nn \g_@@_combine_mark_iow { xunicode-combine-marks.tex } ^^@ \ior_map_inline:Nn \g_@@_unicode_ior { \@@_make_table:w ##1 \q_stop } ^^@ \@@_write_combine_marks: ^^@ \ior_close:N \g_@@_unicode_ior ^^@ \iow_close:N \g_@@_command_iow ^^@ \iow_close:N \g_@@_combine_mark_iow ^^@ \group_end: ^^@ } ^^@ { ^^@ \group_end: ^^@ \msg_fatal:nn { @@ } { UnicodeData } ^^@ } ^^@ } ^^@ \msg_new:nnn { @@ } { UnicodeData } ^^@ { File~`UnicodeData.txt`~not~found. } ^^@ \cs_new_protected:Npn \@@_save_UTF_command:Nn ^^@ { \@@_save_UTF_command:NNn \l_@@_command_prop } ^^@ \cs_new_protected:Npn \@@_save_UTF_command:Nnn ^^@ { \@@_save_UTF_command:NNnn \l_@@_command_prop } ^^@ \cs_new_protected:Npn \@@_save_UTF_command:NNn #1#2#3 ^^@ { \@@_save_UTF_text_command:Nxx #1 { \use_none:n #3 } { \token_to_str:N #2 } } ^^@ \cs_new_protected:Npn \@@_save_UTF_command:NNnn #1#2#3#4 ^^@ { ^^@ \@@_save_UTF_text_command:Nxx #1 { \use_none:n #4 } ^^@ { ^^@ \token_to_str:N #2 \c_left_brace_str ^^@ \tl_if_single:nTF {#3} { \token_to_str:N #3 } { \tl_to_str:n {#3} } ^^@ \c_right_brace_str ^^@ } ^^@ } ^^@ \cs_new_protected:Npn \@@_save_combine_mark:nnnn #1#2#3#4 ^^@ { \@@_save_UTF_text_command:Nxx \l_@@_combine_mark_prop {#3} { \token_to_str:N #2 } } ^^@ \cs_new_protected:Npn \@@_save_combine_marks:nnnn #1#2#3#4 ^^@ { \@@_save_UTF_text_command:Nxx \l_@@_combine_marks_prop { { "#4 } { "#3 } } { \token_to_str:N #2 } } ^^@ \cs_new_protected:Npn \@@_save_UTF_text_command:Nxx #1#2#3 ^^@ { \use:e { \@@_save_UTF_text_command:Nnn \exp_not:N #1 {#2} {#3} } } ^^@ \cs_new_protected:Npn \@@_save_UTF_text_command:Nnn #1#2#3 ^^@ { ^^@ \prop_get:NnNTF #1 {#2} \l_@@_command_tl ^^@ { \prop_put:Nno #1 {#2} { \l_@@_command_tl , #3 } } ^^@ { \prop_put:Nnn #1 {#2} {#3} } ^^@ } ^^@ \cs_new_protected:Npn \@@_make_table:w #1 ; #2 ; #3 \q_stop ^^@ { ^^@ \prop_get:NnNT \l_@@_command_prop {#1} \l_@@_command_tl ^^@ { ^^@ \iow_now:Ne \g_@@_command_iow ^^@ { \token_to_str:N \UnicodeTextSymbol { "#1 } { \l_@@_command_tl } {#2} } ^^@ } ^^@ \prop_get:NnNT \l_@@_combine_mark_prop {#1} \l_@@_command_tl ^^@ { ^^@ \iow_now:Ne \g_@@_combine_mark_iow ^^@ { \token_to_str:N \UnicodeCombineMark { "#1 } { \l_@@_command_tl } {#2} } ^^@ } ^^@ } ^^@ \cs_new_protected:Npn \@@_write_combine_marks: ^^@ { ^^@ \prop_map_inline:Nn \l_@@_combine_marks_prop ^^@ { ^^@ \iow_now:Ne \g_@@_combine_mark_iow ^^@ { \token_to_str:N \UnicodeCombineMarks ##1 {##2} } ^^@ } ^^@ } ^^@ \ExplSyntaxOff ^^@ \SaveUTFCommands ^^@ \ExplSyntaxOn \@@_restore_catcode: \int_new:N \g_@@_symbol_int \int_new:N \g_@@_table_int \DeclareDocumentCommand \UnicodeTextSymbol { m v m } { \int_gincr:N \g_@@_symbol_int \use_none:n #1 & \scan_stop: \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f: \cs_if_exist_use:N \xunsymNoto \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f: \cs_if_exist_use:N \xunsymSymbola \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f: \cs_if_exist_use:N \xunsymSegoe \reverse_if:N \tex_iffontchar:D \tex_font:D #1 \exp_stop_f: \cs_if_exist_use:N \xunsymDejaVu \fi: \fi: \fi: \fi: \tex_char:D #1 \exp_stop_f: & \tl_set:Nn \l_tmpa_clist {#2} \clist_use:Nn \l_tmpa_clist { \par } \strut & #3 \\ } \cs_set_eq:NN \UnicodeCombineMark \UnicodeTextSymbol \DeclareDocumentCommand \UnicodeCombineMarks { m m v } { \int_gincr:N \g_@@_symbol_int \use_none:n #1 ~ \use_none:n #2 & \tex_char:D #1 \exp_stop_f: \tex_char:D #2 \exp_stop_f: & \tl_set:Nn \l_tmpa_clist {#3} \clist_use:Nn \l_tmpa_clist { \par } \strut \\ } \DeclareDocumentCommand \UTFTABLE { m m } { \int_gincr:N \g_@@_table_int \exp_args:Ne \@@_make_table:nnn { UTFTABLE - \int_use:N \g_@@_table_int } {#1} {#2} } \cs_new_protected:Npn \@@_make_table:nnn #1#2#3 { \section{#2~(\zref@extractdefault{#1}{default}{0})} \zref@refused{#1} \noindent \begin{longtable}[c]{>{\ttfamily\footnotesize}rc>{\ttfamily\footnotesize}p{55mm}>{\tiny}l} \toprule \multicolumn1c{\normalfont\normalsize USV} & \normalfont\normalsize Symbol & \normalfont\normalsize Macro(s) & \normalfont\normalsize Description \\ \midrule \endhead \bottomrule \endfoot \int_gzero:N \g_@@_symbol_int \input{#3} \end{longtable} \group_begin: \zref@setcurrent{default}{\int_use:N\g_@@_symbol_int} \zref@wrapper@immediate{\zref@labelbyprops{#1}{default}} \group_end: } \cs_generate_variant:Nn \@@_make_table:nnn { x } \ExplSyntaxOff \begin{document} \UTFTABLE{Symbols}{xunicode-commands.tex} \UTFTABLE{Combining Marks}{xunicode-combine-marks.tex} \end{document} % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % \endinput