[Tex/LaTex] Margin notes with a pointing arrow that are automatically vertically adjusted

marginnotemarginpar

I am trying to emulate margin notes as they are in my (favourite) macroeconomics textbook.

enter image description here

The crucial thing here is that the little arrow points to the line where the margin note is set. I have come up with the following dirty code.

Edit: I have updated the code below to provide some commands that either centre the note, push it upwards or allow manual adjustment (thanks to egreg, Andrew Swann and Martin Scharrer who helped me with problems related to this).

The normal \marginpar command is used to set the note and \marginnote is used to set the text.

marginfix takes good care of notes at the bottom of page (see the last note in the picture). However, I would like to create some automatic process that moves the note:

  • if the note is at the end of a paragraph/section/chapter move it up (see second note).
  • Find an equivalent to marginfix that moves the note to the top margin of the page if there is enough space.

This does not cover all usecases, so what is needed is some clever automatic mechanism that adjusts the position of the notes but keeps the arrow fixed. Can something like that be done? Are there any "ifatendparagraph" or "ifatendsection" hooks that I could use?

\documentclass[12pt]{scrartcl}

\usepackage{lmodern}
\usepackage{geometry}
\usepackage{etoolbox}

\usepackage{marginfix}
\setlength{\parskip}{0pt}% fix as marginfix inserts a 1pt ghost parskip

% Create length for the baselineskip of text in footnotesize
\newdimen\footnotesizebaselineskip
\newcommand{\test}[1]{%
 \setbox0=\vbox{\footnotesize\strut Test \strut}
 \global\footnotesizebaselineskip=\ht0 \global\advance\footnotesizebaselineskip by \dp0
}

% Use the measure to offset marginpars by one baseline using marginfix command
\AtBeginDocument{%
 \test{}%
 \marginposadjustment=1\footnotesizebaselineskip
}

\geometry{left=2.5cm,textwidth=130mm}

\setlength{\marginparwidth}{4cm}
\setlength{\marginparsep}{2em}

\usepackage[noadjust]{marginnote}

\newdimen\myheight
\newcommand{\boxheight}[1]{%
 \setbox0=\vbox{\strut#1\strut}
 \global\myheight=\ht0 \global\advance\myheight by \dp0
}

\newrobustcmd{\mynote}[2][]{% first the settings that are equal for all
 \linespread{1.0}% reset line spacing if using setspace
 \parbox[t]{\marginparwidth}{\footnotesize\itshape\raggedright\boxheight{#2}}% set text in parbox to measure the height of the marginnote
 \setbox0=\vtop{\footnotesize\llap{$\leftarrow$\,\,}}\marginnote{\footnotesize{\llap{$\leftarrow$\,\,}}}[2.7pt]% set the arrow
 \ifstrequal{#1}{up}{% option UP
   \setbox0=\vtop{#2}\marginpar{\footnotesize\itshape\raggedright\vspace{-\ht0}\vspace{-1\dimexpr1\myheight-1\baselineskip}#2}%
   }{% option MIDDLE
   \ifstrequal{#1}{middle}{%
    \setbox0=\vtop{#2}\marginpar{\footnotesize\itshape\raggedright\vspace{-\ht0}\vspace{-.5\dimexpr1\myheight-1\baselineskip}#2}%
    }{% nothing specified
    \ifstrempty{#1}{%
     \setbox0=\vtop{#2}\marginpar{\footnotesize\itshape\raggedright\vspace{-\ht0}#2}%
     }{% something specified
     \setbox0=\vtop{#2}\marginpar{\footnotesize\itshape\raggedright\vspace{-\ht0}\vspace{#1}#2}%
   }{\relax}%
  }{\relax}%
 }%
}%

\begin{document}
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, vulpu-
tate 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 vestibu- lum urna
fringilla ultrices.\mynote[middle]{\textbf{This note is centered because
there is enough space below and it is in the middle of a paragraph.} Erat
ligula aliquet magna, vitae ornare odio metus a mi. Morbi ac orci et nisl
hendrerit mollis.} Phasellus eu tellus sit amet tortor gravida placerat.
Integer sapien est, iaculis in, pretium quis, viverra ac, nunc. Nam arcu
libero, nonummy eget, consectetuer id, vulpu- tate a, magna. Donec
vehicula augue eu neque. Pellentesque habitant morbi tristique senectus et
netus et malesuada fames ac turpis egestas.

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. Pellen- tesque
a nulla. Cum sociis natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Aliquam tincidunt urna. Nulla ullam- corper
vestibulum turpis. Pellentesque cursus luctus
mauris.\mynote[up]{\textbf{This note is pushed upwards because it is at
the end of a pragraph}}

Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at,
tincidunt tristique, libero. Vivamus viverra fermentum felis. Donec
nonummy pellentesque ante. Phasellus adipiscing semper elit. Proin
fermentum massa ac quam. Sed diam turpis, molestie vitae, placerat a,
molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend at,
accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc
eleifend consequat lorem. Sed lacinia nulla vitae enim. Pellentesque
tincidunt purus vel magna. Integer non enim. Praesent euismod nunc eu
purus. Donec bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec
et mi. Nam vulputate metus eu enim. Vestibulum pellentesque felis eu
massa.

Quisque ullamcorper placerat ipsum. Cras nibh. Morbi vel justo vi- tae
lacus tincidunt ultrices. Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. In hac habitasse platea dictumst. Integer tempus con-
vallis augue. Etiam facilisis. Nunc elementum fermentum wisi. Aenean
placerat. Ut imperdiet, enim sed gravida sollicitudin, felis odio placerat
quam, ac pulvinar elit purus eget enim. Nunc vitae tortor. Proin tempus
nibh sit amet nisl. Vivamus quis tortor vitae risus porta
vehicula.\mynote{\textbf{This note is automatically adjusted by
`marginfix'.} Phasellus adipiscing semper elit. Proin fermentum massa ac
quam. Sed diam turpis, molestie vitae, placerat a, molestie nec, leo.
Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec, suscipit a,
ipsum.} Fusce mauris. Vestibulum luctus nibh at lectus. Sed bibendum,
nulla a vel justo vi- tae lacus tinci dunt ultrices. Lorem ipsum dolor sit
amet. Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec,
suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend
consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt
purus vel magna. Integer non enim. Praesent euismod nunc eu purus. Donec
bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec et mi. Nam
vulputate metus eu enim. Vestibulum pellentesque felis eu massa.

\end{document}

Related to David's answer and my comment, if two notes are close together it would be good if there is a minimum space between the two, as you can see in the picture between "Note 1" and "Note 2":

enter image description here

Best Answer

This probably isn't as robust as it could be as it's only tested on this one page but it shows a mechanism (relying on pdftex \pdfsavepos in this version) that automatically moves notes up at the end of (explicit) paragraphs, and shrinks the inter-note space as needed to avoid the notes falling off the page. In this version it never splits a note over a page, if the notes on a page do not fit they will produce an over=full box.

I left in a vertical and two horizontal rules as a debugging aid to show the margin area.

enter image description here

\documentclass[12pt]{scrartcl}

\usepackage{lmodern}
\usepackage{geometry}

\geometry{left=2.5cm,textwidth=130mm}

\setlength{\marginparwidth}{4cm}
\setlength{\marginparsep}{2em}
\newcounter{mpcnt}

\makeatletter


\gdef \@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
%
   \let\@elt\relax
   \xdef\@freelist{\@freelist\@midlist}%
   \global \let \@midlist \@empty
   \@combinefloats
   \ifvbox\@kludgeins
     \@makespecialcolbox
   \else
\ifvoid\mpins
     \setbox\@outputbox \vbox to\@colht {%
       \@texttop
       \dimen@ \dp\@outputbox
       \unvbox \@outputbox
       \vskip -\dimen@
       \@textbottom
       }%
\else
     \setbox\@outputbox \hbox{\vbox to\@colht {%
       \@texttop
       \dimen@ \dp\@outputbox
       \unvbox \@outputbox
       \vskip -\dimen@
       \@textbottom
       }%
\vrule
\kern\marginparsep
%\showboxdepth1
%\tracingonline3\tracingoutput\@ne
%\showboxbreadth20
%\showbox\mpins
\ifx\mpins@@\@undefined
\@tempdima\@colht
\else
\@tempdima\expandafter\@gobble\mpins@@
\fi
%\showthe\@colht
\vbox to \dimexpr\@colht\relax{%2
\pdfsavepos
\edef\tmp{\write\noexpand\@auxout{%
\string\mpardata{@}{\noexpand\thepage}{\noexpand\the\noexpand\pdflastypos}}}\tmp
\hrule
\vbadness\maxdimen
\loop
\setbox0=\vsplit\mpins to \maxdimen
\setbox0\vbox{\unvbox\z@\global\setbox\@ne\lastbox}%
\@tempdimb=\dimexpr\@tempdima-\wd\@ne-(\ht\z@+\dp\z@)/2\relax
\ifdim\@tempdimb<\z@\@tempdimb\z@\fi
\vskip\@tempdimb \@minus\@tempdimb
\@tempdima=\dimexpr\@tempdima-\@tempdimb-\ht\z@-\dp\z@\relax
\box\z@
\ifvoid\mpins\else
\repeat
\vskip\z@\@plus\@colht
\hrule
}%
}
\fi
   \fi
   \global \maxdepth \@maxdepth
}


\def\mpardata#1#2#3{%
\expandafter\gdef\csname mpins@#1\endcsname{\kern#3sp}%
}


\long\def\mynote#1{%
\refstepcounter{mpcnt}%
\strut
\pdfsavepos
\edef\tmp{\write\noexpand\@auxout{%
\string\mpardata{\the\c@mpcnt}{\noexpand\thepage}{\noexpand\the\noexpand\pdflastypos}}}\tmp
\vadjust{%
\hbox to \linewidth{%
\hfill\smash{\raise\dp\strutbox\hbox to \z@{$\leftarrow$\hss}}}}
\@savemarbox\@ne{\raggedright\footnotesize#1\par}%
\@ifnextchar\par{%
\expandafter\ifx\csname mpins@\the\c@mpcnt\endcsname\relax
\@insertmpins{}%
\else
\edef\@tmp{\kern\the\dimexpr (\ht\@ne+\dp\@ne)/2\relax}%
\@insertmpins{%
\csname mpins@\the\c@mpcnt\endcsname\relax
\@tmp
}%
\fi}{%
\@insertmpins{\csname mpins@\the\c@mpcnt\endcsname}}}

\def\@insertmpins#1{%
\insert\mpins{%
\box\@ne
\nointerlineskip
\hbox{#1}%
\penalty-\@M}}

\newinsert\mpins
\skip\mpins\z@
\count\mpins\z@
\dimen\mpins\textheight

\begin{document}
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, vulpu-
tate 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 vestibu- lum urna
fringilla ultrices.\mynote{\textbf{This note is centered because
there is enough space below and it is in the middle of a paragraph.} Erat
ligula aliquet magna, vitae ornare odio metus a mi. Morbi ac orci et nisl
hendrerit mollis.} Phasellus eu tellus sit amet tortor gravida placerat.
Integer sapien est, iaculis in, pretium quis, viverra ac, nunc. Nam arcu
libero, nonummy eget, consectetuer id, vulpu- tate a, magna. Donec
vehicula augue eu neque. Pellentesque habitant morbi tristique senectus et
netus et malesuada fames ac turpis egestas.

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. Pellen- tesque
a nulla. Cum sociis natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Aliquam tincidunt urna. Nulla ullam- corper
vestibulum turpis. Pellentesque cursus luctus
mauris.\mynote{\textbf{This note is pushed upwards because it is at
the end of a pragraph}}

Nulla malesuada porttitor diam. Donec felis erat, congue non, volutpat at,
tincidunt tristique, libero. Vivamus viverra fermentum felis. Donec
nonummy pellentesque ante. Phasellus adipiscing semper elit. Proin
fermentum massa ac quam. Sed diam turpis, molestie vitae, placerat a,
molestie nec, leo. Maecenas lacinia. Nam ipsum ligula, eleifend at,
accumsan nec, suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc
eleifend consequat lorem. Sed lacinia nulla vitae enim. Pellentesque
tincidunt purus vel magna. Integer non enim. Praesent euismod nunc eu
purus. Donec bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec
et mi. Nam vulputate metus eu enim. Vestibulum pellentesque felis eu
massa.

Quisque ullamcorper placerat ipsum. Cras nibh. Morbi vel justo vi- tae
lacus tincidunt ultrices. Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. In hac habitasse platea dictumst. Integer tempus con-
vallis augue. Etiam facilisis. Nunc elementum fermentum wisi. Aenean
placerat. Ut imperdiet, enim sed gravida sollicitudin, felis odio placerat
quam, ac pulvinar elit purus eget enim. Nunc vitae tortor. Proin tempus
nibh sit amet nisl. Vivamus quis tortor vitae risus porta
vehicula.\mynote{\textbf{This note is automatically adjusted by
`marginfix'.} Phasellus adipiscing semper elit. Proin fermentum massa ac
quam. Sed diam turpis, molestie vitae, placerat a, molestie nec, leo.
Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec, suscipit a,
ipsum. make ot just a bit longer to show it moving better.} Fusce mauris. Vestibulum luctus nibh at lectus. Sed bibendum,
nulla a vel justo vi- tae lacus tinci dunt ultrices. Lorem ipsum dolor sit
amet. Maecenas lacinia. Nam ipsum ligula, eleifend at, accumsan nec,
suscipit a, ipsum. Morbi blandit ligula feugiat magna. Nunc eleifend
consequat lorem. Sed lacinia nulla vitae enim. Pellentesque tincidunt
purus vel magna. Integer non enim. Praesent euismod nunc eu purus. Donec
bibendum quam in tellus. Nullam cursus pulvinar lectus. Donec et mi. Nam
vulputate metus eu enim. Vestibulum pellentesque felis eu massa.

\end{document}
Related Question