To get back to your original approach, I cooked up something:
#let repeated-table(num-repeats: 2, ..args) = {
let options = args.named()
let data = args.pos()
// STEP 1: transform table options to apply to a multiple of the original columns
let columns = options.at("columns", default: ())
let (column-count, columns) = if type(columns) == int {
// for numbers, that's number of columns
(columns, columns * num-repeats)
} else if type(columns) == array and columns != () {
// for arrays with elements, the number of elements is number of columns
(columns.len(), columns * num-repeats)
} else {
// lengths, auto or an empty array mean there's one column
(1, (auto,) * num-repeats)
}
options.columns = columns
// TODO transform other per-column fields, such as align
// STEP 2: separate header and footer from the table body, with repeated cells
let header = if data.len() > 0 and type(data.first()) == content and data.first().func() == table.header {
let (children, ..args) = data.remove(0).fields()
table.header(..args, ..children * num-repeats)
}
let footer = if data.len() > 0 and type(data.last()) == content and data.last().func() == table.footer {
let (children, ..args) = data.pop().fields()
table.footer(..args, ..children * num-repeats)
}
// STEP 3: rearrange the data, so that after a number of rows the next repetition begins
// split into rows
let rows = data.chunks(column-count)
// split into repeats of rows
let num-rows = calc.ceil(rows.len() / num-repeats)
let repeats = rows.chunks(num-rows)
// pad the last repeat so that all have the same number of rows
let empty-row = (none,) * column-count
repeats.last() += (empty-row,) * (num-rows - repeats.last().len())
// join repeats into combined rows
let rows = array.zip(..repeats)
// combine into flat data
data = rows.flatten()
// STEP 4: re-add header and footer to the data
if header != none {
data.insert(0, header)
}
if footer != none {
data.push(footer)
}
// STEP 5: produce table
table(..options, ..data)
}
#align(center,
repeated-table(
num-repeats: 3,
align: center + horizon,
columns: (10%, 15%),
table.header("Left", "Right", repeat: true),
..array.range(1, 29).map(n => ([l#n], [r#n])).flatten(),
table.footer("Left", "Right"),
)
)
It’s inferior to eventually having balanced columns, but it works right now ;) there is a big TODO
in the code; if you use per-column align
, you still need to fix that, but I think the principle is clear from how columns
is transformed for this to work, and the hard part of transforming the data is done.
For nicer styling, I would recommend looking at double strokes in the table guide; by using the correct column gutters, you can separate the “repeats” of the table and achieve nice double strokes at the same time.