Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ All changes included in 1.9:

### `typst`

- ([#12803](https://github.com/quarto-dev/quarto-cli/issues/12803)): Fix callout text being centered instead of left-aligned when callout has a cross-reference ID prefix (e.g., `#tip-abc`).
- ([#13249](https://github.com/quarto-dev/quarto-cli/pull/13249)): Update to Pandoc's Typst template following Pandoc 3.8.3 and Typst 0.14.2 support:
- Code syntax highlighting now uses Skylighting by default.
- New template variables `mathfont`, `codefont`, and `linestretch` for font and line spacing customization.
Expand Down
9 changes: 4 additions & 5 deletions src/resources/formats/typst/pandoc/quarto/definitions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,14 @@
)

#let block_with_new_content(old_block, new_content) = {
let d = (:)
let fields = old_block.fields()
fields.remove("body")
let _ = fields.remove("body")
if fields.at("below", default: none) != none {
// TODO: this is a hack because below is a "synthesized element"
// according to the experts in the typst discord...
fields.below = fields.below.abs
}
return block.with(..fields)(new_content)
block.with(..fields)(new_content)
}

#let empty(v) = {
Expand Down Expand Up @@ -163,9 +162,9 @@
children.at(0) + new_title // with icon: preserve icon block + new title
}))

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)

block(below: 0pt, new_title_block) +
old_callout.body.children.at(1))
old_callout.body.children.at(1)))
}

// 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead
Expand Down
25 changes: 25 additions & 0 deletions tests/docs/smoke-all/typst/callout-paragraph-alignment.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: "Callout Paragraph Alignment"
format:
typst:
keep-typ: true
_quarto:
tests:
typst:
ensurePdfTextPositions:
- # Paragraphs inside cross-referenced callout should be left-aligned
- subject: "ShortLine"
relation: leftAligned
object: "LongerParagraph with additional words"
tolerance: 2
- []
noErrors: default
---

::: {.callout-tip #tip-alignment}
## Alignment Test

ShortLine

LongerParagraph with additional words filling out this line.
:::
Loading