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
213 stars 15 forks source link

v5.1.0(pre2): Extra top space if `parbox=false` tcolorbox starts with a list #262

Closed muzimuzhi closed 6 months ago

muzimuzhi commented 6 months ago

Unfortunately, the fix for #171 caused regression of #123. The extra top space actually consists of vertical spacing of an empty paragraph (\baselineskip) plus \parskip.

Since the culprit is the insertion position of \noindent, different from that in #171 which was the color whatsit, I choose to open a new issue.

\documentclass{article}
\usepackage{tcolorbox}

\tcbset{set parbox/.style={parbox=#1, title={\texttt{parbox=#1}}}}

\begin{document}

% issue #123, extra top space in "parbox=false" tcolorbox starting with a list
\begin{tcolorbox}
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

\begin{tcolorbox}[set parbox=false]
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

% issue #171, no "before skip" if nested in a "parbox=false" tcolorbox
\begin{tcolorbox}
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\begin{tcolorbox}[set parbox=false]
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\end{document}

image

T-F-S commented 6 months ago

Currently, I see no good solution here.

For an automatic solution I see no way. A manual way to remove the extra space, if the content starts with a list, is also not pleasant to implement, see special remove 1 and special remove 2:

\documentclass{article}
\usepackage{tcolorbox}

\tcbset{set parbox/.style={parbox=#1, title={\texttt{parbox=#1}}}}

\makeatletter
\tcbset{
  special remove 1/.style={%
      before upper={\vspace{-13.055555pt}},%     value ?
    },
  special remove 2/.code={%
      \appto\tcb@lateoptions@hook{\preto\kvtcb@before@upper{\let\tcb@set@parbox@indent\@empty}}%     upper
    },
}
\makeatother

\begin{document}

% issue #123, extra top space in "parbox=false" tcolorbox starting with a list
\begin{tcolorbox}
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

\begin{tcolorbox}[set parbox=false,
  % special remove 1,
  special remove 2,
]
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

% issue #171, no "before skip" if nested in a "parbox=false" tcolorbox
\begin{tcolorbox}
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\begin{tcolorbox}[set parbox=false]
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\end{document}

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

\tcbset{set parbox/.style={parbox=#1, title={\texttt{parbox=#1}}}}

\begin{document}

\tcbset{size=minimal,draft}

% issue #123, extra top space in "parbox=false" tcolorbox starting with a list
\begin{tcolorbox}[set parbox=true]
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

\bigskip

\begin{tcolorbox}[set parbox=false]
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}

\bigskip

\makeatletter
\begin{tcolorbox}[set parbox=false,
  %before upper={\vspace{-13.055555pt}},
  %code={\let\tcb@set@parbox@indent\@empty},
  %code={\appto\tcb@lateoptions@hook{\preto\kvtcb@before@upper{\let\tcb@set@parbox@indent\@empty}}},
  ]
  %\vspace{-13.055555pt}%
  %\show\tcb@lateoptions@hook
  %\show\kvtcb@before@upper
  \begin{enumerate}
    \item abc
  \end{enumerate}
\end{tcolorbox}
\makeatother

\bigskip

% issue #171, no "before skip" if nested in a "parbox=false" tcolorbox
\begin{tcolorbox}[set parbox=true]
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\bigskip

\begin{tcolorbox}[set parbox=false]
  content
  \begin{tcolorbox}[before skip=20pt]
    test \texttt{before skip balanced}
  \end{tcolorbox}
\end{tcolorbox}

\end{document}

The parbox=false option is documented as experimental setting. Maybe, I should make the warning more prominent...

muzimuzhi commented 6 months ago

As a replacement of \noindent, \AddToHookNext{para/begin}{\OmitIndent}, initially proposed in your comment https://github.com/T-F-S/tcolorbox/issues/171#issuecomment-1047908828, works well when content is non-empty and not starts with another tcolorbox.

Here is a loose attempt which tries to not apply \OmitIndent in that two corner cases.

Example v2, using \AddToHookNext{para/begin}{<if ... then \OmitIndent fi>}

```tex \documentclass{article} \usepackage{multicol} \usepackage{tcolorbox} \makeatletter \ExplSyntaxOn \def\tcb@set@parbox@indent@{ % "\hook_gput_next_code:ne" is the same as "\ExpandArgs{ne} \AddToHookNext" \hook_gput_next_code:ne {para/begin} { \tcobox_set_parbox_indent_aux:n { \thetcolorboxnumber } } \let\tcb@set@parbox@indent\@empty } % re-initialize \let\tcb@set@parbox@indent\tcb@set@parbox@indent@ \cs_new_protected:Npn \tcobox_set_parbox_indent_aux:n #1 { \bool_lazy_and:nnT % if not inside a nested tcolorbox { \int_compare_p:nNn {#1} = { \thetcolorboxnumber } } % and if inside a "tcb@savebox" env (upper or lower part is non-empty), % hence not at the time of using saved \tcb@upperbox or \tcb@lowerbox % TODO: use a better check { \cs_if_exist_p:N \tcbbreak } { \OmitIndent % debug: typest a visual marker \llap{\smash{\rule{.2em}{1em}\textsuperscript{\thetcolorboxnumber}}} } } \cs_generate_variant:Nn \hook_gput_next_code:nn { ne } \cs_generate_variant:Nn \tcobox_set_parbox_indent_aux:n { o } \ExplSyntaxOff \makeatother \tcbset{set parbox/.style={parbox=#1, title={\texttt{parbox=#1}}}} \newcounter{ypos} \newcommand{\testParboxFalse}[2][]{% \stepcounter{ypos} \begin{multicols}{2} \begin{tcolorbox}[set parbox=true,#1] #2 \end{tcolorbox}% % \rlap{\rule{\columnwidth}{.5pt}} \RecordProperties{parbox=true\arabic{ypos}}{ypos}% \newcolumn \begin{tcolorbox}[set parbox=false,#1] #2 \end{tcolorbox}% % \clap{\rule{2\columnwidth}{.5pt}} % two rules overlaps => "parbox=(true|false)" results in the same height \RecordProperties{parbox=false\arabic{ypos}}{ypos}% \end{multicols} \par % raise an error if heights of boxes with "parbox=(true|false)" are different \ifnum\RefProperty{parbox=true\arabic{ypos}}{ypos}=% \RefProperty{parbox=false\arabic{ypos}}{ypos}\relax \else \PackageError{debug}{Different heights!}{\detokenize{#2}}% \fi } \begin{document} %% Emptyness % All empty \testParboxFalse[title=]{\tcblower} \testParboxFalse{ \tcblower bar\par bar} \testParboxFalse{foo\par foo \tcblower} \testParboxFalse{foo\par foo \tcblower bar\par bar} \newpage %% Special beginning content(s) % Content starts with a list % issue #123, extra top space in "parbox=false" tcolorbox starting with a list \testParboxFalse{ \begin{enumerate} \item abc \end{enumerate} \tcblower \begin{enumerate} \item abc \end{enumerate} } % Content starts with another tcolorbox \testParboxFalse{ \begin{tcolorbox} nested\par foo \end{tcolorbox} \tcblower \begin{tcolorbox}[set parbox=false] nested\par bar \end{tcolorbox} } %\newpage %% Misc special cases % Content consists of nested tcolorbox(es) % from issue #171, no "before skip" if nested in a "parbox=false" tcolorbox \testParboxFalse{ before \begin{tcolorbox}[before skip=20pt] test \texttt{before skip} \end{tcolorbox} after \tcblower before \begin{tcolorbox}[before skip=0pt] test \texttt{before skip} \end{tcolorbox} after } \end{document} ```

Current (v6.1.0) \OmitIndent unconditionally \OmitIndent conditionally
image image image
image image image

Example v2 typesets 7 pairs of examples

T-F-S commented 6 months ago

This is a very interesting idea. I tested your code and added some further tests. Currently, all seems to work with the conditional \OmitIndent. Thank you!

Even, if new things come up, the approach may be adaptable to further conditions. I will make further tests.

By the way: The height control test with \RecordProperties did not work for me. All ypos values are 0 in my .aux file...

muzimuzhi commented 6 months ago

By the way: The height control test with \RecordProperties did not work for me. All ypos values are 0 in my .aux file...

I forgot to save positions first, (see texdoc ltproperties-doc, doc for predefined property xpos and ypos). :)

This \testParboxFalse should work. \UseName{tex_savepos:D}% added right before each \RecordProperties.

\newcommand{\testParboxFalse}[2][]{%
  \stepcounter{ypos}
  \begin{multicols}{2}
    \begin{tcolorbox}[set parbox=true,#1]
      #2
    \end{tcolorbox}%
    % \rlap{\rule{\columnwidth}{.5pt}}
    \UseName{tex_savepos:D}%
    \RecordProperties{parbox=true\arabic{ypos}}{ypos}%
    \newcolumn
    \begin{tcolorbox}[set parbox=false,#1]
      #2
    \end{tcolorbox}%
    % \clap{\rule{2\columnwidth}{.5pt}}
    % two rules overlaps => "parbox=(true|false)" results in the same height
    \UseName{tex_savepos:D}%
    \RecordProperties{parbox=false\arabic{ypos}}{ypos}%
  \end{multicols}
  \par
  % raise an error if heights of boxes with "parbox=(true|false)" are different
  \ifnum\RefProperty{parbox=true\arabic{ypos}}{ypos}=%
        \RefProperty{parbox=false\arabic{ypos}}{ypos}\relax
  \else
    \PackageError{debug}{Different heights!}{\detokenize{#2}}%
  \fi
}
T-F-S commented 6 months ago

This \testParboxFalse should work. \UseName{tex_savepos:D}% added right before each \RecordProperties.

Yes, thank you. Works fine :-)

T-F-S commented 6 months ago

Fixed with https://github.com/T-F-S/tcolorbox/releases/tag/v6.2.0 @muzimuzhi Thanks again for the valuable input.

muzimuzhi commented 5 months ago

I'm still more or less worried about using \cs_if_exist_p:N \tcbbreak as a marker to check if it's inside a tcb@savebox environment (aka, if it's being saved to a title/upper/lower box). (\tcbbreak was chosen because I found no better markers in \tcb@lrbox.)

Would a new, specific boolean be needed?

T-F-S commented 5 months ago

I'm still more or less worried about using \cs_if_exist_p:N \tcbbreak as a marker to check if it's inside a tcb@savebox environment (aka, if it's being saved to a title/upper/lower box). (\tcbbreak was chosen because I found no better markers in \tcb@lrbox.)

Would a new, specific boolean be needed?

A boolean marker will not enhance the code per se, but may prevent accidential code changes in future which may break the test. So, yes, I will add a boolean as marker.