Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bbox's bounding box too small and arrow tips cut off #856

Closed
ghost opened this issue May 19, 2020 · 21 comments
Closed

bbox's bounding box too small and arrow tips cut off #856

ghost opened this issue May 19, 2020 · 21 comments

Comments

@ghost
Copy link

ghost commented May 19, 2020

Feeding the code

\documentclass{article}
\pagestyle{empty}
\usepackage{tikz}
\usetikzlibrary{bbox,positioning,calc}
\begin{document}
  \begin{tikzpicture}[bezier bounding box=true,atomicNode/.style={rectangle,draw,minimum width=4em,minimum height=4em}]
    \node[atomicNode] (upper) {};
    \node[atomicNode,below=of upper] (lower) {};
    \coordinate (zUpper) at ($(upper.north west)!.75!(upper.south west)$);
    \coordinate (yUpper) at (upper.east);
    \coordinate (zLower) at ($(lower.north east)!.25!(lower.south east)$);
    \coordinate (yLower) at (lower.west);
    \draw[-latex] (yUpper) .. controls ($(yUpper)+(13em,0)$) and ($(yLower)-(13em,0)$) .. (yLower);
    \draw[-latex] (zLower) .. controls ($(zLower)+(13em,0)$) and ($(zUpper)-(13em,0)$) .. (zUpper);
    \draw  (current bounding box.north west) rectangle (current bounding box.south east);
  \end{tikzpicture}
\end{document}

to pdflatex results in

mwe

BUGS:

  1. The lower parts of the two arrow tips are missing.
  2. The drawn bounding box visibly leaves stuff to its left. We all know that doing maths is imprecise in LaTeX, but it is really that much imprecise, or is there a bug somewhere? (In the first case, I recall that Numerical Recipies in Fortran went great lengths to get usable results on low-precision machines; perhaps, some of their tricks can be reused.)

Bugfixing would be very much appreciated.

@Mo-Gul Mo-Gul added the bbox label May 19, 2020
@hmenke hmenke assigned ghost May 19, 2020
@ghost
Copy link

ghost commented May 22, 2020

Thanks for the report! Yes, there is a problem due to the fact that arrow heads are also curves. One has to switch off the Bezier bounding box for them. I am on it.

@ghost
Copy link

ghost commented May 22, 2020

@hmenke Do you know a way to find out whether or not a given path is a path inside an arrow head? (Or, alternatively, is there a way to tell all arrow head paths to set the Bezier bounding box key to false?) The other bug can be fixed with fpu.

@ghost
Copy link

ghost commented May 22, 2020

@MdAyquassar The bounding box becomes correct if you just load the bending library. This is because ordinary arrows actually distort the path, but when bending is loaded (pgfmanual 204)

When this library is loaded, flex becomes the default mode that is used with all paths, unless quick is explicitly selected for the arrow tip.

However, the arrow head problem remains a problem, (And I wanted to rewrite the library anyway to make it cleaner and more accurate.)

@ghost
Copy link
Author

ghost commented May 22, 2020

@tallmarmot Thanks for letting me know. Do you mean loading bending before loading bbox, after it, instead of it, or do you mean something else? Yes, I'd welcome bug-fixing, thank you for being on it. (Each time I include one more library or one more package, the chances, that something else breaks inside the huge book I'm editing, get higher.)

@ghost
Copy link

ghost commented May 22, 2020

@MdAyquassar You need to load both, the order does not matter. I agree that the current version of bbox was added too early to the TikZ package. Let us not discuss how that happened. ;-)

@ghost
Copy link

ghost commented May 22, 2020

@MdAyquassar Add the following file to the directory you are compiling in:

\usepgflibrary{fpu}
\global\let\pgf@bbox@lt@curveto@normal\pgf@lt@curveto
\global\let\pgf@bbox@nlt@curveto@normal\pgf@nlt@curveto

\pgfqkeys{/pgf}{bezier bounding box/.is if=pgf@bbox@switch@}

\def\pgf@bbox@switch@false{%
  \let\pgf@lt@curveto \pgf@bbox@lt@curveto@normal
  \let\pgf@nlt@curveto\pgf@bbox@nlt@curveto@normal
}

\def\pgf@bbox@switch@true{%
  \let\pgf@lt@curveto \pgf@bbox@curveto
  \let\pgf@nlt@curveto\pgf@bbox@curveto
}
\def\pgf@bbox@curveto#1#2#3#4#5#6{%
\begingroup
\pgfkeys{/pgf/fpu,/pgf/fpu/output format=fixed}%
  % extrema in x
  % first discriminant d1, must be \ne 0
  %\typeout{x0=\the\pgf@path@lastx,xa=\the#1,xb=\the#3,x1=\the#5}%
  \pgfmathsetmacro{\pgf@temp@a}{(\pgf@path@lastx)-(#5)-3*(#1)+3*(#3)}%
  %\typeout{d1=\pgf@temp@a}%
  \pgfmathtruncatemacro{\pgf@temp@c}{(abs(\pgf@temp@a)>0.1?1:0)}%
  \ifnum\pgf@temp@c=1\relax
	% second discriminant d2, must be \ge 0
	\pgfmathsetmacro{\pgf@temp@b}{(\pgf@path@lastx)*(#5)-(#5)*(#1)+(#1)*(#1)-(\pgf@path@lastx)*(#3)-(#1)*(#3)+(#3)*(#3)}%
	\pgfmathtruncatemacro{\pgf@temp@c}{sign(\pgf@temp@b)}%
    %\typeout{d2=\pgf@temp@b}%
	\ifnum\pgf@temp@c<0
	\else
	  \pgfmathsetmacro{\pgf@temp@b}{sqrt(abs(\pgf@temp@b))}%
	  \pgfmathsetmacro{\pgf@temp@c}{max(0,min(1,((\pgf@path@lastx)-2*(#1)+(#3)-\pgf@temp@b)/\pgf@temp@a))}%	 
      %\typeout{t1=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lastx)*pow((1-\pgf@temp@c),3)+3*(#1)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#3)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#5)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgfutil@tempdimb}{\pgf@path@lasty}%
	  \pgfmathsetmacro{\pgf@temp@c}{max(0,min(1,((\pgf@path@lastx)-2*(#1)+(#3)+\pgf@temp@b)/\pgf@temp@a))}%	 
      %\typeout{t2=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lastx)*pow((1-\pgf@temp@c),3)+3*(#1)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#3)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#5)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgfutil@tempdimb}{\pgf@path@lasty}%
	\fi
  \else
    % third discriminant d3, must be \ne 0
    \pgfmathsetmacro{\pgf@temp@b}{abs((#5)+(#1)-2*(#3))}%
	\ifdim\pgf@temp@b pt<0.1pt\relax
	\else 
	  \pgfmathsetmacro{\pgf@temp@c}{((#5)+2*(#1)-3*(#3))/((#5)+(#1)-2*(#3))}%
      %\typeout{t3=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lastx)*pow((1-\pgf@temp@c),3)+3*(#1)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#3)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#5)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgfutil@tempdimb}{\pgf@path@lasty}%
	\fi
  \fi
%  
% y code
  % first discriminant d1, must be \ne 0
  %\typeout{y0=\the\pgf@path@lasty,ya=\the#2,yb=\the#4,y1=\the#6}%
  \pgfmathsetmacro{\pgf@temp@a}{(\pgf@path@lasty)-(#6)-3*(#2)+3*(#4)}%
  %\typeout{d1=\pgf@temp@a}%
  \pgfmathtruncatemacro{\pgf@temp@c}{(abs(\pgf@temp@a)>0.1?1:0)}%
  \ifnum\pgf@temp@c=1\relax
	% second discriminant d2, must be \ge 0
	\pgfmathsetmacro{\pgf@temp@b}{(\pgf@path@lasty)*(#6)-(#6)*(#2)+(#2)*(#2)-(\pgf@path@lasty)*(#4)-(#2)*(#4)+(#4)*(#4)}%
	\pgfmathtruncatemacro{\pgf@temp@c}{sign(\pgf@temp@b)}%
    %\typeout{d2=\pgf@temp@b}%
	\ifnum\pgf@temp@c<0
	\else
	  \pgfmathsetmacro{\pgf@temp@b}{sqrt(abs(\pgf@temp@b))}%
	  \pgfmathsetmacro{\pgf@temp@c}{max(0,min(1,((\pgf@path@lasty)-2*(#2)+(#4)-\pgf@temp@b)/\pgf@temp@a))}%	 
      %\typeout{t1=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lasty)*pow((1-\pgf@temp@c),3)+3*(#2)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#4)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#6)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgf@path@lastx}{\pgfutil@tempdimb}%
	  \pgfmathsetmacro{\pgf@temp@c}{max(0,min(1,((\pgf@path@lasty)-2*(#2)+(#4)+\pgf@temp@b)/\pgf@temp@a))}%	 
      %\typeout{t2=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lasty)*pow((1-\pgf@temp@c),3)+3*(#2)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#4)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#6)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgf@path@lastx}{\pgfutil@tempdimb}%
	\fi
  \else
    % third discriminant d3, must be \ne 0
    \pgfmathsetmacro{\pgf@temp@b}{abs((#6)+(#2)-2*(#4))}%
	\ifdim\pgf@temp@b pt<0.1pt\relax
	\else 
	  \pgfmathsetmacro{\pgf@temp@c}{((#6)+2*(#2)-3*(#4))/((#6)+(#2)-2*(#4))}%
      %\typeout{t3=\pgf@temp@c}%
	  \pgfmathparse{(\pgf@path@lasty)*pow((1-\pgf@temp@c),3)+3*(#2)*pow((1-\pgf@temp@c),2)*\pgf@temp@c+3*(#4)*(1-\pgf@temp@c)*\pgf@temp@c*\pgf@temp@c+(#6)*\pgf@temp@c*\pgf@temp@c*\pgf@temp@c}%
	  \pgfutil@tempdimb=\pgfmathresult pt\relax%
      \pgf@protocolsizes{\pgf@path@lastx}{\pgfutil@tempdimb}%
	\fi
  \fi
%  
  \pgf@protocolsizes{\pgf@path@lastx}{\pgf@path@lasty}%
  \pgf@protocolsizes{#5}{#6}%
  \endgroup
  \pgfsyssoftpath@curveto{\the#1}{\the#2}{\the#3}{\the#4}{\the#5}{\the#6}%
}
\endinput

and call it pgflibrarybbox.code.tex. Then compiling

\documentclass{article}
\pagestyle{empty}
\usepackage{tikz}

\usetikzlibrary{bbox,positioning,calc,bending}
\begin{document}
\noindent
  \begin{tikzpicture}[%
  atomicNode/.style={rectangle,draw,minimum width=4em,minimum height=4em}]
    \node[atomicNode] (upper) {};
    \node[atomicNode,below=of upper] (lower) {};
    \coordinate (zUpper) at ($(upper.north west)!.75!(upper.south west)$);
    \coordinate (yUpper) at (upper.east);
    \coordinate (zLower) at ($(lower.north east)!.25!(lower.south east)$);
    \coordinate (yLower) at (lower.west);
	\begin{scope}[bezier bounding box=true]
    \draw[-latex] (yUpper) .. controls ($(yUpper)+(13em,0)$) and ($(yLower)-(13em,0)$) .. (yLower);
    \draw[-latex] (zLower) .. controls ($(zLower)+(13em,0)$) and ($(zUpper)-(13em,0)$) .. (zUpper);
	\end{scope}
    \draw  (current bounding box.north west) rectangle (current bounding box.south east);
  \end{tikzpicture}
\end{document}

should give you

Screen Shot 2020-05-22 at 3 01 55 PM

@ghost ghost mentioned this issue May 22, 2020
@ghost
Copy link
Author

ghost commented May 22, 2020

@tallmarmot You are the hero of the day. Thanks a lot!

@ghost ghost closed this as completed May 22, 2020
@hmenke hmenke reopened this May 22, 2020
@hmenke
Copy link
Member

hmenke commented May 22, 2020

This is not yet fixed upstream.

@ghost
Copy link

ghost commented May 22, 2020

@hmenke Of course not. The most important question before changing the code in the actual pgf distribution is what one should do with the 0/0=finite cases. On the one hand it would be good to have some of them in, but on the other hand the devil is in the details, it may affect other things. What do you think? (P.S. Contrary to my previous statement after a more careful coding it may well be that there is no need to treat arrow paths different. Puh.)

@ghost
Copy link
Author

ghost commented May 23, 2020

In the example, I don't understand what restricting the scope is good for. I don't see any difference between turning bezier bounding box on only locally and for the whole TikZ picture.
In general, @tallmarmot's suggestion (the bending lib and putting his new file into the working dir) seems to perform relatively well in a huge book with lots of TikZ. The most important change is that bent arrows and arrow tips are drawn slightly differently now (not right or wrong, just differently). The difference is not noticeable to an untrained eye but to diffpdf. I can live with the change.

@ghost
Copy link

ghost commented May 23, 2020

@MdAyquassar In a perfect world one should be able to set the bezier bounding box globally. That is, the better the library code becomes, the less necessary it should be to confine it to a scope. And in the best of all worlds there should not be a difference. But we do not live in the best of all worlds so the strategy is to make the bezier bounding box optional. If it has bad side effects, one can clip the bounding box by hand. What precisely goes wrong with the "quick" arrow heads is something I do not know. But in general loading the bending library should make the arrowed paths look "better" and not worse. That is, without bending an arrow head destroys the symmetry of a curve, which is the main effect of the curious inaccuracy you found. What was a bit surprising to me was to find out that TikZ adds the arrow heads (and moves the curve) after it had reported it to the bounding box. (Obviously if you shorten a path by a negative distance the overshoot will also not be reported to the bounding box, so maybe this is not toooo surprising.)

@hmenke
Copy link
Member

hmenke commented May 23, 2020

Just like in any other programming language you have to handle 0/0 cases manually, like

if numerator == 0 and denominator == 0 then
    perform l'Hospital's rule
else
    numerator / denominator

It is not possible to handle this in, say \pgfmathdivide@ because the function just gets handed two zeros without any knowledge of the original expressions, so there is no way to perform l'Hospital's rule. The only systems that can handle these things correctly are Computer Algerbra Systems like Mathematica because they keep track of the full expression tree.

@ghost
Copy link

ghost commented May 23, 2020

@hmenke Yes, sure, the first order l'Hospital rule is already included in this post. It works. However, to cover all cases one would have to go to order two.

@ghost
Copy link

ghost commented May 23, 2020

@MdAyquassar @hmenke The current version of the library is at https://github.com/tallmarmot/pgf/tree/master/experiments/Marmot/bbox. I ran a rather large sample of tests. So far so good. There can be dimension too large errors. As far as I can tell, they do not come from the computations done in the library, but from the aftermath, in which pgf computes reciprocals. If I change the reciprocals to fpu (locally of course), I did not get any of these errors. The same reciprocal switch fixes many dimension too large errors in decorations.

@hmenke
Copy link
Member

hmenke commented May 23, 2020

You haven't really changed the entire library code, haven't you? I would be much happier if you could submit a sane patch as a pull request or via email.

@ghost
Copy link

ghost commented May 23, 2020

@hmenke At this point I have changed the whole library in the local experiments folder. I have not touched the "real" library in the pgf system, also because I do not know how to do that, but more importantly because I wanted to do more tests and see if anyone finds issues. Sadly, I do not know what a "sane patch" is. To me the whole GitHub saga is extremely counter intuitive and I know barely the commands that allow me to update the stuff in the experiment folder. (I have accepted the fact that I have to "add" a file to update it, but I find it extremely counter intuitive, so it is very hard for me to do more than that.)

@hmenke
Copy link
Member

hmenke commented May 24, 2020

I think it is easier to communicate patches by email. I've sent you a message.

@ghost
Copy link
Author

ghost commented May 24, 2020

I think it is easier to communicate patches by email. I've sent you a message.

@hmenke I have not received any patch via my main e-mail address, so, I think you wanted this to go to tallmarmot only (use the symbol @ ) . Or should I search in any of a dozen or so mailboxes of mine?

hmenke pushed a commit to hmenke/pgf that referenced this issue May 24, 2020
@hmenke hmenke closed this as completed May 24, 2020
@ghost
Copy link
Author

ghost commented Feb 5, 2021

@hmenke , @Mo-Gul : I see @tallmarmot gone. Sigh. The latest version of pgflibrarybbox.code.tex in TeXLive is from 2019 according to http://www.tug.org/texlive/Contents/live/texmf-dist/tex/generic/pgf/libraries/. With that old version, the arrow tips are still cut off. So, purely technically, this bug is not fixed (at least, not fully fixed) yet in TeXLive, somewhat contradicting to the "closed-fixed" status. Would it be possible to push any better version into TeXLive? (Yes, I read that the code is imperfect.)

@muzimuzhi
Copy link
Member

@MdAyquassar Check tikz-bbox, a new tikz library published as separate CTAN package, and is already included in both distributions. In its GitHub repo you will find "Mr. Marmot" is back.

@ghost
Copy link
Author

ghost commented Feb 5, 2021

@muzimuzhi Oh, thanks, I completely missed all these developments! Will check it out in short.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants