13

I just came to the following problem - using \settowidth on a length in a tikzpicture resets that length to 0? Here is a (totally command-line) MWE, compiled with pdflatex test.tex:

\documentclass[12pt]{article}

\usepackage{tikz}

\begin{document}

\newlength{\mypgVTitleWidth}
\newbox\tbxa

\setlength{\mypgVTitleWidth}{1.0pt}
\typeout{ = A: \mypgVTitleWidth is \the\mypgVTitleWidth }

\ %

\settowidth{\mypgVTitleWidth}{MMMMM}
\typeout{ = B: \mypgVTitleWidth is \the\mypgVTitleWidth }

\setbox\tbxa\hbox{{MMMMM}}
\mypgVTitleWidth\wd\tbxa
\typeout{ = B2: \mypgVTitleWidth is \the\mypgVTitleWidth }


\fontsize{10}{10}\selectfont
\ %
\settowidth{\mypgVTitleWidth}{MMMMM}
\typeout{ = C: \mypgVTitleWidth is \the\mypgVTitleWidth }

{

\fontsize{30}{30}\selectfont
\ %
\settowidth{\mypgVTitleWidth}{MMMMM}
\typeout{ = D: \mypgVTitleWidth is \the\mypgVTitleWidth }

}

\begin{tikzpicture}
\typeout{ = E: \mypgVTitleWidth is \the\mypgVTitleWidth }

\fontsize{30}{30}\selectfont
\ %
\settowidth{\mypgVTitleWidth}{MMMMM}
\typeout{ = F: \mypgVTitleWidth is \the\mypgVTitleWidth }
\end{tikzpicture}

\end{document}

first problem

With that code, with tikzpicture, the following log is generated:

 = A: \mypgVTitleWidth is 1.0pt
 = B: \mypgVTitleWidth is 53.83278pt
 = B2: \mypgVTitleWidth is 53.83278pt
 = C: \mypgVTitleWidth is 45.83344pt

LaTeX Font Warning: Font shape `OT1/cmr/m/n' in size <30> not available
(Font)              size <24.88> substituted on input line 30.

 = D: \mypgVTitleWidth is 105.74562pt
 = E: \mypgVTitleWidth is 45.83344pt
 = F: \mypgVTitleWidth is 0.0pt

Notably, trace F shows 0.0pt. Comment out the begin and end tikzpicture - and this shows in the log:

 ...
 = D: \mypgVTitleWidth is 105.74562pt
 = E: \mypgVTitleWidth is 45.83344pt
 = F: \mypgVTitleWidth is 105.74562pt

... as expected.

second problem

If I use \fontsize..\selectfont in a group to make temporary changes to calculate a length; I get one length inside the {...} group - and the previous as soon as I exit:

 = D: \mypgVTitleWidth is 105.74562pt
 = E: \mypgVTitleWidth is 45.83344pt

... Ok, this I guess is the very purpose of the group (to make sure changes are local) - but if I wanted to save this length for using further on in the code (say in the tikzpicture), what would I do? I tried also with second \newlength set to 0 and \addtolength, that resets itself as well.

Any ideas why this is happening, and how to: get \settowidth working also in tikz? - and read \settowidth from groups?

1
  • 2
    In the tikzpicture environment the current font is nullfont, which has no characters. Look for nullfont on this site.
    – egreg
    May 18, 2012 at 14:54

3 Answers 3

16

As explained in How can I use an hbox inside a TikZ environment for text dimension measurement? PGF/TikZ sets the current font inside a picture environment to \nullfont to ignore any text. Inside \node {..}; the font is then restored using \pgfinterruptpicture .. \endpgfinterruptpicture

In order to make \settowidth work you need add \pgfinterruptpicture .. \endpgfinterruptpicture around the content:

\settowidth{\somelength}{\pgfinterruptpicture Text\endpgfinterruptpicture}

It would be even possible to patch the underlying macro in \settowidth to add these macros automatically. There are two TikZ macros which change definitions of certain macros (like \node, etc.) to the TikZ definition inside a picture and then back again. These can be used to use the redefined version only inside pictures while using the normal one outside:

\documentclass{article}

%\usepackage{calc}
\usepackage{tikz}

\usepackage{etoolbox}

\makeatletter
\let\normal@settodim\@settodim
\let\tikz@settodim\@settodim
\patchcmd{\tikz@settodim}{\setbox\@tempboxa\hbox}{\my@tikz@setbox}{}{}
\def\my@tikz@setbox#1{%
    \setbox\@tempboxa\hbox{\pgfinterruptpicture #1\endpgfinterruptpicture}%
}
\appto\tikz@installcommands{%
    \let\@settodim\tikz@settodim
}
\appto\tikz@uninstallcommands{%
    \let\@settodim\normal@settodim
}
\makeatother

\begin{document}

\newlength\mylength
\settowidth{\mylength}{Test}
\the\mylength

\begin{tikzpicture}
    \settowidth{\mylength}{Test}
    \node{\the\mylength};
\end{tikzpicture}

\end{document}
4
  • 2
    Alternatively \pgfmathparse{width("MMMMM")} \setlength{\mypgVTitleWidth}{\pgfmathresult pt} does the \hbox action for you.
    – percusse
    May 18, 2012 at 15:25
  • Many thanks for that, @MartinScharrer and @percusse ! I believe also, now that I know how to "save" lengths from local groups for later with \edef - the same could be used to measure widths before descending to the tikz environment. Thanks again - cheers!
    – sdaau
    May 18, 2012 at 15:32
  • 1
    @sdaau: Please also see my update where I show how to automate this. May 18, 2012 at 15:36
  • Thank you! This should be a library or a package or something.
    – cfr
    Apr 9, 2017 at 23:33
7

Maybe the OP has other needs for etoolbox package, but the following solution doesn't need it.

\documentclass{article}
\usepackage{tikz}
\makeatletter
\let\sda@ltx@settodim\@settodim
\protected\def\sda@settodim#1#2#3{%
  \setbox\@tempboxa\hbox{\pgfinterruptpicture{#3}\endpgfinterruptpicture}%
  #2#1\@tempboxa\setbox\@tempboxa\box\voidb@x
}
% If you're wary of LaTeX's \g@addto@macro, then use:
\protected\def\l@addto@macro#1#2{%
  \begingroup
  \toks1\expandafter{#1}\toks@{#2}%
  \edef#1{\endgroup\edef\noexpand#1{%
    \noexpand\unexpanded{\the\toks1 \the\toks@}}}#1%
}
\l@addto@macro\tikz@installcommands{\let\@settodim\sda@settodim}
\l@addto@macro\tikz@uninstallcommands{\let\@settodim\sda@ltx@settodim}

% Alternative. No need to pre-declare a dimension when calling the following:
\def\tikzsetcmdtoheight{\tikz@cmd@settodim\ht}
\def\tikzsetcmdtodepth {\tikz@cmd@settodim\dp}
\def\tikzsetcmdtowidth {\tikz@cmd@settodim\wd}
\protected\def\tikz@cmd@settodim#1#2#3{%
  \setbox\@tempboxa\hbox{\pgfinterruptpicture{#3}\endpgfinterruptpicture}%
  \edef#2{\the#1\@tempboxa}\setbox\@tempboxa\box\voidb@x
}
\makeatother

\newlength\mylength
\begin{document}
\begin{tikzpicture}
  \settowidth{\mylength}{Test}
  \tikzsetcmdtowidth\cmda{Test}
  \draw (0,0) node(a) [draw] {{\tt\string\mylength} result: \the\mylength}
    (1.5,1.5) node(b) [draw] {{\tt\string\cmda} result: \cmda};
  \draw[color=red] (a.east) -| (5,1.5) -| (b.east);
\end{tikzpicture}
\end{document}

EDIT

Another approach. No need to pre-declare a dimension when calling the following commands. And no need to load etoolbox package. The commands can be used inside and outside of \tikz, and even when tikz isn't loaded.

\documentclass{article}
\usepackage{tikz}
\makeatletter
\protected\def\setcmdtoheight{\cmd@settodim\ht}
\protected\def\setcmdtodepth{\cmd@settodim\dp}
\protected\def\setcmdtowidth{\cmd@settodim\wd}
\def\cmd@settodim#1#2#3{%
  \setbox\@tempboxa\hbox{%
    \@ifundefined{pgfpictureid}{{#3}}{%
      \pgfinterruptpicture{#3}\endpgfinterruptpicture
    }%
  }%
  \edef#2{\the#1\@tempboxa}\setbox\@tempboxa\box\voidb@x
}
\makeatother

% For comparison later, let us set \mylength:
\newlength\mylength
\settowidth{\mylength}{Test}

\begin{document}
\def\print#1#2{Value of {\tt\string#2}: \if#1c\the\fi#2}
\setcmdtowidth\cmda{Test}
Outside {\tt\string\tikz}:\par
\print c\mylength, \print n\cmda
\par\bigskip
Inside {\tt\string\tikz}:\par\medskip
\begin{tikzpicture}
  \setcmdtowidth\cmda{Test}
  \draw (0,0) node(a) [draw,color=red,rounded corners=5pt] {\print c\mylength}
    (1.5,1.5) node(b) [draw,color=blue,rounded corners=5pt] {\print n\cmda};
  \draw[color=red] (a.east) -| (5,1.5) -| (b.east);
  \draw[color=blue] (a.west) -| (-4,1.5) -| (b.west);
  \fill [gray!25!purple!25](5,0) circle [x radius=1cm, y radius=5mm, rotate=30];
  \fill [gray!25!red!25](-4,1.5) circle [x radius=1cm, y radius=5mm, rotate=30];
\end{tikzpicture}
\end{document}

enter image description here

2
  • Many thanks for that example, @AhmedMusa - indeed, etoolbox was there for other needs (I just put it in there, unsure if it was maybe a cause of a problem). Thanks again - cheers!
    – sdaau
    May 18, 2012 at 21:06
  • The \tikz@installcommands etc. can be redefined globally without any issue. I was originally going to redefine \@settodim myself like you did and using \g@addto@macro for the un-/install macros but then decided to go for a more "user-friendly" approach. But it's good to have the direct approach as well. May 19, 2012 at 7:29
3

Thanks to the comment by @egreg, I tracked down the post "How can I use an hbox inside a TikZ environment for text dimension measurement?" which is nearly the same. The answer explains about nullfont, and proposes a \let\origselectfont\selectfont... method for having font-based text box width measurement withing tikz.

For my second problem - what seems to work, is to "convert" the "length" "type" in mypgVTitleWidth to "string" using \edef; but that will still cause a local change - unless prepended by \global. So now I have the following snippet:

....
\def\txxx{INIT}

{
  \fontsize{30}{30}\selectfont
  \settowidth{\mypgVTitleWidth}{MMMMM}
  \typeout{ = D: \mypgVTitleWidth is \the\mypgVTitleWidth }
  \global\edef\txxx{\the\mypgVTitleWidth} % "length to string"
  \setbox\tbxa\hbox{{MMMMM}}
  \typeout{ = D2: \tbxa is \the\wd\tbxa, \txxx }
}


\typeout{ = D3: \tbxa is \the\wd\tbxa, \txxx }
\setlength{\mypgVTitleWidth}{\txxx} % "string to length"

\begin{tikzpicture}
\typeout{ = E: \mypgVTitleWidth is \the\mypgVTitleWidth }
....

which seems to work for me:

 = C: \mypgVTitleWidth is 45.83344pt
 = D: \mypgVTitleWidth is 105.74562pt
 = D2: \tbxa is 105.74562pt, 105.74562pt
 = D3: \tbxa is 53.83278pt, 105.74562pt
 = E: \mypgVTitleWidth is 105.74562pt

Well, ... I hope I understood this right :),
Cheers!

PS: also nice reads:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .