How to typeset correct letterspacing ("Sperrsatz") for German typography

In German typography, Sperrsatz (letterspacing) is the traditional way to emphasize text – as italics are in English.

The rules are:

  1. The spacing between letters is increased (e.g. by 0.125em, standard)
  2. The word spacing before and after the emphasized word is increased by the same amount
  3. A compensating space equal to the tracking value is added before the first letter and after the last letter of the spaced text – but not before punctuation

What I tried

Attempt 1: tracking alone

#let ls(content) = text(tracking: 0.125em, content)

Problem: tracking adds space after every letter except the last one. So the compensating space before the first letter and after the last letter is missing.

Attempt 2: tracking + spacing

#let ls(content) = text(tracking: 0.125em, spacing: 125%, content)

Problem: spacing does not seem to affect word spacing when only a single word is passed.

Attempt 3: manual compensating space before first letter

#let ls(content) = h(0.125em) + text(tracking: 0.125em, content) + h(0.125em) 

Problem: This adds the compensating space before the first letter, but also adds it after the last letter – which causes an unwanted gap after punctuation. That is, the tracking handles the punctuation correctly (so there is no additional space between a letter and punctuation), but after the punctuation the additional space is added.

Is there a way to achieve the correct Sperrsatz in Typst? Does anyone have a solution or workaround?

Here’s a crack at it. The second to last line shows possibly unwanted behavior when the function is applied to the first word on a line.

Code to produce image
#set page(height: auto)
#set page(margin: 1em)
#set page(width: 20em)

#let ls(content) = {
  set text(
    tracking: 0.125em,
    spacing: 125%
  )
  
  let sp = box(stroke: 0.25pt + yellow, height: .5em, h(0.125em))
  let neg-sp = h(-0.125em)
  

  //Add space before and after each word
  show regex(`\w+`.text): word => box(stroke: 0.25pt + blue, [#sp#word#sp])

  //Remove any spacing before punctuation
  show regex(`\p{P}`.text): punctuation => box(stroke: 0.25pt + orange, [#neg-sp#punctuation])
  
  box(
    stroke: 0.25pt + red,
    {
      content
    }
  )
}

Fischer Fritz fischt #ls[fresche] Fisch.\
Fischer Fritz fischt fresche #ls[Fisch].\
Fischer Fritz fischt fresche #ls[Fisch.]\
Fischer Fritz fischt #ls[fresche Fisch].\
Fischer Fritz fischt #ls[fresche Fisch.]\
#ls[Fischer Fritz fischt fresche Fisch.  Fischer Fritz fischt fresche Fisch.]\

Function without debugging:

#let ls(content, space-size: 0.125em) = {
  set text(
    tracking: space-size,
    spacing: 125%
  )
  
  //Add space before and after each word
  show regex(`\w+`.text): word => [#h(space-size)#word#h(space-size)]

  //Remove any spacing before punctuation
  show regex(`\p{P}`.text): punctuation => [#h(-space-size)#punctuation]
  
  content
}
1 Like

I’ve also tried a few things, and this is what works best by now:

#let ls(body) = context {
  set text(
    tracking: 0.125em,
    spacing: 0.4em,
    discretionary-ligatures: false,
    ligatures: false,
    kerning: true,
  )
  show regex("[\.]"): pt => [#{
    set text(tracking: 0em)
    pt
  }]
  show smartquote: sq => {
    set text(tracking: 0em)
    sq
  }
  show regex("[0-9]"): pt => [#{
    set text(tracking: 0em)
    pt
  }]
  h(0.4em, weak: true)
  body
  h(0.4em, weak: true)
}
1 Like