initial html target support

This commit is contained in:
PgBiel 2025-05-05 03:11:33 -03:00
parent 1e3e6167af
commit 07a060a9da
4 changed files with 243 additions and 7 deletions

View File

@ -292,18 +292,61 @@ fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
elem(tag::tr, Content::sequence(row)) elem(tag::tr, Content::sequence(row))
}; };
// TODO(subfooters): similarly to headers, take consecutive footers from
// the end for 'tfoot'.
let footer = grid.footer.map(|ft| { let footer = grid.footer.map(|ft| {
let rows = rows.drain(ft.start..); let rows = rows.drain(ft.start..);
elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row)))) elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
}); });
// TODO: Headers and footers in arbitrary positions
// Right now, only those at either end are accepted
let header = grid.headers.first().filter(|h| h.start == 0).map(|hd| {
let rows = rows.drain(..hd.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))); // Store all consecutive headers at the start in 'thead'. All remaining
// headers are just 'th' rows across the table body.
let mut consecutive_header_end = 0;
let first_mid_table_header = grid
.headers
.iter()
.take_while(|hd| {
let is_consecutive = hd.start == consecutive_header_end;
consecutive_header_end = hd.end;
is_consecutive
})
.count();
let (y_offset, header) = if first_mid_table_header > 0 {
let removed_header_rows =
grid.headers.get(first_mid_table_header - 1).unwrap().end;
let rows = rows.drain(..removed_header_rows);
(
removed_header_rows,
Some(elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))),
)
} else {
(0, None)
};
// TODO: Consider improving accessibility properties of multi-level headers
// inside tables in the future, e.g. indicating which columns they are
// relative to and so on. See also:
// https://www.w3.org/WAI/tutorials/tables/multi-level/
let mut next_header = first_mid_table_header;
let mut body =
Content::sequence(rows.into_iter().enumerate().map(|(relative_y, row)| {
let y = relative_y + y_offset;
if let Some(current_header) =
grid.headers.get(next_header).filter(|h| h.range().contains(&y))
{
if y + 1 == current_header.end {
next_header += 1;
}
tr(tag::th, row)
} else {
tr(tag::td, row)
}
}));
if header.is_some() || footer.is_some() { if header.is_some() || footer.is_some() {
body = elem(tag::tbody, body); body = elem(tag::tbody, body);
} }

View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<thead>
<tr>
<th>First</th>
<th>Header</th>
</tr>
<tr>
<th>Second</th>
<th>Header</th>
</tr>
<tr>
<th>Level 2</th>
<th>Header</th>
</tr>
<tr>
<th>Level 3</th>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr>
<td>Body</td>
<td>Cells</td>
</tr>
<tr>
<td>Yet</td>
<td>More</td>
</tr>
<tr>
<th>Level 2</th>
<th>Header Inside</th>
</tr>
<tr>
<th>Level 3</th>
<th></th>
</tr>
<tr>
<td>Even</td>
<td>More</td>
</tr>
<tr>
<td>Body</td>
<td>Cells</td>
</tr>
<tr>
<th>One Last Header</th>
<th>For Good Measure</th>
</tr>
</tbody>
<tfoot>
<tr>
<td>Footer</td>
<td>Row</td>
</tr>
<tr>
<td>Ending</td>
<td>Table</td>
</tr>
</tfoot>
</table>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<table>
<thead>
<tr>
<th>First</th>
<th>Header</th>
</tr>
<tr>
<th>Second</th>
<th>Header</th>
</tr>
<tr>
<th>Level 2</th>
<th>Header</th>
</tr>
<tr>
<th>Level 3</th>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr>
<td>Body</td>
<td>Cells</td>
</tr>
<tr>
<td>Yet</td>
<td>More</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Footer</td>
<td>Row</td>
</tr>
<tr>
<td>Ending</td>
<td>Table</td>
</tr>
</tfoot>
</table>
</body>
</html>

View File

@ -57,3 +57,78 @@
[d], [e], [f], [d], [e], [f],
[g], [h], [i] [g], [h], [i]
) )
--- multi-header-table html ---
#table(
columns: 2,
table.header(
[First], [Header]
),
table.header(
[Second], [Header]
),
table.header(
[Level 2], [Header],
level: 2,
),
table.header(
[Level 3], [Header],
level: 3,
),
[Body], [Cells],
[Yet], [More],
table.footer(
[Footer], [Row],
[Ending], [Table],
),
)
--- multi-header-inside-table html ---
#table(
columns: 2,
table.header(
[First], [Header]
),
table.header(
[Second], [Header]
),
table.header(
[Level 2], [Header],
level: 2,
),
table.header(
[Level 3], [Header],
level: 3,
),
[Body], [Cells],
[Yet], [More],
table.header(
[Level 2], [Header Inside],
level: 2,
),
table.header(
[Level 3],
level: 3,
),
[Even], [More],
[Body], [Cells],
table.header(
[One Last Header],
[For Good Measure],
repeat: false,
level: 4,
),
table.footer(
[Footer], [Row],
[Ending], [Table],
),
)