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