Distributing tikz elements evenly using positioning library and length precalculation leads to wrong result

I am trying to distribute a number of tikz elements/groups consisting of one \pic (= an icon) and one text \node (= label) each evenly across the length of the text area. I went about it by using a discard layer to draw dummies of my elements on it, measuring their widths, accumulatively subtracting them from \textwidth and then dividing by the number of elements/groups to get the seperation between my groups. Then I use the positioning tikz-library together with the precalculated seperation length to place my groups. But for some reason this doesn't lead to my groups actually filling the whole width of the text area.

Here is a simplyfied MWE, where I also introduced a test length (visulized by the read line added to the picture) which seems to verify that all the parts of my calculation actually add up to \textwidth. Yet my groups don't fill the whole width of the text area:

\documentclass[12pt,a4paper,ngerman,oneside]{scrlttr2}

\def\fontscalingfac{1}
% \renewcommand{\fontscalingfac}{0.85}
\usepackage[sfdefault,scaled=\fontscalingfac]{FiraSans}
\usepackage[LGR,T1]{fontenc}
\usepackage[utf8]{inputenx}

\usepackage{tikz}
\usepackage{tikzpagenodes}
\usetikzlibrary{calc}
\usetikzlibrary{positioning}

\usepackage{showframe}

\newlength{\iconcirclelinewidth}
\setlength{\iconcirclelinewidth}{1.5pt}
\newlength{\iconlabelsep}
\setlength{\iconlabelsep}{0.1cm}
\newlength{\testlen}
\setlength{\testlen}{0pt}

\def\textA{Text piece A}
\def\textB{Text piece B}
\def\textC{Text piece C}

\begin{document}

\begin{tikzpicture}[remember picture,overlay]
% calculate separation lengths for icon-text elements such that they
% are evenly distributed across the \textwidth
% discard the nodes required for the calculations afterwards
\foreach \infoelement in {\textA, \textB, \textC}
{%
\node[circle, line width=\iconcirclelinewidth, anchor=west, minimum height=2\iconcircleradius] (icon circle) at (0,0) {};
\node[font=\small, right=\iconlabelsep of icon circle, anchor=west] (text node) {\infoelement};
\newdimen\xEast
\pgfextractx{\xEast}{\pgfpointanchor{text node}{east}}
\newdimen\xWest
\pgfextractx{\xWest}{\pgfpointanchor{icon circle}{west}}
% introduce a test length to check if it accumulates to \textwidth
\global\advance\testlen by \dimexpr(\xEast - \xWest - 2\iconcirclelinewidth)\relax
}
\end{pgfonlayer}
%
% place icon-text elements evenly across text width
% Icon A
\node[draw, circle, line width=\iconcirclelinewidth, anchor=west, minimum height=2\iconcircleradius] (icon A) at (current page text area.north west) {};
\node[color=black, font=\small, right=\iconlabelsep of icon A, anchor=west] (text A) {\textA};
% Icon B
\node[draw, circle, line width=\iconcirclelinewidth, minimum height=2\iconcircleradius, right=\headermatrixsep of text A, anchor=west] (icon B) {};
\node[color=black, font=\small, right=\iconlabelsep of icon B, anchor=west] (text B) {\textB};
% Icon C
\node[draw, circle, line width=\iconcirclelinewidth, minimum height=2\iconcircleradius, right=\headermatrixsep of text B, anchor=west] (icon C) {};
\node[color=black, font=\small, right=\iconlabelsep of icon C, anchor=west] (text C) {\textC};
\draw[red] ($(current page text area.north west) - (0, 10pt)$) -- ($(current page text area.north west) + (\testlen, -10pt)$);

\end{document}

This leads to the following picture

The variable \fontscalingfac was introduced because in my real world example its introduction just about fixes the spacing issue (I scale the font in order for its xheight to match with the newtxsf packages I use it with). In my MWE scaling the font actually leads to a slightly different spacing which I would also like to understand why. But my main issue is: Why does my method for distributing my drawing elements evenly across the page and thereby filling the whole width fail?

• \tikz{...}\hfill\tikz{...}\hfill\tikz{...}? One thing that prevents me from spelling this out is \usepackage[sfdefault,scaled=\fontscalingfac]{FiraSans} which I never got to work on my machine. – marmot May 8 at 21:04
• @marmot I use the evenly spaced elements within the header of my document which I create with the package scrlayer-scrpage using \DeclareNewLayer. I will have to think about it some more but I fear your approach might not be feasible in my situation. – Philipp May 8 at 21:15
• Fair enough. I just cannot follow your computations. (I know, I know, I should be the last one to complain... ;-) – marmot May 8 at 21:31
• @marmot While formulating an explanation of my computations I stumbled uppon the error. This attests to the truth in the saying: The best way to understand something is to explain it to others. The error has nothing to do with TeX, it is simply my own stupidity that was at work here. Sorry for the noise and thanks for having a look at my problem. Do you think I should delete my question, since the core of the problem is just a simple methodological mistake and not something related to TeX? – Philipp May 8 at 22:17
• This is up to you. Generally there is a lack of some equivalent to \hfill inside TikZ pictures IMHO. (If you want to see some basic attempts, have a quick look e.g. at this post, but I would say there is no general solution.) If you feel that what you achieved may help others there is nothing wrong with keeping the question and writing your own answer IMHO. – marmot May 8 at 22:21

This solution first subdivides the image/page into three scopes, then draws the text etc. inside the scopes.

Note that [overlay] turns off all bounding boxes, even local bounding boxes, which is why I had to turn it off.

\documentclass[12pt,a4paper,ngerman,oneside]{scrlttr2}

\def\fontscalingfac{1}
% \renewcommand{\fontscalingfac}{0.85}
%\usepackage[sfdefault,scaled=\fontscalingfac]{FiraSans}% it is easier for me just to comment these out
%\usepackage[LGR,T1]{fontenc}
%\usepackage[utf8]{inputenx}

\usepackage{tikz}
%\usepackage{tikzpagenodes}
%\usetikzlibrary{calc}
%\usetikzlibrary{positioning}

\usepackage{showframe}

\newlength{\boxwidth}
\setlength{\boxwidth}{0.333\textwidth}
\newlength{\boxheight}
\setlength{\boxheight}{1cm}

\def\textA{Text piece A}
\def\textB{Text piece B}
\def\textC{Text piece C}

\begin{document}

\noindent\begin{tikzpicture}
% place icon-text elements evenly across text width
\begin{scope}[local bounding box=box A]
\clip (0,0) rectangle (\boxwidth,-\boxheight);
% Icon A
\node[draw, circle, right] at (0,-0.5\boxheight) (icon A) {};
\node[color=black, font=\small, right] at (icon A.east) (text A) {\textA};
\end{scope}
\draw[red] (box A.north west) rectangle (box A.south east);
%
\begin{scope}[shift=(box A.north east), local bounding box=box B]
\clip (0,0) rectangle (\boxwidth,-\boxheight);
% Icon A
\node[draw, circle, right] at (0,-0.5\boxheight) (icon B) {};
\node[color=black, font=\small, right] at (icon B.east) (text B) {\textB};
\end{scope}
\draw[green] (box B.north west) rectangle (box B.south east);
%
\begin{scope}[shift=(box B.north east), local bounding box=box C]
\clip (0,0) rectangle (\boxwidth,-\boxheight);
% Icon A
\node[draw, circle, right] at (0,-0.5\boxheight) (icon C) {};
\node[color=black, font=\small, right] at (icon C.east) (text C) {\textC};
\end{scope}
\draw[blue] (box C.north west) rectangle (box C.south east);
\end{tikzpicture}

\end{document}
• Nice solution and quite simple as well. In its current form your MWE doesn't give me what I'm looking for since I want the east node of "Text piece C" placed at the right border of the text area but your code can easily be adapted to that requirement. One downside to your method is that it won't work well, i.e. won't give exactly even separations, for placing elements of very different lengths. Thank you very much for this insightful answer. – Philipp May 9 at 16:03

It turned out, that in my concrete and more complex use case I got distracted from the real problem. My method actually works but I had a rather simple error in my reasoning: I calculate the width of each group I want to place and successively subtract those values from \textwidth which should give me the total amount of "white space" that I can distribute between my groups such that they fill the whole width of the text area. So far so good, but then I divided this length by the number of groups I want to place - let's call it N (in my MWE this is 3) - where I rather should have divided it by N-1, since my two outermost groups are aligned with their respective borders. And that basically fixes my problem.

There were two other minor corrections that I also needed to apply: For one thing while calculating the width of the groups I subracted the line width of the circle node twice, i.e. 2\iconcirclelinewidth. But I actually only need to do this once. Furthermore my real use case document led me to suspect that maybe the scaling factor of my font might somehow interact badly with tikz, which is why I introduced a font scaling factor, \fontscalingfac, into my calculations. This however is just wrong and the font scaling doesn't influence the calculations.

Thus, when I correct my MWE accordingly, I get to my desired result:

\documentclass[12pt,a4paper,ngerman,oneside]{scrlttr2}

\usepackage[sfdefault,scaled=0.85]{FiraSans}
\usepackage[LGR,T1]{fontenc}
\usepackage[utf8]{inputenx}

\usepackage{tikz}
\usepackage{tikzpagenodes}
\usetikzlibrary{calc}
\usetikzlibrary{positioning}

\usepackage{showframe}

\newlength{\iconcirclelinewidth}
\setlength{\iconcirclelinewidth}{1.5pt}
\newlength{\iconlabelsep}
\setlength{\iconlabelsep}{0.1cm}
\newlength{\testlen}
\setlength{\testlen}{0pt}

\def\textA{Text piece A}
\def\textB{Text piece B}
\def\textC{Text piece C}

\begin{document}

\begin{tikzpicture}[remember picture,overlay]
% calculate separation lengths for icon-text elements such that they
% are evenly distributed across the \textwidth
% discard the nodes required for the calculations afterwards
\foreach \infoelement in {\textA, \textB, \textC}
{%
\node[circle, line width=\iconcirclelinewidth, anchor=west, minimum height=2\iconcircleradius] (icon circle) at (0,0) {};
\node[font=\small, right=\iconlabelsep of icon circle, anchor=west] (text node) {\infoelement};
\newdimen\xEast
\pgfextractx{\xEast}{\pgfpointanchor{text node}{east}}
\newdimen\xWest
\pgfextractx{\xWest}{\pgfpointanchor{icon circle}{west}}
% introduce a test length to check if it accumulates to \textwidth
\global\advance\testlen by \dimexpr(\xEast - \xWest - \iconcirclelinewidth)\relax
}
\end{pgfonlayer}
%
% place icon-text elements evenly across text width
% Icon A
\node[draw, circle, line width=\iconcirclelinewidth, anchor=west, minimum height=2\iconcircleradius] (icon A) at (current page text area.north west) {};
\node[color=black, font=\small, right=\iconlabelsep of icon A, anchor=west] (text A) {\textA};
% Icon B
\node[draw, circle, line width=\iconcirclelinewidth, minimum height=2\iconcircleradius, right=\headermatrixsep of text A, anchor=west] (icon B) {};
\node[color=black, font=\small, right=\iconlabelsep of icon B, anchor=west] (text B) {\textB};
% Icon C
\node[draw, circle, line width=\iconcirclelinewidth, minimum height=2\iconcircleradius, right=\headermatrixsep of text B, anchor=west] (icon C) {};
\node[color=black, font=\small, right=\iconlabelsep of icon C, anchor=west] (text C) {\textC};
\draw[red] ($(current page text area.north west) - (0, 10pt)$) -- ($(current page text area.north west) + (\testlen, -10pt)$);
\end{tikzpicture}

\end{document}