Skip to content

Typst brand fonts never download a bold-italic face from Google Fonts #14883

Description

@cwickham

I have:

  • searched the issue tracker for similar issues
  • installed the latest version of Quarto CLI
  • formatted my issue following the Bug Reports guide

Bug description

For format: typst, Quarto downloads the Google fonts named in the brand
into .quarto/typst/fonts/. When a font entry gives both a style and a
weight, the two never combine. Quarto downloads the weights at normal style
and weight 400 at each style. It never downloads a bold-italic face.

The result is silent. Typst has no face to use, so #strong[... #emph[x] ...]
shows italic at regular weight. A brand that asks for a bold-italic heading
gets the same. Nothing in the render log says a face is missing.

The cause is the URL that Quarto builds for the Google Fonts API. In
src/command/render/pandoc.ts#L1548-L1560,
the family, the style list, and the weight list are joined with :

const parts = [family!];
if (style) {
  style = Array.isArray(style) ? style : [style];
  parts.push(style.join(","));
}
if (weight) {
  weight = Array.isArray(weight) ? weight : [weight];
  parts.push(weight.join(","));
}
const response = await fetch(
  `${base_urls[source]}?family=${parts.join(":")}`,
);

For style: [normal, italic] and weight: [400, 700] this gives:

https://fonts.googleapis.com/css?family=Atkinson Hyperlegible:normal,italic:400,700

That is not valid syntax for the v1 CSS API. The v1 API expects one
comma-separated variant list, in which a weight and a style are one token
(700italic). The API splits the string on commas and reads each part as a
separate variant. The parts here are normal, italic:400 and 700, so it
returns normal 400, italic 400 and normal 700.

Two faults follow from this:

  1. A weight and a style never combine, so bold italic is unreachable.
  2. The first weight in the list is joined to the last style and is lost. For
    style: italic and weight: [700, 900] the token italic:700 returns
    italic 400, and only 900 arrives as a weight.

HTML is not affected. HTML uses a separate code path in
src/core/sass/brand.ts#L119-L153,
which builds a v2 (css2) URL with correct ital,wght@ pairs. For the same
brand it emits
css2?family=Atkinson+Hyperlegible:ital,wght@0,700;1,700, and that URL does
serve italic 700.

Steps to reproduce

Render this document. The brand is in the document YAML, so no _brand.yml
file is necessary. It asks for two weights at two styles.

---
title: "Bold italic"
format: typst
brand:
  typography:
    fonts:
      - family: Atkinson Hyperlegible
        source: google
        weight: [400, 700]
        style: [normal, italic]
    base: Atkinson Hyperlegible
---

Normal. *Italic.* **Bold.** ***Bold italic.***

Then ask Typst which variants it found in the font cache:

quarto typst fonts --variants --font-path .quarto/typst/fonts

Actual behavior

Rendered document showing the 'bold italic' heading and text italic but not bold.

No bold-italic face is in the cache:

Atkinson Hyperlegible
  ├ .../9Bt43C1KxNDXMspQ1lPyU89-1h6ONRlW45G056IqUwU.ttf
  │   Style: Italic, Weight: 400, Stretch: 100%
  ├ .../9Bt73C1KxNDXMspQ1lPyU89-1h6ONRlW45G8Wbc9dCWK.ttf
  │   Style: Normal, Weight: 700, Stretch: 100%
  └ .../9Bt23C1KxNDXMspQ1lPyU89-1h6ONRlW45G04pIt.ttf
      Style: Normal, Weight: 400, Stretch: 100%

Three faces arrive out of the four requested combinations, and italic 700 is
the one that never arrives. All four exist for this family. The render gives
no warning, so ***Bold italic.*** shows italic at regular weight.

The result is the same when the brand asks for the bold-italic face alone.
With weight: 700 and style: italic, one face arrives, and it is
Style: Italic, Weight: 400.

Two separate fonts: entries for the same family give the same result. A
_brand.yml file gives the same result as the document YAML above: both
produce an identical .typ file.

Google does serve the face. This command returns italic 700:

curl -s "https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700"

Expected behavior

Quarto downloads a face for each combination of the requested weights and
styles that exist for the family. For the document above,
quarto typst fonts --variants also reports Style: Italic, Weight: 700.
If a combination does not exist for the family, Quarto says so in the render
log, as it already does for an unusable font format.

Your environment

  • IDE: Positron 1.130.0
  • OS: macOS 26.6.2 (build 25G83)

The fault is also present in the development version. The font fetch is
TypeScript that a development build runs from source, and the lines above are
unchanged between the v1.11.4 tag and main at 8c87d79. A development build
of main downloads the same single 9Bt4 file.

Quarto check output

Quarto 1.11.4
[✓] Checking environment information...
      Quarto cache location: /Users/charlottewickham/Library/Caches/quarto
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.10.0: OK
      Dart Sass version 1.101.0: OK
      Deno version 2.7.14: OK
      Typst version 0.15.1: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 1.11.4
      Path: /Applications/quarto/bin

[✓] Checking tools....................OK
      TinyTeX: v2026.04
      Chrome Headless Shell: 150.0.7871.115
      VeraPDF: 1.28.2

[✓] Checking LaTeX....................OK
      Using: TinyTex
      Path: /Users/charlottewickham/Library/TinyTeX/bin/universal-darwin
      Version: 2026

[✓] Checking Chrome Headless....................OK
      Using: Chrome Headless Shell installed by Quarto
      Path: /Users/charlottewickham/Library/Application Support/quarto/chrome-headless-shell/chrome-headless-shell-mac-arm64/chrome-headless-shell
      Version: 150.0.7871.115

[✓] Checking basic markdown render....OK

[✓] Checking R installation...........OK
      Version: 4.5.2
      Path: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources
      LibPaths:
        - /Users/charlottewickham/Library/R/arm64/4.5/library
        - /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
      knitr: 1.51
      rmarkdown: 2.30

[✓] Checking Knitr engine render......OK

[✓] Checking Python 3 installation....OK
      Version: 3.12.2
      Path: /Users/charlottewickham/.pyenv/versions/3.12.2/bin/python3
      Jupyter: 5.9.1
      Kernels: python3

[✓] Checking Jupyter engine render....OK

[✓] Checking Julia installation...

An AI assistant helped investigate this, grounded in a local clone of
quarto-cli, as described in Using AI tools to investigate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    brand`_brand.yml`bugSomething isn't workingtypst

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions