Skip to content

render: keep literal text from continuing an embedded expression - #1503

Merged
developer0hye merged 1 commit into
developer0hye:mainfrom
aktanazat:fix/linebreak-terminator
Sep 2, 2026
Merged

render: keep literal text from continuing an embedded expression#1503
developer0hye merged 1 commit into
developer0hye:mainfrom
aktanazat:fix/linebreak-terminator

Conversation

@aktanazat

Copy link
Copy Markdown
Contributor

Summary

Typst keeps parsing an embedded code expression after its closing bracket. Markup that starts with ( becomes an argument list, [ a trailing content argument, and .name a field access. A spreadsheet cell reading "New York" over "(07) Western" produced #linebreak()(07) and the whole document failed with Typst compilation failed: expected function, found content.

Two places let bare text follow a call:

  • Inside one escaped string, escape_typst now ends the hard break with a semicolon (#linebreak();), the same terminator the code-mode space strings already use.
  • At a run boundary, the existing #[...] safety wrap in write_run_content only looked for a preceding ]. It now also covers ), which closes #linebreak(), #h(..), #o2p-pptx-space(), and the PowerPoint grid words.

Related issue

None filed. Reproduced on a public spreadsheet (VA VALERI fee cost schedule, cell "New York\n(07) Western").

Testing

  • cargo test -p office2pdf --lib (2780 passed)
  • cargo fmt --all --check, cargo clippy -p office2pdf --all-targets (only the pre-existing effective_last_resort_family dead-code warning)
  • New tests: test_escape_typst_terminates_hard_linebreak_before_parenthesis, test_escape_typst_terminates_hard_linebreak_before_field_access, test_render_document_hard_linebreak_before_parenthesis (asserts the terminator never renders), test_fixed_page_text_box_soft_line_break_before_parenthesis_compiles, test_fixed_page_text_box_grid_word_before_parenthesised_run_compiles. All five fail on main and pass here.
  • Verified with typst 0.15.1 that A#linebreak().5 percent compiles (a leading .5 is a number), A#linebreak().NET fails with linebreak does not have field "NET", and A#h(1em)(07) B fails with expected function, found content.

Visual impact

  • No rendered PDF change
  • Rendered PDF change or visual evidence added
  • Reason: the semicolon and the #[...] wrap are consumed by the parser; documents that compiled before produce byte-identical Typst layout, and documents that failed before now compile. The render test asserts no ; reaches the text layer.

Checklist

  • Commits include a Signed-off-by line
  • PR scope contains one root cause
  • Remaining visual deviations each reference an open issue (none observed)

Typst keeps parsing an embedded code expression after its closing
bracket. Following markup that starts with `(` is an argument list,
`[` a trailing content argument, and `.name` a field access, so a cell
that reads "New York" over "(07) Western" produced `#linebreak()(07)`
and failed the whole document with "expected function, found content".

Two places let bare text follow a call. Inside one escaped string the
hard break now ends with a semicolon, the terminator the code-mode
space strings already use. At a run boundary the existing `#[...]`
safety wrap only looked for a preceding `]`; it now also covers `)`,
which closes `#linebreak()`, `#h(..)`, and the PowerPoint grid words.

Assisted-by: LLM (Claude)
Signed-off-by: Aktan Azat <aktanaazat@gmail.com>
@aktanazat
aktanazat force-pushed the fix/linebreak-terminator branch from 8de7c08 to 3586736 Compare September 1, 2026 23:28
@developer0hye
developer0hye merged commit 151e0fd into developer0hye:main Sep 2, 2026
18 checks passed
@developer0hye

Copy link
Copy Markdown
Owner

Thank you for this fix, and for the precise diagnosis. Merged as 151e0fd.

What I verified before merging:

  • Reproduced the parser behaviour with the typst 0.15.1 CLI: New York#linebreak()(07) Western and A#h(1em)(07) B fail with "expected function, found content", Runtime#linebreak().NET pricing fails with linebreak does not have field "NET", and both the ; terminator and the #[...] wrap fix them. The same holds inside a #text(...)[...] content block, and the semicolon never reaches the text layer.
  • Traced the three other places that still emit a bare #linebreak() (the PowerPoint line split, the overlong-Latin chunk boundary from fix(docx): wrap overlong Latin table tokens #1456, and the PPTX soft-break marker). Every one of them is followed by text that goes through write_run_content, so the new ) guard covers them too.
  • Nothing else in the render code pattern-matches the literal #linebreak() string, so the trailing ; cannot confuse a measurement path.

One note for the record, not a request: the guard also fires after a literal ) in text (e.g. "(a)" followed by a run starting with "("), which only adds a harmless #[...]. And the protection is implicit in that it relies on all bare text passing through write_run_content; a future emitter that bypasses it would reintroduce the failure. Fine as is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants