89 lines
2.9 KiB
Python
89 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Draw desktop/icon-source.png — the 1024² master the icon set is cut from.
|
|
|
|
The wiki's own mark is the ∑ in the sidebar (AppHeader.vue's .logo-mark) over
|
|
the dark reading surface, so the app icon is that: the sigma, in the accent the
|
|
wiki wears by default, on the dark navy of its dark theme.
|
|
|
|
python3 desktop/make-icon.py # writes desktop/icon-source.png
|
|
npm run desktop:icons # cuts src-tauri/icons/ from it
|
|
|
|
Requires Pillow (pip install pillow). Nothing else in the project uses it —
|
|
this is build tooling, not app code.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
SIZE = 1024
|
|
PAD = 64 # the margin macOS wants round an app icon; harmless elsewhere
|
|
RADIUS = 200
|
|
TOP = (16, 21, 44) # the navy a touch above --bg, so the square reads as an object
|
|
BOTTOM = (6, 8, 14) # ... down to just under it
|
|
ACCENT = (124, 140, 255) # --accent, dark theme (#7c8cff)
|
|
MARK = "\u2211" # ∑ — the wiki's mark
|
|
|
|
FONTS = [
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf",
|
|
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
|
"/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf",
|
|
]
|
|
|
|
OUT = Path(__file__).resolve().parent / "icon-source.png"
|
|
|
|
|
|
def load_font(size: int) -> ImageFont.FreeTypeFont:
|
|
"""The first system font that actually carries a sigma."""
|
|
for path in FONTS:
|
|
try:
|
|
font = ImageFont.truetype(path, size)
|
|
except OSError:
|
|
continue
|
|
if font.getbbox(MARK)[2] > 0:
|
|
return font
|
|
raise SystemExit(f"no font with a {MARK} found — look for one of: {', '.join(FONTS)}")
|
|
|
|
|
|
def main() -> None:
|
|
# vertical gradient, then clipped to a rounded square
|
|
gradient = Image.new("RGB", (1, SIZE))
|
|
for y in range(SIZE):
|
|
t = y / (SIZE - 1)
|
|
gradient.putpixel((0, y), tuple(round(a + (b - a) * t) for a, b in zip(TOP, BOTTOM)))
|
|
gradient = gradient.resize((SIZE, SIZE))
|
|
|
|
mask = Image.new("L", (SIZE, SIZE), 0)
|
|
ImageDraw.Draw(mask).rounded_rectangle(
|
|
[PAD, PAD, SIZE - PAD, SIZE - PAD], radius=RADIUS, fill=255
|
|
)
|
|
|
|
icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
|
icon.paste(gradient, (0, 0), mask)
|
|
|
|
draw = ImageDraw.Draw(icon)
|
|
# a hairline of accent light along the edge, so the dark square does not
|
|
# vanish into a dark taskbar
|
|
draw.rounded_rectangle(
|
|
[PAD, PAD, SIZE - PAD, SIZE - PAD], radius=RADIUS, outline=(124, 140, 255, 56), width=3
|
|
)
|
|
|
|
font = load_font(600)
|
|
left, top, right, bottom = draw.textbbox((0, 0), MARK, font=font)
|
|
w, h = right - left, bottom - top
|
|
# centred on the box the glyph is drawn in, not on its typographic cell:
|
|
# an ascent-based centre would sit the sigma high
|
|
draw.text(
|
|
((SIZE - w) / 2 - left, (SIZE - h) / 2 - top),
|
|
MARK,
|
|
font=font,
|
|
fill=ACCENT + (255,),
|
|
)
|
|
|
|
icon.save(OUT)
|
|
print(f"wrote {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|