2017-11-12 13:55:07 +01:00
|
|
|
|
|
2017-08-28 15:49:53 +02:00
|
|
|
|
# coding: utf-8
|
2016-11-30 14:45:41 +01:00
|
|
|
|
from __future__ import print_function, division, absolute_import
|
|
|
|
|
|
|
|
|
|
from ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3
|
|
|
|
|
from pprint import pprint
|
2017-10-27 09:47:16 +02:00
|
|
|
|
import logging
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
A subclassed DesignSpaceDocument that can
|
|
|
|
|
- process the document and generate finished UFOs with MutatorMath.
|
2017-03-20 14:21:58 +01:00
|
|
|
|
- read and write documents
|
2016-11-30 14:45:41 +01:00
|
|
|
|
- bypass and eventually replace the mutatormath ufo generator.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
from designSpaceDocument import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules
|
2016-11-30 14:45:41 +01:00
|
|
|
|
from defcon.objects.font import Font
|
2017-11-12 13:55:07 +01:00
|
|
|
|
from defcon.pens.transformPointPen import TransformPointPen
|
|
|
|
|
from defcon.objects.component import _defaultTransformation
|
2016-11-30 14:45:41 +01:00
|
|
|
|
import defcon
|
2016-11-30 16:38:13 +01:00
|
|
|
|
from fontMath.mathGlyph import MathGlyph
|
|
|
|
|
from fontMath.mathInfo import MathInfo
|
|
|
|
|
from fontMath.mathKerning import MathKerning
|
2016-11-30 14:45:41 +01:00
|
|
|
|
from mutatorMath.objects.mutator import buildMutator
|
|
|
|
|
from mutatorMath.objects.location import biasFromLocations, Location
|
2017-08-28 15:49:53 +02:00
|
|
|
|
import plistlib
|
2016-11-30 14:45:41 +01:00
|
|
|
|
import os
|
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
Swap the contents of two glyphs.
|
|
|
|
|
- contours
|
|
|
|
|
- components
|
|
|
|
|
- width
|
|
|
|
|
- group membership
|
|
|
|
|
- kerning
|
|
|
|
|
|
|
|
|
|
+ Remap components so that glyphs that reference either of the swapped glyphs maintain appearance
|
|
|
|
|
+ Keep the unicode value of the original glyph.
|
|
|
|
|
|
|
|
|
|
Notes
|
|
|
|
|
Parking the glyphs under a swapname is a bit lazy, but at least it guarantees the glyphs have the right parent.
|
|
|
|
|
|
|
|
|
|
"""
|
2017-08-16 14:18:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" These are some UFO specific tools for use with Mutator.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build() is a convenience function for reading and executing a designspace file.
|
|
|
|
|
documentPath: filepath to the .designspace document
|
|
|
|
|
outputUFOFormatVersion: ufo format for output
|
|
|
|
|
verbose: True / False for lots or no feedback
|
|
|
|
|
logPath: filepath to a log file
|
|
|
|
|
progressFunc: an optional callback to report progress.
|
|
|
|
|
see mutatorMath.ufo.tokenProgressFunc
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def build(
|
|
|
|
|
documentPath,
|
2017-08-28 15:49:53 +02:00
|
|
|
|
outputUFOFormatVersion=3,
|
2017-08-16 14:18:35 +02:00
|
|
|
|
roundGeometry=True,
|
|
|
|
|
verbose=True, # not supported
|
|
|
|
|
logPath=None, # not supported
|
|
|
|
|
progressFunc=None, # not supported
|
2017-08-25 13:17:25 +02:00
|
|
|
|
processRules=True,
|
2017-10-27 09:47:16 +02:00
|
|
|
|
logger=None
|
2017-08-16 14:18:35 +02:00
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Simple builder for UFO designspaces.
|
|
|
|
|
"""
|
|
|
|
|
import os, glob
|
|
|
|
|
if os.path.isdir(documentPath):
|
|
|
|
|
# process all *.designspace documents in this folder
|
|
|
|
|
todo = glob.glob(os.path.join(documentPath, "*.designspace"))
|
|
|
|
|
else:
|
|
|
|
|
# process the
|
|
|
|
|
todo = [documentPath]
|
|
|
|
|
results = []
|
|
|
|
|
for path in todo:
|
|
|
|
|
reader = DesignSpaceProcessor(ufoVersion=outputUFOFormatVersion)
|
|
|
|
|
reader.roundGeometry = roundGeometry
|
|
|
|
|
reader.read(path)
|
2017-10-27 09:47:16 +02:00
|
|
|
|
try:
|
|
|
|
|
r = reader.generateUFO(processRules=processRules)
|
|
|
|
|
results.append(r)
|
|
|
|
|
except:
|
|
|
|
|
if logger:
|
|
|
|
|
logger.exception("ufoProcessor error")
|
|
|
|
|
#results += reader.generateUFO(processRules=processRules)
|
2017-08-16 14:18:35 +02:00
|
|
|
|
reader = None
|
|
|
|
|
return results
|
|
|
|
|
|
2017-08-28 15:49:53 +02:00
|
|
|
|
def getUFOVersion(ufoPath):
|
|
|
|
|
# <?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
# <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
|
|
|
# <plist version="1.0">
|
|
|
|
|
# <dict>
|
|
|
|
|
# <key>creator</key>
|
|
|
|
|
# <string>org.robofab.ufoLib</string>
|
|
|
|
|
# <key>formatVersion</key>
|
|
|
|
|
# <integer>2</integer>
|
|
|
|
|
# </dict>
|
|
|
|
|
# </plist>
|
|
|
|
|
metaInfoPath = os.path.join(ufoPath, u"metainfo.plist")
|
|
|
|
|
p = plistlib.readPlist(metaInfoPath)
|
|
|
|
|
return p.get('formatVersion')
|
2017-08-16 14:18:35 +02:00
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________swap"):
|
|
|
|
|
if not oldName in font or not newName in font:
|
|
|
|
|
return None
|
|
|
|
|
swapName = oldName + swapNameExtension
|
|
|
|
|
# park the old glyph
|
|
|
|
|
if not swapName in font:
|
|
|
|
|
font.newGlyph(swapName)
|
|
|
|
|
# swap the outlines
|
|
|
|
|
font[swapName].clear()
|
|
|
|
|
p = font[swapName].getPointPen()
|
|
|
|
|
font[oldName].drawPoints(p)
|
|
|
|
|
font[swapName].width = font[oldName].width
|
2017-08-16 14:18:35 +02:00
|
|
|
|
# lib?
|
2016-12-13 17:56:21 +01:00
|
|
|
|
|
|
|
|
|
font[oldName].clear()
|
|
|
|
|
p = font[oldName].getPointPen()
|
|
|
|
|
font[newName].drawPoints(p)
|
|
|
|
|
font[oldName].width = font[newName].width
|
|
|
|
|
|
|
|
|
|
font[newName].clear()
|
|
|
|
|
p = font[newName].getPointPen()
|
|
|
|
|
font[swapName].drawPoints(p)
|
|
|
|
|
font[newName].width = font[swapName].width
|
|
|
|
|
|
|
|
|
|
# remap the components
|
|
|
|
|
for g in font:
|
|
|
|
|
for c in g.components:
|
|
|
|
|
if c.baseGlyph == oldName:
|
|
|
|
|
c.baseGlyph = swapName
|
|
|
|
|
continue
|
|
|
|
|
for g in font:
|
|
|
|
|
for c in g.components:
|
|
|
|
|
if c.baseGlyph == newName:
|
|
|
|
|
c.baseGlyph = oldName
|
|
|
|
|
continue
|
|
|
|
|
for g in font:
|
|
|
|
|
for c in g.components:
|
|
|
|
|
if c.baseGlyph == swapName:
|
|
|
|
|
c.baseGlyph = newName
|
|
|
|
|
|
|
|
|
|
# change the names in groups
|
|
|
|
|
# the shapes will swap, that will invalidate the kerning
|
|
|
|
|
# so the names need to swap in the kerning as well.
|
|
|
|
|
newKerning = {}
|
|
|
|
|
for first, second in font.kerning.keys():
|
|
|
|
|
value = font.kerning[(first,second)]
|
|
|
|
|
if first == oldName:
|
|
|
|
|
first = newName
|
|
|
|
|
elif first == newName:
|
|
|
|
|
first = oldName
|
|
|
|
|
if second == oldName:
|
|
|
|
|
second = newName
|
|
|
|
|
elif second == newName:
|
|
|
|
|
second = oldName
|
|
|
|
|
newKerning[(first, second)] = value
|
|
|
|
|
font.kerning.clear()
|
|
|
|
|
font.kerning.update(newKerning)
|
|
|
|
|
|
|
|
|
|
for groupName, members in font.groups.items():
|
|
|
|
|
newMembers = []
|
|
|
|
|
for name in members:
|
|
|
|
|
if name == oldName:
|
|
|
|
|
newMembers.append(newName)
|
|
|
|
|
elif name == newName:
|
|
|
|
|
newMembers.append(oldName)
|
|
|
|
|
else:
|
|
|
|
|
newMembers.append(name)
|
|
|
|
|
font.groups[groupName] = newMembers
|
|
|
|
|
|
|
|
|
|
remove = []
|
|
|
|
|
for g in font:
|
|
|
|
|
if g.name.find(swapNameExtension)!=-1:
|
|
|
|
|
remove.append(g.name)
|
|
|
|
|
for r in remove:
|
|
|
|
|
del font[r]
|
|
|
|
|
|
2017-11-12 13:55:07 +01:00
|
|
|
|
class DecomposePointPen(object):
|
|
|
|
|
|
|
|
|
|
def __init__(self, glyphSet, outPointPen):
|
|
|
|
|
self._glyphSet = glyphSet
|
|
|
|
|
self._outPointPen = outPointPen
|
|
|
|
|
self.beginPath = outPointPen.beginPath
|
|
|
|
|
self.endPath = outPointPen.endPath
|
|
|
|
|
self.addPoint = outPointPen.addPoint
|
|
|
|
|
|
|
|
|
|
def addComponent(self, baseGlyphName, transformation):
|
|
|
|
|
if baseGlyphName in self._glyphSet:
|
|
|
|
|
baseGlyph = self._glyphSet[baseGlyphName]
|
|
|
|
|
if transformation == _defaultTransformation:
|
|
|
|
|
baseGlyph.drawPoints(self)
|
|
|
|
|
else:
|
|
|
|
|
transformPointPen = TransformPointPen(self, transformation)
|
|
|
|
|
baseGlyph.drawPoints(transformPointPen)
|
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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
|
|
|
|
|
|
2017-08-28 15:49:53 +02:00
|
|
|
|
def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=3):
|
2016-12-14 12:51:14 +01:00
|
|
|
|
super(DesignSpaceProcessor, self).__init__(readerClass=readerClass, writerClass=writerClass, fontClass=fontClass)
|
2016-11-30 16:52:51 +01:00
|
|
|
|
self.ufoVersion = ufoVersion # target UFO version
|
2016-11-30 14:45:41 +01:00
|
|
|
|
self.roundGeometry = False
|
2016-11-30 17:48:05 +01:00
|
|
|
|
self._glyphMutators = {}
|
|
|
|
|
self._infoMutator = None
|
|
|
|
|
self._kerningMutator = None
|
2017-01-06 17:37:29 +01:00
|
|
|
|
self._preppedAxes = None
|
2016-11-30 14:45:41 +01:00
|
|
|
|
self.fonts = {}
|
2017-11-12 13:55:07 +01:00
|
|
|
|
self._fontsLoaded = False
|
2016-11-30 17:48:05 +01:00
|
|
|
|
self.glyphNames = [] # list of all glyphnames
|
2016-12-13 17:56:21 +01:00
|
|
|
|
self.processRules = True
|
2017-01-09 11:36:36 +01:00
|
|
|
|
self.problems = [] # receptacle for problem notifications. Not big enough to break, but also not small enough to ignore.
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
def generateUFO(self, processRules=True):
|
2016-11-30 17:48:05 +01:00
|
|
|
|
# makes the instances
|
2016-12-13 17:56:21 +01:00
|
|
|
|
# option to execute the rules
|
2017-08-28 15:49:53 +02:00
|
|
|
|
# make sure we're not trying to overwrite a newer UFO format
|
2016-11-30 17:48:05 +01:00
|
|
|
|
self.loadFonts()
|
2017-01-10 14:37:48 +01:00
|
|
|
|
self.checkDefault()
|
2017-08-28 15:49:53 +02:00
|
|
|
|
v = 0
|
2016-11-30 14:45:41 +01:00
|
|
|
|
for instanceDescriptor in self.instances:
|
2016-11-30 17:48:05 +01:00
|
|
|
|
if instanceDescriptor.path is None:
|
|
|
|
|
continue
|
2016-12-13 17:56:21 +01:00
|
|
|
|
font = self.makeInstance(instanceDescriptor, processRules)
|
2017-08-28 15:49:53 +02:00
|
|
|
|
folder = os.path.dirname(instanceDescriptor.path)
|
|
|
|
|
path = instanceDescriptor.path
|
|
|
|
|
if not os.path.exists(folder):
|
|
|
|
|
os.makedirs(folder)
|
|
|
|
|
if os.path.exists(path):
|
|
|
|
|
existingUFOFormatVersion = getUFOVersion(path)
|
|
|
|
|
if existingUFOFormatVersion > self.ufoVersion:
|
2017-09-16 09:07:42 -04:00
|
|
|
|
self.problems.append(u"Can’t overwrite existing UFO%d with UFO%d."%(existingUFOFormatVersion, self.ufoVersion))
|
2017-08-28 15:49:53 +02:00
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
font.save(path, self.ufoVersion)
|
2017-09-16 09:07:42 -04:00
|
|
|
|
self.problems.append("Generated %s as UFO%d"%(os.path.basename(path), self.ufoVersion))
|
2016-11-30 17:48:05 +01:00
|
|
|
|
|
|
|
|
|
def getInfoMutator(self):
|
|
|
|
|
""" Returns a info mutator """
|
|
|
|
|
if self._infoMutator:
|
|
|
|
|
return self._infoMutator
|
2016-11-30 14:45:41 +01:00
|
|
|
|
infoItems = []
|
2016-11-30 17:48:05 +01:00
|
|
|
|
for sourceDescriptor in self.sources:
|
|
|
|
|
loc = Location(sourceDescriptor.location)
|
|
|
|
|
sourceFont = self.fonts[sourceDescriptor.name]
|
|
|
|
|
infoItems.append((loc, self.mathInfoClass(sourceFont.info)))
|
2017-01-06 17:37:29 +01:00
|
|
|
|
bias, self._infoMutator = buildMutator(infoItems, axes=self._preppedAxes, bias=self.defaultLoc)
|
2016-11-30 17:48:05 +01:00
|
|
|
|
return self._infoMutator
|
|
|
|
|
|
|
|
|
|
def getKerningMutator(self):
|
|
|
|
|
""" Return a kerning mutator """
|
|
|
|
|
if self._kerningMutator:
|
|
|
|
|
return self._kerningMutator
|
2016-11-30 14:45:41 +01:00
|
|
|
|
kerningItems = []
|
2016-11-30 17:48:05 +01:00
|
|
|
|
for sourceDescriptor in self.sources:
|
|
|
|
|
loc = Location(sourceDescriptor.location)
|
|
|
|
|
sourceFont = self.fonts[sourceDescriptor.name]
|
2017-09-19 16:24:35 +02:00
|
|
|
|
# this makes assumptions about the groups of all sources being the same.
|
2016-11-30 17:48:05 +01:00
|
|
|
|
kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups)))
|
2017-01-06 17:37:29 +01:00
|
|
|
|
bias, self._kerningMutator = buildMutator(kerningItems, axes=self._preppedAxes, bias=self.defaultLoc)
|
2016-11-30 17:48:05 +01:00
|
|
|
|
return self._kerningMutator
|
|
|
|
|
|
2017-11-12 13:55:07 +01:00
|
|
|
|
def getGlyphMutator(self, glyphName, decomposeComponents=False):
|
|
|
|
|
""" Return a glyph mutator.defaultLoc
|
|
|
|
|
decomposeComponents = True causes the source glyphs to be decomposed first
|
|
|
|
|
before building the mutator. That gives you instances that do not depend
|
|
|
|
|
on a complete font. If you're calculating previews for instance.
|
|
|
|
|
"""
|
2016-11-30 17:48:05 +01:00
|
|
|
|
if glyphName in self._glyphMutators:
|
|
|
|
|
return self._glyphMutators[glyphName]
|
|
|
|
|
items = []
|
2016-11-30 14:45:41 +01:00
|
|
|
|
for sourceDescriptor in self.sources:
|
|
|
|
|
loc = Location(sourceDescriptor.location)
|
|
|
|
|
f = self.fonts[sourceDescriptor.name]
|
2016-11-30 17:48:05 +01:00
|
|
|
|
if glyphName in sourceDescriptor.mutedGlyphNames:
|
|
|
|
|
continue
|
2016-11-30 22:37:58 +01:00
|
|
|
|
if not glyphName in f:
|
|
|
|
|
# log this>
|
|
|
|
|
continue
|
2017-11-12 13:55:07 +01:00
|
|
|
|
sourceGlyphObject = f[glyphName]
|
|
|
|
|
if decomposeComponents:
|
|
|
|
|
temp = self.glyphClass()
|
|
|
|
|
p = temp.getPointPen()
|
|
|
|
|
dpp = DecomposePointPen(f, p)
|
|
|
|
|
sourceGlyphObject.drawPoints(dpp)
|
|
|
|
|
temp.width = sourceGlyphObject.width
|
|
|
|
|
temp.name = sourceGlyphObject.name
|
|
|
|
|
#temp.lib = sourceGlyphObject.lib
|
|
|
|
|
processThis = temp
|
|
|
|
|
else:
|
|
|
|
|
processThis = sourceGlyphObject
|
|
|
|
|
items.append((loc, self.mathGlyphClass(processThis)))
|
2017-01-06 17:37:29 +01:00
|
|
|
|
bias, self._glyphMutators[glyphName] = buildMutator(items, axes=self._preppedAxes, bias=self.defaultLoc)
|
2016-11-30 17:48:05 +01:00
|
|
|
|
return self._glyphMutators[glyphName]
|
|
|
|
|
|
2017-11-12 13:55:07 +01:00
|
|
|
|
|
|
|
|
|
def loadFonts(self, reload=False):
|
2016-11-30 17:48:05 +01:00
|
|
|
|
# Load the fonts and find the default candidate based on the info flag
|
2017-11-12 13:55:07 +01:00
|
|
|
|
if self._fontsLoaded and not reload:
|
|
|
|
|
return
|
2017-01-10 14:37:48 +01:00
|
|
|
|
names = set()
|
2016-11-30 14:45:41 +01:00
|
|
|
|
for sourceDescriptor in self.sources:
|
|
|
|
|
if not sourceDescriptor.name in self.fonts:
|
|
|
|
|
self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path)
|
2017-09-19 16:24:35 +02:00
|
|
|
|
self.problems.append("loaded master from %s, format %d"%(sourceDescriptor.path, getUFOVersion(sourceDescriptor.path)))
|
2016-12-14 12:51:14 +01:00
|
|
|
|
names = names | set(self.fonts[sourceDescriptor.name].keys())
|
2016-11-30 17:48:05 +01:00
|
|
|
|
self.glyphNames = list(names)
|
2017-11-12 13:55:07 +01:00
|
|
|
|
self._fontsLoaded = True
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
2017-01-29 11:56:17 +01:00
|
|
|
|
def makeInstance(self, instanceDescriptor, doRules=False, glyphNames=None):
|
2016-11-30 17:48:05 +01:00
|
|
|
|
""" Generate a font object for this instance """
|
2016-11-30 14:45:41 +01:00
|
|
|
|
font = self._instantiateFont(None)
|
2017-01-06 17:37:29 +01:00
|
|
|
|
self._preppedAxes = self._prepAxesForBender()
|
2016-11-30 14:45:41 +01:00
|
|
|
|
# make fonty things here
|
|
|
|
|
loc = Location(instanceDescriptor.location)
|
2017-09-19 16:24:35 +02:00
|
|
|
|
# groups,
|
|
|
|
|
if hasattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps"):
|
|
|
|
|
renameMap = self.fonts[self.default.name].kerningGroupConversionRenameMaps
|
|
|
|
|
self.problems.append("renameMap %s"%renameMap)
|
|
|
|
|
else:
|
|
|
|
|
renameMap = {}
|
|
|
|
|
font.kerningGroupConversionRenameMaps = renameMap
|
2016-11-30 17:48:05 +01:00
|
|
|
|
# make the kerning
|
|
|
|
|
if instanceDescriptor.kerning:
|
2017-03-20 14:21:58 +01:00
|
|
|
|
try:
|
|
|
|
|
self.getKerningMutator().makeInstance(loc).extractKerning(font)
|
|
|
|
|
except:
|
|
|
|
|
self.problems.append("Could not make kerning for %s"%loc)
|
2016-11-30 17:48:05 +01:00
|
|
|
|
# make the info
|
2016-11-30 14:45:41 +01:00
|
|
|
|
if instanceDescriptor.info:
|
2017-03-20 14:21:58 +01:00
|
|
|
|
try:
|
|
|
|
|
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
|
2017-05-13 10:46:49 +02:00
|
|
|
|
# localised names need to go to the right openTypeNameRecords
|
|
|
|
|
# records = []
|
|
|
|
|
# nameID = 1
|
|
|
|
|
# platformID =
|
|
|
|
|
# for languageCode, name in instanceDescriptor.localisedStyleMapFamilyName.items():
|
|
|
|
|
# # Name ID 1 (font family name) is found at the generic styleMapFamily attribute.
|
|
|
|
|
# records.append((nameID, ))
|
|
|
|
|
|
2017-03-20 14:21:58 +01:00
|
|
|
|
except:
|
|
|
|
|
self.problems.append("Could not make fontinfo for %s"%loc)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
# 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:
|
2017-08-16 14:18:35 +02:00
|
|
|
|
# excplicitly copy the font.lib items
|
|
|
|
|
for key, value in self.fonts[sourceDescriptor.name].lib.items():
|
|
|
|
|
font.lib[key] = value
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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
|
2017-01-29 11:56:17 +01:00
|
|
|
|
if glyphNames:
|
|
|
|
|
selectedGlyphNames = glyphNames
|
|
|
|
|
else:
|
|
|
|
|
selectedGlyphNames = self.glyphNames
|
2017-09-19 16:24:35 +02:00
|
|
|
|
# add the glyphnames to the font.lib['public.glyphOrder']
|
|
|
|
|
if not 'public.glyphOrder' in font.lib.keys():
|
|
|
|
|
font.lib['public.glyphOrder'] = selectedGlyphNames
|
2017-01-29 11:56:17 +01:00
|
|
|
|
for glyphName in selectedGlyphNames:
|
2017-03-20 14:21:58 +01:00
|
|
|
|
try:
|
|
|
|
|
glyphMutator = self.getGlyphMutator(glyphName)
|
|
|
|
|
except:
|
|
|
|
|
self.problems.append("Could not make mutator for glyph %s"%glyphName)
|
|
|
|
|
continue
|
2016-11-30 17:48:05 +01:00
|
|
|
|
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}}],
|
2017-08-25 13:17:25 +02:00
|
|
|
|
# 'unicodes': [36]}
|
2016-11-30 17:48:05 +01:00
|
|
|
|
glyphData = instanceDescriptor.glyphs[glyphName]
|
2016-11-30 14:45:41 +01:00
|
|
|
|
else:
|
|
|
|
|
glyphData = {}
|
2016-11-30 17:48:05 +01:00
|
|
|
|
font.newGlyph(glyphName)
|
|
|
|
|
font[glyphName].clear()
|
2016-11-30 14:45:41 +01:00
|
|
|
|
if glyphData.get('mute', False):
|
|
|
|
|
# mute this glyph, skip
|
|
|
|
|
continue
|
|
|
|
|
glyphInstanceLocation = Location(glyphData.get("instanceLocation", instanceDescriptor.location))
|
2016-12-17 11:46:30 +01:00
|
|
|
|
try:
|
2017-08-25 13:17:25 +02:00
|
|
|
|
uniValues = glyphMutator[()][0].unicodes
|
2016-12-17 11:46:30 +01:00
|
|
|
|
except IndexError:
|
2017-08-25 13:17:25 +02:00
|
|
|
|
uniValues = []
|
|
|
|
|
glyphInstanceUnicodes = glyphData.get("unicodes", uniValues)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
note = glyphData.get("note")
|
|
|
|
|
if note:
|
2016-11-30 17:48:05 +01:00
|
|
|
|
font[glyphName] = note
|
2016-11-30 14:45:41 +01:00
|
|
|
|
masters = glyphData.get("masters", None)
|
|
|
|
|
if masters:
|
|
|
|
|
items = []
|
|
|
|
|
for glyphMaster in masters:
|
|
|
|
|
sourceGlyphFont = glyphMaster.get("font")
|
2016-11-30 17:48:05 +01:00
|
|
|
|
sourceGlyphName = glyphMaster.get("glyphName", glyphName)
|
2016-11-30 22:37:58 +01:00
|
|
|
|
m = self.fonts.get(sourceGlyphFont)
|
|
|
|
|
if not sourceGlyphName in m:
|
|
|
|
|
continue
|
|
|
|
|
sourceGlyph = MathGlyph(m[sourceGlyphName])
|
2016-11-30 14:45:41 +01:00
|
|
|
|
sourceGlyphLocation = Location(glyphMaster.get("location"))
|
|
|
|
|
items.append((sourceGlyphLocation, sourceGlyph))
|
2017-01-06 17:37:29 +01:00
|
|
|
|
bias, glyphMutator = buildMutator(items, axes=self._preppedAxes, bias=self.defaultLoc)
|
2016-12-19 10:25:20 +01:00
|
|
|
|
try:
|
|
|
|
|
glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation)
|
|
|
|
|
except IndexError:
|
|
|
|
|
# alignment problem with the data?
|
|
|
|
|
print("Error making instance %s"%glyphName)
|
|
|
|
|
continue
|
2016-11-30 17:48:05 +01:00
|
|
|
|
font.newGlyph(glyphName)
|
|
|
|
|
font[glyphName].clear()
|
2016-11-30 14:45:41 +01:00
|
|
|
|
if self.roundGeometry:
|
|
|
|
|
try:
|
|
|
|
|
glyphInstanceObject = glyphInstanceObject.round()
|
|
|
|
|
except AttributeError:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
2016-11-30 17:48:05 +01:00
|
|
|
|
glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
except TypeError:
|
|
|
|
|
# this causes ruled glyphs to end up in the wrong glyphname
|
|
|
|
|
# but defcon2 objects don't support it
|
2016-11-30 17:48:05 +01:00
|
|
|
|
pPen = font[glyphName].getPointPen()
|
|
|
|
|
font[glyphName].clear()
|
2016-11-30 14:45:41 +01:00
|
|
|
|
glyphInstanceObject.drawPoints(pPen)
|
2016-11-30 17:48:05 +01:00
|
|
|
|
font[glyphName].width = glyphInstanceObject.width
|
2017-08-25 13:17:25 +02:00
|
|
|
|
font[glyphName].unicodes = glyphInstanceUnicodes
|
2016-12-13 17:56:21 +01:00
|
|
|
|
if doRules:
|
|
|
|
|
resultNames = processRules(self.rules, loc, self.glyphNames)
|
|
|
|
|
for oldName, newName in zip(self.glyphNames, resultNames):
|
|
|
|
|
if oldName != newName:
|
|
|
|
|
swapGlyphNames(font, oldName, newName)
|
2017-08-16 14:18:35 +02:00
|
|
|
|
# copy the glyph lib?
|
|
|
|
|
#for sourceDescriptor in self.sources:
|
|
|
|
|
# if sourceDescriptor.copyLib:
|
|
|
|
|
# pass
|
|
|
|
|
# pass
|
2017-01-16 16:17:05 +01:00
|
|
|
|
# store designspace location in the font.lib
|
2017-08-07 10:29:39 +01:00
|
|
|
|
font.lib['designspace'] = list(instanceDescriptor.location.items())
|
2016-11-30 17:48:05 +01:00
|
|
|
|
return font
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
|
|
|
|
def _instantiateFont(self, path):
|
2016-11-30 17:48:05 +01:00
|
|
|
|
""" Return a instance of a font object with all the given subclasses"""
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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):
|
2016-11-30 17:48:05 +01:00
|
|
|
|
""" Copy the non-calculating fields from the source info."""
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
2017-01-06 17:37:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-11-30 14:45:41 +01:00
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
# standalone test
|
|
|
|
|
import shutil
|
2016-12-13 17:56:21 +01:00
|
|
|
|
import os
|
|
|
|
|
from defcon.objects.font import Font
|
2016-12-14 12:51:14 +01:00
|
|
|
|
import logging
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
|
|
|
|
def addGlyphs(font, s):
|
|
|
|
|
# we need to add the glyphs
|
2016-12-13 17:56:21 +01:00
|
|
|
|
step = 0
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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()
|
2016-12-13 17:56:21 +01:00
|
|
|
|
g.move((0,s+step))
|
2016-11-30 14:45:41 +01:00
|
|
|
|
g.width = s
|
2016-12-13 17:56:21 +01:00
|
|
|
|
step += 50
|
|
|
|
|
for n, w in [('wide', 800), ('narrow', 100)]:
|
|
|
|
|
font.newGlyph(n)
|
|
|
|
|
g = font[n]
|
|
|
|
|
p = g.getPen()
|
|
|
|
|
p.moveTo((0,0))
|
|
|
|
|
p.lineTo((w,0))
|
|
|
|
|
p.lineTo((w,font.info.ascender))
|
|
|
|
|
p.lineTo((0,font.info.ascender))
|
|
|
|
|
p.closePath()
|
|
|
|
|
g.width = w
|
2016-12-17 12:04:46 +01:00
|
|
|
|
font.newGlyph("wide.component")
|
|
|
|
|
g = font["wide.component"]
|
|
|
|
|
comp = g.instantiateComponent()
|
|
|
|
|
comp.baseGlyph = "wide"
|
|
|
|
|
comp.offset = (0,0)
|
|
|
|
|
g.appendComponent(comp)
|
|
|
|
|
g.width = font['wide'].width
|
|
|
|
|
font.newGlyph("narrow.component")
|
|
|
|
|
g = font["narrow.component"]
|
|
|
|
|
comp = g.instantiateComponent()
|
|
|
|
|
comp.baseGlyph = "narrow"
|
|
|
|
|
comp.offset = (0,0)
|
|
|
|
|
g.appendComponent(comp)
|
|
|
|
|
g.width = font['narrow'].width
|
2016-12-17 11:46:30 +01:00
|
|
|
|
uniValue = 200
|
|
|
|
|
for g in font:
|
|
|
|
|
g.unicode = uniValue
|
|
|
|
|
uniValue += 1
|
2016-12-13 17:56:21 +01:00
|
|
|
|
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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")
|
|
|
|
|
f1 = Font()
|
2016-12-13 17:56:21 +01:00
|
|
|
|
fillInfo(f1)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
addGlyphs(f1, 100)
|
|
|
|
|
f1.features.text = u"# features text from master 1"
|
|
|
|
|
f2 = Font()
|
2016-12-13 17:56:21 +01:00
|
|
|
|
fillInfo(f2)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
addGlyphs(f2, 500)
|
|
|
|
|
f2.features.text = u"# features text from master 2"
|
|
|
|
|
f1.info.ascender = 400
|
|
|
|
|
f1.info.descender = -200
|
|
|
|
|
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"
|
2017-08-16 14:18:35 +02:00
|
|
|
|
f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1"
|
|
|
|
|
f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2"
|
2016-11-30 14:45:41 +01:00
|
|
|
|
f1.save(path1, 2)
|
|
|
|
|
f2.save(path2, 2)
|
|
|
|
|
return path1, path2, path3, path4, path5
|
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
def makeSwapFonts(rootPath):
|
|
|
|
|
""" Make some test fonts that have the kerning problem."""
|
|
|
|
|
path1 = os.path.join(rootPath, "Swap.ufo")
|
|
|
|
|
path2 = os.path.join(rootPath, "Swapped.ufo")
|
|
|
|
|
f1 = Font()
|
|
|
|
|
fillInfo(f1)
|
|
|
|
|
addGlyphs(f1, 100)
|
|
|
|
|
f1.features.text = u"# features text from master 1"
|
|
|
|
|
f1.info.ascender = 800
|
|
|
|
|
f1.info.descender = -200
|
|
|
|
|
f1.kerning[('glyphOne', 'glyphOne')] = -10
|
|
|
|
|
f1.kerning[('glyphTwo', 'glyphTwo')] = 10
|
|
|
|
|
f1.save(path1, 2)
|
|
|
|
|
return path1, path2
|
|
|
|
|
|
2017-08-25 13:41:21 +02:00
|
|
|
|
def testDocument(docPath):
|
2016-11-30 14:45:41 +01:00
|
|
|
|
# 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
|
2016-11-30 14:45:41 +01:00
|
|
|
|
s1.copyFeatures = True
|
2017-08-16 14:18:35 +02:00
|
|
|
|
s1.copyLib = True
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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)
|
2017-08-16 14:18:35 +02:00
|
|
|
|
i.copyLib = True
|
2017-08-25 13:41:21 +02:00
|
|
|
|
if counter == 2:
|
|
|
|
|
i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125])
|
2016-11-30 14:45:41 +01:00
|
|
|
|
d.addInstance(i)
|
|
|
|
|
d.write(docPath)
|
|
|
|
|
|
2017-08-25 13:41:21 +02:00
|
|
|
|
def testGenerateInstances(docPath):
|
2016-11-30 14:45:41 +01:00
|
|
|
|
# execute the test document
|
|
|
|
|
d = DesignSpaceProcessor()
|
|
|
|
|
d.read(docPath)
|
2017-09-16 09:07:42 -04:00
|
|
|
|
d.generateUFO()
|
|
|
|
|
if d.problems:
|
|
|
|
|
print(d.problems)
|
2016-11-30 14:45:41 +01:00
|
|
|
|
|
2016-12-13 17:56:21 +01:00
|
|
|
|
def testSwap(docPath):
|
|
|
|
|
srcPath, dstPath = makeSwapFonts(os.path.dirname(docPath))
|
|
|
|
|
|
|
|
|
|
f = Font(srcPath)
|
|
|
|
|
swapGlyphNames(f, "narrow", "wide")
|
|
|
|
|
f.info.styleName = "Swapped"
|
|
|
|
|
f.save(dstPath)
|
|
|
|
|
|
|
|
|
|
# test the results in newly opened fonts
|
|
|
|
|
old = Font(srcPath)
|
|
|
|
|
new = Font(dstPath)
|
|
|
|
|
assert new.kerning.get(("narrow", "narrow")) == old.kerning.get(("wide","wide"))
|
|
|
|
|
assert new.kerning.get(("wide", "wide")) == old.kerning.get(("narrow","narrow"))
|
2016-12-17 12:04:46 +01:00
|
|
|
|
# after the swap these widths should be the same
|
|
|
|
|
assert old['narrow'].width == new['wide'].width
|
|
|
|
|
assert old['wide'].width == new['narrow'].width
|
|
|
|
|
# The following test may be a bit counterintuitive:
|
|
|
|
|
# the rule swaps the glyphs, but we do not want glyphs that are not
|
|
|
|
|
# specifically affected by the rule to *appear* any different.
|
|
|
|
|
# So, components have to be remapped.
|
|
|
|
|
assert new['wide.component'].components[0].baseGlyph == "narrow"
|
|
|
|
|
assert new['narrow.component'].components[0].baseGlyph == "wide"
|
2016-12-13 17:56:21 +01:00
|
|
|
|
|
2017-08-25 13:41:21 +02:00
|
|
|
|
def testUnicodes(docPath):
|
|
|
|
|
# after executing testSwap there should be some test fonts
|
|
|
|
|
# let's check if the unicode values for glyph "narrow" arrive at the right place.
|
|
|
|
|
d = DesignSpaceProcessor()
|
|
|
|
|
d.read(docPath)
|
|
|
|
|
for instance in d.instances:
|
2017-08-28 15:49:53 +02:00
|
|
|
|
if os.path.exists(instance.path):
|
|
|
|
|
f = Font(instance.path)
|
|
|
|
|
if instance.name == "TestFamily-TestStyle_pop1000.000":
|
|
|
|
|
assert f['narrow'].unicodes == [291, 292, 293]
|
|
|
|
|
else:
|
|
|
|
|
assert f['narrow'].unicodes == [207]
|
2017-08-25 13:41:21 +02:00
|
|
|
|
else:
|
2017-08-28 15:49:53 +02:00
|
|
|
|
print("Missing test font at %s"%instance.path)
|
2017-08-25 13:41:21 +02:00
|
|
|
|
|
2016-11-30 14:45:41 +01:00
|
|
|
|
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")
|
2017-08-25 13:41:21 +02:00
|
|
|
|
testDocument(docPath)
|
|
|
|
|
testGenerateInstances(docPath)
|
2016-12-13 17:56:21 +01:00
|
|
|
|
testSwap(docPath)
|
2017-08-25 13:41:21 +02:00
|
|
|
|
testUnicodes(docPath)
|