gusbrs / zref-clever

Clever LaTeX cross-references based on zref
LaTeX Project Public License v1.3c
11 stars 4 forks source link

Using \zcref in section titles #24

Closed Enivex closed 7 months ago

Enivex commented 7 months ago

For obvious reasons, hyperref complains when \zcref is used in section titles

One way around this is \texorpdfstring{\zcref{some_theorem}}{Theorem~\ref{some_theorem}}}, however this defeats part of the reason for using zref-clever in the first place.

There are some workarounds I've seen, but they all revolve around cleveref. For instance

\usepackage{crossreftools}
\pdfstringdefDisableCommands{%
    \let\zcref\crtCref
}

However, this only works if cleveref is also loaded.

Is there a better way to solve this?

gusbrs commented 7 months ago

For obvious reasons, hyperref complains when \zcref is used in section titles

I'd say expected indeed. But, can you please provide a MWE of the issue? (It spares me the guesswork and the rework. If at all possible, you should always try to provide one when reporting).

There are some workarounds I've seen, but they all revolve around cleveref.

Could you provide links to them?

Enivex commented 7 months ago

I'd say expected indeed. But, can you please provide a MWE of the issue? (It spares me the guesswork and the rework. If at all possible, you should always try to provide one when reporting).

My bad

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}

\begin{document}

\section{Hello}
\label{first}

\section{World \zcref{first}}

\end{document}

will give the following error

Token not allowed in a PDF string (Unicode):
(hyperref)  removing `\zcref'.

and the pdf bookmark will be World first instead of World section 1

However

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}

\usepackage{cleveref}
\usepackage{crossreftools}
\pdfstringdefDisableCommands{%
    \let\zcref\crtcref
}

\begin{document}

\section{Hello}
\label{first}

\section{World \zcref{first}}

\end{document}

gives the desired output

Could you provide links to them?

https://ctan.org/pkg/crossreftools?lang=en provides expandable versions of cleveref commands

I found the workaround in https://github.com/latex3/hyperref/issues/148#issuecomment-660202930

gusbrs commented 7 months ago

I'm not sure the use case crossreftools had in mind in offering \crtcref and \crtCref but, in my view, using them to do:

\pdfstringdefDisableCommands{%
    \let\Cref\crtCref
    \let\cref\crtcref
}

is a mistake. Because \cref and \Cref can receive a comma separated list of labels, while \crtcref and \crtCref can only receive a single label as argument. So, if you happen to try:

\documentclass{article}

\usepackage{hyperref}

\usepackage{cleveref}
\usepackage{crossreftools}
\pdfstringdefDisableCommands{%
    \let\cref\crtcref
}

\begin{document}

\section{Hello}
\label{first}

\section{Foo}
\label{second}

\section{World \cref{first,second}}

\end{document}

You get:

Screenshot from 2024-01-18 14-57-56

And the error is completely silent, since we cannot even issue a warning there, given the macros need to be expandable.

Also, as documented in crossreftools, besides the fact it can't handle lists of labels, \crtcref and \crtCref are not fully general replacements to \cref and \Cref since it ignores \crefformat etc. and only retrieve the \crefname of the counter for the label. So \crtcref may differ from \cref even for a single label.

This point is valid for both cleveref and zref-clever. The problem is the same. And I don't think it is possible to provide a fully general expandable version of either \cref or \zcref. So, I don't think it is an oversight of cleveref not to try to provide such macros. If crossreftools decided to provide some half-baked ones regardless, I personally still don't understand the intended use cases for them. And, as I said above, using them for this particular use case is, imho, a bad idea.

The case is actually worse for \zcref because it has an optional argument which \crtcref does not. So if you do \let\zcref\crtcref, as soon as you do something like \section{\zcref[S]{first} bar}, you get a broken document.

My recommendation for this is to take the traditional approach, and use \texorpdfstring, constructing the reference manually. Like so:

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}

\begin{document}

\section{Hello}
\label{first}

\section{Foo \texorpdfstring{\zcref{first}}{section~\ref{first}}}

\end{document}

(Depending on the case, you may also use the short title/optional arg of the sectioning command. Just, as you know, it affects not only the PDF bookmarks but also things like ToC etc. It depends on what you want.)

You say "this defeats part of the reason for using zref-clever in the first place". I'd say hardly. It is indeed a known limitation, and working around it requires a little manual work on the occasional use of \zcref in a section title. Not a significant dent in the grand scheme of things.

If you really, really want to avoid this manual work, you may get away by leveraging \autoref for your bookmarks with something like:

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}
\NewExpandableDocumentCommand{\myautoref}{O{}m}{\autoref{#2}}
\pdfstringdefDisableCommands{%
  \let\zcref\myautoref
}

\begin{document}

\section{Hello}
\label{first}

\section{World \zcref{first}}

\end{document}

However, this is subject to the same fundamental problem as using \let\cref\crtcref with cleveref, plus any differences of output between \zcref and \autoref (Well, \crtcref is arguably closer to \autoref than to \cref anyway...). But, you should get reasonable results most of the time. Caveat emptor though, of course.

Naturally, zref-clever does store the name(s) for a given reference type somewhere, and you can (expandably) retrieve the type of a given label with zref tools. So it is technically viable to build something like \crtcref for zref-clever. (Though there are some subtle differences, e.g.: i) the case is not controlled by the main macro but by an option, which you would not have access to; ii) the name of the variable which stores the reference type name may depend on the declension cases defined for a given language, etc.). Anyway, this is neither recommended, nor supported, so I won't post this here. Of course, as you know, (La)TeX allows you to dig the code and do it regardless.

PS: @muzimuzhi Since you posted the crossreftools approach at the hyperref issue tracker, you may be interested on my take on its risks above. :wink: (Don't worry about it, I'm really just pinging because you may be interested).

Edit: A little trick to get a warning in case of multiple labels (at the cost of having to use a dedicated helper macro):

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}
\NewExpandableDocumentCommand{\myzcrefsection}{O{}m}{%
  \texorpdfstring{\refused{#2}\zcref[#1]{#2}}{\autoref{#2}}}

\begin{document}

\section{Foo}
\label{first}

\section{Bar}
\label{second}

% OK
\section{Baz \myzcrefsection{first}}

% Not OK, but at least generates unresolved references warning.
\section{Baz \myzcrefsection{first,second}}

\end{document}
Enivex commented 7 months ago

Thanks for the exhaustive reply. I'll probably end up just sticking with \texorpdfstring. (I know only just about enough to be dangerous.)

You say "this defeats part of the reason for using zref-clever in the first place". I'd say hardly. It is indeed a known limitation, and working around it requires a little manual work on the occasional use of \zcref in a section title. Not a significant dent in the grand scheme of things.

This is true. I was exaggerating for effect.

It's just unfortunate that such a seemingly easy (for the user) thing is so hard to do in LaTeX. I seems like a thing that "should" be possible.

gusbrs commented 7 months ago

I'll probably end up just sticking with \texorpdfstring.

I think that's the wisest. I do get your mumble, but I think you'll get used to it in no time, and it shouldn't bother you much in your actual document. And, even if you occasionally need to set up a cross-ref manually for the bookmark, the actually typeset material can always be kept perfectly consistent with the use of \zcref.

It's just unfortunate that such a seemingly easy (for the user) thing is so hard to do in LaTeX. I seems like a thing that "should" be possible.

The problem well known and low level, but not that low. I'd call it a limitation of how hyperref handles bookmarks rather than a "LaTeX limitation". Consider, for example, the following document to generate a tagged PDF:

\DocumentMetadata{testphase=phase-III}

\documentclass{article}

\usepackage{zref-clever}
\usepackage{hyperref}

\begin{document}

\section{Hello}
\label{first}

\section{Foo \texorpdfstring{\zcref{first}}{section~\ref{first}}}

\end{document}

And open the resunting PDF at https://ngpdf.com/loadFile (to inspect the HTML extracted from the tagged structure). You get:

Screenshot from 2024-01-19 15-38-50

I take from there LaTeX can, in principle, already "see the text generated by protected/non-expandable material and send it to the PDF". (Says the person who has no real idea of how the bookmarks are created... :wink:, so take it with a grain of salt). So things may evolve eventually (of course, the time scale in which things move in the LaTeX world might not be to your liking...).

Thanks for the exhaustive reply.

Thanks for taking the time to report. Cheers! :smile: