How can I customise the last item in enum?

I want to customise the numbered list function, enum, such that the last item in the list should have the numbering “(C)”, but all the other items should have the numbering “(P1)”. So, it should look like this:

How to do this? I’ve figured out how to define a custom function that does this:

#let custom-enum(..items) = {
  enum(
    numbering: (..nums) => {
      if nums.pos().first() == items.pos().len() { 
        "(C)" 
      } else { 
        "(P" + nums.pos().map(str).join(".") + ")"
      }
    },
    ..items
  )
}

#custom-enum[1][2][3]

But I would like to customise enum directly, not define a separate function.

Any help is appreciated :)

I’ve got a horrid implementation, where I just rebuild the whole list piece by piece and change the numbering for the last entry by just replacing it with the string "(C)". It works but it might not be the best implementation…

#show enum: it => {
  stack(dir: ttb,
    spacing: if (it.tight) {0.6em} else {1em}, // might have to replace with it.spacing
    ..for (i,entry) in it.children.enumerate() {
      (box(
          width: enum.body-indent,
          align(right,
            if i + 1 == it.children.len() {
              "(C)"
            } else {
              numbering("(P1)",entry.number)
            })
          ) + h(0.5em) + entry.body,)
    }
  )
}



1. Hello
2. Hello World
3. This is another World

2 Likes

This show rule builds a new enum but passes along all parameters that the original has except for the numbering which follows the rules you defined. I’m not sure what the intended behavior is for a reversed list.

(edit: used @sijo’s suggestion for dealing with parameters and handling when number is auto. Updated the examples to match)

#show enum: it => {
  if it.has("label") and it.label == <already-processed> { return it }

  let (children, ..parameters) = it.fields()
  
  let content-to-place = enum(
    ..parameters,
    numbering: (..nums) => {
      if nums.pos().first() == {
        if children.last().has("number") and children.last().number != auto { children.last().number} else { children.len() }
      } { 
        "(C)" 
      } else { 
        "(P" + nums.pos().map(str).join(".") + ")"
      }
    },
    ..children
  )
  [#content-to-place<already-processed>]
}

And given these examples:

Normal
1. one
2. two
3. three

Non-Sequential
2. two
4. four
6. six

Reversed
#enum(
  reversed: true,
  [one],
  [two],
  [three]
)

No starting number
+ one
+ two
+ last

this is the output:

4 Likes

For Typst 0.14 I think you need to check if the number field is present but equal to auto.

By the way the code

let parameters = it.fields()
let _ = parameters.remove("children")
let _ = parameters.remove("numbering")

can be simplified to

let (children, ..parameters) = it.fields()

(Then you can also replace all it.children with children.)

You can add numbering on the left hand side but it’s not necessary since it gets overwritten by the explicit parameter in the enum() call below.

3 Likes

I don’t know how to create a situation where it.number is set to auto. If it’s needed, the fix should be swapping from

if children.last().has("number") { children.last().number} else { children.len() }

to

if children.last().has("number") and children.last().number != auto { children.last().number } else { children.len() }

Good tip on simplifying dealing with the parameters, I’ve updated the code in my post above.

It happens if you don’t specify the enum number:

+ A
+ B
+ C
1 Like

You can simplify that a bit as well, by writing

"(P" + numbering("1", ..nums) + ")"

or something like that. Then the premise numbers are defined by regular numbering patterns; it could even use the original numbering:

"(P" + numbering(parameters.numbering, ..nums) + ")"

that is also not entirely right; you can mix + and explicit numbers, and the + will continue counting where the explicit numbers left off:

+ element 1
3. element 3
+ element 4 -- `auto` but not `children.len()`

The following seems to work:

let last-number = if parameters.reversed {
  let it = children.first()
  // for `reversed` this logic works: the first element can't be preceded by explicit numbers
  if it.has("number") and it.number != auto { it.number }
  else { children.len() }
} else {
  // for regular enums, find the last non-auto element
  let numbered = children.enumerate().rev().find(it => it.last().has("number") and it.last().number != auto)
  if numbered != none {
    let (pos, it) = numbered
    // add to the last number the number of elements after it
    it.number + (children.len() - pos - 1)
  } else {
    children.len()
  }
}

it fulfills these extra test cases:

Hybrid numbering
+ one
3. two
+ last

#set enum(reversed: true)
Hybrid reversed (kinda nonsensical)
+ one
5. two
+ last
2 Likes