diff --git a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd index c068b1c0420..12975ec0d8b 100644 --- a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd +++ b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd @@ -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} diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index c4908d9192e..887a00ac61a 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -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. diff --git a/src/resources/filters/modules/typst_css.lua b/src/resources/filters/modules/typst_css.lua index 6660a71022b..c97ea91c714 100644 --- a/src/resources/filters/modules/typst_css.lua +++ b/src/resources/filters/modules/typst_css.lua @@ -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 @@ -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 diff --git a/tests/docs/smoke-all/typst/brand-yaml/font-filtering-generics/font-filtering-generics.qmd b/tests/docs/smoke-all/typst/brand-yaml/font-filtering-generics/font-filtering-generics.qmd index 89fb7ecb7fc..8c67a0a704f 100644 --- a/tests/docs/smoke-all/typst/brand-yaml/font-filtering-generics/font-filtering-generics.qmd +++ b/tests/docs/smoke-all/typst/brand-yaml/font-filtering-generics/font-filtering-generics.qmd @@ -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. diff --git a/tests/unit-lua/typst-css.test.lua b/tests/unit-lua/typst-css.test.lua index 1faa2302deb..ce4cee59299 100644 --- a/tests/unit-lua/typst-css.test.lua +++ b/tests/unit-lua/typst-css.test.lua @@ -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())