Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LaTeX: fix footnote marks for multiple references #10191

Merged
merged 5 commits into from Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -86,6 +86,8 @@ Bugs fixed
bulding texinfo document
* #10000: LaTeX: glossary terms with common definition are rendered with
too much vertical whitespace
* #10188: LaTeX: alternating multiply referred footnotes produce a ``?`` in
pdf output
* #10318: ``:prepend:`` option of :rst:dir:`literalinclude` directive does not
work with ``:dedent:`` option

Expand Down
13 changes: 3 additions & 10 deletions sphinx/texinputs/sphinx.sty
Expand Up @@ -298,18 +298,11 @@
%% FOOTNOTES
%
% Support scopes for footnote numbering
% This is currently stepped at each input file
\newcounter{sphinxscope}
\newcommand{\sphinxstepscope}{\stepcounter{sphinxscope}}
% Some footnotes are multiply referred-to. For unique hypertarget in pdf,
% we need an additional counter. It is called "sphinxexplicit" for legacy
% reasons as "explicitly" numbered footnotes may be multiply referred-to.
\newcounter{sphinxexplicit}
\newcommand{\sphinxstepexplicit}{\stepcounter{sphinxexplicit}}
% Some babel/polyglossia languages fiddle with \@arabic, so let's be extra
% cautious and redefine \thesphinxscope with \number not \@arabic.
% Memo: we expect some subtle redefinition of \thesphinxscope to be a part of page
% scoping for footnotes, when we shall implement it.
\renewcommand{\thesphinxscope}{\number\value{sphinxscope}.\number\value{sphinxexplicit}}
% We ensure \thesphinxscope expands to digits tokens, independently of language
\renewcommand{\thesphinxscope}{\number\value{sphinxscope}}
\newcommand\sphinxthefootnotemark[2]{%
% this is used to make reference to an explicitly numbered footnote not on same page
% #1=label of footnote text, #2=page number where footnote text was printed
Expand Down
4 changes: 4 additions & 0 deletions sphinx/texinputs/sphinxlatexobjects.sty
Expand Up @@ -137,7 +137,11 @@
\pysigadjustitemsep
}
\newcommand{\pysiglinewithargsret}[3]{%
% as #1 may contain a footnote using \label we need to make \label
% a no-op here to avoid LaTeX complaining about duplicates
\let\spx@label\label\let\label\@gobble
\settowidth{\py@argswidth}{#1\sphinxcode{(}}%
\let\label\spx@label
\py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax
\item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}]
\pysigadjustitemsep
Expand Down
162 changes: 99 additions & 63 deletions sphinx/texinputs/sphinxpackagefootnote.sty
@@ -1,27 +1,37 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{sphinxpackagefootnote}%
[2021/02/04 v1.1d footnotehyper adapted to sphinx (Sphinx team)]
% Provides support for this output mark-up from Sphinx latex writer:
% - footnote environment
% - savenotes environment (table templates)
% - \sphinxfootnotemark
%
[2022/02/12 v4.5.0 Sphinx custom footnotehyper package (Sphinx team)]
%%
%% Package: sphinxpackagefootnote
%% Version: based on footnotehyper.sty 2021/02/04 v1.1d
%% as available at https://www.ctan.org/pkg/footnotehyper
%% https://www.ctan.org/pkg/footnotehyper
%% License: the one applying to Sphinx
%%
%% Refer to the PDF documentation at https://www.ctan.org/pkg/footnotehyper for
%% the code comments.
% Provides support for footnote mark-up from Sphinx latex writer:
% - "footnote" and "footnotetext" environments allowing verbatim material
% - "savenotes" environment for wrapping environments, such as for tables
% which have problems with LaTeX footnotes
% - hyperlinks
%
% Sphinx uses exclusively this mark-up for footnotes:
% - \begin{footnote}[N]
% - \begin{footnotetext}[N]
% - \sphinxfootnotemark[N]
% where N is a number.
%
%% Some small differences from upstream footnotehyper.sty:
%% - a tabulary compatibility layer (partial but enough for Sphinx),
%% - usage of \spx@opt@BeforeFootnote
%% - usage of \sphinxunactivateextrasandspace from sphinx.sty,
%% - \sphinxlongtablepatch
%%
%% Starting with Sphinx v4.5.0, inherited footnotehyper macros for
%% footnote/footnotetext receive some Sphinx specific extras to
%% implement "intelligent" footnote marks checking page numbers.
%%
%% Differences:
%% 1. a partial tabulary compatibility layer added (enough for Sphinx mark-up),
%% 2. use of \spx@opt@BeforeFootnote from sphinx.sty,
%% 3. use of \sphinxunactivateextrasandspace from sphinx.sty,
%% 4. macro definition \sphinxfootnotemark,
%% 5. macro definition \sphinxlongtablepatch
%% 6. replaced some \undefined by \@undefined
%% All footnotes output from Sphinx are hyperlinked. With "savenotes"
%% footnotes may appear on page distinct from footnote mark, the latter
%% will indicate page number of the footnote.
\newif\iffootnotehyperparse\footnotehyperparsetrue
\DeclareOption*{\PackageWarning{sphinxpackagefootnote}{Option `\CurrentOption' is unknown}}%
\ProcessOptions\relax
Expand All @@ -42,6 +52,7 @@
\let\footnotetext \FNH@footnotetext
\let\endfootnote \FNH@endfntext
\let\endfootnotetext\FNH@endfntext
% always True branch taken with Sphinx
\@ifpackageloaded{hyperref}
{\ifHy@hyperfootnotes
\let\FNH@H@@footnotetext\H@@footnotetext
Expand Down Expand Up @@ -175,12 +186,40 @@
}%
\def\FNH@footnoteenv@i[#1]{%
\begingroup
% This legacy code from LaTeX core restricts #1 to be digits only
% This limitation could be lifted but legacy Sphinx anyhow obeys it
\csname c@\@mpfn\endcsname #1\relax
\unrestored@protected@xdef\@thefnmark{\thempfn}%
\endgroup
% -- Sphinx specific:
% currently commented out due to
% https://github.com/sphinx-doc/sphinx/pull/10191#issuecomment-1038807448
% Memo: memoir class detection of successive footnote marks (to separate them
% by commas) is broken by \refstepcounter and also by \label, and some
% mitigation such as in \sphinxfootref would be needed
% \global\let\spx@saved@thefnmark\@thefnmark
% % this is done to access robustly the page number where footnote mark is
% \refstepcounter{sphinxfootnotemark}\label{footnotemark.\thesphinxfootnotemark}%
% % if possible, compare page numbers of mark and footnote to define \@thefnmark
% \ltx@ifundefined{r@\thesphinxscope.footnote.#1}%
% {}% one more latex run is needed
% {\sphinx@xdef@thefnmark{#1}}% check of page numbers possible
% --
\@footnotemark
\def\FNH@endfntext@fntext{\@footnotetext}%
% -- Sphinx specific:
% we need to reset \@thefnmark as it is used by \FNH@startfntext via
% \FNH@startnote to set \@currentlabel which will be used by \label
% currently commented out (see above)
% \global\let\@thefnmark\spx@saved@thefnmark
% --
\FNH@startfntext
% -- again Sphinx specific
% \@currentlabel as needed by \label got set by \FNH@startnote
% insert this at start of footnote text then the label will allow
% to robustly know on which page the footnote text ends up
% currently only of use for extra footnote marks so in case footnote multiply referred
\phantomsection\label{\thesphinxscope.footnote.#1}%
}%
\def\FNH@footnotetext{%
\ifx\@currenvir\FNH@footnotetext@envname
Expand All @@ -207,6 +246,8 @@
\def\FNH@endfntext@fntext{\FNH@H@@footnotetext}%
\fi
\FNH@startfntext
% -- Sphinx specific addition
\phantomsection\label{\thesphinxscope.footnote.#1}%
}%
\def\FNH@startfntext{%
\setbox\z@\vbox\bgroup
Expand Down Expand Up @@ -329,60 +370,55 @@
}%
%
% some extras for Sphinx :
% \sphinxfootnotemark: usable in section titles and silently removed from TOCs.
% \sphinxfootnotemark:
% - if in section titles will auto-remove itself from TOC
\def\sphinxfootnotemark [#1]%
{\ifx\thepage\relax\else\sphinxfootref{#1}\fi}%
% \sphinxfootref:
% - \spx@opt@BeforeFootnote is from BeforeFootnote sphinxsetup option
% - \ref:
% the latex.py writer inserts a \phantomsection\label{<scope>.<num>}
% whenever
% - the footnote was explicitly numbered in sources,
% - or it was in restrained context and is rendered using footnotetext
%
% These are the two types of footnotes that \sphinxfootnotemark must
% handle. But for explicitly numbered footnotes the same number
% can be found in document. So a secondary part in <scope> is updated
% at each novel such footnote to know what is the target from then on
% for \sphinxfootnotemark and already encountered [1], or [2],...
%
% LaTeX package varioref is not supported by hyperref (from its doc: "There
% are too many problems with varioref. Nobody has time to sort them out.
% Therefore this package is now unsupported.") So we will simply use our own
% macros to access the page number of footnote text and decide whether to print
% it. \pagename is internationalized by latex-babel.
\def\spx@thefnmark#1#2{%
% #1=label for reference, #2=page where footnote was printed
\ifx\spx@tempa\spx@tempb
% same page
#1%
\else
\sphinxthefootnotemark{#1}{#2}%
\fi
\newcounter{sphinxfootnotemark}
\renewcommand\thesphinxfootnotemark{\number\value{sphinxfootnotemark}}
% - compares page number of footnote mark versus the one of footnote text
\def\sphinx@xdef@thefnmark#1{%
\expandafter\expandafter\expandafter\sphinx@footref@get
\csname r@\thesphinxscope.footnote.#1\endcsname\relax
\expandafter\expandafter\expandafter\sphinx@footmark@getpage
\csname r@footnotemark.\thesphinxfootnotemark\endcsname\thepage\relax
\protected@xdef\@thefnmark{%
\ifx\spx@footmarkpage\spx@footrefpage
\spx@footreflabel
\else
% the macro \sphinxthefootnotemark is in sphinx.sty
\sphinxthefootnotemark{\spx@footreflabel}{\spx@footrefpage}%
\fi
}%
}%
\def\sphinx@footref@get #1#2#3#4#5\relax{%
\def\spx@footreflabel{#1}%
\def\spx@footrefpage {#2}%
\def\spx@footrefHref {#4}%
}%
\def\sphinxfootref@get #1#2#3#4#5\relax{%
\def\sphinxfootref@label{#1}%
\def\sphinxfootref@page {#2}%
\def\sphinxfootref@Href {#4}%
\def\sphinx@footmark@getpage #1#2#3\relax{%
\edef\spx@footmarkpage{#2}%
}%
\protected\def\sphinxfootref#1{% #1 always explicit number in Sphinx usage
\protected\def\sphinxfootref#1{% #1 always is explicit number in Sphinx
\spx@opt@BeforeFootnote
\ltx@ifundefined{r@\thesphinxscope.#1}%
{\gdef\@thefnmark{?}\H@@footnotemark}%
{\expandafter\expandafter\expandafter\sphinxfootref@get
\csname r@\thesphinxscope.#1\endcsname\relax
\edef\spx@tempa{\thepage}\edef\spx@tempb{\sphinxfootref@page}%
\protected@xdef\@thefnmark{\spx@thefnmark{\sphinxfootref@label}{\sphinxfootref@page}}%
\let\spx@@makefnmark\@makefnmark
\def\@makefnmark{%
\hyper@linkstart{link}{\sphinxfootref@Href}%
\spx@@makefnmark
% each of \refstepcounter and \label interferes with memoir class detection
% of successive footnote marks, so we move them to inside \@makefnmark
\let\spx@saved@makefnmark\@makefnmark
\ltx@ifundefined{r@\thesphinxscope.footnote.#1}%
{\gdef\@thefnmark{?}% on first LaTeX run
\refstepcounter{sphinxfootnotemark}\label{footnotemark.\thesphinxfootnotemark}%
}%
{\sphinx@xdef@thefnmark{#1}% also defines \spx@footrefHref
\def\@makefnmark{% will be used by \H@@footnotemark
\refstepcounter{sphinxfootnotemark}\label{footnotemark.\thesphinxfootnotemark}%
\hyper@linkstart{link}{\spx@footrefHref}%
\spx@saved@makefnmark
\hyper@linkend
}%
\H@@footnotemark
\let\@makefnmark\spx@@makefnmark
}%
}%
}%
\H@@footnotemark
\let\@makefnmark\spx@saved@makefnmark
}%
\AtBeginDocument{%
% let hyperref less complain
\pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}%
Expand Down
10 changes: 3 additions & 7 deletions sphinx/writers/latex.py
Expand Up @@ -814,16 +814,14 @@ def depart_rubric(self, node: Element) -> None:
def visit_footnote(self, node: Element) -> None:
self.in_footnote += 1
label = cast(nodes.label, node[0])
if 'referred' in node:
self.body.append(r'\sphinxstepexplicit ')
if self.in_parsed_literal:
self.body.append(r'\begin{footnote}[%s]' % label.astext())
else:
self.body.append('%' + CR)
self.body.append(r'\begin{footnote}[%s]' % label.astext())
if 'referred' in node:
self.body.append(r'\phantomsection'
r'\label{\thesphinxscope.%s}%%' % label.astext() + CR)
# TODO: in future maybe output a latex macro with backrefs here
pass
self.body.append(r'\sphinxAtStartFootnote' + CR)

def depart_footnote(self, node: Element) -> None:
Expand Down Expand Up @@ -1717,9 +1715,7 @@ def depart_footnotemark(self, node: Element) -> None:
def visit_footnotetext(self, node: Element) -> None:
label = cast(nodes.label, node[0])
self.body.append('%' + CR)
self.body.append(r'\begin{footnotetext}[%s]'
r'\phantomsection\label{\thesphinxscope.%s}%%'
% (label.astext(), label.astext()) + CR)
self.body.append(r'\begin{footnotetext}[%s]' % label.astext())
self.body.append(r'\sphinxAtStartFootnote' + CR)

def depart_footnotetext(self, node: Element) -> None:
Expand Down