# -*- coding: utf-8 -*- """Generates the 100cIA brand kit. Text is converted to curves with Inkscape, so the SVGs do not depend on the font being installed.""" import os, re, subprocess, tempfile import xml.etree.ElementTree as ET HERE = os.path.dirname(os.path.abspath(__file__)) BG = "#0B0F1A" POINTS = "#243050" WHITE_C = "#FFFFFF" AMBER = "#FFD166" TRACK = "#1B2233" C_GRAY = "#8D99AE" FONT_NAME = "Roboto Black" TRACKING = -0.035 # em def curve_list(parts, size=400, tracking=TRACKING): """parts = [(text, color), ...] -> (inner_svg, x, y, w, h) already as curves.""" tspans = "".join( f'{t}' for i, (t, c) in enumerate(parts)) src = (f'{tspans}') with tempfile.TemporaryDirectory() as d: a, b = os.path.join(d, "a.svg"), os.path.join(d, "b.svg") open(a, "w", encoding="utf-8").write(src) subprocess.run(["inkscape", "--export-text-to-path", "--export-plain-svg", f"--export-filename={b}", a], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) raw = open(b, encoding="utf-8").read() q = {k: float(subprocess.run(["inkscape", "--query-id=w", f"--query-{k}", b], capture_output=True, text=True).stdout.strip()) for k in ("x", "y", "width", "height")} NS = "http://www.w3.org/2000/svg" ET.register_namespace("", NS) root = ET.fromstring(raw) node = next(e for e in root.iter() if e.get("id") == "w") for e in node.iter(): e.attrib.pop("id", None) e.attrib.pop("aria-label", None) body_obj = ET.tostring(node, encoding="unicode") body_obj = body_obj.replace(f' xmlns="{NS}"', "") if not body_obj.lstrip().startswith("{body_obj}" return body_obj, q["x"], q["y"], q["width"], q["height"] def place(g, bx, by, bw, bh, cx, cy, width_px): s = width_px / bw tx = cx - s * (bx + bw / 2) ty = cy - s * (by + bh / 2) return f'{g}' def point_list(id_, sep=118, r=4.2, color=POINTS, op=0.55): return (f'' f'') def arc_m(cx, cy, r, since, until, color, w): """Arc in degrees, 0 = up, clockwise.""" import math def p(a): t = math.radians(a - 90) return cx + r * math.cos(t), cy + r * math.sin(t) x0, y0 = p(since) x1, y1 = p(until) largo = 1 if (until - since) % 360 > 180 else 0 return (f'') # ---------------------------------------------------------------- avatar def avatar(file_path, chip=False, pct=0.62): D = 1024 R = D / 2 ring_r, ring_w = R - 26, 20 if chip: g1, x1, y1, w1, h1 = curve_list([("100c", WHITE_C)]) g2, x2, y2, w2, h2 = curve_list([("IA", "#0B0F1A")]) else: g1, x1, y1, w1, h1 = curve_list([("100c", WHITE_C), ("IA", AMBER)]) body_obj = [ f'', f'', f'', arc_m(R, R, ring_r, 0, 360 * pct, AMBER, ring_w), ] if chip: total_width = 760 # the "IA" chip takes ~38% of the width, like the chips in the videos w_text = total_width * 0.60 body_obj.append(place(g1, x1, y1, w1, h1, R - total_width / 2 + w_text / 2, R, w_text)) scale_f = w_text / w1 cw, ch = w2 * scale_f * 1.0 + 70, h1 * scale_f + 62 cxc = R - total_width / 2 + w_text + 26 + cw / 2 body_obj.append(f'') body_obj.append(place(g2, x2, y2, w2, h2, cxc, R, w2 * scale_f)) else: body_obj.append(place(g1, x1, y1, w1, h1, R, R, 740)) svg = (f'{point_list("dots")}' + "".join(body_obj) + '') open(os.path.join(HERE, file_path), "w", encoding="utf-8").write(svg) return file_path # ---------------------------------------------------------------- wordmark # The chip around "IA" is the brand: it goes on every variant except the # "flat" one, kept as an alternative. MODES = { # ink chip background chip letter "oscuro": (WHITE_C, AMBER, "#0B0F1A"), "claro": ("#0B0F1A", "#D98C00", "#FFFFFF"), "mono": (WHITE_C, WHITE_C, None), # knocked-out letters } def _lockup(mode, alto=520): """Returns (svg_pieces, width, height) of the '100c' lockup + 'IA' chip.""" ink, chip_bg, letter = MODES[mode] g1, x1, y1, w1, h1 = curve_list([("100c", ink)]) g2, x2, y2, w2, h2 = curve_list([("IA", letter or "#000000")]) scale_f = alto / h1 wt = w1 * scale_f cw = w2 * scale_f + 0.68 * alto ch = alto + 0.60 * alto gap_m = 0.13 * alto W = wt + gap_m + cw cy = ch / 2 rx = ch * 0.28 cx_chip = wt + gap_m + cw / 2 chip_x = wt + gap_m pieces = [place(g1, x1, y1, w1, h1, wt / 2, cy, wt)] if letter is None: # a single ink: the IA is knocked out mascara = (f'' f'' + place(g2, x2, y2, w2, h2, cx_chip, cy, w2 * scale_f) + '') pieces.append(f'{mascara}') pieces.append(f'') else: pieces.append(f'') pieces.append(place(g2, x2, y2, w2, h2, cx_chip, cy, w2 * scale_f)) return pieces, W, ch def wordmark(file_path, mode="oscuro", margin=0.10): pieces, W0, H0 = _lockup(mode) m = margin * H0 W, H = int(round(W0 + 2 * m)), int(round(H0 + 2 * m)) svg = (f'' + "".join(pieces) + '') open(os.path.join(HERE, file_path), "w", encoding="utf-8").write(svg) return file_path def wordmark_plano(file_path): """Chip-less alternative: '100cIA' in one run, the IA in amber.""" g, x, y, w, h = curve_list([("100c", WHITE_C), ("IA", AMBER)]) W = 2400 width_px = W * 0.88 H = int(round(h / w * width_px + 2 * 0.06 * W)) svg = (f'' + place(g, x, y, w, h, W / 2, H / 2, width_px) + '') open(os.path.join(HERE, file_path), "w", encoding="utf-8").write(svg) return file_path # ---------------------------------------------------------------- banner def banner(file_path, tagline="ciencia en 40 segundos"): W, H = 1920, 1080 g, x, y, w, h = curve_list([("100c", WHITE_C)]) t, tx, ty, tw, th = curve_list([(tagline, "#C9D3E5")], size=200, tracking=0.012) body_obj = [ f'', f'', f'', f'', None, # the lockup is built below (with the chip, like the avatar) place(t, tx, ty, tw, th, W / 2, H / 2 + 205, 830), ] # lockup with chip, centred, 1150 px total width g2, x2, y2, w2, h2 = curve_list([("IA", "#0B0F1A")]) WIDTH, cy = 1150, H / 2 - 95 alto = 210 scale_f = alto / h wt = w * scale_f cw, ch = w2 * scale_f + 0.68 * alto, alto + 0.60 * alto gap_m = 0.13 * alto total = wt + gap_m + cw k = WIDTH / total wt, cw, ch, gap_m = wt * k, cw * k, ch * k, gap_m * k left_part = W / 2 - WIDTH / 2 body_obj[body_obj.index(None)] = ( place(g, x, y, w, h, left_part + wt / 2, cy, wt) + f'' + place(g2, x2, y2, w2, h2, left_part + wt + gap_m + cw / 2, cy, w2 * scale_f * k)) svg = (f'{point_list("dots", sep=140, r=5)}' + "".join(body_obj) + '') open(os.path.join(HERE, file_path), "w", encoding="utf-8").write(svg) return file_path if __name__ == "__main__": # the chip variant is the brand: it keeps the main file names for f in (avatar("avatar.svg", chip=True), avatar("avatar-plano.svg"), wordmark("wordmark.svg", "oscuro"), wordmark("wordmark-claro.svg", "claro"), wordmark("wordmark-mono.svg", "mono"), wordmark_plano("wordmark-plano.svg"), banner("banner.svg")): print(" ->", f)