2022-12-07 16:12:24 -06:00
|
|
|
#!/usr/bin/env python3
|
2021-11-09 12:02:47 -06:00
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
import argparse
|
|
|
|
import itertools
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
from pathlib import Path
|
|
|
|
from typing import List, Set
|
2021-11-09 12:02:47 -06:00
|
|
|
|
2023-03-20 01:33:21 -05:00
|
|
|
FONT_SRC = (Path(__file__).parent / "MonoLisa").absolute()
|
2021-11-09 12:02:47 -06:00
|
|
|
|
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
class Color:
|
|
|
|
def __init__(self):
|
2022-12-13 00:12:00 -06:00
|
|
|
self.bold = "\033[1m"
|
2022-12-07 16:12:24 -06:00
|
|
|
self.red = "\033[1;31m"
|
|
|
|
self.green = "\033[1;32m"
|
|
|
|
self.yellow = "\033[1;33m"
|
|
|
|
self.magenta = "\033[1;35m"
|
|
|
|
self.cyan = "\033[1;36m"
|
|
|
|
self.end = "\033[0m"
|
2021-11-09 12:02:47 -06:00
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
if os.getenv("NO_COLOR"):
|
|
|
|
for attr in self.__dict__:
|
|
|
|
setattr(self, attr, "")
|
2021-11-09 12:02:47 -06:00
|
|
|
|
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
class Spinner:
|
|
|
|
# https://raw.githubusercontent.com/Tagar/stuff/master/spinner.py
|
|
|
|
def __init__(self, message, delay=0.1):
|
|
|
|
# self.spinner = itertools.cycle(["-", "/", "|", "\\"])
|
|
|
|
self.spinner = itertools.cycle([f"{c} " for c in "⣾⣽⣻⢿⡿⣟⣯⣷"])
|
|
|
|
|
|
|
|
self.delay = delay
|
|
|
|
self.busy = False
|
|
|
|
self.spinner_visible = False
|
|
|
|
sys.stdout.write(message)
|
|
|
|
|
|
|
|
def write_next(self):
|
|
|
|
with self._screen_lock:
|
|
|
|
if not self.spinner_visible:
|
|
|
|
sys.stdout.write(next(self.spinner))
|
|
|
|
self.spinner_visible = True
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
def remove_spinner(self, cleanup=False):
|
|
|
|
with self._screen_lock:
|
|
|
|
if self.spinner_visible:
|
|
|
|
# sys.stdout.write("\b")
|
|
|
|
sys.stdout.write("\b\b\b")
|
|
|
|
self.spinner_visible = False
|
|
|
|
if cleanup:
|
|
|
|
sys.stdout.write(" ") # overwrite spinner with blank
|
|
|
|
# sys.stdout.write("\r") # move to next line
|
|
|
|
sys.stdout.write("\r\033[K")
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
def spinner_task(self):
|
|
|
|
while self.busy:
|
|
|
|
self.write_next()
|
|
|
|
time.sleep(self.delay)
|
|
|
|
self.remove_spinner()
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
if sys.stdout.isatty():
|
|
|
|
self._screen_lock = threading.Lock()
|
|
|
|
self.busy = True
|
|
|
|
self.thread = threading.Thread(target=self.spinner_task)
|
|
|
|
self.thread.start()
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_traceback):
|
|
|
|
if sys.stdout.isatty():
|
|
|
|
self.busy = False
|
|
|
|
self.remove_spinner(cleanup=True)
|
|
|
|
else:
|
|
|
|
sys.stdout.write("\r")
|
|
|
|
|
|
|
|
|
|
|
|
def run_cmd(
|
2022-12-13 00:12:00 -06:00
|
|
|
command: List[str], fontfile: Path, verbose: bool, ignore_error: bool = False
|
2022-12-07 16:12:24 -06:00
|
|
|
) -> None:
|
|
|
|
"""run a subcommand
|
|
|
|
Args:
|
|
|
|
command: Subcommand to be run in subprocess.
|
|
|
|
fontfile: Path to font file that will be patched
|
|
|
|
verbose: If true, print subcommand output.
|
|
|
|
"""
|
|
|
|
|
|
|
|
p = subprocess.run(
|
2022-12-13 00:12:00 -06:00
|
|
|
command,
|
2022-12-07 16:12:24 -06:00
|
|
|
stdout=None if verbose else subprocess.PIPE,
|
|
|
|
stderr=None if verbose else subprocess.STDOUT,
|
|
|
|
universal_newlines=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
if p.returncode != 0 and not ignore_error:
|
|
|
|
print()
|
|
|
|
print(p.stdout)
|
|
|
|
err_msg = (
|
|
|
|
f"{color.red}ERROR{color.end}: patching font file "
|
|
|
|
f"{fontfile.name} see above for font-patcher output"
|
|
|
|
)
|
|
|
|
echo(err_msg, hue="red")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def get_args():
|
|
|
|
parser = argparse.ArgumentParser()
|
2022-12-11 17:45:24 -06:00
|
|
|
parser.add_argument(
|
|
|
|
"-f",
|
|
|
|
"--font-path",
|
|
|
|
help="path to font files",
|
|
|
|
action="append",
|
|
|
|
type=Path,
|
|
|
|
required=True,
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-o", "--output", help="target output directory", default="patched", type=Path
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-d", "--docker", help="use nerdfonts/patcher docker image", action="store_true"
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-v", "--verbose", help="show subprocess output", action="store_true"
|
|
|
|
)
|
2022-12-07 16:12:24 -06:00
|
|
|
return parser.parse_known_args()
|
|
|
|
|
|
|
|
|
|
|
|
def patch_single_font(
|
|
|
|
font_path: Path, output_dir: Path, fp_args: str, verbose: bool
|
|
|
|
) -> None:
|
2023-01-03 13:19:40 -06:00
|
|
|
# update display name to full resolved path
|
|
|
|
rel_path = font_path.absolute().relative_to(FONT_SRC)
|
|
|
|
output_path = (output_dir / rel_path).parent
|
|
|
|
output_path.mkdir(exist_ok=True, parents=True)
|
2022-12-07 16:12:24 -06:00
|
|
|
|
2022-12-13 00:12:00 -06:00
|
|
|
cmd = [
|
|
|
|
"fontforge",
|
|
|
|
"-script",
|
2023-05-02 14:57:12 -05:00
|
|
|
"./font-patcher",
|
2022-12-13 00:12:00 -06:00
|
|
|
"--glyphdir",
|
|
|
|
"./src/glyphs/",
|
|
|
|
"-out",
|
|
|
|
f"{output_path}",
|
2023-01-03 13:19:40 -06:00
|
|
|
f"{font_path.absolute()}",
|
2022-12-13 00:12:00 -06:00
|
|
|
]
|
2022-12-07 16:12:24 -06:00
|
|
|
|
2023-01-03 13:19:40 -06:00
|
|
|
if fp_args:
|
|
|
|
cmd += fp_args.split(" ")
|
2023-01-07 10:14:43 -06:00
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
if verbose:
|
2022-12-13 00:12:00 -06:00
|
|
|
echo(f"cmd: {' '.join(cmd)}")
|
2022-12-07 16:12:24 -06:00
|
|
|
run_cmd(cmd, font_path, verbose)
|
|
|
|
else:
|
2022-12-13 00:12:00 -06:00
|
|
|
with Spinner(
|
|
|
|
f"{color.yellow}:::{color.end} Patching font "
|
|
|
|
f"{color.bold}{font_path.name}{color.end}... "
|
|
|
|
):
|
2022-12-07 16:12:24 -06:00
|
|
|
run_cmd(cmd, font_path, verbose)
|
|
|
|
|
2023-01-03 13:19:40 -06:00
|
|
|
echo(f"{rel_path} patched!", hue="green")
|
2022-12-07 16:12:24 -06:00
|
|
|
|
|
|
|
|
|
|
|
def collect_files_by_dir(fontfiles: List[Path]) -> Set[Path]:
|
2023-01-03 13:19:40 -06:00
|
|
|
return set([f.parent.absolute() for f in fontfiles])
|
2022-12-07 16:12:24 -06:00
|
|
|
|
|
|
|
|
|
|
|
def patch_font_dir_docker(
|
|
|
|
font_dir_path: Path, output_dir: Path, fp_args: str, verbose: bool
|
|
|
|
) -> None:
|
2023-01-03 13:19:40 -06:00
|
|
|
output_path = (output_dir / font_dir_path.relative_to(FONT_SRC)).absolute()
|
|
|
|
output_path.mkdir(exist_ok=True, parents=True)
|
2022-12-07 16:12:24 -06:00
|
|
|
|
2022-12-13 00:12:00 -06:00
|
|
|
cmd = [
|
|
|
|
"docker",
|
|
|
|
"run",
|
|
|
|
"--rm",
|
|
|
|
"-v",
|
|
|
|
f"{font_dir_path}:/in",
|
|
|
|
"-v",
|
|
|
|
f"{output_path}:/out",
|
|
|
|
"nerdfonts/patcher",
|
|
|
|
]
|
2022-12-07 16:12:24 -06:00
|
|
|
|
2023-01-03 13:19:40 -06:00
|
|
|
if fp_args:
|
|
|
|
cmd += fp_args.split(" ")
|
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
# ignoring the fact that docker exits with code 1
|
|
|
|
if verbose:
|
2022-12-13 00:12:00 -06:00
|
|
|
echo(f"cmd: {' '.join(cmd)}")
|
2022-12-07 16:12:24 -06:00
|
|
|
run_cmd(cmd, font_dir_path, verbose, ignore_error=True)
|
|
|
|
else:
|
|
|
|
with Spinner(
|
2022-12-13 00:12:00 -06:00
|
|
|
f"{color.yellow}:::{color.end} Patching fonts in "
|
|
|
|
f"{color.bold}{font_dir_path.name}{color.end}... "
|
2022-12-07 16:12:24 -06:00
|
|
|
):
|
|
|
|
run_cmd(cmd, font_dir_path, verbose, ignore_error=True)
|
|
|
|
|
|
|
|
echo(f"{font_dir_path.name}/ fonts patched!", hue="green")
|
|
|
|
|
|
|
|
|
2023-03-20 01:33:21 -05:00
|
|
|
def get_font_files(p):
|
|
|
|
exts = ["otf", "ttf", "woff", "woff2"]
|
|
|
|
|
|
|
|
if p.is_file():
|
|
|
|
return (p,)
|
|
|
|
|
|
|
|
if p.is_dir():
|
|
|
|
return (f for ext in exts for f in p.glob(f"**/*.{ext}"))
|
|
|
|
return ()
|
|
|
|
|
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
def echo(msg: str, header=False, hue="cyan") -> None:
|
|
|
|
if header:
|
|
|
|
print(f"==>{color.magenta} {msg} {color.end}<==")
|
|
|
|
else:
|
|
|
|
print(f"{color.__dict__[hue]}::{color.end} {msg}")
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
echo("MonoLisa NerdFont Patcher", header=True)
|
|
|
|
args, fp_args = get_args()
|
|
|
|
fp_args = " ".join(fp_args)
|
|
|
|
if fp_args:
|
|
|
|
echo(f"Flags passed to font-patcher: {fp_args}")
|
2023-01-03 11:35:53 -06:00
|
|
|
if args.verbose:
|
2023-01-07 10:14:43 -06:00
|
|
|
echo("Patching the following files")
|
2023-01-03 11:35:53 -06:00
|
|
|
for fontfile in args.font_path:
|
2023-01-03 13:19:40 -06:00
|
|
|
sys.stdout.write(f" {color.magenta}->{color.end} {fontfile}\n")
|
2023-03-20 01:33:21 -05:00
|
|
|
|
|
|
|
fontfiles = [f for p in args.font_path for f in get_font_files(p)]
|
|
|
|
|
2022-12-07 16:12:24 -06:00
|
|
|
if args.docker:
|
|
|
|
echo("==> DOCKER MODE ENABLED")
|
2023-03-20 01:33:21 -05:00
|
|
|
for font_dir in collect_files_by_dir(fontfiles):
|
2022-12-07 16:12:24 -06:00
|
|
|
patch_font_dir_docker(font_dir, args.output, fp_args, args.verbose)
|
|
|
|
else:
|
2023-03-20 01:33:21 -05:00
|
|
|
for fontfile in fontfiles:
|
2022-12-13 00:12:00 -06:00
|
|
|
patch_single_font(Path(fontfile), args.output, fp_args, args.verbose)
|
2022-12-07 16:12:24 -06:00
|
|
|
|
|
|
|
echo("fonts are patched", hue="green")
|
|
|
|
echo("Happy typing!", hue="green")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
color = Color()
|
|
|
|
main()
|