Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I would like to create a TikZ style that adds a thick outline to the node text using the \contour{<colour>}{<text>} macro from the contour package (as in the answer to the question TikZ: halo around text?), i.e. I want to define a style halo so that

\node [halo] {Text};

is equivalent to

\node {\contour{red}{Text}};

My problem is that I cannot tell TikZ to use the node text as an argument for a macro.

The font option only works with switches like \itshape (as opposed to \textit{}, which requires the text to be italicised to be provided in the argument).

There are two keys execute at begin node={<code>} and execute at end node={<code>} that can be used to call code at the beginning and end of the node, but I can't split the \contour command up using execute at begin node={\contour{red{} and execute at end node={}} because the brackets don't match. Replacing { with \bgroup and } with \egroup fails with Missing } inserted.

Here's am MWE to play with:

\documentclass{article}

\usepackage{tikz}
\usepackage[outline]{contour}
\contourlength{1.2pt}


\begin{document}

    \begin{tikzpicture}
    \node {\contour{red}{Text}};
%    \node [font=\contour{red}{}] {Text}; % Doesn't change the text
%    \node [execute at begin node={\contour{red}{},execute at end node={}}] {Text};  % Fails with ``Runaway argument?''
%    \node [execute at begin node={\contour{red}\bgroup},execute at end node={\egroup}] {Text};  % Fails with ``Missing } inserted''
    \end{tikzpicture}
\end{document}
share|improve this question
    
Couldn't you split the command using \bgroup and \egroup? If you look at the code for the key matrix of nodes in the matrix library then that's what it does (according to the manual - I didn't look at the code). –  Loop Space May 18 '11 at 12:46
    
@Andrew: Good idea, but unfortunately it doesn't work in this case. Expansion stuff, probably (that's my catch-all excuse TeX problems that I don't understand)... –  Jake May 18 '11 at 12:52
    
Does looking at the code of the contour package help? Maybe internally it is defined in a way that would allow you to split it in the right way? –  Seamus May 18 '11 at 13:02
1  
The node content is actually not an argument of \node but typeset as a box. Also with "function" you mean "macro", right? –  Martin Scharrer May 18 '11 at 13:09

1 Answer 1

up vote 11 down vote accepted

After looking at the source code of contour (at least at the pdftex driver part) I figured that it typesets the text twice, once for the contour and once normally. The content must be short (i.e. not \long) and is processed in restricted horizontal mode. The issue is that TikZ processes the node content as box, not as macro argument. This is normally very good because it allows verbatim content etc. in the node, but doesn't allow direct access to the un-typeset content required for \contour and other macros.

Note that \contour partly works with boxed content if you use \unhbox. The issue is just that you need it twice and can't change the color! So either both have the same color which makes the contour not very useful or you only have contour without the real text. I patched the contour source code (for pdftex only) to get you there:

% (uncomment the `%` lines to enable typesetting of the real text, but in the same color than the contour)
\documentclass{article}

\usepackage{tikz}
\usepackage[outline]{contour}
\contourlength{.1pt}

\makeatletter
\def\tikzcontour#1{%
    \begingroup
    \color{#1}%
    %\def\contourcolor{#1}%
    \begin{lrbox}{\@tempboxa}%
}
% Mostly taken from `\@contour@outline` from `contour`s `pdftex.cnt` driver file:
% Work with pdftex only
\def\endtikzcontour{%
    \end{lrbox}%
    %\setbox0=\hbox{\usebox\@tempboxa}%
    \begingroup
    \setlength\con@length{\con@base@length}%
    \setlength\con@length{2\con@length}%
    \setlength\con@length{0.99626400996\con@length}%
    %\color{\contourcolor}%
    \con@coloroff
    \pdfliteral{%
      q
      1 j
      1 J
      1 Tr
      \strip@pt\con@length\space w
    }%
    %\rlap
    \mbox% change back to `\rlap` when "real" text is drawn
    {\unhbox\@tempboxa}%
    \pdfliteral{%
      Q
    }%
    \endgroup
    %\unhbox0
    \endgroup
}
\makeatother

\begin{document}
    \begin{tikzpicture}
    \node [execute at begin node={\tikzcontour{red}},execute at end node={\endtikzcontour}] {Text};
    \end{tikzpicture}
\end{document}

This gives:

Result


However it is possible to collect the content of the TikZ node with the same technique my collcell package uses to collect the content of a tabular cell, i.e. to skip the internal code header and read it token by token until the end is reached.

Here some proof-of-concept quick&dirty solution which reuses code from collcell. Do not use \collectcell with tables in the same file. I will write a dedicated, more stable solution when I find the time.

\documentclass{article}

\usepackage{tikz}
\usepackage[outline]{contour}
\contourlength{.05em}
\usepackage{collcell}

\makeatletter

\def\collect@cell@look@{%
  \cc@case
    \@sptoken{%
      \edef\collect@cell@spaces{\collect@cell@spaces\space}%
      \collect@cell@eatspace
    }%
    \bgroup{\collect@cell@group}%
    \egroup{\afterassignment\collect@cell@end\let\@let@token=}%
    %\egroup{\collect@cell@end}%
    \default{\collect@cell@arg}%
  \endcc@case
}

\def\tikzcontour#1#2{\@tikzcontour{#1}#2\@endtikzcontour}
\def\@tikzcontour#1#2\ignorespaces#3\@endtikzcontour{%
          \bgroup%
            \ifx\tikz@textcolor\pgfutil@empty%
            \else%
              \pgfutil@colorlet{.}{\tikz@textcolor}%
            \fi%
            \pgfsetcolor{.}%
            \setbox\tikz@figbox=\box\pgfutil@voidb@x%
            \tikz@uninstallcommands%
            \tikz@halign@check%
            \contour{#1}{\ignorespaces #3\unskip}%
            \egroup
            \tikz@fig@collectresetcolor%
        }
\let\collcell@afteruser=\empty
\makeatother

\tikzset{contour/.style={execute at begin node={\collectcell{\tikzcontour{#1}}\ignorespaces}}}

\begin{document}
    \begin{tikzpicture}
        \node [draw,green,contour=red,circle] {Text};
    \end{tikzpicture}
\end{document}

Result 2

share|improve this answer
    
Wow, that looks very promising! One small issue: As it is now, this approach causes TikZ to lose track of the width (interestingly not of the height) of the text, adding draw will result in a rectangle of minimum width, but with correct height. –  Jake May 18 '11 at 14:24
1  
@Jake: I fixed that (was \rlap vs. \mbox) and also added a better solution. –  Martin Scharrer May 18 '11 at 14:50

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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