4

Consider the following MWE. I want to give a color (red) to the boxed object (Hello). Unfortunately, it does not work as expected as if the boxed objects are shielded. How to remove such a shield?

\documentclass{article}
\usepackage{xcolor}

\newsavebox\MyBox
\savebox\MyBox{Hello}

\begin{document}
\textcolor{red}{\usebox\MyBox}
\end{document}
dgoodmaniii
  • 4,270
  • 1
  • 19
  • 36

3 Answers3

16

\savebox is defined using \sbox and the latter is defined in LaTeX as:

\long\def\sbox#1#2{\setbox#1\hbox{%
  \color@setgroup#2\color@endgroup}}

The text argument of \sbox is wrapped in a group \color@setgroup and \color@endgroup, when package color is loaded:

\def\color@setgroup{\begingroup\set@color}
\let\color@begingroup\begingroup
\def\color@endgroup{\endgraf\endgroup}

Additionally \color@setgroup sets the current color that is active, when the box is build. This is on purpose, because also other properties are fixed like the font. A box contents does not change in \textit or \textbf, neither the font family or size can be changed.

Colors are usually implemented by specials, then the following definition helps:

\makeatletter
\newcommand{\sboxcolorless}[2]{%
  \setbox#1\hbox{\color@begingroup#2\color@endgroup}%
}
\makeatother

It is very, very important, that the argument is put into a group, here \color@begingroup and \color@endgroup, because #2 can contain \color commands, which automatically reset the color after the group. If an explicit group is missing inside \hbox, the reset color special would leak outside the \hbox and the \setbox statement.

\documentclass{article}
\usepackage{xcolor}

\newsavebox\MyBoxA
\savebox\MyBoxA{Hello}

\makeatletter
\newcommand{\sboxcolorless}[2]{%
  \setbox#1\hbox{\color@begingroup#2\color@endgroup}%
}
\makeatother

\newsavebox\MyBoxB
\sboxcolorless\MyBoxB{Hello}

\begin{document}
  \textcolor{red}{Red \usebox\MyBoxA\ red \usebox\MyBoxB\ red.}
\end{document}

Result

However, this trick depends on implementation of color. LuaTeX has a concept of attributes, where color can be much more closer implemented to font attributes. Package luacolor implements this. Then the elements, characters are attributed with the current color, when they are created. A reuse in a box will not change this. Therefore with package luacolor the example will look different, both "Hello" in black:

Result with luacolor

If you want to have more portability and flexibility, a macro can be used instead, where the control over the fixed properties is much larger:

\documentclass{article}
\usepackage{xcolor}

\newcommand*{\Hello}{%
  \begingroup
    \fontfamily\rmdefault % roman font, no non-serif or typewriter
    \upshape % no italics, small caps
    % but series (bold) or font size are flexible
    Hello% 
  \endgroup
}

\begin{document}
  \textcolor{red}{Red \Hello\ \textbf{bold \Hello} \textit{italics \Hello}}
\end{document}

Result macro

This also works with all color implementations.

A note to XeTeX

The color driver xetex.def for XeTeX defines the color commands quite late via \AtBeginDocument. That means the color commands are not fully operational in the preamble. For example, \textcolor{blue}{...} inside a box will not work either, if the box is set in the preamble. If the box is saved after `\begin{document}, the box will work as in the cases with the other drivers.

\documentclass{article}
\usepackage{xcolor}

\newsavebox\MyBox
\savebox\MyBox{Hello \textcolor{blue}{World}!}

\begin{document}
  \textcolor{red}{Red \usebox\MyBox\ Red}

  \savebox\MyBox{Hello \textcolor{blue}{World}!}

  \textcolor{red}{Red \usebox\MyBox\ Red}
\end{document}

Result XeTeX

The reason behind this "odd" behavior is that xetex.def wants to allow the user to disable the stack color implementation (maybe needed in some rare cases) via \noXeTeXcolorstack.

Workaround to enable color in the preamble:

\makeatletter
\check@for@XeTeX@colorstack
\let\check@for@XeTeX@colorstack\relax
\makeatother
Heiko Oberdiek
  • 271,626
2

As I understand it, when LaTeX creates a box using \savebox, the box is created at that moment, and any colors are already assigned; so when you \usebox, the current color doesn't have any effect.

One option would be to define the box with the color originally:

\savebox\MyBox{\textcolor{red}{Hello}}

Another, more flexible solution would be to define a command that builds the box dynamically upon issue:

\documentclass{article}
\usepackage{xcolor}
\def\makecolbox#1#2{%
    \hbox{\textcolor{#1}{#2}}%
}
\begin{document}
\makecolbox{red}{Hello}
\makecolbox{green}{Goodbye}
\makecolbox{black}{Whatever}
\end{document}

If the contents of the box are the same, you can eliminate #2, and just hardcode the text in that way.

dgoodmaniii
  • 4,270
  • 1
  • 19
  • 36
1

Here is a TikZ solution. I define two macros:

  • \savecolorablebox{boxname}{content} to define and save a colorable box.

  • \usecoloredbox[color]{boxname} to use and to color a box.

enter image description here

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{fadings}
\newcommand\savecolorablebox[2]{
  \expandafter\newsavebox\csname#1\endcsname
  \expandafter\savebox\csname#1\endcsname{\textcolor{white}{#2}}
  \begin{tikzfadingfrompicture}[name=#1]
    \node[text=transparent!0,fill=transparent!100]{\expandafter\usebox\csname#1\endcsname};
  \end{tikzfadingfrompicture}
}
\newcommand\usecoloredbox[2][black]{%
  \bgroup%
  \pgfmathsetmacro\myw{width("\expandafter\usebox\csname#2\endcsname")}%
  \pgfmathsetmacro\myh{height("\expandafter\usebox\csname#2\endcsname")}%
  \pgfmathsetmacro\myd{depth("\expandafter\usebox\csname#2\endcsname")}%
  \begin{tikzpicture}[baseline=-1 * \myh pt / 2 + \myd pt / 2]
    \fill[path fading=#2,fit fading=false,fill=#1]
    (-1 * \myw pt / 2, -1 * \myh pt / 2 - 1 * \myd pt / 2)
    rectangle (\myw pt / 2,\myh pt /2 + \myd pt / 2);
  \end{tikzpicture}%
  \egroup%
}
\begin{document}
\savecolorablebox{MyBox}{Hello}
test \usecoloredbox[orange]{MyBox} test \usecoloredbox[blue]{MyBox}
\savecolorablebox{MyBox2}{Hello gg}
test \usecoloredbox[cyan]{MyBox2} test \usecoloredbox[green]{MyBox2}
\savecolorablebox{MyBox3}{$x=\frac{a}{b}$}
test \usecoloredbox[cyan]{MyBox3} test \usecoloredbox[red]{MyBox3}
\end{document}
Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283