3

In a document with wide margins, I want to offer a figure environment which extends into the marginpar-area. Furthermore, I want the caption-text to be placed in this area, too, either above or below the figure, depending on the figure placement. Additionally, this should work on even and odd pages (using scrbook in my case, but the twoside option would have the same effect, too).

What I'd like to achieve in the end might be illustrated best by the following figures (very quick drawings in inkscape, don't care about the different sizes):

enter image description hereenter image description hereenter image description here

First of all, I'd like to collect the references from \cite-macros and to output them in the margin at the bottom (this is working and hence not shown in the MWE). A wide figure should have its caption in the margin, too. If a wide figure occurs at the bottom, I'd like the references to be above the caption of the figure in the margin. It might occur that there is something which is output in the top-margin, too. As you see from the figures, it should work in twoside mode.

To detect where a figure is placed, I use code suggested by David Carlisle. To position the caption in the margin, either at the bottom or the top of a page, I use code suggested by Heiko Oberdiek. To test whether the current page is odd or even, if use \ifthispageodd from KOMA-Script and to ensure that the figure extends into the outer margin, I use the addmargin* environment from KOMA-Script.

Besides several warnings which I chose to deal with later, the approach works as expected on odd pages, but the content of my new widefigure-environment does not appear on even pages, although there is space left in the respective area.

The problem is that both code pieces mentioned above deal (somehow) with the output routine (the word changes in the title might not be completely correct), which is a black box for me. I have only introduced minor changes to the code (renamed David's \floatswitch to \@floatswitch, \foo to \@helper and changed the length arguments of the \put macro Heiko used to position the notes in the margin).

Can anyone explain why the figure contents do not appear on even pages (or, even better, make them appear)?

The MWE is long due to the code snippets mentioned above. I tried to add some comments (in my code) that help to understand the approach, Heiko's code was commented, too.

\documentclass[paper=a4,11pt]{scrbook}

\usepackage[left=1in,top=1in,headsep=2\baselineskip,
  textwidth=26pc,marginparsep=2pc,marginparwidth=12pc,
  textheight=44\baselineskip,headheight=\baselineskip]{geometry}

% It took me a while to figure out the (x)color here is not merely used for
% providing color...

\usepackage{xcolor,atbegshi,picture,zref-abspage}
\usepackage{lipsum}
\makeatletter
%\input{../features/widefigures.tex}

% This magic has been posted by David Carlisle on TeX-SX:
% https://tex.stackexchange.com/questions/56017/formatting-floats-differently-based-on-placement

\def\@floatswitch#1#2#3#4{%
  \def\@elt##1{\global\value{##1}\the\value{##1}\relax}%
  \edef\FS@ckpt{\cl@@ckpt}%
  \let\@elt\relax
\hbox to 3sp{%
\vbox{{\FS@ckpt#1\par}}%
\vbox{{\FS@ckpt#2\par}}%
\vbox{{\FS@ckpt#3\par}}%
\vbox{{\FS@ckpt#4\par}}%
\hss}\aftergroup\break}

\def\FS@checkswitch#1{%
\ifdim\wd#1=3sp %
\setbox\z@\box#1%
\begingroup
\vbadness\maxdimen
\setbox\z@\vsplit\z@ to \textheight
\setbox\z@\vbox{\unvbox\z@\global\setbox\@ne\lastbox}%
\setbox\z@\vbox{\unvbox\@ne\unskip\global\setbox\@ne\lastbox}
\setbox\z@\hbox{\unhbox\@ne\unskip
\FS@junk
\global\setbox\@ne\lastbox}%
\endgroup
\else
\global\setbox\@ne\box#1%
\fi}

\def\@comflelt#1{%
\FS@checkswitch#1%
\setbox\@tempboxa
      \vbox{\unvbox\@tempboxa\box\@ne\vskip\floatsep}}

\let\saved@cflt\@cflt
\def\@cflt{%
\def\FS@junk{\setbox\z@\lastbox\setbox\z@\lastbox\setbox\z@\lastbox}%
\saved@cflt}

\let\FS@junk\relax
\let\saved@cflb\@cflb
\def\@cflb{%
\def\FS@junk{\setbox\z@\lastbox\setbox\z@\lastbox}%
\saved@cflb}


\def\@helper#1\box\@currbox#2!!{%
\def\@addtocurcol{%
#1%
\let\FS@junk\relax
\FS@checkswitch\@currbox
\box\@ne
#2}}%
\expandafter\@helper\@addtocurcol!!

\def\@wtryfc #1{%
  \global\setbox\@outputbox\vbox{%
    \unvbox\@outputbox
    \vskip\@fpsep
\def\FS@junk{\setbox\z@\lastbox}%
\FS@checkswitch#1%
\box\@ne}}

%
% Provide a macro that allows typesetting into the margin 
%

\newcommand\@wide[2]{%#1%
  \begin{addmargin*}[0cm]{#1}#2\end{addmargin*}%
}

%
% An environment to deal with figure-like content
%

\newsavebox{\@ContentCollectorBox}
\newenvironment{@CollectContentAndCaption}[1][\linewidth]{%
  % This environment collects its content in the box \@ContentCollectorBox,
  % the width of the box is given by the optional argument #1 (defaults to
  % \linewidth) 
  % Furthermore, it redefines the \caption macro and saves its content in the
  % macros \@CurrCaptionLong and \@CurrCaptionShort. 
  %
  % The box and the two macros are available globally.
  \begingroup
    \begin{lrbox}{0\null\global\setbox\@ContentCollectorBox}%
      \begin{minipage}{#1}
        \renewcommand\caption[2][]{%
           \gdef\@CurrCaptionLong{##2}%
           \ifx\\##1\\
             \gdef\@CurrCaptionShort{##2}%
           \else
             \gdef\@CurrCaptionShort{##1}%
           \fi
        }%
}{%
      \end{minipage}
    \end{lrbox}
  \endgroup
}

%
% An environment providing a floating figure which extends in the margin.
%

\newenvironment{widefigure}[1][]{%
  % Store the optional argument. If provided, add square brackets around.
  \ifx\\#1\\
    \def\@rgOne{}%
  \else
    \def\@rgOne{[#1]}%
  \fi
  %
  % Start the environment defined above
  \begin{@CollectContentAndCaption}[\dimexpr\textwidth+\marginparsep+\marginparwidth]%
}{%
  % End the environment 
  \end{@CollectContentAndCaption}%
  % Start the figure-environment with the optional argument from
  % the \begin{widefigure} part. I use \figure here to avoid an
  % \expandafter-orgy to expand the argument
  \expandafter\figure\@rgOne
  % \@floatswitch is a command from within the magic, I just added the @ 
    \@floatswitch{%
      % If you comment the \topmarginpar-commands here, the figure content
      % will appear at the desired position.
      %
      % Output the caption. 
      \topmarginpar{\@CurrCaptionLong}%
      % add a rule to the topmargin-collection with a width of zero and the
      % height+depth of the box containing the content without the caption
      % from the environment used  above.
      \topmarginpar{\rule{0pt}{%
          \dimexpr\ht\@ContentCollectorBox+\dp\@ContentCollectorBox}}%
      %
      %
      % use the content of the widefigure-environment.
      %%%
      %%% This does not appear on even pages
      %%%
      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }{%
      % Same as above. If you comment the \botmarginpar-commands here, the
      % figure content  will appear at the desired position.
      %
      \botmarginpar{\@CurrCaptionLong}%
      \botmarginpar{\rule{0pt}{%
          \dimexpr\ht\@ContentCollectorBox+\dp\@ContentCollectorBox}}%
      % The bottom-content appears only on odd pages
      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }{%
      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }{%
      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }
  \endfigure
}

%
% This magic has been posted by Heiko Oberdiek
%  https://tex.stackexchange.com/questions/69517/send-and-stack-marginpar-to-the-top-or-the-bottom-of-the-page


\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#3%
        \par
        \begingroup
          \tbmparItemSep
        \endgroup
        \unvbox#2%
      }%
    }%  
  \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.
\def\@marginparxpos{0pt}\def\@marginparypos{0pt}%
\AtBeginShipout{%
  \AtBeginShipoutUpperLeft{%
    %
    % I added the following definition of the corresponding lengths to be able
    % to distinguish odd and even pages. 
    %
    \ifthispageodd{%
      \def\@marginparxpos{\dimexpr
        1in+\oddsidemargin+\textwidth+\marginparsep\relax}%
      \def\@marginparypos{-\dimexpr
        1in+\topmargin+\headheight+\headsep+\textheight\relax}% 
    }{%
      \def\@marginparxpos{\dimexpr
        1in+\evensidemargin-\marginparsep-\marginparwidth\relax}%
      \def\@marginparypos{-\dimexpr
        1in+\topmargin+\headheight+\headsep+\textheight\relax}% 
    }
    \put(%
      \@marginparxpos,\@marginparypos%
    ){%
      \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
\begin{document}
%
% If you comment the next line, there is an additional error. If someone could
% solve this, help is appreciated. But currently that's not the main issue.
\topmarginpar{}
 \begin{widefigure}[t]
   \color{green}\rule{\linewidth}{2cm}
   \caption{Top placement of figure, odd page.} 
 \end{widefigure}
\botmarginpar{Bottom margin note seems to be fine.}
\lipsum[2-5]
% Now we are on an even page
\topmarginpar{\lipsum[57]}
\botmarginpar{\lipsum[57]}
\lipsum[1-6]
% On an odd page, again
\begin{widefigure}[b]
  \color{green}\rule{\linewidth}{2cm}
  \caption{Bottom placement, odd page}
\end{widefigure}
\lipsum[1-3]
% Now we are on an even page!
\begin{widefigure}[t]
  \color{red}\rule{\linewidth}{2cm}
  \caption[A normal caption]{Top placement, even page. Figure vanished!}
\end{widefigure}
\lipsum
\begin{widefigure}[b]
  \color{red}\rule{\linewidth}{2cm}
  \caption[A normal caption]{Bottom placement, even page. Figure vanished!}
\end{widefigure}
\lipsum[1-12]
\end{document}
  • You could just use \rlap{\begin{minipage}...\end{minipage}} for odd pages and \hspace*{\textwidth}\llap{\begin{minipage}...\end{minipage}} for even pages. – John Kormylo Feb 19 '15 at 17:31
  • I have tried solutions with \llap and similar commands, and they work. However, in addition I would like to use \botmarginpar to collect all citations that appear on one page. And for this I have to set the caption this way, too, to avoid that citations and caption are output on top of each other. – Patrick Happel Feb 19 '15 at 18:13
  • BTW, \ifthispageodd or even \ifodd\value{page} don't seem to work inside a figure environment. The tikz package has a test which does work inside a figure as does marginnote, or you can do it yourself by writing the page number for each widefigure to the aux file. – John Kormylo Feb 19 '15 at 18:27
  • It's not that clear what features you need but the output routine is the one place where you don't need a two pass \label mechanism to determine the parity of the page. The page number is completely determined at that point. – David Carlisle Feb 19 '15 at 21:51
  • David, I simply believe you that it is not required to use a \label-based mechanism as \ifthispageodd does. But this part works as far as I see (apart from the warnings). The problem is to tell Heiko's \botmarginpar to skip the space occupied by the figure and its caption. If I do this by using \botmarginpar to insert some space and the caption content, the figure content vanishes as shown in the MWE. I've edited the question to make it (hopefully) more clear. – Patrick Happel Feb 19 '15 at 23:17
1

I modified the solution to include the captions. The \gettruepage macro now returns the x,y location as well (to check for bottom vs top). Note, since it uses the aux file it takes two runs to work right.

\documentclass[paper=a4,11pt]{scrbook}

\usepackage[left=1in,top=1in,headsep=2\baselineskip,
  textwidth=26pc,marginparsep=2pc,marginparwidth=12pc,
  textheight=44\baselineskip,headheight=\baselineskip]{geometry}

% It took me a while to figure out the (x)color here is not merely used for
% providing color...

\usepackage{xcolor}
\usepackage{lipsum}
\usepackage{environ}% create an environment using \BODY

% The \gettruepage marcro returns the page number (as \truepage} in places where \thepage won't,
% such as inside floats or paragraphs split over 2 pages.

\pdfpageheight=\paperheight
\pdfpagewidth=\paperwidth

\newcounter{truepageindex}
\newcount{\truepage}% returns page
\newlength{\truex}% returns distance from left side of text area
\newlength{\truey}% returns distance from top of text area

\newcommand{\newtruepage}[4]% #1 = \thetruepageindex, #2 = \thepage, #3 = \pdflastxpos, #4 = \pdflastypos
{\global\expandafter\edef\csname truepage#1\endcsname{#2}%
 \global\expandafter\edef\csname truex#1\endcsname{#3}%
 \global\expandafter\edef\csname truey#1\endcsname{#4}}

\makeatletter
\newcommand{\gettruepage}{\stepcounter{truepageindex}%
  \pdfsavepos
  \protected@write\@auxout{}{\string\newtruepage{\thetruepageindex}{\thepage}
    {\noexpand\number\pdflastxpos}{\noexpand\number\pdflastypos}}%
  \@ifundefined{truepage\thetruepageindex}%
   {\truepage=\c@page \truex=0pt \truey=0pt}%
   {\truepage=\csname truepage\thetruepageindex\endcsname\relax
    \truex=\csname truex\thetruepageindex\endcsname sp\relax
    \truey=\csname truey\thetruepageindex\endcsname sp\relax
    \truey=\dimexpr \paperheight-\truey-1in-\topmargin-\headheight-\headsep\relax
    \advance\truex by -1in
    \ifodd\truepage\relax\advance\truex by -\oddsidemargin
    \else \advance\truex by -\evensidemargin
    \fi}%
}%
\makeatother

% caption handler
\newif{\iffakecaption}
\newcommand{\fakecaptiontext}{}% reserve name
\makeatletter
\newcommand{\fakecaption}[2][\@empty]% #1 = short caption (optional), #2 = long caption
 {\global\fakecaptiontrue
  \refstepcounter{figure}%
  \ifx#1\@empty\def\fakecaptiontext{#2}%
  \else\def\fakecaptiontext{#1}%
  \fi
  \addcontentsline{lof}{figure}{\string\numberline {\thefigure}{\ignorespaces \fakecaptiontext}}%
  \gdef\fakecaptiontext{#2}}
\makeatother

% widefigure environment

\newsavebox{\widefigurebox}
\newsavebox{\widefigurecaption}
\newlength{\widefiguretest}% compares middle of figure to middle of text area
\newlength{\widefigureup}% baseline to baseline for caption above
\newlength{\widefiguredown}% baseline to baseline for caption below
\newlength{\widefiguresize}% total height of figure plus caption plus extra \marginparpush
\newcount{\widefigurelast}% last page with figure on bottom

\NewEnviron{widefigure}[1][\empty]{%
\figure[#1]%
\let\oldcaption=\caption
\let\caption=\fakecaption
\fakecaptionfalse
\savebox{\widefigurebox}{\begin{minipage}{\dimexpr \textwidth+\marginparsep+\marginparwidth}%
\BODY\end{minipage}}%
\let\caption=\oldcaption
\settoheight{\widefigureup}{\usebox{\widefigurebox}}%
\settodepth{\widefiguredown}{\usebox{\widefigurebox}}%
\widefiguretest=\dimexpr .5\textheight-.5\widefigureup +.5\widefiguredown\relax
\advance\widefigureup by \marginparpush
\iffakecaption
  \savebox{\widefigurecaption}{\parbox{\marginparwidth}%
    {\raggedright\textbf{Figure \thefigure: }\fakecaptiontext}}%
    \settodepth{\widefiguresize}{\usebox{\widefigurecaption}}%
    \advance\widefigureup by \widefiguresize
  \settoheight{\widefiguresize}{\usebox{\widefigurecaption}}%
    \advance\widefiguredown by \widefiguresize
  \advance\widefiguredown by \marginparpush
\fi
\gettruepage
\leavevmode% needed for \rlap and \llap
\ifodd\truepage\relax
  \rlap{\usebox{\widefigurebox}}%
  \iffakecaption
      \ifdim\truey>\widefiguretest\relax
      \rlap{\hspace{\textwidth}\hspace{\marginparsep}%
        \raisebox{\widefigureup}[0pt][0pt]{\usebox{\widefigurecaption}}}%
      \global\widefigurelast=\truepage
    \else
      \rlap{\hspace{\textwidth}\hspace{\marginparsep}%
        \raisebox{-\widefiguredown}[0pt][0pt]{\usebox{\widefigurecaption}}}%
    \fi
  \fi
\else
  \rlap{\hspace{-\marginparwidth}\hspace{-\marginparsep}\usebox{\widefigurebox}}%
  \iffakecaption
      \ifdim\truey>\widefiguretest\relax
      \llap{\raisebox{\widefigureup}[0pt][0pt]{\usebox{\widefigurecaption}}%
        \hspace{\marginparsep}}%
      \global\widefigurelast=\truepage
    \else
      \llap{\raisebox{-\widefiguredown}[0pt][0pt]{\usebox{\widefigurecaption}}%
        \hspace{\marginparsep}}%
    \fi
  \fi
\fi
\advance\widefigureup by \widefiguredown
\global\widefiguresize=\widefigureup
\endfigure}

\begin{document}
%
% If you comment the next line, there is an additional error. If someone could
% solve this, help is appreciated. But currently that's not the main issue.
%\topmarginpar{}
\begin{widefigure}[t]
   \color{green}\rule{\linewidth}{2cm}%  percent needed to prevent blank line at botttom
   \caption{Top placement of figure, odd page.}\label{test}
\end{widefigure}
This is a label test for Figure \ref{test}.
The height of the figure plus caption is \the\widefiguresize.

\lipsum[2-5]
% Now we are on an even page
\lipsum[1-6]
% On an odd page, again
\begin{widefigure}[b]
  \color{green}\rule{\linewidth}{2cm}%
  \caption{Bottom placement, odd page}
\end{widefigure}
\lipsum[1-3]
% Now we are on an even page!
\begin{widefigure}[t]
  \color{red}\rule{\linewidth}{2cm}%
  \caption[A normal caption]{Top placement, even page. Figure vanished!}
\end{widefigure}
\lipsum
\begin{widefigure}[b]
  \color{red}\rule{\linewidth}{2cm}%
  \caption[A normal caption]{Bottom placement, even page. Figure vanished!}
\end{widefigure}
\lipsum[1-3]
\listoffigures
\end{document}
  • Thanks for the answer, John, and I'll have a close look at the \truepage macro. Anyway, my problem is not working out whether I'm on an odd or even page. \ifthispageodd does this job as expected, although, as David stated above, it is not required. I'll edit the question. – Patrick Happel Feb 19 '15 at 22:55
  • I've thought of a way to place the caption in the margins where you want them using the \marginfill macro (see tex.stackexchange.com/questions/215322/…). Basically you use a blank \marginfill the same size as the figure then add the caption \marginfill either above or below it (for bottom or top, respectively). – John Kormylo Feb 20 '15 at 2:19
  • The solution in the code looks interesting, however, I don't see an easy way to use the \marginfill macro to collect things (the citations) at the bottom of the page, independent from where in the page they have been called. But this might be possible and I just don't see it. – Patrick Happel Feb 20 '15 at 8:14
  • To a man with a hammer, everything begins to look like a nail. – John Kormylo Feb 20 '15 at 16:22
0

I have found something which seems to be a solution. Although it is not yet complete and some underful boxes occur, it more or less does what I want. The spacing seems to be not correct, too.

The goal to this solution was that I realized (after quite a while of trying to understand David's code) that \@cflt and \@cflb are expanded depending on the positioning of the float. David uses this to define different versions of \FS@junk which throws away the boxes that are not needed and I use it to set a length that is \vspace*d either at top or bottom of the box containing the content of the margin.

This works until two floats appear on the same page, since I only use only one length to store the vertical dimension of the float, which is simply overwritten by the second float. Here, a solution might be to make use of the respective counter (figure/table) or to introduce a new one.

Furthermore, I do not use a label-red-based mechanism to detect whether the page is even or odd anymore, which leads to no complaints about undefined labels (Herbert's code needs labels and thus two runs, but at the second run there are no warnings).

Here's the MWE (again long, both code snippets linked above are included). I have added some comments to David's code (as far as I understand it), maybe that helps others to understand it better.

\documentclass[paper=a4,11pt]{scrbook}
\usepackage[T1]{fontenc}
\usepackage[left=1in,top=1in,headsep=2\baselineskip,
  textwidth=26pc,marginparsep=2pc,marginparwidth=12pc,
  textheight=44\baselineskip,headheight=\baselineskip]{geometry}

\usepackage{xcolor,atbegshi,picture,zref-abspage,ragged2e}
\usepackage{lipsum}
\makeatletter

% This magic has been posted by David Carlisle on TeX-SX:
% http://tex.stackexchange.com/questions/56017/formatting-floats-differently-based-on-placement


%
% The \@floatswitch-macro expects 4 arguments that is the content of the float
% that should be typeset if positioned at top (#1), bottom (#2), on a single
% page(#3) or here (#4)
%

\def\@floatswitch#1#2#3#4{%
  % (guess)  These lines somehow deal with the counters to avoid that the
  % counter are  increased by more than one. 
  \def\@elt##1{\global\value{##1}\the\value{##1}\relax}%
  \edef\FS@ckpt{\cl@@ckpt}%
  \let\@elt\relax
  % Counter-dealing ends here, but is used by \FS@ckpt
  \hbox to 3sp{%
    \vbox{{\FS@ckpt#1\par}}%
    \vbox{{\FS@ckpt#2\par}}%
    \vbox{{\FS@ckpt#3\par}}%
    \vbox{{\FS@ckpt#4\par}}%
    \hss}\aftergroup\break%
}

%
% Here, the correct box is selected. The macro that does the job is \FS@junk,
% which throws away the boxes that are not used.
%
\def\FS@checkswitch#1{%#
  \ifdim\wd#1=3sp %
    \setbox\z@\box#1%
    \begingroup
      \vbadness\maxdimen
      \setbox\z@\vsplit\z@ to \textheight
      \setbox\z@\vbox{\unvbox\z@\global\setbox\@ne\lastbox}%
      \setbox\z@\vbox{\unvbox\@ne\unskip\global\setbox\@ne\lastbox}%
      \setbox\z@\hbox{\unhbox\@ne\unskip
        \FS@junk
        \global\setbox\@ne\lastbox}%
    \endgroup
  \else
    \global\setbox\@ne\box#1%
  \fi}


% I have no idea about the follwing macro

\def\@comflelt#1{%
  \FS@checkswitch#1%
  \setbox\@tempboxa
     \vbox{\unvbox\@tempboxa\box\@ne\vskip\floatsep}}

% Obviously (well, it is just an educated guess), \@cflt is used when a float
% is positioned at the top of the page. Once I realised this, my problem was
% nearly solved.

\let\saved@cflt\@cflt

\def\@cflt{%
  \def\FS@junk{%
    % Since the float is positioned at the top of the page, we increase the
    % \tmbparTopSepLength to obtain some space at the top of the box
    % containing the content of the margin
    \setlength{\tbmparTopSepLength}{\baselineskip}%
    \addtolength{\tbmparTopSepLength}{\captionlength}%
    \addtolength{\tbmparTopSepLength}{\contentlength}%
    \global\tbmparTopSepLength=\tbmparTopSepLength
    \setbox\z@\lastbox\setbox\z@\lastbox\setbox\z@\lastbox}%
  \saved@cflt}

\let\FS@junk\relax

%  This is (another eductaed guess) the command that is expanded if a float is
%  placed at the bottom of a page. The method used is the same as above.
\let\saved@cflb\@cflb

\def\@cflb{%
  \def\FS@junk{%
    \setlength{\tbmparBottomSepLength}{2\baselineskip}%
    \addtolength{\tbmparBottomSepLength}{\captionlength}%
    \addtolength{\tbmparBottomSepLength}{\contentlength}%
    \global\tbmparBottomSepLength=\tbmparBottomSepLength
    \setbox\z@\lastbox\setbox\z@\lastbox}%
  \saved@cflb}

% Again, no idea.

\def\@helper#1\box\@currbox#2!!{%
  \def\@addtocurcol{%
    #1%
    \let\FS@junk\relax
    \FS@checkswitch\@currbox
    \box\@ne
    #2}}%

\expandafter\@helper\@addtocurcol!!

% And again, no idea.

\def\@wtryfc #1{%
  \global\setbox\@outputbox\vbox{%
    \unvbox\@outputbox
    \vskip\@fpsep
  \def\FS@junk{\setbox\z@\lastbox}%
  \FS@checkswitch#1%
  \box\@ne}}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



%
% An environment to deal with figure-like content
%
\newsavebox{\@ContentCollectorBox}
\newsavebox{\@CaptionCollectorBox}
\newenvironment{@CollectContentAndCaption}[1][\linewidth]{%
  % This environment collects its content in the box \@ContentCollectorBox,
  % the width of the box is given by the optional argument #1 (defaults to
  % \linewidth) 
  % Furthermore, it redefines the \caption macro and saves its content in the
  % macros \@CurrCaptionLong and \@CurrCaptionShort. 
  %
  % The box and the two macros are available globally.
  \begingroup
    \begin{lrbox}{0\null\global\setbox\@ContentCollectorBox}%
      \begin{minipage}{#1}%
        \renewcommand\caption[2][]{%
           \gdef\@CurrCaptionLong{##2}%
           \ifx\\##1\\
             \gdef\@CurrCaptionShort{##2}%
           \else
             \gdef\@CurrCaptionShort{##1}%
           \fi
        }%
}{%
      \end{minipage}%
    \end{lrbox}%
  \endgroup
}

%
% An environment providing a floating figure which extends in the margin.
%
\newenvironment{widefigure}[1][]{%
  % Store the optional argument. If provided, add square brackets around.
  \ifx\\#1\\
    \def\@rgOne{}%
  \else
    \def\@rgOne{[#1]}%
  \fi
  %
  % Start the environment defined above
  \begin{@CollectContentAndCaption}[\dimexpr\textwidth+\marginparsep+\marginparwidth]%
}{%
  % End the environment 
  \end{@CollectContentAndCaption}%
  \begin{lrbox}{\@CaptionCollectorBox}%
    \begin{minipage}{\marginparwidth}%
      \RaggedRight
      \@CurrCaptionLong
    \end{minipage}
  \end{lrbox}
  %  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %  This is part of the solution (and makes new problems)
  %  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Here I globally set two length that contain the vertical dimension of the
  % caption and the content
  \setlength{\captionlength}{\ht\@CaptionCollectorBox}%
  \addtolength{\captionlength}{\dp\@CaptionCollectorBox}%
  \global\captionlength=\captionlength\relax
  \setlength{\contentlength}{\ht\@ContentCollectorBox}%
  \addtolength{\contentlength}{\dp\@ContentCollectorBox}%
  \global\contentlength=\contentlength\relax
  % Start the figure-environment with the optional argument from
  % the \begin{widefigure} part. I use \figure here to avoid an
  % \expandafter-orgy to expand the argument
  \expandafter\figure\@rgOne
  %
  % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % This is new, too. Here, some underfull boxes occur which still need to be
  % inspected.
  % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % \@floatswitch is a command from within the magic, I just added the @ 
    \@floatswitch{%
      \ifodd\c@page%
        \rlap{\usebox{\@ContentCollectorBox}}%
        \vbox to 0pt{%
          \rlap{%
            \hspace*{\dimexpr\textwidth+\marginparsep}%
            \usebox{\@CaptionCollectorBox}%
          }%
          \vss
        }
      \else
        \rlap{\hspace*{\dimexpr-\marginparwidth-\marginparsep}\usebox{\@ContentCollectorBox}}%
        \vbox to 0pt{%
          \llap{%
            \usebox{\@CaptionCollectorBox}%
            \hspace*{\marginparsep}%
          }%
          \vss
        }%
      \fi
    }{%
      %
      % More or less the same as above, but for bottom placement.
      %
      \ifodd\c@page%
        \vbox to 0pt{%
          \vss
          \rlap{\hspace*{\dimexpr\textwidth+\marginparsep}%
            \usebox{\@CaptionCollectorBox}%
          }%
          \vspace*{\baselineskip}%
        }%
        \rlap{\usebox{\@ContentCollectorBox}}%
      \else
        \vbox to 0pt{%
          \vss
          \llap{%
            \usebox{\@CaptionCollectorBox}%
            \hspace*{\marginparsep}%
          }%
          \vspace*{\baselineskip}%
        }%
        \rlap{\hspace*{\dimexpr-\marginparwidth-\marginparsep}\usebox{\@ContentCollectorBox}}%
      \fi
    }{%
      % I still need something for page-placement
%      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }{%
      % I don't want figures to be placed [h]ere. Anyway, I need a solution to
      % this, too. 
%      \@wide{\wd\@ContentCollectorBox}{\usebox{\@ContentCollectorBox}}%
    }%
  \endfigure
}


%
% This magic has been posted by Heiko Oberdiek
%  http://tex.stackexchange.com/questions/69517/send-and-stack-marginpar-to-the-top-or-the-bottom-of-the-page


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

% I define four new lengths, to that are used to leave space at the top and
% bottom of a tbmpar-node

\newlength\tbmparTopSepLength
\newlength\tbmparBottomSepLength

% and two that keep the vertical dimension of the boxes containing the caption
% and content of a float
\newlength\captionlength
\newlength\contentlength

% Two commands that are exanded at the top and bottom of the box containing
% the contents of the margin
\newcommand*{\tbmparTopSep}{%
  \vspace*{\tbmparTopSepLength}%
}
\newcommand*{\tbmparBottomSep}{%
  \vspace*{\tbmparBottomSepLength}%
}

% A macro to reset the lengths 
\newcommand\restoreSeps{%
  \global\tbmparTopSepLength0pt\relax%\@zp
  \global\tbmparBottomSepLength0pt\relax
}
\restoreSeps

% Next lines are Heiko's code again

% * 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@box. 
\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#3%
        \par
        \begingroup
          \tbmparItemSep
        \endgroup
        \unvbox#2%
      }%
    }%  
  \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.
\def\@marginparxpos{0pt}\def\@marginparypos{0pt}%
\AtBeginShipout{%
  \AtBeginShipoutUpperLeft{%
    %
    % I added the following definition of the corresponding lengths to be able
    % to distinguish odd and even pages. 
    %
    \ifodd\c@page%
      \def\@marginparxpos{\dimexpr
        1in+\oddsidemargin+\textwidth+\marginparsep\relax}%
      \def\@marginparypos{-\dimexpr
        1in+\topmargin+\headheight+\headsep+\textheight\relax}% 
    \else
      \def\@marginparxpos{\dimexpr
        1in+\evensidemargin-\marginparsep-\marginparwidth\relax}%
      \def\@marginparypos{-\dimexpr
        1in+\topmargin+\headheight+\headsep+\textheight\relax}% 
    \fi
    \put(%
      \@marginparxpos,\@marginparypos%
    ){%
      \begingroup
        \global\let\tbmpar@inuse=N%
        \setbox\tbmpar@box=\tbmpar@VBox{%
          \penalty-\@M
          %
          % Here I insert the space at the top
          % 
          \tbmparTopSep
          \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%   
          }%
          %
          % Here I insert the space at the bottom
          % 
          \tbmparBottomSep
        }%  
        \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
    }%
  }%  
  % At the end, the lengths are reset.
  \restoreSeps
}     

\makeatother
\begin{document}
\lipsum[1]
\topmarginpar{A longer top note! A longer top note! A longer top note! A longer top note! A longer top note! }
 \begin{widefigure}[t]
   \color{green}\rule{\linewidth}{2cm}
   \caption{A longer caption at the top! A longer caption at the top! A longer caption at the top! A longer caption at the top!} 
 \end{widefigure}
\botmarginpar{Bottom margin is at the bottom.}
\lipsum[2-5]
% Now we are on an even page
\botmarginpar{A longer bottom note! A longer bottom note! A longer bottom note! A longer bottom note!}
\begin{widefigure}[b]
  \color{green}\rule{\linewidth}{2cm}
  \caption{Bottom placement with a longer caption. Bottom placement with a longer caption. Bottom placement with a longer caption. Bottom placement with a longer caption. Bottom placement with a longer caption. }
\end{widefigure}
\lipsum[1-3]

\begin{widefigure}[t]
  \color{red}\rule{\linewidth}{1cm}
  \caption{A top caption, defined first.}
\end{widefigure}
\topmarginpar{A top note with too large distance.}
\botmarginpar{A bottom note with the same distance (in this case ok).}
\begin{widefigure}[b]
  \color{red}\rule{\linewidth}{2cm}
  \caption{A bottom caption, defined second This one is longer than the top
    one. Also the content is longer.}
\end{widefigure}
\lipsum
\end{document}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.