Skip to content
Closed
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
6 changes: 6 additions & 0 deletions src/format/pdf/format-pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ function createPdfFormat(
extras.metadata = extras.metadata || {};
extras.metadata[kPdfStandardApplied] = standards;
}
// Expose tagging state to Lua filters so they can adjust
// figure placement (e.g., avoid [H] which breaks tag structure)
if (needsTagging) {
extras.metadata = extras.metadata || {};
extras.metadata["pdf-tagging"] = true;
}
}

return extras;
Expand Down
6 changes: 5 additions & 1 deletion src/resources/filters/layout/pandoc3_figure.lua
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ function render_pandoc3_figure()
-- if this ends up in a layout without fig-pos = H, it'll fail
-- 'H' forces it to not float
if figure.identifier == "" then
-- Use [htbp] instead of [H] when PDF tagging is active.
-- [H] (float package) breaks lualatex's tag structure.
-- See https://github.com/quarto-dev/quarto-cli/issues/14164
local forced_pos = option("pdf-tagging", false) and "h" or "H"
figure = _quarto.ast.walk(figure, {
Image = function(image)
image.attributes['fig-pos'] = 'H'
image.attributes['fig-pos'] = forced_pos
return image
end
})
Expand Down
16 changes: 11 additions & 5 deletions src/resources/filters/quarto-post/latex.lua
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,19 @@ function render_latex()
if f ~= nil then
noteHasColumns()
el.content = strip(el.content, f)
tprepend(el.content, {pandoc.RawBlock("latex", "\\begin{figure*}[H]")})
local star_pos = option("pdf-tagging", false) and "h" or "H"
tprepend(el.content, {pandoc.RawBlock("latex", "\\begin{figure*}[" .. star_pos .. "]")})
tappend(el.content, {pandoc.RawBlock("latex", "\\end{figure*}")})
return el, false
end
end
end

local function handle_panel_layout(panel)
-- Use [htbp] instead of [H] when PDF tagging is active.
-- [H] (float package) breaks lualatex's tag structure.
-- See https://github.com/quarto-dev/quarto-cli/issues/14164
local forced_pos = option("pdf-tagging", false) and "h" or "H"
panel.rows = _quarto.ast.walk(panel.rows, {
FloatRefTarget = function(float)
if float.attributes["ref-parent"] == nil then
Expand All @@ -278,14 +283,14 @@ function render_latex()
-- give up
return nil
end
float.attributes[ref .. "-pos"] = "H"
float.attributes[ref .. "-pos"] = forced_pos
return float
end
end,
Figure = function(figure)
if figure.identifier ~= nil then
local ref = refType(figure.identifier) or "fig"
figure.attributes[ref .. "-pos"] = "H"
figure.attributes[ref .. "-pos"] = forced_pos
end
return figure
end
Expand Down Expand Up @@ -337,11 +342,12 @@ function render_latex()
end
})
end
local panel_pos = option("pdf-tagging", false) and "h" or "H"
float.content = _quarto.ast.walk(quarto.utils.as_blocks(float.content), {
PanelLayout = function(panel)
panel.attributes["fig-pos"] = "H"
panel.attributes["fig-pos"] = panel_pos
return panel
end
end
})
return float, false
end,
Expand Down
8 changes: 8 additions & 0 deletions src/resources/filters/quarto-pre/figures.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ function quarto_pre_figures()
if el.attributes[kFigPos] == "FALSE" then
el.attributes[kFigPos] = nil
end
-- Replace fig-pos='H' with 'htbp' when PDF tagging is active.
-- The [H] specifier (float package) breaks lualatex's tag structure,
-- causing /Caption and /Figure to be direct children of /Document.
-- Standard [htbp] works correctly with tagging.
-- See https://github.com/quarto-dev/quarto-cli/issues/14164
if el.attributes[kFigPos] == "H" and option("pdf-tagging", false) then
el.attributes[kFigPos] = "h"
end
local figEnv = param(kFigEnv)

if figEnv and not el.attributes[kFigEnv] then
Expand Down
35 changes: 35 additions & 0 deletions tests/docs/smoke-all/pdf-standard/ua-code-chunk-figure.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "Code chunk figure tag structure"
lang: en
format:
pdf:
pdf-standard: ua-2
keep-tex: true
_quarto:
tests:
run:
# verapdf validation not available on Windows CI
not_os: windows
pdf:
noErrors: default
ensureLatexFileRegexMatches:
- ['\\DocumentMetadata\{', 'pdfstandard=\{ua-2\}', 'tagging=on', '\\begin\{figure\}']
# Must NOT have \begin{figure}[H] — [H] breaks PDF tag structure
# See https://github.com/quarto-dev/quarto-cli/issues/14164
- ['\\begin\{figure\}\[H\]']
---

# Code chunk figure with cross-ref

R code chunk figures with `fig-cap` get automatic `fig-pos='H'` when code is echoed.
The `[H]` specifier (from LaTeX's `float` package) breaks lualatex's PDF tagging,
causing `/Caption` and `/Figure` to be direct children of `/Document` instead of
nested inside `/figures` → `/float`.

```{r}
#| label: fig-test-plot
#| fig-cap: "Test plot"
plot(1:10)
```

See @fig-test-plot.
35 changes: 8 additions & 27 deletions tests/docs/smoke-all/pdf-standard/ua2-unlabeled-figure-caption.qmd
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "UA-2: unlabeled figure caption produces invalid structure"
title: "UA-2: unlabeled figure caption"
lang: en
format:
pdf:
Expand All @@ -14,34 +14,15 @@ _quarto:
noErrors: default
ensureLatexFileRegexMatches:
- ['\\DocumentMetadata\{', 'pdfstandard=\{ua-2\}', 'tagging=on']
- []
printsMessage:
# Known issue: unlabeled captioned figures go through pandoc3_figure.lua
# which produces a bare \begin{figure}[H] environment. LaTeX's tagpdf
# places <Caption> as a sibling of <Figure> under <Document> instead of
# nesting them inside a grouping element. This violates UA-2 which
# requires <Caption> to be a child of <Figure>, <Table>, or <Formula>.
#
# Labeled figures ({#fig-label}) go through FloatRefTarget, which wraps
# the figure in a \Div that provides the grouping context tagpdf needs.
#
# Structure produced (invalid):
# /Document
# /Caption <- should be inside a grouping element
# /Figure <- sibling instead of parent
#
# Expected (valid, as produced by labeled figures):
# /Document
# /Div
# /Caption <- properly nested
# /Figure
level: WARN
regex: "PDF validation failed for ua-2"
# Must NOT have \begin{figure}[H] — [H] breaks PDF tag structure
# See https://github.com/quarto-dev/quarto-cli/issues/14164
- ['\\begin\{figure\}\[H\]']
---

# Known LaTeX tagging limitation
# Unlabeled captioned figure

An unlabeled captioned figure produces invalid UA-2 structure because
tagpdf does not nest `<Caption>` inside a grouping element.
An unlabeled captioned figure. Previously this produced invalid UA-2
structure because `[H]` placement broke lualatex's tag tree. With
`[htbp]` the tag structure is correct.

![This is a caption on an unlabeled figure](penrose.svg)
Loading