From 25979ab216ca2cc2539696420f23be8508d3184f Mon Sep 17 00:00:00 2001
From: Felix Nie <hongtuo.nie@u.nus.edu>
Date: Wed, 19 Mar 2025 04:43:32 +0000
Subject: [PATCH] feat(fonts): allow PageTitle to have its own font subset (#1848)

---
 quartz/util/theme.ts                          |   18 +++++++++++++++---
 quartz/plugins/emitters/componentResources.ts |   16 ++++++++++++++--
 docs/configuration.md                         |    9 +++++----
 quartz/components/PageTitle.tsx               |    1 +
 quartz/components/Head.tsx                    |    5 ++++-
 5 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/docs/configuration.md b/docs/configuration.md
index e29dc80..4026c51 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -41,11 +41,12 @@
 - `ignorePatterns`: a list of [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) patterns that Quartz should ignore and not search through when looking for files inside the `content` folder. See [[private pages]] for more details.
 - `defaultDateType`: whether to use created, modified, or published as the default date to display on pages and page listings.
 - `theme`: configure how the site looks.
-  - `cdnCaching`: If `true` (default), use Google CDN to cache the fonts. This will generally will be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
+  - `cdnCaching`: if `true` (default), use Google CDN to cache the fonts. This will generally be faster. Disable (`false`) this if you want Quartz to download the fonts to be self-contained.
   - `typography`: what fonts to use. Any font available on [Google Fonts](https://fonts.google.com/) works here.
-    - `header`: Font to use for headers
-    - `code`: Font for inline and block quotes.
-    - `body`: Font for everything
+    - `title`: font for the title of the site (optional, same as `header` by default)
+    - `header`: font to use for headers
+    - `code`: font for inline and block quotes
+    - `body`: font for everything
   - `colors`: controls the theming of the site.
     - `light`: page background
     - `lightgray`: borders
diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx
index 60dce6e..23183ca 100644
--- a/quartz/components/Head.tsx
+++ b/quartz/components/Head.tsx
@@ -1,7 +1,7 @@
 import { i18n } from "../i18n"
 import { FullSlug, getFileExtension, joinSegments, pathToRoot } from "../util/path"
 import { CSSResourceToStyleElement, JSResourceToScriptElement } from "../util/resources"
-import { googleFontHref } from "../util/theme"
+import { googleFontHref, googleFontSubsetHref } from "../util/theme"
 import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
 import { unescapeHTML } from "../util/escape"
 import { CustomOgImagesEmitterName } from "../plugins/emitters/ogImage"
@@ -45,6 +45,9 @@
             <link rel="preconnect" href="https://fonts.googleapis.com" />
             <link rel="preconnect" href="https://fonts.gstatic.com" />
             <link rel="stylesheet" href={googleFontHref(cfg.theme)} />
+            {cfg.theme.typography.title && (
+              <link rel="stylesheet" href={googleFontSubsetHref(cfg.theme, cfg.pageTitle)} />
+            )}
           </>
         )}
         <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="anonymous" />
diff --git a/quartz/components/PageTitle.tsx b/quartz/components/PageTitle.tsx
index 046dc52..53ee824 100644
--- a/quartz/components/PageTitle.tsx
+++ b/quartz/components/PageTitle.tsx
@@ -17,6 +17,7 @@
 .page-title {
   font-size: 1.75rem;
   margin: 0;
+  font-family: var(--titleFont);
 }
 `
 
diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts
index 540a373..5dd67e6 100644
--- a/quartz/plugins/emitters/componentResources.ts
+++ b/quartz/plugins/emitters/componentResources.ts
@@ -9,7 +9,12 @@
 import popoverStyle from "../../components/styles/popover.scss"
 import { BuildCtx } from "../../util/ctx"
 import { QuartzComponent } from "../../components/types"
-import { googleFontHref, joinStyles, processGoogleFonts } from "../../util/theme"
+import {
+  googleFontHref,
+  googleFontSubsetHref,
+  joinStyles,
+  processGoogleFonts,
+} from "../../util/theme"
 import { Features, transform } from "lightningcss"
 import { transform as transpile } from "esbuild"
 import { write } from "./helpers"
@@ -211,9 +216,16 @@
         // let the user do it themselves in css
       } else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) {
         // when cdnCaching is true, we link to google fonts in Head.tsx
-        const response = await fetch(googleFontHref(ctx.cfg.configuration.theme))
+        const theme = ctx.cfg.configuration.theme
+        const response = await fetch(googleFontHref(theme))
         googleFontsStyleSheet = await response.text()
 
+        if (theme.typography.title) {
+          const title = ctx.cfg.configuration.pageTitle
+          const response = await fetch(googleFontSubsetHref(theme, title))
+          googleFontsStyleSheet += `\n${await response.text()}`
+        }
+
         if (!cfg.baseUrl) {
           throw new Error(
             "baseUrl must be defined when using Google Fonts without cfg.theme.cdnCaching",
diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts
index 56261e3..4a06425 100644
--- a/quartz/util/theme.ts
+++ b/quartz/util/theme.ts
@@ -25,6 +25,7 @@
 
 export interface Theme {
   typography: {
+    title?: FontSpecification
     header: FontSpecification
     body: FontSpecification
     code: FontSpecification
@@ -48,7 +49,10 @@
   return spec.name
 }
 
-function formatFontSpecification(type: "header" | "body" | "code", spec: FontSpecification) {
+function formatFontSpecification(
+  type: "title" | "header" | "body" | "code",
+  spec: FontSpecification,
+) {
   if (typeof spec === "string") {
     spec = { name: spec }
   }
@@ -82,12 +86,19 @@
 }
 
 export function googleFontHref(theme: Theme) {
-  const { code, header, body } = theme.typography
+  const { header, body, code } = theme.typography
   const headerFont = formatFontSpecification("header", header)
   const bodyFont = formatFontSpecification("body", body)
   const codeFont = formatFontSpecification("code", code)
 
-  return `https://fonts.googleapis.com/css2?family=${bodyFont}&family=${headerFont}&family=${codeFont}&display=swap`
+  return `https://fonts.googleapis.com/css2?family=${headerFont}&family=${bodyFont}&family=${codeFont}&display=swap`
+}
+
+export function googleFontSubsetHref(theme: Theme, text: string) {
+  const title = theme.typography.title || theme.typography.header
+  const titleFont = formatFontSpecification("title", title)
+
+  return `https://fonts.googleapis.com/css2?family=${titleFont}&text=${encodeURIComponent(text)}&display=swap`
 }
 
 export interface GoogleFontFile {
@@ -135,6 +146,7 @@
   --highlight: ${theme.colors.lightMode.highlight};
   --textHighlight: ${theme.colors.lightMode.textHighlight};
 
+  --titleFont: "${getFontSpecificationName(theme.typography.title || theme.typography.header)}", ${DEFAULT_SANS_SERIF};
   --headerFont: "${getFontSpecificationName(theme.typography.header)}", ${DEFAULT_SANS_SERIF};
   --bodyFont: "${getFontSpecificationName(theme.typography.body)}", ${DEFAULT_SANS_SERIF};
   --codeFont: "${getFontSpecificationName(theme.typography.code)}", ${DEFAULT_MONO};

--
Gitblit v1.10.0