Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ _quarto:
typst:
ensureTypstFileRegexMatches:
-
- '#{set text\(font: \("Georgia", "serif"\)\); table\('
- []
# Georgia is kept; the CSS generic "serif" is dropped (not a real
# Typst font), leaving a single-element font tuple.
- '#{set text\(font: \("Georgia",\)\); table\('
-
- '"serif"'
---

```{=html}
Expand Down
2 changes: 1 addition & 1 deletion news/changelog-1.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ All changes included in 1.10:

### `typst`

- ([#12556](https://github.com/quarto-dev/quarto-cli/issues/12556)): Filter unavailable fonts from CSS `font-family` fallback lists before passing them to Typst, suppressing `unknown font family` warnings from Typst 1.12+ for fonts not installed locally. Available fonts are enumerated via `typst fonts` and cached per project.
- ([#12556](https://github.com/quarto-dev/quarto-cli/issues/12556)): Filter unavailable fonts from CSS `font-family` fallback lists before passing them to Typst, suppressing `unknown font family` warnings from Typst 1.12+ for fonts not installed locally. Available fonts are enumerated via `typst fonts` and cached per project. CSS generic family keywords (`serif`, `sans-serif`, `monospace`, `cursive`, `fantasy`, `math`) are also dropped, as Typst has no concept of them and would otherwise warn — for example a table styled with `font-family: Arial, sans-serif` no longer emits a warning for `sans-serif`.
- ([#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.
- ([#14460](https://github.com/quarto-dev/quarto-cli/issues/14460)): Fix CSS `border` and `border-color` declarations losing tokens that precede an `rgb()`/`rgba()` color (e.g. `border: 0px solid rgb(255, 0, 0)` rendering as a 2.25pt border instead of being suppressed). Also fixes: `var(--brand-NAME)` references crashing the Typst CSS translator when `NAME` contained digits (e.g. `--brand-red-50`); a crash when an `rgba()` alpha is unparseable; the `dvmin` length unit being silently rejected (a stray space in the unit table); CSS keywords like `BOLD` not matching as `bold` (CSS keywords are case-insensitive); invalid hex colors like `#fffff` being silently accepted as 2-component colors.
- ([#14511](https://github.com/quarto-dev/quarto-cli/issues/14511)): Fix brand fonts downloaded for a Typst book project not being passed to `typst compile`, causing `unknown font family` warnings and fallback to Libertinus Serif.
Expand Down
10 changes: 5 additions & 5 deletions src/resources/filters/modules/typst_css.lua
Original file line number Diff line number Diff line change
Expand Up @@ -658,10 +658,6 @@ end

local _available_fonts = nil
local _fonts_initialized = false
local _generic_families = {
["serif"] = true, ["sans-serif"] = true, ["monospace"] = true,
["cursive"] = true, ["fantasy"] = true, ["math"] = true,
}

local function init_available_fonts(list)
_fonts_initialized = true
Expand Down Expand Up @@ -694,7 +690,11 @@ local function translate_font_family_list(sl)
local cleaned = dequote(s)
local quoted = quote(cleaned)
table.insert(all_strings, quoted)
if not _available_fonts or _available_fonts[cleaned:lower()] or _generic_families[cleaned:lower()] then
-- CSS generic families (serif, sans-serif, monospace, ...) are not real
-- Typst fonts and trigger "unknown font family" warnings, so they are
-- not kept here. When no real font remains, the all-filtered fallback
-- below re-emits the original list to avoid an empty (illegal) tuple.
if not _available_fonts or _available_fonts[cleaned:lower()] then
table.insert(filtered, quoted)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ _quarto:
typst:
ensureTypstFileRegexMatches:
-
# CSS generic families (sans-serif, monospace) preserved alongside real fonts
- 'font: \("Roboto", "sans-serif"\),'
- 'codefont: \("Inconsolata", "monospace"\),'
- []
# Real available fonts are kept; CSS generic families are dropped.
- 'font: \("Roboto",\),'
- 'codefont: \("Inconsolata",\),'
-
# Generic families must not reach Typst: they are not real fonts and
# would trigger "unknown font family" warnings.
- '"sans-serif"'
- '"monospace"'
---

Verifies that CSS generic font family names (sans-serif, monospace, serif,
cursive, fantasy, math) are preserved during font availability filtering,
since they are valid fallback specifiers even though they don't appear in
`typst fonts` output.
Verifies that CSS generic font family names are stripped from the Typst
font-family output during availability filtering. They are not real Typst
fonts and would otherwise trigger "unknown font family" warnings. The real
brand fonts (Roboto, Inconsolata, both downloaded from Google) are preserved.
27 changes: 27 additions & 0 deletions tests/unit-lua/typst-css.test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,31 @@ function TestFontFiltering:testEmptyMetaListNoFiltering()
'("Inter", "Arial")')
end

function TestFontFiltering:testStripsGenericWhenRealFontAvailable()
-- CSS generic families (sans-serif, serif, ...) are not real Typst fonts;
-- Typst warns "unknown font family" on them. When an available real font
-- remains, the generic is pure dead weight and is dropped.
typst_css.init_available_fonts({ 'arial' })
lu.assertEquals(
typst_css.translate_font_family_list('Arial, sans-serif'),
'("Arial",)')
end

function TestFontFiltering:testStripsGenericKeepsMultipleReal()
typst_css.init_available_fonts({ 'roboto', 'inconsolata' })
lu.assertEquals(
typst_css.translate_font_family_list('Roboto, Inconsolata, monospace'),
'("Roboto", "Inconsolata")')
end

function TestFontFiltering:testGenericOnlyFallsBackToOriginal()
-- A generic-only stack filters to empty; the all-filtered fallback
-- re-emits the original so Typst never receives an empty tuple (a hard
-- error). Typst still warns here, but the document compiles.
typst_css.init_available_fonts({ 'arial' })
lu.assertEquals(
typst_css.translate_font_family_list('sans-serif'),
'("sans-serif",)')
end

os.exit(lu.LuaUnit.run())
Loading