From 5400b273f70f9eee25da07ec18917a3b7631e179 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Fri, 22 Sep 2023 11:55:36 +0200 Subject: [PATCH] feat: dynamically generate og images, write to fs as png --- package.json | 1 + quartz/components/Head.tsx | 112 ++++++++++++++++++++++++++++--------- quartz/util/fonts.ts | 14 +++++ 3 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 quartz/util/fonts.ts diff --git a/package.json b/package.json index 8cb21ca41..ab4736693 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@clack/prompts": "^0.6.3", "@floating-ui/dom": "^1.4.0", "@napi-rs/simple-git": "0.1.9", + "@resvg/resvg-js": "^2.4.1", "async-mutex": "^0.4.0", "chalk": "^4.1.2", "chokidar": "^3.5.3", diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index ccc1d764d..19dca6f69 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -3,65 +3,119 @@ import { JSResourceToScriptElement } from "../util/resources" import { QuartzComponentConstructor, QuartzComponentProps } from "./types" import satori from "satori" import * as fs from "fs" +import { getTtfFromGfont } from "../util/fonts" +import { GlobalConfiguration } from "../cfg" +import { Resvg } from "@resvg/resvg-js" -const robotoData = await ( - await fetch("https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me5Q.ttf") -).arrayBuffer() +// const robotoData = await ( +// await fetch("https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me5Q.ttf") +// ).arrayBuffer() -async function generateSvg(title: string, filePath: string) { +async function generateSvg( + title: string, + description: string, + filePath: string, + fontName: string, + cfg: GlobalConfiguration, +) { + const font = (await getTtfFromGfont(fontName)) as ArrayBuffer const svg = await satori(
- {title} +
+
+ {title} +
+
+ {description} +
+
+
, { width: 1200, height: 675, fonts: [ { - name: "Roboto", - // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here. - data: robotoData, + name: fontName, + data: font, + weight: 800, + style: "normal", + }, + { + name: fontName, + data: font, weight: 400, style: "normal", }, ], }, ) - - fs.writeFileSync(`public/static/${filePath}.svg`, svg) + const resvg = new Resvg(svg) + const pngData = resvg.render() + const pngBuffer = pngData.asPng() + fs.writeFileSync(`public/static/${filePath}.png`, pngBuffer) } export default (() => { + let font: Promise function Head({ cfg, fileData, externalResources }: QuartzComponentProps) { + if (!font) { + font = getTtfFromGfont(cfg.theme.typography.header) + } const dir = "public/static" const slug = fileData.filePath const filePath = slug?.replaceAll("/", "-") const ogArr = slug?.split("/") - const ogTitle = fileData.frontmatter?.title ?? "Untitled" + const title = fileData.frontmatter?.title ?? "Untitled" + const description = fileData.description?.trim() ?? "No description provided" - // const title = fileData?.fron - // console.log("filePath: ", filePath) - // if (ogTitle && filePath) { - console.log("OG title: ", ogTitle) - generateSvg(ogTitle as string, filePath as string) + generateSvg(title, description, filePath as string, cfg.theme.typography.header, cfg) // } if (!fs.existsSync(dir)) { fs.mkdirSync(dir) } - const title = fileData.frontmatter?.title ?? "Untitled" - const description = fileData.description?.trim() ?? "No description provided" const { css, js } = externalResources const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) @@ -70,15 +124,23 @@ export default (() => { const iconPath = joinSegments(baseDir, "static/icon.png") const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png` - const ogImagePathNew = `https://${cfg.baseUrl}/static/${filePath}.svg` + const ogImagePathNew = `https://${cfg.baseUrl}/static/${filePath}.png` return ( {title} + + + + + + + + {cfg.baseUrl && } {cfg.baseUrl && } diff --git a/quartz/util/fonts.ts b/quartz/util/fonts.ts new file mode 100644 index 000000000..77e9df73a --- /dev/null +++ b/quartz/util/fonts.ts @@ -0,0 +1,14 @@ +/** + * Get the `.ttf` file (as ArrayBuffer) of a font by google font name + * @param font a valid google font + */ +export async function getTtfFromGfont(font: string) { + const css = await (await fetch(`https://fonts.googleapis.com/css?family=${font}`)).text() + const urlRegex = /url\((https:\/\/fonts.gstatic.com\/s\/.*?.ttf)\)/g + const match = urlRegex.exec(css) + if (match) { + // fontData is an ArrayBuffer containing the .ttf file data + const fontTtf = await (await fetch(match[1])).arrayBuffer() + return fontTtf + } +}