Created
December 5, 2023 09:46
-
-
Save stefan6419846/2cbdbf255661c2d774822166b140b90e to your computer and use it in GitHub Desktop.
Analyze fonts using fontTools
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
import time | |
from pathlib import Path | |
from fontTools import ttLib | |
from fontTools import ttx | |
from fontTools.misc import timeTools | |
XML_DIRECTORY = Path('ttx') | |
# https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids | |
NAME_IDS = [ | |
'Copyright notice', | |
'Font family name', | |
'Font subfamily name', | |
'Unique font identifier', | |
'Full font name', | |
'Version string', | |
'PostScript name', | |
'Trademark', | |
'Manufacturer', | |
'Designer', | |
'Description', | |
'URL Vendor', | |
'URL Designer', | |
'License Description', | |
'License Info URL', | |
'Reserved', | |
'Typographic Family name', | |
'Typographic Subfamily name', | |
'Compatible Full (Mac only)', | |
'Sample text', | |
'PostScript CID findfont name', | |
'WWS Family Name', | |
'WWS Subfamily Name', | |
'Light Background Palette', | |
'Dark Background Palette', | |
'Variations PostScript Name Prefix', | |
] | |
# https://learn.microsoft.com/en-us/typography/opentype/spec/head | |
def convert_head_flags(v): | |
result = [] | |
verbose = [ | |
'Baseline for font at y=0', | |
'Left sidebearing point at x=0', | |
'Instructions may depend on point size', | |
'Force ppem to integer values', | |
'Instructions may alter advance width', | |
'', | |
'', | |
'', | |
'', | |
'', | |
'', | |
'Lossless font data', | |
'Font converted', | |
'Font optimized for ClearType', | |
'Last Resort font', | |
'' | |
] | |
for i, verbose_ in enumerate(verbose): | |
if v & (1 << i): | |
result.append(verbose_) | |
return '; '.join(filter(None, result)) if result else '%' | |
def convert_timestamp(v): | |
return datetime.datetime.fromtimestamp(time.mktime(time.gmtime(max(0, v + timeTools.epoch_diff)))) | |
def convert_mac_style(v): | |
result = [] | |
verbose = ['Bold', 'Italic', 'Underline', 'Outline', 'Shadow', 'Condensed', 'Extended'] | |
for i, verbose_ in enumerate(verbose): | |
if v & (1 << i): | |
result.append(verbose_) | |
return ', '.join(result) if result else '%' | |
def convert_font_direction_hint(v): | |
return { | |
0: 'Fully mixed directional glyphs', | |
1: 'Only strongly left to right', | |
2: 'Only strongly left to right, but also contains neutrals', | |
-1: 'Only strongly right to left', | |
-2: 'Only strongly right to left, but also contains neutrals', | |
}[v] | |
def convert_loc_format(v): | |
return { | |
0: 'Short offsets (Offset16)', | |
1: 'Long offsets (Offset32)', | |
}[v] | |
HEAD_IDS = { | |
'tableVersion': 'Font Table Version', | |
'fontRevision': 'Font Revision', | |
'checkSumAdjustment': 'Checksum', | |
'magicNumber': 'Magic number', | |
'flags': ('Flags', convert_head_flags), | |
'unitsPerEm': 'Units per em', | |
'created': ('Created', convert_timestamp), | |
'modified': ('Modified', convert_timestamp), | |
'xMin': 'xMin', | |
'yMin': 'yMin', | |
'xMax': 'xMax', | |
'yMax': 'yMax', | |
'macStyle': ('Mac Style', convert_mac_style), | |
'lowestRecPPEM': 'Smallest readable size in pixels', | |
'fontDirectionHint': ('Font direction hint', convert_font_direction_hint), | |
'indexToLocFormat': ('Index to Loc format', convert_loc_format), | |
'glyphDataFormat': 'Glyph Data Format', | |
} | |
root = Path('djangocms_frontend/contrib/icon/static/djangocms_frontend/icon/vendor/assets/fonts') | |
if not XML_DIRECTORY.is_dir(): | |
XML_DIRECTORY.mkdir() | |
for path in sorted(root.glob('*'), key=str): | |
print('=' * 80) | |
print(path) | |
if path.suffix in {'.ttf', '.woff', '.woff2'}: | |
font = ttLib.TTFont(path) | |
else: | |
continue | |
# print(font) | |
for key in font.keys(): | |
if key not in {'head', 'name'}: | |
continue | |
value = font.get(key) | |
if key == 'head': | |
for key_, value_ in value.__dict__.items(): | |
if key_ not in HEAD_IDS: | |
continue | |
parsed_value = value_ if not isinstance(HEAD_IDS[key_], tuple) else HEAD_IDS[key_][1](value_) | |
identifier = HEAD_IDS[key_] if not isinstance(HEAD_IDS[key_], tuple) else HEAD_IDS[key_][0] | |
print(identifier + ':', parsed_value) | |
elif key == 'name': | |
for index in range(26): | |
name = value.getDebugName(index) | |
if name: | |
print(NAME_IDS[index] + ':', name) | |
font.close() | |
ttx.ttDump(path, XML_DIRECTORY / (path.name[:-len(path.suffix)] + '.ttx'), ttx.Options(rawOptions=[], numFiles=1)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment