(latex) hyperlinked footnotes from table bodies

Memo: footnotehyper-sphinx LaTeX package incorporates here some fixes
from upstream footnotehyper 0.99. At Sphinx 1.6 il will for simplicity
copy all of upstream footnotehyper 0.99. Then old footnote.sty LaTeX
package is not a dependency of Sphinx anymore.
This commit is contained in:
jfbu 2017-02-14 23:30:50 +01:00
parent 36e9e98bb6
commit 9a8e36568d
8 changed files with 105 additions and 56 deletions

View File

@ -56,6 +56,7 @@ Features added
:confval:`suppress_warnings`.
* #3377: latex: Add support for Docutils 0.13 ``:align:`` option for tables
(but does not implement text flow around table).
* (latex) footnotes from inside tables are hyperlinked (except inside headers).
Bugs fixed
----------
@ -82,6 +83,10 @@ Deprecated
- ``BuildEnvironment.create_index()``
Please use ``sphinx.environment.adapters`` modules instead.
* The LaTeX package ``footnote.sty`` will not be loaded anymore at Sphinx 1.7,
as Sphinx's ``footnotehyper-sphinx.sty`` will define all macros rather than
patch them. In particular the ``\makesavenoteenv`` macro is not in use anymore
and its definition will be removed at Sphinx 1.7.
Release 1.5.3 (in development)
==============================

View File

@ -1,4 +1,4 @@
\begin{longtable}
\begin{savenotes}\begin{longtable}
<%- if table.align == 'center' -%>
[c]
<%- elif table.align == 'left' -%>
@ -28,4 +28,4 @@
\endlastfoot
<%= ''.join(table.body) %>
\end{longtable}
\end{longtable}\end{savenotes}

View File

@ -1,4 +1,4 @@
\begingroup
\begin{savenotes}
<% if table.align -%>
<%- if table.align == 'center' -%>
\centering
@ -23,4 +23,4 @@
\end{threeparttable}
<%- endif %>
\par
\endgroup
\end{savenotes}

View File

@ -1,4 +1,4 @@
\begingroup
\begin{savenotes}
<% if table.align -%>
<%- if table.align == 'center' -%>
\centering
@ -23,4 +23,4 @@
\end{threeparttable}
<%- endif %>
\par
\endgroup
\end{savenotes}

View File

@ -1,6 +1,6 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{footnotehyper-sphinx}%
[2017/01/16 v1.5.2 hyperref aware footnote.sty for sphinx (JFB)]
[2017/02/15 v1.6 hyperref aware footnote.sty for sphinx (JFB)]
%%
%% Package: footnotehyper-sphinx
%% Version: based on footnotehyper.sty v0.9f (2016/10/03)
@ -10,13 +10,14 @@
%% Differences from footnotehyper v0.9f (2016/10/03):
%% 1. hyperref is assumed in use (with default hyperfootnotes=true),
%% 2. no need to check if footnote.sty was loaded,
%% 3. a special tabulary compatibility layer added, (partial but enough for
%% Sphinx),
%% 4. \sphinxfootnotemark, and use of \spx@opt@BeforeFootnote from sphinx.sty.
%% Note: with \footnotemark[N]/\footnotetext[N] syntax, hyperref
%% does not insert an hyperlink. This is _not_ improved here.
%% 3. a tabulary compatibility layer added, (partial but enough for Sphinx),
%% 4. \sphinxfootnotemark, and use of \spx@opt@BeforeFootnote from sphinx.sty,
%% 5. use of \sphinxunactivateextrasandspace for parsed literals
%%
%% 6. macro \sphinxlongtablepatch
%% 7. some import from future footnotehyper v0.99 (\FNH@fn@fntext)
%% 8. deprecation of \makesavenoteenv.
%% Future version of footnotehyper will not load footnote.sty and it will define
%% all macros rather than adding hyperref awareness and fixing bugs.
\DeclareOption*{\PackageWarning{footnotehyper}{Option `\CurrentOption' is unknown}}%
\ProcessOptions\relax
\let\FNH@@makefntext\@makefntext\let\@makefntext\@firstofone
@ -31,6 +32,7 @@
\let\fn@latex@@footnote \footnote % meaning of \footnote at end of preamble
\let\fn@latex@@footnotetext\footnotetext
\let\fn@fntext \FNH@hyper@fntext
\let\savenotes \FNH@savenotes
\let\spewnotes \FNH@hyper@spewnotes
\let\endsavenotes\spewnotes
\let\fn@endfntext\FNH@fixed@endfntext
@ -39,13 +41,33 @@
\let\endfootnote\fn@endfntext
\let\endfootnotetext\endfootnote
}%
\def\FNH@hyper@fntext {%
%% patch \savenotes for good functioning of \footnotetext[N]{..} syntax even
%% though Sphinx now uses only environment form. In future, will simply copy
%% over full footnotehyper v0.99.
\toks@\expandafter\expandafter\expandafter{\expandafter\@gobble\savenotes}%
\edef\FNH@savenotes{\begingroup
\unexpanded{\if@savingnotes\else\let\H@@mpfootnotetext\FNH@fn@fntext\fi}%
\the\toks@ }%
\def\FNH@fn@fntext {\FNH@fntext\FNH@fn@fntext@i}%
\def\FNH@hyper@fntext{\FNH@fntext\FNH@hyper@fntext@i}%
\def\FNH@fntext #1{%
%% amsmath compatibility
\ifx\ifmeasuring@\undefined\expandafter\@secondoftwo
\else\expandafter\@firstofone\fi
{\ifmeasuring@\expandafter\@gobbletwo\else\expandafter\@firstofone\fi}%
%% partial tabulary compatibility, [N] must be used, but Sphinx does it
{\ifx\equation$\expandafter\@gobbletwo\fi\FNH@hyper@fntext@i }%$
{\ifx\equation$\expandafter\@gobbletwo\fi #1}%$
}%
%% footnote.sty's replacement for \@footnotetext
\long\def\FNH@fn@fntext@i #1{\global\setbox\fn@notes\vbox
{\unvbox\fn@notes
\fn@startnote
\@makefntext
{\rule\z@\footnotesep\ignorespaces
#1%
\@finalstrut\strutbox
}%
\fn@endnote }%
}%
\long\def\FNH@hyper@fntext@i #1{\global\setbox\fn@notes\vbox
{\unvbox\fn@notes
@ -62,7 +84,8 @@
\let\@currentHref\Hy@footnote@currentHref
\let\@currentlabelname\@empty
#1}%
\@finalstrut\strutbox }%
\@finalstrut\strutbox
}%
\fn@endnote }%
}%
\def\FNH@hyper@spewnotes {\endgroup
@ -87,7 +110,7 @@
\def\FNH@endfntext@nolink {\begingroup
\let\@makefntext\@empty\let\@finalstrut\@gobble
\let\rule\@gobbletwo
\if@savingnotes\expandafter\fn@fntext\else\expandafter\H@@footnotetext\fi
\if@savingnotes\expandafter\FNH@fn@fntext\else\expandafter\H@@footnotetext\fi
{\unvbox\z@}\endgroup
}%
%% \spx@opt@BeforeFootnote is defined in sphinx.sty
@ -139,12 +162,13 @@
\def\FNH@check@c #11.2!3?4,#2#3\relax
{\ifx\FNH@check@c#2\expandafter\@gobble\fi\FNH@bad@footnote@env}%
\def\FNH@bad@footnote@env
{\PackageWarningNoLine{footnotehyper}%
{The footnote environment from package footnote^^J
will be dysfunctional, sorry (not my fault...). You may try to make a bug^^J
report at https://github.com/sphinx-doc/sphinx including the next lines:}%
{\PackageWarningNoLine{footnotehyper-sphinx}%
{Footnotes will be sub-optimal, sorry. This is due to the class or^^J
some package modifying macro \string\@makefntext.^^J
You can try to report this incompatibility at^^J
https://github.com/sphinx-doc/sphinx with this info:}%
\typeout{\meaning\@makefntext}%
\let\fn@prefntext\@empty\let\fn@postfntext\@empty
\let\fn@prefntext\@empty\let\fn@postfntext\@empty
}%
%% \sphinxfootnotemark: usable in section titles and silently removed from
%% TOCs.
@ -153,6 +177,19 @@
\protect\footnotemark[#1]\fi}%
\AtBeginDocument % let hyperref less complain
{\pdfstringdefDisableCommands{\def\sphinxfootnotemark [#1]{}}}%
% to obtain hyperlinked footnotes in longtable environment we must replace
% hyperref's patch of longtable's patch of \@footnotetext by our own
\def\sphinxlongtablepatch {% only for longtable wrapped in "savenotes"
\let\LT@p@ftntext\FNH@hyper@fntext
}%
%% deprecate \makesavenoteenv
\def\makesavenoteenv{%
\AtEndDocument{\PackageWarning{footnotehyper-sphinx}
{^^J^^J^^J!\@spaces**** SPHINX DEPRECATION WARNING ****^^J!^^J%
!\@spaces\string\makesavenoteenv\space from footnote.sty is deprecated^^J%
!\@spaces and will be removed at Sphinx 1.7 !^^J^^J^^J}}%
\@ifnextchar[\fn@msne@ii\fn@msne@i%]
}%
\endinput
%%
%% End of file `footnotehyper-sphinx.sty'.

View File

@ -59,10 +59,7 @@
% For hyperlinked footnotes in tables; also for gathering footnotes from
% topic and warning blocks. Also to allow code-blocks in footnotes.
\RequirePackage{footnotehyper-sphinx}
\makesavenoteenv{tabulary}
\makesavenoteenv{tabular}
\makesavenoteenv{threeparttable}
% (longtable is hyperref compatible and needs no special treatment here.)
\AtBeginDocument{\@ifpackageloaded{longtable}{\sphinxlongtablepatch}{}}%
% For the H specifier. Do not \restylefloat{figure}, it breaks Sphinx code
% for allowing figures in tables.
\RequirePackage{float}

View File

@ -1262,7 +1262,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
def depart_collected_footnote(self, node):
# type: (nodes.Node) -> None
if 'footnotetext' in node:
self.body.append('%\n\\end{footnotetext}')
# the \ignorespaces in particular for after table header use
self.body.append('%\n\\end{footnotetext}\\ignorespaces ')
else:
if self.in_parsed_literal:
self.body.append('\\end{footnote}')
@ -1310,7 +1311,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.body.append(table)
self.body.append("\n")
self.unrestrict_footnote(node)
self.table = None
def visit_colspec(self, node):
@ -1342,6 +1342,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_tbody(self, node):
# type: (nodes.Node) -> None
self.pushbody(self.table.body) # Redirect body output until table is finished.
if self.footnote_restricted:
# releases footnotetexts from header in first non header cell
self.unrestrict_footnote(node.parent.parent)
def depart_tbody(self, node):
# type: (nodes.Node) -> None
@ -2244,7 +2247,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_line(self, node):
# type: (nodes.Node) -> None
self.body.append(r'\item[] ')
self.body.append('\\item[] ')
def depart_line(self, node):
# type: (nodes.Node) -> None

View File

@ -480,12 +480,11 @@ def test_footnote(app, status, warning):
'{\\phantomsection\\label{\\detokenize{footnote:bar}} '
'\ncite\n}') in result
assert '\\caption{Table caption \\sphinxfootnotemark[4]' in result
assert 'name \\sphinxfootnotemark[5]' in result
assert ('\\end{threeparttable}\n\\par\n\\endgroup\n%\n'
'\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
'footnotes in table caption\n%\n\\end{footnotetext}%\n'
assert ('\\begin{footnotetext}[4]\\sphinxAtStartFootnote\n'
'footnotes in table caption\n%\n\\end{footnotetext}\\ignorespaces %\n'
'\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
'footnotes in table\n%\n\\end{footnotetext}') in result
'footnotes in table\n%\n\\end{footnotetext}\\ignorespaces ') in result
assert '\\end{threeparttable}\n\\par\n\\end{savenotes}\n' in result
@pytest.mark.sphinx('latex', testroot='footnotes')
@ -514,14 +513,18 @@ def test_reference_in_caption_and_codeblock_in_footnote(app, status, warning):
'in caption of normal table}\\label{\\detokenize{index:id28}}') in result
assert ('\\caption{footnote \\sphinxfootnotemark[8] '
'in caption \\sphinxfootnotemark[9] of longtable}') in result
assert ('\\end{longtable}\n%\n\\begin{footnotetext}[8]'
'\\sphinxAtStartFootnote\n'
'Foot note in longtable\n%\n\\end{footnotetext}' in result)
assert ('\\begin{footnotetext}[8]\\sphinxAtStartFootnote\n'
'Foot note in longtable\n%\n\\end{footnotetext}\\ignorespaces %\n'
'\\begin{footnotetext}[9]\\sphinxAtStartFootnote\n'
'Second footnote in caption of longtable\n') in result
assert ('This is a reference to the code-block in the footnote:\n'
'{\\hyperref[\\detokenize{index:codeblockinfootnote}]'
'{\\sphinxcrossref{\\DUrole{std,std-ref}{I am in a footnote}}}}') in result
assert ('&\nThis is one more footnote with some code in it '
'\\sphinxfootnotemark[10].\n\\\\') in result
assert ('&\nThis is one more footnote with some code in it %\n'
'\\begin{footnote}[10]\\sphinxAtStartFootnote\n'
'Third footnote in longtable\n') in result
assert ('\\end{sphinxVerbatim}\n\\let\\sphinxVerbatimTitle\\empty\n'
'\\let\\sphinxLiteralBlockLabel\\empty\n%\n\\end{footnote}.\n') in result
assert '\\begin{sphinxVerbatim}[commandchars=\\\\\\{\\}]' in result
@ -561,7 +564,8 @@ def test_latex_show_urls_is_inline(app, status, warning):
'(http://sphinx-doc.org/)}] \\leavevmode\nDescription' in result)
assert ('\\item[{Footnote in term \\sphinxfootnotemark[5]}] '
'\\leavevmode%\n\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\nDescription') in result
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n'
'Description') in result
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist} '
'(http://sphinx-doc.org/)}] \\leavevmode\nDescription') in result
assert '\\url{https://github.com/sphinx-doc/sphinx}\n' in result
@ -607,15 +611,16 @@ def test_latex_show_urls_is_footnote(app, status, warning):
'{URL in term}\\sphinxfootnotemark[8]}] '
'\\leavevmode%\n\\begin{footnotetext}[8]\\sphinxAtStartFootnote\n'
'\\nolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\nDescription') in result
'\\end{footnotetext}\\ignorespaces \nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[10]}] '
'\\leavevmode%\n\\begin{footnotetext}[10]\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\nDescription') in result
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n'
'Description') in result
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}'
'\\sphinxfootnotemark[9]}] '
'\\leavevmode%\n\\begin{footnotetext}[9]\\sphinxAtStartFootnote\n'
'\\nolinkurl{http://sphinx-doc.org/}\n%\n'
'\\end{footnotetext}\nDescription') in result
'\\end{footnotetext}\\ignorespaces \nDescription') in result
assert ('\\url{https://github.com/sphinx-doc/sphinx}\n' in result)
assert ('\\href{mailto:sphinx-dev@googlegroups.com}'
'{sphinx-dev@googlegroups.com}\n') in result
@ -655,7 +660,8 @@ def test_latex_show_urls_is_no(app, status, warning):
'\\leavevmode\nDescription') in result
assert ('\\item[{Footnote in term \\sphinxfootnotemark[5]}] '
'\\leavevmode%\n\\begin{footnotetext}[5]\\sphinxAtStartFootnote\n'
'Footnote in term\n%\n\\end{footnotetext}\nDescription') in result
'Footnote in term\n%\n\\end{footnotetext}\\ignorespaces \n'
'Description') in result
assert ('\\item[{\\href{http://sphinx-doc.org/}{Term in deflist}}] '
'\\leavevmode\nDescription') in result
assert ('\\url{https://github.com/sphinx-doc/sphinx}\n' in result)
@ -831,32 +837,33 @@ def test_latex_table_tabulars(app, status, warning):
# simple_table
table = tables['simple table']
assert ('\\begingroup\n\\centering\n\\begin{tabulary}{\\linewidth}{|L|L|}' in table)
assert ('\\begin{savenotes}\n\\centering\n'
'\\begin{tabulary}{\\linewidth}{|L|L|}' in table)
assert ('\\hline\n'
'\\sphinxstylethead{\\relax \nheader1\n\\unskip}\\relax &'
'\\sphinxstylethead{\\relax \nheader2\n\\unskip}\\relax' in table)
assert ('\\hline\ncell1-1\n&\ncell1-2\n\\\\' in table)
assert ('\\hline\ncell2-1\n&\ncell2-2\n\\\\' in table)
assert ('\\hline\ncell3-1\n&\ncell3-2\n\\\\' in table)
assert ('\\hline\n\\end{tabulary}\n\\par\n\\endgroup' in table)
assert ('\\hline\n\\end{tabulary}\n\\par\n\\end{savenotes}' in table)
# table having :widths: option
table = tables['table having :widths: option']
assert ('\\begingroup\n\\centering\n'
assert ('\\begin{savenotes}\n\\centering\n'
'\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}' in table)
assert ('\\hline\n\\end{tabular}\n\\par\n\\endgroup' in table)
assert ('\\hline\n\\end{tabular}\n\\par\n\\end{savenotes}' in table)
# table having :align: option (tabulary)
table = tables['table having :align: option (tabulary)']
assert ('\\begingroup\n\\raggedleft\n'
assert ('\\begin{savenotes}\n\\raggedleft\n'
'\\begin{tabulary}{\\linewidth}{|L|L|}\n' in table)
assert ('\\hline\n\\end{tabulary}\n\\par\n\\endgroup' in table)
assert ('\\hline\n\\end{tabulary}\n\\par\n\\end{savenotes}' in table)
# table having :align: option (tabular)
table = tables['table having :align: option (tabular)']
assert ('\\begingroup\n\\raggedright\n'
assert ('\\begin{savenotes}\n\\raggedright\n'
'\\begin{tabular}{|\\X{30}{100}|\\X{70}{100}|}\n' in table)
assert ('\\hline\n\\end{tabular}\n\\par\n\\endgroup' in table)
assert ('\\hline\n\\end{tabular}\n\\par\n\\end{savenotes}' in table)
# table with tabularcolumn
table = tables['table with tabularcolumn']
@ -864,12 +871,12 @@ def test_latex_table_tabulars(app, status, warning):
# table having caption
table = tables['table having caption']
assert ('\\begingroup\n\\centering\n'
assert ('\\begin{savenotes}\n\\centering\n'
'\\begin{threeparttable}\n\\capstart\\caption{caption for table}'
'\\label{\\detokenize{tabular:id1}}' in table)
assert ('\\begin{tabulary}{\\linewidth}{|L|L|}' in table)
assert ('\\hline\n\\end{tabulary}\n\\end{threeparttable}'
'\n\\par\n\\endgroup' in table)
'\n\\par\n\\end{savenotes}' in table)
# table having verbatim
table = tables['table having verbatim']
@ -898,7 +905,7 @@ def test_latex_table_longtable(app, status, warning):
# longtable
table = tables['longtable']
assert ('\\begin{longtable}{|l|l|}\n\\hline' in table)
assert ('\\begin{savenotes}\\begin{longtable}{|l|l|}\n\\hline' in table)
assert ('\\hline\n'
'\\sphinxstylethead{\\relax \nheader1\n\\unskip}\\relax &'
'\\sphinxstylethead{\\relax \nheader2\n\\unskip}\\relax \\\\\n'
@ -915,7 +922,7 @@ def test_latex_table_longtable(app, status, warning):
assert ('\ncell1-1\n&\ncell1-2\n\\\\' in table)
assert ('\\hline\ncell2-1\n&\ncell2-2\n\\\\' in table)
assert ('\\hline\ncell3-1\n&\ncell3-2\n\\\\' in table)
assert ('\\hline\n\\end{longtable}' in table)
assert ('\\hline\n\\end{longtable}\\end{savenotes}' in table)
# longtable having :widths: option
table = tables['longtable having :widths: option']