# Adapted from https://github.com/larsenwork/monoid # Copyright 2015 Chase Colman (chase@colman.io) # LICENSE: MIT # vim: sts=4 sw=4 ts=4 et import fontforge from itertools import compress import os from os.path import basename, splitext, join import subprocess SCRIPTS = os.path.dirname(os.path.realpath(__file__)) def mkdir_p(path): normalized = os.path.normpath(path) try: os.makedirs(normalized) except OSError: pass # Builder def style(name, does): if not isinstance(does, list): does = [does] option(name, name, [Variation(name)] + does) return name def option(abrv, name, does): if not isinstance(does, list): does = [does] option.operations[abrv] = does option.abrvs.append(abrv) option.names[abrv] = name return abrv # Initialize the operations map, abbreviation list, and name map option.operations = {} option.abrvs = [] option.names = {} def conflicting(*abrvs): """Wrap the abbreviations as a tuple in the option abbreviation list""" # Assumes last #abrvs abbreviations are conflicting options option.abrvs = option.abrvs[:-len(abrvs)] + [tuple(abrvs)] def _expand_options(bitmap): # Apply the bitmap to the options opts = compress(option.abrvs, bitmap) # Expand the permutations for all options expanded = [[]] for opt in opts: if isinstance(opt, tuple): expanded = [items + [prmtn] for items in expanded for prmtn in opt] else: expanded = [items + [opt] for items in expanded] return expanded def permutations(): """Yields all possible permutations from the options list""" count = len(option.abrvs) # Each option is a binary choice, so we use an int as a quick bitmap. # To iterate over every possible permutation, all we have to do is increment # up to the maximum value 2^(#options) bitmap_max = 1 << count # Iterate over all possible permutations for i in xrange(bitmap_max): # Map the iteration's permutations using a bitmap bitmap = [i >> n & 1 for n in xrange(count)] for opts in _expand_options(bitmap): yield(int(float(i)/bitmap_max*100), opts) def _build(dstdir, font, permutations): for prcnt, opts in permutations: # Open the original font fnt = fontforge.open(font) # Get the base name for the font name = splitext(basename(font))[0] # Build a variant name based on applied options variants = [] for opt in opts: # Append this option to the font name variants.append(str(opt)) # Run all the operations for this option for oper in option.operations[opt]: oper(fnt) variant = '-'.join(variants) or 'Normal' variant_dir = join(dstdir, variant) print('Generating ' + variant_dir) mkdir_p(join(variant_dir, 'TTF')) mkdir_p(join(variant_dir, 'OTF')) mkdir_p(join(variant_dir, 'Webfonts')) # Output the files and cleanup fnt.generate(join(variant_dir, 'TTF', name + '.ttf'), flags=("opentype", "dummy-dsig")) fnt.generate(join(variant_dir, 'OTF', name + '.otf'), flags=("opentype", "dummy-dsig")) fnt.generate(join(variant_dir, 'Webfonts', name + '.svg')) fnt.close() # Output other formats and the CSS declaration subprocess.check_call( [join(SCRIPTS, 'generate-other-formats'), font], cwd=variant_dir ) subprocess.check_call( [join(SCRIPTS, 'generate-css-decl'), font], cwd=variant_dir ) def build(dstdir, font): _build(dstdir, font, permutations()) def build_batch(dstdir, font, total_nodes, node_number): # Starting at (i) node_number, build option every (n) total_nodes _build(dstdir, font, list(permutations())[node_number::total_nodes]) # Operations ## NOTE: ## All operations return a closure with the 1st argument being a fontforge.font def Line(ascent, descent): """Sets the ascent and/or descent of the font's line""" def line_op(fnt): fnt.os2_winascent = fnt.os2_typoascent = fnt.hhea_ascent = ascent fnt.os2_windescent = descent fnt.os2_typodescent = fnt.hhea_descent = -descent return line_op def Bearing(left=0, right=0): """Adjusts the left and/or right bearings of all glyphs""" def bearing_op(fnt): for glyph in fnt.glyphs(): if left != 0: glyph.left_side_bearing += left if right != 0: glyph.right_side_bearing += right return bearing_op def Swap(glyph1, glyph2): """Swaps the places of two glyphs""" def swap_op(fnt): # Unlike selections, glyph layer data is returned as a copy swp = fnt[glyph1].foreground fnt[glyph1].foreground = fnt[glyph2].foreground fnt[glyph2].foreground = swp return swap_op def SwapLookup(target_lookup): """Swaps the places of glyphs based on an OpenType lookup table""" def swaplookup_op(fnt): # Get every subtable for every matching lookup lookups = [i for i in fnt.gsub_lookups if fnt.getLookupInfo(i)[2][0][0] == target_lookup] subtables = [] for lookup in lookups: for subtable in fnt.getLookupSubtables(lookup): subtables.append(subtable) for glyph in fnt.glyphs(): subbed = False for subtable in subtables: posSub = glyph.getPosSub(subtable) if not subbed and posSub and posSub[0][1] == "Substitution": subbed = True # Don't double tap if there are duplicates sub = posSub[0][2] swp = glyph.foreground glyph.foreground = fnt[sub].foreground fnt[sub].foreground = swp return swaplookup_op def DropCAltAndLiga(): """Removes Contextual Alternates and Ligatures""" def dropcaltandliga_op(fnt): for lookup in fnt.gsub_lookups: if fnt.getLookupInfo(lookup)[0] in ['gsub_ligature', 'gsub_contextchain']: fnt.removeLookup(lookup) return dropcaltandliga_op def Variation(name): """Changes the subfamily/variation of the font""" def variation_op(fnt): # Get the SFNT information as dictionary {property: value} # where English (US) is the language... Here be dragons. # # o # /\ # /::\ # /::::\ # ,a_a /\::::/\ # {/ ''\_ /\ \::/\ \ # {\ ,_oo) /\ \ \/\ \ \ # {/ (_^____/ \ \ \ \ \ \ # .=. {/ \___)))*) \ \ \ \ \/ # (.=.`\ {/ /=; ~/ \ \ \ \/ # \ `\{/( \/\ / \ \ \/ # \ `. `\ ) ) \ \/ # \ // /_/_ \/ # '==''---)))) sfnt_dict = {sfnt[1]: sfnt[2] for sfnt in fnt.sfnt_names if sfnt[0] == 'English (US)'} fnt.familyname = sfnt_dict['Family'] + ' ' + name fnt.fullname = fnt.familyname + ' ' + sfnt_dict['SubFamily'] fnt.fontname = fnt.fullname.replace(' ', '-') fnt.appendSFNTName('English (US)', 'Family', fnt.familyname) fnt.appendSFNTName('English (US)', 'Fullname', fnt.fullname) fnt.appendSFNTName('English (US)', 'PostScriptName', fnt.fontname) fnt.appendSFNTName('English (US)', 'SubFamily', sfnt_dict['SubFamily']) fnt.appendSFNTName('English (US)', 'UniqueID', sfnt_dict['UniqueID'] + ' : ' + name) return variation_op