Table setrow/setcol styling proof of concept

This is a proof of concept of #setrow(prop) / #setcol(prop) styling for tables, similar to what exists in the tabularray latex package.

A #setrow(style) call inside a table cell becomes the equvialent to the show rule show table.cell.where(..): style for the particular row the directive was placed.

The proof of concept also has a special case where #setrow(c) sets the fill color of the row (or column) if c is a color. This is done by changing the table fill field, since we can’t change cell fill color using a style rule on table.cell itself.

Style
/// Take element and replace it with a new version of itself with arguments ..args applied
/// pos: which named fields to consider as positional, for replacement
/// if pos is auto, any body or children fields are considered positional
#let replace-elt(element, pos: auto, ..args) = {
  let fields = element.fields()
  if pos == auto { pos = fields.keys().filter(f => f in ("body", "children")) }
  let (fields, posargs) = pos.fold((fields, ()), ((fields, args), elt) => {
    let value = fields.remove(elt)
    (fields, (..args, ..if type(value) == array { value } else { (value, )}))
  })
  (element.func())(..fields, ..posargs, ..args)
}

#assert.eq(
  replace-elt(strong[x], delta: 1),
  strong(delta: 1, [x])
)
#assert.eq(
  replace-elt(table(stroke: 2pt)[x][y], columns: 2, [z]),
  table(stroke: 2pt, columns: 2, [x], [y], [z]),
)

#let prefix = "__setcol_"
#let tablenr = counter(prefix + "tablenr")
#let setrowlab = label(prefix + "setrow")
#let setcollab = label(prefix + "setcol")
#let processedlab = label(prefix + "processed")
#let procrules(tab) = {
  if tab.at("label", default: none) == processedlab {
    return tab
  }
  tablenr.step()
  context {
    let nr = str(tablenr.get().first())
    let rowrules = state(prefix + "rowrules:" + nr, ())
    let colrules = state(prefix + "colrules:" + nr, ())
    let rowv = rowrules.final()
    let colv = colrules.final()
    let show-rules = (
      rowv.map(((y, func)) => (arguments(y: y), func))
      + colv.map(((x, func)) => (arguments(x: x), func)))
    let fill-rules = show-rules.filter(((_, f)) => type(f) == std.color)
    let show-rules = show-rules.filter(((_, f)) => type(f) != std.color)
    let fill = (x, y) => {
      for (rule, color) in fill-rules {
        if rule.at("x", default: none) == x { return color }
        if rule.at("y", default: none) == y { return color }
      }
      if tab.fill != none {
        if type(tab.fill) == function { (tab.fill)(x, y) } else { tab.fill }
      }
    }
    show: doc => {
      let doc = doc
      for (args, func) in show-rules {
        doc = {
          show std.table.cell.where(..args): func
          doc
        }
      }
      doc
    }
    show std.table.cell: it => {
      if it.x == auto or it.y == auto { return it }
      show selector.and(metadata, setrowlab): meta => {
        rowrules.update(arr => {
          (..arr, (it.y, meta.value))
        })
        meta
      }
      show selector.and(metadata, setcollab): meta => {
        colrules.update(arr => {
          (..arr, (it.x, meta.value))
        })
        meta
      }
      it
    }
    [#replace-elt(tab, fill: fill)#processedlab]
  }
}

Document
/// Style current table cell row using the given function.
/// if the argument is a color, set the table fill color for this row.
#let setrow(func) = {
  [#metadata(func)#setrowlab]
}
/// Style current table cell column using the given function.
/// if the argument is a color, set the table fill color for this column.
#let setcol(func) = {
  [#metadata(func)#setcollab]
}


#show table: procrules

#table(
  columns: 2,
  [#setrow(text.with(green))#setcol(strong)  A], [B],
  [#setrow(text.with(purple))#setcol(yellow) C], [#setcol(emph)#setcol(aqua) D],
)

#import "@preview/rowmantic:0.3.0": rowtable, expandcell
#rowtable(
  separator: ",",
  fill: (x, y) => {
    yellow.desaturate(50%)
  },
  [ A #setcol(red),
    B #setcol(blue)#setcol(text.with(white)),
    C,
    D #setcol(yellow)#setrow(strong)#setrow(text.with(1.44em))],
  [E, F, G, H],
  [I, J, K, L],
  [M, N, O, P],
  [Q, R, S, T #setrow(lower)],
  [U, V, X, Y],
  [#expandcell[Z #setrow(aqua)#setrow(align.with(center))], #table.cell(fill: black)[]],
)

The example uses rowmantic/rowtable for creating the table but it is not related or necessary for the setrow/setcol proof of concept at all.


Once we have this proof of concept going we can ask - is this a good idea? Is this a good interface?

I don’t think it’s ideal but it has some good things going for it.

3 Likes