import io
import os
import urllib.parse
from flask import Flask, request, send_file, render_template_string
from PIL import Image, ImageFont, ImageDraw, ImageFilter
from pilmoji import Pilmoji
app = Flask(__name__)
# ==========================================
# UI HTML (Mobile Friendly)
# ==========================================
HTML_TEMPLATE = """
Brat Generator
Brat Generator ⬜
"""
# ==========================================
# Fungsi Pencari Font Fleksibel
# ==========================================
def get_font(size):
# Mencari Arial atau font mirip Arial di berbagai OS
font_paths = [
"/usr/share/fonts/truetype/msttcorefonts/Arial.ttf", # Ubuntu/Linux Arial
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", # Linux Fallback
"C:\\Windows\\Fonts\\arial.ttf", # Windows
"/System/Library/Fonts/Supplemental/Arial.ttf", # Mac OS
]
for path in font_paths:
if os.path.exists(path):
try:
return ImageFont.truetype(path, size)
except:
continue
return ImageFont.load_default()
# ==========================================
# Algoritma Auto-Scaling yang Diperbaiki (Struktur Tetap Terjaga)
# ==========================================
def get_optimal_font_and_text(text, max_width, max_height, line_spacing=10):
# BATASAN UKURAN FONT:
# Dimulai dari 75px (tidak 180px) agar kalimat seperti "Ini adalah"
# tidak terpecah menjadi 2 baris karena font terlalu besar.
max_font_size = 75
min_font_size = 15
# Dummy image untuk menghitung text bounding box
temp_img = Image.new('RGB', (1, 1))
draw = ImageDraw.Draw(temp_img)
for size in range(max_font_size, min_font_size, -1):
font = get_font(size)
lines = []
# Menghormati tombol "Enter" yang diketik user
for paragraph in text.split('\n'):
words = paragraph.split(' ')
current_line = []
for word in words:
test_line = ' '.join(current_line + [word]) if current_line else word
if font.getlength(test_line) <= max_width:
current_line.append(word)
else:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
else:
lines.append(word)
current_line = []
if current_line:
lines.append(' '.join(current_line))
wrapped_text = '\n'.join(lines)
# Cek apakah blok teks (termasuk spasi antar baris) muat di layar
bbox = draw.multiline_textbbox((0, 0), wrapped_text, font=font, align="center", spacing=line_spacing)
text_w = bbox[2] - bbox[0]
text_h = bbox[3] - bbox[1]
if text_w <= max_width and text_h <= max_height:
return font, wrapped_text
return font, wrapped_text
# ==========================================
# Routes
# ==========================================
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/generate')
def generate():
text = request.args.get('text', 'Ini adalah\ncontoh\nbrat😭').strip()
if not text:
text = "Ini adalah\ncontoh\nbrat😭"
# Konfigurasi Canvas 1:1 (512x512)
img_size = 512
# Padding diperbesar (60px) agar area teks tidak terlalu menempel ke pinggir layar
padding = 60
line_spacing = 15 # Jarak antar baris teks
# Warna Canvas (Putih Murni) & Warna Teks (Abu-abu Gelap, mencegah kontras berlebih)
bg_color = (255, 255, 255)
text_color = (40, 40, 40)
img = Image.new('RGB', (img_size, img_size), color=bg_color)
max_text_width = img_size - (padding * 2)
max_text_height = img_size - (padding * 2)
# Dapatkan font size ideal yang menjaga struktur kalimat
font, wrapped_text = get_optimal_font_and_text(text, max_text_width, max_text_height, line_spacing)
# Rendering Teks dengan Pilmoji
with Pilmoji(img) as pilmoji:
pilmoji.text(
(img_size // 2, img_size // 2),
wrapped_text,
fill=text_color,
font=font,
anchor="mm", # Fix posisi benar-benar di tengah absolut
align="center", # Rata tengah untuk tiap baris
spacing=line_spacing, # Spasi antar baris
emoji_scale_factor=1.05 # Skala emoji sedikit dikecilkan agar seimbang
)
# =============== EFEK BLUR (LOW QUALITY VIBE) =================
# Radius 0.5 memberi efek 'soft' persis kompresi gambar WhatsApp/Meme
img = img.filter(ImageFilter.GaussianBlur(radius=0.5))
# ==============================================================
# Output memori
img_io = io.BytesIO()
img.save(img_io, 'PNG')
img_io.seek(0)
return send_file(img_io, mimetype='image/png')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860)