pax_global_header 0000666 0000000 0000000 00000000064 13636425227 0014524 g ustar 00root root 0000000 0000000 52 comment=88323a406152636f60f78e46cd41106445915877
ufoProcessor-1.9.0/ 0000775 0000000 0000000 00000000000 13636425227 0014224 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/.gitignore 0000664 0000000 0000000 00000001367 13636425227 0016223 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.idea
*.log
# Distribution / Packaging
*.egg-info
*.eggs
build
dist
# Unit test / coverage files
.coverage
.coverage.*
.pytest_cache
.tox/
htmlcov/
# auto-generated version file
Lib/ufoProcessor/_version.py
Lib/ufoProcessor/automatic_testfonts
Lib/ufoProcessor/automatic_testfonts_mutator
Lib/ufoProcessor/automatic_testfonts_varlib
Tests/automatic_testfonts_mutator
Tests/automatic_testfonts_mutator_defcon
Tests/automatic_testfonts_mutator_fontparts
Tests/automatic_testfonts_varlib
Tests/automatic_testfonts_varlib_defcon
Tests/automatic_testfonts_varlib_fontparts
Tests/automatic_testfonts*
test*.designspace
test*.sp3
Tests/Instances
Tests/_*
Tests/20190830 benders/Skateboard Previews
ufoProcessor-1.9.0/.travis.yml 0000664 0000000 0000000 00000002204 13636425227 0016333 0 ustar 00root root 0000000 0000000 language: python
env:
global:
- TWINE_USERNAME="letterror-ci"
# Note: TWINE_PASSWORD is set in Travis settings
matrix:
include:
- python: 2.7
- python: 3.6
env: PYPI_DEPLOY=true
install:
- pip install -U setuptools wheel
# - pip install pytest coverage codecov
script:
# generate wheel
# (that's all it can be done for now since ufoProcessor's test suite is not setup)
- python setup.py bdist_wheel
after_success:
# generate source distribution file
# and deploy wheel and sdist files to PyPI on git tags
- |
if [ $TRAVIS_TAG ] && [ $PYPI_DEPLOY ]; then
python -m pip install twine
python setup.py sdist
python -m twine upload dist/*
fi
# Erik needs to generate a valid 'api_key' for this to work
# https://docs.travis-ci.com/user/deployment/releases/#authenticating-with-an-oauth-token
# Travis command line client: https://github.com/travis-ci/travis.rb#installation
# deploy:
# # create a GitHub (Pre)Release on git tags
# - provider: releases
# api_key:
# secure: YOUR_API_KEY_ENCRYPTED
# skip_cleanup: true
# prerelease: true
# on:
# tags: true
ufoProcessor-1.9.0/LICENSE 0000664 0000000 0000000 00000002115 13636425227 0015230 0 ustar 00root root 0000000 0000000 Copyright (c) 2017-2018 LettError and Erik van Blokland
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
ufoProcessor-1.9.0/Lib/ 0000775 0000000 0000000 00000000000 13636425227 0014732 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Lib/ufoProcessor/ 0000775 0000000 0000000 00000000000 13636425227 0017423 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Lib/ufoProcessor/__init__.py 0000664 0000000 0000000 00000120775 13636425227 0021550 0 ustar 00root root 0000000 0000000 # coding: utf-8
from __future__ import print_function, division, absolute_import
import os
import logging, traceback
import collections
# from pprint import pprint
from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules
from fontTools.misc import plistlib
from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3
from fontTools.varLib.models import VariationModel, normalizeLocation
import defcon
import fontParts.fontshell.font
import defcon.objects.font
from defcon.objects.font import Font
from defcon.pens.transformPointPen import TransformPointPen
from defcon.objects.component import _defaultTransformation
from fontMath.mathGlyph import MathGlyph
from fontMath.mathInfo import MathInfo
from fontMath.mathKerning import MathKerning
# 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
from ufoProcessor.varModels import VariationModelMutator
from ufoProcessor.emptyPen import checkGlyphIsEmpty
try:
from ._version import version as __version__
except ImportError:
__version__ = "0.0.0+unknown"
class UFOProcessorError(Exception):
def __init__(self, msg, obj=None):
self.msg = msg
self.obj = obj
def __str__(self):
return repr(self.msg) + repr(self.obj)
def getDefaultLayerName(f):
# get the name of the default layer from a defcon font and from a fontparts font
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
def getLayer(f, layerName):
# get the layer from a defcon font and from a fontparts font
if issubclass(type(f), defcon.objects.font.Font):
if layerName in f.layers:
return f.layers[layerName]
elif issubclass(type(f), fontParts.fontshell.font.RFont):
if layerName in f.layerOrder:
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,
outputUFOFormatVersion=3,
roundGeometry=True,
verbose=True, # not supported
logPath=None, # not supported
progressFunc=None, # not supported
processRules=True,
logger=None,
useVarlib=False,
):
"""
Simple builder for UFO designspaces.
"""
import os, glob
if os.path.isdir(documentPath):
# process all *.designspace documents in this folder
todo = glob.glob(os.path.join(documentPath, "*.designspace"))
else:
# process the
todo = [documentPath]
results = []
for path in todo:
document = DesignSpaceProcessor(ufoVersion=outputUFOFormatVersion)
document.useVarlib = useVarlib
document.roundGeometry = roundGeometry
document.read(path)
try:
r = document.generateUFO(processRules=processRules)
results.append(r)
except:
if logger:
logger.exception("ufoProcessor error")
reader = None
return results
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 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)
# 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
font[newName].clear()
p = font[newName].getPointPen()
font[swapName].drawPoints(p)
font[newName].width = font[swapName].width
# remap the components
for g in font:
for c in g.components:
if c.baseGlyph == oldName:
c.baseGlyph = swapName
continue
for g in font:
for c in g.components:
if c.baseGlyph == newName:
c.baseGlyph = oldName
continue
for g in font:
for c in g.components:
if c.baseGlyph == swapName:
c.baseGlyph = newName
# change the names in groups
# the shapes will swap, that will invalidate the kerning
# so the names need to swap in the kerning as well.
newKerning = {}
for first, second in font.kerning.keys():
value = font.kerning[(first,second)]
if first == oldName:
first = newName
elif first == newName:
first = oldName
if second == oldName:
second = newName
elif second == newName:
second = oldName
newKerning[(first, second)] = value
font.kerning.clear()
font.kerning.update(newKerning)
for groupName, members in font.groups.items():
newMembers = []
for name in members:
if name == oldName:
newMembers.append(newName)
elif name == newName:
newMembers.append(oldName)
else:
newMembers.append(name)
font.groups[groupName] = newMembers
remove = []
for g in font:
if g.name.find(swapNameExtension)!=-1:
remove.append(g.name)
for r in remove:
del font[r]
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)
class DesignSpaceProcessor(DesignSpaceDocument):
"""
A subclassed DesignSpaceDocument that can
- process the document and generate finished UFOs with MutatorMath or varLib.model.
- read and write documents
- Replacement for the mutatorMath.ufo generator.
"""
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, 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
self._glyphMutators = {}
self._infoMutator = None
self._kerningMutator = None
self._kerningMutatorPairs = None
self.fonts = {}
self._fontsLoaded = False
self.mutedAxisNames = None # list of axisname that need to be muted
self.glyphNames = [] # list of all glyphnames
self.processRules = True
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):
# makes the instances
# option to execute the rules
# make sure we're not trying to overwrite a newer UFO format
self.loadFonts()
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)
v = 0
for instanceDescriptor in self.instances:
if instanceDescriptor.path is None:
continue
font = self.makeInstance(instanceDescriptor,
processRules,
glyphNames=glyphNames,
pairs=pairs,
bend=bend)
folder = os.path.dirname(os.path.abspath(instanceDescriptor.path))
path = instanceDescriptor.path
if not os.path.exists(folder):
os.makedirs(folder)
if os.path.exists(path):
existingUFOFormatVersion = getUFOVersion(path)
if existingUFOFormatVersion > self.ufoVersion:
self.problems.append("Can’t overwrite existing UFO%d with UFO%d." % (existingUFOFormatVersion, self.ufoVersion))
continue
font.save(path, self.ufoVersion)
self.problems.append("Generated %s as UFO%d"%(os.path.basename(path), self.ufoVersion))
return True
def getSerializedAxes(self):
return [a.serialize() for a in self.axes]
def getMutatorAxes(self):
# map the axis values?
d = collections.OrderedDict()
for a in self.axes:
d[a.name] = a.serialize()
return d
def _getAxisOrder(self):
return [a.name for a in self.axes]
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 getVariationModel(self, items, axes, bias=None):
# Return either a mutatorMath or a varlib.model object for calculating.
try:
if self.useVarlib:
# use the varlib variation model
try:
return dict(), VariationModelMutator(items, self.axes)
except (KeyError, AssertionError):
error = traceback.format_exc()
self.toolLog.append("UFOProcessor.getVariationModel error: %s" % error)
self.toolLog.append(items)
return {}, 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):
""" Returns a info mutator """
if self._infoMutator:
return self._infoMutator
infoItems = []
for sourceDescriptor in self.sources:
if sourceDescriptor.layerName is not None:
continue
loc = Location(sourceDescriptor.location)
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)
bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.serializedAxes, bias=infoBias)
return self._infoMutator
def getKerningMutator(self, pairs=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
kerningItems = []
foregroundLayers = [None, 'foreground', 'public.default']
if pairs is None:
for sourceDescriptor in self.sources:
if sourceDescriptor.layerName not in foregroundLayers:
continue
if not sourceDescriptor.muteKerning:
loc = Location(sourceDescriptor.location)
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:
# 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
loc = Location(sourceDescriptor.location)
# 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)
bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.serializedAxes, bias=kerningBias)
return self._kerningMutator
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
for aD in self.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):
ignoreMaster = True
del new[mutedAxisName]
return ignoreMaster, new
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)
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
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
return thing
def collectMastersForGlyph(self, glyphName, decomposeComponents=False):
""" Return a glyph mutator.defaultLoc
decomposeComponents = True causes the source glyphs to be decomposed first
before building the mutator. That gives you instances that do not depend
on a complete font. If you're calculating previews for instance.
XXX check glyphs in layers
"""
items = []
empties = []
foundEmpty = False
for sourceDescriptor in self.sources:
if not os.path.exists(sourceDescriptor.path):
#kthxbai
p = "\tMissing UFO at %s" % sourceDescriptor.path
if p not in self.problems:
self.problems.append(p)
continue
if glyphName in sourceDescriptor.mutedGlyphNames:
continue
thisIsDefault = self.default == sourceDescriptor
ignoreMaster, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames)
if ignoreMaster:
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 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)
items.append((loc, 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
def getNeutralFont(self):
# Return a font object for the neutral font
# self.fonts[self.default.name] ?
neutralLoc = self.newDefaultLocation(bend=True)
for sd in self.sources:
if sd.location == neutralLoc:
if sd.name in self.fonts:
#candidate = self.fonts[sd.name]
#if sd.layerName:
# if sd.layerName in candidate.layers:
return self.fonts[sd.name]
return None
def findDefault(self):
"""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)
for sourceDescriptor in self.sources:
if sourceDescriptor.location == default_location_design:
self.default = sourceDescriptor
return sourceDescriptor
return None
def newDefaultLocation(self, bend=False):
# overwrite from fontTools.newDefaultLocation
# we do not want this default location to be mapped.
loc = collections.OrderedDict()
for axisDescriptor in self.axes:
if bend:
loc[axisDescriptor.name] = axisDescriptor.map_forward(
axisDescriptor.default
)
else:
loc[axisDescriptor.name] = axisDescriptor.default
return loc
def loadFonts(self, reload=False):
# Load the fonts and find the default candidate based on the info flag
if self._fontsLoaded and not reload:
return
names = set()
for i, sourceDescriptor in enumerate(self.sources):
if sourceDescriptor.name is None:
# make sure it has a unique name
sourceDescriptor.name = "master.%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)))
names |= set(self.fonts[sourceDescriptor.name].keys())
else:
self.fonts[sourceDescriptor.name] = None
self.problems.append("source ufo not found at %s" % (sourceDescriptor.path))
self.glyphNames = list(names)
self._fontsLoaded = True
def getFonts(self):
# returnn a list of (font object, location) tuples
fonts = []
for sourceDescriptor in self.sources:
f = self.fonts.get(sourceDescriptor.name)
if f is not None:
fonts.append((f, sourceDescriptor.location))
return fonts
def makeInstance(self, instanceDescriptor,
doRules=False,
glyphNames=None,
pairs=None,
bend=False):
""" Generate a font object for this instance """
font = self._instantiateFont(None)
# make fonty things here
loc = Location(instanceDescriptor.location)
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)
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()
if kerningMutator is not None:
kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend)
kerningObject.extractKerning(font)
# make the info
try:
infoMutator = self.getInfoMutator()
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:
try:
infoInstanceObject = infoInstanceObject.round()
except AttributeError:
pass
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
# 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:
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:
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
if sourceDescriptor.copyFeatures:
if self.fonts[sourceDescriptor.name] is not None:
featuresText = self.fonts[sourceDescriptor.name].features.text
font.features.text = featuresText
# glyphs
if glyphNames:
selectedGlyphNames = glyphNames
else:
selectedGlyphNames = self.glyphNames
# add the glyphnames to the font.lib['public.glyphOrder']
if not 'public.glyphOrder' in font.lib.keys():
font.lib['public.glyphOrder'] = selectedGlyphNames
for glyphName in selectedGlyphNames:
try:
glyphMutator = self.getGlyphMutator(glyphName)
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 = {}
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))
try:
if not self.isAnisotropic(glyphInstanceLocation):
glyphInstanceObject = glyphMutator.makeInstance(glyphInstanceLocation, 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)
# 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:
pass
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
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
def isAnisotropic(self, location):
for v in location.values():
if type(v)==tuple:
return True
return False
def splitAnisotropic(self, location):
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
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)
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)
ufoProcessor-1.9.0/Lib/ufoProcessor/emptyPen.py 0000775 0000000 0000000 00000005555 13636425227 0021613 0 ustar 00root root 0000000 0000000 # coding: utf-8
from fontTools.pens.pointPen import AbstractPointPen
"""
Simple pen object to determine if a glyph contains any geometry.
"""
class EmptyPen(AbstractPointPen):
def __init__(self):
self.points = 0
self.contours = 0
self.components = 0
def beginPath(self, identifier=None, **kwargs):
pass
def endPath(self):
self.contours += 1
def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs):
self.points+=1
def addComponent(self, baseGlyphName=None, transformation=None, identifier=None, **kwargs):
self.components+=1
def getCount(self):
return self.points, self.contours, self.components
def isEmpty(self):
return self.points==0 and self.contours==0 and self.components==0
def checkGlyphIsEmpty(glyph, allowWhiteSpace=True):
"""
This will establish if the glyph is completely empty by drawing the glyph with an EmptyPen.
Additionally, the unicode of the glyph is checked against a list of known unicode whitespace
characters. This makes it possible to filter out glyphs that have a valid reason to be empty
and those that can be ignored.
"""
whiteSpace = [ 0x9, # horizontal tab
0xa, # line feed
0xb, # vertical tab
0xc, # form feed
0xd, # carriage return
0x20, # space
0x85, # next line
0xa0, # nobreak space
0x1680, # ogham space mark
0x180e, # mongolian vowel separator
0x2000, # en quad
0x2001, # em quad
0x2003, # en space
0x2004, # three per em space
0x2005, # four per em space
0x2006, # six per em space
0x2007, # figure space
0x2008, # punctuation space
0x2009, # thin space
0x200a, # hair space
0x2028, # line separator
0x2029, # paragraph separator
0x202f, # narrow no break space
0x205f, # medium mathematical space
0x3000, # ideographic space
]
emptyPen = EmptyPen()
glyph.drawPoints(emptyPen)
if emptyPen.isEmpty():
# we're empty?
if glyph.unicode in whiteSpace and allowWhiteSpace:
# are we allowed to be?
return False
return True
return False
if __name__ == "__main__":
p = EmptyPen()
assert p.isEmpty() == True
p.addPoint((0,0))
assert p.isEmpty() == False
p = EmptyPen()
assert p.isEmpty() == True
p.addComponent((0,0))
assert p.isEmpty() == False
ufoProcessor-1.9.0/Lib/ufoProcessor/sp3.py 0000664 0000000 0000000 00000053030 13636425227 0020503 0 ustar 00root root 0000000 0000000 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.interactionSourceshorizontalignoreverticalcom.letterror.skateboard.mutedSourcesIBM Plex Sans Condensed-Bold.ufoforegroundcom.letterror.skateboard.previewLocationweight0.0com.letterror.skateboard.previewTextSKATE
"""
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() ufoProcessor-1.9.0/Lib/ufoProcessor/varModels.py 0000664 0000000 0000000 00000015275 13636425227 0021743 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import
from fontTools.varLib.models import VariationModel, normalizeLocation
# alternative axisMapper that uses map_forward and map_backward from fonttools
class AxisMapper(object):
def __init__(self, axes):
# axes: list of axis axisdescriptors
self.axisOrder = [a.name for a in axes]
self.axisDescriptors = {}
for a in axes:
self.axisDescriptors[a.name] = a
def getMappedAxisValues(self):
values = {}
for axisName in self.axisOrder:
a = self.axisDescriptors[axisName]
values[axisName] = a.map_forward(a.minimum), a.map_forward(a.default), a.map_forward(a.maximum)
return values
def __call__(self, location):
return self.map_forward(location)
def _normalize(self, location):
new = {}
for axisName in location.keys():
new[axisName] = normalizeLocation(dict(w=location[axisName]), dict(w=self.axes[axisName]))
return new
def map_backward(self, location):
new = {}
for axisName in location.keys():
if not axisName in self.axisOrder:
continue
if axisName not in location:
continue
new[axisName] = self.axisDescriptors[axisName].map_backward(location[axisName])
return new
def map_forward(self, location):
new = {}
for axisName in location.keys():
if not axisName in self.axisOrder:
continue
if axisName not in location:
continue
new[axisName] = self.axisDescriptors[axisName].map_forward(location[axisName])
return new
class VariationModelMutator(object):
""" a thing that looks like a mutator on the outside,
but uses the fonttools varlib logic to calculate.
"""
def __init__(self, items, axes, model=None):
# items: list of locationdict, value tuples
# axes: list of axis dictionaried, not axisdescriptor objects.
# model: a model, if we want to share one
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)
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)
else:
self.model = model
self.masters = [b for a, b in items]
self.locations = [a for a, b in items]
def get(self, key):
if key in self.model.locations:
i = self.model.locations.index(key)
return self.masters[i]
return None
def getFactors(self, location):
nl = self._normalize(location)
return self.model.getScalars(nl)
def getMasters(self):
return self.masters
def getSupports(self):
return self.model.supports
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)
def _normalize(self, location):
return normalizeLocation(location, self.axes)
if __name__ == "__main__":
from fontTools.designspaceLib import AxisDescriptor
a = AxisDescriptor()
a.name = "A"
a.tag = "A___"
a.minimum = 40
a.default = 45
a.maximum = 50
a.map = [(40, -100), (45,0), (50, 100)]
b = AxisDescriptor()
b.name = "B"
b.tag = "B___"
b.minimum = 0
b.default = 50
b.maximum = 100
axes = [a,b]
items = [
({}, 0),
#({'A': 50, 'B': 50}, 10),
({'A': 40}, 10),
({'B': 50}, -10),
#({'B': -100}, -10), # this will fail, no extrapolating
({'A': 40, 'B': 50}, 22),
#({'A': 55, 'B': 75}, 1),
#({'A': 65, 'B': 99}, 1),
]
am = AxisMapper(axes)
#assert am(dict(A=0)) == {'A': 45}
print(1, am(dict(A=40, B=None)))
#assert am(dict(A=0, B=100)) == {'A': 45}
# mm = VariationModelMutator(items, axes)
# assert mm.makeInstance(dict(A=0, B=0)) == 0
# assert mm.makeInstance(dict(A=100, B=0)) == 10
# assert mm.makeInstance(dict(A=0, B=100)) == 10
# assert mm.makeInstance(dict(A=100, B=100)) == 0
# assert mm.makeInstance(dict(A=50, B=0),bend=False) == 5
# assert mm.makeInstance(dict(A=50, B=0),bend=True) == 2.5
# mm.getReach()
a = AxisDescriptor()
a.name = "Weight"
a.tag = "wght"
a.minimum = 300
a.default = 300
a.maximum = 600
a.map = ((300,0), (600,1000))
b = AxisDescriptor()
b.name = "Width"
b.tag = "wdth"
b.minimum = 200
b.default = 800
b.maximum = 800
b.map = ((200,5), (800,10))
axes = [a,b]
aam = AxisMapper(axes)
print(2, aam({}))
print(2, aam(dict(Weight=300, Width=200)))
print(2, aam(dict(Weight=0, Width=0)))
print(2, 'getMappedAxisValues', aam.getMappedAxisValues())
print(2, aam.map_forward({'Weight': 0}))
# fine. sources are in user values. Progress.
# are they?
items = [
({}, 13),
({'Weight': 0, 'Width': 5}, 20),
({'Weight': 1000, 'Width': 10}, 60),
]
mm = VariationModelMutator(items, axes)
# ok so normalise uses designspace coordinates
print(3, "_normalize", mm._normalize(dict(Weight=0, Width=1000)))
# oh wow, master locations need to be in user coordinates!?
print('mm.makeInstance(dict())', mm.makeInstance(dict()))
assert mm.makeInstance(dict()) == 13
assert mm.makeInstance(dict(Weight=0, Width=10)) == 13
l = dict(Weight=400, Width=200)
lmapped = aam(l)
print('0 loc', l)
print('0 loc mapped', lmapped)
print('1 with map', mm.makeInstance(l, bend=True))
print('1 without map', mm.makeInstance(l, bend=False))
print('2 with map', mm.makeInstance(lmapped, bend=True))
print('2 without map', mm.makeInstance(lmapped, bend=False))
ufoProcessor-1.9.0/README.md 0000664 0000000 0000000 00000011506 13636425227 0015506 0 ustar 00root root 0000000 0000000 [](https://travis-ci.org/LettError/ufoProcessor)
[](https://pypi.org/project/ufoprocessor)
# ufoProcessor
Python package based on the **designSpaceDocument** from [fontTools.designspaceLib](https://github.com/fonttools/fonttools/tree/master/Lib/fontTools/designspaceLib)) specifically to _process_ and _generate_ instances for UFO files, glyphs and other data.
* Collect source materials
* Provide mutators for specific glyphs, font info, kerning so that other tools can generate partial instances. Either from `MutatorMath` or `fonttools varlib.model`.
* Support designspace format 4 with layers.
* Apply avar-like designspace bending
* Apply rules
* Generate actual UFO instances in formats 2 and 3.
* Round geometry as requested
* Try to stay up to date with fontTools
* Baseclass for tools that need access to designspace data.
## Usage
The easiest way to use ufoProcessor is to call `build(designspacePath)`
* **documentPath**: path to the designspace file.
* **outputUFOFormatVersion**: integer, 2, 3. Format for generated UFOs. Note: can be different from source UFO format.
* **roundGeometry**: bool, if the geometry needs to be rounded to whole integers. This affects glyphs, metrics, kerning, select font info.
* **processRules**: bool, when generating UFOs, execute designspace rules as swaps.
* **logger**: optional logger object.
* **documentPath**: filepath to the .designspace document
* **outputUFOFormatVersion**: ufo format for output, default is the current, so 3.
* **useVarlib**: True if you want the geometry to be generated with `varLib.model` instead of `mutatorMath`.
## Examples
Generate all the instances (using the varlib model, no rounding):
```python
import ufoProcessor
myPath = "myDesignspace.designspace"
ufoProcessor.build(myPath)
```
Generate all the instances (using the varlib model, but round all the geometry to integers):
```python
import ufoProcessor
myPath = "myDesignspace.designspace"
ufoProcessor.build(myPath, roundGeometry=True)
```
Generate all the instances (using the mutatormath model, no rounding):
```python
import ufoProcessor
myPath = "myDesignspace.designspace"
ufoProcessor.build(myPath, useVarlib=False)
```
Generate an instance for one glyph, `"A"` at `width=100, weight=200`. (assuming the designspace has those axes and the masters have that glyph)
```python
import ufoProcessor
myPath = "myDesignspace.designspace"
doc = ufoProcessor.DesignSpaceProcessor()
doc.read(myPath)
doc.loadFonts()
glyphMutator = doc.getGlyphMutator("A")
instance = glyphMutator.makeInstance(Location(width=100, weight=200)
```
Depending on the setting for `usevarlib`, the `glyphMutator` object returned by `getGlyphMutator` in the example above can either be a `MutatorMath.Mutator`, or a `VariationModelMutator` object. That uses the `fontTools.varLib.models.VariationModel` but it is wrapped and can be called as a Mutator object to generate instances. This way `DesignSpaceProcessor` does not need to know much about which math model it is using.
## Convert Superpolator to designspace
The ufoProcessor.sp3 module has some tools for interpreting Superpolator .sp3 documents. Not all data is migrated. But the important geometry is there. Given that Superpolator can read designspace files, there is hopefully no real need for a writer. Note that this conversion is lossy.
* Axis
* dimensions
* name
* tag
* Source
* ufo path
* familyname, stylename
* mute state (stored in lib)
* location
* Instance
* ufo path
* familyname, stylename
* stylemap names
* location
* Rules
* *Simple Rules* are wrapped in a conditionset.
* most of the really old Superpolator rules can't be converted. Only rules with `<` or `>` operators are used.
* Some Superpolator user prefs
* Preview text
* Which axes used vertically and horizontally
## Usage
```python
# convert sp3 file to designspace
# first make a new designspace doc object
doc = DesignSpaceDocument()
# feed it to the reader
reader = SuperpolatorReader(sp3path, doc)
reader.read()
# now you can work with it, even save it
doc.write(designspacePath)
```
Indeed that last example comes from this convenience function:
```sp3_to_designspace(sp3path, designspacePath=None)```
If designspacePath = None, sp3_to_designspace will use the same path for the output, but replace the `.sp3` with `.designspace` extension. If the file exists it will overwrite.
## Notes
* Glyph-specific masters in instances are ignored.
* Instance notes are ignored.
* Designspace geometry requires the default master to be on the default value of each axis. Superpolator handled that differently, it would find the default dynamically. So it is possible that converted designspaces need some work in terms of the basic structure. That can't be handled automatically.
ufoProcessor-1.9.0/Tests/ 0000775 0000000 0000000 00000000000 13636425227 0015326 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/ 0000775 0000000 0000000 00000000000 13636425227 0017637 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/ 0000775 0000000 0000000 00000000000 13636425227 0022607 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist 0000664 0000000 0000000 00000001602 13636425227 0025505 0 ustar 00root root 0000000 0000000
ascender750capHeight500descender-250familyNameBenderTestguidelinespostscriptBlueValuespostscriptFamilyBluespostscriptFamilyOtherBluespostscriptOtherBluespostscriptStemSnapHpostscriptStemSnapVstyleNameOneunitsPerEm1000xHeight500
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/ 0000775 0000000 0000000 00000000000 13636425227 0026233 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist 0000664 0000000 0000000 00000000267 13636425227 0031152 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist 0000664 0000000 0000000 00000000370 13636425227 0031300 0 ustar 00root root 0000000 0000000
color0,0.8,0.2,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/ 0000775 0000000 0000000 00000000000 13636425227 0024115 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif 0000664 0000000 0000000 00000001051 13636425227 0025175 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist 0000664 0000000 0000000 00000000355 13636425227 0027032 0 ustar 00root root 0000000 0000000
aa.glif
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist 0000664 0000000 0000000 00000000367 13636425227 0027170 0 ustar 00root root 0000000 0000000
color1,0.75,0,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/kerning.plist 0000664 0000000 0000000 00000000431 13636425227 0025317 0 ustar 00root root 0000000 0000000
aa-100
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/layercontents.plist 0000664 0000000 0000000 00000000577 13636425227 0026567 0 ustar 00root root 0000000 0000000
foregroundglyphsbackgroundglyphs.background
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/lib.plist 0000664 0000000 0000000 00000015773 13636425227 0024447 0 ustar 00root root 0000000 0000000
com.defcon.sortDescriptorascendingLatin-1typecharacterSetcom.typemytype.robofont.compileSettings.autohintcom.typemytype.robofont.compileSettings.checkOutlinescom.typemytype.robofont.compileSettings.createDummyDSIGcom.typemytype.robofont.compileSettings.decomposecom.typemytype.robofont.compileSettings.generateFormat0com.typemytype.robofont.compileSettings.releaseModecom.typemytype.robofont.italicSlantOffset0com.typemytype.robofont.segmentTypecurvecom.typemytype.robofont.shouldAddPointsInSplineConversion1public.glyphOrderspaceexclamquotedblnumbersigndollarpercentampersandparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingcurrencyyenbrokenbarsectiondieresiscopyrightordfeminineguillemotleftlogicalnotregisteredmacrondegreeplusminustwosuperiorthreesuperioracutemuparagraphperiodcenteredcedillaonesuperiorordmasculineguillemotrightonequarteronehalfthreequartersquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisdotlessicircumflexcaronbrevedotaccentringogonektildehungarumlautquoteleftquoterightminus
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/metainfo.plist 0000664 0000000 0000000 00000000476 13636425227 0025475 0 ustar 00root root 0000000 0000000
creatorcom.github.fonttools.ufoLibformatVersion3
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/ 0000775 0000000 0000000 00000000000 13636425227 0022610 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist 0000664 0000000 0000000 00000001602 13636425227 0025506 0 ustar 00root root 0000000 0000000
ascender750capHeight601descender-250familyNameBenderTestguidelinespostscriptBlueValuespostscriptFamilyBluespostscriptFamilyOtherBluespostscriptOtherBluespostscriptStemSnapHpostscriptStemSnapVstyleNameTwounitsPerEm1000xHeight500
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/ 0000775 0000000 0000000 00000000000 13636425227 0026234 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist 0000664 0000000 0000000 00000000267 13636425227 0031153 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist 0000664 0000000 0000000 00000000370 13636425227 0031301 0 ustar 00root root 0000000 0000000
color0,0.8,0.2,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/ 0000775 0000000 0000000 00000000000 13636425227 0024116 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif 0000664 0000000 0000000 00000001053 13636425227 0025200 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist 0000664 0000000 0000000 00000000355 13636425227 0027033 0 ustar 00root root 0000000 0000000
aa.glif
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist 0000664 0000000 0000000 00000000367 13636425227 0027171 0 ustar 00root root 0000000 0000000
color1,0.75,0,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/kerning.plist 0000664 0000000 0000000 00000000431 13636425227 0025320 0 ustar 00root root 0000000 0000000
aa-200
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/layercontents.plist 0000664 0000000 0000000 00000000577 13636425227 0026570 0 ustar 00root root 0000000 0000000
foregroundglyphsbackgroundglyphs.background
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/lib.plist 0000664 0000000 0000000 00000015773 13636425227 0024450 0 ustar 00root root 0000000 0000000
com.defcon.sortDescriptorascendingLatin-1typecharacterSetcom.typemytype.robofont.compileSettings.autohintcom.typemytype.robofont.compileSettings.checkOutlinescom.typemytype.robofont.compileSettings.createDummyDSIGcom.typemytype.robofont.compileSettings.decomposecom.typemytype.robofont.compileSettings.generateFormat0com.typemytype.robofont.compileSettings.releaseModecom.typemytype.robofont.italicSlantOffset0com.typemytype.robofont.segmentTypecurvecom.typemytype.robofont.shouldAddPointsInSplineConversion1public.glyphOrderspaceexclamquotedblnumbersigndollarpercentampersandparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingcurrencyyenbrokenbarsectiondieresiscopyrightordfeminineguillemotleftlogicalnotregisteredmacrondegreeplusminustwosuperiorthreesuperioracutemuparagraphperiodcenteredcedillaonesuperiorordmasculineguillemotrightonequarteronehalfthreequartersquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisdotlessicircumflexcaronbrevedotaccentringogonektildehungarumlautquoteleftquoterightminus
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/metainfo.plist 0000664 0000000 0000000 00000000476 13636425227 0025476 0 ustar 00root root 0000000 0000000
creatorcom.github.fonttools.ufoLibformatVersion3
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/ 0000775 0000000 0000000 00000000000 13636425227 0022611 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist 0000664 0000000 0000000 00000001604 13636425227 0025511 0 ustar 00root root 0000000 0000000
ascender750capHeight700descender-250familyNameBenderTestguidelinespostscriptBlueValuespostscriptFamilyBluespostscriptFamilyOtherBluespostscriptOtherBluespostscriptStemSnapHpostscriptStemSnapVstyleNameThreeunitsPerEm1000xHeight500
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/ 0000775 0000000 0000000 00000000000 13636425227 0026235 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist 0000664 0000000 0000000 00000000267 13636425227 0031154 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist 0000664 0000000 0000000 00000000370 13636425227 0031302 0 ustar 00root root 0000000 0000000
color0,0.8,0.2,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/ 0000775 0000000 0000000 00000000000 13636425227 0024117 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif 0000664 0000000 0000000 00000001047 13636425227 0025204 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist 0000664 0000000 0000000 00000000355 13636425227 0027034 0 ustar 00root root 0000000 0000000
aa.glif
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist 0000664 0000000 0000000 00000000367 13636425227 0027172 0 ustar 00root root 0000000 0000000
color1,0.75,0,0.7
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/kerning.plist 0000664 0000000 0000000 00000000431 13636425227 0025321 0 ustar 00root root 0000000 0000000
aa-100
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/layercontents.plist 0000664 0000000 0000000 00000000577 13636425227 0026571 0 ustar 00root root 0000000 0000000
foregroundglyphsbackgroundglyphs.background
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/lib.plist 0000664 0000000 0000000 00000015773 13636425227 0024451 0 ustar 00root root 0000000 0000000
com.defcon.sortDescriptorascendingLatin-1typecharacterSetcom.typemytype.robofont.compileSettings.autohintcom.typemytype.robofont.compileSettings.checkOutlinescom.typemytype.robofont.compileSettings.createDummyDSIGcom.typemytype.robofont.compileSettings.decomposecom.typemytype.robofont.compileSettings.generateFormat0com.typemytype.robofont.compileSettings.releaseModecom.typemytype.robofont.italicSlantOffset0com.typemytype.robofont.segmentTypecurvecom.typemytype.robofont.shouldAddPointsInSplineConversion1public.glyphOrderspaceexclamquotedblnumbersigndollarpercentampersandparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingcurrencyyenbrokenbarsectiondieresiscopyrightordfeminineguillemotleftlogicalnotregisteredmacrondegreeplusminustwosuperiorthreesuperioracutemuparagraphperiodcenteredcedillaonesuperiorordmasculineguillemotrightonequarteronehalfthreequartersquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisdotlessicircumflexcaronbrevedotaccentringogonektildehungarumlautquoteleftquoterightminus
ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/metainfo.plist 0000664 0000000 0000000 00000000476 13636425227 0025477 0 ustar 00root root 0000000 0000000
creatorcom.github.fonttools.ufoLibformatVersion3
ufoProcessor-1.9.0/Tests/20190830 benders/instances/ 0000775 0000000 0000000 00000000000 13636425227 0021626 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/ 0000775 0000000 0000000 00000000000 13636425227 0025653 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist 0000664 0000000 0000000 00000001601 13636425227 0030550 0 ustar 00root root 0000000 0000000
ascender750capHeight997descender-250familyNameBenderTestguidelinespostscriptBlueValuespostscriptFamilyBluespostscriptFamilyOtherBluespostscriptOtherBluespostscriptStemSnapHpostscriptStemSnapVstyleNameFarOutunitsPerEm1000.0xHeight500
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/ 0000775 0000000 0000000 00000000000 13636425227 0027161 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif 0000664 0000000 0000000 00000001125 13636425227 0030243 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist 0000664 0000000 0000000 00000000355 13636425227 0032076 0 ustar 00root root 0000000 0000000
aa.glif
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist 0000664 0000000 0000000 00000000430 13636425227 0030362 0 ustar 00root root 0000000 0000000
aa200
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist 0000664 0000000 0000000 00000000437 13636425227 0031626 0 ustar 00root root 0000000 0000000
public.defaultglyphs
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist 0000664 0000000 0000000 00000016217 13636425227 0027505 0 ustar 00root root 0000000 0000000
com.defcon.sortDescriptorascendingLatin-1typecharacterSetcom.typemytype.robofont.compileSettings.autohintcom.typemytype.robofont.compileSettings.checkOutlinescom.typemytype.robofont.compileSettings.createDummyDSIGcom.typemytype.robofont.compileSettings.decomposecom.typemytype.robofont.compileSettings.generateFormat0com.typemytype.robofont.compileSettings.releaseModecom.typemytype.robofont.italicSlantOffset0com.typemytype.robofont.segmentTypecurvecom.typemytype.robofont.shouldAddPointsInSplineConversion1designspace.locationtest2500.0public.glyphOrderspaceexclamquotedblnumbersigndollarpercentampersandparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingcurrencyyenbrokenbarsectiondieresiscopyrightordfeminineguillemotleftlogicalnotregisteredmacrondegreeplusminustwosuperiorthreesuperioracutemuparagraphperiodcenteredcedillaonesuperiorordmasculineguillemotrightonequarteronehalfthreequartersquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisdotlessicircumflexcaronbrevedotaccentringogonektildehungarumlautquoteleftquoterightminus
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist 0000664 0000000 0000000 00000000476 13636425227 0030541 0 ustar 00root root 0000000 0000000
creatorcom.github.fonttools.ufoLibformatVersion3
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/ 0000775 0000000 0000000 00000000000 13636425227 0027065 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist 0000664 0000000 0000000 00000001613 13636425227 0031765 0 ustar 00root root 0000000 0000000
ascender750capHeight601descender-250familyNameBenderTestguidelinespostscriptBlueValuespostscriptFamilyBluespostscriptFamilyOtherBluespostscriptOtherBluespostscriptStemSnapHpostscriptStemSnapVstyleNameIntermediateunitsPerEm1000xHeight500
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/ 0000775 0000000 0000000 00000000000 13636425227 0030373 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif 0000664 0000000 0000000 00000001054 13636425227 0031456 0 ustar 00root root 0000000 0000000
contents.plist 0000664 0000000 0000000 00000000355 13636425227 0033231 0 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs
aa.glif
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist 0000664 0000000 0000000 00000000431 13636425227 0031575 0 ustar 00root root 0000000 0000000
aa-200
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist 0000664 0000000 0000000 00000000437 13636425227 0033040 0 ustar 00root root 0000000 0000000
public.defaultglyphs
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist 0000664 0000000 0000000 00000016216 13636425227 0030716 0 ustar 00root root 0000000 0000000
com.defcon.sortDescriptorascendingLatin-1typecharacterSetcom.typemytype.robofont.compileSettings.autohintcom.typemytype.robofont.compileSettings.checkOutlinescom.typemytype.robofont.compileSettings.createDummyDSIGcom.typemytype.robofont.compileSettings.decomposecom.typemytype.robofont.compileSettings.generateFormat0com.typemytype.robofont.compileSettings.releaseModecom.typemytype.robofont.italicSlantOffset0com.typemytype.robofont.segmentTypecurvecom.typemytype.robofont.shouldAddPointsInSplineConversion1designspace.locationtest500.0public.glyphOrderspaceexclamquotedblnumbersigndollarpercentampersandparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingcurrencyyenbrokenbarsectiondieresiscopyrightordfeminineguillemotleftlogicalnotregisteredmacrondegreeplusminustwosuperiorthreesuperioracutemuparagraphperiodcenteredcedillaonesuperiorordmasculineguillemotrightonequarteronehalfthreequartersquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisdotlessicircumflexcaronbrevedotaccentringogonektildehungarumlautquoteleftquoterightminus
ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist 0000664 0000000 0000000 00000000476 13636425227 0031753 0 ustar 00root root 0000000 0000000
creatorcom.github.fonttools.ufoLibformatVersion3
ufoProcessor-1.9.0/Tests/20190830 benders/test.py 0000664 0000000 0000000 00000003673 13636425227 0021201 0 ustar 00root root 0000000 0000000 """
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()) ufoProcessor-1.9.0/Tests/kerningTest.py 0000664 0000000 0000000 00000003550 13636425227 0020200 0 ustar 00root root 0000000 0000000 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())
ufoProcessor-1.9.0/Tests/mathKerningTest.py 0000664 0000000 0000000 00000000561 13636425227 0021011 0 ustar 00root root 0000000 0000000 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())
ufoProcessor-1.9.0/Tests/spReader_testdocs/ 0000775 0000000 0000000 00000000000 13636425227 0021003 5 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1.sp3 0000664 0000000 0000000 00000010123 13636425227 0025774 0 ustar 00root root 0000000 0000000
ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace 0000664 0000000 0000000 00000010120 13636425227 0031622 0 ustar 00root root 0000000 0000000
com.letterror.skateboard.interactionSourceshorizontalwidthignoreverticalweightcom.letterror.skateboard.mutedSourcesufo/MutatorSansLightCondensed.ufoforegroundufo/MutatorSansBoldCondensed.ufoforegroundcom.letterror.skateboard.previewTextVAcom.superpolator.dataexpandRuleshorizontalPreviewAxiswidthincludeLegacyRulesinstancefolderinstanceskeepWorkFileslineInvertedlineStackedlinedlineViewFilledoutputFormatUFO3.0previewtextVAroundGeometryverticalPreviewAxisweight
ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace 0000664 0000000 0000000 00000010066 13636425227 0034001 0 ustar 00root root 0000000 0000000
com.letterror.skateboard.interactionSourceshorizontalwidthignoreverticalweightcom.letterror.skateboard.mutedSourcesufo/MutatorSansLightCondensed.ufoforegroundufo/MutatorSansBoldCondensed.ufoforegroundcom.letterror.skateboard.previewTextVAcom.superpolator.dataexpandRuleshorizontalPreviewAxiswidthincludeLegacyRulesinstancefolderinstanceskeepWorkFileslineInvertedlineStackedlinedlineViewFilledoutputFormatUFO3.0previewtextVAroundGeometryverticalPreviewAxisweight
ufoProcessor-1.9.0/Tests/testOpenTypeAttrs_3.py 0000664 0000000 0000000 00000007117 13636425227 0021611 0 ustar 00root root 0000000 0000000
# interesting problem with mathInfo objects
# when processing attributes that do not exist in the other object.
# This can happen when one of the masters has more values set than the other.
# For instance when preparing the "deltas", the objects with relative data
# In these tests mathINfo objects are added, multiplied and subtracted
# None - value, None + value, value + value and None + None
from fontParts.world import RFont
from fontMath import MathInfo
import fontMath
ok = "✅"
notOk = "🚫"
def extractValue(m, attrName, expected=None):
f = RFont()
f.info.fromMathInfo(m)
v = getattr(f.info, attrName)
if v == expected:
t = ok
else:
t = notOk
print("\t", t, v, "\t", attrName)
# master 1
f1 = RFont()
f1.info.ascender = 800 # present in both masters
f1.info.openTypeHheaAscender = 330 # example value that won't be in master 2
m1 = f1.info.toMathInfo()
f2 = RFont()
f2.info.ascender = 750 # present in both masters
f2.info.openTypeOS2TypoAscender = 555 # example value that won't be in master 1
m2 = f2.info.toMathInfo()
# subtraction
m3 = m1 - m2
print("\nm1 \"default\"")
extractValue(m1, "ascender", 800)
extractValue(m1, "openTypeOS2TypoAscender", None)
extractValue(m1, "openTypeHheaAscender", 330)
print("\nm2")
extractValue(m2, "ascender", 750)
extractValue(m2, "openTypeOS2TypoAscender", 555)
extractValue(m2, "openTypeHheaAscender", None) # not set
print("\nm3 = m1 - m2")
extractValue(m3, "ascender", 50)
extractValue(m3, "openTypeOS2TypoAscender", 0)
extractValue(m3, "openTypeHheaAscender", 0)
# addition
m3b = m1 + m2
print("\nm3b = m1 + m2")
extractValue(m3b, "ascender", 1550)
extractValue(m3b, "openTypeOS2TypoAscender", 555) # None + 555
extractValue(m3b, "openTypeHheaAscender", 330)
m3c = m1 + m1
print("\nm3c = m1 + m1")
extractValue(m3c, "ascender", 1600)
extractValue(m3c, "openTypeOS2TypoAscender", None) # None + None
extractValue(m3c, "openTypeHheaAscender", 660) # 330 + 330
m3d = m2 + m2
print("\nm3d = m2 + m2")
extractValue(m3d, "ascender", 1500)
extractValue(m3d, "openTypeOS2TypoAscender", 1110) # 555 + 555
extractValue(m3d, "openTypeHheaAscender", None) # None + None
m3e = m2 - m2
print("\nm3e = m2 - m2")
extractValue(m3e, "ascender", 0)
extractValue(m3e, "openTypeOS2TypoAscender", 0) # 555 - 555
extractValue(m3e, "openTypeHheaAscender", None) # None - None
m3f = m1 - m1
print("\nm3e = m1 - m1")
extractValue(m3f, "ascender", 0)
extractValue(m3f, "openTypeOS2TypoAscender", None) # None - None
extractValue(m3f, "openTypeHheaAscender", 0) # 330 - 330
# if c = a - b
# then a = c + b
m4 = m3 + m2
print("\nm4 = m3 + m2")
extractValue(m4, "ascender", 800)
extractValue(m4, "openTypeOS2TypoAscender", 555)
extractValue(m4, "openTypeHheaAscender", 0)
m5 = .5 * m1
m6 = 2 * m5
print("\nm5 half")
extractValue(m5, "ascender", 400)
extractValue(m5, "openTypeOS2TypoAscender", None)
extractValue(m5, "openTypeHheaAscender", 165)
print("\nm6 duped again")
extractValue(m6, "ascender", 800)
extractValue(m6, "openTypeOS2TypoAscender", None)
extractValue(m6, "openTypeHheaAscender", 330)
f = .6666
m7 = m1 + f * (m2-m1)
print("\nm7 interpolated with %3.3f" % f)
extractValue(m7, "ascender", 766.67)
extractValue(m7, "openTypeOS2TypoAscender", 0)
extractValue(m7, "openTypeHheaAscender", 330)
# maybe it should be like this:
# - = 0
# + =
# - = 0
# + =
# scalar * =
# / factor =
# This works in mutatormath as it started with the actual default object
# but varlib starts with the esult of 1.0 * default object.
# ufoProcessor-1.9.0/Tests/tests.py 0000664 0000000 0000000 00000037312 13636425227 0017050 0 ustar 00root root 0000000 0000000 # standalone test
import shutil
import os
import defcon.objects.font
import fontParts.fontshell.font
import logging
from ufoProcessor import *
# 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
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]
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
g.unicode = uni
uni += 1
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
if addSupportLayer:
font.newLayer('support')
layer = font.layers['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"]
comp = g.instantiateComponent()
comp.baseGlyph = "wide"
comp.offset = (0,0)
g.appendComponent(comp)
g.width = font['wide'].width
font.newGlyph("narrow.component")
g = font["narrow.component"]
comp = g.instantiateComponent()
comp.baseGlyph = "narrow"
comp.offset = (0,0)
g.appendComponent(comp)
g.width = font['narrow'].width
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 _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")
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")
path6 = os.path.join(rootPath, "instances", "extrapolate", "geometryInstance%s.ufo")
f1 = Font()
fillInfo(f1)
addGlyphs(f1, 100, addSupportLayer=False)
addExtraGlyph(f1, "extra.glyph.for.neutral")
f1.features.text = u"# features text from master 1"
f2 = Font()
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']
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])
_create_parent_dir(path1)
_create_parent_dir(path2)
f1.save(path1, 3)
f2.save(path2, 3)
return path1, path2, path3, path4, path5, path6
def _makeSwapFonts(rootPath):
""" Make some test fonts that have the kerning problem."""
path1 = os.path.join(rootPath, "Swap.ufo")
path2 = os.path.join(rootPath, "Swapped.ufo")
f1 = Font()
fillInfo(f1)
addGlyphs(f1, 100)
f1.features.text = u"# features text from master 1"
f1.info.ascender = 800
f1.info.descender = -200
f1.kerning[('glyphOne', 'glyphOne')] = -10
f1.kerning[('glyphTwo', 'glyphTwo')] = 10
f1.save(path1, 2)
return path1, path2
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):
# 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))
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)]
d.addAxis(a)
s1 = SourceDescriptor()
s1.path = m1
s1.location = dict(pop=a.map_forward(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"
s2.muteKerning = True
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)
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
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
i.postScriptFontName = "TestFamily PSName %s" % i.styleName
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 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()
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()
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(pop=500, name="This is a named location, stored in the document lib.")
d.write(docPath)
def _testGenerateInstances(docPath, useVarlib=True, useDefcon=True, roundGeometry=False):
# execute the test document
if useDefcon:
d = DesignSpaceProcessor_using_defcon(useVarlib=useVarlib)
else:
d = DesignSpaceProcessor_using_fontparts(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"
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.
d = DesignSpaceProcessor(useVarlib=useVarlib)
d.read(docPath)
for instance in d.instances:
if os.path.exists(instance.path):
f = Font(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 ['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()
ufoProcessor-1.9.0/Tests/tests_fp.py 0000664 0000000 0000000 00000024057 13636425227 0017537 0 ustar 00root root 0000000 0000000 # 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
if addSupportLayer:
font.newLayer('support')
print(n for n in font.layers if n.name == 'support')
layer = font.layers['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")
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 = [(500,250)]
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.Font(srcPath)
swapGlyphNames(f, "narrow", "wide")
f.info.styleName = "Swapped"
f.save(dstPath)
# test the results in newly opened fonts
old = fontParts.fontshell.Font(srcPath)
new = fontParts.fontshell.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"
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.Font(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)
ufoProcessor-1.9.0/requirements.txt 0000664 0000000 0000000 00000000000 13636425227 0017476 0 ustar 00root root 0000000 0000000 ufoProcessor-1.9.0/setup.cfg 0000664 0000000 0000000 00000000127 13636425227 0016045 0 ustar 00root root 0000000 0000000 [bdist_wheel]
universal = 1
[sdist]
formats = zip
[metadata]
license_file = LICENSE
ufoProcessor-1.9.0/setup.py 0000664 0000000 0000000 00000003421 13636425227 0015736 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import sys
from setuptools import setup, find_packages
from io import open
needs_wheel = {'bdist_wheel'}.intersection(sys.argv)
wheel = ['wheel'] if needs_wheel else []
with open('README.md', 'r', encoding='utf-8') as f:
long_description = f.read()
setup(
name="ufoProcessor",
use_scm_version={"write_to": "Lib/ufoProcessor/_version.py"},
description="Read, write and generate UFOs with designspace data.",
long_description=long_description,
long_description_content_type='text/markdown',
author="Erik van Blokland",
author_email="erik@letterror.com",
url="https://github.com/LettError/ufoProcessor",
keywords='font development tools',
license="MIT",
packages=find_packages("Lib"),
package_dir={"": "Lib"},
python_requires='>=2.7',
setup_requires=wheel + ["setuptools_scm"],
install_requires=[
"defcon[lxml]>=0.6.0",
"fontMath>=0.4.9",
"fontParts>=0.8.2",
"fontTools[ufo,lxml]>=3.32.0",
"mutatorMath>=2.1.2",
],
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Environment :: Other Environment",
"Intended Audience :: Developers",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Topic :: Multimedia :: Graphics",
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
"Topic :: Multimedia :: Graphics :: Editors :: Vector-Based",
"Topic :: Software Development :: Libraries :: Python Modules",
],
)