119 lines
6 KiB
Python
119 lines
6 KiB
Python
# Genera docs/calle.svg: ilustración stencil "a la calle"
|
|
import sys, random
|
|
from fontTools.ttLib import TTFont
|
|
from fontTools.pens.svgPathPen import SVGPathPen
|
|
from fontTools.pens.transformPen import TransformPen
|
|
random.seed(26)
|
|
FONT, OUT = sys.argv[1], sys.argv[2]
|
|
W, H = 1200, 640
|
|
INK, RED = "#000", "#ff1f1f"
|
|
out = []
|
|
def add(s): out.append(s)
|
|
|
|
def text_path(txt, x, y, size, fill, anchor="middle", font=FONT):
|
|
f = TTFont(font); gs = f.getGlyphSet(); cmap = f.getBestCmap()
|
|
upm = f["head"].unitsPerEm; k = size / upm
|
|
widths = [f["hmtx"][cmap[ord(c)]][0] for c in txt]
|
|
total = sum(widths) * k
|
|
cx = x - total / 2 if anchor == "middle" else x
|
|
d = []
|
|
for c, w in zip(txt, widths):
|
|
pen = SVGPathPen(gs)
|
|
gs[cmap[ord(c)]].draw(TransformPen(pen, (k, 0, 0, -k, cx, y)))
|
|
d.append(pen.getCommands()); cx += w * k
|
|
return f'<path fill="{fill}" d="{" ".join(d)}"/>'
|
|
|
|
def blob(cx, cy, r, n, jit, fill, op=1):
|
|
import math
|
|
pts = []
|
|
for i in range(n):
|
|
a = 2 * math.pi * i / n
|
|
rr = r * (1 + random.uniform(-jit, jit))
|
|
if random.random() < .25: rr *= 1.25
|
|
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
|
|
d = f"M{pts[0][0]:.0f},{pts[0][1]:.0f} " + " ".join(
|
|
f"Q{p[0]:.0f},{p[1]:.0f} {(p[0]+q[0])/2:.0f},{(p[1]+q[1])/2:.0f}"
|
|
for p, q in zip(pts, pts[1:] + pts[:1])) + "Z"
|
|
add(f'<path d="{d}" fill="{fill}" opacity="{op}"/>')
|
|
|
|
def splats(cx, cy, spread, n, rmax, fill):
|
|
for _ in range(n):
|
|
x = cx + random.gauss(0, spread); y = cy + random.gauss(0, spread * .6)
|
|
add(f'<circle cx="{x:.0f}" cy="{y:.0f}" r="{random.uniform(2, rmax):.1f}" fill="{fill}"/>')
|
|
|
|
def drips(x0, x1, y, fill, n):
|
|
for _ in range(n):
|
|
x = random.uniform(x0, x1); l = random.uniform(15, 70); w = random.uniform(5, 11)
|
|
add(f'<path d="M{x-w/2:.0f},{y} v{l:.0f} a{w/2:.1f},{w/2:.1f} 0 0 0 {w:.0f},0 v-{l:.0f}z" fill="{fill}"/>')
|
|
|
|
def tent(x, y, w, h):
|
|
add(f'<path d="M{x},{y} L{x+w/2},{y-h} L{x+w},{y} Z" fill="#fff" stroke="{INK}" stroke-width="8" stroke-linejoin="round"/>')
|
|
add(f'<path d="M{x+w/2},{y-h} L{x+w*.38},{y} L{x+w*.62},{y} Z" fill="{INK}"/>')
|
|
add(f'<path d="M{x+w/2},{y-h} l0,-22 l26,9 l-26,9" fill="{RED}" stroke="{INK}" stroke-width="4" stroke-linejoin="round"/>')
|
|
|
|
def fig(x, y, s=1.0, pose="down", step=1):
|
|
L = lambda *p: " ".join(f"{a:.0f}" for a in p)
|
|
sw = 19 * s
|
|
st = f'stroke="{INK}" stroke-width="{sw:.0f}" stroke-linecap="round" stroke-linejoin="round" fill="none"'
|
|
hipy = y - 78 * s; shy = y - 150 * s
|
|
# piernas (andando)
|
|
add(f'<path d="M{L(x-9*s, hipy)} L{L(x-9*s-24*s*step, y-38*s)} L{L(x-12*s-30*s*step, y)}" {st}/>')
|
|
add(f'<path d="M{L(x+9*s, hipy)} L{L(x+14*s+16*s*step, y-40*s)} L{L(x+20*s+26*s*step, y)}" {st}/>')
|
|
# torso (chupa)
|
|
add(f'<path d="M{L(x-31*s, shy)} L{L(x+31*s, shy)} L{L(x+24*s, hipy+6*s)} L{L(x-24*s, hipy+6*s)} Z" fill="{INK}" stroke="{INK}" stroke-width="{8*s:.0f}" stroke-linejoin="round"/>')
|
|
# cresta (detrás) + cabeza
|
|
if pose in ("fist", "cresta"):
|
|
for i in range(5):
|
|
dx = (i - 2) * 11 * s
|
|
add(f'<path d="M{L(x+dx-8*s, shy-40*s)} L{L(x+dx*1.9, shy-112*s)} L{L(x+dx+8*s, shy-40*s)} Z" fill="{RED}" stroke="{INK}" stroke-width="{5*s:.0f}" stroke-linejoin="round"/>')
|
|
add(f'<circle cx="{x:.0f}" cy="{shy-36*s:.0f}" r="{24*s:.0f}" fill="{INK}"/>')
|
|
# brazos
|
|
ls, rs = (x - 27 * s, shy + 8 * s), (x + 27 * s, shy + 8 * s)
|
|
if pose == "fist":
|
|
add(f'<path d="M{L(*rs)} L{L(x+52*s, shy-38*s)} L{L(x+50*s, shy-100*s)}" {st}/>')
|
|
add(f'<rect x="{x+33*s:.0f}" y="{shy-138*s:.0f}" width="{34*s:.0f}" height="{40*s:.0f}" rx="{10*s:.0f}" fill="{INK}"/>')
|
|
add(f'<path d="M{L(*ls)} L{L(x-46*s, shy+50*s)} L{L(x-40*s, shy+88*s)}" {st}/>')
|
|
elif pose.startswith("pole"):
|
|
side = -1 if pose == "poleL" else 1
|
|
px = x + side * 46 * s
|
|
add(f'<path d="M{L(*(rs if side>0 else ls))} L{L(px, shy-20*s)}" {st}/>')
|
|
add(f'<path d="M{L(*(ls if side>0 else rs))} L{L(x, shy+30*s)} L{L(px, shy+20*s)}" {st}/>')
|
|
else:
|
|
add(f'<path d="M{L(*ls)} L{L(x-44*s, shy+50*s)} L{L(x-38*s, shy+90*s)}" {st}/>')
|
|
add(f'<path d="M{L(*rs)} L{L(x+44*s, shy+50*s)} L{L(x+38*s, shy+90*s)}" {st}/>')
|
|
|
|
add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">')
|
|
add(f'<rect width="{W}" height="{H}" fill="#fff"/>')
|
|
# mancha roja de spray detrás
|
|
blob(600, 300, 250, 22, .18, RED)
|
|
splats(600, 300, 230, 70, 9, RED)
|
|
drips(420, 780, 470, RED, 9)
|
|
# calle: asfalto + paso de cebra
|
|
GY = 540
|
|
add(f'<rect x="0" y="{GY}" width="{W}" height="{H-GY}" fill="{INK}"/>')
|
|
for i in range(-2, 14):
|
|
x = i * 100
|
|
add(f'<path d="M{x+30},{GY+14} L{x+80},{GY+14} L{x+60},{H-14} L{x-10},{H-14} Z" fill="#fff"/>')
|
|
# tiendas al fondo
|
|
tent(40, GY, 190, 140); tent(190, GY, 140, 100)
|
|
tent(930, GY, 200, 150); tent(1070, GY, 120, 90)
|
|
# pancarta entre dos palos
|
|
bx0, bx1, by = 300, 900, 50
|
|
add(f'<path d="M{bx0},{by} L{bx1},{by} L{bx1},{by+115} Q{(bx0+bx1)/2},{by+140} {bx0},{by+115} Z" fill="#fff" stroke="{INK}" stroke-width="9" stroke-linejoin="round"/>')
|
|
add(text_path("¡A LA CALLE!", 600, by + 86, 74, INK))
|
|
drips(bx0 + 40, bx1 - 40, by + 122, INK, 6)
|
|
add(f'<line x1="{bx0}" y1="{by-20}" x2="{bx0}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>')
|
|
add(f'<line x1="{bx1}" y1="{by-20}" x2="{bx1}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>')
|
|
# gente
|
|
fig(bx0 + 46, GY - 4, 1.05, "poleL", 1)
|
|
fig(470, GY - 2, .95, "down", -1)
|
|
fig(600, GY, 1.15, "fist", 1)
|
|
fig(730, GY - 2, .95, "cresta", -1)
|
|
fig(bx1 - 46, GY - 4, 1.05, "poleR", 1)
|
|
# estrellas
|
|
for (sx, sy, r) in [(120, 90, 26), (1080, 110, 32), (990, 300, 16), (200, 300, 14)]:
|
|
import math
|
|
pts = " ".join(f"{sx + (r if i%2==0 else r*.42)*math.cos(math.pi/2 + i*math.pi/5):.0f},{sy - (r if i%2==0 else r*.42)*math.sin(math.pi/2 + i*math.pi/5):.0f}" for i in range(10))
|
|
add(f'<polygon points="{pts}" fill="{INK}"/>')
|
|
add('</svg>')
|
|
open(OUT, "w").write("\n".join(out))
|