def renameClasses(self, oldToNew): """ Renames all class names (in each StateRow, and in the class table) as specified by the oldToNew dict, which maps old strings to new ones. If an old name is not present as a key in oldToNew, that class name will not be changed. >>> oldToNew = {"acute": "acuteaccent", "grave": "graveaccent"} >>> obj = _makePointExample() >>> obj.renameClasses(oldToNew) >>> obj.pprint(onlySignificant=True) State 'Start of text': Class 'x': Mark this glyph, then go to state 'Saw x' State 'Start of line': Class 'x': Mark this glyph, then go to state 'Saw x' State 'Saw x': Class 'acuteaccent': Go to state 'Start of text' after doing this alignment: Marked glyph's point: 19 Current glyph's point: 12 Class 'graveaccent': Go to state 'Start of text' after doing this alignment: Marked glyph's point: 19 Current glyph's point: 4 Class 'x': Mark this glyph, then go to state 'Saw x' Class table: 30: x 96: acuteaccent 97: graveaccent Header information: Horizontal With-stream No variation kerning Process forward """ ct = classtable.ClassTable() for glyph, className in self.classTable.items(): ct[glyph] = oldToNew.get(className, className) self.classTable = ct for stateName in self: stateRow = self[stateName] sr = staterow.StateRow() for className, entryObj in stateRow.items(): sr[oldToNew.get(className, className)] = entryObj self[stateName] = sr
def makeInitialClassTable(self, gVec): """ Creates and returns a ClassTable from the specified vector of tuples. """ ct = classtable.ClassTable() for t in gVec: for g in t: if g is not None and g not in ct: ct[g] = self.nmbf(g) return ct
def analyze_shift_makeClasses(glyphTuples, effectTuples, nmbf): """ Creates a ClassTable based on an analysis of the specified glyphs and effects. ### E = effect.Effect ### e0 = E() ### e1 = E(xShift=-40) ### e2 = E(xShift=25) ### gt = ((15, 39), (28, 50), (56, 84)) ### et = ((e0, e1), (e0, e2), (e0, e1)) ### analyze_shift_makeClasses(gt, et, str).pprint() 15: 15 28: 28 39: like 39 50: 50 56: 56 84: like 39 """ ct = classtable.ClassTable() d = collections.defaultdict(set) for tG, tE in zip(glyphTuples, effectTuples): for g, e in zip(tG, tE): if e: d[g].add((e.xShift, e.yShift)) elif g not in ct: ct[g] = nmbf(g) d = {g: frozenset(s) for g, s in d.items()} dInv = utilities.invertDictFull(d, asSets=True) for eGroup, gSet in dInv.items(): gv = sorted(gSet - {None}) if not gv: continue if len(gv) == 1: ct[gv[0]] = nmbf(gv[0]) else: s = "like %s" % (nmbf(gv[0]), ) for g in gv: ct[g] = s return ct
def _makeCoordExample(): cv = coverage.Coverage(crossStream=False) ct = classtable.ClassTable({30: "x", 96: "acute", 97: "grave"}) pe1 = coordentry.CoordEntry(markedX=200, markedY=1600, currentX=450, currentY=1100) pe2 = coordentry.CoordEntry(markedX=200, markedY=1600, currentX=300, currentY=1000) e1 = entry4.Entry(newState="Start of text") e2 = entry4.Entry(newState="Saw x", mark=True) e3 = entry4.Entry(newState="Start of text", action=pe1) e4 = entry4.Entry(newState="Start of text", action=pe2) row1 = staterow.StateRow({ "End of text": e1, "Out of bounds": e1, "Deleted glyph": e1, "End of line": e1, "x": e2, "acute": e1, "grave": e1 }) row2 = staterow.StateRow({ "End of text": e1, "Out of bounds": e1, "Deleted glyph": e1, "End of line": e1, "x": e2, "acute": e3, "grave": e4 }) return Format4( { "Start of text": row1, "Start of line": row1, "Saw x": row2 }, coverage=cv, classTable=ct)
def fromkern_format1(cls, k1, **kwArgs): """ Creates and returns a new Format1 object using the specified 'kern' Format1 object. """ cv = coverage.Coverage.fromkern_coverage(k1.coverage) ct = classtable.ClassTable(k1.classTable) r = cls({}, coverage=cv, tupleIndex=k1.tupleIndex, classTable=ct) for stateName, stateRowObj in k1.items(): newRow = staterow.StateRow() for className, entryObj in stateRowObj.items(): # The main difference that needs to be taken account of is the # different position of the "reset cross-stream kerning" flag. # In the 'kern' world, that's all the way down in the Value # object, while in the 'kerx' world it's up at the Entry level. reset = False if entryObj.values is not None: v = [] for value in entryObj.values: v.append(int(value)) if value.resetCrossStream: reset = True values = valuetuple.ValueTuple(v) else: values = None newRow[className] = entry.Entry(newState=entryObj.newState, noAdvance=entryObj.noAdvance, push=entryObj.push, reset=reset, values=values) r[stateName] = newRow return r
def _makePointExample(): cv = coverage.Coverage(crossStream=False) ct = classtable.ClassTable({30: "x", 96: "acute", 97: "grave"}) pe1 = pointentry.PointEntry(markedPoint=19, currentPoint=12) pe2 = pointentry.PointEntry(markedPoint=19, currentPoint=4) e1 = entry4.Entry(newState="Start of text") e2 = entry4.Entry(newState="Saw x", mark=True) e3 = entry4.Entry(newState="Start of text", action=pe1) e4 = entry4.Entry(newState="Start of text", action=pe2) row1 = staterow.StateRow({ "End of text": e1, "Out of bounds": e1, "Deleted glyph": e1, "End of line": e1, "x": e2, "acute": e1, "grave": e1 }) row2 = staterow.StateRow({ "End of text": e1, "Out of bounds": e1, "Deleted glyph": e1, "End of line": e1, "x": e2, "acute": e3, "grave": e4 }) return Format4( { "Start of text": row1, "Start of line": row1, "Saw x": row2 }, coverage=cv, classTable=ct)
def renameClasses(self, oldToNew): """ Renames all class names (in each StateRow, and in the class table) as specified by the oldToNew dict, which maps old strings to new ones. If an old name is not present as a key in oldToNew, that class name will not be changed. >>> obj = _testingValues[1].__deepcopy__() >>> oldToNew = {"Space": "Whitespace"} >>> obj.renameClasses(oldToNew) >>> obj.pprint(namer=namer.testingNamer(), onlySignificant=True) State 'Start of text': Class 'Letter': Go to state 'In word' State 'Start of line': Class 'Letter': Go to state 'In word' State 'In word': Class 'Out of bounds': Go to state 'In word' Class 'Deleted glyph': Go to state 'In word' Class 'Letter': Push this glyph, then go to state 'In word' after applying these kerning shifts to the popped glyphs: Pop #1: 682 Class 'Punctuation': Go to state 'In word' Class 'Whitespace': Reset kerning, then go to state 'Start of text' Class table: afii60001: Letter afii60002: Letter xyz13: Punctuation xyz14: Punctuation xyz31: Letter xyz32: Letter xyz4: Whitespace xyz5: Punctuation xyz52: Letter xyz6: Punctuation xyz7: Punctuation Header information: Horizontal Cross-stream No variation kerning Process forward """ ct = classtable.ClassTable() for glyph, className in self.classTable.items(): ct[glyph] = oldToNew.get(className, className) self.classTable = ct for stateName in self: stateRow = self[stateName] sr = staterow.StateRow() for className, entryObj in stateRow.items(): sr[oldToNew.get(className, className)] = entryObj self[stateName] = sr
def _combinedClasses(leftCD, rightCD, leftMap=None): """ Given a pair of classDefs (or similar dict-like objects) which might have some glyphs in common, return a new ClassTable object. This is necessary in AAT because kerning state tables don't have separate left and right class maps. If a leftMap is provided, it will be filled with keys of leftCD class indices pointing to sets of class names involving those indices. >>> L = { ... 6: 1, ... 8: 1, ... 10: 2, ... 14: 2, ... 16: 2, ... 23: 3, ... 29: 3, ... 31: 4, ... 32: 4, ... 34: 4, ... 35: 4} >>> R = { ... 7: 1, ... 9: 1, ... 10: 2, ... 11: 2, ... 12: 2, ... 23: 5, ... 24: 4, ... 25: 3, ... 29: 5, ... 32: 5} >>> M = {} >>> Format1._combinedClasses(L, R, M).pprint() 6: class_L_1 7: class_R_1 8: class_L_1 9: class_R_1 10: class_LR_2_2 11: class_R_2 12: class_R_2 14: class_L_2 16: class_L_2 23: class_LR_3_5 24: class_R_4 25: class_R_3 29: class_LR_3_5 31: class_L_4 32: class_LR_4_5 34: class_L_4 35: class_L_4 >>> for lc, s in sorted(M.items()): print(lc, sorted(s)) 1 ['class_L_1'] 2 ['class_LR_2_2', 'class_L_2'] 3 ['class_LR_3_5'] 4 ['class_LR_4_5', 'class_L_4'] """ leftCD = {k: v for k, v in leftCD.items() if v} rightCD = {k: v for k, v in rightCD.items() if v} allLeftGlyphs = set(leftCD) allRightGlyphs = set(rightCD) allGlyphs = allLeftGlyphs | allRightGlyphs d = {} # combinedName -> glyph set if leftMap is None: leftMap = {} for glyph in allGlyphs: if glyph in allLeftGlyphs and glyph in allRightGlyphs: leftClass = leftCD[glyph] rightClass = rightCD[glyph] name = "class_LR_%d_%d" % (leftClass, rightClass) elif glyph in allLeftGlyphs: leftClass = leftCD[glyph] name = "class_L_%d" % (leftClass, ) else: leftClass = None name = "class_R_%d" % (rightCD[glyph], ) d.setdefault(name, set()).add(glyph) if leftClass is not None: leftMap.setdefault(leftClass, set()).add(name) return classtable.ClassTable((g, cn) for cn, s in d.items() for g in s)
if __debug__: from fontio3.kerx import coverage from fontio3.utilities import namer _srtv = staterow._testingValues _cv = coverage.Coverage(crossStream=True) _ct = classtable.ClassTable({ 3: "Space", 4: "Punctuation", 5: "Punctuation", 6: "Punctuation", 12: "Punctuation", 13: "Punctuation", 30: "Letter", 31: "Letter", 51: "Letter", 96: "Letter", 97: "Letter" }) _testingValues = (Format1(), Format1( { "Start of text": _srtv[1], "Start of line": _srtv[1], "In word": _srtv[2] }, classTable=_ct,
def analyze_attach_makeClasses(glyphTuples, effectTuples, nmbf, hmtxObj): """ Creates and returns the ClassTable for the specified glyphs and effects. ### ed = _fakeEditor() ### E = effect.Effect ### nmbf = (lambda n: "class %d" % (n,)) ### e0 = E() ### e1 = E(xAttach=-34) ### e2 = E(xAttach=-74) ### gt = ((20, 10),) ### et = ((e0, e1),) ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint() 10: class 10 20: class 20 ### gt = ((20, 10), (20, 11)) ### et = ((e0, e1), (e0, e1)) ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint() 10: mark group class 10 11: mark group class 10 20: class 20 ### gt = ((20, 10), (20, 11)) ### et = ((e0, e1), (e0, e2)) ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint() 10: class 10 11: class 11 20: class 20 ### gt = ((20, 10), (21, 11)) ### et = ((e0, e1), (e0, e2)) ### analyze_attach_makeClasses(gt, et, nmbf, ed.hmtx).pprint() 10: mark group class 10 11: mark group class 10 20: base group class 20 21: base group class 20 """ # The coordObjPool is a dict mapping the base glyph's (x, y) to a # corresponding CoordEntry. Note that the attaching glyph's coordinates # will always be (0, 0) in this model! coordObjPool = {} # The commonEffects variable is a dict mapping a base glyph's (x, y) to a # set of mark glyph indices that use that particular effect. # # The baseCollection variable is a dict mapping base glyph indices to a set # of (x, y) values. Two base glyphs can be combined into the same class # only if their sets are equal. commonEffects = collections.defaultdict(set) baseCollection = collections.defaultdict(set) CE = coordentry.CoordEntry allBases, allMarks = set(), set() for gt, et in zip(glyphTuples, effectTuples): if len(gt) > 2: vGT, vET = [], [] for g, e in zip(gt, et): if g is None: if e: raise ValueError("Bad mark-to-ligature data!") else: vGT.append(g) vET.append(e) gt = tuple(vGT) et = tuple(vET) gBase, gMark = gt eBase, eMark = et assert (not eBase) aw = hmtxObj[gBase].advance t = (aw + eMark.xAttach, eMark.yAttach) baseCollection[gBase].add(t) if t not in coordObjPool: co = CE(t[0], t[1], 0, 0) coordObjPool[t] = co co = coordObjPool[t] commonEffects[t].add(gMark) allBases.add(gBase) allMarks.add(gMark) # The inWhich variable is a dict mapping glyphs to sets of all the keys in # commonEffects in whose corresponding sets the glyph appears. inWhich = collections.defaultdict(set) for k, s in commonEffects.items(): for g in s: inWhich[g].add(k) # The inWhichRev variable is a dict with the inverse map of inWhich: that # is to say, mapping frozenset versions of inWhich's values to sets of # glyphs that share those values. inWhichRev = collections.defaultdict(set) for g, keySet in inWhich.items(): inWhichRev[frozenset(keySet)].add(g) ct = classtable.ClassTable() for glyphSet in inWhichRev.values(): # Remove any baseforms (probably won't be any) glyphSet -= allBases if not glyphSet: continue avatar = nmbf(min(glyphSet)) if len(glyphSet) == 1: groupName = avatar else: groupName = "mark group %s" % (avatar, ) for glyph in glyphSet: ct[glyph] = groupName # Now combine (where possible) bases, using the baseCollection dict created # above in the main walker loop. bcRev = collections.defaultdict(set) for g, xyGroup in baseCollection.items(): bcRev[frozenset(xyGroup)].add(g) for xyGroup, glyphSet in bcRev.items(): if len(xyGroup) > 1 or len(glyphSet) < 2: continue avatar = nmbf(min(glyphSet)) groupName = "base group %s" % (avatar, ) for glyph in glyphSet: ct[glyph] = groupName # Finally, any glyphs not yet in the class table at this point are just # given a class to themselves. leftToDo = (allBases | allMarks) - set(ct) for glyph in leftToDo: ct[glyph] = nmbf(glyph) return ct