From 5928d82a561d74c4656a633d8af4f03f4b97dd73 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 13 Mar 2025 12:11:27 -0700 Subject: [PATCH] fix(og): search for font family properly --- quartz/plugins/emitters/componentResources.ts | 2 +- quartz/util/og.tsx | 80 +++++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/quartz/plugins/emitters/componentResources.ts b/quartz/plugins/emitters/componentResources.ts index 29ff3bd..fe855ba 100644 --- a/quartz/plugins/emitters/componentResources.ts +++ b/quartz/plugins/emitters/componentResources.ts @@ -234,7 +234,7 @@ export const ComponentResources: QuartzEmitterPlugin = () => { for (const fontFile of fontFiles) { const res = await fetch(fontFile.url) if (!res.ok) { - throw new Error(`failed to fetch font ${fontFile.filename}`) + throw new Error(`Failed to fetch font ${fontFile.filename}`) } const buf = await res.arrayBuffer() diff --git a/quartz/util/og.tsx b/quartz/util/og.tsx index 96a14d5..f912b8a 100644 --- a/quartz/util/og.tsx +++ b/quartz/util/og.tsx @@ -3,12 +3,13 @@ import { FontWeight, SatoriOptions } from "satori/wasm" import { GlobalConfiguration } from "../cfg" import { QuartzPluginData } from "../plugins/vfile" import { JSXInternal } from "preact/src/jsx" -import { FontSpecification, ThemeKey } from "./theme" +import { FontSpecification, getFontSpecificationName, ThemeKey } from "./theme" import path from "path" import { QUARTZ } from "./path" import { formatDate, getDate } from "../components/Date" import readingTime from "reading-time" import { i18n } from "../i18n" +import chalk from "chalk" const defaultHeaderWeight = [700] const defaultBodyWeight = [400] @@ -26,29 +27,38 @@ export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: Fo const headerFontName = typeof headerFont === "string" ? headerFont : headerFont.name const bodyFontName = typeof bodyFont === "string" ? bodyFont : bodyFont.name - // Fetch fonts for all weights - const headerFontPromises = headerWeights.map((weight) => fetchTtf(headerFontName, weight)) - const bodyFontPromises = bodyWeights.map((weight) => fetchTtf(bodyFontName, weight)) + // Fetch fonts for all weights and convert to satori format in one go + const headerFontPromises = headerWeights.map(async (weight) => { + const data = await fetchTtf(headerFontName, weight) + if (!data) return null + return { + name: headerFontName, + data, + weight, + style: "normal" as const, + } + }) - const [headerFontData, bodyFontData] = await Promise.all([ + const bodyFontPromises = bodyWeights.map(async (weight) => { + const data = await fetchTtf(bodyFontName, weight) + if (!data) return null + return { + name: bodyFontName, + data, + weight, + style: "normal" as const, + } + }) + + const [headerFonts, bodyFonts] = await Promise.all([ Promise.all(headerFontPromises), Promise.all(bodyFontPromises), ]) - // Convert fonts to satori font format and return + // Filter out any failed fetches and combine header and body fonts const fonts: SatoriOptions["fonts"] = [ - ...headerFontData.map((data, idx) => ({ - name: headerFontName, - data, - weight: headerWeights[idx], - style: "normal" as const, - })), - ...bodyFontData.map((data, idx) => ({ - name: bodyFontName, - data, - weight: bodyWeights[idx], - style: "normal" as const, - })), + ...headerFonts.filter((font): font is NonNullable => font !== null), + ...bodyFonts.filter((font): font is NonNullable => font !== null), ] return fonts @@ -61,10 +71,11 @@ export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: Fo * @returns `.ttf` file of google font */ export async function fetchTtf( - fontName: string, + rawFontName: string, weight: FontWeight, -): Promise> { - const cacheKey = `${fontName.replaceAll(" ", "-")}-${weight}` +): Promise | undefined> { + const fontName = rawFontName.replaceAll(" ", "+") + const cacheKey = `${fontName}-${weight}` const cacheDir = path.join(QUARTZ, ".quartz-cache", "fonts") const cachePath = path.join(cacheDir, cacheKey) @@ -87,20 +98,19 @@ export async function fetchTtf( const match = urlRegex.exec(css) if (!match) { - throw new Error("Could not fetch font") + console.log( + chalk.yellow( + `\nWarning: Failed to fetch font ${rawFontName} with weight ${weight}, got ${cssResponse.statusText}`, + ), + ) + return } // fontData is an ArrayBuffer containing the .ttf file data const fontResponse = await fetch(match[1]) const fontData = Buffer.from(await fontResponse.arrayBuffer()) - - try { - await fs.mkdir(cacheDir, { recursive: true }) - await fs.writeFile(cachePath, fontData) - } catch (error) { - console.warn(`Failed to cache font: ${error}`) - // Continue even if caching fails - } + await fs.mkdir(cacheDir, { recursive: true }) + await fs.writeFile(cachePath, fontData) return fontData } @@ -173,7 +183,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = ( { colorScheme }: UserOpts, title: string, description: string, - fonts: SatoriOptions["fonts"], + _fonts: SatoriOptions["fonts"], fileData: QuartzPluginData, ) => { const fontBreakPoint = 32 @@ -192,6 +202,8 @@ export const defaultImage: SocialImageOptions["imageStructure"] = ( // Get tags if available const tags = fileData.frontmatter?.tags ?? [] + const bodyFont = getFontSpecificationName(cfg.theme.typography.body) + const headerFont = getFontSpecificationName(cfg.theme.typography.header) return (
{/* Header Section */} @@ -227,7 +239,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = ( display: "flex", fontSize: 32, color: cfg.theme.colors[colorScheme].gray, - fontFamily: fonts[1].name, + fontFamily: bodyFont, }} > {cfg.baseUrl} @@ -246,7 +258,7 @@ export const defaultImage: SocialImageOptions["imageStructure"] = ( style={{ margin: 0, fontSize: useSmallerFont ? 64 : 72, - fontFamily: fonts[0].name, + fontFamily: headerFont, fontWeight: 700, color: cfg.theme.colors[colorScheme].dark, lineHeight: 1.2,