[LyX/master] XHTML tables: fix borders and implement booktabs.

Thibaut Cuvelier tcuvelier at lyx.org
Sat Apr 2 01:00:27 UTC 2022


commit 544adb065b89afa27002dc8b391ee7c907e200ac
Author: Thibaut Cuvelier <tcuvelier at lyx.org>
Date:   Fri Apr 1 21:15:13 2022 +0200

    XHTML tables: fix borders and implement booktabs.
    
    https://www.lyx.org/trac/ticket/10154
    
    Contributed by raccoon.
---
 autotests/export/xhtml/table_borders.lyx   |  235 ++++++++++++++++++++++++++++
 autotests/export/xhtml/table_borders.xhtml |   91 +++++++++++
 lib/layouts/stdinsets.inc                  |    1 -
 src/insets/InsetTabular.cpp                |   88 ++++++++++-
 src/insets/InsetTabular.h                  |   20 +++
 5 files changed, 427 insertions(+), 8 deletions(-)

diff --git a/autotests/export/xhtml/table_borders.lyx b/autotests/export/xhtml/table_borders.lyx
new file mode 100644
index 0000000..7b59f5f
--- /dev/null
+++ b/autotests/export/xhtml/table_borders.lyx
@@ -0,0 +1,235 @@
+#LyX 2.4 created this file. For more info see https://www.lyx.org/
+\lyxformat 609
+\begin_document
+\begin_header
+\save_transient_properties true
+\origin unavailable
+\textclass scrbook
+\begin_preamble
+% Added by lyx2lyx
+\setlength{\parskip}{\medskipamount}
+\setlength{\parindent}{0pt}
+\end_preamble
+\use_default_options true
+\maintain_unincluded_children no
+\language ngerman
+\language_package default
+\inputencoding auto-legacy
+\fontencoding auto
+\font_roman "lmodern" "default"
+\font_sans "lmss" "default"
+\font_typewriter "lmtt" "default"
+\font_math "auto" "auto"
+\font_default_family default
+\use_non_tex_fonts false
+\font_sc false
+\font_roman_osf false
+\font_sans_osf false
+\font_typewriter_osf false
+\font_sf_scale 100 100
+\font_tt_scale 100 100
+\use_microtype false
+\use_dash_ligatures true
+\graphics default
+\default_output_format default
+\output_sync 0
+\bibtex_command default
+\index_command default
+\paperfontsize default
+\spacing single
+\use_hyperref false
+\papersize default
+\use_geometry false
+\use_package amsmath 1
+\use_package amssymb 1
+\use_package cancel 1
+\use_package esint 1
+\use_package mathdots 0
+\use_package mathtools 0
+\use_package mhchem 1
+\use_package stackrel 1
+\use_package stmaryrd 1
+\use_package undertilde 0
+\cite_engine basic
+\cite_engine_type default
+\biblio_style plain
+\use_bibtopic false
+\use_indices false
+\paperorientation portrait
+\suppress_date false
+\justification true
+\use_refstyle 0
+\use_minted 0
+\use_lineno 0
+\branch Test
+\selected 1
+\filename_suffix 0
+\color #e9c792 #16386d
+\end_branch
+\index Stichwortverzeichnis
+\shortcut idx
+\color #008000
+\end_index
+\secnumdepth 3
+\tocdepth 3
+\paragraph_separation indent
+\paragraph_indentation default
+\is_math_indent 0
+\math_numbering_side default
+\quotes_style english
+\dynamic_quotes 0
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+\tablestyle default
+\tracking_changes false
+\output_changes false
+\change_bars false
+\postpone_fragile_content false
+\html_math_output 0
+\html_css_as_file 0
+\html_be_strict false
+\docbook_table_output 0
+\docbook_mathml_prefix 1
+\html_latex_start <span class='latex'>
+\html_latex_end </span>
+\end_header
+
+\begin_body
+
+\begin_layout Title
+Check HTML export
+\end_layout
+
+\begin_layout Standard
+\begin_inset Tabular
+<lyxtabular version="3" rows="4" columns="3">
+<features tabularvalignment="middle">
+<column alignment="center" valignment="top" width="3cm">
+<column alignment="center" valignment="top">
+<column alignment="center" valignment="top">
+<row>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+a
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+d
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" rightline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+s
+\end_layout
+
+\end_inset
+</cell>
+</row>
+<row>
+<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+</row>
+<row>
+<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+</row>
+<row>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" rightline="true" usebox="none">
+\begin_inset Text
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+</cell>
+</row>
+</lyxtabular>
+
+\end_inset
+
+
+\end_layout
+
+\end_body
+\end_document
diff --git a/autotests/export/xhtml/table_borders.xhtml b/autotests/export/xhtml/table_borders.xhtml
new file mode 100644
index 0000000..7e0602e
--- /dev/null
+++ b/autotests/export/xhtml/table_borders.xhtml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN" "http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="GENERATOR" content="LyX 2.4.0dev" />
+<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
+<title>Check HTML export</title>
+<style type='text/css'>
+/* Layout-provided Styles */
+h1.title {
+font-family: sans-serif;
+font-weight: bold;
+font-size: x-large;
+margin-bottom: 1ex;
+text-align: center;
+
+}
+h1.chapter {
+font-family: sans-serif;
+font-weight: bold;
+font-size: x-large;
+margin-top: 2ex;
+margin-bottom: 0.8ex;
+text-align: left;
+
+}
+div.standard {
+	text-indent: 2em;
+	margin-bottom: 2ex;
+}
+div.plain_layout {
+text-align: left;
+
+}
+table {
+	border-collapse: collapse;
+	display: inline-block;
+}
+td {
+	padding: 0.5ex;
+}
+
+
+</style>
+</head>
+<body dir="auto">
+<h1 class="title" id='magicparlabel-1'>Check HTML export</h1>
+<section>
+<h1 class="chapter" id='magicparlabel-2'><span class="chapter_label">1</span> here<a id="sec_ere__dsd" /></h1>
+<div class="standard" id='magicparlabel-3'>see <a href="#sec_ere__dsd">1</a></div>
+
+<div class="standard" id='magicparlabel-4'><table>
+<tbody>
+<tr>
+<td style='width: 3cm; border-bottom: 3.000000px double; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'><div class="plain_layout" id='magicparlabel-26'>a</div>
+</td>
+<td style='border-bottom: 3.000000px double; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'><div class="plain_layout" id='magicparlabel-29'>d</div>
+</td>
+<td style='border-bottom: 3.000000px double; border-right: 1px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'><div class="plain_layout" id='magicparlabel-32'>s</div>
+</td>
+</tr>
+<tr>
+<td style='width: 3cm; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-right: 1px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+</tr>
+<tr>
+<td style='width: 3cm; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-right: 1px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+</tr>
+<tr>
+<td style='width: 3cm; border-bottom: 1.000000px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-bottom: 1.000000px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+<td style='border-bottom: 1.000000px solid; border-right: 1px solid; border-left: 1px solid; border-top: 1.000000px solid' align='center' valign='top'>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</section>
+</body>
+</html>
diff --git a/lib/layouts/stdinsets.inc b/lib/layouts/stdinsets.inc
index 32a0af3..074b274 100644
--- a/lib/layouts/stdinsets.inc
+++ b/lib/layouts/stdinsets.inc
@@ -776,7 +776,6 @@ InsetLayout Tabular
 			display: inline-block;
 		}
 		td {
-			border: 1px solid black;
 			padding: 0.5ex;
 		}
 	EndHTMLStyle
diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp
index 0832054..478ecf1 100644
--- a/src/insets/InsetTabular.cpp
+++ b/src/insets/InsetTabular.cpp
@@ -3654,6 +3654,77 @@ std::string Tabular::getHAlignAsXmlAttribute(idx_type cell, bool is_xhtml) const
 	}
 }
 
+Tabular::XmlRowWiseBorders Tabular::computeXmlBorders(row_type row) const
+{
+	Tabular::XmlRowWiseBorders borders;
+
+	// Determine whether borders are required.
+	for (col_type c = 0; c < ncols(); ++c) {
+		if (row < nrows() - 1) {
+			if (!bottomLine(cellIndex(row, c))
+			    || !topLine(cellIndex(row + 1, c))) {
+				borders.completeBorder = false;
+			}
+			if (!bottomLine(cellIndex(row, c))
+			    && !topLine(cellIndex(row + 1, c))) {
+				borders.completeBorderBelow = false;
+			}
+		} else if (row == nrows() - 1 && !bottomLine(cellIndex(row, c))) {
+			borders.completeBorderBelow = false;
+		}
+
+		if ((row > 0 && !bottomLine(cellIndex(row - 1, c)) && !topLine(cellIndex(row, c))) ||
+		    (row == 0 && !topLine(cellIndex(row, c)))) {
+			borders.completeBorderAbove = false;
+		}
+	}
+
+	// Size of booktabs borders.
+	if (use_booktabs) {
+		if (borders.completeBorderAbove)
+			borders.borderTopWidth = row == 0 ? 2 : 1.5;
+		if (borders.completeBorderBelow) {
+			borders.borderBottomWidth = row == nrows() - 1 ? 2 : 1.5;
+			borders.borderBottomWidthComplete = 3 * borders.borderBottomWidth;
+		}
+	}
+
+	return borders;
+}
+
+
+std::vector<std::string> Tabular::computeCssStylePerCell(row_type row, col_type col, idx_type cell) const
+{
+	std::vector<std::string> styles;
+
+	// Fixed width.
+	Length const col_width = column_info[col].p_width;
+	if (!col_width.zero())
+		styles.emplace_back("width: " + col_width.asHTMLString());
+
+	// Borders and booktabs.
+	const Tabular::XmlRowWiseBorders borders = computeXmlBorders(row);
+
+	if (bottomLine(cell)) {
+		if (row < nrows() - 1 && borders.completeBorder)
+			styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidthComplete) + "px double");
+		else
+			styles.emplace_back("border-bottom: " + to_string(borders.borderBottomWidth) + "px solid");
+	}
+	if (rightLine(cell)) {
+		if (col < ncols() - 1 && leftLine(cell + 1))
+			styles.emplace_back("border-right: 3px double");
+		else
+			styles.emplace_back("border-right: 1px solid");
+	}
+	if (leftLine(cell))
+		styles.emplace_back("border-left: 1px solid");
+	if (topLine(cell))
+		styles.emplace_back("border-top: " + to_string(borders.borderTopWidth) + "px solid");
+
+	return styles;
+}
+
 
 docstring Tabular::xmlRow(XMLStream & xs, row_type row, OutputParams const & runparams,
 	bool header, bool is_xhtml, BufferParams::TableOutput docbook_table_output) const
@@ -3663,22 +3734,26 @@ docstring Tabular::xmlRow(XMLStream & xs, row_type row, OutputParams const & run
 
 	std::string const row_tag = is_xhtml_table ? "tr" : "row";
 	std::string const cell_tag = is_xhtml_table ? (header ? "th" : "td") : "entry";
+	Tabular::XmlRowWiseBorders const borders = computeXmlBorders(row);
 	idx_type cell = getFirstCellInRow(row);
 
 	xs << xml::StartTag(row_tag);
 	xs << xml::CR();
-	for (col_type c = 0; c < ncols(); ++c) {
+	for (col_type c = 0; c < ncols(); ++c, ++cell) {
 		if (isPartOfMultiColumn(row, c) || isPartOfMultiRow(row, c))
 			continue;
 
 		stringstream attr;
 
 		if (is_xhtml_table) {
-			Length const cwidth = column_info[c].p_width;
-			if (!cwidth.zero()) {
-				string const hwidth = cwidth.asHTMLString();
-				attr << "style='width: " << hwidth << ";' ";
-			}
+			const std::vector<std::string> styles = computeCssStylePerCell(row, c, cell);
+			attr << "style='" ;
+	        for (auto it = styles.begin(); it != styles.end(); ++it) {
+				attr << *it;
+				if (it != styles.end() - 1)
+					attr << "; ";
+	        }
+			attr << "' ";
 		}
 
 		attr << getHAlignAsXmlAttribute(cell, false) << " " << getVAlignAsXmlAttribute(cell);
@@ -3707,7 +3782,6 @@ docstring Tabular::xmlRow(XMLStream & xs, row_type row, OutputParams const & run
 		}
 		xs << xml::EndTag(cell_tag);
 		xs << xml::CR();
-		++cell;
 	}
 	xs << xml::EndTag(row_tag);
 	xs << xml::CR();
diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h
index 8a99ef4..0fbafa8 100644
--- a/src/insets/InsetTabular.h
+++ b/src/insets/InsetTabular.h
@@ -850,6 +850,24 @@ public:
 	///
 	typedef std::vector<ColumnData> column_vector;
 
+private:
+	// Determines the style of borders, per row.
+	class XmlRowWiseBorders {
+	public:
+		// Whether to draw double bottom line.
+		bool completeBorder = true;
+
+		// Whether to draw booktabs' thicker lines.
+		bool completeBorderAbove = true;
+		bool completeBorderBelow = true;
+
+		// Size of the borders.
+		double borderBottomWidth = 1.0;
+		double borderBottomWidthComplete = 3.0;
+		double borderTopWidth = 1.0;
+	};
+
+public:
 	///
 	idx_type numberofcells;
 	///
@@ -939,6 +957,8 @@ public:
 	docstring xmlRow(XMLStream & xs, row_type row, OutputParams const &,
 	                 bool header = false, bool is_xhtml = true,
 					 BufferParams::TableOutput docbook_table_output = BufferParams::TableOutput::HTMLTable) const;
+	XmlRowWiseBorders computeXmlBorders(row_type row) const;
+	std::vector<std::string> computeCssStylePerCell(row_type row, col_type col, idx_type cell) const;
 
 	/// Transforms the vertical alignment of the given cell as a prebaked XML attribute (for HTML and CALS).
 	std::string getHAlignAsXmlAttribute(idx_type cell, bool is_xhtml = true) const;


More information about the lyx-cvs mailing list