Cleveref and caption-based command – wrong references

captionscleverefcross-referencing

I am trying to create a caption command that highlights the first sentence and also uses the first sentence as a short caption for the table of *.
Let's call it kaption.
This is done using the listofitems package to split the caption of a figure/table/listing based on dots.
This works but in combination with cleveref, cleveref is unable to generate the correct reference and instead uses the label of the current hierarchy (eg section).

Why is cleveref unable to find the correct reference? And how can I fix this?

Manually altering the captions is not a viable solution as my book has more than 100 images and tables, thus, I want to use a somehow automatic way.

MWE:

\documentclass{article}

\usepackage{cleveref}
\usepackage{graphicx}
\usepackage{ifthen}
\usepackage{listofitems}

\newcommand*{\kaption}[1]{
  \setsepchar{.}\readlist*\pdots{#1}{
    \caption[{\pdots[1]}]
    {%
      \foreachitem\sentence\in\pdots{
      \ifnum \sentencecnt = 1
        {\textbf{\pdots[\sentencecnt].}}
      \else 
        {\pdots[\sentencecnt]\pdotssep[\sentencecnt]}
      \fi
    }}
  }
}

\begin{document}

\section{With caption}

Reference fig 1 before: \cref{fig1}

\begin{figure}[h]
    \centering
    \includegraphics[height=2cm]{example-image-a}
    \caption{Caption with one sentence. And another sentence of course.}
    \label{fig1}
\end{figure}

Reference fig 1 after: \cref{fig1}

\section{With kaption}

Reference fig 2 before: \cref{fig2}

\begin{figure}[h]
    \centering
    \includegraphics[height=2cm]{example-image-a}
    \kaption{Caption with one sentence. And another sentence of course.}
    \label{fig2}
\end{figure}

Reference fig 2 after: \cref{fig2}

\end{document}

mwe

Best Answer

The issue you've encountered is not limited to \cref: it crops up with the basic \ref macro as well.

@daleif has already identified the issue: The \readlist*\pdots{#1}{...} material forms a group, and what's inside that group is not visible to the \label instruction. The only thing that LaTeX can do in this situation is to associate the argument of \label with the most recently incremented visible counter, which happens to be the section counter. Not good.

A klugdy fix would be to change

\newcommand*{\kaption}[1]{
  \setsepchar{.}\readlist*\pdots{#1}{

to

\newcommand*{\kaption}[1]{\refstepcounter{figure}
  \setsepchar{.}\readlist*\pdots{#1}{\addtocounter{figure}{-1}

If you plan to apply \kaption to table environments as well, you'll also need to create a separate macro -- called, say, \kaptiont -- just for tables. As I said, it's a kludge.


A non-kludgy solution is available if you are free to use LuaLaTeX to compile your document: the creation of a Lua function that modifies all instances of \caption{First. Last.} automatically to \caption[First.]{\textbf{First.} Last.}. To achieve this setup, it's necessary to assign this function to LuaTeX's process_input_buffer callback, so that it may act as a preprocessor on the input material, before TeX starts its usual processing.

Whitespace between \caption and its argument is allowed. If there's just one sentence in the caption, the entire caption is bolded in the body of the document. The only restrictive input requirement is that line breaks are not allowed either in the argument of \caption{ ... } or between \caption and its argument.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{graphicx}
\usepackage[colorlinks,allcolors=blue,linktocpage]{hyperref} % optional
\usepackage[nameinlink]{cleveref}

\usepackage{luacode} % for 'luacode' environment
\begin{luacode}
function modify_captions ( s )
  s = s:gsub ('\\caption%s*(%b{})' , function ( x )
             -- strip off opening and closing curly braces:
             x = x:sub ( 2 , -2 ) 
             -- find location of first 'dot' followed by space:
             dot = x:find ( '%.%s' )
             if dot then
               first = x:sub ( 1 , dot )
               last  = x:sub ( dot+1 )
               return ( '\\caption[' .. first .. ']{\\textbf{' .. first .. '}' .. last ..'}' )
             else
               return ( '\\caption[' .. x .. ']{\\textbf{' .. x .. '}}' )
             end
           end )
  return ( s )
end
\end{luacode}
% assign 'modify_captions' to the 'process_input_buffer' callback:
\AtBeginEnvironment{\directlua{luatexbase.add_to_callback ( 
  'process_input_buffer', modify_captions, 'modify_captions' )}}
   
\begin{document}
\listoffigures
\medskip\hrule

\addtocounter{figure}{5} % just for this example

\begin{figure}[h]
\centering
    \includegraphics[height=2cm]{example-image-a}
    \caption{Caption with one sentence. And another sentence.}
    \label{figA}
\end{figure}

\noindent
Cross-references: \cref{figA}\quad\ref{figA}
\end{document}
Related Question