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.
129 lines
4.9 KiB
Python
Executable File
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))
|