def fromwalker(cls, w, **kwArgs): """ Creates and returns a Format0 object from the specified walker, which should start at the subtable header. >>> obj = _testingValues[1] >>> obj == Format0.frombytes( ... obj.binaryString(), ... coverage = coverage_v1.Coverage()) True """ nPairs = w.unpack("H6x") r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) fw = glyphpair.GlyphPair.fromwalker while nPairs: key = fw(w) r[key] = w.unpack("h") nPairs -= 1 if w.length() >= 6: # last subtable might just zero-pad, so check this sentinel = w.unpack("3h", advance=False) if sentinel == (-1, -1, 0): w.skip(6) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Rearrangement object from the specified walker, which must start at the beginning (numClasses) of the state table. >>> obj = _testingValues[0] >>> obj == Rearrangement.frombytes( ... obj.binaryString(), ... coverage = obj.coverage, ... maskValue = 0x04000000) True """ numClasses, oCT, oSA, oET = w.unpack("4H") wCT, wSA, wET = stutils.offsetsToSubWalkers(w.subWalker(0), oCT, oSA, oET) rawEntries = wET.unpackRest("2H") maxOffset = max(offset for offset, flags in rawEntries) numStates = 1 + (maxOffset - oSA) // numClasses nsObj = namestash.NameStash.readormake(w, (oCT, oSA, oET), numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromwalker(wCT, classNames=classNames) rawStateArray = wSA.group("B" * numClasses, numStates) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) S = staterow_rearrangement.StateRow E = entry_rearrangement.Entry V = verbs_rearrangement.Verb for stateIndex, rawState in enumerate(rawStateArray): thisRow = S() for classIndex, entryIndex in enumerate(rawState): newStateOffset, flags = rawEntries[entryIndex] thisRow[classNames[classIndex]] = E( markFirst=bool(flags & 0x8000), markLast=bool(flags & 0x2000), newState=stateNames[(newStateOffset - oSA) // numClasses], noAdvance=bool(flags & 0x4000), verb=V(flags & 0x000F)) r[stateNames[stateIndex]] = thisRow return r
def fromgsubsingle(cls, singleObj, **kwArgs): """ Creates and returns a new Noncontextual object from the specified GSUB Single object. """ return cls(singleObj, **utilities.filterKWArgs(cls, kwArgs))
def fromformat3(cls, format3Obj): """ Creates a Format2 object from the specified Format3 object. Conversions in this direction always work, since Format2 objects do not have the count limits that Format3 objects have. """ return cls(dict(format3Obj), **utilities.filterKWArgs(cls, format3Obj.__dict__))
def fromformat2(cls, format2Obj): """ Creates a Format3 object from the specified Format2 object. Note this is simply a pass-through; checks on count limits are not made until the binary version is generated. """ return cls(dict(format2Obj), **utilities.filterKWArgs(cls, format2Obj.__dict__))
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Format1 object from the specified walker. >>> d = {'coverage': _testingValues[1].coverage.__copy__()} >>> obj = _testingValues[1] >>> obj == Format1.frombytes(obj.binaryString(), **d) True """ numClasses, oCT, oSA, oET, oVT = w.unpack("5L") wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers( w.subWalker(0), oCT, oSA, oET, oVT) wETCopy = wET.subWalker(0, relative=True) v = wETCopy.unpackRest("3H", strict=False) numStates = 1 + utilities.safeMax(x[0] for x in v) numEntries = len(v) nsObj = namestash.NameStash.readormake(w, (oCT, oSA, oET, oVT), numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromwalker(wCT, classNames=classNames) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) # build value table valueDict = {} fw = valuetuple.ValueTuple.fromwalker index = 0 while wVT.stillGoing(): valueDict[index] = fw(wVT) index += 1 # build entry table fw = entry.Entry.fromwalker d = {'stateNames': stateNames, 'valueDict': valueDict} entries = [fw(wET, **d) for i in range(numEntries)] # finally, build state table fw = staterow.StateRow.fromwalker d = {'classNames': classNames, 'entries': entries} for stateName in stateNames: r[stateName] = fw(wSA, **d) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Noncontextual object from the specified walker. """ r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) r.update(lookup.Lookup.fromwalker(w, **kwArgs)) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Contextual object from the specified walker, which must start at the beginning (numClasses) of the state table. >>> obj = _testingValues[0] >>> obj == Contextual.frombytes( ... obj.binaryString(), coverage=obj.coverage, maskValue=0x10000000) True """ analyzer = contextanalyzer.Analyzer(w.subWalker(0, relative=True)) markAnalysis, currAnalysis = analyzer.analyze() numClasses, oCT, oSA, oET, oST = w.unpack("5H") # in the following, note that limits are correctly set for all walkers wCT, wSA, wET, wST = stutils.offsetsToSubWalkers( w.subWalker(0), oCT, oSA, oET, oST) numStates = analyzer.numStates nsObj = namestash.NameStash.readormake(w, (oCT, oSA, oET, oST), numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromwalker(wCT, classNames=classNames) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) entryTable = analyzer.entryTable Entry = entry_contextual.Entry GlyphDict = glyphdict.GlyphDict StateRow = staterow_contextual.StateRow for stateIndex, rawState in enumerate(analyzer.stateArray): newRow = staterow_contextual.StateRow() for classIndex, entryIndex in enumerate(rawState): newStateOffset, flags = entryTable[entryIndex][0:2] newStateIndex = (newStateOffset - oSA) // numClasses newEntry = Entry(newState=stateNames[newStateIndex], mark=bool(flags & 0x8000), noAdvance=bool(flags & 0x4000), markSubstitutionDict=GlyphDict( markAnalysis.get(entryIndex, {})), currSubstitutionDict=GlyphDict( currAnalysis.get(entryIndex, {}))) newRow[classNames[classIndex]] = newEntry r[stateNames[stateIndex]] = newRow return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a Format0 object from the specified walker, which should start at the subtable header. The following keyword arguments are suggested (if they are not present, the default values for coverage and tupleIndex will be used, which won't usually be what's wanted). coverage A Coverage object. tupleIndex The variations tuple index. NOTE: see the docstring for the Format0 class for a discussion of record formats. >>> obj = _testingValues[1] >>> obj == Format0.frombytes( ... obj.binaryString(), ... coverage = coverage.Coverage()) True """ count = w.unpack("L12x") r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) if count: data = w.group("2Hh", count) GP = glyphpair.GlyphPair dups = set() for *t, value in data: key = GP(t) if key in r: dups.add(key) # We don't put an "else" here to allow the last present entry to # "win", in the case where there are duplicate keys. r[key] = value if dups: warnings.warn( "Duplicate keys in format0 data: %s" % (sorted(dups),)) # Skip the sentinel, if one is present (it's ambiguous) if w.length() >= 6: sentinel = w.unpack("3h", advance=False) if sentinel == (-1, -1, 0): w.skip(6) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Sbit object from the specified walker (which will be for a dat table). There are two required keyword arguments: flavor One of 'bloc', 'EBDT', or 'CBLC' to indicate the type wLoc A walker for the corresponding loc table (bloc for bdat, EBLC for EBDT, CBLC for CBDT). """ flavor = kwArgs.get('flavor') version = w.unpack("L", advance=False) wLoc = kwArgs['wLoc'] wLocWalk = wLoc.subWalker(0) locversion = wLocWalk.unpack("L") expectedversion = FLAVOR_VERSIONS.get(flavor, 0) if version != expectedversion: if flavor == 'CBDT' and version == 0x00030000: # allow this, since the spec currently calls for v3 for CBDT pass else: # but no other flavor-version mismatches. raise ValueError("Unknown %s version: 0x%08X " "(expected 0x%08X)" % (flavor, version, expectedversion)) if locversion != expectedversion: if flavor == 'CBDT' and locversion == 0x00030000: # allow this, since the spec currently calls for v3 for CBDT pass else: # but no other flavor-version mismatches. raise ValueError("Unknown location table version: " "0x%08X (expected 0x%08X)" % (locversion, expectedversion)) count = wLocWalk.unpack("L") r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) while count: stk = strike.Strike.fromwalker(wLocWalk, wDat=w, **kwArgs) key = (stk.ppemX, stk.ppemY, stk.bitDepth) r[key] = stk count -= 1 return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Noncontextual object from the specified walker, doing data validation. """ r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) d = lookup.Lookup.fromvalidatedwalker(w, **kwArgs) if d is None: return None r.update(d) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a Format3 object from the specified walker, which should start at the subtable header. The following keyword arguments are suggested (if they are not present, the default values for coverage and tupleIndex will be used, which won't usually be what's wanted): coverage A Coverage object. tupleIndex The variations tuple index. >>> obj = _testingValues[1] >>> obj == Format3.frombytes(obj.binaryString(), coverage=obj.coverage) True """ glyphCount, kCount, lCount, rCount = w.unpack("4H2x") # flags not used kValues = w.group("h", kCount) lClasses = w.group("H", glyphCount) rClasses = w.group("H", glyphCount) indices = w.group("H", lCount * rCount) # Note the assumption implicit in the following two lines of code: the # [0] element of the kernValue array has a numeric value of zero FUnits # (i.e. a non-kerning pair). This assumption is not documented in # Apple's 'kerx' table documentation. leftCD = classdef.ClassDef((g, c) for g, c in enumerate(lClasses) if c) rightCD = classdef.ClassDef( (g, c) for g, c in enumerate(rClasses) if c) for delKey in {'leftClassDef', 'rightClassDef'}: kwArgs.pop(delKey, None) r = cls({}, leftClassDef=leftCD, rightClassDef=rightCD, **utilities.filterKWArgs(cls, kwArgs)) i = 0 for rowIndex in range(lCount): for colIndex in range(rCount): value = kValues[indices[i]] if value: r[ClassPair([rowIndex, colIndex])] = value i += 1 return r
def fromwalker(cls, w, **kwArgs): """ Returns a new Anchor_Point object from the specified walker. >>> atp = _testingValues[0] >>> atp == Anchor_Point.frombytes(atp.binaryString(), glyphIndex=40) True """ format = w.unpack("H") if format != 2: raise ValueError("Unknown format for Anchor_Point: %d" % (format,)) x, y, p = w.unpack("2hH") return cls(x, y, pointIndex=p, **utilities.filterKWArgs(cls, kwArgs))
def fromstring(cls, s, **kwArgs): """ Creates and returns a new Span from the specified string, which should have its values expressed in decimal. Note that showInHex will be set to False in the resulting Span if the caller did not explicitly set its value in kwArgs. >>> print(Span.fromstring("256-511")) 256-511 >>> print(Span.fromstring("256-511", showInHex=True)) 100-1FF """ r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) r.addFromString(s) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a Format3 object from the specified walker. >>> obj = _testingValues[1] >>> obj == Format3.frombytes(obj.binaryString(), coverage=obj.coverage) True """ glyphCount, kCount, lCount, rCount = w.unpack("H3Bx") # flags not used kValues = w.group("h", kCount) lClasses = w.group("B", glyphCount) rClasses = w.group("B", glyphCount) indices = w.group("B", lCount * rCount) # Note the assumption implicit in the following two lines of code: the # [0] element of the kernValue array has a numeric value of zero FUnits # (i.e. a non-kerning pair). This assumption is not documented in # Apple's 'kern' table documentation. leftCD = classdef.ClassDef((g, c) for g, c in enumerate(lClasses) if c) rightCD = classdef.ClassDef( (g, c) for g, c in enumerate(rClasses) if c) for delKey in {'leftClassDef', 'rightClassDef'}: kwArgs.pop(delKey, None) r = cls({}, leftClassDef=leftCD, rightClassDef=rightCD, **utilities.filterKWArgs(cls, kwArgs)) i = 0 CP = classpair.ClassPair for rowIndex in range(lCount): for colIndex in range(rCount): value = kValues[indices[i]] if value: r[CP([rowIndex, colIndex])] = value i += 1 return r
def fromhexstring(cls, s, **kwArgs): """ Creates and returns a new Span from the specified string, which should have its values expressed in hex. Note that showInHex will be set to True in the resulting Span if the caller did not explicitly set its value in kwArgs. >>> print(Span.fromhexstring("100-1FF")) 100-1FF >>> print(Span.fromhexstring("100-1FF", showInHex=False)) 256-511 """ if 'showInHex' not in kwArgs: kwArgs['showInHex'] = True r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) r.addFromHexString(s) return r
def fromranges(cls, iterable, **kwArgs): """ Creates and returns a new Span from the specified iterable, which should yield (first, last) pairs. >>> print(Span.fromranges([(4, 50), (100, 200), (900, 1200)])) 4-50, 100-200, 900-1200 Duplication is fine: >>> print(Span.fromranges([(4, 50), (60, 90), (15, 85)])) 4-90 """ r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) for first, last in iterable: r.update(range(first, last + 1)) return r
def fromscaler(cls, s, ppemList, **kwArgs): """ Creates a new Sbit object from the specified scaler using ppemList (a list of (xPPEM, yPPEM, bitDepth) tuples). The scaler needs to have ben initialized and had 'setFont' called on the desired source for bitmap images. Though merely passed through here, if you want to limit the generation to specific glyphs, pass in the iterable with the 'glyphList' kwArg. If you do not do that, you MUST pass in the 'editor' kwArg (to generate all glyphs). """ r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) for p in ppemList: tstrike = strike.Strike.fromscaler(s, *p, **kwArgs) d, r[p] = strike._recalc_lineMetrics(tstrike) return r
def fromversion1(cls, v1Obj, **kwArgs): """ Returns a new version 2 GlyphProperties object from the specified version 1 GlyphProperties object. >>> s = utilities.fromhex("FD 0B") >>> v1Obj = glyphproperties_v1.GlyphProperties.frombytes( ... s, ... glyphIndex = 100) >>> v2Obj = GlyphProperties.fromversion1(v1Obj) >>> ( v1Obj.binaryString(glyphIndex=100) == ... v2Obj.binaryString(glyphIndex=100)) True >>> v2Obj.attachesRight = True >>> ( v1Obj.binaryString(glyphIndex=100) == ... v2Obj.binaryString(glyphIndex=100)) False """ delKeys = { 'attachesRight', 'directionality', 'floater', 'hangsLeftTop', 'hangsRightBottom', 'mirrorGlyph'} for delKey in delKeys: kwArgs.pop(delKey, None) return cls( floater = v1Obj.floater, hangsLeftTop = v1Obj.hangsLeftTop, hangsRightBottom = v1Obj.hangsRightBottom, mirrorGlyph = v1Obj.mirrorGlyph, attachesRight = False, directionality = v1Obj.directionality, **utilities.filterKWArgs(cls, kwArgs))
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Format4 object from the specified walker. >>> obj = _makePointExample() >>> k = {'coverage': obj.coverage, 'tupleIndex': obj.tupleIndex} >>> bs = obj.binaryString() >>> obj2 = Format4.frombytes(bs, **k) >>> obj == obj2 True >>> obj = _makeAnchorExample() >>> bs = obj.binaryString() >>> obj2 = Format4.frombytes(bs, **k) >>> obj == obj2 True >>> obj = _makeCoordExample() >>> bs = obj.binaryString() >>> obj2 = Format4.frombytes(bs, **k) >>> obj == obj2 True """ numClasses, oCT, oSA, oET, kind, oVT = w.unpack("4LBT") kind >>= 6 if kind not in {0, 1, 2}: raise ValueError("Unknown action type mask!") t = (oCT, oSA, oET, oVT) wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers(w.subWalker(0), *t) wETCopy = wET.subWalker(0, relative=True) v = wETCopy.unpackRest("3H", strict=False) wET = wET.subWalker(0, relative=True, newLimit=6 * len(v)) numStates = max(2, 1 + utilities.safeMax(x[0] for x in v)) nsObj = namestash.NameStash.readormake(w, t, numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() fw = classtable.ClassTable.fromwalker classTable = fw(wCT, classNames=classNames) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) wVTCopy = wVT.subWalker(0, relative=True) v = wVTCopy.unpackRest(("2H" if kind < 2 else "4h"), strict=False) wVT = wVT.subWalker(0, relative=True, newLimit=(4 if kind < 2 else 8) * len(v)) v = _actionClasses[kind].groupfromwalker(wVT, **kwArgs) actionMap = dict(enumerate(v)) gfw = entry4.Entry.groupfromwalker entries = gfw(wET, actionMap=actionMap, stateNames=stateNames) fw = staterow.StateRow.fromwalker for stateName in stateNames: r[stateName] = fw(wSA, classNames=classNames, entries=entries) return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Format4 object from the specified walker, doing source validation. >>> obj = _makePointExample() >>> k = {'coverage': obj.coverage, 'tupleIndex': obj.tupleIndex} >>> k['logger'] = utilities.makeDoctestLogger("fvw") >>> bs = obj.binaryString() >>> obj2 = Format4.fromvalidatedbytes(bs, **k) fvw.format4 - DEBUG - Walker has 152 remaining bytes. fvw.format4.namestash - DEBUG - Walker has 132 remaining bytes. fvw.format4 - DEBUG - Walker has 28 remaining bytes. fvw.format4.lookup_aat - DEBUG - Walker has 28 remaining bytes. fvw.format4.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 26 remaining bytes. fvw.format4.actions.[0].pointentry - DEBUG - Walker has 8 remaining bytes. fvw.format4.actions.[1].pointentry - DEBUG - Walker has 4 remaining bytes. fvw.format4.entries.[0].entry4 - DEBUG - Walker has 24 remaining bytes. fvw.format4.entries.[1].entry4 - DEBUG - Walker has 18 remaining bytes. fvw.format4.entries.[2].entry4 - DEBUG - Walker has 12 remaining bytes. fvw.format4.entries.[3].entry4 - DEBUG - Walker has 6 remaining bytes. fvw.format4.state Start of text.staterow - DEBUG - Walker has 44 remaining bytes. fvw.format4.state Start of line.staterow - DEBUG - Walker has 30 remaining bytes. fvw.format4.state Saw x.staterow - DEBUG - Walker has 16 remaining bytes. >>> obj == obj2 True """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("format4") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 20: logger.error(('V0004', (), "Insufficient bytes.")) return None stBaseOffset = w.getOffset() numClasses, oCT, oSA, oET, kind, oVT = w.unpack("4LBT") if numClasses < 4: logger.error( ('V0634', (numClasses, ), "The number of classes in the state table must be at least " "four, but is only %d.")) return None if kind & 0x3F: logger.warning(( 'V0879', (kind, ), "One or more reserved bits in the flag byte %02X are not zero." )) kind >>= 6 if kind == 3: logger.error( ('V0880', (), "Action type mask is 3, which is undefined.")) return None t = (oCT, oSA, oET, oVT) firstValid = w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + w.length() if any(o < firstValid or o >= lastValidPlusOne for o in t): logger.error( ('V0635', (), "One or more offsets to state table components are outside " "the bounds of the state table itself.")) return None wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers(w.subWalker(0), *t) wETCopy = wET.subWalker(0, relative=True) v = wETCopy.unpackRest("3H", strict=False) wET = wET.subWalker(0, relative=True, newLimit=6 * len(v)) numStates = max(2, 1 + utilities.safeMax(x[0] for x in v)) fvw = namestash.NameStash.readormake_validated nsObj = fvw(w, t, numStates, numClasses, logger=logger) if nsObj is None: return None stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() fvw = classtable.ClassTable.fromvalidatedwalker classTable = fvw(wCT, classNames=classNames, logger=logger) if classTable is None: return None kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) wVTCopy = wVT.subWalker(0, relative=True) v = wVTCopy.unpackRest(("2H" if kind < 2 else "4h"), strict=False) wVT = wVT.subWalker(0, relative=True, newLimit=(4 if kind < 2 else 8) * len(v)) gfvw = _actionClasses[kind].groupfromvalidatedwalker v = gfvw(wVT, logger=logger.getChild("actions"), **kwArgs) if v is None: return None actionMap = dict(enumerate(v)) entries = entry4.Entry.groupfromvalidatedwalker( wET, actionMap=actionMap, stateNames=stateNames, logger=logger.getChild("entries")) if entries is None: return None fvw = staterow.StateRow.fromvalidatedwalker for stateName in stateNames: obj = fvw(wSA, classNames=classNames, entries=entries, logger=logger.getChild("state %s" % (stateName, ))) if obj is None: return None r[stateName] = obj return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Format1 object from the specified walker, which must start at the beginning (numClasses) of the state table. The following keyword arguments are supported: coverage A Coverage object. This is required. """ numClasses, oCT, oSA, oET, oVT = w.unpack("5H") wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers( w.subWalker(0), # can't just use w; it gets reset oCT, oSA, oET, oVT) numStates = cls._findNumStates(wET.subWalker(0, relative=True), oSA, numClasses) nsObj = namestash.NameStash.readormake(w, (oCT, oSA, oET, oVT), numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromwalker(wCT, classNames=classNames) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) fw = staterow.StateRow.fromwalker cover = kwArgs['coverage'] entryPool = {} valueTuplePool = {} delKeys = { 'classNames', 'entryPool', 'isCrossStream', 'stateArrayBaseOffset', 'stateNames', 'valueTuplePool', 'wEntryTable', 'wSubtable' } for delKey in delKeys: kwArgs.pop(delKey, None) for stateName in stateNames: r[stateName] = fw(wSA, classNames=classNames, entryPool=entryPool, isCrossStream=cover.crossStream, stateArrayBaseOffset=oSA, stateNames=stateNames, valueTuplePool=valueTuplePool, wEntryTable=wET, wSubtable=w, **kwArgs) return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Format1 object from the specified walker, doing source validation. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("format1") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 20: logger.error(('V0004', (), "Insufficient bytes.")) return None stBaseOffset = w.getOffset() t = w.unpack("5L") numClasses, oCT, oSA, oET, oVT = t if numClasses < 4: logger.error( ('V0634', (numClasses, ), "The number of classes in the state table must be at least " "four, but is only %d.")) return None firstValid = w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + w.length() if any(o < firstValid or o >= lastValidPlusOne for o in t[1:]): logger.error( ('V0635', (), "One or more offsets to state table components are outside " "the bounds of the state table itself.")) return None wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers( w.subWalker(0), *t[1:]) wETCopy = wET.subWalker(0, relative=True) v = wETCopy.unpackRest("3H", strict=False) numStates = 1 + utilities.safeMax(x[0] for x in v) numEntries = len(v) nsObj = namestash.NameStash.readormake_validated(w, (oCT, oSA, oET, oVT), numStates, numClasses, logger=logger) if nsObj is None: return None stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromvalidatedwalker( wCT, classNames=classNames, logger=logger) if classTable is None: return None kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) # build value table valueDict = {} fvw = valuetuple.ValueTuple.fromvalidatedwalker index = 0 while wVT.stillGoing(): obj = fvw(wVT, logger=logger.getChild("value %d" % (index, ))) if obj is None: return None valueDict[index] = obj index += 1 # build entry table entries = [] index = 0 fvw = entry.Entry.fromvalidatedwalker while index < numEntries: obj = fvw(wET, stateNames=stateNames, valueDict=valueDict, logger=logger.getChild("entry %d" % (index, ))) if obj is None: return None entries.append(obj) index += 1 # finally, build state table fvw = staterow.StateRow.fromvalidatedwalker for stateName in stateNames: obj = fvw(wSA, classNames=classNames, entries=entries, logger=logger.getChild("state %s" % (stateName, ))) if obj is None: return None r[stateName] = obj return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Format1 object from the specified walker, doing source validation, which must start at the beginning (numClasses) of the state table. The following keyword arguments are supported: coverage A Coverage object. This is required. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("format1") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 10: logger.error(('V0004', (), "Insufficient bytes.")) return None stBaseOffset = w.getOffset() t = w.unpack("5H") numClasses, oCT, oSA, oET, oVT = t if numClasses < 4: logger.error( ('V0634', (numClasses, ), "The number of classes in the state table must be at least " "four, but is only %d.")) return None firstValid = w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + w.length() if any(o < firstValid or o >= lastValidPlusOne for o in t[1:]): logger.error( ('V0635', (), "One or more offsets to state table components are outside " "the bounds of the state table itself.")) return None wCT, wSA, wET, wVT = stutils.offsetsToSubWalkers( w.subWalker(0), *t[1:]) wETCopy = wET.subWalker(0, relative=True) if wETCopy.length() < 4: logger.error( ('V0636', (), "The entry table is missing or incomplete.")) return None v = wETCopy.unpackRest("2H") maxOffset = max(offset for offset, flags in v) - oSA numStates = 1 + (maxOffset // numClasses) nsObj = namestash.NameStash.readormake(w, t[1:], numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() fgc = kwArgs.pop('fontGlyphCount') classTable = classtable.ClassTable.fromvalidatedwalker( wCT, classNames=classNames, logger=logger, fontGlyphCount=fgc) if classTable is None: return None kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) fvw = staterow.StateRow.fromvalidatedwalker cover = kwArgs['coverage'] entryPool = {} valueTuplePool = {} delKeys = { 'classNames', 'entryPool', 'isCrossStream', 'logger', 'stateArrayBaseOffset', 'stateNames', 'valueTuplePool', 'wEntryTable', 'wSubtable' } for delKey in delKeys: kwArgs.pop(delKey, None) for stateName in stateNames: itemLogger = logger.getChild("state '%s'" % (stateName, )) obj = fvw(wSA, classNames=classNames, entryPool=entryPool, isCrossStream=cover.crossStream, logger=itemLogger, stateArrayBaseOffset=oSA, stateNames=stateNames, valueTuplePool=valueTuplePool, wEntryTable=wET, wSubtable=w, **kwArgs) if obj is None: return None r[stateName] = obj return r
def fromformat2(cls, fmt2Obj, **kwArgs): """ Creates and returns a new Format1 object from the specified Format2 object. >>> obj = _f2() >>> obj.pprint() ClassPair((1, 1)): -25 ClassPair((1, 2)): -10 ClassPair((2, 1)): 15 Left-hand classes: 15: 1 25: 1 35: 2 Right-hand classes: 9: 1 12: 1 15: 1 40: 2 Header information: Vertical text: False Cross-stream: False >>> Format1.fromformat2(obj).pprint(onlySignificant=True) State 'Start of text': Class 'class_LR_1_1': Go to state 'Saw_L_1' Class 'class_L_1': Go to state 'Saw_L_1' Class 'class_L_2': Go to state 'Saw_L_2' State 'Start of line': Class 'class_LR_1_1': Go to state 'Saw_L_1' Class 'class_L_1': Go to state 'Saw_L_1' Class 'class_L_2': Go to state 'Saw_L_2' State 'Saw_L_1': Class 'class_LR_1_1': Push this glyph, then go to state 'Saw_L_1' after performing these actions: Pop #1: Value: -25 Class 'class_L_1': Go to state 'Saw_L_1' Class 'class_L_2': Go to state 'Saw_L_2' Class 'class_R_1': Push this glyph, then go to state 'Start of text' after performing these actions: Pop #1: Value: -25 Class 'class_R_2': Push this glyph, then go to state 'Start of text' after performing these actions: Pop #1: Value: -10 State 'Saw_L_2': Class 'class_LR_1_1': Push this glyph, then go to state 'Saw_L_1' after performing these actions: Pop #1: Value: 15 Class 'class_L_1': Go to state 'Saw_L_1' Class 'class_L_2': Go to state 'Saw_L_2' Class 'class_R_1': Push this glyph, then go to state 'Start of text' after performing these actions: Pop #1: Value: 15 Class table: 9: class_R_1 12: class_R_1 15: class_LR_1_1 25: class_L_1 35: class_L_2 40: class_R_2 Header information: Vertical text: False Cross-stream: False Variations tuple index: 0 """ if 'coverage' not in kwArgs: kwArgs['coverage'] = coverage_v1.Coverage() if 'tupleIndex' not in kwArgs: kwArgs['tupleIndex'] = 0 leftMap = {} unionCT = _combinedClasses(fmt2Obj.leftClassDef, fmt2Obj.rightClassDef, leftMap) kwArgs.pop('classTable', None) r = cls({}, classTable=unionCT, **utilities.filterKWArgs(cls, kwArgs)) 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 defaultEntry = entry.Entry() for stateName in stateNames: newRow = staterow.StateRow() for className in classNames: 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 = entry.Entry(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([value.Value(dist)]) if className == match3: r[sn][className] = entry.Entry( newState="Start of text", push=True, values=val) else: r[sn][className] = entry.Entry( 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] = entry.Entry( newState="Saw_L_%s" % (className.split('_')[2], )) return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Ligature object from the specified walker, doing source validation. The walker should be based at the state table start (i.e. at numClasses). >>> origObj = _makeObj().trimmedToValidEntries() >>> s = origObj.binaryString() >>> logger = utilities.makeDoctestLogger("ligature_fvw") >>> fvb = Ligature.fromvalidatedbytes >>> d = { ... 'coverage': origObj.coverage, ... 'maskValue': 0x00000080, ... 'logger': logger, ... 'fontGlyphCount': 0x1000} >>> obj = fvb(s, **d) ligature_fvw.ligature - DEBUG - Walker has 514 remaining bytes. ligature_fvw.ligature.lookup_aat - DEBUG - Walker has 32 remaining bytes. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 5, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 6, class 7 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature - WARNING - The entry for state 2, class 6 does ligature substitution, but does not lead back to the ground state. This might be problematic. ligature_fvw.ligature.namestash - DEBUG - Walker has 486 remaining bytes. ligature_fvw.ligature.clstable - DEBUG - Walker has 32 remaining bytes. >>> obj == origObj True >>> fvb(s[:19], **d) ligature_fvw.ligature - DEBUG - Walker has 19 remaining bytes. ligature_fvw.ligature - ERROR - Insufficient bytes. >>> fvb(s[:39], **d) ligature_fvw.ligature - DEBUG - Walker has 39 remaining bytes. ligature_fvw.ligature - ERROR - One or more offsets to state table components are outside the bounds of the state table itself. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild('ligature') logger.debug(( 'V0001', (w.length(),), "Walker has %d remaining bytes.")) analyzer = ligatureanalyzer.Analyzer(w.subWalker(0, relative=True)) analyzer.analyze(logger=logger, **kwArgs) if analyzer.analysis is None: return None # Note that some of the size and other validation was already done in # the analyze() call, and is not reduplicated here. numClasses, oCT, oSA, oET, oLA, oCP, oLG = w.unpack("7L") offsets = (oCT, oSA, oET, oLA, oCP, oLG) wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( w.subWalker(0), *offsets) numStates = analyzer.numStates nsObj = namestash.NameStash.readormake_validated( w, offsets, numStates, numClasses, logger = logger) if nsObj is None: return None stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() fgc = utilities.getFontGlyphCount(**kwArgs) classTable = classtable.ClassTable.fromvalidatedwalker( wCT, classNames = classNames, logger = logger, fontGlyphCount = fgc) if classTable is None: return None kwArgs.pop('classTable', None) r = cls( {}, classTable = classTable, **utilities.filterKWArgs(cls, kwArgs)) entryPool = {} # entryIndex -> Entry object for stateIndex, rawStateRow in enumerate(analyzer.stateArray): thisRow = staterow_ligature.StateRow() for classIndex, rawEntryIndex in enumerate(rawStateRow): if rawEntryIndex not in entryPool: t = analyzer.entryTable[rawEntryIndex] newStateIndex, flags, laIndex = t entryPool[rawEntryIndex] = entry_ligature.Entry( newState = stateNames[newStateIndex], push = bool(flags & 0x8000), noAdvance = bool(flags & 0x4000)) e = entryPool[rawEntryIndex] d = analyzer.finalDicts.get((stateIndex, classIndex), None) if d is not None: for inGlyphs, outGlyphs in d.items(): gti = glyphtuple.GlyphTupleInput(inGlyphs) if gti not in e.actions: gto = glyphtuple.GlyphTupleOutput(outGlyphs) e.actions[gti] = gto thisRow[classNames[classIndex]] = e r[stateNames[stateIndex]] = thisRow return r.trimmedToValidEntries()
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 fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a Format0 object from the specified walker, doing source validation. The walker should start at the subtable header. The following keyword arguments are suggested (if they are not present, the default values for coverage and tupleIndex will be used, which won't usually be what's wanted). NOTE: see the docstring for the Format0 class for a discussion of record formats. coverage A Coverage object. logger A logger to which messages will be posted. tupleIndex The variations tuple index. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("format0") logger.debug(( 'V0001', (w.length(),), "Walker has %d remaining bytes.")) if w.length() < 16: logger.error(('V0004', (), "Insufficient bytes.")) return None count = w.unpack("L12x") r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) if count: if w.length() < 6 * count: logger.error(('V0004', (), "Insufficient bytes for array.")) return None data = w.group("2Hh", count) GP = glyphpair.GlyphPair for *t, value in data: key = GP(t) if key in r: if value == r[key]: logger.warning(( 'V0770', (key,), "A duplicate key %s was found.")) else: logger.error(( 'V0771', (key, value, r[key]), "A duplicate key %s was found with value %d, " "but that key was previously added with value %d.")) return None r[key] = value # Skip the sentinel, if one is present (it's ambiguous) if w.length() >= 6: sentinel = w.unpack("3h", advance=False) if sentinel == (-1, -1, 0): w.skip(6) return r
def fromwalker(cls, w, **kwArgs): """ Creates and returns a new Ligature object from the specified walker, which should be based at the state header start (numClasses). >>> origObj = _makeObj().trimmedToValidEntries() >>> d = {'coverage': origObj.coverage, 'maskValue': 0x00000080} >>> obj = Ligature.frombytes(origObj.binaryString(), **d) >>> obj == origObj True """ analyzer = ligatureanalyzer.Analyzer(w.subWalker(0, relative=True)) analyzer.analyze(**kwArgs) numClasses, oCT, oSA, oET, oLA, oCP, oLG = w.unpack("7L") offsets = (oCT, oSA, oET, oLA, oCP, oLG) wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( w.subWalker(0), *offsets) numStates = analyzer.numStates nsObj = namestash.NameStash.readormake( w, offsets, numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() classTable = classtable.ClassTable.fromwalker( wCT, classNames = classNames) kwArgs.pop('classTable', None) r = cls( {}, classTable = classTable, **utilities.filterKWArgs(cls, kwArgs)) entryPool = {} # entryIndex -> Entry object for stateIndex, rawStateRow in enumerate(analyzer.stateArray): thisRow = staterow_ligature.StateRow() for classIndex, rawEntryIndex in enumerate(rawStateRow): if rawEntryIndex not in entryPool: t = analyzer.entryTable[rawEntryIndex] newStateIndex, flags, laIndex = t entryPool[rawEntryIndex] = entry_ligature.Entry( newState = stateNames[newStateIndex], push = bool(flags & 0x8000), noAdvance = bool(flags & 0x4000)) e = entryPool[rawEntryIndex] d = analyzer.finalDicts.get((stateIndex, classIndex), None) if d is not None: for inGlyphs, outGlyphs in d.items(): gti = glyphtuple.GlyphTupleInput(inGlyphs) if gti not in e.actions: gto = glyphtuple.GlyphTupleOutput(outGlyphs) e.actions[gti] = gto thisRow[classNames[classIndex]] = e r[stateNames[stateIndex]] = thisRow return r.trimmedToValidEntries()
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Sbit object from the specified walker (which will be for a dat table), with source validation. There is one required keyword argument: flavor One of 'bloc', 'EBDT', or 'CBLC' to indicate the type wLoc A walker for the corresponding loc table (bloc for bdat, EBLC for EBDT, CBLC for CBDT). """ flavor = kwArgs.get('flavor', 'sbits') logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild(flavor) logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 4: logger.error(('V0004', (), "Insufficient bytes.")) return None version = w.unpack("L", advance=False) expectedversion = FLAVOR_VERSIONS.get(flavor, 0) if version != expectedversion: logger.error(('E0600', ( expectedversion, version, ), "Expected version 0x%08X, but got 0x%08X.")) if flavor == 'CBDT' and version == 0x00030000: logger.warning(('Vxxxx', (), "CBDT uses v3 which is correct per spec, but " "in actual practice, v2 is required.")) else: # don't allow other version-flavor mismatches. return None wLoc = kwArgs['wLoc'] wLocWalk = wLoc.subWalker(0) if wLocWalk.length() < 8: logger.error(('V0004', (), "Insufficient bytes.")) return None version, bstCount = wLocWalk.unpack("2L") if version != expectedversion: logger.error(('E0702', ( expectedversion, version, ), "Location table version should be 0x%08X but is 0x%08X.")) if flavor == 'CBDT' and version == 0x00030000: logger.warning(('Vxxxx', (), "CBLC uses v3 which is correct per spec, but " "in actual practice, v2 is required.")) else: # don't allow other version-flavor mismatches. return None r = cls({}, **utilities.filterKWArgs(cls, kwArgs)) fvw = strike.Strike.fromvalidatedwalker for i in range(bstCount): stk = fvw(wLocWalk, wDat=w, logger=logger.getChild("bitmapSizeTable %d" % (i, )), **kwArgs) if stk is None: return None r[(stk.ppemX, stk.ppemY, stk.bitDepth)] = stk return r