10
  • I would like to define a command \dialoguestart{black!10}{black!25} that starts setting the paragraphs into tcolorboxes, alternating between lighter (black!10) and darker (black!25) ones.
  • The default setting of paragraphs should be returned to after another command \dialogueend

Edit: After discussion with @Schrödinger's cat, @siracusa and @cfr it became clear that the initial question was too big and too unspecific. It emerged that a reasonable way to tackle the issue is using an environment and I have adjusted the post accordingly.

To illustrate, the snippet

\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}

should produce

enter image description here

The MWE below produces the desired result. It does not take me all the way, however, as the new environment can only handle one paragraph at a time.

\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}

To handle multiple paragraphs, \toggleboxend and \toggleboxstart have to be called between paragraphs within the toggling environment. I have tried adding

\let\origpar\par
\let\origeverypar\everypar
\renewcommand\everypar{\origeverypar\toggleboxstart}
\renewcommand\par{\toggleboxend\origpar}

to the environment's definition, which throws Paragraph ended before \renew@command was complete. And pushing my commands using the {everyhook} package, which solved this problem similar to the one at hand

\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

results in being prompted TeX capacity exceeded, sorry [grouping levels=255]. What else can I do to execute some code between paragraphs? Within the environment, there will only be plain text.


Edit (by jakun who started the second bounty): My attempt to copy and paste my code into the bounty description did not work out well so I post it here again for better readability:

\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}

This code fails with

LaTeX Error: \begin{tcolorbox} on input line 36 ended by \end{tcb@savebox}.

or

Extra }, or forgotten \endgroup.

when commenting out the optional arguments of \begin{tcolorbox}.

Why does this not work and how could one make it work?

(When commenting out the \begin{tcolorbox} and \end{tcolorbox} and commenting in the test text in parentheses I get the expected output.)

Edit: I am using the following versions (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).]

I have tried using tcolorbox 2019/11/15 version 4.22 instead and get the same result.

9
  • Maybe something like \everypar would be a way to go, see e.g. this answer: tex.stackexchange.com/a/119760.
    – user194703
    Nov 16, 2019 at 14:56
  • I have tried modifying \everypar to begin and \par to end the respective environments. But both commands are called too often by LaTeX itself. My code now causes too many problems for me to fix. This might work, but goes beyond my knowledge and does not yet produce an MWE...
    – D.Roepo
    Nov 17, 2019 at 9:45
  • 1
    Could you add a MWE that demonstrates how your colorboxes should be used, and some example code that shows what contexts the paragraph hook should be able to "survive"?
    – siracusa
    Nov 18, 2019 at 22:14
  • I feel that this will require major efforts to produce something that most likely never will be fully robust. How much resistance do you have to having to add some very short macro or whatever at the end of each paragraph? (@siracusa You need to load tcolorbox with the breakable option to make the above boxes work.)
    – user194703
    Nov 19, 2019 at 1:41
  • 2
    If you're redefining the sectioning commands, then that will complicate things because you want to redefine them again to add a hook ending the highlighting. If you were willing to use an environment \begin{highlightingpars} ... [lots of paragraphs] \end{highlightingpars} that would be easier and potentially less fragile. (It depends what you want to highlight. Does that material itself include tcolorboxes, for example?) Right now, I think this question is either too big (i.e. can't be reasonably done here) or ill-defined (i.e. there are constraints we don't know).
    – cfr
    Nov 20, 2019 at 1:12

1 Answer 1

4
+150

I don't know why but I couldn't get it running easily with \everypar. (Inserting a dummy text between paragraphs worked perfectly but replacing that dummy text with \end{tcolorbox}\begin{tcolorbox} caused an Extra }, or forgotten \endgroup. error.)

Instead I am offering a different approach based on replacing paragraph ends with delimited arguments. It has the disadvantage of being less efficient and that the entire "environment" body is read before hand. So don't try to use anything which relies on changing catcodes like the \verb command inside it. But the implementation is fairly easy:

\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 is just a wrapper around the \dialogueiter macro in order to avoid an error if the argument does not contain a paragraph end (end of paragraph = empty line = \par).

\dialogueiter is the macro which is doing the real job. It splits the argument in two with an argument delimiter. The first part is the next paragraph, the second part is the remaining rest. If the remaining rest is not empty \dialogueiter uses recursion to process it further.

\dialogueend is just an argument delimiter and is never expanded or executed.

enter image description here


Update: The xparse package provides (among others) the \NewDocumentCommand macro which is far more powerful than the LaTeX2e \newcommand macro by allowing to have delimited arguments (which is why I did not use \newcommand for this answer) and several optional arguments. Also it is more comfortable than the TeX primitive \def (which I used above) in this case because it supports optional arguments.

\NewDocumentCommand is also safer than \def because it checks whether a macro of that name exists already and prints an error if it does instead of silently overwriting the existing macro.

(If you wanted to implement optional arguments with \def you could lookahead for the next token with \kernel@ifnextchar and then use either a macro which expects this argument or another macro which does not.)

\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
}
3
  • This works like a charm. How could \dialogestart be redefined to have the colors being optional arguments defaulting to black!10 and black!25?
    – D.Roepo
    Nov 23, 2019 at 12:37
  • @D.Roepo please see my updated answer
    – jakun
    Nov 23, 2019 at 14:48
  • @jakun Re your new bounty code: What tcolorbox version are you using? I can't reproduce the error with 4.22 by just commenting out the optional parameters (though the output is wrong).
    – siracusa
    Nov 24, 2019 at 21:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .