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