Emoji utilities for favicon generation.
Provides emoji suggestions based on project descriptions and emoji rendering.
from typing import List, Dict, Optional, Tuple
from PIL import Image, ImageDraw, ImageFont
import emoji as emoji_lib
from pilmoji import Pilmoji
except (ImportError, AttributeError):
# Handle both import errors and compatibility issues with pilmoji/emoji
PILMOJI_AVAILABLE = False
# Curated emoji database with keywords for better matching
# Format: emoji -> (name, keywords, category)
# Technology & Development
"🚀": ("rocket", ["rocket", "launch", "startup", "space", "speed", "growth"], "tech"),
"💻": ("laptop", ["computer", "laptop", "code", "dev", "developer", "programming"], "tech"),
"⚡": ("lightning", ["fast", "speed", "energy", "power", "quick", "performance"], "tech"),
"🔧": ("wrench", ["tool", "tools", "build", "fix", "settings", "maintenance"], "tech"),
"🛠️": ("tools", ["tool", "tools", "build", "construction", "development"], "tech"),
"📱": ("phone", ["mobile", "app", "phone", "smartphone", "application"], "tech"),
"🌐": ("globe", ["web", "website", "global", "internet", "world", "online"], "tech"),
"💡": ("bulb", ["idea", "innovation", "creative", "light", "bright", "solution"], "tech"),
"🏪": ("store", ["shop", "store", "retail", "business", "commerce"], "business"),
"🛒": ("cart", ["shopping", "cart", "ecommerce", "buy", "purchase"], "business"),
"💼": ("briefcase", ["business", "work", "professional", "office", "corporate"], "business"),
"💰": ("money", ["money", "finance", "payment", "cash", "wealth"], "business"),
"💳": ("card", ["credit", "card", "payment", "transaction", "banking"], "business"),
"📊": ("chart", ["analytics", "chart", "data", "stats", "graph", "metrics"], "business"),
"📈": ("trending", ["growth", "trending", "increase", "success", "profit"], "business"),
"☕": ("coffee", ["coffee", "cafe", "beverage", "drink", "tea"], "food"),
"🍕": ("pizza", ["pizza", "food", "restaurant", "italian", "dining"], "food"),
"🍔": ("burger", ["burger", "food", "restaurant", "fast food", "dining"], "food"),
"🍰": ("cake", ["cake", "bakery", "dessert", "sweet", "pastry"], "food"),
"🍺": ("beer", ["beer", "bar", "pub", "drink", "alcohol", "brewery"], "food"),
"🍽️": ("dining", ["restaurant", "dining", "food", "meal", "eat"], "food"),
"🥗": ("salad", ["healthy", "salad", "food", "fresh", "organic", "vegetable"], "food"),
"💪": ("muscle", ["fitness", "gym", "health", "workout", "strong", "exercise"], "health"),
"❤️": ("heart", ["health", "love", "care", "wellness", "medical"], "health"),
"🏃": ("running", ["fitness", "run", "sport", "exercise", "active"], "health"),
"🧘": ("yoga", ["yoga", "meditation", "wellness", "mindfulness", "zen"], "health"),
"📚": ("books", ["education", "learning", "books", "study", "knowledge"], "education"),
"🎓": ("graduation", ["education", "school", "university", "learning", "graduate"], "education"),
"✏️": ("pencil", ["write", "edit", "draw", "create", "education"], "education"),
"🧠": ("brain", ["think", "smart", "intelligence", "learning", "knowledge"], "education"),
"🎨": ("art", ["art", "design", "creative", "paint", "color"], "creative"),
"📷": ("camera", ["photo", "photography", "picture", "image", "visual"], "creative"),
"🎬": ("movie", ["video", "film", "movie", "production", "cinema"], "creative"),
"🎵": ("music", ["music", "audio", "sound", "song", "melody"], "creative"),
"✨": ("sparkles", ["magic", "special", "shine", "star", "highlight"], "creative"),
"💬": ("chat", ["chat", "message", "talk", "communication", "conversation"], "social"),
"📧": ("email", ["email", "mail", "message", "contact", "communication"], "social"),
"📢": ("announcement", ["announce", "broadcast", "news", "alert", "notification"], "social"),
"🤝": ("handshake", ["partnership", "deal", "agreement", "collaboration"], "social"),
"✈️": ("airplane", ["travel", "flight", "airplane", "trip", "vacation"], "travel"),
"🏨": ("hotel", ["hotel", "accommodation", "stay", "lodging", "hospitality"], "travel"),
"🗺️": ("map", ["map", "navigation", "location", "direction", "travel"], "travel"),
"📍": ("pin", ["location", "place", "map", "pin", "address", "local"], "travel"),
"🌍": ("earth", ["global", "world", "international", "planet", "earth"], "travel"),
"🎮": ("game", ["game", "gaming", "play", "video game", "entertainment"], "entertainment"),
"🎯": ("target", ["goal", "target", "aim", "focus", "precision"], "entertainment"),
"🎪": ("circus", ["event", "entertainment", "show", "fun", "festival"], "entertainment"),
"🎉": ("party", ["celebrate", "party", "event", "celebration", "fun"], "entertainment"),
"🌱": ("plant", ["growth", "nature", "plant", "green", "eco", "organic"], "nature"),
"🌳": ("tree", ["nature", "tree", "environment", "green", "eco"], "nature"),
"🐶": ("dog", ["dog", "pet", "animal", "puppy"], "nature"),
"🐱": ("cat", ["cat", "pet", "animal", "kitten"], "nature"),
"⭐": ("star", ["star", "favorite", "best", "quality", "rating"], "generic"),
"🌟": ("glowing", ["star", "shine", "special", "featured", "highlight"], "generic"),
"✅": ("check", ["check", "done", "complete", "verified", "success"], "generic"),
"🔥": ("fire", ["hot", "trending", "popular", "fire", "exciting"], "generic"),
"👍": ("thumbs_up", ["like", "good", "approve", "yes", "positive"], "generic"),
"🏆": ("trophy", ["winner", "award", "achievement", "success", "champion"], "generic"),
def extract_keywords(description: str) -> List[str]:
Extract keywords from project description.
description: Project description text
List of lowercase keywords
text = description.lower()
# Remove punctuation and split
words = re.findall(r'\b\w+\b', text)
# Filter out common stop words
stop_words = {'a', 'an', 'the', 'is', 'are', 'was', 'were', 'for', 'to', 'in', 'on', 'at',
'with', 'from', 'by', 'about', 'my', 'our', 'your', 'this', 'that'}
keywords = [w for w in words if w not in stop_words and len(w) > 2]
def score_emoji(emoji_data: tuple, keywords: List[str]) -> int:
Score an emoji based on keyword matches.
emoji_data: Tuple of (name, keywords, category)
keywords: List of user keywords
Match score (higher is better)
name, emoji_keywords, category = emoji_data
if keyword in emoji_keywords:
elif any(keyword in ek or ek in keyword for ek in emoji_keywords):
if keyword in name or name in keyword:
def suggest_emojis(description: str, count: int = 4) -> List[Dict[str, str]]:
Suggest relevant emojis based on project description.
description: Description of the project/website/app
count: Number of emoji suggestions to return (default: 4)
List of dicts with 'emoji', 'name', and 'description' keys
# Return generic popular emojis if no description
{"emoji": "🚀", "name": "Rocket", "description": "Launch, startup, fast growth"},
{"emoji": "⭐", "name": "Star", "description": "Featured, favorite, quality"},
{"emoji": "💡", "name": "Light Bulb", "description": "Ideas, innovation, solutions"},
{"emoji": "🌟", "name": "Glowing Star", "description": "Special, highlighted, shine"},
# Extract keywords from description
keywords = extract_keywords(description)
for emoji_char, emoji_data in EMOJI_DATABASE.items():
score = score_emoji(emoji_data, keywords)
if score > 0: # Only include emojis with matches
name, emoji_keywords, category = emoji_data
"name": name.replace("_", " ").title(),
"description": ", ".join(emoji_keywords[:3]),
# Sort by score (highest first)
scored_emojis.sort(key=lambda x: x['score'], reverse=True)
# Ensure diversity: try to get emojis from different categories
# First pass: select top scoring emoji from each category
for emoji in scored_emojis:
if len(selected) >= count:
if emoji['category'] not in used_categories:
used_categories.add(emoji['category'])
# Second pass: fill remaining slots with highest scoring regardless of category
for emoji in scored_emojis:
if len(selected) >= count:
if emoji not in selected:
# If still not enough, add generic fallbacks
{"emoji": "⭐", "name": "Star", "description": "Featured, favorite, quality"},
{"emoji": "🌟", "name": "Glowing Star", "description": "Special, highlighted"},
{"emoji": "✨", "name": "Sparkles", "description": "Magic, special"},
{"emoji": "💫", "name": "Dizzy", "description": "Exciting, dynamic"},
for fallback in fallbacks:
if len(selected) >= count:
if fallback not in selected:
selected.append(fallback)
# Remove score from output and return only the requested count
for emoji in selected[:count]:
"description": emoji["description"]
def get_emoji_name(emoji_char: str) -> str:
Get the name of an emoji character.
emoji_char: Emoji character (e.g., "🚀")
Emoji name or empty string if not found
if emoji_char in EMOJI_DATABASE:
return EMOJI_DATABASE[emoji_char][0].replace("_", " ").title()
# Fallback to emoji library if available
return emoji_lib.demojize(emoji_char).strip(':').replace('_', ' ').title()
def generate_emoji_icon(emoji_char: str, size: Tuple[int, int], bg_color: Optional[str] = None) -> Image.Image:
Generate an icon image from an emoji character.
emoji_char: Unicode emoji character (e.g., "🚀")
size: Tuple of (width, height) in pixels
bg_color: Optional background color (hex or name). If None, transparent background.
PIL Image object with the emoji rendered
ImportError: If pilmoji library is not installed
ValueError: If emoji_char is empty or invalid
if not PILMOJI_AVAILABLE:
"pilmoji library is required for emoji rendering. "
"Install it with: pip install pilmoji"
raise ValueError("emoji_char cannot be empty")
# Create image with background
img = Image.new('RGB', size, bg_color)
img = Image.new('RGBA', size, (0, 0, 0, 0))
# Calculate emoji font size
# Use 75% of the minimum dimension to leave padding
emoji_size = int(min(size) * 0.75)
# Use Pilmoji to render the emoji
with Pilmoji(img) as pilmoji:
# Get text bounding box to center properly
# Pilmoji doesn't provide easy centering, so we estimate
# Emojis are roughly square, so we can center based on size
# Calculate position (centered)
x = (size[0] - emoji_size) // 2
y = (size[1] - emoji_size) // 2
# Note: Pilmoji uses font_size parameter for emoji size
pilmoji.text((x, y), emoji_char, font=None, fill=(0, 0, 0, 0),
emoji_scale_factor=emoji_size/64) # Pilmoji default emoji size is 64
def generate_emoji_icon_fallback(emoji_char: str, size: Tuple[int, int], bg_color: Optional[str] = None) -> Image.Image:
Fallback method to generate emoji icon using system fonts.
Used when Pilmoji is not available.
emoji_char: Unicode emoji character
size: Tuple of (width, height)
bg_color: Optional background color
PIL Image with emoji (may not render properly on all systems)
img = Image.new('RGB', size, bg_color)
img = Image.new('RGBA', size, (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Try to use a color emoji font
emoji_size = int(min(size) * 0.75)
# Try common emoji font paths
"/System/Library/Fonts/Apple Color Emoji.ttc", # macOS
"/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", # Linux
"C:\\Windows\\Fonts\\seguiemj.ttf", # Windows
for font_path in font_paths:
font = ImageFont.truetype(font_path, emoji_size)
# Fallback to default font with text representation
font = ImageFont.load_default()
text = f"Emoji: {emoji_char}"
# Get text bounding box for centering
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (size[0] - text_width) // 2
y = (size[1] - text_height) // 2
draw.text((x, y), text, font=font, embedded_color=True, fill=(0, 0, 0))
if __name__ == '__main__':
# Test the suggestion system
"pizza delivery service",
"online education platform",
print("Emoji Suggestion Tests")
for description in test_cases:
print(f"\nDescription: '{description}'")
suggestions = suggest_emojis(description, count=4)
for i, emoji_data in enumerate(suggestions, 1):
print(f"{i}. {emoji_data['emoji']} {emoji_data['name']:<20} - {emoji_data['description']}")