This is a very partial attempt. It doesn't really work, but could help someone find a better solution.
The idea I have is to set \parshape for every paragraph, depending on the space remaining on the page. Namely, the number of lines that should fit on the page is (\pagegoal-\pagetotal)/\baselineskip. The \parshape begins with lines of full length \hsize, then some shortened lines for the cutout, and further lines are set with full length \hsize. The number of lines with full length is (\pagegoal-\pagetotal-\l_cutout_height_dim)/\baselineskip, the number of shorter lines is the difference.
These calculations unfortunately do not account for the fact that pages can stretch or shrink. Besides, I am not sure whether I should be using \int_div_truncate:nn or \int_div_round:nn (i.e., well, truncating the result or rounding it to the nearest integer) to compute the numbers of lines.
Also, using \everypar directly is not very good idea in general, and I should perhaps use the everyhook package.
\documentclass{article}
\usepackage{lipsum}
\usepackage{expl3,xparse}
\ExplSyntaxOn
% This code accesses \everypar, \value{page}, \hsize, \pagegoal,
% \pagetotal, \baselineskip, \parshape, \interlinepenalties from
% TeX/LaTeX2e directly, as they do not have an expl3 interface.
%
\dim_new:N \l__cutout_height_dim
\dim_new:N \l__cutout_width_dim
\dim_new:N \l__cutout_hsize_dim
\int_new:N \l__cutout_long_lines_int
\int_new:N \l__cutout_total_lines_int
\NewDocumentCommand { \cutoutbottomodd } { m m }
{ \__cutout_bottom_odd:nn {#1} {#2} }
\cs_new_protected:Npn \__cutout_bottom_odd:nn #1#2
{
\dim_set:Nn \l__cutout_height_dim {#1}
\dim_set:Nn \l__cutout_width_dim {#2}
\exp_args:Nx \everypar
{
\exp_not:V \everypar
\exp_not:N \__cutout_everypar_test:
}
}
\cs_new_protected_nopar:Npn \__cutout_everypar_test:
{ \int_if_odd:nT { \value{page} } { \__cutout_everypar: } }
\cs_new_protected:Npn \__cutout_everypar:
{
\dim_set:Nn \l__cutout_hsize_dim
{ \hsize - \l__cutout_width_dim }
\int_set:Nn \l__cutout_total_lines_int
{
\int_max:nn { \c_one }
{
\int_div_truncate:nn
{ \pagegoal - \pagetotal }
{ \baselineskip }
}
}
\int_set:Nn \l__cutout_long_lines_int
{
\int_div_truncate:nn
{ \pagegoal - \pagetotal - \l__cutout_height_dim }
{ \baselineskip }
}
\int_compare:nNnF \l__cutout_long_lines_int > \c_zero
{ \int_zero:N \l__cutout_long_lines_int }
\parshape
= \int_eval:n { \l__cutout_total_lines_int + \c_one } \exp_stop_f:
\prg_replicate:nn \l__cutout_long_lines_int
{ \c_zero_dim \hsize }
\prg_replicate:nn
{ \l__cutout_total_lines_int - \l__cutout_long_lines_int }
{ \c_zero_dim \l__cutout_hsize_dim }
\c_zero_dim \hsize
\interlinepenalties
= \int_eval:n { \l__cutout_total_lines_int + \c_one } \exp_stop_f:
\prg_replicate:nn { \l__cutout_total_lines_int - \c_one }
{ 10000 \exp_stop_f: }
-10000 \exp_stop_f:
10000 \exp_stop_f:
}
\ExplSyntaxOff
\begin{document}
\cutoutbottomodd{96pt}{120pt}
\lipsum[1-10]
\large
\lipsum[11-20]
\Large
\lipsum[21-30]
\scriptsize
\lipsum[31-50]
\footnotesize
\lipsum[51-70]
\end{document}
EDIT: code above updated to only put the hole on odd pages. Also, in the previous version, long lines sometimes came from the next page onto the previous page, hence bleeding through the hole. Now I am using eTeX's \interlinepenalties to force a page break.
EDIT2: Use \int_eval:n instead of the long-deprecated \int_eval:w. Also bring the code up to more recent coding conventions.
\parshapemacro can do for you? – Mico Dec 02 '11 at 11:11