Improve text rendering on images by removing emojis and handling long words

Add emoji removal from text before rendering on images. Implement logic to break long words that exceed the maximum text width.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: d948b4ab-8a36-4043-aed1-fc4629c72817
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/N0tTJcl
This commit is contained in:
michaelschal committed 2025-11-18 13:37:44 +00:00
1 parent 4a2ff72072
commit 5053cca73e
2 files changed
+66 -2

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

+66 -2
View File
@@ -108,6 +108,21 @@ export class ImageProcessor {
}
}
/**
* Removes emojis from text since canvas doesn't support them natively
*/
private removeEmojis(text: string): string {
// Remove emojis and other unicode symbols that canvas can't render
// Using a regex that removes emoji-like characters
return text
.replace(/[\u2600-\u26FF]/g, '') // Misc symbols
.replace(/[\u2700-\u27BF]/g, '') // Dingbats
.replace(/[\uD800-\uDFFF]/g, '') // Surrogate pairs (emoji range)
.replace(/[\uFE00-\uFE0F]/g, '') // Variation selectors
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
}
async addTextToStoryImage(imageUrl: string, text: string): Promise<Buffer> {
const STORY_WIDTH = 1080;
const STORY_HEIGHT = 1920;
@@ -117,6 +132,9 @@ export class ImageProcessor {
const PADDING = 40;
const LINE_HEIGHT_MULTIPLIER = 1.3;
// Remove emojis from text as canvas doesn't support them
const cleanText = this.removeEmojis(text);
const canvas = createCanvas(STORY_WIDTH, STORY_HEIGHT);
const ctx = canvas.getContext('2d');
@@ -161,7 +179,7 @@ export class ImageProcessor {
while (fontSize >= MIN_FONT_SIZE) {
ctx.font = `bold ${fontSize}px "DejaVu Sans", Arial, sans-serif`;
lines = this.wrapText(ctx, text, maxTextWidth);
lines = this.wrapText(ctx, cleanText, maxTextWidth);
const lineHeight = fontSize * LINE_HEIGHT_MULTIPLIER;
totalTextHeight = lines.length * lineHeight;
@@ -175,7 +193,7 @@ export class ImageProcessor {
if (fontSize < MIN_FONT_SIZE) {
fontSize = MIN_FONT_SIZE;
ctx.font = `bold ${fontSize}px "DejaVu Sans", Arial, sans-serif`;
lines = this.wrapText(ctx, text, maxTextWidth);
lines = this.wrapText(ctx, cleanText, maxTextWidth);
const lineHeight = fontSize * LINE_HEIGHT_MULTIPLIER;
totalTextHeight = lines.length * lineHeight;
@@ -214,8 +232,27 @@ export class ImageProcessor {
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine) {
// Current line would exceed width, push it and start new line
lines.push(currentLine);
currentLine = word;
// Check if the word itself is too long for a single line
const wordMetrics = ctx.measureText(word);
if (wordMetrics.width > maxWidth) {
// Word is too long, need to break it into chunks
const chunks = this.breakLongWord(ctx, word, maxWidth);
for (let i = 0; i < chunks.length - 1; i++) {
lines.push(chunks[i]);
}
currentLine = chunks[chunks.length - 1];
}
} else if (metrics.width > maxWidth && !currentLine) {
// First word is too long, need to break it
const chunks = this.breakLongWord(ctx, word, maxWidth);
for (let i = 0; i < chunks.length - 1; i++) {
lines.push(chunks[i]);
}
currentLine = chunks[chunks.length - 1];
} else {
currentLine = testLine;
}
@@ -227,6 +264,33 @@ export class ImageProcessor {
return lines;
}
/**
* Breaks a long word into chunks that fit within maxWidth
*/
private breakLongWord(ctx: any, word: string, maxWidth: number): string[] {
const chunks: string[] = [];
let currentChunk = '';
for (let i = 0; i < word.length; i++) {
const char = word[i];
const testChunk = currentChunk + char;
const metrics = ctx.measureText(testChunk);
if (metrics.width > maxWidth && currentChunk) {
chunks.push(currentChunk);
currentChunk = char;
} else {
currentChunk = testChunk;
}
}
if (currentChunk) {
chunks.push(currentChunk);
}
return chunks.length > 0 ? chunks : [word];
}
}
export const imageProcessor = new ImageProcessor();