Skip to content

Typst writer: leading-dot string arguments get an invalid backslash prepended #11761

Description

@cderv

A string that starts with a literal . gets a spurious \ prepended when the Typst writer emits it as a string-literal argument. The clearest case is a relative --bibliography path, but it hits any leading-dot string routed through escapeTypst (image paths, etc.).

Command and input:

printf 'test\n' | pandoc -s -f markdown -t typst --bibliography=../refs.bib

What pandoc emits:

#bibliography(("\../refs.bib"))

Expected #bibliography(("../refs.bib")) — a clean forward-slash path.

The backslash is invalid Typst. Typst 0.15 rejects it outright:

error: path must not contain a backslash
    ┌─ doc.typ:151:14
    │
151 │ #bibliography(("\../refs.bib"))
    │               ^^^^^^^^^^^^^^^^
    │
    = hint: use forward slashes instead: `"/../refs.bib"`
    = hint: in earlier Typst versions, backslashes indicated path separators on Windows
    = hint: this behavior is no longer supported as it is not portable

Any leading-dot string is affected; strings without a leading dot are fine:

--bibliography=./refs.bib   →  #bibliography(("\./refs.bib"))
--bibliography=sub/refs.bib →  #bibliography(("sub/refs.bib"))     (clean)

Regression: absent in pandoc 3.8.3, present in 3.10. The commit below is in tags 3.9.0.1 / 3.9.0.2 / 3.10 but not 3.8.3.

The cause could be the leading-. rule added to escapeTypst in 80c704a (#11511, "properly escape . after bracketed argument"). That rule prepends \ before a . at the very start of a string, which is correct when the text follows a ]/) in body content (e.g. .foo\.foo). But metadata string values are escaped through the same function — metaToContext renders them via inlinesToTypst, which calls escapeTypst — so a --bibliography path lands there too and gets the same leading \, where in a Typst string literal it's invalid:

escapeTypst :: Bool -> EscapeContext -> Text -> Doc Text
escapeTypst smart context t =
(case T.uncons t of
Just (c, rest)
| c == ';' -> char '\\' -- see #9252
| c == '.'
, not (T.null rest) -> char '\\' -- see #11511
| needsEscapeAtLineStart c || isOrderedListMarker t
-> afterBreak "\\"
_ -> mempty) <>
literal (snd $ T.foldl' go ('\n', mempty) t)

Where the right fix belongs (making the leading-. escape context-aware, or not routing verbatim path arguments through escapeTypst) is your call — I may be missing the context that rule guards against.

Version: pandoc 3.10 on Windows 11.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions