22

Why does the following latex document:

\documentclass{minimal}
\usepackage{tikz}

\begin{document}
    \noindent
    \begin{tikzpicture}
        \draw (0,0) -- (\textwidth,0);
    \end{tikzpicture}
\end{document}

give an overfull \hbox of 0.4pt? The line cap option has no effect. An ultra thick line gives a larger overfull \hbox of 1.6pt.

Is there a better (more TikZ-like) way to achieve the same effect, i.e., draw a line to the edge of the text box? Ideally I'd like to do this without scaling the figure.

4
  • 4
    a line the width of the text area can be done with the TeX primitive \hrule if that's all you need? Commented Jun 2, 2012 at 16:38
  • Yes, I know about \hrulefill and the like, but I can't use those in a TikZ picture. In my actual use case I'm trying to make a picture-based page header that includes a line going to the end of the text. Commented Jun 2, 2012 at 21:00
  • 1
    In a complex picture it might be easiest to explicitly set the bounding box with [use as bounding box].
    – Caramdir
    Commented Jun 3, 2012 at 3:17
  • Related Question: Drawing a line the width of the text using tikz. Commented Dec 28, 2012 at 14:29

4 Answers 4

21

The code can be adapted very easily to not get the warning anymore:

\documentclass{minimal}
\usepackage{tikz}

\begin{document}
    \noindent
    \begin{tikzpicture}
        \draw[line cap=rect] (0,0) -- (\linewidth-\pgflinewidth,0);
    \end{tikzpicture}
\end{document}

EDIT: I think I also have an explanation: Each time tikz draws a line, it uses 2 bounding boxes (cf. PGF manual v 2.10 - page 579):

As a side-effect, the path construction commands keep track of two bounding boxes. One is the bounding box for the current path, the other is a bounding box for all paths in the current picture.

Section 7.13 details these parameters.

What I think is happening, is that regardless of the way how the line ends (line cap), its bounding box protrudes by \pgflinewidth beyond the edge of the text area by this length. This is probably necessary to accommodate the whole line in case of nicely rounded line caps. Since this requires in fact .5\pgflinewidth on each end and the bounding box of the line starts at exactly (0,0) the overhang will be \pgflinewidth at the end of the line.

3
  • 1
    I'm reasonably convinced that this is the correct explanation, i.e., the bounding box for the line leaves extra space for a line cap, even if none is used. Thus, to get a line of the correct length and the correct bounding box, I've used the code \draw[line cap=rect] (0,0) -- (\textwidth-\pgflinewidth,0);. Commented Jun 2, 2012 at 21:03
  • 1
    Having two bounding boxes in memory (one for the whole tikzpicture and one for the current \draw) has nothing to do with the problem. Also (as Jan noted), your line is too short, but can be made the correct size by adding line caps.
    – Caramdir
    Commented Jun 3, 2012 at 3:30
  • @JanLadislavDussek: I modified my code according to your suggestion.
    – Count Zero
    Commented Jun 3, 2012 at 8:48
15

This is an answer to the first question: Why does TikZ do that?

To look at this we need to understand how TikZ works internally. The following is a very simplified (but I hope correct) account of how TikZ code gets processed:

  1. Frontend code (i.e. TikZ code) is translated to “basic layer” code (i.e. pgf code). For example,

     \draw (0,0) -- (\textwidth,0);
    

    (basically) becomes

     \pgfmoveto{\pgfpoint{0pt}{0pt}}
     \pgflineto{\pgfpoint{\textwidth}{0pt}}
     \pgfusepath{stroke}
    
  2. The pgf code constructs a path. A path is a mathematical object and so everything is represented as having infinitely small line width. Thus the first two \pgf... lines above will result in a path that consists just of one line segment. The bounding box is calculated in this step by simply looking at the extremal values of each part of the path. So in this example, the bounding box of the path will be rectangle of width \textwidth and height 0.

  3. The path is then “used” with \pgfusepath and similar commands. This translates it into drawing commands of the output format (e.g. PostScript or PDF).

The problem with this approach is that the bounding box might change between steps 2 and 3 as stroking a path with a positive line width will change its size. However pgf never gets the information of the final bounding box, because the stroking is only done at the level of the viewer (the PDF contains the information of the path and the line width and then the PDF viewer has to figure out how to display that correctly).

Considering the line width while calculating the bounding box in step 2 is I think significantly harder than just calculating the bounding box of the path itself. Thus pgf takes the easy way out: after finishing the second step, it just adds half the line width in every direction to the bounding box. This is done in pgfcorepathusage.code.tex:

  %
  % Check whether the path is stroked. If so, add half the line width
  % to the bounding box.
  %
  \ifpgf@relevantforpicturesize%
    \ifx\pgf@up@stroke\pgfutil@empty%
    \else%
      \ifdim\pgf@picmaxx=-16000pt\relax%
      \else%
        \pgf@x=\pgf@pathminx\advance\pgf@x by-.5\pgflinewidth%
        \ifdim\pgf@x<\pgf@picminx\global\pgf@picminx\pgf@x\fi%
        \pgf@y=\pgf@pathminy\advance\pgf@y by-.5\pgflinewidth%
        \ifdim\pgf@y<\pgf@picminy\global\pgf@picminy\pgf@y\fi%
        \pgf@x=\pgf@pathmaxx\advance\pgf@x by.5\pgflinewidth%
        \ifdim\pgf@x>\pgf@picmaxx\global\pgf@picmaxx\pgf@x\fi%
        \pgf@y=\pgf@pathmaxy\advance\pgf@y by.5\pgflinewidth%
        \ifdim\pgf@y>\pgf@picmaxy\global\pgf@picmaxy\pgf@y\fi%
      \fi%
    \fi%
  \fi%

Of course this approximation might also be wrong in the other direction, as the following example shows:

\documentclass{article}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
    \draw[line width=1cm] (0,0) -- (5,0) -- (0,2);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}
\end{document}

example

Exercise: Compute the correct bounding box for this example.

1
  • You're right: pgf computes an approximation of the bounding box. This approximation is exact only with line cap=round and line join=round. Commented Jun 3, 2012 at 8:39
14

A possibility to get a line with a length equal to \linewidth or \textwidth without overfull box is to use a clip or, as I wrote in a comment, to modify the bounding box.

\documentclass{minimal}
\usepackage{tikz}

\begin{document}
    \noindent
    \begin{tikzpicture} 
        \draw (0,0.1) -- (\linewidth-\pgflinewidth,0.1); 
        \clip (0,-5\pgflinewidth) rectangle (\textwidth,5\pgflinewidth);
        \draw (0,0) -- (\textwidth,0);
    \end{tikzpicture}
\end{document} 

enter image description here

Remark : It's not another possibility because with \draw (0,0) -- (\linewidth-\pgflinewidth,0); the length is not correct.

Explanations :

I think that TikZ (or PGF with the basic layer) needs to be efficient at each step of the drawing. Firstly, the dimensions must be accurate enough and it's difficult with TeX, but Tikz needs to be fast enough. It's complicated like Caramdir shows in a example to calculate exactly the bounding box in all the cases. Some rules are necessary to avoid being too slow and to get correct results.

1) When you draw a simple line you get a complicated bounding box

enter image description here

This picture shows the problem at each side of the picture. In this first picture I used line join=round but in the next I used the values by default.

enter image description here

2) why add 0.5\pgflinewidth

When you draw a rectangle for example, the width is determined from the middle of line (with rounded corners or not). So I think that the main rule is to add 0.5\pgflinewidth in all the cases.

enter image description here enter image description here

3) Now why clip is fine to get exactly what you want

When a clip is used, the line width is not used

enter image description here

4) Conclusion. I think it's preferable to avoid very large width for the line and in some cases it's necessary to have this question in memory to get exact dimensions.

Here is the code for experimentation:

\documentclass{minimal}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture} 
    \draw[help lines] (0,0) grid (1,1);  
    \draw[line width=.4cm,opacity=.2,line join=round] (0,0) -- (1,0) -- (0,1);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}

\vspace*{12pt}   
\begin{tikzpicture} 
    \draw[help lines] (0,0) grid (1,1);  
    \draw[line width=.4cm,opacity=.2] (0,0) -- (1,0) -- (0,1);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}   

\vspace*{12pt}
 \begin{tikzpicture}
  \draw[help lines] (0,0) grid (2,1);
    \draw[line width=.4cm,rounded corners,opacity=.2] (0,0) rectangle (2,1);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}

\vspace*{12pt}
 \begin{tikzpicture}
  \draw[help lines] (0,0) grid (2,1);
    \draw[line width=.4cm,opacity=.2] (0,0) rectangle (2,1);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture} 

\vspace*{12pt}    
\begin{tikzpicture}
  \draw[help lines] (0,0) grid (2,1);
    \clip (0,0) rectangle (2,1);  
    \draw[line width=.4cm,opacity=.2] (0,0) rectangle (2,1);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}  

\end{document} 
8
  • Which length are you referring to in your remark? Could you elaborate please?
    – Count Zero
    Commented Jun 2, 2012 at 17:20
  • I want only to say that it's possible to have a length = \textwidth > \textwidth-\pgflinewidth without an overfull box Commented Jun 2, 2012 at 18:02
  • ^ Indeed you can avoid overfull, even in much easier way: \hbox to \textwidth {\begin{tikzpicture} \draw (0,0) -- (\textwidth,0); \end{tikzpicture}\hss}
    – yo'
    Commented Jun 2, 2012 at 18:09
  • much easier ? I'm not sure. If the problem is to create a picture inside a page with a normal width, I prefer to control the bounding box inside the tikzpicture environment and to adapt the size of the box created by tikz. Another possibility is to fix the bounding box directly ! Commented Jun 2, 2012 at 18:14
  • So basically my suspicion that the bounding box is the culprit, proves to be correct? And my code generates a line with actual length \textwidth-\pgflinewidth, but the bounding box has width \textwidth?
    – Count Zero
    Commented Jun 2, 2012 at 18:48
9

The first two tikzpictures show the bug calculation of the bounding box.

The third tikzpicture answers the question via a filled rectangle. ;-)

\documentclass{minimal}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}[outer sep=0pt,inner sep=0pt]
  \draw[line cap=butt,line width=10pt] (0,0) -- (1,0);
  \draw[line width=1pt,red]
  (current bounding box.south east)
  rectangle
  (current bounding box.north west);
\end{tikzpicture}

\begin{tikzpicture}[outer sep=0pt,inner sep=0pt]
  \draw[line cap=round,line width=10pt] (0,0) -- (1,0);
  \draw[line width=1pt,red]
  (current bounding box.south east)
  rectangle
  (current bounding box.north west);
\end{tikzpicture}

\noindent
\begin{tikzpicture}
  \fill (0,0) rectangle (\linewidth,1pt);
\end{tikzpicture}
\end{document}

result

Another solution uses trim left and trim right to compute good (horizontal) bounding box without clipping (as pgf manual says, "[...]baseline, trim left and trim right are currently the only supported way of truncated bounding boxes which are compatible with image externalization[...]").

\documentclass{minimal}
\usepackage{tikz}

\begin{document}
\noindent
\begin{tikzpicture}[trim left=0cm,trim right=\linewidth]
  \draw (0,0) -- (\linewidth,0);
\end{tikzpicture}

\end{document}
4
  • You're right: the best (cleanest) solution seems to be to use a rectangle instead of a line. Commented Jun 3, 2012 at 0:16
  • but in this case you get a problem with colors between drawing and filling. Commented Jun 3, 2012 at 7:01
  • @Altermundus You are right so I propose a new solution... Commented Jun 3, 2012 at 8:57
  • Finally all the solutions consist to adapt the bounding box of the picture. Commented Jun 3, 2012 at 9:17

You must log in to answer this question.

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