21

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}
29
  • 2
    @Colas: The first "bug" is not related to package mdframed. You require a shorter last page (10cm). If the last page is 11cm, for example, you have a problem: 11cm does not fit on 10cm and 11cm does not fill 15cm and leaves something for the final 10cm page. You have to shorten your text or make it longer. Commented May 27, 2014 at 12:26
  • 1
    @Colas: There is not enough material to fill the page. You can add \flushbottom and add some glue to \parskip/\baselineskip, e.g. \addtolength{\parskip}{0pt plus 2ex} to stretch the white spaces. Commented May 27, 2014 at 12:36
  • 1
    @Colas: \setlength{\maxdepth}{0pt}\flushbottom should move the last line of the text page right to the bottom border of the text area without descenders outside the text area. Commented May 27, 2014 at 12:47
  • 1
    @Colas: Yes, I have read 10 instead of 15 for the last page. Answer updated. Commented May 27, 2014 at 13:15
  • 1
    @Colas: You could set \setlength{\topskip}{0pt}, but then the first baseline might vary depending on the text. An alternative: \settoheight{\topskip}{ABC}. Commented May 28, 2014 at 11:26

2 Answers 2

14
+300

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.

20
  • Awesome ! That really looks like what I was looking for!
    – Colas
    Commented May 23, 2014 at 0:16
  • there is a clash between your solution and the package mdframed. Do you think you can fix it? Thank you very much!! I put a MWE as en edit of my question.
    – Colas
    Commented May 27, 2014 at 12:09
  • Yes I have found an occurrence of this situation without mdframed (see my edit). Can it be fixed automatically? Thanks!!
    – Colas
    Commented May 27, 2014 at 12:23
  • 1
    @Colas That comment was not intended as a criticism. It is a (possibly twisted) joke based on a typo in one of Heiko's comments above. ('If it is not working, then you can claim him.' I take it that the intended statement was 'If it is not working, then you can blaim him.' Claiming him would be another matter altogether!)
    – cfr
    Commented Nov 28, 2014 at 12:39
  • 2
    Whatever blaiming is, of course.
    – cfr
    Commented Sep 17, 2015 at 21:05
14

The page height is controlled by \pdfpageheight primitive register. It means that the following code does what you want:

\def\lorem{Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
  eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad   
  minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
  commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
  velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
  cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
  est laborum} 

\setbox0=\vbox{\lorem.\par\lorem.}

\pdfpageheight=\ht0 
\advance\pdfpageheight by2\voffset \advance\pdfpageheight by2in
\vsize=\ht0

\vbox to 0pt{\box0\vss}\vfil\break

\pdfpageheight=15cm 
\vsize=\pdfpageheight \advance\vsize by-2in \advance\vsize by-2\voffset

\lorem.\par \lorem. \lorem.\par
\lorem.\par \lorem. \lorem.\par
\lorem.\par \lorem.\par
\lorem.\par \lorem.\par

\pdfpageheight=\pagetotal \vsize=\pagetotal
\advance\pdfpageheight by2\voffset \advance\pdfpageheight by2in

\end

Compile it by pdftex document.

Edit: I'v added the last page calculation.

Why there is non-LaTeX solution? Because 1) LaTeX was not specified in the question, 2) I never use LaTeX, 3) I want to show the core of the solution: setting the \pdfpageheight primitive register. This core is not exacly shown in the second answer here but it must use it.

EDIT2 (May 28): Colas: Can you make it a LaTeX solution...

Because I am not using LaTeX, my LaTeX solution can look as Assembler code inside C++ language. But it works.

\documentclass{article}

\topmargin=0pt  % vertical shift of the text in the page
\pdfvorigin=1in % 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
}
\def\lorem{Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
  eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
  minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
  ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
  velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
  cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
  est laborum}

\begin{document}

\firstpage{\lorem.\par \lorem.}

\lorem.\par \lorem. \lorem.\par
\lorem.\par \lorem. \lorem.\par
\lorem.\par \lorem.\par
\lorem.\par \lorem.\par

\end{document}

Note the dirty trick \let\textheight=\vsize and \let\@colht=\vsize. LaTeX uses \vsize primitive as \textheight for users and uses \@colht in its \output routine. I need to let all of these as one object because my macros manipulate only with \vsize.

10
  • 1
    Thanks! Can you provide a MWE and a picture of the output?
    – Colas
    Commented May 23, 2014 at 0:18
  • 1
    The code itself is working example. You can grab it to the file (say) document.tex and you can process the command pdftex document. You can get picture of the output when you use evince or another PDF viewer.
    – wipet
    Commented May 23, 2014 at 3:46
  • Very impressive! I wish I knew TeX... Can I learn it quickly to you think?
    – Colas
    Commented May 23, 2014 at 8:57
  • 6
    @colas Here is my evolution: first (20 years ago) cca 1 month with TeXbook and intesive study, then maintenance the cplain format and csfonts (till now), then rewriting internals of TeX (encTeX extension which can deal with all UTF-8 characters as with one token in old pdfTeX engine), then writing comprehensive books about TeX internals and macro programming for Czech users (TeX for pragmatics, TeXbook inside out, both have full text available at internet), then writing simple macro OPmac (funcionality comparable with whole LaTeX+nfss+hyperref+colors+... but only with 1.7k lines of code).
    – wipet
    Commented May 24, 2014 at 1:59
  • 1
    @Colas Sorry, I never heard about mdframed. If I need to put the text into colored box, I am able to write my own macro for such purpose (very simple and written in short time). The basic principle for your original task is known: you have to set \pdfpageheight primitive properly. All others is only a fight with the LaTeX's complicated macros and its complicated packages. I'll not subordinate to this, because I need not use LaTeX and I advice to other people: don't use LaTeX.
    – wipet
    Commented Oct 13, 2014 at 12:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .