* [PATCH 1/2] docs: apply contained horizontal scroll overflow to tables
2026-07-24 21:42 [PATCH 0/2] docs: make tables responsive with content-aware column widths Rito Rhymes
@ 2026-07-24 21:42 ` Rito Rhymes
2026-07-24 21:42 ` [PATCH 2/2] docs: derive table column minimum widths from content Rito Rhymes
2026-07-24 23:02 ` [PATCH 0/2] docs: make tables responsive with content-aware column widths Jonathan Corbet
2 siblings, 0 replies; 5+ messages in thread
From: Rito Rhymes @ 2026-07-24 21:42 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Jonathan Corbet, Daniel Lundberg Pedersen, Randy Dunlap,
linux-doc, linux-media
Documentation tables can currently fail in two opposite ways on
constrained layouts: columns may collapse until their contents wrap into
nearly vertical text, or a wide table may expand the document and break
the page margins.
Add a foundational containment layer for Sphinx-rendered tables. Wrap
each table in an outer container that owns horizontal overflow while
leaving the table itself in its native layout mode. This allows wide
table content to remain readable through contained scrolling without
expanding the page width and breaking the layout.
Applying overflow directly to the table changes its display behavior and
causes rendering defects. It can add an outer border that overlaps the
table's existing border, making the perimeter appear thicker, and can
leave an awkward gap between that outer border and the rightmost column.
Keeping overflow on a wrapper avoids those border and caption-layout
problems.
The wrapper also provides a useful affordance on narrow viewports. When
a table overflows, its right border remains outside the visible area
until the user scrolls to reveal it, helping indicate that additional
content is available horizontally. Otherwise, showing complete borders
on both sides while content is hidden may leave users unaware that the
table is scrollable.
Give the wrapper the full width available from the document body. With
the 120em body maximum retained, tables can use that space before local
scrolling becomes necessary. Tables whose readable width still exceeds
the body remain contained and horizontally scrollable rather than
widening the page.
This patch establishes the overflow boundary but does not attempt to
assign stable widths to table content across all table shapes. Content-
derived column minimums are added separately.
Signed-off-by: Rito Rhymes <rito@ritovision.com>
Assisted-by: ChatGPT SOL 5.6
---
The following pages demonstrate the two opposite table-layout failures
this patch is intended to support.
At a viewport width of 500px or less, a table expands the page width and
breaks the layout:
https://docs.kernel.org/7.0/userspace-api/media/v4l/vidioc-enuminput.html
In the 7.1 documentation, the table instead attempts to fit within the
viewport by collapsing its columns until some content wraps into nearly
vertical text (500px or less):
https://docs.kernel.org/7.1/userspace-api/media/v4l/vidioc-enuminput.html
With both this patch and its follow-up applied, wider tables remain
contained within the page and scroll horizontally, including at a
viewport width of 500px or less:
https://linux-tables.ritovision.com/userspace-api/media/v4l/vidioc-enuminput.html
The wrapper introduced here provides the horizontal overflow boundary.
The follow-up patch adds content-derived column minimums to prevent the
vertical text collapse.
The following test cases highlight problems with alternative approaches
and explain the implementation choices made by this patch.
Applying the horizontal overflow CSS directly to a table without a
wrapper causes a doubled outer border and an empty gap beside the
rightmost column.
Test page:
https://docs.kernel.org/7.1/process/debugging/kgdb.html#run-time-parameter-kgdbreboot
Apply the horizontal overflow rules directly to the table instead of an
outer wrapper to reproduce the rendering defects.
A body maximum that is too narrow causes contained tables to become
locally scrollable at ordinary desktop viewport widths even when unused
viewport space remains. The retained 120em body maximum avoids that
premature constraint for ordinary layouts, while tables that genuinely
exceed the available body width continue to scroll as intended.
Test page:
https://docs.kernel.org/7.1/userspace-api/media/v4l/vidioc-enuminput.html
To reproduce the premature scrolling, reduce the body maximum to 800px
while applying contained horizontal overflow.
If a narrower maximum width is later desired for prose, it should be
applied selectively to prose or another inner content container rather
than globally reducing the width available to table wrappers.
Documentation/conf.py | 1 +
Documentation/sphinx-static/custom.css | 11 ++++++++
Documentation/sphinx/table_layout.py | 38 ++++++++++++++++++++++++++
3 files changed, 50 insertions(+)
create mode 100644 Documentation/sphinx/table_layout.py
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 9b822ab47..53b9eee2e 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -153,6 +153,7 @@ extensions = [
"kernel_feat",
"kernel_include",
"kfigure",
+ "table_layout",
"maintainers_include",
"parser_yaml",
"rstFlatTable",
diff --git a/Documentation/sphinx-static/custom.css b/Documentation/sphinx-static/custom.css
index aa2a3cf11..e9fc17f5b 100644
--- a/Documentation/sphinx-static/custom.css
+++ b/Documentation/sphinx-static/custom.css
@@ -27,6 +27,17 @@ div.document {
width: auto;
}
+/*
+ * Put overflow on an outer container rather than changing the table's display
+ * type, preserving native table, caption, and collapsed-border rendering.
+ */
+div.body div.table-overflow {
+ display: block;
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: hidden;
+}
+
/* Size the logo appropriately */
img.logo {
width: 104px;
diff --git a/Documentation/sphinx/table_layout.py b/Documentation/sphinx/table_layout.py
new file mode 100644
index 000000000..029b31ee6
--- /dev/null
+++ b/Documentation/sphinx/table_layout.py
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+"""Provide responsive table layout hooks for HTML documentation.
+
+Wrap rendered tables in an outer container that enables contained horizontal
+scrolling on narrow viewports as needed. Allowing the table to remain wider
+than the viewport helps prevent columns from collapsing into unreadable
+vertical text, while containing page-wide overflow that would increase the
+total page width and break the page margins. Applying overflow directly to the
+table instead of the wrapper creates a double-border rendering defect.
+"""
+
+from sphinx.writers.html5 import HTML5Translator
+
+__version__ = "1.0"
+
+
+class LayoutInjectionHTMLTranslator(HTML5Translator):
+ """Add HTML containers needed for responsive layout behavior."""
+
+ def visit_table(self, node):
+ self.body.append('<div class="table-overflow">\n')
+ super().visit_table(node)
+
+ def depart_table(self, node):
+ super().depart_table(node)
+ self.body.append("</div>\n")
+
+
+def setup(app):
+ for builder in ("html", "dirhtml", "singlehtml"):
+ app.set_translator(builder, LayoutInjectionHTMLTranslator, override=True)
+
+ return dict(
+ version=__version__,
+ parallel_read_safe=True,
+ parallel_write_safe=True,
+ )
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] docs: derive table column minimum widths from content
2026-07-24 21:42 [PATCH 0/2] docs: make tables responsive with content-aware column widths Rito Rhymes
2026-07-24 21:42 ` [PATCH 1/2] docs: apply contained horizontal scroll overflow to tables Rito Rhymes
@ 2026-07-24 21:42 ` Rito Rhymes
2026-07-24 23:02 ` [PATCH 0/2] docs: make tables responsive with content-aware column widths Jonathan Corbet
2 siblings, 0 replies; 5+ messages in thread
From: Rito Rhymes @ 2026-07-24 21:42 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Hans Verkuil
Cc: Jonathan Corbet, Daniel Lundberg Pedersen, Randy Dunlap,
linux-doc, linux-media
The contained overflow wrapper added previously allows wide tables to
scroll within the document instead of widening the page. It does not,
however, prevent individual columns from collapsing until their contents
wrap into narrow, nearly vertical text.
CSS alone cannot derive a useful minimum from the contents of each
column. Extend table_layout.py to inspect tables in the resolved doctree
and measure the longest normalized single-column cell in each logical
column. Assign the resulting table-column-width-N class to every
single-column cell in that column.
Track row-spanning cells while traversing each row so later entries
remain aligned with their logical columns. Ignore cells that extend
across multiple columns when measuring and assigning widths because
their content cannot be attributed to one column.
Map the generated classes to ch-based minimum widths in CSS. Limit the
derived value to 40 characters and cap each CSS minimum at 60vw so
unusually long content cannot create an excessive column floor on narrow
viewports. These remain minimums rather than fixed widths, leaving
native table layout free to distribute additional space.
Allow otherwise unbreakable table-cell content to wrap anywhere. The
generated minimums keep this wrapping from collapsing columns
prematurely, while long identifiers, URLs, and inline literals can still
break instead of forcing unbounded table expansion. Any remaining
table-level width is handled by the contained overflow wrapper.
Signed-off-by: Rito Rhymes <rito@ritovision.com>
Assisted-by: ChatGPT SOL 5.6
---
This patch assumes that commit 1afefde902c5 ("Revert \"docs: allow
inline literals in paragraphs to wrap to prevent overflow\"") is not
carried forward. It retains overflow-wrap: anywhere and addresses the
table regression that motivated the revert with content-derived column
minimums.
The same table can collapse in opposite directions depending on its
content and the generalized CSS rules applied to it. A rule that keeps
one column readable can cause another to absorb too much or too little
space, creating a whack-a-mole effect where fixing one imbalance
introduces another. Because generalized selectors cannot derive a
different minimum from the contents of each column, preserving readable
widths requires a targeted, per-column solution.
The kgdbreboot parameter table demonstrates two opposite column-collapse
failure modes across documentation versions:
To reproduce these failure modes, view each page at a viewport width of
500px or narrower.
v7.1 collapses the left column of inline literals into nearly vertical
text while leaving the right column readable:
https://www.kernel.org/doc/html/v7.1/process/debugging/kgdb.html#run-time-parameter-kgdbreboot
v7.0 leaves the left column readable but collapses the right column into
nearly vertical text:
https://www.kernel.org/doc/html/v7.0/process/debugging/kgdb.html#run-time-parameter-kgdbreboot
With this series applied, both columns retain readable widths and any
remaining table width is contained through horizontal scrolling instead
of widening the page:
https://linux-tables.ritovision.com/process/debugging/kgdb.html#run-time-parameter-kgdbreboot
Documentation/sphinx-static/custom.css | 187 ++++++++++++++++++++++++-
Documentation/sphinx/table_layout.py | 98 +++++++++++++
2 files changed, 284 insertions(+), 1 deletion(-)
diff --git a/Documentation/sphinx-static/custom.css b/Documentation/sphinx-static/custom.css
index e9fc17f5b..6a33d3362 100644
--- a/Documentation/sphinx-static/custom.css
+++ b/Documentation/sphinx-static/custom.css
@@ -38,6 +38,183 @@ div.body div.table-overflow {
overflow-y: hidden;
}
+/*
+ * Let long identifiers, URLs, and other unbreakable cell content contribute a
+ * smaller intrinsic minimum width so the viewport-capped column minimums can
+ * take effect. Normal text still wraps at ordinary break opportunities; a
+ * break inside a token is used only when needed to avoid overflow.
+ */
+div.body table.docutils th,
+div.body table.docutils td {
+ overflow-wrap: anywhere;
+}
+
+/*
+ * table_layout.py derives a minimum width from the longest non-spanning
+ * cell in each column and assigns the corresponding table-column-width-N
+ * class. These minimums prevent premature wrapping, while overflow-wrap:
+ * anywhere above prevents long tokens from forcing unbounded expansion.
+ *
+ * Cap the generated floor at 60vw so an individual column's minimum remains
+ * fully visible on narrow viewports and does not itself require horizontal
+ * scrolling to read. Keep this range in sync with MAX_COLUMN_WIDTH.
+ */
+div.body table.docutils .table-column-width-2 {
+ min-width: min(2ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-3 {
+ min-width: min(3ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-4 {
+ min-width: min(4ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-5 {
+ min-width: min(5ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-6 {
+ min-width: min(6ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-7 {
+ min-width: min(7ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-8 {
+ min-width: min(8ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-9 {
+ min-width: min(9ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-10 {
+ min-width: min(10ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-11 {
+ min-width: min(11ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-12 {
+ min-width: min(12ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-13 {
+ min-width: min(13ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-14 {
+ min-width: min(14ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-15 {
+ min-width: min(15ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-16 {
+ min-width: min(16ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-17 {
+ min-width: min(17ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-18 {
+ min-width: min(18ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-19 {
+ min-width: min(19ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-20 {
+ min-width: min(20ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-21 {
+ min-width: min(21ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-22 {
+ min-width: min(22ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-23 {
+ min-width: min(23ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-24 {
+ min-width: min(24ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-25 {
+ min-width: min(25ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-26 {
+ min-width: min(26ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-27 {
+ min-width: min(27ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-28 {
+ min-width: min(28ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-29 {
+ min-width: min(29ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-30 {
+ min-width: min(30ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-31 {
+ min-width: min(31ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-32 {
+ min-width: min(32ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-33 {
+ min-width: min(33ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-34 {
+ min-width: min(34ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-35 {
+ min-width: min(35ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-36 {
+ min-width: min(36ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-37 {
+ min-width: min(37ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-38 {
+ min-width: min(38ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-39 {
+ min-width: min(39ch, 60vw);
+}
+
+div.body table.docutils .table-column-width-40 {
+ min-width: min(40ch, 60vw);
+}
+
/* Size the logo appropriately */
img.logo {
width: 104px;
@@ -171,8 +348,16 @@ div.language-selection ul li:hover {
}
/*
- * Let long inline literals in paragraph text wrap as needed to prevent
+ * Treat inline literals like normal text for wrapping in ordinary layouts,
+ * while allowing breaks inside long unbroken tokens when needed to prevent
* overflow.
+ *
+ * In tables, this aggressive wrapping can otherwise collapse narrow columns
+ * into nearly vertical text. The content-derived minimum widths assigned by
+ * table_layout.py establish a readable floor for each column. The column
+ * width adapts without falling below that floor, while long inline literals
+ * wrap within the available width rather than forcing the table to expand or
+ * collapsing into vertical text.
*/
code.docutils.literal span.pre {
white-space: normal;
diff --git a/Documentation/sphinx/table_layout.py b/Documentation/sphinx/table_layout.py
index 029b31ee6..88de246c5 100644
--- a/Documentation/sphinx/table_layout.py
+++ b/Documentation/sphinx/table_layout.py
@@ -8,13 +8,109 @@ than the viewport helps prevent columns from collapsing into unreadable
vertical text, while containing page-wide overflow that would increase the
total page width and break the page margins. Applying overflow directly to the
table instead of the wrapper creates a double-border rendering defect.
+
+Derive a targeted minimum width for each logical column from the content
+belonging to that column. These minimums prevent aggressive wrapping from
+collapsing text and inline literals into nearly vertical or excessively narrow
+columns, while preserving content-aware column widths and allowing the overall
+table to scroll within its container on narrow viewports.
"""
+from docutils import nodes
from sphinx.writers.html5 import HTML5Translator
__version__ = "1.0"
+MAX_COLUMN_WIDTH = 40
+
+
+def _content_length(entry):
+ """Return a stable character count for rendered cell content."""
+ return len(" ".join(entry.astext().split()))
+
+
+def _width_class(content_length):
+ if content_length <= 1:
+ return None
+
+ width = min(content_length, MAX_COLUMN_WIDTH)
+ return f"table-column-width-{width}"
+
+
+def _table_rows(tgroup):
+ for section in tgroup.children:
+ if not isinstance(section, (nodes.thead, nodes.tbody)):
+ continue
+ for row in section.children:
+ if isinstance(row, nodes.row):
+ yield row
+
+
+def _inject_table_column_classes(table):
+ tgroup = next(
+ (child for child in table.children if isinstance(child, nodes.tgroup)),
+ None,
+ )
+ if tgroup is None:
+ return
+
+ column_count = int(tgroup.get("cols", 0))
+ if column_count < 1:
+ return
+
+ column_lengths = [0] * column_count
+ positioned_entries = []
+ active_rowspans = [0] * column_count
+
+ for row in _table_rows(tgroup):
+ next_rowspans = [max(0, span - 1) for span in active_rowspans]
+ column = 0
+
+ for entry in row.children:
+ if not isinstance(entry, nodes.entry):
+ continue
+
+ while column < column_count and active_rowspans[column]:
+ column += 1
+ if column >= column_count:
+ break
+
+ colspan = int(entry.get("morecols", 0)) + 1
+ colspan = min(colspan, column_count - column)
+ positioned_entries.append((entry, column, colspan))
+
+ if colspan == 1:
+ column_lengths[column] = max(
+ column_lengths[column],
+ _content_length(entry),
+ )
+
+ rowspan = int(entry.get("morerows", 0))
+ if rowspan:
+ for index in range(column, column + colspan):
+ next_rowspans[index] = max(next_rowspans[index], rowspan)
+
+ column += colspan
+
+ active_rowspans = next_rowspans
+
+ column_classes = [_width_class(length) for length in column_lengths]
+ for entry, column, colspan in positioned_entries:
+ if colspan != 1:
+ continue
+
+ class_name = column_classes[column]
+ if class_name:
+ entry["classes"].append(class_name)
+
+
+def inject_layout_classes(_app, doctree, _docname):
+ """Add generated classes used by the documentation layout CSS."""
+ for table in doctree.traverse(nodes.table):
+ _inject_table_column_classes(table)
+
+
class LayoutInjectionHTMLTranslator(HTML5Translator):
"""Add HTML containers needed for responsive layout behavior."""
@@ -28,6 +124,8 @@ class LayoutInjectionHTMLTranslator(HTML5Translator):
def setup(app):
+ app.connect("doctree-resolved", inject_layout_classes)
+
for builder in ("html", "dirhtml", "singlehtml"):
app.set_translator(builder, LayoutInjectionHTMLTranslator, override=True)
--
2.51.0
^ permalink raw reply related [flat|nested] 5+ messages in thread