summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjfbu <jfbu@free.fr>2021-02-09 18:51:40 +0100
committerjfbu <jfbu@free.fr>2021-02-09 19:03:38 +0100
commitb9c53c8ca61d642ee0339f13c0c67134d35f19a8 (patch)
treeb6f92bd67af01f45990ce1c79c67b75768b24ac0
parent66aa22f1ea9ef79034812c801432ba152ad96b41 (diff)
parent15209223ac105820c1057398e15b42b90307cdfd (diff)
downloadsphinx-git-b9c53c8ca61d642ee0339f13c0c67134d35f19a8.tar.gz
Merge branch '3.x' into merge_3.x_into_master
Resolved Conflicts: sphinx/texinputs/sphinx.sty Some hunks needed to go to sphinx/texinputs/sphinxlatexliterals.py, others already belonged to split files.
-rw-r--r--CHANGES8
-rw-r--r--doc/latex.rst101
-rw-r--r--sphinx/texinputs/sphinx.sty25
-rw-r--r--sphinx/texinputs/sphinxlatexliterals.sty181
-rw-r--r--sphinx/texinputs/sphinxpackagefootnote.sty54
-rw-r--r--sphinx/writers/latex.py9
-rw-r--r--tests/test_build_latex.py83
7 files changed, 416 insertions, 45 deletions
diff --git a/CHANGES b/CHANGES
index fa08b237f..ad5961718 100644
--- a/CHANGES
+++ b/CHANGES
@@ -199,8 +199,13 @@ Bugs fixed
* #8683: :confval:`html_last_updated_fmt` generates wrong time zone for %Z
* #1112: ``download`` role creates duplicated copies when relative path is
specified
+* #2616 (fifth item): LaTeX: footnotes from captions are not clickable,
+ and for manually numbered footnotes only first one with same number is
+ an hyperlink
* #7576: LaTeX with French babel and memoir crash: "Illegal parameter number
in definition of ``\FNH@prefntext``"
+* #8055: LaTeX (docs): A potential display bug with the LaTeX generation step
+ in Sphinx (how to generate one-column index)
* #8072: LaTeX: Directive :rst:dir:`hlist` not implemented in LaTeX
* #8214: LaTeX: The :rst:role:`index` role and the glossary generate duplicate
entries in the LaTeX index (if both used for same term)
@@ -213,6 +218,9 @@ Bugs fixed
* #8780: LaTeX: long words in narrow columns may not be hyphenated
* #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be
bracketed, not braced (and is anyhow not needed)
+* #8849: LaTex: code-block printed out of margin (see the opt-in LaTeX syntax
+ boolean :ref:`verbatimforcewraps <latexsphinxsetupforcewraps>` for use via
+ the :ref:`'sphinxsetup' <latexsphinxsetup>` key of ``latex_elements``)
Testing
--------
diff --git a/doc/latex.rst b/doc/latex.rst
index d6ae2fee6..b736c56ef 100644
--- a/doc/latex.rst
+++ b/doc/latex.rst
@@ -469,11 +469,22 @@ Keys that don't need to be overridden unless in special cases are:
.. versionchanged:: 1.6
Remove unneeded ``{}`` after ``\\hrule``.
+``'makeindex'``
+ "makeindex" call, the last thing before ``\begin{document}``. With
+ ``'\\usepackage[columns=1]{idxlayout}\\makeindex'`` the index will use
+ only one column. You may have to install ``idxlayout`` LaTeX package.
+
+ Default: ``'\\makeindex'``
+
``'printindex'``
"printindex" call, the last thing in the file. Override if you want to
- generate the index differently or append some content after the index. For
- example ``'\\footnotesize\\raggedright\\printindex'`` is advisable when the
- index is full of long entries.
+ generate the index differently, append some content after the index, or
+ change the font. As LaTeX uses two-column mode for the index it is
+ often advisable to set this key to
+ ``'\\footnotesize\\raggedright\\printindex'``. Or, to obtain a one-column
+ index, use ``'\\def\\twocolumn[#1]{#1}\\printindex'`` (this trick may fail
+ if using a custom document class; then try the ``idxlayout`` approach
+ described in the documentation of the ``'makeindex'`` key).
Default: ``'\\printindex'``
@@ -505,7 +516,6 @@ Keys that are set by other options and therefore should not be overridden are:
``'title'``
``'release'``
``'author'``
-``'makeindex'``
.. _latexsphinxsetup:
@@ -563,7 +573,9 @@ The below is included at the end of the chapter::
\endgroup
-LaTeX boolean keys require *lowercase* ``true`` or ``false`` values.
+LaTeX syntax for boolean keys requires *lowercase* ``true`` or ``false``
+e.g ``'sphinxsetup': "verbatimwrapslines=false"``. If setting the
+boolean key to ``true``, ``=true`` is optional.
Spaces around the commas and equal signs are ignored, spaces inside LaTeX
macros may be significant.
@@ -614,14 +626,68 @@ macros may be significant.
Boolean to specify if long lines in :rst:dir:`code-block`\ 's contents are
wrapped.
+ If ``true``, line breaks may happen at spaces (the last space before the
+ line break will be rendered using a special symbol), and at ascii
+ punctuation characters (i.e. not at letters or digits). Whenever a long
+ string has no break points, it is moved to next line. If its length is
+ longer than the line width it will overflow.
+
Default: ``true``
-``literalblockcappos``
- Decides the caption position: either ``b`` ("bottom") or ``t`` ("top").
+.. _latexsphinxsetupforcewraps:
- Default: ``t``
+``verbatimforcewraps``
+ Boolean to specify if long lines in :rst:dir:`code-block`\ 's contents
+ should be forcefully wrapped to never overflow due to long strings.
- .. versionadded:: 1.7
+ .. note::
+
+ It is assumed that the Pygments_ LaTeXFormatter has not been used with
+ its ``texcomments`` or similar options which allow additional
+ (arbitrary) LaTeX mark-up.
+
+ Also, in case of :confval:`latex_engine` set to ``'pdflatex'``, only
+ the default LaTeX handling of Unicode code points, i.e. ``utf8`` not
+ ``utf8x`` is allowed.
+
+ .. _Pygments: https://pygments.org/
+
+ Default: ``false``
+
+ .. versionadded:: 3.5.0
+
+``verbatimmaxoverfull``
+ A number. If an unbreakable long string has length larger than the total
+ linewidth plus this number of characters, and if ``verbatimforcewraps``
+ mode is on, the input line will be reset using the forceful algorithm
+ which applies breakpoints at each character.
+
+ Default: ``3``
+
+ .. versionadded:: 3.5.0
+
+``verbatimmaxunderfull``
+ A number. If ``verbatimforcewraps`` mode applies, and if after applying
+ the line wrapping at spaces and punctuation, the first part of the split
+ line is lacking at least that number of characters to fill the available
+ width, then the input line will be reset using the forceful algorithm.
+
+ As the default is set to a high value, the forceful algorithm is triggered
+ only in overfull case, i.e. in presence of a string longer than full
+ linewidth. Set this to ``0`` to force all input lines to be hard wrapped
+ at the current avaiable linewidth::
+
+ latex_elements = {
+ 'sphinxsetup': "verbatimforcewraps, verbatimmaxunderfull=0",
+ }
+
+ This can be done locally for a given code-block via the use of raw latex
+ directives to insert suitable ``\sphinxsetup`` (before and after) into the
+ latex file.
+
+ Default: ``100``
+
+ .. versionadded:: 3.5.0
``verbatimhintsturnover``
Boolean to specify if code-blocks display "continued on next page" and
@@ -687,10 +753,10 @@ macros may be significant.
Default: ``{rgb}{0.126,0.263,0.361}``
-.. warning::
+ .. warning::
- Colours set via ``'sphinxsetup'`` must obey the syntax of the
- argument of the ``color/xcolor`` packages ``\definecolor`` command.
+ Colours set via ``'sphinxsetup'`` must obey the syntax of the
+ argument of the ``color/xcolor`` packages ``\definecolor`` command.
``InnerLinkColor``
A colour passed to ``hyperref`` as value of ``linkcolor`` and
@@ -721,10 +787,10 @@ macros may be significant.
.. versionadded:: 1.6.6
-.. note::
+ .. note::
- Starting with this colour key, and for all others coming next, the actual
- names declared to "color" or "xcolor" are prefixed with "sphinx".
+ Starting with this colour, and for all others following, the
+ names declared to "color" or "xcolor" are prefixed with "sphinx".
``verbatimsep``
The separation between code lines and the frame.
@@ -812,9 +878,8 @@ macros may be significant.
``attentionBorderColor``, ``dangerBorderColor``,
``errorBorderColor``
-.. |wgbdcolorslatex| replace:: ``warningBorderColor``, ``cautionBorderColor``,
- ``attentionB..C..``, ``dangerB..C..``,
- ``errorB..C..``
+.. |wgbdcolorslatex| replace:: ``warningBorderColor``, and
+ ``(caution|attention|danger|error)BorderColor``
.. else latex goes into right margin, as it does not hyphenate the names
diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty
index 6b7e5dcce..9edde8498 100644
--- a/sphinx/texinputs/sphinx.sty
+++ b/sphinx/texinputs/sphinx.sty
@@ -92,6 +92,9 @@
% verbatim
\DeclareBoolOption[true]{verbatimwithframe}
\DeclareBoolOption[true]{verbatimwrapslines}
+\DeclareBoolOption[false]{verbatimforcewraps}
+\DeclareStringOption[3]{verbatimmaxoverfull}
+\DeclareStringOption[100]{verbatimmaxunderfull}
\DeclareBoolOption[true]{verbatimhintsturnover}
\DeclareBoolOption[true]{inlineliteralwraps}
\DeclareStringOption[t]{literalblockcappos}
@@ -298,6 +301,28 @@
%% FOOTNOTES
%
+% Support scopes for footnote numbering
+\newcounter{sphinxscope}
+\newcommand{\sphinxstepscope}{\stepcounter{sphinxscope}}
+% Explictly numbered footnotes may be referred to, and for this to be
+% clickable we need to have only one target. So we will step this at each
+% explicit footnote and let \thesphinxscope take it into account
+\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}}
+\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
+ \ifdefined\pagename
+ \pagename\space#2, % <- space
+ \else
+ p. #2, % <- space
+ \fi #1% no space
+}
% support large numbered footnotes in minipage; but this is now obsolete
% from systematic use of savenotes environment around minipages
\def\thempfootnote{\arabic{mpfootnote}}
diff --git a/sphinx/texinputs/sphinxlatexliterals.sty b/sphinx/texinputs/sphinxlatexliterals.sty
index bda032bca..3aed425d7 100644
--- a/sphinx/texinputs/sphinxlatexliterals.sty
+++ b/sphinx/texinputs/sphinxlatexliterals.sty
@@ -268,13 +268,188 @@
% no need to restore \fboxsep here, as this ends up in a \hbox from fancyvrb
}%
% \sphinxVerbatimFormatLine will be set locally to one of those two:
-\newcommand\sphinxVerbatimFormatLineWrap[1]{%
- \hsize\linewidth
+\newcommand\sphinxVerbatimFormatLineWrap{%
+ \hsize\linewidth
+ \ifspx@opt@verbatimforcewraps
+ \expandafter\spx@verb@FormatLineForceWrap
+ \else\expandafter\spx@verb@FormatLineWrap
+ \fi
+}%
+\newcommand\sphinxVerbatimFormatLineNoWrap[1]{\hb@xt@\linewidth{\strut #1\hss}}%
+\long\def\spx@verb@FormatLineWrap#1{%
\vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@
\doublehyphendemerits\z@\finalhyphendemerits\z@
\strut #1\strut}%
}%
-\newcommand\sphinxVerbatimFormatLineNoWrap[1]{\hb@xt@\linewidth{\strut #1\hss}}%
+%
+% The normal line wrapping allows breaks at spaces and ascii non
+% letters, non digits. The \raggedright above means there will be
+% an overfilled line only if some non-breakable "word" was
+% encountered, which is longer than a line (it is moved always to
+% be on its own on a new line).
+%
+% The "forced" line wrapping will parse the tokens to add potential
+% breakpoints at each character. As some strings are highlighted,
+% we have to apply the highlighting character per character, which
+% requires to manipulate the output of the Pygments LaTeXFormatter.
+%
+% Doing this at latex level is complicated. The contents should
+% be as expected: i.e. some active characters from
+% \sphinxbreaksviaactive, some Pygments character escapes such as
+% \PYGZdl{}, and the highlighting \PYG macro with always 2
+% arguments. No other macros should be there, except perhaps
+% zero-parameter macros. In particular:
+% - the texcomments Pygments option must be set to False
+%
+% With pdflatex, Unicode input gives multi-bytes characters
+% where the first byte is active. We support the "utf8" macros
+% only. "utf8x" is not supported.
+%
+% The highlighting macro \PYG will be applied character per
+% character. Highlighting via a colored background gives thus a
+% chain of small colored boxes which may cause some artefact in
+% some pdf viewers. Can't do anything here if we do want the line
+% break to be possible.
+%
+% First a measurement step is done of what would the standard line
+% wrapping give (i.e line breaks only at spaces and non-letter,
+% non-digit ascii characters), cf TeX by Topic for the basic
+% dissecting technique: TeX unfortunately when building a vertical
+% box does not store in an accessible way what was the maximal
+% line-width during paragraph building.
+%
+% If the max width exceeds the linewidth by more than verbatimmaxoverfull
+% character widths, or if the min width plus verbatimmaxunderfull character
+% widths is inferior to linewidth, then we apply the "force wrapping" with
+% potential line break at each character, else we don't.
+\long\def\spx@verb@FormatLineForceWrap#1{%
+ % \spx@image@box is a scratch box register that we can use here
+ \global\let\spx@verb@maxwidth\z@
+ \global\let\spx@verb@minwidth\linewidth
+ \setbox\spx@image@box
+ \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@
+ \doublehyphendemerits\z@\finalhyphendemerits\z@
+ \strut #1\strut\@@par
+ \spx@verb@getwidths}%
+ \ifdim\spx@verb@maxwidth>
+ \dimexpr\linewidth+\spx@opt@verbatimmaxoverfull\fontcharwd\font`X \relax
+ \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}%
+ \else
+ \ifdim\spx@verb@minwidth<
+ \dimexpr\linewidth-\spx@opt@verbatimmaxunderfull\fontcharwd\font`X \relax
+ \spx@verb@FormatLineWrap{\spx@verb@wrapPYG #1\spx@verb@wrapPYG}%
+ \else
+ \spx@verb@FormatLineWrap{#1}%
+ \fi\fi
+}%
+% auxiliary paragraph dissector to get max and min widths
+\newbox\spx@scratchbox
+\def\spx@verb@getwidths {%
+ \unskip\unpenalty
+ \setbox\spx@scratchbox\lastbox
+ \ifvoid\spx@scratchbox
+ \else
+ \setbox\spx@scratchbox\hbox{\unhbox\spx@scratchbox}%
+ \ifdim\spx@verb@maxwidth<\wd\spx@scratchbox
+ \xdef\spx@verb@maxwidth{\number\wd\spx@scratchbox sp}%
+ \fi
+ \ifdim\spx@verb@minwidth>\wd\spx@scratchbox
+ \xdef\spx@verb@minwidth{\number\wd\spx@scratchbox sp}%
+ \fi
+ \expandafter\spx@verb@getwidths
+ \fi
+}%
+% auxiliary macros to implement "cut long line even in middle of word"
+\catcode`Z=3 % safe delimiter
+\def\spx@verb@wrapPYG{%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@i
+}%
+\def\spx@verb@wrapPYG@i{%
+ \ifx\spx@nexttoken\spx@verb@wrapPYG\let\next=\@gobble\else
+ \ifx\spx@nexttoken\PYG\let\next=\spx@verb@wrapPYG@PYG@onebyone\else
+ \discretionary{}{\sphinxafterbreak}{}%
+ \let\next\spx@verb@wrapPYG@ii
+ \fi\fi
+ \next
+}%
+% Let's recognize active characters. We don't support utf8x only utf8.
+% And here #1 should not have picked up (non empty) braced contents
+\long\def\spx@verb@wrapPYG@ii#1{%
+ \ifcat\noexpand~\noexpand#1\relax% active character
+ \expandafter\spx@verb@wrapPYG@active
+ \else % non-active character, control sequence such as \PYGZdl, or empty
+ \expandafter\spx@verb@wrapPYG@one
+ \fi {#1}%
+}%
+\long\def\spx@verb@wrapPYG@active#1{%
+% Let's hope expansion of active character does not really require arguments,
+% as we certainly don't want to go into expanding upfront token stream anyway.
+ \expandafter\spx@verb@wrapPYG@iii#1{}{}{}{}{}{}{}{}{}Z#1%
+}%
+\long\def\spx@verb@wrapPYG@iii#1#2Z{%
+ \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@four\else
+ \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@three\else
+ \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@two\else
+ \let\next=\spx@verb@wrapPYG@one
+ \fi\fi\fi
+ \next
+}%
+\long\def\spx@verb@wrapPYG@one #1{#1\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}%
+\long\def\spx@verb@wrapPYG@two #1#2{#1#2\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}%
+\long\def\spx@verb@wrapPYG@three #1#2#3{#1#2#3\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}%
+\long\def\spx@verb@wrapPYG@four #1#2#3#4{#1#2#3#4\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}%
+% Replace \PYG by itself applied one character at a time! This way breakpoints
+% can be inserted.
+\def\spx@verb@wrapPYG@PYG@onebyone#1#2#3{% #1 = \PYG, #2 = highlight spec, #3 = tokens
+ \def\spx@verb@wrapPYG@PYG@spec{{#2}}%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i#3Z%
+}%
+\def\spx@verb@wrapPYG@PYG@i{%
+ \ifx\spx@nexttokenZ\let\next=\spx@verb@wrapPYG@PYG@done\else
+ \discretionary{}{\sphinxafterbreak}{}%
+ \let\next\spx@verb@wrapPYG@PYG@ii
+ \fi
+ \next
+}%
+\def\spx@verb@wrapPYG@PYG@doneZ{\futurelet\spx@nexttoken\spx@verb@wrapPYG@i}%
+\long\def\spx@verb@wrapPYG@PYG@ii#1{%
+ \ifcat\noexpand~\noexpand#1\relax% active character
+ \expandafter\spx@verb@wrapPYG@PYG@active
+ \else % non-active character, control sequence such as \PYGZdl, or empty
+ \expandafter\spx@verb@wrapPYG@PYG@one
+ \fi {#1}%
+}%
+\long\def\spx@verb@wrapPYG@PYG@active#1{%
+% Let's hope expansion of active character does not really require arguments,
+% as we certainly don't want to go into expanding upfront token stream anyway.
+ \expandafter\spx@verb@wrapPYG@PYG@iii#1{}{}{}{}{}{}{}{}{}Z#1%
+}%
+\long\def\spx@verb@wrapPYG@PYG@iii#1#2Z{%
+ \ifx\UTFviii@four@octets#1\let\next=\spx@verb@wrapPYG@PYG@four\else
+ \ifx\UTFviii@three@octets#1\let\next=\spx@verb@wrapPYG@PYG@three\else
+ \ifx\UTFviii@two@octets#1\let\next=\spx@verb@wrapPYG@PYG@two\else
+ \let\next=\spx@verb@wrapPYG@PYG@one
+ \fi\fi\fi
+ \next
+}%
+\long\def\spx@verb@wrapPYG@PYG@one#1{%
+ \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1}%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i
+}%
+\long\def\spx@verb@wrapPYG@PYG@two#1#2{%
+ \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2}%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i
+}%
+\long\def\spx@verb@wrapPYG@PYG@three#1#2#3{%
+ \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3}%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i
+}%
+\long\def\spx@verb@wrapPYG@PYG@four#1#2#3#4{%
+ \expandafter\PYG\spx@verb@wrapPYG@PYG@spec{#1#2#3#4}%
+ \futurelet\spx@nexttoken\spx@verb@wrapPYG@PYG@i
+}%
+\catcode`Z 11%
+%
\g@addto@macro\FV@SetupFont{%
\sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}%
\sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}%
diff --git a/sphinx/texinputs/sphinxpackagefootnote.sty b/sphinx/texinputs/sphinxpackagefootnote.sty
index 529d24f33..a6071cf10 100644
--- a/sphinx/texinputs/sphinxpackagefootnote.sty
+++ b/sphinx/texinputs/sphinxpackagefootnote.sty
@@ -331,8 +331,58 @@
% some extras for Sphinx :
% \sphinxfootnotemark: usable in section titles and silently removed from TOCs.
\def\sphinxfootnotemark [#1]%
- {\ifx\thepage\relax\else\protect\spx@opt@BeforeFootnote
- \protect\footnotemark[#1]\fi}%
+ {\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
+}%
+\def\sphinxfootref@get #1#2#3#4#5\relax{%
+ \def\sphinxfootref@label{#1}%
+ \def\sphinxfootref@page {#2}%
+ \def\sphinxfootref@Href {#4}%
+}%
+\protected\def\sphinxfootref#1{% #1 always explicit number in Sphinx usage
+ \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
+ \hyper@linkend
+ }%
+ \H@@footnotemark
+ \let\@makefnmark\spx@@makefnmark
+ }%
+}%
\AtBeginDocument{%
% let hyperref less complain
\pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}%
diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py
index f7cde64d6..433535dce 100644
--- a/sphinx/writers/latex.py
+++ b/sphinx/writers/latex.py
@@ -844,11 +844,16 @@ class LaTeXTranslator(SphinxTranslator):
def visit_footnote(self, node: Element) -> None:
self.in_footnote += 1
label = cast(nodes.label, node[0])
+ if 'auto' not in node:
+ self.body.append('\\sphinxstepexplicit ')
if self.in_parsed_literal:
self.body.append('\\begin{footnote}[%s]' % label.astext())
else:
self.body.append('%\n')
self.body.append('\\begin{footnote}[%s]' % label.astext())
+ if 'auto' not in node:
+ self.body.append('\\phantomsection'
+ '\\label{\\thesphinxscope.%s}%%\n' % label.astext())
self.body.append('\\sphinxAtStartFootnote\n')
def depart_footnote(self, node: Element) -> None:
@@ -1746,7 +1751,9 @@ class LaTeXTranslator(SphinxTranslator):
label = cast(nodes.label, node[0])
self.body.append('%\n')
self.body.append('\\begin{footnotetext}[%s]'
- '\\sphinxAtStartFootnote\n' % label.astext())
+ '\\phantomsection\\label{\\thesphinxscope.%s}%%\n'
+ % (label.astext(), label.astext()))
+ self.body.append('\\sphinxAtStartFootnote\n')
def depart_footnotetext(self, node: Element) -> None:
# the \ignorespaces in particular for after table header use
diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py
index 63cca61ed..4c735952f 100644
--- a/tests/test_build_latex.py
+++ b/tests/test_build_latex.py
@@ -723,7 +723,8 @@ def test_footnote(app, status, warning):
print(result)
print(status.getvalue())
print(warning.getvalue())
- assert ('\\begin{footnote}[1]\\sphinxAtStartFootnote\nnumbered\n%\n'
+ assert ('\\sphinxstepexplicit %\n\\begin{footnote}[1]\\phantomsection'
+ '\\label{\\thesphinxscope.1}%\n\\sphinxAtStartFootnote\nnumbered\n%\n'
'\\end{footnote}') in result
assert ('\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n'
'\\end{footnote}') in result
@@ -731,9 +732,13 @@ def test_footnote(app, status, warning):
assert '\\sphinxcite{footnote:bar}' in result
assert ('\\bibitem[bar]{footnote:bar}\n\\sphinxAtStartPar\ncite\n') in result
assert '\\sphinxcaption{Table caption \\sphinxfootnotemark[4]' in result
- assert ('\\hline%\n\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
+ assert ('\\hline%\n\\begin{footnotetext}[4]'
+ '\\phantomsection\\label{\\thesphinxscope.4}%\n'
+ '\\sphinxAtStartFootnote\n'
'footnote in table caption\n%\n\\end{footnotetext}\\ignorespaces %\n'
- '\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
+ '\\begin{footnotetext}[5]'
+ '\\phantomsection\\label{\\thesphinxscope.5}%\n'
+ '\\sphinxAtStartFootnote\n'
'footnote in table header\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\n'
'VIDIOC\\_CROPCAP\n&\n\\sphinxAtStartPar\n') in result
@@ -759,19 +764,27 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
assert '\\subsubsection*{The rubric title with a reference to {[}AuthorYear{]}}' in result
assert ('\\chapter{The section with a reference to \\sphinxfootnotemark[5]}\n'
'\\label{\\detokenize{index:the-section-with-a-reference-to}}'
- '%\n\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
+ '%\n\\begin{footnotetext}[5]'
+ '\\phantomsection\\label{\\thesphinxscope.5}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote in section\n%\n\\end{footnotetext}') in result
assert ('\\caption{This is the figure caption with a footnote to '
'\\sphinxfootnotemark[7].}\\label{\\detokenize{index:id29}}\\end{figure}\n'
- '%\n\\begin{footnotetext}[7]\\sphinxAtStartFootnote\n'
+ '%\n\\begin{footnotetext}[7]'
+ '\\phantomsection\\label{\\thesphinxscope.7}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote in caption\n%\n\\end{footnotetext}') in result
assert ('\\sphinxcaption{footnote \\sphinxfootnotemark[8] in '
'caption of normal table}\\label{\\detokenize{index:id30}}') in result
assert ('\\caption{footnote \\sphinxfootnotemark[9] '
'in caption \\sphinxfootnotemark[10] of longtable\\strut}') in result
- assert ('\\endlastfoot\n%\n\\begin{footnotetext}[9]\\sphinxAtStartFootnote\n'
+ assert ('\\endlastfoot\n%\n\\begin{footnotetext}[9]'
+ '\\phantomsection\\label{\\thesphinxscope.9}%\n'
+ '\\sphinxAtStartFootnote\n'
'Foot note in longtable\n%\n\\end{footnotetext}\\ignorespaces %\n'
- '\\begin{footnotetext}[10]\\sphinxAtStartFootnote\n'
+ '\\begin{footnotetext}[10]'
+ '\\phantomsection\\label{\\thesphinxscope.10}%\n'
+ '\\sphinxAtStartFootnote\n'
'Second footnote in caption of longtable\n') in result
assert ('This is a reference to the code\\sphinxhyphen{}block in the footnote:\n'
'{\\hyperref[\\detokenize{index:codeblockinfootnote}]'
@@ -792,7 +805,9 @@ def test_latex_show_urls_is_inline(app, status, warning):
print(result)
print(status.getvalue())
print(warning.getvalue())
- assert ('Same footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Same footnote number \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result
@@ -806,12 +821,16 @@ def test_latex_show_urls_is_inline(app, status, warning):
'{\\sphinxcrossref{The section with a reference to }}}' in result)
assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result
- assert ('Second footnote: %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Second footnote: \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'Second\n%\n\\end{footnote}') in result
assert '\\sphinxhref{http://sphinx-doc.org/}{Sphinx} (http://sphinx\\sphinxhyphen{}doc.org/)' in result
assert ('Third footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[4]\n%\n\\end{footnote}%\n'
- '\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
+ '\\begin{footnotetext}[4]'
+ '\\phantomsection\\label{\\thesphinxscope.4}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote inside footnote\n%\n\\end{footnotetext}\\ignorespaces') in result
assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde} '
'(http://sphinx\\sphinxhyphen{}doc.org/\\textasciitilde{}test/)') in result
@@ -819,7 +838,9 @@ def test_latex_show_urls_is_inline(app, status, warning):
'(http://sphinx\\sphinxhyphen{}doc.org/)}] '
'\\leavevmode\n\\sphinxAtStartPar\nDescription' in result)
assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] '
- '\\leavevmode%\n\\begin{footnotetext}[6]\\sphinxAtStartFootnote\n'
+ '\\leavevmode%\n\\begin{footnotetext}[6]'
+ '\\phantomsection\\label{\\thesphinxscope.6}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist} '
@@ -840,7 +861,9 @@ def test_latex_show_urls_is_footnote(app, status, warning):
print(result)
print(status.getvalue())
print(warning.getvalue())
- assert ('Same footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Same footnote number \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result
@@ -853,14 +876,18 @@ def test_latex_show_urls_is_footnote(app, status, warning):
'{\\sphinxcrossref{The section with a reference to }}}') in result
assert ('First footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result
- assert ('Second footnote: %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Second footnote: \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'Second\n%\n\\end{footnote}') in result
assert ('\\sphinxhref{http://sphinx-doc.org/}{Sphinx}'
'%\n\\begin{footnote}[4]\\sphinxAtStartFootnote\n'
'\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n\\end{footnote}') in result
assert ('Third footnote: %\n\\begin{footnote}[6]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[7]\n%\n\\end{footnote}%\n'
- '\\begin{footnotetext}[7]\\sphinxAtStartFootnote\n'
+ '\\begin{footnotetext}[7]'
+ '\\phantomsection\\label{\\thesphinxscope.7}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote inside footnote\n%\n'
'\\end{footnotetext}\\ignorespaces') in result
assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}'
@@ -868,16 +895,22 @@ def test_latex_show_urls_is_footnote(app, status, warning):
'\\sphinxnolinkurl{http://sphinx-doc.org/~test/}\n%\n\\end{footnote}') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}'
'{URL in term}\\sphinxfootnotemark[9]}] '
- '\\leavevmode%\n\\begin{footnotetext}[9]\\sphinxAtStartFootnote\n'
+ '\\leavevmode%\n\\begin{footnotetext}[9]'
+ '\\phantomsection\\label{\\thesphinxscope.9}%\n'
+ '\\sphinxAtStartFootnote\n'
'\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[11]}] '
- '\\leavevmode%\n\\begin{footnotetext}[11]\\sphinxAtStartFootnote\n'
+ '\\leavevmode%\n\\begin{footnotetext}[11]'
+ '\\phantomsection\\label{\\thesphinxscope.11}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}'
'\\sphinxfootnotemark[10]}] '
- '\\leavevmode%\n\\begin{footnotetext}[10]\\sphinxAtStartFootnote\n'
+ '\\leavevmode%\n\\begin{footnotetext}[10]'
+ '\\phantomsection\\label{\\thesphinxscope.10}%\n'
+ '\\sphinxAtStartFootnote\n'
'\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' in result)
@@ -895,7 +928,9 @@ def test_latex_show_urls_is_no(app, status, warning):
print(result)
print(status.getvalue())
print(warning.getvalue())
- assert ('Same footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Same footnote number \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result
@@ -908,18 +943,24 @@ def test_latex_show_urls_is_no(app, status, warning):
'{\\sphinxcrossref{The section with a reference to }}}' in result)
assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result
- assert ('Second footnote: %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
+ assert ('Second footnote: \\sphinxstepexplicit %\n'
+ '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n'
+ '\\sphinxAtStartFootnote\n'
'Second\n%\n\\end{footnote}') in result
assert '\\sphinxhref{http://sphinx-doc.org/}{Sphinx}' in result
assert ('Third footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[4]\n%\n\\end{footnote}%\n'
- '\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
+ '\\begin{footnotetext}[4]'
+ '\\phantomsection\\label{\\thesphinxscope.4}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote inside footnote\n%\n\\end{footnotetext}\\ignorespaces') in result
assert '\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}' in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{URL in term}}] '
'\\leavevmode\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] '
- '\\leavevmode%\n\\begin{footnotetext}[6]\\sphinxAtStartFootnote\n'
+ '\\leavevmode%\n\\begin{footnotetext}[6]'
+ '\\phantomsection\\label{\\thesphinxscope.6}%\n'
+ '\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}}] '