Merge pull request #107 from R0m1k3/dev

Dev
This commit is contained in:
LogiFlow authored and GitHub committed 2025-12-30 13:07:40 +01:00
commit c3660f811b
3 files changed
+47 -85

No files matched your search

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+32
View File
@@ -0,0 +1,32 @@
import { emojiRenderer } from "./services/emojiRenderer";
import { createCanvas } from "canvas";
import fs from "fs";
import path from "path";
async function run() {
const width = 500;
const height = 200;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = "black";
ctx.font = "30px Arial";
const text = "Hello 🌍 World! 😂";
console.log(`Rendering text: ${text}`);
try {
await emojiRenderer.drawText(text, ctx, 50, 100, 30);
const buffer = canvas.toBuffer("image/png");
fs.writeFileSync("emoji_test.png", buffer);
console.log("Image saved to emoji_test.png");
} catch (error) {
console.error("Error rendering emoji:", error);
}
}
run();
+15 -85
View File
@@ -1,10 +1,6 @@
import { parse } from 'twemoji-parser';
import { createCanvas, loadImage, CanvasRenderingContext2D } from 'canvas';
import path from 'path';
import fs from 'fs/promises';
import { createWriteStream } from 'fs';
import { Readable } from 'stream';
import { finished } from 'stream/promises';
import sharp from 'sharp';
interface EmojiSegment {
type: 'text' | 'emoji';
@@ -18,18 +14,12 @@ interface EmojiSegment {
*/
export class EmojiRenderer {
private emojiCache = new Map<string, Buffer>();
private emojiAssetsDir = path.join(process.cwd(), 'server', 'assets', 'emoji');
/**
* Parse text into segments of text and emojis
*/
private parseTextWithEmojis(text: string): EmojiSegment[] {
const emojis = parse(text);
if (emojis.length > 0) {
console.log(`📝 Texte avec ${emojis.length} emoji(s) détecté(s)`);
}
if (emojis.length === 0) {
return [{ type: 'text', content: text }];
}
@@ -68,90 +58,31 @@ export class EmojiRenderer {
}
/**
* Extract emoji filename from URL
* Example: https://twemoji.maxcdn.com/v/latest/72x72/1f600.png => 1f600.png
* Download and cache emoji image
*/
private getEmojiFilename(url: string): string | null {
const match = url.match(/\/([a-f0-9\-]+\.png)$/i);
return match ? match[1] : null;
}
/**
* Ensure emoji assets directory exists
*/
private async ensureEmojiDir(): Promise<void> {
try {
await fs.access(this.emojiAssetsDir);
} catch {
await fs.mkdir(this.emojiAssetsDir, { recursive: true });
async getEmojiImage(url: string): Promise<Buffer> {
if (this.emojiCache.has(url)) {
return this.emojiCache.get(url)!;
}
}
/**
* Download emoji from CDN and save locally
*/
private async downloadEmoji(filename: string): Promise<Buffer> {
const url = `https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/${filename}`;
const localPath = path.join(this.emojiAssetsDir, filename);
console.log(`🔍 Téléchargement emoji: ${filename}`);
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000); // 15 second timeout
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeout);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
throw new Error(`Failed to fetch emoji: ${response.status} ${response.statusText}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
// Save to disk for future use
await fs.writeFile(localPath, buffer);
console.log(`✅ Emoji sauvegardé: ${filename}`);
// Convert SVG to PNG using sharp because node-canvas struggles with SVGs lacking dimensions
const pngBuffer = await sharp(buffer).png().toBuffer();
return buffer;
this.emojiCache.set(url, pngBuffer);
return pngBuffer;
} catch (error) {
console.error(`❌ Erreur téléchargement ${filename}:`, error instanceof Error ? error.message : error);
console.error(`Error downloading emoji from ${url}:`, error);
throw error;
}
}
/**
* Get emoji image - from cache, disk, or download
*/
private async getEmojiImage(url: string): Promise<Buffer> {
const filename = this.getEmojiFilename(url);
if (!filename) {
throw new Error(`Invalid emoji URL: ${url}`);
}
// Check memory cache first
const cacheKey = filename;
if (this.emojiCache.has(cacheKey)) {
return this.emojiCache.get(cacheKey)!;
}
// Ensure directory exists
await this.ensureEmojiDir();
// Check if file exists locally
const localPath = path.join(this.emojiAssetsDir, filename);
try {
const buffer = await fs.readFile(localPath);
this.emojiCache.set(cacheKey, buffer);
return buffer;
} catch {
// File doesn't exist locally, download it
const buffer = await this.downloadEmoji(filename);
this.emojiCache.set(cacheKey, buffer);
return buffer;
}
}
/**
* Measure the width of text with emojis
*/
@@ -193,15 +124,14 @@ export class EmojiRenderer {
const emojiBuffer = await this.getEmojiImage(segment.url);
const emojiImage = await loadImage(emojiBuffer);
// Draw emoji centered vertically with text
// When textBaseline is 'middle', y represents the vertical center
// Draw emoji slightly above baseline to align with text
const emojiSize = fontSize;
const emojiY = y - (emojiSize / 2); // Center the emoji around y
const emojiY = y - fontSize * 0.8; // Adjust vertical position
ctx.drawImage(emojiImage, currentX, emojiY, emojiSize, emojiSize);
currentX += emojiSize;
} catch (error) {
console.error(`❌ Impossible de charger l'emoji "${segment.content}":`, error instanceof Error ? error.message : error);
console.error('Error loading emoji:', error);
// Fallback: draw text representation
ctx.fillText(segment.content, currentX, y);
currentX += ctx.measureText(segment.content).width;