Skip to content

fix(typst): left-align cross-ref callouts#14130

Merged
cscheid merged 4 commits intoquarto-dev:mainfrom
mcanouil:fix/issue12803
Feb 27, 2026
Merged

fix(typst): left-align cross-ref callouts#14130
cscheid merged 4 commits intoquarto-dev:mainfrom
mcanouil:fix/issue12803

Conversation

@mcanouil
Copy link
Collaborator

@mcanouil mcanouil commented Feb 27, 2026

Corrects the text alignment issue in typst format's callouts when using a prefix. The text was previously centred, and this change ensures it is left-aligned as expected. Fixes #12803

This also fix the warning arising inside cross-reference callouts because the discarded element was not caught thus the explicit call to return.

@posit-snyk-bot
Copy link
Collaborator

posit-snyk-bot commented Feb 27, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@mcanouil
Copy link
Collaborator Author

If this PR seems good, I will add tests to check alignment using the new tools in the test suite.

@mcanouil mcanouil changed the title fix: typst format's callouts show different text alignment with Prefix fix: left-align cross-ref callouts Feb 27, 2026
@mcanouil mcanouil changed the title fix: left-align cross-ref callouts fix(typst): left-align cross-ref callouts Feb 27, 2026
@gordonwoodhull
Copy link
Contributor

LGTM! I am not sure if ensurePdfTextPositions can be used to test this; you could try having two paragraphs inside the callout and see if they are left-aligned. Whether or not we can test, we should merge this for 1.9

@gordonwoodhull gordonwoodhull added this to the v1.9 milestone Feb 27, 2026
@cscheid cscheid marked this pull request as ready for review February 27, 2026 19:51
@mcanouil
Copy link
Collaborator Author

mcanouil commented Feb 27, 2026

I've tested locally, but did not want yet to spend time on trying to make a test if the proposed two fixes were not suitable.

edit: also missing changelog entry(ies)

}))

block_with_new_content(old_callout,
align(left, block_with_new_content(old_callout,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoding to left here is a little concerning, because it means that in general it won't be possible to affirmatively choose to center figure content. Or am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for callouts? Do we support non-left callout content?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm fiddling to see another approach that would let users define something that would allow changing the alignment while keeping cross-ref callouts/ non cross-ref consistent.

not directly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me scope the align() to allow figure show rule like the one from Andrew to work.

Copy link
Collaborator Author

@mcanouil mcanouil Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, here are the results:

Setting align() inside the show rule either as I did or adding a new rule, like the one from Andrew will forbid any change by the users.

One alternative if we want to set align and allow users to change it would be to use state context variable, but this only change cross-ref callouts.

+ #let quarto-callout-align = state("quarto-callout-align", left)

// callout rendering
// this is a figure show rule because callouts are crossreferenceable
#show figure: it => {
  ...
-  align(left, block_with_new_content(old_callout, block_with_new_content(old_callout,
+  context align(quarto-callout-align.get(), block_with_new_content(old_callout,
    block(below: 0pt, new_title_block) +
    old_callout.body.children.at(1)))
}

Then users can do:

---
format:
  typst:
    include-in-header:
      - text: |
          #quarto-callout-align.update(right)
---

::: {.callout-tip #tip-abc}  
## Tip with Title

This is an example of a callout with a title.

This is an example of a 'folded' caution callout that can be expanded by the user. You can use `collapse="true"` to collapse it by default or `collapse="false"` to make a collapsible callout that is expanded by default.
:::

::: {.callout-tip}  
## Tip with Title

This is an example of a callout with a title.

This is an example of a 'folded' caution callout that can be expanded by the user. You can use `collapse="true"` to collapse it by default or `collapse="false"` to make a collapsible callout that is expanded by default.

:::

Copy link
Collaborator Author

@mcanouil mcanouil Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want this customisation of alignement to work also for non-crossref, it would require more changes: either in Lua to surround calls to callouts with #context align(quarto-callout-align.get(), [, ], or create a wrapper which contains this, then update the Lua filter to call the wrapper.

-> The current #callout() has no selector that would allow changing alignment from a show rule.

Copy link
Collaborator Author

@mcanouil mcanouil Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a (easy) way to keep cross-ref/non cross-ref consistent in all scenarios.

Blocks inherit from the context in which they are called.

  • In cross-ref, "callout" will inherit from "figure" alignment (center)
  • Non cross-ref in most cases will inherits from the page alignement (left)

As stated before, if we set set align(left), align(left)[..., or set a show rule, it means the alignment is hardcoded and cannot be changed unless removed by users.

----
format:
  typst:
    keep-typ: true
---

```{=typst}
#show figure: it => {
  if type(it.kind) != str { return it }
  if it.kind.starts-with("quarto-callout-") {
    return align(left, it)
  }
  it
}
// See that the second rule does not work
#show figure: it => {
  if type(it.kind) != str { return it }
  if it.kind.starts-with("quarto-callout-") {
    return align(right, it)
  }
  it
}
#align(right)[
```

::: {.callout-tip #tip-abc}  
## Tip with Title

This is an example of a callout with a title.

This is an example of a 'folded' caution callout that can be expanded by the user. You can use `collapse="true"` to collapse it by default or `collapse="false"` to make a collapsible callout that is expanded by default.
:::

::: {.callout-tip}  
## Tip with Title

This is an example of a callout with a title.

This is an example of a 'folded' caution callout that can be expanded by the user. You can use `collapse="true"` to collapse it by default or `collapse="false"` to make a collapsible callout that is expanded by default.

:::

```{=typst}
]
```

Copy link
Collaborator Author

@mcanouil mcanouil Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the changes ready with the state variable.
This will makes callouts no longer inherit from context (page, column, etc.)

Screen.Recording.2026-02-27.at.21.56.05.mov

Copy link
Collaborator

@cscheid cscheid Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I should have qualified my "little concerning" more strongly. I'm actually happy with the original PR as it was. This state business is pretty cool, but I think it's a bigger change than I'm ready to put into 1.9 at this stage.

Copy link
Collaborator Author

@mcanouil mcanouil Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state variable is really good when you want to control the state of something across context changes.
The main use in Typst is usually for counters.

For reference, the code from the screencast:

And this in definitions.typ or anywhere before content:

#let quarto-callout-align = state("quarto-callout-align", left)

@gordonwoodhull
Copy link
Contributor

gordonwoodhull commented Feb 27, 2026

Okay, I've pushed a test and changelog.

@cscheid
Copy link
Collaborator

cscheid commented Feb 27, 2026

Ok, let's merge this. Thank you, @andrewheiss!

@cscheid cscheid merged commit 00d595a into quarto-dev:main Feb 27, 2026
51 checks passed
@mcanouil mcanouil deleted the fix/issue12803 branch February 27, 2026 21:13
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.

typst format's callouts show different text alignment with Prefix

4 participants