#!/usr/bin/env python3 from os import system as run import os from subprocess import check_output as run_and_read import sys from typing import Dict, Set from OpenFont import parse_font_directory def get_vendor(file: str) -> str: command = f"otfinfo -i {file} | grep 'Vendor ID' | awk '{{print $3}}'" return run_and_read(command, shell=True).decode(encoding='ascii').rstrip('\n') def get_tables(file: str) -> list[str]: USE_OFTINFO = '--no-otfinfo' not in sys.argv if USE_OFTINFO: command = f"otfinfo -t {file} | awk '{{print $2}}'" return run_and_read(command, shell=True).decode(encoding='ascii').rstrip('\n').split('\n') else: with open(file, 'rb') as f: font_directory = parse_font_directory(f) # Since the font directory is always at the start of the file sorted_entries = sorted(font_directory.table_directory, key=lambda entry: entry.offset) return [entry.tableTag._value_ for entry in sorted_entries] FILENAME = os.path.join(os.path.dirname(__file__), "tables") graph: Dict[str, Set[str]] = {} def verify_node(tag: str): if tag not in graph: graph[tag] = set() def add_edge(tag1: str, tag2: str): verify_node(tag1) verify_node(tag2) graph[tag1].add(tag2) MODE = None if not sys.stdin.isatty(): MODE = 'direct' if '--direct' in sys.argv else 'before' accumulator: Dict[tuple[str, str], int] = {} # acc[(tag1, tag2)] is number of times tag1 occured before tag2 for file in sys.stdin: file = file.rstrip('\n') print(f"{get_vendor(file):<4} {file}") tables = get_tables(file) def add_to_acc(tag1: str, tag2: str): accumulator[(tag1, tag2)] = accumulator.get((tag1, tag2), 0)+1 match MODE: case 'before': for i, tag1 in enumerate(tables): for tag2 in tables[i+1:]: add_to_acc(tag1, tag2) case 'direct': for i, tag1 in enumerate(tables[:-1]): tag2 = tables[i+1] add_to_acc(tag1, tag2) case _: assert False, f"Invalid mode: '{MODE}'" with open(f"{FILENAME}.txt", 'w') as f: f.write(f"mode: {MODE}\n") for (tag1, tag2) in accumulator: f.write(f"'{tag1:<4}', '{tag2:<4}', {accumulator[(tag1, tag2)]}, {accumulator.get((tag2, tag1), 0)}\n") for (tag1, tag2) in accumulator: add_edge(tag1, tag2) else: with open(f"{FILENAME}.txt", 'r') as f: for i, line in enumerate(f.readlines()): if i == 0: MODE = line[6:-1] continue tag1, tag2 = line[1:5], line[9:13] add_edge(tag1, tag2) assert MODE, "Unreachable" UNTRANSITIVITY = '--untrans' in sys.argv def untransitivity() -> None: to_remove: Dict[str, Set[str]] = {tag: set() for tag in graph} for tag1 in graph: for tag2 in graph[tag1]: for tag3 in graph[tag2]: if tag3 in graph[tag1]: # find a->b, b->c where a->c to_remove[tag1].add(tag3) # to_remove = { # tag1: {tag3 for tag3 in graph[tag2] for tag2 in graph[tag1] if tag3 in graph[tag1]} # for tag1 in graph # } for tag1 in to_remove: for tag3 in to_remove[tag1]: graph[tag1].remove(tag3) if UNTRANSITIVITY: untransitivity() GENERATE = '--svg' in sys.argv def generate_svg() -> int: def node_name(tag: str) -> str: return '"'+tag+'"' with open(f"{FILENAME}.dot", 'w') as f: f.write("digraph {\n") f.write(f"\tlayout=dot\n") for node in graph: f.write(f"\t{node_name(node)}\n") for neighbour in graph[node]: f.write(f"\t{node_name(node)} -> {node_name(neighbour)};\n") f.write("}") return run(f"dot -Tsvg {FILENAME}.dot > {FILENAME}.svg") if GENERATE: generate_svg() print(f"{MODE = }\n{UNTRANSITIVITY = }\n{GENERATE = }")