mirror of
https://github.com/ChenQihan666/MyBlog-Next.git
synced 2026-08-14 11:29:33 +08:00
fix: resolve non-latin char issue in generated OG images (#318)
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 <satnaingdev@gmail.com>
This commit is contained in:
Generated
+3
-3
@@ -2143,9 +2143,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-dom": {
|
"node_modules/@types/react-dom": {
|
||||||
"version": "18.2.7",
|
"version": "18.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||||
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
|
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -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`.
|
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`.
|
||||||

|

|
||||||
|
|
||||||
|
### 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
|
## 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.
|
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.
|
- Besides, RTL languages are not supported yet.
|
||||||
- [Using emoji](https://github.com/vercel/satori#emojis) in the title might be a little bit tricky.
|
- [Using emoji](https://github.com/vercel/satori#emojis) in the title might be a little bit tricky.
|
||||||
|
|||||||
@@ -1,47 +1,8 @@
|
|||||||
import satori, { type SatoriOptions } from "satori";
|
|
||||||
import { Resvg } from "@resvg/resvg-js";
|
import { Resvg } from "@resvg/resvg-js";
|
||||||
import { type CollectionEntry } from "astro:content";
|
import { type CollectionEntry } from "astro:content";
|
||||||
import postOgImage from "./og-templates/post";
|
import postOgImage from "./og-templates/post";
|
||||||
import siteOgImage from "./og-templates/site";
|
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) {
|
function svgBufferToPngBuffer(svg: string) {
|
||||||
const resvg = new Resvg(svg);
|
const resvg = new Resvg(svg);
|
||||||
const pngData = resvg.render();
|
const pngData = resvg.render();
|
||||||
@@ -49,11 +10,11 @@ function svgBufferToPngBuffer(svg: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function generateOgImageForPost(post: CollectionEntry<"blog">) {
|
export async function generateOgImageForPost(post: CollectionEntry<"blog">) {
|
||||||
const svg = await satori(postOgImage(post), options);
|
const svg = await postOgImage(post);
|
||||||
return svgBufferToPngBuffer(svg);
|
return svgBufferToPngBuffer(svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateOgImageForSite() {
|
export async function generateOgImageForSite() {
|
||||||
const svg = await satori(siteOgImage(), options);
|
const svg = await siteOgImage();
|
||||||
return svgBufferToPngBuffer(svg);
|
return svgBufferToPngBuffer(svg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { SITE } from "@config";
|
import satori from "satori";
|
||||||
import type { CollectionEntry } from "astro:content";
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import { SITE } from "@config";
|
||||||
|
import loadGoogleFonts, { type FontOptions } from "../loadGoogleFont";
|
||||||
|
|
||||||
export default (post: CollectionEntry<"blog">) => {
|
export default async (post: CollectionEntry<"blog">) => {
|
||||||
return (
|
return satori(
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: "#fefbfb",
|
background: "#fefbfb",
|
||||||
@@ -91,6 +93,14 @@ export default (post: CollectionEntry<"blog">) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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[],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import satori from "satori";
|
||||||
import { SITE } from "@config";
|
import { SITE } from "@config";
|
||||||
|
import loadGoogleFonts, { type FontOptions } from "../loadGoogleFont";
|
||||||
|
|
||||||
export default () => {
|
export default async () => {
|
||||||
return (
|
return satori(
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: "#fefbfb",
|
background: "#fefbfb",
|
||||||
@@ -82,6 +84,12 @@ export default () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>,
|
||||||
|
{
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
embedFont: true,
|
||||||
|
fonts: (await loadGoogleFonts(SITE.title + SITE.desc)) as FontOptions[],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user