10

I would like to create some split rectangles whose widths are defined by the length of a designated line. I would like to do this through a key such as in:

\documentclass[border=4pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart}
\newlength\aetmplength

\begin{document}

\begin{tikzpicture}[
  set text to width of/.code={%%
    \settowidth\aetmplength{#1}%%
    \typeout{--->`#1`::\the\aetmplength}%%
    \pgfkeysalso{text width=\the\aetmplength}%%
  },
  ]
  \node[draw,
        shape=rectangle split,
        rectangle split parts=2,
        set text to width of=This is the top of the node,
        %text width=0.95in
        ] () 
        {
         This is the top of the node
         \nodepart{two}
           first item\\
           second item\\
           \ldots};
\end{tikzpicture}

\end{document}

But this doesn't work. I could easily spend sometime working around this. So this isn't a problem of how to set the length I want. What I want to understand is why this definition for the key isn't doing what I want it to do.

Andrew Swann
  • 95,762
A.Ellett
  • 50,533
  • 1
    It is \nullfont inside the tikz picture. – percusse May 03 '15 at 17:42
  • @percusse I'm not sure I understand what you mean. What's \nullfont? And whatever that is, is there a way to work around it? – A.Ellett May 03 '15 at 17:44
  • Sorry my mistaken presumption. I meant: \nullfont is the primitive, roughly speaking, that maps the font definition to zero. Hence in the code, your text input is expanded to zero width as text is mapped to nullfont. You can place things into a temporary box and measure its width instead. TikZ does that to ignore any dangling text commands etc. within the picture. – percusse May 03 '15 at 17:50
  • @percusse I don't think I'm getting what you're talking about. I tried something along the lines of \settowidth\aetmplength{\mbox{#1}} and I tried \sbox\aetmpbox{#1}\aetmplength\wd\aetmpbox, but neither approach is working. – A.Ellett May 03 '15 at 18:50
  • Try this as the code of the key : \pgfutil@selectfont\settowidth\aetmplength{#1}\pgfkeysalso{text width=\the\aetmplength} – percusse May 03 '15 at 23:29
  • @percusse That works. Unfortunately there is no documentation for \pgfutil@selectfont. Care to write up an answer? Where did you find this macro? – A.Ellett May 04 '15 at 00:09
  • @percusse Interestingly enough, \pgfutil@selectfont appears to expand to \protect\selectfont and yet, when I replace \pgfutil@selectfont with \protect\selectfont things break once more. Any clue? – A.Ellett May 04 '15 at 00:24
  • I read a lot of PGF code some time ago, that's how I remember it. And as I tried to show below its meaning is context dependent on \nullfont activation. – percusse May 04 '15 at 09:34

2 Answers2

10

The explanation, that TikZ is using \nullfont is already given in percusse's answer. Therefore \settowidth will not work as expected.

\pgfmathsetlength{width("#1")}

Instead of \settowidth the pgfmath function width can be used instead:

\pgfmathsetlength\aetmplength{width("#1")}

Full example:

\documentclass[border=4pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart}
\newlength\aetmplength

\begin{document}

\begin{tikzpicture}[
  set text to width of/.code={%%
    % \settowidth\aetmplength{#1}%%
    \pgfmathsetlength\aetmplength{width("#1")}%
    \typeout{--->`#1`::\the\aetmplength}%%
    \pgfkeysalso{text width=\the\aetmplength}%%
  },
  ] 
  \node[draw,
        shape=rectangle split,
        rectangle split parts=2,
        set text to width of=This is the top of the node,
        %text width=0.95in
        ] ()
        {   
         This is the top of the node
         \nodepart{two}
           first item\\
           second item\\
           \ldots};
\end{tikzpicture}

\end{document}

Result

\pgftext{...}

Another way is \pgftext, which escapes back to TeX, where \settowidth will work again. Since the argument of \pgftext is processed inside a group, \aetmplength is assigned globally and \setwidth act on a local temporary dimen register. Local and global assignments should not be mixed on the same control sequence for memory reasons.

\pgftext{%
  \settowidth{\dimen0}{#1}%%
  \global\aetmplength=\dimen0\relax
}%

node font

The latest version can also be used to respect the value of option node font. The value is stored in macro \tikz@node@textfont. Without \makeatletter and \makeatother the control sequence can also be specified by \csname and \endcsname:

\documentclass[border=4pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes.multipart}
\newlength\aetmplength

\begin{document}

\begin{tikzpicture}[node font=\tiny,
  set text to width of/.code={%%
    \pgftext{%
      \csname tikz@node@textfont\endcsname
      \settowidth{\dimen0}{#1}%%
      \global\aetmplength=\dimen0\relax
    }%
    \typeout{--->`#1`::\the\aetmplength}%%
    \pgfkeysalso{text width=\the\aetmplength}%%
  },
  ] 
  \node[draw,
        shape=rectangle split,
        rectangle split parts=2,
        set text to width of=This is the top of the node,
        %text width=0.95in
        ] ()
        {   
         This is the top of the node
         \nodepart{two}
           first item\\
           second item\\
           \ldots};
\end{tikzpicture}

\end{document}

Result with node font setting

Heiko Oberdiek
  • 271,626
9

TikZ/PGF design is done in such a way that its regular syntax doesn't have any spurious whitespace leaks or unintended characters are printed out inside the code etc. One way of doing it is to map the current font to the special primitive \nullfont.

This causes that you cannot simply handle text width without recreating the original non-\nullfont state. Just to give some context, below is taken from the pgf@picture environment begin definition:

    \begingroup%
      \let\pgf@setlengthorig=\setlength%
      \let\pgf@addtolengthorig=\addtolength%
      \let\pgf@selectfontorig=\selectfont%  <== Records the original
      \let\setlength=\pgf@setlength%
      \let\addtolength=\pgf@addtolength%
      \let\selectfont=\pgf@selectfont% <== Overwrites
      \nullfont\spaceskip0pt\xspaceskip0pt% <== Resets font
      \setbox\pgf@layerbox@main\hbox to0pt\bgroup%
        \begingroup%

Once we know this then you can temporarily go back to original definition inside a group get the width and survive the group.

\documentclass[border=4pt,tikz]{standalone}
\usetikzlibrary{shapes.multipart}
\makeatletter
\tikzset{set text to width of/.code={%%
\begingroup%
\pgfutil@selectfont%
\settowidth\pgf@xc{#1}% <== PGF internal scratch registers xa,ya,xb,yb,xc,yc
\edef\temp{\noexpand\pgfkeysalso{text width=\the\pgf@xc}}%<- Nonzero
\expandafter\endgroup\temp%%
\typeout{--->`#1`::\the\pgf@xc}%<-Still zero!
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
  \node[draw,
        align=center,%<- for line breaks
        shape=rectangle split,
        rectangle split parts=2,
        set text to width of=This is looooooooonnnnnnnnger than the top,
        ] () 
        {
         This is the top of the node
         \nodepart{two}
           first item\\
           second item\\
           \ldots};
\end{tikzpicture}
\end{document}

enter image description here

percusse
  • 157,807