Skip to content

Commit 112cfe9

Browse files
committed
fix: pass Inter font via resvg fontBuffers for visible text in OG images
1 parent ce20225 commit 112cfe9

File tree

1 file changed

+46
-13
lines changed

1 file changed

+46
-13
lines changed

src/routes/[username]/[slug]/og/+server.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import type { RequestHandler } from './$types';
22
import { Resvg } from '@cf-wasm/resvg';
33

4+
let fontCache: Uint8Array | null = null;
5+
6+
async function loadFont(): Promise<Uint8Array> {
7+
if (fontCache) return fontCache;
8+
9+
const css = await fetch(
10+
'https://fonts.googleapis.com/css2?family=Inter:wght@400;700&subset=latin',
11+
{
12+
headers: {
13+
'User-Agent':
14+
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1'
15+
}
16+
}
17+
).then((r) => r.text());
18+
19+
const fontUrl = css.match(/src: url\((.+?)\) format\('(opentype|truetype)'\)/)?.[1];
20+
if (!fontUrl) throw new Error('Could not find font URL');
21+
22+
const fontBuffer = await fetch(fontUrl).then((r) => r.arrayBuffer());
23+
fontCache = new Uint8Array(fontBuffer);
24+
return fontCache;
25+
}
26+
427
function esc(s: string): string {
528
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
629
}
@@ -29,13 +52,13 @@ function buildSvg(
2952

3053
function addGroup(label: string, count: number, pkgs: { name: string }[], color: string) {
3154
if (curY > maxY) return;
32-
tagsSvg += `<text x="${pad}" y="${curY}" fill="#555" font-size="11" font-family="monospace" letter-spacing="1.5">${label} · ${count}</text>`;
55+
tagsSvg += `<text x="${pad}" y="${curY}" fill="#555" font-size="11" font-family="Inter" letter-spacing="1.5">${label} · ${count}</text>`;
3356
curY += 20;
3457
curX = pad;
3558

3659
for (let i = 0; i < pkgs.length; i++) {
3760
if (curY > maxY) {
38-
tagsSvg += `<text x="${curX}" y="${curY + 18}" fill="#555" font-size="13" font-family="sans-serif">+${pkgs.length - i} more</text>`;
61+
tagsSvg += `<text x="${curX}" y="${curY + 18}" fill="#555" font-size="13" font-family="Inter">+${pkgs.length - i} more</text>`;
3962
curY += tagH + tagGap;
4063
return;
4164
}
@@ -47,14 +70,14 @@ function buildSvg(
4770
curX = pad;
4871
curY += tagH + tagGap;
4972
if (curY > maxY) {
50-
tagsSvg += `<text x="${curX}" y="${curY + 18}" fill="#555" font-size="13" font-family="sans-serif">+${pkgs.length - i} more</text>`;
73+
tagsSvg += `<text x="${curX}" y="${curY + 18}" fill="#555" font-size="13" font-family="Inter">+${pkgs.length - i} more</text>`;
5174
curY += tagH + tagGap;
5275
return;
5376
}
5477
}
5578

5679
tagsSvg += `<rect x="${curX}" y="${curY}" width="${tw}" height="${tagH}" rx="6" fill="#141414" stroke="#252525" stroke-width="1"/>`;
57-
tagsSvg += `<text x="${curX + tagPadX}" y="${curY + 18}" fill="${color}" font-size="13" font-family="monospace">${esc(text)}</text>`;
80+
tagsSvg += `<text x="${curX + tagPadX}" y="${curY + 18}" fill="${color}" font-size="13" font-family="Inter">${esc(text)}</text>`;
5881
curX += tw + tagGap;
5982
}
6083
curY += tagH + tagGap + 8;
@@ -65,34 +88,34 @@ function buildSvg(
6588
if (npm.length > 0) addGroup('NPM', npm.length, npm, '#22c55e');
6689

6790
const descLine = description
68-
? `<text x="${pad}" y="138" fill="#888" font-size="16" font-family="sans-serif">${esc(description.slice(0, 80))}</text>`
91+
? `<text x="${pad}" y="138" fill="#888" font-size="16" font-family="Inter">${esc(description.slice(0, 80))}</text>`
6992
: '';
7093

7194
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
72-
<rect width="${W}" height="${H}" fill="#0a0a0a"/>
7395
<defs>
7496
<radialGradient id="glow" cx="0.2" cy="0.15" r="0.55">
7597
<stop offset="0%" stop-color="#22c55e" stop-opacity="0.07"/>
7698
<stop offset="40%" stop-color="#22c55e" stop-opacity="0.03"/>
7799
<stop offset="100%" stop-color="#22c55e" stop-opacity="0"/>
78100
</radialGradient>
79101
</defs>
102+
<rect width="${W}" height="${H}" fill="#0a0a0a"/>
80103
<rect width="${W}" height="${H}" fill="url(#glow)"/>
81104
82-
<text x="${pad}" y="76" fill="#22c55e" font-size="28" font-weight="bold" font-family="sans-serif">OpenBoot</text>
83-
<text x="${pad}" y="112" fill="#ffffff" font-size="28" font-weight="bold" font-family="sans-serif">${esc(name)}</text>
105+
<text x="${pad}" y="76" fill="#22c55e" font-size="28" font-weight="bold" font-family="Inter">OpenBoot</text>
106+
<text x="${pad}" y="112" fill="#ffffff" font-size="28" font-weight="bold" font-family="Inter">${esc(name)}</text>
84107
${descLine}
85108
86-
<text x="${W - pad}" y="68" fill="#666" font-size="15" font-family="sans-serif" text-anchor="end">@${esc(username)}</text>
87-
<text x="${W - pad}" y="90" fill="#22c55e" font-size="15" font-family="sans-serif" text-anchor="end">${total} packages</text>
109+
<text x="${W - pad}" y="68" fill="#666" font-size="15" font-family="Inter" text-anchor="end">@${esc(username)}</text>
110+
<text x="${W - pad}" y="90" fill="#22c55e" font-size="15" font-family="Inter" text-anchor="end">${total} packages</text>
88111
89112
<line x1="${pad}" y1="158" x2="${W - pad}" y2="158" stroke="#1a1a1a" stroke-width="1"/>
90113
91114
${tagsSvg}
92115
93116
<line x1="${pad}" y1="${H - 56}" x2="${W - pad}" y2="${H - 56}" stroke="#1a1a1a" stroke-width="1"/>
94-
<text x="${pad}" y="${H - 28}" fill="#444" font-size="14" font-family="sans-serif">openboot.dev</text>
95-
<text x="${W - pad}" y="${H - 28}" fill="#444" font-size="13" font-family="sans-serif" text-anchor="end">${esc(preset)} preset</text>
117+
<text x="${pad}" y="${H - 28}" fill="#444" font-size="14" font-family="Inter">openboot.dev</text>
118+
<text x="${W - pad}" y="${H - 28}" fill="#444" font-size="13" font-family="Inter" text-anchor="end">${esc(preset)} preset</text>
96119
</svg>`;
97120
}
98121

@@ -135,6 +158,8 @@ export const GET: RequestHandler = async ({ params, platform }) => {
135158
const npm = rawPkgs.filter((p) => p.type === 'npm');
136159
const total = rawPkgs.length;
137160

161+
const fontData = await loadFont();
162+
138163
const svg = buildSvg(
139164
config.name,
140165
config.description || '',
@@ -146,7 +171,15 @@ export const GET: RequestHandler = async ({ params, platform }) => {
146171
);
147172

148173
try {
149-
const resvg = await Resvg.async(svg, { fitTo: { mode: 'width' as const, value: 1200 } });
174+
const resvg = await Resvg.async(svg, {
175+
fitTo: { mode: 'width' as const, value: 1200 },
176+
font: {
177+
fontBuffers: [fontData],
178+
defaultFontFamily: 'Inter',
179+
sansSerifFamily: 'Inter',
180+
defaultFontSize: 16
181+
}
182+
});
150183
const pngData = resvg.render();
151184
const pngBuffer = pngData.asPng();
152185

0 commit comments

Comments
 (0)