|
#!/usr/bin/env python3 |
|
"""Render a combined QWERTY + Colemak-DH keyboard image (ANSI). |
|
|
|
Drawn from scratch (no source screenshot needed) so the geometry matches a |
|
physical ANSI keyboard such as the ThinkPad X1 Carbon: full-width left Shift |
|
(no ISO "<>" key), single-row wide Enter, backslash on the top row. |
|
|
|
The Colemak-DH letters here reflect the *actual* output of GNOME's |
|
`us+colemak_dh` variant as verified by typing on ANSI hardware -- it is the |
|
ISO "angle-mod" flavour, so the left-hand bottom row is shifted: |
|
|
|
you press: z x c v b n m |
|
you get: x c d v z k h |
|
|
|
(If you ever switch to a non-angle Colemak-DH variant where z/x/c stay put, |
|
update the bottom row in ROWS below.) |
|
|
|
Deps: pillow (Ubuntu: python3-pil) |
|
Fonts: fonts-dejavu-core (/usr/share/fonts/truetype/dejavu/) |
|
|
|
Usage: |
|
python3 render_ansi.py [OUTPUT_PNG] |
|
""" |
|
import os |
|
import sys |
|
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
|
OUT = os.path.expanduser( |
|
sys.argv[1] if len(sys.argv) > 1 |
|
else "~/Pictures/Screenshots/us-qwerty-colemak-dh-ansi.png") |
|
|
|
FP = "/usr/share/fonts/truetype/dejavu/" |
|
BG = (36, 36, 36) |
|
FILL = (67, 67, 71) |
|
WHITE = (228, 228, 228) |
|
AMBER = (245, 175, 45) |
|
DIM = (150, 150, 150) |
|
SPC = (160, 160, 160) |
|
|
|
U = 64 # width of one key unit, px |
|
LEFT = 16 |
|
TOP = 80 |
|
PITCH = 59 # vertical distance between rows |
|
INSET = 4 # gap between adjacent keys |
|
RAD = 8 # key corner radius |
|
|
|
# Each key: (label, width_units, colemak). |
|
# colemak is None -> unchanged letter/number (white, centred) |
|
# 'sp' -> special/modifier key (grey word label) |
|
# '' -> blank key |
|
# <letter> -> differs: big amber colemak + small grey qwerty |
|
ROWS = [ |
|
[("`", 1, None), ("1", 1, None), ("2", 1, None), ("3", 1, None), |
|
("4", 1, None), ("5", 1, None), ("6", 1, None), ("7", 1, None), |
|
("8", 1, None), ("9", 1, None), ("0", 1, None), ("-", 1, None), |
|
("=", 1, None), ("Bksp", 2, "sp")], |
|
[("Tab", 1.5, "sp"), ("q", 1, None), ("w", 1, None), ("e", 1, "f"), |
|
("r", 1, "p"), ("t", 1, "b"), ("y", 1, "j"), ("u", 1, "l"), |
|
("i", 1, "u"), ("o", 1, "y"), ("p", 1, ";"), ("[", 1, None), |
|
("]", 1, None), ("\\", 1.5, "sp")], |
|
[("Caps", 1.75, "sp"), ("a", 1, None), ("s", 1, "r"), ("d", 1, "s"), |
|
("f", 1, "t"), ("g", 1, None), ("h", 1, "m"), ("j", 1, "n"), |
|
("k", 1, "e"), ("l", 1, "i"), (";", 1, "o"), ("'", 1, None), |
|
("Enter", 2.25, "sp")], |
|
[("Shift", 2.25, "sp"), ("z", 1, "x"), ("x", 1, "c"), ("c", 1, "d"), |
|
("v", 1, None), ("b", 1, "z"), ("n", 1, "k"), ("m", 1, "h"), |
|
(",", 1, None), (".", 1, None), ("/", 1, None), ("Shift", 2.75, "sp")], |
|
[("Ctrl", 1.25, "sp"), ("", 1.25, ""), ("Alt", 1.25, "sp"), |
|
("", 6.25, ""), ("Alt", 1.25, "sp"), ("", 1.25, ""), ("", 1.25, ""), |
|
("Ctrl", 1.25, "sp")], |
|
] |
|
|
|
|
|
def main(): |
|
f_num = ImageFont.truetype(FP + "DejaVuSans.ttf", 22) |
|
f_big = ImageFont.truetype(FP + "DejaVuSans-Bold.ttf", 24) |
|
f_small = ImageFont.truetype(FP + "DejaVuSans.ttf", 14) |
|
f_sp = ImageFont.truetype(FP + "DejaVuSans.ttf", 14) |
|
f_title = ImageFont.truetype(FP + "DejaVuSans.ttf", 20) |
|
f_leg = ImageFont.truetype(FP + "DejaVuSans.ttf", 17) |
|
f_legb = ImageFont.truetype(FP + "DejaVuSans-Bold.ttf", 17) |
|
|
|
w = 993 |
|
h = TOP + len(ROWS) * PITCH + 8 + 46 |
|
img = Image.new("RGB", (w, h), BG) |
|
d = ImageDraw.Draw(img) |
|
|
|
d.text((w // 2, 30), "Keyboard Layout — English (US · ANSI)", |
|
font=f_title, fill=WHITE, anchor="mm") |
|
|
|
for r, row in enumerate(ROWS): |
|
yslot = TOP + r * PITCH |
|
x = LEFT |
|
for label, units, cm in row: |
|
kw = units * U |
|
x0, y0 = x + INSET, yslot + INSET |
|
x1, y1 = x + kw - INSET, yslot + PITCH - INSET |
|
d.rounded_rectangle([x0, y0, x1, y1], radius=RAD, fill=FILL) |
|
cxk, cyk = (x0 + x1) // 2, (y0 + y1) // 2 |
|
if cm is None: |
|
d.text((cxk, cyk), label, font=f_num, fill=WHITE, anchor="mm") |
|
elif cm == "sp": |
|
d.text((cxk, cyk), label, font=f_sp, fill=SPC, anchor="mm") |
|
elif cm == "": |
|
pass |
|
else: |
|
d.rounded_rectangle([x0 + 1, y0 + 1, x1 - 1, y1 - 1], |
|
radius=RAD + 1, outline=AMBER, width=2) |
|
d.text((cxk, cyk + 4), cm, font=f_big, fill=AMBER, anchor="mm") |
|
d.text((x0 + 7, y0 + 6), label, font=f_small, fill=DIM, |
|
anchor="lt") |
|
x += kw |
|
|
|
_legend(d, h, f_small, f_leg, f_legb) |
|
|
|
img.save(OUT) |
|
print("saved", OUT, img.size) |
|
|
|
|
|
def _legend(d, height, f_small, f_leg, f_legb): |
|
ly, lx = height - 23, 20 |
|
d.rounded_rectangle([lx, ly - 11, lx + 22, ly + 11], radius=5, |
|
fill=FILL, outline=AMBER, width=2) |
|
d.text((lx + 13, ly + 3), "f", |
|
font=ImageFont.truetype(FP + "DejaVuSans-Bold.ttf", 16), |
|
fill=AMBER, anchor="mm") |
|
d.text((lx + 4, ly - 10), "e", font=f_small, fill=DIM, anchor="lt") |
|
lx += 36 |
|
for text, font, colour, gap in [ |
|
("big amber = Colemak-DH", f_legb, AMBER, 18), |
|
("small grey = QWERTY", f_leg, DIM, 18), |
|
("· only differing keys marked", f_leg, (120, 120, 120), 0), |
|
]: |
|
d.text((lx, ly), text, font=font, fill=colour, anchor="lm") |
|
lx += d.textlength(text, font=font) + gap |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |