Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
75 lines
2.5 KiB
JavaScript
75 lines
2.5 KiB
JavaScript
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { TeknavData } from '../teknav-data.js';
|
|
|
|
const { createCanvas, GlobalFonts } = await import('@napi-rs/canvas');
|
|
|
|
GlobalFonts.registerFromPath(resolve('public/fonts/Vazirmatn-Bold.ttf'), 'Vazirmatn');
|
|
GlobalFonts.registerFromPath(resolve('public/fonts/Vazirmatn-Regular.ttf'), 'Vazirmatn');
|
|
GlobalFonts.registerFromPath(resolve('public/fonts/Vazirmatn-SemiBold.ttf'), 'Vazirmatn');
|
|
|
|
const OUT_DIR = resolve('public/images/og');
|
|
|
|
mkdirSync(OUT_DIR, { recursive: true });
|
|
|
|
function isPublishedArticle(article) {
|
|
if (article.status === 'منتشرشده') return true;
|
|
if (article.status === 'پیشنویس' || article.status === 'در انتظار بررسی') return false;
|
|
if (['rust-systems', 'venture-capital-ai'].includes(article.slug)) return false;
|
|
return !!article.dateEn;
|
|
}
|
|
|
|
function wrap(ctx, text, x, y, maxWidth, lineHeight, maxLines) {
|
|
const words = String(text).split(/\s+/);
|
|
let line = '';
|
|
let lines = 0;
|
|
for (const word of words) {
|
|
const next = line ? `${line} ${word}` : word;
|
|
if (ctx.measureText(next).width > maxWidth && line) {
|
|
ctx.fillText(line, x, y);
|
|
y += lineHeight;
|
|
lines += 1;
|
|
line = word;
|
|
if (lines >= maxLines) return y;
|
|
} else {
|
|
line = next;
|
|
}
|
|
}
|
|
if (line && lines < maxLines) ctx.fillText(line, x, y);
|
|
return y + lineHeight;
|
|
}
|
|
|
|
for (const article of TeknavData.articles.filter(isPublishedArticle)) {
|
|
const canvas = createCanvas(1200, 630);
|
|
const ctx = canvas.getContext('2d');
|
|
const gradient = ctx.createLinearGradient(0, 0, 1200, 630);
|
|
gradient.addColorStop(0, '#0F6B73');
|
|
gradient.addColorStop(0.55, '#20343A');
|
|
gradient.addColorStop(1, '#D49A2A');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, 1200, 630);
|
|
|
|
ctx.fillStyle = 'rgba(250,247,240,0.10)';
|
|
for (let i = 0; i < 12; i += 1) {
|
|
ctx.fillRect(80 + i * 88, 80 + (i % 3) * 34, 48, 300);
|
|
}
|
|
|
|
ctx.direction = 'rtl';
|
|
ctx.textAlign = 'right';
|
|
ctx.fillStyle = '#FAF7F0';
|
|
ctx.font = 'bold 64px Vazirmatn, sans-serif';
|
|
wrap(ctx, article.title, 1080, 190, 900, 82, 3);
|
|
|
|
ctx.font = '30px Vazirmatn, sans-serif';
|
|
ctx.fillStyle = '#F4EFE6';
|
|
wrap(ctx, article.summary, 1080, 445, 860, 44, 2);
|
|
|
|
ctx.font = 'bold 28px Vazirmatn, sans-serif';
|
|
ctx.fillStyle = '#FFDC83';
|
|
ctx.fillText('teknav.ir', 1080, 565);
|
|
|
|
writeFileSync(resolve(OUT_DIR, `${article.slug}.jpg`), canvas.toBuffer('image/jpeg'));
|
|
}
|
|
|
|
console.log(`[og] generated ${TeknavData.articles.length} article image candidates in public/images/og`);
|