#!/usr/bin/env python3 """Generate Provenance brand assets as portable, font-independent SVGs.""" import os from matplotlib.textpath import TextPath from matplotlib.font_manager import FontProperties from matplotlib.path import Path SERIF = "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf" SANS = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf" INK = "#1A1A17" INK_DARK = "#F2EEE6" BRONZE = "#A06A42" PAPER = "#F7F3EC" MUTED = "#6B6862" MUTED_DARK = "#9A968E" OUT = os.path.join(os.path.dirname(os.path.abspath(__file__))) os.makedirs(OUT, exist_ok=True) def text_to_path(s, font_path, size): """Return (d_string, width, height). Coordinates: top-left origin, y down.""" fp = FontProperties(fname=font_path) tp = TextPath((0, 0), s, size=size, prop=fp) verts, codes = tp.vertices, tp.codes xs = verts[:, 0] ys = verts[:, 1] xmin, xmax = xs.min(), xs.max() ymin, ymax = ys.min(), ys.max() def tx(x): return round(float(x - xmin), 2) def ty(y): return round(float(ymax - y), 2) d = [] i = 0 n = len(codes) while i < n: c = codes[i] if c == Path.MOVETO: x, y = verts[i] d.append(f"M{tx(x)} {ty(y)}") i += 1 elif c == Path.LINETO: x, y = verts[i] d.append(f"L{tx(x)} {ty(y)}") i += 1 elif c == Path.CURVE3: x1, y1 = verts[i] x2, y2 = verts[i + 1] d.append(f"Q{tx(x1)} {ty(y1)} {tx(x2)} {ty(y2)}") i += 2 elif c == Path.CURVE4: x1, y1 = verts[i] x2, y2 = verts[i + 1] x3, y3 = verts[i + 2] d.append(f"C{tx(x1)} {ty(y1)} {tx(x2)} {ty(y2)} {tx(x3)} {ty(y3)}") i += 3 elif c == Path.CLOSEPOLY: d.append("Z") i += 1 else: i += 1 return "".join(d), round(float(xmax - xmin), 2), round(float(ymax - ymin), 2) def origin_mark(cx, cy, R, conn_sw=1.5, tick_sw=2): """Origin/survey-datum mark primitives as an SVG fragment string. Ink elements get class='ink'; bronze elements class='br'.""" import math diag = R * 0.7071 nodes = [(cx + diag, cy - diag), (cx - diag, cy - diag), (cx + diag, cy + diag), (cx - diag, cy + diag)] parts = [] # ring (bronze) parts.append(f'') # cardinal ticks (bronze) crossing the ring t_in, t_out = R - 4, R + 4 for (dx, dy) in [(0, -1), (0, 1), (1, 0), (-1, 0)]: x1, y1 = cx + dx * t_in, cy + dy * t_in x2, y2 = cx + dx * t_out, cy + dy * t_out parts.append(f'') # connectors (ink) center -> diagonal nodes for (nx, ny) in nodes: parts.append(f'') # diagonal nodes (ink) nr = max(2.0, R * 0.105) for (nx, ny) in nodes: parts.append(f'') # center dot (ink) parts.append(f'') return "\n".join(parts) STYLE = f""".ink{{fill:{INK}}}.ink-s{{stroke:{INK}}}.br{{fill:{BRONZE}}}.br-s{{stroke:{BRONZE}}}.muted{{fill:{MUTED}}} @media (prefers-color-scheme:dark){{.ink{{fill:{INK_DARK}}}.ink-s{{stroke:{INK_DARK}}}.muted{{fill:{MUTED_DARK}}}}}""" # ---- 1. Primary horizontal lockup ---- WM_SIZE = 64 TAG_SIZE = 15 wm_d, wm_w, wm_h = text_to_path("Provenance", SERIF, WM_SIZE) tag_d, tag_w, tag_h = text_to_path("where it came from matters", SANS, TAG_SIZE) pad = 28 R = 30 mark_box = 2 * (R + 6) gap = 26 block_gap = 12 text_block_h = wm_h + block_gap + tag_h content_h = max(mark_box, text_block_h) H = round(pad * 2 + content_h, 2) mark_cx = pad + (R + 6) mark_cy = round(H / 2, 2) text_x = pad + mark_box + gap W = round(text_x + max(wm_w, tag_w) + pad, 2) # vertically center the text block block_top = (H - text_block_h) / 2 wm_y = round(block_top, 2) tag_y = round(block_top + wm_h + block_gap, 2) logo = f''' Provenance {origin_mark(mark_cx, mark_cy, R)} ''' open(f"{OUT}/provenance-logo.svg", "w").write(logo) # ---- 1b. Lockup without tagline ---- H2 = round(pad * 2 + max(mark_box, wm_h), 2) mcy2 = round(H2 / 2, 2) wm_y2 = round((H2 - wm_h) / 2, 2) W2 = round(text_x + wm_w + pad, 2) logo2 = f''' Provenance {origin_mark(mark_cx, mcy2, R)} ''' open(f"{OUT}/provenance-logo-plain.svg", "w").write(logo2) # ---- 2. Mark only (square) ---- S = 96 c = S / 2 markonly = f''' Provenance mark {origin_mark(c, c, 34, conn_sw=1.6, tick_sw=2.2)} ''' open(f"{OUT}/provenance-mark.svg", "w").write(markonly) # ---- 3. App icon / monogram tile (square) ---- def monogram(side, radius_ratio=0.22): p_size = side * 0.62 pd, pw, ph = text_to_path("P", SERIF, p_size) px = round((side - pw) / 2, 2) py = round((side - ph) / 2, 2) rx = round(side * radius_ratio, 2) return f''' Provenance icon ''' open(f"{OUT}/provenance-icon.svg", "w").write(monogram(512)) open(f"{OUT}/favicon.svg", "w").write(monogram(48, radius_ratio=0.18)) print("WROTE:") for f in sorted(os.listdir(OUT)): p = os.path.join(OUT, f) print(f" {f} ({os.path.getsize(p)} bytes)") print(f"\nlogo lockup: {W} x {H}") print(f"wordmark path size: w={wm_w} h={wm_h}") print(f"tagline path size: w={tag_w} h={tag_h}")