diff --git a/driver-pwa/frontend/public/icons/apple-touch-icon.png b/driver-pwa/frontend/public/icons/apple-touch-icon.png index 5ba5e2c..aae4b2c 100644 Binary files a/driver-pwa/frontend/public/icons/apple-touch-icon.png and b/driver-pwa/frontend/public/icons/apple-touch-icon.png differ diff --git a/driver-pwa/frontend/public/icons/icon-192.png b/driver-pwa/frontend/public/icons/icon-192.png index 8dd1960..1916635 100644 Binary files a/driver-pwa/frontend/public/icons/icon-192.png and b/driver-pwa/frontend/public/icons/icon-192.png differ diff --git a/driver-pwa/frontend/public/icons/icon-512.png b/driver-pwa/frontend/public/icons/icon-512.png index b6b9efc..817b286 100644 Binary files a/driver-pwa/frontend/public/icons/icon-512.png and b/driver-pwa/frontend/public/icons/icon-512.png differ diff --git a/driver-pwa/frontend/public/icons/icon-maskable-512.png b/driver-pwa/frontend/public/icons/icon-maskable-512.png index e8d50a2..f53d53e 100644 Binary files a/driver-pwa/frontend/public/icons/icon-maskable-512.png and b/driver-pwa/frontend/public/icons/icon-maskable-512.png differ diff --git a/driver-pwa/frontend/scripts/gen-icons.py b/driver-pwa/frontend/scripts/gen-icons.py index e03e9f2..6b3ba6a 100644 --- a/driver-pwa/frontend/scripts/gen-icons.py +++ b/driver-pwa/frontend/scripts/gen-icons.py @@ -1,92 +1,63 @@ -"""Иконки приложения из фирменного знака «Премиум Парк». +"""Иконки приложения — тот же значок, что у сайта pptaxi.ru. -Знак — вектор (assets/logos/premium_park_favicon_pack/favicon.svg, поле 64×64, -чёрные штрихи + янтарная дуга). Растеризуем его сами: путей всего три, команды — -только M/L/H/V/C/Z, а тянуть в проект зависимость ради этого не хочется. +Сайт отдаёт `favicon.svg`: красная скруглённая плитка --s-accent и белая «П». +Повторяем её здесь, чтобы значок в браузере, на экране телефона и на сайте был +один и тот же. -Сглаживание — сверхдискретизацией ×4: рисуем крупнее и уменьшаем. Так края -получаются чище, чем у любой заливки «в лоб». +Рисуем сами (Pillow), потому что тянуть растеризатор SVG ради одной плитки с +буквой незачем. Сглаживание — сверхдискретизацией ×4. Запуск (нужен Pillow): python scripts/gen-icons.py """ from pathlib import Path -import re -from PIL import Image, ImageDraw +from PIL import Image, ImageDraw, ImageFont -SVG = Path(r"C:\VibeCoding\assets\logos\premium_park_favicon_pack\favicon.svg") OUT = Path(__file__).resolve().parent.parent / "public" / "icons" -BG = (255, 255, 255, 255) # --s-paper сайта: знак нарисован для светлой подложки -SS = 4 # кратность сверхдискретизации -# Полезная часть знака внутри поля 64×64 (замерено по путям). -MARK = (11.0, 8.0, 59.4, 43.7) +ACCENT = (220, 38, 38, 255) # --s-accent сайта +PAPER = (255, 255, 255, 255) # --s-paper +SS = 4 + +# Шрифт ищем среди системных: у сайта заголовки набраны Manrope, но ради одной +# буквы тащить файл шрифта в репозиторий не стоит — берём ближайший гротеск. +FONT_CANDIDATES = [ + r"C:\Windows\Fonts\segoeuib.ttf", + r"C:\Windows\Fonts\arialbd.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", +] -def _curve(p0, p1, p2, p3, steps=24): - for i in range(1, steps + 1): - t = i / steps - u = 1 - t - yield (u*u*u*p0[0] + 3*u*u*t*p1[0] + 3*u*t*t*p2[0] + t*t*t*p3[0], - u*u*u*p0[1] + 3*u*u*t*p1[1] + 3*u*t*t*p2[1] + t*t*t*p3[1]) +def _font(size: int) -> ImageFont.FreeTypeFont: + for path in FONT_CANDIDATES: + if Path(path).exists(): + return ImageFont.truetype(path, size) + raise SystemExit("не нашёл жирный шрифт для буквы") -def flatten(d: str) -> list[tuple[float, float]]: - """Путь SVG → многоугольник. Поддержаны M, L, H, V, C, Z — больше в знаке нет.""" - tokens = re.findall(r"[MLHVCZmlhvcz]|-?\d*\.?\d+", d) - pts: list[tuple[float, float]] = [] - cur = (0.0, 0.0) - cmd = "" - i = 0 - while i < len(tokens): - t = tokens[i] - if t.isalpha(): - cmd = t - i += 1 - continue - n = lambda k: float(tokens[i + k]) - if cmd in "ML": - cur = (n(0), n(1)); pts.append(cur); i += 2 - elif cmd == "H": - cur = (n(0), cur[1]); pts.append(cur); i += 1 - elif cmd == "V": - cur = (cur[0], n(0)); pts.append(cur); i += 1 - elif cmd == "C": - p1, p2, p3 = (n(0), n(1)), (n(2), n(3)), (n(4), n(5)) - pts.extend(_curve(cur, p1, p2, p3)); cur = p3; i += 6 - else: - i += 1 - return pts - - -def paths() -> list[tuple[list[tuple[float, float]], str]]: - svg = SVG.read_text(encoding="utf-8") - out = [] - for d, fill in re.findall(r' Image.Image: - """inset — доля стороны под знак. Для maskable нужна безопасная зона: - Android режет иконку по кругу, и знак без запаса теряет края.""" +def compose(size: int, inset: float, radius_ratio: float = 0.22) -> Image.Image: + """inset — доля стороны под плитку. У maskable она меньше: Android режет + иконку по кругу, и плитка без запаса теряет углы.""" big = size * SS - img = Image.new("RGBA", (big, big), BG) + img = Image.new("RGBA", (big, big), PAPER) draw = ImageDraw.Draw(img) - x0, y0, x1, y1 = MARK - scale = min(big * inset / (x1 - x0), big * inset / (y1 - y0)) - dx = (big - (x1 - x0) * scale) / 2 - x0 * scale - dy = (big - (y1 - y0) * scale) / 2 - y0 * scale - for pts, fill in paths(): - draw.polygon([(x * scale + dx, y * scale + dy) for x, y in pts], fill=fill) + pad = big * (1 - inset) / 2 + box = (pad, pad, big - pad, big - pad) + draw.rounded_rectangle(box, radius=(box[2] - box[0]) * radius_ratio, fill=ACCENT) + + letter = "П" + font = _font(int((box[3] - box[1]) * 0.72)) + l, t, r, b = draw.textbbox((0, 0), letter, font=font) + draw.text(((box[0] + box[2] - (r - l)) / 2 - l, + (box[1] + box[3] - (b - t)) / 2 - t), letter, font=font, fill=PAPER) return img.resize((size, size), Image.LANCZOS) if __name__ == "__main__": OUT.mkdir(parents=True, exist_ok=True) - for name, size, inset in (("icon-192.png", 192, 0.82), - ("icon-512.png", 512, 0.82), - ("icon-maskable-512.png", 512, 0.60), - ("apple-touch-icon.png", 180, 0.82)): + for name, size, inset in (("icon-192.png", 192, 1.0), + ("icon-512.png", 512, 1.0), + ("icon-maskable-512.png", 512, 0.72), + ("apple-touch-icon.png", 180, 1.0)): compose(size, inset).save(OUT / name) print("собрано", name)