2

To solve a part of this question, I'd like to measure the size of a caption before it is typeset, and then use it to calculate the size of a matrix of nodes.

The first problem I can see is that \caption has a different definition, depending (at least) on whether the package {caption} is used, or just plain Latex:

$ texdef -t latex \@caption

\@caption:
\long macro:#1[#2]#3->\par \addcontentsline {\csname ext@#1\endcsname }{#1}{\protect \numberline {\csname the#1\endcsname }{\ignorespaces #2}}\begingroup \@parboxrestore \if@minipage \@setminipage \fi \normalsize \@makecaption {\csname fnum@#1\endcsname }{\ignorespaces #3}\par \endgroup 

$ texdef -t latex -p caption \@caption

\@caption:
\long macro:#1[#2]#3->\ifcaption@star \else \caption@prepareanchor {#1}{#2}\memcaptioninfo {#1}{\csname the#1\endcsname }{#2}{#3}\@nameuse {nag@hascaptiontrue}\fi \par \caption@beginex {#1}{#2}{#3}\caption@setfloatcapt {\caption@boxrestore \if@minipage \@setminipage \fi \caption@normalsize \ifcaption@star \let \caption@makeanchor \@firstofone \fi \@makecaption {\csname fnum@#1\endcsname }{\ignorespaces \caption@makeanchor {#3}}\par \caption@if@minipage \@minipagetrue \@minipagefalse }\caption@end 

To make the problem worse, I want that size after the caption is rotated; here is an MWE based on this other question:

% modified from: https://tex.stackexchange.com/questions/44427/rotate-picture-with-caption

\documentclass{article}
\usepackage[demo]{graphicx}
\usepackage{adjustbox}
\usepackage{lipsum}


\begin{document}
\lipsum[1]

\begin{figure}[ht]
  \begin{adjustbox}{addcode={\begin{minipage}{\width}}{%
      \caption{%
        Here is a caption of the figure which is so long that
        it has to be wrapped over multiple lines, but should
        not exceed the width (height after the rotation) of the image.
      }%
      \makeatletter%
      \ifx\caption@@make\undefined%
        \typeout{\string\caption@@make is undefined, prob. not using \{caption\}}%
      \else%
        \typeout{\string\caption@@make is defined, prob. using \{caption\}}%
        \typeout{\string\caption@box is: \meaning\caption@box}%
      \fi%
      \typeout{\string\@makecaption is: \meaning\@makecaption}%
      \makeatother%
      \end{minipage}},rotate=90,center}
    \includegraphics[width=0.6\textwidth,height=0.4\textwidth]{demo}%
  \end{adjustbox}
\end{figure}

\lipsum[2]
\end{document}

I was basically hoping that by reading into the definition of \caption, I'd find a box where the caption was typeset into, and measure its width/height/depth; but unfortunately, that depends on whether the {caption} is loaded, as well as on how the caption is styled (e.g., what size fonts are used) - and it would still be information after the caption is typeset.

Btw, the MWE outputs this:

test.png

... and prints out:

\caption@@make is defined, prob. using \{caption\}
\caption@box is: macro:->\ifx \@captype \@undefined \@latex@error {\noexpand \caption outside float}\@ehd \expandafter \@gobble \else \refstepcounter \@captype \expandafter \@firstofone \fi {\@dblarg {\@caption \@captype }}@box
\@makecaption is: macro:->\spacefactor \@m makecaption

... which seems to point that the {caption} package is used, even if not explicitly specified wrong, see comment below (also, the definitions I get here are a bit different from what texdef provides, but nevermind that).

So my question is - how could I obtain the size of the rotated caption on the image from MWE, such that I could use it in a calculation (say, for the height of the rotated image = the width of the original one)?

  • 1
    caption package is not being used \makeatletter% \ifx\caption@ is comparing \caption with @ which is false. Put \makeatletter at the start of the document then @ will be a letter and this will compare \caption@@make with \undefined which will be true. (The \makeatletter inside a macro argument never does anything, just as \verb can not be used inside a macro argument – David Carlisle Feb 8 '15 at 10:24
  • Thanks for that, @DavidCarlisle - I could feel something was suspicious, but couldn't really confirm that before your comment; cheers! – sdaau Feb 8 '15 at 10:51
  • \caption uses \refstepcounter{figure} which you do NOT want to do twice per image. Personally, I would avoid putting \caption inside anything other than a float. – John Kormylo Feb 8 '15 at 20:03
1

OK, I think I got something, not sure how correct it is. Essentially:

  • We can use a \savebox to typeset a caption into a box beforehand; but:
    • the \caption (and the \savebox) must be inside a {figure}; and:
    • there must a \parbox or {minipage} wrapping the \caption inside the \savebox - else no line breaking will be applied

It turns out, a linebreak of sorts (\break, \linebreak, \\ or \newline) is needed to position the \usebox below the image, like the normal \caption is - otherwise, for some reason it will stick to the right of the image. The below MWE results with this output (click for full res):

test.png

The first figure is the normal usage as in the OP.

The second figure is by using \savebox{ \parbox{ ... \caption... and \break \usebox{..., just to confirm if it is correct in respect to first figure. It generally is, except the caption is a tiny bit off, which I've helpfully indicated with the red dashed line (placed in gimp).

The third figure is where the size of the caption is calculated in advance, and used to resize the image (newheight = original height - half height of caption). Now here there is some trickery where one needs to be careful:

  • the addcode= of \adjustbox actually consist of two parts; code before (or precode=) and code after (or appcode=); I've indicated that at places with a space - and for the final figure, I've used explicitly precode=, appcode=
  • With this, one might think that eventual construct will be like:

    from \adjustbox:precode -> \begin{minipage}{\width}
    inside -> \includegraphics{...}
    from \adjustbox:appcode -> \end{minipage}
    

    ... and that is generally true - except, in terms of execution, the code "inside" executes first, even before the precode=! It goes:

    inside -> \includegraphics{...}
    from \adjustbox:precode -> \begin{minipage}{\width}
    from \adjustbox:appcode -> \end{minipage}
    
  • Similarly, the \width parameter exists only in pre/appcode - doesn't exist in the "inside" code

  • Consequently, even if you can \savebox the \caption in the precode, and \usebox it in the appcode - it doesn't matter, by that time the image has been typeset, and its size fixed - so we cannot modify its size according to the caption size at these points

So, the only solution is to know/set at least one dimension of the image a priori - here the width (unrotated), which is shared between the image and the caption (which needs it to break the text lines, anyways). If one accepts that as inevitability, then one can:

  • Typeset the \caption in a \savebox, inside the {figure}, but before the {adjustbox} altogether
  • Then, in the "inside" code, when the \includegraphics is typeset, both the saved box is known, and its apriori dimensions - so the modified dimension can be calculated in time;
  • Finally, the \usebox can either go here - or as in the MWE below, in the appcode= of \adjustbox (the precode of \adjustbox only has to open \begin{minipage}{\width}

The MWE code:

% modified from: http://tex.stackexchange.com/questions/44427/rotate-picture-with-caption

\documentclass{article}
\usepackage[demo]{graphicx}
\usepackage{adjustbox}
%\usepackage{lipsum}

\usepackage{lua-visual-debug} % compile with lualatex test.tex, for visual indication of margins

% "The text in a \savebox is typeset all on one line. You need to use a \parbox inside \savebox."
% http://tex.stackexchange.com/questions/194811/how-to-measure-the-height-and-width-of-several-lines-paragraph
% "'Missing \endgroup inserted' errors - a horizontal (LR) box which can not directly contain vertical (par) mode display material, you need a minipage to get into vertical mode"
% http://tex.stackexchange.com/questions/74896/using-align-within-savebox

% http://tex.stackexchange.com/questions/60491/latex-tracing
\def\tracingboxes{\showboxbreadth = \maxdimen
  \showboxdepth = \maxdimen}%
% must have \tracingboxes here before \tracingoutput,
% else nothing will be printed
\tracingboxes
\tracingoutput1

\begin{document}
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut purus elit,
vestibulum ut, placerat ac, adipiscing vitae, felis. Curabitur dictum ...

\begin{figure}[ht]
  \begin{adjustbox}{addcode={\begin{minipage}{\width}} {%
      \caption{%
        Here is a caption of the figure which is so long that
        it has to be wrapped over multiple lines, but should
        not exceed the width (height after the rotation) of the image.
      }%
      \end{minipage}},rotate=90,center}
    \includegraphics[width=0.4\textwidth,height=0.3\textwidth]{demo}%
  \end{adjustbox}
\end{figure}

\begin{figure}[ht]
  \newsavebox{\boxcapt}% is allocated (though not set) globally now, http://tex.stackexchange.com/questions/7134/making-register-declarations-local-undef
  \begin{adjustbox}{addcode={\begin{minipage}{\width}} {%
      \savebox{\boxcapt}{%
      \parbox{\width}{%
        \caption{%
          Here is a caption of the figure which is so long that
          it has to be wrapped over multiple lines, but should
          not exceed the width (height after the rotation) of the image.
        }%
      }% %end \parbox%
      }% %end \savebox%
      \typeout{boxcapt:  wd is: \the\wd\boxcapt, ht is: \the\ht\boxcapt, dp is: \the\dp\boxcapt}%
      %\break\fbox{\usebox{\boxcapt}}% fbox will mess up spacing
      \break% same w/ \linebreak, \\ or \newline
      \usebox{\boxcapt}%
      \end{minipage}},rotate=90,center}
    \includegraphics[width=0.4\textwidth,height=0.3\textwidth]{demo}%
  \end{adjustbox}
\end{figure}

Nam dui ligula, fringilla a, euismod sodales, sollicitudin vel, wisi. Morbi auctor lorem non justo. Nam lacus libero, pretium at, lobortis vitae, ...

% http://tex.stackexchange.com/questions/45396/is-it-possible-to-remove-length-counter-dim-etc-that-have-been-declared
% No, it's not possible: \newlength allocates a register and this allocation is permanent. If you do the \newlength inside a group, you'll lose the register name at group end, but the register will not be reusable.
\newlength{\globimw}
\newlength{\globimh}

% %%%%%%%%%%%%% nowork like this: %%%%%%%%%%%%%
% \begin{figure}[!h]
%   \begin{adjustbox}{addcode={\begin{minipage}{\width}%
%       \savebox{\boxcapt}{%
%       \parbox{\width}{%
%         \caption{%
%           Here is a caption of the figure which is so long that
%           it has to be wrapped over multiple lines, but should
%           not exceed the width (height after the rotation) of the image.
%         }%
%       }% %end \parbox%
%       }% %end \savebox%
%       \setlength{\skip0}{\ht\boxcapt}%
%       \global\setlength{\skip1}{\ht\boxcapt}% nope
%       \global\skip1=\skip0% nope
%       \global\setlength{\globimw}{\ht\boxcapt}% nope
%       \global\globimw=\globimw%
%       \typeout{boxcapt in:  wd is: \the\wd\boxcapt, ht is: \the\ht\boxcapt, dp is: \the\dp\boxcapt ; skip0: \the\skip0; skip1: \the\skip1; glp: \the\globimw}% ok here! but runs after boxcapt out?
%   } {% addcode= code after:
%       \break% same w/ \linebreak, \\ or \newline
%       \usebox{\boxcapt}%
%       \end{minipage}},rotate=90,center}
%     % use temp "scratch" register to calculate new height (rotated: width) of image, to its original height minus half height of caption
%       \typeout{boxcapt out:  wd is: \the\wd\boxcapt, ht is: \the\ht\boxcapt, dp is: \the\dp\boxcapt ; skip0: \the\skip0; skip1: \the\skip1; glp: \the\globimw}% all are zeroes!
%     \setlength{\skip0}{\dimexpr 0.3\textwidth-\expandafter\ht\boxcapt}
%     \includegraphics[width=0.4\textwidth,height=\skip0]{demo}%
%   \end{adjustbox}
% \end{figure}


\begin{figure}[!h]
  \setlength{\globimw}{0.4\textwidth}%
  \setlength{\globimh}{0.3\textwidth}%
  \global\globimw=\globimw% % must have, to propagate
  \global\globimh=\globimh%
  \savebox{\boxcapt}{%
  \parbox{\globimw}{% \width here is undefined (\adjustbox is below)
    \caption{%
      Here is a caption of the figure which is so long that
      it has to be wrapped over multiple lines, but should
      not exceed the width (height after the rotation) of the image.
    }%
  }% %end \parbox%
  }% %end \savebox%
  \begin{adjustbox}{
    precode={\begin{minipage}{\width}},
    appcode={% addcode= code after:
      \typeout{boxcapt out:  wd is: \the\wd\boxcapt, ht is: \the\ht\boxcapt, dp is: \the\dp\boxcapt ; skip1: \the\skip1; skip3: \the\skip3; glw,h: \the\globimw, \the\globimh; tw \the\textwidth}% all are here
      \break% same w/ \linebreak, \\ or \newline
      \usebox{\boxcapt}% place the caption, finally
      \vspace{\skip3}% this just to push everything "up" (i.e. left in rotated perspective), so "top" (left) edge of this figure aligns with the others - since \adjustbox does the rotation centered.
      \end{minipage}%
    },
    rotate=90,center%
  }% %closing of \begin{adjustbox}%
    %\includegraphics[width=\globimw,height=\globimh]{demo}% ok
    %\includegraphics[width=0.4\textwidth,height=0.3\textwidth]{demo}% ok
    % use temp "scratch" register to calculate new height (rotated: width) of image, to its original height minus half height of caption
    % skip1, 3 should be \global % http://tex.stackexchange.com/questions/29950/is-there-a-way-of-limiting-the-scope-of-newlength
    %\typeout{\width} % \width ! Undefined control sequence.
    \setlength{\skip3}{\dimexpr 0.5\dimexpr(\ht\boxcapt+\dp\boxcapt)}%
    \setlength{\skip1}{\dimexpr 0.3\textwidth-\skip3}%
    \global\skip1=\skip1% w. \global keeps val until next step
    \global\skip3=\skip3%
    \typeout{boxcapt inside:  wd is: \the\wd\boxcapt, ht is: \the\ht\boxcapt, dp is: \the\dp\boxcapt ; skip1: \the\skip1; skip3: \the\skip3; glw: \the\globimw}% boxcapt is now known
    \includegraphics[width=0.4\textwidth,height=\skip1]{demo}%
  \end{adjustbox}
\end{figure}

\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.