Skip to content

Commit 4b185d0

Browse files
committed
typst - reimplement subfloat counters
1 parent cc7bd24 commit 4b185d0

File tree

9 files changed

+157
-99
lines changed

9 files changed

+157
-99
lines changed

news/changelog-1.5.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ All changes included in 1.5:
6969

7070
## Typst
7171

72-
- ([#9619](https://github.com/quarto-dev/quarto-cli/pull/9619)): Typst CSS - for a small set of elements and properties, Quarto will translate the CSS property to a Typst property. This is especially useful for processed HTML tables and `<pre>`s.
7372
- ([#8539](https://github.com/quarto-dev/quarto-cli/issues/8539)): Support for Typst theorems and their ilk via [typst-theorems](https://github.com/sahasatvik/typst-theorems).
73+
- ([#9619](https://github.com/quarto-dev/quarto-cli/pull/9619)): Typst CSS - for a small set of elements and properties, Quarto will translate the CSS property to a Typst property. This is especially useful for processed HTML tables and `<pre>`s.
7474
- ([#9293](https://github.com/quarto-dev/quarto-cli/pull/9293)): Add `toc-indent` to control indentation of entries in the table of contents.
75+
- ([#9671](https://github.com/quarto-dev/quarto-cli/issues/9671)): Reimplement `typst` subfloats to fix subfloat counters.
7576
- ([#9694](https://github.com/quarto-dev/quarto-cli/issues/9694)): Fix default callout (`::: callout ... ::: `) in Typst.
7677
- Upgrade Typst to 0.11
7778
- Upgrade the Typst template to draw tables without grid lines by default, in accordance with latest Pandoc.

src/resources/filters/crossref/preprocess.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ function crossref_mark_subfloats()
99
FloatRefTarget = function(float)
1010
float.content = _quarto.ast.walk(float.content, {
1111
FloatRefTarget = function(subfloat)
12+
float.has_subfloats = true
1213
crossref.subfloats[subfloat.identifier] = {
1314
parent_id = float.identifier
1415
}

src/resources/filters/crossref/refs.lua

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,7 @@ function resolveRefs()
8787
elseif _quarto.format.isAsciiDocOutput() then
8888
ref = pandoc.List({pandoc.RawInline('asciidoc', '<<' .. label .. '>>')})
8989
elseif _quarto.format.isTypstOutput() then
90-
-- if we're referencing a subfloat,
91-
-- we need to package the parent_id information in the
92-
-- supplement as well, so that we can provide
93-
-- better numbering in the typst renderer
94-
local subfloat_info = crossref.subfloats[label]
95-
if subfloat_info == nil then
96-
ref = pandoc.List({pandoc.RawInline('typst', '@' .. label)})
97-
else
98-
ref = pandoc.List({pandoc.RawInline('typst', '@' .. label .. '[45127368-afa1-446a-820f-fc64c546b2c5%' .. subfloat_info.parent_id .. ']')})
99-
end
90+
ref = pandoc.List({pandoc.RawInline('typst', '@' .. label)})
10091
else
10192
if not resolve then
10293
local refClasses = pandoc.List({"quarto-unresolved-ref"})

src/resources/filters/customnodes/floatreftarget.lua

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -974,40 +974,47 @@ end, function(float)
974974
return float.content
975975
-- luacov: enable
976976
end
977-
local kind
978-
local supplement = ""
979-
local numbering = ""
980-
local content = float.content
981-
982-
if float.parent_id then
983-
kind = "quarto-subfloat-" .. ref
984-
numbering = "(a)"
985-
else
986-
kind = "quarto-float-" .. ref
987-
numbering = "1"
988-
supplement = info.name
989-
end
990-
977+
local kind = "quarto-float-" .. ref
978+
local supplement = info.name
979+
-- FIXME: custom numbering doesn't work yet
980+
-- local numbering = ""
981+
-- if float.parent_id then
982+
-- numbering = "(a)"
983+
-- else
984+
-- numbering = "1"
985+
-- end
986+
local content = quarto.utils.as_blocks(float.content or {})
991987
local caption_location = cap_location(float)
992988

993-
-- FIXME: custom numbering doesn't work yet
994-
995989
if (ref == "lst") then
996990
-- FIXME:
997991
-- Listings shouldn't emit centered blocks.
998992
-- We don't know how to disable that right now using #show rules for #figures in template.
999-
content = { pandoc.RawBlock("typst", "#set align(left)"), content }
993+
content:insert(1, pandoc.RawBlock("typst", "#set align(left)"))
1000994
end
1001995

1002-
return make_typst_figure {
1003-
content = content,
1004-
caption_location = caption_location,
1005-
caption = float.caption_long,
1006-
kind = kind,
1007-
supplement = supplement,
1008-
numbering = numbering,
1009-
identifier = float.identifier
1010-
}
996+
if float.has_subfloats then
997+
return _quarto.format.typst.function_call("quarto_super", {
998+
{"kind", kind},
999+
{"caption", float.caption_long},
1000+
{"label", pandoc.RawInline("typst", "<" .. float.identifier .. ">")},
1001+
{"position", pandoc.RawInline("typst", caption_location)},
1002+
{"supplement", supplement},
1003+
{"subrefnumbering", "1a"},
1004+
{"subcapnumbering", "(a)"},
1005+
content
1006+
}, false)
1007+
else
1008+
return make_typst_figure {
1009+
content = content,
1010+
caption_location = caption_location,
1011+
caption = float.caption_long,
1012+
kind = kind,
1013+
supplement = supplement,
1014+
numbering = numbering,
1015+
identifier = float.identifier
1016+
}
1017+
end
10111018
end)
10121019

10131020
_quarto.ast.add_renderer("FloatRefTarget", function(_)

src/resources/filters/layout/typst.lua

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -95,41 +95,53 @@ end, function(layout)
9595
return float.content
9696
-- luacov: enable
9797
end
98+
local supplement = info.name
9899

99100
-- typst output currently only supports a single grid
100101
-- as output, so no rows of varying columns, etc.
101102
local n_cols = layout.attributes[kLayoutNcol] or "1"
102-
103-
local typst_figure_content = pandoc.Div({})
104-
typst_figure_content.content:insert(pandoc.RawInline("typst", "#grid(columns: " .. n_cols .. ", gutter: 2em,\n"))
105-
local is_first = true
106-
_quarto.ast.walk(layout.float.content, {
107-
FloatRefTarget = function(_, float_obj)
108-
if is_first then
109-
is_first = false
110-
else
111-
typst_figure_content.content:insert(pandoc.RawInline("typst", ",\n"))
112-
end
113-
typst_figure_content.content:insert(pandoc.RawInline("typst", " ["))
114-
typst_figure_content.content:insert(float_obj)
115-
typst_figure_content.content:insert(pandoc.RawInline("typst", "]"))
116-
return nil
117-
end
118-
})
119-
typst_figure_content.content:insert(pandoc.RawInline("typst", ")\n"))
120103
local result = pandoc.Blocks({})
121104
if layout.preamble then
122-
result:insert(layout.preamble)
105+
if pandoc.utils.type(layout.preamble) == "Blocks" then
106+
result:extend(layout.preamble)
107+
else
108+
result:insert(layout.preamble)
109+
end
123110
end
124111
local caption_location = cap_location(layout.float)
125112

126-
return make_typst_figure {
127-
content = typst_figure_content,
128-
caption_location = caption_location,
129-
caption = layout.float.caption_long,
130-
kind = kind,
131-
supplement = info.prefix,
132-
numbering = info.numbering,
133-
identifier = layout.float.identifier
134-
}
113+
local cells = pandoc.Blocks({})
114+
cells:insert(pandoc.RawInline("typst", "#grid(columns: " .. n_cols .. ", gutter: 2em,\n"))
115+
layout.rows.content:map(function(row)
116+
-- print(row)
117+
return row.content:map(function(cell)
118+
cells:insert(pandoc.RawInline("typst", " ["))
119+
cells:insert(cell)
120+
cells:insert(pandoc.RawInline("typst", "],\n"))
121+
end)
122+
end)
123+
cells:insert(pandoc.RawInline("typst", ")\n"))
124+
if layout.float.has_subfloats then
125+
result:insert(_quarto.format.typst.function_call("quarto_super", {
126+
{"kind", kind},
127+
{"caption", layout.float.caption_long},
128+
{"label", pandoc.RawInline("typst", "<" .. layout.float.identifier .. ">")},
129+
{"position", pandoc.RawInline("typst", caption_location)},
130+
{"supplement", supplement},
131+
{"subrefnumbering", "1a"},
132+
{"subcapnumbering", "(a)"},
133+
cells
134+
}, false))
135+
else
136+
result:insert(make_typst_figure {
137+
content = cells,
138+
caption_location = caption_location,
139+
caption = layout.float.caption_long,
140+
kind = kind,
141+
supplement = info.prefix,
142+
numbering = info.numbering,
143+
identifier = layout.float.identifier
144+
})
145+
end
146+
return result
135147
end)

src/resources/filters/modules/typst.lua

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55
-- this module is exposed as quarto.format.typst
66

77
local function _main()
8-
local function typst_function_call(name, params)
8+
local function typst_function_call(name, params, keep_scaffold)
99
local result = pandoc.Blocks({})
1010
result:insert(pandoc.RawInline("typst", "#" .. name .. "("))
1111
local function add(v)
12-
if type(v) == "userdata" or type(v) == "table" then
12+
if type(v) == "number" then
13+
result:insert(pandoc.RawInline("typst", tostring(v)))
14+
elseif type(v) == "string" then
15+
result:insert(pandoc.RawInline("typst", "\"" .. v .. "\""))
16+
elseif v.t == "RawInline" or v.t == "RawBlock" then
17+
result:insert(v)
18+
elseif type(v) == "userdata" or type(v) == "table" then
19+
result:insert(pandoc.RawInline("typst", "["))
1320
result:extend(quarto.utils.as_blocks(v) or {})
21+
result:insert(pandoc.RawInline("typst", "]"))
1422
else
1523
result:extend(quarto.utils.as_blocks({pandoc.utils.stringify(v)}) or {})
1624
end
1725
end
1826
-- needs to be array of pairs because order matters for typst
1927
local n = #params
2028
for i, pair in ipairs(params) do
21-
if type(pair) == "table" then
29+
if pandoc.utils.type(pair) == "table" then
2230
local k = pair[1]
2331
local v = pair[2]
2432
if v ~= nil then
@@ -40,7 +48,11 @@ local function _main()
4048
-- The result from this div cannot be a quarto-scaffold because some typst template
4149
-- functions assume they can get the element's children. pandoc.Div is converted to
4250
-- a #block, and those are guaranteed to have children.
43-
return pandoc.Div(result)
51+
if keep_scaffold == false then
52+
return pandoc.Div(result, pandoc.Attr("", {"quarto-scaffold"}))
53+
else
54+
return pandoc.Div(result)
55+
end
4456
end
4557

4658
local function as_typst_content(content)

src/resources/filters/quarto-post/typst.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ function render_typst()
2929
end
3030
},
3131
{
32+
FloatRefTarget = function(float)
33+
if float.content.t == "Table" then
34+
-- this needs the fix from https://github.com/jgm/pandoc/pulls/9778
35+
float.content.classes:insert("typst-no-figure")
36+
else
37+
float.content = _quarto.ast.walk(float.content, {
38+
Table = function(tbl)
39+
tbl.classes:insert("typst-no-figure")
40+
return tbl
41+
end
42+
})
43+
end
44+
return float
45+
end,
3246
Div = function(div)
3347
if div.classes:includes("block") then
3448
div.classes = div.classes:filter(function(c) return c ~= "block" end)

src/resources/formats/typst/pandoc/quarto/definitions.typ

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,54 @@
6161

6262
}
6363

64+
// Subfloats
65+
// This is a technique that we adapted from https://github.com/tingerrr/subpar/
66+
#let quartosubfloatcounter = counter("quartosubfloatcounter")
67+
68+
#let quarto_super(
69+
kind: str,
70+
caption: none,
71+
label: none,
72+
supplement: str,
73+
position: none,
74+
subrefnumbering: "1a",
75+
subcapnumbering: "(a)",
76+
body,
77+
) = {
78+
context {
79+
let figcounter = counter(figure.where(kind: kind))
80+
let n-super = figcounter.get().first() + 1
81+
set figure.caption(position: position)
82+
[#figure(
83+
kind: kind,
84+
supplement: supplement,
85+
caption: caption,
86+
{
87+
show figure.where(kind: kind): set figure(numbering: _ => numbering(subrefnumbering, n-super, quartosubfloatcounter.get().first() + 1))
88+
show figure.where(kind: kind): set figure.caption(position: position)
89+
90+
show figure: it => {
91+
let num = numbering(subcapnumbering, n-super, quartosubfloatcounter.get().first() + 1)
92+
show figure.caption: it => {
93+
num.slice(2) // I don't understand why the numbering contains output that it really shouldn't, but this fixes it shrug?
94+
[ ]
95+
it.body
96+
}
97+
98+
quartosubfloatcounter.step()
99+
it
100+
counter(figure.where(kind: it.kind)).update(n => n - 1)
101+
}
102+
103+
quartosubfloatcounter.update(0)
104+
body
105+
}
106+
)#label]
107+
}
108+
}
109+
110+
// callout rendering
111+
// this is a figure show rule because callouts are crossreferenceable
64112
#show figure: it => {
65113
if type(it.kind) != "string" {
66114
return it
@@ -98,35 +146,6 @@
98146
old_callout.body.children.at(1))
99147
}
100148

101-
#show ref: it => locate(loc => {
102-
let suppl = it.at("supplement", default: none)
103-
if suppl == none or suppl == auto {
104-
it
105-
return
106-
}
107-
108-
let sup = it.supplement.text.matches(regex("^45127368-afa1-446a-820f-fc64c546b2c5%(.*)")).at(0, default: none)
109-
if sup != none {
110-
let target = query(it.target, loc).first()
111-
let parent_id = sup.captures.first()
112-
let parent_figure = query(label(parent_id), loc).first()
113-
let parent_location = parent_figure.location()
114-
115-
let counters = numbering(
116-
parent_figure.at("numbering"),
117-
..parent_figure.at("counter").at(parent_location))
118-
119-
let subcounter = numbering(
120-
target.at("numbering"),
121-
..target.at("counter").at(target.location()))
122-
123-
// NOTE there's a nonbreaking space in the block below
124-
link(target.location(), [#parent_figure.at("supplement") #counters#subcounter])
125-
} else {
126-
it
127-
}
128-
})
129-
130149
// 2023-10-09: #fa-icon("fa-info") is not working, so we'll eval "#fa-info()" instead
131150
#let callout(body: [], title: "Callout", background_color: rgb("#dddddd"), icon: none, icon_color: black) = {
132151
block(

src/resources/lua-types/quarto/format/typst.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ Create a node that represents a typst function call.
77
]]
88
---@param name string Name of function
99
---@param params table<string, any> Array of {param, value} pairs
10+
---@param keep_scaffold? boolean If true, the result will be wrapped in a typst block.
1011
---@return pandoc.Div
11-
function quarto.format.typst.function_call(name, params) end
12+
function quarto.format.typst.function_call(name, params, keep_scaffold) end
1213

1314
--[[
1415
Create a node that represents typst content (`[Hello, world]`). Use

0 commit comments

Comments
 (0)