fix: embed fonts in OG images

This commit is contained in:
Peter Steinberger
2026-01-08 05:54:49 +01:00
parent d7650583dc
commit 0131229843
2 changed files with 58 additions and 11 deletions
+48 -10
View File
@@ -11,8 +11,12 @@ type OgQuery = {
description?: string
}
const FONT_SANS = 'Bricolage Grotesque'
const FONT_MONO = 'IBM Plex Mono'
let markDataUrlPromise: Promise<string> | null = null
let wasmInitPromise: Promise<void> | null = null
let fontBuffersPromise: Promise<Uint8Array[]> | null = null
async function ensureWasm() {
if (!wasmInitPromise) {
@@ -26,6 +30,32 @@ async function ensureWasm() {
await wasmInitPromise
}
async function getFontBuffers() {
if (!fontBuffersPromise) {
fontBuffersPromise = Promise.all([
readFile(
new URL(
'../../../node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2',
import.meta.url,
),
),
readFile(
new URL(
'../../../node_modules/@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-500-normal.woff2',
import.meta.url,
),
),
readFile(
new URL(
'../../../node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2',
import.meta.url,
),
),
]).then((buffers) => buffers.map((buffer) => new Uint8Array(buffer)))
}
return fontBuffersPromise
}
function getNitroServerRootUrl() {
const nitroMain = (globalThis as unknown as { __nitro_main__?: unknown }).__nitro_main__
if (typeof nitroMain !== 'string') return null
@@ -197,30 +227,30 @@ function buildSvg(params: {
<text x="186" y="163"
fill="#F6EFE4"
font-size="18"
font-weight="650"
font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, Arial, sans-serif"
font-weight="600"
font-family="${FONT_SANS}, sans-serif"
opacity="0.92">${escapeXml(pillText)}</text>
</g>
<text x="114" y="${titleY}"
fill="#F6EFE4"
font-size="${titleFontSize}"
font-weight="760"
font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, Arial, sans-serif">${titleTspans}</text>
font-weight="800"
font-family="${FONT_SANS}, sans-serif">${titleTspans}</text>
<text x="114" y="${descY}"
fill="#C6B8A8"
font-size="26"
font-weight="520"
font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Helvetica, Arial, sans-serif">${descTspans}</text>
font-weight="500"
font-family="${FONT_SANS}, sans-serif">${descTspans}</text>
<rect x="114" y="472" width="110" height="6" rx="3" fill="#E86A47"/>
<text x="114" y="530"
fill="#F6EFE4"
font-size="20"
font-weight="650"
font-weight="500"
opacity="0.90"
font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace">${escapeXml(params.footer)}</text>
font-family="${FONT_MONO}, monospace">${escapeXml(params.footer)}</text>
</svg>`
}
@@ -245,7 +275,10 @@ export default defineEventHandler(async (event) => {
setHeader(event, 'Cache-Control', cacheKey)
setHeader(event, 'Content-Type', 'image/png')
const [markDataUrl] = await Promise.all([getMarkDataUrl(), ensureWasm()])
const [markDataUrl, fontBuffers] = await Promise.all([
getMarkDataUrl(),
ensureWasm().then(() => getFontBuffers()),
])
const svg = buildSvg({
markDataUrl,
title,
@@ -257,7 +290,12 @@ export default defineEventHandler(async (event) => {
const resvg = new Resvg(svg, {
fitTo: { mode: 'width', value: 1200 },
font: { loadSystemFonts: true },
font: {
fontBuffers,
defaultFontFamily: FONT_SANS,
sansSerifFamily: FONT_SANS,
monospaceFamily: FONT_MONO,
},
})
const png = resvg.render().asPng()
resvg.free()
+10 -1
View File
@@ -9,6 +9,15 @@ import viteTsConfigPaths from 'vite-tsconfig-paths'
const require = createRequire(import.meta.url)
const resvgWasmPath = require.resolve('@resvg/resvg-wasm/index_bg.wasm')
const bricolageBoldPath = require.resolve(
'@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-800-normal.woff2',
)
const bricolageTextPath = require.resolve(
'@fontsource/bricolage-grotesque/files/bricolage-grotesque-latin-500-normal.woff2',
)
const plexMonoPath = require.resolve(
'@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2',
)
const config = defineConfig({
plugins: [
@@ -16,7 +25,7 @@ const config = defineConfig({
nitro({
serverDir: 'server',
externals: {
traceInclude: [resvgWasmPath],
traceInclude: [resvgWasmPath, bricolageBoldPath, bricolageTextPath, plexMonoPath],
},
}),
// this is the plugin that enables path aliases