How to conditionally set heading block parameters?

Hello typstologists,

I am struggeling to come up with a conditional setting for block parameters in headings. In detail, a heading of level 1 should be shown as follows:

#show heading.where(level:1): it => {
  set block(above: 2em)
  it
}

The heading of level 2 should be shown as follows:

#show heading.where(level: 2): it => {
  set block(above: 2em)
  it
}

However, whenever a level-1-heading directly precedes a level-2-heading I want the above parameter of the latter to be just 0.4em as in the following reprex:

Random stuff being written here.

= A brilliant level 1 heading
== followed directly by a mediocre level 2 heading
This being followed by some random text.

Of course, I could set the above parameter of the level-2-heading accordingly whenever the above mentioned situation occurs, but I was looking for a smarter solution. Is it possible to formulate a conditional show rule in which I could tell typst “show level-2-heading like this, whenever a level-1-heading preceeds”?

Happy to tap your brains and many thanks in advance
Amman

2 Likes

I’ve thought about this as well (e.g., this post where I was hoping to add spacing conditional on what followed the content). I think such conditional typesetting is currently not very easy in Typst. If it were to be done via show rules, I think there would have to be something like a next() function that can be used with context to find what the next element is that follows the current one.

Perhaps there is some way to do this that I’m unaware of (maybe with query), or maybe such functionality is in the works already? Regardless, the best way I can think of solving your issue is to pass all content of the document to a function, which checks the content and inserts spacing around headers conditionally, like below. This code abuses repr and sidesteps Typst’s normal structure for showing. It should not be used.

#let get_header_depth(elem) = {
  if repr(elem.func()) == "heading" { // this abuses repr and should not be used but I don't know an alternative
    return elem.depth
  }
  return none
}

#let add_conditional_header_spaces(txt) = {
  if not txt.has("children") {return txt}
  let elems = txt.children.filter(x => x != [ ])
  for (indx, elem) in elems.enumerate() {
    if get_header_depth(elem) == 1 {
      if indx != elems.len() - 1 and get_header_depth(elems.at(indx + 1)) == 2 {
        [#elem \[SMALL SPACE HERE\] ]
      } else {
        [#elem \[LARGE SPACE HERE\] ]
      }
    } else {
      elem
    }
  }
}

#add_conditional_header_spaces[= hello
#lorem(10)

= goodbye
== a subheading
#lorem(10)

= foo
#lorem(10)
== a subheading
#lorem(10)]

If anyone has suggestions on how conditional seting or showing might be completed based off of neighboring elements I would love to hear it.

1 Like

According to the documentation, weak spacings will always collapse adjacent paragraph spacing, even if the paragraph spacing is larger. I guess this is also the case for heading block spacing.

#show heading: it => {
  set block(above: 2em)
  it
  v(0.4em, weak: true)
}
2 Likes

Sometimes life can be so simple :sunrise:. How could I forget about weak spacings. Thanks a lot for your answer, @sjfhsjfh . That solved the issue.

1 Like