# 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'' 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'') 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'') 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'') def tent(x, y, w, h): add(f'') add(f'') add(f'') 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'') add(f'') # torso (chupa) add(f'') # cresta (detrás) + cabeza if pose in ("fist", "cresta"): for i in range(5): dx = (i - 2) * 11 * s add(f'') add(f'') # brazos ls, rs = (x - 27 * s, shy + 8 * s), (x + 27 * s, shy + 8 * s) if pose == "fist": add(f'') add(f'') add(f'') elif pose.startswith("pole"): side = -1 if pose == "poleL" else 1 px = x + side * 46 * s add(f'') add(f'') else: add(f'') add(f'') add(f'') add(f'') # 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'') for i in range(-2, 14): x = i * 100 add(f'') # 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'') add(text_path("¡A LA CALLE!", 600, by + 86, 74, INK)) drips(bx0 + 40, bx1 - 40, by + 122, INK, 6) add(f'') add(f'') # 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'') add('') open(OUT, "w").write("\n".join(out))