sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ 3786b4de9f64ef894661d9dde2ebca6d9dc4a299
scripts/logo-ascii.py
Ñawpa willay
Pi imata llamkarqan
ch'uya
marca: el logo de terminal pasa a medios bloques, con placa y juntas
1#!/usr/bin/env python3
2"""Emite el símbolo de takana como arte de terminal, al estilo de los `*fetch`.
3
4Se DERIVA del SVG de `docs/marca/`, no se dibuja a mano: una copia dibujada
5aparte se desincroniza del símbolo el día que la marca cambie, y nadie se
6entera.
7
8Tres formas, de más a menos fiel:
9
10 --medio (default) MEDIOS BLOQUES: `▀` con color de frente y de fondo, así
11 cada renglón de terminal lleva DOS filas de píxeles. Es lo que usan
12 los fetch modernos y lo que permite subir la escala sin que el
13 dibujo se vuelva un ladrillo. Trae la placa de fondo y la celda de
14 espacio libre que pide la hoja de marca.
15 --bloques BLOQUES ENTEROS: `██` por celda. Sin fondo. Para terminales que
16 renderizan mal los medios bloques.
17 --plano ASCII de densidad, sin color. Para pipes, `TERM` tonto y consolas
18 que no hablan 24-bit.
19
20**No se interpola ni se suaviza el gradiente.** El símbolo ES una retícula
21tocapu de cuadrados; difuminarlo sería «recolorear y deformar», que la hoja de
22marca prohíbe explícitamente. Subir `--escala` repite píxeles (vecino más
23cercano) y mantiene el borde duro.
24"""
25import re, sys, pathlib
26
27RAIZ = pathlib.Path(__file__).resolve().parent.parent
28SVG = RAIZ / "docs/marca/simbolo-fondo-oscuro.svg"
29OBSIDIANA = "#17130E" # el fondo de la hoja de marca
30DENSIDAD = {"#A63A25": "#", "#DF6B2A": "*", "#E3A63D": "+", "#FFF6DE": "@"}
31
32
33def rgb(h):
34 return int(h[1:3], 16), int(h[3:5], 16), int(h[5:7], 16)
35
36
37def grilla():
38 """La retícula {(fila, col): color} leída del SVG."""
39 s = SVG.read_text()
40 # El SVG trae un manifiesto C2PA enorme en <metadata>; los rects van después.
41 cuerpo = s[s.rfind("</metadata>") + 11:] if "</metadata>" in s else s
42 celdas = {}
43 for m in re.finditer(
44 r'<rect x="(\d+)" y="(\d+)" width="(\d+)" height="(\d+)" fill="(#[0-9A-Fa-f]{6})"', cuerpo
45 ):
46 celdas[(int(m.group(2)) // 108, int(m.group(1)) // 108)] = m.group(5)
47 if not celdas:
48 sys.exit(f"no encontré celdas en {SVG} — ¿cambió el formato del símbolo?")
49 return celdas, max(r for r, _ in celdas) + 1, max(c for _, c in celdas) + 1
50
51
52def pixeles(escala, margen, juntas=None):
53 """Matriz de colores ya escalada, con el margen de espacio libre alrededor.
54
55 `juntas` deja el último píxel de cada celda en fondo, reproduciendo la
56 separación entre teselas que el símbolo tiene de verdad (en el SVG son
57 cuadrados de 100 con 8 de aire: la tocapu son piezas separadas, no un
58 bloque macizo). Sólo cabe desde escala 3 — a escala 2 la junta se comería
59 la mitad de la tesela, así que ahí se apaga sola.
60 """
61 if juntas is None:
62 juntas = escala >= 3
63 celdas, filas, cols = grilla()
64 alto, ancho = filas * escala + 2 * margen, cols * escala + 2 * margen
65 m = [[None] * ancho for _ in range(alto)]
66 lleno = escala - 1 if juntas else escala
67 for (r, c), f in celdas.items():
68 for dr in range(lleno):
69 for dc in range(lleno):
70 m[margen + r * escala + dr][margen + c * escala + dc] = f
71 return m
72
73
74def medio(escala=3, margen=1):
75 """`▀`: color de frente = píxel de arriba, de fondo = el de abajo."""
76 m = pixeles(escala, margen)
77 if len(m) % 2:
78 m.append([None] * len(m[0]))
79 out = []
80 for y in range(0, len(m), 2):
81 linea = []
82 for x in range(len(m[0])):
83 ar, ab = m[y][x] or OBSIDIANA, m[y + 1][x] or OBSIDIANA
84 fr, fg, fb = rgb(ar)
85 br, bg, bb = rgb(ab)
86 linea.append(f"\x1b[38;2;{fr};{fg};{fb};48;2;{br};{bg};{bb}m▀")
87 out.append("".join(linea) + "\x1b[0m")
88 return "\n".join(out)
89
90
91def bloques():
92 celdas, filas, cols = grilla()
93 out = []
94 for r in range(filas):
95 linea = []
96 for c in range(cols):
97 f = celdas.get((r, c))
98 if f is None:
99 linea.append(" ")
100 else:
101 rr, gg, bb = rgb(f)
102 linea.append(f"\x1b[38;2;{rr};{gg};{bb}m██\x1b[0m")
103 out.append("".join(linea).rstrip())
104 return "\n".join(out)
105
106
107def plano():
108 celdas, filas, cols = grilla()
109 return "\n".join(
110 "".join(DENSIDAD[celdas[(r, c)]] * 2 if (r, c) in celdas else " " for c in range(cols)).rstrip()
111 for r in range(filas)
112 )
113
114
115if __name__ == "__main__":
116 a = sys.argv[1:]
117 esc = int(next((x.split("=")[1] for x in a if x.startswith("--escala=")), 3))
118 if "--regenerar" in a:
119 (RAIZ / "docs/marca/simbolo.ansi").write_text(medio(esc) + "\n")
120 (RAIZ / "docs/marca/simbolo-bloques.ansi").write_text(bloques() + "\n")
121 (RAIZ / "docs/marca/simbolo.txt").write_text(plano() + "\n")
122 print("docs/marca/simbolo{.ansi,-bloques.ansi,.txt} regenerados desde el SVG")
123 elif "--plano" in a:
124 print(plano())
125 elif "--bloques" in a:
126 print(bloques())
127 else:
128 print(medio(esc))