[Tex/LaTex] tikz bounding box / cropping: too much space for curves

bounding boxspacingtikz-pgf

I have a problem with TikZ's auto-cropping/auto-calculating the bounding box for a tikzpicture.

Look at the following example:

\documentclass{article}
\usepackage{tikz}
\begin{document}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.

\begin{center}
\begin{tikzpicture}
\node[draw,circle] (A) at (0,0){A};
\node[draw,circle] (B) at (3,3){B};
\draw (A) to (B);
\end{tikzpicture}
\end{center}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.

\begin{center}
\begin{tikzpicture}
\node[draw,circle] (A) at (0,0){A};
\node[draw,circle] (B) at (3,3){B};
\draw[bend left=90,looseness=2] (A) to (B);
\draw[bend right=90,looseness=2] (A) to (B);
\end{tikzpicture}
\end{center}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam scelerisque massa quis nibh egestas, sed aliquam justo gravida. Integer eget felis vel erat auctor sagittis. In eget ligula eu velit rutrum sodales sed at velit. Proin id blandit ante, tristique bibendum magna.
\end{document}

This produces the following output:
enter image description here

As you can see the cropping of the picture with the straight lines is perfectly fine. However, for the curved lines there is too much (unnecessary) white-space before and after the picture.

I know I can manually fix this by changing the boundingbox or simply using \vspace, but is there an automatic way to get accurate bounding boxes?

(Note: This is very similar to this question, but the answers there do not seem to help with the automatic calculation, mostly checking first what the bounding box is and then applying some sort of clipping.)

Best Answer

Here is my attempt to get an automatic method. Read this page to know how to split Bézier curves.

I define the new limit bb style with two arguments:

  1. the maximum distance between actual bounding box and perfect bounding box.
  2. the action (draw, fill...) applied to the path.

This new style splits automatically and recursively all Bézier curves to remove too distant control points.

enter image description here

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,decorations.pathreplacing}
\tikzset{
  bezier/controls/.code args={(#1) and (#2)}{
    \def\mystartcontrol{#1}
    \def\mytargetcontrol{#2}
  },
  bezier/limit/.store in=\mylimit,
  bezier/limit=1cm,
  bezier/.code={
    \tikzset{bezier/.cd,#1}
    \tikzset{
      to path={
        let
        \p0=(\tikztostart),    \p1=(\mystartcontrol),
        \p2=(\mytargetcontrol), \p3=(\tikztotarget),
        \n0={veclen(\x1-\x0,\y1-\y0)},
        \n1={veclen(\x3-\x2,\y3-\y2)},
        \n2={\mylimit}
        in  \pgfextra{
          \pgfmathtruncatemacro\ok{max((\n0>\n2),(\n1>\n2))}
        }
        \ifnum\ok=1 %
        let
        \p{01}=($(\p0)!.5!(\p1)$), \p{12}=($(\p1)!.5!(\p2)$), \p{23}=($(\p2)!.5!(\p3)$),
        \p{0112}=($(\p{01})!.5!(\p{12})$), \p{1223}=($(\p{12})!.5!(\p{23})$),
        \p{01121223}=($(\p{0112})!.5!(\p{1223})$)
        in
        to[bezier={controls={(\p{01}) and (\p{0112})}}]
        (\p{01121223})
        to[bezier={controls={(\p{1223}) and (\p{23})}}]
        (\p3)
        \else
        [overlay=false] .. controls (\p1) and (\p2) ..  (\p3) [overlay=true]
        \fi
      },
    }%, <-- Comma here results in "Missing character: There is no , in font nullfont!"
  },
  limit bb/.style n args={2}{
    overlay,
    decorate,
    decoration={
      show path construction,
      moveto code={},
      lineto code={\path[#2] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);},
      curveto code={
        \path[#2]
        (\tikzinputsegmentfirst)
        to[bezier={limit=#1,controls={(\tikzinputsegmentsupporta) and (\tikzinputsegmentsupportb)}}]
        (\tikzinputsegmentlast);
      },
      closepath code={\path[#2] (\tikzinputsegmentfirst) -- (\tikzinputsegmentlast);},
    },
  },
  limit bb/.default={1mm}{draw},
}

\begin{document}
\begin{tikzpicture}
  \node[draw,circle] (A) at (0,0){A};
  \node[draw,circle] (B) at (3,3){B};
  \draw[limit bb={1mm}{draw=red},bend left=90,looseness=2] (A) to (B);
  \draw[limit bb={1mm}{draw=blue},bend right=90,looseness=2] (A) to (B);
  \draw[green] (current bounding box.south west) rectangle (current bounding box.north east);
\end{tikzpicture}
\end{document}