3

I would like to reliably detect how much space is left on the last page of the document. Preferably, this should be accessible in a \AtEndDocument or similar hook, so that I can write this information to my own auxiliary file. Another option is if it gets automatically written in the standard .aux file; I can then recover the information from there in the next run, that's not a big deal. More importantly, it has to take account floats and footnotes on the last page, including [b] ones.

Use case

When putting together journal issues the "TUGboat" style, where articles need not start at a new page, it is necessary to know how much space was left by the previous article, and this really should be automated and reliable. This would get incorporated into a package I work on which allows for journal issues' compilation.

Examples

In the examples below, the \rule serves as a measurement tool to detect the resulting space by trial and error.

Example 1 – no figures

\documentclass[a4paper]{article}
\usepackage{lipsum,showframe}
\begin{document}
\lipsum
%\rule[-1ex]{10pt}{0.75\textheight}
\end{document}
% SHOULD GIVE "0.75\textheight"

Example 2 – top figure

\documentclass[a4paper]{article}
\usepackage{lipsum,showframe}
\begin{document}
\lipsum

\begin{figure}[t] \rule{3cm}{3cm} \caption{A black square} \end{figure}

%\rule[-1ex]{10pt}{0.54\textheight}
\end{document}
% SHOULD GIVE "0.54\textheight"

Example 3 – top and bottom figure

\documentclass[a4paper]{article}
\usepackage{lipsum,showframe}
\begin{document}
\lipsum

\begin{figure}[t] \rule{2cm}{2cm} \caption{A small black square} \end{figure}

\begin{figure}[b] \rule{3cm}{3cm} \caption{A black square} \end{figure}

%\rule[-1ex]{10pt}{0.37\textheight}
\end{document}
% SHOULD GIVE "flag: figure/footnote at the bottom of the last page" and "0.37\textheight" OR "0\textheight"

Example 4 – figure on top, footnote on bottom

\documentclass[a4paper]{article}
\usepackage{lipsum,showframe}
\begin{document}
\lipsum

Random text\footnote{A random footnote.}.

\begin{figure}[t] \rule{3cm}{3cm} \caption{A small black square} \end{figure}

%\rule[-1ex]{10pt}{0.50\textheight}
\end{document}
% SHOULD GIVE "flag: figure/footnote at the bottom of the last page" and "0.50\textheight" OR "0\textheight"
  • By the time \AtEndDocument executes, it is too late. \ht\@outputbox=0pt, \@colroom=\textheight - the size of the float. At the very least you would have to modify \output to compute and save the desired size. – John Kormylo Jul 25 '18 at 3:38
  • @JohnKormylo I suspected it will be too late. Also I supposed that an \output hack would be needed, I hope there are people willing to do it for me :) (some people also know the motivation, which comes from my TUG talk...) – yo' Jul 25 '18 at 20:57
  • @yo' I have no idea to what end you need that. Could you explain, please? – Keks Dose Jul 27 '18 at 11:50
  • @KeksDose I added the explanation. – yo' Jul 29 '18 at 13:38
1

This uses \tikzmark and \vfill to compute the remaining space on last page (or whatever page \pagespace is used). It take two runs to compute the length, and another to get the value back to \begin{document}.

It is possible to do this using \pdflastypos instead of tikz. You could even reduce it to 2 runs by computing \lastpagegap when the aux file is read rather than at the end.

\documentclass[a4paper]{article}
\usepackage{lipsum,showframe}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\newlength{\lastpagegap}% reserve global name

\makeatletter
\newcommand{\pagespace}{%
  \hrule height0pt %terminate last paragraph, it needed
  \strut\tikzmark{lastpage}
  \vfill
  \noindent\begin{tikzpicture}[remember picture,overlay]
    \coordinate (lastpage) at (pic cs:lastpage);
    \pgfextracty{\lastpagegap}{\pgfpointanchor{lastpage}{center}}
    \global\advance\lastpagegap by \ht\strutbox
    %\node[above right]{\the\lastpagegap};
  \end{tikzpicture}%
  \write\@auxout{\string\global\string\lastpagegap=\the\lastpagegap}% store in aux file
 }
\makeatother

\begin{document}
\the\lastpagegap

\lipsum

Random text\footnote{A random footnote.}.

\begin{figure}[t] \rule{3cm}{3cm} \caption{A small black square} \end{figure}

\pagespace\smash{\rule{10pt}{\lastpagegap}}
\end{document}
1

Here is a solution using \pagegoal and \pagetotal. The macro \getremainingheight writes a command to the aux file, which writes the remaining height to another auxiliary file (here \jobname.xyz, I marked every occurrence the file ending in the source, since it's a bit hard to find them all). The value in the file is valid as soon as the height in the file is final, so in most cases after the first run.

There is no need to do anything special for floats and/or footnotes, since TeX/LaTeX does this already. And since the hook for \AtEndDocument is executed pretty much at the beginning of \end{document}, the macro can be put in \AtEndDocument.

If there is a float page at the end, the macro will not recognize this. It will report the remaining height of the last non-float page.

If for some reason there is a \newpage or similar at the end (may be hidden in some macro), a remaining height of 0pt will be reported (you can easily change this height). This can't be prevented, since the new page is already started at the point \getremainingheight is called. But I implemented a flag \ifendonnewpage and its state is also written to the auxiliary file.

I also took the freedom to implement an additional feature: you can set a \minimumheight. If the remaining height is less than that, then a height of 0pt is reported instead.

\documentclass[a4paper]{article}
\usepackage{showframe}
\usepackage{lipsum}

\makeatletter
% write information about free space to \jobname.xyz
\AtEndDocument{\if@filesw\newwrite\tf@xyz
    %                                 ^^^
    \immediate\openout\tf@xyz\jobname.xyz\fi
    %                     ^^^         ^^^
}
\newdimen\remainingheight
\newdimen\minimumheight
\newif\ifendonnewpage
\endonnewpagefalse
\newcommand*{\calcremaining}{%
    \par
    % in case a new page just started report a remaining height of 0pt
    \ifdim\pagegoal=\maxdimen
        \remainingheight\z@
        \endonnewpagetrue
    \else
        \remainingheight\pagegoal
        \advance\remainingheight by -\pagetotal
        \advance\remainingheight by -\lineskip
        \endonnewpagefalse
    \fi
}
\newcommand*{\getremainingheight}{%
    \calcremaining
    % if not enough space, report 0pt remaining space
    \ifdim\remainingheight<\minimumheight
        \remainingheight\z@
    \else
        % important, otherwise the remaining height reported would be slightly too large
        \noindent
        \calcremaining
    \fi
    % write to own aux file via main aux
    \immediate\write\@auxout{\string\@writefile{xyz}{\string\remainingheight\the\remainingheight}}%
    %                                           ^^^
    \immediate\write\@auxout{\string\@writefile{xyz}{\ifendonnewpage\string\endonnewpagetrue\else\string\endonnewpagefalse\fi}}%
    %                                           ^^^
}
% just load the file to show the values in this document
% this will of course need two runs
\AtBeginDocument{\InputIfFileExists{\jobname.xyz}{}{}}
%                                            ^^^
\AtEndDocument{\getremainingheight
% just for testing, will of course produce a third page, if the
% remaining height is less then \baselineskip
%    \ifdim\remainingheight>0pt\rule{10pt}{\remainingheight}\fi
}
\makeatother

% set minimum space to be reported, less then that will be reported as 0pt
\setlength{\minimumheight}{\baselineskip}

\begin{document}
\lipsum

Remaining height: \the\remainingheight; ended on new page: \ifendonnewpage yes\else no\fi

Random text\footnote{A random footnote.}.

% see what happens if page is full
%\lipsum[4]
%\lipsum[4]
%One more line to fill the page.

\begin{figure}[t] \rule{2cm}{2cm} \caption{A small black square} \end{figure}

\begin{figure}[b] \rule{3cm}{3cm} \caption{A black square} \end{figure}

% this will produce a float page, which is not recognized at all
%\begin{figure}[p] \rule{4cm}{4cm} \caption{A big black square} \end{figure}

% see what happens, if there is a newpage at the end
%\newpage
\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.