Skip to content

Commit 9c09585

Browse files
fix: override Skylighting function for proper Typst code block styling
Fixes #14126 The Pandoc-generated Skylighting function uses `block(fill: bgcolor, blocks)` which lacks width, inset, and radius. This temporarily overrides it from the typst-brand-yaml Lua filter to add `width: 100%`, `inset: 8pt`, `radius: 2pt`, matching the styling of native code blocks. Also fixes brand `monospace-block.background-color` not applying to Skylighting output (since `#show raw.where(block: true)` doesn't match `#Skylighting()` calls). This override will be removed once the fix is upstreamed to Skylighting.
1 parent 3f3f341 commit 9c09585

16 files changed

Lines changed: 440 additions & 0 deletions

File tree

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ All changes included in 1.9:
8383
- ([#13954](https://github.com/quarto-dev/quarto-cli/issues/13954)): Add support for Typst book projects via format extensions. Quarto now bundles the `orange-book` extension which provides a textbook-style format with chapter numbering, cross-references, and professional styling. Book projects with `format: typst` automatically use this extension.
8484
- ([#13978](https://github.com/quarto-dev/quarto-cli/pull/13978)): Keep term and description together in definition lists to avoid breaking across pages. (author: @mcanouil)
8585
- ([#13878](https://github.com/quarto-dev/quarto-cli/issues/13878)): Typst now uses Pandoc's skylighting for syntax highlighting by default (consistent with other formats). Use `syntax-highlighting: idiomatic` to opt-in to Typst's native syntax highlighting instead.
86+
- ([#14126](https://github.com/quarto-dev/quarto-cli/issues/14126)): Fix Skylighting code blocks in Typst lacking full-width background, padding, and border radius. Temporarily overrides the Pandoc-generated `Skylighting` Typst function to add `width: 100%`, `inset: 8pt`, and `radius: 2pt` to the block call, matching the styling of native code blocks. Brand `monospace-block.background-color` also now correctly applies to Skylighting output. This override will be removed once the fix is upstreamed to Skylighting.
8687

8788
### `pdf`
8889

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,31 @@ function render_typst_brand_yaml()
5656
return '"' .. value .. '"'
5757
end
5858

59+
-- Generate a replacement #let Skylighting() function with proper block styling.
60+
-- The default Pandoc-generated version only uses block(fill: bgcolor, blocks)
61+
-- which lacks width, inset, and radius (issue #14126).
62+
local function emit_skylighting_override(bgcolor, lineNumberColor)
63+
local lineNumberFill = ''
64+
if lineNumberColor and type(lineNumberColor) == 'string' then
65+
lineNumberFill = 'fill: rgb("' .. lineNumberColor .. '"), '
66+
end
67+
quarto.doc.include_text('in-header', table.concat({
68+
'#let Skylighting(fill: none, number: false, start: 1, sourcelines) = {\n',
69+
' let blocks = []\n',
70+
' let lnum = start - 1\n',
71+
' let bgcolor = ', bgcolor, '\n',
72+
' for ln in sourcelines {\n',
73+
' if number {\n',
74+
' lnum = lnum + 1\n',
75+
' blocks = blocks + box(width: if start + sourcelines.len() > 999 { 30pt } else { 24pt }, text(', lineNumberFill, '[ #lnum ]))\n',
76+
' }\n',
77+
' blocks = blocks + ln + EndLine()\n',
78+
' }\n',
79+
' block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks)\n',
80+
'}\n',
81+
}))
82+
end
83+
5984
return {
6085
Pandoc = function(pandoc0)
6186
local brand = param('brand')
@@ -228,6 +253,27 @@ function render_typst_brand_yaml()
228253
end
229254
end
230255

256+
-- When Skylighting is active, Pandoc emits #Skylighting() calls instead
257+
-- of raw code blocks, so #show raw.where(block: true) rules above don't
258+
-- apply. Override the Skylighting function to include proper block styling.
259+
-- Use brand background-color if set, otherwise fall back to theme bgcolor.
260+
local highlightMethod = PANDOC_WRITER_OPTIONS.highlight_method
261+
if monospaceBlock and next(monospaceBlock)
262+
and type(highlightMethod) == 'table' then
263+
local bgcolor = monospaceBlock['background-color']
264+
if not bgcolor then
265+
local themeBg = highlightMethod['background-color']
266+
if type(themeBg) == 'string' then
267+
bgcolor = 'rgb("' .. themeBg .. '")'
268+
end
269+
end
270+
if bgcolor then
271+
local lineNumberColor = type(highlightMethod['line-number-color']) == 'string'
272+
and highlightMethod['line-number-color'] or nil
273+
emit_skylighting_override(bgcolor, lineNumberColor)
274+
end
275+
end
276+
231277
local link = _quarto.modules.brand.get_typography(brandMode, 'link')
232278
local primaryColor = _quarto.modules.brand.get_color(brandMode, 'primary')
233279
if link and next(link) or primaryColor then
@@ -250,6 +296,24 @@ function render_typst_brand_yaml()
250296
}))
251297
end
252298
end
299+
300+
-- Non-brand Skylighting override: even without a brand, the Pandoc-generated
301+
-- Skylighting function lacks width/inset/radius. Override it using the
302+
-- theme's own background color from PANDOC_WRITER_OPTIONS.highlight_method.
303+
-- Skip if brand already emitted an override above.
304+
local highlightMethod = PANDOC_WRITER_OPTIONS.highlight_method
305+
local monospaceBlock = brand and brand.processedData
306+
and _quarto.modules.brand.get_typography(brandMode, 'monospace-block')
307+
local brandAlreadyOverrode = monospaceBlock and next(monospaceBlock)
308+
and type(highlightMethod) == 'table'
309+
if not brandAlreadyOverrode and type(highlightMethod) == 'table' then
310+
local themeBg = highlightMethod['background-color']
311+
if type(themeBg) == 'string' then
312+
local lineNumberColor = type(highlightMethod['line-number-color']) == 'string'
313+
and highlightMethod['line-number-color'] or nil
314+
emit_skylighting_override('rgb("' .. themeBg .. '")', lineNumberColor)
315+
end
316+
end
253317
end,
254318
Meta = function(meta)
255319
local brand = param('brand')
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
color:
2+
palette:
3+
code-fg: "#2d3748"
4+
5+
typography:
6+
monospace-block:
7+
color: code-fg
8+
weight: 500
9+
size: 10pt
10+
line-height: 1.5
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Brand Monospace Block without Background Color
3+
format:
4+
typst:
5+
keep-typ: true
6+
_quarto:
7+
tests:
8+
typst:
9+
ensureTypstFileRegexMatches:
10+
-
11+
# Skylighting is active (default)
12+
- "#Skylighting"
13+
- "#KeywordTok"
14+
# Brand monospace-block text properties emitted as show rules
15+
- '^#show raw\.where\(block: true\): set text\(weight: 500, size: 10pt, fill: rgb\("#2d3748"\), \)$'
16+
- '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$'
17+
# Even without brand bg, Skylighting override uses theme bgcolor
18+
# so that width/inset/radius are applied
19+
- 'let bgcolor = rgb\("#f1f3f5"\)'
20+
- 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)'
21+
# No brand background-color show rule (not configured)
22+
- ['^#show raw\.where\(block: true\): set block\(fill:']
23+
---
24+
25+
Brand sets monospace-block color, weight, size, and line-height but NOT
26+
background-color. The Skylighting override should still be emitted using
27+
the theme's background color so that code blocks get proper width/inset/radius.
28+
29+
```python
30+
def hello():
31+
x = 1 + 2
32+
print(f"result: {x}")
33+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
color:
2+
palette:
3+
code-bg: "#1e1e2e"
4+
code-fg: "#cdd6f4"
5+
6+
typography:
7+
monospace-block:
8+
color: code-fg
9+
background-color: code-bg
10+
size: 10pt
11+
weight: 400
12+
line-height: 1.6
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: Brand Monospace Block with Skylighting
3+
format:
4+
typst:
5+
keep-typ: true
6+
_quarto:
7+
tests:
8+
typst:
9+
ensureTypstFileRegexMatches:
10+
-
11+
# Skylighting is active (default)
12+
- "#Skylighting"
13+
- "#KeywordTok"
14+
# Brand monospace-block properties are emitted as show rules
15+
# (still useful for idiomatic mode fallback)
16+
- '^#show raw\.where\(block: true\): set text\(weight: 400, size: 10pt, fill: rgb\("#cdd6f4"\), \)$'
17+
- '^#show raw\.where\(block: true\): set block\(fill: rgb\("#1e1e2e"\)\)$'
18+
- '^#show raw\.where\(block: true\): set par\(leading: 0\.85em\)$'
19+
# Quarto-generated Skylighting override with brand bg and proper block styling
20+
- 'let bgcolor = rgb\("#1e1e2e"\)'
21+
- 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)'
22+
# Should NOT have raw fenced blocks
23+
- ["```python"]
24+
---
25+
26+
Brand monospace-block options should apply to Skylighting code blocks.
27+
28+
```python
29+
def hello():
30+
x = 1 + 2
31+
print(f"result: {x}")
32+
```
33+
34+
Inline code like `hello()` should NOT get monospace-block styling.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
color:
2+
palette:
3+
block-bg: "#f0f4f8"
4+
block-fg: "#1a365d"
5+
inline-bg: "#fed7d7"
6+
inline-fg: "#9b2c2c"
7+
8+
typography:
9+
monospace-block:
10+
color: block-fg
11+
background-color: block-bg
12+
size: 11pt
13+
weight: 400
14+
line-height: 1.5
15+
monospace-inline:
16+
color: inline-fg
17+
background-color: inline-bg
18+
weight: 600
19+
size: 0.9rem
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: Brand Monospace with Idiomatic Highlighting
3+
format:
4+
typst:
5+
keep-typ: true
6+
syntax-highlighting: idiomatic
7+
_quarto:
8+
tests:
9+
typst:
10+
ensureTypstFileRegexMatches:
11+
-
12+
# Idiomatic = native typst highlighting = raw fenced code blocks
13+
- "```python"
14+
# Brand monospace-block properties (these target raw.where(block: true)
15+
# which DOES match native/idiomatic code blocks)
16+
- '^#show raw\.where\(block: true\): set text\(weight: 400, size: 11pt, fill: rgb\("#1a365d"\), \)$'
17+
- '^#show raw\.where\(block: true\): set block\(fill: rgb\("#f0f4f8"\)\)$'
18+
- '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$'
19+
# Brand monospace-inline properties
20+
- '^#show raw\.where\(block: false\): set text\(weight: 600, size: 0\.9em, fill: rgb\("#9b2c2c"\), \)$'
21+
- '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fed7d7"\), content\)$'
22+
# Should NOT have Skylighting tokens
23+
- ["#Skylighting", "#KeywordTok"]
24+
---
25+
26+
With idiomatic highlighting, brand monospace-block properties apply directly
27+
to `raw.where(block: true)` which matches native Typst code blocks.
28+
This is the baseline that "just works."
29+
30+
Here's `inline code` with brand styling.
31+
32+
```python
33+
def hello():
34+
x = 1 + 2
35+
print(f"result: {x}")
36+
```
37+
38+
Both inline and block code should reflect brand styling.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
color:
2+
palette:
3+
mono-base-fg: "#2d3748"
4+
block-bg: "#edf2f7"
5+
inline-bg: "#fefcbf"
6+
7+
typography:
8+
fonts:
9+
- source: google
10+
family: Fira Code
11+
weight: [300, 400, 700]
12+
# Base monospace: family and weight inherited by both inline and block
13+
monospace:
14+
family: Fira Code
15+
weight: 400
16+
size: 0.85rem
17+
color: mono-base-fg
18+
# Block overrides only background-color; inherits family, weight, size, color
19+
monospace-block:
20+
background-color: block-bg
21+
line-height: 1.5
22+
# Inline overrides only background-color and weight; inherits family, size, color
23+
monospace-inline:
24+
background-color: inline-bg
25+
weight: 700
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
title: Brand Monospace Inheritance with Skylighting
3+
format:
4+
typst:
5+
keep-typ: true
6+
_quarto:
7+
tests:
8+
typst:
9+
ensureTypstFileRegexMatches:
10+
-
11+
# Skylighting is active (default)
12+
- "#Skylighting"
13+
# Base monospace family applied via codefont
14+
- 'codefont: \("Fira Code",\),$'
15+
# monospace-block inherits color from base monospace, gets its own bg
16+
- '^#show raw\.where\(block: true\): set text\(weight: 400, size: 0\.85em, fill: rgb\("#2d3748"\), \)$'
17+
- '^#show raw\.where\(block: true\): set block\(fill: rgb\("#edf2f7"\)\)$'
18+
- '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$'
19+
# Quarto Skylighting override with inherited brand bg
20+
- 'let bgcolor = rgb\("#edf2f7"\)'
21+
- 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)'
22+
# monospace-inline overrides weight to 700, inherits color, gets its own bg
23+
- '^#show raw\.where\(block: false\): set text\(weight: 700, size: 0\.85em, fill: rgb\("#2d3748"\), \)$'
24+
- '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fefcbf"\), content\)$'
25+
- ["```python"]
26+
---
27+
28+
This tests that `monospace` base properties are properly inherited by
29+
`monospace-block` and `monospace-inline`, with specific overrides taking
30+
precedence.
31+
32+
Inline code: `hello()` and `x + y` should use bold weight (700) from
33+
monospace-inline override, with yellow background.
34+
35+
```python
36+
# Block code inherits base weight (400) with blue-gray background
37+
def greet(name):
38+
return f"Hello, {name}!"
39+
```
40+
41+
More `inline()` code references.

0 commit comments

Comments
 (0)