4

The code below does a rather code job except that it uses opacity instead of z-index such as to have a soft red background. In the following screenshot you can see that the frame is upon the tcolorbox and the minted highlighted code.

enter image description here

I would like to draw my frame between the tcolorbox and the minted highlighted code. Is there a way to do that ?

\documentclass[12pt,a4paper]{article}

\usepackage[skins, many, minted, breakable, hooks]{tcolorbox}
\usetikzlibrary{tikzmark,calc,fit}

\makeatletter

% Source: http://tex.stackexchange.com/a/50054/3954
\newcommand\myframe[5][-1ex]{%
    \tikz[remember picture,overlay,pin distance=0cm]{
        \draw[draw=#4,line width=1pt,fill=#4!20,rectangle,rounded corners, opacity=#5]
        ( $ (pic cs:#2) + (-1ex,2ex) $ ) rectangle ( $ (pic cs:#3) + (1ex,#1) $ );
        \draw[draw=#4,line width=1pt,rectangle,rounded corners, opacity=1]
        ( $ (pic cs:#2) + (-1ex,2ex) $ ) rectangle ( $ (pic cs:#3) + (1ex,#1) $ );
    }
}

% Source: https://tex.stackexchange.com/a/124688/6880
\newcommand{\mynewminted}[3]{%
    \newminted[#1]{#2}{#3}%
    \tcbset{myminted/#1/.style = {
        minted language = #2, 
        minted options  = {#3}
    }}
}

\newcommand{\create@new@coding@env@title}[4]{%
    \mynewminted{for-coding-#1}{python}{escapeinside=||}
    \newtcblisting[]{coding-#1}[2][1]{%
        listing only,
        width        = ##1\linewidth,
        title        = ##2,
        myminted/for-coding-python,
    }
}

\create@new@coding@env@title{python}{\Large\faPython}{2mm}{1.8mm}

\makeatother


\begin{document}

\section{Hack that works}

\begin{coding-python}{Basic loop
Python}
|\tikzmark{topleft}|def oneloop(n):            
    for i in range(n):
        print(i**2)   |\tikzmark{downright}|

print(oneloop(n))
\end{coding-python}

\myframe{topleft}{downright}{red}{0.2}



\section{Indeed the frame is upon !}

\begin{coding-python}{Basic loop
Python}
|\tikzmark{topleftbis}|def oneloop(n):            
    for i in range(n):
        print(i**2)   |\tikzmark{downrightbis}|

print(oneloop(n))
\end{coding-python}

\myframe{topleftbis}{downrightbis}{red}{1}



\section{Here the frame is under !}

\myframe[-15ex]{topleftter}{downrightter}{red}{1}

\begin{coding-python}{Basic loop
Python}
|\tikzmark{topleftter}|def oneloop(n):            
    for i in range(n):
        print(i**2)   |\tikzmark{downrightter}|

print(oneloop(n))
\end{coding-python}

\end{document}
  • I didn't understand your question. – AndréC Oct 13 '19 at 4:21
  • @AndréC Hello. I have updated my question to clarify the problem I am facing to. – projetmbc Oct 13 '19 at 8:17
4

Yes, this can be achieved rather easily. Draw the box before the code. Now you may say: "Wait, before the code? But then the tikzmarks are not yet set.". This is true, but tikzmark works by writing stuff to the aux file, which gets read at the very beginning. In order to avoid annoying error messages in the first run I wrap the code that draws the frame into \@ifundefined{save@pt@\tikzmark@pp@name{#2}}{}{<code>}, where #2 is the name of the mark. This is tikzmarks builtin way to check whether or not a mark exists. Clearly, this trick has many more seemingly "causality-violating" applications that can be used to draw anything as an "underlay".

\documentclass[12pt,a4paper]{article}

\usepackage[skins, many, minted, breakable, hooks]{tcolorbox}
\usetikzlibrary{tikzmark,calc,fit}

\makeatletter

% Source: http://tex.stackexchange.com/a/50054/3954
\newcommand\myframe[4][-1ex]{%
    \tikz[remember picture,overlay,pin distance=0cm]{
        \@ifundefined{save@pt@\tikzmark@pp@name{#2}}{%
        %    
        }{%
        \draw[draw=#4,line width=1pt,fill=#4!20,rectangle,rounded corners]
        ( $ (pic cs:#2) + (-1ex,2ex) $ ) rectangle ( $ (pic cs:#3) + (1ex,#1) $ );
        \draw[draw=#4,line width=1pt,rectangle,rounded corners, opacity=1]
        ( $ (pic cs:#2) + (-1ex,2ex) $ ) rectangle ( $ (pic cs:#3) + (1ex,#1) $ );
        }
    }% <-added
}

% Source: https://tex.stackexchange.com/a/124688/6880
\newcommand{\mynewminted}[3]{%
    \newminted[#1]{#2}{#3}%
    \tcbset{myminted/#1/.style = {
        minted language = #2, 
        minted options  = {#3}
    }}
}

\newcommand{\create@new@coding@env@title}[4]{%
    \mynewminted{for-coding-#1}{python}{escapeinside=||}
    \newtcblisting[]{coding-#1}[2][1]{%
        listing only,
        width        = ##1\linewidth,
        title        = ##2,
        myminted/for-coding-python,
    }
}

\create@new@coding@env@title{python}{\Large\faPython}{2mm}{1.8mm}

\makeatother


\begin{document}

\section{tikzmark underlay}

\begin{coding-python}{Basic loop
Python}
|\myframe{topleft}{downright}{red}\tikzmark{topleft}|def oneloop(n):            
    for i in range(n):
        print(i**2)   |\tikzmark{downright}|

print(oneloop(n))
\end{coding-python}
\begin{tikzpicture}[overlay,remember picture]
\path (pic cs:topleft) coordinate (aux);
\draw[orange] (aux|-current page.north)
-- (aux|-current page.south);
\end{tikzpicture}


How does a \verb|tikzmark| underlay work?
\begin{enumerate}
 \item Put the \verb|\tikz[overlay,remember picture]{...}| stuff \emph{before} the text/code/whatever which you wish to
annotate (but on the same page).
 \item Inside the \verb|\tikz[overlay,remember picture]{...}|| wrap the pieces that use the \verb|tikzmark|s into 
  \begin{verbatim}
  \@ifundefined{save@pt@\tikzmark@pp@name{#2}}{}{
   <code>}
  \end{verbatim} 
  Here, \verb|<code>| are these pieces.
\end{enumerate}
Since the coordinates of the \verb|tikzmark|s get written to the \verb|aux|
file, they are ``known'' before the compiler goes (in the second run) through
the \LaTeX\ code that defines them.

\end{document}

enter image description here

The orange line is just to guide the eye.

Just for the records: I found the first version of the question very clear and well written.

ADDENDUM: Some other applications of the tikzmark "underly" trick: an environment that does the check.

\documentclass[12pt,a4paper]{article}
\usepackage{environ}
\usepackage{tikz}
\usetikzlibrary{tikzmark}
\makeatletter
\NewEnviron{pendingtikzmark}[1]{\@ifundefined{save@pt@\tikzmark@pp@name{#1}}{%
        %    
        }{\BODY}}
\makeatother
\begin{document}
\begin{tikzpicture}[overlay,remember picture]
\begin{pendingtikzmark}{A}
\fill[red] ([yshift=-0.1ex]pic cs:A) rectangle ([yshift=2ex]pic cs:B);
\end{pendingtikzmark}
\end{tikzpicture}
\begin{tabular}{c|c}
\tikzmark{A}A & B\tikzmark{B}\\
\end{tabular}
\end{document}

enter image description here

Note that in this case you could achieve the same effect with atbegshi (or eso-pic), i.e. by putting the overlay picture in a hook that moves it to the beginning of the shipout, but this might be less efforts. In any case, for the tcolorbox situation above atbegshi will not save the day.

  • I have tried your solution but there is a little problem. The text in frame is indented a little to the right. – projetmbc Oct 13 '19 at 21:00
  • @projetmbc Yes, you are right. Good catch! There was a spurious space caused by \myframe. I removed it by adding a %, and added an orange line to show that it works now. – Schrödinger's cat Oct 13 '19 at 21:09
  • Thanks. I should be more suspicious about the spurious. – projetmbc Oct 13 '19 at 21:28
  • @projetmbc You're welcome! I actually really like this question a lot. – Schrödinger's cat Oct 13 '19 at 21:33
  • @projetmbc I added a version of this to your other recent question. Clearly, there is a lot of room for improvement in this addendum, yet it shows how to use TikZ coordinates in a "causality violating" way: just use \path (<coordinate>) node{\tikzmark{<name of mark>}};. Surely one could modify the TikZ coordinates and nodes in such a way that they write their definition to the aux file, but this would be a major surgery and easily lead to confusion so I did not attempt doing that. – Schrödinger's cat Oct 14 '19 at 4:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.