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

注册于 4年前

回答
244
文章
2
关注者
25

一般情况下,如果要实现类似的需求,是交换 1、2 个参数的顺序,或者用不同类型的定界符([] () <> 等)。

也可以用一个特殊的值表示默认值,遇到这个特殊值的时候把它替换为默认值。

\ExplSyntaxOn
\cs_new_protected:Npn \__my_make_default:nnn #1#2#3
  {
    \tl_if_eq:nnTF {#3} {#1}
      { \tl_set:Nn \ProcessedArgument {#2} }
      { \tl_set:Nn \ProcessedArgument {#3} }
  }
\cs_new_eq:NN \TrueDefault \__my_make_default:nnn
\ExplSyntaxOff

\NewDocumentCommand{\cmdt}{ 
  >{\TrueDefault{?}{default1}} O{?} % 如果参数为 ? 就替换为 default1
  >{\TrueDefault{?}{default2}} O{?}
  m
}{%
    \textbf{The result of \texttt{\string\cmdt}:} \par
    optional param1: #1 \par
    optional param2: #2 \par
    mandattory param: #3 \par
}

\cmdt{Explorer}

\cmdt[anaconda][python]{Explorer}

\cmdt[anaconda]{Explorer}

\cmdt[?][python]{Explorer}

猜你想要 lttemplates(即 xtemplate),即将进入 LaTeX2e 内核(不出意外的话是 Released 2024-11-01),目前(2024.10.31)可以使用 xelatex-dev 等 dev 版。

l3keys(包括其它键值宏包)都不会每次没给出键时自动使用默认值,只会使用初始值,但 lttemplates 会。

\documentclass{article}

\ExplSyntaxOn
\quark_new:N \q__skyrmion_mark
\quark_new:N \q__skyrmion_stop
\prg_new_conditional:Npnn \skyrmion_if_item_star:n #1 { TF } % 完全可展的函数
  {
    \str_if_eq:eeTF { \skyrmion_strip_end_star:n {#1} } { \exp_not:n {#1} }
      { \prg_return_false: } % 如果移除掉结尾 * 号之后,两个相同,说明原来没有星号
      { \prg_return_true:  } % 反之,则有
  }
\cs_generate_variant:Nn \skyrmion_if_item_star:nTF { e }
\cs_new:Npn \skyrmion_strip_end_star:n #1 % 完全可展的函数,用 e 展开可得到结果
  { 
    \__skyrmion_strip_end_star:w 
      \prg_do_nothing: #1   \q__skyrmion_mark % 如果 #1 以 * 结尾,就用这个
      #1 * \q__skyrmion_mark % 自己加上 *,如果 #1 不以 * 结尾,就会用这个
      \q__skyrmion_stop 
      {#1} % <- 注意这个
  }
\cs_new:Npn \__skyrmion_strip_end_star:w #1 *\q__skyrmion_mark #2 \q__skyrmion_stop
  {
    \tl_if_empty:nTF {#2} % 如果 #2 为空,说明使用是我们自己加的 *
      { \exp_not:n } % <- 这个保护的是上面带花括号的 {#1}
      { \exp_not:o {#1} \use_none:n } % <- \use_none:n 移除上面的 {#1}
  }
\cs_generate_variant:Nn \skyrmion_strip_end_star:n { e }

\clist_new:N \l__skyrmion_tmp_clist
\NewDocumentCommand \skyrmionif { m }
  {
    \skyrmion_strip_end_star:e { \clist_item:Nn \l__skyrmion_tmp_clist {#1} }
    { ~is~ } % <- TeX 会忽略每行开头的空格,
    \skyrmion_if_item_star:eTF
      { \clist_item:Nn \l__skyrmion_tmp_clist {#1} }
      { True } { False }
  }
\clist_set:Nn \l__skyrmion_tmp_clist
  { explorer, fishrows*, eureka, admin, skyrmion }
\ExplSyntaxOff

\begin{document}

\skyrmionif{1}

\skyrmionif{2}

\ExplSyntaxOn \cs_generate_variant:Nn \tl_to_str:n { e } \ttfamily
\tl_to_str:e { \skyrmion_strip_end_star:n { fishrows* } } |
\tl_to_str:e { \skyrmion_strip_end_star:n { {}* } } |
\tl_to_str:e { \skyrmion_strip_end_star:n { {} } } |
\tl_to_str:e { \skyrmion_strip_end_star:n {   * } } |
\ExplSyntaxOff

\end{document}

如果设置了 BoldFontItalicFontxeCJK 不会再使用伪粗体。而 ctex 为预定义的字体都设置了这两个键,因此后续的修改无效。只需重新定义 xeCJKBoldFontItalicFont 这两个键即可。

必须在加载 ctex 之前就加载 xeCJK,或者用 \PassOptionsToPackage,否则设置的 xeCJK 宏包选项无效。

\documentclass{article}

\PassOptionsToPackage{AutoFakeBold=3.0}{xeCJK}
\ExplSyntaxOn
\AddToHook{package/ctex/before}{
  \AddToHook{package/xeCJK/after}{
    \keys_define:nn { xeCJK / features }
      { BoldFont .code:n = , ItalicFont .code:n = }
  }
}
\AddToHook{package/ctex/after}{
  \keys_define:nn { xeCJK / features }
    {
      BoldFont .tl_set:N = \l__xeCJK_font_name_bf_tl ,
      ItalicFont .tl_set:N = \l__xeCJK_font_name_it_tl
    }
}
\ExplSyntaxOff
\usepackage[UTF8]{ctex}
\usepackage{fontspec}

\begin{document}

我上{\bfseries 早八}

\CJKfontspec[BoldFont=FandolHei, ItalicFont=FandolKai]{FandolSong}
我上{\bfseries 早八}

\end{document}

只可用 xelatex 编译。

也可把这两个键定义为使用 AutoFakeBoldAutoFakeSlant 等。

\makeatletter\ExplSyntaxOn
\clist_new:N \g__my_packages_clist
\int_new:N \g__my_level_int
\AddToHook{package/before}{\int_gincr:N \g__my_level_int}
\AddToHook{package/after}{
  \int_gdecr:N \g__my_level_int
  \int_compare:nNnT \g__my_level_int = { 0 }
    { \clist_gput_right:Nx \g__my_packages_clist { \@currname } }
}
\AddToHook{enddocument/info}{ \tl_show:e { Packages:~ \g__my_packages_clist } }
\ExplSyntaxOff\makeatother

\documentclass{} % <- 改成你的文档类
\begin{document}
\end{document}

然后在终端中正常编译。这样可以列出使用的文档类中直接加载的宏包(不包括它们依赖的宏包,如果需要,把代码中的 level=0 改成其它值,或直接删去即可),也会包含这个文档在导言区加载的宏包(这里没有)。

如果不是用 xelatex 编译的,最后一个加载的宏包可能是 epstopdf-base,这是 LaTeX 自动加载的,直接忽略即可。

用通用命令钩子 \AddToHook{cmd/<document cmd>/before}{<write to aux>} 在需要判断是否在正文使用了的命令前加上一段代码,在辅助文件中写入一些标记,然后在 begindocument 钩子(或 \AtBeginDocument,或 begindocument/after 钩子)判断是否有此标记,然后根据需要重定义命令即可。

实际上,正是由于 elegantbook 加载了 bm 宏包才导致这个错误。bm 设置 \let\boldsymbol\bm,但为什么直接嵌套 \bm 不会出错呢?因为最外层的那个 \bm 设置 \let\bm\@firstofone,所以里面的 \bm 不会生效。但并没有 \let\boldsymbol\@firstofone,嵌套 \boldsymbol 就会出错。

\boldsymbol 直接把字体替换为加粗的,如果加粗的字体没有这个字形,就保持不变。而 \bm 会尝试替换字体,如果没有会使用 “poor man's bold”,即伪粗体。

问题2,没有一劳永逸的解决方案,具体问题具体分析。

像下面那个例子一样,加上 \clap

text 键设置的内容应当是宽度为 0pt 的。\clap 等于 \makebox[0pt][c]

怎么没解决呢?

\renewcommand\mainmatter{\if@openright\cleardoublepage\else\clearpage\fi % <-
  \@mainmattertrue
  \pagenumbering{arabic}}

报错信息要看完整。

(./skyrmion-part1-module.code.tex
! Undefined control sequence.
l.1 \skyrmion
             _provide_module:n {part1}
? 

说明是在 skyrmion-part1-module.code.tex 这个文件的第一行报错,明显是由于没有处于 LaTeX3 环境而出错。因为是在导言区导入的模块,并不是 LaTeX3 环境。把名字替换成 \SkyrmionProvideModule 就好了。

另外,只是简单的用 \file_if_exist_input:n..,如果重复加载一个模块,就会出现重复定义的问题,还可能会像加载 tikz 库一样,遇到类别码的问题,建议使用 \@onefilewithoptions 加载文件,不仅可以解决这两个问题,还可以像加载宏包时有宏包选项一样,有自己的模块选项。

%% skyrmion.cls, 另外两个模块文件的第一行也要修改
\def\skyrmion@date{2024/10/05}
\def\skyrmion@version{0.1.1}

\ExplSyntaxOn
\cs_new_protected_nopar:Npn \SkyrmionProvideModule #1
  {
    \ProvidesExplFile{skyrmion-#1-module.code.tex}{\skyrmion@date}{\skyrmion@version}
    {skyrmion~ \text_titlecase:n { #1 } ~Module}
  }
\ExplSyntaxOff

\ProvidesExplClass{skyrmion} {\skyrmion@date} {\skyrmion@version}
{Skyrmion in Hong Kong}

\cs_new_protected:Npn \skyrmion_msg_new:nn #1#2 
  { \msg_new:nnn { skyrmion } { #1 } { #2 } }
\cs_new_protected:Npn \skyrmion_msg_error:nn #1#2
  { \msg_error:nnn { skyrmion } { #1 } { #2 } }
\cs_generate_variant:Nn \skyrmion_msg_error:nn { nx }
\skyrmion_msg_new:nn { not found module }
  {
    The~skyrmion~module~`#1'~not~found.
  }
\tl_const:Nn \c__skyrmion_module_ext_tl { tex }
\cs_new_protected_nopar:Npn \skyrmion_load_module:n #1 
  {
    \clist_map_inline:nn { #1 }
    {
      \file_if_exist:nTF { skyrmion-##1-module.code. \c__skyrmion_module_ext_tl }
      { \@onefilewithoptions { skyrmion-##1-module.code } [{}] [0000-00-00] { \c__skyrmion_module_ext_tl } } % 第一个方括号是模块选项,第二个是日期
      {
        \skyrmion_msg_error:nn { not found module } { ##1 }
      }
    }
  }

\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}}
\ProcessOptions\relax
\LoadClass[a4paper]{article}

\keys_define:nn {skyrmion/foo}
{
  tel.tl_set:N = \l__skyrmion_tel_tl
}

\NewDocumentCommand{\foo}{m}
{
  \keys_set:nn {skyrmion/foo} {#1}
  \int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {8}
   {
     \skyrmion_load_module:n {part1}
   }
  \int_compare:nNnT { \tl_count:N \l__skyrmion_tel_tl } = {10}
   {
     \skyrmion_load_module:n {part2}
   }
}
% \skyrmion_load_module:n {part1} % ?

\endinput

没想到简单的方法,只能重写整个 output routine,很麻烦,而且很多宏包都不容易兼容。

加上 varwidth 环境。

\documentclass{article}
\usepackage[a2paper,margin=1in]{geometry}
\usepackage{lipsum}
\usepackage{graphicx}
\usepackage{graphbox} % thanks to 鱼香肉丝没有鱼先生...
\usepackage{tabularray}
\usepackage{varwidth}

\begin{document}
\begin{tblr}{
    colspec={|c|*{4}{Q[c,m,7cm]|}},
    rowspec={|*{4}{Q|}},
}
    & short text & image without `align=m' & image with `align=m' & long text\\
    
    pure image
    & \lipsum[2] 
    & \begin{varwidth}{7cm}\includegraphics[width=4.8cm]{example-image}\end{varwidth}
    & \includegraphics[align=m,width=4.8cm]{example-image}
    &\lipsum[1] \\

    image with text before
    & \lipsum[2] 
    & {some text before\\ \includegraphics[width=4.8cm]{example-image}} 
    & {some text before\\ \includegraphics[align=b,width=4.8cm]{example-image}} 
    & \lipsum[1] \\

    image with text after
    & \lipsum[2] 
    & {\begin{varwidth}{7cm}\centering
      \includegraphics[width=4.8cm]{example-image}\\ some text after\end{varwidth}} 
    & {\includegraphics[align=t,width=4.8cm]{example-image}\\ some text after}
    & \lipsum[1] \\
\end{tblr}

\end{document}

配合 functional 库,应该可以做到检测 \includegraphics 自动加上 varwidth 环境(留作习题)。

首先看你学了这些想做什么。
如果只是要做模板、普通宏包的话,可以参看这篇回答。如果想做到更深一点,涉及到和字体、PDF 等相关内容的话,学怎么写 TeX 宏就不再是首要任务了。

要写宏的话,The TeXbook(以下简写为 BOOK)第 7、20 章、TeX by Topic(以下简写为 TOPIC)第 1、2 章是一定要看的。然后核心的一些概念比如 count、dim 寄存器等等都可以从 BOOK 中找到,TOPIC 和另一本叫 The advanced TeXbook (by David Salomon) 的书可做参考。BOOK 第 24、25、26 是一些东西的汇总,其中有很重要的一个东西就是给出了 TeX 的语法(syntax)规则,比如什么是一个 TeX 意义上的数字,对于理解一些 tricks 有作用(比如 \exp_end_continue_f:w\exp_end: 是怎么实现的)。

然后就是和排版有关的一些宏和 primitive 了,比如每行之间的间距是多少,怎么计算的,怎么断行的,允许在哪些位置断行,哪些位置不能断行,怎么分页的,页眉页脚是怎么实现的,没了 amsmath 怎么打数学公式,等等这些内容基本都是各个引擎通用的,都可以在 BOOK 和 TOPIC 上找到。

还有 output routine,这在 LaTeX 格式下非常复杂,但也是阅读 multicol、lineno 源码的所必需的,属于比较“高级”的内容了。

有了一定的 TeX 基础后,读 BOOK、TOPIC 就可以选读了,从它们的目录就能找到这些知识对应的章节。

但是呢,只是读 BOOK 和 TOPIC 还不能知道诸如 \marks \expanded \Ucharcat 这些 primitive 的用法,这时就需要读 etex、pdftex、xetex、luatex 等等的文档了。

对于 luatex,还有要提的就是嵌入的 lua 脚本引擎了,它扩展了 TeX 的很多功能,文档上都有,这里不多赘述。

对于 LaTeX,新内容说多不多,说少也不少。一些新的玩意,比如 hook、marks、properties、template 等等,基本上看看文档就行了。对于 NTFS(现在基本都用 fontspec 了)、output routine 这种可以不读,但是如果涉及字体就离不开 NTFS 的文档,涉及多栏排版就离不开 output routine(还有“意想不到的”lineno 也和 output routine 有关),如果要编写比较好的绕排宏包需要的知识就更多了,首先对 TeX 的分段算法要了解,LaTeX 的列表的实现也需要了解(可参考 wrapstuff),自定义标题和目录要对 LaTeX 定义标题和目录的方式有所了解,诸如此类,如果在阅读源码是发现不熟悉的命令可以先看看是不是 LaTeX 定义的。

当然,很多时候用 LaTeX 并不需要自己写轮子,用别人写好的宏包就行了,不过有时还是少不了要看源码的。

还有用的很多的就是用 \NewDocumentCommand 定义命令,文档写得也算比较详细了,也可以看看我写的一个相似的宏包 https://github.com/Sophanatprime/lt3ekeys 。如果想知道是怎么实现的,也可以参考它的源码,和 ltcmd 的实现方式基本一致。

对于 LaTeX3,其实只要知道有哪些基本类型,每个类型有什么操作,对它有一个整体了解就行,需要用的时候查查文档。有些命令不知道用法的,一般都有其它宏包用过了,LaTeX3 项目组写的宏包都是不错的参考例子。
如果想读它的源码,挑自己感兴趣的命令去读就好了,很多命令的实现还是很有技巧的。

学 TeX,要多用,用的多了就了解了。

\cs_new_protected:Npn \my_settowidth:Nn #1#2
  {
    \hbox_set:Nn \l_tmpa_box {#2}
    \dim_set:Nn #1 { \box_wd:N \l_tmpa_box }
  }

如果要保持 \l_tmpa_box 不变,可以

\cs_new_protected:Npn \my_settowidth:Nn #1#2
  {
    \group_begin:
    \hbox_set:Nn \l_tmpa_box {#2}
    \exp_args:NNNo \group_end:
    \dim_set:Nn #1 { \dim_use:N \box_wd:N \l_tmpa_box }
  }

发布
问题