pax_global_header00006660000000000000000000000064136364252270014524gustar00rootroot0000000000000052 comment=88323a406152636f60f78e46cd41106445915877 ufoProcessor-1.9.0/000077500000000000000000000000001363642522700142245ustar00rootroot00000000000000ufoProcessor-1.9.0/.gitignore000066400000000000000000000013671363642522700162230ustar00rootroot00000000000000# 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.yml000066400000000000000000000022041363642522700163330ustar00rootroot00000000000000language: 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/LICENSE000066400000000000000000000021151363642522700152300ustar00rootroot00000000000000Copyright (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/000077500000000000000000000000001363642522700147325ustar00rootroot00000000000000ufoProcessor-1.9.0/Lib/ufoProcessor/000077500000000000000000000000001363642522700174235ustar00rootroot00000000000000ufoProcessor-1.9.0/Lib/ufoProcessor/__init__.py000066400000000000000000001207751363642522700215500ustar00rootroot00000000000000# 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.py000077500000000000000000000055551363642522700216130ustar00rootroot00000000000000# 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.py000066400000000000000000000530301363642522700205030ustar00rootroot00000000000000import os import glob from fontTools.misc.loggingTools import LogMixin from fontTools.designspaceLib import DesignSpaceDocument, AxisDescriptor, SourceDescriptor, RuleDescriptor, InstanceDescriptor try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.ElementTree as ET # Reader that parses Superpolator documents and buidls designspace objects. # Note: the Superpolator document format precedes the designspace documnt format. # For now I just want to migrate data out of Superpolator into designspace. # So not all data will migrate, just the stuff we can use. """ com.letterror.skateboard.interactionSources horizontal ignore vertical com.letterror.skateboard.mutedSources IBM Plex Sans Condensed-Bold.ufo foreground com.letterror.skateboard.previewLocation weight 0.0 com.letterror.skateboard.previewText SKATE """ superpolatorDataLibKey = "com.superpolator.data" # lib key for Sp data in .designspace skateboardInteractionSourcesKey = "com.letterror.skateboard.interactionSources" skateboardMutedSourcesKey = "com.letterror.skateboard.mutedSources" skipExportKey = "public.skipExportGlyphs" skateboardPreviewLocationsKey = "com.letterror.skateboard.previewLocation" skateboardPreviewTextKey = "com.letterror.skateboard.previewText" class SuperpolatorReader(LogMixin): ruleDescriptorClass = RuleDescriptor axisDescriptorClass = AxisDescriptor sourceDescriptorClass = SourceDescriptor instanceDescriptorClass = InstanceDescriptor def __init__(self, documentPath, documentObject, convertRules=True, convertData=True, anisotropic=False): self.path = documentPath self.documentObject = documentObject self.convertRules = convertRules self.convertData = convertData self.allowAnisotropic = anisotropic # maybe add conversion options later tree = ET.parse(self.path) self.root = tree.getroot() self.documentObject.formatVersion = self.root.attrib.get("format", "3.0") self.axisDefaults = {} self._strictAxisNames = True @classmethod def fromstring(cls, string, documentObject): f = BytesIO(tobytes(string, encoding="utf-8")) self = cls(f, documentObject) self.path = None return self def read(self): self.readAxes() if self.convertData: self.readData() if self.convertRules: self.readOldRules() self.readSimpleRules() self.readSources() self.readInstances() def readData(self): # read superpolator specific data, view prefs etc. # if possible convert it to skateboard interactionSources = {'horizontal': [], 'vertical': [], 'ignore': []} ignoreElements = self.root.findall(".ignore") ignoreGlyphs = [] for ignoreElement in ignoreElements: names = ignoreElement.attrib.get('glyphs') if names: ignoreGlyphs = names.split(",") if ignoreGlyphs: self.documentObject.lib[skipExportKey] = ignoreGlyphs dataElements = self.root.findall(".data") if not dataElements: return newLib = {} interactionSourcesAdded = False for dataElement in dataElements: name = dataElement.attrib.get('name') value = dataElement.attrib.get('value') if value in ['True', 'False']: value = value == "True" else: try: value = float(value) except ValueError: pass if name == "previewtext": self.documentObject.lib[skateboardPreviewTextKey] = value elif name == "horizontalPreviewAxis": interactionSources['horizontal'].append(value) interactionSourcesAdded = True elif name == "verticalPreviewAxis": interactionSources['vertical'].append(value) interactionSourcesAdded = True newLib[name] = value if interactionSourcesAdded: self.documentObject.lib[skateboardInteractionSourcesKey] = interactionSources if newLib: self.documentObject.lib[superpolatorDataLibKey] = newLib def readOldRules(self): # read the old rules # # # # superpolator old rule to simple rule # if op in ['<', '<=']: # # old style data # axes[axisName]['maximum'] = conditionDict['values'] # newRule.name = "converted %s < and <= "%(axisName) # elif op in ['>', '>=']: # # old style data # axes[axisName]['minimum'] = conditionDict['values'] # newRule.name = "converted %s > and >= "%(axisName) # elif op == "==": # axes[axisName]['maximum'] = conditionDict['values'] # axes[axisName]['minimum'] = conditionDict['values'] # newRule.name = "converted %s == "%(axisName) # newRule.enabled = False # elif op == "!=": # axes[axisName]['maximum'] = conditionDict['values'] # axes[axisName]['minimum'] = conditionDict['values'] # newRule.name = "unsupported %s != "%(axisName) # newRule.enabled = False # else: # axes[axisName]['maximum'] = conditionDict['minimum'] # axes[axisName]['minimum'] = conditionDict['maximum'] # newRule.name = "minmax legacy rule for %s"%axisName # newRule.enabled = False rules = [] for oldRuleElement in self.root.findall(".rule"): ruleObject = self.ruleDescriptorClass() # only one condition set in these old rules cds = [] a = oldRuleElement.attrib['resultfalse'] b = oldRuleElement.attrib['resulttrue'] ruleObject.subs.append((a,b)) for oldConditionElement in oldRuleElement.findall(".condition"): cd = {} operator = oldConditionElement.attrib['operator'] axisValue = float(oldConditionElement.attrib['xvalue']) axisName = oldConditionElement.attrib['axisname'] if operator in ['<', '<=']: cd['maximum'] = axisValue cd['minimum'] = None cd['name'] = axisName ruleObject.name = "converted %s < and <= "%(axisName) elif operator in ['>', '>=']: cd['maximum'] = None cd['minimum'] = axisValue cd['name'] = axisName ruleObject.name = "converted %s > and >= "%(axisName) elif operator in ["==", "!="]: # can't convert this one continue cds.append(cd) if cds: ruleObject.conditionSets.append(cds) self.documentObject.addRule(ruleObject) def readSimpleRules(self): # read the simple rule elements # # # # # # # rulesContainerElements = self.root.findall(".simplerules") rules = [] for rulesContainerElement in rulesContainerElements: for ruleElement in rulesContainerElement: ruleObject = self.ruleDescriptorClass() ruleName = ruleObject.name = ruleElement.attrib['name'] # subs for subElement in ruleElement.findall('.sub'): a = subElement.attrib['name'] b = subElement.attrib['with'] ruleObject.subs.append((a, b)) # condition sets, .sp3 had none externalConditions = self._readConditionElements( ruleElement, ruleName, ) if externalConditions: ruleObject.conditionSets.append(externalConditions) self.log.info( "Found stray rule conditions outside a conditionset. " "Wrapped them in a new conditionset." ) self.documentObject.addRule(ruleObject) def _readConditionElements(self, parentElement, ruleName=None): # modified from the method from fonttools.designspaceLib # it's not the same! cds = [] for conditionElement in parentElement.findall('.condition'): cd = {} cdMin = conditionElement.attrib.get("minimum") if cdMin is not None: cd['minimum'] = float(cdMin) else: # will allow these to be None, assume axis.minimum cd['minimum'] = None cdMax = conditionElement.attrib.get("maximum") if cdMax is not None: cd['maximum'] = float(cdMax) else: # will allow these to be None, assume axis.maximum cd['maximum'] = None cd['name'] = conditionElement.attrib.get("axisname") # # test for things if cd.get('minimum') is None and cd.get('maximum') is None: raise DesignSpaceDocumentError( "condition missing required minimum or maximum in rule" + (" '%s'" % ruleName if ruleName is not None else "")) cds.append(cd) return cds def readAxes(self): # read the axes elements, including the warp map. axisElements = self.root.findall(".axis") if not axisElements: # raise error, we need axes return for axisElement in axisElements: axisObject = self.axisDescriptorClass() axisObject.name = axisElement.attrib.get("name") axisObject.tag = axisElement.attrib.get("shortname") axisObject.minimum = float(axisElement.attrib.get("minimum")) axisObject.maximum = float(axisElement.attrib.get("maximum")) axisObject.default = float(axisElement.attrib.get("initialvalue", axisObject.minimum)) self.documentObject.axes.append(axisObject) self.axisDefaults[axisObject.name] = axisObject.default self.documentObject.defaultLoc = self.axisDefaults def colorFromElement(self, element): elementColor = None for colorElement in element.findall('.color'): elementColor = self.readColorElement(colorElement) def readColorElement(self, colorElement): pass def locationFromElement(self, element): elementLocation = None for locationElement in element.findall('.location'): elementLocation = self.readLocationElement(locationElement) break if not self.allowAnisotropic: # don't want any anisotropic values here split = {} for k, v in elementLocation.items(): if type(v) == type(()): split[k] = v[0] else: split[k] = v elementLocation = split return elementLocation def readLocationElement(self, locationElement): """ Format 0 location reader """ if self._strictAxisNames and not self.documentObject.axes: raise DesignSpaceDocumentError("No axes defined") loc = {} for dimensionElement in locationElement.findall(".dimension"): dimName = dimensionElement.attrib.get("name") if self._strictAxisNames and dimName not in self.axisDefaults: # In case the document contains no axis definitions, self.log.warning("Location with undefined axis: \"%s\".", dimName) continue xValue = yValue = None try: xValue = dimensionElement.attrib.get('xvalue') xValue = float(xValue) except ValueError: self.log.warning("KeyError in readLocation xValue %3.3f", xValue) try: yValue = dimensionElement.attrib.get('yvalue') if yValue is not None: yValue = float(yValue) except ValueError: pass if yValue is not None: loc[dimName] = (xValue, yValue) else: loc[dimName] = xValue return loc def readSources(self): for sourceCount, sourceElement in enumerate(self.root.findall(".master")): filename = sourceElement.attrib.get('filename') if filename is not None and self.path is not None: sourcePath = os.path.abspath(os.path.join(os.path.dirname(self.path), filename)) else: sourcePath = None sourceName = sourceElement.attrib.get('name') if sourceName is None: # add a temporary source name sourceName = "temp_master.%d" % (sourceCount) sourceObject = self.sourceDescriptorClass() sourceObject.path = sourcePath # absolute path to the ufo source sourceObject.filename = filename # path as it is stored in the document sourceObject.name = sourceName familyName = sourceElement.attrib.get("familyname") if familyName is not None: sourceObject.familyName = familyName styleName = sourceElement.attrib.get("stylename") if styleName is not None: sourceObject.styleName = styleName sourceObject.location = self.locationFromElement(sourceElement) isMuted = False for maskedElement in sourceElement.findall('.maskedfont'): # mute isn't stored in the sourceDescriptor, but we can store it in the lib if maskedElement.attrib.get('font') == "1": isMuted = True for libElement in sourceElement.findall('.provideLib'): if libElement.attrib.get('state') == '1': sourceObject.copyLib = True for groupsElement in sourceElement.findall('.provideGroups'): if groupsElement.attrib.get('state') == '1': sourceObject.copyGroups = True for infoElement in sourceElement.findall(".provideInfo"): if infoElement.attrib.get('state') == '1': sourceObject.copyInfo = True for featuresElement in sourceElement.findall(".provideFeatures"): if featuresElement.attrib.get('state') == '1': sourceObject.copyFeatures = True for glyphElement in sourceElement.findall(".glyph"): glyphName = glyphElement.attrib.get('name') if glyphName is None: continue if glyphElement.attrib.get('mute') == '1': sourceObject.mutedGlyphNames.append(glyphName) self.documentObject.sources.append(sourceObject) if isMuted: if not skateboardMutedSourcesKey in self.documentObject.lib: self.documentObject.lib[skateboardMutedSourcesKey] = [] item = (sourceObject.filename, "foreground") self.documentObject.lib[skateboardMutedSourcesKey].append(item) def readInstances(self): for instanceCount, instanceElement in enumerate(self.root.findall(".instance")): instanceObject = self.instanceDescriptorClass() if instanceElement.attrib.get("familyname"): instanceObject.familyName = instanceElement.attrib.get("familyname") if instanceElement.attrib.get("stylename"): instanceObject.styleName = instanceElement.attrib.get("stylename") if instanceElement.attrib.get("styleMapFamilyName"): instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName") if instanceElement.attrib.get("styleMapStyleName"): instanceObject.styleMapStyleName = instanceElement.attrib.get("styleMapStyleName") if instanceElement.attrib.get("styleMapFamilyName"): instanceObject.styleMapFamilyName = instanceElement.attrib.get("styleMapFamilyName") instanceObject.location = self.locationFromElement(instanceElement) instanceObject.filename = instanceElement.attrib.get('filename') for libElement in instanceElement.findall('.provideLib'): if libElement.attrib.get('state') == '1': instanceObject.lib = True for libElement in instanceElement.findall('.provideInfo'): if libElement.attrib.get('state') == '1': instanceObject.info = True self.documentObject.instances.append(instanceObject) def sp3_to_designspace(sp3path, designspacePath=None): if designspacePath is None: designspacePath = sp3path.replace(".sp3", ".designspace") doc = DesignSpaceDocument() reader = SuperpolatorReader(sp3path, doc) reader.read() doc.write(designspacePath) if __name__ == "__main__": def test_superpolator_testdoc1(): # read superpolator_testdoc1.sp3 # and test all the values testDoc = DesignSpaceDocument() testPath = "../../Tests/spReader_testdocs/superpolator_testdoc1.sp3" reader = SuperpolatorReader(testPath, testDoc) reader.read() # check the axes names = [a.name for a in reader.documentObject.axes] names.sort() assert names == ['grade', 'space', 'weight', 'width'] tags = [a.tag for a in reader.documentObject.axes] tags.sort() assert tags == ['SPCE', 'grad', 'wdth', 'wght'] # check the data items assert superpolatorDataLibKey in reader.documentObject.lib items = list(reader.documentObject.lib[superpolatorDataLibKey].items()) items.sort() assert items == [('expandRules', False), ('horizontalPreviewAxis', 'width'), ('includeLegacyRules', False), ('instancefolder', 'instances'), ('keepWorkFiles', True), ('lineInverted', True), ('lineStacked', 'lined'), ('lineViewFilled', True), ('outputFormatUFO', 3.0), ('previewtext', 'VA'), ('roundGeometry', False), ('verticalPreviewAxis', 'weight')] # check the sources for sd in reader.documentObject.sources: assert sd.familyName == "MutatorMathTest_SourceFamilyName" if sd.styleName == "Default": assert sd.location == {'width': 0.0, 'weight': 0.0, 'space': 0.0, 'grade': -0.5} assert sd.copyLib == True assert sd.copyGroups == True assert sd.copyInfo == True assert sd.copyFeatures == True elif sd.styleName == "TheOther": assert sd.location == {'width': 0.0, 'weight': 1000.0, 'space': 0.0, 'grade': -0.5} assert sd.copyLib == False assert sd.copyGroups == False assert sd.copyInfo == False assert sd.copyFeatures == False # check the instances for nd in reader.documentObject.instances: assert nd.familyName == "MutatorMathTest_InstanceFamilyName" if nd.styleName == "AWeightThatILike": assert nd.location == {'width': 133.152174, 'weight': 723.981097, 'space': 0.0, 'grade': -0.5} assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-AWeightThatILike.ufo" assert nd.styleMapFamilyName == None assert nd.styleMapStyleName == None if nd.styleName == "wdth759.79_SPCE0.00_wght260.72": # note the converted anisotropic location in the width axis. assert nd.location == {'grade': -0.5, 'width': 500.0, 'weight': 260.7217, 'space': 0.0} assert nd.filename == "instances/MutatorMathTest_InstanceFamilyName-wdth759.79_SPCE0.00_wght260.72.ufo" assert nd.styleMapFamilyName == "StyleMappedFamily" assert nd.styleMapStyleName == "bold" # check the rules for rd in reader.documentObject.rules: assert rd.name == "width: < 500.0" assert len(rd.conditionSets) == 1 assert rd.subs == [('I', 'I.narrow')] for conditionSet in rd.conditionSets: for cd in conditionSet: if cd['name'] == "width": assert cd == {'minimum': None, 'maximum': 500.0, 'name': 'width'} if cd['name'] == "grade": assert cd == {'minimum': 0.0, 'maximum': 500.0, 'name': 'grade'} testDoc.write(testPath.replace(".sp3", "_output_roundtripped.designspace")) def test_testDocs(): # read the test files and convert them # no tests root = "../../Tests/spReader_testdocs/test*.sp3" for path in glob.glob(root): sp3_to_designspace(path) test_superpolator_testdoc1() #test_testDocs()ufoProcessor-1.9.0/Lib/ufoProcessor/varModels.py000066400000000000000000000152751363642522700217430ustar00rootroot00000000000000# -*- 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.md000066400000000000000000000115061363642522700155060ustar00rootroot00000000000000[![Travis](https://travis-ci.org/LettError/ufoProcessor.svg?branch=master)](https://travis-ci.org/LettError/ufoProcessor) [![PyPI](https://img.shields.io/pypi/v/ufoprocessor.svg)](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/000077500000000000000000000000001363642522700153265ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/000077500000000000000000000000001363642522700176375ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/000077500000000000000000000000001363642522700226075ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/fontinfo.plist000066400000000000000000000016021363642522700255050ustar00rootroot00000000000000 ascender 750 capHeight 500 descender -250 familyName BenderTest guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName One unitsPerEm 1000 xHeight 500 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/000077500000000000000000000000001363642522700262335ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/contents.plist000066400000000000000000000002671363642522700311520ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs.background/layerinfo.plist000066400000000000000000000003701363642522700313000ustar00rootroot00000000000000 color 0,0.8,0.2,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/000077500000000000000000000000001363642522700241155ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/a.glif000066400000000000000000000010511363642522700251750ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/contents.plist000066400000000000000000000003551363642522700270320ustar00rootroot00000000000000 a a.glif ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671363642522700271700ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/kerning.plist000066400000000000000000000004311363642522700253170ustar00rootroot00000000000000 a a -100 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/layercontents.plist000066400000000000000000000005771363642522700265670ustar00rootroot00000000000000 foreground glyphs background glyphs.background ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/lib.plist000066400000000000000000000157731363642522700244470ustar00rootroot00000000000000 com.defcon.sortDescriptor ascending Latin-1 type characterSet com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.segmentType curve com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder space exclam quotedbl numbersign dollar percent ampersand parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot registered macron degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis dotlessi circumflex caron breve dotaccent ring ogonek tilde hungarumlaut quoteleft quoteright minus ufoProcessor-1.9.0/Tests/20190830 benders/benderTest1.ufo/metainfo.plist000066400000000000000000000004761363642522700254750ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/000077500000000000000000000000001363642522700226105ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/fontinfo.plist000066400000000000000000000016021363642522700255060ustar00rootroot00000000000000 ascender 750 capHeight 601 descender -250 familyName BenderTest guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Two unitsPerEm 1000 xHeight 500 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/000077500000000000000000000000001363642522700262345ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/contents.plist000066400000000000000000000002671363642522700311530ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs.background/layerinfo.plist000066400000000000000000000003701363642522700313010ustar00rootroot00000000000000 color 0,0.8,0.2,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/000077500000000000000000000000001363642522700241165ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/a.glif000066400000000000000000000010531363642522700252000ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/contents.plist000066400000000000000000000003551363642522700270330ustar00rootroot00000000000000 a a.glif ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/glyphs/layerinfo.plist000066400000000000000000000003671363642522700271710ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/kerning.plist000066400000000000000000000004311363642522700253200ustar00rootroot00000000000000 a a -200 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/layercontents.plist000066400000000000000000000005771363642522700265700ustar00rootroot00000000000000 foreground glyphs background glyphs.background ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/lib.plist000066400000000000000000000157731363642522700244500ustar00rootroot00000000000000 com.defcon.sortDescriptor ascending Latin-1 type characterSet com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.segmentType curve com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder space exclam quotedbl numbersign dollar percent ampersand parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot registered macron degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis dotlessi circumflex caron breve dotaccent ring ogonek tilde hungarumlaut quoteleft quoteright minus ufoProcessor-1.9.0/Tests/20190830 benders/benderTest2.ufo/metainfo.plist000066400000000000000000000004761363642522700254760ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/000077500000000000000000000000001363642522700226115ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/fontinfo.plist000066400000000000000000000016041363642522700255110ustar00rootroot00000000000000 ascender 750 capHeight 700 descender -250 familyName BenderTest guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Three unitsPerEm 1000 xHeight 500 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/000077500000000000000000000000001363642522700262355ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/contents.plist000066400000000000000000000002671363642522700311540ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs.background/layerinfo.plist000066400000000000000000000003701363642522700313020ustar00rootroot00000000000000 color 0,0.8,0.2,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/000077500000000000000000000000001363642522700241175ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/a.glif000066400000000000000000000010471363642522700252040ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/contents.plist000066400000000000000000000003551363642522700270340ustar00rootroot00000000000000 a a.glif ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/glyphs/layerinfo.plist000066400000000000000000000003671363642522700271720ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/kerning.plist000066400000000000000000000004311363642522700253210ustar00rootroot00000000000000 a a -100 ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/layercontents.plist000066400000000000000000000005771363642522700265710ustar00rootroot00000000000000 foreground glyphs background glyphs.background ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/lib.plist000066400000000000000000000157731363642522700244510ustar00rootroot00000000000000 com.defcon.sortDescriptor ascending Latin-1 type characterSet com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.segmentType curve com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder space exclam quotedbl numbersign dollar percent ampersand parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot registered macron degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis dotlessi circumflex caron breve dotaccent ring ogonek tilde hungarumlaut quoteleft quoteright minus ufoProcessor-1.9.0/Tests/20190830 benders/benderTest3.ufo/metainfo.plist000066400000000000000000000004761363642522700254770ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.9.0/Tests/20190830 benders/instances/000077500000000000000000000000001363642522700216265ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/000077500000000000000000000000001363642522700256535ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/fontinfo.plist000066400000000000000000000016011363642522700305500ustar00rootroot00000000000000 ascender 750 capHeight 997 descender -250 familyName BenderTest guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName FarOut unitsPerEm 1000.0 xHeight 500 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/000077500000000000000000000000001363642522700271615ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/a.glif000066400000000000000000000011251363642522700302430ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/glyphs/contents.plist000066400000000000000000000003551363642522700320760ustar00rootroot00000000000000 a a.glif ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/kerning.plist000066400000000000000000000004301363642522700303620ustar00rootroot00000000000000 a a 200 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/layercontents.plist000066400000000000000000000004371363642522700316260ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/lib.plist000066400000000000000000000162171363642522700275050ustar00rootroot00000000000000 com.defcon.sortDescriptor ascending Latin-1 type characterSet com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.segmentType curve com.typemytype.robofont.shouldAddPointsInSplineConversion 1 designspace.location test 2500.0 public.glyphOrder space exclam quotedbl numbersign dollar percent ampersand parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot registered macron degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis dotlessi circumflex caron breve dotaccent ring ogonek tilde hungarumlaut quoteleft quoteright minus ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-FarOut.ufo/metainfo.plist000066400000000000000000000004761363642522700305410ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/000077500000000000000000000000001363642522700270655ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/fontinfo.plist000066400000000000000000000016131363642522700317650ustar00rootroot00000000000000 ascender 750 capHeight 601 descender -250 familyName BenderTest guidelines postscriptBlueValues postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Intermediate unitsPerEm 1000 xHeight 500 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/000077500000000000000000000000001363642522700303735ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs/a.glif000066400000000000000000000010541363642522700314560ustar00rootroot00000000000000 contents.plist000066400000000000000000000003551363642522700332310ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/glyphs a a.glif ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/kerning.plist000066400000000000000000000004311363642522700315750ustar00rootroot00000000000000 a a -200 ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/layercontents.plist000066400000000000000000000004371363642522700330400ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/lib.plist000066400000000000000000000162161363642522700307160ustar00rootroot00000000000000 com.defcon.sortDescriptor ascending Latin-1 type characterSet com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.segmentType curve com.typemytype.robofont.shouldAddPointsInSplineConversion 1 designspace.location test 500.0 public.glyphOrder space exclam quotedbl numbersign dollar percent ampersand parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at A B C D E F G H I J K L M N O P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore grave a b c d e f g h i j k l m n o p q r s t u v w x y z braceleft bar braceright asciitilde exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot registered macron degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis dotlessi circumflex caron breve dotaccent ring ogonek tilde hungarumlaut quoteleft quoteright minus ufoProcessor-1.9.0/Tests/20190830 benders/instances/BenderTest-Intermediate.ufo/metainfo.plist000066400000000000000000000004761363642522700317530ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.9.0/Tests/20190830 benders/test.py000066400000000000000000000036731363642522700212010ustar00rootroot00000000000000""" 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.py000066400000000000000000000035501363642522700202000ustar00rootroot00000000000000from 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.py000066400000000000000000000005611363642522700210110ustar00rootroot00000000000000from 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/000077500000000000000000000000001363642522700210035ustar00rootroot00000000000000ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1.sp3000066400000000000000000000101231363642522700257740ustar00rootroot00000000000000 ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1_converted.designspace000066400000000000000000000101201363642522700316220ustar00rootroot00000000000000 com.letterror.skateboard.interactionSources horizontal width ignore vertical weight com.letterror.skateboard.mutedSources ufo/MutatorSansLightCondensed.ufo foreground ufo/MutatorSansBoldCondensed.ufo foreground com.letterror.skateboard.previewText VA com.superpolator.data expandRules horizontalPreviewAxis width includeLegacyRules instancefolder instances keepWorkFiles lineInverted lineStacked lined lineViewFilled outputFormatUFO 3.0 previewtext VA roundGeometry verticalPreviewAxis weight ufoProcessor-1.9.0/Tests/spReader_testdocs/superpolator_testdoc1_output_roundtripped.designspace000066400000000000000000000100661363642522700340010ustar00rootroot00000000000000 com.letterror.skateboard.interactionSources horizontal width ignore vertical weight com.letterror.skateboard.mutedSources ufo/MutatorSansLightCondensed.ufo foreground ufo/MutatorSansBoldCondensed.ufo foreground com.letterror.skateboard.previewText VA com.superpolator.data expandRules horizontalPreviewAxis width includeLegacyRules instancefolder instances keepWorkFiles lineInverted lineStacked lined lineViewFilled outputFormatUFO 3.0 previewtext VA roundGeometry verticalPreviewAxis weight ufoProcessor-1.9.0/Tests/testOpenTypeAttrs_3.py000066400000000000000000000071171363642522700216110ustar00rootroot00000000000000 # 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.py000066400000000000000000000373121363642522700170500ustar00rootroot00000000000000# 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.py000066400000000000000000000240571363642522700175370ustar00rootroot00000000000000# 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.txt000066400000000000000000000000001363642522700174760ustar00rootroot00000000000000ufoProcessor-1.9.0/setup.cfg000066400000000000000000000001271363642522700160450ustar00rootroot00000000000000[bdist_wheel] universal = 1 [sdist] formats = zip [metadata] license_file = LICENSE ufoProcessor-1.9.0/setup.py000066400000000000000000000034211363642522700157360ustar00rootroot00000000000000#!/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", ], )