pax_global_header00006660000000000000000000000064147245427150014525gustar00rootroot0000000000000052 comment=6f2a15fe3316685a54ec8809b95ccc823b6e88da ufoProcessor-1.13.3/000077500000000000000000000000001472454271500143035ustar00rootroot00000000000000ufoProcessor-1.13.3/.github/000077500000000000000000000000001472454271500156435ustar00rootroot00000000000000ufoProcessor-1.13.3/.github/workflows/000077500000000000000000000000001472454271500177005ustar00rootroot00000000000000ufoProcessor-1.13.3/.github/workflows/release.yml000066400000000000000000000026451472454271500220520ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Upload Python Package on: push: tags: - '\d+\.\d+\.[0-9a-z]+' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: permissions: contents: read jobs: release-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.x" - name: Build release distributions run: | # NOTE: put your own distribution build steps here. python -m pip install build python -m build - name: Upload distributions uses: actions/upload-artifact@v4 with: name: release-dists path: dist/ pypi-publish: name: upload release to PyPI runs-on: ubuntu-latest environment: release needs: - release-build permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write contents: write steps: - name: Retrieve release distributions uses: actions/download-artifact@v4 with: name: release-dists path: dist/ - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 ufoProcessor-1.13.3/.gitignore000066400000000000000000000017401472454271500162750ustar00rootroot00000000000000# 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 /Tests/202206 discrete spaces/instances /Tests/202206 discrete spaces/instances_mutMath /Tests/202206 discrete spaces/instances_varlib /_issues /_old_stuff /Tests/ds5/instances /Tests/ds5/ds5_log.txt Tests/ds5/makeOneInstanceOutput* ufoProcessor-1.13.3/Designspaces with python.md000066400000000000000000000103521472454271500214740ustar00rootroot00000000000000# Designspaces and python Designspaces can do different things in different processes. Maybe you want to generate a variable font. Maybe you want to generate UFOs. Maybe you want to resample an existing designspace into something else. While [fonttools.designspacelib](https://fonttools.readthedocs.io/en/latest/designspaceLib/index.html) contains the basic objects to construct, read and write designspaces, the [ufoProcessor package](https://github.com/LettError/ufoProcessor) can also generate instances. ## Basics First I have to make a `DesignSpaceDocument` object. This is an empty container, it has no masters, no axes, no path. from fontTools.designspaceLib import * ds = DesignSpaceDocument() Now I will add an axis to the document by making an `AxisDescriptor` object and adding some values to its attributes. ad = AxisDescriptor() ad.name = "weight" # readable name ad.tag = "wght" # 4 letter tag ad.minimum = 200 ad.maximum = 1000 ad.default = 400 Finally we add the axisDescriptor to the document: ds.addAxis(ad) print(ds) path = "my.designspace" ds.write(path) This writes a very small designspace file: Let's add some sources to the designspace: this needs the absolute path to the file (usually a ufo). When the document is saved the paths will be written as relative to the designspace document. A `SourceDescriptor` object has a lot of attributes, but `path` and `location` are the most important ones. s1 = SourceDescriptor() s1.path = "geometryMaster1.ufo" s1.location = dict(weight=200) ds.addSource(s1) s2 = SourceDescriptor() s2.path = "geometryMaster2.ufo" s2.location = dict(weight=1000) ds.addSource(s2) Let's add some instances. Instances are specific locations in the designspace with names and sometimes paths associated with them. In a variable font you might want these to show up as styles in a menu. But you could also generate UFOs from them. for w in [ad.minimum, .5*(ad.minimum + ad.default), ad.default, .5*(ad.maximum + ad.default), ad.maximum]: # you will probably know more compact # and easier ways to write this, go ahead! i = InstanceDescriptor() i.fileName = "InstanceFamily" i.styleName = "Weight_%d" % w i.location = dict(weight = w) i.filename = "instance_%s.ufo" % i.styleName ds.addInstance(i) The XML now has all it needs: an axis, some sources and ome instances. Whoop well done. ufoProcessor-1.13.3/LICENSE000066400000000000000000000021151472454271500153070ustar00rootroot00000000000000Copyright (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.13.3/Lib/000077500000000000000000000000001472454271500150115ustar00rootroot00000000000000ufoProcessor-1.13.3/Lib/ufoProcessor/000077500000000000000000000000001472454271500175025ustar00rootroot00000000000000ufoProcessor-1.13.3/Lib/ufoProcessor/__init__.py000066400000000000000000001210211472454271500216100ustar00rootroot00000000000000# 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, DecomposePointPen 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) # get anchors oldAnchors = font[oldName].anchors newAnchors = font[newName].anchors # swap the outlines font[swapName].clear() p = font[swapName].getPointPen() font[oldName].drawPoints(p) font[swapName].width = font[oldName].width # lib? font[oldName].clear() p = font[oldName].getPointPen() font[newName].drawPoints(p) font[oldName].width = font[newName].width for a in newAnchors: na = defcon.Anchor() na.name = a.name na.x = a.x na.y = a.y # FontParts and Defcon add anchors in different ways # this works around that. try: font[oldName].naked().appendAnchor(na) except AttributeError: font[oldName].appendAnchor(na) font[newName].clear() p = font[newName].getPointPen() font[swapName].drawPoints(p) font[newName].width = font[swapName].width for a in oldAnchors: na = defcon.Anchor() na.name = a.name na.x = a.x na.y = a.y try: font[newName].naked().appendAnchor(na) except AttributeError: font[newName].appendAnchor(na) # remap the components for g in font: for c in g.components: if c.baseGlyph == oldName: c.baseGlyph = swapName continue for g in font: for c in g.components: if c.baseGlyph == newName: c.baseGlyph = oldName continue for g in font: for c in g.components: if c.baseGlyph == swapName: c.baseGlyph = newName # change the names in groups # the shapes will swap, that will invalidate the kerning # so the names need to swap in the kerning as well. newKerning = {} for first, second in font.kerning.keys(): value = font.kerning[(first,second)] if first == oldName: first = newName elif first == newName: first = oldName if second == oldName: second = newName elif second == newName: second = oldName newKerning[(first, second)] = value font.kerning.clear() font.kerning.update(newKerning) for groupName, members in font.groups.items(): newMembers = [] for name in members: if name == oldName: newMembers.append(newName) elif name == newName: newMembers.append(oldName) else: newMembers.append(name) font.groups[groupName] = newMembers remove = [] for g in font: if g.name.find(swapNameExtension)!=-1: remove.append(g.name) for r in remove: del font[r] class 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.13.3/Lib/ufoProcessor/emptyPen.py000077500000000000000000000075521472454271500216710ustar00rootroot00000000000000# coding: utf-8 from fontTools.pens.pointPen import AbstractPointPen from defcon.pens.transformPointPen import TransformPointPen from defcon.objects.component import _defaultTransformation """ Decompose """ class DecomposePointPen(object): def __init__(self, glyphSet, outPointPen): self._glyphSet = glyphSet self._outPointPen = outPointPen self.beginPath = outPointPen.beginPath self.endPath = outPointPen.endPath self.addPoint = outPointPen.addPoint def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs): if baseGlyphName in self._glyphSet: baseGlyph = self._glyphSet[baseGlyphName] if transformation == _defaultTransformation: baseGlyph.drawPoints(self) else: transformPointPen = TransformPointPen(self, transformation) baseGlyph.drawPoints(transformPointPen) """ Simple pen object to determine if a glyph contains any geometry. """ 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 if "space" in glyph.name: # this is a bold assumption, # and certainly not inclusive 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.13.3/Lib/ufoProcessor/logger.py000066400000000000000000000035161472454271500213400ustar00rootroot00000000000000import sys import time import os import logging class Logger: def __init__(self, path, rootDirectory, nest=0): self.path = path self.rootDirectory = rootDirectory self.nest = nest if not nest: if path is not None: if os.path.exists(path): os.remove(path) if not os.path.exists(path): f = open(path, "w") f.close() def child(self, text=None): logger = Logger( self.path, self.rootDirectory, nest=self.nest + 1 ) if text: logger.info(text) return logger def relativePath(self, path): return os.path.relpath(path, self.rootDirectory) def _makeText(self, text): if self.nest: text = f"{('| ' * self.nest).strip()} {text}" return text def _toConsole(self, text): print(text) def _toFile(self, text): if self.path is None: return text += "\n" f = open(self.path, "a") f.write(text) f.close() def time(self, prefix=None): now = time.strftime("%Y-%m-%d %H:%M") if prefix: now = prefix + " " + now self.info(now) def info(self, text): text = self._makeText(text) self._toConsole(text) self._toFile(text) def infoItem(self, text): text = f"\t- {text}" self.info(text) def infoPath(self, path): text = self.relativePath(path) self.infoItem(text) def detail(self, text): text = self._makeText(text) self._toFile(text) def detailItem(self, text): text = f"- {text}" self.detail(text) def detailPath(self, path): text = self.relativePath(path) self.detailItem(text) ufoProcessor-1.13.3/Lib/ufoProcessor/ufoOperator.py000066400000000000000000002400111472454271500223570ustar00rootroot00000000000000import os import functools import itertools import inspect import random import defcon from warnings import warn import collections import traceback from fontTools.designspaceLib import DesignSpaceDocument, processRules, InstanceDescriptor from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts from fontTools.ufoLib import fontInfoAttributesVersion1, fontInfoAttributesVersion2, fontInfoAttributesVersion3 from fontTools.misc import plistlib from fontMath.mathGlyph import MathGlyph from fontMath.mathInfo import MathInfo from fontMath.mathKerning import MathKerning from mutatorMath.objects.mutator import buildMutator from mutatorMath.objects.location import Location import fontParts.fontshell.font from ufoProcessor.varModels import VariationModelMutator from ufoProcessor.emptyPen import checkGlyphIsEmpty, DecomposePointPen from ufoProcessor.logger import Logger _memoizeCache = dict() _memoizeStats = dict() def ip(a, b, f): return a+f*(b-a) def immutify(obj): # make an immutable version of this object. # assert immutify(10) == (10,) # assert immutify([10, 20, "a"]) == (10, 20, 'a') # assert immutify(dict(aSet={1,2,3}, foo="bar", world=["a", "b"])) == ('foo', ('bar',), 'world', ('a', 'b')) hashValues = [] if isinstance(obj, dict): hashValues.append( MemoizeDict( [(key, immutify(value)) for key, value in obj.items()] ) ) elif isinstance(obj, set): for value in sorted(obj): hashValues.append(immutify(value)) elif isinstance(obj, (list, tuple)): for value in obj: hashValues.append(immutify(value)) else: hashValues.append(obj) if len(hashValues) == 1: return hashValues[0] return tuple(hashValues) class MemoizeDict(dict): """ An immutable dictionary. >>> d = MemoizeDict(name="a", test="b") >>> d["name"] 'a' >>> d["name"] = "c" Traceback (most recent call last): ... RuntimeError: Cannot modify ImmutableDict """ def __readonly__(self, *args, **kwargs): raise RuntimeError("Cannot modify MemoizeDict") __setitem__ = __readonly__ __delitem__ = __readonly__ pop = __readonly__ popitem = __readonly__ clear = __readonly__ update = __readonly__ setdefault = __readonly__ del __readonly__ _hash = None def __hash__(self): if self._hash is None: self._hash = hash(frozenset(self.items())) return self._hash def memoize(function): signature = inspect.signature(function) argsKeys = [parameter.name for parameter in signature.parameters.values()] @functools.wraps(function) def wrapper(*args, **kwargs): immutablekwargs = immutify(dict( **{key: value for key, value in zip(argsKeys, args)}, **kwargs )) key = (function.__name__, immutablekwargs) if key in _memoizeCache: # keep track of how often we get to serve something from the cache # note: if the object itself is part of the key # keeping these stats will keep the object around _memoizeStats[key] += 1 return _memoizeCache[key] else: result = function(*args, **kwargs) _memoizeCache[key] = result _memoizeStats[key] = 1 return result return wrapper def inspectMemoizeCache(): frequency = [] objects = {} items = [] for (funcName, data), value in _memoizeCache.items(): if funcName == "getGlyphMutator": functionName = f"{id(data['self']):X} {funcName}: {data['glyphName']}" else: functionName = f"{id(data['self']):X} {funcName}" if functionName not in objects: objects[functionName] = 0 objects[functionName] += 1 items = [(k, v) for k, v in objects.items()] for key in _memoizeStats.keys(): if funcName == "getGlyphMutator": functionName = f"{id(data['self']):X} {funcName}: {data['glyphName']}" else: functionName = f"{id(data['self']):X} {funcName}" called = _memoizeStats[key] frequency.append((functionName, called)) frequency.sort() return items, frequency def getDefaultLayerName(f): # get the name of the default layer from a defcon font (outside RF) and from a fontparts font (outside and inside RF) if isinstance(f, defcon.objects.font.Font): return f.layers.defaultLayer.name elif isinstance(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 isinstance(f, defcon.objects.font.Font): if layerName in f.layers: return f.layers[layerName] elif isinstance(f, fontParts.fontshell.font.RFont): if layerName in f.layerOrder: return f.getLayer(layerName) return None class UFOOperator(object): # wrapped, not inherited, as Just says. 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 # RF italic slant offset lib key italicSlantOffsetLibKey = "com.typemytype.robofont.italicSlantOffset" def __init__(self, pathOrObject=None, ufoVersion=3, useVarlib=True, extrapolate=False, strict=False, debug=False): self.ufoVersion = ufoVersion self.useVarlib = useVarlib self._fontsLoaded = False self.fonts = {} self.tempLib = {} self.libKeysForProcessing = [self.italicSlantOffsetLibKey] self.roundGeometry = False self.mutedAxisNames = None # list of axisname that need to be muted self.strict = strict self.debug = debug self.extrapolate = extrapolate # if true allow extrapolation self.logger = None self.doc = None if isinstance(pathOrObject, DesignSpaceDocument): self.doc = pathOrObject elif isinstance(pathOrObject, str): self.doc = DesignSpaceDocument() self.doc.read(pathOrObject) else: self.doc = DesignSpaceDocument() if self.debug: self.startLog() def startLog(self): # so we can call it later self.debug = True docBaseName = os.path.splitext(self.doc.path)[0] logPath = f"{docBaseName}_log.txt" self.logger = Logger(path=logPath, rootDirectory=None) self.logger.time() self.logger.info(f"## {self.doc.path}") self.logger.info(f"\tUFO version: {self.ufoVersion}") self.logger.info(f"\tround Geometry: {self.roundGeometry}") if self.useVarlib: self.logger.info(f"\tinterpolating with varlib") else: self.logger.info(f"\tinterpolating with mutatorMath") def _instantiateFont(self, path): """ Return a instance of a font object with all the given subclasses""" try: return self.fontClass( path, layerClass=self.layerClass, libClass=self.libClass, kerningClass=self.kerningClass, groupsClass=self.groupsClass, infoClass=self.infoClass, featuresClass=self.featuresClass, glyphClass=self.glyphClass, glyphContourClass=self.glyphContourClass, glyphPointClass=self.glyphPointClass, glyphComponentClass=self.glyphComponentClass, glyphAnchorClass=self.glyphAnchorClass ) except TypeError: # if our fontClass doesnt support all the additional classes return self.fontClass(path) # UFOProcessor compatibility # not sure whether to expose all the DesignSpaceDocument internals here # One can just use ufoOperator.doc to get it going? # Let's see how difficilt it is def read(self, path): """Wrap a DesignSpaceDocument""" self.doc = DesignSpaceDocument() self.doc.read(path) self.changed() def write(self, path): """Write the wrapped DesignSpaceDocument""" self.doc.write(path) def addAxis(self, axisDescriptor): self.doc.addAxis(axisDescriptor) def addAxisDescriptor(self, **kwargs): return self.doc.addAxisDescriptor(**kwargs) def addLocationLabel(self, locationLabelDescriptor): self.doc.addLocationLabel(locationLabelDescriptor) def addLocationLabelDescriptor(self, **kwargs): return self.doc.addLocationLabelDescriptor(**kwargs) def addRule(self, ruleDescriptor): self.doc.addRule(ruleDescriptor) def addRuleDescriptor(self, **kwargs): return self.doc.addRuleDescriptor(**kwargs) def addSource(self, sourceDescriptor): if sourceDescriptor.font is not None: self.fonts[sourceDescriptor.name] = sourceDescriptor.font self.doc.addSource(sourceDescriptor) def addSourceDescriptor(self, **kwargs): if "font" in kwargs: self.fonts[kwargs["name"]] = kwargs["font"] return self.doc.addSourceDescriptor(**kwargs) def addInstance(self, instanceDescriptor): self.doc.addInstance(instanceDescriptor) def addInstanceDescriptor(self, **kwargs): return self.doc.addInstanceDescriptor(**kwargs) def addVariableFont(self, variableFontDescriptor): self.doc.addVariableFont(variableFontDescriptor) def addVariableFontDescriptor(self, **kwargs): return self.doc.addVariableFontDescriptor(**kwargs) def getVariableFonts(self): return self.doc.getVariableFonts() def getInterpolableUFOOperators(self, useVariableFonts=True): if useVariableFonts: splitFunction = splitVariableFonts else: splitFunction = splitInterpolable for discreteLocationOrName, interpolableDesignspace in splitFunction(self.doc): if isinstance(discreteLocationOrName, dict): basename = "" if self.doc.filename is not None: basename = os.path.splitext(self.doc.filename)[0] elif self.doc.path is not None: basename = os.path.splitext(os.path.basename(self.doc.path))[0] discreteLocationOrName = basename + "-".join([f"{key}_{value:g}" for key, value in discreteLocationOrName.items()]) yield discreteLocationOrName, self.__class__( interpolableDesignspace, ufoVersion=self.ufoVersion, useVarlib=self.useVarlib, extrapolate=self.extrapolate, strict=self.strict, debug=self.debug ) @property def path(self): return self.doc.path @path.setter def path(self, value): self.doc.path = value @property def lib(self): return self.doc.lib @property def axes(self): return self.doc.axes @property def sources(self): return self.doc.sources @property def instances(self): return self.doc.instances @property def formatVersion(self): return self.doc.formatVersion @property def rules(self): return self.doc.rules @property def rulesProcessingLast(self): return self.doc.rulesProcessingLast @property def map_backward(self): return self.doc.map_backward @property def labelForUserLocation(self): return self.doc.labelForUserLocation @property def locationLabels(self): return self.doc.locationLabels @locationLabels.setter def locationLabels(self, locationLabels): self.doc.locationLabels = locationLabels @property def variableFonts(self): return self.doc.variableFonts @property def writerClass(self): return self.doc.writerClass def nameLocation(self, loc): # return a nicely formatted string for this location return ",".join([f"{k}:{v}" for k, v in loc.items()]) @formatVersion.setter def formatVersion(self, value): self.doc.formatVersion = value def getAxis(self, axisName): return self.doc.getAxis(axisName) # loading and updating fonts def loadFonts(self, reload=False): # Load the fonts and find the default candidate based on the info flag if self.logger is None and self.debug: # in some cases the UFOProcessor is initialised without debug # and then it is switched on afterwards. So have to check if # we have a logger before proceding. self.startLog() self.glyphNames = list({glyphname for font in self.fonts.values() for glyphname in font.keys()}) if self._fontsLoaded and not reload: if self.debug: self.logger.info("\t\t-- loadFonts called, but fonts are loaded already and no reload requested") return actions = [] if self.debug: self.logger.info("## loadFonts") for i, sourceDescriptor in enumerate(self.doc.sources): if sourceDescriptor.name is None: # make sure it has a unique name sourceDescriptor.name = "source.%d" % i if sourceDescriptor.name not in self.fonts: if os.path.exists(sourceDescriptor.path): font = self.fonts[sourceDescriptor.name] = self._instantiateFont(sourceDescriptor.path) thisLayerName = getDefaultLayerName(font) if self.debug: actions.append(f"loaded: {os.path.basename(sourceDescriptor.path)}, layer: {thisLayerName}, format: {font.ufoFormatVersionTuple}, id: {id(font):X}") else: self.fonts[sourceDescriptor.name] = None if self.debug: actions.append("source ufo not found at %s" % (sourceDescriptor.path)) if self.debug: for item in actions: self.logger.infoItem(item) self._fontsLoaded = True # XX maybe also make a character map here? def _logLoadedFonts(self): # dump info about the loaded fonts to the log self.logger.info("\t# font status:") for name, font in self.fonts.items(): self.logger.info(f"\t\tloaded: , id: {id(font):X}, {os.path.basename(font.path)}, format: {font.ufoFormatVersionTuple}") def updateFonts(self, fontObjects): # this is to update the loaded fonts. # it should be the way for an editor to provide a list of fonts that are open # self.fonts[sourceDescriptor.name] = None hasUpdated = False for newFont in fontObjects: # XX can we update font objects which arent stored on disk? if newFont.path is not None: for fontName, haveFont in self.fonts.items(): # XX what happens here when the font did not load? # haveFont will be None. Scenario: font initially missing, then added. if haveFont is None: if self.debug: self.logger.time() self.logger.info(f"## updating unloaded source {fontName} with {newFont}") self.fonts[fontName] = newFont hasUpdated = True elif haveFont.path == newFont.path: if self.debug: self.logger.time() self.logger.info(f"## updating source {self.fonts[fontName]} with {newFont}") self.fonts[fontName] = newFont hasUpdated = True if hasUpdated: self.changed() def getFonts(self): # return 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 usesFont(self, fontObj=None): # return True if font is used in this designspace. if fontObj is None: return False for name, otherFontObj in self.fonts.items(): if otherFontObj is None: continue if otherFontObj.path == fontObj.path: # we don't need to know anything else return True return False def getCharacterMapping(self, discreteLocation=None): # return a unicode -> glyphname map for the default of the system or discreteLocation characterMap = {} defaultSourceDescriptor = self.findDefault(discreteLocation=discreteLocation) if not defaultSourceDescriptor: return {} defaultFont = self.fonts.get(defaultSourceDescriptor.name) if defaultFont is None: return {} for glyph in defaultFont: if glyph.unicodes: for u in glyph.unicodes: characterMap[u] = glyph.name return characterMap # caching def __del__(self): self.changed() def changed(self): # clears everything relating to this designspacedocument # the cache could contain more designspacedocument objects. if _memoizeCache == None: # it can happen that changed is called after we're already clearing out. # Otherwise it _memoizeCache will be a dict. # If it is no longer a dict, it will not have anything left in store. return for key in list(_memoizeCache.keys()): funcName, data = key if data["self"] == self: del _memoizeCache[key] if key in _memoizeStats: del _memoizeStats[key] _cachedCallbacksWithGlyphNames = ("getGlyphMutator", "collectSourcesForGlyph", "makeOneGlyph") def glyphChanged(self, glyphName, includeDependencies=False): """Clears this one specific glyph from the memoize cache includeDependencies = True: check where glyphName is used as a component and remove those as well. Note: this must be check in each discreteLocation separately because they can have different constructions.""" changedNames = set() changedNames.add(glyphName) if includeDependencies: dependencies = self.getGlyphDependencies(glyphName) if dependencies: changedNames.update(dependencies) remove = [] for key in list(_memoizeCache.keys()): funcName, data = key if data["self"] == self and funcName in self._cachedCallbacksWithGlyphNames and data["glyphName"] in changedNames: remove.append(key) remove = set(remove) for key in remove: del _memoizeCache[key] if key in _memoizeStats: del _memoizeStats[key] def getGlyphDependencies(self, glyphName): dependencies = set() discreteLocation = self.getDiscreteLocations() if not discreteLocation: discreteLocation = [None] for discreteLocation in discreteLocation: # this is expensive, should it be cached? reverseComponentMap = self.getReverseComponentMapping(discreteLocation) if glyphName not in reverseComponentMap: return None for compName in reverseComponentMap[glyphName]: dependencies.add(compName) return dependencies def glyphsInCache(self): """report which glyphs are in the cache at the moment""" names = set() for funcName, data in list(_memoizeCache.keys()): if funcName in self._cachedCallbacksWithGlyphNames and data["self"] == self: names.add(data["glyphName"]) names = list(names) names.sort() return names # manipulate locations and axes def findAllDefaults(self): # collect all default sourcedescriptors for all discrete locations defaults = [] discreteLocation = self.getDiscreteLocations() if not discreteLocation: discreteLocation = [None] for discreteLocation in discreteLocation: defaultSourceDescriptor = self.findDefault(discreteLocation=discreteLocation) defaults.append(defaultSourceDescriptor) return defaults def findDefault(self, discreteLocation=None): defaultDesignLocation = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) for s in sources: if s.location == defaultDesignLocation: return s return None def findDefaultFont(self, discreteLocation=None): # A system without discrete axes should be able to # find a default here. defaultSourceDescriptor = self.findDefault(discreteLocation=discreteLocation) if defaultSourceDescriptor is None: return None # find the font now return self.fonts.get(defaultSourceDescriptor.name, None) getNeutralFont = findDefaultFont def splitLocation(self, location): # split a location in a continouous and a discrete part # Note: discrete can be None discreteAxes = [a.name for a in self.getOrderedDiscreteAxes()] continuous = {} discrete = {} for name, value in location.items(): if name in discreteAxes: discrete[name] = value else: continuous[name] = value if not discrete: return continuous, None return continuous, discrete def _serializeAnyAxis(self, axis): if hasattr(axis, "serialize"): return axis.serialize() else: if hasattr(axis, "values"): # discrete axis does not have serialize method, meh return dict( tag=axis.tag, name=axis.name, labelNames=axis.labelNames, minimum=min(axis.values), # XX is this allowed maximum=max(axis.values), # XX is this allowed values=axis.values, default=axis.default, hidden=axis.hidden, map=axis.map, axisOrdering=axis.axisOrdering, axisLabels=axis.axisLabels, ) def getSerializedAxes(self, discreteLocation=None): serialized = [] for axis in self.getOrderedContinuousAxes(): serialized.append(self._serializeAnyAxis(axis)) return serialized def getContinuousAxesForMutator(self): # map the axis values? d = collections.OrderedDict() for axis in self.getOrderedContinuousAxes(): d[axis.name] = self._serializeAnyAxis(axis) return d def _getAxisOrder(self): # XX this might be different from the axis order labels return [axisDescriptor.name for axisDescriptor in self.doc.axes] axisOrder = property(_getAxisOrder, doc="get the axis order from the axis descriptors") def getFullDesignLocation(self, location): return self.doc.getFullDesignLocation(location, self.doc) def getDiscreteLocations(self): # return a list of all permutated discrete locations # do we have a list of ordered axes? values = [] names = [] discreteCoordinates = [] for axis in self.getOrderedDiscreteAxes(): values.append(axis.values) names.append(axis.name) if values: for r in itertools.product(*values): # make a small dict for the discrete location values discreteCoordinates.append({a: b for a, b in zip(names, r)}) return discreteCoordinates def getOrderedDiscreteAxes(self): # return the list of discrete axis objects, in the right order axes = [] for axisName in self.doc.getAxisOrder(): axisObj = self.doc.getAxis(axisName) if hasattr(axisObj, "values"): axes.append(axisObj) return axes def getOrderedContinuousAxes(self): # return the list of continuous axis objects, in the right order axes = [] for axisName in self.doc.getAxisOrder(): axisObj = self.doc.getAxis(axisName) if not hasattr(axisObj, "values"): axes.append(axisObj) return axes def checkDiscreteAxisValues(self, location): # check if the discrete values in this location are allowed for discreteAxis in self.getOrderedDiscreteAxes(): testValue = location.get(discreteAxis.name) if testValue not in discreteAxis.values: return False return True def collectBaseGlyphs(self, glyphName, location): # make a list of all baseglyphs needed to build this glyph, at this location # Note: different discrete values mean that the glyph component set up can be different too continuousLocation, discreteLocation = self.splitLocation(location) names = set() def _getComponentNames(glyph): # so we can do recursion names = set() for comp in glyph.components: names.add(comp.baseGlyph) for n in _getComponentNames(glyph.font[comp.baseGlyph]): names.add(n) return list(names) for sourceDescriptor in self.findSourceDescriptorsForDiscreteLocation(discreteLocation): sourceFont = self.fonts[sourceDescriptor.name] if glyphName not in sourceFont: continue [names.add(n) for n in _getComponentNames(sourceFont[glyphName])] return list(names) def findSourceDescriptorsForDiscreteLocation(self, discreteLocDict=None): # return a list of all sourcedescriptors that share the values in the discrete loc tuple # so this includes all sourcedescriptors that point to layers # discreteLocDict {'countedItems': 1.0, 'outlined': 0.0}, {'countedItems': 1.0, 'outlined': 1.0} sources = [] for s in self.doc.sources: ok = True if discreteLocDict is None: sources.append(s) continue for name, value in discreteLocDict.items(): if name in s.location: if s.location[name] != value: ok = False else: ok = False continue if ok: sources.append(s) return sources def getVariationModel(self, items, axes, bias=None): # Return either a mutatorMath or a varlib.model object for calculating. if self.useVarlib: # use the varlib variation model try: return dict(), VariationModelMutator(items, axes=self.doc.axes, extrapolate=True) except TypeError: if self.debug: note = "Error while making VariationModelMutator for {loc}:\n{traceback.format_exc()}" self.logger.info(note) return {}, None except (KeyError, AssertionError): if self.debug: note = "UFOProcessor.getVariationModel error: {traceback.format_exc()}" self.logger.info(note) return {}, None else: # use mutatormath model axesForMutator = self.getContinuousAxesForMutator() # mutator will be confused by discrete axis values. # the bias needs to be for the continuous axes only biasForMutator, _ = self.splitLocation(bias) return buildMutator(items, axes=axesForMutator, bias=biasForMutator) return {}, None def newDefaultLocation(self, bend=False, discreteLocation=None): # overwrite from fontTools.newDefaultLocation # we do not want this default location always to be mapped. loc = collections.OrderedDict() for axisDescriptor in self.doc.axes: axisName = axisDescriptor.name axisValue = axisDescriptor.default if discreteLocation is not None: # if we want to find the default for a specific discreteLoation # we can not use the discrete axis' default value # -> we have to use the value in the given discreteLocation if axisDescriptor.name in discreteLocation: axisValue = discreteLocation[axisDescriptor.name] else: axisValue = axisDescriptor.default if bend: loc[axisName] = axisDescriptor.map_forward( axisValue ) else: loc[axisName] = axisValue return loc def isAnisotropic(self, location): # check if the location has anisotropic values for v in location.values(): if isinstance(v, (list, tuple)): return True return False def splitAnisotropic(self, location): # split the anisotropic location into a horizontal and vertical component x = Location() y = Location() for dim, val in location.items(): if isinstance(val, (tuple, list)): x[dim] = val[0] y[dim] = val[1] else: x[dim] = y[dim] = val return x, y # find out stuff about this designspace def collectForegroundLayerNames(self): """Return list of names of the default layers of all the fonts in this system. Include None and foreground. XX Why """ names = set([None, 'foreground']) for key, font in self.fonts.items(): names.add(getDefaultLayerName(font)) return list(names) def getReverseComponentMapping(self, discreteLocation=None): """Return a dict with reverse component mappings. Check if we're using fontParts or defcon Check which part of the designspace we're in. """ if discreteLocation is not None: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.doc.sources for sourceDescriptor in sources: isDefault = self.isLocalDefault(sourceDescriptor.location) if isDefault: font = self.fonts.get(sourceDescriptor.name) if font is None: return {} if isinstance(font, defcon.objects.font.Font): # defcon reverseComponentMapping = {} for base, comps in font.componentReferences.items(): for c in comps: if base not in reverseComponentMapping: reverseComponentMapping[base] = set() reverseComponentMapping[base].add(c) else: if hasattr(font, "getReverseComponentMapping"): reverseComponentMapping = font.getReverseComponentMapping() return reverseComponentMapping return {} def generateUFOs(self, useVarlib=None): # generate an UFO for each of the instance locations previousModel = self.useVarlib generatedFontPaths = [] if useVarlib is not None: self.useVarlib = useVarlib glyphCount = 0 self.loadFonts() if self.debug: self.logger.info("## generateUFO") for instanceDescriptor in self.doc.instances: if self.debug: self.logger.infoItem(f"Generating UFO at designspaceLocation {instanceDescriptor.getFullDesignLocation(self.doc)}") if instanceDescriptor.path is None: continue pairs = None bend = False font = self.makeInstance( instanceDescriptor, # processRules, glyphNames=self.glyphNames, decomposeComponents=False, pairs=pairs, bend=bend, ) if self.debug: self.logger.info(f"\t\t{os.path.basename(instanceDescriptor.path)}") instanceFolder = os.path.dirname(instanceDescriptor.path) if instanceFolder and not os.path.exists(instanceFolder): os.makedirs(instanceFolder) font.save(instanceDescriptor.path) generatedFontPaths.append(instanceDescriptor.path) glyphCount += len(font) if self.debug: self.logger.info(f"\t\tGenerated {glyphCount} glyphs altogether.") self.useVarlib = previousModel return generatedFontPaths generateUFO = generateUFOs @memoize def getInfoMutator(self, discreteLocation=None): """ Returns a info mutator for this discrete location """ infoItems = [] foregroundLayers = self.collectForegroundLayerNames() if discreteLocation is not None and discreteLocation is not {}: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.doc.sources for sourceDescriptor in sources: if sourceDescriptor.layerName not in foregroundLayers: continue continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue if hasattr(sourceFont.info, "toMathInfo"): infoItems.append((loc, sourceFont.info.toMathInfo())) else: infoItems.append((loc, self.mathInfoClass(sourceFont.info))) infoBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) bias, self._infoMutator = self.getVariationModel(infoItems, axes=self.getSerializedAxes(), bias=infoBias) return self._infoMutator @memoize def getLibEntryMutator(self, discreteLocation=None): """ Returns a mutator for selected lib keys store in self.libKeysForProcessing If there is no entry in the lib, it will ignore the source If there are no libkeys, it will return None. """ libMathItems = [] allValues = {} foregroundLayers = self.collectForegroundLayerNames() if discreteLocation is not None and discreteLocation is not {}: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.doc.sources for sourceDescriptor in sources: #if sourceDescriptor.layerName not in foregroundLayers: # continue continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue mathDict = Location() # we're using this for its math dict skills for libKey in self.libKeysForProcessing: if libKey in sourceFont.lib: # only add values we know mathDict[libKey] = sourceFont.lib[libKey] libMathItems.append((loc, mathDict)) if not libMathItems: # no keys, no mutator. return None libMathBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) bias, libMathMutator = self.getVariationModel(libMathItems, axes=self.getSerializedAxes(), bias=libMathBias) return libMathMutator @memoize def getKerningMutator(self, pairs=None, discreteLocation=None): """ Return a kerning mutator, collect the sources, build mathGlyphs. If no pairs are given: calculate the whole table. If pairs are given then query the sources for a value and make a mutator only with those values. """ if discreteLocation is not None: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.sources kerningItems = [] foregroundLayers = self.collectForegroundLayerNames() if pairs is None: for sourceDescriptor in sources: if sourceDescriptor.layerName not in foregroundLayers: continue if not sourceDescriptor.muteKerning: continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue # this makes assumptions about the groups of all sources being the same. kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) else: self._kerningMutatorPairs = pairs for sourceDescriptor in sources: # XXX check sourceDescriptor layerName, only foreground should contribute if sourceDescriptor.layerName is not None: continue if not os.path.exists(sourceDescriptor.path): continue if not sourceDescriptor.muteKerning: sourceFont = self.fonts[sourceDescriptor.name] if sourceFont is None: continue continuous, discrete = self.splitLocation(sourceDescriptor.location) loc = Location(continuous) # XXX can we get the kern value from the fontparts kerning object? kerningItem = self.mathKerningClass(sourceFont.kerning, sourceFont.groups) if kerningItem is not None: sparseKerning = {} for pair in pairs: v = kerningItem.get(pair) if v is not None: sparseKerning[pair] = v kerningItems.append((loc, self.mathKerningClass(sparseKerning))) kerningBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) bias, thing = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias) #xx bias, self._kerningMutator = self.getVariationModel(kerningItems, axes=self.getSerializedAxes(), bias=kerningBias) return self._kerningMutator @memoize def getGlyphMutator(self, glyphName, decomposeComponents=False, **discreteLocation): """make a mutator / varlib object for glyphName, with the sources for the given discrete location""" items, unicodes = self.collectSourcesForGlyph(glyphName, decomposeComponents=decomposeComponents, **discreteLocation) new = [] for a, b, c in items: if hasattr(b, "toMathGlyph"): # note: calling toMathGlyph ignores the mathGlyphClass preference # maybe the self.mathGlyphClass is not necessary? new.append((a, b.toMathGlyph(strict=self.strict))) else: new.append((a, self.mathGlyphClass(b, strict=self.strict))) thing = None thisBias = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) try: serializedAxes = self.getSerializedAxes() bias, thing = self.getVariationModel(new, axes=serializedAxes, bias=thisBias) # xx except Exception: error = traceback.format_exc() note = f"Error in getGlyphMutator for {glyphName}:\n{error}" if self.debug: self.logger.info(note) return thing, unicodes def isLocalDefault(self, location): # return True if location is a local default # check for bending defaults = {} for aD in self.doc.axes: defaults[aD.name] = aD.map_forward(aD.default) for axisName, value in location.items(): if defaults[axisName] != value: return False return True def axesByName(self): # return a dict[axisName]: axisDescriptor axes = {} for aD in self.doc.axes: axes[aD.name] = aD return axes def locationWillClip(self, location): # return True if this location will be clipped. clipped = self.clipDesignLocation(location) return not clipped == location def getAxisExtremes(self, axisRecord): # return the axis values in designspace coordinates if axisRecord.map is not None: aD_minimum = axisRecord.map_forward(axisRecord.minimum) aD_maximum = axisRecord.map_forward(axisRecord.maximum) aD_default = axisRecord.map_forward(axisRecord.default) return aD_minimum, aD_default, aD_maximum return axisRecord.minimum, axisRecord.default, axisRecord.maximum def clipDesignLocation(self, location): # return a copy of the design location without extrapolation # assume location is in designspace coordinates. # use map_forward on axis extremes, axesByName = self.axesByName() new = {} for axisName, value in location.items(): aD = axesByName.get(axisName) clippedValues = [] if type(value) == tuple: testValues = list(value) else: testValues = [value] for value in testValues: if hasattr(aD, "values"): # a discrete axis # will there be mapped discrete values? mx = max(aD.values) mn = min(aD.values) if value in aD.values: clippedValues.append(value) elif value > mx: clippedValues.append(mx) elif value < mn: clippedValues.append(mn) else: # do we want to test if the value is part of the values allowed in this axes? # or do we just assume it is correct? # possibility: snap to the nearest value? clippedValues.append(value) else: # a continuous axis aD_minimum = aD.map_forward(aD.minimum) aD_maximum = aD.map_forward(aD.maximum) if value < aD_minimum: clippedValues.append(aD_minimum) elif value > aD_maximum: clippedValues.append(aD_maximum) else: clippedValues.append(value) if len(clippedValues)==1: new[axisName] = clippedValues[0] elif len(clippedValues)==2: new[axisName] = tuple(clippedValues) return new def filterThisLocation(self, location, mutedAxes=None): # return location with axes is mutedAxes removed # this means checking if the location is a non-default value if not mutedAxes: return False, location defaults = {} ignoreSource = False for aD in self.doc.axes: defaults[aD.name] = aD.default new = {} new.update(location) for mutedAxisName in mutedAxes: if mutedAxisName not in location: continue if mutedAxisName not in defaults: continue if location[mutedAxisName] != defaults.get(mutedAxisName): ignoreSource = True del new[mutedAxisName] return ignoreSource, new @memoize def collectSourcesForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None, asMathGlyph=True): """ Return all source glyph objects. + either as mathglyphs (for use in mutators) + or source glyphs straight from the fonts decomposeComponents = True causes the source glyphs to be decomposed first before building the mutator. That gives you instances that do not depend on a complete font. If you're calculating previews for instance. findSourceDescriptorsForDiscreteLocation returns sources from layers as well """ items = [] empties = [] foundEmpty = False # is bend=True necessary here? defaultLocation = self.newDefaultLocation(bend=True, discreteLocation=discreteLocation) # if discreteLocation is not None: sources = self.findSourceDescriptorsForDiscreteLocation(discreteLocation) else: sources = self.doc.sources unicodes = set() # unicodes for this glyph for sourceDescriptor in sources: if not os.path.exists(sourceDescriptor.path): #kthxbai note = "\tMissing UFO at %s" % sourceDescriptor.path if self.debug: self.logger.info(note) continue if glyphName in sourceDescriptor.mutedGlyphNames: if self.debug: self.logger.info(f"\t\tglyphName {glyphName} is muted") continue thisIsDefault = self.isLocalDefault(sourceDescriptor.location) ignoreSource, filteredLocation = self.filterThisLocation(sourceDescriptor.location, self.mutedAxisNames) if ignoreSource: continue f = self.fonts.get(sourceDescriptor.name) if f is None: continue loc = Location(sourceDescriptor.location) sourceLayer = f if glyphName not 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 glyphName not in sourceLayer: continue else: sourceGlyphObject = sourceLayer[glyphName] if sourceGlyphObject.unicodes is not None: for u in sourceGlyphObject.unicodes: unicodes.add(u) if checkGlyphIsEmpty(sourceGlyphObject, allowWhiteSpace=True): foundEmpty = True # sourceGlyphObject = None # continue if decomposeComponents: # what about decomposing glyphs in a partial font? temp = self.glyphClass() sourceGlyphObject.drawPoints( DecomposePointPen(sourceLayer, temp.getPointPen()) ) temp.width = sourceGlyphObject.width temp.name = sourceGlyphObject.name temp.anchors = [dict( x=anchor.x, y=anchor.y, name=anchor.name, identifier=anchor.identifier, color=anchor.color ) for anchor in sourceGlyphObject.anchors] temp.guidelines = [dict( x=guideline.x, y=guideline.y, angle=guideline.angle, name=guideline.name, identifier=guideline.identifier, color=guideline.color ) for guideline in sourceGlyphObject.guidelines] processThis = temp else: processThis = sourceGlyphObject sourceInfo = dict( source=f.path, glyphName=glyphName, layerName=layerName, location=filteredLocation, # sourceDescriptor.location, sourceName=sourceDescriptor.name, ) if asMathGlyph: if hasattr(processThis, "toMathGlyph"): processThis = processThis.toMathGlyph(strict=self.strict) else: processThis = self.mathGlyphClass(processThis, strict=self.strict) continuous, discrete = self.splitLocation(loc) items.append((continuous, processThis, sourceInfo)) empties.append((thisIsDefault, foundEmpty)) # check the empties: # if the default glyph is empty, then all must be empty # if the default glyph is not empty then none can be empty checkedItems = [] emptiesAllowed = False # first check if the default is empty. # remember that the sources can be in any order for i, p in enumerate(empties): isDefault, isEmpty = p if isDefault and isEmpty: emptiesAllowed = True # now we know what to look for if not emptiesAllowed: for i, p in enumerate(empties): isDefault, isEmpty = p if not isEmpty: checkedItems.append(items[i]) else: for i, p in enumerate(empties): isDefault, isEmpty = p if isEmpty: checkedItems.append(items[i]) return checkedItems, unicodes def collectMastersForGlyph(self, glyphName, decomposeComponents=False, discreteLocation=None): # compatibility thing for designspaceProblems. checkedItems, unicodes = self.collectSourcesForGlyph(glyphName, decomposeComponents=False, discreteLocation=None) return checkedItems def getLocationType(self, location): """Determine the type of the location: continuous / discrete anisotropic / normal. """ continuousLocation, discreteLocation = self.splitLocation(location) if not self.extrapolate: # Axis values are in userspace, so this needs to happen before bending continuousLocation = self.clipDesignLocation(continuousLocation) #font = self._instantiateFont(None) loc = Location(continuousLocation) anisotropic = False locHorizontal = locVertical = loc if self.isAnisotropic(loc): anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) return anisotropic, continuousLocation, discreteLocation, locHorizontal, locVertical def collectSkippedGlyphs(self): # return a list of all the glyphnames listed in public.skipExportGlyphs names = [] for fontPath, fontObj in self.fonts.items(): for name in fontObj.lib.get('public.skipExportGlyphs', []): if name not in names: names.append(name) if self.debug: self.logger.info(f"collectSkippedGlyphs: {names}") return names def makeInstance(self, instanceDescriptor, doRules=None, glyphNames=None, decomposeComponents=False, pairs=None, bend=False): """ Generate a font object for this instance """ if doRules is not None: warn('The doRules argument in DesignSpaceProcessor.makeInstance() is deprecated', DeprecationWarning, stacklevel=2) if isinstance(instanceDescriptor, dict): instanceDescriptor = self.doc.writerClass.instanceDescriptorClass(**instanceDescriptor) # hmm getFullDesignLocation does not support anisotropc locations? fullDesignLocation = instanceDescriptor.getFullDesignLocation(self.doc) anisotropic, continuousLocation, discreteLocation, locHorizontal, locVertical = self.getLocationType(fullDesignLocation) self.loadFonts() if not self.extrapolate: # Axis values are in userspace, so this needs to happen before bending continuousLocation = self.clipDesignLocation(continuousLocation) font = self._instantiateFont(None) loc = Location(continuousLocation) anisotropic = False locHorizontal = locVertical = loc if self.isAnisotropic(loc): anisotropic = True locHorizontal, locVertical = self.splitAnisotropic(loc) if self.debug: self.logger.info(f"\t\t\tAnisotropic location for \"{instanceDescriptor.name}\"\n\t\t\t{fullDesignLocation}") # makeOneKerning # discreteLocation ? if instanceDescriptor.kerning: kerningObject = self.makeOneKerning(fullDesignLocation, pairs=pairs) if kerningObject is not None: kerningObject.extractKerning(font) # makeOneInfo infoInstanceObject = self.makeOneInfo(fullDesignLocation, roundGeometry=self.roundGeometry, clip=False) if infoInstanceObject is not None: 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 # calculate selected lib key values here libMathMutator = self.getLibEntryMutator(discreteLocation=discreteLocation) if self.debug: self.logger.info(f"\t\t\tlibMathMutator \"{libMathMutator}\"\n\t\t\t{discreteLocation}") if libMathMutator: # use locHorizontal in case this was anisotropic. # remember: libMathDict is a Location object, # each key in the location is the libKey # each value is the calculated value libMathDict = libMathMutator.makeInstance(locHorizontal) if libMathDict: for libKey, mutatedValue in libMathDict.items(): # only add the value to the lib if it is not 0. # otherwise it will always add it? Not sure? font.lib[libKey] = mutatedValue if self.debug: self.logger.info(f"\t\t\tlibMathMutator: libKey \"{libKey}: {mutatedValue}") defaultSourceFont = self.findDefaultFont(discreteLocation=discreteLocation) # found a default source font if defaultSourceFont: # copy info self._copyFontInfo(defaultSourceFont.info, font.info) # copy lib for key, value in defaultSourceFont.lib.items(): # don't overwrite the keys we calculated if key in self.libKeysForProcessing: continue font.lib[key] = value # copy groups for key, value in defaultSourceFont.groups.items(): font.groups[key] = value # copy features font.features.text = defaultSourceFont.features.text # ok maybe now it is time to calculate some glyphs # glyphs if glyphNames: selectedGlyphNames = glyphNames else: # since all glyphs are processed, decomposing components is unecessary # maybe that's confusing and components should be decomposed anyway # if decomposeComponents was set to True? decomposeComponents = False selectedGlyphNames = self.glyphNames if 'public.glyphOrder' not in font.lib.keys(): # should be the glyphorder from the default, yes? font.lib['public.glyphOrder'] = selectedGlyphNames # remove skippable glyphs toSkip = self.collectSkippedGlyphs() selectedGlyphNames = [name for name in selectedGlyphNames if name not in toSkip] for glyphName in selectedGlyphNames: glyphMutator, unicodes = self.getGlyphMutator(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) if glyphMutator is None: if self.debug: note = f"makeInstance: Could not make mutator for glyph {glyphName}" self.logger.info(note) continue font.newGlyph(glyphName) font[glyphName].clear() font[glyphName].unicodes = unicodes try: if not self.isAnisotropic(continuousLocation): glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) else: # split anisotropic location into horizontal and vertical components horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) # merge them again in a beautiful single line: glyphInstanceObject = (1, 0) * horizontalGlyphInstanceObject + (0, 1) * verticalGlyphInstanceObject except IndexError: # alignment problem with the data? if self.debug: note = "makeInstance: Quite possibly some sort of data alignment error in %s" % glyphName self.logger.info(note) continue if self.roundGeometry: try: glyphInstanceObject = glyphInstanceObject.round() except AttributeError: # what are we catching here? # math objects without a round method? if self.debug: note = f"makeInstance: no round method for {glyphInstanceObject} ?" self.logger.info(note) try: # File "/Users/erik/code/ufoProcessor/Lib/ufoProcessor/__init__.py", line 649, in makeInstance # glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True) # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontMath/mathGlyph.py", line 315, in extractGlyph # glyph.anchors = [dict(anchor) for anchor in self.anchors] # File "/Applications/RoboFont.app/Contents/Resources/lib/python3.6/fontParts/base/base.py", line 103, in __set__ # raise FontPartsError("no setter for %r" % self.name) # fontParts.base.errors.FontPartsError: no setter for 'anchors' if hasattr(font[glyphName], "fromMathGlyph"): font[glyphName].fromMathGlyph(glyphInstanceObject) else: glyphInstanceObject.extractGlyph(font[glyphName], onlyGeometry=True) except TypeError: # this causes ruled glyphs to end up in the wrong glyphname # but defcon2 objects don't support it pPen = font[glyphName].getPointPen() font[glyphName].clear() glyphInstanceObject.drawPoints(pPen) font[glyphName].width = glyphInstanceObject.width # add designspace location to lib font.lib['ufoProcessor.fullDesignspaceLocation'] = list(instanceDescriptor.getFullDesignLocation(self.doc).items()) if self.useVarlib: font.lib['ufoProcessor.mathmodel'] = "fonttools.varlib" else: font.lib['ufoProcessor.mathmodel'] = "mutatorMath" if self.debug: self.logger.info(f"\t\t\t{len(selectedGlyphNames)} glyphs added") return font def locationToDescriptiveString(self, loc): # make a nice descriptive string from the location # Check if the discrete location is None. t = [] cl, dl = self.splitLocation(loc) for continuousAxis in sorted(cl.keys()): t.append(f'{continuousAxis}_{cl[continuousAxis]}') if dl is not None: for discreteAxis in sorted(dl.keys()): t.append(f'{discreteAxis}_{dl[discreteAxis]}') return '_'.join(t) def pathForInstance(self, instanceDescriptor): # generate the complete path for this instance descriptor. if self.path is not None and instanceDescriptor.filename is not None: return os.path.abspath(os.path.join(os.path.dirname(self.path), instanceDescriptor.filename)) return None def makeOneInstance(self, location, doRules=None, glyphNames=None, decomposeComponents=False, pairs=None, bend=False): # make one instance for this location. This is a shortcut for making an # instanceDescriptor. So it makes some assumptions about the font names. # Otherwise all the geometry will be exactly what it needs to be. self.loadFonts() continuousLocation, discreteLocation = self.splitLocation(location) defaultFont = self.findDefaultFont(discreteLocation=discreteLocation) if defaultFont is not None: instanceFamilyName = defaultFont.info.familyName else: if self.doc.path is not None: instanceFamilyName = os.path.splitext(self.doc.path)[0] else: instanceFamilyName = "UFOOperatorInstance" tempInstanceDescriptor = InstanceDescriptor() tempInstanceDescriptor.location = location tempInstanceDescriptor.familyName = instanceFamilyName tempInstanceDescriptor.styleName = self.locationToDescriptiveString(location) return self.makeInstance(tempInstanceDescriptor, doRules=doRules, glyphNames=glyphNames, decomposeComponents=decomposeComponents, pairs=pairs, bend=bend) def randomLocation(self, extrapolate=0, anisotropic=False, roundValues=True, discreteLocation=None): """A good random location, for quick testing and entertainment extrapolate: is a factor of the (max-min) distance. 0 = nothing, 0.1 = 0.1 * (max - min) anisotropic= True: *all* continuous axes get separate x, y values for discrete axes: random choice from the defined values for continuous axes: interpolated value between axis.minimum and axis.maximum if discreteLocation is given, make a random location for the continuous part. assuming we want this location for testing the ufoOperator machine: we will eventually need a designspace location, not a userspace location. """ workLocation = {} if discreteLocation: workLocation.update(discreteLocation) else: for aD in self.getOrderedDiscreteAxes(): workLocation[aD.name] = random.choice(aD.values) for aD in self.getOrderedContinuousAxes(): # use the map on the extremes to make sure we randomise between the proper extremes. aD_minimum = aD.map_forward(aD.minimum) aD_maximum = aD.map_forward(aD.maximum) if extrapolate: delta = (aD.maximum - aD.minimum) extraMinimum = aD_minimum - extrapolate * delta extraMaximum = aD_maximum + extrapolate * delta else: extraMinimum = aD_minimum extraMaximum = aD_maximum if anisotropic: x = ip(extraMinimum, extraMaximum, random.random()) y = ip(extraMinimum, extraMaximum, random.random()) if roundValues: x = round(x) y = round(y) workLocation[aD.name] = (x, y) else: v = ip(extraMinimum, extraMaximum, random.random()) if roundValues: v = round(v) workLocation[aD.name] = v return workLocation def getLocationsForFont(self, fontObj): # returns the locations this fontObj is used at, in this designspace # returns [], [] if the fontObj is not used at all # returns [loc], [] if the fontObj has no discrete location. # Note: this returns *a list* as one fontObj can be used at multiple locations in a designspace. # Note: fontObj must have a path. discreteLocations = [] continuousLocations = [] for s in self.sources: if s.path == fontObj.path: cl, dl = self.splitLocation(s.location) discreteLocations.append(dl) continuousLocations.append(cl) return continuousLocations, discreteLocations # @memoize def makeFontProportions(self, location, bend=False, roundGeometry=True): """Calculate the basic font proportions for this location, to map out expectations for drawing""" self.loadFonts() continuousLocation, discreteLocation = self.splitLocation(location) infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) data = dict(unitsPerEm=1000, ascender=750, descender=-250, xHeight=500) if infoMutator is None: return data if not self.isAnisotropic(continuousLocation): infoInstanceObject = infoMutator.makeInstance(continuousLocation, bend=bend) else: locHorizontal, locVertical = self.splitAnisotropic(continuousLocation) horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) # merge them again infoInstanceObject = (1, 0) * horizontalInfoInstanceObject + (0, 1) * verticalInfoInstanceObject if roundGeometry: infoInstanceObject = infoInstanceObject.round() data = dict(unitsPerEm=infoInstanceObject.unitsPerEm, ascender=infoInstanceObject.ascender, descender=infoInstanceObject.descender, xHeight=infoInstanceObject.xHeight) return data @memoize def makeOneGlyph(self, glyphName, location, decomposeComponents=True, useVarlib=False, roundGeometry=False, clip=False): """ glyphName: location: location including discrete axes, in **designspace** coordinates. decomposeComponents: decompose all components so we get a proper representation of the shape useVarlib: use varlib as mathmodel. Otherwise it is mutatorMath roundGeometry: round all geometry to integers clip: restrict axis values to the defined minimum and maximum + Supports extrapolation for varlib and mutatormath: though the results can be different + Supports anisotropic locations for varlib and mutatormath. Obviously this will not be present in any Variable font exports. Returns: a mathglyph, results are cached """ self.loadFonts() continuousLocation, discreteLocation = self.splitLocation(location) bend=False # if not self.extrapolate: # Axis values are in userspace, so this needs to happen *after* clipping. continuousLocation = self.clipDesignLocation(continuousLocation) # check if the discreteLocation, if there is one, is within limits if discreteLocation is not None: if not self.checkDiscreteAxisValues(discreteLocation): if self.debug: self.logger.info(f"\t\tmakeOneGlyph reports: {location} has illegal value for discrete location") return None previousModel = self.useVarlib self.useVarlib = useVarlib glyphInstanceObject = None glyphMutator, unicodes = self.getGlyphMutator(glyphName, decomposeComponents=decomposeComponents, discreteLocation=discreteLocation) if not glyphMutator: return None try: if not self.isAnisotropic(location): glyphInstanceObject = glyphMutator.makeInstance(continuousLocation, bend=bend) else: if self.debug: self.logger.info(f"\t\tmakeOneGlyph anisotropic location: {location}") loc = Location(continuousLocation) locHorizontal, locVertical = self.splitAnisotropic(loc) # split anisotropic location into horizontal and vertical components horizontalGlyphInstanceObject = glyphMutator.makeInstance(locHorizontal, bend=bend) verticalGlyphInstanceObject = glyphMutator.makeInstance(locVertical, bend=bend) # merge them again glyphInstanceObject = (1, 0) * horizontalGlyphInstanceObject + (0, 1) * verticalGlyphInstanceObject if self.debug: self.logger.info(f"makeOneGlyph anisotropic glyphInstanceObject {glyphInstanceObject}") except IndexError: # alignment problem with the data? if self.debug: note = "makeOneGlyph: Quite possibly some sort of data alignment error in %s" % glyphName self.logger.info(note) return None if glyphInstanceObject: glyphInstanceObject.unicodes = unicodes if roundGeometry: glyphInstanceObject.round() self.useVarlib = previousModel return glyphInstanceObject def makeOneInfo(self, location, roundGeometry=False, clip=False): """ Make the fontMath.mathInfo object for this location. You need to extract this to an instance font. location: location including discrete axes, in **designspace** coordinates. """ if self.debug: self.logger.info(f"\t\t\tmakeOneInfo for {location}") self.loadFonts() bend = False anisotropic, continuousLocation, discreteLocation, locHorizontal, locVertical = self.getLocationType(location) # so we can take the math object that comes out of the calculation infoMutator = self.getInfoMutator(discreteLocation=discreteLocation) infoInstanceObject = None if infoMutator is not None: if not anisotropic: infoInstanceObject = infoMutator.makeInstance(continuousLocation, bend=bend) else: horizontalInfoInstanceObject = infoMutator.makeInstance(locHorizontal, bend=bend) verticalInfoInstanceObject = infoMutator.makeInstance(locVertical, bend=bend) # merge them again infoInstanceObject = (1,0) * horizontalInfoInstanceObject + (0,1) * verticalInfoInstanceObject if self.roundGeometry: infoInstanceObject = infoInstanceObject.round() if self.debug: if infoInstanceObject is not None: self.logger.info(f"\t\t\t\tmakeOneInfo outcome: {infoInstanceObject}") else: self.logger.info(f"\t\t\t\tmakeOneInfo outcome: None") return infoInstanceObject def makeOneKerning(self, location, pairs=None): """ Make the fontMath.mathKerning for this location. location: location including discrete axes, in **designspace** coordinates. pairs: a list of pairs, if you want to get a subset """ if self.debug: self.logger.info(f"\t\t\tmakeOneKerning for {location}") self.loadFonts() bend = False kerningObject = None anisotropic, continuousLocation, discreteLocation, locHorizontal, locVertical = self.getLocationType(location) if pairs: try: kerningMutator = self.getKerningMutator(pairs=pairs, discreteLocation=discreteLocation) kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) except Exception: note = f"makeOneKerning: Could not make kerning for {location}\n{traceback.format_exc()}" if self.debug: self.logger.info(note) else: kerningMutator = self.getKerningMutator(discreteLocation=discreteLocation) if kerningMutator is not None: kerningObject = kerningMutator.makeInstance(locHorizontal, bend=bend) # extract the object later if self.debug: self.logger.info(f"\t\t\t\t{len(kerningObject.keys())} kerning pairs added") if self.roundGeometry: kerningObject.round() if self.debug: if kerningObject is not None: self.logger.info(f"\t\t\t\tmakeOneKerning outcome: {kerningObject.items()}") else: self.logger.info(f"\t\t\t\tmakeOneKerning outcome: None") return kerningObject def _copyFontInfo(self, sourceInfo, targetInfo): """ Copy the non-calculating fields from the source info.""" infoAttributes = [ "versionMajor", "versionMinor", "copyright", "trademark", "note", "openTypeGaspRangeRecords", "openTypeHeadCreated", "openTypeHeadFlags", "openTypeNameDesigner", "openTypeNameDesignerURL", "openTypeNameManufacturer", "openTypeNameManufacturerURL", "openTypeNameLicense", "openTypeNameLicenseURL", "openTypeNameVersion", "openTypeNameUniqueID", "openTypeNameDescription", "#openTypeNamePreferredFamilyName", "#openTypeNamePreferredSubfamilyName", "#openTypeNameCompatibleFullName", "openTypeNameSampleText", "openTypeNameWWSFamilyName", "openTypeNameWWSSubfamilyName", "openTypeNameRecords", "openTypeOS2Selection", "openTypeOS2VendorID", "openTypeOS2Panose", "openTypeOS2FamilyClass", "openTypeOS2UnicodeRanges", "openTypeOS2CodePageRanges", "openTypeOS2Type", "postscriptIsFixedPitch", "postscriptForceBold", "postscriptDefaultCharacter", "postscriptWindowsCharacterSet" ] for infoAttribute in infoAttributes: copy = False if self.ufoVersion == 1 and infoAttribute in fontInfoAttributesVersion1: copy = True elif self.ufoVersion == 2 and infoAttribute in fontInfoAttributesVersion2: copy = True elif self.ufoVersion == 3 and infoAttribute in fontInfoAttributesVersion3: copy = True if copy: value = getattr(sourceInfo, infoAttribute) setattr(targetInfo, infoAttribute, value) if __name__ == "__main__": import time, random from fontParts.world import RFont ds5Path = "../../Tests/ds5/ds5.designspace" dumpCacheLog = True makeUFOs = True debug = True startTime = time.time() if ds5Path is None: doc = UFOOperator() else: doc = UFOOperator(ds5Path, useVarlib=True, debug=False) print("Initialised without debug, no logger:", doc.logger) doc.debug=True print("Set debug to True, after initialisation:", doc.debug) doc.loadFonts() print("loadFonts checks and creates a logger if needed:", doc.logger) # test the getLibEntryMutator testLibMathKey = 'com.letterror.ufoOperator.libMathTestValue' doc.libKeysForProcessing.append(testLibMathKey) print('processing these keys', doc.libKeysForProcessing) if makeUFOs: doc.generateUFOs() randomLocation = doc.randomLocation() randomGlyphName = random.choice(doc.glyphNames) res = doc.makeOneGlyph(randomGlyphName, location=randomLocation) endTime = time.time() duration = endTime - startTime print(f"duration: {duration}" ) # make some font proportions print(doc.makeFontProportions(randomLocation)) # some random locations for i in range(10): print(doc.randomLocation(extrapolate=0.1)) # this is what reverse component mapping looks like: print("getReverseComponentMapping:") print(doc.getReverseComponentMapping()) # these are all the discrete locations in this designspace print("getDiscreteLocations()", doc.getDiscreteLocations()) for discreteLocation in doc.getDiscreteLocations(): s = doc.findDefault(discreteLocation) print(f"default for discreteLocation {discreteLocation} {s}") # include glyphs in which the glyph is used a component print(doc.glyphChanged(randomGlyphName, includeDependencies=True)) # get a list of font objects doc.loadFonts() print(doc.glyphsInCache()) print(doc.clipDesignLocation(dict(width=(-1000, 2000)))) print("locationWillClip()", doc.locationWillClip(dict(width=(-1000, 2000)))) defaultLocation = doc.newDefaultLocation() print("locationWillClip(default)", doc.locationWillClip(defaultLocation)) print('newDefaultLocation()', doc.newDefaultLocation(discreteLocation={'countedItems': 3.0, 'outlined': 1.0})) print('newDefaultLocation()', doc.newDefaultLocation()) print("findDefaultFont()", doc.findDefaultFont().path) print("findDefaultFont()", doc.findDefaultFont(discreteLocation={'countedItems': 3.0, 'outlined': 1.0}).path) print("getNeutralFont()", doc.getNeutralFont().path) print("getNeutralFont()", doc.getNeutralFont(discreteLocation={'countedItems': 3.0, 'outlined': 1.0}).path) # generate instances with a limited set of decomposed glyphs # (useful for quick previews) glyph_names = ["glyphTwo"] instanceCounter = 1 for instanceDescriptor in doc.instances: instance = doc.makeInstance(instanceDescriptor, glyphNames=glyph_names, decomposeComponents=True) print("-"*100+"\n"+f"Generated instance {instanceCounter} at {instanceDescriptor.getFullDesignLocation(doc)} with decomposed partial glyph set: {','.join(instance.keys())}") for name in glyph_names: glyph = instance[name] print(f"- {glyph.name} countours:{len(glyph)}, components: {len(glyph.components)}") print() instanceCounter+=1 # component related dependencies glyphName = "glyphOne" dependencies = doc.getGlyphDependencies(glyphName) print(f"{glyphName} dependencies: {dependencies}") # make kerning for one location, for a subset of glyphs randomLocation = doc.randomLocation() kerns = doc.makeOneKerning(randomLocation, pairs=[('glyphOne', 'glyphTwo')]) print('kerns', kerns.items(), "at randomLocation", randomLocation) for i in range(30): print('random location to string', doc.locationToDescriptiveString(doc.randomLocation())) instanceFontObj = doc.makeOneInstance(randomLocation) instanceFontName = doc.locationToDescriptiveString(randomLocation) print("instanceFontObj", instanceFontObj) testInstanceSavePath = f"../../Tests/ds5/makeOneInstanceOutput_{instanceFontObj.info.familyName}-{instanceFontName}.ufo" instanceFontObj.save(testInstanceSavePath) # make font info for one location randomLocation = doc.randomLocation() info = doc.makeOneInfo(randomLocation) outFont = RFont() print(type(outFont)) outFont.info.fromMathInfo(info) print('info', outFont.info, "at randomLocation", randomLocation) for f, loc in doc.getFonts(): continuousLocs, discreteLocs = doc.getLocationsForFont(f) testLoc = continuousLocs[0] testLoc.update(discreteLocs[0]) print(f, testLoc == loc) print(doc.getOrderedDiscreteAxes()) for loc, fontObj in doc.fonts.items(): print("uses", fontObj.path, doc.usesFont(fontObj)) newFontObj = RFont() print(doc.usesFont(newFontObj)) print(doc.findAllDefaults()) # the ds5 test fonts have a value for the italic slant offset. for discreteLocation in doc.getDiscreteLocations(): m = doc.getLibEntryMutator(discreteLocation=discreteLocation) if m: randomLocation = doc.randomLocation() print('italicslantoffset at', randomLocation, m.makeInstance(randomLocation)) else: print("getLibEntryMutator() returned None.") for instanceDescriptor in doc.instances: print('path for instancedescriptor', doc.pathForInstance(instanceDescriptor)) doc.collectSkippedGlyphs()ufoProcessor-1.13.3/Lib/ufoProcessor/varModels.py000066400000000000000000000156201472454271500220140ustar00rootroot00000000000000# -*- 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, extrapolate=True): # items: list of locationdict, value tuples # axes: list of axis dictionaries, not axisdescriptor objects. # model: a model, if we want to share one self.extrapolate = extrapolate self.axisOrder = [a.name for a in axes] self.axisMapper = AxisMapper(axes) self.axes = {} for a in axes: axisMinimum, axisMaximum = self.getAxisMinMax(a) mappedMinimum, mappedDefault, mappedMaximum = a.map_forward(axisMinimum), a.map_forward(a.default), a.map_forward(axisMaximum) self.axes[a.name] = (mappedMinimum, mappedDefault, mappedMaximum) if model is None: dd = [self._normalize(a) for a,b in items] ee = self.axisOrder self.model = VariationModel(dd, axisOrder=ee, extrapolate=self.extrapolate) else: self.model = model self.masters = [b for a, b in items] self.locations = [a for a, b in items] def getAxisMinMax(self, axis): # return tha axis.minimum and axis.maximum for continuous axes # return the min(axis.values), max(axis.values) for discrete axes if hasattr(axis, "values"): return min(axis.values), max(axis.values) return axis.minimum, axis.maximum def get(self, key): if key in self.model.locations: i = self.model.locations.index(key) 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] items.append((self.masters[sortedOrder], s)) return items def makeInstance(self, location, bend=False): # check for anisotropic locations here if bend: location = self.axisMapper(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=20) 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.13.3/README.md000066400000000000000000000101041472454271500155560ustar00rootroot00000000000000[![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. ## UFOOperator takes over from UfoProcessor Some deep changes were necessary to support designspace format 5 files. Rather than try to work it into unwilling old code, UFOOperator is rewritten from the bottom up. The new object *wraps* the FontTools DesignSpaceDocument object, rather than *subclassing* it. The goals for UFOOperator remain largely the same. Though the emphasis is now on providing objects that provide live interpolation, rather than generate stand alone UFOs. * Support designspace format 5, with layers. *Not all elements of designspace 5 are supported.* * 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 anisotropic locations in both math models, even if Variable Fonts won't. These are super useful during design, so I need them to work. * Support extrapolation in both math models, even if Variable Fonts won't. Note that MutatorMath and VarLib approach extrapolation differently. Useful for design-explorations. * Support discrete axes. This is a bit complex, but it is nice when it works. * Apply avar-like designspace bending * Generate actual UFO instances in formats 3. * Round geometry as requested * Try to stay up to date with fontTools * Baseclass for tools that need access to designspace data. * Some caching of MutatorMath and Varlib flavored mutators. ## No more rules UFOProcessor could execute *some* of the feature-variation rules when it generated UFOs. But these variations has become much more complex than can be faked with simple glyph-swapping. So I did not port that to UFOOperator. UFOs generated with UFOOperator will have all the glyphs in the same places as their sources. ## Discrete axes A *discrete axis* is a way to fit different interpolating systems into a single designspace. For instance a *roman* could be on ```italic=0``` and the *italic* on ```italic=1```. The roman and italic are in the same file, but to UFOOperator they are separate systems that interpolate along the normal axes. If is are more than one discrete axis in a designspace, each combination of the axis values, each *discrete location* can be an interpolating system. So some of the methods of UFOOperator require a `discrete location` to know which interpolating system is needed. ## Examples UFOProcessor (old) 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. ufoProcessor-1.13.3/Tests/000077500000000000000000000000001472454271500154055ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/000077500000000000000000000000001472454271500160775ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/doc_version_test.py000066400000000000000000000010501472454271500220160ustar00rootroot00000000000000# test designspacedocument versioning from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor doc = DesignSpaceDocument() doc.formatVersion = "4.0" print(doc.formatTuple) a1 = AxisDescriptor() a1.minimum = 400 a1.maximum = 1000 a1.default = 400 #a1.map = ((400,400), (700,900), (1000,1000)) a1.name = "width" a1.tag = "wdth" #a1.axisOrdering = 1 doc.addAxis(a1) path = "ds4_version_test.designspace" print(doc.formatTuple) doc.write(path) ufoProcessor-1.13.3/Tests/ds4/ds4.designspace000066400000000000000000000060131472454271500210000ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/ds4_makeTestDoc.py000066400000000000000000000044401472454271500214300ustar00rootroot00000000000000# make a test designspace format 4 with 1 continuous axis # shouls save as format 4? # axis width is a normal interpolation with a change in width from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor from fontTools.designspaceLib.split import splitInterpolable import os import fontTools print(fontTools.version) import ufoProcessor print(ufoProcessor.__file__) import ufoProcessor.ufoOperator #doc = DesignSpaceDocument() doc = ufoProcessor.ufoOperator.UFOOperator() doc.formatVersion = "4.0" print(doc.formatVersion) #https://fonttools.readthedocs.io/en/latest/designspaceLib/python.html#axisdescriptor a1 = AxisDescriptor() a1.minimum = 400 a1.maximum = 1000 a1.default = 400 a1.map = ((400,400), (700,900), (1000,1000)) a1.name = "width" a1.tag = "wdth" #a1.axisOrdering = 1 # if we add this the doc version will go to 5.0 doc.addAxis(a1) default = {a1.name: a1.default} # add sources for c in [a1.minimum, a1.maximum]: s1 = SourceDescriptor() s1.path = os.path.join("sources", f"geometrySource_c_{c}.ufo") s1.name = f"geometrySource{c}" sourceLocation = dict(width=c) s1.location = sourceLocation s1.kerning = True s1.familyName = "SourceFamilyName" if default == sourceLocation: s1.copyGroups = True s1.copyFeatures = True s1.copyInfo = True if c == 400: tc = "Narrow" elif c == 1000: tc = "Wide" else: tc = f"weight_{c}" s1.styleName = tc doc.addSource(s1) # add instances extrapolateAmount = 100 interestingWeightValues = [a1.minimum-extrapolateAmount, (400, 1200), 300, 400, 550, 700, a1.maximum, a1.maximum+extrapolateAmount] for c in interestingWeightValues: s1 = InstanceDescriptor() s1.path = os.path.join("instances", f"geometryInstance_c_{c}.ufo") s1.location = dict(width=c) s1.familyName = "InstanceFamilyName" if c == 400: tc = "Narrow" elif c == 1000: tc = "Wide" else: tc = f"weight_{c}" s1.name = f"geometryInstance" s1.styleName = tc s1.kerning = True s1.info = True doc.addInstance(s1) path = "ds4.designspace" print(doc.formatVersion) doc.write(path) for s in doc.sources: print(s.location) # ok. now about generating the instances. #udoc = ufoProcessor.ufoOperator.UFOOperator(path) #udoc.read(path) #udoc.loadFonts() #udoc.generateUFOs() ufoProcessor-1.13.3/Tests/ds4/ds4_test_designspaceProblems.py000066400000000000000000000005331472454271500242540ustar00rootroot00000000000000import designspaceProblems print(designspaceProblems.__file__) path= "ds5.designspace" checker = designspaceProblems.DesignSpaceChecker(path) checker.checkEverything() print(checker.checkDesignSpaceGeometry()) checker.checkSources() checker.checkInstances() print("hasStructuralProblems", checker.hasStructuralProblems()) print(checker.problems)ufoProcessor-1.13.3/Tests/ds4/ds4_version_test.designspace000066400000000000000000000002631472454271500236050ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/000077500000000000000000000000001472454271500200665ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/000077500000000000000000000000001472454271500256235ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/features.fea000066400000000000000000000000671472454271500301210ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/fontinfo.plist000066400000000000000000000030751472454271500305270ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_(400, 1200) unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/glyphs/000077500000000000000000000000001472454271500271315ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/glyphs/contents.plist000066400000000000000000000004701472454271500320440ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/glyphs/glyphO_ne.glif000066400000000000000000000011111472454271500317120ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500317410ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/groups.plist000066400000000000000000000006731472454271500302250ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/kerning.plist000066400000000000000000000007741472454271500303450ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/layercontents.plist000066400000000000000000000004371472454271500315760ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/lib.plist000066400000000000000000000004721472454271500274510ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_(400, 1200).ufo/metainfo.plist000066400000000000000000000004761472454271500305110ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/000077500000000000000000000000001472454271500250605ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/features.fea000066400000000000000000000000671472454271500273560ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/fontinfo.plist000066400000000000000000000030651472454271500277630ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_100 unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/glyphs/000077500000000000000000000000001472454271500263665ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313010ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011111472454271500311470ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500311760ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/groups.plist000066400000000000000000000006731472454271500274620ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/kerning.plist000066400000000000000000000007741472454271500276020ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/layercontents.plist000066400000000000000000000004371472454271500310330ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/lib.plist000066400000000000000000000004721472454271500267060ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_100.ufo/metainfo.plist000066400000000000000000000004761472454271500277460ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/000077500000000000000000000000001472454271500251405ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/features.fea000066400000000000000000000000671472454271500274360ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/fontinfo.plist000066400000000000000000000030571472454271500300440ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Wide unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/glyphs/000077500000000000000000000000001472454271500264465ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313610ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011131472454271500312310ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312560ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/groups.plist000066400000000000000000000006731472454271500275420ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/kerning.plist000066400000000000000000000007741472454271500276620ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/layercontents.plist000066400000000000000000000004371472454271500311130ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/lib.plist000066400000000000000000000004721472454271500267660ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1000.ufo/metainfo.plist000066400000000000000000000004761472454271500300260ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/000077500000000000000000000000001472454271500251415ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/features.fea000066400000000000000000000000671472454271500274370ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/fontinfo.plist000066400000000000000000000030661472454271500300450ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_1100 unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/glyphs/000077500000000000000000000000001472454271500264475ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313620ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011131472454271500312320ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312570ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/groups.plist000066400000000000000000000006731472454271500275430ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/kerning.plist000066400000000000000000000007741472454271500276630ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/layercontents.plist000066400000000000000000000004371472454271500311140ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/lib.plist000066400000000000000000000004721472454271500267670ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_1100.ufo/metainfo.plist000066400000000000000000000004761472454271500300270ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/000077500000000000000000000000001472454271500250625ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/features.fea000066400000000000000000000000671472454271500273600ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/fontinfo.plist000066400000000000000000000030651472454271500277650ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_300 unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/glyphs/000077500000000000000000000000001472454271500263705ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313030ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011111472454271500311510ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312000ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/groups.plist000066400000000000000000000006731472454271500274640ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/kerning.plist000066400000000000000000000007741472454271500276040ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/layercontents.plist000066400000000000000000000004371472454271500310350ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/lib.plist000066400000000000000000000004721472454271500267100ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_300.ufo/metainfo.plist000066400000000000000000000004761472454271500277500ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/000077500000000000000000000000001472454271500250635ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/features.fea000066400000000000000000000000671472454271500273610ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/fontinfo.plist000066400000000000000000000030611472454271500277620ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName Narrow unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/glyphs/000077500000000000000000000000001472454271500263715ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313040ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011111472454271500311520ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312010ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/groups.plist000066400000000000000000000006731472454271500274650ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/kerning.plist000066400000000000000000000007741472454271500276050ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/layercontents.plist000066400000000000000000000004371472454271500310360ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/lib.plist000066400000000000000000000004721472454271500267110ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_400.ufo/metainfo.plist000066400000000000000000000004761472454271500277510ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/000077500000000000000000000000001472454271500250715ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/features.fea000066400000000000000000000000671472454271500273670ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/fontinfo.plist000066400000000000000000000030651472454271500277740ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_550 unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/glyphs/000077500000000000000000000000001472454271500263775ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313120ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011131472454271500311620ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312070ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/groups.plist000066400000000000000000000006731472454271500274730ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/kerning.plist000066400000000000000000000007741472454271500276130ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 275 glyphTwo glyphOne -275 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/layercontents.plist000066400000000000000000000004371472454271500310440ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/lib.plist000066400000000000000000000004721472454271500267170ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_550.ufo/metainfo.plist000066400000000000000000000004761472454271500277570ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/000077500000000000000000000000001472454271500250665ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/features.fea000066400000000000000000000000671472454271500273640ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/fontinfo.plist000066400000000000000000000030651472454271500277710ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName InstanceFamilyName guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName weight_700 unitsPerEm 1000.0 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/glyphs/000077500000000000000000000000001472454271500263745ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500313070ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/glyphs/glyphO_ne.glif000066400000000000000000000011131472454271500311570ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/glyphs/glyphT_wo.glif000066400000000000000000000003001472454271500312040ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/groups.plist000066400000000000000000000006731472454271500274700ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/kerning.plist000066400000000000000000000007741472454271500276100ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 450 glyphTwo glyphOne -450 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/layercontents.plist000066400000000000000000000004371472454271500310410ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/lib.plist000066400000000000000000000004721472454271500267140ustar00rootroot00000000000000 public.glyphOrder glyphOne glyphTwo ufoProcessor-1.13.3/Tests/ds4/instances/geometryInstance_c_700.ufo/metainfo.plist000066400000000000000000000004761472454271500277540ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/readme.md000066400000000000000000000013341472454271500176570ustar00rootroot00000000000000# Test for designspace 5 format with discrete and interpolating axes I need to keep notes somewhere. The script `ds5_makeTestDoc.py` makes a new DS5 designspace file with 3 axes. * `wdth` with minimum at `400`, default `400`, maximum at `1000`. An interpolating axis. * `DSC1`, a discrete axis. Values at `1,2,3`. Default at `1`. Named `countedItems` Variations along this axis consist of 1, 2, and 3 boxes. * `DSC2`, a discrete axis. Values at `0, 1`. Default at `0`. Named `outlined`. Variations along this axis consist of solid shapes and outlined shapes. The `masters` folder has sources for all intersections of these axes. The default of the whole system is at `wdth: 400, countedItems: 1, outlined: 0` ![](masters.jpg) ufoProcessor-1.13.3/Tests/ds4/sources/000077500000000000000000000000001472454271500175625ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/000077500000000000000000000000001472454271500243305ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/features.fea000066400000000000000000000000701472454271500266200ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/fontinfo.plist000066400000000000000000000030731472454271500272320ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo descender -200 familyName One_wide_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_1_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/glyphs/000077500000000000000000000000001472454271500256365ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500305510ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010511472454271500304220ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500304620ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500307110ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/groups.plist000066400000000000000000000006731472454271500267320ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/kerning.plist000066400000000000000000000007741472454271500270520ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/layercontents.plist000066400000000000000000000004371472454271500303030ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/lib.plist000066400000000000000000000022051472454271500261520ustar00rootroot00000000000000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_1000.ufo/metainfo.plist000066400000000000000000000004761472454271500272160ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/000077500000000000000000000000001472454271500242535ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/features.fea000066400000000000000000000000671472454271500265510ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/fontinfo.plist000066400000000000000000000030731472454271500271550ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName One_narrow_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_1_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/glyphs/000077500000000000000000000000001472454271500255615ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500304740ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010471472454271500303520ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500304050ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500306340ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/groups.plist000066400000000000000000000006731472454271500266550ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/kerning.plist000066400000000000000000000007741472454271500267750ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/layercontents.plist000066400000000000000000000004371472454271500302260ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/lib.plist000066400000000000000000000022051472454271500260750ustar00rootroot00000000000000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds4/sources/geometrySource_c_400.ufo/metainfo.plist000066400000000000000000000004761472454271500271410ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/000077500000000000000000000000001472454271500161005ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/ds5.designspace000066400000000000000000000510411472454271500210030ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/ds5_addKerningToTheseMasters.py000066400000000000000000000005101472454271500241220ustar00rootroot00000000000000 p1 = ('glyphOne', 'glyphTwo') p2 = ('glyphTwo', 'glyphOne') g = "glyphOne" for f in AllFonts(): print(f.path, f.kerning.items()) f.kerning[p1] = f[g].width f.kerning[p2] = -f[g].width f.kerning[('glyphTwo', 'glyphTwo')] = -400 f.kerning[('glyphOne', 'glyphOne')] = 400 f.save() f.close() ufoProcessor-1.13.3/Tests/ds5/ds5_extrapolateTest.py000066400000000000000000000015431472454271500224200ustar00rootroot00000000000000# test the extrapolation in VariationModel from fontTools.varLib.models import VariationModel locations = [ dict(wgth=0), dict(wght=1000), ] values = [10, 20] m = VariationModel(locations, extrapolate=True) # interpolating assert m.interpolateFromMasters(dict(wght=0), values) == 10 assert m.interpolateFromMasters(dict(wght=500), values) == 15 assert m.interpolateFromMasters(dict(wght=1000), values) == 20 # extrapolate over max assert m.interpolateFromMasters(dict(wght=1500), values) == 25 assert m.interpolateFromMasters(dict(wght=2000), values) == 30 # extrapolation over min gets stuck print(m.interpolateFromMasters(dict(wght=-500), values), m.interpolateFromMasters(dict(wght=-1000), values)) # would expect: assert m.interpolateFromMasters(dict(wght=-500), values) == -5 assert m.interpolateFromMasters(dict(wght=-1000), values) == -10 ufoProcessor-1.13.3/Tests/ds5/ds5_makeTestDoc.py000066400000000000000000000074321472454271500214360ustar00rootroot00000000000000# make a test designspace format 5 with 1 continuous and 2 discrete axes. # axis width is a normal interpolation with a change in width # axis DSC1 is a discrete axis showing 1, 2, 3 items in the glyph # axis DSC2 is a discrete axis showing a solid or outlined shape from fontTools.designspaceLib import DesignSpaceDocument, SourceDescriptor, InstanceDescriptor, AxisDescriptor, RuleDescriptor, processRules, DiscreteAxisDescriptor from fontTools.designspaceLib.split import splitInterpolable import os import fontTools print(fontTools.version) import ufoProcessor print(ufoProcessor.__file__) doc = DesignSpaceDocument() #https://fonttools.readthedocs.io/en/latest/designspaceLib/python.html#axisdescriptor a1 = AxisDescriptor() a1.minimum = 400 a1.maximum = 1000 a1.default = 400 a1.map = ((400,400), (700,900), (1000,1000)) a1.name = "width" a1.tag = "wdth" a1.axisOrdering = 1 doc.addAxis(a1) a2 = DiscreteAxisDescriptor() a2.values = [1, 2, 3] a2.default = 1 a2.name = "countedItems" a2.tag = "DSC1" a2.axisOrdering = 2 doc.addAxis(a2) a3 = DiscreteAxisDescriptor() a3.values = [0, 1] a3.default = 0 a3.name = "outlined" a3.tag = "DSC2" a3.axisOrdering = 3 doc.addAxis(a3) default = {a1.name: a1.default, a2.name: a2.default, a3.name: a3.default} # add sources # public.skipExportGlyphs for c in [a1.minimum, a1.maximum]: for d1 in a2.values: for d2 in a3.values: s1 = SourceDescriptor() s1.path = os.path.join("sources", f"geometrySource_c_{c}_d1_{d1}_d2_{d2}.ufo") s1.name = f"geometrySource{c} {d1} {d2}" sourceLocation = dict(width=c, countedItems=d1, outlined=d2) s1.location = sourceLocation s1.kerning = True s1.familyName = "SourceFamilyName" if default == sourceLocation: s1.copyGroups = True s1.copyFeatures = True s1.copyInfo = True td1 = ["One", "Two", "Three"][(d1-1)] if c == 400: tc = "Narrow" elif c == 1000: tc = "Wide" if d2 == 0: td2 = "solid" else: td2 = "open" s1.styleName = f"{td1}{tc}{td2}" doc.addSource(s1) def ip(a,b,f): return a + f*(b-a) # add instances steps = 8 extrapolateAmount = 100 interestingWeightValues = [(400, 700), 300, 400, 550, 700, 1000, 1100] mathModelPrefKey = "com.letterror.mathModelPref" mathModelVarlibPref = "previewVarLib" mathModelMutatorMathPref = "previewMutatorMath" # com.letterror.mathModelPref # previewVarLib for c in interestingWeightValues: for d1 in a2.values: for d2 in a3.values: s1 = InstanceDescriptor() s1.path = os.path.join("instances", f"geometryInstance_c_{c}_d1_{d1}_d2_{d2}.ufo") s1.location = dict(width=c, countedItems=d1, outlined=d2) s1.familyName = "InstanceFamilyName" td1 = ["One", "Two", "Three"][(d1-1)] if c == 400: tc = "Narrow" elif c == 1000: tc = "Wide" if d2 == 0: td2 = "Solid" else: td2 = "Open" s1.name = f"geometryInstance {td1} {tc} {td2}" s1.styleName = f"{td1}{tc}{td2}" s1.kerning = True s1.info = True doc.addInstance(s1) # add variable font descriptors splits = splitInterpolable(doc) for discreteLocation, subSpace in splitInterpolable(doc): print(discreteLocation, subSpace) #print(doc.getVariableFonts()) #for item in doc.getVariableFonts(): # doc.addVariableFont(item) doc.variableFonts.clear() print(doc.variableFonts) variableFonts = doc.getVariableFonts() print("variableFonts", variableFonts) doc.addVariableFont(variableFonts[0]) for i, item in enumerate(variableFonts): print(i, item) path = "ds5.designspace" print(doc.lib) doc.write(path) print(dir(doc)) for a in doc.axes: if hasattr(a, "values"): print(a.name, "d", a.values) else: print(a.name, "r", a.minimum, a.maximum) for s in doc.sources: print(s.location) # ok. now about generating the instances. udoc = ufoProcessor.DesignSpaceProcessor() udoc.read(path) ufoProcessor-1.13.3/Tests/ds5/ds5_no_discrete_axes.designspace000066400000000000000000000041411472454271500244000ustar00rootroot00000000000000 com.letterror.mathModelPref previewMutatorMath com.letterror.skateboard.interactionSources horizontal width ignore vertical com.letterror.skateboard.previewLocation width 790.9379266617385 com.letterror.skateboard.previewText SKATEBOARD ufoProcessor-1.13.3/Tests/ds5/ds5_test_designspaceProblems.py000066400000000000000000000005331472454271500242560ustar00rootroot00000000000000import designspaceProblems print(designspaceProblems.__file__) path= "ds5.designspace" checker = designspaceProblems.DesignSpaceChecker(path) checker.checkEverything() print(checker.checkDesignSpaceGeometry()) checker.checkSources() checker.checkInstances() print("hasStructuralProblems", checker.hasStructuralProblems()) print(checker.problems)ufoProcessor-1.13.3/Tests/ds5/readme.md000066400000000000000000000013341472454271500176600ustar00rootroot00000000000000# Test for designspace 5 format with discrete and interpolating axes I need to keep notes somewhere. The script `ds5_makeTestDoc.py` makes a new DS5 designspace file with 3 axes. * `wdth` with minimum at `400`, default `400`, maximum at `1000`. An interpolating axis. * `DSC1`, a discrete axis. Values at `1,2,3`. Default at `1`. Named `countedItems` Variations along this axis consist of 1, 2, and 3 boxes. * `DSC2`, a discrete axis. Values at `0, 1`. Default at `0`. Named `outlined`. Variations along this axis consist of solid shapes and outlined shapes. The `masters` folder has sources for all intersections of these axes. The default of the whole system is at `wdth: 400, countedItems: 1, outlined: 0` ![](masters.jpg) ufoProcessor-1.13.3/Tests/ds5/sources.jpg000066400000000000000000002277761472454271500203120ustar00rootroot00000000000000JFIFHHjExifMM*i&Pڠ4ASCIIScreenshot !http://ns.adobe.com/xap/1.0/ 8Photoshop 3.08BIM8BIM%ُ B~ 4ICC_PROFILE $applmntrRGB XYZ   acspAPPLAPPL-appldescPbdscmcprt#wtptrXYZgXYZbXYZrTRC( aarg 4 vcgt T0ndin >mmod (vcgp 8bTRC( gTRC( aabg 4 aagg 4 descDisplaymluc& hrHR&koKR&nbNO&id&huHU&csCZ&daDK&nlNL&fiFI&itIT&esES&roRO&frCA&ar&ukUA&heIL&zhTW&viVN&skSK&zhCN&ruRU&enGB&frFR&ms&hiIN&thTH&caES&enAU&esXL&deDE&enUS&ptBR&plPL&elGR&svSE&trTR&ptPT&jaJP&Thunderbolt DisplaytextCopyright Apple Inc., 2022XYZ XYZ q9gXYZ a#XYZ # curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6@UL% PT@333333mmod'  xRvcgpffffff3343343344" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC   $$$$$))))))))))C  +++++++++++++++++++++++++++++++++++++++++++++++++++> ?.(((LњZ)P@E:nhQME74P@(ShQM)Pu(SsE:m)E6u@E:nhQME74P@(ShQM)Pu(SsE:m)E6u@E:nhQME74P@(ShQM)Pu(SsE:m)E6u@E:nhQME6&LEPEPEP.()3@i(.i3M&ZL{lѺI'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|ѺFu o F}}n7Ѿ' o QF4n7Ѿ'F}|@7Q}(j47SP@-74QEQE.(&C@iMFZVU?P{ Ջ(|Cߐc{}jVZCߚU[P>O[됲wUVr3~5kum{ZH*r PJ I<_*.k(e??lnCIYY Ǧ3Pػ F.~kV`'д}rNF}vƀ&K F.~kVյ ui")5b1w<_*](/x7U [P>.~kQߚUEc5bo1w<_*( ߚTbx7UlQ@ F.~kV<_*](5oآ1~kQߚUEcx7U [P?_o1w<_*( |]5bK F.~kVߚTbx7UlQ@5oآ1w<_*](/x7U [OgbfHS$ >.~kQߚUN;kvZkb8sۼ}pqzVݝWq@$PJ=#BEPK F.~kVsC+25-cx7U Zr gT 2OAz1~kQߚUi+WIiT]4fQr5fyᶅ.]c5,*I$ ]5wNqXeG t O$ m,j,Oh+/x7U Wlo5;H$(܎tߚTbx7UXUu * C\ڄiSW`ƴ(/x7U [P>.~kQߚUEc5bo1w<_*( ߚTbx7UlQ@ F.~kV<_*](5oآ1~kQߚUEcx7U [P?_o1w<_*( |]5bK F.~kVߚTbx7UlQ@5oآ1w<_*](/x7U [P>.~kQߚUEc5bo1w<_*( ߚTbx7UlQ@ F.~kV<_*](#+x7U/Wo֢2r9__*\$VBx3= j$%C*z`fP9ɍYPR }-6@Q@.Ja4Q1#@OO򮊹+GG'o7=|k~ɾmv:4-V_[+2]gk[[k(54UF8&7O3g}Ώf~\~5խ kw25xcBҧUY;339N? ޯG'o7=|k |1j4Yz#?q ~gF`6>Y@Q 0X(((((((((((((((((uojGd&2 0a:>R>̑]K -KzWwX>"TH-Ѯ4akş%رOaYRQwN{m1e:ַK+z:.Usp56fb-ٓtqAmU-0{3*@n9qT0+SmF]cbk*S49 NDVD\+讔Ҁ\g薂X9%^iČ/r!pB`\lMѥ?[Y } @=h FlAM}YHP;+*_G/混{y.@ZU Fড়{t=2XNB` S  Vv+Ak!#q͖iY/@^:Γ:Bj ىtOfFmE$/qqxvZ_]=Lw36dWBHWU~jZdY@RWCx &_M"+ؑ$sGC;~^ýQ/ufx-diP|Ip0v־K@kWxni|/]Ycmي~ٶܰRW<}ٻ[Rr 4|#~B3E@ Z( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (0?(՝3?1h)aML)˜)(?.NMD!Z sTej!Mf?<]}ZtNњ/OO 2Zhj7({'U#~_NG eaG ^Kմ-jyuīM:tLaO 'ߕvVy鞽l5:Sǻxvi>y\`}Ϡ ^$FDެAyռOw?(}[Z zA+(arYlM3^1Xz޳>evf 3'''y֚ϾmS[piI\)%tfh"ԭP>St7w+2 1[Ĩ:g֦qG\{UMŶ5UxII!PH ]5H:F wq^ZnbW]Lj0,V#EY)R<>6`jTU3 \դ@!8-cZɸDc ľdv3aBX$.zcZ毼FC iggl+<ֺcȝF }N3eľd;Fӎ:uxm%(%d1J5?Ѭ N>\y7V }.e@|qc3hӆW6B;ͻwG8z4+ik:`0Hێ+^+IcQm2Q9NQwn9:SKSG_m{,HѶߺJWfʉ;FqXEfWq̾MˍTT#\$eS>1Aڥ=Ԧ}bK {W?:*2*{VE߈u_mަmzl)0~NGJNG[S>1Aڥ=[n%1LV>cX fE)ЏZEM;Dȣ#9Q"QVzQH8GV\KQ59;y 6޴[`u&-mwg鎟y.O?5"MNZܱیy!z~#KhMNN:-6;OƙxKyg%ۦ&@w%qot_E| -#E[_i&<+n]LQRt\]uQL +:-fCIik, !#>_؞+J&>c[ɺcr1U%rn}WEp~ޛ^Yh7A.,"hPƷ4_ZϦm%HA{Ұ΂( (((((((((((((((((((((((((((((((((lj7՟xq\՘MjiMQĠ QH(R.N @9@$51 +6jƾ?+$ti_ͥ跺Fy-m#]KԌP+~ӈ =‘ .ś^q ܻ^wkuO>$+BQG}ҍqq+ T֟ Sg %XkU{MqTAtB|ё8Ꞿk(hX:8 ~\xo'P290=?gkRvsvgѷlTɦ9ڧMQ@N95|QxOQңWI7,a;~\t⼻Ꮝh7/FKyV1*pGWݸƭiq]u% wo9Z^>xmmR[5đ(nNv{jxyHtG6QodsFfbqekk1)i o?ts}IؤֹORj4"ŋKmH,Rg 6{qo>g-=qRsK8[o-o-c'/WjB'Pc׷4}uw[m'ȸF]~Ӻ>#i>ENǯ[DdbO1bqOS[OD cdh卤c׊m]dhcM[tk`>l;ݝQH>pմXẉZ-R?l`=>o?' ܪ{% rW+Go|տIM|ݤxuw4F. 1/+֏o|'( w*2l1Co%B'FI\k8Ŷ}EV'_zfs}QL唸x9P #dUƌ^5&~f,ZgM6Aa?S"m8xb_ ~_HcvyEAek6&=NLE* ի6(ĺƒ֏~x– d5<͂%(7)k4t=F9K8cN18ho@rVETQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@E-Y;k@Z jWRDjP*58R.iaS5iKYPğ^^kOOJ ELs_5^q\ $:9P]Jg, DcV/5Nm$Gs<(>d9{OL@H7<cYn豼Z=i!kcB\T:ަuĢ&TxB^<޻yjxw5hwCt\pH˕9pz4MSIqGBAW'*]UnؗmҮ+o~-k3`7vʎ杧eweg 'RE "K.-V)-bdR0`b[RY3#$WlʡV..ukFTCW09u«`X&;/L[;h17Ed#:^{[^[C4Qj:+*[k:ޡ`tcQR\z9lVZ[';!j˦ؘ K'],xA+olh0* 55˳9% gHゼt^#m:E4U< ڂwD%ĉ¸ͣ+Z39RűRi6@Om_6oFNA wPXهh"H}3,mbXu1WQ*Z. y$޺}Zn73ěUT0$S)fQEy&e{c&iow4ͳ*]vI8Y$qۜqq\Nif1[fDğo `aW s֩E\[b;{W$nFNA=iwgmeQH 72"*$>kgooEwdPX;$.{jkZm*! H, ͮg'aFm 9V'U\ EqX/2F>_̝: S|'jz^J ;*Z)I06f%(((((((((((((((((((((((((((((((((((-YyPPUy*U}(F  ZAҖ?.iaVR&9+2j¿Q'Wwi/IUP;OWoi894| V߷i=_T1'?F$'(_ؿ0]àqؓ|q@קtlP|?Q? 2~-@Oğ]]B>l)v~|qJ4V~~8|qN4iZz&bO?ŒIOP+OWoi? 1'?@=_G߷j$'(ğ_ҴM'߷j$'(ğ_ӴMv~|qJ4iZz&bO?ŒIOP+OWoi? 1'?@=_G߷j$'(ğ_ҴMV~|qJ4iz&bO?ŒIOP+OWoi? 1'?@fFMV߷j$'(ğ_ӴMV~|qN4iz&bO?Œ??@=_G߷k<zR;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P;OWoiE_ӴMv~P=w*M\He?q{j57'y(R.y*Uy(ԕԂ)iJZ.iaS5iYP??ʺIUwO~j[kImw!!w8F:|ǟJmytac3p:c:W;h>MJKfBS/?0}EwLZ$f@&&Ɓ` ~4zZ"ڡ;@}{~4Yw # c 2/ h饵B u䎝ybZPt$㧯Jm%-%kY-c#kZ֮nt.n/$TiX\PRI=}F(V+V&@!s=kZUҵ;{[A"+Icx۽C(;[N5\=[ƯpTQ&B l|5vj.d;eGpUOKy{{ R=̊=B"Q UP1O9sMQ&j4 opӠ8y^fdQ";~@=?h2Frn<yyvn#}-ǙWٷs¹4uea?o+͛>=UV%TZu[[kLh ,z8 >=sMхTA7V7cޮ]XYoN:`d$d|N=qF>.VH #m,#I9;^陞hսCN#Bb,8e`ϵw֗PZE{lwE:,}UAHC k.EXy¨}LڞOڡN~@U2uKI4:vYgve $8Dr#)oͩ4N@TbČ5QXgm?dF3%1 89=dS#"9yPFj;qZ% ܘ0e$,q,FF+@{˩=G]{[Q #ZH)d2`a{: KKRPk F=wN<5 c^i 7SuKyDۥ`&&=(a؂@>^2ޡ9$;4>Qě]# #sV:*@:uvUy0N ]3 {ǧR[F/Aő~!-(6c ؚ5)9<9[i n;AFGpzg4/-Ԭ̑lᢌGmtIbǐ@+mkإil2~鞿&⿰ZX}alo.638xP]-6Fgy.*#0Y^tPjݴYKom12?/|/P䟦E͵:B#Vmdr3|ÐO hgNfn=9E c*LƬI;M|Cjo+5orDvlJҡQoT0#4v2HŘ$aJ &7WTHٙ(ܛ@/PLáhij~Bj_.c~czPBYw3^\ tYe0%X$c{U7:i[c[wT\ux`OY>_ՓmJU6V4[vv飓 r'ҪjP[A:,O,M(RP+F}{MV}VIfg-vI>c&W_#gGyWlǗ~lv /+f#c|7Ǘ{?V];'?CD4wANZ(((((((((((((((((((((((((((((((((B֌:oO Z0P]UPF  ZAҖ?.iaVR&9+2j¿Q'WaԨL0e q$t*+OTlPi>sE?t*;=4U eS#>Zui6?um˻r>\$ ~+hi={匷OXuN{rܪ1{@Tr\{=t1Kr: |`](??M`xQIWSYii~h;nfE&oCOՌL1?.wkξxfN.e4HV$ J>즦M^JF*(Tq.e6WG}&v?5jZŶBNZ y k(l+7gZ4W׾*deE8b*`o?!jS cGn{x_6q}O LdG%b]z׳7n>s}&v?5Ec}G7?ݏ?Ml@ah c[4P7n>s֡^Uݍ+- sInH(!p8Zv?4}G%Z=6%2Ysט:ilٺjn2IDh7İc M{Ś捬iJ+ ;v2[#O v?4}G/-jOIf ԲrW _ X| #{Q=G[Ӽ_-sh"H̄3"Da܀WvcO4}?ב}QK‘]^I$³Or)q]4$Tڠ6i ڌ|~dr3.Zo1"Jw}?;D^cq&Eo,vFrwkbߢGw~}&( '>wkbߢGw~}&*)y!@cO4}?uxRivwSY_dk4x|ێ/ole8QӮU%NFcm|vcO4}?,7v_cw'pmF֗ӑG8v,].5 %3KIv$m#$~whߢG+'uGZEKer){aGmǎ:։uH!$OM2:rbwhߢG#65kA23.Ƌ}f.wdvBx l39Pg?McO5vy&io.=GS|sCux-$$herXdʸ2= w~}&'5][Ӯ!W͉F}bb86"t%Vkfƙ#Hq@;DG?Mp72 ئ(B߳ Q6;g=:-%JJKI=28 ;F3@;DG?MXYKw$ dԝ(w~}&'آ1whߢG(w~}&'آkqi gIAZ]KMGlCǘͿOjRj:(c,@=I)۲i+aǝ!GlgX,A1s}1^K|Vw[Jp^p{Nk2#o_ RJE:ɺn-ދ_Ǩis"m-Ewkqn?{nлT۰IPEuc6Ə1\>:ŭwEkBȟ< 2{e煠&dVp|N 6ecGl)hy;4y<u?zȴx㳊ͮ&lX Ĝ*K^ ڶ p$I#hĄ l d# 7Ϳ:2ijj2\2:$lmO)I06M'YucwK梱Fʲ2VV1GͿh 1`[d:|{UF{I, U61}$kJOxY[1htkcbNCAeuG4EӡFbv]1\qҞt0ư*vGTL\^+K ֵ{'tHI,arY7`gtW4Ο\]hI#Vڜ vZL6zޯeϳHn>PXrI<^kkWZ@.U?x17?0fhͪzuhKDuqV'qPcsac&{"DMȎ'C/>qܚ\ 4 ,2 =@R\tW=,voۅA4ާr sReӼ7olCm{ǖ>y,1m]G&spQbV]_f:)!\:&n :l&kU qWC@Jy;5ǟyBC)D|U\99ǍL:4t۸@֡xXaӡ?owkxGpb"(m1\u]cF'ӚS'HTRFhy;4y<- Iז\:wHP.Fj+ͧuIscgP]dx@GͿ46Xɨ@HDTHX9V֙Xk6k%\A @ 0A?owjͭ^ ;#+s ~|EEmVHcHF nf="д8lF +B8o!ݞs֠,-'}Qpf c *$D>%4JŖ[zfUxcpȇ; xZۘC]KQ hɨ5 ,#2`?Wݨ!X9T@y d$ݟjy;4y< ֣%Ig=̋Q ?(@>NrF8IO`(L3lN^=Wi01U;AH%IW6&1'ߢ04-Q16GECp1qV&tۅ+=r(D9q9!j@cCLIv'o*=ҫ}Z4:)v1@0r gJj@`u^t=;#KFu=kpMy֏MFN7YgK9VB҂Ξjy8Šh0tܺbX]q00z t0sZ֢1*)a'`,@PRT7:{^ipLl*F1 8[P|UQ41R@y<<mKEE?owhy;5-ͿԴPQ$=z ԃgO!;(R.y*U}(ԂZP- K@.iaVR&9+2j¿Q'Ux6xӊUE{ѽMtb1Uk;†5cԼ!& 7wQ o}^l➵?O\"gO-Kv",J]00ۜ=h#-t{d[YnA*\,_hzC; }pw>czowS^:^LIP[q\{{]Fg6QSw\ϵy*9f-n;k(7ww2H`sx:ޱz)$淞&$veN[3ߞ;}h"ԧ/u+Ԛ5g1 Ʊb6 PI=0+hxZyL6}[UG{7 ;7Gqkswu 6?}6ϯSTK=G yZv6vHyyǃ[u-̛7! B1W7:꯫[u8-OYE`@*|Je Ak]Lu'Եx嶇Q6{buo([r0p}StkjVm KV83+oo.enVN/2G;>&%3[kPcv(xP sO҉V!sdAj9mFZ9I.ڣ85(tK,L,ȡvGFA&dF%Xp7?h.&FHoB4ïj2b!ϷMWG Y?u9 ?zWcxCQMecxAon|O#9cowݼwvyJ2 SoS@oxSԵ[bH˛naΖ CITB0HZ\Gr3L7>:1.dA=moSF4z误M9aYnQl֖4gX#i" ,$PqxZ[bQyDmUWl⠛wznO,hMƝ;!$(ܜ Pq负 72^sߥnS@BK]6Ke7wwrHѕUcO?Ě\ 1^ĎO4%5+u;襶>d2wۚJtrhiuh8n><M*4oS@;ѽMeiE͖j0 <צ 5{ I`*0byc*4oS@;ѽM^SF4u2aF1Ѽ%>Ont tf2͵(P tAGMIEru  H3@Uh/QTw{EQhG{7 Uh/QTw{fu?V5'.s=jEگ%Q%YZZP- K@.iaS5iYP??ʹ?:.|=ۦ+HֲGp輖U`w߅u$t*ʹg=Q~oNuDŽaXPKA4q1@X =? fDKظSKom7N=5yr~$n>u>.ʂd3EQ1r:Vw,o&XcnZik=J<Ҁ+V<ҏ# U#(|@u7׵uQR#DmO9 Fs[W&Ц6 G,׍ Ưl$e䬒*98=JCnW<[=Զ8]7:(W~rG,j7^k8ndm΋.QF`6(\%nSR|o_zŞ$ۦ$s,w-vMt47q #Uhyy,H_Y&~q^p:ҏZ֣yjzgYEcg#D8q5PLV֡ ^ _^Ok $yKEH'twV|m/#;@nºTLS<;+ߘ0 W};' p3*n\623,.{dҬq*'־=J<Ҁg5CxR4$AWQ'KJ."3bU-N+W(]_ķzvh2[\iV2DvЫbbH`cZ<@u7׵uQR#DmO9 FsgJ_#5cGG>FkKo3k\DC.>n@9 K! ~Q:Fdr7Aҏ# |A— m2,<Ѡ< *4|a6ŴYst ÉqcIOX@^tҍ0>I>my9Ԓ 澒?|y|_鏨Z\jKY4iFʈy}&$$b*SZxš}tY vxOgҨYhz|Zu1Lʪ3ӅPk >-Knc#o%8[T@/=ZѲR &=f3O,(t8q8sRWyG@GGPz*ǑQ{^{yWyG@GGPz*ǑQ{^{yWyG@GGPz*ǑQ{^{yWyG@֡Ч>p {*$FHR soxLnmm$9|c IleV3"2<+_#(5]v}oXȺDZhxFpL$x_^k05Eɭmby9'F0FPڽ$[@ 0Ҁ yG@GGPzBG@k->K&aDA0Oz*{yWyG@GGPz*ǑQ{^N~"L0B]8G,pŘ^{yWyG@GGPz*ǑQ{^{yWyG@GGP'.j͔cQ!ZPP]UPF  ZAҖ?.iaVR&9+2j¿Q'W=\?*[ou$V"2=o] ??ʺSDҼK5[`b uźAm;UزYe66\*I־k_mPq,D;Q>~]ϤD@9WB½NV( AhT`˝NQe9 8O8\kz֬f fy]g+S\Ѵi_Է֗V.XMYndOwB`?7tt{}=B+8mEįYb V jz.k0F45-@J@l'oF;i+.-`XlU(Ѓ]%߂tk"(d1H LpCc|'Ʃ7w\ӆmȡQ'RoMŎyi+;i6&Q$(}̶pH\n_x-#&Yg1ܳKmd)cIWggo]khf\6\ g'9=W1/uI^mգem.A+ʖǦ00dٍ/+F㑍 ek~0Fku隻mIH+ d`gx) fZyXrZW>tKF,<ʻ q+7] e%q!F8,u9AyiCm6 ie5Sb .~hڽwאA56$oVvFXHSWCuM.oYd70]K^OO'qfGTs[j[_v7Q/ȻWg_9溿 z[jT3 Gt^:X~D)E-nDm-?5om%Y rv .Fi ^:F8RPȠm3o3haA^նn-n+ǻk/6gBܕMpK0bp2kKW:as`Qܒ)R n$cblWWV[Ci=,q[rKF#,:]3=5#፮ "8i;IF6099liV=g5)#Ƕ8& P#hl+w+oE$bBah.\ Z,|HS5JTI%YrEW OQɨm<^pWom3hΦ}IJ b< ZjZ\3ed+r#%V5Lj.n~m%]8J!I2vvcHKlydm&8$ Iʨ;(F1Wt Zs__]<uˡ0ۀA߾MuSwzzu(ޞE7zz7QMޞ(Swzzu(ޞE7zz7QMޞ(Swzzu(ޞE7zz7QMޞ(Swzz{] KTH 8TQݙP=My=#\RV& -aFS9^^jluX"&9T:9#uh(m-o…W 4jƽ5/}%Ԇ;TgNmvUmo\9~mGOon徊;x.mbZDdbǕ<ڞ R]@"q3('eNkOD-i/$`{E*RInoOQ@=EP pҋB1F8ힵ. ehPFW bMZ\imͩ/& 2dk+DŽ-u;`!WsH99ɮzzu(ޞE7zz7QMޞ(f@3ykGR[RˈK 6c$k׷/KV_AufdkKiL6\) x%Ws6HQMޞ(Swzzu(ޞE7zz7QMޞ(Swzzٿ'w]O f!+((R.y*Uy(ԕԂ)iJZ.iaS5iYP??ʗ0mrDdG yip }%??ʨ|Ht6aҬ-E'q—@F{eX v̒rp9,F>&ž"wXej]dP!tqvFxz k`[XIrg(!t;H2ʅc|j/#UYd̀;)U9m<i\e$Y,jro #3]O;IvxwOvuh͹ĈptgwQw7ZL(4RcDeQxVmu(;r*H}>̑S#={Wl?fFUYp,Lx(-#UI|+K} KVKK7eHweIs,#1p.;LB֗%;Pf2''J_ Cŷڍฆ٭mcds#L\ 8 1<;1jA*BF˖bG]76">h JY2[l>sZܓĉb]q*syk N񦕨lAIf cר#Sd6F'ԣ G {7 & =V Q/ngf ʪ(Fqiaq=Vj040",$85rmwC w48 ;H?Ck^3 :m`6<ȐoY VY95^%{BXlrOmy9^mV;vCJ0Gb?:msoyHD*C+PG^6uu.8*L% s885x'HG$9i%d9Dryڹ; ]]ڙ^@@ \P59u#UUdmcӠ&DԴDf+J̉S;s] tJc[x(t"|$nѺ'#zO:Qh0[Hf[IKu8Q8(YhݝwXFJ+Gt˫]*4jy1Qr|uU3ZM-Ibyfi\(B&< |%[PUZ& 2F$QxNF{5K[yZ0IQ`u߃ޠom!kԭJ heNqdvӼ-%]U)6%&ԅ` Dd].w8:tF";23@Ƒ}(a\H/"é9y9oZY[OPa՚-343yOFݸŸ* 0[:>_4WSx-rO57HРXftD]9m`ʁ |s8zWz&i:jo  E &c̛KuiKiWԂс DOA=N~F#_3vgv1Qz-momI*A</dDwRM]ѵ&7kWMhj)E):'pQEQEQEQEQEQEQEQEQEQEQEQEQE#2c9$ }:2ȱ7c޻st6ep>gd. dB!Ì^x|%[Yͫ6w6f$VI6)auwiInLqQG# MVdC)V;$VBAx  "}G\k3g>5г9Qv;W6ڥ/|I5XaH(>d ((* -;"FvŒ֧n$($̮J  pNN(-zMNT6=`@Gr3c*7j(((+|Wgk 3)kfy 0#8k\iן𚶯 9(((((((B? ӃfI! i@vTb@Ԃ)iJZ.iaVR&9+2j¿Q'Wg-ޟi?: I(q$t*~:7 ؆)=_* n/V՚(-̑ #XuʌgWF Xe` 9ᾀ?4Ĉ;b $0$(POb~Yo|> C Ẅ,L`a PwGGak ÿf9d o?褌]Ɨ[)Ȱm0L7QQGRQ@QQGRQ@QQG^?k^+Z ZuPcu+p.Hpe,#Fm߈u ɨO=E AngLR`hؼ( Ş,tK;.{6ծm؎#l\m85b=^5^ڏC{ZƓ]Aa$ *?J<+km^)mYYɕ2cH$rǗSoQV"$ {ge;$YߐrMz[$[6yC0B@,7wF~$V (`~BZkz*ƶ6vb)ۦVRF#lNJs=sN彞ٝ:8g 1$f+6eJ8/*?J<+S,3vPKEa(T,;[Y]GM.W+ ŢI6RXNv~T~yQT1_YLʐ T+HC;OժʏҘ 3 u=|k7I @FXY>!Pͧpo,"yū;wd(HRrz4RalTF`j"2Aѯ.|oiZYPe-?* -pp:5j~#=6iu84֋ʷwGE%ιePh*}m8Ɠ~D=y Ҥ:u[VQGKs3+!+-vWdާ!]&/_I| %`@rp %4jp2é=@ 8<#ּZޝ{q C6%ێ1 5$Ja ʀUׄYɧh^%׶&ixrWsphx])Q A, )%9{aϯi"i`FmIn~1<9m5լ4ZI$pA¯a cQQGRQ@QQGRQ@QQGRUMSM{1@"s# m;#@"$',!ur qҦ!ŕ㤂 [{y'؎,)"~s;Fӂ9o*ʏҏ*?Jʏҏ*?Jʏҏ*?JʏҢVyegLnPrFzdvpRSt0xCS?o&^K hek4;e T~yQTP~T~yQTP~T~yQTP~T~yQTP~T~yQTP~T~yQTṔ]SiCYN+F ԋ^J^J%F  ZAҖ?.iaS5iYP??ʻ2$(th²F [a|XV/}Jm7/H:,X{H: o?nAZ==tvc+k\B᳝qW^G4vBq1.Q+@fcG *2=A =d" 0TxTVI2QIo\n}Kz})Aܶuz{秽U-yGVD[huLvz\AUt}RI#l`Y^A,HFf9 p 8GR]Ziw"YK3aGU{qݼ6b3[eKQUHsR;ۯ?/ulw:e"FFU]Y#=YiOs ZgRHQwƤ`n\IZfp T6Cu\`1'insi,60Gz(]. uVmc+]6$eHP ` 'AmV hBq&wvV cx*(i`*#o[4vMk0,heXCmW諗Q@Gdԭ⸚)5)*mޭAP ֠м-wk׾TIT1!PGsY:ϊƕyR%*"nI%[%_9<'tlAC,kĩ1b1#<;o>og! @6K[Cg1lHcX,n3PO#k9-w1NТ̒+!';9 wj/m3, bIfvi5<hԹBd:U{M [>`Ċb78=EqmWXSM1beh%g$G O'm)ZآpqX A19c4ުYݤJH P3ԁ>%:}]g0(AS sy tR@-`cDii ʿ.ֺ[oaC[\Xٌ, 4m-'+3@Tvz\^P5,"[sd9Q6:eR I1\.+Đi}Iai褿'-54hN-;$G8nydm_C;;ZD[EBCmq,N+X[-,Үą"G=ko֗R],[n~Xfm8ڹ?jNFI:J>og{q ]ouF!A:VrDѷ$E;6feڸ'wa֗,[\"3($ B u:.kKXh <ʭێp t9⼳d*fWXHT6gXNK->1%=w\Kku\-i<9Ln.hզfo-)#mxCV##ؐG C秽z{ZCjclH@Ş-[``!l=hKV[hFdrhP(~ڼ?xwבKE-Lc9@>IԲ< ;c]Ǎ{Nkxf8Ǒ$py!:lz\o%m )Y qщ AQjZfWDw'ɴևSwhteE*mR\$0>/ưR=F4 va1Pj5wmR!QUS:Vuƅỻ˭:[XNcCTW+qvjL4vܫnGQqljƚtrX[]\$nBKF9R̫ňG5(a,-#TĎJ%3;kW :>1[ o>BY%(ہ;@げs@秽z{Z(מyUh ^z{秽U-yGV秽z{Z(מyUh.cJ(;VdVjF*ajAQH(–t.iaVR&9+2j¿Q'U_O񏆟BԲ*W_2*??ʷ|G;m{S$HQ~ *2hxx[x?}Y *L}NJ:+M#J}#N][ Sܞ'־kbxu+>ͬ&ʊS (`4V\9$Ct# 1ֈ+[][/ e<qVt=AIeܴ.{1$骉ԬFA}3ϖ)c  6Т3 (|;-v}~;c=9|ڽ]G<>\>Nz`5P¶48Hd%Jw. j68RzV c{ƖM0i+"X0$6\*^TPmA}"Y0r#ve #$biv 5fڤ1$0bU/|)eQqqb[a,?!24o۩_J/Fp9g2:/`hV|Vt-K.5]_:ݝ_QgGl]QͶ ";01;.TC+I~ZnKۛAKe,#ʬFyBA'vOT K);$.q g?Y Ȗ{ 9N=hԾge{gEB37w *܍=w_xZokQ4y+7V'Њ3WUchb$tڮ@\ qRZ궷zޗ628\u6ju/;ywϗ166g9j}_ڕƭ-ۋiw+"e nL2lH]Cᾖ:y6 |#;6G*%%beK.O:~?lZr2ýfI xé~6oAkc|o4 qJ3|r9\[]NTEvѓ0Qs@T,5}*MCRk"r0@cVhٺvᤒn"_̑[iʐ9{ WK&djB.K U&3CѼ7vAwyj8qdKEgZP}EgZP}EgZP}EgZP}EgZP}EgZP}EsZ΋a؝;RVhK#mB5~YG^\RFK$ cfGmd+n. b%.U$x |A͵ "C$+#E&Yz0hW:aU[1-YHK6[ +cX[ۥ"FEs՘[{bxAӵ3ܼѣIdXd [#=qF=G:}ɺ0gHJpWPU7=98xAΟzn!'x!MǦONN5P}Eg{ ͬ#DdF@p˸crQV! +z:z޷~[۵^T>NUYO6TcQ5V~r]GOh$ua,26a2+ +BϢ( +BϬAgo[Qmrg]]rwSQe^C F)G +=p}@4EhQ@VEhQ@VEhQ@V2BAZt т5"WW,IQH(–t.iaS5iYP??ʳ>-xSQwVIS%ͳGrlRGF+ND]WB 5R79$ɴ}1*ެGk˛)mvPӳYMë<ǘU M{y@oqY/-PeAdp{Wj;{ !-VRyrnK) ƫ9M{Ǟy@x& EO.@LL,eC|ysk++K!69!^U8̃cdWG㺾x!HfA5o/A|jӧ\7^mY.n]%_HrA#?0#YK+ 5d1u*m9Asj^ӵ4kh!&KM.[H-.'Ȉ uuݣnއ]|(Zs=Ԭ~""l=8=ڀ<'\e=톕hhɩA+MFָb!.zA>Y )FVeFDžXIP Au{Q翵ZC:uaϖ)`C<+'=)޺_=ڀTF#)n݌GA=ڀ>~м+{}cgn7Cc-*;?*?2))n_i={@(c_@yG^&ҵu3gKc[2y#M_Im \ؿ%n!-@A##^{Q翵x/ïg%:i hei1Z}/tkRxG?9wnWr+<j[qݵėI2vavg.w9o4}b;.V7)1,UP2yk<jEUڏ=Wj< TU_=ڀ-QU|jEUڏ=Wj< TU_=ڀ-QU|jEUڏ=Wj< TU_=ڀ-QU|jEUڏ=Wj< )hfP 2=5~IKbR8@e^#kڏ=u]E-EdZZ̑3DRT -k+'Х/w+C$#dm4#i+J ԋ^J_J ֤KH:R.iaVR&9+2j¿Q'WPp` NWyO>łƻKuiڮtBxº7!} jb)D_jB'Ⴥ ApFTwΡw&T,fh1P1OP[|5d%mIA~#޽;o*5$ U@e\[Ay'cc*{辧^xUV]1]SOWUhŌf o0ASXgob8PT`=RyRzQI@RyRzQI@OwzZ5wmjA O\ 8#%n<_ 28YHD$9]Eo^M; Ccpxǖ sUojSIuu f2,tO,de+pv@'~"F!HA+ycb$"T`[;1 K &wq t,WConXK/)`_l[EiJ6nG UU'բ<'-IjʁYyǖQkiw6Aw@TyLҁ gkk$vb[յHK"y-n614@/&VOGJේmY/!s79(4}vqsB_% 񓝎#e=#_mD!%6ncw1U^x3jzV_aHZ%xtm;W$<:y|4~j#&0yQ#(H 995C@uma HDE4qø ;9}NeO pGZz@[tiiWy&.u@W) 1ڬZGGUqt ˴Í9]Ď7(? i5LʨL6f 80+gʓҀ# |S}Ar\O`I&H0'=xWRzW<x^KEf{f;grYrILy6C!Қ;@]_,,7R?1\p xW~<Kux"[')d8lϘGv>Ӊ$e`I& 3CpY]*rZgXʩ< #oǘϟwwEXp1sj—QԒٌz`M N > MqV+RSrLfwb?(waxTk"Kq^Ֆ#矘8_:][kͼ9VO2=Q2 -mekus}MRe 1@}N{s{2rJ$Q'QBmCYqҀ7aׅ.-n/#+XDHO#ޛ/<-I4."Mjpda+z;aOc\9f-n;k(7ww2H`sx:ޱz)$淞&$veN[3ߞ5?zȴx㳊ͮ&lX Ĝ*K^ ڶ p$I#hĄ l d#<6^k7>oosf!2$. !!{r2qYVz&W/geto {h*7mo '=I|e>ն(dduHڞS(sd`mZNLEbdeuꬬ+B7QiB_y #\ܤ$h4lvX\]I-Υ?~ө^19(V5]䪂I@f"2=S9!~f=dVxsыC\r(`G8#wu;7Gqkswu 6?}6Ϩ+K ֵ{'tHI,arY7`gtW4Ο\]hI#Vڜ.(F gy,1m]G&spQbV]_f:)!f\#K|Qyvoo +4bFF.pˀz<㞂O>'Եx嶇Q6{buo([r0p}DxEN&pz1TC4h\]NB1r6QUec-03k|{hDsJ`$ pH; b9mFZ9I.ڣ85(tK,L,ȡvGFA {o}Rao5]!8wT  wi)mR\!X2pWp#<7]\E2DdkPd3] F5Q%K >S` FHko H9 /@!ED9/#miflXpA#9׼ Zαm$e70KKo!դC*Ww$W]-] Lx.#9guYdc2p€:U[RttI0'vD~UX (ϼ1OT]+$E$;K|js1ַaw +1L"!sn˒G)$C1' OI}ia2uH#8Uool-",S+Jރimo^@ӥ-Y01uLchڠsbU4[;Q-h|.yڀd@} Kc$hrNl8Pf|@.n59gGgdFRE`~CgsUjze$"WY m-lJɁnpFqjþ'55]A(adOFiR@JC76,cEh呡UHiL dL W rh񟆭mmdڗGW6V`?1  &;KSr׏t˱"F.i7o)`c'Bþ/$`P.>C8ueuF$C$@/g,%2Xp<|# 1/;YR rYY|6oŶ+uH4laZ[nmndCOcgP$Fk3c>#8:W ( ( ( ( ( ( ( ( ( ( ( ( ( (+^5KbB8]ڠ2zkz,'-E2۴Aydu`'quFYj_b<7W$f F B0\gN]F9t&TVVsm 9a@˵jz}֥.d:(Fvr DH&8c֩׮[T^i4h*$ʈ ,$5&_>e=E$2P>c,oQ@k5l.48l4u$i C A wzע#$Ca}A1ռ#tOfI (Rtj1\7L$%1@a (* [10D@*z y Vi g[ƇYm4< pBeW :Wo^}]ψ{{gI =& ( ( (My֏MFN7YgK9VB҂Ξjy8謡 0Ey-~/R{shN1mDJM8fz}Q@Q@Q@Q@Q@Q@y?B`YN+F ԋ^J_J ֤KH:R.iaVR&9+2j¿Q'WAkПYեRǩ9~D\ƝR־kY#t^KF;v€0,Ȯt4h`t3?{_Y%dᕆAQ_Ӧq;`PbLc @&!c'aO}"B>`Qz.8(7Qn:2}s$n>u>.ʂd3EQ1r:Vw,o&XcnZihiP{mh?S{{PQ H"4Id9S0A`5}N]k k˓lrxЬjFYnJ"ns j!u G9M0RHpH#><[=Զ8]7:(W~rG,j7^k8ndm΋.QF`6(\%]hi=NUm 湖;ˇ‚`c ;ax&OH4q< $/r?a p8g}F;ش̏"'9+P o{}vQ5=WO3,\r&Ndr+ ]kP///o't\6`PлѽMx 2Bڠm(G-ŵ̷-,dix2%]hIo^O{Ӯ&c#K5bbNqPSF5gPlVGYZ03[dq-lVZ}f< ;$OЎ:G҃Dd?ހ=SN:MG0Mm*VKk Wzo*rNr09x-V5&& pI)$LCn;.W% $xޡWw##j{ ^{/R-43[ZMrH M Of1BcoiﵗPGd*H;>P[iq(ص?wFEo6Npqpkz9ı` Akʭ5};' p3*n\623,.{dҬq*'ր>IYVj:<1I|QЁyFyxbykl+6aX@XS?U#|ko4iqys H6OVqwm'NC+ 65ޯ[;@[ -l"WhUɌ11[m]1ۭv :kڄ:gG)L'ʜ#9 h [4y4{{ɿaIf86Y$D2dКKˤQ<QPbcfM#x:7cڛRmp2z Ea>+5Z +E6@35z?1;$D'照7wk GEsP,/q`shޮ$D:ƥw;U~ j&{Apm,&QX k cQTIyiؤWnnUY1+_6ُcl5 Ql'v,nJe]AA^xm.][mWy}{OXs5Ν 20`'rSjr[I.,9nsH ~ ]hV:u]!G"86+n]Bӱcյ[}*[[5ioH0@ {7k3MxKĖHDf7>"j旧zNF$p)1~ʞ@={7W7z' vl`z(ѽM6v4oSMMhoSF4(ѽM6v4oSMMhoSF4(ѽM6v4oSMMhoSF4(ѽM6RKΩi \)s!x2^#)^ wZnjܲ[Z\/ d(.gGE 8wpgvEtM6-2(ngǗ ێx^? Xm|Oܑ]qC$BH$5Յn`jc#8C)0lN(֯|KiiCs>CjZQ 4pMGu%& N>e[xs@18 ѽM6v4B6i*?O@-wg ZWtMqHqzpqV5 t/6Z7S*+dA,B_y$YoSF4(ѽM6v4oSMMPWPJ$AuSБgޮמXi:oIK_hԒ}hw{mhE;{7@iP{mhEqR\z+N̓B? ӂ5"WW,IQH(–t.iaS5iYP??ʻhsmT_\%??ʹZ槡9Wݣ{"y6lkim? wybgq#[tvkeU3l>mU ׭}X ڡ&Xv=X } 4GPv6wv7R,Pn 8yJ}^[o&`40m5#`z8A@7Q{b{yX+!J_ǚy$:Qn`Q:3\,e6!^pa :oZZ} k+E#HĊs` g9ۃG^^am2 ׏qiV+u{wTOcUvkK-.JO38U!Wn (CQpzҼ֗x[{#|$ۑYT_ݸ,8'_jZ=ͽ 7҅cBmR9(Jր:i<+Iiyd/3C q)74 %K$$eGG0pAW1xM!1,kk}/u}0cmysm)55k6-puxkeL;*PWf -?^me2Hm-㸿[+{yPȬ& 0U|H \W].fg8A(ܬ$@>GGU(Q{bs@#_e{3 A` eP:P}?x}H35RvR%eq%ĐYZ!ݧn3)W=I=JAnCWxSu-~Y1nbPt*HKa=41N\qkmtkbD@ F>#8sWjZ)/5 :ҏ#,>*h7XPA+^':lB͵H'ɩ>*iVܺ`XZ.GB * ndV`@L=JO0^?|Eko['f emxܯq[ ~m^٥6ZV\$k .r;x݇#lP}cTn4[;}F`L֢AHl W'5KWܖc_#̐i|m'Sovr,E\[Ӱw%켁AP6RyΟD";k b|h1.N1Qj*٦BtmGNYV9|xnf}H`H*:PG^slK뻖m][ٚMP 5Q|/_yUI(x(=JEW=J<ҬQ@ҏ#P#*_(=JEW=J<ҬQ@ҏ#P#*_(=JEW=J<ҬQ@ҏ#P#*_(=JEW=J<Ғ^IFQn , @$g+-֩qc}to&4UxP[qAO#ڼZ5[!CEh&KyP8 AAIT6k&8cHÀO(,MDOly$ +lH,bƋUFA_/ƟQS42H>m&ɴg9#b澶i]]EupG@捧xOm:mFr9VSJXXh|^8-".{Sܞ٠ -:pl;P!F_9wFy' pq+DXL/"Ӟ -_ Wq 9WW[W?bp Iuwof>p9,F> Zɦi7eYeLG 8f"𽿈Gqs2a r!9dGkcLOE4GmT+/P|8ڪ#&dN $xJ]7Z O[H+ebb`FpǽQм u%LgpqC@I=3K}i{uk8O6D7t+xNG#/"ZOA5}Mi+@bR ɕPv'GXLmj6ݺ^I, PA`B񞢀!_xNfUK]*T>fZyXrZW>tKF,<ʻ q+m9^ID1. 9N<5xJou]6 qyGOQGP+Z,cMk <շ]&2AxSK[YRᗇxS.sbOhm& sC "@IRJ1^,Fg3<2VhJD|2FvcmwC4Ir+29`iR-7w }Fe@*cDz81x/M畓{50xUO99u 9-2ʄGjk=_JV@q!Epvl#NeѮ O'VC$ۑlc+yh>cѬdy"`Ұg&GiNycO-wv<^M `PvIYqn#ٲ ,fڀ45oiz/s<^&Q,>_eʌ Wj6ү.b[I-6f# fh8Rv29/OQFF=EW[+c7a9w Cs:Zu.Jĥh$FNj|-o -&QRv"d6[kޞ]-#Ki-`;,2~P(ޞ@zzE^ޞ*{zzizUQ,6fFnebPǂUw3m]5roM~Ae6vsl$g88vQ=EQ/oOQFF=EU(oOQTh Q=EQ/oOQFF#J+J̓B? ӂ5"WW,IQH(–t.iaS5iYP??ʨ|Ht6gU8ROlZOO46?|mЯcIMČ`9+y|+/<3ؖb=j|(?ĖEͣ;6ѱq7cׂiP[GWĖ n%x1ђݱҾʏҀP4 b-/ "9<N eʏҀ<7^6uu.8*L% s885x'HG$9i%d9Dryڹ*?J<(sʏҏ*?JƩ/==inB bJ9wu#{u<h֗drY`c\dlױQQG@ojަbk]M`m#Rf@SsFJ9E{[Q0=AS(kKǖ=wRZa㷸YEL*nfÍ mIE]VghdBvz@vT~yQP:*T~NGGSyQQG@諞T~yQP:*T~NGGSyQQG@諞T~yQP:*T~NGGSyQQG@諞T~yQP:*T~NW,IY %tRN88EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P7G[>EEcyyQ{P8 q8A@k XHjXhqUR*T%hFi@**5:p ZuQ@.m4y4 Q@Y*Z%j&Jhu2 4kUdEkyTUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUeyyRTQVG@^EEjTUdyQPWGZU/@>EEjTyTQVKPOGZUUey"<_*35 TRXVDu*DVUiUjP(@-(PEP.(NiS PrҕghMkmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]'VѶ*t]YFyukmhK՝m ]]YK+lV6SԸ0 xR)h((.((PhQ@ bPqF)أRb1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ P1F)Q~(3b1@ bPqF)أ\R▀KZ((((.((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ufoProcessor-1.13.3/Tests/ds5/sources/000077500000000000000000000000001472454271500175635ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/000077500000000000000000000000001472454271500257415ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/features.fea000066400000000000000000000000701472454271500302310ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/fontinfo.plist000066400000000000000000000030731472454271500306430ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_1_d2_0.ufo descender -200 familyName One_wide_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_1_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500272475ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321620ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010511472454271500320330ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320730ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323220ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500303430ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304630ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500317140ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/lib.plist000066400000000000000000000023351472454271500275670ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500306270ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/000077500000000000000000000000001472454271500257425ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/features.fea000066400000000000000000000000701472454271500302320ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_1_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/fontinfo.plist000066400000000000000000000030741472454271500306450ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_1_d2_1.ufo descender -200 familyName One_wide_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_1_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500272505ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321630ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000005421472454271500320400ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320740ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323230ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500303440ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304640ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500317150ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/lib.plist000066400000000000000000000023351472454271500275700ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_1_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500306300ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/000077500000000000000000000000001472454271500257425ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/features.fea000066400000000000000000000000701472454271500302320ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_2_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/fontinfo.plist000066400000000000000000000030731472454271500306440ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_2_d2_0.ufo descender -200 familyName Two_wide_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_2_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500272505ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321630ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000016651472454271500320470ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320740ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323230ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500303440ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304640ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500317150ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/lib.plist000066400000000000000000000023351472454271500275700ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500306300ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/000077500000000000000000000000001472454271500257435ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/features.fea000066400000000000000000000000701472454271500302330ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_2_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/fontinfo.plist000066400000000000000000000030741472454271500306460ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_2_d2_1.ufo descender -200 familyName Two_wide_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_2_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500272515ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321640ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010471472454271500320420ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320750ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323240ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500303450ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304650ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500317160ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/lib.plist000066400000000000000000000023351472454271500275710ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_2_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500306310ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/000077500000000000000000000000001472454271500257435ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/features.fea000066400000000000000000000000701472454271500302330ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_3_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/fontinfo.plist000066400000000000000000000030751472454271500306470ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_3_d2_0.ufo descender -200 familyName Three_wide_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_3_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500272515ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321640ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000025011472454271500320360ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320750ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323240ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500303450ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304650ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500317160ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/lib.plist000066400000000000000000000023351472454271500275710ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500306310ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/000077500000000000000000000000001472454271500257445ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/features.fea000066400000000000000000000000701472454271500302340ustar00rootroot00000000000000# features from ufo: geometryMaster_c_1000_d1_3_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/fontinfo.plist000066400000000000000000000030761472454271500306510ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_1000_d1_3_d2_1.ufo descender -200 familyName Three_wide_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_1000_d1_3_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500272525ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321650ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000013541472454271500320440ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320760ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500323250ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500303460ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304660ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 800 glyphTwo glyphOne -800 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500317170ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/lib.plist000066400000000000000000000023351472454271500275720ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 1000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 100 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_1000_d1_3_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500306320ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/000077500000000000000000000000001472454271500256645ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/features.fea000066400000000000000000000000671472454271500301620ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/fontinfo.plist000066400000000000000000000030731472454271500305660ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_0.ufo descender -200 familyName One_narrow_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_1_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500271725ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321050ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010471472454271500317630ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320160ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322450ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500302660ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304060ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500316370ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/lib.plist000066400000000000000000000023301472454271500275050ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 0 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500305520ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/000077500000000000000000000000001472454271500256655ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/features.fea000066400000000000000000000000671472454271500301630ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_1_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/fontinfo.plist000066400000000000000000000030741472454271500305700ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_1_d2_1.ufo descender -200 familyName One_narrow_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_1_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500271735ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321060ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000005421472454271500317630ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320170ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322460ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500302670ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304070ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500316400ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/lib.plist000066400000000000000000000023301472454271500275060ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 0 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_1_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500305530ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/000077500000000000000000000000001472454271500256655ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/features.fea000066400000000000000000000000671472454271500301630ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_2_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/fontinfo.plist000066400000000000000000000030731472454271500305670ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_2_d2_0.ufo descender -200 familyName Two_narrow_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_2_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500271735ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321060ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000016611472454271500317660ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320170ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322460ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500302670ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304070ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500316400ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/lib.plist000066400000000000000000000023301472454271500275060ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 0 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500305530ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/000077500000000000000000000000001472454271500256665ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/features.fea000066400000000000000000000000671472454271500301640ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_2_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/fontinfo.plist000066400000000000000000000030741472454271500305710ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_2_d2_1.ufo descender -200 familyName Two_narrow_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_2_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500271745ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321070ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000010471472454271500317650ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320200ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322470ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500302700ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304100ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500316410ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/lib.plist000066400000000000000000000023301472454271500275070ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 0 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_2_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500305540ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/000077500000000000000000000000001472454271500256665ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/features.fea000066400000000000000000000000671472454271500301640ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_3_d2_0.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/fontinfo.plist000066400000000000000000000030751472454271500305720ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_3_d2_0.ufo descender -200 familyName Three_narrow_open guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_3_d2_0 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/000077500000000000000000000000001472454271500271745ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321070ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphO_ne.glif000066400000000000000000000024731472454271500317710ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320200ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322470ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/groups.plist000066400000000000000000000006731472454271500302700ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/kerning.plist000066400000000000000000000007741472454271500304100ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/layercontents.plist000066400000000000000000000004371472454271500316410ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/lib.plist000066400000000000000000000023301472454271500275070ustar00rootroot00000000000000 com.letterror.ufoOperator.libMathTestValue 0 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_0.ufo/metainfo.plist000066400000000000000000000004761472454271500305540ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/000077500000000000000000000000001472454271500256675ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/features.fea000066400000000000000000000000671472454271500301650ustar00rootroot00000000000000# features from ufo: geometryMaster_c_400_d1_3_d2_1.ufoufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/fontinfo.plist000066400000000000000000000030761472454271500305740ustar00rootroot00000000000000 ascender 400 capHeight 400 copyright # font.info from ufo: geometryMaster_c_400_d1_3_d2_1.ufo descender -200 familyName Three_narrow_solid guidelines openTypeHheaAscender 1036 openTypeHheaDescender -335 openTypeOS2TypoAscender 730 openTypeOS2TypoDescender -270 openTypeOS2WinAscent 1036 openTypeOS2WinDescent 335 postscriptBlueFuzz 0 postscriptBlueScale 0.22 postscriptBlueValues 100 110 postscriptFamilyBlues postscriptFamilyOtherBlues postscriptOtherBlues postscriptStemSnapH postscriptStemSnapV styleName c_400_d1_3_d2_1 unitsPerEm 1000 xHeight 200 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/000077500000000000000000000000001472454271500271755ustar00rootroot00000000000000ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/contents.plist000066400000000000000000000004701472454271500321100ustar00rootroot00000000000000 glyphOne glyphO_ne.glif glyphTwo glyphT_wo.glif ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphO_ne.glif000066400000000000000000000013541472454271500317670ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/glyphT_wo.glif000066400000000000000000000002761472454271500320210ustar00rootroot00000000000000 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/glyphs/layerinfo.plist000066400000000000000000000003671472454271500322500ustar00rootroot00000000000000 color 1,0.75,0,0.7 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/groups.plist000066400000000000000000000006731472454271500302710ustar00rootroot00000000000000 public.kern1.groupA glyphOne glyphTwo public.kern2.groupB glyphThree glyphFour ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/kerning.plist000066400000000000000000000007741472454271500304110ustar00rootroot00000000000000 glyphOne glyphOne 400 glyphTwo 100 glyphTwo glyphOne -100 glyphTwo -400 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/layercontents.plist000066400000000000000000000004371472454271500316420ustar00rootroot00000000000000 public.default glyphs ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/lib.plist000066400000000000000000000022051472454271500275110ustar00rootroot00000000000000 com.typemytype.robofont.compileSettings.autohint com.typemytype.robofont.compileSettings.checkOutlines com.typemytype.robofont.compileSettings.createDummyDSIG com.typemytype.robofont.compileSettings.decompose com.typemytype.robofont.compileSettings.generateFormat 0 com.typemytype.robofont.compileSettings.releaseMode com.typemytype.robofont.generateFeaturesWithFontTools com.typemytype.robofont.italicSlantOffset 0 com.typemytype.robofont.shouldAddPointsInSplineConversion 1 public.glyphOrder glyphOne glyphTwo ufoProcessor.test.lib.entry Lib entry for master 1 ufoProcessor-1.13.3/Tests/ds5/sources/geometrySource_c_400_d1_3_d2_1.ufo/metainfo.plist000066400000000000000000000004761472454271500305550ustar00rootroot00000000000000 creator com.github.fonttools.ufoLib formatVersion 3 ufoProcessor-1.13.3/Tests/testOpenTypeAttrs_3.py000066400000000000000000000071171472454271500216700ustar00rootroot00000000000000 # 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.13.3/Tests/tests.py000066400000000000000000000223011472454271500171170ustar00rootroot00000000000000# standalone test import shutil import os #from defcon.objects.font import Font import logging from ufoProcessor import * import fontParts.fontshell # new place for ufoProcessor tests. # Run in regular python of choice, not ready for pytest just yet. # You may ask "why not?" - you may ask indeed. # Now based on fontParts. def addGlyphs(font, s, addSupportLayer=True): # we need to add the glyphs step = 0 for n in ['glyphOne', 'glyphTwo', 'glyphThree', 'glyphFour', 'glyphFive']: font.newGlyph(n) g = font[n] p = g.getPen() p.moveTo((0,0)) p.lineTo((s,0)) p.lineTo((s,s)) p.lineTo((0,s)) p.closePath() g.move((0,s+step)) g.width = s step += 50 for n, w in [('wide', 800), ('narrow', 100)]: font.newGlyph(n) g = font[n] p = g.getPen() p.moveTo((0,0)) p.lineTo((w,0)) p.lineTo((w,font.info.ascender)) p.lineTo((0,font.info.ascender)) p.closePath() g.width = w g.appendAnchor("top", (0, w)) if addSupportLayer: font.newLayer('support') print(n for n in font.layers if n.name == 'support') layer = font.getLayer('support') layer.newGlyph('glyphFive') layer.newGlyph('glyphOne') # add an empty glyph to see how it is treated lg = layer['glyphFive'] p = lg.getPen() w = 10 y = -400 p.moveTo((0,y)) p.lineTo((s,y)) p.lineTo((s,y+100)) p.lineTo((0,y+100)) p.closePath() lg.width = s font.newGlyph("wide.component") g = font["wide.component"] g.appendComponent("wide", offset=(0,0)) #comp = g.instantiateComponent() #comp.baseGlyph = "wide" #comp.offset = (0,0) #g.appendComponent(comp) g.width = font['wide'].width font.newGlyph("narrow.component") g = font["narrow.component"] g.appendComponent("narrow", offset=(0,0)) #comp = g.instantiateComponent() #comp.baseGlyph = "narrow" #comp.offset = (0,0) #g.appendComponent(comp) g.width = font['narrow'].width uniValue = 200 for g in font: g.unicode = uniValue uniValue += 1 def fillInfo(font): font.info.unitsPerEm = 1000 font.info.ascender = 800 font.info.descender = -200 def _makeTestFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "masters", "geometryMaster1.ufo") path2 = os.path.join(rootPath, "masters", "geometryMaster2.ufo") path3 = os.path.join(rootPath, "instances", "geometryInstance%3.3f.ufo") path4 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic1.ufo") path5 = os.path.join(rootPath, "anisotropic_instances", "geometryInstanceAnisotropic2.ufo") for path in [path1, path2, path3, path4, path5]: d = os.path.dirname(path) if not os.path.exists(d): os.makedirs(d) f1 = fontParts.fontshell.RFont() fillInfo(f1) addGlyphs(f1, 100, addSupportLayer=False) f1.features.text = u"# features text from master 1" f2 = fontParts.fontshell.RFont() fillInfo(f2) addGlyphs(f2, 500, addSupportLayer=True) f2.features.text = u"# features text from master 2" f1.info.ascender = 400 f1.info.descender = -200 f2.info.ascender = 600 f2.info.descender = -100 f1.info.copyright = u"This is the copyright notice from master 1" f2.info.copyright = u"This is the copyright notice from master 2" f1.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 1" f2.lib['ufoProcessor.test.lib.entry'] = "Lib entry for master 2" f1.groups["public.kern1.groupA"] = ['glyphOne', 'glyphTwo'] f1.groups["public.kern2.groupB"] = ['glyphThree', 'glyphFour'] f2.groups.update(f1.groups) f1.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -100 f2.kerning[('public.kern1.groupA', 'public.kern2.groupB')] = -200 f1.kerning[('glyphOne', 'glyphOne')] = -100 f2.kerning[('glyphOne', 'glyphOne')] = 0 f1.kerning[('glyphOne', 'glyphThree')] = 10 f1.kerning[('glyphOne', 'glyphFour')] = 10 # exception f2.kerning[('glyphOne', 'glyphThree')] = 1 f2.kerning[('glyphOne', 'glyphFour')] = 0 print([l.name for l in f1.layers], [l.name for l in f2.layers]) f1.save(path1, 3) f2.save(path2, 3) return path1, path2, path3, path4, path5 def _makeSwapFonts(rootPath): """ Make some test fonts that have the kerning problem.""" path1 = os.path.join(rootPath, "Swap.ufo") path2 = os.path.join(rootPath, "Swapped.ufo") f1 = fontParts.fontshell.RFont() fillInfo(f1) addGlyphs(f1, 100) f1.features.text = u"# features text from master 1" f1.info.ascender = 800 f1.info.descender = -200 f1.kerning[('glyphOne', 'glyphOne')] = -10 f1.kerning[('glyphTwo', 'glyphTwo')] = 10 f1.save(path1, 2) return path1, path2 def _makeTestDocument(docPath, useVarlib=True): # make the test fonts and a test document if useVarlib: extension = "varlib" else: extension = "mutator" testFontPath = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) m1, m2, i1, anisotropicInstancePath1, anisotropicInstancePath2 = _makeTestFonts(testFontPath) d = DesignSpaceProcessor(useVarlib=useVarlib) a = AxisDescriptor() a.name = "pop" a.minimum = 0 a.maximum = 1000 a.default = 0 a.tag = "pop*" a.map = [(0,0),(500,250),(1000,1000)] d.addAxis(a) s1 = SourceDescriptor() s1.path = m1 s1.location = dict(pop=a.default) s1.name = "test.master.1" s1.copyInfo = True s1.copyFeatures = True s1.copyLib = True d.addSource(s1) s2 = SourceDescriptor() s2.path = m2 s2.location = dict(pop=1000) s2.name = "test.master.2" d.addSource(s2) s3 = SourceDescriptor() s3.path = m2 s3.location = dict(pop=500) s3.name = "test.master.support.1" s3.layerName = "support" d.addSource(s3) d.findDefault() for counter in range(3): factor = counter / 2 i = InstanceDescriptor() v = a.minimum+factor*(a.maximum-a.minimum) i.path = i1 % v i.familyName = "TestFamily" i.styleName = "TestStyle_pop%3.3f" % (v) i.name = "%s-%s" % (i.familyName, i.styleName) i.location = dict(pop=v) i.info = True i.kerning = True if counter == 2: i.glyphs['glyphTwo'] = dict(name="glyphTwo", mute=True) i.copyLib = True if counter == 2: i.glyphs['narrow'] = dict(instanceLocation=dict(pop=400), unicodes=[0x123, 0x124, 0x125]) d.addInstance(i) # add anisotropic locations i = InstanceDescriptor() v = a.minimum+0.5*(a.maximum-a.minimum) i.path = anisotropicInstancePath1 i.familyName = "TestFamily" i.styleName = "TestStyle_pop_anisotropic1" i.name = "%s-%s" % (i.familyName, i.styleName) i.location = dict(pop=(1000, 0)) i.info = True i.kerning = True d.addInstance(i) i = InstanceDescriptor() v = a.minimum+0.5*(a.maximum-a.minimum) i.path = anisotropicInstancePath2 i.familyName = "TestFamily" i.styleName = "TestStyle_pop_anisotropic2" i.name = "%s-%s" % (i.familyName, i.styleName) i.location = dict(pop=(0, 1000)) i.info = True i.kerning = True d.addInstance(i) # add data to the document lib d.lib['ufoprocessor.testdata'] = dict(width=500, weight=500, name="This is a named location, stored in the document lib.") d.write(docPath) def _testGenerateInstances(docPath, useVarlib=True): # execute the test document d = DesignSpaceProcessor(useVarlib=useVarlib) d.read(docPath) d.generateUFO() if d.problems: for p in d.problems: print("\t",p) def testUnicodes(docPath, useVarlib=True): # after executing testSwap there should be some test fonts # let's check if the unicode values for glyph "narrow" arrive at the right place. d = DesignSpaceProcessor(useVarlib=useVarlib) d.read(docPath) for instance in d.instances: if os.path.exists(instance.path): f = fontParts.fontshell.RFont(instance.path) print("instance.path", instance.path) print("instance.name", instance.name, "f['narrow'].unicodes", f['narrow'].unicodes) #if instance.name == "TestFamily-TestStyle_pop1000.000": # assert f['narrow'].unicodes == [291, 292, 293] #else: # #assert f['narrow'].unicodes == [207] else: print("Missing test font at %s" % instance.path) selfTest = True if selfTest: for extension in ['varlib', 'mutator']: print("\n\n", extension) USEVARLIBMODEL = extension == 'varlib' testRoot = os.path.join(os.getcwd(), "automatic_testfonts_%s" % extension) if os.path.exists(testRoot): shutil.rmtree(testRoot) docPath = os.path.join(testRoot, "automatic_test.designspace") _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) _makeTestDocument(docPath, useVarlib=USEVARLIBMODEL) _testGenerateInstances(docPath, useVarlib=USEVARLIBMODEL) testUnicodes(docPath, useVarlib=USEVARLIBMODEL) ufoProcessor-1.13.3/pyproject.toml000066400000000000000000000001211472454271500172110ustar00rootroot00000000000000[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" ufoProcessor-1.13.3/requirements.txt000066400000000000000000000000001472454271500175550ustar00rootroot00000000000000ufoProcessor-1.13.3/setup.cfg000066400000000000000000000001271472454271500161240ustar00rootroot00000000000000[bdist_wheel] universal = 1 [sdist] formats = zip [metadata] license_file = LICENSE ufoProcessor-1.13.3/setup.py000066400000000000000000000034211472454271500160150ustar00rootroot00000000000000#!/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", ], ) ufoProcessor-1.13.3/testRefactor_RF.py000066400000000000000000000063171472454271500177200ustar00rootroot00000000000000from random import randint import ufoProcessor import ufoProcessor.ufoOperator import importlib importlib.reload(ufoProcessor.ufoOperator) #print(ufoProcessor.__file__) ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wdth.designspace" ds5Path = "/Users/erik/code/type2/Principia/sources/Principia_wght_wght.designspace" #ds5Path = "/Users/erik/code/ufoProcessor/Tests/ds5/ds5.designspace" doc = ufoProcessor.ufoOperator.UFOOperator(ds5Path, useVarlib=False, debug=False) doc.loadFonts() #doc.generateUFOs() def ip(a, b, f): return a+f*(b-a) font = CurrentFont() loc = doc.newDefaultLocation() loc['width'] = randint(50, 100) #print(loc) # make some tests at different layers randomloc = doc.randomLocation(0.03, anisotropic=True) #print(randomloc) test = [ ("foreground", doc.randomLocation(0.03, anisotropic=True), False), ("background", doc.randomLocation(0.03, anisotropic=True), False), # ("random_width_inter_MM", dict(width=randint(50,100), italic=1), False), # ("random_width_xtr_MM", dict(width=randint(10,150), italic=1), False), # ("random_width_xtr_narrow_VL", dict(width=randint(10,50), italic=1), True), # ("random_width_xtr_wide_VL", dict(width=randint(100,500), italic=1), True), # ("10_width_xtr_VL", dict(width=10, italic=1), True), # ("10_width_xtr_MM", dict(width=10, italic=1), False), # ("200_width_xtr_wide_VL", dict(width=200, italic=1), True), # ("200_width_xtr_wide_MM", dict(width=200, italic=1), False), # ("aniso_width_inter_MM", dict(width=(50,100), italic=0), False), # ("aniso_width_inter_VL", dict(width=(50,100), italic=0), True), # ("aniso_width_xtra_MM", dict(width=(-50,200), italic=0), False), # ("aniso_width_xtra_VL", dict(width=(-50,200), italic=0), True), ] g = CurrentGlyph() dstName = g.name useVarlib = False for layerName, loc, _ in test: res = doc.makeOneGlyph(dstName, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True) dst = font[dstName].getLayer(layerName) dst.clear() if res is not None: res.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved dst.fromMathGlyph(res) dst.width = max(0, res.width) #print(len(dst.components)) for comp in dst.components: #print("-- processing baseglyph", comp.baseGlyph) res2 = doc.makeOneGlyph(comp.baseGlyph, location=loc, bend=True, decomposeComponents=False, useVarlib=useVarlib, roundGeometry=True) # let's make sure the glyph exists in the layer #print('layerName:', layerName) dstLayer = font.getLayer(layerName) if not comp.baseGlyph in dstLayer: dstLayer.newGlyph(comp.baseGlyph) dst2 = dstLayer[comp.baseGlyph] dst2.clear() #print('dst.anchors:', dst.anchors) #print('dst.guidelines:', dst.guidelines) for item in res2.guidelines: print(item) res2.guidelines = [] # delete guidelines in mathglyph until fontparts issue is solved #print('dst.guidelines:', res2.guidelines) dst2.fromMathGlyph(res2) dst2.width = max(0, res2.width) dst2.update() dst.update() ufoProcessor.ufoOperator.inspectMemoizeCache() ufoProcessor-1.13.3/test_window_RF.py000066400000000000000000000115001472454271500176070ustar00rootroot00000000000000""" Test for RF what do we want to test? + get an idea of how the caching in going + get an idea of the speed + drawing different types of data, markers, lines, etc """ import vanilla import importlib import ufoProcessor.ufoOperator importlib.reload(ufoProcessor.ufoOperator) import random class UFOOperatorTester(object): def __init__(self, designspacePath): self.doc = None self.w = vanilla.Window((800,700), "UFOOperator Tester") self.w.reloadButton = vanilla.Button((10, 10, 200, 20), "Reload Designspace", callback=self.reloadDesignspace) self.w.makeSomeInstancesButton = vanilla.Button((10, 40, 400, 20), "Make instances of the same glyph", callback=self.makeInstancesOfSameGlyphButtonCallback) self.w.makeSomeGlyphsButton = vanilla.Button((10, 70, 400, 20), "Make instances of different glyphs", callback=self.makeInstancesOfDifferentGlyphsButtonCallback) self.w.generateInstancesButton = vanilla.Button((10, 100, 400, 20), "Generate instances", callback=self.generateInstancesButtonCallback) self.w.reportGlyphChangedButton = vanilla.Button((10, 130, 400, 20), "Report random glyph as changed", callback=self.reportRandomGlyphChangedButtonCallback) self.w.pathText = vanilla.TextBox((230, 12, -10, 20), "...") self.w.cacheItemsList = vanilla.List((0, 170, -0, 210), [{"funcName": "A", "count": "a"}, {"funcName": "B", "count": "b"}], columnDescriptions=[{"title": "Function", "key": "funcName"}, {"title": "Items stored", "key": "count"}], selectionCallback=self.selectionCallback) self.w.callsToCacheList = vanilla.List((0, 400, -0, -0), [{"funcName": "A", "count": "a"}, {"funcName": "B", "count": "b"}], columnDescriptions=[{"title": "Function", "key": "funcName"}, {"title": "Calls served from cache", "key": "count"}], selectionCallback=self.selectionCallback) self.w.open() self.w.bind("close", self.closeWindow) self.reload() def reloadDesignspace(self, sender=None): print('reloadDesignspace', sender) self.reload() def selectionCallback(self, sender): pass def closeWindow(self, something=None): #print("closeWindow", something) self.doc.changed() pass def reportRandomGlyphChangedButtonCallback(self, sender): for i in range(10): namesLeft = self.doc.glyphsInCache() candidateName = None if namesLeft: candidateName = random.choice(namesLeft) print(f'reportRandomGlyphChangedButtonCallback {i} {candidateName}') if candidateName: self.doc.glyphChanged(candidateName, includeDependencies=True) self.updateList() def generateInstancesButtonCallback(self, sender): self.doc.loadFonts() self.doc.generateUFOs() self.updateList() def makeInstancesOfSameGlyphButtonCallback(self, sender): # make some instances of the same glyph hits = 100 glyphName = random.choice(self.doc.glyphNames) for item in range(hits): location = self.doc.randomLocation() self.doc.makeOneGlyph(glyphName, location, bend=False, decomposeComponents=True, useVarlib=False, roundGeometry=False, clip=False) self.updateList() def makeInstancesOfDifferentGlyphsButtonCallback(self, sender): location = self.doc.randomLocation() for glyphName in self.doc.glyphNames: self.doc.makeOneGlyph(glyphName, location, bend=False, decomposeComponents=True, useVarlib=False, roundGeometry=False, clip=False) self.updateList() def reload(self): if self.doc is not None: # we might still have a previous UFOOperator and we need it to clear the cache self.doc.changed() self.doc = ufoProcessor.ufoOperator.UFOOperator(designspacePath) self.doc.loadFonts() self.doc.changed() self.updateList() def updateList(self): self.w.pathText.set(designspacePath) frequencyItems = [] objectItems = [] objects, frequency = ufoProcessor.ufoOperator.inspectMemoizeCache() for funcName, count in frequency: frequencyItems.append(dict(count=count, funcName= funcName)) for funcName, count in objects: objectItems.append(dict(count=count, funcName= funcName)) self.w.callsToCacheList.set(frequencyItems) self.w.cacheItemsList.set(objectItems) designspacePath = "/Users/erik/code/ufoProcessor/Tests/ds5/ds5.designspace" designspacePath = "/Users/erik/code/type2/Principia/sources/Principia_wght_wght.designspace" UFOOperatorTester(designspacePath)