diff options
Diffstat (limited to 'fonts/Input_Fonts/Scripts/inputCustomize.py')
-rw-r--r-- | fonts/Input_Fonts/Scripts/inputCustomize.py | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/fonts/Input_Fonts/Scripts/inputCustomize.py b/fonts/Input_Fonts/Scripts/inputCustomize.py new file mode 100644 index 0000000..703c4f2 --- /dev/null +++ b/fonts/Input_Fonts/Scripts/inputCustomize.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python2.6 +# -*- coding: utf-8 -*- + +# Thanks to Jonas Kjellström and Cody Boisclair for their help in finding bugs in this script! + +import re +import os +import sys +import tempfile +from fontTools.ttLib import TTFont, newTable + +doc = """USAGE: python /path/to/inputCustomize.py [INPUT] [--dest=OUTPUT] [OPTIONS] +Use this script to customize one or more Input font files. +Requires TTX/FontTools: <http://sourceforge.net/projects/fonttools/> + +If INPUT is missing, it will customize all fonts in the Current Working Directory. +If OUTPUT is missing, it will overwrite the INPUT files. + +Options: + -h, --help Print this help text, and exit. + --lineHeight=<float> A multiplier for the font's built-in line-height. + --fourStyleFamily Only works when four INPUT files are provided. + Assigns Regular, Italic, Bold, and Bold Italic names to the + INPUT fonts in the order provided. + --suffix=<string> Append a suffix to the font names. Takes a string with no spaces. + --a=ss Swaps alternate single-story 'a' for the default double-story 'a' + --g=ss Swaps alternate single-story 'g' for the default double-story 'a' + --i=serif Swaps one of the alternate 'i' for the default in Sans/Mono + serifs + serifs_round + topserif + --l=serif Swaps one of the alternate 'l' for the default in Sans/Mono + serifs + serifs_round + topserif + --zero=slash Swaps the slashed zero for the default dotted zero + nodot Swaps a dotless zero for the default dotted zero + --asterisk=height Swaps the mid-height asterisk for the default superscripted asterisk + --braces=straight Swaps tamer straight-sided braces for the default super-curly braces + + Example 1: + $ cd /path/to/the/top/level/of/the/fonts/you/want/to/edit + $ python /path/to/InputCustomize.py --dest=/path/to/output --lineHeight=1.5 --suffix=Hack --fourStyleFamily --a=ss --g=ss --i=topserif --l=serifs_round --zero=slash --asterisk=height + + Example 2: + $ cd /path/to/the/top/level/of/the/fonts/you/want/to/edit + $ python /path/to/InputCustomize.py InputSans-Regular.ttf InputSans-Italic.ttf InputSans-Bold.ttf InputSerif-Regular.ttf --suffix=Hack --fourStyleFamily +""" + +class InputModifier(object): + """ + An object for manipulating Input, takes a TTFont. Sorry this is a little hacky. + """ + + def __init__(self, f): + self.f = f + + def changeLineHeight(self, lineHeight): + """ + Takes a line height multiplier and changes the line height. + """ + f = self.f + baseAsc = f['OS/2'].sTypoAscender + baseDesc = f['OS/2'].sTypoDescender + multiplier = float(lineHeight) + f['hhea'].ascent = round(baseAsc * multiplier) + f['hhea'].descent = round(baseDesc * multiplier) + f['OS/2'].usWinAscent = round(baseAsc * multiplier) + f['OS/2'].usWinDescent = round(baseDesc * multiplier)*-1 + + def swap(self, swap): + """ + Takes a dictionary of glyphs to swap and swaps 'em. + """ + f = self.f + glyphNames = f.getGlyphNames() + maps = { + 'a': {'a': 97, 'aring': 229, 'adieresis': 228, 'acyrillic': 1072, 'aacute': 225, 'amacron': 257, 'agrave': 224, 'atilde': 227, 'acircumflex': 226, 'aogonek': 261, 'abreve': 259}, + 'g': {'gdotaccent': 289, 'gbreve': 287, 'gcircumflex': 285, 'gcommaaccent': 291, 'g': 103}, + 'i': {'i': 105, 'iacute': 237, 'iogonek': 303, 'igrave': 236, 'itilde': 297, 'icircumflex': 238, 'imacron': 299, 'ij': 307, 'ibreve': 301, 'yicyrillic': 1111, 'idieresis': 239, 'icyrillic': 1110, 'dotlessi': 305,}, + 'l': {'l': 108, 'lcaron': 318, 'lcommaaccent': 316, 'lacute': 314, 'lslash': 322, 'ldot': 320}, + 'zero': {'zero': 48}, + 'asterisk': {'asterisk': 42}, + 'braces': {'braceleft': 123, 'braceright': 125} + } + swapMap = {} + for k, v in swap.items(): + for gname, u in maps[k].items(): + newGname = gname + '.salt_' + v + if newGname in glyphNames: + swapMap[gname] = newGname + for table in f['cmap'].tables: + cmap = table.cmap + for u, gname in cmap.items(): + if swapMap.has_key(gname): + cmap[u] = swapMap[gname] + + def fourStyleFamily(self, position, suffix=None): + """ + Replaces the name table and certain OS/2 values with those that will make a four-style family. + """ + f = self.f + source = TTFont(fourStyleFamilySources[position]) + + tf = tempfile.mkstemp() + pathToXML = tf[1] + source.saveXML(pathToXML, tables=['name']) + os.close(tf[0]) + + with open(pathToXML, "r") as temp: + xml = temp.read() + + # make the changes + if suffix: + xml = xml.replace("Input", "Input" + suffix) + + # save the table + with open(pathToXML, 'w') as temp: + temp.write(xml) + temp.write('\r') + + f['OS/2'].usWeightClass = source['OS/2'].usWeightClass + f['OS/2'].fsType = source['OS/2'].fsType + + # write the table + f['name'] = newTable('name') + f.importXML(pathToXML) + + def changeNames(self, suffix=None): + # this is a similar process to fourStyleFamily() + + tf = tempfile.mkstemp() + pathToXML = tf[1] + f.saveXML(pathToXML, tables=['name']) + os.close(tf[0]) + + with open(pathToXML, "r") as temp: + xml = temp.read() + + # make the changes + if suffix: + xml = xml.replace("Input", "Input" + suffix) + + # save the table + with open(pathToXML, 'w') as temp: + temp.write(xml) + temp.write('\r') + + # write the table + f['name'] = newTable('name') + f.importXML(pathToXML) + + + + + +baseTemplatePath = os.path.split(__file__)[0] +fourStyleFamilySources = [ + os.path.join(baseTemplatePath, '_template_Regular.txt'), + os.path.join(baseTemplatePath, '_template_Italic.txt'), + os.path.join(baseTemplatePath, '_template_Bold.txt'), + os.path.join(baseTemplatePath, '_template_BoldItalic.txt'), +] + +fourStyleFileNameAppend = [ 'Regular', 'Italic', 'Bold', 'BoldItalic' ] + +if __name__ == "__main__": + + # Get command-line arguments + go = True + arguments = sys.argv[1:] + paths = [] + swap = {} + lineHeight = None + fourStyleFamily = None + suffix = None + destBase = None + + + # parse arguments + for argument in arguments: + key = None + value = None + if len(argument.split('=')) == 2: + key, value = argument.split('=') + key = key[2:] + elif argument[0:2] == '--': + key = argument[2:] + value = True + elif argument == '-h': + print doc + go = False + else: + key = argument + value = None + # assign preference variables + if value is None: + paths.append(key) + elif key == 'lineHeight': + lineHeight = value + elif key == 'fourStyleFamily': + fourStyleFamily = True + elif key == 'suffix': + suffix = value + elif key == 'dest': + destBase = value + elif key == 'help': + print doc + go = False + else: + swap[key] = value + + # account for arguments where no value is given (for example, '--a' instead of '--a=ss') + if swap.get('a') is True: + swap['a'] = 'ss' + if swap.get('g') is True: + swap['g'] = 'ss' + if swap.get('i') is True: + swap['i'] = 'serifs' + if swap.get('l') is True: + swap['l'] = 'serifs' + if swap.get('zero') is True: + swap['zero'] = 'slash' + if swap.get('asterisk') is True: + swap['asterisk'] = 'height' + if swap.get('braces') is True: + swap['braces'] = 'straight' + + + # if specific paths were not supplied, collect them from the current directory + if not paths: + for root, dirs, files in os.walk(os.getcwd()): + for filename in files: + basePath, ext = os.path.splitext(filename) + if ext in ['.otf', '.ttf']: + paths.append(os.path.join(root, filename)) + + # if four paths were not supplied, do not process as a four-style family + if len(paths) != 4: + fourStyleFamily = None + + if go: + for i, path in enumerate(paths): + print os.path.split(path)[1] + f = TTFont(path) + c = InputModifier(f) + if lineHeight: + c.changeLineHeight(lineHeight) + if swap: + c.swap(swap) + if fourStyleFamily: + c.fourStyleFamily(i, suffix) + base, ext = os.path.splitext(path) + path = base + '_as_' + fourStyleFileNameAppend[i] + ext + elif suffix: + c.changeNames(suffix) + if destBase: + baseScrap, fileAndExt = os.path.split(path) + destPath = os.path.join(destBase, fileAndExt) + else: + destPath = path + try: + os.remove(destPath) + except: + pass + + # Take care of that weird "post" table issue, just in case. Delta#1 + try: + del f['post'].mapping['Delta#1'] + except: + pass + + f.save(destPath) + print 'done' |