diff --git a/ios_fonts.py b/ios_fonts.py new file mode 100644 index 0000000..5f9d644 --- /dev/null +++ b/ios_fonts.py @@ -0,0 +1,180 @@ +from argparse import ArgumentParser +from os.path import isabs, join, abspath, dirname, exists, isdir, basename +from os import listdir, makedirs, remove +from typing import List +import uuid +from xml.sax.saxutils import escape +from math import floor +from base64 import b64encode +from subprocess import run +from yaml import load +try: + from yaml import CLoader as Loader +except ImportError: + from yaml import Loader +try: + import fontforge + have_fontforge = True +except ImportError: + have_fontforge = False + + +p = ArgumentParser() +p.add_argument('input', help='Input config file', nargs='+', type=str) + + +def get_path(path: str, input: str): + if isabs(path): + return path + return abspath(join(input, path)) + + +def get_input(input: str, input_dir: str, temp_dir: str) -> List[str]: + dinput = get_path(input, input_dir) + if not exists(dinput): + raise FileNotFoundError(dinput) + if isdir(dinput): + files = listdir(dinput) + output = [] + for file in files: + output.extend(get_input(join(input, file), input_dir, temp_dir)) + return output + linput = input.lower() + if linput.endswith('.ttf') or linput.endswith('.otf'): + return [dinput] + if linput.endswith('.ttc'): + if not have_fontforge: + print('FontForge is not installed, skipping', dinput) + return [] + fonts = fontforge.fontsInFile(dinput) + paths = [] + for i in fonts: + font_path = get_path(f"{i}.ttf", temp_dir) + if not exists(font_path): + font = fontforge.open(f"{dinput}({i})") + font.generate(font_path) + font.close() + paths.append(font_path) + return paths + return [] + + +def replace(m): + return "-".join([m.group(1), m.group(2), '5' + m.group(3), + '8' + m.group(5), m.group(5)]) + + +def main(args=None): + args = p.parse_args(args) + for input in args.input: + with open(input, 'r', encoding='UTF-8') as f: + config = load(f, Loader=Loader) + input_dir = dirname(input) + temp_dir = 'tmp' + if 'temp_dir' in config and config['temp_dir']: + temp_dir = config['temp_dir'] + temp_dir = get_path(temp_dir, input_dir) + output = 'output.mobileconfig' + if 'output' in config and config['output']: + output = config['output'] + output = get_path(output, input_dir) + if 'cert_file' not in config or not config['cert_file']: + raise ValueError('cert_file is required') + cert_file = get_path(config['cert_file'], input_dir) + if not exists(cert_file): + raise FileNotFoundError(cert_file) + if 'chain_file' not in config or not config['chain_file']: + raise ValueError('chain_file is required') + chain_file = get_path(config['chain_file'], input_dir) + if not exists(chain_file): + raise FileNotFoundError(chain_file) + if 'privkey_file' not in config or not config['privkey_file']: + raise ValueError('privkey_file is required') + privkey_file = get_path(config['privkey_file'], input_dir) + if not exists(privkey_file): + raise FileNotFoundError(privkey_file) + if 'input' not in config or not config['input']: + raise ValueError('input is required') + if not isinstance(config['input'], list): + raise ValueError('input must be a list') + input_files = [] + makedirs(temp_dir, exist_ok=True) + for input in config['input']: + input_files.extend(get_input(input, input_dir, temp_dir)) + input_files = {i for i in input_files} + name = 'Custom Fonts' + if 'name' in config and config['name']: + name = config['name'] + uuid_str = str(uuid.uuid4()) + identifier = 'com.example-' + uuid_str + if 'identifier' in config and config['identifier']: + identifier = config['identifier'] + description = 'Make the font available to iOS applications.' + if 'description' in config and config['description']: + description = config['description'] + organization = 'Font Profile' + if 'organization' in config and config['organization']: + organization = config['organization'] + temp_output = get_path(basename(output), temp_dir) + with open(temp_output, 'w', encoding='UTF-8') as f: + f.write(f""" + + + + PayloadUUID + {uuid_str} + PayloadVersion + 1 + PayloadIdentifier + {identifier} + PayloadDisplayName + {escape(name)} + PayloadOrganization + {escape(organization)} + PayloadDescription + {escape(description)} + PayloadContent + """) + for i in input_files: + uuid2 = str(uuid.uuid4()) + f.write(f""" + + PayloadVersion + 1 + PayloadType + com.apple.font + PayloadIdentifier + {uuid2} + PayloadUUID + {uuid2} + Font + """) + with open(i, 'rb') as inp: + t = b'' + t += inp.read(4096) + while len(t) >= 3: + read_len = floor(len(t) / 3) * 3 + buf = t[:read_len] + t = t[read_len:] + f.write(b64encode(buf).decode()) + t += inp.read(4096) + if t: + f.write(b64encode(t).decode()) + f.write(f""" + """) + f.write(""" + + PayloadType + Configuration + PayloadRemovalDisallowed + + +""") + run(['openssl', 'smime', '-sign', '-in', temp_output, '-out', output, + '-signer', cert_file, '-inkey', privkey_file, + '-certfile', chain_file, '-outform', 'der', '-nodetach']) + remove(temp_output) + + +if __name__ == '__main__': + main()