aboutsummaryrefslogtreecommitdiffstats
path: root/web/gen-card
blob: b303c293e6bcf7a39cdfc118958a0c762d09d619 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#!/usr/bin/env python3
"""Render the social-embed card, web/card.png, from logo.txt.

The card is what Discord/X/Slack show when someone pastes slosh.foo: a
1200x630 PNG of the logo in the site's palette (contrib/themes/sl0p.kdl --
hot pink on near-black) over the tagline. The logo is drawn as geometry,
exactly like web/gen-logo-mask does for the page (half-block cells, 6x10
units, horizontal runs merged, EPS overlap against seam pinstripes), so the
card can never disagree with the mask.

Like gen-logo-mask this is run by hand when logo.txt or the palette moves,
and the result is committed; build-site only copies it. Needs Pillow.
"""

import os
import sys

from PIL import Image, ImageDraw, ImageFont

HERE = os.path.dirname(os.path.abspath(__file__))
LOGO = os.path.join(os.path.dirname(HERE), "logo.txt")
OUT = os.path.join(HERE, "card.png")

# site.css :root, verbatim.
BG = "#0b0b0e"
FG = "#e8e8ea"
DIM = "#8a8a95"
PINK = "#ff5fd7"

W, H = 1200, 630
SS = 2  # supersample, for crisp downscaled edges and text

# One logo cell in device pixels (at 1x). 6x10 units in the mask; the same
# ratio here, just bigger.
CW, CH, EPS = 19, 32, 1.0

TAGLINE = "a modern terminal multiplexer"
SUBLINE = "bringing the bling back to your shells"
SITE = "https://slosh.foo"


def runs(row, want):
    out, start = [], None
    for i, ch in enumerate(row + " "):
        hit = ch in want
        if hit and start is None:
            start = i
        if not hit and start is not None:
            out.append((start, i))
            start = None
    return out


def font(size, *names):
    for name in names:
        for d in ("/usr/share/fonts/TTF", "/usr/share/fonts/truetype/dejavu"):
            p = os.path.join(d, name)
            if os.path.exists(p):
                return ImageFont.truetype(p, size)
    sys.exit(f"gen-card: none of {names} found -- install DejaVu fonts")


lines = open(LOGO).read().rstrip("\n").splitlines()
cols = max(len(l) for l in lines)

img = Image.new("RGB", (W * SS, H * SS), BG)
draw = ImageDraw.Draw(img)

cw, ch, eps = CW * SS, CH * SS, EPS * SS
lw, lh = cols * cw, len(lines) * ch
lx, ly = (W * SS - lw) // 2, 150 * SS

for j, row in enumerate(lines):
    for a, b in runs(row, "\u2580\u2588"):  # top halves
        draw.rectangle(
            [lx + a * cw, ly + j * ch - eps, lx + b * cw, ly + j * ch + ch / 2 + eps],
            fill=PINK,
        )
    for a, b in runs(row, "\u2584\u2588"):  # bottom halves
        draw.rectangle(
            [
                lx + a * cw,
                ly + j * ch + ch / 2 - eps,
                lx + b * cw,
                ly + (j + 1) * ch + eps,
            ],
            fill=PINK,
        )

mono = font(36 * SS, "DejaVuSansMono.ttf")
mono_sm = font(27 * SS, "DejaVuSansMono.ttf")


def centred(text, y, fnt, fill):
    tw = draw.textlength(text, font=fnt)
    draw.text(((W * SS - tw) / 2, y * SS), text, font=fnt, fill=fill)


centred(TAGLINE, 442, mono, FG)
centred(SUBLINE, 502, mono_sm, DIM)
centred(SITE, 560, mono_sm, PINK)

img = img.resize((W, H), Image.LANCZOS)
img.save(OUT, optimize=True)
print(
    f"gen-card: {W}x{H}, logo {lw // SS}x{lh // SS} at ({lx // SS},{ly // SS}) -> {os.path.relpath(OUT, HERE)}"
)