Skip to content

Commit

Permalink
fix: resolve non-latin char issue in generated OG images (#318)
Browse files Browse the repository at this point in the history
Changes made
- update font source with Google font
- add error handling in font fetching logic
- update docs in dynamic-og-images blog post

---------

Co-authored-by: satnaing <[email protected]>
  • Loading branch information
cantpr09ram and satnaing committed Jul 27, 2024
1 parent 0440d2a commit 2f82feb
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 52 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 39 additions & 1 deletion src/content/blog/dynamic-og-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,48 @@ Dynamic OG images will be generated at build time for blog posts that
Dynamic OG image of AstroPaper includes _the blog post title_, _author name_ and _site title_. Author name and site title will be retrieved via `SITE.author` and `SITE.title` of **"src/config.ts"** file. The title is generated from the blog post frontmatter `title`.
![Example Dynamic OG Image link](https://user-images.githubusercontent.com/53733092/209704501-e9c2236a-3f4d-4c67-bab3-025aebd63382.png)

### Issue Non-Latin Characters

Titles with non-latin characters won't display properly out of the box. To resolve this, we have to replace `fontsConfig` inside `loadGoogleFont.ts` with your preferred font.

```ts
// file: loadGoogleFont.ts

async function loadGoogleFonts(
text: string
): Promise<
Array<{ name: string; data: ArrayBuffer; weight: number; style: string }>
> {
const fontsConfig = [
{
name: "Noto Sans JP",
font: "Noto+Sans+JP",
weight: 400,
style: "normal",
},
{
name: "Noto Sans JP",
font: "Noto+Sans+JP:wght@700",
weight: 700,
style: "normal",
},
{ name: "Noto Sans", font: "Noto+Sans", weight: 400, style: "normal" },
{
name: "Noto Sans",
font: "Noto+Sans:wght@700",
weight: 700,
style: "normal",
},
];
// other codes
}
```

> Check out [this PR](https://github.com/satnaing/astro-paper/pull/318) for more info.
## Limitations

At the time of writing this, [Satori](https://github.com/vercel/satori) is fairly new and has not reached major release yet. So, there are still some limitations to this dynamic OG image feature.

- If you have Blog posts with non-English titles, you have to set `embedFonts` option to `false` (file: `src/utils/generateOgImage.tsx`). Even after this, the OG image might not be displayed very well.
- Besides, RTL languages are not supported yet.
- [Using emoji](https://github.com/vercel/satori#emojis) in the title might be a little bit tricky.
43 changes: 2 additions & 41 deletions src/utils/generateOgImages.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,20 @@
import satori, { type SatoriOptions } from "satori";
import { Resvg } from "@resvg/resvg-js";
import { type CollectionEntry } from "astro:content";
import postOgImage from "./og-templates/post";
import siteOgImage from "./og-templates/site";

const fetchFonts = async () => {
// Regular Font
const fontFileRegular = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.regular.ttf"
);
const fontRegular: ArrayBuffer = await fontFileRegular.arrayBuffer();

// Bold Font
const fontFileBold = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.bold.ttf"
);
const fontBold: ArrayBuffer = await fontFileBold.arrayBuffer();

return { fontRegular, fontBold };
};

const { fontRegular, fontBold } = await fetchFonts();

const options: SatoriOptions = {
width: 1200,
height: 630,
embedFont: true,
fonts: [
{
name: "IBM Plex Mono",
data: fontRegular,
weight: 400,
style: "normal",
},
{
name: "IBM Plex Mono",
data: fontBold,
weight: 600,
style: "normal",
},
],
};

function svgBufferToPngBuffer(svg: string) {
const resvg = new Resvg(svg);
const pngData = resvg.render();
return pngData.asPng();
}

export async function generateOgImageForPost(post: CollectionEntry<"blog">) {
const svg = await satori(postOgImage(post), options);
const svg = await postOgImage(post);
return svgBufferToPngBuffer(svg);
}

export async function generateOgImageForSite() {
const svg = await satori(siteOgImage(), options);
const svg = await siteOgImage();
return svgBufferToPngBuffer(svg);
}
71 changes: 71 additions & 0 deletions src/utils/loadGoogleFont.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { FontStyle, FontWeight } from "satori";

export type FontOptions = {
name: string;
data: ArrayBuffer;
weight: FontWeight | undefined;
style: FontStyle | undefined;
};

async function loadGoogleFont(
font: string,
text: string
): Promise<ArrayBuffer> {
const API = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`;

const css = await (
await fetch(API, {
headers: {
"User-Agent":
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1",
},
})
).text();

const resource = css.match(
/src: url\((.+)\) format\('(opentype|truetype)'\)/
);

if (!resource) throw new Error("Failed to download dynamic font");

const res = await fetch(resource[1]);

if (!res.ok) {
throw new Error("Failed to download dynamic font. Status: " + res.status);
}

const fonts: ArrayBuffer = await res.arrayBuffer();
return fonts;
}

async function loadGoogleFonts(
text: string
): Promise<
Array<{ name: string; data: ArrayBuffer; weight: number; style: string }>
> {
const fontsConfig = [
{
name: "IBM Plex Mono",
font: "IBM+Plex+Mono",
weight: 400,
style: "normal",
},
{
name: "IBM Plex Mono",
font: "IBM+Plex+Mono:wght@700",
weight: 700,
style: "bold",
},
];

const fonts = await Promise.all(
fontsConfig.map(async ({ name, font, weight, style }) => {
const data = await loadGoogleFont(font, text);
return { name, data, weight, style };
})
);

return fonts;
}

export default loadGoogleFonts;
18 changes: 14 additions & 4 deletions src/utils/og-templates/post.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { SITE } from "@config";
import satori from "satori";
import type { CollectionEntry } from "astro:content";
import { SITE } from "@config";
import loadGoogleFonts, { type FontOptions } from "../loadGoogleFont";

export default (post: CollectionEntry<"blog">) => {
return (
export default async (post: CollectionEntry<"blog">) => {
return satori(
<div
style={{
background: "#fefbfb",
Expand Down Expand Up @@ -91,6 +93,14 @@ export default (post: CollectionEntry<"blog">) => {
</div>
</div>
</div>
</div>
</div>,
{
width: 1200,
height: 630,
embedFont: true,
fonts: (await loadGoogleFonts(
post.data.title + post.data.author + SITE.title + "by"
)) as FontOptions[],
}
);
};
14 changes: 11 additions & 3 deletions src/utils/og-templates/site.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import satori from "satori";
import { SITE } from "@config";
import loadGoogleFonts, { type FontOptions } from "../loadGoogleFont";

export default () => {
return (
export default async () => {
return satori(
<div
style={{
background: "#fefbfb",
Expand Down Expand Up @@ -82,6 +84,12 @@ export default () => {
</div>
</div>
</div>
</div>
</div>,
{
width: 1200,
height: 630,
embedFont: true,
fonts: (await loadGoogleFonts(SITE.title + SITE.desc)) as FontOptions[],
}
);
};

0 comments on commit 2f82feb

Please sign in to comment.