blob: 9713a4cc44334fbf6ebdd9b60e6cd86c92281f2a [file] [log] [blame]
Deepanshu Guptaad3f2882014-05-15 18:39:30 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070018Rename the PS name of all fonts in the input directories and copy them to the
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070019output directory.
20
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070021Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070022
23"""
24
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070025import glob
Deepanshu Guptad23417a2014-05-27 10:54:41 -070026from multiprocessing import Pool
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -070027import os
28import re
29import shutil
30import sys
31import xml.etree.ElementTree as etree
32
33# Prevent .pyc files from being created.
34sys.dont_write_bytecode = True
35
36# fontTools is available at platform/external/fonttools
37from fontTools import ttx
Deepanshu Guptad23417a2014-05-27 10:54:41 -070038
39# global variable
40dest_dir = '/tmp'
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070041
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -070042
43class FontInfo(object):
44 family = None
45 style = None
46 version = None
47 ends_in_regular = False
48 fullname = None
49
50
51class InvalidFontException(Exception):
52 pass
53
54
55# These constants represent the value of nameID parameter in the namerecord for
56# different information.
57# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b
58NAMEID_FAMILY = 1
59NAMEID_STYLE = 2
60NAMEID_FULLNAME = 4
61NAMEID_VERSION = 5
62
63
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070064def main(argv):
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070065 if len(argv) < 2:
66 sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/')
Deepanshu Guptad23417a2014-05-27 10:54:41 -070067 for directory in argv:
68 if not os.path.isdir(directory):
69 sys.exit(directory + ' is not a valid directory')
70 global dest_dir
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070071 dest_dir = argv[-1]
72 src_dirs = argv[:-1]
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070073 cwd = os.getcwd()
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070074 os.chdir(dest_dir)
Deepanshu Guptaad3f2882014-05-15 18:39:30 -070075 files = glob.glob('*')
76 for filename in files:
77 os.remove(filename)
78 os.chdir(cwd)
Deepanshu Guptad23417a2014-05-27 10:54:41 -070079 input_fonts = list()
Deepanshu Gupta49a60d42014-05-19 16:16:29 -070080 for src_dir in src_dirs:
Deepanshu Gupta46eff272014-05-27 11:21:24 -070081 for dirname, dirnames, filenames in os.walk(src_dir):
82 for filename in filenames:
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -070083 input_path = os.path.join(dirname, filename)
84 extension = os.path.splitext(filename)[1].lower()
85 if extension == '.ttf':
86 input_fonts.append(input_path)
87 elif extension == '.xml':
88 shutil.copy(input_path, dest_dir)
Deepanshu Gupta46eff272014-05-27 11:21:24 -070089 if '.git' in dirnames:
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -070090 # don't go into any .git directories.
91 dirnames.remove('.git')
Deepanshu Guptad23417a2014-05-27 10:54:41 -070092 # Create as many threads as the number of CPUs
93 pool = Pool(processes=None)
94 pool.map(convert_font, input_fonts)
95
96
Deepanshu Guptad23417a2014-05-27 10:54:41 -070097def convert_font(input_path):
98 filename = os.path.basename(input_path)
99 print 'Converting font: ' + filename
100 # the path to the output file. The file name is the fontfilename.ttx
101 ttx_path = os.path.join(dest_dir, filename)
102 ttx_path = ttx_path[:-1] + 'x'
103 try:
104 # run ttx to generate an xml file in the output folder which represents all
105 # its info
106 ttx_args = ['-q', '-d', dest_dir, input_path]
107 ttx.main(ttx_args)
108 # now parse the xml file to change its PS name.
109 tree = etree.parse(ttx_path)
Deepanshu Guptad23417a2014-05-27 10:54:41 -0700110 root = tree.getroot()
111 for name in root.iter('name'):
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700112 update_tag(name, get_font_info(name))
113 tree.write(ttx_path, xml_declaration=True, encoding='utf-8')
Deepanshu Guptad23417a2014-05-27 10:54:41 -0700114 # generate the udpated font now.
115 ttx_args = ['-q', '-d', dest_dir, ttx_path]
116 ttx.main(ttx_args)
117 except InvalidFontException:
118 # In case of invalid fonts, we exit.
119 print filename + ' is not a valid font'
120 raise
121 except Exception as e:
122 print 'Error converting font: ' + filename
123 print e
124 # Some fonts are too big to be handled by the ttx library.
125 # Just copy paste them.
126 shutil.copy(input_path, dest_dir)
127 try:
128 # delete the temp ttx file is it exists.
129 os.remove(ttx_path)
130 except OSError:
131 pass
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700132
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700133
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700134def get_font_info(tag):
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700135 """ Returns a list of FontInfo representing the various sets of namerecords
136 found in the name table of the font. """
137 fonts = []
138 font = None
139 last_name_id = sys.maxint
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700140 for namerecord in tag.iter('namerecord'):
141 if 'nameID' in namerecord.attrib:
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700142 name_id = int(namerecord.attrib['nameID'])
143 # A new font should be created for each platform, encoding and language
144 # id. But, since the nameIDs are sorted, we use the easy approach of
145 # creating a new one when the nameIDs reset.
146 if name_id <= last_name_id and font is not None:
147 fonts.append(font)
148 font = None
149 last_name_id = name_id
150 if font is None:
151 font = FontInfo()
152 if name_id == NAMEID_FAMILY:
153 font.family = namerecord.text.strip()
154 if name_id == NAMEID_STYLE:
155 font.style = namerecord.text.strip()
156 if name_id == NAMEID_FULLNAME:
157 font.ends_in_regular = ends_in_regular(namerecord.text)
158 font.fullname = namerecord.text.strip()
159 if name_id == NAMEID_VERSION:
160 font.version = get_version(namerecord.text)
161 if font is not None:
162 fonts.append(font)
163 return fonts
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700164
165
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700166def update_tag(tag, fonts):
167 last_name_id = sys.maxint
168 fonts_iterator = fonts.__iter__()
169 font = None
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700170 for namerecord in tag.iter('namerecord'):
171 if 'nameID' in namerecord.attrib:
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700172 name_id = int(namerecord.attrib['nameID'])
173 if name_id <= last_name_id:
174 font = fonts_iterator.next()
175 font = update_font_name(font)
176 last_name_id = name_id
177 if name_id == NAMEID_FAMILY:
178 namerecord.text = font.family
179 if name_id == NAMEID_FULLNAME:
180 namerecord.text = font.fullname
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700181
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700182
183def update_font_name(font):
184 """ Compute the new font family name and font fullname. If the font has a
185 valid version, it's sanitized and appended to the font family name. The
186 font fullname is then created by joining the new family name and the
187 style. If the style is 'Regular', it is appended only if the original font
188 had it. """
189 if font.family is None or font.style is None:
190 raise InvalidFontException('Font doesn\'t have proper family name or style')
191 if font.version is not None:
192 new_family = font.family + font.version
193 else:
194 new_family = font.family
195 if font.style is 'Regular' and not font.ends_in_regular:
196 font.fullname = new_family
197 else:
198 font.fullname = new_family + ' ' + font.style
199 font.family = new_family
200 return font
201
202
203def ends_in_regular(string):
204 """ According to the specification, the font fullname should not end in
205 'Regular' for plain fonts. However, some fonts don't obey this rule. We
206 keep the style info, to minimize the diff. """
207 string = string.strip().split()[-1]
208 return string is 'Regular'
209
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700210
211def get_version(string):
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700212 string = string.strip()
Deepanshu Guptad654b6f2015-04-06 12:49:25 -0700213 # The spec says that the version string should start with "Version ". But not
214 # all fonts do. So, we return the complete string if it doesn't start with
215 # the prefix, else we return the rest of the string after sanitizing it.
216 prefix = 'Version '
217 if string.startswith(prefix):
218 string = string[len(prefix):]
219 return sanitize(string)
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700220
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700221
222def sanitize(string):
Deepanshu Guptad654b6f2015-04-06 12:49:25 -0700223 """ Remove non-standard chars. """
Deepanshu Gupta7ea293b2014-06-27 18:39:32 -0700224 return re.sub(r'[^\w-]+', '', string)
225
Deepanshu Guptaad3f2882014-05-15 18:39:30 -0700226if __name__ == '__main__':
227 main(sys.argv[1:])