Cozette/build.py
2023-06-11 05:04:35 +02:00

242 lines
7 KiB
Python

import argparse
import subprocess
from dataclasses import dataclass
from pathlib import Path
from shlex import quote
from shutil import rmtree
from typing import Optional, Sequence, cast
import crayons # type: ignore
from cozette_builder.changeloggen import get_changelog
from cozette_builder.hidpi import double_size
from cozette_builder.imagegen import (
add_margins,
read_sample,
save_charlist,
save_sample,
)
from cozette_builder.scanner import (
find_missing_codepoints,
print_codepoints_for_changelog,
scan_for_codepoints,
)
REPO_ROOT = Path(__file__).resolve().parent
BUILD_DIR = REPO_ROOT / "build"
FONTNAME = "Cozette"
SFDPATH = REPO_ROOT / "Cozette" / "Cozette.sfd"
@dataclass
class Generate:
filename: str
bitmap_fmt: Optional[str] = None
def __str__(self):
return (
f'Generate("{self.filename}", "{self.bitmap_fmt}")'
if self.bitmap_fmt
else f'Generate("{self.filename}")'
)
def save_images():
print(crayons.yellow("Saving character map"))
save_charlist(FONTNAME, SFDPATH, REPO_ROOT / "img")
print(crayons.yellow("Saving sample image"))
save_sample(
FONTNAME,
read_sample(REPO_ROOT / "img" / "sample.txt"),
REPO_ROOT / "img" / "sample.png",
)
add_margins(REPO_ROOT / "img" / "sample.png")
def fontforge(open: Path, generate: Sequence[Generate]):
BUILD_DIR.mkdir(exist_ok=True)
script = "; ".join(
[
f'Open("{open}")',
'RenameGlyphs("AGL with PUA")',
'Reencode("unicode")',
]
+ [str(gen) for gen in generate]
)
# No idea why this doesn't work without shell=True
subprocess.run(
[f"fontforge -lang ff -c {quote(script)}"], cwd=BUILD_DIR, shell=True
)
def rename_single(dir: Path, pattern: str, newname: str) -> Path:
return cast(Path, next(dir.glob(pattern)).rename(dir / newname))
def gen_bitmap_formats() -> Path:
fontforge(
open=REPO_ROOT / "Cozette" / "Cozette.sfd",
generate=[
Generate("cozette.", bitmap_fmt="bdf"),
Generate("cozette.", "otb"),
Generate("cozette.", "psf"),
Generate("cozette.", "fnt"),
Generate("cozette.dfont", "sbit"),
],
)
rename_single(BUILD_DIR, "*.fnt", "cozette.fnt")
return rename_single(BUILD_DIR, "*.bdf", "cozette.bdf")
def fix_ttf(ttfpath: Path, name: str):
print(crayons.yellow(f"Generating TTF for {name}..."))
script = "; ".join(
[
f'Open("{ttfpath}")',
"SelectWorthOutputting()",
"RemoveOverlap()",
"CorrectDirection()",
"ScaleToEm(2048)",
'RenameGlyphs("AGL with PUA")',
'Reencode("unicode")',
f'SetTTFName(0x409, 3, "{name}")',
f'SetTTFName(0x409, 11, "")',
'SetTTFName(0x409, 13, "MIT")',
'SetTTFName(0x409, 14, "https://opensource.org/licenses/MIT")',
f'Generate("{name}.dfont")',
f'Generate("{name}.otf")',
f'Generate("{name}.ttf")',
]
)
# No idea why this doesn't work without shell=True
subprocess.run(
[f"fontforge -lang ff -c {quote(script)}"],
cwd=BUILD_DIR,
shell=True,
check=True,
)
ttfpath.unlink()
def make_hidpi(bdf_path: Path, out_path: Path):
print(crayons.yellow("Generating hidpi font..."))
with bdf_path.open() as i:
with out_path.open("w") as o:
double_size(i, o)
print(crayons.green("Done!"))
def gen_variants(bdf_path: Path):
hidpi_path = BUILD_DIR / "cozette_hidpi.bdf"
def bnp_invoc_ttf(name: str, format: str):
return [
REPO_ROOT / "bitsnpicas.sh",
"convertbitmap",
"-f",
format,
"-o",
BUILD_DIR / f"{name}_tmp.{format}",
"-s",
"Cozette",
"-r",
name,
"-T",
]
subprocess.run(
[
BUILD_DIR.parent / "bitsnpicas.sh",
"convertbitmap",
"-f",
"psf",
"-o",
BUILD_DIR / f"cozette.psf",
bdf_path,
],
check=True,
)
subprocess.run(
bnp_invoc_ttf("CozetteVector", "ttf") + [bdf_path], check=True
)
subprocess.run(
bnp_invoc_ttf("CozetteVectorBold", "ttf") + ["-b", bdf_path],
check=True,
)
print(crayons.yellow("Fixing TTF variants..."))
fix_ttf(BUILD_DIR / "CozetteVector_tmp.ttf", "CozetteVector")
fix_ttf(BUILD_DIR / "CozetteVectorBold_tmp.ttf", "CozetteVectorBold")
print(crayons.green("Done!"))
make_hidpi(bdf_path, hidpi_path)
fontforge(
open=hidpi_path,
generate=[
Generate(f"{hidpi_path.stem}.", "otb"),
Generate(f"{hidpi_path.stem}.", "fnt"),
Generate(f"{hidpi_path.stem}.dfont", "sbit"),
],
)
rename_single(BUILD_DIR, "*-26.fnt", "cozette_hidpi.fnt")
subprocess.run(
[
BUILD_DIR.parent / "bitsnpicas.sh",
"convertbitmap",
"-f",
"psf",
"-o",
hidpi_path.with_suffix(".psf"),
hidpi_path,
],
check=True,
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="action")
images = subparsers.add_parser("images")
changelog = subparsers.add_parser("changelog")
fonts = subparsers.add_parser("fonts")
scan = subparsers.add_parser("scan")
# noinspection PyTypeChecker
scan.add_argument("path", type=Path) # type: ignore
scan.add_argument("-s", "--print-source-files", action="store_true")
scan.add_argument("-r", "--reverse", action="store_true")
args = parser.parse_args()
if args.action == "scan":
missing_codepoints = find_missing_codepoints(
SFDPATH,
scan_for_codepoints(args.path),
)
if missing_codepoints:
print_codepoints_for_changelog(
missing_codepoints,
print_source_files=args.print_source_files,
reverse=args.reverse,
)
else:
print(
crayons.green(
f"All codepoints under {args.path} already "
f"supported by Cozette."
)
)
if args.action == "images":
print(crayons.blue("Saving sample images..."))
save_images()
print(crayons.green("Done!", bold=True))
elif args.action == "fonts":
rmtree(BUILD_DIR, ignore_errors=True)
BUILD_DIR.mkdir(exist_ok=True)
print(crayons.blue("Building bitmap formats..."))
bdf_path = gen_bitmap_formats()
print(crayons.green("Done!", bold=True))
print(crayons.blue("Building variants..."))
gen_variants(bdf_path)
print(crayons.green("Done!", bold=True))
elif args.action == "changelog":
get_changelog()
else:
parser.print_usage()