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):
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}
\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.\label
mechanism to determine the parity of the page. The page number is completely determined at that point.\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.