summaryrefslogtreecommitdiffstats
path: root/lib/silfont/scripts/psfshownames.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/silfont/scripts/psfshownames.py')
-rwxr-xr-xlib/silfont/scripts/psfshownames.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/lib/silfont/scripts/psfshownames.py b/lib/silfont/scripts/psfshownames.py
new file mode 100755
index 0000000..191e0f8
--- /dev/null
+++ b/lib/silfont/scripts/psfshownames.py
@@ -0,0 +1,235 @@
+#!/usr/bin/python3
+__doc__ = 'Display name fields and other bits for linking fonts into families'
+__url__ = 'http://github.com/silnrsi/pysilfont'
+__copyright__ = 'Copyright (c) 2021 SIL International (http://www.sil.org)'
+__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
+__author__ = 'Bobby de Vos'
+
+from silfont.core import execute, splitfn
+from fontTools.ttLib import TTFont
+import glob
+from operator import attrgetter, methodcaller
+import tabulate
+
+WINDOWS_ENGLISH_IDS = 3, 1, 0x409
+
+FAMILY_RELATED_IDS = {
+ 1: 'Family',
+ 2: 'Subfamily',
+ 4: 'Full name',
+ 6: 'PostScript name',
+ 16: 'Typographic/Preferred family',
+ 17: 'Typographic/Preferred subfamily',
+ 21: 'WWS family',
+ 22: 'WWS subfamily',
+ 25: 'Variations PostScript Name Prefix',
+}
+
+
+class FontInfo:
+ def __init__(self):
+ self.filename = ''
+ self.name_table = dict()
+ self.weight_class = 0
+ self.regular = ''
+ self.bold = ''
+ self.italic = ''
+ self.width = ''
+ self.width_name = ''
+ self.width_class = 0
+ self.wws = ''
+
+ def sort_fullname(self):
+ return self.name_table[4]
+
+
+argspec = [
+ ('font', {'help': 'ttf font(s) to run report against; wildcards allowed', 'nargs': "+"}, {'type': 'filename'}),
+ ('-b', '--bits', {'help': 'Show bits', 'action': 'store_true'}, {}),
+ ('-m', '--multiline', {'help': 'Output multi-line key:values instead of a table', 'action': 'store_true'}, {}),
+]
+
+
+def doit(args):
+ logger = args.logger
+
+ font_infos = []
+ for pattern in args.font:
+ for fullpath in glob.glob(pattern):
+ logger.log(f'Processing {fullpath}', 'P')
+ try:
+ font = TTFont(fullpath)
+ except Exception as e:
+ logger.log(f'Error opening {fullpath}: {e}', 'E')
+ break
+
+ font_info = FontInfo()
+ font_info.filename = fullpath
+ get_names(font, font_info)
+ get_bits(font, font_info)
+ font_infos.append(font_info)
+
+ if not font_infos:
+ logger.log("No files match the filespec provided for fonts: " + str(args.fonts), "S")
+
+ font_infos.sort(key=methodcaller('sort_fullname'))
+ font_infos.sort(key=attrgetter('width_class'), reverse=True)
+ font_infos.sort(key=attrgetter('weight_class'))
+
+ rows = list()
+ if args.multiline:
+ # Multi-line mode
+ for font_info in font_infos:
+ for line in multiline_names(font_info):
+ rows.append(line)
+ if args.bits:
+ for line in multiline_bits(font_info):
+ rows.append(line)
+ align = ['left', 'right']
+ if len(font_infos) == 1:
+ del align[0]
+ for row in rows:
+ del row[0]
+ output = tabulate.tabulate(rows, tablefmt='plain', colalign=align)
+ output = output.replace(': ', ':')
+ output = output.replace('#', '')
+ else:
+ # Table mode
+
+ # Record information for headers
+ headers = table_headers(args.bits)
+
+ # Record information for each instance.
+ for font_info in font_infos:
+ record = table_records(font_info, args.bits)
+ rows.append(record)
+
+ # Not all fonts in a family with have the same name ids present,
+ # for instance 16: Typographic/Preferred family is only needed in
+ # non-RIBBI familes, and even then only for the non-RIBBI instances.
+ # Also, not all the bit fields are present in each instance.
+ # Therefore, columns with no data in any instance are removed.
+ indices = list(range(len(headers)))
+ indices.reverse()
+ for index in indices:
+ empty = True
+ for row in rows:
+ data = row[index]
+ if data:
+ empty = False
+ if empty:
+ for row in rows + [headers]:
+ del row[index]
+
+ # Format 'pipe' is nicer for GitHub, but is wider on a command line
+ output = tabulate.tabulate(rows, headers, tablefmt='simple')
+
+ # Print output from either mode
+ if args.quiet:
+ print(output)
+ else:
+ logger.log('The following family-related values were found in the name, head, and OS/2 tables\n' + output, 'P')
+
+
+def get_names(font, font_info):
+ table = font['name']
+ (platform_id, encoding_id, language_id) = WINDOWS_ENGLISH_IDS
+
+ for name_id in FAMILY_RELATED_IDS:
+ record = table.getName(
+ nameID=name_id,
+ platformID=platform_id,
+ platEncID=encoding_id,
+ langID=language_id
+ )
+ if record:
+ font_info.name_table[name_id] = str(record)
+
+
+def get_bits(font, font_info):
+ os2 = font['OS/2']
+ head = font['head']
+ font_info.weight_class = os2.usWeightClass
+ font_info.regular = bit2code(os2.fsSelection, 6, 'W-')
+ font_info.bold = bit2code(os2.fsSelection, 5, 'W')
+ font_info.bold += bit2code(head.macStyle, 0, 'M')
+ font_info.italic = bit2code(os2.fsSelection, 0, 'W')
+ font_info.italic += bit2code(head.macStyle, 1, 'M')
+ font_info.width_class = os2.usWidthClass
+ font_info.width = str(font_info.width_class)
+ if font_info.width_class == 5:
+ font_info.width_name = 'Width-Normal'
+ if font_info.width_class < 5:
+ font_info.width_name = 'Width-Condensed'
+ font_info.width += bit2code(head.macStyle, 5, 'M')
+ if font_info.width_class > 5:
+ font_info.width_name = 'Width-Extended'
+ font_info.width += bit2code(head.macStyle, 6, 'M')
+ font_info.wws = bit2code(os2.fsSelection, 8, '8')
+
+
+def bit2code(bit_field, bit, code_letter):
+ code = ''
+ if bit_field & 1 << bit:
+ code = code_letter
+ return code
+
+
+def multiline_names(font_info):
+ for name_id in sorted(font_info.name_table):
+ line = [font_info.filename + ':',
+ str(name_id) + ':',
+ FAMILY_RELATED_IDS[name_id] + ':',
+ font_info.name_table[name_id]
+ ]
+ yield line
+
+
+def multiline_bits(font_info):
+ labels = ('usWeightClass', 'Regular', 'Bold', 'Italic', font_info.width_name, 'WWS')
+ values = (font_info.weight_class, font_info.regular, font_info.bold, font_info.italic, font_info.width, font_info.wws)
+ for label, value in zip(labels, values):
+ if not value:
+ continue
+ line = [font_info.filename + ':',
+ '#',
+ str(label) + ':',
+ value
+ ]
+ yield line
+
+
+def table_headers(bits):
+ headers = ['filename']
+ for name_id in sorted(FAMILY_RELATED_IDS):
+ name_id_key = FAMILY_RELATED_IDS[name_id]
+ header = f'{name_id}: {name_id_key}'
+ if len(header) > 20:
+ header = header.replace(' ', '\n')
+ header = header.replace('/', '\n')
+ headers.append(header)
+ if bits:
+ headers.extend(['wght', 'R', 'B', 'I', 'wdth', 'WWS'])
+ return headers
+
+
+def table_records(font_info, bits):
+ record = [font_info.filename]
+ for name_id in sorted(FAMILY_RELATED_IDS):
+ name_id_value = font_info.name_table.get(name_id, '')
+ record.append(name_id_value)
+ if bits:
+ record.append(font_info.weight_class)
+ record.append(font_info.regular)
+ record.append(font_info.bold)
+ record.append(font_info.italic)
+ record.append(font_info.width)
+ record.append(font_info.wws)
+ return record
+
+
+def cmd(): execute('FT', doit, argspec)
+
+
+if __name__ == '__main__':
+ cmd()