TransWikia.com

Automatically connecting adjacent tcolorboxes

TeX - LaTeX Asked on May 12, 2021

I would like some adjacent tcolorboxes to be connected, but not by using the option beforeafter skip=0pt, because I also want those boxes to have normal spacing from other text. That is, I expect the boxes as in this picture:

enter image description here

automatically changing to the looking in the picture below:

enter image description here

How can one achieve this?

Below is a MWE to play with.

documentclass{article}

usepackage[many]{tcolorbox}
usepackage{blindtext}

newtcolorbox{definition}{enhanced jigsaw,pad at break*=1mm,breakable,
left=4mm,right=4mm,top=1mm,bottom=1mm,
% beforeafter skip balanced=0pt,
colback=orange!10,boxrule=0pt,frame hidden,
borderline west={1.5mm}{-1mm}{green!50!black},arc=.7mm}

begin{document}

begin{definition}
    ...
end{definition}

begin{definition}
    blindtext
end{definition}

end{document}

Old:

I have written the following (very ugly) code:

documentclass{article}
usepackage[many]{tcolorbox}

% newenvironment{testbox}
% {begin{tcolorbox}
% [enhanced jigsaw,pad at break*=1mm,breakable,
% colback=orange!10!white,boxrule=0pt,frame hidden,
% borderline west={1.5mm}{-1mm}{green}]}
% {end{tcolorbox}}

usepackage[user,savepos]{zref}
usepackage{xifthen}

newlength{deftop}
newlength{defbot}
newlength{defsep}
newcounter{def}

newenvironment{testbox}
{vspace{-1mm}begin{tcolorbox}[blank,breakable]
stepcounter{def}
zsavepos{defarabic{def}1}
setlength{globaldeftop}{
  dimexprpaperheight - zposy{defarabic{def}1} sp}
setlength{globaldefsep}{deftop-defbot}
ifthenelse{lengthtest{defsep > -0.1mm}}
  {ifthenelse{lengthtest{defsep < 3mm}}{vspace{-4mm}}{}}
  {}par
  % This should be a dynamical quantity,
  % adjusting according to the page's situation
begin{tcolorbox}
[enhanced jigsaw,pad at break*=1mm,breakable,
colback=orange!10!white,boxrule=0pt,frame hidden,
borderline west={1.5mm}{-1mm}{green}]}
{end{tcolorbox}
zsavepos{defarabic{def}2}
setlength{globaldefbot}{
  dimexprpaperheight - zposy{defarabic{def}2} sp}
end{tcolorbox}vspace{-1mm}}

begin{document}
    Some texts.
    begin{testbox}box 1end{testbox}
    Some texts.
    begin{testbox}box 2end{testbox}%vspace{-4mm}
    begin{testbox}box 3end{testbox}
end{document}

It uses zref to record the position of each box so as to check whether two boxes are adjacent. However this solution does not always work. This code doesn’t check if two boxes are on the same page (I did write a version that tries to check the page number issue, but since page number checking in LaTeX doesn’t work pretty well on the edge of the page, I disgarded it). Also, the 4mm is not a good idea because sometimes when a page is very loose, vspace{4mm} is not enough to connect them.

I wish to know if you have a better and prettier way to achieve this effect.

One Answer

Here's an approach using my new favourite tool in expl3's arsenal: peek_analysis_map_inline:n. It allows you to scan the input stream token by token, letting you act on each one as required. The ScanEnv command:

ScanEnv [*] {<env>} {<true>} {<false>}

looks ahead in the token stream, ignoring spaces (and if * is given, ignoring par), looking for begin{<env>}. If that is found, it inserts the code <true> before the ignored spaces and par. If the environment is not found, the <false> code is inserted instead.

With that, you can do what you want with

ScanEnv* {definition} {vspace{-baselineskip}} {}

plus a AfterEnvEnd to be able to use the end environment hook to insert the code. If you want to scan multiple environments, you need nested calls:

ScanEnv* {definition} {vspace{-baselineskip}}
  {ScanEnv* {definition*} {vspace{-baselineskip}} {}}

enter image description here

Here's the code:

RequirePackage{xparse}
ExplSyntaxOn
makeatletter
NewDocumentCommand AfterEnvEnd { +m }
  { jinwen_after_env_end:nw {#1} }
cs_new_protected:Npn jinwen_after_env_end:nw #1 #2
       if@ignore@ignorefalseignorespacesfi
  { #2 if@ignore@ignorefalseignorespacesfi #1 }
makeatother
NewDocumentCommand ScanEnv { s m +m+m }
  {
    IfBooleanTF {#1}
      { jinwen_scan_env_ignore_par:nTF }
      { jinwen_scan_env:nTF }
          {#2} {#3} {#4}
  }
cs_new_protected:Npn jinwen_scan_env:nTF
  { __jinwen_scan_env:NnTF c_false_bool }
cs_new_protected:Npn jinwen_scan_env_ignore_par:nTF
  { __jinwen_scan_env:NnTF c_true_bool }
tl_new:N l__jinwen_collected_tl
cs_new_protected:Npn __jinwen_scan_env:NnTF #1 #2 #3 #4
  {
    tl_clear:N l__jinwen_collected_tl
    peek_analysis_map_inline:n
      {
        tl_put_right:Nn l__jinwen_collected_tl {##1}
        int_compare:nNnTF { "##3 } = { 0 }
          {
            exp_args:No token_if_eq_meaning:NNTF {##1} begin
              { peek_analysis_map_break:n { __jinwen_chk_env:nTFn {#2} {#3} {#4} } }
              {
                bool_lazy_and:nnF {#1}
                    { exp_args:No token_if_eq_meaning_p:NN {##1} par }
                  { __jinwen_scan_env_end:n {#4} }
              }
          }
          { int_compare:nNnF { "##3 } = { 10 } { __jinwen_scan_env_end:n {#4} } }
      }
  }
cs_new_protected:Npn __jinwen_scan_env_end:n #1
  { peek_analysis_map_break:n { __jinwen_reinsert_tokens:nn {#1} { } } }
cs_new_protected:Npn __jinwen_reinsert_tokens:nn #1 #2
  {
    use:x
      {
        tl_clear:N exp_not:N l__jinwen_collected_tl
        exp_not:n {#1} l__jinwen_collected_tl #2
      }
  }
cs_new_protected:Npn __jinwen_chk_env:nTFn #1 #2 #3 #4
  {
    exp_args:Nx __jinwen_reinsert_tokens:nn
      { str_if_eq:nnTF {#1} {#4} { exp_not:n {#2} } { exp_not:n {#3} } } { {#4} }
  }
ExplSyntaxOff

documentclass{article}
usepackage[many]{tcolorbox}
usepackage{blindtext}

newtcolorbox{definition}{enhanced jigsaw,pad at break*=1mm,breakable,
left=4mm,right=4mm,top=1mm,bottom=1mm,
% beforeafter skip balanced=0pt,
colback=orange!10,boxrule=0pt,frame hidden,
borderline west={1.5mm}{-1mm}{green!50!black},arc=.7mm}

newtcolorbox{definition*}{enhanced jigsaw,pad at break*=1mm,breakable,
left=4mm,right=4mm,top=1mm,bottom=1mm,
% beforeafter skip balanced=0pt,
colback=red!10,boxrule=0pt,frame hidden,
borderline west={1.5mm}{-1mm}{green!50!black},arc=.7mm}

defscandefinitionenv{%
  AfterEnvEnd{%
    ScanEnv*{definition}%
      {vspace{-baselineskip}}%
      {ScanEnv*{definition*}%
        {vspace{-baselineskip}}%
        {}}}}

AddToHook{env/definition/end}{scandefinitionenv}%
AddToHook{env/definition*/end}{scandefinitionenv}%

begin{document}

begin{definition}
  This is followed by text
end{definition}

something else

begin{definition}
  This is followed by another definition
end{definition}

begin{definition*}
  See, no gap :)
end{definition*}

begin{quote}
  some other environment
end{quote}

end{document}

Correct answer by Phelype Oleinik on May 12, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP