Files
schedule/tools/make_icons.py
Vladislav Kopnin 3247ebcd64 Фронт: без бейджа «Обе недели», недели на страницах преподов/аудиторий, иконка приложения
- пары с одинаковыми ч/з показываются одним списком без пометки недели
  (группы, преподаватели, аудитории); раньше на страницах преподавателей
  и аудиторий такое занятие дублировалось в обеих колонках
- бейдж «Обе недели» убран как информационный шум (отсутствие пометки
  и означает «любая неделя»)
- favicon: квадрат с градиентом боковой панели (#1b2dd8 → #167cf2) и совой
- PWA: manifest.json, sw.js, apple-touch-icon и иконки 192/512 —
  при добавлении на главный экран смартфона появляется логотип
- tools/make_icons.py — генерация иконок из логотипа (rsvg-convert)
- удалён неиспользуемый roomPeriodCell и мёртвый favicon.png
2026-09-21 21:56:34 +03:00

122 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Генерация иконок приложения (фавиконка + PWA) из логотипа факультета.
Квадрат с градиентом боковой панели сайта (#1b2dd8 → #167cf2), внутрь
помещается белая сова. Результат:
img/app-icon.svg — самодостаточный svg (favicon)
img/icon-192.png — иконка манифеста
img/icon-512.png — иконка манифеста (maskable)
img/apple-touch-icon.png — 180x180 для iOS
img/favicon-32.png — растровая запасная фавиконка
Запуск (нужен rsvg-convert из librsvg2-bin):
python3 tools/make_icons.py
"""
from __future__ import annotations
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
IMG = ROOT / "src" / "frontend" / "static" / "img"
SVG_NS = "http://www.w3.org/2000/svg"
SODIPODI_NS = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
INKSCAPE_NS = "http://www.inkscape.org/namespaces/inkscape"
SIZE = 512
GRADIENT = (("#1b2dd8", "0%"), ("#167cf2", "100%")) # как .side в светлой теме
OWL_SCALE = 0.62 # доля стороны иконки под логотип
def strip_ns(el: ET.Element) -> None:
"""Убирает inkscape/sodipodi-атрибуты (в чужом документе они не нужны)."""
for key in list(el.attrib):
if key.startswith(f"{{{SODIPODI_NS}}}") or key.startswith(f"{{{INKSCAPE_NS}}}"):
del el.attrib[key]
for child in el:
strip_ns(child)
def build_icon_svg() -> str:
owl_root = ET.parse(IMG / "owl-white.svg").getroot()
view_box = owl_root.get("viewBox", "0 0 109.34 109.87")
_, _, ow, oh = (float(v) for v in view_box.replace(",", " ").split())
# размеры и положение логотипа внутри квадрата
target = SIZE * OWL_SCALE
scale = target / max(ow, oh)
tx = (SIZE - ow * scale) / 2
ty = (SIZE - oh * scale) / 2
ET.register_namespace("", SVG_NS)
icon = ET.Element(
f"{{{SVG_NS}}}svg",
{
"viewBox": f"0 0 {SIZE} {SIZE}",
"width": str(SIZE),
"height": str(SIZE),
},
)
defs = ET.SubElement(icon, f"{{{SVG_NS}}}defs")
grad = ET.SubElement(
defs,
f"{{{SVG_NS}}}linearGradient",
{"id": "bg", "x1": "0", "y1": "0", "x2": "0", "y2": "1"},
)
for color, offset in GRADIENT:
ET.SubElement(
grad, f"{{{SVG_NS}}}stop", {"offset": offset, "stop-color": color}
)
ET.SubElement(
icon, f"{{{SVG_NS}}}rect", {"width": str(SIZE), "height": str(SIZE), "fill": "url(#bg)"}
)
group = ET.SubElement(
icon,
f"{{{SVG_NS}}}g",
{"transform": f"translate({tx:.2f} {ty:.2f}) scale({scale:.6f})"},
)
for child in owl_root:
tag = child.tag
if tag.endswith("namedview") or tag.endswith("metadata"):
continue
clone = ET.fromstring(ET.tostring(child))
strip_ns(clone)
group.append(clone)
xml = ET.tostring(icon, encoding="unicode")
return '<?xml version="1.0" encoding="UTF-8"?>\n' + xml + "\n"
def rasterize(svg_path: Path, size: int, out_path: Path) -> None:
subprocess.run(
["rsvg-convert", "-w", str(size), "-h", str(size), "-o", str(out_path), str(svg_path)],
check=True,
)
def main() -> int:
svg = build_icon_svg()
icon = IMG / "app-icon.svg"
icon.write_text(svg, encoding="utf-8")
if not subprocess.run(["which", "rsvg-convert"], capture_output=True).stdout:
print("нет rsvg-convert — svg создан, png не сгенерированы", file=sys.stderr)
return 1
rasterize(icon, 192, IMG / "icon-192.png")
rasterize(icon, 512, IMG / "icon-512.png")
rasterize(icon, 180, IMG / "apple-touch-icon.png")
rasterize(icon, 32, IMG / "favicon-32.png")
for name in ("app-icon.svg", "icon-192.png", "icon-512.png", "apple-touch-icon.png", "favicon-32.png"):
print("готово:", (IMG / name).relative_to(ROOT))
return 0
if __name__ == "__main__":
raise SystemExit(main())