import re import subprocess import sys def run_bytes(args): try: return subprocess.run(args, check=True, stdout=subprocess.PIPE).stdout except FileNotFoundError: sys.exit(f"missing required command: {args[0]}") except subprocess.CalledProcessError as exc: sys.exit(f"command failed ({exc.returncode}): {' '.join(args)}") def run_text(args): return run_bytes(args).decode("utf-8", "replace") def parse_ppm(data): idx = 0 def next_token(): nonlocal idx while idx < len(data) and data[idx] in b" \t\r\n": idx += 1 if idx < len(data) and data[idx] == ord("#"): while idx < len(data) and data[idx] not in b"\r\n": idx += 1 return next_token() start = idx while idx < len(data) and data[idx] not in b" \t\r\n": idx += 1 return data[start:idx] magic = next_token() if magic != b"P6": sys.exit("ImageMagick did not return binary PPM data") width = int(next_token()) height = int(next_token()) max_value = int(next_token()) if max_value != 255: sys.exit(f"unsupported PPM max value: {max_value}") while idx < len(data) and data[idx] in b" \t\r\n": idx += 1 return width, height, data[idx:] def ppm_from_magick(args): return parse_ppm(run_bytes(["magick", *args, "-alpha", "off", "-depth", "8", "ppm:-"])) def try_ppm_from_magick(args): command = ["magick", *args, "-alpha", "off", "-depth", "8", "ppm:-"] try: result = subprocess.run(command, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: sys.exit("missing required command: magick") if result.returncode != 0: return None, result.stderr.decode("utf-8", "replace").strip() try: return parse_ppm(result.stdout), "" except Exception as exc: return None, str(exc) def magick_fonts(): fonts = set() for line in run_text(["magick", "-list", "font"]).splitlines(): match = re.match(r"\s*Font:\s*(\S+)", line) if match: fonts.add(match.group(1)) return fonts def image_size(image_path): output = run_bytes(["magick", "identify", "-format", "%w %h", image_path]).decode("ascii") width, height = output.split() return int(width), int(height)