1#!/usr/bin/env python
2"""Script to add a suffix to all family names in the input font's `name` table,
3and to optionally rename the output files with the given suffix.
4
5The current family name substring is searched in the nameIDs 1, 3, 4, 6, 16,
6and 21, and if found the suffix is inserted after it; or else the suffix is
7appended at the end.
8"""
9from __future__ import print_function, absolute_import, unicode_literals
10import os
11import argparse
12import logging
13from fontTools.ttLib import TTFont
14from fontTools.misc.cliTools import makeOutputFileName
15
16
17logger = logging.getLogger()
18
19WINDOWS_ENGLISH_IDS = 3, 1, 0x409
20MAC_ROMAN_IDS = 1, 0, 0
21
22FAMILY_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
32def 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
54def 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
67def 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
74def 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
84def 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
113def 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
169if __name__ == "__main__":
170    main()
171