#!/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'''
'''
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'''
'''
open(f"{OUT}/provenance-logo-plain.svg", "w").write(logo2)
# ---- 2. Mark only (square) ----
S = 96
c = S / 2
markonly = f'''
'''
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'''
'''
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}")