#!/usr/bin/env python3
"""
Il set di segni dell'interfaccia: ventiquattro icone disegnate qui, non prese da
un catalogo.

Perche' non un font di icone. MaterialCommunityIcons e SF Symbols sono ottimi e
si riconoscono a colpo d'occhio — ed e' esattamente il problema: si riconoscono
come le icone di qualcun altro. Un'app che vuole sembrare disegnata apposta non
puo' avere i segni che ha chiunque. Sono ventiquattro, si disegnano.

Perche' immagini e non vettori. react-native-svg e' una dipendenza nativa, e
questo progetto non e' ancora mai stato compilato sul serio: si aggiungono
dipendenze native quando c'e' un motivo, non per ventiquattro disegni. Le PNG si
colorano con `tintColor`, quindi seguono il tema come farebbe un vettore.

LA REGOLA DEL SET, che e' quello che rende ventiquattro disegni un insieme:
  - griglia di 32, tutto costruito dentro;
  - tratto di 2,6 — piu' spesso della media dei set di sistema, perche' questa
    e' un'app che si guarda da mezzo metro con le mani sporche;
  - terminazioni arrotondate, sempre;
  - le forme chiuse hanno lo stesso raggio d'angolo, 5 su 32, che e' la stessa
    proporzione della pastiglia delle categorie (16 su 52);
  - niente riempimenti: solo tratto, cosi' i segni pesano tutti uguale.

Si disegna a 4x e si riduce: e' l'antialiasing, e senza i tratti obliqui
verrebbero seghettati.

Si rilancia con: python3 scripts/genera-segni.py
"""
import math
from pathlib import Path

from PIL import Image, ImageDraw

G = 32               # la griglia
SCALA = 12           # 32 * 12 = 384, ridotto poi a 96 (tre volte 32)
LATO = 96
TRATTO = 2.6
RAGGIO = 5

USCITA = Path(__file__).resolve().parent.parent / 'assets' / 'segni'


def tela():
    return Image.new('RGBA', (G * SCALA, G * SCALA), (0, 0, 0, 0))


def p(*coppie):
    """Da coordinate della griglia a pixel."""
    return [(x * SCALA, y * SCALA) for x, y in coppie]


def linea(d, *punti, chiusa=False):
    pt = p(*punti)
    if chiusa:
        pt = pt + [pt[0]]
    d.line(pt, fill=(255, 255, 255, 255), width=round(TRATTO * SCALA), joint='curve')
    # I capi: PIL non arrotonda le estremita', si aggiungono a mano.
    r = TRATTO * SCALA / 2
    for x, y in (pt[0], pt[-1]):
        d.ellipse([x - r, y - r, x + r, y + r], fill=(255, 255, 255, 255))


def cerchio(d, cx, cy, raggio):
    r = raggio * SCALA
    x, y = cx * SCALA, cy * SCALA
    d.ellipse([x - r, y - r, x + r, y + r], outline=(255, 255, 255, 255),
              width=round(TRATTO * SCALA))


def disco(d, cx, cy, raggio):
    r = raggio * SCALA
    x, y = cx * SCALA, cy * SCALA
    d.ellipse([x - r, y - r, x + r, y + r], fill=(255, 255, 255, 255))


def ellisse(d, cx, cy, rx, ry):
    """Un cerchio schiacciato: serve solo al meridiano del globo."""
    x, y = cx * SCALA, cy * SCALA
    rx, ry = rx * SCALA, ry * SCALA
    d.ellipse([x - rx, y - ry, x + rx, y + ry], outline=(255, 255, 255, 255),
              width=round(TRATTO * SCALA))


def arco(d, cx, cy, raggio, a0, a1):
    """Un arco di cerchio vero, coi capi tondi come le linee rette. Ci vuole
    un raggio non piccolo e un giro non troppo grande: un arco quasi chiuso
    su un raggio piccolo diventa un gomitolo, i capi tondi si mangiano il
    buco (e' la storia della prima versione di assistenza)."""
    r = raggio * SCALA
    x, y = cx * SCALA, cy * SCALA
    d.arc([x - r, y - r, x + r, y + r], start=a0, end=a1,
          fill=(255, 255, 255, 255), width=round(TRATTO * SCALA))
    rr = TRATTO * SCALA / 2
    for ang in (a0, a1):
        rad = math.radians(ang)
        px, py = x + r * math.cos(rad), y + r * math.sin(rad)
        d.ellipse([px - rr, py - rr, px + rr, py + rr], fill=(255, 255, 255, 255))




def riquadro(d, x0, y0, x1, y1, raggio=RAGGIO):
    d.rounded_rectangle([x0 * SCALA, y0 * SCALA, x1 * SCALA, y1 * SCALA],
                        radius=raggio * SCALA, outline=(255, 255, 255, 255),
                        width=round(TRATTO * SCALA))


# --- i ventiquattro segni -------------------------------------------------------

def indietro(d):
    """Freccia: asta lunga e punta aperta. La punta e' a 50 gradi, non a 45:
    a 45 sembra il chevron di sistema, e questo deve essere un'altra cosa."""
    linea(d, (26, 16), (8, 16))
    linea(d, (15.5, 8.5), (7.6, 16), (15.5, 23.5))


def cerca(d):
    """Lente: il cerchio sta alto a sinistra e il manico scende a 45."""
    cerchio(d, 14, 14, 7)
    linea(d, (19.4, 19.4), (25.5, 25.5))


def chiudi(d):
    linea(d, (9, 9), (23, 23))
    linea(d, (23, 9), (9, 23))


def piu(d):
    linea(d, (16, 8), (16, 24))
    linea(d, (8, 16), (24, 16))


def meno(d):
    linea(d, (8, 16), (24, 16))


def griglia(d):
    """Quattro riquadri: e' la vista a tessere.

    Al primo tentativo erano 7 di lato con raggio 2,4, e restava un puntino
    bianco nel mezzo: con un tratto di 2,6 un quadrato di 7 non ha abbastanza
    interno, e l'arrotondamento se lo mangiava. Nove di lato, raggio 1,8.
    """
    for x in (5.5, 17.5):
        for y in (5.5, 17.5):
            riquadro(d, x, y, x + 9, y + 9, raggio=1.8)


def elenco(d):
    """Tre righe, ognuna con la sua tacca a sinistra: e' la scheda-riga vista da
    lontano, foto a sinistra e testo a destra."""
    for y in (9, 16, 23):
        linea(d, (7, y), (9.6, y))
        linea(d, (13.5, y), (25, y))


def matita(d):
    """Corpo obliquo e punta: il taglio in fondo e' la mina."""
    linea(d, (20.5, 7.5), (24.5, 11.5))
    linea(d, (22.5, 5.5), (8.5, 19.5), (7, 25), (12.5, 23.5), (26.5, 9.5), chiusa=False)
    linea(d, (22.5, 5.5), (26.5, 9.5))


def libro(d):
    """Due pagine aperte con il dorso in mezzo: e' «tutte le ricette»."""
    linea(d, (16, 10), (16, 25))
    linea(d, (16, 10), (10, 7.5), (5.5, 7.5), (5.5, 22.5), (10, 22.5), (16, 25))
    linea(d, (16, 10), (22, 7.5), (26.5, 7.5), (26.5, 22.5), (22, 22.5), (16, 25))


def vassoio(d):
    """Il vassoio dove si posa quello che non e' ancora sistemato: e' «senza
    categoria».

    Aperto in cima, di proposito: la prima versione era un rettangolo chiuso
    con una linea in mezzo e sembrava una valigia. Un vassoio si riconosce
    perche' non ha coperchio.
    """
    linea(d, (4.5, 9), (7.5, 24.5), (24.5, 24.5), (27.5, 9))
    linea(d, (6.4, 19), (25.6, 19))


def altro(d):
    """Tre punti: il menu dell'importa ed esporta."""
    for x in (9, 16, 23):
        disco(d, x, 16, TRATTO / 2)


def condividiios(d):
    """Condividi, come lo disegna iOS: un riquadro aperto in cima e una freccia
    che ne esce verso l'alto. Non e' un'icona nostra: e' quella che chi ha un
    iPhone riconosce a colpo d'occhio, ridisegnata col nostro tratto."""
    linea(d, (12.5, 12.5), (9.5, 12.5), (9.5, 26.5), (22.5, 26.5), (22.5, 12.5), (19.5, 12.5))
    linea(d, (16, 19.5), (16, 5.5))
    linea(d, (11.5, 10), (16, 5.5), (20.5, 10))


def condividiandroid(d):
    """Condividi, come lo disegna Android: tre nodi uniti da due segmenti.
    Stesso motivo del gemello iOS: e' il glifo che quel sistema usa ovunque."""
    for cx, cy in ((22.5, 8), (9.5, 16), (22.5, 24)):
        disco(d, cx, cy, 3.3)
    linea(d, (12.4, 14.2), (19.6, 9.8))
    linea(d, (12.4, 17.8), (19.6, 22.2))


def esporta(d):
    """Un vassoio aperto e una freccia che ne esce verso l'alto: e' l'azione
    di esportare, il contrario di importa qui sotto."""
    linea(d, (10, 19), (8, 26.5), (24, 26.5), (22, 19))
    linea(d, (16, 17.5), (16, 5.5))
    linea(d, (11.5, 10), (16, 5.5), (20.5, 10))


def importa(d):
    """Lo stesso vassoio di esporta, ma la freccia scende: entra invece di
    uscire."""
    linea(d, (10, 19), (8, 26.5), (24, 26.5), (22, 19))
    linea(d, (16, 5.5), (16, 17.5))
    linea(d, (11.5, 13), (16, 17.5), (20.5, 13))


def cestino(d):
    """Il cestino: coperchio, maniglia e due righe dentro il corpo, con aria
    intorno. La prima versione era tozza e fitta: corpo piu' alto, che si
    stringe verso il fondo, e una riga di meno in mezzo."""
    linea(d, (13, 9), (13, 6), (19, 6), (19, 9))
    linea(d, (7.5, 9), (24.5, 9))
    linea(d, (9, 9), (11, 28.5), (21, 28.5), (23, 9))
    linea(d, (13, 13.5), (13, 25))
    linea(d, (19, 13.5), (19, 25))


def tema(d):
    """La tavolozza del pittore: un cerchio grande aperto a destra, i due
    capi uniti da una curva che rientra verso il centro — l'incavo del
    pollice — e tre colori a tratto. Un ottagono coi puntini, provato prima,
    non si leggeva come tavolozza."""
    arco(d, 16, 16, 12, 25, 335)
    linea(d, (26.9, 10.9), (21, 16), (26.9, 21.1))
    cerchio(d, 10, 12, 1.6)
    cerchio(d, 15, 8.5, 1.6)
    cerchio(d, 10, 19, 1.6)


def lingua(d):
    """Il globo, piu' grande e con un solo meridiano: un cerchio, l'equatore
    e un'ellisse stretta. Niente altro — a griglia piccola un secondo
    meridiano si infittiva e basta."""
    cerchio(d, 16, 16, 11)
    linea(d, (5, 16), (27, 16))
    ellisse(d, 16, 16, 4.6, 11)


def assistenza(d):
    """Un punto di domanda dentro un cerchio grande, con un arco vero per
    l'amo: a raggio piccolo l'arco si richiudeva su se stesso e diventava un
    gomitolo, a raggio grande i due capi restano lontani e il buco si legge."""
    cerchio(d, 16, 16, 12.5)
    arco(d, 16, 11.5, 5.5, 195, 75)
    linea(d, (17.1, 16.4), (16.6, 18.5), (16, 20.5))
    disco(d, 16, 24.5, 1.5)


def privacy(d):
    """Il lucchetto: la staffa e' due verticali chiuse da un semicerchio in
    cima, ben staccata dal corpo. La prima versione era piccola e la staffa
    si vedeva appena; ingrandita tutta, coordinate esatte."""
    riquadro(d, 8, 15, 24, 27, raggio=3)
    linea(d, (11, 15), (11, 10))
    linea(d, (21, 15), (21, 10))
    arco(d, 16, 10, 5, 180, 360)
    linea(d, (16, 19), (16, 22.5))


def cuore(d):
    """Il cuore: due lobi con un arco vero ciascuno, e una punta morbida dove
    le due linee dei fianchi si incontrano. La prima versione aveva la cima
    piatta: i due archi coprivano troppo poco cerchio, coordinate esatte."""
    arco(d, 11, 12.5, 5.5, 135, 360)
    arco(d, 20.5, 12.5, 5.5, 180, 405)
    linea(d, (7.1, 16.4), (16, 26))
    linea(d, (24.4, 16.4), (16, 26))


def freccia(d):
    """Il chevron delle righe di Impostazioni: piu' piccolo e dentro la
    griglia, cosi' resta leggero e non compete col resto della riga."""
    linea(d, (13, 10), (19, 16), (13, 22))


def esterno(d):
    """La freccia diagonale di chi apre il browser: l'asta e l'angolo della
    punta, come si disegna dappertutto un link esterno."""
    linea(d, (10, 22), (22, 10))
    linea(d, (15, 10), (22, 10), (22, 17))


def spunta(d):
    """La spunta delle schermate Tema e Lingua: un segno solo, tratto corto
    in basso e tratto lungo che risale, come si scrive una spunta a mano."""
    linea(d, (7, 17), (13, 23), (25, 9))


SEGNI = {
    'indietro': indietro, 'cerca': cerca, 'chiudi': chiudi, 'piu': piu,
    'meno': meno, 'griglia': griglia, 'elenco': elenco, 'matita': matita,
    'libro': libro, 'vassoio': vassoio, 'altro': altro,
    'condividiios': condividiios, 'condividiandroid': condividiandroid,
    'esporta': esporta, 'importa': importa, 'cestino': cestino, 'tema': tema,
    'lingua': lingua, 'assistenza': assistenza, 'privacy': privacy,
    'cuore': cuore, 'freccia': freccia, 'esterno': esterno, 'spunta': spunta,
}


def main():
    USCITA.mkdir(parents=True, exist_ok=True)
    for nome, disegna in SEGNI.items():
        im = tela()
        disegna(ImageDraw.Draw(im))
        im = im.resize((LATO, LATO), Image.LANCZOS)
        f = USCITA / f'{nome}.png'
        im.save(f, optimize=True)
        print(f'  {nome:10} {f.stat().st_size:5} byte')
    print(f'{len(SEGNI)} segni in {USCITA}')


if __name__ == '__main__':
    main()
