mirror of
https://github.com/typst/typst
synced 2025-08-23 03:04:14 +08:00
WIP [no ci] tag table headers
This commit is contained in:
parent
1fa54b751a
commit
46229e73ba
@ -2,6 +2,7 @@ use std::num::{NonZeroU32, NonZeroUsize};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
use typst_macros::Cast;
|
||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
|
use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
|
||||||
@ -361,15 +362,40 @@ fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
|
|||||||
|
|
||||||
impl Show for Packed<TableElem> {
|
impl Show for Packed<TableElem> {
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(if TargetElem::target_in(styles).is_html() {
|
let elem = if TargetElem::target_in(styles).is_html() {
|
||||||
// TODO: This is a hack, it is not clear whether the locator is actually used by 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?
|
// How can we find out whether locator is actually used?
|
||||||
let locator = Locator::root();
|
let locator = Locator::root();
|
||||||
show_cellgrid_html(table_to_cellgrid(self, engine, locator, styles)?, styles)
|
show_cellgrid_html(table_to_cellgrid(self, engine, locator, styles)?, styles)
|
||||||
} else {
|
} else {
|
||||||
BlockElem::multi_layouter(self.clone(), engine.routines.layout_table).pack()
|
let children = self
|
||||||
}
|
.children
|
||||||
.spanned(self.span()))
|
.iter()
|
||||||
|
.map(|c| match c.clone() {
|
||||||
|
TableChild::Header(header) => {
|
||||||
|
let mut header = header.unpack();
|
||||||
|
header.children = header
|
||||||
|
.children
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| match item {
|
||||||
|
TableItem::Cell(cell) => {
|
||||||
|
let mut cell = cell.unpack();
|
||||||
|
cell.push_header_scope(Smart::Custom(TableHeaderScope::Column));
|
||||||
|
TableItem::Cell(Packed::new(cell))
|
||||||
|
}
|
||||||
|
item => item,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
TableChild::Header(Packed::new(header))
|
||||||
|
}
|
||||||
|
child => child,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut table = self.clone().unpack();
|
||||||
|
table.children = children;
|
||||||
|
BlockElem::multi_layouter(Packed::new(table), engine.routines.layout_table).pack()
|
||||||
|
};
|
||||||
|
Ok(elem.spanned(self.span()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -810,6 +836,9 @@ pub struct TableCell {
|
|||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Sides<Option<Option<Arc<Stroke>>>>,
|
pub stroke: Sides<Option<Option<Arc<Stroke>>>>,
|
||||||
|
|
||||||
|
// TODO: feature gate
|
||||||
|
pub header_scope: Smart<TableHeaderScope>,
|
||||||
|
|
||||||
/// Whether rows spanned by this cell can be placed in different pages.
|
/// Whether rows spanned by this cell can be placed in different pages.
|
||||||
/// When equal to `{auto}`, a cell spanning only fixed-size rows is
|
/// When equal to `{auto}`, a cell spanning only fixed-size rows is
|
||||||
/// unbreakable, while a cell spanning at least one `{auto}`-sized row is
|
/// unbreakable, while a cell spanning at least one `{auto}`-sized row is
|
||||||
@ -847,3 +876,10 @@ impl From<Content> for TableCell {
|
|||||||
value.unpack::<Self>().unwrap_or_else(Self::new)
|
value.unpack::<Self>().unwrap_or_else(Self::new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
|
pub enum TableHeaderScope {
|
||||||
|
Both,
|
||||||
|
Column,
|
||||||
|
Row,
|
||||||
|
}
|
||||||
|
@ -5,14 +5,14 @@ use krilla::page::Page;
|
|||||||
use krilla::surface::Surface;
|
use krilla::surface::Surface;
|
||||||
use krilla::tagging::{
|
use krilla::tagging::{
|
||||||
ArtifactType, ContentTag, Identifier, Node, SpanTag, TableCellSpan, TableDataCell,
|
ArtifactType, ContentTag, Identifier, Node, SpanTag, TableCellSpan, TableDataCell,
|
||||||
TableHeaderCell, TableHeaderScope, Tag, TagBuilder, TagGroup, TagKind, TagTree,
|
TableHeaderCell, Tag, TagBuilder, TagGroup, TagKind, TagTree,
|
||||||
};
|
};
|
||||||
use typst_library::foundations::{Content, LinkMarker, Packed, StyleChain};
|
use typst_library::foundations::{Content, LinkMarker, Packed, StyleChain, Smart};
|
||||||
use typst_library::introspection::Location;
|
use typst_library::introspection::Location;
|
||||||
use typst_library::layout::RepeatElem;
|
use typst_library::layout::RepeatElem;
|
||||||
use typst_library::model::{
|
use typst_library::model::{
|
||||||
Destination, FigureCaption, FigureElem, HeadingElem, Outlinable, OutlineBody,
|
Destination, FigureCaption, FigureElem, HeadingElem, Outlinable, OutlineBody,
|
||||||
OutlineEntry, TableCell, TableElem,
|
OutlineEntry, TableCell, TableElem, TableHeaderScope,
|
||||||
};
|
};
|
||||||
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind};
|
use typst_library::pdf::{ArtifactElem, ArtifactKind, PdfTagElem, PdfTagKind};
|
||||||
use typst_library::visualize::ImageElem;
|
use typst_library::visualize::ImageElem;
|
||||||
@ -141,10 +141,14 @@ impl TableCtx {
|
|||||||
|
|
||||||
// TODO: possibly set internal field on TableCell when resolving
|
// TODO: possibly set internal field on TableCell when resolving
|
||||||
// the cell grid.
|
// the cell grid.
|
||||||
let is_header = false;
|
let header_scope = cell.header_scope(StyleChain::default());
|
||||||
let span = TableCellSpan { rows: rowspan as i32, cols: colspan as i32 };
|
let span = TableCellSpan { rows: rowspan as i32, cols: colspan as i32 };
|
||||||
let tag = if is_header {
|
let tag = if let Smart::Custom(scope) = header_scope {
|
||||||
let scope = TableHeaderScope::Column; // TODO
|
let scope = match scope {
|
||||||
|
TableHeaderScope::Both => krilla::tagging::TableHeaderScope::Both,
|
||||||
|
TableHeaderScope::Column => krilla::tagging::TableHeaderScope::Column,
|
||||||
|
TableHeaderScope::Row => krilla::tagging::TableHeaderScope::Row,
|
||||||
|
};
|
||||||
TagKind::TH(TableHeaderCell::new(scope).with_span(span))
|
TagKind::TH(TableHeaderCell::new(scope).with_span(span))
|
||||||
} else {
|
} else {
|
||||||
TagKind::TD(TableDataCell::new().with_span(span))
|
TagKind::TD(TableDataCell::new().with_span(span))
|
||||||
@ -167,14 +171,37 @@ impl TableCtx {
|
|||||||
fn build_table(self, mut nodes: Vec<TagNode>) -> Vec<TagNode> {
|
fn build_table(self, mut nodes: Vec<TagNode>) -> Vec<TagNode> {
|
||||||
// Table layouting ensures that there are no overlapping cells, and that
|
// Table layouting ensures that there are no overlapping cells, and that
|
||||||
// any gaps left by the user are filled with empty cells.
|
// any gaps left by the user are filled with empty cells.
|
||||||
|
let mut row_chunk = Vec::new();
|
||||||
|
let mut is_header_chunk = false;
|
||||||
for row in self.rows.into_iter() {
|
for row in self.rows.into_iter() {
|
||||||
let mut row_nodes = Vec::new();
|
let mut is_header_row = true;
|
||||||
for (_, tag, nodes) in row.into_iter().flatten() {
|
let row_nodes = row
|
||||||
row_nodes.push(TagNode::Group(tag, nodes));
|
.into_iter()
|
||||||
}
|
.flatten()
|
||||||
|
.map(|(cell, tag, nodes)| {
|
||||||
|
is_header_row &= cell.header_scope(StyleChain::default()).is_custom();
|
||||||
|
TagNode::Group(tag, nodes)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// TODO: generate `THead`, `TBody`, and `TFoot`
|
let row = TagNode::Group(TagKind::TR.into(), row_nodes);
|
||||||
nodes.push(TagNode::Group(TagKind::TR.into(), row_nodes));
|
if row_chunk.is_empty() {
|
||||||
|
is_header_chunk = is_header_row;
|
||||||
|
row_chunk.push(row);
|
||||||
|
} else if is_header_chunk == is_header_row {
|
||||||
|
row_chunk.push(row);
|
||||||
|
} else {
|
||||||
|
let tag = if is_header_chunk { TagKind::THead } else { TagKind::TBody };
|
||||||
|
nodes.push(TagNode::Group(tag.into(), std::mem::take(&mut row_chunk)));
|
||||||
|
|
||||||
|
is_header_chunk = is_header_row;
|
||||||
|
row_chunk.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !row_chunk.is_empty() {
|
||||||
|
let tag = if is_header_chunk { TagKind::THead } else { TagKind::TBody };
|
||||||
|
nodes.push(TagNode::Group(tag.into(), row_chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes
|
nodes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user