diff --git a/src/resources/filters/customnodes/floatreftarget.lua b/src/resources/filters/customnodes/floatreftarget.lua index 066ef70dc9..b21965a18d 100644 --- a/src/resources/filters/customnodes/floatreftarget.lua +++ b/src/resources/filters/customnodes/floatreftarget.lua @@ -266,10 +266,12 @@ end, function(float) arg = pandoc.Str(float.identifier) }) latex_caption:insert(1, label_cmd) + local latex_caption_content = latex_caption + latex_caption = quarto.LatexInlineCommand({ name = caption_cmd_name, opt_arg = fig_scap, - arg = pandoc.Span(quarto.utils.as_inlines(latex_caption or {}) or {}) -- unnecessary to do the "or {}" bit but the Lua analyzer doesn't know that + arg = pandoc.Span(quarto.utils.as_inlines(latex_caption_content or {}) or {}) -- unnecessary to do the "or {}" bit but the Lua analyzer doesn't know that }) if float.parent_id then @@ -287,6 +289,56 @@ end, function(float) end end + -- we need Pandoc to render its table ahead of time in order to + -- do the longtable fixups below + float.content = _quarto.ast.walk(quarto.utils.as_blocks(float.content), { + Table = function(tbl) + return pandoc.RawBlock("latex", pandoc.write(pandoc.Pandoc({tbl}), "latex")) + end + }) + + if float_type == "tbl" then + local raw + -- have to redo this as_blocks() call here because assigning to float.content + -- goes through our AST metaclasses which coalesce a singleton list to a single AST element + _quarto.ast.walk(quarto.utils.as_blocks(float.content), { + RawBlock = function(el) + if _quarto.format.isRawLatex(el) and el.text:match(_quarto.patterns.latexLongtablePattern) then + raw = el + end + end + }) + -- special case for singleton longtable floats + if raw then + local longtable_content = raw.text:gsub(_quarto.patterns.latexLongtablePattern, "%2", 1) + -- split the content into params and actual content + -- params are everything in the first line of longtable_content + -- actual content is everything else + local params, content = longtable_content:match("^(.-)\n(.*)$") + local cap_loc = cap_location(float) + if float.parent_id then + -- need to fixup subtables because longtables don't support subcaptions, + -- and longtable captions increment the wrong counter + -- we try our best here + + fatal("longtables are not supported in subtables.\n" .. + "This is not a Quarto bug - the LaTeX longtable environment doesn't support subcaptions.\n") + return {} + else + 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)) + else + result:insert(1, pandoc.RawBlock("latex", content)) + end + result:insert(1, pandoc.RawBlock("latex", "\\begin{longtable}" .. params)) + result:insert(pandoc.RawBlock("latex", "\\end{longtable}")) + return result + end + end + end + local figure_content local pt = pandoc.utils.type(float.content) if pt == "Block" then diff --git a/src/resources/filters/main.lua b/src/resources/filters/main.lua index d5d5223da1..189fd1d611 100644 --- a/src/resources/filters/main.lua +++ b/src/resources/filters/main.lua @@ -253,11 +253,7 @@ local quarto_pre_filters = { { name = "pre-table-captions", filter = table_captions(), flags = { "has_table_captions" } }, - - { name = "pre-longtable-no-caption-fixup", - filter = longtable_no_caption_fixup(), - flags = { "has_longtable_no_caption_fixup" } }, - + { name = "pre-code-annotations", filter = code_annotations(), flags = { "has_code_annotations" } }, @@ -346,6 +342,7 @@ local quarto_post_filters = { { name = "layout-meta-inject-latex-packages", filter = layout_meta_inject_latex_packages() }, -- format fixups post rendering + { name = "post-render-latex-fixups", filter = render_latex_fixups() }, { name = "post-render-html-fixups", filter = render_html_fixups() }, { name = "post-render-ipynb-fixups", filter = render_ipynb_fixups() }, { name = "post-render-typst-fixups", filter = render_typst_fixups() }, diff --git a/src/resources/filters/quarto-post/latex.lua b/src/resources/filters/quarto-post/latex.lua index 2879b77840..608b4281ed 100644 --- a/src/resources/filters/quarto-post/latex.lua +++ b/src/resources/filters/quarto-post/latex.lua @@ -378,4 +378,24 @@ function render_latex() return pandoc.Div(calloutContents) end } -end \ No newline at end of file +end + + +function render_latex_fixups() + if not _quarto.format.isLatexOutput() then + return {} + end + + return { + RawBlock = function(raw) + if _quarto.format.isRawLatex(raw) then + if (raw.text:match(_quarto.patterns.latexLongtablePattern) and + not raw.text:match(_quarto.patterns.latexCaptionPattern)) then + raw.text = raw.text:gsub( + _quarto.patterns.latexLongtablePattern, "\\begin{longtable*}%2\\end{longtable*}", 1) + return raw + end + end + end + } +end diff --git a/src/resources/filters/quarto-pre/parsefiguredivs.lua b/src/resources/filters/quarto-pre/parsefiguredivs.lua index 0930234daf..f543a9c589 100644 --- a/src/resources/filters/quarto-pre/parsefiguredivs.lua +++ b/src/resources/filters/quarto-pre/parsefiguredivs.lua @@ -273,6 +273,9 @@ function parse_reftargets() -- respect single table in latex longtable fixups above if skip_outer_reftarget then + -- we also need to strip the div identifier here + -- or we end up with duplicate identifiers which latex doesn't like + div.identifier = "" div.content = content return div end diff --git a/src/resources/filters/quarto-pre/table-captions.lua b/src/resources/filters/quarto-pre/table-captions.lua index 4a76b2c725..d12460805b 100644 --- a/src/resources/filters/quarto-pre/table-captions.lua +++ b/src/resources/filters/quarto-pre/table-captions.lua @@ -6,21 +6,6 @@ local patterns = require("modules/patterns") kTblCap = "tbl-cap" kTblSubCap = "tbl-subcap" -function longtable_no_caption_fixup() - return { - RawBlock = function(raw) - if _quarto.format.isRawLatex(raw) then - if (raw.text:match(_quarto.patterns.latexLongtablePattern) and - not raw.text:match(_quarto.patterns.latexCaptionPattern)) then - raw.text = raw.text:gsub( - _quarto.patterns.latexLongtablePattern, "\\begin{longtable*}%2\\end{longtable*}", 1) - return raw - end - end - end - } -end - function table_captions() return { Div = function(el) diff --git a/tests/docs/smoke-all/2023/11/16/7604.qmd b/tests/docs/smoke-all/2023/11/16/7604.qmd new file mode 100644 index 0000000000..9577b89cc2 --- /dev/null +++ b/tests/docs/smoke-all/2023/11/16/7604.qmd @@ -0,0 +1,29 @@ +--- +title: "7604" +format: pdf +--- + +```{r} +library(kableExtra) +``` + +```{r} +#| label: tbl-sad-broken +#| tbl-cap: "This table is cross-referenceable but no longer breaks across pages because it's inside `\\centering{}`" +rbind(mtcars, mtcars) |> + kbl(longtable = TRUE, booktabs = TRUE) |> + kable_styling() +``` + +::: {#tbl-also-sad} +```{r} +rbind(mtcars, mtcars) |> + kbl(longtable = TRUE, booktabs = TRUE) |> + kable_styling() +``` + +This table is also cross-referenceable but also doesn't break across pages because of the same `\centering{}` problem +::: + + +see @tbl-sad-broken and @tbl-also-sad. diff --git a/tests/docs/smoke-all/crossrefs/float/latex/latex-longtable-fixup-test.qmd b/tests/docs/smoke-all/crossrefs/float/latex/latex-longtable-fixup-test.qmd index 62802b72aa..e8365a52b1 100644 --- a/tests/docs/smoke-all/crossrefs/float/latex/latex-longtable-fixup-test.qmd +++ b/tests/docs/smoke-all/crossrefs/float/latex/latex-longtable-fixup-test.qmd @@ -7,9 +7,6 @@ _quarto: ensureFileRegexMatches: - - "\\label{tbl-test}" - - "\\label{tbl-test-two-tables}" - - "\\label{tbl-test-two-tables-1}" - - "\\label{tbl-test-two-tables-2}" - [] --- @@ -37,23 +34,3 @@ kable(df, ``` See @tbl-test. - -```{r} -#| echo: false -#| label: tbl-test-two-tables -#| tbl-cap: Overall caption - -kable(df, - format = "latex", - longtable = TRUE, - booktabs = TRUE, - caption = "Table one") - -kable(df, - format = "latex", - longtable = TRUE, - booktabs = TRUE, - caption = "Table two because screw you, that's why") -``` - -See @tbl-test-two-tables, @tbl-test-two-tables-1, and @tbl-test-two-tables-2. \ No newline at end of file diff --git a/tests/smoke/crossref/tables.test.ts b/tests/smoke/crossref/tables.test.ts index 26d6423e72..5162709f4a 100644 --- a/tests/smoke/crossref/tables.test.ts +++ b/tests/smoke/crossref/tables.test.ts @@ -58,8 +58,9 @@ testRender(knitrTablesQmd.input, "html", false, [ /* caption is inserted in the right place in table environment*/ renderVerifyLatexOutput(docs("crossrefs/knitr-tables-latex.qmd"), [ - /\\begin{table}.*\\caption{\\label{tbl-1}.*}.*\\begin{longtable}\[.*\]{.*}.*\\end{longtable}/s, - /\\begin{table}.*\\caption{\\label{tbl-2}.*}.*\\centering.*\\begin{tabular}{.*}/s, - /\\begin{table}.*\\caption{\\label{tbl-3}.*}.*\\centering.*\\begin{longtable\*}{.*}/s, - /\\begin{table}.*\\caption{\\label{tbl-4}.*}.*\\centering\n\\begin{tabular}\[c\]{.*}/s, + /\\begin{longtable}\[.*\]{.*}.*\n+\\caption{\\label{tbl-1}.*}\n+.*\\tabularnewline/, + /\\begin{table}\n+\\caption{\\label{tbl-2}.*}.*\n+\\centering{?\n+\\begin{tabular}{.*}/, + /\\begin{longtable}{.*}.*\n+\\caption{\\label{tbl-3}.*}\n+.*\\tabularnewline/, + // two centering calls here is ugly, but we don't control the input we get + /\\begin{table}\n+\\caption{\\label{tbl-4}.*}.*\n+\\centering{?\n+\\centering\n+\\begin{tabular}\[c\]{.*}/, ]);