[Tex/LaTex] How to force multicols environment to use first column when called from within a minipage

minipagemulticol

I understand how to get multicol package to use the entirety of the first column before moving to the next:

\begin{multicols*}{3} 
....
\end{multicols*}

which is mentioned in section 2.2 of the manual. But it also mentions in that section that the starred environment "only works on top-level." The manual explains that "inside a box one has to balance to determine a column height in absence of a fixed value."

What I would like to know is whether there's a way to get around this when the height of the containing box is known.

In other words, if I would like to write something like

\begin{minipage}[t][4in]{10in}
\begin{multicols*}{3}
....
\end{multicols*}
\end{minipage}

and get the multicol package to think that it's on a page of the specified height.

Does anyone know any work arounds?

I know I could use \newgeometry for a given page. But, my headers and footers get moved around and I'm not happy with that. Moreover, I generally don't want to dedicate entire pages to this multicolumned format.

Here's an example of what the page design should look like. While it might be hard to tell from the image. This is an 8.5×11 page in landscape.

enter image description here

Here's the MWE which created this image. This is a very odd page design, but it meets the needs for the finished document.

\documentclass{article}
%--------------------
\usepackage[top=0.35in,bottom=0.35in,width=4in,landscape,includefoot,includehead]{geometry}
%--------------------
\usepackage{fancyhdr}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\lhead{Left}\rhead{Right}
\pagestyle{fancy}
%--------------------
\usepackage{multicol}
\setlength{\columnseprule}{0.4pt}
\setlength{\columnsep}{0.25in}
%--------------------
\usepackage{lipsum}
\begin{document}

\lipsum[1]
\vspace{2ex}

\noindent
\hspace*{\dimexpr\textwidth/2}%
\hspace*{-5in}%
\begin{minipage}{0pt}
  \begin{minipage}[t][2.85in]{10in}
    \begin{multicols*}{4}
        \lipsum[1-2]
        \vspace{2in}        

        \vspace{2.85in}~
        \hspace*{\fill}.
    \end{multicols*}
  \end{minipage}
\end{minipage}

\vspace{2ex}
\lipsum[2]

\end{document}

The document is deliberately designed to have extremely large margins—so the headers and footers should similarly not span the entire page to preserve the appearance of massive margins.

Obviously I can achieve the effect I want, but I don't want to have to go through each of these multi-column insets and manually place sufficient struts to properly fill up the height of the box.

Incidentally,
the final \hspace*{\fill}. in the multicols environment is only inserted there so that, when I cropped the image, the cropping didn't knock off too much of the empty space.

Two ideas:

I have two ideas about how to accomplish this. But I don't have any idea of how to implement either.

Preliminaries

Though for the final version I would like to be able to choose how many columns I need, for the sake of explaining my ideas, I'll assume that I'm working with 4 columns of text.

Let's say that the total width of the environment is W and that the \columnsep is set to s. Then one column of text within the environment should have a width of C=(W-3s)/4.

Let's say that height of the environment is H. Since I'm formatting four columns of text, where each column has height H, the total effective height of each column, if stacked on top of each other combined, is close to 4H.

First Idea

The first formats a multi-column environment under a single run with pdflatex.

The idea is as follows:

(1) First format the text as though it were just one column of width C

I could do that something like:

\begin{lrbox}{\aebox}
  \begin{minipage}{<width=C>}
  ....TEXT....
  \end{minipage}
\end{lrbox}

(2) Measure the height of this column of text, call that h

\setlength{<height=h>}{\dimexpr\dp\aebox+\ht\aebox\relax}    

(3) If h>4H, then I've got problems. I'm not interested in considering this case.

(4) If h<4H, then I look at the difference 4H-h and create as many struts of height H that will fit in 4H-h and one remainder strut of height 4H-h modulo H

Suppose the columns are lettered, from left to right, as A, B, C, and D. Then I will have lengths

\aestrutDheight      
\aestrutBheight      
\aestrutCheight      
\aestrutRheight <remainder height>

<PSEUDO-CODE>
let L=4H-h

while L > H
{
  <set a length for each iteration>
  \setlength{\aestrut[DBC]height}{<height=H>}
  L=L-H
}
\setlength{\aestrutRheight}{L}

(5) Reprocess the text but with the the remainder strut and other struts appended.

The pseudo-code here is for the case where I have enough text to span 3 columns, but not 4.

\begin{minipage}[t][<height=H>]{<width=C>}
\begin{multicols*}{4}
....TEXT....

\rule{0pt}{\aestrutRheight}

\rule{0pt}{\aestrutDheight}
\end{multicols*}
\end{minipage}

Generally, I think I can do this except for the very last step. Once I've used the text, I've lost it and don't know how to reprocess it.

Second Idea

This idea is very similar to the first. But requires two passes of pdflatex.

(1) Try formatting the text as 4 columns of balanced text

\begin{lrbox}{\aebox}
\begin{minipage}{<width=W>}
\begin{multicols}{4}
....TEXT....
\end{multicols}
\end{minipage}
\end{lrbox}

(2) Measure the height of this box.

(3) Calculate the different of betweeen this height and the target height.

(4) Calculate the lengths of the necessary struts, but write this out to a log file.

(5) On the second rerun of pdflatex, read in the log file and then format the text as

\begin{minipage}[<height=H>]{<width=W>}
\begin{multicols*}{4}
...TEXT...
<insert struts determine by `log` file>
\end{multicols*}
\end{minipage}

But I don't know how to write this information to a log file or how to read in from the log file to create the necessary struts.

UPDATE: The start of a workable solution, but not quite there.

I've created the following work around. But there are several things about it that I can't get right:

\begin{filecontents}{content.tex}
  Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit,
  vestibulum ut, placerat ac, adipiscing vitae, felis. Curabitur dictum
  gravida mauris. Nam arcu libero, nonummy eget, consectetuer id, vulputate
  a, magna. Donec vehicula augue eu neque. Pellentesque habitant morbi
  tristique senectus et netus et malesuada fames ac turpis egestas. Mauris
  ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna
  fringilla ultrices. Phasellus eu tellus sit amet tortor gravida
  placerat. Integer sapien est, iaculis in, pretium quis, viverra ac,
  nunc. Praesent eget sem vel leo ultrices bibendum. Aenean faucibus. Morbi
  dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabitur auctor
  semper nulla. Donec varius orci eget risus. Duis nibh mi, congue eu,
  accumsan eleifend, sagittis quis, diam. Duis eget orci sit amet orci
  dignissim rutrum.

  Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel,
  wisi. Morbi auctor lorem non justo. Nam lacus libero, pretium at, lobortis
  vitae, ultricies et, tellus. Donec aliquet, tortor sed accumsan bibendum,
  erat ligula aliquet magna, vitae ornare odio metus a mi. Morbi ac orci et
  nisl hendrerit mollis. Suspendisse ut massa. Cras nec ante. Pellentesque a
\end{filecontents}
\documentclass{article}
%--------------------
\usepackage[top=0.35in,bottom=0.35in,width=4in,landscape,includefoot,includehead]{geometry}
%--------------------
\usepackage{xcolor}
\usepackage{fancyhdr}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\lhead{Left}\rhead{Right}
\pagestyle{fancy}
%--------------------
\usepackage{multicol}
\setlength{\columnseprule}{0.4pt}
\setlength{\columnsep}{0.25in}
%--------------------
\usepackage{lipsum}

\usepackage{xparse}

\newlength\aetmpdima
\newlength\aetmpdimb
%% Loop to reduce the height to below max value
\def\whileloop#1#2%
  {\aetmpdima#1%
   \aetmpdimb#2%
   \ifdim\aetmpdima>\aetmpdimb\relax
      \aetmpdima\dimexpr\aetmpdima-\aetmpdimb\relax
      \whileloop\aetmpdima\aetmpdimb
   \fi
   #1\aetmpdima
  }

\newlength{\myenvWIDTH}
\newlength{\myenvHEIGHT}
\newlength{\myenvREMAINDER}
\newlength{\myenvSTRUT}
\newcommand{\myenvCOLCNT}{4}
\setlength{\myenvWIDTH}{10in}
\setlength{\myenvHEIGHT}{2.25in}
\setlength{\myenvSTRUT}{0pt}
\newsavebox{\singlecolumnbox}
\NewDocumentCommand{\aecols}{ m }
  {
    %% create a box the width of one column
    \begin{lrbox}{\singlecolumnbox}%
      \begin{minipage}{\dimexpr(\myenvWIDTH-\number\numexpr\myenvCOLCNT-1\relax\columnsep)/\myenvCOLCNT}
        \input{#1}%
      \end{minipage}%
    \end{lrbox}%
    %% reduce lengths to get lengths of the struts
    \setlength{\myenvREMAINDER}{\dimexpr\myenvCOLCNT\myenvHEIGHT-(\ht\singlecolumnbox+\dp\singlecolumnbox)\relax}%
    \ifdim\myenvREMAINDER>\myenvHEIGHT\relax
      \setlength{\myenvSTRUT}{\myenvHEIGHT}\fi          
    \whileloop{\myenvREMAINDER}{\myenvHEIGHT}%
    %% format the multicolumn environment
    \noindent
    \begin{minipage}[t][\myenvHEIGHT]{\myenvWIDTH}%
    \noindent
    \begin{multicols}{\myenvCOLCNT}%
        \raisebox{0pt}[0pt][0pt]{\textcolor{gray!60}{\rule[\dimexpr-\myenvHEIGHT+2ex]{4pt}{\myenvHEIGHT}}}
        \input{#1}
        \textcolor{blue}{\rule{4pt}{\dimexpr\myenvREMAINDER}}
        \textcolor{red}{\rule{4pt}{\dimexpr\myenvSTRUT}}
    \end{multicols}%
    \par\xdef\tpd{\the\prevdepth}
    \end{minipage}%
    \par\prevdepth\tpd
}

\begin{document}

\lipsum[1]
\vspace{2ex}

\noindent\hspace*{\dimexpr\textwidth/2-\myenvWIDTH/2}%
\rule{\myenvWIDTH}{5pt}

\noindent\hspace*{\dimexpr\textwidth/2-\myenvWIDTH/2}%
\aecols{content}

\vspace{2ex}
\lipsum[2]

\end{document}

enter image description here

The first problem is I can't seem to get \vspace to work correctly. So, I have to use \rule. In this above example, I've given width different from 0pt only to help them be visible for development purposes. Similarly, the gray strut at the beginning is only there for development purposes.

The second problem, I sometimes get multiple overful vbox errors: apparently caused by my struts, but only for certain values of the parameters. (Can't seem to reproduce them right now, though.) When I do get the errors, there are far more than struts I've created. Sorry I can't reproduce them right now. 🙁

Third problem, I can't get the spacing after the environment to work out correctly. I tried the \prevdepth trick, but it's not working here.

Fourth, not so much a problem, I'm getting around the issue of having to reformatting the same text by reading the text in twice from an outside file. I can live with this, but it would be nice if I didn't have to go this route.

Any suggestions about how to fix any of this?

PS

If I can rediscover the parameter values which cause the vbox errors, I'll post them in the comments following this question.

Best Answer

I'd use a low level method with \vsplit:

\documentclass{article}
\usepackage[latin]{babel} % to hyphenate lipsum
\usepackage{microtype}    % avoid overfull lines
%--------------------
\usepackage[
  top=0.35in,
  bottom=0.35in,
  width=4in,
  landscape,
  includefoot,
  includehead,
]{geometry}

%--------------------

\usepackage{fancyhdr}
\pagestyle{fancy}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\fancyhead[L]{Left}
\fancyhead[R]{Right}

%--------------------

\usepackage{lipsum}

\begin{document}

\lipsum[1]

\vspace{2ex}

\noindent
\hspace*{-3in}%
\makebox[0pt][l]{%
  \setbox0=\vbox{\hsize=2.5in
    \lipsum[1-2]
  }%
  \splittopskip=0pt \vbadness=10000
  \setbox2=\vsplit0 to 2.5in
  \vtop{\hrule height0pt\unvbox2}\hspace{0.25in}\vrule\hspace{0.25in}%
  \setbox2=\vsplit0 to 2.5in
  \vtop{\hrule height0pt\unvbox2}\hspace{0.25in}\vrule\hspace{0.25in}%
  \vtop{\hrule height0pt\unvbox0}
}

\vspace{3ex}

\lipsum[2]

\end{document}

Adjust the positioning at will.

enter image description here