52

In this document, from page 12 and following, the notes from the marginpar are overflowing in the footer of each page.

overflowing notes

How can this be achieved in LaTeX?

ConTeXT/LuaTeX based solutions are welcome, too!

11
  • 1
    sadly, the cited file throws the message "File does not begin with %PDF-." from adobe reader, and i'm unable to look past the first page. (trying to read with current adobe reader plugin to current firefox on windows xp.) Sep 10, 2011 at 15:36
  • @barbara: I've downloaded it several times without a problem... and acroread 9 opens it well on Ubuntu Natty.
    – raphink
    Sep 10, 2011 at 16:33
  • 6
    @JosephWright and latex3 folks: am I right in thinking this would make a great testcase and usecase for latex3's coffins or galleys, or whichever the right module is these days? :) This seems like an interesting page description challenge: "The textblock is 0.66\pagewidth * 0.75\pageheight. The marginpar area consists of three coffins, of width 0.33\pagewidth and heights \textheight and 0.25\textheight, and the text flows between them." This is the sort of layout that usually made me give up on latex and switch to InDesign or a desktop publishing program... I hope latex can do it soon too!
    – Ben Lerner
    Sep 15, 2011 at 18:19
  • 2
    @Ben. At present, this is well beyond the areas we've examined. 'coffins' are about box alignment, but not reflowing text. I suspect that may come up in the output routine, but even there you have to remember that TeX's model for a page is not really suited to reflowing. Of course, the ConTeXt people may well have a Lua-based solution: I will try to find out.
    – Joseph Wright
    Sep 15, 2011 at 20:02
  • 2
    @Ben Lerner Your suggestion is a variant of multicolumn layouts. This particular layout's main feature is that all notes fit on the same page as the main text (no note is carried over to the next page). This way it can be solved easier by measuring the marginpar boxes, if they exceed overflow to third box, measure again and reduce the textheight. Only complication would be when a note is on the last paragraph or so. When the page textheight is reduced the boxes will change size again and you will need to iterate. I agree though that a general solution is more desirable. Pray for LaTeX3! Sep 15, 2011 at 21:42

3 Answers 3

46
+400

Here's my solution:

marginfootnote example

marginfootnote 2column

Some general remarks on the implementation:

  1. At the moment everything is bottom-aligned. Some positioning magic might be possible to get the margin notes near their footnote marks (especially when they are sparse), but probably only in a second run. It will be a lot of work to get good-looking positions when there are conflicts.
  2. This is mainly based on \footins. I started from the multicol footnote trick from Appendix D of the TeXbook.
  3. I had to make a separate insert for the margin notes, so that they are ignored for calculating the page break position. Whether a footnote is put in the margin or in the foot is decided while executing \footnotetext.
  4. Thanks to David Carlisle for pointing me to a means for recovering footnotes which were split off at page break and putting them back in the margin. See question Can I get at stuff in “current page” or “recent contributions”?.
  5. I'll test the implementation a bit more with standard stuff like floats, fixing issues, but apart from that, I declare this finished. The basic technique requested in the question has been provided. Unless someone has a real use for this and wants some features added or glitches fixed, I won't attempt anything fancy, including the vertical positioning of margin footnotes.

EDIT: The code

The following should be put into a file marginfootnotes.sty:

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{marginfootnotes}[2012/04/03 EXPERIMENTAL package for
putting footnotes into the margin, overflowing into the footer]

\RequirePackage{kvoptions}

\RequirePackage{etoolbox}

% We need this package to do some output / insert recovery magic. 

\RequirePackage{afterpage}

% Number of columns in footer.

\DeclareStringOption[2]{columns}

% Should we reorder footnotes on odd pages?

\DeclareBoolOption[true]{fixorder}

\ProcessKeyvalOptions*

% Need a fresh insert for the marginfoornotes.

\newinsert\marginfootnotes@ofn
\skip\marginfootnotes@ofn=\z@
\count\marginfootnotes@ofn=\z@
\dimen\marginfootnotes@ofn=\textheight

% Measure how much material already is in the margin.

\newdimen\marginht@ofn

% We rebuild page geometry such that there are <columns>+1 columns
% (including marginpar) of width \marginparwidth, separated by
% \marginparsep, and \textwidth covers <columns>-1 of them. 

\@tempdima\marginparwidth
\marginparwidth
\dimexpr
  (\textwidth+\marginparwidth+2\marginparsep)%
  /\numexpr\marginfootnotes@columns+\@ne\relax%
  -\marginparsep
\relax
\textwidth\dimexpr\marginfootnotes@columns\marginparwidth+\numexpr\marginfootnotes@columns-\@ne\relax\marginparsep\relax

\advance\evensidemargin\dimexpr\marginparwidth-\@tempdima\relax

% As there are <columns> columns of footnotes now, we need to adjust
% the magnification factor.

\count\footins=\numexpr1000/\marginfootnotes@columns\relax

% Redefinition of \@footnotetext. Measure whether footnote still fits
% in the margin, otherwise insert as footnote.

\long\def\@footnotetext#1%
{%
  \setbox\@tempboxa=\vbox{\typesetfootnote@ofn{#1}}%
  \ifdim\dimexpr\marginht@ofn+\ht\@tempboxa+\dp\@tempboxa\relax>\textheight
    \insert\footins{\typesetfootnote@ofn{#1}}%
   \else
    \insert\marginfootnotes@ofn{\typesetfootnote@ofn{#1}}%
    \global\advance\marginht@ofn\dimexpr\ht\@tempboxa+\dp\@tempboxa\relax
  \fi
}%

\newcommand\typesetfootnote@ofn[1]
{%
  \reset@font\footnotesize
  \interlinepenalty\interfootnotelinepenalty
  \splittopskip\footnotesep
  \splitmaxdepth \dp\strutbox \floatingpenalty \@MM
  \hsize\marginparwidth \@parboxrestore
  \protected@edef\@currentlabel{%
    \csname p@footnote\endcsname\@thefnmark
  }%
  \color@begingroup
  \@makefntext{%
    \rule\z@\footnotesep\ignorespaces#1\@finalstrut\strutbox}%
  \color@endgroup
}

% We hook into the output routine, replacing the "simple" output of
% \footins by our more complicated scheme.

\patchcmd\@makecol
{%
   \ifvoid\footins
     \setbox\@outputbox \box\@cclv
   \else
     \setbox\@outputbox \vbox {%
       \boxmaxdepth \@maxdepth
       \unvbox \@cclv
       \vskip \skip\footins
       \color@begingroup
         \normalcolor
         \footnoterule
         \unvbox \footins
       \color@endgroup
       }%
   \fi
}{\output@footins}{}{}

% If \footins is void, we still need to output the margin
% box. Otherwise we call the macro for balancing footnote columns.

\newcommand\output@footins
{%
   \ifvoid\footins
     \setbox\@outputbox \vbox {%
       \boxmaxdepth \@maxdepth
       \unvbox \@cclv
       \hbox
       {%
         \if@twoside
           \ifodd\c@page
             \hskip\textwidth\hskip\marginparsep
            \else 
             \hskip-\marginparwidth\hskip-\marginparsep
           \fi
          \else
           \hskip\textwidth\hskip\marginparsep
         \fi
         \smash{\box\marginfootnotes@ofn}%
       }%
     }%
   \else
     \setbox\@outputbox \vbox {%
       \boxmaxdepth \@maxdepth
       \unvbox \@cclv
       \vskip \skip\footins
       \color@begingroup
         \normalcolor
         \footnoterule
         \balancecolumns@ofn
       \color@endgroup
       }%
   \fi
   \global\marginht@ofn\z@
}

% The following is roughly based on the example from appendix D of the
% TeXbook, plus outputting the margin column.

\newcount\k
\newcount\lines@ofn
\newbox\footnotetextbox@ofn
\def\balancecolumns@ofn
{%
  \setbox\footnotetextbox@ofn=\box\footins
  \k=\marginfootnotes@columns
  \hbox to\dimexpr\k\marginparwidth+\marginparwidth+\k\marginparsep\relax
  {%
    \footnotesize
    \lines@ofn
    \numexpr
      \dimexpr\ht\footnotetextbox@ofn+\dp\footnotetextbox@ofn\relax
      /\baselineskip
      /\marginfootnotes@columns
    \relax
    \splittopskip\dimexpr\baselineskip-\dp\strutbox\relax
    \vbadness\maxdimen
    \vfuzz\maxdimen
    \splitmaxdepth \dp\strutbox
    \if@twoside
      \ifodd\c@page
        \ifmarginfootnotes@fixorder
          \setbox\footnotetextbox@ofn=\vbox{\unvbox\marginfootnotes@ofn\unvbox\footnotetextbox@ofn}%
        \fi
       \else
        \hskip-\marginparwidth\hskip-\marginparsep
        \setbox\@tempboxa=\box\marginfootnotes@ofn
        \output@tempboxa
        \hskip\marginparsep
      \fi
     \else
      \ifmarginfootnotes@fixorder
        \setbox\footnotetextbox@ofn=\vbox{\unvbox\marginfootnotes@ofn\unvbox\footnotetextbox@ofn}%
      \fi
    \fi
    \dosplits
    \if@twoside
      \ifodd\c@page
        \ifmarginfootnotes@fixorder
          \setbox\@tempboxa=\box\footnotetextbox@ofn
         \else
          \setbox\@tempboxa=\box\marginfootnotes@ofn
        \fi
        \output@tempboxa
      \fi
     \else
      \ifmarginfootnotes@fixorder
        \setbox\@tempboxa=\box\footnotetextbox@ofn
       \else
        \setbox\@tempboxa=\box\marginfootnotes@ofn
      \fi
      \output@tempboxa
    \fi
  }%
}

\newcommand\output@tempboxa
{%
  \@tempcnta
  \numexpr
    \dimexpr\ht\@tempboxa+\dp\@tempboxa\relax
    /\baselineskip
  \relax
  \smash{\vsplit\@tempboxa to \@tempcnta\baselineskip}%
}

\def\dosplits{\ifnum\k>\@ne \splitoff\hskip\marginparsep
\global\advance\k-1\dosplits\fi}
\def\splitoff
{%
  \vsplit\footnotetextbox@ofn to \lines@ofn\baselineskip
}

% Thanks to David Carlisle for pointing me to this easy solution how
% to recover \footins material which was split off by a page break
% and move it into the margin. afterpage code is used and patched
% to avoid conflicts.

\newtoks\output@ofn

\output@ofn{%
  \the\AP@output
  \ifnum\outputpenalty>-\@Mi
    \aftergroup\fixfootins@ofn
  \fi
}%

\global\output{\the\output@ofn}%

\newcommand\fixfootins@ofn
{%
  \AP@savetop
  \addboxcontents\AP@partial
  \ifvoid\AP@footins\else
    \movefootins@ofn
  \fi
  \AP@noindent
}

\newcommand\movefootins@ofn
{%
  \global\advance\marginht@ofn\dimexpr\ht\AP@footins+\dp\AP@footins\relax
  \insert\marginfootnotes@ofn{\unvbox\AP@footins}%
}

\patchcmd\AP@@
{\insert\footins{\unvbox\AP@footins}}
{\movefootins@ofn}{}{}    

\patchcmd\AP@@
{\AP@noindent}
{\AP@noindent\global\output{\the\output@ofn}}{}{}

% Disable \marginpar because it would conflict with margin footnotes.

\renewcommand\marginpar[2][]
{%
  \PackageError{marginfootnotes}
  {\string\marginpar\space disabled}\@eha
}%

Then test with:

\documentclass[a4paper,twoside]{article}

\usepackage[columns=3]{marginfootnotes}

\usepackage[expansion=alltext]{microtype}

\usepackage{lipsum}

\newcommand\fnlipsum[1]
{%
  \begingroup
    \let\par\empty
    \edef\@temp{\csname lipsum@\romannumeral#1\endcsname}%
    \@temp\footnote{\@temp}%
  \endgroup
  \par
}

\usepackage[latin]{babel}

\showboxdepth\maxdimen
\showboxbreadth\maxdimen

\begin{document}

\fnlipsum{1}
\fnlipsum{2}
\fnlipsum{3}
\afterpage{\noindent\fbox{\parbox{\dimexpr\textwidth-2\fboxsep-2\fboxrule\relax}{\lipsum[1]}}}
\fnlipsum{4}
\fnlipsum{5}
\fnlipsum{6}
\fnlipsum{7}
\fnlipsum{8}
\fnlipsum{9}
\fnlipsum{10}
\fnlipsum{11}
\fnlipsum{12}
\fnlipsum{13}
\end{document}

Note that this is a bit rough, but without a clear application perspective and some nice "real" examples it is hard to do significant testing.

The columns option sets the number of columns in the footer.

By giving the option fixorder=false, reordering of footnotes on odd pages is turned off. As the first footnotes on a page always go into the margin, on odd pages where the margin is to the left, the numerical order combined with the footer looks strange. So normally (default fixorder=true), all the footnotes are reordered on odd pages such that the first footnotes start in the footer.

Enjoy!

Update

Recent edits:

  1. Added support for oneside.
  2. Tested for conflicts with afterpage and fixed some issues.
  3. Tested for conflicts with \marginpar (which of course doesn't work at all) and disabled \marginpar for the time being. If I ever implement the vertical positioning of margin footnotes, I might add \marginpar back in as a non-numbered note.
17
  • I'd be interested in using this, but the initial goal was mostly that I saw this and thought, "Surely, there must be a way to do that with TeX, right?" :-)
    – raphink
    Apr 6, 2012 at 6:22
  • Great job by the way!
    – raphink
    Apr 6, 2012 at 6:22
  • 1
    @wasteofspace Could you be a bit more specific in which way flowfram is relevant for this? Please post an answer to achieve the results above with flowfram. Apr 6, 2012 at 10:57
  • 2
    Definitely a great answer to the question. I'm happy to give you the bounty, and looking forward to seeing the package on CTAN :-)
    – raphink
    Apr 7, 2012 at 11:43
  • 1
    Reconsidering it I also think, a general warning is the best, because it came in my mind, that there are some other packages, too, which put something into the margins (e. g. sidenotes, a command and an environment in pdfcomment, according to marginfix doc in memoir is a command \sidebar – surely there are more), but you can’t catch all cases.
    – Speravir
    Apr 8, 2012 at 13:31
16

Disclaimer: I don't know anything about output routines. This is just meant to be a very partial first step, and the code is a mess. The idea is to collect the margin notes and the body content in two vertical boxes, check at every paragraph whether we've reached the area of the page (converted to a dimension by dividing it by 100pt, to remain within TeX's max dimension). If so, shipout a horizontal box with two parts: a left column with only notes, (\vsplit to \vsize), and a right column with all the body text (hence we assume that this body text fits, in other words that there are at least 1/3 of notes, no check for that), then two columns of notes side by side.

Also, that's plain TeX. Well, I'm not really using much of plain, even.

\catcode`@=11
\newbox\marg@m@galley % contains all marginpars
\newbox\marg@b@galley % contains all of the body text
\newbox\marg@void@box % empty box.
\newdimen\marg@m@area % marginpar surface area
\newdimen\marg@b@area % body surface area
\newdimen\marg@t@area % m+b surface area
\newdimen\marg@p@area % page surface area
\newdimen\marg@m@prevdepth \global\marg@m@prevdepth=-1000pt
\newdimen\marg@b@prevdepth \global\marg@b@prevdepth=-1000pt
\newdimen\marg@notes@space % space between body and bottom notes.
\marg@notes@space=15pt

\def\marg@leavevmode{\begingroup\everypar={}\unhbox\marg@void@box\endgroup}
\def\marg@strip@pt{\expandafter\marg@rem@pt\the}
\begingroup\catcode`P=12\catcode`T=12
  \lowercase{\endgroup\def\marg@rem@pt#1PT{#1}}

\def\marg@set@to@area#1#2{%
  #1=.01\dp#2\relax
  \advance#1by .01\ht#2\relax
  #1=\marg@strip@pt#1\wd#2\relax}
\def\marg@conditionally@output{%
  \marg@set@to@area\marg@m@area\marg@m@galley
  \marg@set@to@area\marg@b@area\marg@b@galley
  \marg@t@area\marg@m@area
  \advance\marg@t@area by \marg@b@area
  \marg@p@area=.0095\hsize
  \marg@p@area=\marg@strip@pt\marg@p@area\vsize
  \ifdim\marg@t@area>\marg@p@area
    \marg@output
  \fi}

\def\marg@output{%
  \shipout\hbox to \hsize{%
    \vbox to \vsize{\ifdim\ht\marg@m@galley>\vsize
      \vsplit\marg@m@galley to \vsize\else\unvbox\marg@m@galley\fi\vfill}%
    \hfill
    \vbox to \vsize{%
      \hsize\wd\marg@b@galley
      \dimen@\vsize
      \advance\dimen@ by -\ht\marg@b@galley
      \advance\dimen@ by -\dp\marg@b@galley
      \unvbox\marg@b@galley
      \vskip\marg@notes@space
      \hbox to \hsize {\vbox{\vsplit\marg@m@galley to \dimen@}%
        \hfil\vbox{\vsplit\marg@m@galley to \dimen@}}%
      \vfill
    }%
  }%
}

\def\marg@m@start{%
  \setbox\marg@m@galley=\vbox\bgroup
    \hsize=.30\hsize
    \unvbox\marg@m@galley
    \prevdepth=\marg@m@prevdepth
    \let\@par\par
    \def\par{\marg@m@stop\par\marg@m@start}}
\def\marg@m@stop{%
    \@par
    \global\marg@m@prevdepth=\prevdepth
  \egroup
  \marg@conditionally@output}

\def\marg@b@start{%
  \setbox\marg@b@galley=\vbox\bgroup
    \hsize=.65\hsize
    \unvbox\marg@b@galley
    \prevdepth=\marg@b@prevdepth
    \let\@par\par
    \def\par{\marg@b@stop\par\marg@b@start}}
\def\marg@b@stop{%
    \@par
    \global\marg@b@prevdepth=\prevdepth
  \egroup
  \marg@conditionally@output}

\long\def\marginpar#1{%
  \marg@b@stop
  \marg@m@start
  #1%
  \marg@m@stop
  \marg@b@start
}

\def\lipsum@i{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.\par}

\emergencystretch=.15\hsize

\marg@b@start
  \lipsum@i
  \lipsum@i
  \marginpar{\lipsum@i\lipsum@i}
  \lipsum@i
  \marginpar{\lipsum@i\lipsum@i}
  \lipsum@i
\marg@b@stop
\marg@output

\bye
3
  • Very interesting! What is the point of changing the catcode for P and T?
    – raphink
    Oct 5, 2011 at 9:39
  • 2
    @Raphink See the definition of \strip@pt in the LaTeX kernel. The result of \the\len (where \len is a dimension register, is a decimal number followed by the two characters pt of category 12. In order to strip them it's customary to define a macro with a delimited argument (the delimiter is exactly pt of category 12).
    – egreg
    Oct 5, 2011 at 11:55
  • Thanks @egreg. In my case it's used to get a decimal number for TeX to do the multiplication of two dimensions. Where it becomes a mess is that I'm starting with .01 times the first dimension to avoid overflowing TeX's dimensions when doing the product. So very rough computations, but that seems to work in very trivial cases. Of course, the correct way would be to think a little bit and only compare the relevant heights. Oct 5, 2011 at 12:15
8

This totally reminds me of the "Typesetting the talmud" challenges presented at the last Dante meeting in Leipzig. In the Talmud the original text version is in the center, around are (onion-like) the texts of scholars commenting on the text.

Talmud example

3
  • 1
    This is not an answer to the OP's question -- but a comment can't include pictures (nice example BTW). Perhaps consider to make your "answer" community wiki. Another option would be to include the material at the end of the original question (with a note "EDIT by Uwe Ziegenhagen").
    – lockstep
    Apr 7, 2012 at 10:28
  • 1
    Uwe, @lockstep: You still can have a link to the image in a comment. Apr 7, 2012 at 12:48
  • 9
    Perhaps it could be made to a real answer by adding some value, like a link to the Talmud project, where one could get further information on this topic.
    – Stefan Kottwitz
    Apr 9, 2012 at 9:22

You must log in to answer this question.

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