mirror of
https://github.com/Gathin23/cleanyourtv.git
synced 2026-10-26 19:47:52 +00:00
docs/workflows/press.md tells a contributor their font path is the copy of Fredoka they downloaded, and the guard added for a missing one answers only for a path that is not there. A path that is there but is not the variable font reached Pillow: Google Fonts serves Fredoka as a zip, so the zip was 'OSError: unknown file format', and one of the static single-weight files beside it has no weight axis, so it was 'OSError: invalid argument' - a traceback in the middle of a draw that names neither the argument nor what it should have been. All three scripts now open the font once, before anything is drawn, and answer both with the file named and what it has to be.
89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
"""Stacks the README stat strip, the Featured in panel and the before/after TVs into one card (docs/card.jpg),
|
|
the file the front page displays.
|
|
|
|
Run tools/readme_stats.py and tools/readme_featured.py first, then:
|
|
python3 tools/readme_card.py Fredoka.ttf docs/card.jpg
|
|
Fredoka is the variable TTF from Google Fonts, and its file is not in this repo: pass the path of the
|
|
copy you downloaded.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
USAGE = ("usage: readme_card.py <font.ttf> <out.jpg>\n"
|
|
" Fredoka, the variable TTF from Google Fonts, is not in this repo: download it and pass the\n"
|
|
" path of the file you saved.")
|
|
|
|
|
|
def font_and_out(argv):
|
|
"""The font and the picture, or a usage line that says where the font comes from.
|
|
|
|
The font is the one argument a reader brings themselves: it is not in this repo, so a wrong path is
|
|
the first thing that fails here, and Pillow's `OSError: cannot open resource` names neither the file
|
|
it wanted nor where to get it.
|
|
"""
|
|
if len(argv) != 3:
|
|
sys.exit(USAGE)
|
|
font, out = argv[1], argv[2]
|
|
if not os.path.isfile(font):
|
|
sys.exit(f"cannot open the font at {font}: Fredoka, the variable TTF from Google Fonts, is not in\n"
|
|
"this repo, so pass the path of the copy you downloaded")
|
|
return font, out
|
|
|
|
|
|
FONT, OUT = font_and_out(sys.argv)
|
|
|
|
# See readme_stats.py: Pillow lives in the optional requirements file, so name it rather than crash.
|
|
try:
|
|
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
|
except ImportError:
|
|
sys.exit("readme_card.py needs Pillow: tools/venv/bin/pip install -r tools/requirements-perf.txt")
|
|
|
|
BG, LINE, DIM, GREEN = "#15181e", "#2c313a", "#aab3bd", "#cfffcf"
|
|
W = 1600
|
|
def f(size, wt):
|
|
# A path that exists is not yet a font: Google Fonts serves Fredoka as a zip holding the variable
|
|
# `Fredoka[wdth,wght].ttf` and a folder of static single-weight files beside it. `truetype` answers a
|
|
# zip with `OSError: unknown file format` and `set_variation_by_axes` answers a static file with
|
|
# `OSError: invalid argument` - neither names the argument that was wrong, and both arrive as a
|
|
# Pillow traceback in the middle of a draw. Name the file and what it has to be instead.
|
|
try:
|
|
x = ImageFont.truetype(FONT, size); x.set_variation_by_axes([wt])
|
|
except (OSError, ValueError) as error:
|
|
sys.exit(f"cannot use the font at {FONT}: {error}. The card is drawn with Fredoka's variable TTF\n"
|
|
"(Fredoka[wdth,wght].ttf from Google Fonts), not the zip it is downloaded in and not one of\n"
|
|
"the static single-weight files beside it, which have no weight to set. Nothing was written.")
|
|
return x
|
|
f(12, 400) # the font is the argument the reader brings, so a wrong one is named before any drawing
|
|
|
|
def spaced(d, cx, y, t, font, sp, fill):
|
|
w = sum(d.textlength(c, font=font) for c in t) + sp * (len(t) - 1); x = cx - w / 2
|
|
for c in t: d.text((x, y), c, font=font, fill=fill); x += d.textlength(c, font=font) + sp
|
|
|
|
# the TV screens, cropped to the bezel; the Wi-Fi name on the after shot is blurred
|
|
before = Image.open("docs/stock-home.jpg").convert("RGB").crop((130, 216, 1317, 864))
|
|
after = Image.open("docs/new-home.jpg").convert("RGB")
|
|
box = (1150, 236, 1240, 280); after.paste(after.crop(box).filter(ImageFilter.GaussianBlur(9)), box)
|
|
after = after.crop((203, 138, 1467, 792))
|
|
PAD, ARROW = 70, 90
|
|
tw = (W - 2 * PAD - ARROW) // 2
|
|
shots = [im.resize((tw, round(im.height * tw / im.width)), Image.LANCZOS) for im in (before, after)]
|
|
th = max(s.height for s in shots)
|
|
tv = Image.new("RGB", (W, 90 + th + 80), BG); d = ImageDraw.Draw(tv)
|
|
lab = f(30, 500)
|
|
for i, (s, t, c) in enumerate(zip(shots, ("BEFORE", "AFTER"), (DIM, GREEN))):
|
|
x = PAD + i * (tw + ARROW); y = 90 + (th - s.height) // 2
|
|
spaced(d, x + tw / 2, 34, t, lab, 8, c)
|
|
tv.paste(s, (x, y)); d.rectangle([x - 1, y - 1, x + s.width, y + s.height], outline=LINE, width=2)
|
|
ax, ay = W / 2, 90 + th / 2
|
|
d.line([(ax - 26, ay), (ax + 26, ay)], fill=GREEN, width=5)
|
|
d.line([(ax + 8, ay - 16), (ax + 27, ay), (ax + 8, ay + 16)], fill=GREEN, width=5, joint="curve")
|
|
|
|
parts = [Image.open("docs/stats.png").convert("RGB"), Image.open("docs/featured.png").convert("RGB"), tv]
|
|
SEP = 24 # extra air on each side of a divider
|
|
H = sum(p.height for p in parts) + 2 * SEP * (len(parts) - 1)
|
|
card = Image.new("RGB", (W, H), BG); d = ImageDraw.Draw(card); y = 0
|
|
for i, p in enumerate(parts):
|
|
if i: y += SEP; d.line([(PAD, y), (W - PAD, y)], fill=LINE, width=2); y += SEP
|
|
card.paste(p, (0, y)); y += p.height
|
|
card.save(OUT, quality=88, optimize=True)
|