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.
This commit is contained in:
jfbu 2021-02-09 18:51:40 +01:00
commit b9c53c8ca6
7 changed files with 416 additions and 45 deletions

View File

@ -199,8 +199,13 @@ Bugs fixed
* #8683: :confval:`html_last_updated_fmt` generates wrong time zone for %Z * #8683: :confval:`html_last_updated_fmt` generates wrong time zone for %Z
* #1112: ``download`` role creates duplicated copies when relative path is * #1112: ``download`` role creates duplicated copies when relative path is
specified 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 * #7576: LaTeX with French babel and memoir crash: "Illegal parameter number
in definition of ``\FNH@prefntext``" 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 * #8072: LaTeX: Directive :rst:dir:`hlist` not implemented in LaTeX
* #8214: LaTeX: The :rst:role:`index` role and the glossary generate duplicate * #8214: LaTeX: The :rst:role:`index` role and the glossary generate duplicate
entries in the LaTeX index (if both used for same term) 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 * #8780: LaTeX: long words in narrow columns may not be hyphenated
* #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be * #8788: LaTeX: ``\titleformat`` last argument in sphinx.sty should be
bracketed, not braced (and is anyhow not needed) 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 Testing
-------- --------

View File

@ -469,11 +469,22 @@ Keys that don't need to be overridden unless in special cases are:
.. versionchanged:: 1.6 .. versionchanged:: 1.6
Remove unneeded ``{}`` after ``\\hrule``. 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'``
"printindex" call, the last thing in the file. Override if you want to "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 generate the index differently, append some content after the index, or
example ``'\\footnotesize\\raggedright\\printindex'`` is advisable when the change the font. As LaTeX uses two-column mode for the index it is
index is full of long entries. 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'`` Default: ``'\\printindex'``
@ -505,7 +516,6 @@ Keys that are set by other options and therefore should not be overridden are:
``'title'`` ``'title'``
``'release'`` ``'release'``
``'author'`` ``'author'``
``'makeindex'``
.. _latexsphinxsetup: .. _latexsphinxsetup:
@ -563,7 +573,9 @@ The below is included at the end of the chapter::
\endgroup \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 Spaces around the commas and equal signs are ignored, spaces inside LaTeX
macros may be significant. 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 Boolean to specify if long lines in :rst:dir:`code-block`\ 's contents are
wrapped. 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`` Default: ``true``
``literalblockcappos`` .. _latexsphinxsetupforcewraps:
Decides the caption position: either ``b`` ("bottom") or ``t`` ("top").
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`` ``verbatimhintsturnover``
Boolean to specify if code-blocks display "continued on next page" and Boolean to specify if code-blocks display "continued on next page" and
@ -723,7 +789,7 @@ macros may be significant.
.. note:: .. note::
Starting with this colour key, and for all others coming next, the actual Starting with this colour, and for all others following, the
names declared to "color" or "xcolor" are prefixed with "sphinx". names declared to "color" or "xcolor" are prefixed with "sphinx".
``verbatimsep`` ``verbatimsep``
@ -812,9 +878,8 @@ macros may be significant.
``attentionBorderColor``, ``dangerBorderColor``, ``attentionBorderColor``, ``dangerBorderColor``,
``errorBorderColor`` ``errorBorderColor``
.. |wgbdcolorslatex| replace:: ``warningBorderColor``, ``cautionBorderColor``, .. |wgbdcolorslatex| replace:: ``warningBorderColor``, and
``attentionB..C..``, ``dangerB..C..``, ``(caution|attention|danger|error)BorderColor``
``errorB..C..``
.. else latex goes into right margin, as it does not hyphenate the names .. else latex goes into right margin, as it does not hyphenate the names

View File

@ -92,6 +92,9 @@
% verbatim % verbatim
\DeclareBoolOption[true]{verbatimwithframe} \DeclareBoolOption[true]{verbatimwithframe}
\DeclareBoolOption[true]{verbatimwrapslines} \DeclareBoolOption[true]{verbatimwrapslines}
\DeclareBoolOption[false]{verbatimforcewraps}
\DeclareStringOption[3]{verbatimmaxoverfull}
\DeclareStringOption[100]{verbatimmaxunderfull}
\DeclareBoolOption[true]{verbatimhintsturnover} \DeclareBoolOption[true]{verbatimhintsturnover}
\DeclareBoolOption[true]{inlineliteralwraps} \DeclareBoolOption[true]{inlineliteralwraps}
\DeclareStringOption[t]{literalblockcappos} \DeclareStringOption[t]{literalblockcappos}
@ -298,6 +301,28 @@
%% FOOTNOTES %% 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 % support large numbered footnotes in minipage; but this is now obsolete
% from systematic use of savenotes environment around minipages % from systematic use of savenotes environment around minipages
\def\thempfootnote{\arabic{mpfootnote}} \def\thempfootnote{\arabic{mpfootnote}}

View File

@ -268,13 +268,188 @@
% no need to restore \fboxsep here, as this ends up in a \hbox from fancyvrb % 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: % \sphinxVerbatimFormatLine will be set locally to one of those two:
\newcommand\sphinxVerbatimFormatLineWrap[1]{% \newcommand\sphinxVerbatimFormatLineWrap{%
\hsize\linewidth \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@ \vtop{\raggedright\hyphenpenalty\z@\exhyphenpenalty\z@
\doublehyphendemerits\z@\finalhyphendemerits\z@ \doublehyphendemerits\z@\finalhyphendemerits\z@
\strut #1\strut}% \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{% \g@addto@macro\FV@SetupFont{%
\sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}% \sbox\sphinxcontinuationbox {\spx@opt@verbatimcontinued}%
\sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}% \sbox\sphinxvisiblespacebox {\spx@opt@verbatimvisiblespace}%

View File

@ -331,8 +331,58 @@
% some extras for Sphinx : % some extras for Sphinx :
% \sphinxfootnotemark: usable in section titles and silently removed from TOCs. % \sphinxfootnotemark: usable in section titles and silently removed from TOCs.
\def\sphinxfootnotemark [#1]% \def\sphinxfootnotemark [#1]%
{\ifx\thepage\relax\else\protect\spx@opt@BeforeFootnote {\ifx\thepage\relax\else\sphinxfootref{#1}\fi}%
\protect\footnotemark[#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{% \AtBeginDocument{%
% let hyperref less complain % let hyperref less complain
\pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}% \pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}%

View File

@ -844,11 +844,16 @@ class LaTeXTranslator(SphinxTranslator):
def visit_footnote(self, node: Element) -> None: def visit_footnote(self, node: Element) -> None:
self.in_footnote += 1 self.in_footnote += 1
label = cast(nodes.label, node[0]) label = cast(nodes.label, node[0])
if 'auto' not in node:
self.body.append('\\sphinxstepexplicit ')
if self.in_parsed_literal: if self.in_parsed_literal:
self.body.append('\\begin{footnote}[%s]' % label.astext()) self.body.append('\\begin{footnote}[%s]' % label.astext())
else: else:
self.body.append('%\n') self.body.append('%\n')
self.body.append('\\begin{footnote}[%s]' % label.astext()) 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') self.body.append('\\sphinxAtStartFootnote\n')
def depart_footnote(self, node: Element) -> None: def depart_footnote(self, node: Element) -> None:
@ -1746,7 +1751,9 @@ class LaTeXTranslator(SphinxTranslator):
label = cast(nodes.label, node[0]) label = cast(nodes.label, node[0])
self.body.append('%\n') self.body.append('%\n')
self.body.append('\\begin{footnotetext}[%s]' 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: def depart_footnotetext(self, node: Element) -> None:
# the \ignorespaces in particular for after table header use # the \ignorespaces in particular for after table header use

View File

@ -723,7 +723,8 @@ def test_footnote(app, status, warning):
print(result) print(result)
print(status.getvalue()) print(status.getvalue())
print(warning.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 '\\end{footnote}') in result
assert ('\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n' assert ('\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n'
'\\end{footnote}') in result '\\end{footnote}') in result
@ -731,9 +732,13 @@ def test_footnote(app, status, warning):
assert '\\sphinxcite{footnote:bar}' in result assert '\\sphinxcite{footnote:bar}' in result
assert ('\\bibitem[bar]{footnote:bar}\n\\sphinxAtStartPar\ncite\n') in result assert ('\\bibitem[bar]{footnote:bar}\n\\sphinxAtStartPar\ncite\n') in result
assert '\\sphinxcaption{Table caption \\sphinxfootnotemark[4]' 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' '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 ' 'footnote in table header\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\n' '\n\\sphinxAtStartPar\n'
'VIDIOC\\_CROPCAP\n&\n\\sphinxAtStartPar\n') in result '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 '\\subsubsection*{The rubric title with a reference to {[}AuthorYear{]}}' in result
assert ('\\chapter{The section with a reference to \\sphinxfootnotemark[5]}\n' assert ('\\chapter{The section with a reference to \\sphinxfootnotemark[5]}\n'
'\\label{\\detokenize{index:the-section-with-a-reference-to}}' '\\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 'Footnote in section\n%\n\\end{footnotetext}') in result
assert ('\\caption{This is the figure caption with a footnote to ' assert ('\\caption{This is the figure caption with a footnote to '
'\\sphinxfootnotemark[7].}\\label{\\detokenize{index:id29}}\\end{figure}\n' '\\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 'Footnote in caption\n%\n\\end{footnotetext}') in result
assert ('\\sphinxcaption{footnote \\sphinxfootnotemark[8] in ' assert ('\\sphinxcaption{footnote \\sphinxfootnotemark[8] in '
'caption of normal table}\\label{\\detokenize{index:id30}}') in result 'caption of normal table}\\label{\\detokenize{index:id30}}') in result
assert ('\\caption{footnote \\sphinxfootnotemark[9] ' assert ('\\caption{footnote \\sphinxfootnotemark[9] '
'in caption \\sphinxfootnotemark[10] of longtable\\strut}') in result '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' '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 'Second footnote in caption of longtable\n') in result
assert ('This is a reference to the code\\sphinxhyphen{}block in the footnote:\n' assert ('This is a reference to the code\\sphinxhyphen{}block in the footnote:\n'
'{\\hyperref[\\detokenize{index:codeblockinfootnote}]' '{\\hyperref[\\detokenize{index:codeblockinfootnote}]'
@ -792,7 +805,9 @@ def test_latex_show_urls_is_inline(app, status, warning):
print(result) print(result)
print(status.getvalue()) print(status.getvalue())
print(warning.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 'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n' assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result '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) '{\\sphinxcrossref{The section with a reference to }}}' in result)
assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n' assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result '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 'Second\n%\n\\end{footnote}') in result
assert '\\sphinxhref{http://sphinx-doc.org/}{Sphinx} (http://sphinx\\sphinxhyphen{}doc.org/)' 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' assert ('Third footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[4]\n%\n\\end{footnote}%\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 'Footnote inside footnote\n%\n\\end{footnotetext}\\ignorespaces') in result
assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde} ' assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde} '
'(http://sphinx\\sphinxhyphen{}doc.org/\\textasciitilde{}test/)') in result '(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/)}] ' '(http://sphinx\\sphinxhyphen{}doc.org/)}] '
'\\leavevmode\n\\sphinxAtStartPar\nDescription' in result) '\\leavevmode\n\\sphinxAtStartPar\nDescription' in result)
assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] ' 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 ' 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result '\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist} ' 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(result)
print(status.getvalue()) print(status.getvalue())
print(warning.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 'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n' assert ('Auto footnote number %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result '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 '{\\sphinxcrossref{The section with a reference to }}}') in result
assert ('First footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n' assert ('First footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result '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 'Second\n%\n\\end{footnote}') in result
assert ('\\sphinxhref{http://sphinx-doc.org/}{Sphinx}' assert ('\\sphinxhref{http://sphinx-doc.org/}{Sphinx}'
'%\n\\begin{footnote}[4]\\sphinxAtStartFootnote\n' '%\n\\begin{footnote}[4]\\sphinxAtStartFootnote\n'
'\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n\\end{footnote}') in result '\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n\\end{footnote}') in result
assert ('Third footnote: %\n\\begin{footnote}[6]\\sphinxAtStartFootnote\n' assert ('Third footnote: %\n\\begin{footnote}[6]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[7]\n%\n\\end{footnote}%\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' 'Footnote inside footnote\n%\n'
'\\end{footnotetext}\\ignorespaces') in result '\\end{footnotetext}\\ignorespaces') in result
assert ('\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}' 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 '\\sphinxnolinkurl{http://sphinx-doc.org/~test/}\n%\n\\end{footnote}') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}' assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}'
'{URL in term}\\sphinxfootnotemark[9]}] ' '{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' '\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result '\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[11]}] ' 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 ' 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result '\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}' assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}'
'\\sphinxfootnotemark[10]}] ' '\\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' '\\sphinxnolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result '\\end{footnotetext}\\ignorespaces \n\\sphinxAtStartPar\nDescription') in result
assert ('\\sphinxurl{https://github.com/sphinx-doc/sphinx}\n' 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(result)
print(status.getvalue()) print(status.getvalue())
print(warning.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 'footnote in bar\n%\n\\end{footnote} in bar.rst') in result
assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n' assert ('Auto footnote number %\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n'
'footnote in baz\n%\n\\end{footnote} in baz.rst') in result '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) '{\\sphinxcrossref{The section with a reference to }}}' in result)
assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n' assert ('First footnote: %\n\\begin{footnote}[2]\\sphinxAtStartFootnote\n'
'First\n%\n\\end{footnote}') in result '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 'Second\n%\n\\end{footnote}') in result
assert '\\sphinxhref{http://sphinx-doc.org/}{Sphinx}' in result assert '\\sphinxhref{http://sphinx-doc.org/}{Sphinx}' in result
assert ('Third footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n' assert ('Third footnote: %\n\\begin{footnote}[3]\\sphinxAtStartFootnote\n'
'Third \\sphinxfootnotemark[4]\n%\n\\end{footnote}%\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 'Footnote inside footnote\n%\n\\end{footnotetext}\\ignorespaces') in result
assert '\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}' in result assert '\\sphinxhref{http://sphinx-doc.org/~test/}{URL including tilde}' in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{URL in term}}] ' assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{URL in term}}] '
'\\leavevmode\n\\sphinxAtStartPar\nDescription') in result '\\leavevmode\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[6]}] ' 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 ' 'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces '
'\n\\sphinxAtStartPar\nDescription') in result '\n\\sphinxAtStartPar\nDescription') in result
assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}}] ' assert ('\\item[{\\sphinxhref{http://sphinx-doc.org/}{Term in deflist}}] '