1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# 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
from features import update_features
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 range(bitmap_max):
# Map the iteration's permutations using a bitmap
bitmap = [i >> n & 1 for n in range(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)
# Update the automatic features (code ligatures)
update_features(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
|