From 09c7c307799b060ca712f91f5ee036d5bc1a04ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Berg=C3=A9?= Date: Fri, 10 Jan 2025 21:38:25 +0100 Subject: [PATCH] Try to fix error on og image generation (#2722) --- .changeset/fluffy-panthers-unite.md | 5 + .../~gitbook/ogimage/[pageId]/route.tsx | 200 +++++++++--------- packages/gitbook/src/fonts/index.ts | 18 ++ 3 files changed, 125 insertions(+), 98 deletions(-) create mode 100644 .changeset/fluffy-panthers-unite.md diff --git a/.changeset/fluffy-panthers-unite.md b/.changeset/fluffy-panthers-unite.md new file mode 100644 index 000000000..6f7ef7668 --- /dev/null +++ b/.changeset/fluffy-panthers-unite.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Try to fix error on og image generation diff --git a/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx b/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx index 9de958c31..1bf5e7dff 100644 --- a/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx +++ b/packages/gitbook/src/app/(site)/(core)/~gitbook/ogimage/[pageId]/route.tsx @@ -5,15 +5,31 @@ import { NextRequest } from 'next/server'; import colorContrast from 'postcss-color-contrast/js'; import React from 'react'; +import { googleFontsMap } from '@/fonts'; import { getGitBookContextFromHeaders } from '@/lib/gitbook-context'; import { getAbsoluteHref } from '@/lib/links'; -import { tcls } from '@/lib/tailwind'; +import { filterOutNullable } from '@/lib/typescript'; import { getContentTitle } from '@/lib/utils'; import { PageIdParams, fetchPageData } from '../../../../fetch'; export const runtime = 'edge'; +async function loadGoogleFont(font: string, text: string) { + const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`; + const css = await (await fetch(url)).text(); + const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/); + + if (resource) { + const response = await fetch(resource[1]); + if (response.status == 200) { + return await response.arrayBuffer(); + } + } + + throw new Error('failed to load font data'); +} + /** * Render the OpenGraph image for a space. */ @@ -21,30 +37,60 @@ export async function GET(req: NextRequest, { params }: { params: Promise res.arrayBuffer()); - const interBold = await fetch( - new URL('../../../../../../fonts/Inter/Inter-Bold.ttf', import.meta.url), - ).then((res) => res.arrayBuffer()); + // Compute all text to load only the necessary fonts + const contentTitle = customization.header.logo + ? '' + : getContentTitle(space, customization, site ?? null); + const pageTitle = page + ? page.title.length > 64 + ? page.title.slice(0, 64) + '...' + : page.title + : 'Not found'; + const pageDescription = + page?.description && page?.title.length <= 64 + ? page.description.length > 164 + ? page.description.slice(0, 164) + '...' + : page.description + : ''; + + const fontFamily = googleFontsMap[customization.styling.font] ?? 'Inter'; + + const regularText = pageDescription; + const boldText = `${contentTitle}${pageTitle}`; + + const fonts = ( + await Promise.all([ + regularText + ? loadGoogleFont(`${fontFamily}:wght@400`, regularText).then((data) => ({ + name: fontFamily, + data, + style: 'normal' as const, + weight: 400 as const, + })) + : null, + boldText + ? loadGoogleFont(`${fontFamily}:wght@700`, `${contentTitle}${pageTitle}`).then( + (data) => ({ + name: fontFamily, + data, + style: 'normal' as const, + weight: 700 as const, + }), + ) + : null, + ]) + ).filter(filterOutNullable); const theme = customization.themes.default; const useLightTheme = theme === 'light'; // We have no access to CSS variables, so we'll have to hardcode some values - const baseColors = { - light: '#ffffff', - dark: '#111827', - }; + const baseColors = { light: '#ffffff', dark: '#111827' }; let colors = { background: baseColors[theme], @@ -92,49 +138,17 @@ export async function GET(req: NextRequest, { params }: { params: Promise { - if ('icon' in customization.favicon) - return ( - Icon - ); - if ('emoji' in customization.favicon) - return ( - - {String.fromCodePoint(parseInt('0x' + customization.favicon.emoji))} - - ); - const src = getAbsoluteHref( - ctx, - `~gitbook/icon?size=medium&theme=${customization.themes.default}`, - true, - ); - return Icon; - })(); - return new ImageResponse( (
{/* Gradient */}
{/* Grid */} - Grid + Grid {/* Logo */} {customization.header.logo ? ( @@ -160,39 +170,46 @@ export async function GET(req: NextRequest, { params }: { params: Promise ) : ( -
- {favicon} -

- {getContentTitle(space, customization, site ?? null)} -

+
+ {(() => { + if ('icon' in customization.favicon) + return ( + Icon + ); + if ('emoji' in customization.favicon) + return ( + + {String.fromCodePoint( + parseInt('0x' + customization.favicon.emoji), + )} + + ); + const src = getAbsoluteHref( + ctx, + `~gitbook/icon?size=medium&theme=${customization.themes.default}`, + true, + ); + return Icon; + })()} +

{contentTitle}

)} {/* Title and description */} -
+

- {page - ? page.title.length > 64 - ? page.title.slice(0, 64) + '...' - : page.title - : 'Not found'} + {pageTitle}

- {page?.description && page?.title.length <= 64 ? ( -

- {page.description.length > 164 - ? page.description.slice(0, 164) + '...' - : page.description} -

+ {pageDescription ? ( +

{pageDescription}

) : null}
@@ -200,20 +217,7 @@ export async function GET(req: NextRequest, { params }: { params: Promise