How to align texts in different sizes and not in monospaced font?

I am doing interlinear Leipzig glossing to explain a sentence. Like this:
example

where “1SG” (note that all big letters are smallcaps) means “I” or “me” or “ich” and so on.
(liebe should be lieb-e but it doesn’t matter for my question.)

It is required to align each pair of word-explaining (left alignment). But the explaining is smaller than the sentence and the font is not monospaced. Is there any way to align each pair?

Grid and Table are not so feasible, for it is necessary to use #h() to adjust their position. Also, the smallcaps are annoying.

#h(44pt)Ed-ex~~~~~~~ lim[n]-bviv~~~~~~~ ṣa-töl~~~~~~~~~~~~~~~~ kof-u.\
#h(44pt)#text(size:9pt)[#show regex("[A-Z]"): x => smallcaps(lower(x))
  1SG-ERG~~~~~~~~2SG-DAT~~~~~~~~~~~~~~~~~~~NMLZ-cook-ABS~~~~~~~teach-INF
  ]\
#h(44pt) (我打算教你做饭。)\ \

屏幕截图 2025-05-15 231038

I want something like & in math equation.

Hello. Have you searched for a package https://typst.app/universe/search?kind=packages&q=gloss? I don’t understand why you can’t use grid, this is the most straightforward solution.

#let glossing(..pairs) = grid(
  columns: pairs.pos().len(),
  column-gutter: 2em,
  row-gutter: 1em,
  ..array
    .zip(..pairs.pos().map(((a, b)) => (a, text(9pt, smallcaps(lower(b))))))
    .flatten()
)

#glossing(
  ([Ich], [1SG.NOM]),
  ([lieb-e], [1OVE-1SG]),
  ([dich.], [2SG-ACC.]),
)

image

well, because I want to use #h() but Grid cannot, otherwise it is a good idea.

Why do you want to use h if you are looking for an automatic alignment (without h)?

1 Like

To coordinate it with the whole text.
But, if I type

#glossing(
  ([~~~~], [~~~~])
  ([Ich], [1sg.nom]),
  ([lieb-e], [1ove-1sg]),
  ([dich.], [2sg-acc.]),
)

I guess #h() can be replaced.

By the way, I hope to set the size of the explaination to 9pt. (seems not hard)

Hi @Wen_Lizhe ,

May I suggest you format your code according to How to post in the Questions category

This will help us help you. You can edit your previous messages accordingly. Thanks.

2 Likes

Are you trying to move grid to the right?

#let glossing(indent: 1em, ..pairs) = pad(left: indent, grid(
  columns: pairs.pos().len(),
  column-gutter: 2em,
  row-gutter: 1em,
  ..array
    .zip(..pairs.pos().map(((a, b)) => (a, text(9pt, smallcaps(lower(b))))))
    .flatten()
))

#glossing(
  ([Ich], [1SG.NOM]),
  ([lieb-e], [1OVE-1SG]),
  ([dich.], [2SG-ACC.]),
)

not really.
like this:

I don’t want it to be “center” for there are several sentences, and not “left” for that’s not pretty.

I hope it can follow texts like “例:” in the picture (which means "For example: ")

I don’t understand what this refers to.

In can:

#let glossing(comment: none, ..pairs) = grid(
  columns: pairs.pos().len(),
  column-gutter: 2em,
  row-gutter: 0.5em,
  ..array
    .zip(..pairs.pos().map(((a, b)) => (a, text(9pt, smallcaps(lower(b))))))
    .flatten(),
  ..if comment != none { (grid.cell(colspan: pairs.pos().len(), comment),) }
)

#let glossing-examples(examples) = grid(
  columns: 2,
  gutter: 1em,
  [例:], examples.children.intersperse(v(1em)).join(),
)

#glossing-examples({
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
})

image

1 Like

Great.
And I changed some details to fit my need:

#let big-small(explaination) = text(size:9pt)[
  #show regex("[A-Z]"): x => smallcaps(lower(x))
  #explaination
]

#let glossing(comment: none, ..pairs) = grid(
  columns: pairs.pos().len(),
  column-gutter: 2em,
  row-gutter: 0.5em,
  ..array
    .zip(..pairs.pos().map(((a, b)) => (a, big-small(b))))
    .flatten(),
  ..if comment != none { (grid.cell(colspan: pairs.pos().len(), comment),) }
)

#let glossing-examples(examples) = grid(
  columns: 2,
  gutter: 1em,
  [~~~~~~~~例:], examples.children.intersperse(v(1em)).join(),
)

#glossing-examples({
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [love-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [love-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [love-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
})

and the result is
屏幕截图 2025-05-16 095356

However, there is a bug: when there is only one sentence, it is wrong:
屏幕截图 2025-05-16 100953

If this bug can be solved, then nothing is troublesome.

I coped with the bug by adding a blank to the end:

glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [love-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)]
  )
glossing(
    ([],[])
  )

and using

#v(-30pt)

Yep. The problem is that both grid and sequence have children, so it switches from one to another when there is only one example. A better spacing between examples can be achieved with show-set rule inside a show rule:

#let glossing(comment: none, ..pairs) = {
  let columns = pairs.pos().len()
  let style-mapper = ((a, b)) => (a, text(9pt, smallcaps(all: true, b)))
  comment = if comment != none { (grid.cell(colspan: columns, comment),) }
  grid(
    columns: columns,
    column-gutter: 2em,
    row-gutter: 0.5em,
    ..array.zip(..pairs.pos().map(style-mapper)).flatten(),
    ..comment,
  )
}

#let glossing-examples(examples) = {
  show grid: it => {
    show grid: set block(spacing: 2em) // Spacing between examples.
    it
  }
  grid(
    columns: (auto, 1fr),
    gutter: 1em,
    [例:], block(examples),
  )
}

#glossing-examples({
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)],
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)],
  )
  glossing(
    ([Ich], [1SG.NOM]),
    ([lieb-e], [1OVE-1SG]),
    ([dich.], [2SG-ACC.]),
    comment: [(Comment.)],
  )
})

Now it should be good to go.

Don’t use no-break space, again, use pad.

1 Like

I will use pad, but why?

pad is more appropriate, idiomatic, readable, precise, and robust.

1 Like

I want to show off an alternative solution, mostly for fun. I’m interested in using existing typst syntax and making documents nice to write.

Because here we have words and descriptions of each word, I don’t think it’s out of line to repurpose the term definition list syntax for this. If needed it can be scoped so that only certain term lists are styled like this.

#show terms: it => {
  set block(spacing: 0.5em)
  let words = it.children.map(ch => ch.term)
  let desc = it.children.map(ch => ch.description)
  let comment = if [-comment] in words {
    let pos = words.position(elt => elt == [-comment])
    let _ = words.remove(pos)
    desc.remove(pos)
  }
  
  show <description>: it => {
    show regex("[A-Z]+"): smallcaps.with(all: true)
    set text(size: 0.9em)
    it
  }
  let desc = desc.map(elt => [#grid.cell(elt)<description>])
  grid(
    columns: (auto, ) * words.len(),
    gutter: 1em,
    ..words, ..desc,
    ..if comment != none { (grid.cell(colspan: words.len(), comment), ) }
  )
}

Document text is here.

#let example(body) = {
  show: pad.with(left: 2em)
  set enum(numbering: x => strong[Example:])
  enum.item(body)
}
#example[
  / Ich : 1SG.NOM
  / lieb-e: love-1SG
  / dich: 2SG-ACC
  / -comment: (Comment.)
  ~
  / Ed-ex : 1SG-ERG
  / lim[n]-bviv: 2SG-DAT
  / los-alo: an-apple-ABS
  / pask-an-va.: give-PFV-3SG
  / -comment: (This is an example from the picture.)
]

I don’t mind the nice solutions you posted here already, but I wanted to share an alternate way to do it.

3 Likes