Skip to content

Commit 4c8fc86

Browse files
Merge pull request #186 from ShokoAnime/general-cleanup
Update build script.
2 parents 999fa92 + b9ca753 commit 4c8fc86

File tree

3 files changed

+360
-11
lines changed

3 files changed

+360
-11
lines changed

docs/scripts/generateOgImages.js

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "fs";
22
import path from "path";
33
import { createContentLoader } from "vitepress";
4+
import sharp from "sharp";
45

56
// Colors
67
const COLORS = {
@@ -24,13 +25,67 @@ const FONT_FACE = {
2425
SECONDARY: "Roboto",
2526
};
2627

27-
// Image paths
28+
// Image paths relative to project root
2829
const IMAGES = {
29-
BACKGROUND: "../open-graph-background.png",
30-
LOGO: "../logo.png",
30+
BACKGROUND: path.resolve(process.cwd(), "docs/public/images/open-graph-background.png"),
31+
LOGO: path.resolve(process.cwd(), "docs/public/images/logo.png"),
3132
};
3233

33-
function buildOgImage({ title, summary, pageUrl }) {
34+
// Function to convert image to base64
35+
async function imageToBase64(imagePath) {
36+
try {
37+
const imageBuffer = await fs.promises.readFile(imagePath);
38+
return `data:image/png;base64,${imageBuffer.toString('base64')}`;
39+
} catch (error) {
40+
console.error(`Error loading image ${imagePath}:`, error);
41+
return null;
42+
}
43+
}
44+
45+
// Text wrapping function
46+
function wrapText(text, maxWidth, fontSize) {
47+
const avgCharWidth = fontSize * 0.6;
48+
const charsPerLine = Math.floor(maxWidth / avgCharWidth);
49+
const words = text.split(' ');
50+
const lines = [];
51+
let currentLine = words[0];
52+
53+
for (let i = 1; i < words.length; i++) {
54+
if (currentLine.length + words[i].length + 1 <= charsPerLine) {
55+
currentLine += ' ' + words[i];
56+
} else {
57+
lines.push(currentLine);
58+
currentLine = words[i];
59+
}
60+
}
61+
lines.push(currentLine);
62+
63+
return lines;
64+
}
65+
66+
async function buildOgImage({ title, summary, pageUrl }) {
67+
// Load images as base64
68+
const backgroundBase64 = await imageToBase64(IMAGES.BACKGROUND);
69+
const logoBase64 = await imageToBase64(IMAGES.LOGO);
70+
71+
if (!backgroundBase64 || !logoBase64) {
72+
throw new Error('Failed to load required images');
73+
}
74+
75+
// Calculate wrapped text
76+
const titleLines = wrapText(title, 1000, parseInt(FONT_SIZE.TITLE));
77+
const summaryLines = wrapText(summary, 1100, parseInt(FONT_SIZE.SUMMARY));
78+
79+
// Generate title tspans with 1.5 line height
80+
const titleTspans = titleLines.map((line, index) =>
81+
`<tspan x="50" dy="${index === 0 ? '0' : '1.5em'}">${line}</tspan>`
82+
).join('');
83+
84+
// Generate summary tspans with 1.5 line height
85+
const summaryTspans = summaryLines.map((line, index) =>
86+
`<tspan x="50" dy="${index === 0 ? '0' : '1.5em'}">${line}</tspan>`
87+
).join('');
88+
3489
return `
3590
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
3691
<defs>
@@ -40,7 +95,7 @@ function buildOgImage({ title, summary, pageUrl }) {
4095
</defs>
4196
4297
<image
43-
href="${IMAGES.BACKGROUND}"
98+
href="${backgroundBase64}"
4499
width="1200"
45100
height="630"
46101
clip-path="url(#clip)"
@@ -55,7 +110,7 @@ function buildOgImage({ title, summary, pageUrl }) {
55110
56111
<g>
57112
<image
58-
href="${IMAGES.LOGO}"
113+
href="${logoBase64}"
59114
x="50"
60115
y="55"
61116
width="117"
@@ -83,14 +138,16 @@ function buildOgImage({ title, summary, pageUrl }) {
83138
font-family="${FONT_FACE.PRIMARY}"
84139
font-weight="bold"
85140
fill="${COLORS.TEXT_PRIMARY}"
86-
>${title}</text>
141+
>${titleTspans}</text>
142+
87143
<text
88144
x="50"
89145
y="400"
90146
font-size="${FONT_SIZE.SUMMARY}"
91147
font-family="${FONT_FACE.SECONDARY}"
92148
fill="${COLORS.TEXT_PRIMARY}"
93-
>${summary}</text>
149+
>${summaryTspans}</text>
150+
94151
<text
95152
x="50"
96153
y="550"
@@ -103,6 +160,18 @@ function buildOgImage({ title, summary, pageUrl }) {
103160
`;
104161
}
105162

163+
async function convertSvgToPng(svgBuffer, outputPath) {
164+
try {
165+
await sharp(Buffer.from(svgBuffer))
166+
.png()
167+
.toFile(outputPath);
168+
return true;
169+
} catch (error) {
170+
console.error('Error converting SVG to PNG:', error);
171+
return false;
172+
}
173+
}
174+
106175
export const generateOgImages = async (config) => {
107176
try {
108177
// Get the output directory from VitePress config
@@ -119,7 +188,7 @@ export const generateOgImages = async (config) => {
119188
try {
120189
const relativePath = file.url.replace(/^\//, "") + ".md";
121190

122-
const svg = buildOgImage({
191+
const svg = await buildOgImage({
123192
title: file.frontmatter?.title || "Shoko",
124193
summary: file.frontmatter?.description || "",
125194
pageUrl: `https://docs.shokoanime.com${file.url}`,
@@ -131,8 +200,11 @@ export const generateOgImages = async (config) => {
131200
.replace(/\s+/g, "-")
132201
.toLowerCase();
133202

134-
fs.writeFileSync(path.join(outputDir, `${filename}.svg`), svg);
135-
console.log(`Generated OG image for ${filename}`);
203+
// Convert directly to PNG without saving SVG
204+
const pngPath = path.join(outputDir, `${filename}.png`);
205+
await convertSvgToPng(svg, pngPath);
206+
207+
console.log(`Generated PNG OG image for ${filename}`);
136208
} catch (fileError) {
137209
console.error(`Error processing file ${file.url}:`, fileError);
138210
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"devDependencies": {
1212
"@nolebase/vitepress-plugin-git-changelog": "^2.13.2",
1313
"dprint": "^0.49.0",
14+
"sharp": "^0.33.5",
1415
"vitepress": "^1.6.3",
1516
"vue-tsc": "^2.2.0"
1617
},

0 commit comments

Comments
 (0)