diff --git a/docs/plugins/CustomOgImages.md b/docs/plugins/CustomOgImages.md index aafea5d89..5d47c419c 100644 --- a/docs/plugins/CustomOgImages.md +++ b/docs/plugins/CustomOgImages.md @@ -107,25 +107,35 @@ export const myImage: SocialImageOptions["imageStructure"] = (...) => { > import fs from "fs" > import path from "path" > -> const headerFont = joinSegments(QUARTZ, "static", "Newsreader.woff2") -> const bodyFont = joinSegments(QUARTZ, "static", "Newsreader.woff2") -> -> export async function getSatoriFont(cfg: GlobalConfiguration): Promise { -> const headerWeight: FontWeight = 700 -> const bodyWeight: FontWeight = 400 -> -> const [header, body] = await Promise.all( -> [headerFont, bodyFont].map((font) => fs.promises.readFile(path.resolve(font))), -> ) -> -> return [ -> { name: cfg.theme.typography.header, data: header, weight: headerWeight, style: "normal" }, -> { name: cfg.theme.typography.body, data: body, weight: bodyWeight, style: "normal" }, +> const newsreaderFontPath = joinSegments(QUARTZ, "static", "Newsreader.woff2") +> export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) { +> // ... rest of implementation remains same +> 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, +> })), +> { +> name: "Newsreader", +> data: await fs.promises.readFile(path.resolve(newsreaderFontPath)), +> weight: 400, +> style: "normal" as const, +> }, > ] +> +> return fonts > } > ``` > -> This font then can be used with your custom structure +> This font then can be used with your custom structure. ## Examples diff --git a/quartz/plugins/emitters/ogImage.tsx b/quartz/plugins/emitters/ogImage.tsx index 5f9c67a96..f2a2bc431 100644 --- a/quartz/plugins/emitters/ogImage.tsx +++ b/quartz/plugins/emitters/ogImage.tsx @@ -2,7 +2,7 @@ import { QuartzEmitterPlugin } from "../types" import { i18n } from "../../i18n" import { unescapeHTML } from "../../util/escape" import { FullSlug, getFileExtension } from "../../util/path" -import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFont } from "../../util/og" +import { ImageOptions, SocialImageOptions, defaultImage, getSatoriFonts } from "../../util/og" import { getFontSpecificationName } from "../../util/theme" import sharp from "sharp" import satori from "satori" @@ -54,9 +54,9 @@ export const CustomOgImages: QuartzEmitterPlugin> = }, async *emit(ctx, content, _resources) { const cfg = ctx.cfg.configuration - const headerFont = getFontSpecificationName(cfg.theme.typography.header) - const bodyFont = getFontSpecificationName(cfg.theme.typography.body) - const fonts = await getSatoriFont(headerFont, bodyFont) + const headerFont = cfg.theme.typography.header + const bodyFont = cfg.theme.typography.body + const fonts = await getSatoriFonts(headerFont, bodyFont) for (const [_tree, vfile] of content) { // if this file defines socialImage, we can skip diff --git a/quartz/util/og.tsx b/quartz/util/og.tsx index 30fd158c7..1ef7e4927 100644 --- a/quartz/util/og.tsx +++ b/quartz/util/og.tsx @@ -2,29 +2,49 @@ import { FontWeight, SatoriOptions } from "satori/wasm" import { GlobalConfiguration } from "../cfg" import { QuartzPluginData } from "../plugins/vfile" import { JSXInternal } from "preact/src/jsx" -import { ThemeKey } from "./theme" +import { FontSpecification, ThemeKey } from "./theme" -/** - * Get an array of `FontOptions` (for satori) given google font names - * @param headerFontName name of google font used for header - * @param bodyFontName name of google font used for body - * @returns FontOptions for header and body - */ -export async function getSatoriFont(headerFontName: string, bodyFontName: string) { - const headerWeight = 700 as FontWeight - const bodyWeight = 400 as FontWeight +const defaultHeaderWeight = [700] +const defaultBodyWeight = [400] +export async function getSatoriFonts(headerFont: FontSpecification, bodyFont: FontSpecification) { + // Get all weights for header and body fonts + const headerWeights: FontWeight[] = ( + typeof headerFont === "string" + ? defaultHeaderWeight + : (headerFont.weights ?? defaultHeaderWeight) + ) as FontWeight[] + const bodyWeights: FontWeight[] = ( + typeof bodyFont === "string" ? defaultBodyWeight : (bodyFont.weights ?? defaultBodyWeight) + ) as FontWeight[] - // Fetch fonts - const [headerFont, bodyFont] = await Promise.all([ - fetchTtf(headerFontName, headerWeight), - fetchTtf(bodyFontName, bodyWeight), + 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)) + + const [headerFontData, bodyFontData] = await Promise.all([ + Promise.all(headerFontPromises), + Promise.all(bodyFontPromises), ]) // Convert fonts to satori font format and return const fonts: SatoriOptions["fonts"] = [ - { name: headerFontName, data: headerFont, weight: headerWeight, style: "normal" }, - { name: bodyFontName, data: bodyFont, weight: bodyWeight, style: "normal" }, + ...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, + })), ] + return fonts } diff --git a/quartz/util/theme.ts b/quartz/util/theme.ts index 1af4b22ef..8de94ea6c 100644 --- a/quartz/util/theme.ts +++ b/quartz/util/theme.ts @@ -15,7 +15,7 @@ interface Colors { darkMode: ColorScheme } -type FontSpecification = +export type FontSpecification = | string | { name: string