def _makeGPOS(): from fontio3.GPOS import (GPOS, pairglyphs, pairglyphs_key, pairvalues, value) from fontio3.opentype import (featuredict, featuretable, langsys, langsys_optfeatset, langsysdict, lookup, scriptdict) # Return a GPOS with (15, 32) kerned -18, and (20, 30) kerned +14 v1 = value.Value(xPlacement=-18) v2 = value.Value(xPlacement=14) pv1 = pairvalues.PairValues(second=v1) pv2 = pairvalues.PairValues(second=v2) k1 = pairglyphs_key.Key((15, 32)) k2 = pairglyphs_key.Key((20, 30)) pairObj = pairglyphs.PairGlyphs({k1: pv1, k2: pv2}) lkObj = lookup.Lookup([pairObj]) ftObj = featuretable.FeatureTable([lkObj]) featObj = featuredict.FeatureDict({b'kern0001': ftObj}) optSetObj = langsys_optfeatset.OptFeatSet({b'kern0001'}) lsObj = langsys.LangSys(optionalFeatures=optSetObj) lsdObj = langsysdict.LangSysDict({}, defaultLangSys=lsObj) scptObj = scriptdict.ScriptDict({b'latn': lsdObj}) return GPOS.GPOS(features=featObj, scripts=scptObj)
def _makeTest(): """ The test case here recognizes the glyph sequence 25-80-26, where 25 is the backtrack and 26 the lookahead. The position of glyph 80 is adjusted. """ from fontio3 import hmtx, utilities from fontio3.GPOS import single, value from fontio3.opentype import (lookup, pschainglyph_glyphtuple, pschainglyph_key, pslookupgroup, pslookuprecord) s1 = single.Single({80: value.Value(xPlacement=-25)}) lk1 = lookup.Lookup([s1]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) psg1 = pslookupgroup.PSLookupGroup([psr1]) backPart = pschainglyph_glyphtuple.GlyphTuple([25]) inPart = pschainglyph_glyphtuple.GlyphTuple([80]) lookPart = pschainglyph_glyphtuple.GlyphTuple([26]) key1 = pschainglyph_key.Key([backPart, inPart, lookPart]) r = ChainGlyph({key1: psg1}) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[25] = hmtx.MtxEntry(900, 50) e.hmtx[26] = hmtx.MtxEntry(970, 40) e.hmtx[80] = hmtx.MtxEntry(1020, 55) return r, e
def _makeTest(): """ The test case here recognizes the glyph sequence 1-2-3, and adjusts the positioning of class 2. """ from fontio3 import hmtx, utilities from fontio3.GPOS import single, value from fontio3.opentype import (classdef, lookup, pschainclass_classtuple, pschainclass_key, pslookupgroup, pslookuprecord) v1 = value.Value(xPlacement=-25) v2 = value.Value(xPlacement=-29) s1 = single.Single({80: v1, 81: v2, 82: v1}) lk1 = lookup.Lookup([s1]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) psg = pslookupgroup.PSLookupGroup([psr1]) backPart = pschainclass_classtuple.ClassTuple([1]) inPart = pschainclass_classtuple.ClassTuple([1]) lookPart = pschainclass_classtuple.ClassTuple([1]) key = pschainclass_key.Key([backPart, inPart, lookPart]) cdBack = classdef.ClassDef({25: 1}) cdIn = classdef.ClassDef({80: 1, 81: 1, 82: 1}) cdLook = classdef.ClassDef({26: 1}) r = ChainClass({key: psg}, classDefBacktrack=cdBack, classDefInput=cdIn, classDefLookahead=cdLook) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[25] = hmtx.MtxEntry(900, 50) e.hmtx[26] = hmtx.MtxEntry(970, 40) e.hmtx[80] = hmtx.MtxEntry(1020, 55) e.hmtx[81] = hmtx.MtxEntry(1090, 85) e.hmtx[82] = hmtx.MtxEntry(1050, 70) return r, e
def _makeTest(): """ The test case here recognizes the glyph sequence 25-80-26, and adjusts the positioning of glyphs 25 and 26. """ from fontio3 import hmtx from fontio3.GPOS import single, value from fontio3.opentype import (lookup, pscontextglyph_key, pslookupgroup, pslookuprecord) s1 = single.Single({25: value.Value(xPlacement=-25)}) s2 = single.Single({26: value.Value(xPlacement=25)}) s3 = single.Single({12: value.Value(xPlacement=-15)}) s4 = single.Single({12: value.Value(xPlacement=15)}) lk1 = lookup.Lookup([s1]) lk2 = lookup.Lookup([s2]) lk3 = lookup.Lookup([s3]) lk4 = lookup.Lookup([s4]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) psr2 = pslookuprecord.PSLookupRecord(2, lk2) psr3 = pslookuprecord.PSLookupRecord(0, lk3) psr4 = pslookuprecord.PSLookupRecord(2, lk4) psg1 = pslookupgroup.PSLookupGroup([psr1, psr2]) psg2 = pslookupgroup.PSLookupGroup([psr3, psr4]) key1 = pscontextglyph_key.Key([25, 80, 26], ruleOrder=0) key2 = pscontextglyph_key.Key([12, 15, 12], ruleOrder=1) r = ContextGlyph({key1: psg1, key2: psg2}) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[12] = hmtx.MtxEntry(700, 50) e.hmtx[15] = hmtx.MtxEntry(690, 50) e.hmtx[25] = hmtx.MtxEntry(900, 50) e.hmtx[26] = hmtx.MtxEntry(970, 40) e.hmtx[80] = hmtx.MtxEntry(1020, 55) return r, e
def _makeTest(): from fontio3 import hmtx, utilities from fontio3.GPOS import single, value from fontio3.opentype import (coverageset, lookup, pschaincoverage_coveragetuple, pschaincoverage_key, pslookupgroup, pslookuprecord) v1 = value.Value(xPlacement=-25) v2 = value.Value(xPlacement=-29) s1 = single.Single({80: v1, 81: v2, 82: v1}) lk1 = lookup.Lookup([s1]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) psg = pslookupgroup.PSLookupGroup([psr1]) cs1 = coverageset.CoverageSet([20, 22]) cs2 = coverageset.CoverageSet([23]) cs3 = coverageset.CoverageSet([80, 81, 82]) cs4 = coverageset.CoverageSet([22]) cs5 = coverageset.CoverageSet([20, 40]) ct1 = pschaincoverage_coveragetuple.CoverageTuple([cs1, cs2]) ct2 = pschaincoverage_coveragetuple.CoverageTuple([cs3]) ct3 = pschaincoverage_coveragetuple.CoverageTuple([cs4, cs5]) key = pschaincoverage_key.Key([ct1, ct2, ct3]) r = ChainCoverage({key: psg}) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[20] = hmtx.MtxEntry(910, 42) e.hmtx[22] = hmtx.MtxEntry(900, 50) e.hmtx[23] = hmtx.MtxEntry(970, 40) e.hmtx[40] = hmtx.MtxEntry(890, 8) e.hmtx[80] = hmtx.MtxEntry(1020, 55) e.hmtx[81] = hmtx.MtxEntry(1090, 85) e.hmtx[82] = hmtx.MtxEntry(1020, 55) return r, e
def _makeTest(): from fontio3 import hmtx, utilities from fontio3.GPOS import single, value from fontio3.opentype import ( coverageset, lookup, pscontextcoverage_key, pslookupgroup, pslookuprecord) v1 = value.Value(xPlacement=-25) v2 = value.Value(xPlacement=-29) s1 = single.Single({20: v1, 25: v2}) lk1 = lookup.Lookup([s1]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) v1 = value.Value(xPlacement=-31) v2 = value.Value(xPlacement=-19) s2 = single.Single({25: v1, 26: v2}) lk2 = lookup.Lookup([s2]) psr2 = pslookuprecord.PSLookupRecord(2, lk2) psg = pslookupgroup.PSLookupGroup([psr1, psr2]) key = pscontextcoverage_key.Key([ coverageset.CoverageSet([20, 25]), coverageset.CoverageSet([81, 82]), coverageset.CoverageSet([25, 26])]) r = ContextCoverage({key: psg}) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[20] = hmtx.MtxEntry(910, 42) e.hmtx[25] = hmtx.MtxEntry(900, 50) e.hmtx[26] = hmtx.MtxEntry(970, 40) e.hmtx[80] = hmtx.MtxEntry(1020, 55) e.hmtx[81] = hmtx.MtxEntry(1090, 85) return r, e
def _makeTest(): """ The test case here recognizes the glyph sequence 1-2-3, and adjusts the positioning of classes 1 and 3. """ from fontio3 import hmtx, utilities from fontio3.GPOS import single, value from fontio3.opentype import ( classdef, lookup, pscontextclass_key, pslookupgroup, pslookuprecord) v1 = value.Value(xPlacement=-25) v2 = value.Value(xPlacement=-29) s1 = single.Single({20: v1, 25: v2}) lk1 = lookup.Lookup([s1]) psr1 = pslookuprecord.PSLookupRecord(0, lk1) s2 = single.Single({26: value.Value(xPlacement=25)}) lk2 = lookup.Lookup([s2]) psr2 = pslookuprecord.PSLookupRecord(2, lk2) psg = pslookupgroup.PSLookupGroup([psr1, psr2]) key = pscontextclass_key.Key([1, 2, 3]) cd = classdef.ClassDef({20: 1, 25: 1, 80: 2, 81: 2, 26: 3}) r = ContextClass({key: psg}, classDef=cd) e = utilities.fakeEditor(0x10000) e.hmtx = hmtx.Hmtx() e.hmtx[20] = hmtx.MtxEntry(910, 42) e.hmtx[25] = hmtx.MtxEntry(900, 50) e.hmtx[26] = hmtx.MtxEntry(970, 40) e.hmtx[80] = hmtx.MtxEntry(1020, 55) e.hmtx[81] = hmtx.MtxEntry(1090, 85) return r, e
def buildBinary(self, w, **kwArgs): """ Adds the binary data to the specified LinkedWriter. The following keyword arguments are supported: devicePool A dict mapping device IDs to the Device objects. This is optional; if not specified, a local pool will be used. The devices will only be actually added if devicePool is not specified; if it is specified, a higher-level client is responsible for this. posBase A stake representing the base from which device offsets will be computed. This is required. valueFormatFirst The mask representing which values are to be included for the second Value. This is optional; if not present the getMasks() mask is used instead. valueFormatSecond The mask representing which values are to be included for the second Value. This is optional; if not present the getMask() mask is used instead. >>> d = { ... 'posBase': "test stake", ... 'valueFormatFirst': 255, ... 'valueFormatSecond': 255} >>> w = writer.LinkedWriter() >>> w.stakeCurrentWithValue("test stake") >>> w.add("l", -1) >>> _testingValues[0].buildBinary(w, **d) >>> utilities.hexdump(w.binaryString()) 0 | FFFF FFFF 0000 0000 0000 0000 0000 0000 |................| 10 | 0000 0000 FFF6 0000 0000 0000 0000 0000 |................| 20 | 0000 0000 |.... | >>> w.reset() >>> w.stakeCurrentWithValue("test stake") >>> w.add("l", -1) >>> _testingValues[1].buildBinary(w, **d) >>> utilities.hexdump(w.binaryString()) 0 | FFFF FFFF 0000 0000 0000 0000 0000 0000 |................| 10 | 0000 0024 0000 0000 0000 0000 0000 0000 |...$............| 20 | 0000 0000 000C 0012 0001 8C04 |............ | >>> w.reset() >>> w.stakeCurrentWithValue("test stake") >>> w.add("l", -1) >>> _testingValues[2].buildBinary(w, **d) >>> utilities.hexdump(w.binaryString()) 0 | FFFF FFFF 001E 0000 0000 0000 0000 0000 |................| 10 | 0000 0030 0000 0000 0000 0000 0030 0024 |...0.........0.$| 20 | 0000 0000 000C 0014 0002 BDF0 0020 3000 |............. 0.| 30 | 000C 0012 0001 8C04 |........ | """ valueFormatFirst = kwArgs.pop('valueFormatFirst', None) valueFormatSecond = kwArgs.pop('valueFormatSecond', None) kwArgs.pop('valueFormat', None) if valueFormatFirst is None or valueFormatSecond is None: m1, m2 = self.getMasks() if valueFormatFirst is None: valueFormatFirst = m1 if valueFormatSecond is None: valueFormatSecond = m2 devicePool = kwArgs.pop('devicePool', None) if devicePool is None: devicePool = {} doLocal = True else: doLocal = False if valueFormatFirst: obj = self.first or value.Value() obj.buildBinary(w, valueFormat=valueFormatFirst, devicePool=devicePool, **kwArgs) if valueFormatSecond: obj = self.second or value.Value() obj.buildBinary(w, valueFormat=valueFormatSecond, devicePool=devicePool, **kwArgs) if doLocal: # we decorate-sort to ensure a repeatable, canonical ordering it = sorted((sorted(obj.asImmutable()[1]), obj, stake) for obj, stake in devicePool.values()) for t in it: t[1].buildBinary(w, stakeValue=t[2], **kwArgs)
def fromValidatedFontWorkerSource(cls, fws, **kwArgs): """ Creates and returns a new PairClasses object from the specified FontWorkerSource, with validation of the source data. >>> logger = utilities.makeDoctestLogger("FW_test") >>> pc = PairClasses.fromValidatedFontWorkerSource(_test_FW_fws2, namer=_test_FW_namer, logger=logger) FW_test.pairclasses - ERROR - line 13 -- unexpected token: foo >>> pc.pprint() (First class 1, Second class 2): First adjustment: FUnit adjustment to origin's x-coordinate: -123 (First class 2, Second class 1): Second adjustment: FUnit adjustment to origin's x-coordinate: -456 Class definition table for first glyph: 2: 1 3: 2 Class definition table for second glyph: 23: 1 29: 2 """ terminalStrings = ('subtable end', 'lookup end') startingLineNumber=fws.lineNumber iskernset = kwArgs.get('iskernset', False) logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("pairclasses") fvfws = classdef.ClassDef.fromValidatedFontWorkerSource PV = pairvalues.PairValues V = value.Value tokenSet = frozenset({ 'left x advance', 'left x placement', 'left y advance', 'left y placement', 'right x advance', 'right x placement', 'right y advance', 'right y placement'}) # place-holders classDef1 = classdef.ClassDef() classDef2 = classdef.ClassDef() pairDict = defaultdict(lambda: [None, None]) for line in fws: if line.lower().strip() in terminalStrings: if iskernset and line.lower() == 'subtable end': continue else: npd, nc1, nc2 = _reindexed( pairDict, classDef1, classDef2, logger = logger) return cls(npd, classDef1=nc1, classDef2=nc2) if len(line) > 0: tokens = [x.strip() for x in line.split('\t')] if tokens[0].lower() == 'firstclass definition begin': classDef1 = fvfws(fws, logger=logger, **kwArgs) elif tokens[0].lower() == 'secondclass definition begin': classDef2 = fvfws(fws, logger=logger, **kwArgs) elif tokens[0].lower() in tokenSet: class1 = int(tokens[1]) class2 = int(tokens[2]) key = (class1, class2) val= int(tokens[3]) if val != 0: pval = pairDict[key] if tokens[0].lower() == 'left x advance': if pval[0] and pval[0].xAdvance: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[0] is None: pval[0] = value.Value() pval[0].xAdvance=val elif tokens[0].lower() == 'right x advance': if pval[1] and pval[1].xAdvance: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[1] is None: pval[1] = value.Value() pval[1].xAdvance=val elif tokens[0].lower() == 'left x placement': if pval[0] and pval[0].xPlacement: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[0] is None: pval[0] = value.Value() pval[0].xPlacement = val elif tokens[0].lower() == 'right x placement': if pval[1] and pval[1].xPlacement: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[1] is None: pval[1] = value.Value() pval[1].xPlacement = val elif tokens[0].lower() == 'left y advance': if pval[0] and pval[0].yAdvance: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[0] is None: pval[0] = value.Value() pval[0].yAdvance=val elif tokens[0].lower() == 'right y advance': if pval[1] and pval[1].yAdvance: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[1] is None: pval[1] = value.Value() pval[1].yAdvance=val elif tokens[0].lower() == 'left y placement': if pval[0] and pval[0].yPlacement: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[0] is None: pval[0] = value.Value() pval[0].yPlacement=val elif tokens[0].lower() == 'right y placement': if pval[1] and pval[1].yPlacement: logger.warning(( 'Vxxxx', (fws.lineNumber, tokens[0], tokens[1], tokens[2]), "line %d -- ignoring duplicate %s for class " "pair %s,%s")) else: if pval[1] is None: pval[1] = value.Value() pval[1].yPlacement=val else: logger.error(( 'V0960', (fws.lineNumber, tokens[0]), 'line %d -- unexpected token: %s')) logger.error(( 'V0958', (startingLineNumber, "/".join(terminalStrings)), 'line %d -- did not find matching \'%s\'')) if pairDict and classDef1 and classDef2: r = cls(pairDict, classDef1=classDef1, classDef2=classDef2) else: logger.error(( 'Vxxxx', (), "Incomplete or invalid lookup data for pair classes.")) r = None return r
def buildBinary(self, w, **kwArgs): """ Adds the binary data for the PairClasses object to the specified LinkedWriter. >>> utilities.hexdump(_testingValues[0].binaryString()) 0 | 0002 004C 0081 0031 0058 006E 0003 0002 |...L...1.X.n....| 10 | 0000 0000 0000 0000 0000 0000 0000 0000 |................| 20 | 0000 0000 0000 0000 0000 0000 0000 0000 |................| 30 | 0000 FFF6 0000 0000 0000 0084 0000 0000 |................| 40 | 0000 001E 0084 0000 0084 0078 0001 0004 |...........x....| 50 | 0005 0006 0007 000F 0002 0003 0005 0006 |................| 60 | 0001 0007 0007 0002 000F 000F 0001 0002 |................| 70 | 0001 0014 0016 0001 000C 0014 0002 BDF0 |................| 80 | 0020 3000 000C 0012 0001 8C04 |. 0......... | """ if 'stakeValue' in kwArgs: stakeValue = kwArgs.pop('stakeValue') w.stakeCurrentWithValue(stakeValue) else: stakeValue = w.stakeCurrent() w.add("H", 2) # format 2 s = set(self.classDef1) | self.coverageExtras covTable = coverage.Coverage.fromglyphset(s) covStake = w.getNewStake() w.addUnresolvedOffset("H", stakeValue, covStake) vf1, vf2 = self.getMasks() w.add("HH", vf1, vf2) cd1Stake = w.getNewStake() cd2Stake = w.getNewStake() w.addUnresolvedOffset("H", stakeValue, cd1Stake) w.addUnresolvedOffset("H", stakeValue, cd2Stake) count1 = 1 + utilities.safeMax(self.classDef1.values()) count2 = 1 + utilities.safeMax(self.classDef2.values()) w.add("HH", count1, count2) emptyPV = pairvalues.PairValues(value.Value(), value.Value()) devicePool = {} Key = pairclasses_key.Key for c1 in range(count1): for c2 in range(count2): obj = self.get(Key([c1, c2]), emptyPV) obj.buildBinary( w, devicePool = devicePool, posBase = stakeValue, valueFormatFirst = vf1, valueFormatSecond = vf2, **kwArgs) # Now add the deferred objects covTable.buildBinary(w, stakeValue=covStake) self.classDef1.buildBinary(w, stakeValue=cd1Stake) self.classDef2.buildBinary(w, stakeValue=cd2Stake) it = sorted( (sorted(obj.asImmutable()[1]), obj, stake) for obj, stake in devicePool.values()) for t in it: t[1].buildBinary(w, stakeValue=t[2], **kwArgs)