def test_twoAxesOffAxis(l, n): """Test for a system with two axes. Three values, two on-axis, one off-axis. >>> test_twoAxesOffAxis(0, 0) 0.0 >>> test_twoAxesOffAxis(1, 1) 50.0 >>> test_twoAxesOffAxis(2, 2) 200.0 >>> test_twoAxesOffAxis(1, 0) 100.0 >>> test_twoAxesOffAxis(0, 1) -100.0 >>> test_twoAxesOffAxis(2, 0) 200.0 >>> test_twoAxesOffAxis(0, 2) -200.0 a value in the middle should be in the middle """ m = Mutator() neutral = 0 value = 100.0 m.setNeutral(neutral - neutral) m.addDelta(Location(pop=1), value - neutral, deltaName="test1") m.addDelta(Location(snap=1), -1 * value - neutral, deltaName="test2") m.addDelta(Location(pop=1, snap=1), 50, punch=True, deltaName="test2") return m.getInstance(Location(pop=l, snap=n)) + neutral
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 _collectOffAxisPoints(self): """ Return a dictionary with all off-axis locations. """ offAxis = {} for l, (value, deltaName) in self.items(): location = Location(l) name = location.isOnAxis() if name is None or name is False: offAxis[l] = 1 return offAxis.keys()
def _collectOffAxisPoints(self): """ Return a dictionary with all off-axis locations. """ offAxis = {} for l, (value, deltaName) in self.items(): location = Location(l) name = location.isOnAxis() if name is None or name is False: offAxis[l] = 1 return list(offAxis.keys())
def _collectAxisPoints(self): """ Return a dictionary with all on-axis locations. """ for l, (value, deltaName) in self.items(): location = Location(l) name = location.isOnAxis() if name is not None and name is not False: if not self._axes.has_key(name): self._axes[name] = [] self._axes[name].append(l) return self._axes
def test_getLimits(a, b, t): """Test the getLimits function >>> test_getLimits(0, 1, 0) {'pop': (None, 0, None)} >>> test_getLimits(0, 1, 0.5) {'pop': (0, None, 1)} >>> test_getLimits(0, 1, 1) {'pop': (None, {1: [<Location pop:1 >]}, None)} """ la = Location(pop=a) lb = Location(pop=b) locations = [la, lb] test = Location(pop=t) print(getLimits(locations, test))
def test_methods(): """ Test some of the methods. >>> m = test_methods() >>> m.getAxisNames() ['snap', 'pop'] """ m = Mutator() neutral = 0 value = 100.0 m.setNeutral(neutral - neutral) m.addDelta(Location(pop=1), value - neutral, deltaName="test1") m.addDelta(Location(snap=1), -1 * value - neutral, deltaName="test2") m.addDelta(Location(pop=1, snap=1), 50, punch=True, deltaName="test2") return m
def getFactors(self, aLocation, axisOnly=False): """ Return a list of all factors and math items at aLocation. factor, mathItem, deltaName """ deltas = [] aLocation.expand(self.getAxisNames()) limits = getLimits(self._allLocations(), aLocation) for deltaLocationTuple, (mathItem, deltaName) in self.items(): deltaLocation = Location(deltaLocationTuple) deltaLocation.expand( self.getAxisNames()) factor = self._accumulateFactors(aLocation, deltaLocation, limits, axisOnly) deltas.append((factor, mathItem, deltaName)) return deltas
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 getFactors(self, aLocation, axisOnly=False): """ Return a list of all factors and math items at aLocation. factor, mathItem, deltaName """ deltas = [] aLocation.expand(self.getAxisNames()) limits = getLimits(self._allLocations(), aLocation) for deltaLocationTuple, (mathItem, deltaName) in self.items(): deltaLocation = Location(deltaLocationTuple) deltaLocation.expand(self.getAxisNames()) factor = self._accumulateFactors(aLocation, deltaLocation, limits, axisOnly) deltas.append((factor, mathItem, deltaName)) return deltas
def _makeWarpFromList(self, axisName, warpMap): if not warpMap: warpMap = [(0, 0), (1000, 1000)] self.maps[axisName] = warpMap items = [] for x, y in warpMap: items.append((Location(w=x), y)) m = WarpMutator() items.sort() bias = biasFromLocations([loc for loc, obj in items], True) m.setBias(bias) n = None ofx = [] onx = [] for loc, obj in items: if (loc - bias).isOrigin(): m.setNeutral(obj) break if m.getNeutral() is None: raise MutatorError("Did not find a neutral for this system", m) for loc, obj in items: lb = loc - bias if lb.isOrigin(): continue if lb.isOnAxis(): onx.append((lb, obj - m.getNeutral())) else: ofx.append((lb, obj - m.getNeutral())) for loc, obj in onx: m.addDelta(loc, obj, punch=False, axisOnly=True) for loc, obj in ofx: m.addDelta(loc, obj, punch=True, axisOnly=True) self.warps[axisName] = m
def getGlyphMutator(self, glyphName, decomposeComponents=False): """ Return a glyph mutator.defaultLoc decomposeComponents = True causes the source glyphs to be decomposed first before building the mutator. That gives you instances that do not depend on a complete font. If you're calculating previews for instance. """ if glyphName in self._glyphMutators: return self._glyphMutators[glyphName] items = [] for sourceDescriptor in self.sources: loc = Location(sourceDescriptor.location) f = self.fonts[sourceDescriptor.name] if glyphName in sourceDescriptor.mutedGlyphNames: continue if not glyphName in f: # log this> continue sourceGlyphObject = f[glyphName] if decomposeComponents: temp = self.glyphClass() p = temp.getPointPen() dpp = DecomposePointPen(f, p) sourceGlyphObject.drawPoints(dpp) temp.width = sourceGlyphObject.width temp.name = sourceGlyphObject.name #temp.lib = sourceGlyphObject.lib processThis = temp else: processThis = sourceGlyphObject items.append((loc, self.mathGlyphClass(processThis))) bias, self._glyphMutators[glyphName] = buildMutator( items, axes=self._preppedAxes, bias=self.defaultLoc) return self._glyphMutators[glyphName]
def collectLocations(self): """ Return a dictionary with all objects. """ pts = [] for l, (value, deltaName) in self.items(): pts.append(Location(l)) return pts
def _allLocations(self): """ Return a list of all locations of all objects. """ l = [] for locationTuple in self.keys(): l.append(Location(locationTuple)) return l
def setNeutral(self, aMathObject, deltaName="origin"): """Set the neutral object.""" self._neutral = aMathObject self.addDelta(Location(), aMathObject - aMathObject, deltaName, punch=False, axisOnly=True)
def buildMutator(items, axes=None, bias=None): """ Build a mutator with the (location, obj) pairs in items. Determine the bias based on the given locations. """ from mutatorMath.objects.bender import Bender items = [(Location(loc), obj) for loc, obj in items] if bias is None: bias = Location() else: bias = Location(bias) m = Mutator() if axes is not None: # make a Bender object # but do not transform the locations from the items bender = Bender(axes) m.setBender(bender) else: bender = noBend # the order itself does not matter, but we should always build in the same order. items = sorted(items) if not bias: bias = biasFromLocations([loc for loc, obj in items], True) m.setBias(bias) n = None ofx = [] onx = [] for loc, obj in items: nn = (loc - bias) if nn.isOrigin(): m.setNeutral(obj) break if m.getNeutral() is None: raise MutatorError("Did not find a neutral for this system", items) for loc, obj in items: lb = loc - bias if lb.isOrigin(): continue if lb.isOnAxis(): onx.append((lb, obj - m.getNeutral())) else: ofx.append((lb, obj - m.getNeutral())) for loc, obj in onx: m.addDelta(loc, obj, punch=False, axisOnly=True) for loc, obj in ofx: m.addDelta(loc, obj, punch=True, axisOnly=True) return bias, m
def _calcOnAxisFactor(self, aLocation, deltaAxis, deltasOnSameAxis, deltaLocation): """ Calculate the on-axis factors. """ if deltaAxis == "origin": f = 0 v = 0 else: f = aLocation[deltaAxis] v = deltaLocation[deltaAxis] i = [] iv = {} for value in deltasOnSameAxis: iv[Location(value)[deltaAxis]] = 1 i = iv.keys() i.sort() r = 0 B, M, A = [], [], [] mA, mB, mM = None, None, None for value in i: if value < f: B.append(value) elif value > f: A.append(value) else: M.append(value) if len(B) > 0: mB = max(B) B.sort() if len(A) > 0: mA = min(A) A.sort() if len(M) > 0: mM = min(M) M.sort() if mM is not None: if ((f - _EPSILON < v) and (f + _EPSILON > v)) or f == v: r = 1 else: r = 0 elif mB is not None and mA is not None: if v < mB or v > mA: r = 0 else: if v == mA: r = float(f - mB) / (mA - mB) else: r = float(f - mA) / (mB - mA) elif mB is None and mA is not None: if v == A[1]: r = float(f - A[0]) / (A[1] - A[0]) elif v == A[0]: r = float(f - A[1]) / (A[0] - A[1]) else: r = 0 elif mB is not None and mA is None: if v == B[-2]: r = float(f - B[-1]) / (B[-2] - B[-1]) elif v == mB: r = float(f - B[-2]) / (B[-1] - B[-2]) else: r = 0 return r
def loadLocations(self): """ Store all locations similary as the `.fonts` dict. The sourceDescriptor name is the key. """ self.locations = dict() for sourceDescriptor in self.sources: location = Location(sourceDescriptor.location) self.locations[sourceDescriptor.name] = location
def getFactors(self, aLocation, axisOnly=False): """ Return a list of all factors and math items at aLocation. factor, mathItem, deltaName """ deltas = [] aLocation.expand(self.getAxisNames()) limits = getLimits(self._allLocations(), aLocation) for deltaLocationTuple, (mathItem, deltaName) in sorted(self.iteritems()): deltaLocation = Location(deltaLocationTuple) deltaLocation.expand( self.getAxisNames()) factor = self._accumulateFactors(aLocation, deltaLocation, limits, axisOnly) if not (factor-_EPSILON < 0 < factor+_EPSILON): # only add non-zero deltas. deltas.append((factor, mathItem, deltaName)) deltas.sort() deltas.reverse() return deltas
def getFactors(self, aLocation, axisOnly=False, allFactors=False): """ Return a list of all factors and math items at aLocation. factor, mathItem, deltaName all = True: include factors that are zero or near-zero """ deltas = [] aLocation.expand(self.getAxisNames()) limits = getLimits(self._allLocations(), aLocation) for deltaLocationTuple, (mathItem, deltaName) in sorted(self.items()): deltaLocation = Location(deltaLocationTuple) deltaLocation.expand( self.getAxisNames()) factor = self._accumulateFactors(aLocation, deltaLocation, limits, axisOnly) if not (factor-_EPSILON < 0 < factor+_EPSILON) or allFactors: # only add non-zero deltas. deltas.append((factor, mathItem, deltaName)) deltas = sorted(deltas, key=itemgetter(0), reverse=True) return deltas
def buildMutator(items, axes=None, bias=None): """ Build a mutator with the (location, obj) pairs in items. Determine the bias based on the given locations. """ from mutatorMath.objects.bender import Bender m = Mutator() if axes is not None: bender = Bender(axes) m.setBender(bender) else: bender = noBend # the order itself does not matter, but we should always build in the same order. items = sorted(items) if not bias: bias = biasFromLocations([bender(Location(loc)) for loc, obj in items], True) else: # note: this means that the actual bias might be different from the initial value. bias = bender(bias) m.setBias(bias) n = None ofx = [] onx = [] for loc, obj in items: loc = bender(Location(loc)) if (loc-bias).isOrigin(): m.setNeutral(obj) break if m.getNeutral() is None: raise MutatorError("Did not find a neutral for this system", m) for loc, obj in items: locbent = bender(Location(loc)) #lb = loc-bias lb = locbent-bias if lb.isOrigin(): continue if lb.isOnAxis(): onx.append((lb, obj-m.getNeutral())) else: ofx.append((lb, obj-m.getNeutral())) for loc, obj in onx: m.addDelta(loc, obj, punch=False, axisOnly=True) for loc, obj in ofx: m.addDelta(loc, obj, punch=True, axisOnly=True) return bias, m
def getFactors(self, aLocation, axisOnly=False): """ Return a list of all factors and math items at aLocation. factor, mathItem, deltaName """ deltas = [] aLocation.expand(self.getAxisNames()) limits = getLimits(self._allLocations(), aLocation) for deltaLocationTuple, (mathItem, deltaName) in sorted(self.iteritems()): deltaLocation = Location(deltaLocationTuple) deltaLocation.expand(self.getAxisNames()) factor = self._accumulateFactors(aLocation, deltaLocation, limits, axisOnly) if not (factor - _EPSILON < 0 < factor + _EPSILON): # only add non-zero deltas. deltas.append((factor, mathItem, deltaName)) deltas.sort() deltas.reverse() return deltas
def makeInstance(self, aLocation, bend=True): """ Calculate an instance with the right bias and add the neutral. aLocation: expected to be in input space """ if bend: aLocation = self._bender(Location(aLocation)) if not aLocation.isAmbivalent(): instanceObject = self.getInstance(aLocation-self._bias) else: locX, locY = aLocation.split() instanceObject = self.getInstance(locX-self._bias)*(1,0)+self.getInstance(locY-self._bias)*(0,1) return instanceObject+self._neutral
def getInfoMutator(self): """ Returns a info mutator """ if self._infoMutator: return self._infoMutator infoItems = [] for sourceDescriptor in self.sources: loc = Location(sourceDescriptor.location) sourceFont = self.fonts[sourceDescriptor.name] infoItems.append((loc, self.mathInfoClass(sourceFont.info))) bias, self._infoMutator = buildMutator(infoItems, axes=self._preppedAxes, bias=self.defaultLoc) return self._infoMutator
def _getTargetLocation(self, stemTarget, masters, workingStems, scale): """ Return a proper Location object for a scaled glyph instance, the essential part lies in the conversion of stem values. so that in anisotropic mode, a MutatorScaleEngine can attempt to produce a glyph with proper stem widths without requiring two-axes interpolation. """ xScale, yScale = scale targetVstem, targetHstem = None, None try: targetVstem, targetHstem = stemTarget except: pass if targetVstem is not None and targetHstem is not None: if workingStems == 'both': return Location(vstem=targetVstem, hstem=targetHstem) elif workingStems == 'vstem': vStems = [master.vstem * xScale for master in masters] hStems = [master.hstem * yScale for master in masters] (minVStem, minStemIndex), (maxVStem, maxStemIndex) = self._getExtremes(vStems) vStemSpan = (minVStem, maxVStem) hStemSpan = hStems[minStemIndex], hStems[maxStemIndex] newHstem = mapValue(targetHstem, hStemSpan, vStemSpan) return Location(stem=(targetVstem, newHstem)) elif workingStems == 'hstem': return Location(stem=targetHstem) else: return Location(stem=stemTarget)
def test_twoAxes(l, n): """Test for a system with two axes, 2 values both on-axis. >>> test_twoAxes(1, 1) 0.0 >>> test_twoAxes(1, 0) 100.0 >>> test_twoAxes(0, 1) -100.0 >>> test_twoAxes(2, 0) 200.0 >>> test_twoAxes(0, 2) -200.0 a value in the middle should be in the middle """ m = Mutator() neutral = 0 value = 100.0 m.setNeutral(neutral - neutral) m.addDelta(Location(pop=1), value - neutral, deltaName="test1") m.addDelta(Location(snap=1), -1 * value - neutral, deltaName="test2") return m.getInstance(Location(pop=l, snap=n)) + neutral
def getKerningMutator(self): """ Return a kerning mutator """ if self._kerningMutator: return self._kerningMutator kerningItems = [] for sourceDescriptor in self.sources: loc = Location(sourceDescriptor.location) sourceFont = self.fonts[sourceDescriptor.name] kerningItems.append((loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) bias, self._kerningMutator = buildMutator(kerningItems, axes=self._preppedAxes, bias=self.defaultLoc) return self._kerningMutator
def test_singleAxis(n): """ Tests for a single axis. A mutator is created with a single value, 100, on a single axis. values we enter should be reproduced >>> test_singleAxis(1) 100.0 >>> test_singleAxis(0) 0.0 a value in the middle should be in the middle >>> test_singleAxis(.5) 50.0 >>> test_singleAxis(.99) 99.0 extrapolation over zero >>> test_singleAxis(-1) -100.0 >>> test_singleAxis(-2) -200.0 >>> test_singleAxis(-1.5) -150.0 extrapolation over value >>> test_singleAxis(2) 200.0 """ m = Mutator() neutral = 0 value = 100.0 m.setNeutral(neutral - neutral) m.addDelta(Location(pop=1), value - neutral, deltaName="test") return m.getInstance(Location(pop=n)) + neutral
def getGlyphMutator(self, glyphName): """ Return a glyph mutator """ if glyphName in self._glyphMutators: return self._glyphMutators[glyphName] items = [] for sourceDescriptor in self.sources: loc = Location(sourceDescriptor.location) f = self.fonts[sourceDescriptor.name] if glyphName in sourceDescriptor.mutedGlyphNames: continue if not glyphName in f: # log this> continue items.append((loc, self.mathGlyphClass(f[glyphName]))) bias, self._glyphMutators[glyphName] = buildMutator( items, axes=self._preppedAxes, bias=self.defaultLoc) return self._glyphMutators[glyphName]
def getKerningMutator(self): """ Return a kerning mutator """ if self._kerningMutator: return self._kerningMutator kerningItems = [] for sourceDescriptor in self.sources: if not sourceDescriptor.muteKerning: loc = Location(sourceDescriptor.location) sourceFont = self.fonts[sourceDescriptor.name] # this makes assumptions about the groups of all sources being the same. kerningItems.append( (loc, self.mathKerningClass(sourceFont.kerning, sourceFont.groups))) bias, self._kerningMutator = buildMutator(kerningItems, axes=self._preppedAxes, bias=self.defaultLoc) return self._kerningMutator
def makeGlyphInstances(self, axesGrid): mutator = self.mutator masterSpots = [spot for spot, masterFont in self.masters] nCellsOnHorizontalAxis, nCellsOnVerticalAxis = axesGrid matrix = self.w.matrix instanceGlyph = None for i in range(nCellsOnHorizontalAxis): ch = getKeyForValue(i) for j in range(nCellsOnVerticalAxis): if (ch, j) not in masterSpots: instanceLocation = Location(horizontal=i, vertical=j) if mutator is not None: try: instanceGlyph = mutator.makeInstance(instanceLocation) except: instanceGlyph = None cell = getattr(matrix, '%s%s'%(ch, j)) cell.glyphView.setGlyph(instanceGlyph)
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 placeGlyphMasters(self, glyphName, axesGrid): availableFonts = AllFonts() masters = [] nCellsOnHorizontalAxis, nCellsOnVerticalAxis = axesGrid matrix = self.w.matrix masterGlyph = None for matrixLocation in self.masters: spot, masterFont = matrixLocation ch, j = spot i = getValueForKey(ch) if (masterFont in availableFonts) and (glyphName is not None) and (glyphName in masterFont): if i <= nCellsOnHorizontalAxis and j <= nCellsOnVerticalAxis: l = Location(horizontal=i, vertical=j) masterGlyph = makePreviewGlyph(masterFont[glyphName]) if masterGlyph is not None: masters.append((l, masterGlyph)) elif (masterFont not in availableFonts): self.masters.remove(matrixLocation) if i < nCellsOnHorizontalAxis and j < nCellsOnVerticalAxis: cell = getattr(matrix, '%s%s'%(ch, j)) cell.glyphView.setGlyph(masterGlyph) if masterGlyph is not None: cell.glyphView.getNSView().setContourColor_(MasterColor) cell.masterMask.show(True) fontName = ' '.join([masterFont.info.familyName, masterFont.info.styleName]) cell.name.set(fontName) elif masterGlyph is None: cell.glyphView.getNSView().setContourColor_(BlackColor) cell.masterMask.show(False) cell.name.set('') if len(masters) > 1: try: bias, mutator = buildMutator(masters) self.mutator = mutator except: self.mutator = None
bias, mb = buildMutator(items) grid = {} for loc, (master, xx) in mb.items(): for axis, value in loc: if not axis in grid: grid[axis] = [] if not (axis,value) in grid[axis]: grid[axis].append((axis,value)) nodes = list(itertools.product(*grid.values())) nodes.sort() corners = {} for n in nodes: l = Location() l.fromTuple(n) for factor, obj, name in mb.getFactors(l, allFactors=True): if not obj.name in corners: corners[obj.name] = [] corners[obj.name].append((factor, l)) def dot((x,y), s=10): save() fill(1,0,0 ,.5) oval(x-.5*s, y-.5*s, s, s) restore() def nameToStroke(name, alpha=1): # simple conversion of a name to some sort of unique-ish color. rann.seed(name)