First commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
"""Generate PNG: Email screenshot from client (Outlook-style)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
HAS_PIL = True
|
||||
except ImportError:
|
||||
HAS_PIL = False
|
||||
|
||||
|
||||
def _get_font(size, bold=False):
|
||||
"""Get a font with CJK support, falling back to default if unavailable."""
|
||||
# Prefer WenQuanYi Micro Hei for CJK support
|
||||
cjk_names = [
|
||||
"/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc",
|
||||
]
|
||||
latin_names = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
||||
if bold
|
||||
else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
|
||||
if bold
|
||||
else "/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
]
|
||||
# Try CJK font first (it includes Latin glyphs too)
|
||||
for name in cjk_names:
|
||||
try:
|
||||
return ImageFont.truetype(name, size, index=0)
|
||||
except (OSError, IOError):
|
||||
continue
|
||||
# Fall back to Latin fonts
|
||||
for name in latin_names:
|
||||
try:
|
||||
return ImageFont.truetype(name, size)
|
||||
except (OSError, IOError):
|
||||
continue
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def generate(
|
||||
output_path: Path,
|
||||
from_email: str,
|
||||
from_name: str,
|
||||
to_email: str,
|
||||
to_name: str,
|
||||
subject: str,
|
||||
body: str,
|
||||
date: str,
|
||||
):
|
||||
"""Generate an Outlook-style email screenshot as PNG."""
|
||||
if not HAS_PIL:
|
||||
# Fallback: write a text file
|
||||
output_path.with_suffix(".txt").write_text(
|
||||
f"From: {from_name} <{from_email}>\n"
|
||||
f"To: {to_name} <{to_email}>\n"
|
||||
f"Date: {date}\n"
|
||||
f"Subject: {subject}\n\n{body}"
|
||||
)
|
||||
return
|
||||
|
||||
# Image dimensions (simulate a screen capture)
|
||||
width, height = 800, 600
|
||||
bg_color = (255, 255, 255)
|
||||
header_bg = (242, 242, 242)
|
||||
accent_color = (0, 120, 212) # Outlook blue
|
||||
text_color = (51, 51, 51)
|
||||
label_color = (130, 130, 130)
|
||||
|
||||
img = Image.new("RGB", (width, height), bg_color)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
font_header = _get_font(11, bold=True)
|
||||
font_label = _get_font(10)
|
||||
font_body = _get_font(11)
|
||||
font_subject = _get_font(13, bold=True)
|
||||
|
||||
# Top bar (Outlook-style)
|
||||
draw.rectangle([0, 0, width, 45], fill=accent_color)
|
||||
draw.text((15, 12), "Mail - Outlook", fill=(255, 255, 255), font=font_header)
|
||||
|
||||
# Email header area
|
||||
y = 55
|
||||
draw.rectangle([0, 45, width, 200], fill=header_bg)
|
||||
|
||||
# Subject
|
||||
draw.text((20, y), subject, fill=text_color, font=font_subject)
|
||||
y += 30
|
||||
|
||||
# From
|
||||
draw.text((20, y), "From:", fill=label_color, font=font_label)
|
||||
draw.text((80, y), f"{from_name} <{from_email}>", fill=text_color, font=font_body)
|
||||
y += 22
|
||||
|
||||
# Sent
|
||||
draw.text((20, y), "Sent:", fill=label_color, font=font_label)
|
||||
draw.text((80, y), date, fill=text_color, font=font_body)
|
||||
y += 22
|
||||
|
||||
# To
|
||||
draw.text((20, y), "To:", fill=label_color, font=font_label)
|
||||
draw.text((80, y), f"{to_name} <{to_email}>", fill=text_color, font=font_body)
|
||||
y += 22
|
||||
|
||||
# Subject line in header
|
||||
draw.text((20, y), "Subject:", fill=label_color, font=font_label)
|
||||
draw.text((80, y), subject[:60], fill=text_color, font=font_body)
|
||||
y += 30
|
||||
|
||||
# Divider
|
||||
draw.line([(15, y), (width - 15, y)], fill=(220, 220, 220), width=1)
|
||||
y += 15
|
||||
|
||||
# Body text
|
||||
for para in body.split("\n\n"):
|
||||
for line in _wrap_text(para.strip(), 90):
|
||||
if y > height - 30:
|
||||
break
|
||||
draw.text((25, y), line, fill=text_color, font=font_body)
|
||||
y += 18
|
||||
y += 8
|
||||
|
||||
img.save(str(output_path), "PNG")
|
||||
|
||||
|
||||
def _wrap_text(text, max_chars):
|
||||
"""Simple word-wrap."""
|
||||
words = text.split()
|
||||
lines = []
|
||||
current = ""
|
||||
for word in words:
|
||||
if len(current) + len(word) + 1 > max_chars:
|
||||
lines.append(current)
|
||||
current = word
|
||||
else:
|
||||
current = f"{current} {word}" if current else word
|
||||
if current:
|
||||
lines.append(current)
|
||||
return lines or [""]
|
||||
Reference in New Issue
Block a user