From 95c6dd6ad4809d38e520d5b427689e1586ea093c Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Thu, 2 Nov 2023 15:29:46 -0700 Subject: [PATCH] add uniform caption location control --- src/resources/filters/crossref/crossref.lua | 2 + .../filters/customnodes/floatreftarget.lua | 23 ++++-- src/resources/filters/layout/html.lua | 6 +- src/resources/filters/layout/latex.lua | 10 +-- src/resources/filters/quarto-pre/options.lua | 2 +- .../filters/quarto-pre/parsefiguredivs.lua | 73 +++++++++++++++++++ 6 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/resources/filters/crossref/crossref.lua b/src/resources/filters/crossref/crossref.lua index b63cfaf69f..1ea56d0acf 100644 --- a/src/resources/filters/crossref/crossref.lua +++ b/src/resources/filters/crossref/crossref.lua @@ -152,6 +152,8 @@ local quarto_normalize_filters = { return quarto_global_state.active_filters.normalization end, normalize_filter()) }, + { name = "normalize-capture-reader-state", filter = normalize_capture_reader_state() }, + { name = "pre-table-merge-raw-html", filter = table_merge_raw_html() }, diff --git a/src/resources/filters/customnodes/floatreftarget.lua b/src/resources/filters/customnodes/floatreftarget.lua index 763beebd70..a48234efd6 100644 --- a/src/resources/filters/customnodes/floatreftarget.lua +++ b/src/resources/filters/customnodes/floatreftarget.lua @@ -42,20 +42,28 @@ _quarto.ast.add_handler({ end }) -function cap_location(float) - local ref = refType(float.identifier) +function cap_location(float_or_layout) + local ref = refType(float_or_layout.identifier) + -- layouts might not have good identifiers, but they might have + -- ref-parents + if ref == nil then + ref = refType(float_or_layout.attributes["ref-parent"] or "") + end + -- last resort, pretend we're a figure + if ref == nil then + ref = "fig" + end local qualified_key = ref .. '-cap-location' local result = ( - float.attributes[qualified_key] or - float.attributes['cap-location'] or + float_or_layout.attributes[qualified_key] or + float_or_layout.attributes['cap-location'] or option_as_string(qualified_key) or option_as_string('cap-location') or - capLocation(ref) or crossref.categories.by_ref_type[ref].default_caption_location) if result ~= "margin" and result ~= "top" and result ~= "bottom" then -- luacov: disable - error("Invalid caption location for float: " .. float.identifier .. + error("Invalid caption location for float: " .. float_or_layout.identifier .. " requested " .. result .. ".\nOnly 'top', 'bottom', and 'margin' are supported. Assuming 'bottom'.") result = "bottom" @@ -203,9 +211,8 @@ end, function(float) local figEnv = latexFigureEnv(float) local figPos = latexFigurePosition(float, figEnv) local float_type = refType(float.identifier) - local crossref_category = crossref.categories.by_ref_type[float_type] or crossref.categories.by_name.Figure - local capLoc = capLocation(float_type, crossref_category.default_caption_location) + local capLoc = cap_location(float) local caption_cmd_name = latexCaptionEnv(float) if float.parent_id then diff --git a/src/resources/filters/layout/html.lua b/src/resources/filters/layout/html.lua index 95563a535c..cdd9b6b698 100644 --- a/src/resources/filters/layout/html.lua +++ b/src/resources/filters/layout/html.lua @@ -189,7 +189,7 @@ end) -- captionPara.content:insert(pandoc.RawInline("html", figcaption)) -- tappend(captionPara.content, caption.content) -- captionPara.content:insert(pandoc.RawInline("html", "")) --- if capLocation('fig', 'bottom') == 'bottom' then +-- if cap_location_from_option('fig', 'bottom') == 'bottom' then -- panel.content:insert(captionPara) -- else -- tprepend(panel.content, { captionPara }) @@ -198,7 +198,7 @@ end) -- local panelCaption = pandoc.Div(caption, pandoc.Attr("", { "panel-caption" })) -- if hasTableRef(divEl) then -- panelCaption.attr.classes:insert("table-caption") --- if capLocation('tbl', 'top') == 'bottom' then +-- if cap_location_from_option('tbl', 'top') == 'bottom' then -- panel.content:insert(panelCaption) -- else -- tprepend(panel.content, { panelCaption }) @@ -312,7 +312,7 @@ function renderHtmlFigure(el, render) )) tappend(figureCaption.content, captionInlines) figureCaption.content:insert(pandoc.RawInline("html", "")) - if capLocation('fig', 'bottom') == 'top' then + if cap_location_from_option('fig', 'bottom') == 'top' then figureDiv.content:insert(figureCaption) tappend(figureDiv.content, figure) else diff --git a/src/resources/filters/layout/latex.lua b/src/resources/filters/layout/latex.lua index 1711cb0a77..1e9584174b 100644 --- a/src/resources/filters/layout/latex.lua +++ b/src/resources/filters/layout/latex.lua @@ -328,13 +328,7 @@ function latexCell(cell, vAlign, endOfRow, endOfTable) local miniPageVAlign = latexMinipageValign(vAlign) latexAppend(prefix, "\\begin{minipage}" .. miniPageVAlign .. "{" .. width .. "}\n") - local capType = "fig" - local locDefault = "bottom" - if isTable then - capType = "tbl" - locDefault = "top" - end - local capLoc = capLocation(capType, locDefault) + local capLoc = cap_location(cell) if (capLoc == "top") then tappend(prefix, subcap) @@ -653,7 +647,7 @@ function renderLatexFigure(el, render) -- get the figure content and caption inlines local figureContent, captionInlines = render(figure) - local capLoc = capLocation("fig", "bottom") + local capLoc = cap_location_from_option("fig", "bottom") -- surround caption w/ appropriate latex (and end the figure) if captionInlines and inlinesToString(captionInlines) ~= "" then diff --git a/src/resources/filters/quarto-pre/options.lua b/src/resources/filters/quarto-pre/options.lua index 6cb8e13da7..1445a18b49 100644 --- a/src/resources/filters/quarto-pre/options.lua +++ b/src/resources/filters/quarto-pre/options.lua @@ -60,7 +60,7 @@ function parseOption(name, options, def) end end -function capLocation(scope, default) +function cap_location_from_option(scope, default) local loc = option(scope .. '-cap-location', option('cap-location', nil)) if loc ~= nil then return inlinesToString(loc) diff --git a/src/resources/filters/quarto-pre/parsefiguredivs.lua b/src/resources/filters/quarto-pre/parsefiguredivs.lua index dc31361bbc..0930234daf 100644 --- a/src/resources/filters/quarto-pre/parsefiguredivs.lua +++ b/src/resources/filters/quarto-pre/parsefiguredivs.lua @@ -3,6 +3,26 @@ local patterns = require("modules/patterns") +local function process_div_caption_classes(div) + -- knitr forwards "cap-location: top" as `.caption-top`... + -- and in that case we don't know if it's a fig- or a tbl- :facepalm: + -- so we have to use cap-locatin generically in the attribute + if div.classes:find_if( + function(class) return class:match("caption%-.+") end) then + local matching_classes = div.classes:filter(function(class) + return class:match("caption%-.+") + end) + div.classes = div.classes:filter(function(class) + return not class:match("caption%-.+") + end) + for i, c in ipairs(matching_classes) do + div.attributes["cap-location"] = c:match("caption%-(.+)") + end + return true + end + return false +end + local function coalesce_code_blocks(content) local result = pandoc.Blocks({}) local state = "start" @@ -134,6 +154,7 @@ function parse_reftargets() end local function parse_float_div(div) + process_div_caption_classes(div) local ref = refType(div.identifier) if ref == nil then fail("Float div without crossref identifier?") @@ -151,6 +172,24 @@ function parse_reftargets() local content = div.content local caption_attr_key = ref .. "-cap" + -- caption location handling + + -- .*-cap-location + local caption_location_attr_key = ref .. "-cap-location" + local caption_location_class_pattern = ".*cap%-location%-(.*)" + local caption_location_classes = div.classes:filter(function(class) + return class:match(caption_location_class_pattern) + end) + + if #caption_location_classes then + div.classes = div.classes:filter(function(class) + return not class:match(caption_location_class_pattern) + end) + for _, class in ipairs(caption_location_classes) do + local c = class:match(caption_location_class_pattern) + div.attributes[caption_location_attr_key] = c + end + end local caption = refCaptionFromDiv(div) if caption ~= nil then div.content:remove(#div.content) @@ -381,6 +420,40 @@ function parse_reftargets() elseif is_theorem_div(div) then return parse_theorem_div(div) end + + if div.classes:includes("cell") then + process_div_caption_classes(div) + -- forward cell attributes to potential FloatRefTargets + div = _quarto.ast.walk(div, { + Figure = function(fig) + if div.attributes["cap-location"] then + fig.attributes["cap-location"] = div.attributes["cap-location"] + end + for i, c in ipairs(div.classes) do + local c = c:match(".*%-?cap%-location%-(.*)") + if c then + fig.attributes["cap-location"] = c + end + end + return fig + end, + CodeBlock = function(block) + for _, k in ipairs({"cap-location", "lst-cap-location"}) do + if div.attributes[k] then + block.attributes[k] = div.attributes[k] + end + end + for i, c in ipairs(div.classes) do + local c = c:match(".*%-?cap%-location%-(.*)") + if c then + block.attributes["cap-location"] = c + end + end + return block + end, + }) + return div + end end, Para = function(para)