403 lines
16 KiB
Python
Raw Normal View History

from __future__ import print_function, division, absolute_import
from ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3
from pprint import pprint
"""
A subclassed DesignSpaceDocument that can
- process the document and generate finished UFOs with MutatorMath.
- read and write
- bypass and eventually replace the mutatormath ufo generator.
"""
from designSpaceDocument import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor
from defcon.objects.font import Font
import defcon
from fontMath.mathGlyph import MathGlyph
from fontMath.mathInfo import MathInfo
from fontMath.mathKerning import MathKerning
from mutatorMath.objects.mutator import buildMutator
from mutatorMath.objects.location import biasFromLocations, Location
import os
class DesignSpaceProcessor(DesignSpaceDocument):
"""
builder of glyphs from designspaces
validate the data
if it works, make a generating thing
"""
fontClass = defcon.Font
glyphClass = defcon.Glyph
libClass = defcon.Lib
glyphContourClass = defcon.Contour
glyphPointClass = defcon.Point
glyphComponentClass = defcon.Component
glyphAnchorClass = defcon.Anchor
kerningClass = defcon.Kerning
groupsClass = defcon.Groups
infoClass = defcon.Info
featuresClass = defcon.Features
mathInfoClass = MathInfo
mathGlyphClass = MathGlyph
mathKerningClass = MathKerning
def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=2):
2016-11-30 16:47:56 +01:00
super(DesignSpaceProcessor, self).__init__(readerClass=None, writerClass=None, fontClass=None)
2016-11-30 16:52:51 +01:00
self.ufoVersion = ufoVersion # target UFO version
self.roundGeometry = False
self._glyphMutators = {}
self._infoMutator = None
self._kerningMutator = None
self.fonts = {}
self.glyphNames = [] # list of all glyphnames
def generateUFO(self):
# makes the instances
self.checkAxes()
self.checkDefault()
self.loadFonts()
for instanceDescriptor in self.instances:
if instanceDescriptor.path is None:
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 getInfoMutator(self):
""" Returns a info mutator """
if self._infoMutator:
return self._infoMutator
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 = []
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:
loc = Location(sourceDescriptor.location)
f = self.fonts[sourceDescriptor.name]
if glyphName in sourceDescriptor.mutedGlyphNames:
continue
if not glyphName in f:
# log this>
continue
items.append((loc, self.mathGlyphClass(f[glyphName])))
bias, self._glyphMutators[glyphName] = buildMutator(items, bias=self.defaultLoc)
return self._glyphMutators[glyphName]
def loadFonts(self):
# Load the fonts and find the default candidate based on the info flag
for sourceDescriptor in self.sources:
names = set()
if not sourceDescriptor.name in self.fonts:
self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path)
names = names | set(self.fonts[sourceDescriptor.name].keys())
self.glyphNames = list(names)
def makeInstance(self, instanceDescriptor):
""" Generate a font object for this instance """
font = self._instantiateFont(None)
# make fonty things here
loc = Location(instanceDescriptor.location)
# make the kerning
if instanceDescriptor.kerning:
self.getKerningMutator().makeInstance(loc).extractKerning(font)
# make the info
if instanceDescriptor.info:
self.getInfoMutator().makeInstance(loc).extractInfo(font.info)
info = self._infoMutator.makeInstance(loc)
info.extractInfo(font.info)
font.info.familyName = instanceDescriptor.familyName
font.info.styleName = instanceDescriptor.styleName
font.info.postScriptFontName = instanceDescriptor.postScriptFontName
font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
# copied info
for sourceDescriptor in self.sources:
if sourceDescriptor.copyInfo:
# this is the source
self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info)
if sourceDescriptor.copyLib:
font.lib.update(self.fonts[sourceDescriptor.name].lib)
if sourceDescriptor.copyFeatures:
featuresText = self.fonts[sourceDescriptor.name].features.text
if isinstance(featuresText, str):
font.features.text = u""+featuresText
elif isinstance(featuresText, unicode):
font.features.text = featuresText
# glyphs
for glyphName in self.glyphNames:
glyphMutator = self.getGlyphMutator(glyphName)
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:
glyphData = {}
font.newGlyph(glyphName)
font[glyphName].clear()
if glyphData.get('mute', False):
# mute this glyph, skip
continue
glyphInstanceLocation = Location(glyphData.get("instanceLocation", instanceDescriptor.location))
glyphInstanceUnicode = glyphData.get("unicodeValue", font[glyphName].unicode)
note = glyphData.get("note")
if note:
font[glyphName] = note
masters = glyphData.get("masters", None)
if masters:
items = []
for glyphMaster in masters:
sourceGlyphFont = glyphMaster.get("font")
sourceGlyphName = glyphMaster.get("glyphName", glyphName)
m = self.fonts.get(sourceGlyphFont)
if not sourceGlyphName in m:
continue
sourceGlyph = MathGlyph(m[sourceGlyphName])
sourceGlyphLocation = Location(glyphMaster.get("location"))
items.append((sourceGlyphLocation, sourceGlyph))
bias, glyphMutator = buildMutator(items, bias=self.defaultLoc)
glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation)
font.newGlyph(glyphName)
font[glyphName].clear()
if self.roundGeometry:
try:
glyphInstanceObject = glyphInstanceObject.round()
except AttributeError:
pass
try:
glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
except TypeError:
# this causes ruled glyphs to end up in the wrong glyphname
# but defcon2 objects don't support it
pPen = font[glyphName].getPointPen()
font[glyphName].clear()
glyphInstanceObject.drawPoints(pPen)
font[glyphName].width = glyphInstanceObject.width
return font
def _instantiateFont(self, path):
""" Return a instance of a font object with all the given subclasses"""
return self.fontClass(path,
libClass=self.libClass,
kerningClass=self.kerningClass,
groupsClass=self.groupsClass,
infoClass=self.infoClass,
featuresClass=self.featuresClass,
glyphClass=self.glyphClass,
glyphContourClass=self.glyphContourClass,
glyphPointClass=self.glyphPointClass,
glyphComponentClass=self.glyphComponentClass,
glyphAnchorClass=self.glyphAnchorClass)
2016-11-30 15:50:19 +01:00
def _copyFontInfo(self, sourceInfo, targetInfo):
""" Copy the non-calculating fields from the source info."""
infoAttributes = [
"versionMajor",
"versionMinor",
"copyright",
"trademark",
"note",
"openTypeGaspRangeRecords",
"openTypeHeadCreated",
"openTypeHeadFlags",
"openTypeNameDesigner",
"openTypeNameDesignerURL",
"openTypeNameManufacturer",
"openTypeNameManufacturerURL",
"openTypeNameLicense",
"openTypeNameLicenseURL",
"openTypeNameVersion",
"openTypeNameUniqueID",
"openTypeNameDescription",
"#openTypeNamePreferredFamilyName",
"#openTypeNamePreferredSubfamilyName",
"#openTypeNameCompatibleFullName",
"openTypeNameSampleText",
"openTypeNameWWSFamilyName",
"openTypeNameWWSSubfamilyName",
"openTypeNameRecords",
"openTypeOS2Selection",
"openTypeOS2VendorID",
"openTypeOS2Panose",
"openTypeOS2FamilyClass",
"openTypeOS2UnicodeRanges",
"openTypeOS2CodePageRanges",
"openTypeOS2Type",
"postscriptIsFixedPitch",
"postscriptForceBold",
"postscriptDefaultCharacter",
"postscriptWindowsCharacterSet"
]
for infoAttribute in infoAttributes:
copy = False
if self.ufoVersion == 1 and infoAttribute in fontInfoAttributesVersion1:
copy = True
elif self.ufoVersion == 2 and infoAttribute in fontInfoAttributesVersion2:
copy = True
elif self.ufoVersion == 3 and infoAttribute in fontInfoAttributesVersion3:
copy = True
if copy:
value = getattr(sourceInfo, infoAttribute)
setattr(targetInfo, infoAttribute, value)
if __name__ == "__main__":
# standalone test
import shutil
def addGlyphs(font, s):
# we need to add the glyphs
for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour']:
font.newGlyph(n)
g = font[n]
p = g.getPen()
p.moveTo((0,0))
p.lineTo((s,0))
p.lineTo((s,s))
p.lineTo((0,s))
p.closePath()
g.move((0,s*2))
g.width = s
def fillInfo(font):
font.info.unitsPerEm = 1000
font.info.ascender = 800
font.info.descender = -200
def makeTestFonts(rootPath):
""" Make some test fonts that have the kerning problem."""
path1 = os.path.join(rootPath, "geometryMaster1.ufo")
path2 = os.path.join(rootPath, "geometryMaster2.ufo")
path3 = os.path.join(rootPath, "my_test_instance_dir_one", "geometryInstance%3.3f.ufo")
path4 = os.path.join(rootPath, "my_test_instance_dir_two", "geometryInstanceAnisotropic1.ufo")
path5 = os.path.join(rootPath, "my_test_instance_dir_two", "geometryInstanceAnisotropic2.ufo")
# Two masters
f1 = Font()
addGlyphs(f1, 100)
f1.features.text = u"# features text from master 1"
f2 = Font()
addGlyphs(f2, 500)
f2.features.text = u"# features text from master 2"
fillInfo(f1)
f1.info.ascender = 400
f1.info.descender = -200
fillInfo(f2)
f2.info.ascender = 600
f2.info.descender = -100
f1.info.copyright = u"This is the copyright notice from master 1"
2016-11-30 15:50:19 +01:00
f2.info.copyright = u"This is the copyright notice from master 2"
# save
f1.save(path1, 2)
f2.save(path2, 2)
return path1, path2, path3, path4, path5
def test0(docPath):
# make the test fonts and a test document
testFontPath = os.path.join(os.getcwd(), "automatic_testfonts")
m1, m2, i1, i2, i3 = makeTestFonts(testFontPath)
d = DesignSpaceProcessor()
a = AxisDescriptor()
a.name = "pop"
a.minimum = 50
a.maximum = 1000
a.default = 0
a.tag = "pop*"
d.addAxis(a)
s1 = SourceDescriptor()
s1.path = m1
s1.location = dict(pop=a.minimum)
s1.name = "test.master.1"
2016-11-30 15:50:19 +01:00
s1.copyInfo = True
s1.copyFeatures = True
d.addSource(s1)
s2 = SourceDescriptor()
s2.path = m2
s2.location = dict(pop=1000)
s2.name = "test.master.2"
#s2.copyInfo = True
d.addSource(s2)
for counter in range(3):
factor = counter / 2
i = InstanceDescriptor()
v = a.minimum+factor*(a.maximum-a.minimum)
i.path = i1%v
i.familyName = "TestFamily"
i.styleName = "TestStyle_pop%3.3f"%(v)
i.name = "%s-%s"%(i.familyName, i.styleName)
i.location = dict(pop=v)
i.info = True
i.kerning = True
if counter == 2:
i.glyphs['glyphTwo'] = dict(name="glyphTwo", mute=True)
d.addInstance(i)
d.write(docPath)
def test1(docPath):
# execute the test document
d = DesignSpaceProcessor()
d.read(docPath)
d.generateUFO()
selfTest = True
if selfTest:
testRoot = os.path.join(os.getcwd(), "automatic_testfonts")
if os.path.exists(testRoot):
shutil.rmtree(testRoot)
docPath = os.path.join(testRoot, "automatic_test.designspace")
test0(docPath)
test1(docPath)