diff --git a/news/changelog-1.9.md b/news/changelog-1.9.md index 36d010ecdb..16cb2cb0da 100644 --- a/news/changelog-1.9.md +++ b/news/changelog-1.9.md @@ -83,6 +83,7 @@ All changes included in 1.9: - ([#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. - ([#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) - ([#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. +- ([#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. ### `pdf` diff --git a/src/resources/filters/quarto-post/typst-brand-yaml.lua b/src/resources/filters/quarto-post/typst-brand-yaml.lua index e23639c86a..f5394c6217 100644 --- a/src/resources/filters/quarto-post/typst-brand-yaml.lua +++ b/src/resources/filters/quarto-post/typst-brand-yaml.lua @@ -56,6 +56,31 @@ function render_typst_brand_yaml() return '"' .. value .. '"' end + -- Generate a replacement #let Skylighting() function with proper block styling. + -- The default Pandoc-generated version only uses block(fill: bgcolor, blocks) + -- which lacks width, inset, and radius (issue #14126). + local function emit_skylighting_override(bgcolor, lineNumberColor) + local lineNumberFill = '' + if lineNumberColor and type(lineNumberColor) == 'string' then + lineNumberFill = 'fill: rgb("' .. lineNumberColor .. '"), ' + end + quarto.doc.include_text('in-header', table.concat({ + '#let Skylighting(fill: none, number: false, start: 1, sourcelines) = {\n', + ' let blocks = []\n', + ' let lnum = start - 1\n', + ' let bgcolor = ', bgcolor, '\n', + ' for ln in sourcelines {\n', + ' if number {\n', + ' lnum = lnum + 1\n', + ' blocks = blocks + box(width: if start + sourcelines.len() > 999 { 30pt } else { 24pt }, text(', lineNumberFill, '[ #lnum ]))\n', + ' }\n', + ' blocks = blocks + ln + EndLine()\n', + ' }\n', + ' block(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks)\n', + '}\n', + })) + end + return { Pandoc = function(pandoc0) local brand = param('brand') @@ -228,6 +253,27 @@ function render_typst_brand_yaml() end end + -- When Skylighting is active, Pandoc emits #Skylighting() calls instead + -- of raw code blocks, so #show raw.where(block: true) rules above don't + -- apply. Override the Skylighting function to include proper block styling. + -- Use brand background-color if set, otherwise fall back to theme bgcolor. + local highlightMethod = PANDOC_WRITER_OPTIONS.highlight_method + if monospaceBlock and next(monospaceBlock) + and type(highlightMethod) == 'table' then + local bgcolor = monospaceBlock['background-color'] + if not bgcolor then + local themeBg = highlightMethod['background-color'] + if type(themeBg) == 'string' then + bgcolor = 'rgb("' .. themeBg .. '")' + end + end + if bgcolor then + local lineNumberColor = type(highlightMethod['line-number-color']) == 'string' + and highlightMethod['line-number-color'] or nil + emit_skylighting_override(bgcolor, lineNumberColor) + end + end + local link = _quarto.modules.brand.get_typography(brandMode, 'link') local primaryColor = _quarto.modules.brand.get_color(brandMode, 'primary') if link and next(link) or primaryColor then @@ -250,6 +296,24 @@ function render_typst_brand_yaml() })) end end + + -- Non-brand Skylighting override: even without a brand, the Pandoc-generated + -- Skylighting function lacks width/inset/radius. Override it using the + -- theme's own background color from PANDOC_WRITER_OPTIONS.highlight_method. + -- Skip if brand already emitted an override above. + local highlightMethod = PANDOC_WRITER_OPTIONS.highlight_method + local monospaceBlock = brand and brand.processedData + and _quarto.modules.brand.get_typography(brandMode, 'monospace-block') + local brandAlreadyOverrode = monospaceBlock and next(monospaceBlock) + and type(highlightMethod) == 'table' + if not brandAlreadyOverrode and type(highlightMethod) == 'table' then + local themeBg = highlightMethod['background-color'] + if type(themeBg) == 'string' then + local lineNumberColor = type(highlightMethod['line-number-color']) == 'string' + and highlightMethod['line-number-color'] or nil + emit_skylighting_override('rgb("' .. themeBg .. '")', lineNumberColor) + end + end end, Meta = function(meta) local brand = param('brand') diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/_brand.yml new file mode 100644 index 0000000000..a56e5aa3b4 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/_brand.yml @@ -0,0 +1,10 @@ +color: + palette: + code-fg: "#2d3748" + +typography: + monospace-block: + color: code-fg + weight: 500 + size: 10pt + line-height: 1.5 diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd new file mode 100644 index 0000000000..3e6bf00c79 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block-no-bg/brand-monospace-block-no-bg.qmd @@ -0,0 +1,33 @@ +--- +title: Brand Monospace Block without Background Color +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting is active (default) + - "#Skylighting" + - "#KeywordTok" + # Brand monospace-block text properties emitted as show rules + - '^#show raw\.where\(block: true\): set text\(weight: 500, size: 10pt, fill: rgb\("#2d3748"\), \)$' + - '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$' + # Even without brand bg, Skylighting override uses theme bgcolor + # so that width/inset/radius are applied + - 'let bgcolor = rgb\("#f1f3f5"\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + # No brand background-color show rule (not configured) + - ['^#show raw\.where\(block: true\): set block\(fill:'] +--- + +Brand sets monospace-block color, weight, size, and line-height but NOT +background-color. The Skylighting override should still be emitted using +the theme's background color so that code blocks get proper width/inset/radius. + +```python +def hello(): + x = 1 + 2 + print(f"result: {x}") +``` diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/_brand.yml new file mode 100644 index 0000000000..b2335f9f42 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/_brand.yml @@ -0,0 +1,12 @@ +color: + palette: + code-bg: "#1e1e2e" + code-fg: "#cdd6f4" + +typography: + monospace-block: + color: code-fg + background-color: code-bg + size: 10pt + weight: 400 + line-height: 1.6 diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd new file mode 100644 index 0000000000..0af4184a34 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-block/brand-monospace-block.qmd @@ -0,0 +1,34 @@ +--- +title: Brand Monospace Block with Skylighting +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting is active (default) + - "#Skylighting" + - "#KeywordTok" + # Brand monospace-block properties are emitted as show rules + # (still useful for idiomatic mode fallback) + - '^#show raw\.where\(block: true\): set text\(weight: 400, size: 10pt, fill: rgb\("#cdd6f4"\), \)$' + - '^#show raw\.where\(block: true\): set block\(fill: rgb\("#1e1e2e"\)\)$' + - '^#show raw\.where\(block: true\): set par\(leading: 0\.85em\)$' + # Quarto-generated Skylighting override with brand bg and proper block styling + - 'let bgcolor = rgb\("#1e1e2e"\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + # Should NOT have raw fenced blocks + - ["```python"] +--- + +Brand monospace-block options should apply to Skylighting code blocks. + +```python +def hello(): + x = 1 + 2 + print(f"result: {x}") +``` + +Inline code like `hello()` should NOT get monospace-block styling. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/_brand.yml new file mode 100644 index 0000000000..cdf5d53d08 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/_brand.yml @@ -0,0 +1,19 @@ +color: + palette: + block-bg: "#f0f4f8" + block-fg: "#1a365d" + inline-bg: "#fed7d7" + inline-fg: "#9b2c2c" + +typography: + monospace-block: + color: block-fg + background-color: block-bg + size: 11pt + weight: 400 + line-height: 1.5 + monospace-inline: + color: inline-fg + background-color: inline-bg + weight: 600 + size: 0.9rem diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/brand-monospace-idiomatic.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/brand-monospace-idiomatic.qmd new file mode 100644 index 0000000000..61fdd2315f --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-idiomatic/brand-monospace-idiomatic.qmd @@ -0,0 +1,38 @@ +--- +title: Brand Monospace with Idiomatic Highlighting +format: + typst: + keep-typ: true + syntax-highlighting: idiomatic +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Idiomatic = native typst highlighting = raw fenced code blocks + - "```python" + # Brand monospace-block properties (these target raw.where(block: true) + # which DOES match native/idiomatic code blocks) + - '^#show raw\.where\(block: true\): set text\(weight: 400, size: 11pt, fill: rgb\("#1a365d"\), \)$' + - '^#show raw\.where\(block: true\): set block\(fill: rgb\("#f0f4f8"\)\)$' + - '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$' + # Brand monospace-inline properties + - '^#show raw\.where\(block: false\): set text\(weight: 600, size: 0\.9em, fill: rgb\("#9b2c2c"\), \)$' + - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fed7d7"\), content\)$' + # Should NOT have Skylighting tokens + - ["#Skylighting", "#KeywordTok"] +--- + +With idiomatic highlighting, brand monospace-block properties apply directly +to `raw.where(block: true)` which matches native Typst code blocks. +This is the baseline that "just works." + +Here's `inline code` with brand styling. + +```python +def hello(): + x = 1 + 2 + print(f"result: {x}") +``` + +Both inline and block code should reflect brand styling. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/_brand.yml new file mode 100644 index 0000000000..d6378b3b31 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/_brand.yml @@ -0,0 +1,25 @@ +color: + palette: + mono-base-fg: "#2d3748" + block-bg: "#edf2f7" + inline-bg: "#fefcbf" + +typography: + fonts: + - source: google + family: Fira Code + weight: [300, 400, 700] + # Base monospace: family and weight inherited by both inline and block + monospace: + family: Fira Code + weight: 400 + size: 0.85rem + color: mono-base-fg + # Block overrides only background-color; inherits family, weight, size, color + monospace-block: + background-color: block-bg + line-height: 1.5 + # Inline overrides only background-color and weight; inherits family, size, color + monospace-inline: + background-color: inline-bg + weight: 700 diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd new file mode 100644 index 0000000000..4df3e99795 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inheritance/brand-monospace-inheritance.qmd @@ -0,0 +1,41 @@ +--- +title: Brand Monospace Inheritance with Skylighting +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting is active (default) + - "#Skylighting" + # Base monospace family applied via codefont + - 'codefont: \("Fira Code",\),$' + # monospace-block inherits color from base monospace, gets its own bg + - '^#show raw\.where\(block: true\): set text\(weight: 400, size: 0\.85em, fill: rgb\("#2d3748"\), \)$' + - '^#show raw\.where\(block: true\): set block\(fill: rgb\("#edf2f7"\)\)$' + - '^#show raw\.where\(block: true\): set par\(leading: 0\.75em\)$' + # Quarto Skylighting override with inherited brand bg + - 'let bgcolor = rgb\("#edf2f7"\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + # monospace-inline overrides weight to 700, inherits color, gets its own bg + - '^#show raw\.where\(block: false\): set text\(weight: 700, size: 0\.85em, fill: rgb\("#2d3748"\), \)$' + - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fefcbf"\), content\)$' + - ["```python"] +--- + +This tests that `monospace` base properties are properly inherited by +`monospace-block` and `monospace-inline`, with specific overrides taking +precedence. + +Inline code: `hello()` and `x + y` should use bold weight (700) from +monospace-inline override, with yellow background. + +```python +# Block code inherits base weight (400) with blue-gray background +def greet(name): + return f"Hello, {name}!" +``` + +More `inline()` code references. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/_brand.yml new file mode 100644 index 0000000000..3439f1a88e --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/_brand.yml @@ -0,0 +1,11 @@ +color: + palette: + inline-bg: "#fff3cd" + inline-fg: "#664d03" + +typography: + monospace-inline: + color: inline-fg + background-color: inline-bg + weight: 600 + size: 0.85rem diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/brand-monospace-inline.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/brand-monospace-inline.qmd new file mode 100644 index 0000000000..11c54d441d --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-inline/brand-monospace-inline.qmd @@ -0,0 +1,28 @@ +--- +title: Brand Monospace Inline with Skylighting +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting is active for code blocks + - "#Skylighting" + # Brand monospace-inline properties are emitted + - '^#show raw\.where\(block: false\): set text\(weight: 600, size: 0\.85em, fill: rgb\("#664d03"\), \)$' + - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#fff3cd"\), content\)$' + # No monospace-block show rules (not configured) + - ['^#show raw\.where\(block: true\): set block\(fill:'] +--- + +Inline code like `hello()` and `x + y` should get brand monospace-inline styling +(background highlight, custom color, weight, size). + +```python +def hello(): + print("world") +``` + +Code blocks use Skylighting and should not be affected by monospace-inline settings. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/_brand.yml b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/_brand.yml new file mode 100644 index 0000000000..9d7a1041f6 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/_brand.yml @@ -0,0 +1,18 @@ +color: + palette: + block-bg: "#282a36" + block-fg: "#f8f8f2" + inline-bg: "#e8e0f0" + inline-fg: "#6c3483" + +typography: + monospace-block: + color: block-fg + background-color: block-bg + weight: 400 + size: 9pt + line-height: 1.4 + monospace-inline: + color: inline-fg + background-color: inline-bg + weight: 500 diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd new file mode 100644 index 0000000000..e209b351b3 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/brand-monospace-with-theme/brand-monospace-with-theme.qmd @@ -0,0 +1,41 @@ +--- +title: Brand Monospace with Explicit Theme +format: + typst: + keep-typ: true + syntax-highlighting: espresso +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting with espresso theme + - "#Skylighting" + - "#KeywordTok" + # Brand monospace-block properties (show rules for idiomatic fallback) + - '^#show raw\.where\(block: true\): set text\(weight: 400, size: 9pt, fill: rgb\("#f8f8f2"\), \)$' + - '^#show raw\.where\(block: true\): set block\(fill: rgb\("#282a36"\)\)$' + - '^#show raw\.where\(block: true\): set par\(leading: 0\.65em\)$' + # Quarto Skylighting override uses brand bg (not espresso theme bg) + - 'let bgcolor = rgb\("#282a36"\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + # Brand monospace-inline properties + - '^#show raw\.where\(block: false\): set text\(weight: 500, fill: rgb\("#6c3483"\), \)$' + - '^#show raw\.where\(block: false\): content => highlight\(fill: rgb\("#e8e0f0"\), content\)$' + - ["```python"] +--- + +This tests that brand monospace options and an explicit syntax-highlighting theme +can coexist. The theme controls token colors; the brand controls the block +container styling. + +Here's some `inline code` with brand styling. + +```python +def fibonacci(n): + if n <= 1: + return n + return fibonacci(n - 1) + fibonacci(n - 2) +``` + +And more `inline()` references. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd new file mode 100644 index 0000000000..144b1964e3 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-default.qmd @@ -0,0 +1,34 @@ +--- +title: Skylighting Default Style +format: + typst: + keep-typ: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Default highlighting uses Skylighting (arrow theme) + - "#Skylighting" + - "#KeywordTok" + - "#let EndLine" + # Quarto override with proper block styling and arrow theme bgcolor + - 'let bgcolor = rgb\("#f1f3f5"\)' + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - ["```python"] +--- + +Default Skylighting (no explicit theme, no brand). This tests the baseline +behavior that code blocks should have proper styling. + +```python +def hello(): + print("world") +``` + +```r +x <- 1 + 1 +print(x) +``` + +And some `inline code` that should use default styling. diff --git a/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd new file mode 100644 index 0000000000..af2fe7e9f3 --- /dev/null +++ b/tests/docs/smoke-all/typst/syntax-highlighting/skylighting-line-numbers.qmd @@ -0,0 +1,31 @@ +--- +title: Skylighting with Line Numbers +format: + typst: + keep-typ: true + syntax-highlighting: tango + code-line-numbers: true +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + # Skylighting with line numbering enabled + - '#Skylighting\(number: true' + - "#KeywordTok" + # Quarto override with proper block styling + - 'block\(fill: bgcolor, width: 100%, inset: 8pt, radius: 2pt, blocks\)' + - ["```python"] +--- + +Code blocks with line numbers should render properly with Skylighting. + +```python +def fibonacci(n): + if n <= 1: + return n + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + return a +```