mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
3 files changed
+47
-85
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -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();
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { parse } from 'twemoji-parser';
|
import { parse } from 'twemoji-parser';
|
||||||
import { createCanvas, loadImage, CanvasRenderingContext2D } from 'canvas';
|
import { createCanvas, loadImage, CanvasRenderingContext2D } from 'canvas';
|
||||||
import path from 'path';
|
import sharp from 'sharp';
|
||||||
import fs from 'fs/promises';
|
|
||||||
import { createWriteStream } from 'fs';
|
|
||||||
import { Readable } from 'stream';
|
|
||||||
import { finished } from 'stream/promises';
|
|
||||||
|
|
||||||
interface EmojiSegment {
|
interface EmojiSegment {
|
||||||
type: 'text' | 'emoji';
|
type: 'text' | 'emoji';
|
||||||
@@ -18,18 +14,12 @@ interface EmojiSegment {
|
|||||||
*/
|
*/
|
||||||
export class EmojiRenderer {
|
export class EmojiRenderer {
|
||||||
private emojiCache = new Map<string, Buffer>();
|
private emojiCache = new Map<string, Buffer>();
|
||||||
private emojiAssetsDir = path.join(process.cwd(), 'server', 'assets', 'emoji');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse text into segments of text and emojis
|
* Parse text into segments of text and emojis
|
||||||
*/
|
*/
|
||||||
private parseTextWithEmojis(text: string): EmojiSegment[] {
|
private parseTextWithEmojis(text: string): EmojiSegment[] {
|
||||||
const emojis = parse(text);
|
const emojis = parse(text);
|
||||||
|
|
||||||
if (emojis.length > 0) {
|
|
||||||
console.log(`📝 Texte avec ${emojis.length} emoji(s) détecté(s)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emojis.length === 0) {
|
if (emojis.length === 0) {
|
||||||
return [{ type: 'text', content: text }];
|
return [{ type: 'text', content: text }];
|
||||||
}
|
}
|
||||||
@@ -68,90 +58,31 @@ export class EmojiRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract emoji filename from URL
|
* Download and cache emoji image
|
||||||
* Example: https://twemoji.maxcdn.com/v/latest/72x72/1f600.png => 1f600.png
|
|
||||||
*/
|
*/
|
||||||
private getEmojiFilename(url: string): string | null {
|
async getEmojiImage(url: string): Promise<Buffer> {
|
||||||
const match = url.match(/\/([a-f0-9\-]+\.png)$/i);
|
if (this.emojiCache.has(url)) {
|
||||||
return match ? match[1] : null;
|
return this.emojiCache.get(url)!;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure emoji assets directory exists
|
|
||||||
*/
|
|
||||||
private async ensureEmojiDir(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await fs.access(this.emojiAssetsDir);
|
|
||||||
} catch {
|
|
||||||
await fs.mkdir(this.emojiAssetsDir, { recursive: true });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
try {
|
||||||
const controller = new AbortController();
|
const response = await fetch(url);
|
||||||
const timeout = setTimeout(() => controller.abort(), 15000); // 15 second timeout
|
|
||||||
|
|
||||||
const response = await fetch(url, { signal: controller.signal });
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
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());
|
const buffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
|
||||||
// Save to disk for future use
|
// Convert SVG to PNG using sharp because node-canvas struggles with SVGs lacking dimensions
|
||||||
await fs.writeFile(localPath, buffer);
|
const pngBuffer = await sharp(buffer).png().toBuffer();
|
||||||
console.log(`✅ Emoji sauvegardé: ${filename}`);
|
|
||||||
|
|
||||||
return buffer;
|
this.emojiCache.set(url, pngBuffer);
|
||||||
|
return pngBuffer;
|
||||||
} catch (error) {
|
} 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;
|
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
|
* Measure the width of text with emojis
|
||||||
*/
|
*/
|
||||||
@@ -193,15 +124,14 @@ export class EmojiRenderer {
|
|||||||
const emojiBuffer = await this.getEmojiImage(segment.url);
|
const emojiBuffer = await this.getEmojiImage(segment.url);
|
||||||
const emojiImage = await loadImage(emojiBuffer);
|
const emojiImage = await loadImage(emojiBuffer);
|
||||||
|
|
||||||
// Draw emoji centered vertically with text
|
// Draw emoji slightly above baseline to align with text
|
||||||
// When textBaseline is 'middle', y represents the vertical center
|
|
||||||
const emojiSize = fontSize;
|
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);
|
ctx.drawImage(emojiImage, currentX, emojiY, emojiSize, emojiSize);
|
||||||
currentX += emojiSize;
|
currentX += emojiSize;
|
||||||
} catch (error) {
|
} 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
|
// Fallback: draw text representation
|
||||||
ctx.fillText(segment.content, currentX, y);
|
ctx.fillText(segment.content, currentX, y);
|
||||||
currentX += ctx.measureText(segment.content).width;
|
currentX += ctx.measureText(segment.content).width;
|
||||||
|
|||||||
Reference in new issue
Block a user