Files
Sergio 6735a75d40 marca: el logo de terminal pasa a medios bloques, con placa y juntas
El ASCII de densidad (#*+@) queda como fallback para consolas sin color; el
default ahora es MEDIOS BLOQUES: `▀` con color de frente Y de fondo, que mete
dos filas de pixeles por renglon de terminal. Trae la placa obsidiana y la celda
de espacio libre que pide la hoja de marca.

Y las JUNTAS entre teselas, que es lo que faltaba para que se pareciera al
simbolo de verdad: en el SVG las teselas son cuadrados de 100 con 8 de aire. La
tocapu son piezas SEPARADAS, no un bloque macizo, y sin la junta el dibujo se
lee como una mancha. Se apagan solas por debajo de escala 3, donde la junta se
comeria media tesela.

Lo que NO se hace, y va escrito: no se interpola ni se suaviza el gradiente.
El simbolo ES una reticula de cuadrados; difuminarlo seria «recolorear y
deformar», que la propia hoja de marca prohibe. Subir la escala repite pixeles.

Verificado reconstruyendo el dibujo desde los colores que emite el propio ANSI,
no mirando el codigo: sale el martillo con las cuatro tintas y la chispa en el
centro. Las tres formas y --escala=5 corren sin error.
2026-09-09 20:53:26 +00:00

129 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Emite el símbolo de takana como arte de terminal, al estilo de los `*fetch`.
Se DERIVA del SVG de `docs/marca/`, no se dibuja a mano: una copia dibujada
aparte se desincroniza del símbolo el día que la marca cambie, y nadie se
entera.
Tres formas, de más a menos fiel:
--medio (default) MEDIOS BLOQUES: `▀` con color de frente y de fondo, así
cada renglón de terminal lleva DOS filas de píxeles. Es lo que usan
los fetch modernos y lo que permite subir la escala sin que el
dibujo se vuelva un ladrillo. Trae la placa de fondo y la celda de
espacio libre que pide la hoja de marca.
--bloques BLOQUES ENTEROS: `██` por celda. Sin fondo. Para terminales que
renderizan mal los medios bloques.
--plano ASCII de densidad, sin color. Para pipes, `TERM` tonto y consolas
que no hablan 24-bit.
**No se interpola ni se suaviza el gradiente.** El símbolo ES una retícula
tocapu de cuadrados; difuminarlo sería «recolorear y deformar», que la hoja de
marca prohíbe explícitamente. Subir `--escala` repite píxeles (vecino más
cercano) y mantiene el borde duro.
"""
import re, sys, pathlib
RAIZ = pathlib.Path(__file__).resolve().parent.parent
SVG = RAIZ / "docs/marca/simbolo-fondo-oscuro.svg"
OBSIDIANA = "#17130E" # el fondo de la hoja de marca
DENSIDAD = {"#A63A25": "#", "#DF6B2A": "*", "#E3A63D": "+", "#FFF6DE": "@"}
def rgb(h):
return int(h[1:3], 16), int(h[3:5], 16), int(h[5:7], 16)
def grilla():
"""La retícula {(fila, col): color} leída del SVG."""
s = SVG.read_text()
# El SVG trae un manifiesto C2PA enorme en <metadata>; los rects van después.
cuerpo = s[s.rfind("</metadata>") + 11:] if "</metadata>" in s else s
celdas = {}
for m in re.finditer(
r'<rect x="(\d+)" y="(\d+)" width="(\d+)" height="(\d+)" fill="(#[0-9A-Fa-f]{6})"', cuerpo
):
celdas[(int(m.group(2)) // 108, int(m.group(1)) // 108)] = m.group(5)
if not celdas:
sys.exit(f"no encontré celdas en {SVG} — ¿cambió el formato del símbolo?")
return celdas, max(r for r, _ in celdas) + 1, max(c for _, c in celdas) + 1
def pixeles(escala, margen, juntas=None):
"""Matriz de colores ya escalada, con el margen de espacio libre alrededor.
`juntas` deja el último píxel de cada celda en fondo, reproduciendo la
separación entre teselas que el símbolo tiene de verdad (en el SVG son
cuadrados de 100 con 8 de aire: la tocapu son piezas separadas, no un
bloque macizo). Sólo cabe desde escala 3 — a escala 2 la junta se comería
la mitad de la tesela, así que ahí se apaga sola.
"""
if juntas is None:
juntas = escala >= 3
celdas, filas, cols = grilla()
alto, ancho = filas * escala + 2 * margen, cols * escala + 2 * margen
m = [[None] * ancho for _ in range(alto)]
lleno = escala - 1 if juntas else escala
for (r, c), f in celdas.items():
for dr in range(lleno):
for dc in range(lleno):
m[margen + r * escala + dr][margen + c * escala + dc] = f
return m
def medio(escala=3, margen=1):
"""`▀`: color de frente = píxel de arriba, de fondo = el de abajo."""
m = pixeles(escala, margen)
if len(m) % 2:
m.append([None] * len(m[0]))
out = []
for y in range(0, len(m), 2):
linea = []
for x in range(len(m[0])):
ar, ab = m[y][x] or OBSIDIANA, m[y + 1][x] or OBSIDIANA
fr, fg, fb = rgb(ar)
br, bg, bb = rgb(ab)
linea.append(f"\x1b[38;2;{fr};{fg};{fb};48;2;{br};{bg};{bb}m▀")
out.append("".join(linea) + "\x1b[0m")
return "\n".join(out)
def bloques():
celdas, filas, cols = grilla()
out = []
for r in range(filas):
linea = []
for c in range(cols):
f = celdas.get((r, c))
if f is None:
linea.append(" ")
else:
rr, gg, bb = rgb(f)
linea.append(f"\x1b[38;2;{rr};{gg};{bb}m██\x1b[0m")
out.append("".join(linea).rstrip())
return "\n".join(out)
def plano():
celdas, filas, cols = grilla()
return "\n".join(
"".join(DENSIDAD[celdas[(r, c)]] * 2 if (r, c) in celdas else " " for c in range(cols)).rstrip()
for r in range(filas)
)
if __name__ == "__main__":
a = sys.argv[1:]
esc = int(next((x.split("=")[1] for x in a if x.startswith("--escala=")), 3))
if "--regenerar" in a:
(RAIZ / "docs/marca/simbolo.ansi").write_text(medio(esc) + "\n")
(RAIZ / "docs/marca/simbolo-bloques.ansi").write_text(bloques() + "\n")
(RAIZ / "docs/marca/simbolo.txt").write_text(plano() + "\n")
print("docs/marca/simbolo{.ansi,-bloques.ansi,.txt} regenerados desde el SVG")
elif "--plano" in a:
print(plano())
elif "--bloques" in a:
print(bloques())
else:
print(medio(esc))