雾月
雾月
这家伙很懒,什么也没写!

注册于 3年前

回答
184
文章
2
关注者
19

\keys_define:nn { my }
  {
    subset-1 .meta:n  = {#1},
    subset-2 .meta:nn = { you } {#1},
  }

类似于

\keys_define:nn { my }
  {
    subset-1 .code:n = { \keys_set:nn { my  } {#1} } ,
    subset-2 .code:n = { \keys_set:nn { you } {#1} } ,
  }

但可以正确处理其它几类设置键的命令(如 \keys_set_filter:nnn)。

第二个问题,只要使用 .meta:n 定义 subset 键即可。

.meta:n.meta:nn 这两个 property 定义的键的作用是把传给它们的值当作键值对来解析,区别是,前者定义的键在解析传进来的值时使用该键的模块名(定义该键时 \keys_define:nn 的第一个参数),后者使用给定的模块名。

LaTeX3 中一些 \.._map_... 函数是通过尾递归来实现的,\prg_break_point:Nn 用于标记循环要跳出时的位置,\prg_map_break:Nn 用于跳出循环。
先看一个例子,这是 \tl_map_function:nN 的简化版实现:

% \cs_new_eq:NN \prg_break_point:Nn \use_ii:nn
\cs_new:Npn \mytl_map_function:nN #1#2 
  {
    \__mytl_map_function:Nn #2 #1 \q_recursion_tail \q_recursion_stop
    \prg_break_point:Nn \mytl_map_break: { }
  }
\cs_new:Npn \mytl_map_break: { \prg_map_break:Nn \mytl_map_break: { } }
\cs_new:Npn \__mytl_map_function:Nn #1#2 
  { 
    \quark_if_recursion_tail_stop:n {#2} 
    #1 {#2}
    \__mytl_map_function:Nn #1
  }

\__mytl_map_function:Nn 有两个参数,第一个是 function,第二个是 tl 的某一项。
比如 \mytl_map_function:nN { abcd } \tl_show:n\__mytl_map_function:Nn 第一个参数都是 \tl_show:n,第二个参数依次是 abcd\q_recursion_tail
当第二个参数不是 \q_recursion_tail 时,先执行 \tl_show:n { .. },然后继续执行 \__mytl_map_function:Nn;当发现第二个参数是 \q_recursion_tail 时,它吞掉 \q_recursion_stop 及其之前的内容,尾递归结束。
但如果改为:

\cs_new:Npn \__my_if_not_c_show:n #1 
  {
    \tl_if_eq:nnTF {#1} { c }
      { \mytl_map_break: }
      { \tl_show:n {#1} }
  }
\mytl_map_function:nN { abcd } \__my_if_not_c_show:n

那么当第二个参数是 c 时,会直接跳出循环,跳出的位置正是 \prg_break_point:Nn 所在的位置。
\prg_map_break:Nn\prg_break_point:Nn 的第一个参数是用于标记不同类型的 map 的,\tl_map_...\tl_map_break:\seq_map_... 则是 \seq_map_break:

相当于某些编程语言里的 goto,只是 \prg_map_break:Nn 只能向后跳转。

这个例子同时也演示了 \quark_if_recursion_tail_stop:n 的用法。

在盒子中需要使用 \internallinenumbers,不过在 breakable 的盒子中仍然会失败。

\documentclass{article}
\usepackage{lipsum}
\usepackage{lineno}
\usepackage{tcolorbox}

\begin{document}

\linenumbers
\lipsum[1]

\nolinenumbers
\begin{tcolorbox}[left=4mm,boxsep=1mm,boxrule=0.5mm]
\internallinenumbers
\addtolength\linenumbersep{5.5mm}% = left + boxsep + boxrule

\lipsum[2]
\end{tcolorbox}

\linenumbers

\lipsum[3]

\end{document}

在 TeX 中,每个盒子都有一个参考点(reference point):
image.png
(来自 The TeXBook 第 63 页)

旋转一个盒子就是以这个 reference point 作为参考点。
coffin 内部使用了一个盒子保存了用 \hcoffin_set:Nn\vcoffin_set:Nn)输入的东西,旋转 coffin 就是直接旋转这个内部盒子(还会设置一些内部变量),旋转参考点也就是这个内部盒子的 reference point,而不是 coffin 的 handle(水平与垂直 pole 的交点,如 (hc,vc),可以参考 xcoffins.pdf)。

实际使用时,选择哪个点作为参考点不是特别重要,poles 会跟着一起旋转,只会改变内部变量的值。而 join,attach,typeset 这些操作都是基于 pole 的,在旋转前后,poles 之间的相对位置保持不变,poles 与盒子的内容的相对位置也保持不变。

先读 interface3.pdf,就算不全读也要读个大概,然后再用 LaTeX3。

定义和设置键值分别用 \keys_define:nn\keys_set:nn。如果你用 TeXLive2023,可以用 LaTeX2e 提供的接口:\DeclareKeys\SetKeys

一般情况下,LaTeX3 的键值接口不使用空格作为单词之间的分隔符,除非需要与 pgf (tikztcolorboxpgfplots 等)交互,而且空格是 ~ 不是 。如 siunitx 宏包使用 -

% 为了避免多次加载,先使用 tl 保存起来,然后用 \AtBeginDocument 来实际加载它
\keys_define:nn { Beautybook / coverstyle }
  {
    cover-choose .choice: ,
    cover-choose .value_required:n = true ,
    cover-choose / cn .code:n = 
      { \tl_gset:Nn \l__Beautybook_cover_choose_tl { \RequirePackage{...} } } ,
    cover-choose / en .code:n = 
      { \tl_gset:Nn \l__Beautybook_cover_choose_tl { \RequirePackage{...} } } ,
    其它键
    cover-choose .initial:n = cn , % 设置 cn 为初始值
  }
\NewDocumentCommand \coverstyle { +m }
  {
    \keys_set:nn { Beautybook / coverstyle } { #1 }
  }
\AtBeginDocument { \l__Beautybook_cover_choose_tl }

TeX 是先读取一整段然后根据文字宽度断行的,所以不能在段落中间改变文字宽度。(好吧,其实可以用 \parshape 改),所以不存在一个通用的命令可以在某页开始改变文字宽度,要改只能整段整段地改(用 \parshape 实现起来很麻烦)。\newgeometry 也需要另起一页。

可以用 changepage 宏包更改某些段落的文字宽度。

\documentclass{article}
\usepackage{changepage}
\usepackage{lipsum}
\begin{document}

\lipsum[1]

% 右边加宽 1cm
\begin{adjustwidth}{0cm}{-1cm}
\lipsum[2-6]
\end{adjustwidth}

\lipsum[7-10]

\end{document}

\enlargethispage 能起作用是因为,TeX 在构建段落时并不需要知道文字的高度,TeX 先“分段为行”,然后“组行为页”,分页是一行一行试出来的,所以让 TeX 多加几行到某页上很容易。

话说,为什么需要这样一个命令呢?

ctex 在 Linux 系统下默认使用 fandol 字库,这个字库的字体包含的字形比较少,换成其它字体就好。

这两个宏都需要吃掉一个参数,并且完全展开这个参数。

第一个区别是,\use:e(在要被完全展开的上下文中)可以被完全展开(expands all tokens fully),\use:x 不能被完全展开。
比如 \use:e { <tl> }<tl> 就是“要被完全展开的上下文”,如果这里面有 \use:e,它也会被完全展开,\use:x 则不会被展开。
例如,

% protected 宏 \__my_unexp: 在要被完全展开的上下文中不会被展开,保持原样
\cs_set_protected:Npn \__my_unexp: { do~something }
\tl_set:Nn \l__my_tl { do~other }

\use:e { \__my_unexp: \use:x { \l__my_tl } }

其结果是 \__my_unexp: \use:x { do~other }\__my_unexp:\use:x 不会被展开,括号也不会被展开,但是 \l__my_tl 会被展开,这个展开是由 \use:e 引起的,而不是 \use:x

\use:e { \__my_unexp: \use:e { \l__my_tl } }

的结果是 \__my_unexp: do~other\__my_unexp: 不会被展开,但是里面的 \use:e 会被展开,它需要一个参数,即 \l__my_tl,然后这个里面的 \use:e 展开它的参数 \l__my_tl
有没有办法阻止记号在要被完全展开的上下文中被展开呢?有。就是 \exp_not:N\exp_not:n(及其变体)。

\use:e { \__my_unexp: \exp_not:N \use:e { \l__my_tl } }

这样,里面的那个 \use:e 就不会被展开,但 \l__my_tl 会被外面那个 \use:e 展开,结果是 \__my_unexp: \use:e { no~other }

\use:e { \__my_unexp: \exp_not:n { \use:e { \l__my_tl } } }

结果是 \__my_unexp: \use:e { \l__my_tl },因为 \exp_not:n 的作用是让它的参数不被展开。
以上代码把外面的那个 \use:e 换成 \use:x,结果完全相同。

另一个区别是,\use:x 的参数中,parameter(catcode=6),比如 #,需要双写,但 \use:e 不需要,如:

\use:x { \cs_set:Npn \exp_not:N \__foo_do:n #1 { } }
\use:e { \cs_set:Npn \exp_not:N \__foo_do:n #1 { } }

第一行会报错。需要写成 ##1

除此之外,它们的作用完全相同。

l3kernel 昨天(2023-05-17,版本为 Released 2023-05-15)的更新中,要求引擎必须有 \expanded primitive 了(从 TeXLive 2019 开始 pdfTeX、XeTeX、LuaTeX 都已经有这个 primitive 了),\use:e 也是使用 \expanded 实现的。
目前,有 e 变体的应当用这个变体,使用 \cs_generate_variant:Nn 时,也应生成 e 变体。
某些宏和 primitive,比如 \tl_set:Nx\cs_set_nopar:Npx,它们内部使用的 primitive 已经完成了完全展开这个操作,所以不再需要使用 e 变体来展开。(当然不全是如此)

TeX(和 LaTeX)里关于“展开”的内容可以写一篇10页以上的文章了。

不必。

代码

unknown .code:n = \tl_set:Nn \l__myTest_标签_tl \l_keys_key_str

的作用是把 \l__myTest_标签_tl 设置为 \l_keys_key_str 而不是它的
在执行完 \keys_set:nn { myTest } { 馒头,序号=23 } 后,\l_keys_key_str 的值是最后那个键名,也就是 序号。所以,\l__myTest_标签_tl 展开为 \l_keys_key_str,然后 \l_keys_key_str 展开为 序号,所以最终显示为了 序号
应该这么写

unknown .code:n = \tl_set:No \l__myTest_标签_tl { \l_keys_key_str }
% 或 \tl_set:NV .. 或 \tl_set:Nx .. 或 \tl_set_eq:NN ..

使用 \tl_set:No,LaTeX 发现 馒头 这个键未定义(此时 \l_keys_key_str 的值已经是 馒头),就先把 \l_keys_key_str 展开一次,其结果是 馒头,然后把这个值保存到 \l__myTest_标签_tl,就得到了想要的结果。

可类比于“浅拷贝”与“深拷贝”。
\tl_set:Nn \l__myTest_标签_tl { \l_keys_key_str } 只是拷贝了“指针”而不是指针指向的值。

把计数器的值保存到 .aux 文件里。

\documentclass{article}
\makeatletter
% 保存那些要记录的计数器,它值的格式为 \ysk@savecurrcounter{counter1}\ysk@savecurrcounter{counter2}...
\newcommand{\ysk@totalcounters}{} 
\AddToHook{enddocument/afterlastpage}{\ysk@totalcounters} % 所有页面都已经输出,但.aux文件还未关闭
\newcommand\ysk@settotalcounter[2]{\global\@namedef{total#1s}{#2}} % 在.aux文件内执行
% 这个命令放在 \ysk@totalcounters 里,由它实际保存计数器的最后一个值
\protected\def\ysk@savecurrcounter#1{\immediate\write\@auxout
  {\string\ysk@settotalcounter{#1}{\number\value{#1}}}}
% 这个命令负责初始化并且全局地为 \ysk@totalcounters 添加新值
\newcommand{\ysk@initandaddtocounter}[1]{%
  \ysk@settotalcounter{#1}{0}% 先暂时定义,以避免.aux不存在时出错
  \xdef\ysk@totalcounters{\ysk@totalcounters\ysk@savecurrcounter{#1}}}
\NewDocumentCommand{\DeclareTotalCounters}{ >{\SplitList{,}} m } % 接受一个逗号分隔的列表
  {\ProcessList{#1}{\ysk@initandaddtocounter}}
% \DeclareTotalCounters 只能用于导言区,\AtBeginDocument(begindocument 钩子)中也不行
\AddToHook{begindocument/before}{\RenewDocumentCommand{\DeclareTotalCounters}{m}{\ERROR}}
\makeatother

\newcounter{yskcount}
\newcounter{foocount}
\DeclareTotalCounters{yskcount,foocount}

\begin{document}

Total yskcount: \totalyskcounts.
Total foocount: \totalfoocounts.

\setcounter{yskcount}{9}
\stepcounter{yskcount} % yskcount=10

\setcounter{foocount}{42} % foocount=42

\end{document}

image.png

另外,参加 totalcountxassoccnt 宏包。

bend leftbend right 可以设置值,弯曲方向与这个值有关。
image.png
bend right=angle 就是 bend left=-angle

对于 bend left 具体来说就是:
从起点 A 到终点 B 引一条(有向)线段,线段的方向就是从起点到终点的方向(向量 AB);
bend left 给定一个值,比如 30,那么从 A 到 B 的那条(有向)曲线在 A 点的切线与 AB 的夹角就是 30°(逆时针为正)。
image.png

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{angles,quotes}
\begin{document}
\begin{tikzpicture}
\coordinate(A) at (0,0); \node[left] at (A) {A};
\coordinate(B) at (3,0); \node[above right] at (B) {B};
\draw[thick,->] (A)--(B);
\draw[bend left=30, red] (A) to (B);
\draw[bend left=-30, blue] (A) to (B);
\draw[->](A)--+(30:1cm) coordinate (C);
\pic["$30^\circ$", draw,->, angle radius=.7cm] {angle=B--A--C};
\end{tikzpicture}
\end{document}

如果是 bend left=-30,也就是 bend right=30,就往顺时针方向取 30°,结果就是上图中蓝色那条线。

结果就是弯曲方向不仅与线段的方向有关,而且与给出的角度有关。

\documentclass{article}
% \usepackage{calc} % calc 必须放在前面
\usepackage{regexpatch}
\makeatletter
\NewCommandCopy{\gaddtolength}{\addtolength}
\xpatchcmd{\gaddtolength}{\advance}{\global\advance}{}{\ERROR}
\makeatother

\newlength\testlen

\begin{document}

\setlength\testlen{0pt}
\begingroup
\addtolength\testlen{10pt} % 局部设置
\endgroup
\the\testlen

\setlength\testlen{0pt}
\begingroup
\gaddtolength\testlen{10pt} % 全局设置
\endgroup
\the\testlen

\end{document}

image.png

\labelformat{计数器}{代码} 可以修改 \ref 的外形。对外形的修改在使用 \label 时就已经完成,直接写入到 .aux 文件里。
比如上一个问题 #如何输出一个行间的只显示计数器的数字的盒子 ,加上下面的代码,\ref 时就会加上 \fcolorbox

\labelformat{dycnt}{\fcolorbox{dyframecol}{dybackcol}{#1}} 

#1\thedycnt

\ref{<label>} 显示的内容实际上是 \r@<label> 的第一项,\r@<label>\newlabel 定义,\newlabel 的第一个参数就是 <label>,第二个参数就是 \r@<label> 的值,不加载 hyperref 时,它只有2项,加载 hyperref 时有5项。你可以查看 .aux 文件。

\r@<label> 的第一项在(前一次编译时)执行 \label{<label>} 时就已经确定了,(局部的)保存在 \@currentlabel 这个内部命令中。更详细的内容可以查看 source2e.pdfhyperref.pdf

有多种实现方式。可以用 xcolor\fcolorbox,也可以用 tcolorbox

\documentclass{ctexart}
\usepackage{xcolor}
% \usepackage{hyperref}

\definecolor{dyframecol}{HTML}{8080FF}
\definecolor{dybackcol}{HTML}{E6E6FF}
\newcounter{dycnt}

%% 第一种
\counterwithin{dycnt}{section}
\renewcommand{\thedycnt}{\arabic{section}.\arabic{dycnt}}
\newcommand{\dy}{\leavevmode\refstepcounter{dycnt}\nobreak
  \fcolorbox[HTML]{8080FF}{E6E6FF}{\thedycnt}~}

%% 第二种
\usepackage{tcolorbox}
\newtcbox[use counter=dycnt, number within=section,
    number freestyle=\noexpand\arabic{section}:\noexpand\arabic{dycnt}]
  {\Dybox}[1][]
  {colframe=dyframecol, colback=dybackcol,
    boxrule=0.4pt, boxsep=0pt, left=3pt,right=3pt,top=3pt,bottom=3pt,
    sharp corners, on line, after=~, #1}
% \tcbset{IfBlankF/.code n args={2}{\ifblank{#1}{}{\pgfkeysalso{#2}}}} % 如果比TeXLive2023旧,加上这个
\newcommand{\Dy}[1][]{\Dybox[IfBlankF={#1}{label={#1}}]{\thedycnt}\ignorespaces}

\begin{document}

\section{节}

\dy \label{dy:1}这是一段文字

\dy 这是一段文字 \ref{dy:4}

\Dy 这是一段文字 \ref{dy:1}

\Dy[dy:4] 这是一段文字

\end{document}

image.png

用法有一点点区别。但效果都是一样的。

postnotes 宏包提供此功能。

\documentclass[UTF8]{ctexart}

\usepackage{tasks}

\usepackage{postnotes}
\postnotesetup{backlink}
\def\endnote{\postnote}
\def\theendnotes{\printpostnotes}

\usepackage{hyperref}

\begin{document}

\begin{tasks}(3)
    \task 周穆王\endnote{陳永伯 原本有目無文。現據《道藏》本《曆世真仙體道通鑒》卷五補。}\label{005陳永伯}
    \task 燕昭王
    \task 彭祖
    \task 魏伯陽
\end{tasks}

\newpage

\theendnotes

\end{document}

某些地方可能和 endnotes 不一样,需要自己调整。

发布
问题