83

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

5
  • Although you are looking for a LaTeX solution - this is probably an area where ConTeXt does a much better job. Commented Nov 21, 2012 at 12:15
  • 5
    The package todonotes does exactly this, but don't ask me how.
    – yo'
    Commented Nov 21, 2012 at 12:20
  • @tohecz I just looked at this after your comment, the package has nice formatting of the note and arrow or line to the callout using tikz which could all probably be layered over the code in my answer as well as it doesnt do anything special with note placement it just uses standard marginpar (or marginnite if your redefine marginpar) so if you redefined marginpar to be as in my answer you would probably get an alternative positioning algorithm but the fancier formatting of todonotes Commented Nov 21, 2012 at 12:56
  • @DavidCarlisle I'm not really sure what exact tweaks todonotes does, I just now that it places a sort-of arrow pointing to the place of reference (after 2nd run), which is something that is asked for here. I thought that the reference might be of interest ;)
    – yo'
    Commented Nov 21, 2012 at 22:26
  • @tohecz todonotes makes a good job making the margin note point to the references, but it is much more liberal with regards to the position of the notes to what is required for my purposed. As David notes, it just uses standard marginpar. But for something like the book shown in the picture above you need some intelligent algorithm to place notes that are at the end of a paragraph or end of a chapter.
    – Jörg
    Commented Nov 21, 2012 at 22:45

2 Answers 2

37
+500

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}
7
  • David, thanks for this! I tested it a bit with several pages and notes and it actually appear to be quite robust. Three comments: 1) The mechanism that pushes notes upwards at the end of a paragraph appears little unstable. As you see on your picture, the note is one line too high, but if you copy and paste everything again the note is correctly adjusted on the next page. 2) Is there any chance for this to work on twoside documents as well? 3) Some "emergency" white space seems to be needed when two notes are close together (see updated question). But again, thanks for your effort!
    – Jörg
    Commented Nov 21, 2012 at 12:00
  • Yes two side is no harder to do, it's just work basically you do exactly the same but stick everything on the left margin on even pages. Yes letting the space shrink to zero is probably not wise changing \vskip\@tempdimb \@minus\@tempdimb to \vskip\@tempdimb \@minus \dimexpr\@tempdimb - 2pt\relax probably keeps 2pt between them. The algorithm for moving the notes around is very simplistic it is not coded in the macro at all it just puts shrinkable glue between each note and then forces all the notes into a box of specified column height so tex just shrinks all the gaps by the same factor. Commented Nov 21, 2012 at 12:05
  • \vskip\@tempdimb \@minus \dimexpr\@tempdimb - 2pt\relax adds a minimum space between notes (it adds the space as well above the very first note of the document, but that's not a big deal I guess). I try to adjust your solution to twoside - let's see how successful I am. If this develops any further you should consider making this a package!
    – Jörg
    Commented Nov 21, 2012 at 12:21
  • yes I may do the twoside version later (may have to be the weekend) also if the ouput routine ships out more than one page (eg makes a float oage) you will lose all the notes, the inserts would need to be re-inserted (most likely enough to make the latex \@reinserts know about \mpins as well as \footuins and \kludgeins, but I haven't tested that). If you do any code extensions or new test cases you could send by email 9or post here) google for my name to get the email:-) Commented Nov 21, 2012 at 12:28
  • 1
    Please make this a package! :D Commented Dec 18, 2014 at 23:06
35

Updates:

These will be removed later. Adding here now while this is being developed/tested.

  • 2012-11-23: Added \AtEndDocument hook to add a phantom para at the end of the document. This corrects issue noticed during development where the margin par at the bottom of the last paragraph was not able to automatically shift itself up.

Here is a version that uses, yep you guessed it, tikz and the infamous \tikzmark. This initially started with my earlier solution at something similar in How can I put real notes in the margin?.

How it works:

It uses \everypar{} to place a \tikzmark at the start of each paragraph. Then when an \MarginPar is encountered the \tikzmarks of the current paragraph and the subsequent paragraph are accesed and measurements are made from the source of the \MarginPar to the end of the paragraph. From this the vertical shift is determined.

Basic Usage:

Using just \MarginPar{<content>} yields the following. Note that I have adjusted the comment about marginfix in the last \MarginPar as marginfix is not being used here.

To make it easier to see that the arrow is pointing to the actual source of the \MarginPar, I manually added red color to the word before, and blue to the word following the source of the \MarginPar.

enter image description here

Fancy:

Since this is \tikz it has a lot of inherent flexibility. There are the usual ones in terms of how you want to style the margin pars, but an added one is that if you use yshift=<length> to move the margin up vertically, the arrow will be drawn differently (but only if there is sufficient vertical movement). The following was produced with:

\MarginPar[draw=brown, fill=yellow!20, inner sep=2pt, yshift=2cm][green]
    {<content>}

The first optional parameter is applied to the margin par content and the second optional parameter is applied to the arrow.

enter image description here

Notes:

  • As with most \tikzmark solutions, this does require two runs. If you see really strange results, before you panic, try rerunning.
  • If you un-coment %\def\DebugMarginPar{} debugging output is enabled and will show up on top the text. The text on the left is the intermediate lengths. and the orange solid line is from the source to the start of the current paragraph, and the red dotted line is form the source to the end of the current paragraph.
  • To change the default styling of the margin par, you can adjust the \MarginParStyle macro.

References:

Further Enhancements/Known Issues

  • Need to incorporate Andrew Stacey's upcoming tikzmark package.

  • If the arrow adjusting feature is useful, might need some more tweaking to determine exactly where it is enabled. I made an initial attempt but it has only had minor testing, so not sure where it'll break.

  • Needs much more testing...

Algorithm:

The may be a better algorithm than what is implemented here, but here is a brief explanation of how this determines the what shifting (if any to apply). The distances here are vertical distance from the location of the arrow, where the source of the \MarginPar is.

if (distance to end of para > 0.5*(height of margin par) then
    margin par can be centered about this point.
    It may go above start of para but I think that is ok
 else 
    move margin par so that bottom of the margin par is at end of para.

Code:

%% Uncomment to enable debug output. Note that this also enable `\layout` 
%% so the text will shift forward. So don't panic, just go to the next page.
%%
%\def\DebugMarginPar{}% 

\documentclass[12pt]{scrartcl}

\usepackage{lmodern}
\usepackage{geometry}
\usepackage{etoolbox}
\usepackage{xcolor}
%\usepackage{marginfix}% Not used

\usepackage{etoolbox}
\usepackage{xparse}
\usepackage{xstring}
\usepackage{tikz}
\usetikzlibrary{calc}

\ifdefined\DebugMarginPar% For debug use only
    % https://tex.stackexchange.com/questions/558/is-there-a-show-for-lengths
    \usepackage{printlen}\uselengthunit{mm}
    \usepackage{layout}
\fi

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

\setlength{\marginparwidth}{4cm}
\setlength{\marginparsep}{2em}
\usepackage[noadjust]{marginnote}


% https://tex.stackexchange.com/questions/33703/extract-x-y-coordinate-of-an-arbitrary-point-in-tikz
\newdimen\XCoord
\newdimen\YCoord
\newcommand*{\ExtractCoordinate}[1]{\path (#1); \pgfgetlastxy{\XCoord}{\YCoord};}%
%
\newcommand*{\SetLengthToYCoordinate}[2]{%
    \ExtractCoordinate{#2}%
    \setlength{#1}{\YCoord}%
}%

% Default style for the margin par node.
\ifdefined\DebugMarginPar
    \tikzset{Default Marginpar Style/.style={
            shape=rectangle,inner sep=0, draw=blue, 
            rounded corners=2pt, blue, font=\itshape}
        }
\else
    \tikzset{Default Marginpar Style/.style={
            shape=rectangle,inner sep=0, draw=none, 
            rounded corners=2pt, blue, font=\itshape}
        }
\fi

\tikzset{Default Arrow Style/.style={red, ultra thick, -stealth, shorten <= 2pt, shorten >= 2pt}}

% Following determined via \layout: 
\newlength{\EndOfTextOffsetFromWest}
\newlength{\StartOfMarginNotes}
\setlength{\EndOfTextOffsetFromWest}{\dimexpr1in+\hoffset+\textwidth\relax}
\setlength{\StartOfMarginNotes}{\dimexpr\EndOfTextOffsetFromWest+\marginparsep\relax}

% Change this to set the default style for the margin par.
\newcommand*{\MarginParStyle}[2][\marginparwidth]{%
    % #1 = optional width of parbox
    % #2 = content of margin par
    %
    % We don't want `\everypar` to have any effect on these paragraphs so
    % disable that while we are in here.
    \global\toggletrue{InMarginNote}% 
    \parbox[t]{#1}{%
        \footnotesize\itshape\raggedright%
        #2%
    }%
    \global\togglefalse{InMarginNote}%
}%

\newlength{\CurrentParHeight}
\newlength{\CurrentMargiparHeight}
\newlength{\CurrentPointToStartOfPara}
\newlength{\CurrentPointToEndOfPara}
\newlength{\VerticalShift}
\newlength{\VerticalDistanceToMarginParSouth}% for fancy arrow
\newcounter{NextPara}
\NewDocumentCommand{\MarginPar}{%
    O{}% #1 = draw options for the marginpar
    O{}% #2 = draw options for the arrow
    m  % #3 = margin par text
    }{%
    \setbox0=\vtop{\MarginParStyle{#3}}%
    \setlength{\CurrentMargiparHeight}{\ht0}% height of this marginpar
    \addtolength{\CurrentMargiparHeight}{\dp0}%
    \begin{tikzpicture}[overlay,remember picture, thick]
        \coordinate (EdgeOfText) at 
            ($(current page.west |- 0,0) 
            + (\EndOfTextOffsetFromWest,0.5ex)$);
        \coordinate (MarginParWestSide) at ($(EdgeOfText)+(\marginparsep,0)$);
        \setcounter{NextPara}{\arabic{ParaCount}};
        \stepcounter{NextPara}

        \path (pic cs:Para\arabic{ParaCount},{(0, \paperheight)}) + 
                  (0, 0.7\baselineskip) coordinate (CurrentParStart);
        \path (pic cs:Para\arabic{NextPara},{(0,-\paperheight)}) + 
                  (0,-0.3\baselineskip) coordinate (NextParStart);

        \ifdefined\DebugMarginPar
            % Debug: Location of start of paragraphs with a marginpar
            \draw [line width=0.5cm,green,opacity=.3] 
                    (CurrentParStart) circle (2pt);
        \fi

        \coordinate (VectorParHeight) at ($(CurrentParStart)-(NextParStart)$);
        \SetLengthToYCoordinate{\CurrentParHeight}{VectorParHeight}

        % Now check that current para does not continue on to the next page.
        % If it does we need to do me some tweakin' and set the end of this
        % para to be the end of the text on this page
        \pgfmathtruncatemacro{\ParaContinuesToNextPage}
                {\CurrentParHeight >  0 ? 0 : 1}
        \IfEq{\ParaContinuesToNextPage}{0}{}{%
            \coordinate (NextParStart) at 
                ($(current page.north)   % top of page
                - (0, 1.0in + \voffset)  % (2) in \layout
                - (0, \headsep)          % (6) in \layout
                - (0, \textheight)       % (7) in \layout
                - (0, 0.7\baselineskip)
                $);
            \coordinate (VectorParHeight) at 
                    ($(CurrentParStart)-(NextParStart)$);
            \SetLengthToYCoordinate{\CurrentParHeight}{VectorParHeight}
        }

        \coordinate (VectorToStartOfPara) at 
                (MarginParWestSide |- CurrentParStart);
        \SetLengthToYCoordinate{\CurrentPointToStartOfPara}{VectorToStartOfPara}

        \ifdefined\DebugMarginPar
        \draw [orange, ultra thick,-latex] 
            ([xshift=-0.25\arabic{ParaCount} cm]MarginParWestSide) -- 
            ([xshift=-0.25\arabic{ParaCount} cm]VectorToStartOfPara);
        \fi

        % The \baselineskip adjustment is so that we are measuring to the end
        % of the current para, as opposed to the start of the next para.
        \coordinate (VectorToEndOfPara) at 
            ($(MarginParWestSide |- NextParStart) +(0,\baselineskip)$);
        \SetLengthToYCoordinate{\CurrentPointToEndOfPara}{VectorToEndOfPara}

        \ifdefined\DebugMarginPar
            % Debugging: line from arrow to end of para
            \draw [red!40, dotted, ultra thick,-latex]% 
                ([xshift=-0.35\arabic{ParaCount} cm]MarginParWestSide) -- 
                ([xshift=-0.35\arabic{ParaCount} cm]VectorToEndOfPara);
        \fi

        % Now we have: 
        %
        % \CurrentParHeight      = the height of the current paragraph saved as 
        % \CurrentMargiparHeight = height of the margin par
        % (MarginParWestSide)    = coordinate of start of arrow
        % \CurrentPointToStartOfPara = distance from start of para to arrow
        % \CurrentPointToEndOfPara   = distance from end of para to arrow
        %
        % So it is just a matter of applying some algorithm to tweak the 
        % vertical placement.  Here  is one possible algorithm: 
        %
        % Note: Distances are measured from the vertical location of the arrow.
        %
        % if (distance to end of para > 0.5*(height of margin par) then
        %    margin par can be centered about this point
        %    it may go above start of para but I think that is ok
        % else 
        %    move margin par so that bottom of the margin par is at end of para.        
        \pgfmathsetlength{\VerticalShift}{
                -\CurrentPointToEndOfPara > 0.5*\CurrentMargiparHeight  ? 0cm :
                % First version aligns bottom of marginpar with arrow location.
                %0.5*(\CurrentMargiparHeight - 0.5*\baselineskip)%
                0.5*(\CurrentMargiparHeight - 0.0*\baselineskip) +
                \CurrentPointToEndOfPara
            }

        \node [Default Marginpar Style, anchor=west, #1] (marginpar) at 
                ($(MarginParWestSide) + (0,\VerticalShift)$)  
                 {\MarginParStyle{#3}};

        % Well, since this is a tikz solution we need to do
        % something slightly fancy, so check if the margin par was
        % moved above far enough that a simple horizontal arrow 
        % is not enough.
        %
        % The last coordinate below is tweak so that we don't end up
        % with a vertical component of the arrow that is hardly visible.
        \coordinate (VerticalVectorFromMarginParToArrow) at 
            ($(marginpar.south) - (EdgeOfText) - (0,4.0pt)$);
        \SetLengthToYCoordinate{\VerticalDistanceToMarginParSouth}{VerticalVectorFromMarginParToArrow}

        \pgfmathtruncatemacro{\NeedFancyArrow}
                {\VerticalDistanceToMarginParSouth >  0 ? 1 : 0}

        \IfEq{\NeedFancyArrow}{0}{%
            \draw [Default Arrow Style, #2] (MarginParWestSide) to (EdgeOfText);
        }{%
            \draw [Default Arrow Style, #2] 
                    (marginpar.south) -- 
                    (marginpar.south |- EdgeOfText) --
                    (EdgeOfText);
        }%

        \ifdefined\DebugMarginPar
       %% Debugging: Y = vertical location of arrow pointing to source
            \node [
                text width=8.5cm, font=\tiny, draw=red, anchor=south, 
                fill=white, fill opacity=1
                ] 
                at  ($(EdgeOfText) - (0.75\textwidth,-0.2)$)
                {\MarginParStyle[8.5cm]{%
                    ParaCount = \arabic{ParaCount}\\
                    CurrentParHeight = \printlength{\CurrentParHeight}\\
                    CurrentMargiparHeight = \printlength{\CurrentMargiparHeight}\\
                    CurrentPointToStartOfPara = \printlength{\CurrentPointToStartOfPara}\\
                    CurrentPointToEndOfPara = \printlength{\CurrentPointToEndOfPara}\\
                    ParaContinuesToNextPage = \ParaContinuesToNextPage\\
                    VerticalDistanceToMarginParSouth = \printlength{\VerticalDistanceToMarginParSouth}
                    }%
                };
        \fi
    \end{tikzpicture}%
}



%--------------------------------------------
% https://tex.stackexchange.com/questions/50015/tikzmark-to-have-different-behaviour-if-first-run-and-mark-locations-not-yet-av
\makeatletter
\tikzset{%
  remember picture with id/.style={%
    remember picture,
    overlay,
    save picture id=#1,
  },
  save picture id/.code={%
    \edef\pgf@temp{#1}%
    \immediate\write\pgfutil@auxout{%
      \noexpand\savepointas{\pgf@temp}{\pgfpictureid}}%
  },
  if picture id/.code args={#1#2#3}{%
    \@ifundefined{save@pt@#1}{%
      \pgfkeysalso{#3}%
    }{
      \pgfkeysalso{#2}%
    }
  }
}

\def\savepointas#1#2{%
  \expandafter\gdef\csname save@pt@#1\endcsname{#2}%
}

\def\tmk@labeldef#1,#2\@nil{%
  \def\tmk@label{#1}%
  \def\tmk@def{#2}%
}

\tikzdeclarecoordinatesystem{pic}{%
  \pgfutil@in@,{#1}%
  \ifpgfutil@in@%
    \tmk@labeldef#1\@nil
  \else
    \tmk@labeldef#1,(0pt,0pt)\@nil
  \fi
  \@ifundefined{save@pt@\tmk@label}{%
    \tikz@scan@one@point\pgfutil@firstofone\tmk@def
  }{%
  \pgfsys@getposition{\csname save@pt@\tmk@label\endcsname}\save@orig@pic%
  \pgfsys@getposition{\pgfpictureid}\save@this@pic%
  \pgf@process{\pgfpointorigin\save@this@pic}%
  \pgf@xa=\pgf@x
  \pgf@ya=\pgf@y
  \pgf@process{\pgfpointorigin\save@orig@pic}%
  \advance\pgf@x by -\pgf@xa
  \advance\pgf@y by -\pgf@ya
  }%
}
\newcommand\tikzmark[2][]{%
\tikz[remember picture with id=#2] #1;}
\makeatother
%--------------------------------------------

\newcounter{ParaCount}
\newtoggle{InMarginNote}

\newcommand*{\MarkParaWithTikzMark}{%
    \iftoggle{InMarginNote}{%
        % Don't want to count the margin note paragraphs.
    }{%
        \stepcounter{ParaCount}%
        \tikzmark{Para\arabic{ParaCount}}%
    }%
}%
\AtBeginDocument{%
    \setcounter{ParaCount}{0}%
    \global\togglefalse{InMarginNote}% 
    % https://tex.stackexchange.com/questions/33849/what-does-the-the-everypar-do
    \everypar{\MarkParaWithTikzMark}%
}%

% Need this to handle the case where there is no aditional text following 
% the last para.
\AtEndDocument{\MarkParaWithTikzMark}%


% Add highlighting to words before and after so that we know
% we are pointing to the correct place.  These are really for
% debugging, but useful to leave them so we know that the
% arrows points to the correct place.
\newcommand*{\Before}[1]{\textcolor{red}{#1}}%
\newcommand*{\After}[1]{\textcolor{blue}{#1}}%


%
\begin{document}\ifdefined\DebugMarginPar\layout\fi
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 \Before{ultrices}.\MarginPar{\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.} 
\After{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
\Before{mauris}.\MarginPar{\textbf{This note is pushed upwards because it is at
the end of a paragraph}}

\After{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
\Before{vehicula}.\MarginPar{\textbf{This note used to be 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.} \After{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.

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.

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 \Before{facilisis}. 
\MarginPar[draw=brown, fill=yellow!20, inner sep=2pt, yshift=2cm][green]
    {Here is an demo of a few of the tikz options and an illustration
    of the auto arrow adjustments.%
    }
\After{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
\end{document}
2
  • That looks quite good, but with a more extensive document to test (bit.ly/SXFx92) you see that a lot of the margins are overlapping. David's answer does that quite well, so maybe a combination of the two approaches is a good idea (e.g. not to rely on \pdfsavepos).
    – Jörg
    Commented Nov 22, 2012 at 16:45
  • @Jörg: Will have a look at this this weekend... Previously known issue with \MarginPar near end of last paragraph corrected. Commented Nov 23, 2012 at 8:31

You must log in to answer this question.

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