diff --git a/.gitignore b/.gitignore
index 1ff6359..5772591 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,8 @@ test*.sp3
Tests/Instances
Tests/_*
Tests/20190830 benders/Skateboard Previews
+/Tests/202206 discrete spaces/instances
+/Tests/202206 discrete spaces/instances_mutMath
+/Tests/202206 discrete spaces/instances_varlib
+/_issues
+/_old_stuff
diff --git a/Designspaces with python.md b/Designspaces with python.md
new file mode 100644
index 0000000..53eca94
--- /dev/null
+++ b/Designspaces with python.md
@@ -0,0 +1,120 @@
+# Designspaces and python
+
+Designspaces can do different things in different processes. Maybe you want to generate a variable font. Maybe you want to generate UFOs. Maybe you want to resample an existing designspace into something else.
+While [fonttools.designspacelib](https://fonttools.readthedocs.io/en/latest/designspaceLib/index.html) contains the basic objects to construct, read and write designspaces, the [ufoProcessor package](https://github.com/LettError/ufoProcessor) can also generate instances.
+
+## Basics
+First I have to make a `DesignSpaceDocument` object. This is an empty container, it has no masters, no axes, no path.
+
+ from fontTools.designspaceLib import *
+ ds = DesignSpaceDocument()
+
+Now I will add an axis to the document by making an `AxisDescriptor` object and adding some values to its attributes.
+
+ ad = AxisDescriptor()
+ ad.name = "weight" # readable name
+ ad.tag = "wght" # 4 letter tag
+ ad.minimum = 200
+ ad.maximum = 1000
+ ad.default = 400
+
+Finally we add the axisDescriptor to the document:
+
+ ds.addAxis(ad)
+ print(ds)
+ path = "my.designspace"
+ ds.write(path)
+
+This writes a very small designspace file:
+
+
+
+
+
+
+
+
+Let's add some sources to the designspace: this needs the absolute path to the file (usually a ufo). When the document is saved the paths will be written as relative to the designspace document. A `SourceDescriptor` object has a lot of attributes, but `path` and `location` are the most important ones.
+
+ s1 = SourceDescriptor()
+ s1.path = "geometryMaster1.ufo"
+ s1.location = dict(weight=200)
+ ds.addSource(s1)
+
+ s2 = SourceDescriptor()
+ s2.path = "geometryMaster2.ufo"
+ s2.location = dict(weight=1000)
+ ds.addSource(s2)
+
+Let's add some instances. Instances are specific locations in the designspace with names and sometimes paths associated with them. In a variable font you might want these to show up as styles in a menu. But you could also generate UFOs from them.
+
+ for w in [ad.minimum, .5*(ad.minimum + ad.default), ad.default, .5*(ad.maximum + ad.default), ad.maximum]:
+ # you will probably know more compact
+ # and easier ways to write this, go ahead!
+ i = InstanceDescriptor()
+ i.fileName = "InstanceFamily"
+ i.styleName = "Weight_%d" % w
+ i.location = dict(weight = w)
+ i.filename = "instance_%s.ufo" % i.styleName
+ ds.addInstance(i)
+
+The XML now has all it needs: an axis, some sources and ome instances.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Whoop well done.
+
diff --git a/Lib/ufoProcessor/__init__.py b/Lib/ufoProcessor/__init__.py
index 3357fe9..a85db62 100644
--- a/Lib/ufoProcessor/__init__.py
+++ b/Lib/ufoProcessor/__init__.py
@@ -1,11 +1,36 @@
# coding: utf-8
-
from __future__ import print_function, division, absolute_import
-import os
+"""
+ UFOProcessor no longer executes the rules in the designspace. Rules now have complex behaviour and need
+ to be properly compiled and executed by something that has an overview of all the features.
+
+ 2022 work on support for DS5
+ https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/designspaceLib/split.py#L53
+
+ 2022 10 extrapolate in varlib only works for -1, 0, 1 systems. So we continue to rely on MutatorMath.
+
+
+"""
+
+
+"""
+ build() is a convenience function for reading and executing a designspace file.
+ documentPath: path to the designspace file.
+ outputUFOFormatVersion: integer, 2, 3. Format for generated UFOs. Note: can be different from source UFO format.
+ useVarlib: True if you want the geometry to be generated with varLib.model instead of mutatorMath.
+"""
+
+
+
+## caching
+import functools
+
+from warnings import warn
+import os, time
import logging, traceback
import collections
-# from pprint import pprint
+import itertools
from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules
from fontTools.misc import plistlib
@@ -15,7 +40,7 @@
import defcon
import fontParts.fontshell.font
import defcon.objects.font
-from defcon.objects.font import Font
+#from defcon.objects.font import Font
from defcon.pens.transformPointPen import TransformPointPen
from defcon.objects.component import _defaultTransformation
from fontMath.mathGlyph import MathGlyph
@@ -25,8 +50,11 @@
# if you only intend to use varLib.model then importing mutatorMath is not necessary.
from mutatorMath.objects.mutator import buildMutator
from mutatorMath.objects.location import Location
+
+# back to these when we're running as a package
+import ufoProcessor.varModels
from ufoProcessor.varModels import VariationModelMutator
-from ufoProcessor.emptyPen import checkGlyphIsEmpty
+from ufoProcessor.pens import checkGlyphIsEmpty
try:
@@ -35,6 +63,40 @@
__version__ = "0.0.0+unknown"
+_memoizeCache = dict()
+
+def immutify(obj):
+ # make an immutable version of this object.
+ # assert immutify(10) == (10,)
+ # assert immutify([10, 20, "a"]) == (10, 20, 'a')
+ # assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b'))
+ hashValues = []
+ if isinstance(obj, dict):
+ for key, value in obj.items():
+ hashValues.extend([key, immutify(value)])
+ elif isinstance(obj, list):
+ for value in obj:
+ hashValues.extend(immutify(value))
+ else:
+ hashValues.append(obj)
+ return tuple(hashValues)
+
+
+def memoize(function):
+ @functools.wraps(function)
+ def wrapper(self, *args, **kwargs):
+ immutableargs = tuple([immutify(a) for a in args])
+ immutablekwargs = immutify(kwargs)
+ key = (function.__name__, self, immutableargs, immutify(kwargs))
+ if key in _memoizeCache:
+ return _memoizeCache[key]
+ else:
+ result = function(self, *args, **kwargs)
+ _memoizeCache[key] = result
+ return result
+ return wrapper
+#####
+
class UFOProcessorError(Exception):
def __init__(self, msg, obj=None):
self.msg = msg
@@ -61,30 +123,6 @@ def getLayer(f, layerName):
return f.getLayer(layerName)
return None
-"""
- Processing of rules when generating UFOs.
- 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.
-
-"""
-
-
-"""
- build() is a convenience function for reading and executing a designspace file.
- documentPath: path to the designspace file.
- outputUFOFormatVersion: integer, 2, 3. Format for generated UFOs. Note: can be different from source UFO format.
- useVarlib: True if you want the geometry to be generated with varLib.model instead of mutatorMath.
-"""
def build(
documentPath,
@@ -114,7 +152,7 @@ def build(
document.roundGeometry = roundGeometry
document.read(path)
try:
- r = document.generateUFO(processRules=processRules)
+ r = document.generateUFO()
results.append(r)
except:
if logger:
@@ -141,110 +179,6 @@ def getUFOVersion(ufoPath):
return p.get('formatVersion')
-def swapGlyphNames(font, oldName, newName, swapNameExtension = "_______________swap"):
- # In font swap the glyphs oldName and newName.
- # Also swap the names in components in order to preserve appearance.
- # Also swap the names in font groups.
- 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)
- # get anchors
- oldAnchors = font[oldName].anchors
- newAnchors = font[newName].anchors
-
- # swap the outlines
- font[swapName].clear()
- p = font[swapName].getPointPen()
- font[oldName].drawPoints(p)
- font[swapName].width = font[oldName].width
- # lib?
- font[oldName].clear()
- p = font[oldName].getPointPen()
- font[newName].drawPoints(p)
- font[oldName].width = font[newName].width
- for a in newAnchors:
- na = defcon.Anchor()
- na.name = a.name
- na.x = a.x
- na.y = a.y
- # FontParts and Defcon add anchors in different ways
- # this works around that.
- try:
- font[oldName].naked().appendAnchor(na)
- except AttributeError:
- font[oldName].appendAnchor(na)
-
- font[newName].clear()
- p = font[newName].getPointPen()
- font[swapName].drawPoints(p)
- font[newName].width = font[swapName].width
- for a in oldAnchors:
- na = defcon.Anchor()
- na.name = a.name
- na.x = a.x
- na.y = a.y
- try:
- font[newName].naked().appendAnchor(na)
- except AttributeError:
- font[newName].appendAnchor(na)
-
-
- # 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]
-
-
class DecomposePointPen(object):
def __init__(self, glyphSet, outPointPen):
@@ -292,7 +226,6 @@ class DesignSpaceProcessor(DesignSpaceDocument):
def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersion=3, useVarlib=False):
super(DesignSpaceProcessor, self).__init__(readerClass=readerClass, writerClass=writerClass)
-
self.ufoVersion = ufoVersion # target UFO version
self.useVarlib = useVarlib
self.roundGeometry = False
@@ -308,12 +241,18 @@ def __init__(self, readerClass=None, writerClass=None, fontClass=None, ufoVersio
self.problems = [] # receptacle for problem notifications. Not big enough to break, but also not small enough to ignore.
self.toolLog = []
- def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False):
+ def hasDiscreteAxes(self):
+ # return True if this designspace has > 0 discrete axes
+ for axis in self.getOrderedDiscreteAxes():
+ return True
+ return False
+
+ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False, discreteLocation=None):
# makes the instances
# option to execute the rules
# make sure we're not trying to overwrite a newer UFO format
self.loadFonts()
- self.findDefault()
+ #self.findDefault()
if self.default is None:
# we need one to genenerate
raise UFOProcessorError("Can't generate UFO from this designspace: no default font.", self)
@@ -325,7 +264,9 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False
processRules,
glyphNames=glyphNames,
pairs=pairs,
- bend=bend)
+ bend=bend,
+ discreteLocation=discreteLocation
+ )
folder = os.path.dirname(os.path.abspath(instanceDescriptor.path))
path = instanceDescriptor.path
if not os.path.exists(folder):
@@ -339,14 +280,37 @@ def generateUFO(self, processRules=True, glyphNames=None, pairs=None, bend=False
self.problems.append("Generated %s as UFO%d"%(os.path.basename(path), self.ufoVersion))
return True
+ def _serializeAnyAxis(self, axis):
+ if hasattr(axis, "serialize"):
+ return axis.serialize()
+ else:
+ if hasattr(axis, "values"):
+ # discrete axis does not have serialize method, meh
+ return dict(
+ tag=axis.tag,
+ name=axis.name,
+ labelNames=axis.labelNames,
+ minimum = min(axis.values), # XX is this allowed
+ maximum = max(axis.values), # XX is this allowed
+ values=axis.values,
+ default=axis.default,
+ hidden=axis.hidden,
+ map=axis.map,
+ axisOrdering=axis.axisOrdering,
+ axisLabels=axis.axisLabels,
+ )
+
def getSerializedAxes(self):
- return [a.serialize() for a in self.axes]
+ serialized = []
+ for axis in self.axes:
+ serialized.append(self._serializeAnyAxis(axis))
+ return serialized
def getMutatorAxes(self):
# map the axis values?
d = collections.OrderedDict()
for a in self.axes:
- d[a.name] = a.serialize()
+ d[a.name] = self._serializeAnyAxis(a)
return d
def _getAxisOrder(self):
@@ -355,14 +319,26 @@ def _getAxisOrder(self):
axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors")
serializedAxes = property(getSerializedAxes, doc="a list of dicts with the axis values")
-
+
+ def _setUseVarLib(self, useVarLib=True):
+ self.changed()
+ self.useVarLib = True
+
+ useVarLib = property(_setUseVarLib, doc="set useVarLib to True use the varlib mathmodel. Set to False to use MutatorMath.")
+
def getVariationModel(self, items, axes, bias=None):
# Return either a mutatorMath or a varlib.model object for calculating.
- try:
+ #try:
+ if True:
if self.useVarlib:
# use the varlib variation model
try:
- return dict(), VariationModelMutator(items, self.axes)
+ return dict(), VariationModelMutator(items, axes=self.axes, extrapolate=True)
+ except TypeError:
+ import fontTools.varLib.models
+ error = traceback.format_exc()
+ print(error)
+ return {}, None
except (KeyError, AssertionError):
error = traceback.format_exc()
self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error)
@@ -371,21 +347,28 @@ def getVariationModel(self, items, axes, bias=None):
else:
# use mutatormath model
axesForMutator = self.getMutatorAxes()
- return buildMutator(items, axes=axesForMutator, bias=bias)
- except:
- error = traceback.format_exc()
- self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error)
- return {}, None
-
- def getInfoMutator(self):
+ # mutator will be confused by discrete axis values.
+ # the bias needs to be for the continuous axes only
+ biasForMutator, _ = self.splitLocation(bias)
+ return buildMutator(items, axes=axesForMutator, bias=biasForMutator)
+ #except:
+ # error = traceback.format_exc()
+ # self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error)
+ return {}, None
+
+ @memoize
+ def getInfoMutator(self, discreteLocation=None):
""" Returns a info mutator """
- if self._infoMutator:
- return self._infoMutator
infoItems = []
- for sourceDescriptor in self.sources:
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.sources
+ for sourceDescriptor in sources:
if sourceDescriptor.layerName is not None:
continue
- loc = Location(sourceDescriptor.location)
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
sourceFont = self.fonts[sourceDescriptor.name]
if sourceFont is None:
continue
@@ -393,32 +376,37 @@ def getInfoMutator(self):
infoItems.append((loc, sourceFont.info.toMathInfo()))
else:
infoItems.append((loc, self.mathInfoClass(sourceFont.info)))
- infoBias = self.newDefaultLocation(bend=True)
+ infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias)
return self._infoMutator
- def getKerningMutator(self, pairs=None):
+ @memoize
+ def getKerningMutator(self, pairs=None, discreteLocation=None):
""" Return a kerning mutator, collect the sources, build mathGlyphs.
If no pairs are given: calculate the whole table.
If pairs are given then query the sources for a value and make a mutator only with those values.
"""
- if self._kerningMutator and pairs == self._kerningMutatorPairs:
- return self._kerningMutator
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.sources
kerningItems = []
foregroundLayers = [None, 'foreground', 'public.default']
if pairs is None:
- for sourceDescriptor in self.sources:
+ for sourceDescriptor in sources:
if sourceDescriptor.layerName not in foregroundLayers:
continue
if not sourceDescriptor.muteKerning:
- loc = Location(sourceDescriptor.location)
+ # filter this XX @@
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
sourceFont = self.fonts[sourceDescriptor.name]
if sourceFont is None: continue
# this makes assumptions about the groups of all sources being the same.
kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups)))
else:
self._kerningMutatorPairs = pairs
- for sourceDescriptor in self.sources:
+ for sourceDescriptor in sources:
# XXX check sourceDescriptor layerName, only foreground should contribute
if sourceDescriptor.layerName is not None:
continue
@@ -428,7 +416,8 @@ def getKerningMutator(self, pairs=None):
sourceFont = self.fonts[sourceDescriptor.name]
if sourceFont is None:
continue
- loc = Location(sourceDescriptor.location)
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
# XXX can we get the kern value from the fontparts kerning object?
kerningItem = self.mathKerningClass(sourceFont.kerning, sourceFont.groups)
if kerningItem is not None:
@@ -438,17 +427,19 @@ def getKerningMutator(self, pairs=None):
if v is not None:
sparseKerning[pair] = v
kerningItems.append((loc, self.mathKerningClass(sparseKerning)))
- kerningBias = self.newDefaultLocation(bend=True)
+ kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ bias, thing = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias) #xx
bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias)
return self._kerningMutator
+ @memoize
def filterThisLocation(self, location, mutedAxes):
# return location with axes is mutedAxes removed
# this means checking if the location is a non-default value
if not mutedAxes:
return False, location
defaults = {}
- ignoreMaster = False
+ ignoreSource = False
for aD in self.axes:
defaults[aD.name] = aD.default
new = {}
@@ -459,18 +450,18 @@ def filterThisLocation(self, location, mutedAxes):
if mutedAxisName not in defaults:
continue
if location[mutedAxisName] != defaults.get(mutedAxisName):
- ignoreMaster = True
+ ignoreSource = True
del new[mutedAxisName]
- return ignoreMaster, new
+ return ignoreSource, new
+ @memoize
def getGlyphMutator(self, glyphName,
decomposeComponents=False,
- fromCache=None):
- # make a mutator / varlib object for glyphName.
- cacheKey = (glyphName, decomposeComponents)
- if cacheKey in self._glyphMutators and fromCache:
- return self._glyphMutators[cacheKey]
- items = self.collectMastersForGlyph(glyphName, decomposeComponents=decomposeComponents)
+ **discreteLocation,
+ ):
+ fromCache = False
+ # make a mutator / varlib object for glyphName, with the sources for the given discrete location
+ items = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation)
new = []
for a, b, c in items:
if hasattr(b, "toMathGlyph"):
@@ -480,27 +471,28 @@ def getGlyphMutator(self, glyphName,
else:
new.append((a,self.mathGlyphClass(b)))
thing = None
- try:
- bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True)) #xx
- except TypeError:
- self.toolLog.append("getGlyphMutator %s items: %s new: %s" % (glyphName, items, new))
- self.problems.append("\tCan't make processor for glyph %s" % (glyphName))
- if thing is not None:
- self._glyphMutators[cacheKey] = thing
+ thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ bias, thing = self.getVariationModel(new, axes=self.serializedAxes, bias=thisBias) #xx
return thing
- def collectMastersForGlyph(self, glyphName, decomposeComponents=False):
- """ Return a glyph mutator.defaultLoc
+ @memoize
+ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None):
+ """ Return a glyph mutator
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.
- XXX check glyphs in layers
+ findSourceDescriptorsForDiscreteLocation returns sources from layers as well
"""
items = []
empties = []
foundEmpty = False
- for sourceDescriptor in self.sources:
+ #
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.sources
+ for sourceDescriptor in sources:
if not os.path.exists(sourceDescriptor.path):
#kthxbai
p = "\tMissing UFO at %s" % sourceDescriptor.path
@@ -510,8 +502,8 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False):
if glyphName in sourceDescriptor.mutedGlyphNames:
continue
thisIsDefault = self.default == sourceDescriptor
- ignoreMaster, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames)
- if ignoreMaster:
+ ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames)
+ if ignoreSource:
continue
f = self.fonts.get(sourceDescriptor.name)
if f is None: continue
@@ -564,7 +556,11 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False):
processThis = processThis.toMathGlyph()
else:
processThis = self.mathGlyphClass(processThis)
- items.append((loc, processThis, sourceInfo))
+ # this is where the location is linked to the glyph
+ # this loc needs to have the discrete location subtracted
+ # XX @@
+ continuous, discrete = self.splitLocation(loc)
+ items.append((continuous, processThis, sourceInfo))
empties.append((thisIsDefault, foundEmpty))
# check the empties:
# if the default glyph is empty, then all must be empty
@@ -590,6 +586,8 @@ def collectMastersForGlyph(self, glyphName, decomposeComponents=False):
checkedItems.append(items[i])
return checkedItems
+ collectMastersForGlyph = collectSourcesForGlyph
+
def getNeutralFont(self):
# Return a font object for the neutral font
# self.fonts[self.default.name] ?
@@ -603,36 +601,61 @@ def getNeutralFont(self):
return self.fonts[sd.name]
return None
- def findDefault(self):
+ def findDefault(self, discreteLocation=None):
"""Set and return SourceDescriptor at the default location or None.
-
The default location is the set of all `default` values in user space of all axes.
"""
self.default = None
# Convert the default location from user space to design space before comparing
# it against the SourceDescriptor locations (always in design space).
- default_location_design = self.newDefaultLocation(bend=True)
+ default_location_design = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ print(f"findDefault for {discreteLocation}: {default_location_design}")
for sourceDescriptor in self.sources:
if sourceDescriptor.location == default_location_design:
+ print(f"findDefault found {sourceDescriptor.location} {default_location_design} {sourceDescriptor.location == default_location_design}")
self.default = sourceDescriptor
return sourceDescriptor
return None
- def newDefaultLocation(self, bend=False):
+ def newDefaultLocation(self, bend=False, discreteLocation=None):
# overwrite from fontTools.newDefaultLocation
- # we do not want this default location to be mapped.
+ # we do not want this default location always to be mapped.
loc = collections.OrderedDict()
for axisDescriptor in self.axes:
+ axisName = axisDescriptor.name
+ axisValue = axisDescriptor.default
+ if discreteLocation is not None:
+ # if we want to find the default for a specific discreteLoation
+ # we can not use the discrete axis' default value
+ # -> we have to use the value in the given discreteLocation
+ if axisDescriptor.name in discreteLocation:
+ axisValue = discreteLocation[axisDescriptor.name]
+ else:
+ axisValue = axisDescriptor.default
if bend:
- loc[axisDescriptor.name] = axisDescriptor.map_forward(
- axisDescriptor.default
+ loc[axisName] = axisDescriptor.map_forward(
+ axisValue
)
else:
- loc[axisDescriptor.name] = axisDescriptor.default
+ loc[axisName] = axisValue
return loc
-
+
+ def updateFonts(self, fontObjects):
+ # this is to update the loaded fonts.
+ # it should be the way for an editor to provide a list of fonts that are open
+ #self.fonts[sourceDescriptor.name] = None
+ hasUpdated = False
+ for newFont in fontObjects:
+ for fontName, haveFont in self.fonts.items():
+ if haveFont.path == newFont.path and id(haveFont)!=id(newFont):
+ #print(f"updating {self.fonts[fontName]} with {newFont}")
+ self.fonts[fontName] = newFont
+ hasUpdated = True
+ if hasUpdated:
+ self.changed()
+
def loadFonts(self, reload=False):
# Load the fonts and find the default candidate based on the info flag
if self._fontsLoaded and not reload:
@@ -641,11 +664,12 @@ def loadFonts(self, reload=False):
for i, sourceDescriptor in enumerate(self.sources):
if sourceDescriptor.name is None:
# make sure it has a unique name
- sourceDescriptor.name = "master.%d" % i
+ sourceDescriptor.name = "source.%d" % i
if sourceDescriptor.name not in self.fonts:
+ #
if os.path.exists(sourceDescriptor.path):
self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path)
- self.problems.append("loaded master from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path)))
+ self.problems.append("loaded source from %s, layer %s, format %d" % (sourceDescriptor.path, sourceDescriptor.layerName, getUFOVersion(sourceDescriptor.path)))
names |= set(self.fonts[sourceDescriptor.name].keys())
else:
self.fonts[sourceDescriptor.name] = None
@@ -663,41 +687,39 @@ def getFonts(self):
return fonts
def makeInstance(self, instanceDescriptor,
- doRules=False,
+ doRules=None,
glyphNames=None,
pairs=None,
bend=False):
""" Generate a font object for this instance """
+ if doRules is not None:
+ warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2)
+ continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location)
+ print('makeInstance', continuousLocation, discreteLocation)
font = self._instantiateFont(None)
# make fonty things here
- loc = Location(instanceDescriptor.location)
+ loc = Location(continuousLocation)
anisotropic = False
locHorizontal = locVertical = loc
if self.isAnisotropic(loc):
anisotropic = True
locHorizontal, locVertical = self.splitAnisotropic(loc)
- # groups
- renameMap = getattr(self.fonts[self.default.name], "kerningGroupConversionRenameMaps", None)
- font.kerningGroupConversionRenameMaps = renameMap if renameMap is not None else {'side1': {}, 'side2': {}}
- # make the kerning
- # this kerning is always horizontal. We can take the horizontal location
- # filter the available pairs?
if instanceDescriptor.kerning:
if pairs:
try:
- kerningMutator = self.getKerningMutator(pairs=pairs)
+ kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation)
kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
kerningObject.extractKerning(font)
except:
self.problems.append("Could not make kerning for %s. %s" % (loc, traceback.format_exc()))
else:
- kerningMutator = self.getKerningMutator()
+ kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation)
if kerningMutator is not None:
kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
kerningObject.extractKerning(font)
- # make the info
+ # # make the info
try:
- infoMutator = self.getInfoMutator()
+ infoMutator = self.getInfoMutator(discreteLocation=discreteLocation)
if infoMutator is not None:
if not anisotropic:
infoInstanceObject = infoMutator.makeInstance(loc, bend=bend)
@@ -717,14 +739,14 @@ def makeInstance(self, instanceDescriptor,
font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation..
font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
- # NEED SOME HELP WITH THIS
- # 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, ))
+ # # NEED SOME HELP WITH THIS
+ # # 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, ))
except:
self.problems.append("Could not make fontinfo for %s. %s" % (loc, traceback.format_exc()))
for sourceDescriptor in self.sources:
@@ -739,11 +761,8 @@ def makeInstance(self, instanceDescriptor,
font.lib[key] = value
if sourceDescriptor.copyGroups:
if self.fonts[sourceDescriptor.name] is not None:
- sides = font.kerningGroupConversionRenameMaps.get('side1', {})
- sides.update(font.kerningGroupConversionRenameMaps.get('side2', {}))
for key, value in self.fonts[sourceDescriptor.name].groups.items():
- if key not in sides:
- font.groups[key] = value
+ font.groups[key] = value
if sourceDescriptor.copyFeatures:
if self.fonts[sourceDescriptor.name] is not None:
featuresText = self.fonts[sourceDescriptor.name].features.text
@@ -754,100 +773,45 @@ def makeInstance(self, instanceDescriptor,
else:
selectedGlyphNames = self.glyphNames
# add the glyphnames to the font.lib['public.glyphOrder']
+ # TODO load ignored glyphs from designspace?
if not 'public.glyphOrder' in font.lib.keys():
+ # should be the glyphorder from the default, yes?
font.lib['public.glyphOrder'] = selectedGlyphNames
for glyphName in selectedGlyphNames:
- try:
- glyphMutator = self.getGlyphMutator(glyphName)
+ #try:
+ if True:
+ glyphMutator = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation)
if glyphMutator is None:
self.problems.append("Could not make mutator for glyph %s" % (glyphName))
continue
- except:
- self.problems.append("Could not make mutator for glyph %s %s" % (glyphName, traceback.format_exc()))
- continue
- if glyphName in instanceDescriptor.glyphs.keys():
- # XXX this should be able to go now that we have full rule support.
- # 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}}],
- # 'unicodes': [36]}
- glyphData = instanceDescriptor.glyphs[glyphName]
- else:
- glyphData = {}
+
+ glyphData = {}
font.newGlyph(glyphName)
font[glyphName].clear()
- if glyphData.get('mute', False):
- # mute this glyph, skip
- continue
- glyphInstanceLocation = glyphData.get("instanceLocation", instanceDescriptor.location)
- glyphInstanceLocation = Location(glyphInstanceLocation)
- uniValues = []
- neutral = glyphMutator.get(())
- if neutral is not None:
- uniValues = neutral[0].unicodes
- else:
- neutralFont = self.getNeutralFont()
- if glyphName in neutralFont:
- uniValues = neutralFont[glyphName].unicodes
- glyphInstanceUnicodes = glyphData.get("unicodes", uniValues)
- note = glyphData.get("note")
- if note:
- font[glyphName] = note
- # XXXX phase out support for instance-specific masters
- # this should be handled by the rules system.
- masters = glyphData.get("masters", None)
- if masters is not None:
- 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
- if hasattr(m[sourceGlyphName], "toMathGlyph"):
- sourceGlyph = m[sourceGlyphName].toMathGlyph()
- else:
- sourceGlyph = MathGlyph(m[sourceGlyphName])
- sourceGlyphLocation = glyphMaster.get("location")
- items.append((Location(sourceGlyphLocation), sourceGlyph))
- bias, glyphMutator = self.getVariationModel(items, axes=self.serializedAxes, bias=self.newDefaultLocation(bend=True))
+ glyphInstanceUnicodes = []
+ neutralFont = self.getNeutralFont()
+ # get the unicodes from the default
+ if glyphName in neutralFont:
+ glyphInstanceUnicodes = neutralFont[glyphName].unicodes
try:
- if not self.isAnisotropic(glyphInstanceLocation):
- glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation, bend=bend)
+ if not self.isAnisotropic(continuousLocation):
+ glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend)
else:
# split anisotropic location into horizontal and vertical components
- horizontal, vertical = self.splitAnisotropic(glyphInstanceLocation)
- horizontalGlyphInstanceObject = glyphMutator.makeInstance(horizontal, bend=bend)
- verticalGlyphInstanceObject = glyphMutator.makeInstance(vertical, bend=bend)
+ horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend)
+ verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend)
# merge them again
glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject
except IndexError:
# alignment problem with the data?
self.problems.append("Quite possibly some sort of data alignment error in %s" % glyphName)
continue
- font.newGlyph(glyphName)
- font[glyphName].clear()
if self.roundGeometry:
try:
glyphInstanceObject = glyphInstanceObject.round()
except AttributeError:
+ # what are we catching here?
+ print(f"no round method for {glyphInstanceObject} ?")
pass
try:
# File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance
@@ -869,26 +833,18 @@ def makeInstance(self, instanceDescriptor,
glyphInstanceObject.drawPoints(pPen)
font[glyphName].width = glyphInstanceObject.width
font[glyphName].unicodes = glyphInstanceUnicodes
- if doRules:
- resultNames = processRules(self.rules, loc, self.glyphNames)
- for oldName, newName in zip(self.glyphNames, resultNames):
- if oldName != newName:
- swapGlyphNames(font, oldName, newName)
- # copy the glyph lib?
- #for sourceDescriptor in self.sources:
- # if sourceDescriptor.copyLib:
- # pass
- # pass
- # store designspace location in the font.lib
font.lib['designspace.location'] = list(instanceDescriptor.location.items())
+
return font
+ @memoize
def isAnisotropic(self, location):
for v in location.values():
- if type(v)==tuple:
+ if isinstance(v, (list, tuple)):
return True
return False
+ @memoize
def splitAnisotropic(self, location):
x = Location()
y = Location()
@@ -969,5 +925,125 @@ def _copyFontInfo(self, sourceInfo, targetInfo):
if copy:
value = getattr(sourceInfo, infoAttribute)
setattr(targetInfo, infoAttribute, value)
-
-
+
+ # some ds5 work
+ def getOrderedDiscreteAxes(self):
+ # return the list of discrete axis objects, in the right order
+ axes = []
+ for axisName in self.getAxisOrder():
+ axisObj = self.getAxis(axisName)
+ if hasattr(axisObj, "values"):
+ axes.append(axisObj)
+ return axes
+
+ def splitLocation(self, location):
+ # split a location in a continouous and a discrete part
+ discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()]
+ continuous = {}
+ discrete = {}
+ for name, value in location.items():
+ if name in discreteAxes:
+ discrete[name] = value
+ else:
+ continuous[name] = value
+ if not discrete:
+ return continuous, None
+ return continuous, discrete
+
+ def getDiscreteLocations(self):
+ # return a list of all permutated discrete locations
+ # do we have a list of ordered axes?
+ values = []
+ names = []
+ discreteCoordinates = []
+ dd = []
+ for axis in self.getOrderedDiscreteAxes():
+ values.append(axis.values)
+ names.append(axis.name)
+ for r in itertools.product(*values):
+ # make a small dict for the discrete location values
+ discreteCoordinates.append({a:b for a,b in zip(names,r)})
+ return discreteCoordinates
+
+ @memoize
+ def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict):
+ # return a list of all sourcedescriptors that share the values in the discrete loc tuple
+ # so this includes all sourcedescriptors that point to layers
+ # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0}
+ sources = []
+ for s in self.sources:
+ ok = True
+ if discreteLocDict is None:
+ sources.append(s)
+ continue
+ for name, value in discreteLocDict.items():
+ if name in s.location:
+ if s.location[name] != value:
+ ok = False
+ else:
+ ok = False
+ continue
+ if ok:
+ sources.append(s)
+ return sources
+
+ # caching
+ def changed(self):
+ # clears everything relating to this designspacedocument
+ # the cache could contain more designspacedocument objects.
+ for key in list(_memoizeCache.keys()):
+ if key[1] == self:
+ del _memoizeCache[key]
+ #_memoizeCache.clear()
+
+ def glyphChanged(self, glyphName):
+ # clears this one specific glyph
+ for key in list(_memoizeCache.keys()):
+ #print(f"glyphChanged {[(i,m) for i, m in enumerate(key)]} {glyphName}")
+ # the glyphname is hiding quite deep in key[2]
+ # (('glyphTwo',),)
+ # this is because of how immutify does it. Could be different I suppose but this works
+ if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName:
+ del _memoizeCache[key]
+
+
+if __name__ == "__main__":
+ # while we're testing
+ import shutil
+ import ufoProcessor
+
+ ds5Path = "../../Tests/ds5/test.ds5.designspace"
+ instancesPath = "../../Tests/ds5/instances"
+ instancesPathMutMath = "../../Tests/ds5/instances_mutMath"
+ instancesPathVarLib = "../../Tests/ds5/instances_varlib"
+
+ def memoizeStats():
+ from pprint import pprint
+ print("_memoizeCache:")
+ items = {}
+ for key, value in _memoizeCache.items():
+ if key[0] not in items:
+ items[key[0]] = 0
+ items[key[0]] +=1
+ pprint(items)
+
+ for useVarlibPref, renameInstancesPath in [(True, instancesPathVarLib), (False, instancesPathMutMath)]:
+ print(f"\n\n\t\t{useVarlibPref}")
+ dsp = DesignSpaceProcessor(useVarlib=useVarlibPref)
+ dsp.read(ds5Path)
+ dsp.loadFonts()
+ print(dsp.glyphNames)
+ dsp.updateFonts(AllFonts())
+ dsp.generateUFO()
+ dsp.glyphChanged("glyphOne")
+ if os.path.exists(renameInstancesPath):
+ shutil.rmtree(renameInstancesPath)
+ shutil.move(instancesPath, renameInstancesPath)
+ if useVarlibPref:
+ # clear only the cached items that belong to the varlib test
+ # just to see if we can
+ dsp.changed()
+ memoizeStats()
+
+print(f"{len(_memoizeCache)} items in _memoizeCache")
+print('done')
diff --git a/Lib/ufoProcessor/logger.py b/Lib/ufoProcessor/logger.py
new file mode 100644
index 0000000..d530408
--- /dev/null
+++ b/Lib/ufoProcessor/logger.py
@@ -0,0 +1,78 @@
+import sys
+import time
+import os
+import logging
+
+class Logger:
+
+ def __init__(self, path, rootDirectory, nest=0):
+ self.path = path
+ self.rootDirectory = rootDirectory
+ self.nest = nest
+ if not nest:
+ if path is not None:
+ # if os.path.exists(path):
+ # os.remove(path)
+ if not os.path.exists(path):
+ f = open(path, "w")
+ f.close()
+
+ def child(self, text=None):
+ logger = Logger(
+ self.path,
+ self.rootDirectory,
+ nest=self.nest + 1
+ )
+ if text:
+ logger.info(text)
+ return logger
+
+ def relativePath(self, path):
+ return os.path.relpath(path, self.rootDirectory)
+
+ def _makeText(self, text):
+ if self.nest:
+ text = f"{('| ' * self.nest).strip()} {text}"
+ return text
+
+ def _toConsole(self, text):
+ print(text)
+
+ def _toFile(self, text):
+ if self.path is None:
+ return
+ text += "\n"
+ f = open(self.path, "a")
+ f.write(text)
+ f.close()
+
+ def time(self, prefix=None):
+ now = time.strftime("%Y-%m-%d %H:%M")
+ if prefix:
+ now = prefix + " " + now
+ self.info(now)
+
+ def info(self, text):
+ text = self._makeText(text)
+ self._toConsole(text)
+ self._toFile(text)
+
+ def infoItem(self, text):
+ text = f"\t- {text}"
+ self.info(text)
+
+ def infoPath(self, path):
+ text = self.relativePath(path)
+ self.infoItem(text)
+
+ def detail(self, text):
+ text = self._makeText(text)
+ self._toFile(text)
+
+ def detailItem(self, text):
+ text = f"- {text}"
+ self.detail(text)
+
+ def detailPath(self, path):
+ text = self.relativePath(path)
+ self.detailItem(text)
diff --git a/Lib/ufoProcessor/emptyPen.py b/Lib/ufoProcessor/pens.py
similarity index 74%
rename from Lib/ufoProcessor/emptyPen.py
rename to Lib/ufoProcessor/pens.py
index 42459d1..db067d0 100755
--- a/Lib/ufoProcessor/emptyPen.py
+++ b/Lib/ufoProcessor/pens.py
@@ -1,5 +1,31 @@
# coding: utf-8
+
from fontTools.pens.pointPen import AbstractPointPen
+from defcon.pens.transformPointPen import TransformPointPen
+from defcon.objects.component import _defaultTransformation
+
+"""
+ Decompose
+
+"""
+
+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)
"""
Simple pen object to determine if a glyph contains any geometry.
@@ -24,7 +50,7 @@ def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=Non
def addComponent(self, baseGlyphName=None, transformation=None, identifier=None, **kwargs):
self.components+=1
-
+
def getCount(self):
return self.points, self.contours, self.components
@@ -71,6 +97,10 @@ def checkGlyphIsEmpty(glyph, allowWhiteSpace=True):
if glyph.unicode in whiteSpace and allowWhiteSpace:
# are we allowed to be?
return False
+ if "space" in glyph.name:
+ # this is a bold assumption,
+ # and certainly not inclusive
+ return False
return True
return False
diff --git a/Lib/ufoProcessor/sp3.py b/Lib/ufoProcessor/sp3.py
deleted file mode 100644
index 46ca167..0000000
--- a/Lib/ufoProcessor/sp3.py
+++ /dev/null
@@ -1,494 +0,0 @@
-import os
-import glob
-
-from fontTools.misc.loggingTools import LogMixin
-from fontTools.designspaceLib import DesignSpaceDocument, AxisDescriptor, SourceDescriptor, RuleDescriptor, InstanceDescriptor
-
-try:
- import xml.etree.cElementTree as ET
-except ImportError:
- import xml.etree.ElementTree as ET
-
-# Reader that parses Superpolator documents and buidls designspace objects.
-# Note: the Superpolator document format precedes the designspace documnt format.
-# For now I just want to migrate data out of Superpolator into designspace.
-# So not all data will migrate, just the stuff we can use.
-
-"""
-
-
-
-
- com.letterror.skateboard.interactionSources
-
- horizontal
-
- ignore
-
- vertical
-
-
- com.letterror.skateboard.mutedSources
-
-
- IBM Plex Sans Condensed-Bold.ufo
- foreground
-
-
- com.letterror.skateboard.previewLocation
-
- weight
- 0.0
-
- com.letterror.skateboard.previewText
- SKATE
-
-
-
-
-
-"""
-
-superpolatorDataLibKey = "com.superpolator.data" # lib key for Sp data in .designspace
-skateboardInteractionSourcesKey = "com.letterror.skateboard.interactionSources"
-skateboardMutedSourcesKey = "com.letterror.skateboard.mutedSources"
-skipExportKey = "public.skipExportGlyphs"
-skateboardPreviewLocationsKey = "com.letterror.skateboard.previewLocation"
-skateboardPreviewTextKey = "com.letterror.skateboard.previewText"
-
-class SuperpolatorReader(LogMixin):
- ruleDescriptorClass = RuleDescriptor
- axisDescriptorClass = AxisDescriptor
- sourceDescriptorClass = SourceDescriptor
- instanceDescriptorClass = InstanceDescriptor
-
- def __init__(self, documentPath, documentObject, convertRules=True, convertData=True, anisotropic=False):
- self.path = documentPath
- self.documentObject = documentObject
- self.convertRules = convertRules
- self.convertData = convertData
- self.allowAnisotropic = anisotropic # maybe add conversion options later
- tree = ET.parse(self.path)
- self.root = tree.getroot()
- self.documentObject.formatVersion = self.root.attrib.get("format", "3.0")
- self.axisDefaults = {}
- self._strictAxisNames = True
-
-
- @classmethod
- def fromstring(cls, string, documentObject):
- f = BytesIO(tobytes(string, encoding="utf-8"))
- self = cls(f, documentObject)
- self.path = None
- return self
-
- def read(self):
- self.readAxes()
- if self.convertData:
- self.readData()
- if self.convertRules:
- self.readOldRules()
- self.readSimpleRules()
- self.readSources()
- self.readInstances()
-
- def readData(self):
- # read superpolator specific data, view prefs etc.
- # if possible convert it to skateboard
- interactionSources = {'horizontal': [], 'vertical': [], 'ignore': []}
- ignoreElements = self.root.findall(".ignore")
- ignoreGlyphs = []
- for ignoreElement in ignoreElements:
- names = ignoreElement.attrib.get('glyphs')
- if names:
- ignoreGlyphs = names.split(",")
- if ignoreGlyphs:
- self.documentObject.lib[skipExportKey] = ignoreGlyphs
- dataElements = self.root.findall(".data")
- if not dataElements:
- return
- newLib = {}
- interactionSourcesAdded = False
- for dataElement in dataElements:
- name = dataElement.attrib.get('name')
- value = dataElement.attrib.get('value')
- if value in ['True', 'False']:
- value = value == "True"
- else:
- try:
- value = float(value)
- except ValueError:
- pass
- if name == "previewtext":
- self.documentObject.lib[skateboardPreviewTextKey] = value
- elif name == "horizontalPreviewAxis":
- interactionSources['horizontal'].append(value)
- interactionSourcesAdded = True
- elif name == "verticalPreviewAxis":
- interactionSources['vertical'].append(value)
- interactionSourcesAdded = True
-
- newLib[name] = value
- if interactionSourcesAdded:
- self.documentObject.lib[skateboardInteractionSourcesKey] = interactionSources
- if newLib:
- self.documentObject.lib[superpolatorDataLibKey] = newLib
-
-
- def readOldRules(self):
- # read the old rules
- #
- #
- #
-
- # superpolator old rule to simple rule
- # if op in ['<', '<=']:
- # # old style data
- # axes[axisName]['maximum'] = conditionDict['values']
- # newRule.name = "converted %s < and <= "%(axisName)
- # elif op in ['>', '>=']:
- # # old style data
- # axes[axisName]['minimum'] = conditionDict['values']
- # newRule.name = "converted %s > and >= "%(axisName)
- # elif op == "==":
- # axes[axisName]['maximum'] = conditionDict['values']
- # axes[axisName]['minimum'] = conditionDict['values']
- # newRule.name = "converted %s == "%(axisName)
- # newRule.enabled = False
- # elif op == "!=":
- # axes[axisName]['maximum'] = conditionDict['values']
- # axes[axisName]['minimum'] = conditionDict['values']
- # newRule.name = "unsupported %s != "%(axisName)
- # newRule.enabled = False
- # else:
- # axes[axisName]['maximum'] = conditionDict['minimum']
- # axes[axisName]['minimum'] = conditionDict['maximum']
- # newRule.name = "minmax legacy rule for %s"%axisName
- # newRule.enabled = False
-
- rules = []
- for oldRuleElement in self.root.findall(".rule"):
- ruleObject = self.ruleDescriptorClass()
- # only one condition set in these old rules
- cds = []
- a = oldRuleElement.attrib['resultfalse']
- b = oldRuleElement.attrib['resulttrue']
- ruleObject.subs.append((a,b))
- for oldConditionElement in oldRuleElement.findall(".condition"):
- cd = {}
- operator = oldConditionElement.attrib['operator']
- axisValue = float(oldConditionElement.attrib['xvalue'])
- axisName = oldConditionElement.attrib['axisname']
- if operator in ['<', '<=']:
- cd['maximum'] = axisValue
- cd['minimum'] = None
- cd['name'] = axisName
- ruleObject.name = "converted %s < and <= "%(axisName)
- elif operator in ['>', '>=']:
- cd['maximum'] = None
- cd['minimum'] = axisValue
- cd['name'] = axisName
- ruleObject.name = "converted %s > and >= "%(axisName)
- elif operator in ["==", "!="]:
- # can't convert this one
- continue
- cds.append(cd)
- if cds:
- ruleObject.conditionSets.append(cds)
- self.documentObject.addRule(ruleObject)
-
- def readSimpleRules(self):
- # read the simple rule elements
- #
- #
- #
- #
- #
- #
- #
-
-
- rulesContainerElements = self.root.findall(".simplerules")
- rules = []
- for rulesContainerElement in rulesContainerElements:
- for ruleElement in rulesContainerElement:
- ruleObject = self.ruleDescriptorClass()
- ruleName = ruleObject.name = ruleElement.attrib['name']
- # subs
- for subElement in ruleElement.findall('.sub'):
- a = subElement.attrib['name']
- b = subElement.attrib['with']
- ruleObject.subs.append((a, b))
- # condition sets, .sp3 had none
- externalConditions = self._readConditionElements(
- ruleElement,
- ruleName,
- )
- if externalConditions:
- ruleObject.conditionSets.append(externalConditions)
- self.log.info(
- "Found stray rule conditions outside a conditionset. "
- "Wrapped them in a new conditionset."
- )
- self.documentObject.addRule(ruleObject)
-
- def _readConditionElements(self, parentElement, ruleName=None):
- # modified from the method from fonttools.designspaceLib
- # it's not the same!
- cds = []
- for conditionElement in parentElement.findall('.condition'):
- cd = {}
- cdMin = conditionElement.attrib.get("minimum")
- if cdMin is not None:
- cd['minimum'] = float(cdMin)
- else:
- # will allow these to be None, assume axis.minimum
- cd['minimum'] = None
- cdMax = conditionElement.attrib.get("maximum")
- if cdMax is not None:
- cd['maximum'] = float(cdMax)
- else:
- # will allow these to be None, assume axis.maximum
- cd['maximum'] = None
- cd['name'] = conditionElement.attrib.get("axisname")
- # # test for things
- if cd.get('minimum') is None and cd.get('maximum') is None:
- raise DesignSpaceDocumentError(
- "condition missing required minimum or maximum in rule" +
- (" '%s'" % ruleName if ruleName is not None else ""))
- cds.append(cd)
- return cds
-
- def readAxes(self):
- # read the axes elements, including the warp map.
- axisElements = self.root.findall(".axis")
- if not axisElements:
- # raise error, we need axes
- return
- for axisElement in axisElements:
- axisObject = self.axisDescriptorClass()
- axisObject.name = axisElement.attrib.get("name")
- axisObject.tag = axisElement.attrib.get("shortname")
- axisObject.minimum = float(axisElement.attrib.get("minimum"))
- axisObject.maximum = float(axisElement.attrib.get("maximum"))
- axisObject.default = float(axisElement.attrib.get("initialvalue", axisObject.minimum))
- self.documentObject.axes.append(axisObject)
- self.axisDefaults[axisObject.name] = axisObject.default
- self.documentObject.defaultLoc = self.axisDefaults
-
- def colorFromElement(self, element):
- elementColor = None
- for colorElement in element.findall('.color'):
- elementColor = self.readColorElement(colorElement)
-
- def readColorElement(self, colorElement):
- pass
-
- def locationFromElement(self, element):
- elementLocation = None
- for locationElement in element.findall('.location'):
- elementLocation = self.readLocationElement(locationElement)
- break
- if not self.allowAnisotropic:
- # don't want any anisotropic values here
- split = {}
- for k, v in elementLocation.items():
- if type(v) == type(()):
- split[k] = v[0]
- else:
- split[k] = v
- elementLocation = split
- return elementLocation
-
- def readLocationElement(self, locationElement):
- """ Format 0 location reader """
- if self._strictAxisNames and not self.documentObject.axes:
- raise DesignSpaceDocumentError("No axes defined")
- loc = {}
- for dimensionElement in locationElement.findall(".dimension"):
- dimName = dimensionElement.attrib.get("name")
- if self._strictAxisNames and dimName not in self.axisDefaults:
- # In case the document contains no axis definitions,
- self.log.warning("Location with undefined axis: \"%s\".", dimName)
- continue
- xValue = yValue = None
- try:
- xValue = dimensionElement.attrib.get('xvalue')
- xValue = float(xValue)
- except ValueError:
- self.log.warning("KeyError in readLocation xValue %3.3f", xValue)
- try:
- yValue = dimensionElement.attrib.get('yvalue')
- if yValue is not None:
- yValue = float(yValue)
- except ValueError:
- pass
- if yValue is not None:
- loc[dimName] = (xValue, yValue)
- else:
- loc[dimName] = xValue
- return loc
-
- def readSources(self):
- for sourceCount, sourceElement in enumerate(self.root.findall(".master")):
- filename = sourceElement.attrib.get('filename')
- if filename is not None and self.path is not None:
- sourcePath = os.path.abspath(os.path.join(os.path.dirname(self.path), filename))
- else:
- sourcePath = None
- sourceName = sourceElement.attrib.get('name')
- if sourceName is None:
- # add a temporary source name
- sourceName = "temp_master.%d" % (sourceCount)
- sourceObject = self.sourceDescriptorClass()
- sourceObject.path = sourcePath # absolute path to the ufo source
- sourceObject.filename = filename # path as it is stored in the document
- sourceObject.name = sourceName
- familyName = sourceElement.attrib.get("familyname")
- if familyName is not None:
- sourceObject.familyName = familyName
- styleName = sourceElement.attrib.get("stylename")
- if styleName is not None:
- sourceObject.styleName = styleName
- sourceObject.location = self.locationFromElement(sourceElement)
- isMuted = False
- for maskedElement in sourceElement.findall('.maskedfont'):
- # mute isn't stored in the sourceDescriptor, but we can store it in the lib
- if maskedElement.attrib.get('font') == "1":
- isMuted = True
- for libElement in sourceElement.findall('.provideLib'):
- if libElement.attrib.get('state') == '1':
- sourceObject.copyLib = True
- for groupsElement in sourceElement.findall('.provideGroups'):
- if groupsElement.attrib.get('state') == '1':
- sourceObject.copyGroups = True
- for infoElement in sourceElement.findall(".provideInfo"):
- if infoElement.attrib.get('state') == '1':
- sourceObject.copyInfo = True
- for featuresElement in sourceElement.findall(".provideFeatures"):
- if featuresElement.attrib.get('state') == '1':
- sourceObject.copyFeatures = True
- for glyphElement in sourceElement.findall(".glyph"):
- glyphName = glyphElement.attrib.get('name')
- if glyphName is None:
- continue
- if glyphElement.attrib.get('mute') == '1':
- sourceObject.mutedGlyphNames.append(glyphName)
- self.documentObject.sources.append(sourceObject)
- if isMuted:
- if not skateboardMutedSourcesKey in self.documentObject.lib:
- self.documentObject.lib[skateboardMutedSourcesKey] = []
- item = (sourceObject.filename, "foreground")
- self.documentObject.lib[skateboardMutedSourcesKey].append(item)
-
- def readInstances(self):
- for instanceCount, instanceElement in enumerate(self.root.findall(".instance")):
- instanceObject = self.instanceDescriptorClass()
- if instanceElement.attrib.get("familyname"):
- instanceObject.familyName = instanceElement.attrib.get("familyname")
- if instanceElement.attrib.get("stylename"):
- instanceObject.styleName = instanceElement.attrib.get("stylename")
- if instanceElement.attrib.get("styleMapFamilyName"):
- instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName")
- if instanceElement.attrib.get("styleMapStyleName"):
- instanceObject.styleMapStyleName = instanceElement.attrib.get("styleMapStyleName")
- if instanceElement.attrib.get("styleMapFamilyName"):
- instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName")
- instanceObject.location = self.locationFromElement(instanceElement)
- instanceObject.filename = instanceElement.attrib.get('filename')
- for libElement in instanceElement.findall('.provideLib'):
- if libElement.attrib.get('state') == '1':
- instanceObject.lib = True
- for libElement in instanceElement.findall('.provideInfo'):
- if libElement.attrib.get('state') == '1':
- instanceObject.info = True
- self.documentObject.instances.append(instanceObject)
-
-def sp3_to_designspace(sp3path, designspacePath=None):
- if designspacePath is None:
- designspacePath = sp3path.replace(".sp3", ".designspace")
- doc = DesignSpaceDocument()
- reader = SuperpolatorReader(sp3path, doc)
- reader.read()
- doc.write(designspacePath)
-
-
-if __name__ == "__main__":
-
- def test_superpolator_testdoc1():
- # read superpolator_testdoc1.sp3
- # and test all the values
- testDoc = DesignSpaceDocument()
- testPath = "../../Tests/spReader_testdocs/superpolator_testdoc1.sp3"
- reader = SuperpolatorReader(testPath, testDoc)
- reader.read()
-
- # check the axes
- names = [a.name for a in reader.documentObject.axes]
- names.sort()
- assert names == ['grade', 'space', 'weight', 'width']
- tags = [a.tag for a in reader.documentObject.axes]
- tags.sort()
- assert tags == ['SPCE', 'grad', 'wdth', 'wght']
-
- # check the data items
- assert superpolatorDataLibKey in reader.documentObject.lib
- items = list(reader.documentObject.lib[superpolatorDataLibKey].items())
- items.sort()
- assert items == [('expandRules', False), ('horizontalPreviewAxis', 'width'), ('includeLegacyRules', False), ('instancefolder', 'instances'), ('keepWorkFiles', True), ('lineInverted', True), ('lineStacked', 'lined'), ('lineViewFilled', True), ('outputFormatUFO', 3.0), ('previewtext', 'VA'), ('roundGeometry', False), ('verticalPreviewAxis', 'weight')]
-
- # check the sources
- for sd in reader.documentObject.sources:
- assert sd.familyName == "MutatorMathTest_SourceFamilyName"
- if sd.styleName == "Default":
- assert sd.location == {'width': 0.0, 'weight': 0.0, 'space': 0.0, 'grade': -0.5}
- assert sd.copyLib == True
- assert sd.copyGroups == True
- assert sd.copyInfo == True
- assert sd.copyFeatures == True
- elif sd.styleName == "TheOther":
- assert sd.location == {'width': 0.0, 'weight': 1000.0, 'space': 0.0, 'grade': -0.5}
- assert sd.copyLib == False
- assert sd.copyGroups == False
- assert sd.copyInfo == False
- assert sd.copyFeatures == False
-
- # check the instances
- for nd in reader.documentObject.instances:
- assert nd.familyName == "MutatorMathTest_InstanceFamilyName"
- if nd.styleName == "AWeightThatILike":
- assert nd.location == {'width': 133.152174, 'weight': 723.981097, 'space': 0.0, 'grade': -0.5}
- assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-AWeightThatILike.ufo"
- assert nd.styleMapFamilyName == None
- assert nd.styleMapStyleName == None
- if nd.styleName == "wdth759.79_SPCE0.00_wght260.72":
- # note the converted anisotropic location in the width axis.
- assert nd.location == {'grade': -0.5, 'width': 500.0, 'weight': 260.7217, 'space': 0.0}
- assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-wdth759.79_SPCE0.00_wght260.72.ufo"
- assert nd.styleMapFamilyName == "StyleMappedFamily"
- assert nd.styleMapStyleName == "bold"
-
- # check the rules
- for rd in reader.documentObject.rules:
- assert rd.name == "width: < 500.0"
- assert len(rd.conditionSets) == 1
- assert rd.subs == [('I', 'I.narrow')]
- for conditionSet in rd.conditionSets:
- for cd in conditionSet:
- if cd['name'] == "width":
- assert cd == {'minimum': None, 'maximum': 500.0, 'name': 'width'}
- if cd['name'] == "grade":
- assert cd == {'minimum': 0.0, 'maximum': 500.0, 'name': 'grade'}
-
-
- testDoc.write(testPath.replace(".sp3", "_output_roundtripped.designspace"))
-
- def test_testDocs():
- # read the test files and convert them
- # no tests
- root = "../../Tests/spReader_testdocs/test*.sp3"
- for path in glob.glob(root):
- sp3_to_designspace(path)
-
- test_superpolator_testdoc1()
- #test_testDocs()
\ No newline at end of file
diff --git a/Lib/ufoProcessor/ufoOperator.py b/Lib/ufoProcessor/ufoOperator.py
new file mode 100644
index 0000000..e9cf37a
--- /dev/null
+++ b/Lib/ufoProcessor/ufoOperator.py
@@ -0,0 +1,1013 @@
+import os
+import glob
+import functools
+
+
+import defcon
+from warnings import warn
+import collections
+import logging, traceback
+
+from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules
+from fontTools.designspaceLib.split import splitInterpolable
+from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3
+from fontTools.misc import plistlib
+
+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 Location
+
+import fontParts.fontshell.font
+
+import ufoProcessor.varModels
+import ufoProcessor.pens
+from ufoProcessor.varModels import VariationModelMutator
+from ufoProcessor.pens import checkGlyphIsEmpty, DecomposePointPen
+from ufoProcessor.logger import Logger
+
+_memoizeCache = dict()
+_memoizeStats = dict()
+
+def immutify(obj):
+ # make an immutable version of this object.
+ # assert immutify(10) == (10,)
+ # assert immutify([10, 20, "a"]) == (10, 20, 'a')
+ # assert immutify(dict(foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b'))
+ hashValues = []
+ if isinstance(obj, dict):
+ for key, value in obj.items():
+ hashValues.extend([key, immutify(value)])
+ elif isinstance(obj, list):
+ for value in obj:
+ hashValues.extend(immutify(value))
+ else:
+ hashValues.append(obj)
+ return tuple(hashValues)
+
+def memoize(function):
+ @functools.wraps(function)
+ def wrapper(self, *args, **kwargs):
+ immutableargs = tuple([immutify(a) for a in args])
+ immutablekwargs = immutify(kwargs)
+ key = (function.__name__, self, immutableargs, immutify(kwargs))
+ if key in _memoizeCache:
+ if not key in _memoizeStats:
+ _memoizeStats[key] = 0
+ _memoizeStats[key] += 1
+ return _memoizeCache[key]
+ else:
+ result = function(self, *args, **kwargs)
+ _memoizeCache[key] = result
+ return result
+ return wrapper
+
+def inspectMemoizeCache():
+ functionNames = []
+ stats = {}
+ for k in _memoizeCache.keys():
+ functionName = k[0]
+ if not functionName in stats:
+ stats[functionName] = 0
+ stats[functionName] += 1
+ print(stats)
+
+def getUFOVersion(ufoPath):
+ # Peek into a ufo to read its format version.
+ #
+ #
+ #
+ #
+ # creator
+ # org.robofab.ufoLib
+ # formatVersion
+ # 2
+ #
+ #
+ metaInfoPath = os.path.join(ufoPath, "metainfo.plist")
+ with open(metaInfoPath, 'rb') as f:
+ p = plistlib.load(f)
+ return p.get('formatVersion')
+
+def getDefaultLayerName(f):
+ # get the name of the default layer from a defcon font (outside RF) and from a fontparts font (outside and inside RF)
+ if issubclass(type(f), defcon.objects.font.Font):
+ return f.layers.defaultLayer.name
+ elif issubclass(type(f), fontParts.fontshell.font.RFont):
+ return f.defaultLayer.name
+ return None
+
+# wrapped, not inherited
+class UFOOperator(object):
+
+ fontClass = defcon.Font
+ layerClass = defcon.Layer
+ 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, pathOrObject=None, ufoVersion=3, useVarlib=True, debug =False):
+ self.ufoVersion = ufoVersion
+ self.useVarlib = useVarlib
+ self._fontsLoaded = False
+ self.fonts = {}
+ self.roundGeometry = False
+ self.mutedAxisNames = None # list of axisname that need to be muted
+ self.debug = debug
+ self.logger = None
+
+ if isinstance(pathOrObject, str):
+ self.doc = DesignSpaceDocument()
+ self.doc.read(pathOrObject)
+ else:
+ # XX test this
+ self.doc = pathOrObject
+
+ if self.debug:
+ docBaseName = os.path.splitext(self.doc.path)[0]
+ logPath = f"{docBaseName}_log.txt"
+ self.logger = Logger(path=logPath, rootDirectory=None)
+ self.logger.time()
+ self.logger.info(f"## {self.doc.path}")
+ self.logger.info(f"\tUFO version: {self.ufoVersion}")
+ self.logger.info(f"\tround Geometry: {self.roundGeometry}")
+ if self.useVarlib:
+ self.logger.info(f"\tinterpolating with varlib")
+ else:
+ self.logger.info(f"\tinterpolating with mutatorMath")
+
+ def _instantiateFont(self, path):
+ """ Return a instance of a font object with all the given subclasses"""
+ try:
+ return self.fontClass(path,
+ layerClass=self.layerClass,
+ 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)
+ except TypeError:
+ # if our fontClass doesnt support all the additional classes
+ return self.fontClass(path)
+
+ # loading and updating fonts
+ def loadFonts(self, reload=False):
+ # Load the fonts and find the default candidate based on the info flag
+ if self._fontsLoaded and not reload:
+ if self.debug:
+ self.logger.info("\t\t-- loadFonts requested, but fonts are loaded already and no reload requested")
+ return
+ names = set()
+ actions = []
+ if self.debug:
+ self.logger.info("## loadFonts")
+ for i, sourceDescriptor in enumerate(self.doc.sources):
+ if sourceDescriptor.name is None:
+ # make sure it has a unique name
+ sourceDescriptor.name = "source.%d" % i
+ if sourceDescriptor.name not in self.fonts:
+ if os.path.exists(sourceDescriptor.path):
+ f = self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path)
+ thisLayerName = getDefaultLayerName(f)
+ actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {getUFOVersion(sourceDescriptor.path)}, id: {id(f):X}")
+ names |= set(self.fonts[sourceDescriptor.name].keys())
+ else:
+ self.fonts[sourceDescriptor.name] = None
+ actions.append("source ufo not found at %s" % (sourceDescriptor.path))
+ self.glyphNames = list(names)
+ if self.debug:
+ for item in actions:
+ self.logger.infoItem(item)
+ self._fontsLoaded = True
+
+ def _logLoadedFonts(self):
+ # dump info about the loaded fonts to the log
+ items = []
+ self.logger.info("\t# font status:")
+ for name, fontObj in self.fonts.items():
+ self.logger.info(f"\t\tloaded: , id: {id(fontObj):X}, {os.path.basename(fontObj.path)}, format: {getUFOVersion(fontObj.path)}")
+
+ def updateFonts(self, fontObjects):
+ # this is to update the loaded fonts.
+ # it should be the way for an editor to provide a list of fonts that are open
+ #self.fonts[sourceDescriptor.name] = None
+ hasUpdated = False
+ for newFont in fontObjects:
+ for fontName, haveFont in self.fonts.items():
+ if haveFont.path == newFont.path and id(haveFont)!=id(newFont):
+ note = f"## updating source {self.fonts[fontName]} with {newFont}"
+ if self.debug:
+ self.logger.time()
+ self.logger.info(note)
+ self.fonts[fontName] = newFont
+ hasUpdated = True
+ if hasUpdated:
+ self.changed()
+
+ # caching
+ def changed(self):
+ # clears everything relating to this designspacedocument
+ # the cache could contain more designspacedocument objects.
+ for key in list(_memoizeCache.keys()):
+ if key[1] == self:
+ del _memoizeCache[key]
+ #_memoizeCache.clear()
+
+ def glyphChanged(self, glyphName):
+ # clears this one specific glyph from the memoize cache
+ for key in list(_memoizeCache.keys()):
+ #print(f"glyphChanged {[(i,m) for i, m in enumerate(key)]} {glyphName}")
+ # the glyphname is hiding quite deep in key[2]
+ # (('glyphTwo',),)
+ # this is because of how immutify does it. Could be different I suppose but this works
+ if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[2][0][0] == glyphName:
+ del _memoizeCache[key]
+
+ # manipulate locations and axes
+ def splitLocation(self, location):
+ # split a location in a continouous and a discrete part
+ discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()]
+ continuous = {}
+ discrete = {}
+ for name, value in location.items():
+ if name in discreteAxes:
+ discrete[name] = value
+ else:
+ continuous[name] = value
+ if not discrete:
+ return continuous, None
+ return continuous, discrete
+
+ def _serializeAnyAxis(self, axis):
+ if hasattr(axis, "serialize"):
+ return axis.serialize()
+ else:
+ if hasattr(axis, "values"):
+ # discrete axis does not have serialize method, meh
+ return dict(
+ tag=axis.tag,
+ name=axis.name,
+ labelNames=axis.labelNames,
+ minimum = min(axis.values), # XX is this allowed
+ maximum = max(axis.values), # XX is this allowed
+ values=axis.values,
+ default=axis.default,
+ hidden=axis.hidden,
+ map=axis.map,
+ axisOrdering=axis.axisOrdering,
+ axisLabels=axis.axisLabels,
+ )
+
+ def getSerializedAxes(self, discreteLocation=None):
+ serialized = []
+ for axis in self.getOrderedContinuousAxes():
+ serialized.append(self._serializeAnyAxis(axis))
+ return serialized
+
+ def getContinuousAxesForMutator(self):
+ # map the axis values?
+ d = collections.OrderedDict()
+ for axis in self.getOrderedContinuousAxes():
+ d[axis.name] = self._serializeAnyAxis(axis)
+ return d
+
+ def _getAxisOrder(self):
+ return [a.name for a in self.doc.axes]
+
+ axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors")
+
+ def getOrderedDiscreteAxes(self):
+ # return the list of discrete axis objects, in the right order
+ axes = []
+ for axisName in self.doc.getAxisOrder():
+ axisObj = self.doc.getAxis(axisName)
+ if hasattr(axisObj, "values"):
+ axes.append(axisObj)
+ return axes
+
+ def getOrderedContinuousAxes(self):
+ # return the list of continuous axis objects, in the right order
+ axes = []
+ for axisName in self.doc.getAxisOrder():
+ axisObj = self.doc.getAxis(axisName)
+ if not hasattr(axisObj, "values"):
+ axes.append(axisObj)
+ return axes
+
+ def checkDiscreteAxisValues(self, location):
+ # check if the discrete values in this location are allowed
+ for discreteAxis in self.getOrderedDiscreteAxes():
+ testValue = location.get(discreteAxis.name)
+ if not testValue in discreteAxis.values:
+ return False
+ return True
+
+ def collectBaseGlyphs(self, glyphName, location):
+ # make a list of all baseglyphs needed to build this glyph, at this location
+ # Note: different discrete values mean that the glyph component set up can be different too
+ continuousLocation, discreteLocation = self.splitLocation(location)
+ names = set()
+ def _getComponentNames(glyph):
+ # so we can do recursion
+ names = set()
+ for comp in glyph.components:
+ names.add(comp.baseGlyph)
+ for n in _getComponentNames(glyph.font[comp.baseGlyph]):
+ names.add(n)
+ return list(names)
+ for sourceDescriptor in self.findSourceDescriptorsForDiscreteLocation(discreteLocation):
+ sourceFont = self.fonts[sourceDescriptor.name]
+ if not glyphName in sourceFont: continue
+ [names.add(n) for n in _getComponentNames(sourceFont[glyphName])]
+ return list(names)
+
+ @memoize
+ def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict=None):
+ # return a list of all sourcedescriptors that share the values in the discrete loc tuple
+ # so this includes all sourcedescriptors that point to layers
+ # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0}
+ sources = []
+ for s in self.doc.sources:
+ ok = True
+ if discreteLocDict is None:
+ sources.append(s)
+ continue
+ for name, value in discreteLocDict.items():
+ if name in s.location:
+ if s.location[name] != value:
+ ok = False
+ else:
+ ok = False
+ continue
+ if ok:
+ sources.append(s)
+ return sources
+
+ def getVariationModel(self, items, axes, bias=None):
+ # Return either a mutatorMath or a varlib.model object for calculating.
+ if self.useVarlib:
+ # use the varlib variation model
+ try:
+ return dict(), VariationModelMutator(items, axes=self.doc.axes, extrapolate=True)
+ except TypeError:
+ if self.debug:
+ error = traceback.format_exc()
+ note = "Error while making VariationModelMutator for {loc}:\n{error}"
+ self.logger.info(note)
+ return {}, None
+ except (KeyError, AssertionError):
+ if self.debug:
+ error = traceback.format_exc()
+ note = "UFOProcessor.getVariationModel error: {error}"
+ self.logger.info(note)
+ return {}, None
+ else:
+ # use mutatormath model
+ axesForMutator = self.getContinuousAxesForMutator()
+ # mutator will be confused by discrete axis values.
+ # the bias needs to be for the continuous axes only
+ biasForMutator, _ = self.splitLocation(bias)
+ return buildMutator(items, axes=axesForMutator, bias=biasForMutator)
+ return {}, None
+
+ @memoize
+ def newDefaultLocation(self, bend=False, discreteLocation=None):
+ # overwrite from fontTools.newDefaultLocation
+ # we do not want this default location always to be mapped.
+ loc = collections.OrderedDict()
+ for axisDescriptor in self.doc.axes:
+ axisName = axisDescriptor.name
+ axisValue = axisDescriptor.default
+ if discreteLocation is not None:
+ # if we want to find the default for a specific discreteLoation
+ # we can not use the discrete axis' default value
+ # -> we have to use the value in the given discreteLocation
+ if axisDescriptor.name in discreteLocation:
+ axisValue = discreteLocation[axisDescriptor.name]
+ else:
+ axisValue = axisDescriptor.default
+ if bend:
+ loc[axisName] = axisDescriptor.map_forward(
+ axisValue
+ )
+ else:
+ loc[axisName] = axisValue
+ return loc
+
+ @memoize
+ def isAnisotropic(self, location):
+ # check if the location has anisotropic values
+ for v in location.values():
+ if isinstance(v, (list, tuple)):
+ return True
+ return False
+
+ @memoize
+ def splitAnisotropic(self, location):
+ # split the anisotropic location into a horizontal and vertical component
+ x = Location()
+ y = Location()
+ for dim, val in location.items():
+ if type(val)==tuple:
+ x[dim] = val[0]
+ y[dim] = val[1]
+ else:
+ x[dim] = y[dim] = val
+ return x, y
+
+ @memoize
+ def _getAxisOrder(self):
+ return [a.name for a in self.doc.axes]
+
+ def generateUFOs(self):
+ # generate an UFO for each of the instance locations
+ glyphCount = 0
+ self.loadFonts()
+ if self.debug:
+ self.logger.info("## generateUFO")
+ for loc, space in splitInterpolable(self.doc):
+ spaceDoc = self.__class__(pathOrObject=space)
+ if self.debug:
+ self.logger.infoItem(f"Generating UFOs for continuous space at discrete location {loc}")
+ v = 0
+ for instanceDescriptor in self.doc.instances:
+ if instanceDescriptor.path is None:
+ continue
+ pairs = None
+ bend = False
+ font = self.makeInstance(instanceDescriptor,
+ processRules,
+ glyphNames=self.glyphNames,
+ pairs=pairs,
+ bend=bend,
+ )
+ if self.debug:
+ self.logger.info(f"\t\t{os.path.basename(instanceDescriptor.path)}")
+ instanceFolder = os.path.dirname(instanceDescriptor.path)
+ if not os.path.exists(instanceFolder):
+ os.makedirs(instanceFolder)
+ font.save(instanceDescriptor.path)
+ glyphCount += len(font)
+ if self.debug:
+ self.logger.info(f"\t\tGenerated {glyphCount} glyphs altogether.")
+
+ generateUFO = generateUFOs
+
+ @memoize
+ def getInfoMutator(self, discreteLocation=None):
+ """ Returns a info mutator for this discrete location """
+ infoItems = []
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.doc.sources
+ for sourceDescriptor in sources:
+ if sourceDescriptor.layerName is not None:
+ continue
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
+ sourceFont = self.fonts[sourceDescriptor.name]
+ if sourceFont is None:
+ continue
+ if hasattr(sourceFont.info, "toMathInfo"):
+ infoItems.append((loc, sourceFont.info.toMathInfo()))
+ else:
+ infoItems.append((loc, self.mathInfoClass(sourceFont.info)))
+ infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.getSerializedAxes(), bias=infoBias)
+ return self._infoMutator
+
+ def collectForegroundLayerNames(self):
+ """Return list of names of the default layers of all the fonts in this system.
+ Include None and foreground. XX Why
+ """
+ names = set([None, 'foreground'])
+ for key, font in self.fonts.items():
+ names.add(getDefaultLayerName(font))
+ return list(names)
+
+ @memoize
+ def getKerningMutator(self, pairs=None, discreteLocation=None):
+ """ Return a kerning mutator, collect the sources, build mathGlyphs.
+ If no pairs are given: calculate the whole table.
+ If pairs are given then query the sources for a value and make a mutator only with those values.
+ """
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.sources
+ kerningItems = []
+ foregroundLayers = self.collectForegroundLayerNames()
+ if pairs is None:
+ for sourceDescriptor in sources:
+ if sourceDescriptor.layerName not in foregroundLayers:
+ continue
+ if not sourceDescriptor.muteKerning:
+ # filter this XX @@
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
+ sourceFont = self.fonts[sourceDescriptor.name]
+ if sourceFont is None: continue
+ # this makes assumptions about the groups of all sources being the same.
+ kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups)))
+ else:
+ self._kerningMutatorPairs = pairs
+ for sourceDescriptor in sources:
+ # XXX check sourceDescriptor layerName, only foreground should contribute
+ if sourceDescriptor.layerName is not None:
+ continue
+ if not os.path.exists(sourceDescriptor.path):
+ continue
+ if not sourceDescriptor.muteKerning:
+ sourceFont = self.fonts[sourceDescriptor.name]
+ if sourceFont is None:
+ continue
+ continuous, discrete = self.splitLocation(sourceDescriptor.location)
+ loc = Location(continuous)
+ # XXX can we get the kern value from the fontparts kerning object?
+ kerningItem = self.mathKerningClass(sourceFont.kerning, sourceFont.groups)
+ if kerningItem is not None:
+ sparseKerning = {}
+ for pair in pairs:
+ v = kerningItem.get(pair)
+ if v is not None:
+ sparseKerning[pair] = v
+ kerningItems.append((loc, self.mathKerningClass(sparseKerning)))
+ kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ bias, thing = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias) #xx
+ bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias)
+ return self._kerningMutator
+
+ @memoize
+ def getGlyphMutator(self, glyphName,
+ decomposeComponents=False,
+ **discreteLocation,
+ ):
+ """make a mutator / varlib object for glyphName, with the sources for the given discrete location"""
+ items, unicodes = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation)
+ new = []
+ for a, b, c in items:
+ if hasattr(b, "toMathGlyph"):
+ # note: calling toMathGlyph ignores the mathGlyphClass preference
+ # maybe the self.mathGlyphClass is not necessary?
+ new.append((a,b.toMathGlyph()))
+ else:
+ new.append((a,self.mathGlyphClass(b)))
+ thing = None
+ thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ try:
+ bias, thing = self.getVariationModel(new, axes=self.getSerializedAxes(), bias=thisBias) #xx
+ except:
+ error = traceback.format_exc()
+ note = f"Error in getGlyphMutator for {glyphName}:\n{error}"
+ if self.debug:
+ self.logger.info(note)
+ return thing, unicodes
+
+ # stats indicate this does not get called very often, so caching may not be useful
+ #@memoize
+ def isLocalDefault(self, location):
+ # return True if location is a local default
+ defaults = {}
+ for aD in self.doc.axes:
+ defaults[aD.name] = aD.default
+ for axisName, value in location.items():
+ if defaults[axisName] != value:
+ return False
+ return True
+
+ # stats indicate this does not get called very often, so caching may not be useful
+ #@memoize
+ def filterThisLocation(self, location, mutedAxes=None):
+ # return location with axes is mutedAxes removed
+ # this means checking if the location is a non-default value
+ if not mutedAxes:
+ return False, location
+ defaults = {}
+ ignoreSource = False
+ for aD in self.doc.axes:
+ defaults[aD.name] = aD.default
+ new = {}
+ new.update(location)
+ for mutedAxisName in mutedAxes:
+ if mutedAxisName not in location:
+ continue
+ if mutedAxisName not in defaults:
+ continue
+ if location[mutedAxisName] != defaults.get(mutedAxisName):
+ ignoreSource = True
+ del new[mutedAxisName]
+ return ignoreSource, new
+
+ @memoize
+ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None):
+ """ Return a glyph mutator
+ 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.
+
+ findSourceDescriptorsForDiscreteLocation returns sources from layers as well
+ """
+ items = []
+ empties = []
+ foundEmpty = False
+ # is bend=True necessary here?
+ defaultLocation = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation)
+ #
+ if discreteLocation is not None:
+ sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation)
+ else:
+ sources = self.doc.sources
+ unicodes = set() # unicodes for this glyph
+ for sourceDescriptor in sources:
+ if not os.path.exists(sourceDescriptor.path):
+ #kthxbai
+ note = "\tMissing UFO at %s" % sourceDescriptor.path
+ if self.debug:
+ self.logger.info(note)
+ continue
+ if glyphName in sourceDescriptor.mutedGlyphNames:
+ self.logger.info(f"\t\tglyphName {glyphName} is muted")
+ continue
+ thisIsDefault = self.isLocalDefault(sourceDescriptor.location)
+ ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames)
+ if ignoreSource:
+ continue
+ f = self.fonts.get(sourceDescriptor.name)
+ if f is None: continue
+ loc = Location(sourceDescriptor.location)
+ sourceLayer = f
+ if not glyphName in f:
+ # log this>
+ continue
+ layerName = getDefaultLayerName(f)
+ sourceGlyphObject = None
+ # handle source layers
+ if sourceDescriptor.layerName is not None:
+ # start looking for a layer
+ # Do not bother for mutatorMath designspaces
+ layerName = sourceDescriptor.layerName
+ sourceLayer = getLayer(f, sourceDescriptor.layerName)
+ if sourceLayer is None:
+ continue
+ if glyphName not in sourceLayer:
+ # start looking for a glyph
+ # this might be a support in a sparse layer
+ # so we're skipping!
+ continue
+ # still have to check if the sourcelayer glyph is empty
+ if not glyphName in sourceLayer:
+ continue
+ else:
+ sourceGlyphObject = sourceLayer[glyphName]
+ if sourceGlyphObject.unicodes is not None:
+ for u in sourceGlyphObject.unicodes:
+ unicodes.add(u)
+ if checkGlyphIsEmpty(sourceGlyphObject, allowWhiteSpace=True):
+ foundEmpty = True
+ #sourceGlyphObject = None
+ #continue
+ if decomposeComponents:
+ # what about decomposing glyphs in a partial font?
+ temp = self.glyphClass()
+ p = temp.getPointPen()
+ dpp = DecomposePointPen(sourceLayer, p)
+ sourceGlyphObject.drawPoints(dpp)
+ temp.width = sourceGlyphObject.width
+ temp.name = sourceGlyphObject.name
+ processThis = temp
+ else:
+ processThis = sourceGlyphObject
+ sourceInfo = dict(source=f.path, glyphName=glyphName,
+ layerName=layerName,
+ location=filteredLocation, # sourceDescriptor.location,
+ sourceName=sourceDescriptor.name,
+ )
+ if hasattr(processThis, "toMathGlyph"):
+ processThis = processThis.toMathGlyph()
+ else:
+ processThis = self.mathGlyphClass(processThis)
+ continuous, discrete = self.splitLocation(loc)
+ items.append((continuous, processThis, sourceInfo))
+ empties.append((thisIsDefault, foundEmpty))
+ # check the empties:
+ # if the default glyph is empty, then all must be empty
+ # if the default glyph is not empty then none can be empty
+ checkedItems = []
+ emptiesAllowed = False
+ # first check if the default is empty.
+ # remember that the sources can be in any order
+ for i, p in enumerate(empties):
+ isDefault, isEmpty = p
+ if isDefault and isEmpty:
+ emptiesAllowed = True
+ # now we know what to look for
+ if not emptiesAllowed:
+ for i, p in enumerate(empties):
+ isDefault, isEmpty = p
+ if not isEmpty:
+ checkedItems.append(items[i])
+ else:
+ for i, p in enumerate(empties):
+ isDefault, isEmpty = p
+ if isEmpty:
+ checkedItems.append(items[i])
+ return checkedItems, unicodes
+
+ collectMastersForGlyph = collectSourcesForGlyph
+
+ def makeInstance(self, instanceDescriptor,
+ doRules=None,
+ glyphNames=None,
+ pairs=None,
+ bend=False):
+ """ Generate a font object for this instance """
+ if doRules is not None:
+ warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2)
+ continuousLocation, discreteLocation = self.splitLocation(instanceDescriptor.location)
+
+ font = self._instantiateFont(None)
+
+ loc = Location(continuousLocation)
+ anisotropic = False
+ locHorizontal = locVertical = loc
+ if self.isAnisotropic(loc):
+ anisotropic = True
+ locHorizontal, locVertical = self.splitAnisotropic(loc)
+ if self.debug:
+ self.logger.info(f"\t\t\tAnisotropic location for {instanceDescriptor.name}\n\t\t\t{instanceDescriptor.location}")
+ if instanceDescriptor.kerning:
+ if pairs:
+ try:
+ kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation)
+ kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
+ kerningObject.extractKerning(font)
+ except:
+ error = traceback.format_exc()
+ note = f"makeInstance: Could not make kerning for {loc}\n{error}"
+ if self.debug:
+ self.logger.info(note)
+ else:
+ kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation)
+ if kerningMutator is not None:
+ kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
+ kerningObject.extractKerning(font)
+ if self.debug:
+ self.logger.info(f"\t\t\t{len(font.kerning)} kerning pairs added")
+
+ # # make the info
+ infoMutator = self.getInfoMutator(discreteLocation=discreteLocation)
+ if infoMutator is not None:
+ if not anisotropic:
+ infoInstanceObject = infoMutator.makeInstance(loc, bend=bend)
+ else:
+ horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend)
+ verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend)
+ # merge them again
+ infoInstanceObject = (1,0)*horizontalInfoInstanceObject + (0,1)*verticalInfoInstanceObject
+ if self.roundGeometry:
+ infoInstanceObject = infoInstanceObject.round()
+ infoInstanceObject.extractInfo(font.info)
+ font.info.familyName = instanceDescriptor.familyName
+ font.info.styleName = instanceDescriptor.styleName
+ font.info.postscriptFontName = instanceDescriptor.postScriptFontName # yikes, note the differences in capitalisation..
+ font.info.styleMapFamilyName = instanceDescriptor.styleMapFamilyName
+ font.info.styleMapStyleName = instanceDescriptor.styleMapStyleName
+
+ for sourceDescriptor in self.doc.sources:
+ if sourceDescriptor.copyInfo:
+ # this is the source
+ if self.fonts[sourceDescriptor.name] is not None:
+ self._copyFontInfo(self.fonts[sourceDescriptor.name].info, font.info)
+ if sourceDescriptor.copyLib:
+ # excplicitly copy the font.lib items
+ if self.fonts[sourceDescriptor.name] is not None:
+ for key, value in self.fonts[sourceDescriptor.name].lib.items():
+ font.lib[key] = value
+ if sourceDescriptor.copyGroups:
+ if self.fonts[sourceDescriptor.name] is not None:
+ for key, value in self.fonts[sourceDescriptor.name].groups.items():
+ font.groups[key] = value
+ if sourceDescriptor.copyFeatures:
+ if self.fonts[sourceDescriptor.name] is not None:
+ featuresText = self.fonts[sourceDescriptor.name].features.text
+ font.features.text = featuresText
+
+ # ok maybe now it is time to calculate some glyphs
+ # glyphs
+ if glyphNames:
+ selectedGlyphNames = glyphNames
+ else:
+ selectedGlyphNames = self.glyphNames
+ if not 'public.glyphOrder' in font.lib.keys():
+ # should be the glyphorder from the default, yes?
+ font.lib['public.glyphOrder'] = selectedGlyphNames
+
+ for glyphName in selectedGlyphNames:
+ # can we take all this into a separate method for making a preview glyph object?
+ glyphMutator, unicodes = self.getGlyphMutator(glyphName, discreteLocation=discreteLocation)
+ if glyphMutator is None:
+ if self.debug:
+ note = f"makeInstance: Could not make mutator for glyph {glyphName}"
+ self.logger.info(note)
+ continue
+
+ font.newGlyph(glyphName)
+ font[glyphName].clear()
+ glyphInstanceUnicodes = []
+ #neutralFont = self.getNeutralFont()
+ font[glyphName].unicodes = unicodes
+
+ try:
+ if not self.isAnisotropic(continuousLocation):
+ glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend)
+ else:
+ # split anisotropic location into horizontal and vertical components
+ horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend)
+ verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend)
+ # merge them again in a beautiful single line:
+ glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject
+ except IndexError:
+ # alignment problem with the data?
+ if self.debug:
+ note = "makeInstance: Quite possibly some sort of data alignment error in %s" % glyphName
+ self.logger.info(note)
+ continue
+ if self.roundGeometry:
+ try:
+ glyphInstanceObject = glyphInstanceObject.round()
+ except AttributeError:
+ # what are we catching here?
+ # math objects without a round method?
+ if self.debug:
+ note = f"makeInstance: no round method for {glyphInstanceObject} ?"
+ self.logger.info(note)
+ try:
+ # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance
+ # glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True)
+ # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontMath/mathGlyph.py", line 315, in extractGlyph
+ # glyph.anchors = [dict(anchor) for anchor in self.anchors]
+ # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontParts/base/base.py", line 103, in __set__
+ # raise FontPartsError("no setter for %r" % self.name)
+ # fontParts.base.errors.FontPartsError: no setter for 'anchors'
+ if hasattr(font[glyphName], "fromMathGlyph"):
+ font[glyphName].fromMathGlyph(glyphInstanceObject)
+ else:
+ 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
+
+ if self.debug:
+ self.logger.info(f"\t\t\t{len(selectedGlyphNames)} glyphs added")
+ return font
+
+ # cache? could cause a lot of material in memory that we don't really need. Test this!
+
+ @memoize
+ def makeOneGlyph(self, glyphName, location, bend=False, decomposeComponents=True, useVarlib=False, roundGeometry=False):
+ # make me one glyph with everything
+ # Unlike makeInstance(), this is focussed on a single glyph, for previewing,
+ # and the location will be the driving factor
+ continuousLocation, discreteLocation = self.splitLocation(location)
+ # check if the discreteLocation is within limits
+ if not self.checkDiscreteAxisValues(discreteLocation):
+ if self.debug:
+ self.logger.info(f"\t\tmakeOneGlyph reports: {location} has illegal value for discrete location")
+ return None
+ previousModel = self.useVarlib
+ self.useVarlib = useVarlib
+ glyphMutator, unicodes = self.getGlyphMutator(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation)
+ if not glyphMutator: return None
+ try:
+ if not self.isAnisotropic(location):
+ glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend)
+ else:
+ anisotropic = True
+ if self.debug:
+ self.logger.info(f"\t\tmakeOneGlyph anisotropic location: {location}")
+ loc = Location(continuousLocation)
+ locHorizontal, locVertical = self.splitAnisotropic(loc)
+ # split anisotropic location into horizontal and vertical components
+ horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend)
+ verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend)
+ # merge them again
+ glyphInstanceObject = (1,0)*horizontalGlyphInstanceObject + (0,1)*verticalGlyphInstanceObject
+ if self.debug:
+ self.logger.info(f"makeOneGlyph anisotropic glyphInstanceObject {glyphInstanceObject}")
+ except IndexError:
+ # alignment problem with the data?
+ if self.debug:
+ note = "makeOneGlyph: Quite possibly some sort of data alignment error in %s" % glyphName
+ self.logger.info(note)
+ return None
+ glyphInstanceObject.unicodes = unicodes
+ if roundGeometry:
+ glyphInstanceObject.round()
+ self.useVarlib = previousModel
+ return glyphInstanceObject
+
+ 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__":
+ import time, random
+ from fontParts.world import RFont
+ ds5Path = "../../Tests/ds5/ds5.designspace"
+ dumpCacheLog = True
+ makeUFOs = True
+ import os
+ if os.path.exists(ds5Path):
+ startTime = time.time()
+ doc = UFOOperator(ds5Path, useVarlib=True, debug=True)
+ doc.loadFonts()
+ print("collectForegroundLayerNames", doc.collectForegroundLayerNames())
+ if makeUFOs:
+ doc.generateUFOs()
+ #doc.updateFonts([f])
+ #doc._logLoadedFonts()
+ loc = doc.newDefaultLocation()
+ res = doc.makeOneGlyph("glyphOne", location=loc)
+
+ if dumpCacheLog:
+ doc.logger.info(f"Test: cached {len(_memoizeCache)} items")
+ for key, item in _memoizeCache.items():
+ doc.logger.info(f"\t\t{key} {item}")
+ endTime = time.time()
+ duration = endTime - startTime
+ print(f"duration: {duration}" )
+
+ inspectMemoizeCache()
+ for key, value in _memoizeStats.items():
+ print(key[0], value)
\ No newline at end of file
diff --git a/Lib/ufoProcessor/ufoProcessorSketch.py b/Lib/ufoProcessor/ufoProcessorSketch.py
new file mode 100644
index 0000000..29d1c33
--- /dev/null
+++ b/Lib/ufoProcessor/ufoProcessorSketch.py
@@ -0,0 +1,94 @@
+## caching
+import functools
+
+
+_memoizeCache = dict()
+
+
+def memoize(function):
+ @functools.wraps(function)
+ def wrapper(self, *args, **kwargs):
+ key = (function.__name__, self, args, tuple((key, kwargs[key]) for key in sorted(kwargs.keys())))
+ if key in _memoizeCache:
+ return _memoizeCache[key]
+ else:
+ result = function(self, *args, **kwargs)
+ _memoizeCache[key] = result
+ return result
+ return wrapper
+
+
+#####
+
+from fontTools.designspaceLib import DesignSpaceDocument
+
+
+class DesignSpaceProcessor(DesignSpaceDocument):
+
+ @memoize
+ def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation):
+ glyphs = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation)
+
+ print("build for glyphName", glyphName, discreteLocation)
+ return "a mutator"
+
+ @memoize
+ def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, **discreteLocation):
+ discreteLocation = self.buildDiscreteLocation(discreteLocation)
+ sources = self.findSourceDescriptorsForDiscreteLocation(**discreteLocation)
+ return []
+
+ @memoize
+ def findSourceDescriptorsForDiscreteLocation(self, **discreteLocation):
+ discreteLocation = self.buildDiscreteLocation(discreteLocation)
+ sources = []
+ for source in self.sources:
+ # check if part of a dict is inside an other dict
+ if discreteLocation.items() <= source.designLocation.items():
+ sources.append(source)
+ return sources
+
+ def buildDiscreteLocation(self, partialDiscretelocation):
+ return {**self.getDiscreteDefaultLocation(), **partialDiscretelocation}
+
+ @property
+ def discreteAxes(self):
+ return [axis for axis in self.axes if hasattr(axis, "values")]
+
+ def getDiscreteDefaultLocation(self):
+ discreteDefault = dict()
+ for axis in self.discreteAxes:
+ discreteDefault[axis.name] = axis.default
+ return discreteDefault
+
+ def getDiscreteLocations(self):
+ for axis in self.discreteAxes:
+ print(axis)
+
+ # chaching tools
+
+ def changed(self):
+ _memoizeCache.clear()
+
+ def glyphChanged(self, glyphName):
+ for key in list(_memoizeCache.keys()):
+ if key[0] in ("getGlyphMutator", "collectSourcesForGlyph") and key[1] == glyphName:
+ del _memoizeCache[key]
+
+
+
+d = DesignSpaceProcessor()
+ds5Path = "../../Tests/202206 discrete spaces/test.ds5.designspace"
+d.read(ds5Path)
+r = d.getGlyphMutator("a", italic=0)
+print(r)
+
+r = d.getGlyphMutator("a", italic=0)
+print(r)
+r = d.getGlyphMutator("a", italic=1)
+print(r)
+
+print(d.getDiscreteDefaultLocation())
+
+print(d.findSourceDescriptorsForDiscreteLocation(countedItems=1))
+print(d.getDiscreteLocations())
\ No newline at end of file
diff --git a/Lib/ufoProcessor/varModels.py b/Lib/ufoProcessor/varModels.py
index 9deef48..565b463 100644
--- a/Lib/ufoProcessor/varModels.py
+++ b/Lib/ufoProcessor/varModels.py
@@ -57,27 +57,35 @@ class VariationModelMutator(object):
but uses the fonttools varlib logic to calculate.
"""
- def __init__(self, items, axes, model=None):
+ def __init__(self, items, axes, model=None, extrapolate=True):
# items: list of locationdict, value tuples
# axes: list of axis dictionaried, not axisdescriptor objects.
# model: a model, if we want to share one
+ self.extrapolate = extrapolate
self.axisOrder = [a.name for a in axes]
self.axisMapper = AxisMapper(axes)
self.axes = {}
for a in axes:
- mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(a.minimum), a.map_forward(a.default), a.map_forward(a.maximum)
- #self.axes[a.name] = (a.minimum, a.default, a.maximum)
+ axisMinimum, axisMaximum = self.getAxisMinMax(a)
+ mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(axisMinimum), a.map_forward(a.default), a.map_forward(axisMaximum)
self.axes[a.name] = (mappedMinimum, mappedDefault, mappedMaximum)
if model is None:
dd = [self._normalize(a) for a,b in items]
ee = self.axisOrder
- self.model = VariationModel(dd, axisOrder=ee)
+ self.model = VariationModel(dd, axisOrder=ee, extrapolate=self.extrapolate)
else:
self.model = model
self.masters = [b for a, b in items]
self.locations = [a for a, b in items]
+ def getAxisMinMax(self, axis):
+ # return tha axis.minimum and axis.maximum for continuous axes
+ # return the min(axis.values), max(axis.values) for discrete axes
+ if hasattr(axis, "values"):
+ return min(axis.values), max(axis.values)
+ return axis.minimum, axis.maximum
+
def get(self, key):
if key in self.model.locations:
i = self.model.locations.index(key)
@@ -98,18 +106,13 @@ def getReach(self):
items = []
for supportIndex, s in enumerate(self.getSupports()):
sortedOrder = self.model.reverseMapping[supportIndex]
- #print("getReach", self.masters[sortedOrder], s)
- #print("getReach", self.locations[sortedOrder])
items.append((self.masters[sortedOrder], s))
return items
-
def makeInstance(self, location, bend=False):
# check for anisotropic locations here
- #print("\t1", location)
if bend:
location = self.axisMapper(location)
- #print("\t2", location)
nl = self._normalize(location)
return self.model.interpolateFromMasters(nl, self.masters)
@@ -206,7 +209,7 @@ def _normalize(self, location):
assert mm.makeInstance(dict(Weight=0, Width=10)) == 13
- l = dict(Weight=400, Width=200)
+ l = dict(Weight=400, Width=20)
lmapped = aam(l)
print('0 loc', l)
print('0 loc mapped', lmapped)
diff --git a/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist
deleted file mode 100644
index 09549d9..0000000
--- a/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- ascender
- 750
- capHeight
- 500
- descender
- -250
- familyName
- BenderTest
- guidelines
-
- postscriptBlueValues
-
- postscriptFamilyBlues
-
- postscriptFamilyOtherBlues
-
- postscriptOtherBlues
-
- postscriptStemSnapH
-
- postscriptStemSnapV
-
- styleName
- One
- unitsPerEm
- 1000
- xHeight
- 500
-
-
diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif
deleted file mode 100644
index 20407c7..0000000
--- a/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/20190830 benders/benderTest1.ufo/lib.plist b/Tests/20190830 benders/benderTest1.ufo/lib.plist
deleted file mode 100644
index d4b67bb..0000000
--- a/Tests/20190830 benders/benderTest1.ufo/lib.plist
+++ /dev/null
@@ -1,236 +0,0 @@
-
-
-
-
- com.defcon.sortDescriptor
-
-
- ascending
- Latin-1
- type
- characterSet
-
-
- com.typemytype.robofont.compileSettings.autohint
-
- com.typemytype.robofont.compileSettings.checkOutlines
-
- com.typemytype.robofont.compileSettings.createDummyDSIG
-
- com.typemytype.robofont.compileSettings.decompose
-
- com.typemytype.robofont.compileSettings.generateFormat
- 0
- com.typemytype.robofont.compileSettings.releaseMode
-
- com.typemytype.robofont.italicSlantOffset
- 0
- com.typemytype.robofont.segmentType
- curve
- com.typemytype.robofont.shouldAddPointsInSplineConversion
- 1
- public.glyphOrder
-
- space
- exclam
- quotedbl
- numbersign
- dollar
- percent
- ampersand
- parenleft
- parenright
- asterisk
- plus
- comma
- hyphen
- period
- slash
- zero
- one
- two
- three
- four
- five
- six
- seven
- eight
- nine
- colon
- semicolon
- less
- equal
- greater
- question
- at
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
- M
- N
- O
- P
- Q
- R
- S
- T
- U
- V
- W
- X
- Y
- Z
- bracketleft
- backslash
- bracketright
- asciicircum
- underscore
- grave
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
- u
- v
- w
- x
- y
- z
- braceleft
- bar
- braceright
- asciitilde
- exclamdown
- cent
- sterling
- currency
- yen
- brokenbar
- section
- dieresis
- copyright
- ordfeminine
- guillemotleft
- logicalnot
- registered
- macron
- degree
- plusminus
- twosuperior
- threesuperior
- acute
- mu
- paragraph
- periodcentered
- cedilla
- onesuperior
- ordmasculine
- guillemotright
- onequarter
- onehalf
- threequarters
- questiondown
- Agrave
- Aacute
- Acircumflex
- Atilde
- Adieresis
- Aring
- AE
- Ccedilla
- Egrave
- Eacute
- Ecircumflex
- Edieresis
- Igrave
- Iacute
- Icircumflex
- Idieresis
- Eth
- Ntilde
- Ograve
- Oacute
- Ocircumflex
- Otilde
- Odieresis
- multiply
- Oslash
- Ugrave
- Uacute
- Ucircumflex
- Udieresis
- Yacute
- Thorn
- germandbls
- agrave
- aacute
- acircumflex
- atilde
- adieresis
- aring
- ae
- ccedilla
- egrave
- eacute
- ecircumflex
- edieresis
- igrave
- iacute
- icircumflex
- idieresis
- eth
- ntilde
- ograve
- oacute
- ocircumflex
- otilde
- odieresis
- divide
- oslash
- ugrave
- uacute
- ucircumflex
- udieresis
- yacute
- thorn
- ydieresis
- dotlessi
- circumflex
- caron
- breve
- dotaccent
- ring
- ogonek
- tilde
- hungarumlaut
- quoteleft
- quoteright
- minus
-
-
-
diff --git a/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist
deleted file mode 100644
index 21747a9..0000000
--- a/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- ascender
- 750
- capHeight
- 601
- descender
- -250
- familyName
- BenderTest
- guidelines
-
- postscriptBlueValues
-
- postscriptFamilyBlues
-
- postscriptFamilyOtherBlues
-
- postscriptOtherBlues
-
- postscriptStemSnapH
-
- postscriptStemSnapV
-
- styleName
- Two
- unitsPerEm
- 1000
- xHeight
- 500
-
-
diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist b/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist
deleted file mode 100644
index 1e96c94..0000000
--- a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif
deleted file mode 100644
index d0bbac1..0000000
--- a/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/20190830 benders/benderTest2.ufo/kerning.plist b/Tests/20190830 benders/benderTest2.ufo/kerning.plist
deleted file mode 100644
index c3bce48..0000000
--- a/Tests/20190830 benders/benderTest2.ufo/kerning.plist
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- a
-
- a
- -200
-
-
-
diff --git a/Tests/20190830 benders/benderTest2.ufo/lib.plist b/Tests/20190830 benders/benderTest2.ufo/lib.plist
deleted file mode 100644
index d4b67bb..0000000
--- a/Tests/20190830 benders/benderTest2.ufo/lib.plist
+++ /dev/null
@@ -1,236 +0,0 @@
-
-
-
-
- com.defcon.sortDescriptor
-
-
- ascending
- Latin-1
- type
- characterSet
-
-
- com.typemytype.robofont.compileSettings.autohint
-
- com.typemytype.robofont.compileSettings.checkOutlines
-
- com.typemytype.robofont.compileSettings.createDummyDSIG
-
- com.typemytype.robofont.compileSettings.decompose
-
- com.typemytype.robofont.compileSettings.generateFormat
- 0
- com.typemytype.robofont.compileSettings.releaseMode
-
- com.typemytype.robofont.italicSlantOffset
- 0
- com.typemytype.robofont.segmentType
- curve
- com.typemytype.robofont.shouldAddPointsInSplineConversion
- 1
- public.glyphOrder
-
- space
- exclam
- quotedbl
- numbersign
- dollar
- percent
- ampersand
- parenleft
- parenright
- asterisk
- plus
- comma
- hyphen
- period
- slash
- zero
- one
- two
- three
- four
- five
- six
- seven
- eight
- nine
- colon
- semicolon
- less
- equal
- greater
- question
- at
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
- M
- N
- O
- P
- Q
- R
- S
- T
- U
- V
- W
- X
- Y
- Z
- bracketleft
- backslash
- bracketright
- asciicircum
- underscore
- grave
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
- u
- v
- w
- x
- y
- z
- braceleft
- bar
- braceright
- asciitilde
- exclamdown
- cent
- sterling
- currency
- yen
- brokenbar
- section
- dieresis
- copyright
- ordfeminine
- guillemotleft
- logicalnot
- registered
- macron
- degree
- plusminus
- twosuperior
- threesuperior
- acute
- mu
- paragraph
- periodcentered
- cedilla
- onesuperior
- ordmasculine
- guillemotright
- onequarter
- onehalf
- threequarters
- questiondown
- Agrave
- Aacute
- Acircumflex
- Atilde
- Adieresis
- Aring
- AE
- Ccedilla
- Egrave
- Eacute
- Ecircumflex
- Edieresis
- Igrave
- Iacute
- Icircumflex
- Idieresis
- Eth
- Ntilde
- Ograve
- Oacute
- Ocircumflex
- Otilde
- Odieresis
- multiply
- Oslash
- Ugrave
- Uacute
- Ucircumflex
- Udieresis
- Yacute
- Thorn
- germandbls
- agrave
- aacute
- acircumflex
- atilde
- adieresis
- aring
- ae
- ccedilla
- egrave
- eacute
- ecircumflex
- edieresis
- igrave
- iacute
- icircumflex
- idieresis
- eth
- ntilde
- ograve
- oacute
- ocircumflex
- otilde
- odieresis
- divide
- oslash
- ugrave
- uacute
- ucircumflex
- udieresis
- yacute
- thorn
- ydieresis
- dotlessi
- circumflex
- caron
- breve
- dotaccent
- ring
- ogonek
- tilde
- hungarumlaut
- quoteleft
- quoteright
- minus
-
-
-
diff --git a/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist b/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist
deleted file mode 100644
index 28c3286..0000000
--- a/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- ascender
- 750
- capHeight
- 700
- descender
- -250
- familyName
- BenderTest
- guidelines
-
- postscriptBlueValues
-
- postscriptFamilyBlues
-
- postscriptFamilyOtherBlues
-
- postscriptOtherBlues
-
- postscriptStemSnapH
-
- postscriptStemSnapV
-
- styleName
- Three
- unitsPerEm
- 1000
- xHeight
- 500
-
-
diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist b/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist
deleted file mode 100644
index 1e96c94..0000000
--- a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif b/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif
deleted file mode 100644
index 4720db2..0000000
--- a/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/20190830 benders/benderTest3.ufo/kerning.plist b/Tests/20190830 benders/benderTest3.ufo/kerning.plist
deleted file mode 100644
index 23382cc..0000000
--- a/Tests/20190830 benders/benderTest3.ufo/kerning.plist
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- a
-
- a
- -100
-
-
-
diff --git a/Tests/20190830 benders/benderTest3.ufo/lib.plist b/Tests/20190830 benders/benderTest3.ufo/lib.plist
deleted file mode 100644
index d4b67bb..0000000
--- a/Tests/20190830 benders/benderTest3.ufo/lib.plist
+++ /dev/null
@@ -1,236 +0,0 @@
-
-
-
-
- com.defcon.sortDescriptor
-
-
- ascending
- Latin-1
- type
- characterSet
-
-
- com.typemytype.robofont.compileSettings.autohint
-
- com.typemytype.robofont.compileSettings.checkOutlines
-
- com.typemytype.robofont.compileSettings.createDummyDSIG
-
- com.typemytype.robofont.compileSettings.decompose
-
- com.typemytype.robofont.compileSettings.generateFormat
- 0
- com.typemytype.robofont.compileSettings.releaseMode
-
- com.typemytype.robofont.italicSlantOffset
- 0
- com.typemytype.robofont.segmentType
- curve
- com.typemytype.robofont.shouldAddPointsInSplineConversion
- 1
- public.glyphOrder
-
- space
- exclam
- quotedbl
- numbersign
- dollar
- percent
- ampersand
- parenleft
- parenright
- asterisk
- plus
- comma
- hyphen
- period
- slash
- zero
- one
- two
- three
- four
- five
- six
- seven
- eight
- nine
- colon
- semicolon
- less
- equal
- greater
- question
- at
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
- M
- N
- O
- P
- Q
- R
- S
- T
- U
- V
- W
- X
- Y
- Z
- bracketleft
- backslash
- bracketright
- asciicircum
- underscore
- grave
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
- u
- v
- w
- x
- y
- z
- braceleft
- bar
- braceright
- asciitilde
- exclamdown
- cent
- sterling
- currency
- yen
- brokenbar
- section
- dieresis
- copyright
- ordfeminine
- guillemotleft
- logicalnot
- registered
- macron
- degree
- plusminus
- twosuperior
- threesuperior
- acute
- mu
- paragraph
- periodcentered
- cedilla
- onesuperior
- ordmasculine
- guillemotright
- onequarter
- onehalf
- threequarters
- questiondown
- Agrave
- Aacute
- Acircumflex
- Atilde
- Adieresis
- Aring
- AE
- Ccedilla
- Egrave
- Eacute
- Ecircumflex
- Edieresis
- Igrave
- Iacute
- Icircumflex
- Idieresis
- Eth
- Ntilde
- Ograve
- Oacute
- Ocircumflex
- Otilde
- Odieresis
- multiply
- Oslash
- Ugrave
- Uacute
- Ucircumflex
- Udieresis
- Yacute
- Thorn
- germandbls
- agrave
- aacute
- acircumflex
- atilde
- adieresis
- aring
- ae
- ccedilla
- egrave
- eacute
- ecircumflex
- edieresis
- igrave
- iacute
- icircumflex
- idieresis
- eth
- ntilde
- ograve
- oacute
- ocircumflex
- otilde
- odieresis
- divide
- oslash
- ugrave
- uacute
- ucircumflex
- udieresis
- yacute
- thorn
- ydieresis
- dotlessi
- circumflex
- caron
- breve
- dotaccent
- ring
- ogonek
- tilde
- hungarumlaut
- quoteleft
- quoteright
- minus
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist
deleted file mode 100644
index de783e4..0000000
--- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- ascender
- 750
- capHeight
- 997
- descender
- -250
- familyName
- BenderTest
- guidelines
-
- postscriptBlueValues
-
- postscriptFamilyBlues
-
- postscriptFamilyOtherBlues
-
- postscriptOtherBlues
-
- postscriptStemSnapH
-
- postscriptStemSnapV
-
- styleName
- FarOut
- unitsPerEm
- 1000.0
- xHeight
- 500
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif
deleted file mode 100644
index 418b368..0000000
--- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist
deleted file mode 100644
index 08fe439..0000000
--- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- a
-
- a
- 200
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist b/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist
deleted file mode 100644
index b2efd11..0000000
--- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist
+++ /dev/null
@@ -1,243 +0,0 @@
-
-
-
-
- com.defcon.sortDescriptor
-
-
- ascending
- Latin-1
- type
- characterSet
-
-
- com.typemytype.robofont.compileSettings.autohint
-
- com.typemytype.robofont.compileSettings.checkOutlines
-
- com.typemytype.robofont.compileSettings.createDummyDSIG
-
- com.typemytype.robofont.compileSettings.decompose
-
- com.typemytype.robofont.compileSettings.generateFormat
- 0
- com.typemytype.robofont.compileSettings.releaseMode
-
- com.typemytype.robofont.italicSlantOffset
- 0
- com.typemytype.robofont.segmentType
- curve
- com.typemytype.robofont.shouldAddPointsInSplineConversion
- 1
- designspace.location
-
-
- test
- 2500.0
-
-
- public.glyphOrder
-
- space
- exclam
- quotedbl
- numbersign
- dollar
- percent
- ampersand
- parenleft
- parenright
- asterisk
- plus
- comma
- hyphen
- period
- slash
- zero
- one
- two
- three
- four
- five
- six
- seven
- eight
- nine
- colon
- semicolon
- less
- equal
- greater
- question
- at
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
- M
- N
- O
- P
- Q
- R
- S
- T
- U
- V
- W
- X
- Y
- Z
- bracketleft
- backslash
- bracketright
- asciicircum
- underscore
- grave
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
- u
- v
- w
- x
- y
- z
- braceleft
- bar
- braceright
- asciitilde
- exclamdown
- cent
- sterling
- currency
- yen
- brokenbar
- section
- dieresis
- copyright
- ordfeminine
- guillemotleft
- logicalnot
- registered
- macron
- degree
- plusminus
- twosuperior
- threesuperior
- acute
- mu
- paragraph
- periodcentered
- cedilla
- onesuperior
- ordmasculine
- guillemotright
- onequarter
- onehalf
- threequarters
- questiondown
- Agrave
- Aacute
- Acircumflex
- Atilde
- Adieresis
- Aring
- AE
- Ccedilla
- Egrave
- Eacute
- Ecircumflex
- Edieresis
- Igrave
- Iacute
- Icircumflex
- Idieresis
- Eth
- Ntilde
- Ograve
- Oacute
- Ocircumflex
- Otilde
- Odieresis
- multiply
- Oslash
- Ugrave
- Uacute
- Ucircumflex
- Udieresis
- Yacute
- Thorn
- germandbls
- agrave
- aacute
- acircumflex
- atilde
- adieresis
- aring
- ae
- ccedilla
- egrave
- eacute
- ecircumflex
- edieresis
- igrave
- iacute
- icircumflex
- idieresis
- eth
- ntilde
- ograve
- oacute
- ocircumflex
- otilde
- odieresis
- divide
- oslash
- ugrave
- uacute
- ucircumflex
- udieresis
- yacute
- thorn
- ydieresis
- dotlessi
- circumflex
- caron
- breve
- dotaccent
- ring
- ogonek
- tilde
- hungarumlaut
- quoteleft
- quoteright
- minus
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist
deleted file mode 100644
index 137f807..0000000
--- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
- ascender
- 750
- capHeight
- 601
- descender
- -250
- familyName
- BenderTest
- guidelines
-
- postscriptBlueValues
-
- postscriptFamilyBlues
-
- postscriptFamilyOtherBlues
-
- postscriptOtherBlues
-
- postscriptStemSnapH
-
- postscriptStemSnapV
-
- styleName
- Intermediate
- unitsPerEm
- 1000
- xHeight
- 500
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif
deleted file mode 100644
index 376c59d..0000000
--- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist
deleted file mode 100644
index 63a44ac..0000000
--- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/contents.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- a
- a.glif
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist
deleted file mode 100644
index c3bce48..0000000
--- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
- a
-
- a
- -200
-
-
-
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist b/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist
deleted file mode 100644
index acfaec2..0000000
--- a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist
+++ /dev/null
@@ -1,243 +0,0 @@
-
-
-
-
- com.defcon.sortDescriptor
-
-
- ascending
- Latin-1
- type
- characterSet
-
-
- com.typemytype.robofont.compileSettings.autohint
-
- com.typemytype.robofont.compileSettings.checkOutlines
-
- com.typemytype.robofont.compileSettings.createDummyDSIG
-
- com.typemytype.robofont.compileSettings.decompose
-
- com.typemytype.robofont.compileSettings.generateFormat
- 0
- com.typemytype.robofont.compileSettings.releaseMode
-
- com.typemytype.robofont.italicSlantOffset
- 0
- com.typemytype.robofont.segmentType
- curve
- com.typemytype.robofont.shouldAddPointsInSplineConversion
- 1
- designspace.location
-
-
- test
- 500.0
-
-
- public.glyphOrder
-
- space
- exclam
- quotedbl
- numbersign
- dollar
- percent
- ampersand
- parenleft
- parenright
- asterisk
- plus
- comma
- hyphen
- period
- slash
- zero
- one
- two
- three
- four
- five
- six
- seven
- eight
- nine
- colon
- semicolon
- less
- equal
- greater
- question
- at
- A
- B
- C
- D
- E
- F
- G
- H
- I
- J
- K
- L
- M
- N
- O
- P
- Q
- R
- S
- T
- U
- V
- W
- X
- Y
- Z
- bracketleft
- backslash
- bracketright
- asciicircum
- underscore
- grave
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
- u
- v
- w
- x
- y
- z
- braceleft
- bar
- braceright
- asciitilde
- exclamdown
- cent
- sterling
- currency
- yen
- brokenbar
- section
- dieresis
- copyright
- ordfeminine
- guillemotleft
- logicalnot
- registered
- macron
- degree
- plusminus
- twosuperior
- threesuperior
- acute
- mu
- paragraph
- periodcentered
- cedilla
- onesuperior
- ordmasculine
- guillemotright
- onequarter
- onehalf
- threequarters
- questiondown
- Agrave
- Aacute
- Acircumflex
- Atilde
- Adieresis
- Aring
- AE
- Ccedilla
- Egrave
- Eacute
- Ecircumflex
- Edieresis
- Igrave
- Iacute
- Icircumflex
- Idieresis
- Eth
- Ntilde
- Ograve
- Oacute
- Ocircumflex
- Otilde
- Odieresis
- multiply
- Oslash
- Ugrave
- Uacute
- Ucircumflex
- Udieresis
- Yacute
- Thorn
- germandbls
- agrave
- aacute
- acircumflex
- atilde
- adieresis
- aring
- ae
- ccedilla
- egrave
- eacute
- ecircumflex
- edieresis
- igrave
- iacute
- icircumflex
- idieresis
- eth
- ntilde
- ograve
- oacute
- ocircumflex
- otilde
- odieresis
- divide
- oslash
- ugrave
- uacute
- ucircumflex
- udieresis
- yacute
- thorn
- ydieresis
- dotlessi
- circumflex
- caron
- breve
- dotaccent
- ring
- ogonek
- tilde
- hungarumlaut
- quoteleft
- quoteright
- minus
-
-
-
diff --git a/Tests/20190830 benders/test.py b/Tests/20190830 benders/test.py
deleted file mode 100644
index e5e46db..0000000
--- a/Tests/20190830 benders/test.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""
-
-
- test with these 3 masters
- on 1 axis that has a map that maps to a different range
-
- axis values are in user coordinates
- designpsace problems should check with the proper mapped values
- masters and instancees are in designspace coordinates
-
- goals:
- * the designspace should validate
- * the generated intermediate should have touching shapes, just like master 2
- * determine if we can get rid of the bend=True/False flags
-
- Suppose the numbers in an axis map are messed up, it's then impossible
- to find the default.
-"""
-
-import importlib
-import ufoProcessor
-importlib.reload(ufoProcessor)
-
-
-import mutatorMath
-print(mutatorMath.__file__)
-import mutatorMath.objects.mutator
-importlib.reload(mutatorMath.objects.mutator)
-from mutatorMath.objects.mutator import Location
-from designspaceProblems import DesignSpaceChecker
-import collections
-from ufoProcessor import DesignSpaceProcessor
-from pprint import pprint
-
-path = "Test.designspace"
-
-dp = DesignSpaceProcessor()
-dp.read(path)
-dp.loadFonts()
-
-dsc = DesignSpaceChecker(dp)
-dsc.checkEverything()
-pprint(dsc.problems)
-print('hasStructuralProblems', dsc.hasStructuralProblems())
-
-
-print(dp.newDefaultLocation())
-print(dp.instances)
-print('findDefault', dp.findDefault())
-dp.useVarlib = False
-print('varlib', dp.useVarlib)
-
-axisMapper = ufoProcessor.varModels.AxisMapper(dp.axes)
-print('axisMapper', axisMapper.getMappedAxisValues())
-r = axisMapper(Location(test=1))
-
-default = dp.getNeutralFont()
-print('default.path', default.path)
-dp.generateUFO()
-
-glyphName = "a"
-print('mutator for a', dp.getGlyphMutator(glyphName))
-print('-'*40)
-print('problems')
-for p in dp.problems:
- print(p)
-print('-'*40)
-print('toollog')
-for line in dp.toolLog:
- print("\t" + line)
-
-
-instancePath = "instances/BenderTest-Intermediate.ufo"
-instance = RFont(instancePath, showUI=False)
-print(instance.info.capHeight)
-print(instance.kerning.items())
-
-from mutatorMath.objects.mutator import Location
-l = Location(test=0)
-print(l.isOrigin())
\ No newline at end of file
diff --git a/Tests/ds5/ds5.designspace b/Tests/ds5/ds5.designspace
new file mode 100644
index 0000000..d986b71
--- /dev/null
+++ b/Tests/ds5/ds5.designspace
@@ -0,0 +1,411 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/ds5_addKerningToTheseMasters.py b/Tests/ds5/ds5_addKerningToTheseMasters.py
new file mode 100644
index 0000000..a9cc9b0
--- /dev/null
+++ b/Tests/ds5/ds5_addKerningToTheseMasters.py
@@ -0,0 +1,15 @@
+
+p1 = ('glyphOne', 'glyphTwo')
+p2 = ('glyphTwo', 'glyphOne')
+
+g = "glyphOne"
+
+for f in AllFonts():
+ print(f.path, f.kerning.items())
+ f.kerning[p1] = f[g].width
+ f.kerning[p2] = -f[g].width
+ f.kerning[('glyphTwo', 'glyphTwo')] = -400
+ f.kerning[('glyphOne', 'glyphOne')] = 400
+ f.save()
+ f.close()
+
diff --git a/Tests/ds5/ds5_extrapolateTest.py b/Tests/ds5/ds5_extrapolateTest.py
new file mode 100644
index 0000000..921f36c
--- /dev/null
+++ b/Tests/ds5/ds5_extrapolateTest.py
@@ -0,0 +1,29 @@
+# test the extrapolation in VariationModel
+
+from fontTools.varLib.models import VariationModel
+
+locations = [
+ dict(wgth=0),
+ dict(wght=1000),
+]
+
+values = [10, 20]
+
+m = VariationModel(locations, extrapolate=True)
+
+# interpolating
+assert m.interpolateFromMasters(dict(wght=0), values) == 10
+assert m.interpolateFromMasters(dict(wght=500), values) == 15
+assert m.interpolateFromMasters(dict(wght=1000), values) == 20
+
+# extrapolate over max
+assert m.interpolateFromMasters(dict(wght=1500), values) == 25
+assert m.interpolateFromMasters(dict(wght=2000), values) == 30
+
+# extrapolation over min gets stuck
+print(m.interpolateFromMasters(dict(wght=-500), values), m.interpolateFromMasters(dict(wght=-1000), values))
+
+# would expect:
+assert m.interpolateFromMasters(dict(wght=-500), values) == -5
+assert m.interpolateFromMasters(dict(wght=-1000), values) == -10
+
diff --git a/Tests/ds5/ds5_makeTestDoc.py b/Tests/ds5/ds5_makeTestDoc.py
new file mode 100644
index 0000000..c4d9963
--- /dev/null
+++ b/Tests/ds5/ds5_makeTestDoc.py
@@ -0,0 +1,163 @@
+# make a test designspace format 5 with 1 continuous and 2 discrete axes.
+
+# axis width is a normal interpolation with a change in width
+# axis DSC1 is a discrete axis showing 1, 2, 3 items in the glyph
+# axis DSC2 is a discrete axis showing a solid or outlined shape
+
+from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor
+from fontTools.designspaceLib.split import splitInterpolable
+
+import os
+import fontTools
+print(fontTools.version)
+
+import ufoProcessor
+print(ufoProcessor.__file__)
+
+doc = DesignSpaceDocument()
+
+#https://fonttools.readthedocs.io/en/latest/designspaceLib/python.html#axisdescriptor
+a1 = AxisDescriptor()
+a1.minimum = 400
+a1.maximum = 1000
+a1.default = 400
+a1.map = ((400,400), (700,900), (1000,1000))
+a1.name = "width"
+a1.tag = "wdth"
+a1.axisOrdering = 1
+doc.addAxis(a1)
+
+a2 = DiscreteAxisDescriptor()
+a2.values = [1, 2, 3]
+a2.default = 1
+a2.name = "countedItems"
+a2.tag = "DSC1"
+a2.axisOrdering = 2
+doc.addAxis(a2)
+
+a3 = DiscreteAxisDescriptor()
+a3.values = [0, 1]
+a3.default = 0
+a3.name = "outlined"
+a3.tag = "DSC2"
+a3.axisOrdering = 3
+doc.addAxis(a3)
+
+default = {a1.name: a1.default, a2.name: a2.default, a3.name: a3.default}
+
+# add sources
+
+
+# public.skipExportGlyphs
+
+for c in [a1.minimum, a1.maximum]:
+ for d1 in a2.values:
+ for d2 in a3.values:
+
+ s1 = SourceDescriptor()
+ s1.path = os.path.join("sources", f"geometrySource_c_{c}_d1_{d1}_d2_{d2}.ufo")
+ s1.name = f"geometrySource{c} {d1} {d2}"
+ sourceLocation = dict(width=c, countedItems=d1, outlined=d2)
+ s1.location = sourceLocation
+ s1.kerning = True
+ s1.familyName = "SourceFamilyName"
+ if default == sourceLocation:
+ s1.copyGroups = True
+ s1.copyFeatures = True
+ s1.copyInfo = True
+ td1 = ["One", "Two", "Three"][(d1-1)]
+ if c == 400:
+ tc = "Narrow"
+ elif c == 1000:
+ tc = "Wide"
+ if d2 == 0:
+ td2 = "solid"
+ else:
+ td2 = "open"
+ s1.styleName = f"{td1}{tc}{td2}"
+ doc.addSource(s1)
+
+def ip(a,b,f):
+ return a + f*(b-a)
+
+# add instances
+steps = 8
+extrapolateAmount = 100
+
+
+interestingWeightValues = [(400, 700), 300, 400, 550, 700, 1000, 1100]
+
+mathModelPrefKey = "com.letterror.mathModelPref"
+mathModelVarlibPref = "previewVarLib"
+mathModelMutatorMathPref = "previewMutatorMath"
+
+# com.letterror.mathModelPref
+# previewVarLib
+
+
+for c in interestingWeightValues:
+ for d1 in a2.values:
+ for d2 in a3.values:
+
+ s1 = InstanceDescriptor()
+ s1.path = os.path.join("instances", f"geometryInstance_c_{c}_d1_{d1}_d2_{d2}.ufo")
+ s1.location = dict(width=c, countedItems=d1, outlined=d2)
+ s1.familyName = "InstanceFamilyName"
+ td1 = ["One", "Two", "Three"][(d1-1)]
+ if c == 400:
+ tc = "Narrow"
+ elif c == 1000:
+ tc = "Wide"
+ if d2 == 0:
+ td2 = "Solid"
+ else:
+ td2 = "Open"
+ s1.name = f"geometryInstance {td1} {tc} {td2}"
+ s1.styleName = f"{td1}{tc}{td2}"
+ s1.kerning = True
+ s1.info = True
+ doc.addInstance(s1)
+
+# add variable font descriptors
+
+splits = splitInterpolable(doc)
+for discreteLocation, subSpace in splitInterpolable(doc):
+ print(discreteLocation, subSpace)
+
+#print(doc.getVariableFonts())
+
+#for item in doc.getVariableFonts():
+# doc.addVariableFont(item)
+
+doc.variableFonts.clear()
+print(doc.variableFonts)
+
+
+variableFonts = doc.getVariableFonts()
+print("variableFonts", variableFonts)
+
+doc.addVariableFont(variableFonts[0])
+
+for i, item in enumerate(variableFonts):
+ print(i, item)
+
+
+path = "ds5.designspace"
+print(doc.lib)
+doc.write(path)
+print(dir(doc))
+
+
+for a in doc.axes:
+ if hasattr(a, "values"):
+ print(a.name, "d", a.values)
+ else:
+ print(a.name, "r", a.minimum, a.maximum)
+
+for s in doc.sources:
+ print(s.location)
+
+# ok. now about generating the instances.
+
+udoc = ufoProcessor.DesignSpaceProcessor()
+udoc.read(path)
diff --git a/Tests/ds5/ds5_test_designspaceProblems.py b/Tests/ds5/ds5_test_designspaceProblems.py
new file mode 100644
index 0000000..fd3f11e
--- /dev/null
+++ b/Tests/ds5/ds5_test_designspaceProblems.py
@@ -0,0 +1,12 @@
+import designspaceProblems
+print(designspaceProblems.__file__)
+
+path= "ds5.designspace"
+checker = designspaceProblems.DesignSpaceChecker(path)
+checker.checkEverything()
+print(checker.checkDesignSpaceGeometry())
+checker.checkSources()
+checker.checkInstances()
+print("hasStructuralProblems", checker.hasStructuralProblems())
+
+print(checker.problems)
\ No newline at end of file
diff --git a/Tests/ds5/readme.md b/Tests/ds5/readme.md
new file mode 100644
index 0000000..72231a0
--- /dev/null
+++ b/Tests/ds5/readme.md
@@ -0,0 +1,13 @@
+# Test for designspace 5 format with discrete and interpolating axes
+
+I need to keep notes somewhere.
+
+The script `ds5_makeTestDoc.py` makes a new DS5 designspace file with 3 axes.
+
+* `wdth` with minimum at `400`, default `400`, maximum at `1000`. An interpolating axis.
+* `DSC1`, a discrete axis. Values at `1,2,3`. Default at `1`. Named `countedItems` Variations along this axis consist of 1, 2, and 3 boxes.
+* `DSC2`, a discrete axis. Values at `0, 1`. Default at `0`. Named `outlined`. Variations along this axis consist of solid shapes and outlined shapes.
+
+The `masters` folder has sources for all intersections of these axes. The default of the whole system is at `wdth: 400, countedItems: 1, outlined: 0`
+![](masters.jpg)
+
diff --git a/Tests/ds5/sources.jpg b/Tests/ds5/sources.jpg
new file mode 100644
index 0000000..5b98578
Binary files /dev/null and b/Tests/ds5/sources.jpg differ
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea
new file mode 100644
index 0000000..a3f867d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..02c1c20
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo
+ descender
+ -200
+ familyName
+ One_wide_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_1_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist
similarity index 61%
rename from Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist
index 63a44ac..4cb18bb 100644
--- a/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist
@@ -2,7 +2,9 @@
- a
- a.glif
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..ab82b9d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/layercontents.plist
similarity index 100%
rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/layercontents.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/metainfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest1.ufo/metainfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/metainfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea
new file mode 100644
index 0000000..27b0ba6
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_1_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..999a9f3
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_1_d2_1.ufo
+ descender
+ -200
+ familyName
+ One_wide_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_1_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist
similarity index 61%
rename from Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist
index 63a44ac..4cb18bb 100644
--- a/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist
@@ -2,7 +2,9 @@
- a
- a.glif
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..c7da8f6
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/layercontents.plist
similarity index 100%
rename from Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/layercontents.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/benderTest2.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/metainfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest2.ufo/metainfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/metainfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea
new file mode 100644
index 0000000..1f64fc1
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_2_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..3f02085
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_2_d2_0.ufo
+ descender
+ -200
+ familyName
+ Two_wide_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_2_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist
similarity index 61%
rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist
index 63a44ac..4cb18bb 100644
--- a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist
@@ -2,7 +2,9 @@
- a
- a.glif
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..42f461a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist
similarity index 65%
rename from Tests/20190830 benders/benderTest1.ufo/layercontents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist
index e9a336b..b9c1a4f 100644
--- a/Tests/20190830 benders/benderTest1.ufo/layercontents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist
@@ -3,12 +3,8 @@
- foreground
+ public.default
glyphs
-
- background
- glyphs.background
-
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/benderTest3.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/metainfo.plist
similarity index 100%
rename from Tests/20190830 benders/benderTest3.ufo/metainfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/metainfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea
new file mode 100644
index 0000000..c866ad3
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_2_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..ff11f80
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_2_d2_1.ufo
+ descender
+ -200
+ familyName
+ Two_wide_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_2_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..928f76f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist
similarity index 85%
rename from Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist
index 87752a4..3cf39b4 100644
--- a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist
@@ -3,6 +3,6 @@
color
- 0,0.8,0.2,0.7
+ 1,0.75,0,0.7
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/benderTest2.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist
similarity index 65%
rename from Tests/20190830 benders/benderTest2.ufo/layercontents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist
index e9a336b..b9c1a4f 100644
--- a/Tests/20190830 benders/benderTest2.ufo/layercontents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist
@@ -3,12 +3,8 @@
- foreground
+ public.default
glyphs
-
- background
- glyphs.background
-
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/metainfo.plist
similarity index 100%
rename from Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/metainfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea
new file mode 100644
index 0000000..b0404fc
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_3_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..992889c
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_3_d2_0.ufo
+ descender
+ -200
+ familyName
+ Three_wide_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_3_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..baf7268
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist
similarity index 85%
rename from Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist
index 87752a4..3cf39b4 100644
--- a/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist
@@ -3,6 +3,6 @@
color
- 0,0.8,0.2,0.7
+ 1,0.75,0,0.7
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/benderTest3.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist
similarity index 65%
rename from Tests/20190830 benders/benderTest3.ufo/layercontents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist
index e9a336b..b9c1a4f 100644
--- a/Tests/20190830 benders/benderTest3.ufo/layercontents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist
@@ -3,12 +3,8 @@
- foreground
+ public.default
glyphs
-
- background
- glyphs.background
-
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/metainfo.plist
similarity index 100%
rename from Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/metainfo.plist
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea
new file mode 100644
index 0000000..95c19dd
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_1000_d1_3_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..140c4bf
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_1000_d1_3_d2_1.ufo
+ descender
+ -200
+ familyName
+ Three_wide_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_1000_d1_3_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..9d65323
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..c523ee7
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist
similarity index 85%
rename from Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist
index 87752a4..3cf39b4 100644
--- a/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist
@@ -3,6 +3,6 @@
color
- 0,0.8,0.2,0.7
+ 1,0.75,0,0.7
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..b54ab73
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 800
+
+ glyphTwo
+
+ glyphOne
+ -800
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist
similarity index 60%
rename from Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist
index 1e96c94..b9c1a4f 100644
--- a/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist
@@ -1,5 +1,10 @@
-
+
+
+ public.default
+ glyphs
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/20190830 benders/benderTest1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist
similarity index 60%
rename from Tests/20190830 benders/benderTest1.ufo/kerning.plist
rename to Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist
index 23382cc..7b8b34a 100644
--- a/Tests/20190830 benders/benderTest1.ufo/kerning.plist
+++ b/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist
@@ -2,10 +2,9 @@
- a
-
- a
- -100
-
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea
new file mode 100644
index 0000000..cf9f1f4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..11ef8cf
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo
+ descender
+ -200
+ familyName
+ One_narrow_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_1_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..cc09903
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist
similarity index 77%
rename from Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist
rename to Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist
index 63a44ac..3cf39b4 100644
--- a/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist
@@ -2,7 +2,7 @@
- a
- a.glif
+ color
+ 1,0.75,0,0.7
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea
new file mode 100644
index 0000000..ed083e0
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_1_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..4758a3e
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_1_d2_1.ufo
+ descender
+ -200
+ familyName
+ One_narrow_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_1_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..f9c3a61
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist
new file mode 100644
index 0000000..3cf39b4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ color
+ 1,0.75,0,0.7
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea
new file mode 100644
index 0000000..4d4466a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_2_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..05fa9c4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_2_d2_0.ufo
+ descender
+ -200
+ familyName
+ Two_narrow_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_2_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..978f672
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist
new file mode 100644
index 0000000..3cf39b4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ color
+ 1,0.75,0,0.7
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea
new file mode 100644
index 0000000..4affb7c
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_2_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..ce0452a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_2_d2_1.ufo
+ descender
+ -200
+ familyName
+ Two_narrow_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_2_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..a74b7e0
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist
new file mode 100644
index 0000000..3cf39b4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ color
+ 1,0.75,0,0.7
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea
new file mode 100644
index 0000000..88a4bea
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_3_d2_0.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist
new file mode 100644
index 0000000..4162a2c
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_3_d2_0.ufo
+ descender
+ -200
+ familyName
+ Three_narrow_open
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_3_d2_0
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..efe81ee
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist
new file mode 100644
index 0000000..3cf39b4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ color
+ 1,0.75,0,0.7
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea
new file mode 100644
index 0000000..a91bb83
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea
@@ -0,0 +1 @@
+# features from ufo: geometryMaster_c_400_d1_3_d2_1.ufo
\ No newline at end of file
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist
new file mode 100644
index 0000000..ff94547
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist
@@ -0,0 +1,55 @@
+
+
+
+
+ ascender
+ 400
+ capHeight
+ 400
+ copyright
+ # font.info from ufo: geometryMaster_c_400_d1_3_d2_1.ufo
+ descender
+ -200
+ familyName
+ Three_narrow_solid
+ guidelines
+
+ openTypeHheaAscender
+ 1036
+ openTypeHheaDescender
+ -335
+ openTypeOS2TypoAscender
+ 730
+ openTypeOS2TypoDescender
+ -270
+ openTypeOS2WinAscent
+ 1036
+ openTypeOS2WinDescent
+ 335
+ postscriptBlueFuzz
+ 0
+ postscriptBlueScale
+ 0.22
+ postscriptBlueValues
+
+ 100
+ 110
+
+ postscriptFamilyBlues
+
+ postscriptFamilyOtherBlues
+
+ postscriptOtherBlues
+
+ postscriptStemSnapH
+
+ postscriptStemSnapV
+
+ styleName
+ c_400_d1_3_d2_1
+ unitsPerEm
+ 1000
+ xHeight
+ 200
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist
new file mode 100644
index 0000000..4cb18bb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ glyphOne
+ glyphO_ne.glif
+ glyphTwo
+ glyphT_wo.glif
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif
new file mode 100644
index 0000000..54862c0
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif
new file mode 100644
index 0000000..7eea472
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist
new file mode 100644
index 0000000..3cf39b4
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ color
+ 1,0.75,0,0.7
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist
new file mode 100644
index 0000000..4dff22d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ public.kern1.groupA
+
+ glyphOne
+ glyphTwo
+
+ public.kern2.groupB
+
+ glyphThree
+ glyphFour
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist
new file mode 100644
index 0000000..ff4ce7d
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist
@@ -0,0 +1,20 @@
+
+
+
+
+ glyphOne
+
+ glyphOne
+ 400
+ glyphTwo
+ 100
+
+ glyphTwo
+
+ glyphOne
+ -100
+ glyphTwo
+ -400
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist
new file mode 100644
index 0000000..b9c1a4f
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist
@@ -0,0 +1,10 @@
+
+
+
+
+
+ public.default
+ glyphs
+
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist
new file mode 100644
index 0000000..ad24abb
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist
@@ -0,0 +1,31 @@
+
+
+
+
+ com.typemytype.robofont.compileSettings.autohint
+
+ com.typemytype.robofont.compileSettings.checkOutlines
+
+ com.typemytype.robofont.compileSettings.createDummyDSIG
+
+ com.typemytype.robofont.compileSettings.decompose
+
+ com.typemytype.robofont.compileSettings.generateFormat
+ 0
+ com.typemytype.robofont.compileSettings.releaseMode
+
+ com.typemytype.robofont.generateFeaturesWithFontTools
+
+ com.typemytype.robofont.italicSlantOffset
+ 0
+ com.typemytype.robofont.shouldAddPointsInSplineConversion
+ 1
+ public.glyphOrder
+
+ glyphOne
+ glyphTwo
+
+ ufoProcessor.test.lib.entry
+ Lib entry for master 1
+
+
diff --git a/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist
new file mode 100644
index 0000000..7b8b34a
--- /dev/null
+++ b/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ creator
+ com.github.fonttools.ufoLib
+ formatVersion
+ 3
+
+
diff --git a/Tests/kerningTest.py b/Tests/kerningTest.py
deleted file mode 100644
index 3f6490c..0000000
--- a/Tests/kerningTest.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from fontMath.mathKerning import MathKerning
-
-import fontMath.mathKerning
-from defcon.objects.font import Font
-from fontParts.fontshell import RFont
-from ufoProcessor.varModels import VariationModelMutator
-from mutatorMath.objects.mutator import buildMutator, Location
-from fontTools.designspaceLib import AxisDescriptor
-
-# kerning exception value. Different results for 1 and 0
-value = 0
-
-#f = Font()
-f = RFont() # doesn't make a difference
-f.groups["public.kern1.groupA"] = ['one', 'Bee']
-f.groups["public.kern2.groupB"] = ['two', 'Three']
-f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100
-f.kerning[("one", "two")] = value
-
-m = MathKerning(f.kerning, f.groups)
-print("mathKerning object items:", m.items())
-print("\tpair", ('public.kern1.groupA', 'public.kern2.groupB'), m[('public.kern1.groupA', 'public.kern2.groupB')])
-print("\tpair", ('public.kern1.groupA', 'two'), m[('public.kern1.groupA', 'two')])
-print("\tpair", ('one', 'public.kern2.groupB'), m[('one', 'public.kern2.groupB')])
-print("\tpair", ('one', 'two'), m[('one', 'two')])
-
-items = [(Location(w=0), m), (Location(w=1), m)]
-a = AxisDescriptor()
-a.name = "w"
-a.minimum = 0
-a.default = 0
-a.maximum = 1
-
-# process with varlib.model
-mut1 = VariationModelMutator(items, [a])
-m1i = mut1.makeInstance(dict(w=1))
-print("\n#varlib")
-print(m1i.items())
-
-# process with mutator
-bias, mut2 = buildMutator(items)
-m2i = mut2.makeInstance(dict(w=1))
-print("\n#mutator")
-print(m2i.items())
-
-# process with the same mathematical operations on a naked mathKerning object
-v = None
-deltas = [m, m]
-scalars = [1.0, 1.0]
-assert len(deltas) == len(scalars)
-for i,(delta,scalar) in enumerate(zip(deltas, scalars)):
- if not scalar: continue
- contribution = delta * scalar
- if v is None:
- v = contribution
- else:
- v += contribution
-print("\n#doing the math that varlib does")
-print(v.items())
-
-print(m.groups())
-print((m*2.0).groups())
diff --git a/Tests/mathKerningTest.py b/Tests/mathKerningTest.py
deleted file mode 100644
index fd9e2d1..0000000
--- a/Tests/mathKerningTest.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from fontMath.mathKerning import MathKerning
-from defcon.objects.font import Font
-
-f = Font()
-f.groups["public.kern1.groupA"] = ['one', 'Bee']
-f.groups["public.kern2.groupB"] = ['two', 'Three']
-f.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100
-f.kerning[('one', 'two')] = 0
-m = MathKerning(f.kerning, f.groups)
-
-print(m.items())
-print((m*1.0).items())
-
-
diff --git a/Tests/spReader_testdocs/superpolator_testdoc1.sp3 b/Tests/spReader_testdocs/superpolator_testdoc1.sp3
deleted file mode 100644
index 7f4a599..0000000
--- a/Tests/spReader_testdocs/superpolator_testdoc1.sp3
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace b/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace
deleted file mode 100644
index 560b3af..0000000
--- a/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- com.letterror.skateboard.interactionSources
-
- horizontal
-
- width
-
- ignore
-
- vertical
-
- weight
-
-
- com.letterror.skateboard.mutedSources
-
-
- ufo/MutatorSansLightCondensed.ufo
- foreground
-
-
- ufo/MutatorSansBoldCondensed.ufo
- foreground
-
-
- com.letterror.skateboard.previewText
- VA
- com.superpolator.data
-
- expandRules
-
- horizontalPreviewAxis
- width
- includeLegacyRules
-
- instancefolder
- instances
- keepWorkFiles
-
- lineInverted
-
- lineStacked
- lined
- lineViewFilled
-
- outputFormatUFO
- 3.0
- previewtext
- VA
- roundGeometry
-
- verticalPreviewAxis
- weight
-
-
-
-
diff --git a/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace b/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace
deleted file mode 100644
index 39fbf32..0000000
--- a/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- com.letterror.skateboard.interactionSources
-
- horizontal
-
- width
-
- ignore
-
- vertical
-
- weight
-
-
- com.letterror.skateboard.mutedSources
-
-
- ufo/MutatorSansLightCondensed.ufo
- foreground
-
-
- ufo/MutatorSansBoldCondensed.ufo
- foreground
-
-
- com.letterror.skateboard.previewText
- VA
- com.superpolator.data
-
- expandRules
-
- horizontalPreviewAxis
- width
- includeLegacyRules
-
- instancefolder
- instances
- keepWorkFiles
-
- lineInverted
-
- lineStacked
- lined
- lineViewFilled
-
- outputFormatUFO
- 3.0
- previewtext
- VA
- roundGeometry
-
- verticalPreviewAxis
- weight
-
-
-
-
diff --git a/Tests/tests.py b/Tests/tests.py
index 142769b..36af9a2 100644
--- a/Tests/tests.py
+++ b/Tests/tests.py
@@ -1,33 +1,21 @@
# standalone test
import shutil
import os
-import defcon.objects.font
-import fontParts.fontshell.font
+#from defcon.objects.font import Font
import logging
from ufoProcessor import *
+import fontParts.fontshell
# new place for ufoProcessor tests.
# Run in regular python of choice, not ready for pytest just yet.
# You may ask "why not?" - you may ask indeed.
-# make the tests w ork with defcon as well as fontparts
-
-def addExtraGlyph(font, name, s=200):
- font.newGlyph(name)
- g = font[name]
- p = g.getPen()
- p.moveTo((0,0))
- p.lineTo((s,0))
- p.lineTo((s,s))
- p.lineTo((0,s))
- p.closePath()
- g.width = s
+# Now based on fontParts.
def addGlyphs(font, s, addSupportLayer=True):
# we need to add the glyphs
step = 0
- uni = 95
for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour', 'glyphFive']:
font.newGlyph(n)
g = font[n]
@@ -39,8 +27,6 @@ def addGlyphs(font, s, addSupportLayer=True):
p.closePath()
g.move((0,s+step))
g.width = s
- g.unicode = uni
- uni += 1
step += 50
for n, w in [('wide', 800), ('narrow', 100)]:
font.newGlyph(n)
@@ -52,15 +38,12 @@ def addGlyphs(font, s, addSupportLayer=True):
p.lineTo((0,font.info.ascender))
p.closePath()
g.width = w
- na = defcon.Anchor()
- na.name = "top"
- na.x = 0
- na.y = w
- g.appendAnchor(na)
+ g.appendAnchor("top", (0, w))
if addSupportLayer:
font.newLayer('support')
- layer = font.layers['support']
+ print(n for n in font.layers if n.name == 'support')
+ layer = font.getLayer('support')
layer.newGlyph('glyphFive')
layer.newGlyph('glyphOne') # add an empty glyph to see how it is treated
lg = layer['glyphFive']
@@ -76,17 +59,19 @@ def addGlyphs(font, s, addSupportLayer=True):
font.newGlyph("wide.component")
g = font["wide.component"]
- comp = g.instantiateComponent()
- comp.baseGlyph = "wide"
- comp.offset = (0,0)
- g.appendComponent(comp)
+ g.appendComponent("wide", offset=(0,0))
+ #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.appendComponent("narrow", offset=(0,0))
+ #comp = g.instantiateComponent()
+ #comp.baseGlyph = "narrow"
+ #comp.offset = (0,0)
+ #g.appendComponent(comp)
g.width = font['narrow'].width
uniValue = 200
for g in font:
@@ -99,16 +84,6 @@ def fillInfo(font):
font.info.ascender = 800
font.info.descender = -200
-def _create_parent_dir(ufo_path):
- """
- Creates the parent directory where the UFO will be saved, in case it
- doesn't exist already. This is required because fontTools.ufoLib no
- longer calls os.makedirs.
- """
- directory = os.path.dirname(os.path.normpath(ufo_path))
- if directory and not os.path.exists(directory):
- os.makedirs(directory)
-
def _makeTestFonts(rootPath):
""" Make some test fonts that have the kerning problem."""
path1 = os.path.join(rootPath, "masters", "geometryMaster1.ufo")
@@ -116,43 +91,26 @@ def _makeTestFonts(rootPath):
path3 = os.path.join(rootPath, "instances", "geometryInstance%3.3f.ufo")
path4 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic1.ufo")
path5 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic2.ufo")
- path6 = os.path.join(rootPath, "instances", "extrapolate", "geometryInstance%s.ufo")
- f1 = Font()
+ for path in [path1, path2, path3, path4, path5]:
+ d = os.path.dirname(path)
+ if not os.path.exists(d):
+ os.makedirs(d)
+ f1 = fontParts.fontshell.RFont()
fillInfo(f1)
addGlyphs(f1, 100, addSupportLayer=False)
- addExtraGlyph(f1, "extra.glyph.for.neutral")
f1.features.text = u"# features text from master 1"
- f2 = Font()
+ f2 = fontParts.fontshell.RFont()
fillInfo(f2)
addGlyphs(f2, 500, addSupportLayer=True)
- addExtraGlyph(f2, "extra.glyph.for.master2")
f2.features.text = u"# features text from master 2"
f1.info.ascender = 400
f1.info.descender = -200
- f1.info.xHeight = 200
- f1.info.capHeight = 400
f2.info.ascender = 600
f2.info.descender = -100
- f2.info.xHeight = 200
- f2.info.capHeight = 600
f1.info.copyright = u"This is the copyright notice from master 1"
f2.info.copyright = u"This is the copyright notice from master 2"
f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1"
f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2"
-
- f1.info.postscriptBlueValues = [100, 110]
- f2.info.postscriptBlueValues = [120, 125]
- f1.info.postscriptBlueFuzz = 0
- f2.info.postscriptBlueFuzz = 1
- f1.info.postscriptBlueScale = 0.11 # should not round
- f1.info.postscriptBlueScale = 0.22
-
- f1.info.openTypeHheaAscender = 1036
- f1.info.openTypeHheaDescender = -335
- f1.info.openTypeOS2TypoAscender = 730
- f1.info.openTypeOS2TypoDescender = -270
- f1.info.openTypeOS2WinAscent = 1036
- f1.info.openTypeOS2WinDescent = 335
f1.groups["public.kern1.groupA"] = ['glyphOne', 'glyphTwo']
f1.groups["public.kern2.groupB"] = ['glyphThree', 'glyphFour']
@@ -170,17 +128,15 @@ def _makeTestFonts(rootPath):
f2.kerning[('glyphOne', 'glyphFour')] = 0
print([l.name for l in f1.layers], [l.name for l in f2.layers])
- _create_parent_dir(path1)
- _create_parent_dir(path2)
f1.save(path1, 3)
f2.save(path2, 3)
- return path1, path2, path3, path4, path5, path6
+ return path1, path2, path3, path4, path5
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()
+ f1 = fontParts.fontshell.RFont()
fillInfo(f1)
addGlyphs(f1, 100)
f1.features.text = u"# features text from master 1"
@@ -191,40 +147,27 @@ def _makeSwapFonts(rootPath):
f1.save(path1, 2)
return path1, path2
-class DesignSpaceProcessor_using_defcon(DesignSpaceProcessor):
- def _instantiateFont(self, path):
- return defcon.objects.font.Font(path)
-
-class DesignSpaceProcessor_using_fontparts(DesignSpaceProcessor):
- def _instantiateFont(self, path):
- return fontParts.fontshell.font.RFont(path)
-
-def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
+def _makeTestDocument(docPath, useVarlib=True):
# make the test fonts and a test document
if useVarlib:
extension = "varlib"
else:
extension = "mutator"
- testFontPath = os.path.join(os.path.dirname(docPath), "automatic_testfonts_%s" % extension)
- print("\ttestFontPath:", testFontPath)
- m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2, extrapolatePath = _makeTestFonts(testFontPath)
- if useDefcon:
- d = DesignSpaceProcessor_using_defcon(useVarlib=useVarlib)
- else:
- d = DesignSpaceProcessor_using_fontparts(useVarlib=useVarlib)
- print("\td", d, type(d))
+ testFontPath = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension)
+ m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2 = _makeTestFonts(testFontPath)
+ d = DesignSpaceProcessor(useVarlib=useVarlib)
a = AxisDescriptor()
a.name = "pop"
a.minimum = 0
a.maximum = 1000
a.default = 0
a.tag = "pop*"
- a.map = [(0,10),(500,250),(1000,990)]
+ a.map = [(0,0),(500,250),(1000,1000)]
d.addAxis(a)
s1 = SourceDescriptor()
s1.path = m1
- s1.location = dict(pop=a.map_forward(a.default))
+ s1.location = dict(pop=a.default)
s1.name = "test.master.1"
s1.copyInfo = True
s1.copyFeatures = True
@@ -235,7 +178,6 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
s2.path = m2
s2.location = dict(pop=1000)
s2.name = "test.master.2"
- s2.muteKerning = True
d.addSource(s2)
s3 = SourceDescriptor()
@@ -245,27 +187,10 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
s3.layerName = "support"
d.addSource(s3)
- s4 = SourceDescriptor()
- s4.path = "missing.ufo"
- s4.location = dict(pop=600)
- s4.name = "test.missing.master"
- d.addSource(s4)
-
- s5 = SourceDescriptor()
- s5.path = m2
- s5.location = dict(pop=620)
- s5.name = "test.existing.ufo_missing.layer"
- s5.layerName = "missing.layer"
- d.addSource(s5)
-
d.findDefault()
- # make sure the default location is bend and unbend as we want.
- assert d.newDefaultLocation().get('pop') == 0
- assert d.newDefaultLocation(bend=True).get('pop') == 10
-
- steps = 6
- for counter in range(steps):
- factor = counter / steps
+
+ for counter in range(3):
+ factor = counter / 2
i = InstanceDescriptor()
v = a.minimum+factor*(a.maximum-a.minimum)
i.path = i1 % v
@@ -275,7 +200,6 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
i.location = dict(pop=v)
i.info = True
i.kerning = True
- i.postScriptFontName = "TestFamily PSName %s" % i.styleName
if counter == 2:
i.glyphs['glyphTwo'] = dict(name="glyphTwo", mute=True)
i.copyLib = True
@@ -283,20 +207,9 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125])
d.addInstance(i)
- # add extrapolatiing location
- i = InstanceDescriptor()
- i.path = extrapolatePath % "TestStyle_Extrapolate"
- print('i.path', i.path)
- i.familyName = "TestFamily"
- i.styleName = "TestStyle_Extrapolate"
- i.name = "%s-%s" % (i.familyName, i.styleName)
- i.location = dict(pop=3000)
- i.info = True
- i.kerning = True
- d.addInstance(i)
-
# add anisotropic locations
i = InstanceDescriptor()
+ v = a.minimum+0.5*(a.maximum-a.minimum)
i.path = anisotropicInstancePath1
i.familyName = "TestFamily"
i.styleName = "TestStyle_pop_anisotropic1"
@@ -307,6 +220,7 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
d.addInstance(i)
i = InstanceDescriptor()
+ v = a.minimum+0.5*(a.maximum-a.minimum)
i.path = anisotropicInstancePath2
i.familyName = "TestFamily"
i.styleName = "TestStyle_pop_anisotropic2"
@@ -317,107 +231,19 @@ def _makeTestDocument(docPath, useVarlib=True, useDefcon=True):
d.addInstance(i)
# add data to the document lib
- d.lib['ufoprocessor.testdata'] = dict(pop=500, name="This is a named location, stored in the document lib.")
+ d.lib['ufoprocessor.testdata'] = dict(width=500, weight=500, name="This is a named location, stored in the document lib.")
d.write(docPath)
-def _testGenerateInstances(docPath, useVarlib=True, useDefcon=True, roundGeometry=False):
+def _testGenerateInstances(docPath, useVarlib=True):
# execute the test document
- if useDefcon:
- d = DesignSpaceProcessor_using_defcon(useVarlib=useVarlib)
- else:
- d = DesignSpaceProcessor_using_fontparts(useVarlib=useVarlib)
+ d = DesignSpaceProcessor(useVarlib=useVarlib)
d.read(docPath)
- d.loadFonts()
- print('---', d.newDefaultLocation())
- d.roundGeometry = roundGeometry
- objectFlavor = [type(f).__name__ for f in d.fonts.values()][0]
- print("objectFlavor", objectFlavor)
d.generateUFO()
if d.problems:
- print("log:")
for p in d.problems:
print("\t",p)
-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"))
- # 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"
- # Check that anchors swapped
- assert new['wide'].anchors[0].y == old['narrow'].anchors[0].y
- assert new['narrow'].anchors[0].y == old['wide'].anchors[0].y
-
-def testAxisMuting():
- d = DesignSpaceProcessor_using_defcon(useVarlib=True)
-
- a = AxisDescriptor()
- a.name = "pop"
- a.minimum = 0
- a.maximum = 1000
- a.default = 0
- a.tag = "pop*"
- d.addAxis(a)
-
- a = AxisDescriptor()
- a.name = "snap"
- a.minimum = 100
- a.maximum = 200
- a.default = 150
- a.tag = "snap"
- d.addAxis(a)
-
- a = AxisDescriptor()
- a.name = "crackle"
- a.minimum = -1
- a.maximum = 1
- a.default = 0
- a.tag = "krak"
- d.addAxis(a)
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), [])
- assert shouldIgnore == False
- assert loc == {'snap': 150, 'crackle': 0, 'pop': 0}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['pop'])
- assert shouldIgnore == False
- assert loc == {'snap': 150, 'crackle': 0}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['pop'])
- assert shouldIgnore == True
- assert loc == {'snap': 150, 'crackle': 0}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['pop', 'crackle'])
- assert shouldIgnore == False
- assert loc == {'snap': 150}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['pop', 'crackle', 'snap'])
- assert shouldIgnore == True
- assert loc == {}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=0), ['one', 'two', 'three'])
- assert shouldIgnore == False
- assert loc == {'snap': 150, 'crackle': 0, 'pop': 0}
-
- shouldIgnore, loc = d.filterThisLocation(dict(snap=150, crackle=0, pop=1), ['one', 'two', 'three'])
- assert shouldIgnore == False
- assert loc == {'snap': 150, 'crackle': 0, 'pop': 1}
-
def testUnicodes(docPath, useVarlib=True):
# after executing testSwap there should be some test fonts
# let's check if the unicode values for glyph "narrow" arrive at the right place.
@@ -425,46 +251,28 @@ def testUnicodes(docPath, useVarlib=True):
d.read(docPath)
for instance in d.instances:
if os.path.exists(instance.path):
- f = Font(instance.path)
+ f = fontParts.fontshell.RFont(instance.path)
print("instance.path", instance.path)
print("instance.name", instance.name, "f['narrow'].unicodes", f['narrow'].unicodes)
- if instance.name == "TestFamily-TestStyle_pop1000.000":
- assert f['narrow'].unicodes == [291, 292, 293]
- else:
- assert f['narrow'].unicodes == [207]
+ #if instance.name == "TestFamily-TestStyle_pop1000.000":
+ # assert f['narrow'].unicodes == [291, 292, 293]
+ #else:
+ # #assert f['narrow'].unicodes == [207]
else:
print("Missing test font at %s" % instance.path)
selfTest = True
if selfTest:
- for extension in ['mutator', 'varlib']:
- for objectFlavor in ['defcon', 'fontparts']:
- for roundGeometry in [True, False]:
- # which object model to use for **executuing** the designspace.
- # all the objects in **this test** are defcon.
-
- print("\n\nRunning the test with ", extension, "and", objectFlavor, "roundGeometry:", roundGeometry)
- print("-"*40)
- USEVARLIBMODEL = extension == 'varlib'
- if roundGeometry:
- roundingTag = "_rounded_geometry"
- else:
- roundingTag = ""
- testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s_%s%s" % (extension, objectFlavor, roundingTag))
- print("\ttestRoot", testRoot)
- if os.path.exists(testRoot):
- shutil.rmtree(testRoot)
- docPath = os.path.join(testRoot, "automatic_test.designspace")
- print("\tdocPath", docPath)
- print("-"*40)
- print("Generate document, masters")
- _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon")
- print("-"*40)
- print("Generate instances", docPath)
- _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon", roundGeometry=roundGeometry)
- testSwap(docPath)
- #_makeTestDocument(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon")
- #_testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL, useDefcon=objectFlavor=="defcon")
-
-
-testAxisMuting()
+ for extension in ['varlib', 'mutator']:
+ print("\n\n", extension)
+ USEVARLIBMODEL = extension == 'varlib'
+ testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension)
+ if os.path.exists(testRoot):
+ shutil.rmtree(testRoot)
+ docPath = os.path.join(testRoot, "automatic_test.designspace")
+ _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL)
+ _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL)
+
+ _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL)
+ _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL)
+ testUnicodes(docPath, useVarlib=USEVARLIBMODEL)
diff --git a/Tests/tests_fp.py b/Tests/tests_fp.py
deleted file mode 100644
index b882898..0000000
--- a/Tests/tests_fp.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# standalone test
-import shutil
-import os
-#from defcon.objects.font import Font
-import logging
-from ufoProcessor import *
-import fontParts.fontshell
-
-
-# new place for ufoProcessor tests.
-# Run in regular python of choice, not ready for pytest just yet.
-# You may ask "why not?" - you may ask indeed.
-
-# Now based on fontParts.
-
-def addGlyphs(font, s, addSupportLayer=True):
- # we need to add the glyphs
- step = 0
- for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour', 'glyphFive']:
- 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+step))
- g.width = s
- 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
- g.appendAnchor("top", (0, w))
-
- if addSupportLayer:
- font.newLayer('support')
- print(n for n in font.layers if n.name == 'support')
- layer = font.getLayer('support')
- layer.newGlyph('glyphFive')
- layer.newGlyph('glyphOne') # add an empty glyph to see how it is treated
- lg = layer['glyphFive']
- p = lg.getPen()
- w = 10
- y = -400
- p.moveTo((0,y))
- p.lineTo((s,y))
- p.lineTo((s,y+100))
- p.lineTo((0,y+100))
- p.closePath()
- lg.width = s
-
- font.newGlyph("wide.component")
- g = font["wide.component"]
- g.appendComponent("wide", offset=(0,0))
- #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"]
- g.appendComponent("narrow", offset=(0,0))
- #comp = g.instantiateComponent()
- #comp.baseGlyph = "narrow"
- #comp.offset = (0,0)
- #g.appendComponent(comp)
- g.width = font['narrow'].width
- uniValue = 200
- for g in font:
- g.unicode = uniValue
- uniValue += 1
-
-
-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, "masters", "geometryMaster1.ufo")
- path2 = os.path.join(rootPath, "masters", "geometryMaster2.ufo")
- path3 = os.path.join(rootPath, "instances", "geometryInstance%3.3f.ufo")
- path4 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic1.ufo")
- path5 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic2.ufo")
- for path in [path1, path2, path3, path4, path5]:
- d = os.path.dirname(path)
- if not os.path.exists(d):
- os.makedirs(d)
- f1 = fontParts.fontshell.RFont()
- fillInfo(f1)
- addGlyphs(f1, 100, addSupportLayer=False)
- f1.features.text = u"# features text from master 1"
- f2 = fontParts.fontshell.RFont()
- fillInfo(f2)
- addGlyphs(f2, 500, addSupportLayer=True)
- 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"
- f2.info.copyright = u"This is the copyright notice from master 2"
- f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1"
- f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2"
-
- f1.groups["public.kern1.groupA"] = ['glyphOne', 'glyphTwo']
- f1.groups["public.kern2.groupB"] = ['glyphThree', 'glyphFour']
- f2.groups.update(f1.groups)
-
- f1.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100
- f2.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -200
-
- f1.kerning[('glyphOne', 'glyphOne')] = -100
- f2.kerning[('glyphOne', 'glyphOne')] = 0
- f1.kerning[('glyphOne', 'glyphThree')] = 10
- f1.kerning[('glyphOne', 'glyphFour')] = 10
- # exception
- f2.kerning[('glyphOne', 'glyphThree')] = 1
- f2.kerning[('glyphOne', 'glyphFour')] = 0
- print([l.name for l in f1.layers], [l.name for l in f2.layers])
-
- f1.save(path1, 3)
- f2.save(path2, 3)
- return path1, path2, path3, path4, path5
-
-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 = fontParts.fontshell.RFont()
- 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
-
-def _makeTestDocument(docPath, useVarlib=True):
- # make the test fonts and a test document
- if useVarlib:
- extension = "varlib"
- else:
- extension = "mutator"
- testFontPath = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension)
- m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2 = _makeTestFonts(testFontPath)
- d = DesignSpaceProcessor(useVarlib=useVarlib)
- a = AxisDescriptor()
- a.name = "pop"
- a.minimum = 0
- a.maximum = 1000
- a.default = 0
- a.tag = "pop*"
- a.map = [(0,0),(500,250),(1000,1000)]
- d.addAxis(a)
-
- s1 = SourceDescriptor()
- s1.path = m1
- s1.location = dict(pop=a.default)
- s1.name = "test.master.1"
- s1.copyInfo = True
- s1.copyFeatures = True
- s1.copyLib = True
- d.addSource(s1)
-
- s2 = SourceDescriptor()
- s2.path = m2
- s2.location = dict(pop=1000)
- s2.name = "test.master.2"
- d.addSource(s2)
-
- s3 = SourceDescriptor()
- s3.path = m2
- s3.location = dict(pop=500)
- s3.name = "test.master.support.1"
- s3.layerName = "support"
- d.addSource(s3)
-
- d.findDefault()
-
- 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)
- i.copyLib = True
- if counter == 2:
- i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125])
- d.addInstance(i)
-
- # add anisotropic locations
- i = InstanceDescriptor()
- v = a.minimum+0.5*(a.maximum-a.minimum)
- i.path = anisotropicInstancePath1
- i.familyName = "TestFamily"
- i.styleName = "TestStyle_pop_anisotropic1"
- i.name = "%s-%s" % (i.familyName, i.styleName)
- i.location = dict(pop=(1000, 0))
- i.info = True
- i.kerning = True
- d.addInstance(i)
-
- i = InstanceDescriptor()
- v = a.minimum+0.5*(a.maximum-a.minimum)
- i.path = anisotropicInstancePath2
- i.familyName = "TestFamily"
- i.styleName = "TestStyle_pop_anisotropic2"
- i.name = "%s-%s" % (i.familyName, i.styleName)
- i.location = dict(pop=(0, 1000))
- i.info = True
- i.kerning = True
- d.addInstance(i)
-
- # add data to the document lib
- d.lib['ufoprocessor.testdata'] = dict(width=500, weight=500, name="This is a named location, stored in the document lib.")
-
- d.write(docPath)
-
-def _testGenerateInstances(docPath, useVarlib=True):
- # execute the test document
- d = DesignSpaceProcessor(useVarlib=useVarlib)
- d.read(docPath)
- d.generateUFO()
- if d.problems:
- for p in d.problems:
- print("\t",p)
-
-def testSwap(docPath):
- srcPath, dstPath = _makeSwapFonts(os.path.dirname(docPath))
- f = fontParts.fontshell.RFont(srcPath)
- swapGlyphNames(f, "narrow", "wide")
- f.info.styleName = "Swapped"
- f.save(dstPath)
- # test the results in newly opened fonts
- old = fontParts.fontshell.RFont(srcPath)
- new = fontParts.fontshell.RFont(dstPath)
- assert new.kerning.get(("narrow", "narrow")) == old.kerning.get(("wide","wide"))
- assert new.kerning.get(("wide", "wide")) == old.kerning.get(("narrow","narrow"))
- # 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"
- # Check that anchors swapped
- assert new['wide'].anchors[0].y == old['narrow'].anchors[0].y
- assert new['narrow'].anchors[0].y == old['wide'].anchors[0].y
-
-def testUnicodes(docPath, useVarlib=True):
- # 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(useVarlib=useVarlib)
- d.read(docPath)
- for instance in d.instances:
- if os.path.exists(instance.path):
- f = fontParts.fontshell.RFont(instance.path)
- print("instance.path", instance.path)
- print("instance.name", instance.name, "f['narrow'].unicodes", f['narrow'].unicodes)
- #if instance.name == "TestFamily-TestStyle_pop1000.000":
- # assert f['narrow'].unicodes == [291, 292, 293]
- #else:
- # #assert f['narrow'].unicodes == [207]
- else:
- print("Missing test font at %s" % instance.path)
-
-selfTest = True
-if selfTest:
- for extension in ['varlib', 'mutator']:
- print("\n\n", extension)
- USEVARLIBMODEL = extension == 'varlib'
- testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension)
- if os.path.exists(testRoot):
- shutil.rmtree(testRoot)
- docPath = os.path.join(testRoot, "automatic_test.designspace")
- _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL)
- _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL)
-
- testSwap(docPath)
- _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL)
- _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL)
- testUnicodes(docPath, useVarlib=USEVARLIBMODEL)
diff --git a/testRefactor_RF.py b/testRefactor_RF.py
new file mode 100644
index 0000000..e864b4a
--- /dev/null
+++ b/testRefactor_RF.py
@@ -0,0 +1,77 @@
+from random import randint
+import ufoProcessor
+import ufoProcessor.ufoOperator
+import importlib
+importlib.reload(ufoProcessor.ufoOperator)
+print(ufoProcessor.__file__)
+ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace"
+doc = ufoProcessor.ufoOperator.UFOOperator(ds5Path, useVarlib=False, debug=True)
+doc.loadFonts()
+#doc.generateUFOs()
+
+def ip(a, b, f):
+ return a+f*(b-a)
+
+font = CurrentFont()
+
+loc = doc.newDefaultLocation()
+loc['width'] = randint(50, 100)
+print(loc)
+
+# make some tests at different layers
+test = [
+ ("foreground", dict(width=75, italic=0), False),
+ ("background", dict(width=75, italic=1), False),
+ ("random_width_inter_MM", dict(width=randint(50,100), italic=1), False),
+ ("random_width_xtr_MM", dict(width=randint(10,150), italic=1), False),
+ ("random_width_xtr_narrow_VL", dict(width=randint(10,50), italic=1), True),
+ ("random_width_xtr_wide_VL", dict(width=randint(100,500), italic=1), True),
+
+ ("10_width_xtr_VL", dict(width=10, italic=1), True),
+ ("10_width_xtr_MM", dict(width=10, italic=1), False),
+ ("200_width_xtr_wide_VL", dict(width=200, italic=1), True),
+ ("200_width_xtr_wide_MM", dict(width=200, italic=1), False),
+
+ ("aniso_width_inter_MM", dict(width=(50,100), italic=0), False),
+ ("aniso_width_inter_VL", dict(width=(50,100), italic=0), True),
+
+ ("aniso_width_xtra_MM", dict(width=(-50,200), italic=0), False),
+ ("aniso_width_xtra_VL", dict(width=(-50,200), italic=0), True),
+
+ ]
+
+g = CurrentGlyph()
+dstName = g.name
+
+for layerName, loc, useVarlib in test:
+ res = doc.makeOneGlyph(dstName, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True)
+ dst = font[dstName].getLayer(layerName)
+ dst.clear()
+ res.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved
+ dst.fromMathGlyph(res)
+ dst.width = max(0, res.width)
+
+ print(len(dst.components))
+ for comp in dst.components:
+ print("-- processing baseglyph", comp.baseGlyph)
+ res2 = doc.makeOneGlyph(comp.baseGlyph, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True)
+ # let's make sure the glyph exists in the layer
+ print('layerName:', layerName)
+ dstLayer = font.getLayer(layerName)
+ if not comp.baseGlyph in dstLayer:
+ dstLayer.newGlyph(comp.baseGlyph)
+ dst2 = dstLayer[comp.baseGlyph]
+ dst2.clear()
+ print('dst.anchors:', dst.anchors)
+ print('dst.guidelines:', dst.guidelines)
+ for item in res2.guidelines:
+ print(item)
+ res2.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved
+ print('dst.guidelines:', res2.guidelines)
+ dst2.fromMathGlyph(res2)
+ dst2.width = max(0, res2.width)
+
+ dst2.update()
+ dst.update()
+
+ufoProcessor.refactor.inspectMemoizeCache()