[Tex/LaTex] How to have a precise control on the size of each page

lengthspaper-sizepdf

I would like to produce a pdf document whose pages have various lengths and fixed widths.

An easier situation

The easiest case would be for instance: the first page has a length of 10cm all the next pages have a length of 15cm. But, what I am looking is more complicated. I wonder if it is possible.

The real situation

Here is what I would like to achieve:

  • the first page has a variable length, depending of the content (cf. this question), which is at most 10cm
  • the next pages have a length equal to 15cm
  • the last page has a variable length, depending of the content, which is at most 15cm

Bounty-rewarded

For those interested, there will be bounties for answers.

"Bug" in Heiko's solution

The solution propped by Heiko, which is great by the way, seems to have problem handling the list. Here is a MWE that shows this problem. The problem is that when there is a list (an itemize environment), the page change is done too early, and, as a result, the first page, whose height should be 8cm in the following example, is too short.

\documentclass{article}

\usepackage%
[paperwidth=10.000000cm,
paperheight=8cm,
hmargin=1.000000mm,
top=1.000000mm,
bottom=1.000000mm]
{geometry}


\pagestyle{empty}

\flushbottom
\setlength{\maxdepth}{0pt}% to address the "third bug"
\setlength{\topskip}{0pt}% no space above the first line

% assuming the page number is the absolute page number
\usepackage{zref-totpages,zref-savepos}
\usepackage{atbegshi}

\makeatletter
\providecommand*{\zsaveposy}{\zsavepos}% for older zref-savepos
\def\@oddhead{\PosFirstHead\PosLastHead\hss}%
\def\@evenhead{\PosLastHead\hss}%
\newcommand*{\PosFirstHead}{%
  \ifnum\value{page}=1 %
    \zsaveposy{PosFirstHead}%
    \global\let\PosFirstHead\@empty
  \fi
}
\newcommand*{\PosLastHead}{%
  \ifnum\value{page}=\ztotpages
    \zsaveposy{PosLastHead}%
    \global\let\PosLastHead\@empty
  \fi
}
\AtBeginShipout{%
  \ifnum\value{page}=1 %
    \dimen@=\dimexpr
      \zposy{PosFirstHead}sp-\headsep
      -\zposy{PosFirst}sp%
    \relax
    \setbox\AtBeginShipoutBox=\vbox{%
      \kern-\dimen@ %
      \copy\AtBeginShipoutBox
    }%
    \advance\pdfpageheight by -\dimen@
  \else
    \ifnum\value{page}=\ztotpages
      \advance\pdfpageheight by%
        -\dimexpr
          \textheight
          -\zposy{PosLastHead}sp+\headsep
          +\zposy{PosLast}sp%
        \relax
    \fi
  \fi
}
\AtEndDocument{%
  \par
  \nobreak
  \dimen@=\prevdepth
  \ifdim\dimen@>\maxdepth
    \kern-\maxdepth
  \else
    \ifdim\dimen@>0pt %
      \kern-\dimen@
    \fi
  \fi
  \zsaveposy{PosLast}%
}
\makeatother

\usepackage{lipsum}% provides dummy text

\interlinepenalty=-100

\begin{document}
\vspace*{\dimexpr0.000cm-\topskip plus 1fill}
\zsaveposy{PosFirst}
\nointerlineskip

This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it?

This is a test:
\begin{itemize}
\item Hello

\item This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test.

\item Good Bye

\item Hello

\item This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test.
\end{itemize}

This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it?

This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it?

This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it? This is a test. This is a longer sentence with some more words, isn't it?
\end{document}

enter image description here

"Bug" in wipet's solution

I tried wipet's solution with the package mdframed and it clashes. Here is a MWE. It produces a document with only one page.

\documentclass{article}

\usepackage[
  paperwidth=15cm,
  paperheight=15cm,
  hmargin=1cm,
  vmargin=0cm,
]{geometry}

\usepackage[framemethod=tikz, needspace=1.5cm]{mdframed}
\usepackage{lipsum}


\newmdenv[%
innerleftmargin = 2mm,
innerrightmargin = 2mm,
innertopmargin = 2mm,
innerbottommargin = 2mm,
leftmargin = 0mm,
rightmargin = 0mm,
splitbottomskip = 2mm,
splittopskip = 4mm,
middlelinewidth = 0mm,
linecolor = red,
backgroundcolor = red,
roundcorner = 0pt,
skipbelow = 0mm,
skipabove = 0mm,
]
{mybox}

\pagestyle{empty}



\topmargin=0pt  % vertical shift of the text in the page
\pdfvorigin=0in % vertical margins above and below the text

\let\textheight=\vsize
\expandafter\let\csname @colht\endcsname=\vsize
\newdimen\topbotmargin
\topbotmargin=2\pdfvorigin \advance\topbotmargin by2\voffset
\headheight=0pt \headsep=0pt
\long\def\firstpage#1{\setbox0=\vbox{#1}\pdfpageheight=\ht0
   \advance\pdfpageheight by\topbotmargin \vsize=\ht0 \box0 \vfil\break
   \pdfpageheight=15cm \vsize=\pdfpageheight \advance\vsize by-\topbotmargin
}
\AtEndDocument{\pdfpageheight=\pagetotal \vsize=\pagetotal
   \advance\pdfpageheight by\topbotmargin
}


\begin{document}

\begin{mybox}
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. 
\end{mybox}

\lipsum[1-9]
\end{document}

Best Answer

The following example uses package geometry to set the page layout with page height 15cm for all pages. At the beginning of the first page and at the end of the last page a space of 5cm plus 1fill is added. It fills the remaining space and ensures that the contents is small enough for a maximum page height of 10cm.

Position markers of package zref-savepos are set on the first page in the header and the start of the contents after the filler space. Package atbegshi is used to hook into the output routine. The free space is measured using the positions and the whole page is moved to the top by the amount of the free space and the page height is shortened accordingly.

For the last page, the position markers are added in the header and the end of the contents. Again the page height is shortened by the amount of free space that is calculated via the position markers and the height of the text block (\textheight).

For the previous action we must know the page number of the last pages. This is provided by package zref-totpages that stores the number of pages in \ztotpages via the reference system. It is assumed that the page numbering of the document is absolute.

Because both the reference for the number of pages and the position markers need an additional LaTeX run, three LaTeX runs are needed.

Both pdfTeX and LuaTeX support position markers (\pdfsavepos and \pdflastposy are used by package zref-savepos) and the setting of the media size (\pdfpageheight).

\flushbottom fills the text area to the bottom. \setlength{\maxdepth}{0pt} avoids that the descenders of the last line are outside the text area. Important, if the vertical margins are zero. Then the descenders would be out of the page.

\setlength{\topskip}{0pt} and \nointerlineskip after \zsavepos{PosFirst} remove white space at the top of the text area.

\documentclass{article}
\usepackage[
  paperwidth=15cm,
  paperheight=15cm,
  hmargin=1cm,
  vmargin=0cm,
]{geometry}
\pagestyle{empty}

\flushbottom
\setlength{\maxdepth}{0pt}% to address the "third bug"
\setlength{\topskip}{0pt}% no space above the first line

% assuming the page number is the absolute page number
\usepackage{zref-totpages,zref-savepos}
\usepackage{atbegshi}

\makeatletter
\providecommand*{\zsaveposy}{\zsavepos}% for older zref-savepos
\def\@oddhead{\PosFirstHead\PosLastHead\hss}%
\def\@evenhead{\PosLastHead\hss}%
\newcommand*{\PosFirstHead}{%
  \ifnum\value{page}=1 %
    \zsaveposy{PosFirstHead}%
    \global\let\PosFirstHead\@empty
  \fi
}
\newcommand*{\PosLastHead}{%
  \ifnum\value{page}=\ztotpages
    \zsaveposy{PosLastHead}%
    \global\let\PosLastHead\@empty
  \fi
}
\AtBeginShipout{%
  \ifnum\value{page}=1 %
    \dimen@=\dimexpr
      \zposy{PosFirstHead}sp-\headsep
      -\zposy{PosFirst}sp%
    \relax
    \setbox\AtBeginShipoutBox=\vbox{%
      \kern-\dimen@ %
      \copy\AtBeginShipoutBox
    }%
    \advance\pdfpageheight by -\dimen@
  \else
    \ifnum\value{page}=\ztotpages
      \advance\pdfpageheight by%
        -\dimexpr
          \textheight
          -\zposy{PosLastHead}sp+\headsep
          +\zposy{PosLast}sp%
        \relax
    \fi
  \fi
}
\AtEndDocument{%
  \par
  \nobreak
  \dimen@=\prevdepth
  \ifdim\dimen@>\maxdepth
    \kern-\maxdepth
  \else
    \ifdim\dimen@>0pt %
      \kern-\dimen@
    \fi
  \fi
  \zsaveposy{PosLast}%
}
\makeatother

\usepackage{lipsum}% provides dummy text

\begin{document}
\vspace*{\dimexpr5cm-\topskip plus 1fill}% first page should not be larger than 15cm
\zsaveposy{PosFirst}
\nointerlineskip

\lipsum[1-9]

\end{document}

Page 1 Page 2 Page 3


Older version with the constraint that the last page does not exceed 10 cm:

\documentclass{article}
\usepackage[
  paperwidth=15cm,
  paperheight=15cm,
  hmargin=1cm,
  vmargin=.9cm,
]{geometry}
\pagestyle{empty}

% \flushbottom % optional
\setlength{\maxdepth}{0pt}% to address the "third bug"

% assuming the page number is the absolute page number
\usepackage{zref-totpages,zref-savepos}
\usepackage{atbegshi}

\makeatletter
\providecommand*{\zsaveposy}{\zsavepos}% for older zref-savepos
\def\@oddhead{\PosFirstHead\PosLastHead\hss}%
\def\@evenhead{\PosLastHead\hss}%
\newcommand*{\PosFirstHead}{%
  \ifnum\value{page}=1 %
    \zsaveposy{PosFirstHead}%
    \global\let\PosFirstHead\@empty
  \fi
}
\newcommand*{\PosLastHead}{%
  \ifnum\value{page}=\ztotpages
    \zsaveposy{PosLastHead}%
    \global\let\PosLastHead\@empty
  \fi
}
\AtBeginShipout{%
  \ifnum\value{page}=1 %
    \dimen@=\dimexpr
      \zposy{PosFirstHead}sp-\headsep
      -\zposy{PosFirst}sp%
    \relax
    \setbox\AtBeginShipoutBox=\vbox{%
      \kern-\dimen@ %
      \copy\AtBeginShipoutBox
    }%
    \advance\pdfpageheight by -\dimen@
  \else
    \ifnum\value{page}=\ztotpages
      \advance\pdfpageheight by%
        -\dimexpr
          \textheight
          -\zposy{PosLastHead}sp+\headsep
          +\zposy{PosLast}sp%
        \relax
    \fi
  \fi
}
\AtEndDocument{%
  \par
  \nobreak
  \dimen@=\prevdepth
  \ifdim\dimen@>\maxdepth
    \kern-\maxdepth
  \else
    \ifdim\dimen@>0pt %
      \kern-\dimen@
    \fi
  \fi
  \zsaveposy{PosLast}%
  \vspace*{5cm plus 1fill}% last page should not be largern than 15cm
}
\makeatother

\usepackage{lipsum}% provides dummy text

\begin{document}
\vspace*{\dimexpr5cm-\topskip plus 1fill}% first page should not be larger than 15cm
\zsaveposy{PosFirst}

\lipsum[1-11]

\end{document}

Page 1 Page 2 Page 3 Page 4

Workaround for mdframed version

The environment of mdframed adds additional breakpoints (e.g. via \needspace), which are preferred over the regular break points between lines.

As workaround the \interlinepenalty can be set to a negative value (-100):

\documentclass{article}
\usepackage[
  paperwidth=15cm,
  paperheight=15cm,
  hmargin=1cm,
  vmargin=0cm,
]{geometry}

\usepackage[framemethod=tikz, needspace=1.5cm]{mdframed}

\newmdenv[%
innerleftmargin = 2mm,
innerrightmargin = 2mm,
innertopmargin = 2mm,
innerbottommargin = 2mm,
leftmargin = 0mm,
rightmargin = 0mm,
splitbottomskip = 2mm,
splittopskip = 4mm,
middlelinewidth = 0mm,
linecolor = red,
backgroundcolor = red,
roundcorner = 0pt,
skipbelow = 0mm,
skipabove = 0mm,
]
{mybox}

\pagestyle{empty}

\flushbottom
\setlength{\maxdepth}{0pt}% to address the "third bug"
\setlength{\topskip}{0pt}% no space above the first line

% assuming the page number is the absolute page number
\usepackage{zref-totpages,zref-savepos}
\usepackage{atbegshi}

\makeatletter
\providecommand*{\zsaveposy}{\zsavepos}% for older zref-savepos
\def\@oddhead{\PosFirstHead\PosLastHead\hss}%
\def\@evenhead{\PosLastHead\hss}%
\newcommand*{\PosFirstHead}{%
  \ifnum\value{page}=1 %
    \zsaveposy{PosFirstHead}%
    \global\let\PosFirstHead\@empty
  \fi
}
\newcommand*{\PosLastHead}{%
  \ifnum\value{page}=\ztotpages
    \zsaveposy{PosLastHead}%
    \global\let\PosLastHead\@empty
  \fi
}
\AtBeginShipout{%
  \ifnum\value{page}=1 %
    \dimen@=\dimexpr
      \zposy{PosFirstHead}sp-\headsep
      -\zposy{PosFirst}sp%
    \relax
    \setbox\AtBeginShipoutBox=\vbox{%
      \kern-\dimen@ %
      \copy\AtBeginShipoutBox
    }%
    \advance\pdfpageheight by -\dimen@
  \else
    \ifnum\value{page}=\ztotpages
      \advance\pdfpageheight by%
        -\dimexpr
          \textheight
          -\zposy{PosLastHead}sp+\headsep
          +\zposy{PosLast}sp%
        \relax
    \fi
  \fi
}
\AtEndDocument{%
  \par
  \nobreak
  \dimen@=\prevdepth
  \ifdim\dimen@>\maxdepth
    \kern-\maxdepth
  \else
    \ifdim\dimen@>0pt %
      \kern-\dimen@
    \fi
  \fi
  \zsaveposy{PosLast}%
}
\makeatother

\usepackage{lipsum}% provides dummy text

\interlinepenalty=-100

\begin{document}
\vspace*{\dimexpr5cm-\topskip plus 1fill}% first page should not be larger than 15cm
\zsaveposy{PosFirst}
\nointerlineskip
\begin{mybox}
\lipsum[1]
\end{mybox}
\lipsum[1-9]

\end{document}

Page 1 Page 2 Page 3

A negative \interlinepenalty has its drawbacks (encouraging too early page breaks).

An alternative would be to put the framed environment inside a non-breakable minipage that removes the additional breakpoints:

\noindent
\begin{minipage}[b]{\linewidth}
\begin{mybox}
\lipsum[1]
\end{mybox}%
\end{minipage}

But then a longer mybox cannot be broken across pages.

A more reliable solution can probably be provided by a rewrite/patch of the output routine. However, that might clash with packages like longtable, which uses their own modifications of the output routine.