27
  • I'm looking to define something like \topmarginpar{<text>}, which would essentially create a \marginpar{<text>} and align its top with the top of the current page.

  • I would also like multiple \topmarginpar calls to stack these \marginpar objects (as opposed to having them overlap).

  • And finally I'd like to create a similar \bottommargin command.

Is there some existing package providing this functionality, or am I on my own here (I'm LaTeX fluent but worthless around raw TeX)?

Thanks for any suggestions...

3
  • Related: How to repeat automatically last marginnote on a new page
    – Werner
    Aug 30, 2012 at 22:18
  • 2
    spec questions: a) I assume that the text from \topmarginpar is supposed to show on the page that also contains the position the command was found. b) what should happen if the stacked \topmarginpars are bigger than the page, start a new page, or shift the marginpar to the next? c) I presume you do not want real marginpars at the same time, or do you? Aug 31, 2012 at 7:37
  • 9
    The memoir class has has \sidebar which looks like your \topmarginpar plus it has \sidefootnote which looks like a numbered version of your \bottommargin` macro
    – daleif
    Aug 31, 2012 at 8:19

5 Answers 5

19
+100

This solution deals with the asynchronous output routine:

  • The pages are identified by absolute page numbers and the correct page numbers are available in the second LaTeX run using labels.

  • Each page get two collector boxes that collect the marginal notes for the top and the bottom. At shipout time the absolute page number is known and the collector boxes for the page are output in the "marginpar" area of the page.

  • Box registers are allocated dynamically and managed in a pool. After a page shipout, its collector box registers are freed and put back in the pool.

  • \topskip and \maxdepth are respected for the "marginpar" area.

  • Too many marginal notes on a page are reported as overfull \vbox warnings.

  • LaTeX's \marginpar is not supported, the marginal notes of different kinds do not know each other and will happily overprint each other.

The example document:

\documentclass{article}

\usepackage{atbegshi}
\usepackage{zref-abspage}
\usepackage{picture}

\makeatletter
\providecommand*{\c@zabspage}{\c@abspage}

% * User macros for configuring
%
% \tbmparItemSep is inserted between marginal notes
% \tbmparMiddleSep is inserted between top and bottom marginal notes.

\newcommand*{\tbmparItemSep}{%
  \vspace{1ex minus .5ex}%
  \hrule
  \vspace{1ex minus .5ex}%
}
\newcommand*{\tbmparMiddleSep}{%
  \vspace*{0pt plus 1fil}%
}

% * Debug messages
%
\newcommand*{\tbmparDebug}[1]{%
  \typeout{[tbmpar] #1}%
}

% * Label management to remember absolute page number
%
% \tbmpar@PageByLabel stores and loads absolute page number from
% label and defines \tbmpar@page with absolute page number or
% zero if the label is not yet available.

\newcount\c@tbmpar@item
\c@tbmpar@item\z@

\newcommand*{\tbmpar@PageByLabel}{%
  \global\advance\c@tbmpar@item\@ne
  \zref@labelbyprops{tbmpar\the\c@tbmpar@item}{abspage}%
  \edef\tbmpar@page{%
    \zref@extractdefault{tbmpar\the\c@tbmpar@item}{abspage}{0}%
  }%
  \zref@refused{tbmpar\the\c@tbmpar@item}%
  \tbmparDebug{Item \the\c@tbmpar@item\space on page \tbmpar@page}%
}

% * Box register management

\newcount\c@tbmpar@box
\c@tbmpar@box\z@

\let\tbmpar@boxfreelist\@empty

% Get a new free box register either from the free list or,
% if the free list is empty, allocate a new box register.
\newcommand*{\tbmpar@NextBox}[1]{%
  \@next#1\tbmpar@boxfreelist{%  
    \tbmparDebug{Reused box: #1}%
  }{%
    \global\advance\c@tbmpar@box\@ne
    \expandafter\newbox\csname tbmpar@box\the\c@tbmpar@box\endcsname
    \edef#1{\csname tbmpar@box\the\c@tbmpar@box\endcsname}%
    \tbmparDebug{New box: #1}%
  }%
}
% Put free box in free list.
\newcommand*{\tbmpar@FreeBox}[1]{%
  \begingroup
    \let\@elt\relax
    \xdef\tbmpar@boxfreelist{%
      \tbmpar@boxfreelist
      \@elt#1%
    }%
    \tbmparDebug{Free box: #1}%
  \endgroup
}

\newsavebox{\tbmpar@box}

% Each marginpar is put in a box that is initialized as
% parbox/minipage.
\newcommand*{\tbmparBoxSetup}{}
\newcommand{\tbmpar@VBox}[1]{% 
  \vbox{%
    \color@begingroup
    \hsize\marginparwidth
    \edef\tbmpar@restore@ifminipage{%
      \if@minipage
        \noexpand\@minipagetrue
      \else
        \noexpand\@minipagefalse
      \fi
    }%   
    \@parboxrestore
    \@marginparreset
    \tbmparBoxSetup 
    #1%
    \tbmpar@restore@ifminipage
    \color@endgroup
  }%
}   

% Macro \tbmpar@marginpar looks for the page, where the margin note
% belongs to, stores the note in a box and appends the box to the  
% note collector register of the page.
% Each page is assigned a box collector registers that collect
% the top notes and a register that collect the bottom notes. 
% The name of the box register is \tbmpar@<top|bot>box<page>. 
\newcommand{\tbmpar@marginpar}[4]{%
  \ifhmode
    \@bsphack
  \fi
  \tbmpar@PageByLabel
  \ifnum\tbmpar@page>\z@
    \setbox\tbmpar@box\tbmpar@VBox{#4}%
    \@ifundefined{tbmpar@#1box\tbmpar@page}{%
      \tbmpar@NextBox\tbmpar@currbox
      \global\expandafter\let
          \csname tbmpar@#1box\tbmpar@page\endcsname
          \tbmpar@currbox
      \global\setbox\tbmpar@currbox=\vbox{%
        \unvbox\tbmpar@box
      }%
    }{% 
      \tbmparDebug{Use box: \tbmpar@currbox}%
      \expandafter\let\expandafter\tbmpar@currbox
          \csname tbmpar@#1box\tbmpar@page\endcsname
      \global\setbox\tbmpar@currbox\tbmpar@VBox{%   
        \unvbox#2%
        \par
        \begingroup
          \tbmparItemSep
        \endgroup
        \unvbox#3%
      }%
    }%  
  \fi   
  \ifhmode
    \@esphack
  \fi
}
\newcommand*{\topmarginpar}{%
  \tbmpar@marginpar{top}\tbmpar@currbox\tbmpar@box
}
\newcommand*{\botmarginpar}{%
  \tbmpar@marginpar{bot}\tbmpar@box\tbmpar@currbox
}

% At shipout time we look for the box collector registers of this
% page and set these boxes in the marginpar box with respecting  
% \topskip and \maxdepth.
\AtBeginShipout{%
  \AtBeginShipoutUpperLeft{%
    \put(%
      \dimexpr 1in+\oddsidemargin+\textwidth+\marginparsep\relax,%
      -\dimexpr 1in+\topmargin+\headheight+\headsep+\textheight\relax
    ){%
      \begingroup
        \global\let\tbmpar@inuse=N%
        \setbox\tbmpar@box=\tbmpar@VBox{%
          \penalty-\@M
          \edef\tbmpar@tmp{tbmpar@topbox\the\value{zabspage}}%
          \@ifundefined{\tbmpar@tmp}{%
          }{%
            \expandafter\let\expandafter\tbmpar@currbox
                \csname\tbmpar@tmp\endcsname
            \unvbox\tbmpar@currbox
            \tbmpar@FreeBox\tbmpar@currbox
            \global\let\tbmpar@inuse=Y%   
          }%
          \endgraf
          \tbmparMiddleSep
          \edef\tbmpar@tmp{tbmpar@botbox\the\value{zabspage}}%
          \@ifundefined{\tbmpar@tmp}{%
          }{%
            \expandafter\let\expandafter\tbmpar@currbox
                \csname\tbmpar@tmp\endcsname
            \unvbox\tbmpar@currbox
            \tbmpar@FreeBox\tbmpar@currbox
            \global\let\tbmpar@inuse=Y%   
          }%
        }%  
        \ifx\tbmpar@inuse Y%
          \splittopskip=\topskip
          \setbox0=\vsplit\tbmpar@box to\z@
          \boxmaxdepth=\maxdepth
          \setbox\tbmpar@box=\vbox to\textheight{%
            \unvbox\tbmpar@box
          }%
          \box\tbmpar@box
        \fi
      \endgroup
    }%
  }%  
}     

\makeatother

% Testing

\usepackage[
  a5paper,   
  left=10mm, 
  right=10mm,
  marginparwidth=40mm,
  includemp,
]{geometry}
\usepackage{microtype}  
\usepackage[T1]{fontenc}
\usepackage{lmodern}

\clubpenalty=10000
\flushbottom
\settodepth\maxdepth{g}  
\setlength{\fboxsep}{1ex}
\usepackage{lipsum}
\usepackage{color}

\newcommand*{\shortlipsum}[1]{%
  \begingroup
    \long\def\y##1. ##2\@nil{##1.}%
    \edef\x{\csname lipsum@\romannumeral#1\endcsname}%
    \expandafter\y\x. \@nil
  \endgroup
}

\begin{document}
  \topmarginpar{\color{blue}\shortlipsum{1}}
  \lipsum[1]
  \botmarginpar{%
    Show effect of \texttt{\textbackslash maxdepth}:
    $\displaystyle\sum_{\textstyle i=\frac{a}{g}}^\infty i = x$}
  \lipsum[2]
  \topmarginpar{\shortlipsum{3}}%
  \botmarginpar{Second bottom marginal note}%
  \lipsum[3-4]
  \noindent a\botmarginpar{a} b\botmarginpar{b} c \botmarginpar{c} d\\
  e\\f\par
  \topmarginpar{\fbox{\shortlipsum{5}}}
  \lipsum[5]
  Text with footnote and marginal note\footnote{Marginal note X}.
  \topmarginpar{This is marginal note X}
  \lipsum[6]
  \botmarginpar{\shortlipsum{7}}
  \lipsum[7]  
\end{document}

Result

6
  • 1
    running your example with pdflatex and TL2012 I get: ! You can't use `\relax' after \the. <recently read> \c@zabspage l.237 \lipsum[3-4]
    – user2478
    Sep 18, 2012 at 12:21
  • 1
    @Herbert Probably the two occurrences of zabspage should be abspage.
    – egreg
    Sep 18, 2012 at 12:34
  • 1
    @Herbert Sorry, I had renamed counter abspage to zabspage in the not yet released version of zref to avoid name clash with package perpage. Add `\providecommand*{\c@zabspage}{\c@abspage}. Sep 18, 2012 at 12:52
  • 6
    I feel that this is worth it to become another package...
    – Daniel
    Sep 18, 2012 at 13:30
  • I'm way out of the loop. What is \z@? Aug 4, 2015 at 23:43
7

Here's an attempt using Martin Scharrer's package tikzpagenodes. As it uses the remember picture, overlay options of TikZ, you'll need to compile twice to get it right. It provides the two commands \marpartop and \marparbot which both take two arguments: the content and the text color, this could easily be expanded to be more customizable.

Code

\documentclass{scrartcl}
\usepackage[left=15mm,top=40mm,bottom=40mm,right=50mm,a4paper]{geometry}
\usepackage{tikzpagenodes}
\usepackage{xifthen}

\usepackage{lipsum}

\setlength{\marginparwidth}{40mm}
\pagestyle{empty}

\def\myyshifttop{0}
\def\mypagetop{0}

\newcommand{\marpartop}[2]% content, color
{   \begin{tikzpicture}[remember picture, overlay]
        \ifthenelse{\thepage=\mypagetop}{}{\xdef\myyshifttop{0}}        
        \xdef\mypagetop{\thepage}
        \node[below right, yshift=\myyshifttop, text width=\marginparwidth-4pt, inner sep=2pt, #2] (tempnode) at (current page marginpar area.north west) {#1};
        \path (current page marginpar area.north west);
        \pgfgetlastxy{\tempxone}{\tempyone}
        \path (tempnode.south west);
        \pgfgetlastxy{\tempxtwo}{\tempytwo}
        \pgfmathsetmacro{\diffy}{(\tempytwo-\tempyone)}
        \xdef\myyshifttop{\diffy}
    \end{tikzpicture}
}

\def\myyshiftbot{0}
\def\mypagebot{0}

\newcommand{\marparbot}[2]% content, color
{   \begin{tikzpicture}[remember picture, overlay]
        \ifthenelse{\thepage=\mypagebot}{}{\xdef\myyshiftbot{0}}        
        \xdef\mypagebot{\thepage}
        \node[above right, yshift=\myyshiftbot, text width=\marginparwidth-4pt, inner sep=2pt, #2] (tempnode) at (current page marginpar area.south west) {#1};
        \path (current page marginpar area.south west);
        \pgfgetlastxy{\tempxone}{\tempyone}
        \path (tempnode.north west);
        \pgfgetlastxy{\tempxtwo}{\tempytwo}
        \pgfmathsetmacro{\diffy}{(\tempytwo-\tempyone)}
        \xdef\myyshiftbot{\diffy}
    \end{tikzpicture}
}

\begin{document}
\marpartop{On a journey to find the cure for a Tatarigami's curse, Ashitaka finds himself in the middle of a war between the forest gods and Tatara, a mining colony.}{red}
\marparbot{What begins as an open and shut case of murder soon becomes a mini-drama of each of the jurors' prejudices and preconceptions about the trial, the accused, and each other.}{orange!50!gray}
\lipsum[1-3]
\marparbot{The defense and the prosecution have rested and the jury is filing into the jury room to decide if a young Spanish-American is guilty or innocent of murdering his father.}{green!50!gray}
\marpartop{In this quest he also meets San, the Mononoke Hime.}{blue}
\lipsum[4-7]
\marpartop{On a journey to find the cure for a Tatarigami's curse, Ashitaka finds himself in the middle of a war between the forest gods and Tatara, a mining colony.}{red}
\marparbot{What begins as an open and shut case of murder soon becomes a mini-drama of each of the jurors' prejudices and preconceptions about the trial, the accused, and each other.}{orange!50!gray}
\lipsum[8]
\marpartop{In this quest he also meets San, the Mononoke Hime.}{blue}
\marparbot{The defense and the prosecution have rested and the jury is filing into the jury room to decide if a young Spanish-American is guilty or innocent of murdering his father.}{green!50!gray}
\lipsum[9-12]
\end{document}

Result

enter image description here

6
  • +1 I discard my solution... Too late and too many similarities. Sep 16, 2012 at 0:31
  • 2
    Have you found the bug of tikzpagenodes with the twoside option? Sep 16, 2012 at 0:34
  • Martin Scharer is very reactive: a new version of tikzpagenodes with bug fixed is coming to CTAN. Sep 16, 2012 at 16:18
  • 1
    Because of the asynchronous output routine you need a label/ref system to get the correct page number. \thepage might be different from an arabic number. Sep 18, 2012 at 7:25
  • Again because of the asynchronous output routine \marpartop or \marparbot can process a margin par that will not be set on the current page, but to a following page. Sep 18, 2012 at 7:30
7

(Note: it's a community answer because original idea is from Tom Bombadil.)

Here is a variant of Tom Bombadil's answer with a few improvements:

  • The two macros are named \topmarginpar and \botmarginpar as requested.

  • Their first argument (optional and empty by default) is some options for the TikZ node (to allow filling, drawing a border, etc.). Their second argument is the content of the paragraph.

  • There are some calculations to correct a bug of tikzpagenodes with even page (edit: Martin Scharer is very reactive: a version with bug fixed is coming to CTAN.).

  • The code uses let operations (and calc TikZ library). I think it is more readable than PGF calls.

Limits:

  • \botmarginpar should stack paragraphs in reverse order!
  • If a stack is full, it does not continue to the next page. It overflows up or down the page.
  • If \topmarginpar (or \bormarginpar) is called in the last paragraph of a page, the margin paragraph can appear on the next page and not in the right order.

Here are two pages (odd then even):

first page (odd) second page (even)

The code (with my comments):

\documentclass[twoside]{report}
\usepackage{tikzpagenodes}
\usepackage{xifthen}
\usetikzlibrary{calc}

\def\myyshifttop{0}
\def\mypagetop{0}
\newcommand{\topmarginpar}[2][]{% tikz options of node, content
  \begin{tikzpicture}[remember picture, overlay]
    % reset position on new page
    \ifthenelse{\thepage=\mypagetop}{}{\xdef\myyshifttop{0}\xdef\mypagetop{\thepage}}
    % a big path with many actions
    \path let
    % patch for bug in tikzpagenodes with even pages
    \p1=(current page marginpar area.north west),
    \p2=(current page marginpar area.north east)
    in \pgfextra{
      \pgfmathsetmacro{\xw}{\x1<\x2?\x1:\x2}
      \pgfmathsetmacro{\yw}{\y1<\y2?\y1:\y2}
      \edef\coord{\xw pt,\yw pt}
    }
    % draw topmarginpar
    node[below right, yshift=\myyshifttop, text width=\marginparwidth-4pt, inner sep=2pt, #1]
    (tempnode) at (\coord) {#2}
    % next position
    let \p1=(\coord), \p2=(tempnode.south west) in \pgfextra{
      \pgfmathsetmacro{\diffy}{(\y2-\y1)}
      \xdef\myyshifttop{\diffy}
    };
  \end{tikzpicture}%
}

\def\myyshiftbot{0}
\def\mypagebot{0}
\newcommand{\botmarginpar}[2][]{% tikz options of node, content
  \begin{tikzpicture}[remember picture, overlay]
    % reset position on new page
    \ifthenelse{\thepage=\mypagebot}{}{\xdef\myyshiftbot{0}\xdef\mypagebot{\thepage}}
    % a big path with many actions
    \path let
    % patch for bug in tikzpagenodes with even pages
    \p1=(current page marginpar area.south west),
    \p2=(current page marginpar area.south east)
    in \pgfextra{
      \pgfmathsetmacro{\xw}{\x1<\x2?\x1:\x2}
      \pgfmathsetmacro{\yw}{\y1<\y2?\y1:\y2}
      \edef\coord{\xw pt,\yw pt}
    }
    % draw botmarginpar
    node[above right, yshift=\myyshiftbot, text width=\marginparwidth-4pt, inner sep=2pt, #1]
    (tempnode) at (\coord) {#2}
    % next position
    let \p1=(\coord), \p2=(tempnode.north west) in \pgfextra{
      \pgfmathsetmacro{\diffy}{(\y2-\y1)}
      \xdef\myyshiftbot{\diffy}
    };
  \end{tikzpicture}%
}

\usepackage{lipsum}

\begin{document}
\topmarginpar[red,fill=yellow!30]{On a journey to find the cure for a
  Tatarigami's curse, Ashitaka finds himself in the middle of a war
  between the forest gods and Tatara, a mining colony.}%
\botmarginpar{What begins as an open and shut case of murder soon
  becomes a mini-drama of each of the jurors' prejudices and
  preconceptions about the trial, the accused, and each other.}%
\lipsum[1-3]%
\botmarginpar[font=\itshape\footnotesize,text=green!50!black]{The
  defense and the prosecution have rested and the jury is filing into
  the jury room to decide if a young Spanish-American is guilty or
  innocent of murdering his father.}%
\topmarginpar[blue]{In this quest he also meets San, the Mononoke
  Hime.}%
\lipsum[4-7]%
\topmarginpar[red]{On a journey to find the cure for a Tatarigami's
  curse, Ashitaka finds himself in the middle of a war between the
  forest gods and Tatara, a mining colony.}%
\botmarginpar[orange!50!gray]{What begins as an open and shut case of
  murder soon becomes a mini-drama of each of the jurors' prejudices and
  preconceptions about the trial, the accused, and each other.}%
\lipsum[8]%
\topmarginpar[blue]{In this quest he also meets San, the Mononoke
  Hime.}%
\botmarginpar[green!50!gray]{The defense and the prosecution have rested
  and the jury is filing into the jury room to decide if a young
  Spanish-American is guilty or innocent of murdering his father.}%
\lipsum[9-12]
\end{document}
5
  • Thanks for the code which works fine, except for one detail: the bottom marginal notes do not align perfectly with the bottom of the main page. This is quite annoying, is there some way to fix it?
    – yannis
    Feb 14, 2015 at 20:07
  • I found a solution for that problem. But now there is another one: The notes are placed in inverse order: the first note of the page is placed on the bottom and the second above it, and so on. How can I get them in the same order as in the text?
    – yannis
    Feb 14, 2015 at 22:55
  • how can shift above the \botmarginpar text some (e.g .5 cm) ?
    – iman
    Jan 16, 2016 at 12:01
  • How would you go about making the font of the margin paragraph smaller ?
    – RichWalt
    Sep 16, 2021 at 2:28
  • @RichWalt In my code, the second call to \bottomarginpar use the option font=\itshape\footnotesize to make the font smaller (and with italic). Sep 17, 2021 at 4:59
2

Thanks for all the feedback so far. Here is the code I managed to piece together. It provides commands \topmarginnote and \footmarginnote which create "floating" margin notes which will be placed according to the following (arbitrary) rules:

  • topmarginnotes are printed at the top of the margin, in the order they were defined (oldest on top);
  • footmarginnotes are printed at the bottom of the margin, in the order they were defined (oldest on top);
  • when there isn't enough vertical space, footmarginnotes take precedence over topmarginnotes (because I plan to use the former for citation references, and the latter for small figures);
  • Unprinted notes carry over to the next page.

It's still far from perfect:

  • I'm not using box registers because I was too lazy to learn how to create and use them.
  • silent failure when a single note is taller than the margin;
  • notes still unprinted after the last text page is shipped out are lost.

Criticism welcome!

The code:

\documentclass{article}

\usepackage[%
    a4paper,
    left=2cm,
    textheight=24cm,
    textwidth=10cm,
    marginparsep=2cm,
    marginparwidth=5cm
    ]{geometry}

\usepackage{forarray,etoolbox,xifthen,,graphicx,picture,calc,atbegshi,lipsum,color}

\newlength{\vincrement}

\newlength{\fmvorigin}
\newlength{\fmhorigin}
\newlength{\fmvoffset}
\newlength{\nextfmvoffset}
\newlength{\fmvskip}

\setlength{\fmvorigin}{-1in-\voffset-\topmargin-\headheight-\headsep-\textheight}
\makeatletter
    \ifthenelse{\isundefined{\Gm@lmargin}}%
    {\setlength{\fmhorigin}{1in+\hoffset+\oddsidemargin+\textwidth+\marginparsep}}%
    {\setlength{\fmhorigin}{\Gm@lmargin+\textwidth+\marginparsep}}
\makeatother
\setlength{\fmvoffset}{0pt}
\setlength{\fmvskip}{3ex}

\newlength{\tmvorigin}
\newlength{\tmhorigin}
\newlength{\tmvoffset}
\newlength{\nexttmvoffset}
\newlength{\tmvskip}
\newlength{\maxtmnheight}

\setlength{\tmvorigin}{-1in-\voffset-\topmargin-\headheight-\headsep-\baselineskip}
\setlength{\tmhorigin}{\fmhorigin}
\setlength{\tmvoffset}{0pt}
\setlength{\tmvskip}{6ex}

\newcounter{fmn}
\newcounter{tmn}

\newboolean{marginisfull}

\newcommand{\fmnbuffer}{}
\newcommand{\currentfmnqueue}{}
\newcommand{\nextfmnqueue}{}

\newcommand{\tmnbuffer}{}
\newcommand{\currenttmnqueue}{}
\newcommand{\nexttmnqueue}{}

\newcommand{\append}[2]{%
    \ifdefempty{#1}{}{\eappto{#1}{,}}%
    \eappto{#1}{#2}%
    }

\newcommand{\prepend}[2]{%
    \ifdefempty{#1}{}{\epreto{#1}{,}}%
    \epreto{#1}{#2}%
    }

\newcommand{\footmarginnote}[1]{%
    \addtocounter{fmn}{1}%
    \expandafter\newsavebox\csname fmnbox\roman{fmn}\endcsname%
    \expandafter\savebox\csname fmnbox\roman{fmn}\endcsname{\parbox[b]{\marginparwidth}{#1}}%
    \append{\currentfmnqueue}{\roman{fmn}}%
    }

\newcommand{\topmarginnote}[1]{%
    \addtocounter{tmn}{1}%
    \expandafter\newsavebox\csname tmnbox\roman{tmn}\endcsname%
    \expandafter\savebox\csname tmnbox\roman{tmn}\endcsname{\parbox[t]{\marginparwidth}{\vskip-0.75\baselineskip#1}}%
    \append{\currenttmnqueue}{\roman{tmn}}%
    }

\newcommand{\printfmn}{%
    \setlength{\fmvoffset}{0pt}%
    \setboolean{marginisfull}{false}%
    \ifdefempty{\currentfmnqueue}{}{%
        \gdef\nextfmnqueue{}%
        \gdef\fmnbuffer{}%
        \ForEachX{,}{%
            \ifthenelse{\boolean{marginisfull}}%
                {\append{\nextfmnqueue}{\thislevelitem}}%
                {%
                \setlength{\vincrement}%
                    {\fmvskip+\expandafter\ht\csname fmnbox\thislevelitem\endcsname+\dp\csname fmnbox\thislevelitem\endcsname}%
                \setlength{\nextfmvoffset}{\fmvoffset+\vincrement}%
                \ifthenelse{\lengthtest{\nextfmvoffset>\textheight}}{%
                    \setboolean{marginisfull}{true}%
                    \append{\nextfmnqueue}{\thislevelitem}%
                    }{%
                    \prepend{\fmnbuffer}{\thislevelitem}%
                    \addtolength{\fmvoffset}{\vincrement}%
                    }%
                }%
            }%
            {\currentfmnqueue}%
        \setlength{\fmvoffset}{0pt}%
        \ForEachX{,}{%
            \put(\fmhorigin,\fmvorigin+\fmvoffset){\expandafter\usebox\csname fmnbox\thislevelitem\endcsname}%
            \setlength{\vincrement}%
                {\fmvskip+\expandafter\ht\csname fmnbox\thislevelitem\endcsname+\dp\csname fmnbox\thislevelitem\endcsname}%
            \addtolength{\fmvoffset}{\vincrement}%
            }%
            {\fmnbuffer}%
        \global\let\currentfmnqueue\nextfmnqueue%
        }%
    }

\newcommand{\printtmn}{%
    \setlength{\maxtmnheight}{\textheight-\fmvoffset}%
    \setlength{\tmvoffset}{0pt}%
    \setboolean{marginisfull}{false}%
    \ifdefempty{\currenttmnqueue}{}{%
        \gdef\nexttmnqueue{}%
        \gdef\tmnbuffer{}%
        \ForEachX{,}{%
            \ifthenelse{\boolean{marginisfull}}%
                {\append{\nexttmnqueue}{\thislevelitem}}%
                {%
                \setlength{\vincrement}%
                    {\tmvskip+\expandafter\ht\csname tmnbox\thislevelitem\endcsname+\dp\csname tmnbox\thislevelitem\endcsname}%
                \setlength{\nexttmvoffset}{\tmvoffset+\vincrement}%
                \ifthenelse{\lengthtest{\nexttmvoffset>\maxtmnheight}}{%
                    \setboolean{marginisfull}{true}%
                    \append{\nexttmnqueue}{\thislevelitem}%
                    }{%
                    \append{\tmnbuffer}{\thislevelitem}%
                    \addtolength{\tmvoffset}{\vincrement}%
                    }%
                }%
            }%
            {\currenttmnqueue}%
        \setlength{\tmvoffset}{0pt}%
        \ifdefempty{\tmnbuffer}{}{%
            \ForEachX{,}{%
                \put(\tmhorigin,\tmvorigin-\tmvoffset){\expandafter\usebox\csname tmnbox\thislevelitem\endcsname}%
                \setlength{\vincrement}%
                    {\tmvskip+\expandafter\ht\csname tmnbox\thislevelitem\endcsname+\dp\csname tmnbox\thislevelitem\endcsname}%
                \addtolength{\tmvoffset}{\vincrement}%
                }%
                {\tmnbuffer}%
            }%
        \global\let\currenttmnqueue\nexttmnqueue%
        }%
    }

\AtBeginShipout{\AtBeginShipoutUpperLeft{\printfmn\printtmn}}

\begin{document}

{\color{red}(t1)}\topmarginnote{\footnotesize\color{red}[t1] \lipsum[1]}
{\color{green}(t2)}\topmarginnote{\footnotesize\color{green}[t2] \lipsum[2]}
{\color{blue}(t3)}\topmarginnote{\footnotesize\color{blue}[t3] \lipsum[3]}
{\color{green}(f1)}\footmarginnote{\footnotesize\color{green}[f1] \lipsum[4]}
{\color{blue}(f2)}\footmarginnote{\footnotesize\color{blue}[f2] \lipsum[5]}
\lipsum[1-10]
{\color{red}(f3)}\footmarginnote{\footnotesize\color{red}[f3] \lipsum[6]}

\end{document}

The output:

mwe

0

I made a simple hack into marginfit package and got a simple solution.

\documentclass[11pt,a4paper]{article}

%% Needs marginfit package to be loaded before:
\RequirePackage{marginfit}

\makeatletter
%% Top marginpar:
\def\marginfit@writepost#1{%
    \write\@auxout{\string\@newl@bel{label@marginfit}{#1}{47040224}}%  %% Note it from aux file entry with @t
}
\def\marginpart{%
    \global\advance\c@marginfit@w\@ne%
    \expandafter\marginfit@writepost\expandafter{\the\c@marginfit@w @m}%
    \@ifnextchar[\marginfit@mpar@ii\marginfit@mpar@i%
}
%% Bottom marginpar:
\def\marginfit@writeposb#1{%
    \write\@auxout{\string\@newl@bel{label@marginfit}{#1}{0}}%  %% Minimum is 0
}
\def\marginparb{%
    \global\advance\c@marginfit@w\@ne%
    \expandafter\marginfit@writeposb\expandafter{\the\c@marginfit@w @m}%
    \@ifnextchar[\marginfit@mpar@ii\marginfit@mpar@i%
}
\makeatother


\begin{document}

\marginpar{OM O MO M M M M M M}

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC


\marginpar{OM O MO M M M M M M\par\medskip\hrule}


ABC


\marginpart{To be Top OM O MO M M M M M M}


\newpage  %--------------------------------------------------------------------


ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC

ABC


\marginpart{Top OM O MO M M M M M M}

\marginparb{Bottom OM O MO M M M M M M}


ABC

ABC

ABC

ABC

\end{document}

It provides two new commands \marginpart for top marginpar and \marginparb for bottom marginpar. The number 47040224 in it is \pdflastypos for top most marginpar. This number can be still higher, marginfit will set its position in the final placement so that it doesn't cross the top edge. Try higher values depending on your page size. Best method to figure out is to first create a single marginpar at the beginning of the document so that it is at the top most and look into its entry in aux file.

This is how it works: marginfit package saves y position of all marginpars with an assigned label. I just borrowed the code which writes the ypos of the current marginpar into aux file and made it to set ypos to maximum (and minimum 0 for bottom marginpar).

You must log in to answer this question.

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