HTML tables (#5666)

This commit is contained in:
Michael Färber 2025-01-23 13:08:48 +01:00 committed by GitHub
parent 52ee33a275
commit dda486a412
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 134 additions and 6 deletions

View File

@ -120,7 +120,10 @@ fn write_element(w: &mut Writer, element: &HtmlElement) -> SourceResult<()> {
/// Whether the element should be pretty-printed.
fn is_pretty(element: &HtmlElement) -> bool {
tag::is_block_by_default(element.tag) || matches!(element.tag, tag::meta)
matches!(
element.tag,
tag::meta | tag::table | tag::thead | tag::tbody | tag::tfoot | tag::tr
) || tag::is_block_by_default(element.tag)
}
/// Escape a character.

View File

@ -602,7 +602,7 @@ pub enum Entry<'a> {
impl<'a> Entry<'a> {
/// Obtains the cell inside this entry, if this is not a merged cell.
fn as_cell(&self) -> Option<&Cell<'a>> {
pub fn as_cell(&self) -> Option<&Cell<'a>> {
match self {
Self::Cell(cell) => Some(cell),
Self::Merged { .. } => None,

View File

@ -7,7 +7,11 @@ use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
TargetElem,
};
use crate::html::{tag, HtmlAttr, HtmlAttrs, HtmlElem, HtmlTag};
use crate::introspection::Locator;
use crate::layout::grid::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
use crate::layout::{
show_grid_cell, Abs, Alignment, BlockElem, Celled, GridCell, GridFooter, GridHLine,
GridHeader, GridVLine, Length, OuterHAlignment, OuterVAlignment, Rel, Sides,
@ -258,11 +262,65 @@ impl TableElem {
type TableFooter;
}
fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
let cell = cell.body.clone();
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
let mut attrs = HtmlAttrs::default();
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
if let Some(colspan) = span(cell.colspan(styles)) {
attrs.push(HtmlAttr::constant("colspan"), colspan);
}
if let Some(rowspan) = span(cell.rowspan(styles)) {
attrs.push(HtmlAttr::constant("rowspan"), rowspan);
}
HtmlElem::new(tag)
.with_body(Some(cell.body.clone()))
.with_attrs(attrs)
.pack()
.spanned(cell.span())
}
fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
let mut rows: Vec<_> = grid.entries.chunks(grid.cols.len()).collect();
let tr = |tag, row: &[Entry]| {
let row = row
.iter()
.flat_map(|entry| entry.as_cell())
.map(|cell| show_cell_html(tag, cell, styles));
elem(tag::tr, Content::sequence(row))
};
let footer = grid.footer.map(|ft| {
let rows = rows.drain(ft.unwrap().start..);
elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
});
let header = grid.header.map(|hd| {
let rows = rows.drain(..hd.unwrap().end);
elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))
});
let mut body = Content::sequence(rows.into_iter().map(|row| tr(tag::td, row)));
if header.is_some() || footer.is_some() {
body = elem(tag::tbody, body);
}
let content = header.into_iter().chain(core::iter::once(body)).chain(footer);
elem(tag::table, Content::sequence(content))
}
impl Show for Packed<TableElem> {
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_table)
.pack()
.spanned(self.span()))
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(if TargetElem::target_in(styles).is_html() {
// TODO: This is a hack, it is not clear whether the locator is actually used by HTML.
// How can we find out whether locator is actually used?
let locator = Locator::root();
show_cellgrid_html(table_to_cellgrid(self, engine, locator, styles)?, styles)
} else {
BlockElem::multi_layouter(self.clone(), engine.routines.layout_table).pack()
}
.spanned(self.span()))
}
}

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<thead>
<tr>
<th>The</th><th>first</th><th>and</th>
</tr>
<tr>
<th>the</th><th>second</th><th>row</th>
</tr>
</thead>
<tbody>
<tr>
<td>Foo</td><td rowspan="2">Baz</td><td>Bar</td>
</tr>
<tr>
<td>1</td><td>2</td>
</tr>
<tr>
<td colspan="2">3</td><td>4</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>The</td><td>last</td><td>row</td>
</tr>
</tfoot>
</table>
</body>
</html>

View File

@ -0,0 +1,32 @@
--- basic-table html ---
#table(
columns: 3,
rows: 3,
table.header(
[The],
[first],
[and],
[the],
[second],
[row],
table.hline(stroke: red)
),
table.cell(x: 1, rowspan: 2)[Baz],
[Foo],
[Bar],
[1],
// Baz spans into the next cell
[2],
table.cell(colspan: 2)[3],
[4],
table.footer(
[The],
[last],
[row],
),
)