diff options
author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2022-02-11 13:18:59 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-11 13:18:59 +0900 |
commit | a32d60906176cf19fdc83da85836aa254f7fb848 (patch) | |
tree | 0281ace1afdc4e2ebf92e1eab8643cb7564b7a46 | |
parent | b6931acac6f71ac52ca74c9e78c0f427c63b8080 (diff) | |
parent | 777b1fa1056d763fa69cf93f381a40110bc5a2db (diff) | |
download | sphinx-git-a32d60906176cf19fdc83da85836aa254f7fb848.tar.gz |
Merge pull request #10169 from tk0miya/9529_named_footnotes_in_latex
Fix #9529: LaTeX: named footnotes are converted to "?"
-rw-r--r-- | CHANGES | 2 | ||||
-rw-r--r-- | sphinx/builders/latex/transforms.py | 31 | ||||
-rw-r--r-- | sphinx/texinputs/sphinx.sty | 6 | ||||
-rw-r--r-- | sphinx/writers/latex.py | 4 | ||||
-rw-r--r-- | tests/roots/test-footnotes/index.rst | 9 | ||||
-rw-r--r-- | tests/test_build_latex.py | 71 |
6 files changed, 75 insertions, 48 deletions
@@ -26,6 +26,8 @@ Bugs fixed * #10133: autodoc: Crashed when mocked module is used for type annotation * #10146: autodoc: :confval:`autodoc_default_options` does not support ``no-value`` option +* #9529: LaTeX: named auto numbered footnote (ex. ``[#named]``) that is referred + multiple times was rendered to a question mark * #10122: sphinx-build: make.bat does not check the installation of sphinx-build command before showing help diff --git a/sphinx/builders/latex/transforms.py b/sphinx/builders/latex/transforms.py index b12882df6..621c6a7cf 100644 --- a/sphinx/builders/latex/transforms.py +++ b/sphinx/builders/latex/transforms.py @@ -237,7 +237,8 @@ class LaTeXFootnoteTransform(SphinxPostTransform): blah blah blah ... * Replace second and subsequent footnote references which refers same footnote definition - by footnotemark node. + by footnotemark node. Additionally, the footnote definition node is marked as + "referred". Before:: @@ -258,7 +259,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform): After:: blah blah blah - <footnote ids="id1"> + <footnote ids="id1" referred=True> <label> 1 <paragraph> @@ -358,7 +359,7 @@ class LaTeXFootnoteTransform(SphinxPostTransform): class LaTeXFootnoteVisitor(nodes.NodeVisitor): def __init__(self, document: nodes.document, footnotes: List[nodes.footnote]) -> None: - self.appeared: Set[Tuple[str, str]] = set() + self.appeared: Dict[Tuple[str, str], nodes.footnote] = {} self.footnotes: List[nodes.footnote] = footnotes self.pendings: List[nodes.footnote] = [] self.table_footnotes: List[nodes.footnote] = [] @@ -439,22 +440,24 @@ class LaTeXFootnoteVisitor(nodes.NodeVisitor): def visit_footnote_reference(self, node: nodes.footnote_reference) -> None: number = node.astext().strip() docname = node['docname'] - if self.restricted: - mark = footnotemark('', number, refid=node['refid']) - node.replace_self(mark) - if (docname, number) not in self.appeared: - footnote = self.get_footnote_by_reference(node) - self.pendings.append(footnote) - elif (docname, number) in self.appeared: + if (docname, number) in self.appeared: + footnote = self.appeared.get((docname, number)) + footnote["referred"] = True + mark = footnotemark('', number, refid=node['refid']) node.replace_self(mark) else: footnote = self.get_footnote_by_reference(node) - self.footnotes.remove(footnote) - node.replace_self(footnote) - footnote.walkabout(self) + if self.restricted: + mark = footnotemark('', number, refid=node['refid']) + node.replace_self(mark) + self.pendings.append(footnote) + else: + self.footnotes.remove(footnote) + node.replace_self(footnote) + footnote.walkabout(self) - self.appeared.add((docname, number)) + self.appeared[(docname, number)] = footnote raise nodes.SkipNode def get_footnote_by_reference(self, node: nodes.footnote_reference) -> nodes.footnote: diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 4d42199a7..6c9f1606e 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -300,9 +300,9 @@ % Support scopes for footnote numbering \newcounter{sphinxscope} \newcommand{\sphinxstepscope}{\stepcounter{sphinxscope}} -% Explicitly 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 +% 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 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 0e95317d2..a2905e807 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -856,14 +856,14 @@ 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: + 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 'auto' not in node: + if 'referred' in node: self.body.append(r'\phantomsection' r'\label{\thesphinxscope.%s}%%' % label.astext() + CR) self.body.append(r'\sphinxAtStartFootnote' + CR) diff --git a/tests/roots/test-footnotes/index.rst b/tests/roots/test-footnotes/index.rst index 05b55f29a..9ac1a9c7a 100644 --- a/tests/roots/test-footnotes/index.rst +++ b/tests/roots/test-footnotes/index.rst @@ -177,3 +177,12 @@ The section with an object description .. py:function:: dummy(N) :noindex: + +Footnotes referred twice +======================== + +* Explicitly numbered footnote: [100]_ [100]_ +* Named footnote: [#twice]_ [#twice]_ + +.. [100] Numbered footnote +.. [#twice] Named footnote diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 223792b04..d156d1589 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -723,9 +723,8 @@ def test_footnote(app, status, warning): print(result) print(status.getvalue()) print(warning.getvalue()) - assert ('\\sphinxstepexplicit %\n\\begin{footnote}[1]\\phantomsection' - '\\label{\\thesphinxscope.1}%\n\\sphinxAtStartFootnote\nnumbered\n%\n' - '\\end{footnote}') in result + assert ('\\sphinxAtStartPar\n%\n\\begin{footnote}[1]\\sphinxAtStartFootnote\n' + 'numbered\n%\n\\end{footnote}') in result assert ('\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n' '\\end{footnote}') in result assert '\\begin{footnote}[3]\\sphinxAtStartFootnote\nnamed\n%\n\\end{footnote}' in result @@ -769,13 +768,13 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning): '\\sphinxAtStartFootnote\n' 'Footnote in section\n%\n\\end{footnotetext}') in result assert ('\\caption{This is the figure caption with a footnote to ' - '\\sphinxfootnotemark[8].}\\label{\\detokenize{index:id30}}\\end{figure}\n' + '\\sphinxfootnotemark[8].}\\label{\\detokenize{index:id35}}\\end{figure}\n' '%\n\\begin{footnotetext}[8]' '\\phantomsection\\label{\\thesphinxscope.8}%\n' '\\sphinxAtStartFootnote\n' 'Footnote in caption\n%\n\\end{footnotetext}') in result assert ('\\sphinxcaption{footnote \\sphinxfootnotemark[9] in ' - 'caption of normal table}\\label{\\detokenize{index:id31}}') in result + 'caption of normal table}\\label{\\detokenize{index:id36}}') in result assert ('\\caption{footnote \\sphinxfootnotemark[10] ' 'in caption \\sphinxfootnotemark[11] of longtable\\strut}') in result assert ('\\endlastfoot\n%\n\\begin{footnotetext}[10]' @@ -796,6 +795,26 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning): assert '\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]' in result +@pytest.mark.sphinx('latex', testroot='footnotes') +def test_footnote_referred_multiple_times(app, status, warning): + app.builder.build_all() + result = (app.outdir / 'python.tex').read_text() + print(result) + print(status.getvalue()) + print(warning.getvalue()) + + assert ('Explicitly numbered footnote: \\sphinxstepexplicit %\n' + '\\begin{footnote}[100]\\phantomsection\\label{\\thesphinxscope.100}%\n' + '\\sphinxAtStartFootnote\nNumbered footnote\n%\n' + '\\end{footnote} \\sphinxfootnotemark[100]\n' + in result) + assert ('Named footnote: \\sphinxstepexplicit %\n' + '\\begin{footnote}[13]\\phantomsection\\label{\\thesphinxscope.13}%\n' + '\\sphinxAtStartFootnote\nNamed footnote\n%\n' + '\\end{footnote} \\sphinxfootnotemark[13]\n' + in result) + + @pytest.mark.sphinx( 'latex', testroot='footnotes', confoverrides={'latex_show_urls': 'inline'}) @@ -805,25 +824,23 @@ def test_latex_show_urls_is_inline(app, status, warning): print(result) print(status.getvalue()) print(warning.getvalue()) - assert ('Same footnote number \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Same footnote number %\n' + '\\begin{footnote}[1]\\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 - assert ('\\phantomsection\\label{\\detokenize{index:id33}}' + assert ('\\phantomsection\\label{\\detokenize{index:id38}}' '{\\hyperref[\\detokenize{index:the-section' '-with-a-reference-to-authoryear}]' '{\\sphinxcrossref{The section with a reference to ' '\\sphinxcite{index:authoryear}}}}') in result - assert ('\\phantomsection\\label{\\detokenize{index:id34}}' + assert ('\\phantomsection\\label{\\detokenize{index:id39}}' '{\\hyperref[\\detokenize{index:the-section-with-a-reference-to}]' '{\\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: \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Second footnote: %\n' + '\\begin{footnote}[1]\\sphinxAtStartFootnote\n' 'Second\n%\n\\end{footnote}\n') 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' @@ -863,24 +880,22 @@ def test_latex_show_urls_is_footnote(app, status, warning): print(result) print(status.getvalue()) print(warning.getvalue()) - assert ('Same footnote number \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Same footnote number %\n' + '\\begin{footnote}[1]\\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 - assert ('\\phantomsection\\label{\\detokenize{index:id33}}' + assert ('\\phantomsection\\label{\\detokenize{index:id38}}' '{\\hyperref[\\detokenize{index:the-section-with-a-reference-to-authoryear}]' '{\\sphinxcrossref{The section with a reference ' 'to \\sphinxcite{index:authoryear}}}}') in result - assert ('\\phantomsection\\label{\\detokenize{index:id34}}' + assert ('\\phantomsection\\label{\\detokenize{index:id39}}' '{\\hyperref[\\detokenize{index:the-section-with-a-reference-to}]' '{\\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: \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Second footnote: %\n' + '\\begin{footnote}[1]\\sphinxAtStartFootnote\n' 'Second\n%\n\\end{footnote}') in result assert ('\\sphinxhref{http://sphinx-doc.org/}{Sphinx}' '%\n\\begin{footnote}[4]\\sphinxAtStartFootnote\n' @@ -932,24 +947,22 @@ def test_latex_show_urls_is_no(app, status, warning): print(result) print(status.getvalue()) print(warning.getvalue()) - assert ('Same footnote number \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Same footnote number %\n' + '\\begin{footnote}[1]\\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 - assert ('\\phantomsection\\label{\\detokenize{index:id33}}' + assert ('\\phantomsection\\label{\\detokenize{index:id38}}' '{\\hyperref[\\detokenize{index:the-section-with-a-reference-to-authoryear}]' '{\\sphinxcrossref{The section with a reference ' 'to \\sphinxcite{index:authoryear}}}}') in result - assert ('\\phantomsection\\label{\\detokenize{index:id34}}' + assert ('\\phantomsection\\label{\\detokenize{index:id39}}' '{\\hyperref[\\detokenize{index:the-section-with-a-reference-to}]' '{\\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: \\sphinxstepexplicit %\n' - '\\begin{footnote}[1]\\phantomsection\\label{\\thesphinxscope.1}%\n' - '\\sphinxAtStartFootnote\n' + assert ('Second footnote: %\n' + '\\begin{footnote}[1]\\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' |