Repository: typst/typst
File: crates/typst-library/src/model/table.rs (Rust)

Code before:
    #[elem]
    type TableHeader;

    #[elem]
    type TableFooter;
}

impl Show for Packed<TableElem> {
    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();
            let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
            let grid = table_to_cellgrid(self, engine, locator, styles)?;
            let rows = grid.entries.chunks(grid.cols.len()).map(|row| {
                let row = row.iter().flat_map(|entry| entry.as_cell());
                elem(tag::tr, Content::sequence(row.map(|cell| cell.body.clone())))
            });
            let mut rows: Vec<_> = rows.collect();

            let footer = grid.footer.map(|ft| {
                elem(tag::tfoot, Content::sequence(rows.drain(ft.unwrap().start..)))
            });
            let header = grid.header.map(|hd| {
                elem(tag::thead, Content::sequence(rows.drain(..hd.unwrap().end)))
            });

            let mut body = Content::sequence(rows);
            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))
        } else {
            BlockElem::multi_layouter(self.clone(), engine.routines.layout_table).pack()
        }
        .spanned(self.span()))
    }
}

impl LocalName for Packed<TableElem> {
    const KEY: &'static str = "table";
}

impl Figurable for Packed<TableElem> {}

/// Any child of a table element.
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum TableChild {

Review comment (suggestion):
Cells within headers should be `th` instead of `td`.

Code after:
    #[elem]
    type TableHeader;

    #[elem]
    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);
    }