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 _buildInputs(self): self.w.reset() t = self.w.unpack("5L") self.numClasses, oCT, oSA, oET, oGT = t wCT, wSA, wET, wGT = stutils.offsetsToSubWalkers(self.w, *t[1:]) self.entryTable = wET.unpackRest("4H", strict=False) self.numStates = max( int(wSA.length()) // (2 * self.numClasses), 1 + max(t[0] for t in self.entryTable)) self.classMap = lookup.Lookup.fromwalker(wCT) d = utilities.invertDictFull(self.classMap, asSets=True) self.classToGlyphs = {k: v for k, v in d.items() if k >= 4} self.classToGlyphs[2] = {0xFFFF} self.emptyClasses = (set(range(4, self.numClasses)) - set(self.classToGlyphs)) self.stateArray = wSA.group("H" * self.numClasses, self.numStates) numLookups = 1 + max(n for t in self.entryTable for n in t[2:4] if n != 0xFFFF) self.glyphLookups = [None] * numLookups offsets = wGT.group("L", numLookups) for i, offset in enumerate(offsets): wSub = wGT.subWalker(offset) self.glyphLookups[i] = lookup.Lookup_OutGlyph.fromwalker(wSub)
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 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 _buildInputs(self): self.w.reset() t = self.w.unpack("7H") self.numClasses, oCT, oSA, oET, oLA, oCP, oLG = t self.offsets = t[1:] wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( self.w, *self.offsets) self._findNumStates(wET.subWalker(0, relative=True), oSA) self._makeClassMap(wCT) self.stateArray = wSA.group("B" * self.numClasses, self.numStates) self.entryTable = wET.unpackRest("2H", strict=False) self.ligActions = wLA.unpackRest("L", strict=False) self.ligActionIsLast = [bool(n & 0x80000000) for n in self.ligActions] self.compOffsets = wCP.unpackRest("H") self.ligatures = wLG.unpackRest("H")
def _buildInputs(self): self.w.reset() self.numClasses, oCT, oSA, oET, oST = self.w.unpack("5H") self.offsets = (oSA, oST) wCT, wSA, wET, wST = stutils.offsetsToSubWalkers( self.w, oCT, oSA, oET, oST) self._findNumStates( wET.subWalker(0, relative=True), oSA, self.numClasses) self._makeClassMap(wCT) self.stateArray = wSA.group("B" * self.numClasses, self.numStates) self.entryTable = wET.unpackRest("4H", strict=False) self.substTable = wST.unpackRest("H")
def _buildInputs(self): self.w.reset() t = self.w.unpack("7L") self.numClasses, oCT, oSA, oET, oLA, oCP, oLG = t wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( self.w, *t[1:]) self.entryTable = list(wET.unpackRest("3H", strict=False)) self.numStates = max( int(wSA.length()) // (2 * self.numClasses), 1 + max(t[0] for t in self.entryTable)) self.classMap = lookup.Lookup.fromwalker(wCT) d = utilities.invertDictFull(self.classMap, asSets=True) self.classToGlyphs = {k: frozenset(v) for k, v in d.items() if k >= 4} self.classToGlyphs[2] = frozenset([0xFFFF]) self.stateArray = wSA.group("H" * self.numClasses, self.numStates) self.ligActions = wLA.unpackRest("L") self.ligActionIsLast = [bool(n & 0x80000000) for n in self.ligActions] self.compIndices = wCP.unpackRest("H") self.ligatures = wLG.unpackRest("H")
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 _buildInputs_validated(self, logger): self.w.reset() stBaseOffset = self.w.getOffset() if self.w.length() < 10: logger.error(('V0004', (), "Insufficient bytes.")) return False t = self.w.unpack("5H") self.numClasses, oCT, oSA, oET, oST = t if self.numClasses < 4: logger.error(( 'V0634', (self.numClasses,), "The number of classes in a state table must be at least " "four, but is only %d.")) return False firstValid = self.w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + self.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 False self.offsets = (oSA, oST) wCT, wSA, wET, wST = stutils.offsetsToSubWalkers(self.w, *t[1:]) self.entryTable = wET.unpackRest("4H", strict=False) if not self.entryTable: logger.error(( 'V0636', (), "The entry table is missing or incomplete.")) return False maxOffset = max(t[0] for t in self.entryTable) - oSA self.numStates = 1 + (maxOffset // self.numClasses) okToProceed = self._makeClassMap_validated( wCT, logger = logger.getChild("classmap")) if not okToProceed: return False if wSA.length() < self.numClasses * self.numStates: logger.error(( 'V0676', (), "The state array is missing or incomplete.")) return False self.stateArray = wSA.group("B" * self.numClasses, self.numStates) maxEntryIndex = max(n for row in self.stateArray for n in row) if maxEntryIndex >= len(self.entryTable): logger.error(( 'V0724', (stateNames[stateIndex], classNames[classIndex]), "At least one state array cell contains an entry index that " "is out of range.")) return False self.substTable = wST.unpackRest("H") if not self.substTable: logger.error(( 'V0679', (), "The substitution table is missing or incomplete.")) return False return True
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, 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 _buildInputs_validated(self, logger): self.w.reset() stBaseOffset = self.w.getOffset() if self.w.length() < 14: logger.error(('V0004', (), "Insufficient bytes.")) return False t = self.w.unpack("7H") self.numClasses, oCT, oSA, oET, oLA, oCP, oLG = t self.offsets = t[1:] if self.numClasses < 4: logger.error(( 'V0634', (self.numClasses,), "The number of classes in a state table must be at least " "four, but is only %d.")) return False firstValid = self.w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + self.w.length() if any(o < firstValid or o >= lastValidPlusOne for o in self.offsets): logger.error(( 'V0635', (), "One or more offsets to state table components are outside " "the bounds of the state table itself.")) return False wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( self.w, *self.offsets) self.entryTable = wET.unpackRest("2H", strict=False) if not self.entryTable: logger.error(( 'V0636', (), "The entry table is missing or incomplete.")) return False maxOffset = max(t[0] for t in self.entryTable) - oSA self.numStates = 1 + (maxOffset // self.numClasses) okToProceed = self._makeClassMap_validated( wCT, logger = logger.getChild("classmap")) if not okToProceed: return False if wSA.length() < self.numClasses * self.numStates: logger.error(( 'V0676', (), "The state array is missing or incomplete.")) return False self.stateArray = wSA.group("B" * self.numClasses, self.numStates) maxEntryIndex = max(n for row in self.stateArray for n in row) if maxEntryIndex >= len(self.entryTable): logger.error(( 'V0724', (), "At least one state array cell contains an entry index that " "is out of range.")) return False self.ligActions = wLA.unpackRest("L", strict=False) if not self.ligActions: logger.error(( 'V0690', (), "The ligature action list is missing or incomplete.")) return False self.ligActionIsLast = [bool(n & 0x80000000) for n in self.ligActions] self.compOffsets = wCP.unpackRest("H") if not self.compOffsets: logger.error(( 'V0691', (), "The component offset table is missing or incomplete.")) return False self.ligatures = wLG.unpackRest("H") if not self.ligatures: logger.error(( 'V0692', (), "The ligature table is missing or incomplete.")) return False return True
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Insertion object from the specified walker, doing source validation. The walker must start at the beginning (numClasses) of the state table. >>> s = _testingValues[0].binaryString() >>> logger = utilities.makeDoctestLogger("insertion_fvw") >>> fvb = Insertion.fromvalidatedbytes >>> d = { ... 'coverage': _testingValues[0].coverage, ... 'maskValue': 0x00000001, ... 'fontGlyphCount': 0x1000, ... 'logger': logger} >>> obj = fvb(s, **d) insertion_fvw.insertion - DEBUG - Walker has 220 remaining bytes. insertion_fvw.insertion.clstable - DEBUG - Walker has 32 remaining bytes. insertion_fvw.insertion.clstable.binsearch.binsrch header - DEBUG - Walker has 30 remaining bytes. >>> fvb(s[:9], **d) insertion_fvw.insertion - DEBUG - Walker has 9 remaining bytes. insertion_fvw.insertion - ERROR - Insufficient bytes. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("insertion") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 20: logger.error(('V0004', (), "Insufficient bytes.")) return None fgc = utilities.getFontGlyphCount(**kwArgs) stBaseOffset = w.getOffset() tHeader = w.unpack("5L") numClasses, oCT, oSA, oET, oIG = tHeader 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 tHeader[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, wIG = stutils.offsetsToSubWalkers( w.subWalker(0), *tHeader[1:]) rawEntries = wET.unpackRest("4H") numStates = max( int(wSA.length()) // (2 * numClasses), 1 + max(t[0] for t in rawEntries)) if numStates < 2: logger.error( ('V0725', (), "The number of states in the state table is less than two. " "The two fixed states must always be present.")) return None rawInsertionGlyphs = wIG.unpackRest("H") nsObj = namestash.NameStash.readormake_validated( w, tHeader[1:], numStates, numClasses) if nsObj is None: return None stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() E = entry_insertion.Entry GTO = glyphtuple.GlyphTupleOutput entries = [None] * len(rawEntries) for i, raw in enumerate(rawEntries): newStateIndex, flags, currIGIndex, markIGIndex = raw currCount = (flags & 0x03E0) >> 5 markCount = flags & 0x001F if currCount: if currIGIndex == 0xFFFF: logger.error( ('V0726', (i, ), "The current insert count for entry %d is nonzero " "but the corresponding glyph index is missing.")) return None elif currIGIndex + currCount > len(rawInsertionGlyphs): logger.error(( 'V0727', (i, ), "The sum of the current insert count and the starting " "glyph index for entry %d is beyond the end of the " "insertion glyph array.")) return None t = rawInsertionGlyphs[currIGIndex:currIGIndex + currCount] else: t = [] currGlyphs = GTO(t) if markCount: if markIGIndex == 0xFFFF: logger.error( ('V0726', (i, ), "The marked insert count for entry %d is nonzero " "but the corresponding glyph index is missing.")) return None elif markIGIndex + markCount > len(rawInsertionGlyphs): logger.error( ('V0727', (i, ), "The sum of the marked insert count and the starting " "glyph index for entry %d is beyond the end of the " "insertion glyph array.")) return None t = rawInsertionGlyphs[markIGIndex:markIGIndex + markCount] else: t = [] markGlyphs = GTO(t) entries[i] = E(newState=stateNames[newStateIndex], mark=bool(flags & 0x8000), noAdvance=bool(flags & 0x4000), currentIsKashidaLike=bool(flags & 0x2000), markedIsKashidaLike=bool(flags & 0x1000), currentInsertBefore=bool(flags & 0x0800), markedInsertBefore=bool(flags & 0x0400), currentInsertGlyphs=currGlyphs, markedInsertGlyphs=markGlyphs) classTable = classtable.ClassTable.fromvalidatedwalker( wCT, classNames=classNames, logger=logger, fontGlyphCount=fgc) if classTable is None: return None if wSA.length() < 2 * numClasses * numStates: logger.error( ('V0676', (), "The state array is missing or incomplete.")) return None rawStateArray = wSA.group("H" * numClasses, numStates) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) S = staterow_insertion.StateRow for stateIndex, rawState in enumerate(rawStateArray): r[stateNames[stateIndex]] = thisRow = S() for classIndex, entryIndex in enumerate(rawState): if entryIndex >= len(entries): logger.error( ('V0724', (stateNames[stateIndex], classNames[classIndex]), "The state array cell for state '%s' and class '%s' " "specifies an entry index that is out of range.")) return None thisRow[classNames[classIndex]] = entries[entryIndex] 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 fromwalker(cls, w, **kwArgs): """ Creates and returns a new Insertion object from the specified walker, which must start at the beginning (numClasses) of the state table. >>> obj = _testingValues[0] >>> s = obj.binaryString() >>> d = { ... 'maskValue': obj.maskValue, ... 'coverage': obj.coverage.__deepcopy__()} >>> obj == Insertion.frombytes(s, **d) True """ numClasses, oCT, oSA, oET, oIG = w.unpack("5L") wCT, wSA, wET, wIG = stutils.offsetsToSubWalkers( w.subWalker(0), oCT, oSA, oET, oIG) rawEntries = wET.unpackRest("4H") numStates = max( int(wSA.length()) // (2 * numClasses), 1 + max(t[0] for t in rawEntries)) rawInsertionGlyphs = wIG.unpackRest("H") nsObj = namestash.NameStash.readormake(w, (oCT, oSA, oET, oIG), numStates, numClasses) stateNames = nsObj.allStateNames() classNames = nsObj.allClassNames() E = entry_insertion.Entry GTO = glyphtuple.GlyphTupleOutput entries = [None] * len(rawEntries) for i, raw in enumerate(rawEntries): newStateIndex, flags, currIGIndex, markIGIndex = raw currCount = (flags & 0x03E0) >> 5 markCount = flags & 0x001F if currCount: t = rawInsertionGlyphs[currIGIndex:currIGIndex + currCount] else: t = [] currGlyphs = GTO(t) if markCount: t = rawInsertionGlyphs[markIGIndex:markIGIndex + markCount] else: t = [] markGlyphs = GTO(t) entries[i] = E(newState=stateNames[newStateIndex], mark=bool(flags & 0x8000), noAdvance=bool(flags & 0x4000), currentIsKashidaLike=bool(flags & 0x2000), markedIsKashidaLike=bool(flags & 0x1000), currentInsertBefore=bool(flags & 0x0800), markedInsertBefore=bool(flags & 0x0400), currentInsertGlyphs=currGlyphs, markedInsertGlyphs=markGlyphs) classTable = classtable.ClassTable.fromwalker(wCT, classNames=classNames) rawStateArray = wSA.group("H" * numClasses, numStates) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) S = staterow_insertion.StateRow for stateIndex, rawState in enumerate(rawStateArray): r[stateNames[stateIndex]] = thisRow = S() for classIndex, entryIndex in enumerate(rawState): thisRow[classNames[classIndex]] = entries[entryIndex] 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 Contextual object from the specified walker, doing source validation. >>> s = _testingValues[0].binaryString() >>> logger = utilities.makeDoctestLogger("contextual_fvw") >>> fvb = Contextual.fromvalidatedbytes >>> d = { ... 'coverage': _testingValues[0].coverage, ... 'maskValue': 0x10000000, ... 'logger': logger} >>> obj = fvb(s, **d) contextual_fvw.contextual - DEBUG - Walker has 126 remaining bytes. contextual_fvw.contextual.namestash - DEBUG - Walker has 116 remaining bytes. contextual_fvw.contextual.classtable - DEBUG - Walker has 34 remaining bytes. >>> obj == _testingValues[0] True >>> fvb(s[:5], **d) contextual_fvw.contextual - DEBUG - Walker has 5 remaining bytes. contextual_fvw.contextual - ERROR - Insufficient bytes. >>> fvb(s[:23], **d) contextual_fvw.contextual - DEBUG - Walker has 23 remaining bytes. contextual_fvw.contextual - 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("contextual") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) analyzer = contextanalyzer.Analyzer(w.subWalker(0, relative=True)) markAnalysis, currAnalysis = analyzer.analyze(logger=logger) if markAnalysis is None or currAnalysis 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. t = w.unpack("5H") numClasses, oCT, oSA, oET, oST = t t = t[1:] wCT, wSA, wET, wST = stutils.offsetsToSubWalkers(w.subWalker(0), *t) numStates = analyzer.numStates nsObj = namestash.NameStash.readormake_validated(w, t, 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)) 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 fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Rearrangement object from the specified walker, doing source validation. The walker must start at the beginning (numClasses) of the state table. >>> s = _testingValues[0].binaryString() >>> logger = utilities.makeDoctestLogger("rearrangement_fvw") >>> fvb = Rearrangement.fromvalidatedbytes >>> d = { ... 'logger': logger, ... 'fontGlyphCount': 0x1000, ... 'coverage': _testingValues[0].coverage, ... 'maskValue': 0x04000000} >>> obj = fvb(s, **d) rearrangement_fvw.rearrangement - DEBUG - Walker has 148 remaining bytes. rearrangement_fvw.rearrangement.namestash - DEBUG - Walker has 140 remaining bytes. rearrangement_fvw.rearrangement.classtable - DEBUG - Walker has 44 remaining bytes. >>> obj == _testingValues[0] True >>> fvb(s[:6], **d) rearrangement_fvw.rearrangement - DEBUG - Walker has 6 remaining bytes. rearrangement_fvw.rearrangement - ERROR - Insufficient bytes. >>> fvb(utilities.fromhex("00 02") + s[2:], **d) rearrangement_fvw.rearrangement - DEBUG - Walker has 148 remaining bytes. rearrangement_fvw.rearrangement - ERROR - The number of classes in the state table must be at least four, but is only 2. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("rearrangement") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 8: logger.error(('V0004', (), "Insufficient bytes.")) return None stBaseOffset = w.getOffset() t = w.unpack("4H") numClasses, oCT, oSA, oET = 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 = 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 rawEntries = wET.unpackRest("2H") maxOffset = max(offset for offset, flags in rawEntries) numStates = 1 + (maxOffset - oSA) // numClasses nsObj = namestash.NameStash.readormake_validated(w, t[1:], numStates, numClasses, logger=logger) if nsObj is None: return nsObj 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 if wSA.length() < numClasses * numStates: logger.error( ('V0676', (), "The state array is missing or incomplete.")) return None 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): if entryIndex >= len(rawEntries): logger.error( ('V0724', (stateNames[stateIndex], classNames[classIndex]), "The state array cell for state '%s' and class '%s' " "specifies an entry index that is out of range.")) return None 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 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 _buildInputs_validated(self, logger): self.w.reset() stBaseOffset = self.w.getOffset() if self.w.length() < 20: logger.error(('V0004', (), "Insufficient bytes.")) return False t = self.w.unpack("5L") self.numClasses, oCT, oSA, oET, oGT = t if self.numClasses < 4: logger.error( ('V0634', (self.numClasses, ), "The number of classes in a state table must be at least " "four, but is only %d.")) return False firstValid = self.w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + self.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 False wCT, wSA, wET, wGT = stutils.offsetsToSubWalkers(self.w, *t[1:]) self.entryTable = wET.unpackRest("4H", strict=False) if not self.entryTable: logger.error( ('V0636', (), "The entry table is missing or incomplete.")) return False if len(self.entryTable) != len(set(self.entryTable)): logger.error( ('Vxxxx', (), "The entry table has duplicate rows; this usually means " "a copy/paste error or some other problem.")) return False self.numStates = max( int(wSA.length()) // (2 * self.numClasses), 1 + max(t[0] for t in self.entryTable)) if self.numStates < 2: logger.error( ('V0725', (), "The number of states in the state table is less than two. " "The two fixed states must always be present.")) return None self.classMap = lookup.Lookup.fromvalidatedwalker( wCT, logger=logger.getChild("classmap")) if not self.classMap: return False d = utilities.invertDictFull(self.classMap, asSets=True) self.classToGlyphs = {k: v for k, v in d.items() if k >= 4} self.classToGlyphs[2] = {0xFFFF} self.emptyClasses = (set(range(4, self.numClasses)) - set(self.classToGlyphs)) if wSA.length() < 2 * self.numClasses * self.numStates: logger.error( ('V0676', (), "The state array is missing or incomplete.")) return False self.stateArray = wSA.group("H" * self.numClasses, self.numStates) maxEntryIndex = max(n for row in self.stateArray for n in row) if maxEntryIndex >= len(self.entryTable): logger.error( ('V0724', (stateNames[stateIndex], classNames[classIndex]), "At least one state array cell contains an entry index that " "is out of range.")) return False numLookups = 1 + max(n for t in self.entryTable for n in t[2:4] if n != 0xFFFF) if wGT.length() < 4 * numLookups: logger.error( ('V0728', (), "The offset header to the per-glyph lookup tables is " "missing or incomplete.")) return False self.glyphLookups = [None] * numLookups offsets = wGT.group("L", numLookups) f = lookup.Lookup_OutGlyph.fromvalidatedwalker for i, offset in enumerate(offsets): wSub = wGT.subWalker(offset) self.glyphLookups[i] = f(wSub, logger=logger.getChild("per-glyph %d" % (i, ))) if self.glyphLookups[i] is None: return False return True
def _buildInputs_validated(self, logger): self.w.reset() stBaseOffset = self.w.getOffset() if self.w.length() < 28: logger.error(('V0004', (), "Insufficient bytes.")) return False t = self.w.unpack("7L") self.numClasses, oCT, oSA, oET, oLA, oCP, oLG = t if self.numClasses < 4: logger.error( ('V0634', (self.numClasses, ), "The number of classes in a state table must be at least " "four, but is only %d.")) return False firstValid = self.w.getOffset() - stBaseOffset lastValidPlusOne = firstValid + self.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 False wCT, wSA, wET, wLA, wCP, wLG = stutils.offsetsToSubWalkers( self.w, *t[1:]) self.entryTable = list(wET.unpackRest("3H", strict=False)) if not self.entryTable: logger.error( ('V0636', (), "The entry table is missing or incomplete.")) return False self.numStates = max( int(wSA.length()) // (2 * self.numClasses), 1 + max(t[0] for t in self.entryTable)) if self.numStates < 2: logger.error( ('V0725', (), "The number of states in the state table is less than two. " "The two fixed states must always be present.")) return None self.classMap = lookup.Lookup.fromvalidatedwalker(wCT, logger=logger) if self.classMap is None: return False d = utilities.invertDictFull(self.classMap, asSets=True) self.classToGlyphs = {k: frozenset(v) for k, v in d.items() if k >= 4} self.classToGlyphs[2] = frozenset([0xFFFF]) if wSA.length() < 2 * self.numClasses * self.numStates: logger.error( ('V0676', (), "The state array is missing or incomplete.")) return False self.stateArray = wSA.group("H" * self.numClasses, self.numStates) maxEntryIndex = max(n for row in self.stateArray for n in row) if maxEntryIndex >= len(self.entryTable): logger.error( ('V0724', (), "At least one state array cell contains an entry index that " "is out of range.")) return False self.ligActions = wLA.unpackRest("L") if not self.ligActions: logger.error( ('V0690', (), "The ligature action list is missing or incomplete.")) return False self.ligActionIsLast = [bool(n & 0x80000000) for n in self.ligActions] self.compIndices = wCP.unpackRest("H") if not self.compIndices: logger.error( ('V0691', (), "The component table is missing or incomplete.")) return False self.ligatures = wLG.unpackRest("H") if not self.ligatures: logger.error( ('V0692', (), "The ligature table is missing or incomplete.")) return False return True
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Insertion object from the specified walker, doing source validation. The walker must start at the beginning (numClasses) of the state table. """ logger = kwArgs.pop('logger', logging.getLogger()) logger = logger.getChild("insertion") logger.debug( ('V0001', (w.length(), ), "Walker has %d remaining bytes.")) if w.length() < 8: logger.error(('V0004', (), "Insufficient bytes.")) return None stBaseOffset = w.getOffset() tHeader = w.unpack("4H") numClasses, oCT, oSA, oET = tHeader 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 tHeader[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 = stutils.offsetsToSubWalkers(w.subWalker(0), *tHeader[1:]) if wET.length() < 8: logger.error( ('V0636', (), "The entry table is missing or incomplete.")) return None rawEntries = wET.unpackRest("4H") maxOffset = max(t[0] for t in rawEntries) numStates = 1 + (maxOffset - oSA) // numClasses nsObj = namestash.NameStash.readormake_validated(w, tHeader[1:], 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, fontGlyphCount=kwArgs.pop('fontGlyphCount')) if classTable is None: return None E = entry_insertion.Entry GTO = glyphtuple.GlyphTupleOutput entries = [None] * len(rawEntries) for i, raw in enumerate(rawEntries): newStateOffset, flags, currOffset, markOffset = raw currCount = (flags & 0x03E0) >> 5 markCount = flags & 0x001F if currCount: if currOffset == 0xFFFF: logger.error( ('V0720', (i, ), "The current insert count for entry %d is nonzero " "but the corresponding offset is the missing value.")) return None wSub = w.subWalker(currOffset) if wSub.length() < 2 * currCount: logger.error( ('V0721', (i, ), "The current insert list for entry %d is missing " "or incomplete.")) return None t = wSub.group("H", currCount) else: t = [] currGlyphs = GTO(t) if markCount: if markOffset == 0xFFFF: logger.error( ('V0722', (i, ), "The marked insert count for entry %d is nonzero " "but the corresponding offset is the missing value.")) return None wSub = w.subWalker(markOffset) if wSub.length() < 2 * markCount: logger.error( ('V0723', (i, ), "The marked insert list for entry %d is missing " "or incomplete.")) return None t = wSub.group("H", markCount) else: t = [] markGlyphs = GTO(t) entries[i] = E(newState=stateNames[(newStateOffset - oSA) // numClasses], mark=bool(flags & 0x8000), noAdvance=bool(flags & 0x4000), currentIsKashidaLike=bool(flags & 0x2000), markedIsKashidaLike=bool(flags & 0x1000), currentInsertBefore=bool(flags & 0x0800), markedInsertBefore=bool(flags & 0x0400), currentInsertGlyphs=currGlyphs, markedInsertGlyphs=markGlyphs) if wSA.length() < numClasses * numStates: logger.error( ('V0676', (), "The state array is missing or incomplete.")) return None rawStateArray = wSA.group("B" * numClasses, numStates) kwArgs.pop('classTable', None) r = cls({}, classTable=classTable, **utilities.filterKWArgs(cls, kwArgs)) S = staterow_insertion.StateRow for stateIndex, rawState in enumerate(rawStateArray): r[stateNames[stateIndex]] = thisRow = S() for classIndex, entryIndex in enumerate(rawState): if entryIndex >= len(entries): logger.error( ('V0724', (stateNames[stateIndex], classNames[classIndex]), "The state array cell for state '%s' and class '%s' " "specifies an entry index that is out of range.")) return None thisRow[classNames[classIndex]] = entries[entryIndex] return r