diff --git a/client/src/components/preview-modal.tsx b/client/src/components/preview-modal.tsx index 6ae0d7d..21e82ef 100644 --- a/client/src/components/preview-modal.tsx +++ b/client/src/components/preview-modal.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { ChevronLeft, ChevronRight, Send, Facebook, Instagram, Heart, MessageCircle, Share2, Bookmark } from 'lucide-react'; import type { Media } from '@shared/schema'; +import { removeEmojis } from '@shared/emoji'; interface PreviewModalProps { open: boolean; @@ -222,19 +223,6 @@ export function PreviewModal({ ); - /** - * Removes emojis from text since they don't display well in some contexts - */ - const removeEmojis = (text: string): string => { - 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(); - }; - const wrapText = (text: string, maxWidth: number, fontSize: number): string[] => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); diff --git a/package-lock.json b/package-lock.json index 45fd703..1a39110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "drizzle-orm": "^0.39.1", "drizzle-zod": "^0.7.0", "embla-carousel-react": "^8.6.0", + "emoji-regex": "^10.6.0", "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", @@ -5341,9 +5342,9 @@ } }, "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/encodeurl": { @@ -8293,6 +8294,12 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", diff --git a/package.json b/package.json index e688d05..c1437bd 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "drizzle-orm": "^0.39.1", "drizzle-zod": "^0.7.0", "embla-carousel-react": "^8.6.0", + "emoji-regex": "^10.6.0", "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", diff --git a/server/services/imageProcessor.ts b/server/services/imageProcessor.ts index 3e547ed..e63bcba 100644 --- a/server/services/imageProcessor.ts +++ b/server/services/imageProcessor.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import path from "path"; import fs from "fs/promises"; import { createCanvas, loadImage, registerFont } from "canvas"; +import { removeEmojis } from "@shared/emoji"; interface CropDimensions { width: number; @@ -108,21 +109,6 @@ 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 { const STORY_WIDTH = 1080; const STORY_HEIGHT = 1920; @@ -133,7 +119,7 @@ export class ImageProcessor { const LINE_HEIGHT_MULTIPLIER = 1.3; // Remove emojis from text as canvas doesn't support them - const cleanText = this.removeEmojis(text); + const cleanText = removeEmojis(text); const canvas = createCanvas(STORY_WIDTH, STORY_HEIGHT); const ctx = canvas.getContext('2d'); diff --git a/shared/emoji.ts b/shared/emoji.ts new file mode 100644 index 0000000..41e6231 --- /dev/null +++ b/shared/emoji.ts @@ -0,0 +1,16 @@ +import emojiRegex from 'emoji-regex'; + +// Cached regex instance for performance +const emojiPattern = emojiRegex(); + +/** + * Removes emojis from text while preserving other Unicode characters + * Uses the emoji-regex package which correctly identifies emojis without + * removing legitimate astral-plane characters (CJK Extension B, etc.) + */ +export function removeEmojis(text: string): string { + return text + .replace(emojiPattern, '') + .replace(/\s+/g, ' ') // Normalize whitespace + .trim(); +}