import os import argparse from fontTools.ttLib import TTFont, TTLibError # --- Configuration --- # The character limit imposed by Microsoft Office's LogFontA struct. CHAR_LIMIT = 32 # The root directory to start searching from. Assumes a structure like ./license/familyname/ ROOT_DIRECTORY = '.' def get_name_by_id(font, name_id): """ Retrieves a name string from the font's 'name' table by its ID. It prioritizes Windows platform, English language names. """ for record in font['name'].names: if record.nameID == name_id and record.platformID == 3 and record.langID == 1033: return record.toUnicode() # Fallback to any other platform/language if the preferred one isn't found for record in font['name'].names: if record.nameID == name_id: return record.toUnicode() return None def check_font_family(family_path, family_name): """ Analyzes all TTF files within a specific family directory to check for name length violations. """ violations = [] try: font_files = [f for f in os.listdir(family_path) if f.lower().endswith('.ttf')] if not font_files: return [] # We only need to check one font file for the family-wide names and fvar table. # Assuming all fonts in the family folder are part of the same variable font or static family. font_path = os.path.join(family_path, font_files[0]) with TTFont(font_path) as font: # Check if it's a variable font by looking for the 'fvar' table if 'fvar' in font: # For variable fonts, the name is Typographic Family + fvar instance name base_family_name = get_name_by_id(font, 16) or get_name_by_id(font, 1) if not base_family_name: print(f" [WARNING] Could not find NameID 16 or 1 for variable font in '{family_name}'. Skipping.") return [] for instance in font['fvar'].instances: subfamily_name = get_name_by_id(font, instance.subfamilyNameID) if not subfamily_name: continue # MS Office combines the names with a space full_name = f"{base_family_name} {subfamily_name}" if len(full_name) >= CHAR_LIMIT: violations.append((full_name, len(full_name))) # It's a static font (or multiple static fonts) else: for font_file in font_files: static_font_path = os.path.join(family_path, font_file) with TTFont(static_font_path) as static_font: # Prefer Typographic Family (NameID 16 + 17) family = get_name_by_id(static_font, 16) subfamily = get_name_by_id(static_font, 17) # Fallback to Font Family (NameID 1 + 2) if not family or not subfamily: family = get_name_by_id(static_font, 1) subfamily = get_name_by_id(static_font, 2) if not family or not subfamily: print(f" [WARNING] Could not determine name for static font '{font_file}'. Skipping.") continue full_name = f"{family} {subfamily}" if len(full_name) >= CHAR_LIMIT: violations.append((full_name, len(full_name))) except TTLibError: print(f" [ERROR] Could not process a font file in '{family_name}'. It might be corrupted.") except Exception as e: print(f" [ERROR] An unexpected error occurred while processing '{family_name}': {e}") return list(set(violations)) # Return unique violations def main(): """ Main function to scan directories and report font name length issues. """ parser = argparse.ArgumentParser( description="Check font family names against Microsoft Office's 32-character limit." ) parser.add_argument( "license_dir", nargs='?', # Makes the argument optional default=os.path.join(ROOT_DIRECTORY, 'license'), help="Path to the directory containing font family subdirectories. Defaults to './license'." ) args = parser.parse_args() license_dir = args.license_dir print("--- Font Name Length Checker for Microsoft Office ---") print(f"Scanning for font families that exceed the {CHAR_LIMIT}-character limit...") violating_families = {} if not os.path.isdir(license_dir): print(f"\n[ERROR] The specified directory was not found: '{os.path.abspath(license_dir)}'") print("Please provide a valid path to the directory containing font families.") return family_names = [d for d in os.listdir(license_dir) if os.path.isdir(os.path.join(license_dir, d))] if not family_names: print(f"\nNo font family subdirectories found in '{license_dir}'.") return print(f"Found {len(family_names)} families to check in '{os.path.abspath(license_dir)}'.\n") for family_name in family_names: family_path = os.path.join(license_dir, family_name) violations = check_font_family(family_path, family_name) if violations: violating_families[family_name] = violations print("--- Scan Complete ---") if not violating_families: print("Success! No families were found with names exceeding the limit.") else: print(f"\nFound {len(violating_families)} families with name length issues:\n") for family, violations in violating_families.items(): print(f"Family: {family}") for name, length in sorted(violations): print(f" - \"{name}\" ({length} chars)") print("") if __name__ == "__main__": main()