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

注册于 4年前

回答
223
文章
2
关注者
23

实际上,正是由于 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 }
  }

这是因为 TeX 会自动在行间加上一定的间距,第二个图并不是没有加,只是版心太高,不明显而已。具体可参看 The TeXbook 第 12 章,TeX by Topic 第 15 章等。

\documentclass[zihao=-4,a4paper,twoside]{ctexart}
\usepackage{geometry,calc}
\geometry{
  showframe,
  paperwidth   = 9cm,
  paperheight  = 8.4cm,
  scale=0.9, centering
}

\begin{document}
\bfseries\centering
\vspace*{\fill}
\vskip-\topskip
\nointerlineskip
\zihao{0}1234\par
\zihao{-0}567\par

\vspace*{\fill}

\end{document}

FangSong 这个字体名称对应的是 simfang.ttf 这个字体文件。需要(为所有用户)安装这个字体,然后执行 fc-cache -fsv 刷新字体缓存。或者直接放在工作目录。

你使用 \tableofcontents 然后查看 .toc 文件就能看到区别。可以再定义一个 expandable document cmd,放在 \section 里,再看看目录文件。

“展开”与上下文有关,需要区分“展开”和“执行”。“展开”是宏替换,把宏替换为其它东西。

在正常的上下文中,读入、展开、执行、输出这四个依次交替执行,这是一般情形。在所谓的“完全展开”的上下文中,比如 \expanded 的“参数”(也就是 latex3的 e 型展开),还有用 \write 把“参数”写入文件等等,这些参数都会被“展开”,但不会“执行”,“赋值”操作不会生效的。还有 f 型的展开,和 \csname \endcsname 之间的内容的展开。

与之相关的还有 protected 宏、robust 宏。前者不会在 e 型展开中被展开(但在正常的上下文中仍然会被展开)。后者是没有 protected 宏的时代为了阻止宏被展开的一个 trick。

interface3.pdf 中命令标有实心星号的可以在 e 型和 f 型中安全的展开,空心星号的可以在 e 型但不能在 f 型中安全的展开。

这些是下面要讲的内容的基础。

\NewDocumentCommand 定义的命令是 protected 宏,e 型展开不会展开它,所以写入文件时、做为 \expanded 的参数等情形,它都保持不变;而不是 protected 宏,比如 expandable document cmd,则会被替换为它的“替换文本”,这个替换是完全替换,也就是在此上下文中所有能被替换的都会替换,这种替换也就是“完全展开”。

要构造非得用 expandable document cmd 的情形,就比如在会展开而不会执行的上下文中,比如 \tl_set:Ne\str_set:Ne,然后用 \tl_show:N 查看它的值。你的例子只要把 \section 替换为 \tl_set:Ne 就好了。

另外,我们知道,latex3 除了有 int 类型,还有一种特殊的类型:flag,可以自己试试在 e 型展开中 \int_incr:N\flag_raise:N 有何区别。

TeXLive 2024 中,LaTeX3 有部分 API 名称发生了改变,exam-zh 应该是没有更新,可以在 https://gitee.com/xkwxdyy/exam-zh/issues/ 上提 issue。

第二种目录可以认为是一种特殊的索引,先分离出日期,然后分别按照标题和索引的方式处理就好了。可以做到像按字母分类一样按年月日分类。

cus 使用自己的方式制作标题和目录,不依赖也不兼容 etoc 和 titlesec,但是文档完整,样例基本可以直接复制使用。

https://github.com/Sophanatprime/cus 下载全部文件,并下载 https://github.com/Sophanatprime/lt3ekeys 中的所有 .sty 文件(或者把这些文件放在一个名为 lt3ekeys 的文件夹里,然后把这个文件夹(或者这些 .sty 文件)移动到 cus.sty 所属的文件夹下。

也可以直接克隆这两个 repo 到 TDS 目录里,然后执行 texhash 刷新数据库。

推荐后一种。

然后直接编译第一个链接里的 example 文件夹下的主文件。

有问题可以在对应 repo 里提 discussion 或 issue。

发布
问题