mirror of
https://github.com/typst/typst
synced 2025-07-27 14:27:56 +08:00
feat: tag table headers and footers
This commit is contained in:
parent
bfcf2bd4cc
commit
3404fecd36
@ -22,6 +22,7 @@ use typst_syntax::Span;
|
|||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
|
|
||||||
use crate::introspection::SplitLocator;
|
use crate::introspection::SplitLocator;
|
||||||
|
use crate::model::TableCellKind;
|
||||||
|
|
||||||
/// Convert a grid to a cell grid.
|
/// Convert a grid to a cell grid.
|
||||||
#[typst_macros::time(span = elem.span())]
|
#[typst_macros::time(span = elem.span())]
|
||||||
@ -217,6 +218,7 @@ impl ResolvableCell for Packed<TableCell> {
|
|||||||
breakable: bool,
|
breakable: bool,
|
||||||
locator: Locator<'a>,
|
locator: Locator<'a>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
|
kind: Smart<TableCellKind>,
|
||||||
) -> Cell<'a> {
|
) -> Cell<'a> {
|
||||||
let cell = &mut *self;
|
let cell = &mut *self;
|
||||||
let colspan = cell.colspan(styles);
|
let colspan = cell.colspan(styles);
|
||||||
@ -224,6 +226,8 @@ impl ResolvableCell for Packed<TableCell> {
|
|||||||
let breakable = cell.breakable(styles).unwrap_or(breakable);
|
let breakable = cell.breakable(styles).unwrap_or(breakable);
|
||||||
let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
|
let fill = cell.fill(styles).unwrap_or_else(|| fill.clone());
|
||||||
|
|
||||||
|
let kind = cell.kind(styles).or(kind);
|
||||||
|
|
||||||
let cell_stroke = cell.stroke(styles);
|
let cell_stroke = cell.stroke(styles);
|
||||||
let stroke_overridden =
|
let stroke_overridden =
|
||||||
cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
|
cell_stroke.as_ref().map(|side| matches!(side, Some(Some(_))));
|
||||||
@ -267,6 +271,7 @@ impl ResolvableCell for Packed<TableCell> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
cell.push_breakable(Smart::Custom(breakable));
|
cell.push_breakable(Smart::Custom(breakable));
|
||||||
|
cell.push_kind(kind);
|
||||||
Cell {
|
Cell {
|
||||||
body: self.pack(),
|
body: self.pack(),
|
||||||
locator,
|
locator,
|
||||||
@ -312,6 +317,7 @@ impl ResolvableCell for Packed<GridCell> {
|
|||||||
breakable: bool,
|
breakable: bool,
|
||||||
locator: Locator<'a>,
|
locator: Locator<'a>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
|
_: Smart<TableCellKind>,
|
||||||
) -> Cell<'a> {
|
) -> Cell<'a> {
|
||||||
let cell = &mut *self;
|
let cell = &mut *self;
|
||||||
let colspan = cell.colspan(styles);
|
let colspan = cell.colspan(styles);
|
||||||
@ -522,6 +528,7 @@ pub trait ResolvableCell {
|
|||||||
breakable: bool,
|
breakable: bool,
|
||||||
locator: Locator<'a>,
|
locator: Locator<'a>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
|
kind: Smart<TableCellKind>,
|
||||||
) -> Cell<'a>;
|
) -> Cell<'a>;
|
||||||
|
|
||||||
/// Returns this cell's column override.
|
/// Returns this cell's column override.
|
||||||
@ -1206,8 +1213,12 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
// a non-empty row.
|
// a non-empty row.
|
||||||
let mut first_available_row = 0;
|
let mut first_available_row = 0;
|
||||||
|
|
||||||
|
let mut cell_kind: Smart<TableCellKind> = Smart::Auto;
|
||||||
|
|
||||||
let (header_footer_items, simple_item) = match child {
|
let (header_footer_items, simple_item) = match child {
|
||||||
ResolvableGridChild::Header { repeat, level, span, items, .. } => {
|
ResolvableGridChild::Header { repeat, level, span, items, .. } => {
|
||||||
|
cell_kind = Smart::Custom(TableCellKind::Header);
|
||||||
|
|
||||||
row_group_data = Some(RowGroupData {
|
row_group_data = Some(RowGroupData {
|
||||||
range: None,
|
range: None,
|
||||||
span,
|
span,
|
||||||
@ -1239,6 +1250,8 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
bail!(span, "cannot have more than one footer");
|
bail!(span, "cannot have more than one footer");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cell_kind = Smart::Custom(TableCellKind::Footer);
|
||||||
|
|
||||||
row_group_data = Some(RowGroupData {
|
row_group_data = Some(RowGroupData {
|
||||||
range: None,
|
range: None,
|
||||||
span,
|
span,
|
||||||
@ -1447,7 +1460,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
|
|
||||||
// Let's resolve the cell so it can determine its own fields
|
// Let's resolve the cell so it can determine its own fields
|
||||||
// based on its final position.
|
// based on its final position.
|
||||||
let cell = self.resolve_cell(cell, x, y, rowspan, cell_span)?;
|
let cell = self.resolve_cell(cell, x, y, rowspan, cell_span, cell_kind)?;
|
||||||
|
|
||||||
if largest_index >= resolved_cells.len() {
|
if largest_index >= resolved_cells.len() {
|
||||||
// Ensure the length of the vector of resolved cells is
|
// Ensure the length of the vector of resolved cells is
|
||||||
@ -1542,6 +1555,10 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
// and footers without having to loop through them each time.
|
// and footers without having to loop through them each time.
|
||||||
// Cells themselves, unfortunately, still have to.
|
// Cells themselves, unfortunately, still have to.
|
||||||
assert!(resolved_cells[*local_auto_index].is_none());
|
assert!(resolved_cells[*local_auto_index].is_none());
|
||||||
|
let kind = match row_group.kind {
|
||||||
|
RowGroupKind::Header => TableCellKind::Header,
|
||||||
|
RowGroupKind::Footer => TableCellKind::Header,
|
||||||
|
};
|
||||||
resolved_cells[*local_auto_index] =
|
resolved_cells[*local_auto_index] =
|
||||||
Some(Entry::Cell(self.resolve_cell(
|
Some(Entry::Cell(self.resolve_cell(
|
||||||
T::default(),
|
T::default(),
|
||||||
@ -1549,6 +1566,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
first_available_row,
|
first_available_row,
|
||||||
1,
|
1,
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
|
Smart::Custom(kind),
|
||||||
)?));
|
)?));
|
||||||
|
|
||||||
group_start..group_end
|
group_start..group_end
|
||||||
@ -1673,6 +1691,9 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
y,
|
y,
|
||||||
1,
|
1,
|
||||||
Span::detached(),
|
Span::detached(),
|
||||||
|
// FIXME: empty cells will within header and footer rows
|
||||||
|
// will prevent row group tags.
|
||||||
|
Smart::Auto,
|
||||||
)?))
|
)?))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1918,6 +1939,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
y: usize,
|
y: usize,
|
||||||
rowspan: usize,
|
rowspan: usize,
|
||||||
cell_span: Span,
|
cell_span: Span,
|
||||||
|
kind: Smart<TableCellKind>,
|
||||||
) -> SourceResult<Cell<'x>>
|
) -> SourceResult<Cell<'x>>
|
||||||
where
|
where
|
||||||
T: ResolvableCell + Default,
|
T: ResolvableCell + Default,
|
||||||
@ -1954,6 +1976,7 @@ impl<'x> CellGridResolver<'_, '_, 'x> {
|
|||||||
breakable,
|
breakable,
|
||||||
self.locator.next(&cell_span),
|
self.locator.next(&cell_span),
|
||||||
self.styles,
|
self.styles,
|
||||||
|
kind,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
@ -810,6 +811,12 @@ pub struct TableCell {
|
|||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Sides<Option<Option<Arc<Stroke>>>>,
|
pub stroke: Sides<Option<Option<Arc<Stroke>>>>,
|
||||||
|
|
||||||
|
// TODO: feature gate
|
||||||
|
pub kind: Smart<TableCellKind>,
|
||||||
|
|
||||||
|
// 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 +854,18 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
|
pub enum TableCellKind {
|
||||||
|
Header,
|
||||||
|
Footer,
|
||||||
|
#[default]
|
||||||
|
Data,
|
||||||
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
use std::cell::OnceCell;
|
use std::cell::OnceCell;
|
||||||
use std::num::NonZeroU32;
|
use std::num::{NonZeroU32, NonZeroUsize};
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use krilla::page::Page;
|
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, Smart, StyleChain};
|
||||||
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, TableCellKind, 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;
|
||||||
@ -126,7 +126,42 @@ impl OutlineCtx {
|
|||||||
|
|
||||||
pub(crate) struct TableCtx {
|
pub(crate) struct TableCtx {
|
||||||
table: Packed<TableElem>,
|
table: Packed<TableElem>,
|
||||||
rows: Vec<Vec<Option<(Packed<TableCell>, Tag, Vec<TagNode>)>>>,
|
rows: Vec<Vec<GridCell>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
enum GridCell {
|
||||||
|
Cell(TableCtxCell),
|
||||||
|
Spanned(usize, usize),
|
||||||
|
#[default]
|
||||||
|
Missing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridCell {
|
||||||
|
fn as_cell(&self) -> Option<&TableCtxCell> {
|
||||||
|
if let Self::Cell(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_cell(self) -> Option<TableCtxCell> {
|
||||||
|
if let Self::Cell(v) = self {
|
||||||
|
Some(v)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TableCtxCell {
|
||||||
|
rowspan: NonZeroUsize,
|
||||||
|
colspan: NonZeroUsize,
|
||||||
|
kind: TableCellKind,
|
||||||
|
header_scope: Smart<TableHeaderScope>,
|
||||||
|
nodes: Vec<TagNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableCtx {
|
impl TableCtx {
|
||||||
@ -137,51 +172,134 @@ impl TableCtx {
|
|||||||
fn insert(&mut self, cell: Packed<TableCell>, nodes: Vec<TagNode>) {
|
fn insert(&mut self, cell: Packed<TableCell>, nodes: Vec<TagNode>) {
|
||||||
let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
let x = cell.x(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
||||||
let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
let y = cell.y(StyleChain::default()).unwrap_or_else(|| unreachable!());
|
||||||
let rowspan = cell.rowspan(StyleChain::default()).get();
|
let rowspan = cell.rowspan(StyleChain::default());
|
||||||
let colspan = cell.colspan(StyleChain::default()).get();
|
let colspan = cell.colspan(StyleChain::default());
|
||||||
|
let kind = cell.kind(StyleChain::default());
|
||||||
|
let header_scope = cell.header_scope(StyleChain::default());
|
||||||
|
|
||||||
// TODO: possibly set internal field on TableCell when resolving
|
// The explicit cell kind takes precedence, but if it is `auto` and a
|
||||||
// the cell grid.
|
// scope was specified, make this a header cell.
|
||||||
let is_header = false;
|
let kind = match (kind, header_scope) {
|
||||||
let span = TableCellSpan { rows: rowspan as i32, cols: colspan as i32 };
|
(Smart::Custom(kind), _) => kind,
|
||||||
let tag = if is_header {
|
(Smart::Auto, Smart::Custom(_)) => TableCellKind::Header,
|
||||||
let scope = TableHeaderScope::Column; // TODO
|
(Smart::Auto, Smart::Auto) => TableCellKind::Data,
|
||||||
TagKind::TH(TableHeaderCell::new(scope).with_span(span))
|
|
||||||
} else {
|
|
||||||
TagKind::TD(TableDataCell::new().with_span(span))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let required_height = y + rowspan;
|
// Extend the table grid to fit this cell.
|
||||||
|
let required_height = y + rowspan.get();
|
||||||
|
let required_width = x + colspan.get();
|
||||||
if self.rows.len() < required_height {
|
if self.rows.len() < required_height {
|
||||||
self.rows.resize_with(required_height, Vec::new);
|
self.rows
|
||||||
|
.resize(required_height, vec![GridCell::Missing; required_width]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let required_width = x + colspan;
|
|
||||||
let row = &mut self.rows[y];
|
let row = &mut self.rows[y];
|
||||||
if row.len() < required_width {
|
if row.len() < required_width {
|
||||||
row.resize_with(required_width, || None);
|
row.resize_with(required_width, || GridCell::Missing);
|
||||||
}
|
}
|
||||||
|
|
||||||
row[x] = Some((cell, tag.into(), nodes));
|
// Store references to the cell for all spanned cells.
|
||||||
|
for i in y..y + rowspan.get() {
|
||||||
|
for j in x..x + colspan.get() {
|
||||||
|
self.rows[i][j] = GridCell::Spanned(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rows[y][x] =
|
||||||
|
GridCell::Cell(TableCtxCell { rowspan, colspan, kind, header_scope, nodes });
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
for row in self.rows.into_iter() {
|
|
||||||
let mut row_nodes = Vec::new();
|
// Only generate row groups such as `THead`, `TFoot`, and `TBody` if
|
||||||
for (_, tag, nodes) in row.into_iter().flatten() {
|
// there are no rows with mixed cell kinds.
|
||||||
row_nodes.push(TagNode::Group(tag, nodes));
|
let mut mixed_row_kinds = false;
|
||||||
|
let row_kinds = (self.rows.iter())
|
||||||
|
.map(|row| {
|
||||||
|
row.iter()
|
||||||
|
.filter_map(|cell| match cell {
|
||||||
|
GridCell::Cell(cell) => Some(cell),
|
||||||
|
&GridCell::Spanned(x, y) => self.rows[y][x].as_cell(),
|
||||||
|
GridCell::Missing => None,
|
||||||
|
})
|
||||||
|
.map(|cell| cell.kind)
|
||||||
|
.reduce(|a, b| {
|
||||||
|
if a != b {
|
||||||
|
mixed_row_kinds = true;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
})
|
||||||
|
.expect("tables must have at least one column")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let Some(mut chunk_kind) = row_kinds.first().copied() else {
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
let mut row_chunk = Vec::new();
|
||||||
|
for (row, row_kind) in self.rows.into_iter().zip(row_kinds) {
|
||||||
|
let row_nodes = row
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|cell| {
|
||||||
|
let cell = cell.into_cell()?;
|
||||||
|
let span = TableCellSpan {
|
||||||
|
rows: cell.rowspan.get() as i32,
|
||||||
|
cols: cell.colspan.get() as i32,
|
||||||
|
};
|
||||||
|
let tag = match cell.kind {
|
||||||
|
TableCellKind::Header => {
|
||||||
|
let scope = match cell.header_scope {
|
||||||
|
Smart::Custom(scope) => table_header_scope(scope),
|
||||||
|
Smart::Auto => krilla::tagging::TableHeaderScope::Column,
|
||||||
|
};
|
||||||
|
TagKind::TH(TableHeaderCell::new(scope).with_span(span))
|
||||||
|
}
|
||||||
|
TableCellKind::Footer | TableCellKind::Data => {
|
||||||
|
TagKind::TD(TableDataCell::new().with_span(span))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(TagNode::Group(tag.into(), cell.nodes))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let row = TagNode::Group(TagKind::TR.into(), row_nodes);
|
||||||
|
|
||||||
|
// Push the `TR` tags directly.
|
||||||
|
if mixed_row_kinds {
|
||||||
|
nodes.push(row);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: generate `THead`, `TBody`, and `TFoot`
|
// Generate row groups.
|
||||||
nodes.push(TagNode::Group(TagKind::TR.into(), row_nodes));
|
if row_kind != chunk_kind {
|
||||||
|
let tag = match chunk_kind {
|
||||||
|
TableCellKind::Header => TagKind::THead,
|
||||||
|
TableCellKind::Footer => TagKind::TFoot,
|
||||||
|
TableCellKind::Data => TagKind::TBody,
|
||||||
|
};
|
||||||
|
nodes.push(TagNode::Group(tag.into(), std::mem::take(&mut row_chunk)));
|
||||||
|
|
||||||
|
chunk_kind = row_kind;
|
||||||
|
}
|
||||||
|
row_chunk.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !row_chunk.is_empty() {
|
||||||
|
let tag = match chunk_kind {
|
||||||
|
TableCellKind::Header => TagKind::THead,
|
||||||
|
TableCellKind::Footer => TagKind::TFoot,
|
||||||
|
TableCellKind::Data => TagKind::TBody,
|
||||||
|
};
|
||||||
|
nodes.push(TagNode::Group(tag.into(), row_chunk));
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes
|
nodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) enum TagNode {
|
pub(crate) enum TagNode {
|
||||||
Group(Tag, Vec<TagNode>),
|
Group(Tag, Vec<TagNode>),
|
||||||
Leaf(Identifier),
|
Leaf(Identifier),
|
||||||
@ -489,6 +607,14 @@ fn start_artifact(gc: &mut GlobalContext, loc: Location, kind: ArtifactKind) {
|
|||||||
gc.tags.in_artifact = Some((loc, kind));
|
gc.tags.in_artifact = Some((loc, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn table_header_scope(scope: TableHeaderScope) -> krilla::tagging::TableHeaderScope {
|
||||||
|
match scope {
|
||||||
|
TableHeaderScope::Both => krilla::tagging::TableHeaderScope::Both,
|
||||||
|
TableHeaderScope::Column => krilla::tagging::TableHeaderScope::Column,
|
||||||
|
TableHeaderScope::Row => krilla::tagging::TableHeaderScope::Row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn artifact_type(kind: ArtifactKind) -> ArtifactType {
|
fn artifact_type(kind: ArtifactKind) -> ArtifactType {
|
||||||
match kind {
|
match kind {
|
||||||
ArtifactKind::Header => ArtifactType::Header,
|
ArtifactKind::Header => ArtifactType::Header,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user