예제 #1
0
    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
예제 #2
0
    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
예제 #3
0
 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))
예제 #4
0
    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__))
예제 #5
0
    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__))
예제 #6
0
    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
예제 #7
0
 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
예제 #8
0
    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
예제 #9
0
 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
예제 #10
0
    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
예제 #11
0
 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
예제 #12
0
    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
예제 #13
0
 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))
예제 #14
0
    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
예제 #15
0
    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
예제 #16
0
    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
예제 #17
0
    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
예제 #18
0
    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
예제 #19
0
 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))
예제 #20
0
    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
예제 #21
0
    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
예제 #22
0
    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
예제 #23
0
    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
예제 #24
0
    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
예제 #25
0
    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
예제 #26
0
 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()
예제 #27
0
 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
예제 #28
0
 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
예제 #29
0
 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()
예제 #30
0
    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