«

Typst smartquote algorithm implemented in Typst

Typst contains a “smartquote” algorithm, to automatically balance non-fancy quotes to fancy quotes. It also handles apostrophes and primes. While I don’t think the algorithm is particularly good, it’s probably as correct as any such algorithm can be without having a registry of special (I’ve, ’em, …) character sequences.

Currently, the algorithm is only available to the layout engine, with no direct programmatic access possible. Here, I’ve reimplemented the algorithm in pure Typst, so it can be used to balance strings in addition to content:

Reveal code
let balance-quotes(input) = {
    if input == "" { return "" }

    let graphemes = input.clusters()
    let windows = ((" ", graphemes.at(0)),) + graphemes.windows(2)

    let quote-rule(
        prev, char, nesting-stack,
        rules: ("\"": ("“", "”"), "'": ("‘", "’"))
    ) = {
        if (char not in rules) {
            return (char, nesting-stack)
        }

        let (opening, closing) = rules.at(char)
        let opened = nesting-stack.last(default: none)

        if (
            opened != char
            and prev.contains(regex("\d"))
        ) {
            let prime = ("\"": "″", "'": "′").at(char)
            (prime, nesting-stack)
        } else if (
            char == "'"
            and opened != char
            and prev.contains(regex("[\w\u{FFFC}]"))
        ) {
            ("’", nesting-stack)
        } else if (
            char == opened
            and not prev.contains(regex("[\s\n(\[{]"))
        ) {
            (closing, nesting-stack.slice(0, -1))
        } else {
            (opening, nesting-stack + (char,))
        }
    }

    let (balanced, _) = windows.fold(
        ("", ()),
        ((acc, nesting-stack), (prev, char)) => {
            let (char, nesting-stack) = quote-rule(prev, char, nesting-stack)
            (acc + char, nesting-stack)
        }
    )

    balanced
}