Different first header and last footer in table

Is it possible to give a table different-style first header, continuing header, continuing footer and final footer, as done by the LaTeX package longtable?
I find solutions that would work to make the first header different, e.g., https://forum.typst.app/t/page-x-of-y-as-part-of-a-table-header/6685, but how can a footer know whether it is the last?

There’s an example of continuation header and footer here (taken from here), I hope that makes it clear how it can be done. We compare the current counter value to the final value to know if we’re on the final footer:

// This is an example of a table
// with continuation headers and footers
//
// See also these links:
// - https://discord.com/channels/1054443721975922748/1221887107350396999
// - https://github.com/typst/typst/issues/735
//

#let table-counter = counter("_table_continuation")

/// This style rule needs to be applied so that we have separate counters per table
#let table-continuation-style(body) = {
  show table: it => table-counter.step() + it
  body
}

#let headcounter() = counter("_table_continuation_header:" + str(table-counter.get().first()))
#let footcounter() = counter("_table_continuation_footer:" + str(table-counter.get().first()))

#let continuation-header(first, cont, inset: auto, ..args) = {
  let cellargs = args.named()
  if inset != auto { cellargs += (inset: inset) }
  table.header(table.cell(..cellargs, context {
    let counter = headcounter()
    counter.step()
    let current = 1 + counter.get().first()
    if current == 1 {
      first
    } else {
      cont
    }
  }))
}

#let continuation-footer(cont, inset: 0pt, last: none, ..args) = {
  // Need to make the cell "invisible" / take no space when not used, so inset is 0pt by default
  let cellargs = args.named()
  if inset != auto { cellargs += (inset: inset) }
  table.footer(table.cell(..cellargs, context {
    let counter = footcounter()
    counter.step()
    let current = 1 + counter.get().first()
    let final = counter.final().first()
    if current < final {
      cont
    } else {
      last
    }
  }))
}


// BEGIN Example Document
#set page(margin: 1cm, width: 10cm, height: 6cm)
#set document(date: none)

#show: table-continuation-style

#show math.equation: set block(breakable: true)

#table(
  stroke: 0.2pt,
  continuation-header([Example], [Example (cont'd)]),
  {
  $
    [ sqrt(2)(cos pi/8 + i sin pi/8)]^12: \
    [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 = \
    // just filler here
    = [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
    = [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
    = [ sqrt(2)(cos pi/8 + i sin pi/8)]^12 \
  $
  },
  continuation-footer[#align(right, box(inset: 5pt)[continued $->$])]
)

// for layout convergence there is a pagebreak separating the two examples.
// Yes, conditionally inserting a footer can cause convergence problems. As a
// last resort it can also be avoided by making sure the continuation footer
// always has the same size, even in the last (empty) instance.
#pagebreak()

Note the convergence comment in the end of the code - in my experience it’s a bit fragile to layout convergence problems. If the two cases of footer are the same size, it should not be a problem.

@bluss already gave the solution, but I had the idea to wrap a second table around the first table. It doesn’t work because both headers appear at the top and both footers at the bottom. Possibly this could be made to work but I don’t see it having any improvements over the existing answer.

Code and output if you're interested
#set page(height: 4cm)
#set page(width: auto)

#table(
  columns: 1,
  row-gutter: 0mm,
  stroke: red,
  
  [Outer header],
  table.cell(
    inset: 0mm, 
    table(
      columns: 1,
      stroke: 0.5pt + blue,
    
      table.header("Inner Header"),
      ..range(6).map(str),
      table.footer("Inner Footer")
    )
  ),
  [Outer footer]
)

Awesome! It now even quite easy to realise what I was trying here in LaTeX :grinning:

Let’s now how this issue will be solved…

1 Like