Home | History | Annotate | Download | only in Snippets
      1 #!/usr/bin/env python
      2 """Script to add a suffix to all family names in the input font's `name` table,
      3 and to optionally rename the output files with the given suffix.
      4 
      5 The current family name substring is searched in the nameIDs 1, 3, 4, 6, 16,
      6 and 21, and if found the suffix is inserted after it; or else the suffix is
      7 appended at the end.
      8 """
      9 from __future__ import print_function, absolute_import, unicode_literals
     10 import os
     11 import argparse
     12 import logging
     13 from fontTools.ttLib import TTFont
     14 from fontTools.misc.cliTools import makeOutputFileName
     15 
     16 
     17 logger = logging.getLogger()
     18 
     19 WINDOWS_ENGLISH_IDS = 3, 1, 0x409
     20 MAC_ROMAN_IDS = 1, 0, 0
     21 
     22 FAMILY_RELATED_IDS = dict(
     23     LEGACY_FAMILY=1,
     24     TRUETYPE_UNIQUE_ID=3,
     25     FULL_NAME=4,
     26     POSTSCRIPT_NAME=6,
     27     PREFERRED_FAMILY=16,
     28     WWS_FAMILY=21,
     29 )
     30 
     31 
     32 def get_current_family_name(table):
     33     family_name_rec = None
     34     for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
     35         for name_id in (
     36             FAMILY_RELATED_IDS["PREFERRED_FAMILY"],
     37             FAMILY_RELATED_IDS["LEGACY_FAMILY"],
     38         ):
     39             family_name_rec = table.getName(
     40                 nameID=name_id,
     41                 platformID=plat_id,
     42                 platEncID=enc_id,
     43                 langID=lang_id,
     44             )
     45             if family_name_rec is not None:
     46                 break
     47         if family_name_rec is not None:
     48             break
     49     if not family_name_rec:
     50         raise ValueError("family name not found; can't add suffix")
     51     return family_name_rec.toUnicode()
     52 
     53 
     54 def insert_suffix(string, family_name, suffix):
     55     # check whether family_name is a substring
     56     start = string.find(family_name)
     57     if start != -1:
     58         # insert suffix after the family_name substring
     59         end = start + len(family_name)
     60         new_string = string[:end] + suffix + string[end:]
     61     else:
     62         # it's not, we just append the suffix at the end
     63         new_string = string + suffix
     64     return new_string
     65 
     66 
     67 def rename_record(name_record, family_name, suffix):
     68     string = name_record.toUnicode()
     69     new_string = insert_suffix(string, family_name, suffix)
     70     name_record.string = new_string
     71     return string, new_string
     72 
     73 
     74 def rename_file(filename, family_name, suffix):
     75     filename, ext = os.path.splitext(filename)
     76     ps_name = family_name.replace(" ", "")
     77     if ps_name in filename:
     78         ps_suffix = suffix.replace(" ", "")
     79         return insert_suffix(filename, ps_name, ps_suffix) + ext
     80     else:
     81         return insert_suffix(filename, family_name, suffix) + ext
     82 
     83 
     84 def add_family_suffix(font, suffix):
     85     table = font["name"]
     86 
     87     family_name = get_current_family_name(table)
     88     logger.info("  Current family name: '%s'", family_name)
     89 
     90     # postcript name can't contain spaces
     91     ps_family_name = family_name.replace(" ", "")
     92     ps_suffix = suffix.replace(" ", "")
     93     for rec in table.names:
     94         name_id = rec.nameID
     95         if name_id not in FAMILY_RELATED_IDS.values():
     96             continue
     97         if name_id == FAMILY_RELATED_IDS["POSTSCRIPT_NAME"]:
     98             old, new = rename_record(rec, ps_family_name, ps_suffix)
     99         elif name_id == FAMILY_RELATED_IDS["TRUETYPE_UNIQUE_ID"]:
    100             # The Truetype Unique ID rec may contain either the PostScript
    101             # Name or the Full Name string, so we try both
    102             if ps_family_name in rec.toUnicode():
    103                 old, new = rename_record(rec, ps_family_name, ps_suffix)
    104             else:
    105                 old, new = rename_record(rec, family_name, suffix)
    106         else:
    107             old, new = rename_record(rec, family_name, suffix)
    108         logger.info("    %r: '%s' -> '%s'", rec, old, new)
    109 
    110     return family_name
    111 
    112 
    113 def main(args=None):
    114     parser = argparse.ArgumentParser(
    115         description=__doc__,
    116         formatter_class=argparse.RawDescriptionHelpFormatter,
    117     )
    118     parser.add_argument("-s", "--suffix", required=True)
    119     parser.add_argument("input_fonts", metavar="FONTFILE", nargs="+")
    120     output_group = parser.add_mutually_exclusive_group()
    121     output_group.add_argument("-i", "--inplace", action="store_true")
    122     output_group.add_argument("-d", "--output-dir")
    123     output_group.add_argument("-o", "--output-file")
    124     parser.add_argument("-R", "--rename-files", action="store_true")
    125     parser.add_argument("-v", "--verbose", action="count", default=0)
    126     options = parser.parse_args(args)
    127 
    128     if not options.verbose:
    129         level = "WARNING"
    130     elif options.verbose == 1:
    131         level = "INFO"
    132     else:
    133         level = "DEBUG"
    134     logging.basicConfig(level=level, format="%(message)s")
    135 
    136     if options.output_file and len(options.input_fonts) > 1:
    137         parser.error(
    138             "argument -o/--output-file can't be used with multiple inputs"
    139         )
    140     if options.rename_files and (options.inplace or options.output_file):
    141         parser.error("argument -R not allowed with arguments -i or -o")
    142 
    143     for input_name in options.input_fonts:
    144         logger.info("Renaming font: '%s'", input_name)
    145 
    146         font = TTFont(input_name)
    147         family_name = add_family_suffix(font, options.suffix)
    148 
    149         if options.inplace:
    150             output_name = input_name
    151         elif options.output_file:
    152             output_name = options.output_file
    153         else:
    154             if options.rename_files:
    155                 input_name = rename_file(
    156                     input_name, family_name, options.suffix
    157                 )
    158             output_name = makeOutputFileName(input_name, options.output_dir)
    159 
    160         font.save(output_name)
    161         logger.info("Saved font: '%s'", output_name)
    162 
    163         font.close()
    164         del font
    165 
    166     logger.info("Done!")
    167 
    168 
    169 if __name__ == "__main__":
    170     main()
    171