[Tex/LaTex] Overlapping figures on underfull page

floatspositioning

The following example gives me overlapping figures (figures overlapping themselves and figures overlapping the text) as can be seen in the image below.

I can work around this if I change the surrounding text, the image sizes or remove the h-placement modifier of the second figure. Also with using \raggedbottom the overlapping disappears. The example gives an underfull vbox warning, but on other pages where these warnings occur latex just strechtes the vertical skips so that it doesn't look that good anymore, but at least it's still readable.

So why is this happening? And is there a way to fix this without rewriting some text or not placing the figure "here"?

enter image description here

\documentclass[parskip=half]{scrbook}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\begin{document}

\begin{figure}[th]
    \fbox{\parbox[c][6cm]{7cm}{img 1}}
    \caption{caption 1}
\end{figure}

x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x

\begin{figure}[th]
    \raggedleft
    \fbox{\parbox[c][4cm]{5cm}{img 2}}
    \caption{caption 2}
\end{figure}

\section{A section}
y\\y\\y\\y\\y

\end{document}

Best Answer

What a fascinating problem!

As it turns out this is (probably) a bug in LaTeX in there since the dawn of time, but as it touches so delicate areas (where TeX has its limitations) I'm not sure there could be a safe cure for it even if we want to attempt it --- I offer a solution later on, but that may have other issues I haven't thought about yet.

So what is the problem? We need some very special circumstances in the first place

  • scrbook with option parskip=half is essential
  • the page needs some very special structure
  • the h float needs to come at the very bottom

It is possible to shorten the MWE even further and that helped me to pinpoint the issue:

\documentclass[parskip=half]{scrbook}

\tracingonline1\tracingpages1

\begin{document}

\begin{figure}[th]
    \fbox{\parbox[c][6cm]{7cm}{img 1}}
    \caption{caption 1}
\end{figure}

x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x

\begin{figure}[h]
    \raggedleft
    \fbox{\parbox[c][4cm]{5cm}{img 2}}
    \caption{caption 2}
\end{figure}

\addvspace{3cm}
\addpenalty{-300}

Z

\end{document}

The most important line I added was

\tracingonline1\tracingpages1

which gives us tracing of the page breaking. If we run that we get

%% goal height=595.80026, max depth=5.5
% t=0.0 g=595.80026 b=10000 p=0 c=100000#
% t=11.0 g=595.80026 b=10000 p=-10003 c=-10003#
%% goal height=595.80026, max depth=5.5
% t=0.0 g=595.80026 b=10000 p=0 c=100000#
% t=202.1165 g=595.80026 b=10000 p=0 c=100000#
% t=227.7165 plus 2.0 minus 2.0 g=595.80026 b=10000 p=150 c=100000#
% t=241.31651 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=254.91652 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=268.51653 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=282.11653 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=295.71654 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=309.31654 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=322.91655 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=336.51656 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=350.11656 plus 2.0 minus 2.0 g=595.80026 b=10000 p=150 c=100000#
% t=363.71657 plus 2.0 minus 2.0 g=595.80026 b=10000 p=-10004 c=-10004#
%% goal height=16383.99998, max depth=5.5
% t=11.0 g=16383.99998 b=10000 p=-10003 c=-10003#
%% goal height=595.80026, max depth=5.5
% t=0.0 g=595.80026 b=10000 p=0 c=100000#
% t=202.1165 g=595.80026 b=10000 p=0 c=100000#
% t=227.7165 plus 2.0 minus 2.0 g=595.80026 b=10000 p=150 c=100000#
% t=241.31651 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=254.91652 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=268.51653 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=282.11653 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=295.71654 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=309.31654 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=322.91655 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=336.51656 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=350.11656 plus 2.0 minus 2.0 g=595.80026 b=10000 p=150 c=100000#
% t=363.71657 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=520.92757 plus 4.0 minus 4.0 g=595.80026 b=10000 p=0 c=100000#
% t=526.12756 plus -0.8 minus 6.0 g=595.80026 b=10000 p=-300 c=100000#
% t=631.88583 plus 6.0 minus 6.0 g=595.80026 b=* p=0 c=*

Underfull \vbox (badness 10000) has occurred while \output is active []

This shows how the first here float is added c=-10003 then there are all the lines with x on them until we have

% t=363.71657 plus 2.0 minus 2.0 g=595.80026 b=10000 p=-10004 c=-10004#

which corresponds to the second here float. At that point the page is rebuild so we see all the lines coming up again and then ending in this:

% t=363.71657 plus 2.0 minus 2.0 g=595.80026 b=10000 p=0 c=100000#
% t=520.92757 plus 4.0 minus 4.0 g=595.80026 b=10000 p=0 c=100000#
% t=526.12756 plus -0.8 minus 6.0 g=595.80026 b=10000 p=-300 c=100000#
% t=631.88583 plus 6.0 minus 6.0 g=595.80026 b=* p=0 c=*

The first of those is the last x line then there is a big jump in t= due to the fact that we got the second here float added and then at t=526.12756 we see a p=-300, i.e., our explicit penalty. We don't see the space of 3cm because \addpenalty moves itself in front of a preceding skip.

The final line then has c=* meaning we have too much material (that's now the 3cm added). So the page break is taken at the explicit penalty on the line before, i.e., at

% t=526.12756 plus -0.8 minus 6.0 g=595.80026 b=10000 p=-300 c=100000#

Now if you look at this tracing output (and you have your TeXbook ready, or the LaTeX Companion or you know these things by heart :-) ) then you will notice why the page comes out as it does ... do you? No?

Ok here we go:

  • The page has a height of t=526.12756 but the page goals is g=595.80026 so we are far too short and the page material needs to stretch;
  • however the stretch part plus -0.8 is actually negative on this line (and only this line) so in order to stretch from 526pt to 595pt we have to multiply this by -86 (roughly);
  • so TeX goes ahead and multiplies all "plus" parts of all glue on the page by -86;
  • now after the first here float there is glue which is 12pt plus 2pt minus 2pt, so if its plus part is multiplied -86 we get 12pt plus -172pt so a skip of -150pt in total and that drives the lines of x-es upwards;
  • similarly, in front of the second here float there is also such a space so that one then also drive the float upwards by -150pt and this is what you send up with.

So the question that remains is why do we end up with this negative "plus part" on this very line?

The answer to this is deeply hidden in a macro called \@addcurcol whose task it is to attach a float in its proper place. In case of a "here float" it will execute this code:

               \vskip \intextsep
               \box\@currbox
               \penalty\interlinepenalty
               \vskip\intextsep
               \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi

At its very end it issues in certain circumstances (when the float was encountered in vertical mode) \vskip - \parskip and with \parskip being 6.8pt plus 6.8pt due to the option to scrbook we end up with

\vskip -6.8pt plus -6.8pt

and with the rest of the plus parts of on the page totally to 6pt (you can see that in the tracing above) we get the plus -0.8 overall ... and there we are ... sigh.

So what goes wrong here? This negative \parskip is added after the here float to account for the fact that another \parskip will be added soon and if we don't cancel it out the spacing will look very uneven --- especially so if you have a large \parskip in the first place like in the example.

However, in our example we end up getting a page break after it, so there isn't any parskip following that needs canceling and so we have this extra negative one that really messes up the page calculations.

In other words, the page break should never happen there but before that negative \parskip or in fact even before the \intextsep following the here float. But this is quite difficult to arrange. Something like \addpenalty or \addvspace never gets to see this space (as it is added inside the output routine and when the output routine ends you will get automatically a \penalty 10000 added at the very end and that hides anything before.

So I tried a different approach:

  • calculate the space to be added (either \intextsep or \intextsep - \parskip) inside the output routine;
  • then add the \vskip only after the output routine has ended by pushing it out using \aftergroup.

So applying this to the original MWE we get:

\documentclass[parskip=half]{scrbook}

\tracingonline1\tracingpages1

\usepackage{etoolbox}

\makeatletter

\patchcmd\@addtocurcol
    {\vskip\intextsep  \ifnum\outputpenalty <-\@Mii \vskip -\parskip\fi}
    {\calc@after@float@skip
      \aftergroup\add@after@float@skip}
    {\typeout{Patch Successfully applied}}{\typeout{Patch failed!}\ERROR}

\def\calc@after@float@skip{
   \@tempskipa\intextsep
   \ifnum\outputpenalty <-\@Mii \advance\@tempskipa-\parskip \fi
   \xdef\add@after@float@skip{\vskip\the\@tempskipa\relax}%
}

\makeatother

\begin{document}

\begin{figure}[th]
    \fbox{\parbox[c][6cm]{7cm}{img 1}}
    \caption{caption 1}
\end{figure}

x\\x\\x\\x\\x\\x\\x\\x\\x\\x\\x

\begin{figure}[h]
    \raggedleft
    \fbox{\parbox[c][4cm]{5cm}{img 2}}
    \caption{caption 2}
\end{figure}

\section{A section}
y\\y\\y\\y\\y

\end{document}

And the result is

enter image description here

No guarantee though that this isn't changing other documents because that now changes the spacing after here floats near the end of a page.