180 lines
4.8 KiB
JavaScript
180 lines
4.8 KiB
JavaScript
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Registra il font JetBrains Mono
|
|
const fontPath = path.join(
|
|
__dirname,
|
|
"../static/fonts/JetBrainsMono-Medium.ttf",
|
|
);
|
|
GlobalFonts.registerFromPath(fontPath, "JetBrains Mono");
|
|
|
|
// Configurazione (come in Hugo)
|
|
const config = {
|
|
x: 141,
|
|
y: 300,
|
|
fontSize: 55,
|
|
color: "#ffffff",
|
|
lineSpacing: 1.2,
|
|
maxWidth: 1080, // larghezza massima per il testo
|
|
basePngPath: path.join(__dirname, "../og_base.png"),
|
|
};
|
|
|
|
/**
|
|
* Genera un'immagine OpenGraph con il titolo sovrapposto
|
|
* @param {string} title - Il titolo da scrivere
|
|
* @param {string} outputPath - Percorso dove salvare l'immagine
|
|
*/
|
|
export async function generateOGImage(title, outputPath) {
|
|
try {
|
|
// Carica l'immagine base
|
|
const baseImage = await loadImage(config.basePngPath);
|
|
|
|
// Crea canvas con le stesse dimensioni dell'immagine base
|
|
const canvas = createCanvas(baseImage.width, baseImage.height);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
// Disegna l'immagine base
|
|
ctx.drawImage(baseImage, 0, 0);
|
|
|
|
// Configura il testo
|
|
ctx.font = `${config.fontSize}px "JetBrains Mono"`;
|
|
ctx.fillStyle = config.color;
|
|
ctx.textBaseline = "top";
|
|
|
|
// Gestisci il text wrapping
|
|
const lines = wrapText(ctx, title, config.maxWidth);
|
|
|
|
// Disegna ogni linea
|
|
lines.forEach((line, index) => {
|
|
const y = config.y + index * config.fontSize * config.lineSpacing;
|
|
ctx.fillText(line, config.x, y);
|
|
});
|
|
|
|
// Salva l'immagine
|
|
const dir = path.dirname(outputPath);
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
const buffer = canvas.toBuffer("image/png");
|
|
fs.writeFileSync(outputPath, buffer);
|
|
|
|
console.log(`✓ Generated OG image: ${outputPath}`);
|
|
return outputPath;
|
|
} catch (error) {
|
|
console.error(`✗ Error generating OG image for "${title}":`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Divide il testo in più righe se necessario
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
* @param {string} text
|
|
* @param {number} maxWidth
|
|
* @returns {string[]}
|
|
*/
|
|
function wrapText(ctx, text, maxWidth) {
|
|
const words = text.split(" ");
|
|
const lines = [];
|
|
let currentLine = words[0];
|
|
|
|
for (let i = 1; i < words.length; i++) {
|
|
const word = words[i];
|
|
const width = ctx.measureText(currentLine + " " + word).width;
|
|
|
|
if (width < maxWidth) {
|
|
currentLine += " " + word;
|
|
} else {
|
|
lines.push(currentLine);
|
|
currentLine = word;
|
|
}
|
|
}
|
|
lines.push(currentLine);
|
|
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Genera immagini OG per tutti i contenuti markdown
|
|
*/
|
|
export async function generateAllOGImages() {
|
|
const contentDir = path.join(__dirname, "../content");
|
|
const publicDir = path.join(__dirname, "../public/og");
|
|
|
|
// Leggi tutti i file markdown
|
|
const files = getAllMarkdownFiles(contentDir);
|
|
|
|
console.log(`\nGenerating OpenGraph images for ${files.length} files...\n`);
|
|
|
|
for (const file of files) {
|
|
const content = fs.readFileSync(file, "utf-8");
|
|
const title = extractTitle(content);
|
|
|
|
if (title) {
|
|
const relativePath = path.relative(contentDir, file);
|
|
const slug = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
|
|
const outputPath = path.join(publicDir, `${slug}.png`);
|
|
|
|
await generateOGImage(title, outputPath);
|
|
}
|
|
}
|
|
|
|
console.log(`\nAll OpenGraph images generated!\n`);
|
|
}
|
|
|
|
/**
|
|
* Trova tutti i file markdown ricorsivamente
|
|
* @param {string} dir
|
|
* @returns {string[]}
|
|
*/
|
|
function getAllMarkdownFiles(dir, fileList = []) {
|
|
const files = fs.readdirSync(dir);
|
|
|
|
files.forEach((file) => {
|
|
const filePath = path.join(dir, file);
|
|
const stat = fs.statSync(filePath);
|
|
|
|
if (stat.isDirectory()) {
|
|
getAllMarkdownFiles(filePath, fileList);
|
|
} else if (file.endsWith(".md") && !file.startsWith("_")) {
|
|
fileList.push(filePath);
|
|
}
|
|
});
|
|
|
|
return fileList;
|
|
}
|
|
|
|
/**
|
|
* Estrae il titolo dal frontmatter o dal primo heading
|
|
* @param {string} content
|
|
* @returns {string|null}
|
|
*/
|
|
function extractTitle(content) {
|
|
// Cerca nel frontmatter
|
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (frontmatterMatch) {
|
|
const titleMatch = frontmatterMatch[1].match(/title:\s*['"](.*?)['"]/);
|
|
if (titleMatch) return titleMatch[1];
|
|
}
|
|
|
|
// Cerca nel primo heading
|
|
const headingMatch = content.match(/^#\s+(.+)$/m);
|
|
if (headingMatch) return headingMatch[1];
|
|
|
|
return null;
|
|
}
|
|
|
|
// Esegui lo script se chiamato direttamente
|
|
if (import.meta.url.startsWith("file:")) {
|
|
const modulePath = fileURLToPath(import.meta.url);
|
|
const scriptPath = process.argv[1];
|
|
if (modulePath === scriptPath) {
|
|
generateAllOGImages().catch(console.error);
|
|
}
|
|
}
|