def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Version object from the specified walker, doing validation. >>> bs = bytes.fromhex("00020001") >>> obj = Version.fromvalidatedbytes(bs) version - DEBUG - Walker has 4 remaining bytes. >>> obj.pprint() Major version: 2 Minor version: 1 >>> Version.fromvalidatedbytes(bs[:-1]) version - DEBUG - Walker has 3 remaining bytes. version - ERROR - Insufficient bytes. """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('version') else: logger = utilities.makeDoctestLogger('version') logger.debug(( 'V0001', int(w.length()), "Walker has %d remaining bytes.")) if w.length() < 4: logger.error(('V0004', (), "Insufficient bytes.")) return None t = w.group("H", 2) return cls(major=t[0], minor=t[1])
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new AxialCoordinate object from the data in the specified walker, doing validation. If a logger is not provided, one will be made. >>> bs = utilities.fromhex("40 00") >>> obj = AxialCoordinate.fromvalidatedbytes(bs) coord - DEBUG - Walker has 2 remaining bytes. coord - DEBUG - Coordinate value is 16384 >>> obj == 1.0 True >>> AxialCoordinate.fromvalidatedbytes(bs[:1]) coord - DEBUG - Walker has 1 remaining bytes. coord - ERROR - Insufficient bytes. """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('coord') else: logger = utilities.makeDoctestLogger('coord') logger.debug(( 'V0001', int(w.length()), "Walker has %d remaining bytes.")) if w.length() < 2: logger.error(('V0004', (), "Insufficient bytes.")) return None raw = w.unpack("h") logger.debug(( 'Vxxxx', (raw,), "Coordinate value is %d")) return cls(raw / 16384) # we've imported division from __future__
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new AxialCoordinates object from the specified walker, doing validation. The caller must provide an 'axisOrder' kwArg. >>> ao = ('wght', 'wdth') >>> obj = AxialCoordinates((AC(1.5), AC(-0.25)), axisOrder=ao) >>> bs = obj.binaryString() >>> fvb = AxialCoordinates.fromvalidatedbytes >>> obj2 = fvb(bs, axisOrder=ao) coords - DEBUG - Walker has 4 remaining bytes. coords.wght.coord - DEBUG - Walker has 4 remaining bytes. coords.wght.coord - DEBUG - Coordinate value is 24576 coords.wdth.coord - DEBUG - Walker has 2 remaining bytes. coords.wdth.coord - DEBUG - Coordinate value is -4096 coords - DEBUG - Coordinate is (wght 1.5, wdth -0.25) >>> obj2 == obj True >>> fvb(bs[:-1], axisOrder=ao) coords - DEBUG - Walker has 3 remaining bytes. coords.wght.coord - DEBUG - Walker has 3 remaining bytes. coords.wght.coord - DEBUG - Coordinate value is 24576 coords.wdth.coord - DEBUG - Walker has 1 remaining bytes. coords.wdth.coord - ERROR - Insufficient bytes. Note that validation here catches cases where one or more axis tags are duplicated: >>> aoBad = ('wght', 'wght') >>> fvb(bs, axisOrder=aoBad) coords - DEBUG - Walker has 4 remaining bytes. coords - ERROR - There are duplicate axis tags """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('coords') else: logger = utilities.makeDoctestLogger('coords') logger.debug( ('V0001', int(w.length()), "Walker has %d remaining bytes.")) ao = kwArgs['axisOrder'] # the absence is a code error, not a font one if len(ao) != len(set(ao)): logger.error(('V1034', (), "There are duplicate axis tags")) return None v = [] fvw = AC.fromvalidatedwalker for axisTag in ao: subLogger = logger.getChild(axisTag) obj = fvw(w, logger=subLogger) if obj is None: return None v.append(obj) r = cls(v, axisOrder=ao) logger.debug(('Vxxxx', (r, ), "Coordinate is %s")) return r
def analyzeFPGM(fpgmObj, **kwArgs): """ Returns a dict mapping FDEF index numbers to tuples (see below). This code takes care of the details of doing analysis of non-CALL guys first. Note that the Hints objects in the Fpgm object will not be h2 Hints, but are old hints_tt Hints; this code handles that. The returned value is either a dict (if the useDictForm kwArg is True), or a tuple (if it is False, the default, for historical reasons). The per-FPGM tuple has the following form: [0] dIn A tuple of kinds of input arguments. This comprises a series of (stack index, kind) pairs. [1] inCount The number of input arguments. Note that this might not necessarily be the length of dIn! [2] inDeepest The deepest level of popping that is attained by this FDEF. Because of intrinsic operations, this might be considerably larger than either len(dIn) or inCount. [3] ops Some operations (like MINDEX) can leave the stack in a state that can't be emulated by a simple series of pops and pushes. In this case this value, ops, will be a tuple of commands needed to adjust the stack after the hint executes. For instance, a sample value of ops might be (('delete', -3, -2),), meaning the [-3:-2] slice should be deleted in order to bring the stack into agreement with how the hint would have left things. [4] dOut A tuple of kinds of output arguments. As with dIn, this comprises a series of (stack index, kind) pairs. [5] outCount The number of output arguments. As with inCount this might be different than len(dOut)! """ if 'logger' in kwArgs: logger = kwArgs.pop('logger') else: logger = utilities.makeDoctestLogger("analyzeFPGM", level=50) fb = hints_tt.Hints.frombytes f2 = {} for n, h in fpgmObj.items(): bsOld = h.binaryString() if bsOld == _fdef17_orig: bsOld = _fdef17_rewrite logger.info( ('Vxxxx', (), "Rewrote FDEF 17 to remove computed CALL")) elif bsOld == _fdef44_bad: bsOld = _fdef44_good logger.info(('Vxxxx', (), "Corrected erroneous FDEF 44")) f2[n] = fb(bsOld, infoString=h.infoString) rTemp = {} r = {} # do a separate first pass for non-CALLers; then loop... thisRound = set() badSet = {0x1C, 0x2A, 0x2B, 0x78, 0x79} for n, h in f2.items(): if all(op.isPush or (op not in badSet) for op in h): thisRound.add(n) if not thisRound: logger.error(('Vxxxx', (), "All FDEFs call other FDEFs")) return None hadInconsistencies = False for n in sorted(thisRound): fictons = f2[n].removeIFs() logger.info(('Vxxxx', (n, len(fictons)), "FDEF %d disassembles into %d ficton(s)")) consistencySet = set() for i, h in enumerate(fictons): obj = Analyzer(h) t = obj.run() consistencySet.add((t[1], t[5])) rTemp[(n, i)] = t if len(consistencySet) > 1: logger.error(('V1071', ( n, sorted(consistencySet), ), "Different fictons for FDEF %d yield different numbers of " "inputs and outputs: %s")) hadInconsistencies = True del f2[n] if hadInconsistencies: return None _mergeAnalysis(rTemp, r, logger) # Now that we have the stack implications for the FDEFs without CALLs, go # through and use this as the callInfo to finish off the rest of the FDEFs. badSet.discard(0x2A) badSet.discard(0x2B) while f2: # A call to run() will return None if unknown CALLs are there. thisRound = set() for n, h in f2.items(): if all(op.isPush or (op not in badSet) for op in h): thisRound.add(n) if not thisRound: logger.error( ('Vxxxx', (), "All remaining FDEFs call other FDEFs that " "themselves call other FDEFs")) return None notYets = set() rTemp = {} hadInconsistencies = False for n in sorted(thisRound): fictons = f2[n].removeIFs() logger.info(('Vxxxx', (n, len(fictons)), "FDEF %d disassembles into %d ficton(s)")) consistencySet = set() for i, h in enumerate(fictons): obj = Analyzer(h) runOut = obj.run(callInfo=r) if runOut is None: notYets.add(n) for k in list(rTemp): if k[0] == n: del rTemp[k] break consistencySet.add((runOut[1], runOut[5])) rTemp[(n, i)] = runOut if len(consistencySet) > 1: logger.error(('V1071', ( n, sorted(consistencySet), ), "Different fictons for FDEF %d yield different numbers of " "inputs and outputs: %s")) hadInconsistencies = True if hadInconsistencies: return None thisRound -= notYets _mergeAnalysis(rTemp, r, logger) for n in thisRound: del f2[n] if kwArgs.get('useDictForm', False): d = {} for fdefIndex, t in r.items(): inTuple, inCount, inDeepest, ops, outTuple, outCount = t if inCount: dIn = dict.fromkeys(range(inCount)) for i, t in inTuple: dIn[i] = t else: dIn = {} if outCount: dOut = dict.fromkeys(range(outCount)) for i, t in inTuple: dOut[i] = t else: dOut = {} d[fdefIndex] = (dIn, inCount, inDeepest, ops, dOut, outCount) r = d return r
def fromvalidatedwalker(cls, w, **kwArgs): """ """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('cvar') else: logger = utilities.makeDoctestLogger('cvar') logger.debug( ('V0001', int(w.length()), "Walker has %d remaining bytes.")) e = kwArgs['editor'] if not e.reallyHas('fvar'): logger.error( ('V1037', (), "Font has a 'cvar' table but no 'fvar' table; this is " "not a valid state.")) return None fvarObj = e.fvar ao = fvarObj.axisOrder tableSize = w.length() if tableSize < 8: logger.error(('V0004', (), "Insufficient bytes.")) return None vers, tupleCount, dataOffset = w.unpack("L2H") if vers != cls.VERSION: logger.error( ('V1051', (cls.VERSION, vers), "Was expecting version 0x%08X, but got 0x%08X instead.")) return None if tupleCount & 0x8000: logger.error( ('V1052', (), "The 'cvar' table's tupleCount field has the 0x8000 bit set. " "This is not support for 'cvar' (only for 'gvar').")) return None overhead = 8 + (4 + 2 * len(ao)) * tupleCount if dataOffset < overhead or dataOffset > tableSize: logger.error( ('V1054', (dataOffset, ), "The dataOffset value 0x%04X appears to be invalid.")) return None tupleHeaders = [] afvw = axial_coordinates.AxialCoordinates.fromvalidatedwalker for i in range(tupleCount): subLogger = logger.getChild("tuple header %d" % (i, )) if w.length() < 4: logger.error(('V0004', (), "Insufficient bytes.")) return None tupleSize, tupleIndex = w.unpack("2H") if (tupleIndex & 0xF000) != 0xA000: subLogger.error(( 'V1053', (tupleIndex & 0xF000, ), "Expected tupleIndex flags to be 0xA000, but got 0x%04X.")) return None coords = afvw(w, axisOrder=ao, logger=subLogger) tupleHeaders.append((tupleSize, coords)) wData = w.subWalker(dataOffset) r = cls({}, axisOrder=ao) DD = deltadict.DeltaDict for tupleSize, coords in tupleHeaders: subLogger = logger.getChild("coord %s" % (coords, )) if wData.length() < tupleSize: logger.error(('V0004', (), "Insufficient bytes.")) return None startOffset = wData.getOffset() cvtIndices = cls._unpackCVTs_validated(wData, e, subLogger) if cvtIndices is None: return None deltas = cls._unpackDeltas_validated(wData, cvtIndices, subLogger) if deltas is None: return None alignSkip = (wData.getOffset() - startOffset) - tupleSize for cvtIndex, dlt in zip(cvtIndices, deltas): if cvtIndex not in r: r[cvtIndex] = DD() r[cvtIndex][coords] = dlt if alignSkip: wData.skip(alignSkip) return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new Avar object from the data in the specified walker, doing validation. Caller must provide either an 'axisOrder' or an 'editor' keyword argument. >>> logger = utilities.makeDoctestLogger('fvw') >>> AD = axisdict.AxisDict >>> ad1 = AD({-1.0: -1.0, 0.0: 0.0, 0.5: 0.25, 1.0: 1.0}) >>> ad2 = AD({-1.0: -1.0, -0.75: -0.25, 0.0: 0.0, 0.5: 0.75, 1.0: 1.0}) >>> ao = ('wght', 'wdth') >>> obj = Avar({'wght': ad1, 'wdth': ad2}, axisOrder=ao) >>> bs = obj.binaryString() >>> obj2 = Avar.fromvalidatedbytes(bs, logger=logger, axisOrder=ao) fvw.axisdict - DEBUG - Walker has 48 remaining bytes. fvw.axisdict.wght.axisdict - DEBUG - Walker has 40 remaining bytes. fvw.axisdict.wdth.axisdict - DEBUG - Walker has 22 remaining bytes. >>> obj == obj2 True >>> bs = utilities.fromhex("0001 0000 0005 0009 C000 C000 0000 0000") >>> obj3 = Avar.fromvalidatedbytes(bs, logger=logger, axisOrder=ao) fvw.axisdict - DEBUG - Walker has 16 remaining bytes. fvw.axisdict - ERROR - Reserved field is 0x0005; expected 0. fvw.axisdict - ERROR - The axisCount 9 does not match axisCount 2 from the 'fvar' table. fvw.axisdict.wght.axisdict - DEBUG - Walker has 8 remaining bytes. fvw.axisdict.wght.axisdict - ERROR - Insufficient bytes. fvw.axisdict.wdth.axisdict - DEBUG - Walker has 6 remaining bytes. fvw.axisdict.wdth.axisdict - ERROR - Insufficient bytes. >>> obj4 = Avar.fromvalidatedbytes(b'ABC', logger=logger) fvw.axisdict - DEBUG - Walker has 3 remaining bytes. fvw.axisdict - ERROR - Insufficient bytes. >>> bs = utilities.fromhex("0123 4567 0000 FFFF") >>> obj5 = Avar.fromvalidatedbytes(bs, logger=logger, axisOrder=ao) fvw.axisdict - DEBUG - Walker has 8 remaining bytes. fvw.axisdict - ERROR - Was expecting 'avar' version 0x00010000 but got 0x01234567 instead. """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('axisdict') else: logger = utilities.makeDoctestLogger('axisdict') logger.debug( ('V0001', int(w.length()), "Walker has %d remaining bytes.")) if w.length() < 8: logger.error(('V0004', (), "Insufficient bytes.")) return None assert ('axisOrder' in kwArgs) or ('editor' in kwArgs) if 'axisOrder' in kwArgs: ao = tuple(kwArgs['axisOrder']) else: ao = tuple(kwArgs['editor'].fvar.axisOrder) vers, reserved, count = w.unpack("LHH") if vers != cls.VERSION: logger.error(('Vxxxx', (cls.VERSION, vers), "Was expecting 'avar' version 0x%08X but got " "0x%08X instead.")) return None if reserved != 0: logger.error(('Vxxxx', (reserved, ), "Reserved field is 0x%04X; expected 0.")) if count != len(ao): logger.error(('Vxxxx', ( count, len(ao) ), "The axisCount %d does not match axisCount %d from the 'fvar' table." )) r = cls({}, axisOrder=ao) fvw = axisdict.AxisDict.fromvalidatedwalker for axisTag in ao: subLogger = logger.getChild(axisTag) r[axisTag] = fvw(w, logger=subLogger, **kwArgs) return r
def fromvalidatedwalker(cls, w, **kwArgs): """ """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('gvar') else: logger = utilities.makeDoctestLogger('gvar') logger.debug( ('V0001', int(w.length()), "Walker has %d remaining bytes.")) e = kwArgs['editor'] if not e.reallyHas('fvar'): logger.error( ('V1037', (), "Font has a 'gvar' table but no 'fvar' table; this is " "not a valid state.")) return None fvarObj = e.fvar ao = fvarObj.axisOrder r = cls(axisOrder=ao) origTableSize = w.length() if origTableSize < 20: logger.error(('V0004', (), "Insufficient bytes.")) return None t = w.unpack("L2HL2HL") vers, axisCount, coordCount, coordOff, glyphCount, flags, dataOff = t logger.debug(('Vxxxx', (vers, ), "Version is 0x%08X")) logger.debug(('Vxxxx', (axisCount, ), "Axis count is %d")) logger.debug(('Vxxxx', (coordCount, ), "Global coord count is %d")) logger.debug(('Vxxxx', (coordOff, ), "Global coord offset is 0x%08X")) logger.debug(('Vxxxx', (glyphCount, ), "Glyph count is %d")) logger.debug(('Vxxxx', (flags, ), "Flags is 0x%04X")) logger.debug(('Vxxxx', (dataOff, ), "Data offset is 0x%08X")) if vers != r.VERSION: logger.error(('V1038', ( r.VERSION, vers, ), "Unknown 'gvar' version; was expecting 0x%08X but " "got 0x%08X instead.")) return None if axisCount != len(ao): logger.error( ('V1039', (len(ao), axisCount), "Mismatch between the axis counts in 'fvar' (%d) and " "'gvar' (%d).")) return None if glyphCount != utilities.getFontGlyphCount(**kwArgs): logger.error( ('V1040', (), "Mismatch between the 'maxp' and 'gvar' glyph counts.")) return None if flags not in {0, 1}: logger.warning( ('V1041', (flags, ), "The 'gvar' flags value is 0x%04X; this includes bits " "that are not defined. These will be ignored.")) # Check the offsets for reasonableness offsetSize = (4 if (flags & 1) else 2) if coordCount: globStop = coordOff + (coordCount * len(ao) * 2) lowBounds = 20 + (glyphCount + 1) * offsetSize if (coordOff < lowBounds) or (globStop > origTableSize): logger.error(('V1042', ( ), "The 'gvar' global coordinates do not fit within the bounds " "of the entire 'gvar' table.")) return None if (dataOff < 20) or (dataOff >= origTableSize): logger.error( ('V1043', (), "The 'gvar' variations data starting offset is outside " "the bounds of the entire 'gvar' table")) return None # Read the global coordinates (if any) globalCoords = [] if coordCount: wSub = w.subWalker(coordOff) fvw = axial_coordinates.AxialCoordinates.fromvalidatedwalker globalCoords = [None] * coordCount for i in range(coordCount): subLogger = logger.getChild("globalCoord %d" % (i, )) obj = fvw(wSub, axisOrder=ao, logger=subLogger) if obj is None: return None globalCoords[i] = obj # Get the offsets to the actual glyph variation data if w.length() < offsetSize * (glyphCount + 1): logger.error(('V1043', (), "The 'gvar' glyph variation array extends past the " "table boundaries.")) return None offsets = w.group("HL"[flags & 1], glyphCount + 1) if offsetSize == 2: offsets = [2 * n for n in offsets] if list(offsets) != sorted(offsets): logger.error( ('V1044', (), "The per-glyph offsets are not monotonically increasing.")) return None for glyphIndex, (off1, off2) in enumerate(utilities.pairwise(offsets)): w.align(2) if off1 == off2: continue # For now I'm assuming the presence of a 'glyf' table... glyphObj = e.glyf[glyphIndex] if glyphObj.isComposite: pointCount = len(glyphObj.components) else: pointCount = glyphObj.pointCount() wSub = w.subWalker(off1 + dataOff) subLogger = logger.getChild("glyph %d" % (glyphIndex, )) wasOK = r._fillGlyph_validated(wSub, glyphIndex, globalCoords, pointCount, logger=subLogger, **kwArgs) if not wasOK: return None return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a Format2 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): coverage A Coverage object. logger A logger to which messages will be posted. tupleIndex The variations tuple index. >>> s = _testingValues[1].binaryString() >>> logger = utilities.makeDoctestLogger("fvw") >>> fvb = Format2.fromvalidatedbytes >>> obj = fvb(s, logger=logger) fvw.format2 - DEBUG - Walker has 94 remaining bytes. fvw.format2.left.lookup_aat - DEBUG - Walker has 78 remaining bytes. fvw.format2.left.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 76 remaining bytes. fvw.format2.right.lookup_aat - DEBUG - Walker has 50 remaining bytes. fvw.format2.right.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 48 remaining bytes. >>> fvb(s[:1], logger=logger) fvw.format2 - DEBUG - Walker has 1 remaining bytes. fvw.format2 - ERROR - Insufficient bytes. >>> fvb(s[:-1], logger=logger) fvw.format2 - DEBUG - Walker has 93 remaining bytes. fvw.format2.left.lookup_aat - DEBUG - Walker has 77 remaining bytes. fvw.format2.left.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 75 remaining bytes. fvw.format2.right.lookup_aat - DEBUG - Walker has 49 remaining bytes. fvw.format2.right.lookup_aat.binsearch.binsrch header - DEBUG - Walker has 47 remaining bytes. fvw.format2 - ERROR - The array is missing or incomplete. """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('format2') else: logger = utilities.makeDoctestLogger('format2') logger.debug( ('V0001', int(w.length()), "Walker has %d remaining bytes.")) kwArgs.pop('fontGlyphCount', None) if w.length() < 16: logger.error(('V0004', (), "Insufficient bytes.")) return None headerAdj = 12 rowBytes = w.unpack("L") if not rowBytes: logger.warning( ('V0605', (), "The rowBytes value is zero, so there is no table data.")) return cls({}, **utilities.filterKWArgs(cls, kwArgs)) nCols = rowBytes // 2 leftOffset, rightOffset, arrayBase = w.unpack("3L") # Determine nRows, by finding the largest offset present in the left # class table. if leftOffset < headerAdj: logger.error(('V0607', (leftOffset, ), "The leftOffset value %d is too small.")) return None wLeft = w.subWalker(leftOffset - headerAdj) leftLookup = lookup.Lookup.fromvalidatedwalker( wLeft, logger=logger.getChild("left"), **kwArgs) if leftLookup is None: return None # error will already have been logged bad = {i for i in leftLookup.values() if i % rowBytes} if bad: logger.error( ('V0612', (sorted(bad), ), "The following left-class values are not multiples of " "the rowBytes value: %s")) return None leftCD = classdef.ClassDef( {i: j // rowBytes for i, j in leftLookup.items()}) usedRows = set(leftCD.values()) nRows = 1 + max(usedRows) unusedRows = (set(range(nRows)) - usedRows) - {0} if unusedRows: logger.warning(('Vxxxx', (sorted(unusedRows), ), "The following rows are never referred to by any " "left-hand entry: %s")) if rightOffset < headerAdj: logger.error(('V0614', (rightOffset, ), "The rightOffset value %d is too small.")) return None wRight = w.subWalker(rightOffset - headerAdj) rightLookup = lookup.Lookup.fromvalidatedwalker( wRight, logger=logger.getChild("right"), **kwArgs) if rightLookup is None: return None # error will already have been logged bad = {i for i in rightLookup.values() if i % 2} if bad: logger.error(( 'V0615', (sorted(bad), ), "The following right-class values are not multiples of 2: %s")) return None rightCD = classdef.ClassDef( {i: j // 2 for i, j in rightLookup.items()}) usedCols = set(rightCD.values()) unusedCols = (set(range(nCols)) - usedCols) - {0} if unusedCols: logger.warning( ('Vxxxx', (sorted(unusedCols), ), "The following columns are never referred to by any " "right-hand entry: %s")) if arrayBase < headerAdj: logger.error(('V0609', (arrayBase, ), "The arrayBase value %d is too small.")) return None wArray = w.subWalker(arrayBase - headerAdj) if wArray.length() < 2 * nCols * nRows: logger.error(('V0610', (), "The array is missing or incomplete.")) return None arrayData = wArray.group("h", nCols * nRows) leftUniques = set(leftLookup.values()) rightUniques = set(rightLookup.values()) for delKey in {'leftClassDef', 'rightClassDef'}: kwArgs.pop(delKey, None) r = cls({}, leftClassDef=leftCD, rightClassDef=rightCD, **utilities.filterKWArgs(cls, kwArgs)) CP = classpair.ClassPair for uLeft in leftUniques: rowIndex = uLeft // rowBytes for uRight in rightUniques: dlt = arrayData[(uLeft + uRight) // 2] if dlt: r[CP([rowIndex, uRight // 2])] = dlt return r
def fromvalidatedwalker(cls, w, **kwArgs): """ Creates and returns a new PackedPoints object from the specified walker, doing validation. The 'pointCount' keyword argument is required, and should just be the actual point count, not including the 4 phantom points. >>> obj = PackedPoints((3, 4, 5, 19, 20, 31, 40, 41, 42, 43)) >>> obj.pprint() 3-5, 19-20, 31, 40-43 >>> bs = obj.binaryString(pointCount=80) >>> obj2 = PackedPoints.fromvalidatedbytes(bs, pointCount=80) points - DEBUG - Walker has 12 remaining bytes. points - DEBUG - Points are [3, 4, 5, 19, 20, 31, 40, 41, 42, 43] >>> PackedPoints.fromvalidatedbytes(bs[:-1], pointCount=80) points - DEBUG - Walker has 11 remaining bytes. points - ERROR - The packed point array ends prematurely for runCount 10 and format 'B' (expectedSize 10, available 9). >>> PackedPoints.fromvalidatedbytes(bs, pointCount=20) points - DEBUG - Walker has 12 remaining bytes. points - ERROR - The unpacked point array contains point indices that are too large for the specified number of points in the glyph. >>> PackedPoints.fromvalidatedbytes(bs, pointCount=4) points - DEBUG - Walker has 12 remaining bytes. points - ERROR - The specified number of points (10) exceeds the number of points in the specified glyph. """ if 'logger' in kwArgs: logger = kwArgs.pop('logger').getChild('points') else: logger = utilities.makeDoctestLogger('points') logger.debug(( 'V0001', int(w.length()), "Walker has %d remaining bytes.")) pointCount = kwArgs['pointCount'] if w.length() < 1: logger.error(('V0004', (), "Insufficient bytes.")) return None totalPoints = w.unpack("B") if not totalPoints: logger.debug(('Vxxxx', (), "Using all points in the glyph")) return cls(range(pointCount + 4)) if totalPoints > 127: if w.length() < 1: logger.error(('V0004', (), "Insufficient bytes.")) return None totalPoints = ((totalPoints - 128) * 256) + w.unpack("B") if totalPoints > (pointCount + 4): logger.error(( 'V0135', (totalPoints,), "The specified number of points (%d) exceeds the number " "of points in the specified glyph.")) return None pointIndices = [] while totalPoints: if w.length() < 1: logger.error(( 'V1036', (), "The packed point array is empty.")) return None runCount = w.unpack("B") fmt = "BH"[runCount > 127] runCount = (runCount & 0x7F) + 1 expectedSize = w.calcsize("%d%s" % (runCount, fmt)) if w.length() < expectedSize: logger.error(( 'V1036', (runCount, fmt, expectedSize, int(w.length())), "The packed point array ends prematurely for " "runCount %d and format '%s' (expectedSize %d, " "available %d).")) return None # The data here are not point indices, but packed point indices, # stored as deltas from the previous point (except the first). for n in w.group(fmt, runCount): if not pointIndices: pointIndices.append(n) else: pointIndices.append(pointIndices[-1] + n) totalPoints -= runCount if pointIndices[-1] >= pointCount + 4: logger.error(( 'V1035', (), "The unpacked point array contains point indices that are too " "large for the specified number of points in the glyph.")) return None logger.debug(('Vxxxx', (pointIndices,), "Points are %s")) return cls(pointIndices)