137 lines
6.3 KiB
Python
137 lines
6.3 KiB
Python
"""Встроенные шаблоны и генерация фонов."""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
from .config import TEMPLATES_DIR
|
|
|
|
GOLD = (198, 158, 45)
|
|
NAVY = (27, 42, 74)
|
|
MAROON = (122, 31, 43)
|
|
CREAM = (255, 253, 247)
|
|
|
|
CLASSIC_PAGE = {"width": 1754, "height": 1240} # A4 landscape @ ~150dpi
|
|
LETTER_PAGE = {"width": 1240, "height": 1754} # A4 portrait @ ~150dpi
|
|
|
|
# Меняйте при правке встроенных шаблонов — тогда они обновятся у пользователей.
|
|
BUILTIN_VERSION = "2"
|
|
|
|
|
|
def _diamond(d: ImageDraw.ImageDraw, cx: float, cy: float, r: float, fill) -> None:
|
|
d.polygon([(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)], fill=fill)
|
|
|
|
|
|
def _frame(img: Image.Image, outer: int = 34) -> ImageDraw.ImageDraw:
|
|
W, H = img.size
|
|
d = ImageDraw.Draw(img)
|
|
d.rectangle([outer, outer, W - outer, H - outer], outline=GOLD, width=8)
|
|
d.rectangle([outer + 24, outer + 24, W - outer - 24, H - outer - 24], outline=NAVY, width=2)
|
|
d.rectangle([outer + 34, outer + 34, W - outer - 34, H - outer - 34], outline=GOLD, width=1)
|
|
inset = outer + 24
|
|
for cx, cy in [(inset, inset), (W - inset, inset), (inset, H - inset), (W - inset, H - inset)]:
|
|
_diamond(d, cx, cy, 18, GOLD)
|
|
_diamond(d, cx, cy, 8, NAVY)
|
|
_diamond(d, cx, cy, 3, CREAM)
|
|
return d
|
|
|
|
|
|
def draw_classic_bg(path: Path) -> None:
|
|
W, H = CLASSIC_PAGE["width"], CLASSIC_PAGE["height"]
|
|
img = Image.new("RGB", (W, H), CREAM)
|
|
d = _frame(img)
|
|
# разделитель под заголовком
|
|
d.line([(W * 0.30, H * 0.245), (W * 0.70, H * 0.245)], fill=GOLD, width=3)
|
|
_diamond(d, W * 0.5, H * 0.245, 9, GOLD)
|
|
# линия для подписи
|
|
d.line([(W * 0.42, H * 0.925), (W * 0.58, H * 0.925)], fill=NAVY, width=2)
|
|
img.save(path, "PNG")
|
|
|
|
|
|
def draw_letter_bg(path: Path) -> None:
|
|
W, H = LETTER_PAGE["width"], LETTER_PAGE["height"]
|
|
img = Image.new("RGB", (W, H), CREAM)
|
|
d = _frame(img)
|
|
d.line([(W * 0.26, H * 0.205), (W * 0.74, H * 0.205)], fill=GOLD, width=3)
|
|
_diamond(d, W * 0.5, H * 0.205, 9, GOLD)
|
|
d.line([(W * 0.42, H * 0.925), (W * 0.58, H * 0.925)], fill=NAVY, width=2)
|
|
img.save(path, "PNG")
|
|
|
|
|
|
def ensure_assets() -> None:
|
|
"""Создать фоны встроенных шаблонов, если их ещё нет."""
|
|
builtin_dir = TEMPLATES_DIR / "builtin"
|
|
builtin_dir.mkdir(parents=True, exist_ok=True)
|
|
classic = builtin_dir / "classic.png"
|
|
letter = builtin_dir / "letter.png"
|
|
if not classic.exists():
|
|
draw_classic_bg(classic)
|
|
if not letter.exists():
|
|
draw_letter_bg(letter)
|
|
|
|
|
|
def builtin_templates() -> list[dict]:
|
|
return [
|
|
{
|
|
"slug": "classic",
|
|
"title": "СЕРТИФИКАТ",
|
|
"kind": "image",
|
|
"background": "builtin/classic.png",
|
|
"page": CLASSIC_PAGE,
|
|
"defaults": {
|
|
"title": "СЕРТИФИКАТ",
|
|
"body": "за успешное прохождение программы",
|
|
},
|
|
"layout": [
|
|
{"key": "title", "x": 0.5, "y": 0.175, "size": 96, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center", "uppercase": True,
|
|
"letter_spacing": 10, "max_width": 0.9},
|
|
{"text": "настоящий сертификат подтверждает, что", "x": 0.5, "y": 0.315,
|
|
"size": 34, "font": "serif-italic", "color": "#5a5a5a", "align": "center"},
|
|
{"key": "fio", "x": 0.5, "y": 0.435, "size": 96, "font": "serif-bold",
|
|
"color": "#7A1F2B", "align": "center", "max_width": 0.82},
|
|
{"key": "body", "x": 0.5, "y": 0.645, "size": 40, "font": "serif",
|
|
"color": "#333333", "align": "center", "max_width": 0.72, "line_spacing": 1.45},
|
|
{"text": "Дата выдачи", "x": 0.26, "y": 0.795, "size": 22, "font": "sans",
|
|
"color": "#8a8a8a", "align": "center"},
|
|
{"key": "date", "x": 0.26, "y": 0.845, "size": 32, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center"},
|
|
{"text": "Номер документа", "x": 0.74, "y": 0.795, "size": 22, "font": "sans",
|
|
"color": "#8a8a8a", "align": "center"},
|
|
{"key": "number", "x": 0.74, "y": 0.845, "size": 32, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center"},
|
|
],
|
|
},
|
|
{
|
|
"slug": "letter",
|
|
"title": "БЛАГОДАРСТВЕННОЕ ПИСЬМО",
|
|
"kind": "image",
|
|
"background": "builtin/letter.png",
|
|
"page": LETTER_PAGE,
|
|
"defaults": {
|
|
"title": "БЛАГОДАРСТВЕННОЕ ПИСЬМО",
|
|
"body": "за вклад в развитие и активное участие",
|
|
},
|
|
"layout": [
|
|
{"key": "title", "x": 0.5, "y": 0.15, "size": 62, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center", "uppercase": True,
|
|
"letter_spacing": 6, "max_width": 0.86},
|
|
{"text": "объявляется благодарность", "x": 0.5, "y": 0.255, "size": 32,
|
|
"font": "serif-italic", "color": "#5a5a5a", "align": "center"},
|
|
{"key": "fio", "x": 0.5, "y": 0.36, "size": 70, "font": "serif-bold",
|
|
"color": "#7A1F2B", "align": "center", "max_width": 0.84},
|
|
{"key": "body", "x": 0.5, "y": 0.50, "size": 34, "font": "serif",
|
|
"color": "#333333", "align": "center", "max_width": 0.74, "line_spacing": 1.5},
|
|
{"text": "Дата", "x": 0.27, "y": 0.80, "size": 22, "font": "sans",
|
|
"color": "#8a8a8a", "align": "center"},
|
|
{"key": "date", "x": 0.27, "y": 0.85, "size": 30, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center"},
|
|
{"text": "Номер", "x": 0.73, "y": 0.80, "size": 22, "font": "sans",
|
|
"color": "#8a8a8a", "align": "center"},
|
|
{"key": "number", "x": 0.73, "y": 0.85, "size": 30, "font": "serif-bold",
|
|
"color": "#1B2A4A", "align": "center"},
|
|
],
|
|
},
|
|
]
|