From bd10782f8d19853b09a6d4cb9e776866a9c20b3e Mon Sep 17 00:00:00 2001 From: Stefan Wiehler Date: Wed, 21 Aug 2019 10:45:22 +0200 Subject: [PATCH] Add support for booktabs-style tables to LaTeX builder Render tables without vertical rules and horizontal rules of varying thickness (with additional space above and below) using the booktabs package. --- doc/usage/configuration.rst | 8 ++++++ sphinx/builders/latex/__init__.py | 1 + sphinx/templates/latex/longtable.tex_t | 26 +++++++++++++++++ sphinx/templates/latex/tabular.tex_t | 10 +++++++ sphinx/templates/latex/tabulary.tex_t | 10 +++++++ sphinx/texinputs/sphinx.sty | 1 + sphinx/writers/latex.py | 39 ++++++++++++++++++-------- 7 files changed, 84 insertions(+), 11 deletions(-) diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index dcf1ad4feac..daccf4eac66 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -2004,6 +2004,14 @@ These options influence LaTeX output. .. versionadded:: 1.8 +.. confval:: latex_booktabs + + If ``True``, render tables without vertical rules and horizontal rules of + varying thickness (with additional space above and below) using the + ``booktabs`` package. + + .. versionadded:: 2.1 + .. confval:: latex_elements .. versionadded:: 0.5 diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 69735ec47ee..4246be5c8e4 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -458,6 +458,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('latex_appendices', [], None) app.add_config_value('latex_use_latex_multicolumn', False, None) app.add_config_value('latex_use_xindy', default_latex_use_xindy, None) + app.add_config_value('latex_booktabs', False, None) app.add_config_value('latex_toplevel_sectioning', None, None, ENUM(None, 'part', 'chapter', 'section')) app.add_config_value('latex_domain_indices', True, None, [list]) diff --git a/sphinx/templates/latex/longtable.tex_t b/sphinx/templates/latex/longtable.tex_t index 5c9a6325352..ed75bf11def 100644 --- a/sphinx/templates/latex/longtable.tex_t +++ b/sphinx/templates/latex/longtable.tex_t @@ -10,25 +10,51 @@ <%- if table.caption -%> \sphinxthelongtablecaptionisattop \caption{<%= ''.join(table.caption) %>\strut}<%= labels %>\\*[\sphinxlongtablecapskipadjust] +<%- if table.booktabs -%> +\toprule +<% else -%> \hline +<% endif -%> <% elif labels -%> +<%- if table.booktabs -%> +\toprule\noalign{\phantomsection<%= labels %>}% +<% else -%> \hline\noalign{\phantomsection<%= labels %>}% +<% endif -%> +<% else -%> +<%- if table.booktabs -%> +\toprule <% else -%> \hline <% endif -%> +<% endif -%> <%= ''.join(table.header) %> \endfirsthead \multicolumn{<%= table.colcount %>}{c}% {\makebox[0pt]{\sphinxtablecontinued{\tablename\ \thetable{} -- <%= _('continued from previous page') %>}}}\\ +<%- if table.booktabs -%> +\toprule +<% else -%> \hline +<% endif -%> <%= ''.join(table.header) %> +<%- if table.header and table.booktabs -%> +\midrule +<% endif -%> \endhead +<%- if table.booktabs -%> +\bottomrule +<% else -%> \hline +<% endif -%> \multicolumn{<%= table.colcount %>}{r}{\makebox[0pt][r]{\sphinxtablecontinued{<%= _('continues on next page') %>}}}\\ \endfoot \endlastfoot <%= ''.join(table.body) %> +<%- if table.booktabs -%> +\bottomrule +<% endif -%> \end{longtable}\sphinxatlongtableend\end{savenotes} diff --git a/sphinx/templates/latex/tabular.tex_t b/sphinx/templates/latex/tabular.tex_t index a0db7faff1f..e1cb39311b2 100644 --- a/sphinx/templates/latex/tabular.tex_t +++ b/sphinx/templates/latex/tabular.tex_t @@ -19,9 +19,19 @@ \phantomsection<%= labels %>\nobreak <% endif -%> \begin{tabular}[t]<%= table.get_colspec() -%> +<%- if table.booktabs -%> +\toprule +<%- else -%> \hline +<%- endif -%> <%= ''.join(table.header) %> +<%- if table.header and table.booktabs -%> +\midrule +<%- endif -%> <%=- ''.join(table.body) %> +<%- if table.booktabs -%> +\bottomrule +<%- endif -%> \end{tabular} \par \sphinxattableend\end{savenotes} diff --git a/sphinx/templates/latex/tabulary.tex_t b/sphinx/templates/latex/tabulary.tex_t index 3236b798a52..15a8f058a8f 100644 --- a/sphinx/templates/latex/tabulary.tex_t +++ b/sphinx/templates/latex/tabulary.tex_t @@ -19,9 +19,19 @@ \phantomsection<%= labels %>\nobreak <% endif -%> \begin{tabulary}{\linewidth}[t]<%= table.get_colspec() -%> +<%- if table.booktabs -%> +\toprule +<%- else -%> \hline +<%- endif -%> <%= ''.join(table.header) %> +<%- if table.header and table.booktabs -%> +\midrule +<%- endif -%> <%=- ''.join(table.body) %> +<%- if table.booktabs -%> +\bottomrule +<%- endif -%> \end{tabulary} \par \sphinxattableend\end{savenotes} diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index 2eebbb91468..0c1470ccd65 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -65,6 +65,7 @@ \fi }% }{} +\RequirePackage{booktabs} \RequirePackage{tabulary} % tabulary has a bug with its re-definition of \multicolumn in its first pass % which is not \long. But now Sphinx does not use LaTeX's \multicolumn but its diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index d0bd38ce2dc..baffeb62fc9 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -330,16 +330,20 @@ def get_colspec(self): elif self.colwidths and 'colwidths-given' in self.classes: total = sum(self.colwidths) colspecs = ['\\X{%d}{%d}' % (width, total) for width in self.colwidths] - return '{|%s|}\n' % '|'.join(colspecs) + return '{%s%s%s}\n' % (self.colsep, self.colsep.join(colspecs), + self.colsep) + elif self.has_problematic: - return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount) + return '{%s*{%d}{\\X{1}{%d}%s}}\n' % (self.colsep, self.colcount, + self.colcount, self.colsep) elif self.get_table_type() == 'tabulary': # sphinx.sty sets T to be J by default. - return '{|' + ('T|' * self.colcount) + '}\n' + return '{' + self.colsep + (('T' + self.colsep) * self.colcount) + '}\n' elif self.has_oldproblematic: - return '{|*{%d}{\\X{1}{%d}|}}\n' % (self.colcount, self.colcount) + return '{%s*{%d}{\\X{1}{%d}%s}}\n' % (self.colsep, self.colcount, + self.colcount, self.colsep) else: - return '{|' + ('l|' * self.colcount) + '}\n' + return '{' + self.colsep + (('l' + self.colsep) * self.colcount) + '}\n' def add_cell(self, height, width): # type: (int, int) -> None @@ -1123,6 +1127,12 @@ def visit_table(self, node): logger.info(__('both tabularcolumns and :widths: option are given. ' ':widths: is ignored.'), location=node) self.next_table_colspec = None + if self.builder.config.latex_booktabs: + self.table.booktabs = True + self.table.colsep = '' + else: + self.table.booktabs = False + self.table.colsep = '|' def depart_table(self, node): # type: (nodes.Element) -> None @@ -1191,13 +1201,17 @@ def visit_row(self, node): # insert suitable strut for equalizing row heights in given multirow self.body.append('\\sphinxtablestrut{%d}' % cell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{|l|}' + self.body.append('\\multicolumn{%d}{%sl%s}' '{\\sphinxtablestrut{%d}}' % - (cell.width, cell.cell_id)) + (cell.width, self.table.colsep, + self.table.colsep, cell.cell_id)) def depart_row(self, node): # type: (nodes.Element) -> None self.body.append('\\\\\n') + if self.table.booktabs: + self.table.row += 1 + return cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)] underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells] if all(underlined): @@ -1223,9 +1237,11 @@ def visit_entry(self, node): if cell.width > 1: if self.builder.config.latex_use_latex_multicolumn: if self.table.col == 0: - self.body.append('\\multicolumn{%d}{|l|}{%%\n' % cell.width) + self.body.append('\\multicolumn{%d}{%sl%s}{%%\n' % + (cell.width, self.table.colsep, self.table.colsep)) else: - self.body.append('\\multicolumn{%d}{l|}{%%\n' % cell.width) + self.body.append('\\multicolumn{%d}{l%s}{%%\n' % + (cell.width, self.table.colsep)) context = '}%\n' else: self.body.append('\\sphinxstartmulticolumn{%d}%%\n' % cell.width) @@ -1281,9 +1297,10 @@ def depart_entry(self, node): self.body.append('\\sphinxtablestrut{%d}' % nextcell.cell_id) else: # use \multicolumn for wide multirow cell - self.body.append('\\multicolumn{%d}{l|}' + self.body.append('\\multicolumn{%d}{l%s}' '{\\sphinxtablestrut{%d}}' % - (nextcell.width, nextcell.cell_id)) + (nextcell.width, self.table.colsep, + nextcell.cell_id)) def visit_acks(self, node): # type: (nodes.Element) -> None