[Tex/LaTex] text, margin, and text + margin width floats without using tufte class

captionsfloatsgraphicssidenotestufte

I want to use the marginfigure, figure*, and figure environment as defined in tufte-book and tufte-handout classes (see respectively Figure 1, 2 and 3 in the template example).

It means being able to use following environments:

  • marginfigure: A figure in the margin that has \marginparwidth width, with its caption below in the margin;
  • figure: A figure in the main text that has \textwidth with, with its caption besides in the margin (and bottom aligned);
  • figure*: A figure over main text and margin that has \textwidth + \marginparsep + \marginparwidth width, with caption below in the margin.

enter image description here

However, since most of my source-code is intended for KOMAscript classes, I don't want to switch to one of the tufte classes just because of that.

Question: How to emulate those environment outside tufte classes?


This question have been solved by the accepted answer, i.e., using the sidenotes-package. However, for keeping someone else from following tortuous paths/culs-de-sac I took, I let herein below my attempts to solve my issue. It gather the previous versions of my question together.

Contents:

  1. Related packages and TeX.SE questions,
  2. Successful workaround for marginfigure using capt-of package,
  3. Trying to emulate environments out of tufte classes source-code:
    • Successful (but unwieldy) for marginfigure;
    • Unsuccessful for figure and figure*

1. Related packages and TeX.SE questions

I am aware of the floatrow package, but even if it seems great for figure and figure*, I couldn't find how to handle marginfigure.

I also found Implementing marginfigure that solves the problem in an other way.

2. Successful workaround for marginfigure using capt-of package

I achieved a workaround for marginfigure using capt-of package, as described here.

\documentclass{scrartcl}
    \usepackage{graphicx}
    \usepackage{showframe}
    \usepackage{capt-of}
\begin{document}
    Some text.
    \marginpar{%
        \includegraphics[width=\marginparwidth]{example-image}%
        \captionof{figure}{My caption}%
    }%
    More text.

    Another paragraph
\end{document}

It's great, but doesn't solve the issue for figure and figure*

3. Trying to emulate environments out of tufte classes source-code

I've read through the sources, and since it all has been implemented (+ managing floats, etc.), my idea was so to re-implement the commands of tufte-common.def available in CTAN to reproduces those environment.

I am thus trying to adapt macros and environments defined in tufte-common.def in order to let them standalone.

I succeeded for marginfigure, but compilation fails when I redefine figure and figure*.

  • marginfigure: I succeeded in copying tufte-book classe's marginfigure environment.

See MWE below.

\documentclass[twoside]{scrartcl}
    \usepackage{showframe}
    \usepackage{graphicx}
    \usepackage{placeins}

    \usepackage[a4paper,left=24.8mm,top=27.4mm,headsep=2\baselineskip,textwidth=107mm,marginparsep=8.2mm,marginparwidth=49.4mm,textheight=49\baselineskip,headheight=\baselineskip]{geometry} % tufte-handout definitions

\makeatletter
        \input{my-tufte-marginfigure}
\makeatother

\begin{document}
    \noindent
    Say what?!

    \begin{marginfigure}%
        \includegraphics[width=\marginparwidth]{example-image-a}
        \caption{My caption}
    \end{marginfigure}
\end{document}

where my-tufte-marginfigure is following excerpt of tufte-common.def

\usepackage{ragged2e}

\newcommand{\@tufte@marginfont}{\normalfont\footnotesize\sffamily}
\newcommand*{\@tufte@caption@font}{\@tufte@marginfont}
\newcommand*{\@tufte@caption@justification}{\justifying} %% or \RaggedLeft or \RaggedRight 

% Paragraph indentation and separation for marginal text
\newcommand{\@tufte@margin@par}{%
    \setlength{\RaggedRightParindent}{0.5pc}%
    \setlength{\JustifyingParindent}{0.5pc}%
    \setlength{\parindent}{0.5pc}%
    \setlength{\parskip}{0pt}%
}

\newsavebox{\@tufte@margin@floatbox}

\newenvironment{@tufte@margin@float}[2][-1.2ex]%
{\FloatBarrier% process all floats before this point so the figure/table numbers stay in order.
    \begin{lrbox}{\@tufte@margin@floatbox}%
        \begin{minipage}{\marginparwidth}%
            \@tufte@caption@font% %%some font definition%%
            \def\@captype{#2}%
            \hbox{}\vspace*{#1}%
            \@tufte@caption@justification%
            \@tufte@margin@par%
            \noindent%
        }
        {\end{minipage}%
    \end{lrbox}%
    \marginpar{\usebox{\@tufte@margin@floatbox}}%
}


%%
% Margin figure environment

\newenvironment{marginfigure}[1][-1.2ex]%
    {\begin{@tufte@margin@float}[#1]{figure}}%
    {\end{@tufte@margin@float}}

%%
% Margin table environment

\newenvironment{margintable}[1][-1.2ex]%
{\begin{@tufte@margin@float}[#1]{table}}
    {\end{@tufte@margin@float}}
  • figure and figure*: I tried to do the same for figure and figure*.

However, the compilation fails.

\documentclass[twoside]{scrartcl}
    \usepackage{showframe}
    \usepackage{graphicx}
    \usepackage{placeins}
    \usepackage{lipsum}

    \usepackage[a4paper,left=24.8mm,top=27.4mm,headsep=2\baselineskip,textwidth=107mm,marginparsep=8.2mm,marginparwidth=49.4mm,textheight=49\baselineskip,headheight=\baselineskip]{geometry}

\makeatletter
        %\input{my-tufte-marginfigure} %% cf. hereinabove
        \input{my-tufte-figure}
\makeatother

\begin{document}

    \begin{figure}
        \includegraphics[width=\linewidth]{example-image-b}
        \caption{My long caption is not \emph{that} long!}
        \setfloatalignment{b}
    \end{figure}

%   \begin{figure*}[h]
%       \includegraphics[width=\linewidth]{example-image-c}%
%       \caption{Some caption!}%
%   \end{figure*}

\end{document}

Where my-tufte-figure is following excerpt of tufte-common.def. (I know it's very long, but I'm sure it's pretty close to the minimal form!)

\usepackage{ifthen}
\usepackage{optparams}
\usepackage{fullwidth}
\usepackage{changepage}

%%
% `symmetric' option -- puts marginpar space to the outside edge of the page
%   Note: this option forces the twoside option (see the .cls files)
\newboolean{@tufte@symmetric}
%%\DeclareOptionX[tufte]<common>{symmetric}{
    \setboolean{@tufte@symmetric}{true}
    %%%\@tufte@info@noline{The `symmetric' option implies `twoside'}
    %%%\ExecuteOptionsX[tufte]<common>{twoside}
%%}

%DEBUT
%%
% A collection of macros to be used with the new Tufte-style float environments.
% \setfloatalignment forces the caption placement to be treated as top, bottom, etc.
% \forcerectofloat forces the float to be treated as if it were appearing on a recto page.
% \forceversofloat does the same, but for verso pages.

\newcommand{\@tufte@float@debug@info}{}% contains debug info generated as the float is processed
\newcommand{\@tufte@float@debug}[1]{% adds debug info to the queue for output
    \ifthenelse{\equal{\@tufte@float@debug@info}{}}%
    {\def\@tufte@float@debug@info{#1}}%
    {\g@addto@macro\@tufte@float@debug@info{\MessageBreak#1}}%
}

\newcommand{\floatalignment}{x}% holds the current float alignment (t, b, h, p)
\newcommand{\setfloatalignment}[1]{\global\def\floatalignment{#1}\@tufte@float@debug{Forcing position: [#1]}}% manually sets the float alignment
\newboolean{@tufte@float@recto}
\newcommand{\forcerectofloat}{\gsetboolean{@tufte@float@recto}{true}\@tufte@float@debug{Forcing page: [recto]}}
\newcommand{\forceversofloat}{\gsetboolean{@tufte@float@recto}{false}\@tufte@float@debug{Forcing page: [verso]}}

% Boxes to temporarily store our float and caption
\newsavebox{\@tufte@figure@box}
\newsavebox{\@tufte@caption@box}

    %MANDATORY
% Save original LaTeX float environment
\let\@tufte@orig@float\@float
\let\@tufte@orig@endfloat\end@float
    %MANDATORY/
% New length for tweaking float captions
\newlength{\@tufte@caption@vertical@offset}
\setlength{\@tufte@caption@vertical@offset}{0pt}

% Store the caption and label contents
\newcommand{\@tufte@stored@shortcaption}{}
\newcommand{\@tufte@stored@caption}{}
\newcommand{\@tufte@stored@label}{}

\long\def\@tufte@caption[#1][#2]#3{%
    \ifthenelse{\isempty{#1}}%
    {\gdef\@tufte@stored@shortcaption{#3}}%
    {\gdef\@tufte@stored@shortcaption{#1}}%
    \gsetlength{\@tufte@caption@vertical@offset}{-#2}% we want a positive offset to lower captions
    \gdef\@tufte@stored@caption{#3}%
}

\newcommand{\@tufte@label}[1]{%
    \gdef\@tufte@stored@label{#1}%
}

\newcommand{\@tufte@fps}{}

\newboolean{@tufte@float@star}
\newlength{\@tufte@float@contents@width}
%FIN

%%
% Compute lengths used for full-width displays

\newlength{\@tufte@overhang}% used by the fullwidth environment and the running heads
\newlength{\@tufte@fullwidth}
\newlength{\@tufte@caption@fill}

\newcommand{\TufteRecalculate}{%
    \setlength{\@tufte@overhang}{\marginparwidth}
    \addtolength{\@tufte@overhang}{\marginparsep}

    \setlength{\@tufte@fullwidth}{\textwidth}
    \addtolength{\@tufte@fullwidth}{\marginparsep}
    \addtolength{\@tufte@fullwidth}{\marginparwidth}

    \setlength{\@tufte@caption@fill}{\textwidth}
    \addtolength{\@tufte@caption@fill}{\marginparsep}
}

\AtBeginDocument{\TufteRecalculate}

%%
% Globally sets a boolean

\newcommand*{\gsetboolean}[2]%
    {% based on code from ifthen pkg
        \lowercase{\def\@tempa{#2}}%
        \@ifundefined{@tempswa\@tempa}%
        {\PackageError{ifthen}{You can only set a boolean to `true' or `false'}\@ehc}%
        {\@ifundefined{#1\@tempa}%
            {\PackageError{ifthen}{Boolean #1 undefined}\@ehc}%
            {\global\csname#1\@tempa\endcsname}%
        }%
    }

%%
% Detect if the subfigure package has been loaded

\newboolean{@tufte@packages@subfigure}
\setboolean{@tufte@packages@subfigure}{false}
\AtBeginDocument{%
    \@ifpackageloaded{subfigure}
    {\gsetboolean{@tufte@packages@subfigure}{true}}
    {\gsetboolean{@tufte@packages@subfigure}{false}}%
}

% Write our own aliases for the \checkoddpage and \ifoddpage or \ifcpoddpage commands
\newboolean{@tufte@odd@page}
\setboolean{@tufte@odd@page}{true}
\newcommand*{\@tufte@checkoddpage}%
    {%
        \checkoddpage%
        \ifthenelse{\boolean{@tufte@changepage}}%
            {%
                \ifoddpage%
                    \setboolean{@tufte@odd@page}{true}%
                \else%
                    \setboolean{@tufte@odd@page}{false}%
                \fi%
            }{%
                \ifcpoddpage%
                    \setboolean{@tufte@odd@page}{true}%
                \else%
                    \setboolean{@tufte@odd@page}{false}%
                \fi%
            }%
    }


%%
% Define a float environment to place the captions in the margin space

\newenvironment{@tufte@float}[3][htbp]%
{% begin @tufte@float
    % Should this float be full-width or just text-width?
    \ifthenelse{\equal{#3}{star}}%
        {\gsetboolean{@tufte@float@star}{true}}%
        {\gsetboolean{@tufte@float@star}{false}}%
    %   
    % Check page side (recto/verso) and store detected value -- can be overriden in environment contents
    \@tufte@checkoddpage%
    \ifthenelse{\boolean{@tufte@odd@page}}%
        {\gsetboolean{@tufte@float@recto}{true}%%%\@tufte@float@debug{Detected page: [recto/odd]}
            }%
        {\gsetboolean{@tufte@float@recto}{false}%%%\@tufte@float@debug{Detected page: [verso/even]}
            }%
    % If the float placement specifier is 'b' and only 'b', then bottom-align the mini-pages, otherwise top-align them.
    \renewcommand{\@tufte@fps}{#1}%
    %%%\@tufte@float@debug{Allowed positions: [#1]}%
    \ifthenelse{\equal{#1}{b}\OR\equal{#1}{B}}%
        {\renewcommand{\floatalignment}{b}%%%\@tufte@float@debug{Presumed position: [bottom]}
            }%
        {\renewcommand{\floatalignment}{t}%%%\@tufte@float@debug{Presumed position: [top]}
            }%
    % Capture the contents of the \caption and \label commands to use later
    \global\let\@tufte@orig@caption\caption%
    \global\let\@tufte@orig@label\label%
    \renewcommand{\caption}{\optparams{\@tufte@caption}{[][0pt]}}%
        %QUESTION: is ##1 ok?
    \renewcommand{\label}[1]{\@tufte@label{##1}}% 
    % Handle subfigure package compatibility
    \ifthenelse{\boolean{@tufte@packages@subfigure}}%
        {% don't move the label while inside a \subfigure or \subtable command
        \global\let\label\@tufte@orig@label%
        }{%
        }% subfigure package is not loaded
    \@tufte@orig@float{#2}[#1]%
    \ifthenelse{\boolean{@tufte@float@star}}%
        {\setlength{\@tufte@float@contents@width}{\@tufte@fullwidth}}%
        {\setlength{\@tufte@float@contents@width}{\textwidth}}%
    \begin{lrbox}{\@tufte@figure@box}%
        \begin{minipage}[\floatalignment]{\@tufte@float@contents@width}\hbox{}%
}
{% end @tufte@float
            \par\hbox{}\vspace{-\baselineskip}\ifthenelse{\prevdepth>0}{\vspace{-\prevdepth}}{}% align baselines of boxes
        \end{minipage}%
    \end{lrbox}%
    % build the caption box
    \begin{lrbox}{\@tufte@caption@box}%
        \begin{minipage}[\floatalignment]{\marginparwidth}\hbox{}%
            \ifthenelse{\NOT\equal{\@tufte@stored@caption}{}}%
                {\@tufte@orig@caption[\@tufte@stored@shortcaption]{\@tufte@stored@caption}}%
                {}%
            \ifthenelse{\NOT\equal{\@tufte@stored@label}{}}%
                {\@tufte@orig@label{\@tufte@stored@label}}%
                {}%
            \par\vspace{-\prevdepth}%% TODO: DOUBLE-CHECK FOR SAFETY
        \end{minipage}%
    \end{lrbox}%
    % now typeset the stored boxes
    \begin{fullwidth}%
        \begin{minipage}[\floatalignment]{\linewidth}%
            \ifthenelse{\boolean{@tufte@float@star}}%
            {\@tufte@float@fullwidth[\@tufte@caption@vertical@offset]{\@tufte@figure@box}{\@tufte@caption@box}}%
            {\@tufte@float@textwidth[\@tufte@caption@vertical@offset]{\@tufte@figure@box}{\@tufte@caption@box}}%
        \end{minipage}%
    \end{fullwidth}%
    \@tufte@orig@endfloat% end original LaTeX float environment
    % output debug info
%   \ifthenelse{\boolean{@tufte@debug}}%
%       {%
%           \typeout{^^J^^J----------- Tufte-LaTeX float information ----------}%
%           \ifthenelse{\equal{\@tufte@stored@label}{}}%
%           {\typeout{Warning: Float unlabeled!}}%
%           {\typeout{Float label: [\@tufte@stored@label]}}%
%           \typeout{Page number: [\thepage]}%
%           \def\MessageBreak{^^J}%
%           \typeout{\@tufte@float@debug@info}%
%           \ifthenelse{\boolean{@tufte@symmetric}}%
%           {\typeout{Symmetric: [true]}}%
%           {\typeout{Symmetric: [false]}}%
%           \typeout{----------------------------------------------------^^J^^J}%
%       }{%
%       }%
    % reset commands and temp boxes and captions
    %%\gdef\@tufte@float@debug@info{}%
    \let\caption\@tufte@orig@caption%
    \let\label\@tufte@orig@label%
    \begin{lrbox}{\@tufte@figure@box}\hbox{}\end{lrbox}%
    \begin{lrbox}{\@tufte@caption@box}\hbox{}\end{lrbox}%
    \gdef\@tufte@stored@shortcaption{}%
    \gdef\@tufte@stored@caption{}%
    \gdef\@tufte@stored@label{}%
    \gsetlength{\@tufte@caption@vertical@offset}{0pt}% reset caption offset
}

\newcommand{\@tufte@float@textwidth}[3][0pt]%
    {%
        \ifthenelse{\NOT\boolean{@tufte@symmetric}\OR\boolean{@tufte@float@recto}}
            {% asymmetric or page is odd, so caption is on the right
                \hbox{%
                    \usebox{#2}%
                    \hspace{\marginparsep}%
                    \smash{\raisebox{#1}{\usebox{#3}}}%
                }%
                \@tufte@float@debug{Caption position: [right]}%
            }{% symmetric pages and page is even, so caption is on the left
                \hbox{%
                    \smash{\raisebox{#1}{\usebox{#3}}}%
                    \hspace{\marginparsep}%
                    \usebox{#2}%
                }%
                \@tufte@float@debug{Caption position: [left]}%
            }%
    }

\newcommand{\@tufte@float@fullwidth}[3][0pt]%
    {%
        \ifthenelse{\equal{\floatalignment}{b}}%
            {% place caption above figure
                \ifthenelse{\NOT\boolean{@tufte@symmetric}\OR\boolean{@tufte@float@recto}}%
                    {\hfill\smash{\raisebox{#1}{\usebox{#3}}}\par\usebox{#2}\@tufte@float@debug{Caption position: [above right]}}% caption on the right
                    {\smash{\raisebox{#1}{\usebox{#3}}}\hfill\par\usebox{#2}\@tufte@float@debug{Caption position: [above left]}}% caption on the left
            }{% place caption below figure
                \ifthenelse{\NOT\boolean{@tufte@symmetric}\OR\boolean{@tufte@float@recto}}%
                    {\usebox{#2}\par\hfill\smash{\raisebox{#1}{\usebox{#3}}}\@tufte@float@debug{Caption position: [below right]}}% caption on the right
                    {\usebox{#2}\par\smash{\raisebox{#1}{\usebox{#3}}}\hfill\@tufte@float@debug{Caption position: [below left]}}% caption on the left
            }%
    }

%%
% Redefine the figure environment to place the captions in the margin space

\renewenvironment{figure}[1][htbp]%
    {%
        \begin{@tufte@float}[#1]{figure}{}%
    }{%
        \end{@tufte@float}%
    }


%%
% Redefine the table environment to place the captions in the margin space

\renewenvironment{table}[1][htbp]%
    {%
        \begin{@tufte@float}[#1]{table}{}%
    }{%
        \end{@tufte@float}%
    }

%%
% Full-width figure

\renewenvironment{figure*}[1][htbp]%
    {%
        \begin{@tufte@float}[#1]{figure}{star}%
    }{%
        \end{@tufte@float}%
    }


%%
% Full-width table

\renewenvironment{table*}[1][htbp]%
    {%
        \begin{@tufte@float}[#1]{table}{star}%
    }{%
        \end{@tufte@float}%
    }

Best Answer

You could use the sidenotes-package:

\documentclass[twoside]{scrartcl}
    \usepackage{showframe}
    \usepackage{graphicx}
    \usepackage{placeins}
    \usepackage{sidenotes}
    \usepackage{kantlipsum}
    \usepackage[a4paper,left=24.8mm,top=27.4mm,headsep=2\baselineskip,textwidth=107mm,marginparsep=8.2mm,marginparwidth=49.4mm,textheight=49\baselineskip,headheight=\baselineskip]{geometry} % tufte-handout definitions


\begin{document}
    \begin{marginfigure}%
        \caption{My caption}
        \includegraphics[width=\marginparwidth]{example-image-a}
    \end{marginfigure}

    \kant[1]
    \begin{figure}[h]
        \sidecaption{My caption}
        \includegraphics[width=\textwidth]{example-image-a}
    \end{figure}

    \begin{figure*}
        \sidecaption{My caption}
        \includegraphics[width=\linewidth]{example-image-a}
    \end{figure*}

\end{document}

enter image description here enter image description here

Related Question