"""Build identities and shared CFF hint constants.""" import argparse import importlib import os import shutil from pathlib import Path import donors from mapping import ALTERNATE_PATH, load_alternates EDITIONS = { "full": ("MTPro2 Math", "MTPro2Math"), "lite": ("MTPro2 Math Lite", "MTPro2MathLite"), } # head.fontRevision is a 16.16 fixed-point field. FONT_REVISION = round(1.4 * 65536) / 65536 FONT_VERSION = "Version 1.4" # Shared FontForge-generated values adopted from the Sol44 Full OTF. # Set each standard width before its stem snaps: FontForge couples them. CFF_HINT_VALUES = { "BlueValues": (-14, 0, 450, 460, 662, 676, 683, 702), "OtherBlues": (-217, -217), "BlueShift": 20, "StdHW": 50, "StemSnapH": (19, 31, 37, 44, 50, 55, 63, 67, 73, 79), "StdVW": 65, "StemSnapV": (37, 44, 50, 57, 65, 70, 76, 80, 84, 90, 102, 140), } def identity(edition): if edition not in EDITIONS: raise ValueError(f"unknown edition: {edition}") family, basename = EDITIONS[edition] return family, basename, "MTP2;" + basename def parse_options(argv=None): parser = argparse.ArgumentParser( prog="./build.sh", allow_abbrev=False, description="Build MTPro2 OpenType math fonts.", ) parser.add_argument( "--edition", choices=EDITIONS, default="full", help="font edition (default: full)", ) parser.add_argument( "--roman-regular", metavar="PATH", help="regular Roman donor (default: donors/NimbusRoman-Regular.otf)", ) parser.add_argument( "--roman-bold", metavar="PATH", help="bold Roman donor (default: donors/NimbusRoman-Bold.otf)", ) parser.add_argument( "--donor-coverage", choices=donors.PUBLIC_MODES, default="minimal", help=( "minimal functional set or extended eligible " "donor characters (default: minimal)" ), ) args = parser.parse_args(argv) return parser, args def resolve_inputs(args, root, log, stage, run, hashes): import catalog import declarations required = declarations.required_source_files( declarations.active_tags(args.edition) ) regular = Path( args.roman_regular or root / "donors/NimbusRoman-Regular.otf" ).resolve() bold = Path( args.roman_bold or root / "donors/NimbusRoman-Bold.otf" ).resolve() paths = [root / "mtpro2" / name for name in required] paths += [regular, bold, donors.SPEC_PATH] if args.edition == "full": paths.append(ALTERNATE_PATH) missing = [] for path in paths: if not path.is_file(): log.write(f"input name={path.name!r} path={str(path)!r} missing\n") missing.append(path) else: log.write( f"input name={path.name!r} path={str(path)!r} " f"size={path.stat().st_size} " f"sha256={catalog.sha(path, hashes)}\n" ) log.flush() # Record both executable paths even when one dependency is absent. commands = {name: shutil.which(name) for name in ("fontforge", "tftopl")} for name, path in commands.items(): log.write(f"dependency {name} path={path or 'unavailable'}\n") log.flush() absent = [name for name, path in commands.items() if path is None] if commands["tftopl"]: probe = run("tftopl version", ["tftopl", "--version"], check=False) if probe.returncode: log.write("tftopl version unretrievable\n") forge = None if commands["fontforge"]: forge = run( "FontForge / embedded Python version and support", [ "fontforge", "-lang=py", "-c", ( "import sys, fontforge, psMat\n" "try:\n" " version = fontforge.version()\n" "except Exception as exc:\n" ' version = "unretrievable (" + str(exc) + ")"\n' 'print("FontForge version:", version)\n' 'print("FontForge Python path:", sys.executable)\n' 'print("FontForge Python version:", sys.version)\n' "sys.version_info >= (3, 10) or " 'sys.exit("FontForge embedded Python 3.10 ' 'or later is required")' ), ], check=False, ) if forge.returncode: log.write("FontForge / Python version or support check failed\n") stage("validate input files and dependencies") if missing: raise ValueError( f"{len(missing)} required input file(s) missing; " f"first: {missing[0]}" ) if args.roman_regular == "" or args.roman_bold == "": raise ValueError("Roman donor paths must not be empty") donors.resolve_mode(args.donor_coverage, os.environ) if args.edition == "full": load_alternates(ALTERNATE_PATH) for module in ( "fontTools.ttLib", "fontTools.t1Lib", "fontTools.pens.cu2quPen", ): importlib.import_module(module) if absent: raise ValueError("required command not found: " + ", ".join(absent)) if forge is not None and forge.returncode: raise ValueError( "FontForge / embedded Python support: " f"exit code {forge.returncode}" ) stage("validate Roman donors") donors.validate_inputs(regular, bold) return regular, bold