aboutsummaryrefslogtreecommitdiff
path: root/Snippets/rename-fonts.py
blob: 3ebd12aaa196c6c7b72a5f4ea7f4d56a24397149 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env python3
"""Script to add a suffix to all family names in the input font's `name` table,
and to optionally rename the output files with the given suffix.

The current family name substring is searched in the nameIDs 1, 3, 4, 6, 16,
and 21, and if found the suffix is inserted after it; or else the suffix is
appended at the end.
"""
import os
import argparse
import logging
from fontTools.ttLib import TTFont
from fontTools.misc.cliTools import makeOutputFileName


logger = logging.getLogger()

WINDOWS_ENGLISH_IDS = 3, 1, 0x409
MAC_ROMAN_IDS = 1, 0, 0

FAMILY_RELATED_IDS = dict(
    LEGACY_FAMILY=1,
    TRUETYPE_UNIQUE_ID=3,
    FULL_NAME=4,
    POSTSCRIPT_NAME=6,
    PREFERRED_FAMILY=16,
    WWS_FAMILY=21,
)


def get_current_family_name(table):
    family_name_rec = None
    for plat_id, enc_id, lang_id in (WINDOWS_ENGLISH_IDS, MAC_ROMAN_IDS):
        for name_id in (
            FAMILY_RELATED_IDS["PREFERRED_FAMILY"],
            FAMILY_RELATED_IDS["LEGACY_FAMILY"],
        ):
            family_name_rec = table.getName(
                nameID=name_id,
                platformID=plat_id,
                platEncID=enc_id,
                langID=lang_id,
            )
            if family_name_rec is not None:
                break
        if family_name_rec is not None:
            break
    if not family_name_rec:
        raise ValueError("family name not found; can't add suffix")
    return family_name_rec.toUnicode()


def insert_suffix(string, family_name, suffix):
    # check whether family_name is a substring
    start = string.find(family_name)
    if start != -1:
        # insert suffix after the family_name substring
        end = start + len(family_name)
        new_string = string[:end] + suffix + string[end:]
    else:
        # it's not, we just append the suffix at the end
        new_string = string + suffix
    return new_string


def rename_record(name_record, family_name, suffix):
    string = name_record.toUnicode()
    new_string = insert_suffix(string, family_name, suffix)
    name_record.string = new_string
    return string, new_string


def rename_file(filename, family_name, suffix):
    filename, ext = os.path.splitext(filename)
    ps_name = family_name.replace(" ", "")
    if ps_name in filename:
        ps_suffix = suffix.replace(" ", "")
        return insert_suffix(filename, ps_name, ps_suffix) + ext
    else:
        return insert_suffix(filename, family_name, suffix) + ext


def add_family_suffix(font, suffix):
    table = font["name"]

    family_name = get_current_family_name(table)
    logger.info("  Current family name: '%s'", family_name)

    # postcript name can't contain spaces
    ps_family_name = family_name.replace(" ", "")
    ps_suffix = suffix.replace(" ", "")
    for rec in table.names:
        name_id = rec.nameID
        if name_id not in FAMILY_RELATED_IDS.values():
            continue
        if name_id == FAMILY_RELATED_IDS["POSTSCRIPT_NAME"]:
            old, new = rename_record(rec, ps_family_name, ps_suffix)
        elif name_id == FAMILY_RELATED_IDS["TRUETYPE_UNIQUE_ID"]:
            # The Truetype Unique ID rec may contain either the PostScript
            # Name or the Full Name string, so we try both
            if ps_family_name in rec.toUnicode():
                old, new = rename_record(rec, ps_family_name, ps_suffix)
            else:
                old, new = rename_record(rec, family_name, suffix)
        else:
            old, new = rename_record(rec, family_name, suffix)
        logger.info("    %r: '%s' -> '%s'", rec, old, new)

    return family_name


def main(args=None):
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("-s", "--suffix", required=True)
    parser.add_argument("input_fonts", metavar="FONTFILE", nargs="+")
    output_group = parser.add_mutually_exclusive_group()
    output_group.add_argument("-i", "--inplace", action="store_true")
    output_group.add_argument("-d", "--output-dir")
    output_group.add_argument("-o", "--output-file")
    parser.add_argument("-R", "--rename-files", action="store_true")
    parser.add_argument("-v", "--verbose", action="count", default=0)
    options = parser.parse_args(args)

    if not options.verbose:
        level = "WARNING"
    elif options.verbose == 1:
        level = "INFO"
    else:
        level = "DEBUG"
    logging.basicConfig(level=level, format="%(message)s")

    if options.output_file and len(options.input_fonts) > 1:
        parser.error("argument -o/--output-file can't be used with multiple inputs")
    if options.rename_files and (options.inplace or options.output_file):
        parser.error("argument -R not allowed with arguments -i or -o")

    for input_name in options.input_fonts:
        logger.info("Renaming font: '%s'", input_name)

        font = TTFont(input_name)
        family_name = add_family_suffix(font, options.suffix)

        if options.inplace:
            output_name = input_name
        elif options.output_file:
            output_name = options.output_file
        else:
            if options.rename_files:
                input_name = rename_file(input_name, family_name, options.suffix)
            output_name = makeOutputFileName(input_name, options.output_dir)

        font.save(output_name)
        logger.info("Saved font: '%s'", output_name)

        font.close()
        del font

    logger.info("Done!")


if __name__ == "__main__":
    main()