T-F-S / tcolorbox

A LaTeX package to create highly customizable colored boxes.
http://www.ctan.org/pkg/tcolorbox
LaTeX Project Public License v1.3c
224 stars 16 forks source link

With `parbox=false`, `\par` is ignored in tcolorbox that starts a list #245

Closed muzimuzhi closed 1 year ago

muzimuzhi commented 1 year ago

(originally reported as part of https://tex.stackexchange.com/q/695327, the TeX-SX question mentioned in #244)

If the content of a list (environment) starts with a parbox=false tcolorbox, then the \par is ignored in this tcolorbox.

\documentclass{article}
\usepackage{tcolorbox}

\begin{document}
\begin{enumerate}
  \item
  \begin{tcolorbox}[title={\texttt{parbox=true}}]
    abc \par def \par ghi
  \end{tcolorbox}
\end{enumerate}

\begin{enumerate}
  \item
  \begin{tcolorbox}[title={\texttt{parbox=false}, first in a list}, parbox=false]
    abc \par def \par ghi
  \end{tcolorbox}
  \begin{tcolorbox}[title={\texttt{parbox=false}, second in a list}, parbox=false]
    abc \par def \par ghi
  \end{tcolorbox}
\end{enumerate}
\end{document}

image

Analysis

Inside a list, \par is redefined as (set by \@setpar in \@trivlist)

\def\@trivlist{%
 ...
  \@setpar{\if@newlist
             \advance\par@deathcycles \@ne
             \ifnum \par@deathcycles >\@m
               \@noitemerr
               {\@@par}%
             \fi
           \else
             {\@@par}%
           \fi}%
  \global \@newlisttrue
  \@outerparskip \parskip}

\def\@setpar#1{\def\par{#1}\def\@par{#1}}

Hence \par will be silently ignored when \if@newlist is true. Normally \if@newlist is set to false globally when the first paragraph inside a list starts, by the special \everypar set by \@item.

To fix #171, \noindent is moved from \tcb@parbox@use@false (actually \tcb@parbox@false@settings) to a latter place, at the beginning of hooks like before upper, see commit 20805e947fa5453f2ada5a26f457c537515062df (shipped with v5.1.0). Unfortunately the \everypar token list use by these two \noindents are different, and this resulted in a regression.

In \tcb@lrbox,

https://github.com/T-F-S/tcolorbox/blob/6d98db4263c612279cca47efde22d1ef8404e573/tex/latex/tcolorbox/tcolorbox.sty#L1012-L1029

An extended example showing some debug info

```tex \documentclass{article} \usepackage{tcolorbox} \begin{document} \makeatletter \def\@par@inlist{% set by \@setpar in \@trivlist \if@newlist \advance\par@deathcycles \@ne \ifnum \par@deathcycles >\@m \@noitemerr {\@@par}% \fi \else {\@@par}% \fi} \def\debugInfo{ % {\ttfamily <@newlist = \if@newlist T\else F\fi, par \ifx\par\@par@inlist= \else!= \fi par@inlist>% }% } \makeatother \LaTeXe: \fmtversion, tcolorbox: \UseName{tcb@version} \begin{enumerate} \item abc \par def \par ghi\debugInfo \end{enumerate} \begin{enumerate} \item \begin{minipage}[t]{10cm} minipage, abc \par def \par ghi\debugInfo \end{minipage} \end{enumerate} \begin{enumerate} \item \parbox[t]{10cm}{parbox, abc \par def \par ghi\debugInfo} \end{enumerate} \begin{enumerate} \item \begin{tcolorbox}[title={\texttt{parbox=true}}] abc \par def \par ghi\debugInfo \end{tcolorbox} \end{enumerate} \begin{enumerate} \item \begin{tcolorbox}[title={\texttt{parbox=false}, first in a list}, parbox=false] abc \par def \par ghi\debugInfo \end{tcolorbox} \begin{tcolorbox}[title={\texttt{parbox=false}, second in a list}, parbox=false] abc \par def \par ghi\debugInfo \end{tcolorbox} \end{enumerate} \end{document} ```

image

I think it's wrong to leave \if@newlist true inside a tcolorbox, no matter /tcb/parbox is true or false. But I'm unsure if adding \global\@newlistfalse to \tcb@lrbox is the right way.

For a minipage as the first element inside a list,

T-F-S commented 1 year ago

Once again parbox=false problems... :-(

I think it's wrong to leave \if@newlist true inside a tcolorbox, no matter /tcb/parbox is true or false. But I'm unsure if adding \global\@newlistfalse to \tcb@lrbox is the right way.

Currently, I see no better way as to add \global\@newlistfalse to \tcb@lrbox and its breakable counterpart \tcb@vbox. Also, I would add \let\par\@@par. This gives the following:

\documentclass{article}
\usepackage[breakable]{tcolorbox}

\makeatletter

% lrbox with integrated minipage
% #1 box
% #2 width
% #3 color (for color stack)
\def\tcb@lrbox#1#2#3{%
  \edef\reserved@a{%
    \endgroup
    \setbox#1\hbox{%
      \begingroup\aftergroup}%
    \def\noexpand\@currenvir{\@currenvir}%
    \def\noexpand\@currenvline{\on@line}}%
  \reserved@a
    \@endpefalse%
    \global\@newlistfalse% +++++++++++++
    \let\par\@@par%        +++++++++++++
    \let\tcbbreak\par%
    \csname tcb@parbox@use@\kvtcb@parbox\endcsname%
    \tcb@minipage{#2}%
    \color{#3}%
    \tcb@hyph@fix\ignorespaces}
\let\endtcb@lrbox=\endminipage

\let\tcb@savebox=\tcb@lrbox
\let\endtcb@savebox=\endtcb@lrbox

\def\tcb@vbox#1#2#3{%
  \edef\reserved@a{%
    \endgroup%
    \setbox#1\vbox{\hsize=#2%
      \begingroup\aftergroup}%
      \def\noexpand\@currenvir{\@currenvir}%
      \def\noexpand\@currenvline{\on@line}}%
  \reserved@a%
  \@endpefalse%
  \global\@newlistfalse% +++++++++++++
  \let\par\@@par%        +++++++++++++
  \let\tcbbreak\tcb@@break%
  \iftcb@usecolorstack%
    \pdfcolSwitchStack{tcb@breakable}%
  \fi%
  \color@begingroup%
  \textwidth\hsize%
  \columnwidth\hsize%
  \csname tcb@parboxrestore@\kvtcb@parbox\endcsname%
  \def\@mpfn{mpfootnote}%
  \def\thempfn{\thempfootnote}%
  \c@mpfootnote\z@%
  \let\@footnotetext\@mpfootnotetext%
  \let\@listdepth\@mplistdepth \@mplistdepth\z@%
  \@setminipage%
  \color{#3}%
  \tcb@hyph@fix%
  \let\tcb@drawcolorbox\tcb@drawcolorbox@standalone%
  \let\FN@pp@footnote\@empty% disable perpage mode of 'footmisc' package
}

\makeatother

\begin{document}
\makeatletter
\def\@par@inlist{% set by \@setpar in \@trivlist
  \if@newlist
    \advance\par@deathcycles \@ne
    \ifnum \par@deathcycles >\@m
      \@noitemerr
      {\@@par}%
    \fi
  \else
    {\@@par}%
  \fi}
\def\debugInfo{ %
  {\ttfamily
    <@newlist = \if@newlist T\else F\fi,
    par \ifx\par\@par@inlist= \else!= \fi par@inlist>%
  }%
}
\makeatother

\LaTeXe: \fmtversion,
tcolorbox: \UseName{tcb@version}

\begin{enumerate}
  \item abc \par def \par ghi\debugInfo
\end{enumerate}

\begin{enumerate}
  \item
  \begin{minipage}[t]{10cm}
    minipage, abc \par def \par ghi\debugInfo
  \end{minipage}
\end{enumerate}

\begin{enumerate}
  \item \parbox[t]{10cm}{parbox, abc \par def \par ghi\debugInfo}
\end{enumerate}

\begin{enumerate}
  \item
  \begin{tcolorbox}[title={\texttt{parbox=true}}]
    abc \par def \par ghi\debugInfo
  \end{tcolorbox}
\end{enumerate}

\begin{enumerate}
  \item
  \begin{tcolorbox}[title={\texttt{parbox=false}, first in a list}, parbox=false]
    abc \par def \par ghi\debugInfo
  \end{tcolorbox}
  \begin{tcolorbox}[title={\texttt{parbox=false}, second in a list}, parbox=false]
    abc \par def \par ghi\debugInfo
  \end{tcolorbox}
\end{enumerate}

\end{document}

parbox=false is a patchwork which I am not so very happy with... OK, fingers crossed that no serious side effects happen.

muzimuzhi commented 1 year ago

Currently, I see no better way as to add \global\@newlistfalse to \tcb@lrbox and its breakable counterpart \tcb@vbox. Also, I would add \let\par\@@par.

Maybe \let\par\@@par can be added to \tcb@parboxrestore (actually \tcb@parbox@false@settings), as how it's contained in \@parboxrestore (actually \@arrayparboxrestore) in LaTeX2e.

\def\tcb@parbox@false@settings{%
  \let\par\@@par%        +++++++++++++
  \linewidth\hsize%
  \@totalleftmargin\z@%
  \leftskip\z@skip%
  \rightskip\z@skip%
  \@rightskip\z@skip%
}

% from tcbbreakable.code.tex
\let\tcb@parboxrestore@false\tcb@parbox@false@settings
Full example, v3

```tex \documentclass{article} \usepackage[breakable]{tcolorbox} \makeatletter % lrbox with integrated minipage % #1 box % #2 width % #3 color (for color stack) \def\tcb@lrbox#1#2#3{% \edef\reserved@a{% \endgroup \setbox#1\hbox{% \begingroup\aftergroup}% \def\noexpand\@currenvir{\@currenvir}% \def\noexpand\@currenvline{\on@line}}% \reserved@a \@endpefalse% \global\@newlistfalse% +++++++++++++ \let\tcbbreak\par% \csname tcb@parbox@use@\kvtcb@parbox\endcsname% \tcb@minipage{#2}% \color{#3}% \tcb@hyph@fix\ignorespaces} \let\endtcb@lrbox=\endminipage \let\tcb@savebox=\tcb@lrbox \let\endtcb@savebox=\endtcb@lrbox \def\tcb@vbox#1#2#3{% \edef\reserved@a{% \endgroup% \setbox#1\vbox{\hsize=#2% \begingroup\aftergroup}% \def\noexpand\@currenvir{\@currenvir}% \def\noexpand\@currenvline{\on@line}}% \reserved@a% \@endpefalse% \global\@newlistfalse% +++++++++++++ \let\tcbbreak\tcb@@break% \iftcb@usecolorstack% \pdfcolSwitchStack{tcb@breakable}% \fi% \color@begingroup% \textwidth\hsize% \columnwidth\hsize% \csname tcb@parboxrestore@\kvtcb@parbox\endcsname% \def\@mpfn{mpfootnote}% \def\thempfn{\thempfootnote}% \c@mpfootnote\z@% \let\@footnotetext\@mpfootnotetext% \let\@listdepth\@mplistdepth \@mplistdepth\z@% \@setminipage% \color{#3}% \tcb@hyph@fix% \let\tcb@drawcolorbox\tcb@drawcolorbox@standalone% \let\FN@pp@footnote\@empty% disable perpage mode of 'footmisc' package } \def\tcb@parbox@false@settings{% \let\par\@@par% +++++++++++++ \linewidth\hsize% \@totalleftmargin\z@% \leftskip\z@skip% \rightskip\z@skip% \@rightskip\z@skip% } % from tcbbreakable.code.tex \let\tcb@parboxrestore@false\tcb@parbox@false@settings \makeatother \begin{document} \makeatletter \def\@par@inlist{% set by \@setpar in \@trivlist \if@newlist \advance\par@deathcycles \@ne \ifnum \par@deathcycles >\@m \@noitemerr {\@@par}% \fi \else {\@@par}% \fi} \def\debugInfo{ % {\ttfamily <@newlist = \if@newlist T\else F\fi, par \ifx\par\@par@inlist= \else!= \fi par@inlist>% }% } \makeatother \LaTeXe: \fmtversion, tcolorbox: \UseName{tcb@version} \begin{enumerate} \item abc \par def \par ghi\debugInfo \end{enumerate} \begin{enumerate} \item \begin{minipage}[t]{10cm} minipage, abc \par def \par ghi\debugInfo \end{minipage} \end{enumerate} \begin{enumerate} \item \parbox[t]{10cm}{parbox, abc \par def \par ghi\debugInfo} \end{enumerate} \def\tcbtests{% \begin{enumerate} \item \begin{tcolorbox}[title={\texttt{parbox=true}}] abc \par def \par ghi\debugInfo \end{tcolorbox} \end{enumerate} \begin{enumerate} \item \begin{tcolorbox}[title={\texttt{parbox=false}, first in a list}, parbox=false] abc \par def \par ghi\debugInfo \end{tcolorbox} \begin{tcolorbox}[title={\texttt{parbox=false}, second in a list}, parbox=false] abc \par def \par ghi\debugInfo \end{tcolorbox} \end{enumerate} } \subsection*{breakable=false} \tcbset{breakable=false} \tcbtests \subsection*{breakable=true} \tcbset{breakable=true} \tcbtests \end{document} ```

T-F-S commented 1 year ago

Maybe \let\par\@@par can be added to \tcb@parboxrestore (actually \tcb@parbox@false@settings), as how it's contained in \@parboxrestore (actually \@arrayparboxrestore) in LaTeX2e.

Yes, that is reasonable. I will do so.

T-F-S commented 1 year ago

Fixed with https://github.com/T-F-S/tcolorbox/releases/tag/v6.1.0