Since table cell dimensions are soft, by default you can’t fill cell’s full height by stretching inner content, as it will just stretch the parent cell. You can provide it a point of reference to patch this, which is a compromise, but the implementation is very simple:
#set page(margin: 1cm)
#show table: set text(9pt)
#let sideways = rotate.with(-90deg, reflow: true)
#let subtable(tallest: none, ..args) = table.cell(inset: 0pt, context {
let rows = auto
if tallest != none { rows = measure(tallest).height + table.inset * 2 }
layout(size => table(
columns: (1fr,) * args.pos().len(),
rows: rows,
table.hline(stroke: 0pt),
..args,
table.hline(stroke: 0pt),
))
})
// Will break nested tables, can be fixed with grids.
// #show table.cell.where(y: 0): strong
// #show table.cell.where(y: 0): set text(white)
// For nested tables to look the same.
#set table(align: center + horizon, stroke: 0.5pt)
#table(
columns: 4,
table.header(
table.cell(colspan: 3, fill: black, text(white)[*Dimension*]),
table.cell(fill: black, text(white)[*Characteristic*]),
),
table.cell(rowspan: 6, sideways[Technical]),
table.cell(rowspan: 3, sideways[Interaction]),
[*Input*],
subtable([Text], [Image], [Audio], [Video]),
[*Output*],
subtable([Text], [Image], [Audio], [Video], [3D-Model]),
[*Interface*],
subtable([Web], [Mobile], [Desktop], [API], [Integrated]),
table.cell(rowspan: 2, sideways[Work]),
[*Type of task*],
subtable([Information processing], [Problem solving], [Social], [Physical]),
[*Method*],
subtable([Autonom], [Teamwork], [Routine]),
table.cell(rowspan: 1, sideways[Use cases]),
[*AI use*],
subtable(
tallest: sideways[Use cases],
[Generation of text],
[Programming],
[Translation],
[Research and education],
[Inspiration and creativity],
),
)
Without tallest
point of reference, it will look like this:
This works for width because of point 3 in How to distribute column widths equally for specific rows in a table - #2 by janekfleper.
But I have a spreadsheet-like document where I want to cross some cells. There is a trick that @Tinger showed me a long time ago. You can pin cell corners without disturbing it, and then use the concrete location data for absolute anchoring. This way, it becomes trivial to get the current cell’s height:
#let make-label(counter, alignment, n: auto) = {
assert(alignment in (top, bottom))
let counter-name = repr(counter).split("\"").at(1)
let alignment-str = repr(alignment)
let n = if n == auto { str(counter.get().first()) } else { str(n) }
label(counter-name + "." + n + "." + alignment-str)
}
#let cell-counter = counter("cell-counter")
#let pin-corner(counter, alignment) = context {
place(alignment)[#metadata(none)#make-label(counter, alignment)]
}
#let pin-top-bottom(counter) = {
pin-corner(counter, top)
pin-corner(counter, bottom)
}
#let cross-cell(counter, body, ..args) = table.cell(
counter.step() + pin-top-bottom(counter) + body,
..args,
)
#let get-pinned-corners(counter) = {
let cell-inset = if table.cell.inset == auto { 5pt } else { cell.inset }
let adjusted-position(pos, alignment) = {
(pos.x - page.margin, pos.y - page.margin)
}
let n = counter.get().first()
let (top, bottom) = (top, bottom).map(alignment => {
adjusted-position(
locate(make-label(counter, alignment, n: n)).position(),
alignment,
)
})
(top.last(), bottom.last())
}
Now using cross-cell
and get-pinned-top-bottom-y
:
#let subtable(..args) = cross-cell(inset: 0pt, cell-counter, context {
let (y1, y2) = get-pinned-top-bottom-y(cell-counter)
layout(size => table(
columns: (1fr,) * args.pos().len(),
rows: y2 - y1,
table.hline(stroke: 0pt),
..args,
table.hline(stroke: 0pt),
))
})
And you get the same result, fully automatically.
Full example
#set page(margin: 1cm)
#show table: set text(9pt)
#let make-label(counter, alignment, n: auto) = {
assert(alignment in (top, bottom))
let counter-name = repr(counter).split("\"").at(1)
let alignment-str = repr(alignment)
let n = if n == auto { str(counter.get().first()) } else { str(n) }
label(counter-name + "." + n + "." + alignment-str)
}
#let cell-counter = counter("cell-counter")
#let pin-corner(counter, alignment) = context {
place(alignment)[#metadata(none)#make-label(counter, alignment)]
}
#let pin-top-bottom(counter) = {
pin-corner(counter, top)
pin-corner(counter, bottom)
}
#let cross-cell(counter, body, ..args) = table.cell(
counter.step() + pin-top-bottom(counter) + body,
..args,
)
#let get-pinned-top-bottom-y(counter) = {
let cell-inset = if table.cell.inset == auto { 5pt } else { cell.inset }
let adjusted-position(pos, alignment) = {
(pos.x - page.margin, pos.y - page.margin)
}
let n = counter.get().first()
let (top, bottom) = (top, bottom).map(alignment => {
adjusted-position(
locate(make-label(counter, alignment, n: n)).position(),
alignment,
)
})
(top.last(), bottom.last())
}
#let sideways = rotate.with(-90deg, reflow: true)
#let subtable(..args) = cross-cell(inset: 0pt, cell-counter, context {
let (y1, y2) = get-pinned-top-bottom-y(cell-counter)
layout(size => table(
columns: (1fr,) * args.pos().len(),
rows: y2 - y1,
table.hline(stroke: 0pt),
..args,
table.hline(stroke: 0pt),
))
})
// Will break nested tables, can be fixed with grids.
// #show table.cell.where(y: 0): strong
// #show table.cell.where(y: 0): set text(white)
// For nested tables to look the same.
#set table(align: center + horizon, stroke: 0.5pt)
#table(
columns: 4,
table.header(
table.cell(colspan: 3, fill: black, text(white)[*Dimension*]),
table.cell(fill: black, text(white)[*Characteristic*]),
),
table.cell(rowspan: 6, sideways[Technical]),
table.cell(rowspan: 3, sideways[Interaction]),
[*Input*],
subtable([Text], [Image], [Audio], [Video]),
[*Output*],
subtable([Text], [Image], [Audio], [Video], [3D-Model]),
[*Interface*],
subtable([Web], [Mobile], [Desktop], [API], [Integrated]),
table.cell(rowspan: 2, sideways[Work]),
[*Type of task*],
subtable([Information processing], [Problem solving], [Social], [Physical]),
[*Method*],
subtable([Autonom], [Teamwork], [Routine]),
table.cell(rowspan: 1, sideways[Use cases]),
[*AI use*],
subtable(
[Generation of text],
[Programming],
[Translation],
[Research and education],
[Inspiration and creativity],
),
)