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 \tikzmark
s 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](https://cdn.statically.io/img/i.sstatic.net/IdFIc.png)
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](https://cdn.statically.io/img/i.sstatic.net/EmpVz.png)
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}
todonotes
does exactly this, but don't ask me how.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 ;)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.