76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""@brief Render a command and its output as a terminal-style PNG screenshot.
|
|
|
|
The screenshots of the README and the wiki are made with this script, so no
|
|
desktop is captured. Needs Pillow (`pip install -e ".[dev]"`).
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
BACKGROUND = (30, 30, 30)
|
|
TITLE_BAR = (50, 50, 50)
|
|
TEXT = (212, 212, 212)
|
|
PROMPT = (96, 200, 96)
|
|
FONT_SIZE = 16
|
|
PADDING = 16
|
|
BAR_HEIGHT = 30
|
|
MAX_COLUMNS = 110
|
|
MAX_LINES = 40
|
|
FONT_FILES = (
|
|
"consola.ttf",
|
|
"DejaVuSansMono.ttf",
|
|
"LiberationMono-Regular.ttf",
|
|
"Menlo.ttc",
|
|
)
|
|
|
|
|
|
def _load_font() -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
|
|
for name in FONT_FILES:
|
|
try:
|
|
return ImageFont.truetype(name, FONT_SIZE)
|
|
except OSError:
|
|
continue
|
|
return ImageFont.load_default()
|
|
|
|
|
|
def _fit(lines: list[str]) -> list[str]:
|
|
fitted = [
|
|
line if len(line) <= MAX_COLUMNS else line[: MAX_COLUMNS - 3] + "..."
|
|
for line in lines
|
|
]
|
|
if len(fitted) > MAX_LINES:
|
|
fitted = fitted[:MAX_LINES] + ["..."]
|
|
return fitted
|
|
|
|
|
|
def render(command: str, output: str, target: Path) -> None:
|
|
"""@brief Draw the command and its output and save the PNG.
|
|
|
|
@param command The command line shown after the prompt.
|
|
@param output The text the command printed.
|
|
@param target The PNG file to write.
|
|
"""
|
|
font = _load_font()
|
|
lines = _fit(output.rstrip().splitlines())
|
|
prompt = "$ "
|
|
columns = max([len(prompt + command), *(len(line) for line in lines)])
|
|
char_width = font.getlength("M")
|
|
line_height = FONT_SIZE + 6
|
|
width = int(columns * char_width) + 2 * PADDING
|
|
height = BAR_HEIGHT + 2 * PADDING + line_height * (len(lines) + 1)
|
|
image = Image.new("RGB", (max(width, 480), height), BACKGROUND)
|
|
draw = ImageDraw.Draw(image)
|
|
draw.rectangle((0, 0, image.width, BAR_HEIGHT), fill=TITLE_BAR)
|
|
for index, color in enumerate(((255, 95, 86), (255, 189, 46), (39, 201, 63))):
|
|
x = PADDING + index * 22
|
|
draw.ellipse((x, 9, x + 12, 21), fill=color)
|
|
y = BAR_HEIGHT + PADDING
|
|
draw.text((PADDING, y), prompt, font=font, fill=PROMPT)
|
|
draw.text((PADDING + font.getlength(prompt), y), command, font=font, fill=TEXT)
|
|
for line in lines:
|
|
y += line_height
|
|
draw.text((PADDING, y), line, font=font, fill=TEXT)
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
image.save(target)
|