1*e1fe3e4aSElliott Hughes"""GlyphSets returned by a TTFont.""" 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughesfrom abc import ABC, abstractmethod 4*e1fe3e4aSElliott Hughesfrom collections.abc import Mapping 5*e1fe3e4aSElliott Hughesfrom contextlib import contextmanager 6*e1fe3e4aSElliott Hughesfrom copy import copy 7*e1fe3e4aSElliott Hughesfrom types import SimpleNamespace 8*e1fe3e4aSElliott Hughesfrom fontTools.misc.fixedTools import otRound 9*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import deprecateFunction 10*e1fe3e4aSElliott Hughesfrom fontTools.misc.transform import Transform 11*e1fe3e4aSElliott Hughesfrom fontTools.pens.transformPen import TransformPen, TransformPointPen 12*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import ( 13*e1fe3e4aSElliott Hughes DecomposingRecordingPen, 14*e1fe3e4aSElliott Hughes lerpRecordings, 15*e1fe3e4aSElliott Hughes replayRecording, 16*e1fe3e4aSElliott Hughes) 17*e1fe3e4aSElliott Hughes 18*e1fe3e4aSElliott Hughes 19*e1fe3e4aSElliott Hughesclass _TTGlyphSet(Mapping): 20*e1fe3e4aSElliott Hughes """Generic dict-like GlyphSet class that pulls metrics from hmtx and 21*e1fe3e4aSElliott Hughes glyph shape from TrueType or CFF. 22*e1fe3e4aSElliott Hughes """ 23*e1fe3e4aSElliott Hughes 24*e1fe3e4aSElliott Hughes def __init__(self, font, location, glyphsMapping, *, recalcBounds=True): 25*e1fe3e4aSElliott Hughes self.recalcBounds = recalcBounds 26*e1fe3e4aSElliott Hughes self.font = font 27*e1fe3e4aSElliott Hughes self.defaultLocationNormalized = ( 28*e1fe3e4aSElliott Hughes {axis.axisTag: 0 for axis in self.font["fvar"].axes} 29*e1fe3e4aSElliott Hughes if "fvar" in self.font 30*e1fe3e4aSElliott Hughes else {} 31*e1fe3e4aSElliott Hughes ) 32*e1fe3e4aSElliott Hughes self.location = location if location is not None else {} 33*e1fe3e4aSElliott Hughes self.rawLocation = {} # VarComponent-only location 34*e1fe3e4aSElliott Hughes self.originalLocation = location if location is not None else {} 35*e1fe3e4aSElliott Hughes self.depth = 0 36*e1fe3e4aSElliott Hughes self.locationStack = [] 37*e1fe3e4aSElliott Hughes self.rawLocationStack = [] 38*e1fe3e4aSElliott Hughes self.glyphsMapping = glyphsMapping 39*e1fe3e4aSElliott Hughes self.hMetrics = font["hmtx"].metrics 40*e1fe3e4aSElliott Hughes self.vMetrics = getattr(font.get("vmtx"), "metrics", None) 41*e1fe3e4aSElliott Hughes self.hvarTable = None 42*e1fe3e4aSElliott Hughes if location: 43*e1fe3e4aSElliott Hughes from fontTools.varLib.varStore import VarStoreInstancer 44*e1fe3e4aSElliott Hughes 45*e1fe3e4aSElliott Hughes self.hvarTable = getattr(font.get("HVAR"), "table", None) 46*e1fe3e4aSElliott Hughes if self.hvarTable is not None: 47*e1fe3e4aSElliott Hughes self.hvarInstancer = VarStoreInstancer( 48*e1fe3e4aSElliott Hughes self.hvarTable.VarStore, font["fvar"].axes, location 49*e1fe3e4aSElliott Hughes ) 50*e1fe3e4aSElliott Hughes # TODO VVAR, VORG 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott Hughes @contextmanager 53*e1fe3e4aSElliott Hughes def pushLocation(self, location, reset: bool): 54*e1fe3e4aSElliott Hughes self.locationStack.append(self.location) 55*e1fe3e4aSElliott Hughes self.rawLocationStack.append(self.rawLocation) 56*e1fe3e4aSElliott Hughes if reset: 57*e1fe3e4aSElliott Hughes self.location = self.originalLocation.copy() 58*e1fe3e4aSElliott Hughes self.rawLocation = self.defaultLocationNormalized.copy() 59*e1fe3e4aSElliott Hughes else: 60*e1fe3e4aSElliott Hughes self.location = self.location.copy() 61*e1fe3e4aSElliott Hughes self.rawLocation = {} 62*e1fe3e4aSElliott Hughes self.location.update(location) 63*e1fe3e4aSElliott Hughes self.rawLocation.update(location) 64*e1fe3e4aSElliott Hughes 65*e1fe3e4aSElliott Hughes try: 66*e1fe3e4aSElliott Hughes yield None 67*e1fe3e4aSElliott Hughes finally: 68*e1fe3e4aSElliott Hughes self.location = self.locationStack.pop() 69*e1fe3e4aSElliott Hughes self.rawLocation = self.rawLocationStack.pop() 70*e1fe3e4aSElliott Hughes 71*e1fe3e4aSElliott Hughes @contextmanager 72*e1fe3e4aSElliott Hughes def pushDepth(self): 73*e1fe3e4aSElliott Hughes try: 74*e1fe3e4aSElliott Hughes depth = self.depth 75*e1fe3e4aSElliott Hughes self.depth += 1 76*e1fe3e4aSElliott Hughes yield depth 77*e1fe3e4aSElliott Hughes finally: 78*e1fe3e4aSElliott Hughes self.depth -= 1 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughes def __contains__(self, glyphName): 81*e1fe3e4aSElliott Hughes return glyphName in self.glyphsMapping 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott Hughes def __iter__(self): 84*e1fe3e4aSElliott Hughes return iter(self.glyphsMapping.keys()) 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes def __len__(self): 87*e1fe3e4aSElliott Hughes return len(self.glyphsMapping) 88*e1fe3e4aSElliott Hughes 89*e1fe3e4aSElliott Hughes @deprecateFunction( 90*e1fe3e4aSElliott Hughes "use 'glyphName in glyphSet' instead", category=DeprecationWarning 91*e1fe3e4aSElliott Hughes ) 92*e1fe3e4aSElliott Hughes def has_key(self, glyphName): 93*e1fe3e4aSElliott Hughes return glyphName in self.glyphsMapping 94*e1fe3e4aSElliott Hughes 95*e1fe3e4aSElliott Hughes 96*e1fe3e4aSElliott Hughesclass _TTGlyphSetGlyf(_TTGlyphSet): 97*e1fe3e4aSElliott Hughes def __init__(self, font, location, recalcBounds=True): 98*e1fe3e4aSElliott Hughes self.glyfTable = font["glyf"] 99*e1fe3e4aSElliott Hughes super().__init__(font, location, self.glyfTable, recalcBounds=recalcBounds) 100*e1fe3e4aSElliott Hughes self.gvarTable = font.get("gvar") 101*e1fe3e4aSElliott Hughes 102*e1fe3e4aSElliott Hughes def __getitem__(self, glyphName): 103*e1fe3e4aSElliott Hughes return _TTGlyphGlyf(self, glyphName, recalcBounds=self.recalcBounds) 104*e1fe3e4aSElliott Hughes 105*e1fe3e4aSElliott Hughes 106*e1fe3e4aSElliott Hughesclass _TTGlyphSetCFF(_TTGlyphSet): 107*e1fe3e4aSElliott Hughes def __init__(self, font, location): 108*e1fe3e4aSElliott Hughes tableTag = "CFF2" if "CFF2" in font else "CFF " 109*e1fe3e4aSElliott Hughes self.charStrings = list(font[tableTag].cff.values())[0].CharStrings 110*e1fe3e4aSElliott Hughes super().__init__(font, location, self.charStrings) 111*e1fe3e4aSElliott Hughes self.blender = None 112*e1fe3e4aSElliott Hughes if location: 113*e1fe3e4aSElliott Hughes from fontTools.varLib.varStore import VarStoreInstancer 114*e1fe3e4aSElliott Hughes 115*e1fe3e4aSElliott Hughes varStore = getattr(self.charStrings, "varStore", None) 116*e1fe3e4aSElliott Hughes if varStore is not None: 117*e1fe3e4aSElliott Hughes instancer = VarStoreInstancer( 118*e1fe3e4aSElliott Hughes varStore.otVarStore, font["fvar"].axes, location 119*e1fe3e4aSElliott Hughes ) 120*e1fe3e4aSElliott Hughes self.blender = instancer.interpolateFromDeltas 121*e1fe3e4aSElliott Hughes 122*e1fe3e4aSElliott Hughes def __getitem__(self, glyphName): 123*e1fe3e4aSElliott Hughes return _TTGlyphCFF(self, glyphName) 124*e1fe3e4aSElliott Hughes 125*e1fe3e4aSElliott Hughes 126*e1fe3e4aSElliott Hughesclass _TTGlyph(ABC): 127*e1fe3e4aSElliott Hughes """Glyph object that supports the Pen protocol, meaning that it has 128*e1fe3e4aSElliott Hughes .draw() and .drawPoints() methods that take a pen object as their only 129*e1fe3e4aSElliott Hughes argument. Additionally there are 'width' and 'lsb' attributes, read from 130*e1fe3e4aSElliott Hughes the 'hmtx' table. 131*e1fe3e4aSElliott Hughes 132*e1fe3e4aSElliott Hughes If the font contains a 'vmtx' table, there will also be 'height' and 'tsb' 133*e1fe3e4aSElliott Hughes attributes. 134*e1fe3e4aSElliott Hughes """ 135*e1fe3e4aSElliott Hughes 136*e1fe3e4aSElliott Hughes def __init__(self, glyphSet, glyphName, *, recalcBounds=True): 137*e1fe3e4aSElliott Hughes self.glyphSet = glyphSet 138*e1fe3e4aSElliott Hughes self.name = glyphName 139*e1fe3e4aSElliott Hughes self.recalcBounds = recalcBounds 140*e1fe3e4aSElliott Hughes self.width, self.lsb = glyphSet.hMetrics[glyphName] 141*e1fe3e4aSElliott Hughes if glyphSet.vMetrics is not None: 142*e1fe3e4aSElliott Hughes self.height, self.tsb = glyphSet.vMetrics[glyphName] 143*e1fe3e4aSElliott Hughes else: 144*e1fe3e4aSElliott Hughes self.height, self.tsb = None, None 145*e1fe3e4aSElliott Hughes if glyphSet.location and glyphSet.hvarTable is not None: 146*e1fe3e4aSElliott Hughes varidx = ( 147*e1fe3e4aSElliott Hughes glyphSet.font.getGlyphID(glyphName) 148*e1fe3e4aSElliott Hughes if glyphSet.hvarTable.AdvWidthMap is None 149*e1fe3e4aSElliott Hughes else glyphSet.hvarTable.AdvWidthMap.mapping[glyphName] 150*e1fe3e4aSElliott Hughes ) 151*e1fe3e4aSElliott Hughes self.width += glyphSet.hvarInstancer[varidx] 152*e1fe3e4aSElliott Hughes # TODO: VVAR/VORG 153*e1fe3e4aSElliott Hughes 154*e1fe3e4aSElliott Hughes @abstractmethod 155*e1fe3e4aSElliott Hughes def draw(self, pen): 156*e1fe3e4aSElliott Hughes """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 157*e1fe3e4aSElliott Hughes how that works. 158*e1fe3e4aSElliott Hughes """ 159*e1fe3e4aSElliott Hughes raise NotImplementedError 160*e1fe3e4aSElliott Hughes 161*e1fe3e4aSElliott Hughes def drawPoints(self, pen): 162*e1fe3e4aSElliott Hughes """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details 163*e1fe3e4aSElliott Hughes how that works. 164*e1fe3e4aSElliott Hughes """ 165*e1fe3e4aSElliott Hughes from fontTools.pens.pointPen import SegmentToPointPen 166*e1fe3e4aSElliott Hughes 167*e1fe3e4aSElliott Hughes self.draw(SegmentToPointPen(pen)) 168*e1fe3e4aSElliott Hughes 169*e1fe3e4aSElliott Hughes 170*e1fe3e4aSElliott Hughesclass _TTGlyphGlyf(_TTGlyph): 171*e1fe3e4aSElliott Hughes def draw(self, pen): 172*e1fe3e4aSElliott Hughes """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 173*e1fe3e4aSElliott Hughes how that works. 174*e1fe3e4aSElliott Hughes """ 175*e1fe3e4aSElliott Hughes glyph, offset = self._getGlyphAndOffset() 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes with self.glyphSet.pushDepth() as depth: 178*e1fe3e4aSElliott Hughes if depth: 179*e1fe3e4aSElliott Hughes offset = 0 # Offset should only apply at top-level 180*e1fe3e4aSElliott Hughes 181*e1fe3e4aSElliott Hughes if glyph.isVarComposite(): 182*e1fe3e4aSElliott Hughes self._drawVarComposite(glyph, pen, False) 183*e1fe3e4aSElliott Hughes return 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes glyph.draw(pen, self.glyphSet.glyfTable, offset) 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes def drawPoints(self, pen): 188*e1fe3e4aSElliott Hughes """Draw the glyph onto ``pen``. See fontTools.pens.pointPen for details 189*e1fe3e4aSElliott Hughes how that works. 190*e1fe3e4aSElliott Hughes """ 191*e1fe3e4aSElliott Hughes glyph, offset = self._getGlyphAndOffset() 192*e1fe3e4aSElliott Hughes 193*e1fe3e4aSElliott Hughes with self.glyphSet.pushDepth() as depth: 194*e1fe3e4aSElliott Hughes if depth: 195*e1fe3e4aSElliott Hughes offset = 0 # Offset should only apply at top-level 196*e1fe3e4aSElliott Hughes 197*e1fe3e4aSElliott Hughes if glyph.isVarComposite(): 198*e1fe3e4aSElliott Hughes self._drawVarComposite(glyph, pen, True) 199*e1fe3e4aSElliott Hughes return 200*e1fe3e4aSElliott Hughes 201*e1fe3e4aSElliott Hughes glyph.drawPoints(pen, self.glyphSet.glyfTable, offset) 202*e1fe3e4aSElliott Hughes 203*e1fe3e4aSElliott Hughes def _drawVarComposite(self, glyph, pen, isPointPen): 204*e1fe3e4aSElliott Hughes from fontTools.ttLib.tables._g_l_y_f import ( 205*e1fe3e4aSElliott Hughes VarComponentFlags, 206*e1fe3e4aSElliott Hughes VAR_COMPONENT_TRANSFORM_MAPPING, 207*e1fe3e4aSElliott Hughes ) 208*e1fe3e4aSElliott Hughes 209*e1fe3e4aSElliott Hughes for comp in glyph.components: 210*e1fe3e4aSElliott Hughes with self.glyphSet.pushLocation( 211*e1fe3e4aSElliott Hughes comp.location, comp.flags & VarComponentFlags.RESET_UNSPECIFIED_AXES 212*e1fe3e4aSElliott Hughes ): 213*e1fe3e4aSElliott Hughes try: 214*e1fe3e4aSElliott Hughes pen.addVarComponent( 215*e1fe3e4aSElliott Hughes comp.glyphName, comp.transform, self.glyphSet.rawLocation 216*e1fe3e4aSElliott Hughes ) 217*e1fe3e4aSElliott Hughes except AttributeError: 218*e1fe3e4aSElliott Hughes t = comp.transform.toTransform() 219*e1fe3e4aSElliott Hughes if isPointPen: 220*e1fe3e4aSElliott Hughes tPen = TransformPointPen(pen, t) 221*e1fe3e4aSElliott Hughes self.glyphSet[comp.glyphName].drawPoints(tPen) 222*e1fe3e4aSElliott Hughes else: 223*e1fe3e4aSElliott Hughes tPen = TransformPen(pen, t) 224*e1fe3e4aSElliott Hughes self.glyphSet[comp.glyphName].draw(tPen) 225*e1fe3e4aSElliott Hughes 226*e1fe3e4aSElliott Hughes def _getGlyphAndOffset(self): 227*e1fe3e4aSElliott Hughes if self.glyphSet.location and self.glyphSet.gvarTable is not None: 228*e1fe3e4aSElliott Hughes glyph = self._getGlyphInstance() 229*e1fe3e4aSElliott Hughes else: 230*e1fe3e4aSElliott Hughes glyph = self.glyphSet.glyfTable[self.name] 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0 233*e1fe3e4aSElliott Hughes return glyph, offset 234*e1fe3e4aSElliott Hughes 235*e1fe3e4aSElliott Hughes def _getGlyphInstance(self): 236*e1fe3e4aSElliott Hughes from fontTools.varLib.iup import iup_delta 237*e1fe3e4aSElliott Hughes from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates 238*e1fe3e4aSElliott Hughes from fontTools.varLib.models import supportScalar 239*e1fe3e4aSElliott Hughes 240*e1fe3e4aSElliott Hughes glyphSet = self.glyphSet 241*e1fe3e4aSElliott Hughes glyfTable = glyphSet.glyfTable 242*e1fe3e4aSElliott Hughes variations = glyphSet.gvarTable.variations[self.name] 243*e1fe3e4aSElliott Hughes hMetrics = glyphSet.hMetrics 244*e1fe3e4aSElliott Hughes vMetrics = glyphSet.vMetrics 245*e1fe3e4aSElliott Hughes coordinates, _ = glyfTable._getCoordinatesAndControls( 246*e1fe3e4aSElliott Hughes self.name, hMetrics, vMetrics 247*e1fe3e4aSElliott Hughes ) 248*e1fe3e4aSElliott Hughes origCoords, endPts = None, None 249*e1fe3e4aSElliott Hughes for var in variations: 250*e1fe3e4aSElliott Hughes scalar = supportScalar(glyphSet.location, var.axes) 251*e1fe3e4aSElliott Hughes if not scalar: 252*e1fe3e4aSElliott Hughes continue 253*e1fe3e4aSElliott Hughes delta = var.coordinates 254*e1fe3e4aSElliott Hughes if None in delta: 255*e1fe3e4aSElliott Hughes if origCoords is None: 256*e1fe3e4aSElliott Hughes origCoords, control = glyfTable._getCoordinatesAndControls( 257*e1fe3e4aSElliott Hughes self.name, hMetrics, vMetrics 258*e1fe3e4aSElliott Hughes ) 259*e1fe3e4aSElliott Hughes endPts = ( 260*e1fe3e4aSElliott Hughes control[1] if control[0] >= 1 else list(range(len(control[1]))) 261*e1fe3e4aSElliott Hughes ) 262*e1fe3e4aSElliott Hughes delta = iup_delta(delta, origCoords, endPts) 263*e1fe3e4aSElliott Hughes coordinates += GlyphCoordinates(delta) * scalar 264*e1fe3e4aSElliott Hughes 265*e1fe3e4aSElliott Hughes glyph = copy(glyfTable[self.name]) # Shallow copy 266*e1fe3e4aSElliott Hughes width, lsb, height, tsb = _setCoordinates( 267*e1fe3e4aSElliott Hughes glyph, coordinates, glyfTable, recalcBounds=self.recalcBounds 268*e1fe3e4aSElliott Hughes ) 269*e1fe3e4aSElliott Hughes self.lsb = lsb 270*e1fe3e4aSElliott Hughes self.tsb = tsb 271*e1fe3e4aSElliott Hughes if glyphSet.hvarTable is None: 272*e1fe3e4aSElliott Hughes # no HVAR: let's set metrics from the phantom points 273*e1fe3e4aSElliott Hughes self.width = width 274*e1fe3e4aSElliott Hughes self.height = height 275*e1fe3e4aSElliott Hughes return glyph 276*e1fe3e4aSElliott Hughes 277*e1fe3e4aSElliott Hughes 278*e1fe3e4aSElliott Hughesclass _TTGlyphCFF(_TTGlyph): 279*e1fe3e4aSElliott Hughes def draw(self, pen): 280*e1fe3e4aSElliott Hughes """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details 281*e1fe3e4aSElliott Hughes how that works. 282*e1fe3e4aSElliott Hughes """ 283*e1fe3e4aSElliott Hughes self.glyphSet.charStrings[self.name].draw(pen, self.glyphSet.blender) 284*e1fe3e4aSElliott Hughes 285*e1fe3e4aSElliott Hughes 286*e1fe3e4aSElliott Hughesdef _setCoordinates(glyph, coord, glyfTable, *, recalcBounds=True): 287*e1fe3e4aSElliott Hughes # Handle phantom points for (left, right, top, bottom) positions. 288*e1fe3e4aSElliott Hughes assert len(coord) >= 4 289*e1fe3e4aSElliott Hughes leftSideX = coord[-4][0] 290*e1fe3e4aSElliott Hughes rightSideX = coord[-3][0] 291*e1fe3e4aSElliott Hughes topSideY = coord[-2][1] 292*e1fe3e4aSElliott Hughes bottomSideY = coord[-1][1] 293*e1fe3e4aSElliott Hughes 294*e1fe3e4aSElliott Hughes for _ in range(4): 295*e1fe3e4aSElliott Hughes del coord[-1] 296*e1fe3e4aSElliott Hughes 297*e1fe3e4aSElliott Hughes if glyph.isComposite(): 298*e1fe3e4aSElliott Hughes assert len(coord) == len(glyph.components) 299*e1fe3e4aSElliott Hughes glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy 300*e1fe3e4aSElliott Hughes for p, comp in zip(coord, glyph.components): 301*e1fe3e4aSElliott Hughes if hasattr(comp, "x"): 302*e1fe3e4aSElliott Hughes comp.x, comp.y = p 303*e1fe3e4aSElliott Hughes elif glyph.isVarComposite(): 304*e1fe3e4aSElliott Hughes glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy 305*e1fe3e4aSElliott Hughes for comp in glyph.components: 306*e1fe3e4aSElliott Hughes coord = comp.setCoordinates(coord) 307*e1fe3e4aSElliott Hughes assert not coord 308*e1fe3e4aSElliott Hughes elif glyph.numberOfContours == 0: 309*e1fe3e4aSElliott Hughes assert len(coord) == 0 310*e1fe3e4aSElliott Hughes else: 311*e1fe3e4aSElliott Hughes assert len(coord) == len(glyph.coordinates) 312*e1fe3e4aSElliott Hughes glyph.coordinates = coord 313*e1fe3e4aSElliott Hughes 314*e1fe3e4aSElliott Hughes if recalcBounds: 315*e1fe3e4aSElliott Hughes glyph.recalcBounds(glyfTable) 316*e1fe3e4aSElliott Hughes 317*e1fe3e4aSElliott Hughes horizontalAdvanceWidth = otRound(rightSideX - leftSideX) 318*e1fe3e4aSElliott Hughes verticalAdvanceWidth = otRound(topSideY - bottomSideY) 319*e1fe3e4aSElliott Hughes leftSideBearing = otRound(glyph.xMin - leftSideX) 320*e1fe3e4aSElliott Hughes topSideBearing = otRound(topSideY - glyph.yMax) 321*e1fe3e4aSElliott Hughes return ( 322*e1fe3e4aSElliott Hughes horizontalAdvanceWidth, 323*e1fe3e4aSElliott Hughes leftSideBearing, 324*e1fe3e4aSElliott Hughes verticalAdvanceWidth, 325*e1fe3e4aSElliott Hughes topSideBearing, 326*e1fe3e4aSElliott Hughes ) 327*e1fe3e4aSElliott Hughes 328*e1fe3e4aSElliott Hughes 329*e1fe3e4aSElliott Hughesclass LerpGlyphSet(Mapping): 330*e1fe3e4aSElliott Hughes """A glyphset that interpolates between two other glyphsets. 331*e1fe3e4aSElliott Hughes 332*e1fe3e4aSElliott Hughes Factor is typically between 0 and 1. 0 means the first glyphset, 333*e1fe3e4aSElliott Hughes 1 means the second glyphset, and 0.5 means the average of the 334*e1fe3e4aSElliott Hughes two glyphsets. Other values are possible, and can be useful to 335*e1fe3e4aSElliott Hughes extrapolate. Defaults to 0.5. 336*e1fe3e4aSElliott Hughes """ 337*e1fe3e4aSElliott Hughes 338*e1fe3e4aSElliott Hughes def __init__(self, glyphset1, glyphset2, factor=0.5): 339*e1fe3e4aSElliott Hughes self.glyphset1 = glyphset1 340*e1fe3e4aSElliott Hughes self.glyphset2 = glyphset2 341*e1fe3e4aSElliott Hughes self.factor = factor 342*e1fe3e4aSElliott Hughes 343*e1fe3e4aSElliott Hughes def __getitem__(self, glyphname): 344*e1fe3e4aSElliott Hughes if glyphname in self.glyphset1 and glyphname in self.glyphset2: 345*e1fe3e4aSElliott Hughes return LerpGlyph(glyphname, self) 346*e1fe3e4aSElliott Hughes raise KeyError(glyphname) 347*e1fe3e4aSElliott Hughes 348*e1fe3e4aSElliott Hughes def __contains__(self, glyphname): 349*e1fe3e4aSElliott Hughes return glyphname in self.glyphset1 and glyphname in self.glyphset2 350*e1fe3e4aSElliott Hughes 351*e1fe3e4aSElliott Hughes def __iter__(self): 352*e1fe3e4aSElliott Hughes set1 = set(self.glyphset1) 353*e1fe3e4aSElliott Hughes set2 = set(self.glyphset2) 354*e1fe3e4aSElliott Hughes return iter(set1.intersection(set2)) 355*e1fe3e4aSElliott Hughes 356*e1fe3e4aSElliott Hughes def __len__(self): 357*e1fe3e4aSElliott Hughes set1 = set(self.glyphset1) 358*e1fe3e4aSElliott Hughes set2 = set(self.glyphset2) 359*e1fe3e4aSElliott Hughes return len(set1.intersection(set2)) 360*e1fe3e4aSElliott Hughes 361*e1fe3e4aSElliott Hughes 362*e1fe3e4aSElliott Hughesclass LerpGlyph: 363*e1fe3e4aSElliott Hughes def __init__(self, glyphname, glyphset): 364*e1fe3e4aSElliott Hughes self.glyphset = glyphset 365*e1fe3e4aSElliott Hughes self.glyphname = glyphname 366*e1fe3e4aSElliott Hughes 367*e1fe3e4aSElliott Hughes def draw(self, pen): 368*e1fe3e4aSElliott Hughes recording1 = DecomposingRecordingPen(self.glyphset.glyphset1) 369*e1fe3e4aSElliott Hughes self.glyphset.glyphset1[self.glyphname].draw(recording1) 370*e1fe3e4aSElliott Hughes recording2 = DecomposingRecordingPen(self.glyphset.glyphset2) 371*e1fe3e4aSElliott Hughes self.glyphset.glyphset2[self.glyphname].draw(recording2) 372*e1fe3e4aSElliott Hughes 373*e1fe3e4aSElliott Hughes factor = self.glyphset.factor 374*e1fe3e4aSElliott Hughes 375*e1fe3e4aSElliott Hughes replayRecording(lerpRecordings(recording1.value, recording2.value, factor), pen) 376