chore: keep the logo generator in the repository
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8db69c5af9
commit
c00134f2eb
7 files changed
+130
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import math
|
||||
import os
|
||||
|
||||
def squircle(size=64, n=5.0, steps=90):
|
||||
r = size/2
|
||||
pts=[]
|
||||
for i in range(steps*4):
|
||||
t = 2*math.pi*i/(steps*4)
|
||||
c, s = math.cos(t), math.sin(t)
|
||||
pts.append((r*math.copysign(abs(c)**(2/n), c)+r, r*math.copysign(abs(s)**(2/n), s)+r))
|
||||
return "M"+" L".join(f"{x:.2f} {y:.2f}" for x,y in pts)+"Z"
|
||||
|
||||
def norm(v):
|
||||
l=math.hypot(*v); return (v[0]/l, v[1]/l)
|
||||
|
||||
def lam(top=(19.2,10.0), bot=(44.8,54.0), foot=(21.0,54.0), join_t=0.50, w=6.8):
|
||||
"""A lambda drawn the way a type designer builds one: two straight strokes of
|
||||
one weight, the long one running top-left to bottom-right and the short one
|
||||
leaving it part way down and landing at the bottom-left, every end cut
|
||||
square to the baseline, and no part of one stroke poking out of the other."""
|
||||
(tx,ty),(bx,by)=top,bot
|
||||
# --- the long stroke: a parallelogram cut horizontally at both ends
|
||||
d=norm((bx-tx,by-ty)); phi_h = w/abs(d[1]) # horizontal thickness
|
||||
def long_x(y): return tx+(bx-tx)*(y-ty)/(by-ty)
|
||||
long_poly=[(tx-phi_h/2,ty),(tx+phi_h/2,ty),(bx+phi_h/2,by),(bx-phi_h/2,by)]
|
||||
# --- the short stroke starts on the long one's centre line
|
||||
jy=ty+(by-ty)*join_t; jx=long_x(jy)
|
||||
sd=norm((foot[0]-jx,foot[1]-jy)) # direction of the short stroke
|
||||
sh = w/abs(sd[1]) # its horizontal thickness
|
||||
nrm=(-sd[1], sd[0]) # unit normal
|
||||
# the two edges of the short stroke, as lines through (jx,jy)+-nrm*w/2
|
||||
def edge_pt(sign, y):
|
||||
ox,oy=jx+sign*nrm[0]*w/2, jy+sign*nrm[1]*w/2
|
||||
t=(y-oy)/sd[1]; return (ox+sd[0]*t, y)
|
||||
# its top end follows the long stroke's own direction, through the join point,
|
||||
# so it ends inside the long stroke instead of sticking out of it
|
||||
def on_long_dir(sign):
|
||||
ox,oy=jx+sign*nrm[0]*w/2, jy+sign*nrm[1]*w/2
|
||||
# intersect edge line (o + sd*t) with the line (j + d*s)
|
||||
a=sd[0]*(-d[1]) - sd[1]*(-d[0]);
|
||||
t=((jx-ox)*(-d[1]) - (jy-oy)*(-d[0]))/a
|
||||
return (ox+sd[0]*t, oy+sd[1]*t)
|
||||
p1,p2=on_long_dir(+1),on_long_dir(-1)
|
||||
q1,q2=edge_pt(+1,foot[1]),edge_pt(-1,foot[1])
|
||||
short_poly=[p1,p2,q2,q1]
|
||||
return long_poly, short_poly
|
||||
|
||||
def path(*polys):
|
||||
return " ".join("M"+" L".join(f"{x:.2f} {y:.2f}" for x,y in p)+"Z" for p in polys)
|
||||
|
||||
BLUE="#1B2BD0"
|
||||
def svg(bg=BLUE, fg="#FFFFFF", size=64):
|
||||
l,s=lam()
|
||||
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="{size}" height="{size}">'
|
||||
f'<path d="{squircle()}" fill="{bg}"/><path d="{path(l,s)}" fill="{fg}"/></svg>')
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Writes services/webmail/src/app/icon.svg and the SVGs the PNG step renders.
|
||||
# Run: python3 scripts/brand/mark.py then node scripts/brand/icons.mjs
|
||||
# ---------------------------------------------------------------------------
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
WEBMAIL = os.path.join(HERE, "..", "..", "services", "webmail")
|
||||
|
||||
def write_all():
|
||||
l, s = lam()
|
||||
glyph = path(l, s)
|
||||
shift = 'transform="translate(-0.9 0)"' # centres the glyph, not its bounding box
|
||||
tile = f'<path d="{squircle()}" fill="{BLUE}"/>'
|
||||
head = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">'
|
||||
icon = f'{head}\n <!-- a lambda built as two square-cut strokes, on a tile with continuous corners -->\n {tile}\n <path {shift} d="{glyph}" fill="#fff"/>\n</svg>\n'
|
||||
full = f'{head}<rect width="64" height="64" fill="{BLUE}"/><path {shift} d="{glyph}" fill="#fff"/></svg>'
|
||||
safe = f'{head}<rect width="64" height="64" fill="{BLUE}"/><g transform="translate(32 32) scale(0.78) translate(-32 -32)"><path {shift} d="{glyph}" fill="#fff"/></g></svg>'
|
||||
open(os.path.join(WEBMAIL, "src", "app", "icon.svg"), "w").write(icon)
|
||||
for name, text in (("logo.svg", icon), ("logo-full.svg", full), ("logo-safe.svg", safe)):
|
||||
open(os.path.join(HERE, name), "w").write(text)
|
||||
print("glyph:", glyph)
|
||||
|
||||
if __name__ == "__main__":
|
||||
write_all()
|
||||
Reference in new issue
Block a user