A bit restructured:

- mutators for info, kerning and glyphs are lazily constructed. Only make them if they're asked for.
- getInfoMutator() makes / returns info mutator.
- getKerningMutator() makes / returns kerning mutator
- getGlyphMutator() makes / returns glyph mutator
- loadFonts attempts to load the master UFOs and determine the defautl font by looking for the copyInfo flag, or if that is not found, by using mutator's findBias. Will warn if there is a conflct, but the copyInfo flag is leading.
- makeInstance() returns a font object for the asked location. You can decide to save it or not elsewher.
- generateUFO() makes UFOs on disk for all defined instances.

Still largely untested. Note: this requires an updated mutatorMath.
This commit is contained in:
Erik 2016-11-30 17:48:05 +01:00
parent 3d15d68270
commit e41b74ca42

View File

@ -48,52 +48,77 @@ class DesignSpaceProcessor(DesignSpaceDocument):
def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=2): def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=2):
super(DesignSpaceProcessor, self).__init__(readerClass=None, writerClass=None, fontClass=None) super(DesignSpaceProcessor, self).__init__(readerClass=None, writerClass=None, fontClass=None)
self.glyphMutators = {}
self.ufoVersion = ufoVersion # target UFO version self.ufoVersion = ufoVersion # target UFO version
self.roundGeometry = False self.roundGeometry = False
self.infoMutator = None self._glyphMutators = {}
self.kerningMutator = None self._infoMutator = None
self._kerningMutator = None
self.default = None # name of the default master self.default = None # name of the default master
self.defaultLoc = None self.defaultLoc = None
self.fonts = {} self.fonts = {}
self.glyphNames = [] # list of all glyphnames
def process(self): def generateUFO(self):
# make the instances # makes the instances
self._loadFonts() self.loadFonts()
self._buildMutators()
for instanceDescriptor in self.instances: for instanceDescriptor in self.instances:
if instanceDescriptor.path is not None: if instanceDescriptor.path is None:
self.makeInstance(instanceDescriptor) continue
font = self.makeInstance(instanceDescriptor)
if not os.path.exists(os.path.dirname(instanceDescriptor.path)):
os.makedirs(os.path.dirname(instanceDescriptor.path))
font.save(instanceDescriptor.path, self.ufoVersion)
def _buildMutators(self): def getInfoMutator(self):
""" Returns a info mutator """
if self._infoMutator:
return self._infoMutator
infoItems = [] infoItems = []
for sourceDescriptor in self.sources:
loc = Location(sourceDescriptor.location)
sourceFont = self.fonts[sourceDescriptor.name]
infoItems.append((loc, self.mathInfoClass(sourceFont.info)))
bias, self._infoMutator = buildMutator(infoItems, bias=self.defaultLoc)
return self._infoMutator
def getKerningMutator(self):
""" Return a kerning mutator """
if self._kerningMutator:
return self._kerningMutator
kerningItems = [] kerningItems = []
glyphItems = {} for sourceDescriptor in self.sources:
loc = Location(sourceDescriptor.location)
sourceFont = self.fonts[sourceDescriptor.name]
kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups)))
bias, self._kerningMutator = buildMutator(kerningItems, bias=self.defaultLoc)
return self._kerningMutator
def getGlyphMutator(self, glyphName):
""" Return a glyph mutator """
if glyphName in self._glyphMutators:
return self._glyphMutators[glyphName]
items = []
for sourceDescriptor in self.sources: for sourceDescriptor in self.sources:
loc = Location(sourceDescriptor.location) loc = Location(sourceDescriptor.location)
f = self.fonts[sourceDescriptor.name] f = self.fonts[sourceDescriptor.name]
infoItems.append((loc, self.mathInfoClass(f.info))) if glyphName in sourceDescriptor.mutedGlyphNames:
kerningItems.append((loc, self.mathKerningClass(f.kerning, f.groups))) continue
for g in f: items.append((loc, self.mathGlyphClass(f[glyphName])))
if g.name in sourceDescriptor.mutedGlyphNames: bias, self._glyphMutators[glyphName] = buildMutator(items, bias=self.defaultLoc)
continue return self._glyphMutators[glyphName]
if not g.name in glyphItems:
glyphItems[g.name] = []
glyphItems[g.name].append((loc, self.mathGlyphClass(g)))
bias, self.infoMutator = buildMutator(infoItems, bias=self.defaultLoc)
bias, self.kerningMutator = buildMutator(kerningItems, bias=self.defaultLoc)
for name, items in glyphItems.items():
bias, self.glyphMutators[name] = buildMutator(items, bias=self.defaultLoc)
def _loadFonts(self): def loadFonts(self):
# find the default candidate based on the info flag # Load the fonts and find the default candidate based on the info flag
defaultCandidate = None defaultCandidate = None
for sourceDescriptor in self.sources: for sourceDescriptor in self.sources:
names = set()
if not sourceDescriptor.name in self.fonts: if not sourceDescriptor.name in self.fonts:
self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path)
names = names | set(self.fonts[sourceDescriptor.name].keys())
if sourceDescriptor.copyInfo: if sourceDescriptor.copyInfo:
# we choose you! # we choose you!
defaultCandidate = sourceDescriptor defaultCandidate = sourceDescriptor
self.glyphNames = list(names)
# find the default based on mutatorMath bias # find the default based on mutatorMath bias
masterLocations = [Location(src.location) for src in self.sources] masterLocations = [Location(src.location) for src in self.sources]
mutatorBias = biasFromLocations(masterLocations) mutatorBias = biasFromLocations(masterLocations)
@ -113,24 +138,23 @@ class DesignSpaceProcessor(DesignSpaceDocument):
self.defaultLoc = Location(self.default.location) self.defaultLoc = Location(self.default.location)
def makeInstance(self, instanceDescriptor): def makeInstance(self, instanceDescriptor):
# generate the UFO for this instance """ Generate a font object for this instance """
if not os.path.exists(os.path.dirname(instanceDescriptor.path)):
os.makedirs(os.path.dirname(instanceDescriptor.path))
font = self._instantiateFont(None) font = self._instantiateFont(None)
# make fonty things here # make fonty things here
loc = Location(instanceDescriptor.location) loc = Location(instanceDescriptor.location)
# calculated info # make the kerning
if instanceDescriptor.kerning:
self.getKerningMutator().makeInstance(loc).extractKerning(font)
# make the info
if instanceDescriptor.info: if instanceDescriptor.info:
info = self.infoMutator.makeInstance(loc) self.getInfoMutator().makeInstance(loc).extractInfo(font.info)
info = self._infoMutator.makeInstance(loc)
info.extractInfo(font.info) info.extractInfo(font.info)
font.info.familyName = instanceDescriptor.familyName font.info.familyName = instanceDescriptor.familyName
font.info.styleName = instanceDescriptor.styleName font.info.styleName = instanceDescriptor.styleName
font.info.postScriptFontName = instanceDescriptor.postScriptFontName font.info.postScriptFontName = instanceDescriptor.postScriptFontName
font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
if instanceDescriptor.kerning:
kerning = self.kerningMutator.makeInstance(loc)
kerning.extractKerning(font)
# copied info # copied info
for sourceDescriptor in self.sources: for sourceDescriptor in self.sources:
if sourceDescriptor.copyInfo: if sourceDescriptor.copyInfo:
@ -144,78 +168,75 @@ class DesignSpaceProcessor(DesignSpaceDocument):
font.features.text = u""+featuresText font.features.text = u""+featuresText
elif isinstance(featuresText, unicode): elif isinstance(featuresText, unicode):
font.features.text = featuresText font.features.text = featuresText
# glyphs # glyphs
for name, glyphMutator in self.glyphMutators.items(): for glyphName in self.glyphNames:
if name in instanceDescriptor.glyphs.keys(): glyphMutator = self.getGlyphMutator(glyphName)
glyphData = instanceDescriptor.glyphs[name] if glyphName in instanceDescriptor.glyphs.keys():
# reminder: this is what the glyphData can look like
# {'instanceLocation': {'custom': 0.0, 'weight': 824.0},
# 'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 0.0}},
# {'font': 'master.Adobe VF Prototype.Master_1.1',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 368.0}},
# {'font': 'master.Adobe VF Prototype.Master_2.2',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 1000.0}},
# {'font': 'master.Adobe VF Prototype.Master_3.3',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 1000.0}},
# {'font': 'master.Adobe VF Prototype.Master_0.4',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 0.0}},
# {'font': 'master.Adobe VF Prototype.Master_4.5',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 368.0}}],
# 'unicodeValue': 36}
glyphData = instanceDescriptor.glyphs[glyphName]
else: else:
glyphData = {} glyphData = {}
# {'instanceLocation': {'custom': 0.0, 'weight': 824.0}, font.newGlyph(glyphName)
# 'masters': [{'font': 'master.Adobe VF Prototype.Master_0.0', font[glyphName].clear()
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 0.0}},
# {'font': 'master.Adobe VF Prototype.Master_1.1',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 368.0}},
# {'font': 'master.Adobe VF Prototype.Master_2.2',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 0.0, 'weight': 1000.0}},
# {'font': 'master.Adobe VF Prototype.Master_3.3',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 1000.0}},
# {'font': 'master.Adobe VF Prototype.Master_0.4',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 0.0}},
# {'font': 'master.Adobe VF Prototype.Master_4.5',
# 'glyphName': 'dollar.nostroke',
# 'location': {'custom': 100.0, 'weight': 368.0}}],
# 'unicodeValue': 36}
font.newGlyph(name)
font[name].clear()
if glyphData.get('mute', False): if glyphData.get('mute', False):
# mute this glyph, skip # mute this glyph, skip
continue continue
glyphInstanceLocation = Location(glyphData.get("instanceLocation", instanceDescriptor.location)) glyphInstanceLocation = Location(glyphData.get("instanceLocation", instanceDescriptor.location))
glyphInstanceUnicode = glyphData.get("unicodeValue", font[name].unicode) glyphInstanceUnicode = glyphData.get("unicodeValue", font[glyphName].unicode)
note = glyphData.get("note") note = glyphData.get("note")
if note: if note:
font[name] = note font[glyphName] = note
masters = glyphData.get("masters", None) masters = glyphData.get("masters", None)
if masters: if masters:
items = [] items = []
for glyphMaster in masters: for glyphMaster in masters:
sourceGlyphFont = glyphMaster.get("font") sourceGlyphFont = glyphMaster.get("font")
sourceGlyphName = glyphMaster.get("glyphName", name) sourceGlyphName = glyphMaster.get("glyphName", glyphName)
sourceGlyph = MathGlyph(self.fonts.get(sourceGlyphFont)[sourceGlyphName]) sourceGlyph = MathGlyph(self.fonts.get(sourceGlyphFont)[sourceGlyphName])
sourceGlyphLocation = Location(glyphMaster.get("location")) sourceGlyphLocation = Location(glyphMaster.get("location"))
items.append((sourceGlyphLocation, sourceGlyph)) items.append((sourceGlyphLocation, sourceGlyph))
bias, glyphMutator = buildMutator(items, bias=self.defaultLoc) bias, glyphMutator = buildMutator(items, bias=self.defaultLoc)
glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation) glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation)
font.newGlyph(name) font.newGlyph(glyphName)
font[name].clear() font[glyphName].clear()
if self.roundGeometry: if self.roundGeometry:
try: try:
glyphInstanceObject = glyphInstanceObject.round() glyphInstanceObject = glyphInstanceObject.round()
except AttributeError: except AttributeError:
pass pass
try: try:
glyphInstanceObject.extractGlyph(font[name], onlyGeometry=True) glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
except TypeError: except TypeError:
# this causes ruled glyphs to end up in the wrong glyphname # this causes ruled glyphs to end up in the wrong glyphname
# but defcon2 objects don't support it # but defcon2 objects don't support it
pPen = font[name].getPointPen() pPen = font[glyphName].getPointPen()
font[name].clear() font[glyphName].clear()
glyphInstanceObject.drawPoints(pPen) glyphInstanceObject.drawPoints(pPen)
font[name].width = glyphInstanceObject.width font[glyphName].width = glyphInstanceObject.width
# save return font
font.save(instanceDescriptor.path, self.ufoVersion)
def _instantiateFont(self, path): def _instantiateFont(self, path):
""" """ Return a instance of a font object with all the given subclasses"""
Return a instance of a font object
with all the given subclasses
"""
return self.fontClass(path, return self.fontClass(path,
libClass=self.libClass, libClass=self.libClass,
kerningClass=self.kerningClass, kerningClass=self.kerningClass,
@ -229,8 +250,7 @@ class DesignSpaceProcessor(DesignSpaceDocument):
glyphAnchorClass=self.glyphAnchorClass) glyphAnchorClass=self.glyphAnchorClass)
def _copyFontInfo(self, sourceInfo, targetInfo): def _copyFontInfo(self, sourceInfo, targetInfo):
""" Copy the non-calculating fields from the source info. """ Copy the non-calculating fields from the source info."""
"""
infoAttributes = [ infoAttributes = [
"versionMajor", "versionMajor",
"versionMinor", "versionMinor",
@ -385,11 +405,7 @@ if __name__ == "__main__":
# execute the test document # execute the test document
d = DesignSpaceProcessor() d = DesignSpaceProcessor()
d.read(docPath) d.read(docPath)
d.process() d.generateUFO()
#print(d.defaultCandidate)
# for w in range(0, 1000, 100):
# r = d.makeGlyph("glyphOne", dict(pop=w))
# print(w, r.width)
selfTest = True selfTest = True
if selfTest: if selfTest: