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.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ All changes included in 1.10:
- ([#13588](https://github.com/quarto-dev/quarto-cli/issues/13588)): Fix Lua error when rendering PDF with `reference-location: margin` and a footnote alongside a figure with `fig-cap`. (author: @mcanouil)
- ([#14553](https://github.com/quarto-dev/quarto-cli/issues/14553)): Fix font fallbacks (`mainfontfallback`, `sansfontfallback`, `monofontfallback`) crashing LuaLaTeX on TeX Live 2026 (luaotfload v3.29+) instead of filling in missing glyphs. Bare fallback names (e.g. `"DejaVu Sans"`) are now automatically colon-terminated as luaotfload requires; names that already carry a terminator or feature options (e.g. `"FreeSans:"`, `"Noto:mode=harf"`) are left untouched. A clear, actionable error is also reported if a fallback crash is still detected.
- ([#14553](https://github.com/quarto-dev/quarto-cli/issues/14553), [#14558](https://github.com/quarto-dev/quarto-cli/issues/14558)): Fix PDF render failing instead of auto-installing a missing font referenced by `monofontfallback` (and other `mainfont`/`sansfont`/`monofont` fallbacks).
- ([#14575](https://github.com/quarto-dev/quarto-cli/issues/14575)): Fix caption being placed inside the table body instead of below it for cross-referenceable computational (knitr/Jupyter) tables with `tbl-cap-location: bottom`.

### `typst`

Expand Down
21 changes: 20 additions & 1 deletion src/resources/filters/customnodes/floatreftarget.lua
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,29 @@ end, function(float)
end
return result
else
-- For a bottom caption, place the caption inside the longtable
-- foot (immediately before \endlastfoot) so it renders below the
-- table, matching Pandoc's native longtable output. Without this,
-- the caption lands after the data rows but inside the body. See #14575.
-- Tables without a foot (e.g. kable(longtable=TRUE)) fall through
-- to the behavior below.
local foot_pos = cap_loc ~= "top" and content:find("\\endlastfoot", 1, true)
if foot_pos then
return pandoc.Blocks({
pandoc.RawBlock("latex", longtable_preamble),
pandoc.RawBlock("latex", start),
pandoc.RawBlock("latex", content:sub(1, foot_pos - 1)),
latex_caption,
pandoc.RawInline("latex", "\\tabularnewline"),
pandoc.RawBlock("latex", content:sub(foot_pos)),
pandoc.RawBlock("latex", "\\end{longtable}"),
pandoc.RawBlock("latex", longtable_postamble),
})
end
local result = pandoc.Blocks({latex_caption, pandoc.RawInline("latex", "\\tabularnewline")})
-- if cap_loc is top, insert content on bottom
if cap_loc == "top" then
result:insert(pandoc.RawBlock("latex", content))
result:insert(pandoc.RawBlock("latex", content))
else
result:insert(1, pandoc.RawBlock("latex", content))
end
Expand Down
43 changes: 43 additions & 0 deletions tests/docs/smoke-all/2026/06/30/14575-knitr.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# Issue #14575 — knitr engine cases. See the body below for details.
format: pdf
tbl-cap-location: bottom
keep-tex: true
_quarto:
tests:
pdf:
noErrors: default
ensureLatexFileRegexMatches:
-
- '\\end\{document\}'
- '\\caption\{[\s\S]{0,60}CAP7 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP8 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
---

With `tbl-cap-location: bottom`, a knitr `kable()` table that Quarto wraps in
`.cell-output-display` must place its bottom caption in the longtable foot —
`\caption{...}\tabularnewline` immediately before `\endlastfoot` — not inside
the table body. Both assertions above check that placement.

Case 7 is the reported bug (cross-referenced kable with a label); it passes
only after the fix. Case 8 (kable with a caption but no label) already
rendered correctly and acts as a regression guard.

The `kable(longtable = TRUE, caption = ...)` variant is a different shape —
its longtable has no `\endlastfoot`, so the caption is orphaned — and is
tracked separately, out of scope here.

# Case 7: knitr kable with label (reported bug)

```{r}
#| label: tbl-c7
#| tbl-cap: "CAP7 caption"
knitr::kable(head(cars, 3))
```

# Case 8: knitr kable with tbl-cap, no label (guard)

```{r}
#| tbl-cap: "CAP8 caption"
knitr::kable(head(cars, 3))
```
84 changes: 84 additions & 0 deletions tests/docs/smoke-all/2026/06/30/14575.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
# Issue #14575 — markdown-only cases (CI-safe, no engine needed).
# See the body below for what each case exercises.
format: pdf
tbl-cap-location: bottom
keep-tex: true
_quarto:
tests:
pdf:
noErrors: default
ensureLatexFileRegexMatches:
-
- '\\end\{document\}'
- '\\caption\{[\s\S]{0,60}CAP1 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP2 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP3 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP4 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP5 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
- '\\caption\{[\s\S]{0,60}CAP6 caption[\s\S]{0,60}\\tabularnewline\s*\\endlastfoot'
---

With `tbl-cap-location: bottom`, a cross-referenceable table's bottom caption
must land in the longtable foot — `\caption{...}\tabularnewline` immediately
before `\endlastfoot` — not after the data rows inside the table body. Each
`CAPn caption` assertion above checks that placement for one input shape.

Cases 1–4 already rendered correctly before the fix and act as regression
guards. Cases 5–6 wrap the table in a `.cell-output-display` Div (the shape
Quarto produces for knitr/Jupyter output); these triggered the bug and pass
only after the fix.

# Case 1: markdown table, caption syntax, no id

| speed| dist|
|-----:|----:|
| 4| 2|

: CAP1 caption

# Case 2: markdown table, caption syntax, with id

| speed| dist|
|-----:|----:|
| 4| 2|

: CAP2 caption {#tbl-c2}

# Case 3: div float, bare table, trailing caption paragraph

::: {#tbl-c3}
| speed| dist|
|-----:|----:|
| 4| 2|

CAP3 caption
:::

# Case 4: div float, tbl-cap attribute, bare table

::: {#tbl-c4 tbl-cap='CAP4 caption'}
| speed| dist|
|-----:|----:|
| 4| 2|
:::

# Case 5: div float, tbl-cap attribute, .cell-output-display wrapper

::: {#tbl-c5 tbl-cap='CAP5 caption'}
::: {.cell-output-display}
| speed| dist|
|-----:|----:|
| 4| 2|
:::
:::

# Case 6: .cell + .cell-output-display (mcanouil reprex)

::: {#tbl-c6 .cell tbl-cap='CAP6 caption'}
::: {.cell-output-display}
| speed| dist|
|-----:|----:|
| 4| 2|
:::
:::
Loading