"""Génère les fichiers SVG de l'identité Ustagammon : symbole P3d, logotype L3. Le symbole : deux pions vus de trois quarts (le maître en noyer devant, l'apprenti en ivoire derrière) posés sur une flèche brique ; leurs deux silhouettes dessinent un 8, clin d'œil à l'Atelier du Huit. Le logotype : « Ustagammon » en Alegreya Sans Bold (SIL OFL), dont les deux « m » sont redessinés en quatre flèches au trait, alternant noyer et brique. Usage : python3 -I generer-logo.py Licence des dessins produits : CC BY-SA 4.0. """ import os import sys from fontTools.pens.svgPathPen import SVGPathPen from fontTools.ttLib import TTFont FONT, OUT = sys.argv[1], sys.argv[2] os.makedirs(OUT, exist_ok=True) # ---------- Palettes ---------- # d/ds/de : face, tranche et filet du pion noyer ; l/ls : face et tranche du pion ivoire ; # o : contour du pion ivoire ; a : flèche et filet brique ; sep : liseré du pion noyer ; # txt / acc : texte du logotype et flèches brique des « mm ». COULEUR = dict(d="#4A2E1F", ds="#2B1A10", de="#7A5038", l="#F3EAD8", ls="#CDB898", o="#2B1A10", a="#A8432B", sep="#F3EAD8", txt="#2B1D15", acc="#A8432B") SOMBRE = dict(COULEUR, a="#C9583C", txt="#F3EAD8", acc="#D0694B") # Monochromes : I = encre, H = trou (transparent) MONO = dict(d="I", ds="I", de="H", l="H", ls="I", o="I", a="I", sep="H", txt="I", acc="I") def symbol(p, simple=False): """Symbole P3d dans une boîte de 64 × 64. `simple` : sans filets, pour le favicon.""" s = [f''] ow = 3 if simple else 2.2 s.append(f'') s.append(f'') if not simple: s.append(f'') sw = 2.6 if simple else 2 s.append(f'') s.append(f'') if not simple: s.append(f'') return "".join(s) # ---------- Logotype ---------- font = TTFont(FONT) gs = font.getGlyphSet() cmap = font.getBestCmap() hmtx = font["hmtx"] XH = font["OS/2"].sxHeight # hauteur d'x (465 pour Alegreya Sans Bold) STROKE = 0.21 * XH # épaisseur du trait des flèches, proche du fût des lettres MM_W = 3.0 * XH # largeur du bloc « mm » GAP = 20 def glyph(ch, x, fill): g = cmap[ord(ch)] pen = SVGPathPen(gs) gs[g].draw(pen) return f'', hmtx[g][0] def inter(p1, d1, p2, d2): """Intersection de deux droites données par un point et une direction.""" (x1, y1), (dx1, dy1), (x2, y2), (dx2, dy2) = p1, d1, p2, d2 det = dx1 * -dy2 - dy1 * -dx2 t = ((x2 - x1) * -dy2 - (y2 - y1) * -dx2) / det return (x1 + t * dx1, y1 + t * dy1) def chevron(x0, x1, top): """Flèche « ^ » au trait : pieds coupés sur la ligne de base (y = 0), sommet en onglet.""" peak = ((x0 + x1) / 2, -top) h = STROKE / 2 lines = [] for a, b in (((x0, 0), peak), (peak, (x1, 0))): dx, dy = b[0] - a[0], b[1] - a[1] n = (dx * dx + dy * dy) ** 0.5 nx, ny = -dy / n, dx / n lines.append(((a[0] + nx * h, a[1] + ny * h), (dx, dy), (a[0] - nx * h, a[1] - ny * h))) (lo, ld, li), (ro, rd, ri) = lines base = ((0, 0), (1, 0)) l_ext, l_int = (li, lo) if li[1] < lo[1] else (lo, li) r_ext, r_int = (ri, ro) if ri[1] < ro[1] else (ro, ri) pts = [inter(l_ext, ld, *base), inter(l_ext, ld, r_ext, rd), inter(r_ext, rd, *base), inter(r_int, rd, *base), inter(l_int, ld, r_int, rd), inter(l_int, ld, *base)] return " ".join(f"{x:.1f},{y:.1f}" for x, y in pts) def logotype(p, x=0.0): parts = [] for ch in "Ustaga": s, adv = glyph(ch, x, p["txt"]) parts.append(s) x += adv x += GAP unit = MM_W / 300 # coordonnées de la maquette : boîte de 300 × 100 top = XH * 1.06 # léger dépassement de la hauteur d'x, comme une lettre pointue for i, (a, b) in enumerate(((8, 74), (74, 140), (160, 226), (226, 292))): fill = p["txt"] if i % 2 == 0 else p["acc"] parts.append(f'') x += MM_W + GAP for ch in "on": s, adv = glyph(ch, x, p["txt"]) parts.append(s) x += adv return "".join(parts), x ASC, DESC = 680, 240 # marges verticales autour de la ligne de base def mono_wrap(body, vb, ink, mid): """Transforme un dessin encre/trou en dessin d'une seule couleur, trous transparents.""" body = body.replace('"I"', '"#fff"').replace('"H"', '"#000"') x, y, w, h = vb return (f'' f'{body}' f'') def svg(vb, body, title): return (f'{title}{body}\n') def write(name, content): with open(os.path.join(OUT, name), "w", encoding="utf-8") as f: f.write(content) VARIANTS = {"couleur": COULEUR, "sombre": SOMBRE, "noir": "#000000", "blanc": "#FFFFFF"} def palette(v): return VARIANTS[v] if isinstance(VARIANTS[v], dict) else MONO def ink(body, v): return body if isinstance(VARIANTS[v], dict) else body.replace('"I"', f'"{VARIANTS[v]}"') # ---------- Symbole ---------- for v in VARIANTS: vb = (0, 0, 64, 64) body = symbol(palette(v)) if not isinstance(VARIANTS[v], dict): body = mono_wrap(body, vb, VARIANTS[v], "m") write(f"symbole-{v}.svg", svg(vb, body, "Ustagammon")) # ---------- Logotype seul ---------- for v in VARIANTS: body, w = logotype(palette(v)) write(f"logotype-{v}.svg", svg((-10, -ASC, round(w + 20), ASC + DESC), ink(body, v), "Ustagammon")) # ---------- Logo horizontal ---------- SYM = 1450 # hauteur du symbole, pour un corps de 1000 CENTER = -322 # centre optique : mi-hauteur des capitales for v in VARIANTS: p = palette(v) sym = f'{symbol(p)}' text, w = logotype(p, x=SYM + 280) vb = (-20, CENTER - SYM / 2 - 20, round(w + 40), SYM + 40) if not isinstance(VARIANTS[v], dict): sym = mono_wrap(sym, vb, VARIANTS[v], "m") write(f"logo-horizontal-{v}.svg", svg(vb, sym + ink(text, v), "Ustagammon")) # ---------- Icônes ---------- IVOIRE = "#F3EAD8" def placed(frac, size, pal=COULEUR, simple=False): """Symbole centré dans un carré de `size`, occupant la fraction `frac` du côté.""" s = size * frac / 64 off = (size - 64 * s) / 2 return f'{symbol(pal, simple)}' # Icône « any » : tuile ivoire aux coins arrondis, transparente autour write("icone.svg", svg((0, 0, 512, 512), f'' + placed(0.70, 512), "Ustagammon")) # Icône « maskable » : fond plein, symbole dans la zone de sécurité (cercle de 80 %) write("icone-maskable.svg", svg((0, 0, 512, 512), f'' + placed(0.56, 512), "Ustagammon")) # Favicon : dessin simplifié (sans filets), traits épaissis, fond transparent write("favicon.svg", svg((0, 0, 64, 64), symbol(COULEUR, simple=True), "Ustagammon")) # Android, icône adaptative : canevas de 108 × 108 dp, zone sûre de 66 dp au centre write("android-premier-plan.svg", svg((0, 0, 108, 108), placed(0.58, 108), "Ustagammon, premier plan")) write("android-arriere-plan.svg", svg((0, 0, 108, 108), f'', "Ustagammon, arrière-plan")) write("android-monochrome.svg", svg((0, 0, 108, 108), mono_wrap(placed(0.58, 108, MONO), (0, 0, 108, 108), "#000000", "m"), "Ustagammon, monochrome")) print("\n".join(sorted(os.listdir(OUT))))