- 我想定义一个命令
\dialoguestart{black!10}{black!25}
,开始将段落设置为tcolorboxes
,在较浅(黑色!10)和较深(黑色!25)之间交替。- 在另一个命令之后应返回段落的默认设置
\dialogueend
编辑:在与@Schrödinger's cat、@siracusa 和 @cfr 讨论后,很明显最初的问题太大而且太不具体。我发现解决这个问题的一个合理方法是使用环境,我已经相应地调整了帖子。
为了说明这一点,以下代码片段
\documentclass{article}
\usepackage{lipsum}
\usepackage[breakable]{tcolorbox}
%\input{solution_to_this_problem}
\begin{document}
\lipsum[66]
\dialoguestart{black!10}{black!25}
\lipsum[75]
\lipsum[66]
\dialogueend
\lipsum[75]
\end{document}
应该产生
下面的 MWE 产生了所需的结果。但是,它并没有完全满足我的需求,因为新环境一次只能处理一个段落。
\documentclass{article}
\usepackage{lipsum}
\usepackage[breakable]{tcolorbox}
\newif\iflight\lightfalse
\newcommand\toggleboxstart{%
\iflight\begin{dark}%
\else\begin{light}\fi}
\newcommand\toggleboxend{%
\iflight\end{dark}\global\lightfalse%
\else\end{light}\global\lighttrue\fi}
\newenvironment{toggling}[2]{%
\newtcolorbox{light}{colback=#1,colframe=#1,sharp corners,breakable}
\newtcolorbox{dark}{colback=#2,colframe=#2,sharp corners,breakable}
% something that ensures `\toggleboxend\toggleboxstart` being called between paragraphs
}
\newcommand\dialoguestart[2]{\begin{toggling}{#1}{#2}\toggleboxstart}
\newcommand\dialogueend{\toggleboxend\end{toggling}}
\begin{document}
\lipsum[66]
\dialoguestart{black!10}{black!25}
\lipsum[75]
\dialogueend % these two commands
\dialoguestart{black!10}{black!25} % I would like to avoid
\lipsum[66]
\dialogueend
\lipsum[75]
\end{document}
处理多个段落,\toggleboxend
必须\toggleboxstart
在切换环境中的段落之间调用。我尝试添加
\let\origpar\par
\let\origeverypar\everypar
\renewcommand\everypar{\origeverypar\toggleboxstart}
\renewcommand\par{\toggleboxend\origpar}
到环境的定义,这会抛出Paragraph ended before \renew@command was complete
。并使用 {everyhook} 包推送我的命令,这解决了这个问题类似于手头上的
\usepackage{everyhook}
\newenvironment{toggling}[2]{%
\newtcolorbox{light}{colback=#1,colframe=#1,sharp corners,breakable}
\newtcolorbox{dark}{colback=#2,colframe=#2,sharp corners,breakable}
\PushPreHook{par}{\toggleboxstart}
\PushPostHook{par}{\toggleboxend}
}
\newcommand\dialoguestart[2]{\begin{toggling}{#1}{#2}} % no more \toggleboxstart
\newcommand\dialogueend{\end{toggling}} % no more \toggleboxend
结果提示TeX capacity exceeded, sorry [grouping levels=255]
。我还能做什么来执行段落之间的一些代码?在环境中,只会有纯文本。
编辑(经过贾昆谁发起了第二次赏金):我尝试将我的代码复制并粘贴到赏金描述中但没有成功,因此我再次将其发布在这里以提高可读性:
\documentclass{article}
\usepackage{lipsum}
\usepackage[breakable]{tcolorbox}
\newif\iflight
\newenvironment{toggling}[2]{%
\newcommand\toggleboxstart{%
\iflight
%(BEGIN LIGHT)
\begin{tcolorbox}[colback=#1,colframe=#1,sharp corners,breakable]%
\else
%(BEGIN DARK)
\begin{tcolorbox}[colback=#2,colframe=#2,sharp corners,breakable]%
\fi
}%
\newcommand\toggleboxend{%
\end{tcolorbox}\relax
%(END)
\iflight
\global\lightfalse
\else
\global\lighttrue
\fi
}%
\toggleboxstart
\everypar{\toggleboxend\toggleboxstart}%
}{%
\toggleboxend
}
\begin{document}
\lipsum[66]
\begin{toggling}{black!10}{black!25}
\lipsum[75]
\lipsum[66]
\lipsum[66]
\end{toggling}
\lipsum[75]
\end{document}
此代码失败
LaTeX 错误:\begin{tcolorbox} 在输入第 36 行以 \end{tcb@savebox} 结束。
或者
额外},或者被遗忘的\endgroup。
当注释掉的可选参数时\begin{tcolorbox}
。
为什么这不起作用以及如何使其起作用?
(当注释掉\begin{tcolorbox}
和\end{tcolorbox}
并在括号中的测试文本中注释时,我得到了预期的输出。)
编辑:我正在使用以下版本(grep -i version main.log
):
This is pdfTeX, Version 3.14159265-2.6-1.40.20 (TeX Live 2019/Arch Linux) (preloaded format=pdflatex 2019.11.15) 25 NOV 2019 21:59
Package: tcolorbox 2019/09/19 version 4.21 text color boxes
(/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-0-65.sty
Package: pgfcomp-version-0-65 2019/08/03 v3.1.4b (3.1.4b)
(/usr/share/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version-1-18.sty
Package: pgfcomp-version-1-18 2019/08/03 v3.1.4b (3.1.4b)
Library (tcolorbox): 'tcbbreakable.code.tex' version '4.21'
[Loading MPS to PDF converter (version 2006.09.02).]
我尝试使用 tcolorbox 2019/11/15 版本 4.22 并得到相同的结果。
答案1
我不知道为什么,但我无法轻松地用 运行它\everypar
。(在段落之间插入虚拟文本效果很好,但用 替换该虚拟文本\end{tcolorbox}\begin{tcolorbox}
会导致Extra }, or forgotten \endgroup.
错误。)
相反,我提供了一种不同的方法,即用分隔参数替换段落结尾。它的缺点是效率较低,并且会预先读取整个“环境”主体。因此,不要尝试使用任何依赖于更改 catcode 的东西,例如\verb
其中的命令。但实现起来相当简单:
\documentclass{article}
\usepackage{lipsum}
\usepackage[breakable]{tcolorbox}
% soluton_to_this_problem START
\long\def\dialoguestart#1#2#3\dialogueend{% #1: color 1, #2: color 2, #3: body
\dialogueiter{#1}{#2}#3\par\dialogueend
}
\long\def\dialogueiter#1#2#3\par#4\dialogueend{% #1: this color, #2: alternative color, #3: next paragraph, #4: rest of body
\begin{tcolorbox}[colback=#1,colframe=#1,sharp corners,breakable]
#3
\end{tcolorbox}
\if\relax\detokenize{#4}\relax
% #4 is empty => finish
\else
% note how I have swapped #1 and #2 to toggle the colors
\dialogueiter{#2}{#1}#4\dialogueend
\fi
}
% soluton_to_this_problem END
\begin{document}
\lipsum[66]
\dialoguestart{black!10}{black!25}
\lipsum[75]
\lipsum[66]
\lipsum[66]
\dialogueend
\lipsum[75]
\end{document}
\dialoguestart
只是\dialogueiter
宏的一个包装器,以避免在参数不包含段落结尾(段落结尾 = 空行 = \par
)时出现错误。
\dialogueiter
是执行实际工作的宏。它使用参数分隔符将参数分成两部分。第一部分是下一段,第二部分是剩余部分。如果剩余部分不为空,\dialogueiter
则使用递归进一步处理它。
\dialogueend
只是一个参数分隔符,永远不会扩展或执行。
更新:xparse 包提供了(除其他外)\NewDocumentCommand
比 LaTeX2e 宏更强大的宏,\newcommand
允许使用分隔参数(这就是我没有\newcommand
在本答案中使用的原因)和几个可选参数。此外,在这种情况下,它比 TeX 原语(我上面使用过)更舒适,\def
因为它支持可选参数。
\NewDocumentCommand
也比它更安全,\def
因为它会检查该名称的宏是否已经存在,如果存在则打印错误,而不是默默地覆盖现有的宏。
(如果您想实现可选参数,\def
您可以提前查找下一个标记\kernel@ifnextchar
,然后使用需要该参数的宏或不需要该参数的另一个宏。)
\usepackage{xparse}
\NewDocumentCommand{\dialoguestart}{O{black!10} O{black!25} +u\dialogueend}{% [#1: color 1], [#2: color 2], #3: body
\dialogueiter[#1][#2]#3\par\dialogueend
}
\NewDocumentCommand{\dialogueiter}{r[] r[] u\par +u\dialogueend}{% #1: this color, #2: alternative color, #3: next paragraph, #4: rest of body
\begin{tcolorbox}[colback=#1,colframe=#1,sharp corners,breakable]
#3
\end{tcolorbox}
\if\relax\detokenize{#4}\relax
% #4 is empty => finish
\else
% note how I have swapped #1 and #2 to toggle the colors
\dialogueiter[#2][#1]#4\dialogueend
\fi
}