def analyze_attach(glyphTuples, effectTuples, nmbf, hmtxObj): """ """ ct = analyze_attach_makeClasses(glyphTuples, effectTuples, nmbf, hmtxObj) r = format4.Format4({}, classTable=ct, coverage=coverage.Coverage()) Entry = entry4.Entry SR = staterow.StateRow r['Start of text'] = SR() 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 baseRow = r['Start of text'] baseClass = ct[gBase] secondState = "Saw %s" % (nmbf(gBase), ) if baseClass not in baseRow: baseRow[baseClass] = Entry(mark=True, newState=secondState) if secondState not in r: r[secondState] = SR() markRow = r[secondState] markClass = ct[gMark] if markClass not in markRow: aw = hmtxObj[gBase].advance bx = aw + eMark.xAttach by = eMark.yAttach action = coordentry.CoordEntry(bx, by, 0, 0) markRow[markClass] = Entry(action=action, newState='Start of text') r.normalize() return [r]
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 analyze_kern_with(glyphTuples, effectTuples, agf, vert): """ """ r = format0.Format0() for tG, tE in zip(glyphTuples, effectTuples): key = glyphpair.GlyphPair(abs(n) for n in tG) r[key] = agf(tE[1]) rSize = len(r.binaryString()) f2 = format2.Format2.fromformat0(r) if len(f2.binaryString()) < rSize: r = f2 r.coverage = coverage.Coverage(crossStream=False, vertical=vert) 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)
_testingValues = (Format3(), Format3( { ClassPair([1, 1]): -25, ClassPair([1, 2]): -10, ClassPair([2, 1]): 15 }, leftClassDef=classdef.ClassDef({ 15: 1, 25: 1, 35: 2 }), rightClassDef=classdef.ClassDef({ 9: 1, 12: 1, 15: 1, 40: 2 }), coverage=coverage.Coverage())) def _test(): import doctest doctest.testmod() if __name__ == "__main__": if __debug__: _test()
def fromgpospairs(cls, pgObj, **kwArgs): """ Creates and returns a new Format0 object from the specified PairGlyphs object. If there are any keys whose associated Values cannot be converted, they will be shown in a ValueError raised by this method. The following keyword arguments are used: coverage A Coverage object. If not provided, a default Coverage will be created and used. tupleIndex The variations tuple index. Default is 0. """ if 'coverage' not in kwArgs: kwArgs['coverage'] = coverage.Coverage() if 'tupleIndex' not in kwArgs: kwArgs['tupleIndex'] = 0 r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) couldNotProcess = set() horizontal = not kwArgs['coverage'].vertical GP = glyphpair.GlyphPair for gposKey, gposValue in pgObj.items(): fmt0Key = GP(gposKey) gposFirst = gposValue.first gposMask1 = (0 if gposFirst is None else gposFirst.getMask()) gposSecond = gposValue.second gposMask2 = (0 if gposSecond is None else gposSecond.getMask()) delta = 0 # We only process effects that move the glyphs internally. That # means any Values that use anything other than an advance shift # on the first glyph or an origin shift on the second are not # processed, and their keys will be added to couldNotProcess. if horizontal: if gposMask1 == 4: # xAdvance delta += gposFirst.xAdvance elif gposMask1: couldNotProcess.add(gposKey) continue if gposMask2 == 1: # xPlacement delta += gposSecond.xPlacement elif gposMask2: couldNotProcess.add(gposKey) continue else: if gposMask1 == 8: # yAdvance delta += gposFirst.yAdvance elif gposMask1: couldNotProcess.add(gposKey) continue if gposMask2 == 2: # yPlacement delta += gposSecond.yPlacement elif gposMask2: couldNotProcess.add(gposKey) continue if delta: r[fmt0Key] = delta if couldNotProcess: v = sorted(couldNotProcess) raise ValueError("Could not process these GPOS keys: %s" % (v,)) return r
def fromformat2(cls, fmt2Obj, **kwArgs): """ Creates and returns a new Format1 object from the specified 'kerx' Format2 object. """ leftMap = {} unionCT = cls._combinedClasses(fmt2Obj.leftClassDef, fmt2Obj.rightClassDef, leftMap) r = cls({}, classTable=unionCT, coverage=kwArgs.pop('coverage', coverage.Coverage()), tupleIndex=kwArgs.pop('tupleIndex', 0)) addedClassNames = tuple(sorted(set(unionCT.values()))) classNames = namestash.fixedClassNames + addedClassNames addedStateNames = tuple( "Saw_L_%d" % (n, ) for n in sorted(set(fmt2Obj.leftClassDef.values()))) stateNames = namestash.fixedStateNames + addedStateNames E = entry.Entry defaultEntry = E() for stateName in stateNames: newRow = staterow.StateRow() for className in classNames: if className == 'Deleted glyph': newRow[className] = E(newState=stateName) else: newRow[className] = defaultEntry r[stateName] = newRow for cp, dist in fmt2Obj.items(): if not dist: continue # For the SOT and SOL states, each cell belonging to either # class_L_cp[0] or class_LR_cp[0]* gets a link to the Saw_L_cp[0] # state. f2LeftClass, f2RightClass = cp firstCell = E(newState="Saw_L_%d" % (f2LeftClass, )) match1 = "class_L_%d" % (f2LeftClass, ) match2 = "class_LR_%d_" % (f2LeftClass, ) for className in classNames[4:]: if className == match1 or className.startswith(match2): for sn in ("Start of text", "Start of line"): if r[sn][className] == defaultEntry: r[sn][className] = firstCell match3 = "class_R_%d" % (f2RightClass, ) match4 = "class_LR_" match5 = "_%d" % (f2RightClass, ) for className in classNames[4:]: isLR = (className.startswith(match4) and className.endswith(match5)) if (className == match3) or isLR: sn = "Saw_L_%d" % (f2LeftClass, ) assert r[sn][className] == defaultEntry val = valuetuple.ValueTuple([dist]) if className == match3: r[sn][className] = E(newState="Start of text", push=True, values=val) else: r[sn][className] = E(newState="Saw_L_%s" % (className.split('_')[2], ), push=True, values=val) # Now that the nonzero kerning entries are there, go through and fill # in the links for any cells which are still empty but have a LR class. for stateName in stateNames[2:]: for className in classNames[4:]: if className.startswith("class_R"): continue cell = r[stateName][className] if cell != defaultEntry: continue r[stateName][className] = E(newState="Saw_L_%s" % (className.split('_')[2], )) return r
# # Test code # if 0: def __________________(): pass 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" })
def analyze_shift_with(glyphTuples, effectTuples, nmbf, agf): """ Do non-kerning generic with-stream shifts. ### E = effect.Effect ### e0 = E() ### e1 = E(xShift=-40) ### e2 = E(xShift=25) ### gt = ((2, 4, 6, 8),) ### et = ((e0, e1, e0, e2),) ### r = analyze_shift_with(gt, et, str, operator.attrgetter('xShift')) ### len(r) 1 ### r[0].pprint(onlySignificant=True) State 'Start of text': Class '2': Go to state 'Saw 2' State 'Start of line': Class '2': Go to state 'Saw 2' State 'Saw 2': Class 'Deleted glyph': Go to state 'Saw 2' Class '2': Go to state 'Saw 2' Class '4': Push this glyph, then go to state 'Saw 2_4' State 'Saw 2_4': Class 'Deleted glyph': Go to state 'Saw 2_4' Class '2': Go to state 'Saw 2' Class '6': Go to state 'Saw 2_4_6' State 'Saw 2_4_6': Class 'Deleted glyph': Go to state 'Saw 2_4_6' Class '2': Go to state 'Saw 2' Class '8': Push this glyph, then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Pop #2: -40 Class table: 2: 2 4: 4 6: 6 8: 8 Header information: Horizontal With-stream No variation kerning ### gt = ((2, 4, 6, 8, None),) ### et = ((e0, e0, e1, e0, e2),) ### r = analyze_shift_with(gt, et, str, operator.attrgetter('xShift')) ### len(r) 1 ### r[0].pprint(onlySignificant=True) State 'Start of text': Class '2': Go to state 'Saw 2' State 'Start of line': Class '2': Go to state 'Saw 2' State 'Saw 2': Class 'Deleted glyph': Go to state 'Saw 2' Class '2': Go to state 'Saw 2' Class '4': Go to state 'Saw 2_4' State 'Saw 2_4': Class 'Deleted glyph': Go to state 'Saw 2_4' Class '2': Go to state 'Saw 2' Class '6': Push this glyph, then go to state 'Saw 2_4_6' State 'Saw 2_4_6': Class 'Deleted glyph': Go to state 'Saw 2_4_6' Class '2': Go to state 'Saw 2' Class '8': Go to state 'Shift 25' State 'Shift 25': Class 'Out of bounds': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class 'Deleted glyph': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class '2': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class '4': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class '6': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class '8': Push this glyph (without advancing), then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: 25 Class table: 2: 2 4: 4 6: 6 8: 8 Header information: Horizontal With-stream No variation kerning """ ct = analyze_shift_makeClasses(glyphTuples, effectTuples, nmbf) r = format1.Format1({}, classTable=ct, coverage=coverage.Coverage()) SR = staterow.StateRow Entry = entry.Entry specialShifts = set() for tG, tE in zip(glyphTuples, effectTuples): currState = 'Start of text' vtv = [] isSpecial = 0 if tG[-1] is None: tG = tG[:-1] if agf(tE[-1]): isSpecial = agf(tE[-1]) specialShifts.add(isSpecial) tE = tE[:-1] for i, (g, e) in enumerate(zip(tG, tE)): currClass = ct[g] if i == len(tG) - 1: if isSpecial: nextState = "Shift %d" % (isSpecial, ) else: nextState = 'Start of text' elif i: nextState = '%s_%s' % (currState, currClass) else: nextState = 'Saw %s' % (currClass, ) if currState not in r: r[currState] = SR() thisRow = r[currState] if currClass not in thisRow: if not e: thisRow[currClass] = Entry(newState=nextState) elif i < len(tG) - 1: thisRow[currClass] = Entry(newState=nextState, push=True) vtv.append(agf(e)) else: thisRow[currClass] = Entry(push=True, values=valuetuple.ValueTuple( reversed(vtv + [agf(e)])), newState=nextState) break currState = nextState # Now add the special shifts (if any) r.normalize() allClasses = set(r['Start of text']) ignore = {'End of text', 'End of line'} for specialShift in sorted(specialShifts): cell = Entry(push=True, newState='Start of text', noAdvance=True, values=valuetuple.ValueTuple([specialShift])) currState = "Shift %d" % (specialShift, ) r[currState] = SR() for c in allClasses: r[currState][c] = (Entry() if c in ignore else cell) return [r]
def analyze_shift_cross(glyphTuples, effectTuples, nmbf, agf): """ Do non-kerning generic cross-stream shifts. ### E = effect.Effect ### e0 = E() ### e1 = E(yShift=250) ### e2 = E(yShift=120) ### gt = ((2, 4, 6, 8),) ### et = ((e0, e1, e2, e0),) ### r = analyze_shift_cross(gt, et, str, operator.attrgetter('yShift')) ### len(r) 1 ### r[0].pprint(onlySignificant=True) State 'Start of text': Class '2': Go to state 'Saw 2' State 'Start of line': Class '2': Go to state 'Saw 2' State 'Saw 2': Class 'Deleted glyph': Go to state 'Saw 2' Class '2': Go to state 'Saw 2' Class '4': Push this glyph, then go to state 'Saw 2_4' State 'Saw 2_4': Class 'Deleted glyph': Go to state 'Saw 2_4' Class '2': Go to state 'Saw 2' Class '6': Push this glyph, then go to state 'Saw 2_4_6' State 'Saw 2_4_6': Class 'Deleted glyph': Go to state 'Saw 2_4_6' Class '2': Go to state 'Saw 2' Class '8': Reset kerning, then go to state 'Start of text' after applying these kerning shifts to the popped glyphs: Pop #1: -130 Pop #2: 250 Class table: 2: 2 4: 4 6: 6 8: 8 Header information: Horizontal Cross-stream No variation kerning """ ct = analyze_shift_makeClasses(glyphTuples, effectTuples, nmbf) r = format1.Format1({}, classTable=ct, coverage=coverage.Coverage(crossStream=True)) SR = staterow.StateRow Entry = entry.Entry specialShifts = set() for tG, tE in zip(glyphTuples, effectTuples): currState = 'Start of text' vtv = [] runningShift = 0 for i, (g, e) in enumerate(zip(tG, tE)): currClass = ct[g] if i == len(tG) - 1: nextState = 'Start of text' elif i: nextState = '%s_%s' % (currState, currClass) else: nextState = 'Saw %s' % (currClass, ) if currState not in r: r[currState] = SR() thisRow = r[currState] d = {'newState': nextState} if currClass not in thisRow: if not e: if runningShift: d['reset'] = True runningShift = 0 else: delta = agf(e) - runningShift if delta: vtv.append(delta) runningShift = agf(e) d['push'] = True if i == len(tG) - 1: d['values'] = valuetuple.ValueTuple(reversed(vtv)) thisRow[currClass] = Entry(**d) currState = nextState r.normalize() return [r]