From 079115ae7e921c58a3d8b1cde46559e50c13df99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 4 Apr 2026 16:06:13 +0200 Subject: [PATCH 1/2] fix: add Typst rendering for proof, remark, and solution environments (#14290) The Proof renderer lacked a Typst-specific branch, causing remark and solution environments to fall through to the generic renderer which produces plain `#block[...]` output that Typst cannot cross-reference. Add a Typst branch that generates proper theorion `make-frame()` calls with labels, matching how theorem environments already work. Extract shared theorion setup logic into `common/theorems.lua` to avoid duplication between theorem.lua and proof.lua. Closes #14290 --- news/changelog-1.10.md | 1 + src/resources/filters/common/theorems.lua | 91 +++++++++++++ src/resources/filters/customnodes/proof.lua | 54 +++++++- src/resources/filters/customnodes/theorem.lua | 125 ++++-------------- .../2026/02/04/issue-13992-proof.qmd | 3 +- .../docs/smoke-all/2026/04/04/issue-14290.qmd | 43 ++++++ 6 files changed, 216 insertions(+), 101 deletions(-) create mode 100644 tests/docs/smoke-all/2026/04/04/issue-14290.qmd diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index 6f85f67d76f..e55b3184235 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -11,6 +11,7 @@ All changes included in 1.10: ### `typst` - ([#14261](https://github.com/quarto-dev/quarto-cli/issues/14261)): Fix theorem/example block titles containing inline code producing invalid Typst markup when syntax highlighting is applied. +- ([#14290](https://github.com/quarto-dev/quarto-cli/issues/14290)): Fix cross-referencing `remark` and `solution` environments producing invalid Typst output. ## Commands diff --git a/src/resources/filters/common/theorems.lua b/src/resources/filters/common/theorems.lua index a88eb355a50..0893a1e6fb1 100644 --- a/src/resources/filters/common/theorems.lua +++ b/src/resources/filters/common/theorems.lua @@ -1,2 +1,93 @@ -- theorems.lua -- Copyright (C) 2020-2022 Posit Software, PBC + +local typst_theorem_appearance_imported = false +local typst_theorem_like_frames = {} +local typst_simple_renderers = {} + +function theoremTypstAppearance() + local appearance = option("theorem-appearance", "simple") + if type(appearance) == "table" then + appearance = pandoc.utils.stringify(appearance) + end + return appearance or "simple" +end + +function ensureTheoremTypstAppearanceImports() + local appearance = theoremTypstAppearance() + if typst_theorem_appearance_imported then + return appearance + end + + typst_theorem_appearance_imported = true + if appearance == "fancy" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.fancy: fancy-box, set-primary-border-color, set-primary-body-color, set-secondary-border-color, set-secondary-body-color, set-tertiary-border-color, set-tertiary-body-color, get-primary-border-color, get-primary-body-color, get-secondary-border-color, get-secondary-body-color, get-tertiary-border-color, get-tertiary-body-color +]]) + quarto.doc.include_text("before-body", [[ +#set-primary-border-color(brand-color.at("primary", default: green.darken(30%))) +#set-primary-body-color(brand-color.at("primary", default: green).lighten(90%)) +#set-secondary-border-color(brand-color.at("secondary", default: orange)) +#set-secondary-body-color(brand-color.at("secondary", default: orange).lighten(90%)) +#set-tertiary-border-color(brand-color.at("tertiary", default: blue.darken(30%))) +#set-tertiary-body-color(brand-color.at("tertiary", default: blue).lighten(90%)) +]]) + elseif appearance == "clouds" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.clouds: render-fn as clouds-render +]]) + elseif appearance == "rainbow" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.rainbow: render-fn as rainbow-render +]]) + else + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame +]]) + end + + return appearance +end + +function ensureTheoremTypstSimpleRender(render_name, italic_body) + if typst_simple_renderers[render_name] then + return + end + + typst_simple_renderers[render_name] = true + local body_render = "body" + if italic_body then + body_render = "emph(body)" + end + + quarto.doc.include_text("in-header", "#let " .. render_name .. [[(prefix: none, title: "", full-title: auto, body) = { + if full-title != "" and full-title != auto and full-title != none { + strong[#full-title.] + h(0.5em) + } + ]] .. body_render .. "\n" .. [[ + parbreak() +} +]]) +end + +function ensureTheoremTypstFrame(env_name, title, render_code) + if typst_theorem_like_frames[env_name] then + return false + end + + typst_theorem_like_frames[env_name] = true + quarto.doc.include_text("in-header", "#let (" .. env_name .. "-counter, " .. env_name .. "-box, " .. + env_name .. ", show-" .. env_name .. ") = make-frame(\n" .. + " \"" .. env_name .. "\",\n" .. + " text(weight: \"bold\")[" .. title .. "],\n" .. + " inherited-levels: theorem-inherited-levels,\n" .. + " numbering: theorem-numbering,\n" .. + render_code .. + ")") + quarto.doc.include_text("in-header", "#show: show-" .. env_name) + return true +end diff --git a/src/resources/filters/customnodes/proof.lua b/src/resources/filters/customnodes/proof.lua index 6d700c0664d..1d63ca834a5 100644 --- a/src/resources/filters/customnodes/proof.lua +++ b/src/resources/filters/customnodes/proof.lua @@ -61,6 +61,37 @@ _quarto.ast.add_handler({ end }) +-- Color mapping for clouds/rainbow themes (per proof type) +local proof_theme_colors = { + proof = "gray", remark = "orange", solution = "teal" +} + +local function ensure_typst_proofs(proof_env) + local appearance = ensureTheoremTypstAppearanceImports() + local proof_info = proof_types[proof_env] + local title = envTitle(proof_env, proof_info.title) + local render_code + + if appearance == "fancy" then + render_code = " render: fancy-box.with(\n" .. + " get-border-color: get-tertiary-border-color,\n" .. + " get-body-color: get-tertiary-body-color,\n" .. + " get-symbol: loc => none,\n" .. + " ),\n" + elseif appearance == "clouds" then + local color = proof_theme_colors[proof_env] or "gray" + render_code = " render: clouds-render.with(fill: " .. color .. ".lighten(85%)),\n" + elseif appearance == "rainbow" then + local color = proof_theme_colors[proof_env] or "gray" + render_code = " render: rainbow-render.with(fill: " .. color .. ".darken(20%)),\n" + else + ensureTheoremTypstSimpleRender("simple-proof-render", false) + render_code = " render: simple-proof-render,\n" + end + + ensureTheoremTypstFrame(proof_env, title, render_code) +end + function is_proof_div(div) local ref = refType(div.identifier) if ref ~= nil then @@ -141,6 +172,27 @@ end, function(proof_tbl) end elseif _quarto.format.isJatsOutput() then el = jatsTheorem(el, nil, name ) + elseif _quarto.format.isTypstOutput() then + if #el.content == 0 then + warn("Proof block has no content; skipping") + return pandoc.Null() + end + ensure_typst_proofs(proof.env) + local preamble = pandoc.Plain({pandoc.RawInline("typst", "#" .. proof.env .. "(")}) + if name and #name > 0 then + preamble.content:insert(pandoc.RawInline("typst", 'title: [')) + tappend(preamble.content, name) + preamble.content:insert(pandoc.RawInline("typst", ']')) + end + preamble.content:insert(pandoc.RawInline("typst", ")[")) + local callproof = make_scaffold(pandoc.Div, preamble) + tappend(callproof.content, quarto.utils.as_blocks(el.content)) + if proof_tbl.identifier and proof_tbl.identifier ~= "" then + callproof.content:insert(pandoc.RawInline("typst", "] <" .. proof_tbl.identifier .. ">")) + else + callproof.content:insert(pandoc.RawInline("typst", "]")) + end + return callproof else el.classes:insert(proof.title:lower()) local span_title = pandoc.Emph(pandoc.Str(envTitle(proof.env, proof.title))) @@ -176,4 +228,4 @@ end, function(proof_tbl) return el -end) \ No newline at end of file +end) diff --git a/src/resources/filters/customnodes/theorem.lua b/src/resources/filters/customnodes/theorem.lua index 29c222aae51..8938faa032f 100644 --- a/src/resources/filters/customnodes/theorem.lua +++ b/src/resources/filters/customnodes/theorem.lua @@ -93,115 +93,42 @@ _quarto.ast.add_handler({ end }) --- Get theorem-appearance option (simple, fancy, clouds, rainbow) -local function get_theorem_appearance() - local appearance = option("theorem-appearance", "simple") - if appearance ~= nil and type(appearance) == "table" then - appearance = pandoc.utils.stringify(appearance) - end - return appearance or "simple" -end - -- Color mapping for clouds/rainbow themes (per theorem type) local theme_colors = { thm = "red", lem = "teal", cor = "navy", prp = "blue", cnj = "navy", def = "olive", exm = "green", exr = "purple", alg = "maroon" } -local included_typst_theorems = false -local letted_typst_theorem = {} local function ensure_typst_theorems(reftype) - local appearance = get_theorem_appearance() - - if not included_typst_theorems then - included_typst_theorems = true - - if appearance == "fancy" then - -- Import theorion's make-frame and fancy-box theming - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.fancy: fancy-box, set-primary-border-color, set-primary-body-color, set-secondary-border-color, set-secondary-body-color, set-tertiary-border-color, set-tertiary-body-color, get-primary-border-color, get-primary-body-color, get-secondary-border-color, get-secondary-body-color, get-tertiary-border-color, get-tertiary-body-color -]]) - -- Set theorem colors from brand-color (runs in before-body, after brand-color is defined) - quarto.doc.include_text("before-body", [[ -#set-primary-border-color(brand-color.at("primary", default: green.darken(30%))) -#set-primary-body-color(brand-color.at("primary", default: green).lighten(90%)) -#set-secondary-border-color(brand-color.at("secondary", default: orange)) -#set-secondary-body-color(brand-color.at("secondary", default: orange).lighten(90%)) -#set-tertiary-border-color(brand-color.at("tertiary", default: blue.darken(30%))) -#set-tertiary-body-color(brand-color.at("tertiary", default: blue).lighten(90%)) -]]) - elseif appearance == "clouds" then - -- Import theorion's make-frame and clouds render function - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.clouds: render-fn as clouds-render -]]) - elseif appearance == "rainbow" then - -- Import theorion's make-frame and rainbow render function - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.rainbow: render-fn as rainbow-render -]]) - else -- simple (default) - -- Import only make-frame and define simple render function - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame - -// Simple theorem render: bold title with period, italic body -#let simple-theorem-render(prefix: none, title: "", full-title: auto, body) = { - if full-title != "" and full-title != auto and full-title != none { - strong[#full-title.] - h(0.5em) - } - emph(body) - parbreak() -} -]]) + local appearance = ensureTheoremTypstAppearanceImports() + local theorem_type = theorem_types[reftype] + local title = titleString(reftype, theorem_type.title) + local render_code + + if appearance == "fancy" then + local color_scheme = "secondary" + if theorem_type.style == "definition" then + color_scheme = "primary" + elseif reftype == "prp" then + color_scheme = "tertiary" end + render_code = " render: fancy-box.with(\n" .. + " get-border-color: get-" .. color_scheme .. "-border-color,\n" .. + " get-body-color: get-" .. color_scheme .. "-body-color,\n" .. + " get-symbol: loc => none,\n" .. + " ),\n" + elseif appearance == "clouds" then + local color = theme_colors[reftype] or "gray" + render_code = " render: clouds-render.with(fill: " .. color .. ".lighten(85%)),\n" + elseif appearance == "rainbow" then + local color = theme_colors[reftype] or "gray" + render_code = " render: rainbow-render.with(fill: " .. color .. ".darken(20%)),\n" + else + ensureTheoremTypstSimpleRender("simple-theorem-render", true) + render_code = " render: simple-theorem-render,\n" end - if not letted_typst_theorem[reftype] then - letted_typst_theorem[reftype] = true - local theorem_type = theorem_types[reftype] - local title = titleString(reftype, theorem_type.title) - - -- Build render code based on appearance - local render_code - if appearance == "fancy" then - -- Map theorem styles to color schemes (primary=definitions, secondary=theorems, tertiary=propositions) - local color_scheme = "secondary" -- default for most theorem types - if theorem_type.style == "definition" then - color_scheme = "primary" - elseif reftype == "prp" then - color_scheme = "tertiary" - end - render_code = " render: fancy-box.with(\n" .. - " get-border-color: get-" .. color_scheme .. "-border-color,\n" .. - " get-body-color: get-" .. color_scheme .. "-body-color,\n" .. - " get-symbol: loc => none,\n" .. - " ),\n" - elseif appearance == "clouds" then - local color = theme_colors[reftype] or "gray" - render_code = " render: clouds-render.with(fill: " .. color .. ".lighten(85%)),\n" - elseif appearance == "rainbow" then - local color = theme_colors[reftype] or "gray" - render_code = " render: rainbow-render.with(fill: " .. color .. ".darken(20%)),\n" - else -- simple - render_code = " render: simple-theorem-render,\n" - end - - -- Use theorion's make-frame with appropriate render - quarto.doc.include_text("in-header", "#let (" .. theorem_type.env .. "-counter, " .. theorem_type.env .. "-box, " .. - theorem_type.env .. ", show-" .. theorem_type.env .. ") = make-frame(\n" .. - " \"" .. theorem_type.env .. "\",\n" .. - " text(weight: \"bold\")[" .. title .. "],\n" .. - " inherited-levels: theorem-inherited-levels,\n" .. - " numbering: theorem-numbering,\n" .. - render_code .. - ")") - quarto.doc.include_text("in-header", "#show: show-" .. theorem_type.env) - end + ensureTheoremTypstFrame(theorem_type.env, title, render_code) end diff --git a/tests/docs/smoke-all/2026/02/04/issue-13992-proof.qmd b/tests/docs/smoke-all/2026/02/04/issue-13992-proof.qmd index 6c1b98a8a77..54c89783a51 100644 --- a/tests/docs/smoke-all/2026/02/04/issue-13992-proof.qmd +++ b/tests/docs/smoke-all/2026/02/04/issue-13992-proof.qmd @@ -13,8 +13,9 @@ _quarto: - [] - ['Proof content visible'] typst: + noErrors: default ensureTypstFileRegexMatches: - - ['#emph\[Proof\]\. Proof content visible'] + - ['#proof\('] - [] --- diff --git a/tests/docs/smoke-all/2026/04/04/issue-14290.qmd b/tests/docs/smoke-all/2026/04/04/issue-14290.qmd new file mode 100644 index 00000000000..cfe8f63ae4b --- /dev/null +++ b/tests/docs/smoke-all/2026/04/04/issue-14290.qmd @@ -0,0 +1,43 @@ +--- +title: "Cross-reference remark and solution in Typst (#14290)" +keep-typ: true +_quarto: + tests: + typst: + noErrors: default + ensureTypstFileRegexMatches: + - + - '#remark\(' + - '#solution\(' + - '' + - '' + - '#show: show-remark' + - '#show: show-solution' + - '#ref\(' + - '#ref\(' + - [] +--- + +::: {#rem-example} + +## A Remark + +This is a remark that should be cross-referenceable. + +::: + +::: {#sol-exercise} + +## A Solution + +This is a solution that should be cross-referenceable. + +::: + +::: {.proof} + +This is an unnumbered proof. + +::: + +See @rem-example and @sol-exercise. From 514baffae4b3a865f030c76507fefb6fd86794f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 4 Apr 2026 17:39:58 +0200 Subject: [PATCH 2/2] refactor: move theorems.lua to a require() module to avoid Lua local variable limit The import()-based common/theorems.lua added locals to the main chunk scope, pushing past Lua's 200-variable limit and breaking CI. Move the logic to modules/theorems.lua loaded via require(), which runs in its own scope. --- src/resources/filters/common/theorems.lua | 91 ---------------- src/resources/filters/customnodes/proof.lua | 6 +- src/resources/filters/customnodes/theorem.lua | 6 +- src/resources/filters/modules/import_all.lua | 1 + src/resources/filters/modules/theorems.lua | 101 ++++++++++++++++++ 5 files changed, 108 insertions(+), 97 deletions(-) create mode 100644 src/resources/filters/modules/theorems.lua diff --git a/src/resources/filters/common/theorems.lua b/src/resources/filters/common/theorems.lua index 0893a1e6fb1..a88eb355a50 100644 --- a/src/resources/filters/common/theorems.lua +++ b/src/resources/filters/common/theorems.lua @@ -1,93 +1,2 @@ -- theorems.lua -- Copyright (C) 2020-2022 Posit Software, PBC - -local typst_theorem_appearance_imported = false -local typst_theorem_like_frames = {} -local typst_simple_renderers = {} - -function theoremTypstAppearance() - local appearance = option("theorem-appearance", "simple") - if type(appearance) == "table" then - appearance = pandoc.utils.stringify(appearance) - end - return appearance or "simple" -end - -function ensureTheoremTypstAppearanceImports() - local appearance = theoremTypstAppearance() - if typst_theorem_appearance_imported then - return appearance - end - - typst_theorem_appearance_imported = true - if appearance == "fancy" then - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.fancy: fancy-box, set-primary-border-color, set-primary-body-color, set-secondary-border-color, set-secondary-body-color, set-tertiary-border-color, set-tertiary-body-color, get-primary-border-color, get-primary-body-color, get-secondary-border-color, get-secondary-body-color, get-tertiary-border-color, get-tertiary-body-color -]]) - quarto.doc.include_text("before-body", [[ -#set-primary-border-color(brand-color.at("primary", default: green.darken(30%))) -#set-primary-body-color(brand-color.at("primary", default: green).lighten(90%)) -#set-secondary-border-color(brand-color.at("secondary", default: orange)) -#set-secondary-body-color(brand-color.at("secondary", default: orange).lighten(90%)) -#set-tertiary-border-color(brand-color.at("tertiary", default: blue.darken(30%))) -#set-tertiary-body-color(brand-color.at("tertiary", default: blue).lighten(90%)) -]]) - elseif appearance == "clouds" then - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.clouds: render-fn as clouds-render -]]) - elseif appearance == "rainbow" then - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame, cosmos -#import cosmos.rainbow: render-fn as rainbow-render -]]) - else - quarto.doc.include_text("in-header", [[ -#import "@preview/theorion:0.4.1": make-frame -]]) - end - - return appearance -end - -function ensureTheoremTypstSimpleRender(render_name, italic_body) - if typst_simple_renderers[render_name] then - return - end - - typst_simple_renderers[render_name] = true - local body_render = "body" - if italic_body then - body_render = "emph(body)" - end - - quarto.doc.include_text("in-header", "#let " .. render_name .. [[(prefix: none, title: "", full-title: auto, body) = { - if full-title != "" and full-title != auto and full-title != none { - strong[#full-title.] - h(0.5em) - } - ]] .. body_render .. "\n" .. [[ - parbreak() -} -]]) -end - -function ensureTheoremTypstFrame(env_name, title, render_code) - if typst_theorem_like_frames[env_name] then - return false - end - - typst_theorem_like_frames[env_name] = true - quarto.doc.include_text("in-header", "#let (" .. env_name .. "-counter, " .. env_name .. "-box, " .. - env_name .. ", show-" .. env_name .. ") = make-frame(\n" .. - " \"" .. env_name .. "\",\n" .. - " text(weight: \"bold\")[" .. title .. "],\n" .. - " inherited-levels: theorem-inherited-levels,\n" .. - " numbering: theorem-numbering,\n" .. - render_code .. - ")") - quarto.doc.include_text("in-header", "#show: show-" .. env_name) - return true -end diff --git a/src/resources/filters/customnodes/proof.lua b/src/resources/filters/customnodes/proof.lua index 1d63ca834a5..76439316dd0 100644 --- a/src/resources/filters/customnodes/proof.lua +++ b/src/resources/filters/customnodes/proof.lua @@ -67,7 +67,7 @@ local proof_theme_colors = { } local function ensure_typst_proofs(proof_env) - local appearance = ensureTheoremTypstAppearanceImports() + local appearance = _quarto.modules.theorems.ensure_appearance_imports() local proof_info = proof_types[proof_env] local title = envTitle(proof_env, proof_info.title) local render_code @@ -85,11 +85,11 @@ local function ensure_typst_proofs(proof_env) local color = proof_theme_colors[proof_env] or "gray" render_code = " render: rainbow-render.with(fill: " .. color .. ".darken(20%)),\n" else - ensureTheoremTypstSimpleRender("simple-proof-render", false) + _quarto.modules.theorems.ensure_simple_render("simple-proof-render", false) render_code = " render: simple-proof-render,\n" end - ensureTheoremTypstFrame(proof_env, title, render_code) + _quarto.modules.theorems.ensure_frame(proof_env, title, render_code) end function is_proof_div(div) diff --git a/src/resources/filters/customnodes/theorem.lua b/src/resources/filters/customnodes/theorem.lua index 8938faa032f..79745d65c3d 100644 --- a/src/resources/filters/customnodes/theorem.lua +++ b/src/resources/filters/customnodes/theorem.lua @@ -100,7 +100,7 @@ local theme_colors = { } local function ensure_typst_theorems(reftype) - local appearance = ensureTheoremTypstAppearanceImports() + local appearance = _quarto.modules.theorems.ensure_appearance_imports() local theorem_type = theorem_types[reftype] local title = titleString(reftype, theorem_type.title) local render_code @@ -124,11 +124,11 @@ local function ensure_typst_theorems(reftype) local color = theme_colors[reftype] or "gray" render_code = " render: rainbow-render.with(fill: " .. color .. ".darken(20%)),\n" else - ensureTheoremTypstSimpleRender("simple-theorem-render", true) + _quarto.modules.theorems.ensure_simple_render("simple-theorem-render", true) render_code = " render: simple-theorem-render,\n" end - ensureTheoremTypstFrame(theorem_type.env, title, render_code) + _quarto.modules.theorems.ensure_frame(theorem_type.env, title, render_code) end diff --git a/src/resources/filters/modules/import_all.lua b/src/resources/filters/modules/import_all.lua index 61429636fca..3aa5f319b5d 100644 --- a/src/resources/filters/modules/import_all.lua +++ b/src/resources/filters/modules/import_all.lua @@ -20,6 +20,7 @@ _quarto.modules = { scope = require("modules/scope"), string = require("modules/string"), tablecolwidths = require("modules/tablecolwidths"), + theorems = require("modules/theorems"), typst = require("modules/typst"), listtable = require("modules/listtable"), tableutils = require("modules/tableutils"), diff --git a/src/resources/filters/modules/theorems.lua b/src/resources/filters/modules/theorems.lua new file mode 100644 index 00000000000..437dd71fa8e --- /dev/null +++ b/src/resources/filters/modules/theorems.lua @@ -0,0 +1,101 @@ +-- theorems.lua +-- Shared Typst theorem/proof setup logic using theorion +-- Copyright (C) 2020-2022 Posit Software, PBC + +local typst_theorem_appearance_imported = false +local typst_theorem_like_frames = {} +local typst_simple_renderers = {} + +local function appearance() + local val = option("theorem-appearance", "simple") + if type(val) == "table" then + val = pandoc.utils.stringify(val) + end + return val or "simple" +end + +local function ensure_appearance_imports() + local val = appearance() + if typst_theorem_appearance_imported then + return val + end + + typst_theorem_appearance_imported = true + if val == "fancy" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.fancy: fancy-box, set-primary-border-color, set-primary-body-color, set-secondary-border-color, set-secondary-body-color, set-tertiary-border-color, set-tertiary-body-color, get-primary-border-color, get-primary-body-color, get-secondary-border-color, get-secondary-body-color, get-tertiary-border-color, get-tertiary-body-color +]]) + quarto.doc.include_text("before-body", [[ +#set-primary-border-color(brand-color.at("primary", default: green.darken(30%))) +#set-primary-body-color(brand-color.at("primary", default: green).lighten(90%)) +#set-secondary-border-color(brand-color.at("secondary", default: orange)) +#set-secondary-body-color(brand-color.at("secondary", default: orange).lighten(90%)) +#set-tertiary-border-color(brand-color.at("tertiary", default: blue.darken(30%))) +#set-tertiary-body-color(brand-color.at("tertiary", default: blue).lighten(90%)) +]]) + elseif val == "clouds" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.clouds: render-fn as clouds-render +]]) + elseif val == "rainbow" then + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame, cosmos +#import cosmos.rainbow: render-fn as rainbow-render +]]) + else + quarto.doc.include_text("in-header", [[ +#import "@preview/theorion:0.4.1": make-frame +]]) + end + + return val +end + +local function ensure_simple_render(render_name, italic_body) + if typst_simple_renderers[render_name] then + return + end + + typst_simple_renderers[render_name] = true + local body_render = "body" + if italic_body then + body_render = "emph(body)" + end + + quarto.doc.include_text("in-header", "#let " .. render_name .. [[(prefix: none, title: "", full-title: auto, body) = { + if full-title != "" and full-title != auto and full-title != none { + strong[#full-title.] + h(0.5em) + } + ]] .. body_render .. "\n" .. [[ + parbreak() +} +]]) +end + +local function ensure_frame(env_name, title, render_code) + if typst_theorem_like_frames[env_name] then + return false + end + + typst_theorem_like_frames[env_name] = true + quarto.doc.include_text("in-header", "#let (" .. env_name .. "-counter, " .. env_name .. "-box, " .. + env_name .. ", show-" .. env_name .. ") = make-frame(\n" .. + " \"" .. env_name .. "\",\n" .. + " text(weight: \"bold\")[" .. title .. "],\n" .. + " inherited-levels: theorem-inherited-levels,\n" .. + " numbering: theorem-numbering,\n" .. + render_code .. + ")") + quarto.doc.include_text("in-header", "#show: show-" .. env_name) + return true +end + +return { + appearance = appearance, + ensure_appearance_imports = ensure_appearance_imports, + ensure_simple_render = ensure_simple_render, + ensure_frame = ensure_frame, +}